Skip to main content

vk/memory/
allocator.rs

1//! Separate and pooled ordinary buffers with explicit native lifetime management.
2//!
3//! Donor: VMA 3.4.0, commit 3aa921224c154a0d2c43912bc88e1c42ce1f7607,
4//! `GetBufferMemoryRequirements` and `AllocateDedicatedMemory`. See LICENSE-VMA-MIT.
5//! Adaptation: owned capability admission, checked tokens, fallible host reservation,
6//! cached PFNs and explicit Rust error-bearing cleanup; no implicit native Drop.
7
8use super::{
9	AllocError, AllocRequest, Allocation, AllocationCreateInfo, AllocationPolicy, AllocatorStats,
10	MemoryTypeSelector,
11	block::{Block, Mapping},
12	tlsf::Tlsf,
13};
14use crate::{core as vk, core::Device, load::CallError};
15use std::{collections::HashMap, sync::Arc};
16
17struct Live {
18	serial: u64,
19	handle: vk::DeviceMemory,
20	size: u64,
21	reserved_size: u64,
22	mapped: bool,
23	persistent: bool,
24	buffer: Option<vk::Buffer>,
25	placement: Option<(usize, u64)>,
26	mapping: Mapping,
27}
28
29#[derive(Clone, Copy)]
30enum RequirementsRoute {
31	Legacy,
32	Core,
33	Khr,
34}
35
36/// Borrows one device; native memory is released only by explicit operations.
37///
38/// Each allocation has an allocator identity and generation. Dropping either
39/// the allocator or a token can leak native objects; it never calls Vulkan.
40pub struct Allocator<'device> {
41	device: &'device Device,
42	selector: MemoryTypeSelector,
43	owner: Arc<()>,
44	next_serial: u64,
45	live: HashMap<u64, Live>,
46	blocks: Vec<Block>,
47	native_count: usize,
48	native_bytes: u64,
49	heap_bytes: [u64; 16],
50	allocation_limit: u32,
51	allocation_size_limit: Option<u64>,
52	closed: bool,
53}
54
55impl<'device> Allocator<'device> {
56	/// Construct from caller-supplied physical-device metadata.
57	///
58	/// Prefer [`Self::new_from_physical_device`] for queried properties and the
59	/// local native-allocation count limit. Managed buffers require the device's
60	/// owned creation capabilities; raw imports without them support only the
61	/// explicitly unsafe low-level allocation route.
62	///
63	/// # Safety
64	/// Properties and atom size must belong to this device's physical device.
65	/// Caller must enforce device allocation size/count limits and enabled features.
66	pub unsafe fn new(
67		device: &'device Device,
68		properties: vk::PhysicalDeviceMemoryProperties,
69		atom: u64,
70	) -> Result<Self, AllocError> {
71		let selector = MemoryTypeSelector::new(properties, atom)?;
72		require(device.fp().allocate_memory.is_some(), "vkAllocateMemory")?;
73		require(device.fp().free_memory.is_some(), "vkFreeMemory")?;
74		Ok(Self {
75			device,
76			selector,
77			owner: Arc::new(()),
78			next_serial: 1,
79			live: HashMap::new(),
80			blocks: Vec::new(),
81			native_count: 0,
82			native_bytes: 0,
83			heap_bytes: [0; 16],
84			allocation_limit: u32::MAX,
85			allocation_size_limit: None,
86			closed: false,
87		})
88	}
89
90	/// Query properties and limits for the selected physical device.
91	///
92	/// Requires owned instance/device creation metadata and a compatible parent
93	/// snapshot. It does not discover a logical device's physical-device identity.
94	/// Queries the maximum allocation size when core 1.1 or enabled maintenance3
95	/// provides it. Legacy 1.0 leaves this limit unknown; see [`Self::max_allocation_size`].
96	/// The local count limit excludes allocations made by other allocators/raw calls.
97	///
98	/// # Safety
99	/// `physical_device` must belong to `instance` and be the physical device used
100	/// to create `device`. All contexts must remain live; native ownership and
101	/// host synchronization remain the caller's responsibility. Unqueried size
102	/// limits and resource valid usage must still be observed by the caller.
103	pub unsafe fn new_from_physical_device(
104		instance: &vk::Instance,
105		physical_device: vk::PhysicalDevice,
106		device: &'device Device,
107	) -> Result<Self, AllocError> {
108		let parent = instance
109			.capabilities()
110			.ok_or(AllocError::UnsupportedCapability(
111				"instance creation metadata is required",
112			))?;
113		let capabilities = device
114			.capabilities()
115			.ok_or(AllocError::UnsupportedCapability(
116				"device creation metadata is required",
117			))?;
118		if !capabilities.matches_instance(parent) {
119			return Err(AllocError::InvalidProperties(
120				"device/instance metadata disagree",
121			));
122		}
123		if physical_device.is_null() {
124			return Err(AllocError::InvalidProperties("null physical device"));
125		}
126		// SAFETY: caller guarantees the selected physical device and parent lifetime.
127		let properties = unsafe { instance.get_physical_device_properties(physical_device) }?;
128		// These are property-query availability gates, not feature enablement.
129		// Use conservative owned provenance; never infer command legality from a PFN.
130		let maintenance3 = capabilities
131			.device_extensions()
132			.is_some_and(|mut extensions| extensions.any(|name| name == c"VK_KHR_maintenance3"));
133		let allocation_size_limit = if capabilities.api_version() >= vk::API_VERSION_1_1 || maintenance3
134		{
135			let mut limits = vk::PhysicalDeviceMaintenance3Properties::default();
136			let mut output = vk::PhysicalDeviceProperties2 {
137				p_next: std::ptr::from_mut(&mut limits).cast(),
138				..Default::default()
139			};
140			if parent.instance_api_version() >= vk::API_VERSION_1_1 {
141				// SAFETY: admitted core query, matching physical device and initialized chain.
142				unsafe { instance.get_physical_device_properties2(physical_device, &mut output) }?;
143			} else if parent
144				.instance_extensions()
145				.any(|name| name == c"VK_KHR_get_physical_device_properties2")
146			{
147				// SAFETY: exact KHR query is admitted by the enabled instance extension;
148				// the device enabled maintenance3, which supplies this property record.
149				unsafe { instance.get_physical_device_properties2_khr(physical_device, &mut output) }?;
150			} else {
151				return Err(AllocError::UnsupportedCapability(
152					"maintenance3 requires properties2",
153				));
154			}
155			if limits.max_memory_allocation_size == 0 {
156				return Err(AllocError::InvalidProperties(
157					"zero native allocation-size limit",
158				));
159			}
160			Some(limits.max_memory_allocation_size)
161		} else {
162			None
163		};
164		// SAFETY: same live physical device.
165		let memory = unsafe { instance.get_physical_device_memory_properties(physical_device) }?;
166		if properties.limits.max_memory_allocation_count == 0 {
167			return Err(AllocError::InvalidProperties(
168				"zero native allocation limit",
169			));
170		}
171		// SAFETY: metadata was queried from the caller's matching physical device.
172		let mut allocator =
173			unsafe { Self::new(device, memory, properties.limits.non_coherent_atom_size) }?;
174		allocator.allocation_limit = properties.limits.max_memory_allocation_count;
175		allocator.allocation_size_limit = allocation_size_limit;
176		// Select/validate the exact admitted requirements command before publication.
177		allocator.buffer_route()?;
178		Ok(allocator)
179	}
180
181	pub fn live_count(&self) -> usize {
182		self.live.len()
183	}
184
185	pub fn is_closed(&self) -> bool {
186		self.closed
187	}
188
189	/// Queried native per-allocation size ceiling, or unknown on the caller-supplied
190	/// metadata route and legacy 1.0 without admitted maintenance3 properties.
191	/// A known ceiling is not a promise of available heap space or allocation success.
192	pub fn max_allocation_size(&self) -> Option<u64> {
193		self.allocation_size_limit
194	}
195
196	/// Close only when all allocations have been explicitly released; repeat close succeeds.
197	pub fn close(&mut self) -> Result<(), AllocError> {
198		close(self)
199	}
200
201	/// Allocate separate native storage without a resource-specific allocation chain.
202	/// # Safety
203	/// Request must satisfy resource requirements and device size/count limits.
204	/// No external, protected, device-address or required-dedication semantics are admitted.
205	pub unsafe fn allocate(&mut self, request: &AllocRequest) -> Result<Allocation, AllocError> {
206		// SAFETY: caller supplies the canonical operation's native guarantees.
207		unsafe { allocate(self, request) }
208	}
209
210	/// # Safety
211	/// Destroy every bound resource and complete all GPU/host accesses first.
212	pub unsafe fn free(&mut self, allocation: &mut Allocation) -> Result<(), AllocError> {
213		// SAFETY: caller supplies the canonical operation's native guarantees.
214		unsafe { free(self, allocation) }
215	}
216
217	/// Map the complete allocation; repeated maps are rejected.
218	/// # Safety
219	/// Synchronize host/GPU access; returned pointers cannot outlive the mapping.
220	pub unsafe fn map(
221		&mut self,
222		allocation: &Allocation,
223	) -> Result<*mut std::ffi::c_void, AllocError> {
224		// SAFETY: caller supplies the canonical operation's native guarantees.
225		unsafe { map(self, allocation) }
226	}
227
228	/// # Safety
229	/// No pointer into this mapping may be used after unmapping.
230	pub unsafe fn unmap(&mut self, allocation: &Allocation) -> Result<(), AllocError> {
231		// SAFETY: caller supplies the canonical operation's native guarantees.
232		unsafe { unmap(self, allocation) }
233	}
234
235	/// # Safety
236	/// Mapping and accesses must satisfy Vulkan host/device synchronization rules.
237	pub unsafe fn flush(
238		&self,
239		allocation: &Allocation,
240		offset: u64,
241		size: u64,
242	) -> Result<(), AllocError> {
243		// SAFETY: caller supplies the canonical operation's native guarantees.
244		unsafe { flush(self, allocation, offset, size) }
245	}
246
247	/// # Safety
248	/// Complete device writes before invalidating and do not race host accesses.
249	pub unsafe fn invalidate(
250		&self,
251		allocation: &Allocation,
252		offset: u64,
253		size: u64,
254	) -> Result<(), AllocError> {
255		// SAFETY: caller supplies the canonical operation's native guarantees.
256		unsafe { invalidate(self, allocation, offset, size) }
257	}
258
259	/// Create, allocate and bind an ordinary buffer transactionally.
260	/// # Safety
261	/// Info must satisfy device limits/valid usage. Sparse, external, protected
262	/// and device-address buffers and arbitrary pNext chains are not admitted.
263	pub unsafe fn create_buffer(
264		&mut self,
265		info: &vk::BufferCreateInfo,
266		required: vk::MemoryPropertyFlags,
267		preferred: vk::MemoryPropertyFlags,
268	) -> Result<(vk::Buffer, Allocation), AllocError> {
269		// SAFETY: caller supplies the canonical operation's native guarantees.
270		unsafe { create_buffer(self, info, required, preferred) }
271	}
272
273	/// Destroy a matching managed buffer before freeing its allocation.
274	/// # Safety
275	/// Complete every CPU/GPU use; do not separately destroy this buffer first.
276	pub unsafe fn destroy_buffer(
277		&mut self,
278		buffer: vk::Buffer,
279		allocation: &mut Allocation,
280	) -> Result<(), AllocError> {
281		// SAFETY: caller supplies the canonical operation's native guarantees.
282		unsafe { destroy_buffer(self, buffer, allocation) }
283	}
284
285	/// Create and bind an ordinary buffer using explicit separate/pooled policy.
286	/// # Safety
287	/// Same native valid-usage, completion and synchronization obligations as `create_buffer`.
288	pub unsafe fn create_buffer_with_options(
289		&mut self,
290		info: &vk::BufferCreateInfo,
291		options: &AllocationCreateInfo,
292	) -> Result<(vk::Buffer, Allocation), AllocError> {
293		// SAFETY: caller supplies the canonical operation's guarantees.
294		unsafe { create_buffer_with_options(self, info, options) }
295	}
296
297	/// Observe a current explicit or persistent mapping without acquiring a hold.
298	pub fn mapped_pointer(
299		&self,
300		allocation: &Allocation,
301	) -> Result<Option<*mut std::ffi::c_void>, AllocError> {
302		mapped_pointer(self, allocation)
303	}
304
305	pub fn statistics(&self) -> AllocatorStats {
306		statistics(self)
307	}
308
309	fn ensure_open(&self) -> Result<(), AllocError> {
310		if self.closed {
311			Err(AllocError::Closed)
312		} else {
313			Ok(())
314		}
315	}
316
317	fn validate(&self, allocation: &Allocation) -> Result<&Live, AllocError> {
318		self.ensure_open()?;
319		if !Arc::ptr_eq(&self.owner, &allocation.owner) {
320			return Err(AllocError::WrongAllocator);
321		}
322		self
323			.live
324			.get(&allocation.serial)
325			.filter(|live| live.serial == allocation.serial && live.handle == allocation.handle)
326			.ok_or(AllocError::UnknownAllocation)
327	}
328
329	fn reserve(&mut self) -> Result<(), AllocError> {
330		self.ensure_open()?;
331		self
332			.next_serial
333			.checked_add(1)
334			.ok_or(AllocError::SerialExhausted)?;
335		self
336			.live
337			.try_reserve(1)
338			.map_err(|_| AllocError::HostAllocationFailed)
339	}
340
341	fn validate_size(&self, size: u64) -> Result<(), AllocError> {
342		if let Some(limit) = self.allocation_size_limit
343			&& size > limit
344		{
345			return Err(AllocError::AllocationTooLarge {
346				requested: size,
347				limit,
348			});
349		}
350		Ok(())
351	}
352
353	fn check_native(&self, size: u64, index: u32) -> Result<(), AllocError> {
354		self.validate_size(size)?;
355		if self.native_count >= self.allocation_limit as usize {
356			return Err(AllocError::AllocationLimit);
357		}
358		let heap = self.selector.properties.memory_types[index as usize].heap_index as usize;
359		let available = self.selector.properties.memory_heaps[heap].size - self.heap_bytes[heap];
360		if size > available {
361			return Err(AllocError::OutOfSpace);
362		}
363		self
364			.native_bytes
365			.checked_add(size)
366			.ok_or(AllocError::InvalidRange)?;
367		Ok(())
368	}
369
370	fn add_native(&mut self, size: u64, index: u32) {
371		let heap = self.selector.properties.memory_types[index as usize].heap_index as usize;
372		self.native_count += 1;
373		self.native_bytes += size;
374		self.heap_bytes[heap] += size;
375	}
376
377	fn remove_native(&mut self, size: u64, index: u32) {
378		let heap = self.selector.properties.memory_types[index as usize].heap_index as usize;
379		self.native_count -= 1;
380		self.native_bytes -= size;
381		self.heap_bytes[heap] -= size;
382	}
383
384	fn mapping(&self, allocation: &Allocation) -> &Mapping {
385		if self.live[&allocation.serial].placement.is_some() {
386			&self
387				.blocks
388				.iter()
389				.find(|b| b.handle == allocation.handle)
390				.expect("live block")
391				.mapping
392		} else {
393			&self.live[&allocation.serial].mapping
394		}
395	}
396
397	fn mapping_mut(&mut self, allocation: &Allocation) -> &mut Mapping {
398		if self.live[&allocation.serial].placement.is_some() {
399			&mut self
400				.blocks
401				.iter_mut()
402				.find(|b| b.handle == allocation.handle)
403				.expect("live block")
404				.mapping
405		} else {
406			&mut self
407				.live
408				.get_mut(&allocation.serial)
409				.expect("validated live region")
410				.mapping
411		}
412	}
413
414	fn contains_memory(&self, memory: vk::DeviceMemory) -> bool {
415		self.blocks.iter().any(|b| b.handle == memory)
416			|| self.live.values().any(|live| live.handle == memory)
417	}
418
419	fn buffer_route(&self) -> Result<RequirementsRoute, AllocError> {
420		let capabilities = self
421			.device
422			.capabilities()
423			.ok_or(AllocError::UnsupportedCapability(
424				"managed buffers require device creation metadata",
425			))?;
426		if capabilities.api_version() >= vk::API_VERSION_1_1 {
427			require(
428				self.device.fp().get_buffer_memory_requirements2.is_some(),
429				"vkGetBufferMemoryRequirements2",
430			)?;
431			return Ok(RequirementsRoute::Core);
432		}
433		let enabled = |name| {
434			capabilities
435				.device_extensions()
436				.is_some_and(|mut extensions| extensions.any(|extension| extension == name))
437		};
438		if enabled(c"VK_KHR_dedicated_allocation") {
439			if !enabled(c"VK_KHR_get_memory_requirements2") {
440				return Err(AllocError::UnsupportedCapability(
441					"dedication requires VK_KHR_get_memory_requirements2",
442				));
443			}
444			require(
445				self
446					.device
447					.fp()
448					.get_buffer_memory_requirements2_khr
449					.is_some(),
450				"vkGetBufferMemoryRequirements2KHR",
451			)?;
452			Ok(RequirementsRoute::Khr)
453		} else {
454			require(
455				self.device.fp().get_buffer_memory_requirements.is_some(),
456				"vkGetBufferMemoryRequirements",
457			)?;
458			Ok(RequirementsRoute::Legacy)
459		}
460	}
461}
462
463fn require(present: bool, name: &'static str) -> Result<(), AllocError> {
464	if present {
465		Ok(())
466	} else {
467		Err(CallError::MissingCommand(name).into())
468	}
469}
470
471/// Checked shutdown after all regions and their blocks have been explicitly released.
472pub fn close(allocator: &mut Allocator<'_>) -> Result<(), AllocError> {
473	if !allocator.live.is_empty() {
474		return Err(AllocError::LiveAllocations);
475	}
476	allocator.closed = true;
477	Ok(())
478}
479
480/// Allocate separate native storage with no advanced allocation chain.
481/// # Safety
482/// Requirements, size/count limits and enabled features must be valid. Required
483/// dedication, protected, device-address and external allocation are excluded.
484pub unsafe fn allocate(
485	allocator: &mut Allocator<'_>,
486	request: &AllocRequest,
487) -> Result<Allocation, AllocError> {
488	// SAFETY: caller provides the requirements and ordinary allocation semantics.
489	unsafe { allocate_storage(allocator, request, None) }
490}
491
492unsafe fn allocate_storage(
493	allocator: &mut Allocator<'_>,
494	request: &AllocRequest,
495	dedicated: Option<&vk::MemoryDedicatedAllocateInfo>,
496) -> Result<Allocation, AllocError> {
497	allocator.ensure_open()?;
498	allocator.validate_size(request.size)?;
499	if request.required_flags.as_raw() & vk::MemoryPropertyFlagBits::PROTECTED_BIT.as_raw() != 0 {
500		return Err(AllocError::UnsupportedCapability("protected allocation"));
501	}
502	if allocator.native_count >= allocator.allocation_limit as usize {
503		return Err(AllocError::AllocationLimit);
504	}
505	allocator.reserve()?;
506	let mut bits = request.memory_type_bits;
507	let mut last = None;
508	loop {
509		let candidate = AllocRequest {
510			memory_type_bits: bits,
511			..*request
512		};
513		let index = match allocator.selector.find_memory_type(&candidate) {
514			Ok(index) => index,
515			Err(error) => return Err(last.unwrap_or(error)),
516		};
517		bits &= !(1 << index);
518		if let Err(error) = allocator.check_native(request.size, index) {
519			if error == AllocError::OutOfSpace {
520				last = Some(error);
521				continue;
522			}
523			return Err(error);
524		}
525		let info = vk::MemoryAllocateInfo {
526			allocation_size: request.size,
527			memory_type_index: index,
528			p_next: dedicated.map_or(std::ptr::null(), |info| std::ptr::from_ref(info).cast()),
529			..Default::default()
530		};
531		// SAFETY: matching requirements, admitted chain and reserved bookkeeping.
532		let handle = match unsafe { allocator.device.allocate_memory(&info, None) } {
533			Ok(handle) => handle,
534			Err(error) if recoverable(&error) => {
535				last = Some(error.into());
536				continue;
537			}
538			Err(error) => return Err(error.into()),
539		};
540		if handle.is_null() || allocator.contains_memory(handle) {
541			return Err(
542				CallError::InvalidOutput("allocator returned a null or already-live memory handle").into(),
543			);
544		}
545		allocator.add_native(request.size, index);
546		return Ok(publish(
547			allocator,
548			Region {
549				handle,
550				index,
551				size: request.size,
552				reserved_size: request.size,
553				offset: 0,
554				block_size: request.size,
555				placement: None,
556			},
557		));
558	}
559}
560
561fn recoverable(error: &CallError) -> bool {
562	matches!(error, CallError::Vulkan(result) if *result == vk::Result::ERROR_OUT_OF_DEVICE_MEMORY || *result == vk::Result::ERROR_OUT_OF_HOST_MEMORY)
563}
564
565// Publication is infallible after tracking reservation and native/placement success.
566struct Region {
567	handle: vk::DeviceMemory,
568	index: u32,
569	size: u64,
570	reserved_size: u64,
571	offset: u64,
572	block_size: u64,
573	placement: Option<(usize, u64)>,
574}
575
576fn publish(allocator: &mut Allocator<'_>, region: Region) -> Allocation {
577	let Region {
578		handle,
579		index,
580		size,
581		reserved_size,
582		offset,
583		block_size,
584		placement,
585	} = region;
586	let serial = allocator.next_serial;
587	allocator.next_serial += 1;
588	allocator.live.insert(
589		serial,
590		Live {
591			serial,
592			handle,
593			size,
594			reserved_size,
595			mapped: false,
596			persistent: false,
597			buffer: None,
598			placement,
599			mapping: Mapping::default(),
600		},
601	);
602	let memory = allocator.selector.properties.memory_types[index as usize];
603	Allocation {
604		handle,
605		size,
606		reserved_size,
607		offset,
608		block_size,
609		memory_type_index: index,
610		heap_index: memory.heap_index,
611		flags: memory.property_flags,
612		atom: allocator.selector.atom,
613		owner: allocator.owner.clone(),
614		serial,
615	}
616}
617
618/// Release a separate allocation or pooled placement and invalidate its token.
619/// # Safety
620/// Destroy all bound resources and complete every host/GPU access first. This
621/// is also the low-level escape route after manually destroying a managed buffer.
622pub unsafe fn free(
623	allocator: &mut Allocator<'_>,
624	allocation: &mut Allocation,
625) -> Result<(), AllocError> {
626	let live = allocator.validate(allocation)?;
627	if live.mapped {
628		return Err(AllocError::AlreadyMapped);
629	}
630	let placement = live.placement;
631	let persistent = live.persistent;
632	if persistent {
633		let device = allocator.device;
634		// SAFETY: no explicit maps remain; caller ends every persistent pointer use.
635		unsafe {
636			allocator
637				.mapping_mut(allocation)
638				.release(device, allocation.handle)
639		}?;
640	}
641	if let Some((index, generation)) = placement {
642		let block_index = allocator
643			.blocks
644			.iter()
645			.position(|b| b.handle == allocation.handle)
646			.expect("live block");
647		let block = &mut allocator.blocks[block_index];
648		block
649			.metadata
650			.free(index, generation)
651			.expect("validated private placement");
652		if block.metadata.statistics().allocation_count == 0 {
653			debug_assert_eq!(block.mapping.references, 0);
654			// SAFETY: caller ended every resource/access; the final region released the block.
655			unsafe { allocator.device.free_memory(allocation.handle, None) }?;
656			let block = allocator.blocks.remove(block_index);
657			allocator.remove_native(block.size, block.memory_type_index);
658		}
659	} else {
660		// SAFETY: checked ownership; caller guarantees resource destruction/completion.
661		unsafe { allocator.device.free_memory(allocation.handle, None) }?;
662		allocator.remove_native(allocation.block_size, allocation.memory_type_index);
663	}
664	allocator.live.remove(&allocation.serial);
665	allocation.handle = vk::DeviceMemory::NULL;
666	Ok(())
667}
668
669/// Map one allocation. Distinct regions share a reference-counted block mapping.
670/// Repeated explicit maps of the same region return `AlreadyMapped`.
671/// # Safety
672/// Synchronize host/GPU access. Pointers remain valid only while an explicit or
673/// persistent hold for this allocation remains live. Do not alias Rust references.
674pub unsafe fn map(
675	allocator: &mut Allocator<'_>,
676	allocation: &Allocation,
677) -> Result<*mut std::ffi::c_void, AllocError> {
678	if allocator.validate(allocation)?.mapped {
679		return Err(AllocError::AlreadyMapped);
680	}
681	if !allocation.is_host_visible() {
682		return Err(AllocError::NotHostVisible);
683	}
684	let device = allocator.device;
685	// SAFETY: live visible block, checked host extent in Mapping; caller synchronizes.
686	let base = unsafe {
687		allocator
688			.mapping_mut(allocation)
689			.acquire(device, allocation.handle, allocation.block_size)
690	}?;
691	allocator
692		.live
693		.get_mut(&allocation.serial)
694		.expect("validated live region")
695		.mapped = true;
696	// wrapping_add preserves pointer provenance without claiming a Rust object
697	// bounds proof for an FFI mapping. Checked native bounds/host extent precede it.
698	Ok(
699		base
700			.cast::<u8>()
701			.wrapping_add(allocation.offset as usize)
702			.cast(),
703	)
704}
705
706/// Release one explicit map; persistent holds and other regions remain mapped.
707/// # Safety
708/// Stop using pointers acquired by this explicit hold. This never flushes or waits.
709pub unsafe fn unmap(
710	allocator: &mut Allocator<'_>,
711	allocation: &Allocation,
712) -> Result<(), AllocError> {
713	if !allocator.validate(allocation)?.mapped {
714		return Err(AllocError::NotMapped);
715	}
716	let device = allocator.device;
717	// SAFETY: a checked explicit hold exists and the caller ends its pointer uses.
718	unsafe {
719		allocator
720			.mapping_mut(allocation)
721			.release(device, allocation.handle)
722	}?;
723	allocator
724		.live
725		.get_mut(&allocation.serial)
726		.expect("validated live region")
727		.mapped = false;
728	Ok(())
729}
730
731/// Return a region pointer only if that region has an explicit or persistent hold.
732/// Does not map, acquire a hold, synchronize, or authorize pointer dereferences.
733pub fn mapped_pointer(
734	allocator: &Allocator<'_>,
735	allocation: &Allocation,
736) -> Result<Option<*mut std::ffi::c_void>, AllocError> {
737	let live = allocator.validate(allocation)?;
738	if !live.mapped && !live.persistent {
739		return Ok(None);
740	}
741	Ok(Some(
742		allocator
743			.mapping(allocation)
744			.pointer
745			.cast::<u8>()
746			.wrapping_add(allocation.offset as usize)
747			.cast(),
748	))
749}
750
751/// Calculate local live-region/native-block accounting without querying the driver.
752pub fn statistics(allocator: &Allocator<'_>) -> AllocatorStats {
753	AllocatorStats {
754		allocation_count: allocator.live.len(),
755		native_allocation_count: allocator.native_count,
756		pooled_block_count: allocator.blocks.len(),
757		allocation_bytes: allocator.live.values().map(|v| v.size).sum(),
758		reserved_bytes: allocator.live.values().map(|v| v.reserved_size).sum(),
759		native_bytes: allocator.native_bytes,
760		mapped_native_count: allocator
761			.blocks
762			.iter()
763			.filter(|b| b.mapping.references != 0)
764			.count()
765			+ allocator
766				.live
767				.values()
768				.filter(|v| v.placement.is_none() && v.mapping.references != 0)
769				.count(),
770	}
771}
772
773/// Flush a bounded allocation-relative mapped range, observing atom/end rules.
774/// # Safety
775/// Follow Vulkan's host/device synchronization and mapping requirements.
776pub unsafe fn flush(
777	allocator: &Allocator<'_>,
778	allocation: &Allocation,
779	offset: u64,
780	size: u64,
781) -> Result<(), AllocError> {
782	let live = allocator.validate(allocation)?;
783	if !live.mapped && !live.persistent {
784		return Err(AllocError::NotMapped);
785	}
786	if let Some(range) = allocation.mapped_range(offset, size)? {
787		// SAFETY: whole allocation mapped; range is bounded and atom aligned.
788		unsafe { allocator.device.flush_mapped_memory_ranges(&[range]) }?;
789	}
790	Ok(())
791}
792
793/// Invalidate a bounded allocation-relative mapped range; never waits for the GPU.
794/// # Safety
795/// Complete device writes first and do not race host accesses.
796pub unsafe fn invalidate(
797	allocator: &Allocator<'_>,
798	allocation: &Allocation,
799	offset: u64,
800	size: u64,
801) -> Result<(), AllocError> {
802	let live = allocator.validate(allocation)?;
803	if !live.mapped && !live.persistent {
804		return Err(AllocError::NotMapped);
805	}
806	if let Some(range) = allocation.mapped_range(offset, size)? {
807		// SAFETY: whole allocation mapped; range is bounded and atom aligned.
808		unsafe { allocator.device.invalidate_mapped_memory_ranges(&[range]) }?;
809	}
810	Ok(())
811}
812
813/// Transactionally create, query, allocate and bind an ordinary buffer.
814///
815/// Core 1.1 or enabled KHR dedication uses requirements2 with a dedicated
816/// requirements output chain and dedicates this separate allocation to the
817/// buffer, even when dedication is only preferred or unnecessary. Core 1.0
818/// without dedication uses legacy requirements. Exact PFN spellings are retained.
819///
820/// # Safety
821/// Info/limits/queue-family data and ordinary buffer usage must satisfy Vulkan
822/// valid usage. Arbitrary chains, sparse/protected/external/BDA are excluded.
823pub unsafe fn create_buffer(
824	allocator: &mut Allocator<'_>,
825	info: &vk::BufferCreateInfo,
826	required: vk::MemoryPropertyFlags,
827	preferred: vk::MemoryPropertyFlags,
828) -> Result<(vk::Buffer, Allocation), AllocError> {
829	let options = AllocationCreateInfo {
830		required_flags: required,
831		preferred_flags: preferred,
832		..Default::default()
833	};
834	// SAFETY: same native guarantees with the existing separate-storage policy.
835	unsafe { create_buffer_with_options(allocator, info, &options) }
836}
837
838/// Create, allocate, optionally map and bind an ordinary buffer transactionally.
839///
840/// Only recoverable native allocation OOM retries another compatible memory type.
841/// Required/preferred dedication and regions larger than the explicit block use
842/// separate storage. Empty blocks are released immediately, without caching.
843/// # Errors
844/// Returns checked policy/identity/limit errors or the preserved native failure.
845/// # Safety
846/// Info and queue-family pointers must satisfy native valid usage. Complete uses
847/// before release. Sparse, external, protected, BDA and arbitrary chains are excluded.
848pub unsafe fn create_buffer_with_options(
849	allocator: &mut Allocator<'_>,
850	info: &vk::BufferCreateInfo,
851	options: &AllocationCreateInfo,
852) -> Result<(vk::Buffer, Allocation), AllocError> {
853	let mut required = options.required_flags;
854	if options.persistent_map {
855		required = required | vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT;
856	}
857	let preferred = options.preferred_flags;
858	allocator.ensure_open()?;
859	allocator.validate_size(info.size)?;
860	if !info.p_next.is_null()
861		|| info.flags.as_raw() != 0
862		|| info.size == 0
863		|| info.usage.as_raw() == 0
864		|| info.usage.as_raw()
865			& (vk::BufferUsageFlagBits::SHADER_DEVICE_ADDRESS_BIT.as_raw()
866				| vk::BufferUsageFlagBits::TILE_MEMORY_BIT_QCOM.as_raw())
867			!= 0
868	{
869		return Err(AllocError::InvalidRequest("ordinary nonempty buffers only"));
870	}
871	if required.as_raw() & vk::MemoryPropertyFlagBits::PROTECTED_BIT.as_raw() != 0 {
872		return Err(AllocError::UnsupportedCapability("protected allocation"));
873	}
874	if let AllocationPolicy::Pooled { block_size: 0 } = options.policy {
875		return Err(AllocError::InvalidRequest(
876			"pooled block size must be nonzero",
877		));
878	}
879	if options.policy == AllocationPolicy::Separate
880		&& allocator.native_count >= allocator.allocation_limit as usize
881	{
882		return Err(AllocError::AllocationLimit);
883	}
884	if options.persistent_map {
885		require(allocator.device.fp().map_memory.is_some(), "vkMapMemory")?;
886		require(
887			allocator.device.fp().unmap_memory.is_some(),
888			"vkUnmapMemory",
889		)?;
890	}
891	let route = allocator.buffer_route()?;
892	let fp = allocator.device.fp();
893	require(fp.create_buffer.is_some(), "vkCreateBuffer")?;
894	require(fp.destroy_buffer.is_some(), "vkDestroyBuffer")?;
895	require(fp.bind_buffer_memory.is_some(), "vkBindBufferMemory")?;
896	allocator.reserve()?;
897	// SAFETY: caller guarantees ordinary buffer valid usage; cleanup/metadata preflighted.
898	let buffer = unsafe { allocator.device.create_buffer(info, None) }?;
899	if buffer.is_null() {
900		return Err(CallError::InvalidOutput("vkCreateBuffer returned a null handle").into());
901	}
902	let mut dedication = vk::MemoryDedicatedRequirements::default();
903	let mut requirements2 = vk::MemoryRequirements2 {
904		p_next: std::ptr::from_mut(&mut dedication).cast(),
905		..Default::default()
906	};
907	let query = vk::BufferMemoryRequirementsInfo2 {
908		buffer,
909		..Default::default()
910	};
911	// SAFETY: live unbound ordinary buffer; exact route admitted and PFN preflighted.
912	let requirements = unsafe {
913		match route {
914			RequirementsRoute::Legacy => allocator.device.get_buffer_memory_requirements(buffer),
915			RequirementsRoute::Core => allocator
916				.device
917				.get_buffer_memory_requirements2(&query, &mut requirements2)
918				.map(|()| requirements2.memory_requirements),
919			RequirementsRoute::Khr => allocator
920				.device
921				.get_buffer_memory_requirements2_khr(&query, &mut requirements2)
922				.map(|()| requirements2.memory_requirements),
923		}
924	};
925	let request = requirements.map(|requirements| AllocRequest {
926		size: requirements.size,
927		alignment: requirements.alignment,
928		memory_type_bits: requirements.memory_type_bits & options.memory_type_bits,
929		required_flags: required,
930		preferred_flags: preferred,
931	});
932	let dedicated = vk::MemoryDedicatedAllocateInfo {
933		buffer,
934		..Default::default()
935	};
936	let dedicated = match route {
937		RequirementsRoute::Legacy => None,
938		_ => Some(&dedicated),
939	};
940	// SAFETY: queried compatible requirements and legal buffer-specific dedication.
941	let result = match request {
942		Ok(request) => {
943			let force_separate = dedication.requires_dedicated_allocation != 0
944				|| dedication.prefers_dedicated_allocation != 0;
945			if let AllocationPolicy::Pooled { block_size } = options.policy
946				&& !force_separate
947				&& request.size <= block_size
948			{
949				// SAFETY: queried ordinary buffer requirements and explicitly selected policy.
950				unsafe { allocate_pooled(allocator, &request, block_size, options.strategy) }
951			} else {
952				// SAFETY: queried matching requirements and admitted buffer dedication.
953				unsafe { allocate_storage(allocator, &request, dedicated) }
954			}
955		}
956		Err(error) => Err(error.into()),
957	};
958	let mut allocation = match result {
959		Ok(allocation) => allocation,
960		Err(error) => {
961			// SAFETY: unbound new buffer, cached immutable destruction PFN preflighted.
962			unsafe { allocator.device.destroy_buffer(buffer, None) }.expect("preflighted cleanup");
963			return Err(error);
964		}
965	};
966	if options.persistent_map {
967		// SAFETY: storage is host visible; no pointers have been published.
968		if let Err(error) = unsafe { map(allocator, &allocation) } {
969			// SAFETY: newly created buffer/storage only; no external use exists.
970			unsafe {
971				allocator
972					.device
973					.destroy_buffer(buffer, None)
974					.expect("preflighted cleanup");
975				free(allocator, &mut allocation).expect("preflighted allocation rollback");
976			}
977			return Err(error);
978		}
979		let live = allocator
980			.live
981			.get_mut(&allocation.serial)
982			.expect("new allocation");
983		live.mapped = false;
984		live.persistent = true;
985	}
986	// SAFETY: matching queried requirements, legal dedication or isolated pooled offset.
987	if let Err(error) = unsafe {
988		allocator
989			.device
990			.bind_buffer_memory(buffer, allocation.handle, allocation.offset)
991	} {
992		// SAFETY: only newly acquired objects are rolled back; no GPU use occurred.
993		unsafe {
994			allocator
995				.device
996				.destroy_buffer(buffer, None)
997				.expect("preflighted cleanup");
998			free(allocator, &mut allocation).expect("preflighted allocation rollback");
999		}
1000		return Err(error.into());
1001	}
1002	allocator.live.get_mut(&allocation.serial).unwrap().buffer = Some(buffer);
1003	Ok((buffer, allocation))
1004}
1005
1006// Donor: VMA BlockVector existing-block-first policy, explicit-size blocks,
1007// FindMemoryTypeIndex retry, GetMemoryTypeMinAlignment. Adaptations: homogeneous
1008// buffers using the verified virtual TLSF bucket geometry, insertion-order block
1009// search, atom-rounded reservation and immediate empty-block release (no hysteresis).
1010unsafe fn allocate_pooled(
1011	allocator: &mut Allocator<'_>,
1012	request: &AllocRequest,
1013	block_size: u64,
1014	strategy: super::PlacementStrategy,
1015) -> Result<Allocation, AllocError> {
1016	allocator.validate_size(block_size)?;
1017	let mut bits = request.memory_type_bits;
1018	let mut last = None;
1019	loop {
1020		let candidate = AllocRequest {
1021			memory_type_bits: bits,
1022			..*request
1023		};
1024		let index = match allocator.selector.find_memory_type(&candidate) {
1025			Ok(index) => index,
1026			Err(error) => return Err(last.unwrap_or(error)),
1027		};
1028		bits &= !(1 << index);
1029		let flags = allocator.selector.properties.memory_types[index as usize].property_flags;
1030		let noncoherent = flags.as_raw()
1031			& (vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT.as_raw()
1032				| vk::MemoryPropertyFlagBits::HOST_COHERENT_BIT.as_raw())
1033			== vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT.as_raw();
1034		let atom = if noncoherent {
1035			allocator.selector.atom
1036		} else {
1037			1
1038		};
1039		let alignment = request.alignment.max(atom);
1040		if !alignment.is_power_of_two() || !request.alignment.is_power_of_two() || request.size == 0 {
1041			return Err(AllocError::InvalidRequest(
1042				"invalid native buffer requirements",
1043			));
1044		}
1045		let reserved = request
1046			.size
1047			.checked_add(atom - 1)
1048			.map(|v| v & !(atom - 1))
1049			.ok_or(AllocError::InvalidRange)?;
1050		if reserved > block_size {
1051			// The nominal requirement fits, but its private atom extent does not.
1052			last = Some(AllocError::OutOfSpace);
1053			continue;
1054		}
1055		for block in &mut allocator.blocks {
1056			if block.memory_type_index != index || block.size != block_size {
1057				continue;
1058			}
1059			let placement = match block.metadata.allocate(reserved, alignment, strategy) {
1060				Ok(placement) => placement,
1061				Err(AllocError::OutOfSpace) => continue,
1062				Err(error) => return Err(error),
1063			};
1064			let offset = block
1065				.metadata
1066				.info(placement.0, placement.1)
1067				.expect("new placement")
1068				.offset;
1069			let handle = block.handle;
1070			return Ok(publish(
1071				allocator,
1072				Region {
1073					handle,
1074					index,
1075					size: request.size,
1076					reserved_size: reserved,
1077					offset,
1078					block_size,
1079					placement: Some(placement),
1080				},
1081			));
1082		}
1083		if let Err(error) = allocator.check_native(block_size, index) {
1084			// A count ceiling prevents growth, not reuse of another compatible type.
1085			if error == AllocError::OutOfSpace || error == AllocError::AllocationLimit {
1086				last = Some(error);
1087				continue;
1088			}
1089			return Err(error);
1090		}
1091		allocator
1092			.blocks
1093			.try_reserve(1)
1094			.map_err(|_| AllocError::HostAllocationFailed)?;
1095		let mut metadata = Tlsf::new(block_size)?;
1096		let placement = metadata.allocate(reserved, alignment, strategy)?;
1097		let info = vk::MemoryAllocateInfo {
1098			allocation_size: block_size,
1099			memory_type_index: index,
1100			..Default::default()
1101		};
1102		// SAFETY: admitted plain buffer storage, native limits and all metadata preflighted.
1103		let handle = match unsafe { allocator.device.allocate_memory(&info, None) } {
1104			Ok(handle) => handle,
1105			Err(error) if recoverable(&error) => {
1106				last = Some(error.into());
1107				continue;
1108			}
1109			Err(error) => return Err(error.into()),
1110		};
1111		if handle.is_null() || allocator.contains_memory(handle) {
1112			return Err(
1113				CallError::InvalidOutput("allocator returned a null or already-live memory handle").into(),
1114			);
1115		}
1116		allocator.blocks.push(Block {
1117			handle,
1118			size: block_size,
1119			memory_type_index: index,
1120			metadata,
1121			mapping: Mapping::default(),
1122		});
1123		allocator.add_native(block_size, index);
1124		return Ok(publish(
1125			allocator,
1126			Region {
1127				handle,
1128				index,
1129				size: request.size,
1130				reserved_size: reserved,
1131				offset: 0,
1132				block_size,
1133				placement: Some(placement),
1134			},
1135		));
1136	}
1137}
1138
1139/// Destroy a checked managed resource pair in buffer-before-memory order.
1140/// # Safety
1141/// Complete all host/GPU uses. Neither object may have been externally destroyed.
1142pub unsafe fn destroy_buffer(
1143	allocator: &mut Allocator<'_>,
1144	buffer: vk::Buffer,
1145	allocation: &mut Allocation,
1146) -> Result<(), AllocError> {
1147	let live = allocator.validate(allocation)?;
1148	if live.buffer != Some(buffer) {
1149		return Err(AllocError::WrongResource);
1150	}
1151	if live.mapped {
1152		return Err(AllocError::AlreadyMapped);
1153	}
1154	// Both transitions must be possible before any native destruction occurs.
1155	require(
1156		allocator.device.fp().destroy_buffer.is_some(),
1157		"vkDestroyBuffer",
1158	)?;
1159	require(allocator.device.fp().free_memory.is_some(), "vkFreeMemory")?;
1160	// SAFETY: association/state validated; caller guarantees exact completion.
1161	unsafe {
1162		allocator.device.destroy_buffer(buffer, None)?;
1163		free(allocator, allocation)
1164	}
1165}
1166
1167#[cfg(test)]
1168#[path = "../../../test/rs/memory/test_lifecycle.rs"]
1169mod tests;