Skip to main content

vk/mem/
external.rs

1//! Native external-memory services. Donor: pinned VMA 3.4.0
2//! `pTypeExternalMemoryHandleTypes`, `VmaWin32Handle` and interop allocation chains.
3//! Rust adaptation: copied export profiles, exact enabled-extension admission,
4//! retained native handle caches and explicit ownership/error paths.
5use super::*;
6use crate as vk;
7use crate::mem::{AllocationChain, chain::ChainData};
8
9pub(super) fn validate_types(
10	allocator: &Allocator<'_>,
11	types: vk::ExternalMemoryHandleTypeFlags,
12) -> Result<(), AllocError> {
13	crate::mem::resource::validate_external_types(allocator.device.as_device(), types)
14}
15
16pub(super) fn allocation_exports(
17	allocator: &Allocator<'_>,
18	index: u32,
19	chain: ChainData,
20) -> Result<vk::ExternalMemoryHandleTypeFlags, AllocError> {
21	validate_allocation_chain(allocator, chain)?;
22	let configured = allocator.external_types[index as usize];
23	if configured.as_raw() != 0 && chain.has_export {
24		return Err(AllocError::InvalidRequest(
25			"duplicate configured and chained export records",
26		));
27	}
28	let types = configured | chain.exports;
29	validate_types(allocator, types)?;
30	Ok(types)
31}
32
33/// Configure copied per-memory-type external export flags before creating pools/storage.
34/// Zero flags leave that type ordinary. Every native allocation of a configured
35/// type receives `VkExportMemoryAllocateInfo`, including separate allocations.
36/// # Errors
37/// Rejects live domains, incorrect array length and unavailable handle extensions.
38/// # Safety
39/// Flags must be supported and mutually compatible for every resource subsequently
40/// allocated on each type, including external dedication requirements. No support
41/// query is implied. Matching external resource create chains remain required.
42pub unsafe fn set_external_memory_types(
43	allocator: &mut Allocator<'_>,
44	types: &[vk::ExternalMemoryHandleTypeFlags],
45) -> Result<(), AllocError> {
46	allocator.ensure_global()?;
47	allocator.ensure_open()?;
48	if allocator.native_count != 0 || !allocator.live.is_empty() {
49		return Err(AllocError::LiveAllocations);
50	}
51	if !allocator.pools.is_empty() {
52		return Err(AllocError::LivePools);
53	}
54	if types.len() != allocator.selector.properties.memory_type_count as usize {
55		return Err(AllocError::InvalidRequest(
56			"external profile must cover exactly the native memory types",
57		));
58	}
59	for &types in types {
60		validate_types(allocator, types)?;
61	}
62	allocator
63		.external_types
64		.fill(vk::ExternalMemoryHandleTypeFlags::empty());
65	allocator.external_types[..types.len()].copy_from_slice(types);
66	Ok(())
67}
68
69pub(super) fn exports(
70	allocator: &Allocator<'_>,
71	allocation: &Allocation,
72) -> Result<vk::ExternalMemoryHandleTypeFlags, AllocError> {
73	let live = allocator.validate(allocation)?;
74	if live.placement.is_some() {
75		Ok(
76			allocator
77				.blocks
78				.iter()
79				.find(|block| block.handle == live.handle)
80				.ok_or(AllocError::UnknownAllocation)?
81				.export_types,
82		)
83	} else {
84		Ok(live.export_types)
85	}
86}
87
88/// Observe the export flags retained for this allocation's entire native block.
89pub fn external_memory_types(
90	allocator: &Allocator<'_>,
91	allocation: &Allocation,
92) -> Result<vk::ExternalMemoryHandleTypeFlags, AllocError> {
93	exports(allocator, allocation)
94}
95
96/// Export a new caller-owned file descriptor for the complete backing memory.
97/// Allocation offsets still apply. Close the returned descriptor using the OS;
98/// neither token nor allocator owns it. No GPU synchronization occurs.
99/// # Errors
100/// Rejects stale/non-exportable allocations, invalid types, missing enabled
101/// extensions/PFNs and native failures. Native failure transfers no descriptor.
102/// # Safety
103/// Meet native external-memory compatibility, sharing/security and synchronization
104/// contracts. The caller closes each successful descriptor exactly once.
105#[cfg(unix)]
106pub unsafe fn get_memory_fd(
107	allocator: &Allocator<'_>,
108	allocation: &Allocation,
109	handle_type: vk::ExternalMemoryHandleTypeFlagBits,
110) -> Result<std::os::fd::RawFd, AllocError> {
111	use vk::ExternalMemoryHandleTypeFlagBits as F;
112	if handle_type != F::OPAQUE_FD_BIT && handle_type != F::DMA_BUF_BIT_EXT {
113		return Err(AllocError::InvalidRequest(
114			"file descriptor handle type required",
115		));
116	}
117	validate_export(allocator, allocation, handle_type)?;
118	require(
119		allocator.device.fp().get_memory_fd_khr.is_some(),
120		"vkGetMemoryFdKHR",
121	)?;
122	let info = vk::MemoryGetFdInfoKHR {
123		memory: allocation.handle,
124		handle_type,
125		..Default::default()
126	};
127	let mut fd = -1;
128	// SAFETY: checked native identity/export admission; output is initialized and caller meets sharing requirements.
129	unsafe { allocator.device.get_memory_fd_khr(&info, &mut fd) }?;
130	if fd < 0 {
131		return Err(
132			vk::load::CallError::InvalidOutput("vkGetMemoryFdKHR returned an invalid descriptor").into(),
133		);
134	}
135	Ok(fd)
136}
137
138fn validate_export(
139	allocator: &Allocator<'_>,
140	allocation: &Allocation,
141	handle_type: vk::ExternalMemoryHandleTypeFlagBits,
142) -> Result<(), AllocError> {
143	let bits = handle_type.as_raw();
144	if bits == 0 || !bits.is_power_of_two() || exports(allocator, allocation)?.as_raw() & bits == 0 {
145		return Err(AllocError::InvalidRequest(
146			"handle type was not enabled for this native allocation",
147		));
148	}
149	validate_types(allocator, handle_type)
150}
151
152pub(super) unsafe fn required_resource_types(
153	chain: *const std::ffi::c_void,
154) -> vk::ExternalMemoryHandleTypeFlags {
155	let mut types = vk::ExternalMemoryHandleTypeFlags::empty();
156	let mut cursor = chain.cast::<vk::BaseInStructure>();
157	// Callers first validate the bounded resource chain; never traverse a fresh unchecked pointer.
158	while !cursor.is_null() {
159		// SAFETY: prior resource-chain validation and unsafe operation contract cover all records.
160		let node = unsafe { &*cursor };
161		match node.s_type {
162			vk::StructureType::EXTERNAL_MEMORY_BUFFER_CREATE_INFO => {
163				// SAFETY: complete matching record from the validated chain.
164				types = unsafe { (*cursor.cast::<vk::ExternalMemoryBufferCreateInfo>()).handle_types };
165			}
166			vk::StructureType::EXTERNAL_MEMORY_IMAGE_CREATE_INFO => {
167				// SAFETY: complete matching record from the validated chain.
168				types = unsafe { (*cursor.cast::<vk::ExternalMemoryImageCreateInfo>()).handle_types };
169			}
170			_ => {}
171		}
172		cursor = node.p_next;
173	}
174	types
175}
176
177#[inline]
178pub(super) unsafe fn resource_options<'options, 'a>(
179	allocator: &Allocator<'_>,
180	options: &'options AllocationCreateInfo<'a>,
181	chain: *const std::ffi::c_void,
182) -> Result<std::borrow::Cow<'options, AllocationCreateInfo<'a>>, AllocError> {
183	if chain.is_null() {
184		return Ok(std::borrow::Cow::Borrowed(options));
185	}
186	// SAFETY: preceding resource validation established the readable chain contract.
187	unsafe { external_resource_options(allocator, options, chain) }
188}
189
190#[inline(never)]
191unsafe fn external_resource_options<'options, 'a>(
192	allocator: &Allocator<'_>,
193	options: &'options AllocationCreateInfo<'a>,
194	chain: *const std::ffi::c_void,
195) -> Result<std::borrow::Cow<'options, AllocationCreateInfo<'a>>, AllocError> {
196	// SAFETY: resource validation already established a bounded readable chain.
197	let required = unsafe { required_resource_types(chain) };
198	if required.as_raw() == 0 {
199		return Ok(std::borrow::Cow::Borrowed(options));
200	}
201	let tail = options.memory_allocate_next.map_or_else(
202		|| {
203			options
204				.pool
205				.and_then(|pool| allocator.pools.get(&pool.serial))
206				.map_or(Default::default(), |pool| pool.allocation_chain)
207		},
208		|chain| chain.data,
209	);
210	// Imported payloads need not be re-exportable; native compatibility is caller-owned.
211	if tail.imports {
212		return Ok(std::borrow::Cow::Borrowed(options));
213	}
214	let mut bits = 0;
215	for index in 0..allocator.selector.properties.memory_type_count {
216		if options.memory_type_bits & (1 << index) == 0
217			|| options
218				.pool
219				.is_some_and(|pool| allocator.pools[&pool.serial].info.memory_type_index != index)
220		{
221			continue;
222		}
223		// A chained export cannot be prepended to a configured export on the same
224		// type. Exclude that type without rejecting another compatible profile.
225		if tail.has_export && allocator.external_types[index as usize].as_raw() != 0 {
226			continue;
227		}
228		let types = allocation_exports(allocator, index, tail)?;
229		if types.as_raw() & required.as_raw() == required.as_raw() {
230			bits |= 1 << index;
231		}
232	}
233	if bits & options.memory_type_bits == 0 {
234		return Err(AllocError::NoCompatibleMemoryType);
235	}
236	Ok(std::borrow::Cow::Owned(AllocationCreateInfo {
237		memory_type_bits: options.memory_type_bits & bits,
238		..*options
239	}))
240}
241
242impl<'device> Allocator<'device> {
243	/// Copy an external export profile before any pool/native acquisition.
244	/// # Errors
245	/// Same validation errors as `mem::set_external_memory_types`.
246	/// # Safety
247	/// All future resources and native allocations must meet the configured export valid usage.
248	pub unsafe fn set_external_memory_types(
249		&mut self,
250		types: &[vk::ExternalMemoryHandleTypeFlags],
251	) -> Result<(), AllocError> {
252		// SAFETY: same profile and future-resource obligations.
253		unsafe { set_external_memory_types(self, types) }
254	}
255
256	pub fn external_memory_types(
257		&self,
258		allocation: &Allocation,
259	) -> Result<vk::ExternalMemoryHandleTypeFlags, AllocError> {
260		external_memory_types(self, allocation)
261	}
262
263	/// Export one caller-owned descriptor for the complete backing memory.
264	/// # Errors
265	/// Same checked identity/admission and native failures as `mem::get_memory_fd`.
266	/// # Safety
267	/// Caller meets sharing/synchronization requirements and closes the returned descriptor.
268	#[cfg(unix)]
269	pub unsafe fn get_memory_fd(
270		&self,
271		allocation: &Allocation,
272		handle_type: vk::ExternalMemoryHandleTypeFlagBits,
273	) -> Result<std::os::fd::RawFd, AllocError> {
274		// SAFETY: identical external-memory and caller descriptor-ownership obligations.
275		unsafe { get_memory_fd(self, allocation, handle_type) }
276	}
277
278	/// Create a custom pool retaining a borrowed chain for future native allocations.
279	/// # Errors
280	/// Same checked policy/chain/native errors as `mem::create_pool_with_chain`.
281	/// # Safety
282	/// Retain the complete immutable chain for the device borrow; every block must meet native valid usage.
283	pub unsafe fn create_pool_with_chain(
284		&mut self,
285		info: &PoolCreateInfo,
286		chain: AllocationChain<'device>,
287	) -> Result<Pool, AllocError> {
288		// SAFETY: identical native metadata and retained-chain contract.
289		unsafe { create_pool_with_chain(self, info, chain) }
290	}
291}
292
293#[cfg(all(windows, feature = "platform-win32"))]
294use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
295
296/// One root per (memory, type), required by Vulkan's NT-handle export rules.
297/// `OwnedHandle` releases only an OS reference; it never frees Vulkan memory,
298/// submits or waits. Explicit memory release removes these roots. Returned
299/// duplicates belong to the caller/target process and are never retained here.
300#[cfg(all(windows, feature = "platform-win32"))]
301pub(super) struct CachedHandle {
302	pub memory: vk::DeviceMemory,
303	kind: vk::ExternalMemoryHandleTypeFlagBits,
304	handle: *mut std::ffi::c_void,
305	_root: Option<OwnedHandle>,
306}
307
308#[cfg(all(windows, feature = "platform-win32"))]
309#[link(name = "kernel32")]
310unsafe extern "system" {
311	fn GetCurrentProcess() -> *mut std::ffi::c_void;
312	fn DuplicateHandle(
313		source_process: *mut std::ffi::c_void,
314		source: *mut std::ffi::c_void,
315		target_process: *mut std::ffi::c_void,
316		target: *mut *mut std::ffi::c_void,
317		access: u32,
318		inherit: i32,
319		options: u32,
320	) -> i32;
321}
322
323/// Export one Win32 handle for the entire backing native allocation.
324///
325/// NT roots are fetched once per native memory/type and cached. Each call
326/// duplicates the root into the requested process; close each returned NT handle
327/// in that process. KMT handles are borrowed, never closed, and cannot be
328/// duplicated into a target process. Native memory destruction invalidates them.
329/// # Errors
330/// Preserves native and OS duplication failures, and rejects stale allocations,
331/// unavailable export types/PFNs and target-process requests for KMT handles.
332/// # Safety
333/// Meet native external-memory/synchronization/security valid usage. A non-null
334/// `target_process` must be a live handle with PROCESS_DUP_HANDLE permission.
335/// The caller/target process owns each returned NT duplicate and closes it.
336#[cfg(all(windows, feature = "platform-win32"))]
337pub unsafe fn get_memory_win32_handle(
338	allocator: &mut Allocator<'_>,
339	allocation: &Allocation,
340	handle_type: vk::ExternalMemoryHandleTypeFlagBits,
341	target_process: *mut std::ffi::c_void,
342) -> Result<vk::HANDLE, AllocError> {
343	use vk::ExternalMemoryHandleTypeFlagBits as F;
344	let nt = match handle_type {
345		F::OPAQUE_WIN32_BIT | F::D3D11_TEXTURE_BIT | F::D3D12_HEAP_BIT | F::D3D12_RESOURCE_BIT => true,
346		F::OPAQUE_WIN32_KMT_BIT | F::D3D11_TEXTURE_KMT_BIT => false,
347		_ => return Err(AllocError::InvalidRequest("Win32 handle type required")),
348	};
349	validate_export(allocator, allocation, handle_type)?;
350	if !nt && !target_process.is_null() {
351		return Err(AllocError::InvalidRequest(
352			"KMT handles cannot be duplicated into a target process",
353		));
354	}
355	require(
356		allocator.device.fp().get_memory_win32_handle_khr.is_some(),
357		"vkGetMemoryWin32HandleKHR",
358	)?;
359	let cached = allocator
360		.external_handles
361		.iter()
362		.position(|entry| entry.memory == allocation.handle && entry.kind == handle_type);
363	let index = if let Some(index) = cached {
364		index
365	} else {
366		if let Some(shared) = allocator.parallel_state() {
367			shared.reserve_external()?;
368		}
369		allocator
370			.external_handles
371			.try_reserve(1)
372			.map_err(|_| AllocError::HostAllocationFailed)?;
373		let info = vk::MemoryGetWin32HandleInfoKHR {
374			memory: allocation.handle,
375			handle_type,
376			..Default::default()
377		};
378		let mut handle = std::ptr::null_mut();
379		// SAFETY: cached export admission/identity and initialized output; caller establishes sharing valid usage.
380		unsafe {
381			allocator
382				.device
383				.as_device()
384				.get_memory_win32_handle_khr(&info, &mut handle)
385		}?;
386		if handle.is_null() || handle == (-1_isize) as *mut std::ffi::c_void {
387			return Err(
388				vk::load::CallError::InvalidOutput("vkGetMemoryWin32HandleKHR returned an invalid handle")
389					.into(),
390			);
391		}
392		let root = if nt {
393			// SAFETY: successful export returned a new owned NT handle, never exposed or adopted elsewhere.
394			Some(unsafe { OwnedHandle::from_raw_handle(handle) })
395		} else {
396			None
397		};
398		let index = allocator.external_handles.len();
399		allocator.external_handles.push_reserved(CachedHandle {
400			memory: allocation.handle,
401			kind: handle_type,
402			handle,
403			_root: root,
404		});
405		if let Some(shared) = allocator.parallel_state() {
406			shared.external_added();
407		}
408		index
409	};
410	let entry = &allocator.external_handles[index];
411	if !nt {
412		return Ok(entry.handle);
413	}
414	let root = entry
415		._root
416		.as_ref()
417		.expect("NT export root owns its handle");
418	// SAFETY: process pseudo-handle is valid in the current process, requires no close.
419	let current = unsafe { GetCurrentProcess() };
420	let target = if target_process.is_null() {
421		current
422	} else {
423		target_process
424	};
425	let mut duplicate = std::ptr::null_mut();
426	// SAFETY: live owned source, valid target process per caller, initialized output,
427	// DUPLICATE_SAME_ACCESS preserves permissions without transferring our root.
428	if unsafe {
429		DuplicateHandle(
430			current,
431			root.as_raw_handle(),
432			target,
433			&mut duplicate,
434			0,
435			0,
436			2,
437		)
438	} == 0
439	{
440		let error = std::io::Error::last_os_error();
441		return Err(AllocError::Output {
442			kind: error.kind(),
443			code: error.raw_os_error(),
444		});
445	}
446	Ok(duplicate)
447}
448
449#[cfg(all(windows, feature = "platform-win32"))]
450impl Allocator<'_> {
451	/// Export a caller-owned NT duplicate or a borrowed KMT handle.
452	/// # Errors
453	/// Same checked/native/OS errors as `mem::get_memory_win32_handle`.
454	/// # Safety
455	/// Same sharing, target-process permissions and handle-closing obligations.
456	pub unsafe fn get_memory_win32_handle(
457		&mut self,
458		allocation: &Allocation,
459		handle_type: vk::ExternalMemoryHandleTypeFlagBits,
460		target_process: *mut std::ffi::c_void,
461	) -> Result<vk::HANDLE, AllocError> {
462		// SAFETY: identical native export and target-process ownership contract.
463		unsafe { get_memory_win32_handle(self, allocation, handle_type, target_process) }
464	}
465}
466
467fn validate_allocation_chain(
468	allocator: &Allocator<'_>,
469	chain: ChainData,
470) -> Result<(), AllocError> {
471	if let Some(name) = chain.import_extension
472		&& !crate::mem::resource::extension(allocator.device.as_device(), name)
473	{
474		return Err(AllocError::UnsupportedCapability(
475			"memory import extension is not enabled",
476		));
477	}
478	if let Some(flags) = chain.flags {
479		if !allocator
480			.device
481			.as_device()
482			.capabilities()
483			.is_some_and(|caps| caps.api_version() >= vk::API_VERSION_1_1)
484			&& !crate::mem::resource::extension(allocator.device.as_device(), c"VK_KHR_device_group")
485		{
486			return Err(AllocError::UnsupportedCapability(
487				"allocation flags require core 1.1 or enabled device_group",
488			));
489		}
490		if flags.as_raw() & vk::MemoryAllocateFlagBits::DEVICE_ADDRESS_BIT.as_raw() != 0
491			&& !allocator.buffer_device_address
492		{
493			return Err(AllocError::UnsupportedCapability(
494				"allocation address flag requires enabled feature",
495			));
496		}
497	}
498	Ok(())
499}