Skip to main content

vk/mem/
chain.rs

1//! Borrowed native allocation chains. Donor: pinned VMA 3.4.0
2//! `vmaAllocateDedicatedMemory` and `VmaPoolCreateInfo::pMemoryAllocateNext`.
3//! Adaptation: bounded validation, borrowed storage and single-attempt imports.
4mod import;
5pub(super) use import::ImportKind;
6
7use super::{AllocError, ImportMemoryInfo};
8use crate as vk;
9use std::{ffi::c_void, marker::PhantomData, ptr};
10
11/// A caller-owned `VkMemoryAllocateInfo::pNext` chain.
12///
13/// The allocator prepends its dedication and admitted priority/address records.
14/// Explicit flags are preserved, including a device mask; resource dedication
15/// and priority belong to the allocator and must not also appear in this chain.
16/// No native handle is owned by this borrowed descriptor.
17#[derive(Clone, Copy, Debug)]
18pub struct AllocationChain<'a> {
19	pub(super) data: ChainData,
20	borrow: PhantomData<&'a c_void>,
21}
22
23#[derive(Clone, Copy, Debug)]
24pub(super) struct ChainData {
25	pub next: *const c_void,
26	pub flags: Option<vk::MemoryAllocateFlags>,
27	pub exports: vk::ExternalMemoryHandleTypeFlags,
28	pub has_export: bool,
29	pub imports: bool,
30	pub import_extension: Option<&'static std::ffi::CStr>,
31	pub import_kind: ImportKind,
32	pub import_info: Option<ImportMemoryInfo>,
33}
34
35impl ChainData {
36	// Shared immutable empty policy; native calls borrow it instead of copying
37	// the full optional platform-import metadata through every allocation layer.
38	pub(super) const EMPTY: Self = Self {
39		next: ptr::null(),
40		flags: None,
41		exports: vk::ExternalMemoryHandleTypeFlags::empty(),
42		has_export: false,
43		imports: false,
44		import_extension: None,
45		import_kind: ImportKind::Ordinary,
46		import_info: None,
47	};
48}
49
50impl Default for ChainData {
51	fn default() -> Self {
52		Self::EMPTY
53	}
54}
55
56impl<'a> AllocationChain<'a> {
57	/// Borrow a registered allocation extension and its linked records.
58	/// # Errors
59	/// Rejects cycles, duplicate tags, overlong chains, allocator-owned records and
60	/// native records without an admitted allocator ownership/accounting contract.
61	/// # Safety
62	/// Every reachable header and full payload must be valid, correctly tagged,
63	/// immutable and readable for `'a`; all nested pointers/handles and enabled
64	/// capabilities must meet native valid usage at allocation. Imports must use
65	/// an explicitly compatible memory type/size. Native import handle transfer
66	/// occurs on successful `vkAllocateMemory`, even if later mapping/binding fails.
67	pub unsafe fn new_from_record<T: vk::Extends<vk::MemoryAllocateInfo<'a>>>(
68		record: &'a T,
69	) -> Result<Self, AllocError> {
70		// SAFETY: the sealed registry relationship supplies a repr(C) native header;
71		// caller establishes complete chain storage and payload validity for 'a.
72		let data = unsafe { inspect(ptr::from_ref(record).cast(), None) }?;
73		Ok(Self {
74			data,
75			borrow: PhantomData,
76		})
77	}
78
79	/// Borrow an import chain with explicit external payload accounting.
80	///
81	/// Use this for platform imports whose native allocation size is ignored or
82	/// whose compatible types come from external properties. No OS query is guessed.
83	/// # Errors
84	/// Same chain validation as `new_from_record`, plus absent/invalid import
85	/// metadata, null resource identities and incompatible dedication policy.
86	/// # Safety
87	/// The complete `new_from_record` contract applies. `info` must describe this
88	/// exact payload and device: obtain size/types from the platform Vulkan query
89	/// or authoritative OS/export metadata. Include all backing bytes, even when
90	/// native allocationSize is ignored/zero. A supplied resource must be live,
91	/// unbound, compatible, and have requirements covered by this size/type mask.
92	/// Every native dedication rule still applies, including an exact requirements
93	/// size wherever required. Fuchsia collection resource/index/size must match.
94	/// Android/QNX/OHOS image profiles and Metal textures require their image.
95	/// External host/OS objects must survive every native use, not just this borrow.
96	pub unsafe fn new_from_record_with_import<T: vk::Extends<vk::MemoryAllocateInfo<'a>>>(
97		record: &'a T,
98		info: ImportMemoryInfo,
99	) -> Result<Self, AllocError> {
100		// SAFETY: caller supplies the complete immutable chain and truthful payload
101		// metadata; inspection validates shape without dereferencing external objects.
102		let data = unsafe { inspect(ptr::from_ref(record).cast(), Some(info)) }?;
103		Ok(Self {
104			data,
105			borrow: PhantomData,
106		})
107	}
108
109	pub fn as_ptr(&self) -> *const c_void {
110		self.data.next
111	}
112}
113
114unsafe fn inspect(
115	next: *const c_void,
116	import_info: Option<ImportMemoryInfo>,
117) -> Result<ChainData, AllocError> {
118	let mut data = ChainData {
119		next,
120		import_info,
121		..Default::default()
122	};
123	let mut seen = [ptr::null::<vk::BaseInStructure>(); 64];
124	let mut tags = [vk::StructureType::from_raw(0); 64];
125	let mut count = 0;
126	let mut cursor = next.cast::<vk::BaseInStructure>();
127	while !cursor.is_null() {
128		if count == seen.len() || seen[..count].contains(&cursor) {
129			return Err(AllocError::InvalidRequest(
130				"cyclic or excessive allocation chain",
131			));
132		}
133		// SAFETY: construction contract guarantees readable complete native records.
134		let node = unsafe { &*cursor };
135		if tags[..count].contains(&node.s_type) {
136			return Err(AllocError::InvalidRequest(
137				"duplicate allocation chain record",
138			));
139		}
140		seen[count] = cursor;
141		tags[count] = node.s_type;
142		count += 1;
143		match node.s_type {
144			vk::StructureType::MEMORY_DEDICATED_ALLOCATE_INFO
145			| vk::StructureType::DEDICATED_ALLOCATION_MEMORY_ALLOCATE_INFO_NV
146			| vk::StructureType::MEMORY_PRIORITY_ALLOCATE_INFO_EXT
147			| vk::StructureType::MEMORY_DEDICATED_ALLOCATE_INFO_TENSOR_ARM => {
148				return Err(AllocError::InvalidRequest(
149					"dedication and priority are allocator-owned",
150				));
151			}
152			vk::StructureType::MEMORY_ALLOCATE_FLAGS_INFO => {
153				// SAFETY: caller supplies the complete record matching this tag.
154				let flags = unsafe { &*cursor.cast::<vk::MemoryAllocateFlagsInfo>() };
155				if flags.flags.as_raw() & vk::MemoryAllocateFlagBits::DEVICE_MASK_BIT.as_raw() != 0
156					&& flags.device_mask == 0
157				{
158					return Err(AllocError::InvalidRequest(
159						"device-mask allocation requires a nonzero mask",
160					));
161				}
162				if flags.flags.as_raw()
163					& vk::MemoryAllocateFlagBits::DEVICE_ADDRESS_CAPTURE_REPLAY_BIT.as_raw()
164					!= 0
165				{
166					return Err(AllocError::UnsupportedCapability(
167						"allocation capture/replay",
168					));
169				}
170				data.flags = Some(flags.flags);
171			}
172			vk::StructureType::EXPORT_MEMORY_ALLOCATE_INFO => {
173				// SAFETY: caller supplies the complete record matching this tag.
174				data.exports = unsafe { (*cursor.cast::<vk::ExportMemoryAllocateInfo>()).handle_types };
175				data.has_export = true;
176			}
177			vk::StructureType::IMPORT_MEMORY_FD_INFO_KHR => {
178				if data.imports {
179					return Err(AllocError::InvalidRequest(
180						"multiple memory import operations",
181					));
182				}
183				// SAFETY: complete native record is guaranteed by construction.
184				let import = unsafe { &*cursor.cast::<vk::ImportMemoryFdInfoKHR>() };
185				if import.fd < 0
186					|| (import.handle_type != vk::ExternalMemoryHandleTypeFlagBits::OPAQUE_FD_BIT
187						&& import.handle_type != vk::ExternalMemoryHandleTypeFlagBits::DMA_BUF_BIT_EXT)
188				{
189					return Err(AllocError::InvalidRequest("invalid memory fd import"));
190				}
191				data.imports = true;
192				data.import_extension = Some(c"VK_KHR_external_memory_fd");
193			}
194			vk::StructureType::IMPORT_MEMORY_HOST_POINTER_INFO_EXT => {
195				if data.imports {
196					return Err(AllocError::InvalidRequest(
197						"multiple memory import operations",
198					));
199				}
200				// SAFETY: complete native record is guaranteed by construction.
201				let import = unsafe { &*cursor.cast::<vk::ImportMemoryHostPointerInfoEXT>() };
202				if import.p_host_pointer.is_null()
203					|| (import.handle_type != vk::ExternalMemoryHandleTypeFlagBits::HOST_ALLOCATION_BIT_EXT
204						&& import.handle_type
205							!= vk::ExternalMemoryHandleTypeFlagBits::HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT)
206				{
207					return Err(AllocError::InvalidRequest("invalid host pointer import"));
208				}
209				data.imports = true;
210				data.import_kind = ImportKind::Host;
211				data.import_extension = Some(c"VK_EXT_external_memory_host");
212			}
213			vk::StructureType::MEMORY_OPAQUE_CAPTURE_ADDRESS_ALLOCATE_INFO => {
214				return Err(AllocError::UnsupportedCapability(
215					"allocation capture/replay",
216				));
217			}
218			#[cfg(feature = "platform-win32")]
219			vk::StructureType::EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR => {}
220			_ => {
221				// SAFETY: same complete tagged-record guarantee as the core cases.
222				let (extension, kind) = unsafe { import::inspect(cursor) }?;
223				if data.imports {
224					return Err(AllocError::InvalidRequest(
225						"multiple memory import operations",
226					));
227				}
228				data.imports = true;
229				data.import_extension = Some(extension);
230				data.import_kind = kind;
231			}
232		}
233		cursor = node.p_next;
234	}
235	import::validate(&data)?;
236	Ok(data)
237}
238
239#[cfg(test)]
240#[path = "../../../test/rs/mem/test_chain.rs"]
241mod tests;