Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions blade-graphics/src/vulkan/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ struct AdapterCapabilities {
pipeline_executable_properties: bool,
full_screen_exclusive: bool,
external_memory: bool,
external_memory_host: bool,
/// `VkPhysicalDeviceExternalMemoryHostPropertiesEXT::minImportedHostPointerAlignment`.
/// Allocation size and host-pointer alignment for an imported
/// `Memory::External(HostAllocation(..))` allocation must be a
/// multiple of this value (0 when the extension is unsupported).
min_imported_host_pointer_alignment: u64,
timing: bool,
dual_source_blending: bool,
shader_float16: bool,
Expand Down Expand Up @@ -374,6 +380,27 @@ fn inspect_adapter(
&vk::KHR_EXTERNAL_MEMORY_FD_NAME
});

// `VK_EXT_external_memory_host` is what lets us import a plain host
// allocation (`ExternalMemorySource::HostAllocation(ptr)`) as a
// Vulkan buffer without a staging copy. It's orthogonal to the
// FD/Win32 variants above — a driver may support one without the
// other. RADV, NVIDIA, and modern Intel all expose it.
let external_memory_host = supported_extensions.contains(&vk::EXT_EXTERNAL_MEMORY_HOST_NAME);
let min_imported_host_pointer_alignment = if external_memory_host {
let mut host_props = vk::PhysicalDeviceExternalMemoryHostPropertiesEXT::default();
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut host_props);
unsafe {
instance
.get_physical_device_properties2
.get_physical_device_properties2(phd, &mut props2);
}
// The spec forbids 0; guard anyway so downstream alignment
// math stays well-defined.
host_props.min_imported_host_pointer_alignment.max(1)
} else {
0
};

let timing = if properties.limits.timestamp_compute_and_graphics == vk::FALSE {
log::info!("No timing because of queue support");
false
Expand Down Expand Up @@ -547,6 +574,8 @@ fn inspect_adapter(
pipeline_executable_properties,
full_screen_exclusive,
external_memory,
external_memory_host,
min_imported_host_pointer_alignment,
timing,
dual_source_blending,
shader_float16,
Expand Down Expand Up @@ -951,6 +980,9 @@ impl super::Context {
vk::KHR_EXTERNAL_MEMORY_FD_NAME
});
}
if capabilities.external_memory_host {
device_extensions.push(vk::EXT_EXTERNAL_MEMORY_HOST_NAME);
}
if capabilities.cooperative_matrix.is_supported() {
device_extensions.push(vk::KHR_COOPERATIVE_MATRIX_NAME);
if capabilities.api_version < vk::API_VERSION_1_2 {
Expand Down Expand Up @@ -1184,6 +1216,15 @@ impl super::Context {
} else {
None
},
external_memory_host: if capabilities.external_memory_host {
Some(ash::ext::external_memory_host::Device::new(
&instance.core,
&device_core,
))
} else {
None
},
min_imported_host_pointer_alignment: capabilities.min_imported_host_pointer_alignment,
core: device_core,
device_information: capabilities.device_information,
command_scope: if desc.capture {
Expand Down
8 changes: 8 additions & 0 deletions blade-graphics/src/vulkan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ struct Device {
external_memory: Option<ash::khr::external_memory_win32::Device>,
#[cfg(not(target_os = "windows"))]
external_memory: Option<ash::khr::external_memory_fd::Device>,
/// `VK_EXT_external_memory_host` device wrapper. Populated when
/// the extension is enabled; imports of
/// `Memory::External(HostAllocation(..))` fail clearly when this
/// is `None`.
external_memory_host: Option<ash::ext::external_memory_host::Device>,
/// Driver-reported alignment for imported host pointers and
/// allocation sizes (0 when `external_memory_host` is `None`).
min_imported_host_pointer_alignment: u64,
command_scope: Option<CommandScopeDevice>,
timing: Option<TimingDevice>,
workarounds: Workarounds,
Expand Down
86 changes: 79 additions & 7 deletions blade-graphics/src/vulkan/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,83 @@ impl super::Context {
.core
.get_physical_device_memory_properties(self.physical_device)
};
let memory_type_index = memory_types.ilog2();
let memory_type: vk::MemoryType =
memory_properties.memory_types[memory_type_index as usize];

let handle_type = external_source_handle_type(e);

// For a `HostAllocation` import the driver constrains
// which memory types can back the pointer — query it
// via `vkGetMemoryHostPointerPropertiesEXT` and
// intersect with the buffer's requirements. FD / Win32
// / Export paths keep the historical "any valid bit"
// heuristic.
let host_pointer_memory_type_bits = match e {
crate::ExternalMemorySource::HostAllocation(ptr) => {
let ext = self.device.external_memory_host.as_ref().expect(
"Memory::External(HostAllocation) requires \
VK_EXT_external_memory_host — check \
Capabilities::external_memory_host before calling",
);
let mut host_props = vk::MemoryHostPointerPropertiesEXT::default();
unsafe {
(ext.fp().get_memory_host_pointer_properties_ext)(
self.device.core.handle(),
handle_type,
ptr as *const std::ffi::c_void,
&mut host_props,
)
.result()
.expect("vkGetMemoryHostPointerPropertiesEXT");
}
Some(host_props.memory_type_bits)
}
_ => None,
};

let allowed = memory_types & host_pointer_memory_type_bits.unwrap_or(!0);
let (memory_type_index, memory_type) = if host_pointer_memory_type_bits.is_some() {
// Host-pointer imports need HOST_VISIBLE. Prefer
// HOST_COHERENT to skip explicit flushes.
(0..memory_properties.memory_type_count)
.filter(|&i| allowed & (1 << i) != 0)
.map(|i| (i, memory_properties.memory_types[i as usize]))
.filter(|item| {
item.1
.property_flags
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE)
})
.min_by_key(|item| {
if item
.1
.property_flags
.contains(vk::MemoryPropertyFlags::HOST_COHERENT)
{
0u8
} else {
1u8
}
})
.expect(
"no host-visible memory type compatible with the imported \
host pointer",
)
} else {
assert!(
allowed != 0,
"no memory type satisfies the external-buffer requirements"
);
let idx = allowed.ilog2();
(idx, memory_properties.memory_types[idx as usize])
};

// `VkPhysicalDeviceExternalMemoryHostPropertiesEXT::minImportedHostPointerAlignment`
// — allocationSize must be a multiple of this when
// importing a host pointer.
let allocation_size = if host_pointer_memory_type_bits.is_some() {
let align = self.device.min_imported_host_pointer_alignment.max(1);
requirements.size.next_multiple_of(align)
} else {
requirements.size
};

let external_info: &mut dyn vk::ExtendsMemoryAllocateInfo = match e {
#[cfg(target_os = "windows")]
crate::ExternalMemorySource::Win32(Some(handle))
Expand Down Expand Up @@ -89,7 +161,7 @@ impl super::Context {
};

let allocation_info = vk::MemoryAllocateInfo {
allocation_size: requirements.size,
allocation_size,
memory_type_index,
..vk::MemoryAllocateInfo::default()
}
Expand All @@ -99,7 +171,7 @@ impl super::Context {
self.device
.core
.allocate_memory(&allocation_info, None)
.unwrap()
.expect("vkAllocateMemory (external import)")
};

unsafe {
Expand All @@ -108,7 +180,7 @@ impl super::Context {
memory_type_index,
gpu_alloc_ash::memory_properties_from_ash(memory_type.property_flags),
0,
requirements.size,
allocation_size,
)
}
}
Expand Down
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ Changelog for *Blade* project

## (TBD)

- vk: support `VK_EXT_external_memory_host` — enable the extension, query memory-type compatibility via `vkGetMemoryHostPointerPropertiesEXT`, and round allocation size to `minImportedHostPointerAlignment` so `Memory::External(HostAllocation)` imports succeed on drivers that expose the extension

## blade-graphics-0.8.4 (17 Apr 2026)

- vk: use driver API version for instance creation
Expand Down
Loading