Skip to main content

vk/mem/
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.txt.
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, AllocationInfo, AllocationPolicy,
10	AllocatorStats, MemoryPriority, MemoryTypeSelector,
11	block::{Block, BlockDomain, MappingOwner, MappingRead, MappingWrite},
12	granularity::{Granularity, ResourceClass},
13	host::{HostMap, HostMemory, HostString, HostVec, Identity},
14	metadata::{AllocationAlgorithm, Metadata, Placement},
15};
16use crate::{self as vk, Device, load::CallError};
17
18#[derive(Clone, Copy, PartialEq, Eq)]
19pub(in crate::mem) enum Resource {
20	Buffer(vk::Buffer),
21	Image(vk::Image),
22}
23
24impl Resource {
25	fn is_null(self) -> bool {
26		match self {
27			Self::Buffer(buffer) => buffer.is_null(),
28			Self::Image(image) => image.is_null(),
29		}
30	}
31}
32
33impl From<vk::Buffer> for Resource {
34	fn from(buffer: vk::Buffer) -> Self {
35		Self::Buffer(buffer)
36	}
37}
38
39impl From<vk::Image> for Resource {
40	fn from(image: vk::Image) -> Self {
41		Self::Image(image)
42	}
43}
44
45#[path = "synchronized/native.rs"]
46pub(in crate::mem) mod native;
47
48fn import_resource(resource: Option<super::ImportResource>) -> Option<Resource> {
49	resource.map(|resource| match resource {
50		super::ImportResource::Buffer(buffer) => Resource::Buffer(buffer),
51		super::ImportResource::Image(image) => Resource::Image(image),
52	})
53}
54
55struct Live<'callbacks> {
56	serial: u64,
57	pool: Option<u64>,
58	handle: vk::DeviceMemory,
59	size: u64,
60	reserved_size: u64,
61	// Native extent containing this region; never changes after publication.
62	block_size: u64,
63	memory_type_index: u32,
64	offset: u64,
65	alignment: u64,
66	map_count: u32,
67	persistent: bool,
68	resource: Option<Resource>,
69	bound: bool,
70	name: Option<HostString<'callbacks>>,
71	user_data: usize,
72	pinned: bool,
73	can_alias: bool,
74	imported: bool,
75	plane: Option<vk::ImageAspectFlagBits>,
76	export_types: vk::ExternalMemoryHandleTypeFlags,
77	guard_size: u64,
78	guard_offset: u64,
79	debug_ready: bool,
80	class: ResourceClass,
81	placement: Option<Placement>,
82	mapping: MappingOwner<'callbacks>,
83	native_users: u32,
84	pending_binding: Option<(Resource, Option<vk::ImageAspectFlagBits>)>,
85}
86
87#[derive(Clone, Copy)]
88enum RequirementsRoute {
89	Legacy,
90	Core,
91	Khr,
92}
93
94/// Device retention for an [`Allocator`]: a caller borrow or an owned shared
95/// reference. The owned form lets the allocator outlive its construction
96/// scope and travel to a dedicated cleanup thread; it never creates a second
97/// device identity.
98#[derive(Clone)]
99pub enum DeviceRetention<'device> {
100	/// Borrowed device; the allocator cannot outlive the borrow.
101	Borrowed(&'device Device),
102	/// Owned shared device; the allocator may outlive its construction scope.
103	Owned(std::sync::Arc<Device>),
104}
105
106impl DeviceRetention<'_> {
107	/// Access the retained device context.
108	pub fn as_device(&self) -> &Device {
109		match self {
110			Self::Borrowed(device) => device,
111			Self::Owned(device) => device,
112		}
113	}
114}
115
116impl std::ops::Deref for DeviceRetention<'_> {
117	type Target = Device;
118
119	fn deref(&self) -> &Device {
120		self.as_device()
121	}
122}
123
124/// Borrows one device; native memory is released only by explicit operations.
125///
126/// Each allocation has an allocator identity and generation. Dropping either
127/// the allocator or a token can leak native objects; it never calls Vulkan.
128pub struct Allocator<'device> {
129	host: HostMemory<'device>,
130	device: DeviceRetention<'device>,
131	callbacks: Option<&'device vk::AllocationCallbacks>,
132	device_memory_callbacks: Option<&'device super::DeviceMemoryCallbacks>,
133	physical_device: Option<vk::PhysicalDevice>,
134	instance_handle: Option<vk::InstanceHandle>,
135	physical_properties: Option<vk::PhysicalDeviceProperties>,
136	selector: MemoryTypeSelector,
137	owner: Identity,
138	next_serial: u64,
139	live: HostMap<'device, Live<'device>>,
140	// One key per native backing, independent of the number of placed regions.
141	native_handles: HostMap<'device, ()>,
142	associated: binding::Associations<'device>,
143	pools: HostMap<'device, pool::PoolState<'device>>,
144	blocks: HostVec<'device, Block<'device>>,
145	native_count: usize,
146	native_bytes: u64,
147	heap_bytes: [u64; 16],
148	heap_limits: [Option<u64>; 16],
149	allocation_limit: u32,
150	allocation_size_limit: Option<u64>,
151	integrated: Option<bool>,
152	buffer_device_address: bool,
153	memory_priority: bool,
154	protected_memory: bool,
155	budget_query: Option<budget::BudgetQuery>,
156	budget_cache: Option<super::BudgetSnapshot>,
157	budget_operations: u32,
158	current_frame_index: u32,
159	debug_settings: super::DebugMemorySettings,
160	external_types: [vk::ExternalMemoryHandleTypeFlags; 32],
161	preferred_large_heap_block_size: u64,
162	mapping_hysteresis: bool,
163	#[cfg(all(windows, feature = "platform-win32"))]
164	external_handles: HostVec<'device, external::CachedHandle>,
165	granularity: Granularity,
166	closed: bool,
167	parallel: Option<std::ptr::NonNull<std::ffi::c_void>>,
168	partition: parallel::Domain,
169	partition_id: u64,
170}
171
172#[path = "synchronized/scope.rs"]
173mod parallel;
174pub(super) use parallel::{Selection, with_domains};
175
176#[path = "external.rs"]
177mod external;
178#[cfg(unix)]
179pub use external::get_memory_fd;
180#[cfg(all(windows, feature = "platform-win32"))]
181pub use external::get_memory_win32_handle;
182pub use external::{external_memory_types, set_external_memory_types};
183
184#[path = "guard.rs"]
185mod guard;
186pub use guard::{check_corruption, check_pool_corruption, set_debug_memory_settings};
187
188#[path = "defragmentation.rs"]
189mod defragmentation;
190pub use defragmentation::{
191	DefragmentationAlgorithm, DefragmentationContext, DefragmentationInfo, DefragmentationMove,
192	DefragmentationPass, DefragmentationResource, DefragmentationStats, begin_defragmentation,
193	begin_defragmentation_with_break, set_allocation_pinned,
194};
195
196#[path = "services.rs"]
197mod services;
198pub use services::{
199	CacheRange, DetailedStatistics, MemoryStatistics, allocate_many, allocation_name, allocator_info,
200	copy_from_allocation, copy_to_allocation, create_aliasing_buffer, create_aliasing_image,
201	detailed_pool_statistics, detailed_statistics, flush_many, free_many, invalidate_many,
202	memory_properties, memory_type_properties, physical_device_properties, pool_name,
203	set_allocation_name, set_allocation_user_data, set_pool_name, write_statistics_json,
204};
205
206#[path = "request.rs"]
207mod request;
208pub use request::{
209	allocate_dedicated, allocate_with_options, create_dedicated_buffer, create_dedicated_image,
210	find_memory_type, find_memory_type_for_buffer_info, find_memory_type_for_image_info,
211};
212
213#[path = "binding.rs"]
214mod binding;
215pub use binding::{
216	allocate_for_buffer, allocate_for_image, bind_buffer_memory, bind_buffer_memory2,
217	bind_image_memory, bind_image_memory2,
218};
219
220#[path = "budget.rs"]
221mod budget;
222pub use budget::{
223	TrimStats, budget_snapshot, heap_budgets, query_budget, refresh_budget,
224	refresh_budget_with_context, set_current_frame_index, set_heap_limit, trim, trim_with_budget,
225};
226
227#[path = "pool.rs"]
228mod pool;
229pub use pool::{
230	Pool, PoolCreateInfo, PoolStats, create_pool, create_pool_with_chain, destroy_pool,
231	pool_statistics, set_mapping_hysteresis, set_preferred_block_size,
232};
233
234impl<'device> Allocator<'device> {
235	/// Construct from caller-supplied physical-device metadata.
236	///
237	/// Prefer [`Self::new_from_physical_device`] for queried properties and the
238	/// local native-allocation count limit. Managed buffers require the device's
239	/// owned creation capabilities; raw imports without them support only the
240	/// explicitly unsafe low-level allocation route.
241	/// Without a buffer/image granularity limit, blocks conservatively exclude
242	/// conflicting image tilings. Use [`Self::new_with_granularity`] to supply it.
243	///
244	/// # Safety
245	/// Properties and atom size must belong to this device's physical device.
246	/// Caller must enforce device allocation size/count limits and enabled features.
247	pub unsafe fn new(
248		device: &'device Device,
249		properties: vk::PhysicalDeviceMemoryProperties,
250		atom: u64,
251	) -> Result<Self, AllocError> {
252		Self::build(DeviceRetention::Borrowed(device), properties, atom)
253	}
254
255	/// Construct retaining one shared device context.
256	///
257	/// The allocator owns an `Arc` clone of the device, so it may outlive its
258	/// construction scope and be adopted or destroyed on another thread.
259	/// Follows [`Self::new`]; callback forms stay borrowed-only.
260	///
261	/// # Safety
262	/// Properties and atom size must belong to this device's physical device.
263	/// Caller must enforce device allocation size/count limits and enabled features.
264	pub unsafe fn new_retained(
265		device: std::sync::Arc<Device>,
266		properties: vk::PhysicalDeviceMemoryProperties,
267		atom: u64,
268	) -> Result<Allocator<'static>, AllocError> {
269		Self::build(DeviceRetention::Owned(device), properties, atom)
270	}
271
272	fn build<'a>(
273		device: DeviceRetention<'a>,
274		properties: vk::PhysicalDeviceMemoryProperties,
275		atom: u64,
276	) -> Result<Allocator<'a>, AllocError> {
277		let mut selector = MemoryTypeSelector::new(properties, atom)?;
278		selector.device_coherent = super::resource::device_coherent_memory(device.as_device());
279		require(device.fp().allocate_memory.is_some(), "vkAllocateMemory")?;
280		require(device.fp().free_memory.is_some(), "vkFreeMemory")?;
281		let buffer_device_address = super::resource::buffer_device_address(device.as_device());
282		let memory_priority = super::resource::memory_priority(device.as_device());
283		let protected_memory = super::resource::protected_memory(device.as_device());
284		Ok(Allocator {
285			host: HostMemory::default(),
286			device,
287			callbacks: None,
288			device_memory_callbacks: None,
289			physical_device: None,
290			instance_handle: None,
291			physical_properties: None,
292			selector,
293			owner: Identity::new()?,
294			next_serial: 1,
295			live: HostMap::new(HostMemory::default()),
296			native_handles: HostMap::new(HostMemory::default()),
297			associated: binding::Associations::new(HostMemory::default()),
298			pools: HostMap::new(HostMemory::default()),
299			blocks: HostVec::new(HostMemory::default()),
300			native_count: 0,
301			native_bytes: 0,
302			heap_bytes: [0; 16],
303			heap_limits: [None; 16],
304			allocation_limit: u32::MAX,
305			allocation_size_limit: None,
306			integrated: None,
307			buffer_device_address,
308			memory_priority,
309			protected_memory,
310			budget_query: None,
311			budget_cache: None,
312			budget_operations: 0,
313			current_frame_index: 0,
314			debug_settings: super::DebugMemorySettings::new(),
315			external_types: [vk::ExternalMemoryHandleTypeFlags::empty(); 32],
316			preferred_large_heap_block_size: 256 * 1024 * 1024,
317			mapping_hysteresis: true,
318			#[cfg(all(windows, feature = "platform-win32"))]
319			external_handles: HostVec::new(HostMemory::default()),
320			granularity: Granularity(None),
321			closed: false,
322			parallel: None,
323			partition: parallel::Domain::Global,
324			partition_id: 0,
325		})
326	}
327
328	/// Construct from memory properties and both physical-device alignment limits.
329	///
330	/// Enables mixed buffer/linear/optimal image blocks with checked granularity
331	/// page separation. Otherwise follows [`Self::new`].
332	/// # Errors
333	/// Rejects invalid memory properties, non-power-of-two limits or missing PFNs.
334	/// # Safety
335	/// Properties, `atom` (`nonCoherentAtomSize`) and `granularity`
336	/// (`bufferImageGranularity`) must describe this device's physical device.
337	/// The caller enforces native size/count limits, enabled features and lifetimes.
338	pub unsafe fn new_with_granularity(
339		device: &'device Device,
340		properties: vk::PhysicalDeviceMemoryProperties,
341		atom: u64,
342		granularity: u64,
343	) -> Result<Self, AllocError> {
344		if !granularity.is_power_of_two() {
345			return Err(AllocError::InvalidProperties(
346				"buffer/image granularity must be a nonzero power of two",
347			));
348		}
349		// SAFETY: identical supplied metadata and native lifetime contract.
350		let mut allocator = unsafe { Self::new(device, properties, atom) }?;
351		allocator.granularity = Granularity(Some(granularity));
352		Ok(allocator)
353	}
354
355	/// [`Self::new_with_granularity`] retaining one shared device context.
356	/// # Safety
357	/// Same metadata and native lifetime contract as `new_with_granularity`.
358	pub unsafe fn new_retained_with_granularity(
359		device: std::sync::Arc<Device>,
360		properties: vk::PhysicalDeviceMemoryProperties,
361		atom: u64,
362		granularity: u64,
363	) -> Result<Allocator<'static>, AllocError> {
364		if !granularity.is_power_of_two() {
365			return Err(AllocError::InvalidProperties(
366				"buffer/image granularity must be a nonzero power of two",
367			));
368		}
369		// SAFETY: identical supplied metadata and native lifetime contract.
370		let mut allocator = unsafe { Self::new_retained(device, properties, atom) }?;
371		allocator.granularity = Granularity(Some(granularity));
372		Ok(allocator)
373	}
374
375	/// Query properties and limits for the selected physical device.
376	///
377	/// Requires owned instance/device creation metadata and a compatible parent
378	/// snapshot. It does not discover a logical device's physical-device identity.
379	/// Queries the maximum allocation size when core 1.1 or enabled maintenance3
380	/// provides it. Legacy 1.0 leaves this limit unknown; see [`Self::max_allocation_size`].
381	/// The local count limit excludes allocations made by other allocators/raw calls.
382	///
383	/// # Safety
384	/// `physical_device` must belong to `instance` and be the physical device used
385	/// to create `device`. All contexts must remain live; native ownership and
386	/// host synchronization remain the caller's responsibility. Unqueried size
387	/// limits and resource valid usage must still be observed by the caller.
388	pub unsafe fn new_from_physical_device(
389		instance: &vk::Instance,
390		physical_device: vk::PhysicalDevice,
391		device: &'device Device,
392	) -> Result<Self, AllocError> {
393		let parent = instance
394			.capabilities()
395			.ok_or(AllocError::UnsupportedCapability(
396				"instance creation metadata is required",
397			))?;
398		let capabilities = device
399			.capabilities()
400			.ok_or(AllocError::UnsupportedCapability(
401				"device creation metadata is required",
402			))?;
403		if !capabilities.matches_instance(parent) {
404			return Err(AllocError::InvalidProperties(
405				"device/instance metadata disagree",
406			));
407		}
408		if physical_device.is_null() {
409			return Err(AllocError::InvalidProperties("null physical device"));
410		}
411		// SAFETY: caller guarantees the selected physical device and parent lifetime.
412		let properties = unsafe { instance.get_physical_device_properties(physical_device) }?;
413		// These are property-query availability gates, not feature enablement.
414		// Use conservative owned provenance; never infer command legality from a PFN.
415		let maintenance3 = capabilities
416			.device_extensions()
417			.is_some_and(|mut extensions| extensions.any(|name| name == c"VK_KHR_maintenance3"));
418		let allocation_size_limit = if capabilities.api_version() >= vk::API_VERSION_1_1 || maintenance3
419		{
420			let mut limits = vk::PhysicalDeviceMaintenance3Properties::new();
421			let mut output = vk::PhysicalDeviceProperties2 {
422				p_next: std::ptr::from_mut(&mut limits).cast(),
423				..Default::default()
424			};
425			if parent.instance_api_version() >= vk::API_VERSION_1_1 {
426				// SAFETY: admitted core query, matching physical device and initialized chain.
427				unsafe { instance.get_physical_device_properties2(physical_device, &mut output) }?;
428			} else if parent
429				.instance_extensions()
430				.any(|name| name == c"VK_KHR_get_physical_device_properties2")
431			{
432				// SAFETY: exact KHR query is admitted by the enabled instance extension;
433				// the device enabled maintenance3, which supplies this property record.
434				unsafe { instance.get_physical_device_properties2_khr(physical_device, &mut output) }?;
435			} else {
436				return Err(AllocError::UnsupportedCapability(
437					"maintenance3 requires properties2",
438				));
439			}
440			if limits.max_memory_allocation_size == 0 {
441				return Err(AllocError::InvalidProperties(
442					"zero native allocation-size limit",
443				));
444			}
445			Some(limits.max_memory_allocation_size)
446		} else {
447			None
448		};
449		// SAFETY: same live physical device.
450		let memory = unsafe { instance.get_physical_device_memory_properties(physical_device) }?;
451		if properties.limits.max_memory_allocation_count == 0 {
452			return Err(AllocError::InvalidProperties(
453				"zero native allocation limit",
454			));
455		}
456		// SAFETY: metadata was queried from the caller's matching physical device.
457		let mut allocator = unsafe {
458			Self::new_with_granularity(
459				device,
460				memory,
461				properties.limits.non_coherent_atom_size,
462				properties.limits.buffer_image_granularity,
463			)
464		}?;
465		allocator.integrated = Some(properties.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU);
466		allocator.physical_device = Some(physical_device);
467		allocator.budget_query = budget::capture_query(instance, physical_device);
468		allocator.instance_handle = Some(instance.handle());
469		allocator.physical_properties = Some(properties);
470		allocator.allocation_limit = properties.limits.max_memory_allocation_count;
471		allocator.allocation_size_limit = allocation_size_limit;
472		// Select/validate the exact admitted requirements command before publication.
473		allocator.buffer_route()?;
474		Ok(allocator)
475	}
476
477	/// [`Self::new_from_physical_device`] retaining one shared device context.
478	///
479	/// # Safety
480	/// `physical_device` must belong to `instance` and be the physical device used
481	/// to create `device`. Native ownership and host synchronization remain the
482	/// caller's responsibility.
483	pub unsafe fn new_retained_from_physical_device(
484		instance: &vk::Instance,
485		physical_device: vk::PhysicalDevice,
486		device: std::sync::Arc<Device>,
487	) -> Result<Allocator<'static>, AllocError> {
488		let parent = instance
489			.capabilities()
490			.ok_or(AllocError::UnsupportedCapability(
491				"instance creation metadata is required",
492			))?;
493		let capabilities = device
494			.capabilities()
495			.ok_or(AllocError::UnsupportedCapability(
496				"device creation metadata is required",
497			))?;
498		if !capabilities.matches_instance(parent) {
499			return Err(AllocError::InvalidProperties(
500				"device/instance metadata disagree",
501			));
502		}
503		if physical_device.is_null() {
504			return Err(AllocError::InvalidProperties("null physical device"));
505		}
506		// SAFETY: caller guarantees the selected physical device and parent lifetime.
507		let properties = unsafe { instance.get_physical_device_properties(physical_device) }?;
508		let maintenance3 = capabilities
509			.device_extensions()
510			.is_some_and(|mut extensions| extensions.any(|name| name == c"VK_KHR_maintenance3"));
511		let allocation_size_limit = if capabilities.api_version() >= vk::API_VERSION_1_1 || maintenance3
512		{
513			let mut limits = vk::PhysicalDeviceMaintenance3Properties::new();
514			let mut output = vk::PhysicalDeviceProperties2 {
515				p_next: std::ptr::from_mut(&mut limits).cast(),
516				..Default::default()
517			};
518			if parent.instance_api_version() >= vk::API_VERSION_1_1 {
519				// SAFETY: admitted core query, matching physical device and initialized chain.
520				unsafe { instance.get_physical_device_properties2(physical_device, &mut output) }?;
521			} else if parent
522				.instance_extensions()
523				.any(|name| name == c"VK_KHR_get_physical_device_properties2")
524			{
525				// SAFETY: exact KHR query is admitted by the enabled instance extension;
526				// the device enabled maintenance3, which supplies this property record.
527				unsafe { instance.get_physical_device_properties2_khr(physical_device, &mut output) }?;
528			} else {
529				return Err(AllocError::UnsupportedCapability(
530					"maintenance3 requires properties2",
531				));
532			}
533			if limits.max_memory_allocation_size == 0 {
534				return Err(AllocError::InvalidProperties(
535					"zero native allocation-size limit",
536				));
537			}
538			Some(limits.max_memory_allocation_size)
539		} else {
540			None
541		};
542		// SAFETY: same live physical device.
543		let memory = unsafe { instance.get_physical_device_memory_properties(physical_device) }?;
544		if properties.limits.max_memory_allocation_count == 0 {
545			return Err(AllocError::InvalidProperties(
546				"zero native allocation limit",
547			));
548		}
549		// SAFETY: metadata was queried from the caller's matching physical device.
550		let mut allocator = unsafe {
551			Self::new_retained_with_granularity(
552				device,
553				memory,
554				properties.limits.non_coherent_atom_size,
555				properties.limits.buffer_image_granularity,
556			)
557		}?;
558		allocator.integrated = Some(properties.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU);
559		allocator.physical_device = Some(physical_device);
560		allocator.budget_query = budget::capture_query(instance, physical_device);
561		allocator.instance_handle = Some(instance.handle());
562		allocator.physical_properties = Some(properties);
563		allocator.allocation_limit = properties.limits.max_memory_allocation_count;
564		allocator.allocation_size_limit = allocation_size_limit;
565		// Select/validate the exact admitted requirements command before publication.
566		allocator.buffer_route()?;
567		Ok(allocator)
568	}
569
570	/// Construct with borrowed callbacks for native effects and CPU metadata.
571	/// Callback storage is borrowed for the allocator's complete borrow lifetime.
572	/// # Errors
573	/// Same metadata/PFN errors as `new`; rejects missing host allocation/free functions.
574	/// # Safety
575	/// All `new` requirements apply. Callbacks/user data must remain valid and
576	/// satisfy Vulkan alignment, thread, reentrancy and non-unwinding contracts.
577	/// They are used consistently for native allocation, creation and rollback;
578	/// CPU metadata uses the allocation/free pair with OBJECT scope. The inline
579	/// allocator and non-owning tokens need no heap identity. Returned Vec output
580	/// collections and caller-owned writers retain their Rust allocation policy.
581	/// Callback free may run while host metadata is dropped; it never frees
582	/// native Vulkan objects implicitly.
583	pub unsafe fn new_with_callbacks(
584		device: &'device Device,
585		properties: vk::PhysicalDeviceMemoryProperties,
586		atom: u64,
587		callbacks: &'device vk::AllocationCallbacks,
588	) -> Result<Self, AllocError> {
589		// SAFETY: caller supplies the canonical native metadata contract.
590		let mut allocator = unsafe { Self::new(device, properties, atom) }?;
591		// SAFETY: fresh empty allocator; caller retains complete callback validity.
592		unsafe { allocator.initialize_callbacks(callbacks) }?;
593		Ok(allocator)
594	}
595
596	/// Query limits and retain matching caller-owned native allocation callbacks.
597	/// # Errors
598	/// Same provenance/property/PFN errors as `new_from_physical_device`.
599	/// # Safety
600	/// Match the physical-device constructor's identity/lifetime contract and the
601	/// callback lifetime/alignment/thread/reentrancy contract of `new_with_callbacks`.
602	pub unsafe fn new_from_physical_device_with_callbacks(
603		instance: &vk::Instance,
604		physical_device: vk::PhysicalDevice,
605		device: &'device Device,
606		callbacks: &'device vk::AllocationCallbacks,
607	) -> Result<Self, AllocError> {
608		// SAFETY: same native identity and queried-property contract; no storage is allocated yet.
609		let mut allocator =
610			unsafe { Self::new_from_physical_device(instance, physical_device, device) }?;
611		// SAFETY: fresh queried allocator; caller retains complete callback validity.
612		unsafe { allocator.initialize_callbacks(callbacks) }?;
613		Ok(allocator)
614	}
615
616	unsafe fn initialize_callbacks(
617		&mut self,
618		callbacks: &'device vk::AllocationCallbacks,
619	) -> Result<(), AllocError> {
620		debug_assert!(self.live.is_empty() && self.pools.is_empty() && self.blocks.is_empty());
621		// SAFETY: same borrowed caller callback/thread contract, now used for
622		// every allocator-owned host buffer as well as native effects.
623		let host = unsafe { HostMemory::new_from_callbacks(callbacks) }?;
624		self.host = host;
625		self.live = HostMap::new(host);
626		self.native_handles = HostMap::new(host);
627		self.associated = binding::Associations::new(host);
628		self.pools = HostMap::new(host);
629		self.blocks = HostVec::new(host);
630		#[cfg(all(windows, feature = "platform-win32"))]
631		{
632			self.external_handles = HostVec::new(host);
633		}
634		self.callbacks = Some(callbacks);
635		Ok(())
636	}
637
638	pub fn live_count(&self) -> usize {
639		self.live.len()
640	}
641
642	pub fn is_closed(&self) -> bool {
643		self.closed
644	}
645
646	/// Queried native per-allocation size ceiling, or unknown on the caller-supplied
647	/// metadata route and legacy 1.0 without admitted maintenance3 properties.
648	/// A known ceiling is not a promise of available heap space or allocation success.
649	pub fn max_allocation_size(&self) -> Option<u64> {
650		self.allocation_size_limit
651	}
652
653	/// Close only when all allocations have been explicitly released; repeat close succeeds.
654	pub fn close(&mut self) -> Result<(), AllocError> {
655		close(self)
656	}
657
658	/// Allocate separate native storage without a resource-specific allocation chain.
659	/// # Safety
660	/// Request must satisfy resource requirements and device size/count limits.
661	/// External and required-dedication semantics are excluded. Protected storage
662	/// requires captured enabled feature metadata and explicit required flags. Owned
663	/// core/KHR device-address enablement automatically flags raw storage.
664	pub unsafe fn allocate(&mut self, request: &AllocRequest) -> Result<Allocation, AllocError> {
665		// SAFETY: caller supplies the canonical operation's native guarantees.
666		unsafe { allocate(self, request) }
667	}
668
669	/// # Safety
670	/// Destroy every bound resource and complete all GPU/host accesses first.
671	pub unsafe fn free(&mut self, allocation: &mut Allocation) -> Result<(), AllocError> {
672		// SAFETY: caller supplies the canonical operation's native guarantees.
673		unsafe { free(self, allocation) }
674	}
675
676	/// Acquire one explicit mapping hold; every successful call needs one unmap.
677	/// # Safety
678	/// Synchronize host/GPU access; returned pointers cannot outlive the mapping.
679	pub unsafe fn map(
680		&mut self,
681		allocation: &Allocation,
682	) -> Result<*mut std::ffi::c_void, AllocError> {
683		// SAFETY: caller supplies the canonical operation's native guarantees.
684		unsafe { map(self, allocation) }
685	}
686
687	/// # Safety
688	/// No pointer into this mapping may be used after unmapping.
689	pub unsafe fn unmap(&mut self, allocation: &Allocation) -> Result<(), AllocError> {
690		// SAFETY: caller supplies the canonical operation's native guarantees.
691		unsafe { unmap(self, allocation) }
692	}
693
694	/// # Safety
695	/// Mapping and accesses must satisfy Vulkan host/device synchronization rules.
696	pub unsafe fn flush(
697		&self,
698		allocation: &Allocation,
699		offset: u64,
700		size: u64,
701	) -> Result<(), AllocError> {
702		// SAFETY: caller supplies the canonical operation's native guarantees.
703		unsafe { flush(self, allocation, offset, size) }
704	}
705
706	/// # Safety
707	/// Complete device writes before invalidating and do not race host accesses.
708	pub unsafe fn invalidate(
709		&self,
710		allocation: &Allocation,
711		offset: u64,
712		size: u64,
713	) -> Result<(), AllocError> {
714		// SAFETY: caller supplies the canonical operation's native guarantees.
715		unsafe { invalidate(self, allocation, offset, size) }
716	}
717
718	/// Create, allocate and bind an ordinary buffer transactionally.
719	/// # Safety
720	/// Info must satisfy device limits/valid usage. Sparse, external
721	/// and arbitrary pNext chains are not admitted. Protected resources need captured
722	/// enabled feature metadata and matching creation flags. Device-address usage requires
723	/// captured enabled core/KHR feature metadata.
724	pub unsafe fn create_buffer(
725		&mut self,
726		info: &vk::BufferCreateInfo,
727		required: vk::MemoryPropertyFlags,
728		preferred: vk::MemoryPropertyFlags,
729	) -> Result<(vk::Buffer, Allocation), AllocError> {
730		// SAFETY: caller supplies the canonical operation's native guarantees.
731		unsafe { create_buffer(self, info, required, preferred) }
732	}
733
734	/// Destroy a matching managed buffer before freeing its allocation.
735	/// # Safety
736	/// Complete every CPU/GPU use; do not separately destroy this buffer first.
737	/// The buffer must use this allocator's callbacks; otherwise destroy it directly then free.
738	pub unsafe fn destroy_buffer(
739		&mut self,
740		buffer: vk::Buffer,
741		allocation: &mut Allocation,
742	) -> Result<(), AllocError> {
743		// SAFETY: caller supplies the canonical operation's native guarantees.
744		unsafe { destroy_buffer(self, buffer, allocation) }
745	}
746
747	/// Create and bind an ordinary buffer using explicit separate/pooled policy.
748	/// # Safety
749	/// Same native valid-usage, completion and synchronization obligations as `create_buffer`.
750	pub unsafe fn create_buffer_with_options(
751		&mut self,
752		info: &vk::BufferCreateInfo,
753		options: &AllocationCreateInfo,
754	) -> Result<(vk::Buffer, Allocation), AllocError> {
755		// SAFETY: caller supplies the canonical operation's guarantees.
756		unsafe { create_buffer_with_options(self, info, options) }
757	}
758
759	/// Create and bind an image using separate storage and explicit memory properties.
760	/// # Safety
761	/// All image/create-chain valid usage and host/GPU lifetimes remain caller obligations.
762	pub unsafe fn create_image(
763		&mut self,
764		info: &vk::ImageCreateInfo,
765		required: vk::MemoryPropertyFlags,
766		preferred: vk::MemoryPropertyFlags,
767	) -> Result<(vk::Image, Allocation), AllocError> {
768		// SAFETY: identical contract to the canonical operation.
769		unsafe { create_image(self, info, required, preferred) }
770	}
771
772	/// Create, allocate and bind an image using explicit or automatic memory policy.
773	/// # Safety
774	/// Match native image, feature, pointer-chain and synchronization valid usage.
775	pub unsafe fn create_image_with_options(
776		&mut self,
777		info: &vk::ImageCreateInfo,
778		options: &AllocationCreateInfo,
779	) -> Result<(vk::Image, Allocation), AllocError> {
780		// SAFETY: identical contract to the canonical operation.
781		unsafe { create_image_with_options(self, info, options) }
782	}
783
784	/// Destroy a managed image and release its allocation after exact completion.
785	/// # Safety
786	/// Destroy views first; complete all host/GPU uses and end explicit maps.
787	/// The image must use this allocator's callbacks; otherwise destroy it directly then free.
788	pub unsafe fn destroy_image(
789		&mut self,
790		image: vk::Image,
791		allocation: &mut Allocation,
792	) -> Result<(), AllocError> {
793		// SAFETY: identical contract to the canonical operation.
794		unsafe { destroy_image(self, image, allocation) }
795	}
796
797	/// Observe a current explicit or persistent mapping without acquiring a hold.
798	pub fn mapped_pointer(
799		&self,
800		allocation: &Allocation,
801	) -> Result<Option<*mut std::ffi::c_void>, AllocError> {
802		mapped_pointer(self, allocation)
803	}
804
805	/// Query checked binding metadata and this region's current mapping without native calls.
806	/// # Errors
807	/// Returns `Closed`, `WrongAllocator` or `UnknownAllocation` for invalid identity/state.
808	pub fn allocation_info(&self, allocation: &Allocation) -> Result<AllocationInfo, AllocError> {
809		allocation_info(self, allocation)
810	}
811
812	pub fn statistics(&self) -> AllocatorStats {
813		statistics(self)
814	}
815
816	fn ensure_open(&self) -> Result<(), AllocError> {
817		if self.closed {
818			Err(AllocError::Closed)
819		} else {
820			Ok(())
821		}
822	}
823
824	fn parallel_state(&self) -> Option<&parallel::Shared<'device>> {
825		// SAFETY: only with_pools installs this pointer in a privately owned child.
826		// The stable callback-owned context outlives every child, operation and
827		// unwind restoration; children cannot escape the borrowed scope.
828		self
829			.parallel
830			.map(|pointer| unsafe { pointer.cast::<parallel::Shared<'device>>().as_ref() })
831	}
832
833	fn total_native_count(&self) -> usize {
834		self
835			.parallel_state()
836			.map_or(self.native_count, parallel::Shared::native_count)
837	}
838
839	fn release_resource_claim(&self, allocation: &Allocation) {
840		if let Some(shared) = self.parallel_state() {
841			let live = &self.live[&allocation.serial];
842			shared.release_resource(live.resource, live.plane, allocation.serial);
843		}
844	}
845
846	fn ensure_allocation(&self) -> Result<(), AllocError> {
847		if matches!(self.partition, parallel::Domain::Block(_)) {
848			Err(AllocError::InvalidRequest(
849				"block scope cannot allocate, free or trim storage",
850			))
851		} else {
852			Ok(())
853		}
854	}
855
856	fn ensure_default(&self) -> Result<(), AllocError> {
857		self.ensure_allocation()?;
858		if matches!(self.partition, parallel::Domain::Pool(_)) {
859			Err(AllocError::InvalidRequest(
860				"pool scope requires its selected pool",
861			))
862		} else {
863			Ok(())
864		}
865	}
866
867	fn ensure_global(&self) -> Result<(), AllocError> {
868		if self.parallel.is_some() {
869			Err(AllocError::InvalidRequest(
870				"allocator-wide operation inside pool scope",
871			))
872		} else {
873			Ok(())
874		}
875	}
876
877	fn token(&self, serial: u64) -> Result<Allocation, AllocError> {
878		let live = self
879			.live
880			.get(&serial)
881			.ok_or(AllocError::UnknownAllocation)?;
882		let memory = self.selector.properties.memory_types[live.memory_type_index as usize];
883		let block_size = if live.placement.is_some() {
884			self
885				.blocks
886				.iter()
887				.find(|block| block.handle == live.handle)
888				.ok_or(AllocError::UnknownAllocation)?
889				.size
890		} else {
891			live.size
892		};
893		Ok(Allocation {
894			handle: live.handle,
895			size: live.size,
896			reserved_size: live.reserved_size,
897			offset: live.offset,
898			block_size,
899			memory_type_index: live.memory_type_index,
900			heap_index: memory.heap_index,
901			flags: memory.property_flags,
902			atom: self.selector.atom,
903			owner: self.owner,
904			serial,
905		})
906	}
907	/// Public token for a serial this allocator has just published.
908	#[inline]
909	fn published(&self, serial: u64) -> Allocation {
910		self.allocation_from_live(self.live.get(&serial).expect("published allocation"))
911	}
912	#[inline]
913	fn allocation_from_live(&self, live: &Live<'_>) -> Allocation {
914		let memory = self.selector.properties.memory_types[live.memory_type_index as usize];
915		Allocation {
916			handle: live.handle,
917			size: live.size,
918			reserved_size: live.reserved_size,
919			offset: live.offset,
920			block_size: live.block_size,
921			memory_type_index: live.memory_type_index,
922			heap_index: memory.heap_index,
923			flags: memory.property_flags,
924			atom: self.selector.atom,
925			owner: self.owner,
926			serial: live.serial,
927		}
928	}
929
930	fn validate(&self, allocation: &Allocation) -> Result<&Live<'device>, AllocError> {
931		self.ensure_open()?;
932		if self.owner != allocation.owner {
933			return Err(AllocError::WrongAllocator);
934		}
935		self
936			.live
937			.get(&allocation.serial)
938			.filter(|live| live.serial == allocation.serial && live.handle == allocation.handle)
939			.ok_or(AllocError::UnknownAllocation)
940	}
941
942	fn reserve(&mut self) -> Result<(), AllocError> {
943		self.ensure_open()?;
944		self.ensure_allocation()?;
945		if let Some(shared) = self.parallel_state() {
946			self.next_serial = shared.reserve_publication()?;
947		}
948		self
949			.next_serial
950			.checked_add(1)
951			.ok_or(AllocError::SerialExhausted)?;
952		self
953			.live
954			.try_reserve(1)
955			.map_err(|_| AllocError::HostAllocationFailed)
956	}
957
958	fn validate_size(&self, size: u64) -> Result<(), AllocError> {
959		if let Some(limit) = self.allocation_size_limit
960			&& size > limit
961		{
962			return Err(AllocError::AllocationTooLarge {
963				requested: size,
964				limit,
965			});
966		}
967		Ok(())
968	}
969
970	fn check_native(&self, size: u64, index: u32) -> Result<(), AllocError> {
971		self.validate_size(size)?;
972		if self.native_count >= self.allocation_limit as usize {
973			return Err(AllocError::AllocationLimit);
974		}
975		let heap = self.selector.properties.memory_types[index as usize].heap_index as usize;
976		let available = self.heap_limits[heap]
977			.unwrap_or(self.selector.properties.memory_heaps[heap].size)
978			- self.heap_bytes[heap];
979		if size > available {
980			return Err(AllocError::OutOfSpace);
981		}
982		self
983			.native_bytes
984			.checked_add(size)
985			.ok_or(AllocError::InvalidRange)?;
986		Ok(())
987	}
988
989	fn add_native(&mut self, size: u64, index: u32, memory: vk::DeviceMemory) {
990		self.native_handles.insert_reserved(memory.as_raw(), ());
991		let heap = self.selector.properties.memory_types[index as usize].heap_index as usize;
992		self.native_count += 1;
993		self.native_bytes += size;
994		self.heap_bytes[heap] += size;
995		if let Some(callbacks) = self.device_memory_callbacks
996			&& let Some(callback) = callbacks.allocated
997		{
998			// SAFETY: callback registration establishes live user data, no unwinding/reentrancy and permitted thread access.
999			unsafe {
1000				callback(
1001					self.device.handle(),
1002					memory,
1003					size,
1004					index,
1005					callbacks.p_user_data,
1006				)
1007			};
1008		}
1009	}
1010
1011	fn remove_native(&mut self, size: u64, index: u32, memory: vk::DeviceMemory) {
1012		self.native_handles.remove(&memory.as_raw());
1013		#[cfg(all(windows, feature = "platform-win32"))]
1014		{
1015			let before = self.external_handles.len();
1016			self.external_handles.retain(|entry| entry.memory != memory);
1017			if let Some(shared) = self.parallel_state() {
1018				shared.external_removed(before - self.external_handles.len());
1019			}
1020		}
1021		let heap = self.selector.properties.memory_types[index as usize].heap_index as usize;
1022		if let Some(shared) = self.parallel_state() {
1023			shared.release_native(size, heap);
1024		}
1025		self.native_count -= 1;
1026		self.native_bytes -= size;
1027		self.heap_bytes[heap] -= size;
1028		if let Some(callbacks) = self.device_memory_callbacks
1029			&& let Some(callback) = callbacks.freed
1030		{
1031			// SAFETY: retained registration remains valid through this actual native free notification.
1032			unsafe {
1033				callback(
1034					self.device.handle(),
1035					memory,
1036					size,
1037					index,
1038					callbacks.p_user_data,
1039				)
1040			};
1041		}
1042	}
1043
1044	fn mapping_owner(&self, allocation: &Allocation) -> &MappingOwner<'device> {
1045		if self.live[&allocation.serial].placement.is_some() {
1046			&self
1047				.blocks
1048				.iter()
1049				.find(|block| block.handle == allocation.handle)
1050				.expect("live block")
1051				.mapping
1052		} else {
1053			&self.live[&allocation.serial].mapping
1054		}
1055	}
1056
1057	fn mapping_owner_mut(&mut self, allocation: &Allocation) -> &mut MappingOwner<'device> {
1058		if self.live[&allocation.serial].placement.is_some() {
1059			&mut self
1060				.blocks
1061				.iter_mut()
1062				.find(|block| block.handle == allocation.handle)
1063				.expect("live block")
1064				.mapping
1065		} else {
1066			&mut self
1067				.live
1068				.get_mut(&allocation.serial)
1069				.expect("validated live region")
1070				.mapping
1071		}
1072	}
1073
1074	fn mapping(&self, allocation: &Allocation) -> MappingRead<'_> {
1075		self.mapping_owner(allocation).read()
1076	}
1077
1078	fn mapping_mut(&mut self, allocation: &Allocation) -> MappingWrite<'_> {
1079		self.mapping_owner_mut(allocation).write()
1080	}
1081
1082	fn contains_memory(&self, memory: vk::DeviceMemory) -> bool {
1083		self.native_handles.get(&memory.as_raw()).is_some()
1084	}
1085
1086	fn buffer_route(&self) -> Result<RequirementsRoute, AllocError> {
1087		self.requirements_route(false)
1088	}
1089
1090	#[inline]
1091	fn requirements_route(&self, image: bool) -> Result<RequirementsRoute, AllocError> {
1092		let capabilities =
1093			self
1094				.device
1095				.as_device()
1096				.capabilities()
1097				.ok_or(AllocError::UnsupportedCapability(
1098					"managed resources require device creation metadata",
1099				))?;
1100		if capabilities.api_version() < vk::API_VERSION_1_1 {
1101			return self.extension_requirements_route(image);
1102		}
1103		let fp = self.device.fp();
1104		if image {
1105			require(
1106				fp.get_image_memory_requirements2.is_some(),
1107				"vkGetImageMemoryRequirements2",
1108			)?;
1109		} else {
1110			require(
1111				fp.get_buffer_memory_requirements2.is_some(),
1112				"vkGetBufferMemoryRequirements2",
1113			)?;
1114		}
1115		Ok(RequirementsRoute::Core)
1116	}
1117	// Pre-1.1 devices select KHR dedication or the legacy query once per request.
1118	#[cold]
1119	fn extension_requirements_route(&self, image: bool) -> Result<RequirementsRoute, AllocError> {
1120		let fp = self.device.fp();
1121		let (khr, legacy) = if image {
1122			(
1123				(
1124					fp.get_image_memory_requirements2_khr.is_some(),
1125					"vkGetImageMemoryRequirements2KHR",
1126				),
1127				(
1128					fp.get_image_memory_requirements.is_some(),
1129					"vkGetImageMemoryRequirements",
1130				),
1131			)
1132		} else {
1133			(
1134				(
1135					fp.get_buffer_memory_requirements2_khr.is_some(),
1136					"vkGetBufferMemoryRequirements2KHR",
1137				),
1138				(
1139					fp.get_buffer_memory_requirements.is_some(),
1140					"vkGetBufferMemoryRequirements",
1141				),
1142			)
1143		};
1144		if super::resource::extension(self.device.as_device(), c"VK_KHR_dedicated_allocation") {
1145			if !super::resource::extension(self.device.as_device(), c"VK_KHR_get_memory_requirements2") {
1146				return Err(AllocError::UnsupportedCapability(
1147					"dedication requires VK_KHR_get_memory_requirements2",
1148				));
1149			}
1150			require(khr.0, khr.1)?;
1151			Ok(RequirementsRoute::Khr)
1152		} else {
1153			require(legacy.0, legacy.1)?;
1154			Ok(RequirementsRoute::Legacy)
1155		}
1156	}
1157}
1158
1159fn require(present: bool, name: &'static str) -> Result<(), AllocError> {
1160	if present {
1161		Ok(())
1162	} else {
1163		Err(CallError::MissingCommand(name).into())
1164	}
1165}
1166
1167/// Checked shutdown after all regions and their blocks have been explicitly released.
1168pub fn close(allocator: &mut Allocator<'_>) -> Result<(), AllocError> {
1169	allocator.ensure_global()?;
1170	if !allocator.live.is_empty() {
1171		return Err(AllocError::LiveAllocations);
1172	}
1173	if !allocator.pools.is_empty() {
1174		return Err(AllocError::LivePools);
1175	}
1176	let mut snapshot = budget::budget_snapshot(allocator);
1177	pool::trim_blocks(allocator, &mut snapshot, true);
1178	allocator.closed = true;
1179	Ok(())
1180}
1181
1182/// Allocate separate native storage with automatically admitted allocation flags.
1183/// # Safety
1184/// Requirements, size/count limits and enabled features must be valid. Required
1185/// dedication and per-request import chains are excluded. Configured exports
1186/// apply to the selected type. Protected memory requires
1187/// captured enabled feature metadata. When owned device
1188/// metadata enables core/KHR buffer device address, raw storage carries its flag.
1189pub unsafe fn allocate(
1190	allocator: &mut Allocator<'_>,
1191	request: &AllocRequest,
1192) -> Result<Allocation, AllocError> {
1193	allocator.ensure_default()?;
1194	// SAFETY: caller provides the requirements and ordinary allocation semantics.
1195	let serial = unsafe {
1196		allocate_storage(
1197			allocator,
1198			request,
1199			StorageOptions {
1200				chain: &super::chain::ChainData::EMPTY,
1201				dedicated: None,
1202				device_address: allocator.buffer_device_address,
1203				priority: MemoryPriority::default(),
1204				within_budget: false,
1205			},
1206			vk::MemoryPropertyFlags::empty(),
1207		)
1208	}?;
1209	let mut allocation = allocator.published(serial);
1210	// SAFETY: new unbound raw storage; this helper owns unpublished initialization/rollback.
1211	if let Err(error) = unsafe { guard::initialize(allocator, &allocation) } {
1212		// SAFETY: initialization failed before publication; no native resource uses exist.
1213		unsafe { rollback_allocation(allocator, &mut allocation) }
1214			.expect("unpublished debug initialization rollback");
1215		return Err(error);
1216	}
1217	Ok(allocation)
1218}
1219
1220#[derive(Clone, Copy)]
1221struct StorageOptions<'a> {
1222	chain: &'a super::chain::ChainData,
1223	dedicated: Option<&'a vk::MemoryDedicatedAllocateInfo<'a>>,
1224	device_address: bool,
1225	priority: MemoryPriority,
1226	within_budget: bool,
1227}
1228
1229unsafe fn allocate_native(
1230	allocator: &mut Allocator<'_>,
1231	size: u64,
1232	index: u32,
1233	storage: StorageOptions<'_>,
1234) -> Result<vk::DeviceMemory, AllocError> {
1235	if storage.within_budget {
1236		budget::admit_native(allocator, index, size)?;
1237	}
1238	external::allocation_exports(allocator, index, *storage.chain)?;
1239	let export = vk::ExportMemoryAllocateInfo {
1240		handle_types: allocator.external_types[index as usize],
1241		p_next: storage.chain.next,
1242		..Default::default()
1243	};
1244	let tail = if export.handle_types.as_raw() != 0 {
1245		std::ptr::from_ref(&export).cast()
1246	} else {
1247		storage.chain.next
1248	};
1249	let mut dedicated = vk::MemoryDedicatedAllocateInfo {
1250		buffer: storage
1251			.dedicated
1252			.map_or(vk::Buffer::NULL, |info| info.buffer),
1253		image: storage.dedicated.map_or(vk::Image::NULL, |info| info.image),
1254		..Default::default()
1255	};
1256	dedicated.p_next = tail;
1257	let tail = if storage.dedicated.is_some() {
1258		std::ptr::from_ref(&dedicated).cast()
1259	} else {
1260		tail
1261	};
1262	if let Some(flags) = storage.chain.flags
1263		&& storage.device_address
1264		&& flags.as_raw() & vk::MemoryAllocateFlagBits::DEVICE_ADDRESS_BIT.as_raw() == 0
1265	{
1266		return Err(AllocError::InvalidRequest(
1267			"explicit allocation flags omit required device address",
1268		));
1269	}
1270	let flags = vk::MemoryAllocateFlagsInfo {
1271		flags: vk::MemoryAllocateFlagBits::DEVICE_ADDRESS_BIT,
1272		p_next: tail,
1273		..Default::default()
1274	};
1275	let next = if storage.device_address && storage.chain.flags.is_none() {
1276		std::ptr::from_ref(&flags).cast()
1277	} else {
1278		tail
1279	};
1280	let priority = vk::MemoryPriorityAllocateInfoEXT {
1281		priority: storage.priority.get(),
1282		p_next: next,
1283		..Default::default()
1284	};
1285	let info = vk::MemoryAllocateInfo {
1286		allocation_size: if storage.chain.import_kind == super::chain::ImportKind::MetalTexture {
1287			0
1288		} else {
1289			size
1290		},
1291		memory_type_index: index,
1292		p_next: if allocator.memory_priority {
1293			std::ptr::from_ref(&priority).cast()
1294		} else {
1295			next
1296		},
1297		..Default::default()
1298	};
1299	// Duplicate-handle detection must remain allocation-free after the driver
1300	// publishes a handle. Failed reservation retains all existing native owners.
1301	allocator.native_handles.try_reserve(1)?;
1302	let reservation = allocator
1303		.parallel_state()
1304		.map(|shared| {
1305			let heap = allocator.selector.properties.memory_types[index as usize].heap_index as usize;
1306			shared.reserve_native(size, heap, storage.within_budget)
1307		})
1308		.transpose()?;
1309	// SAFETY: caller preflights allocation semantics; stack chains and retained callbacks live through this synchronous call.
1310	let handle = unsafe { allocator.device.allocate_memory(&info, allocator.callbacks) }?;
1311	if let Some(reservation) = reservation
1312		&& !handle.is_null()
1313	{
1314		reservation.commit();
1315	}
1316	Ok(handle)
1317}
1318
1319unsafe fn allocate_storage(
1320	allocator: &mut Allocator<'_>,
1321	request: &AllocRequest,
1322	storage: StorageOptions<'_>,
1323	avoided: vk::MemoryPropertyFlags,
1324) -> Result<u64, AllocError> {
1325	allocator.ensure_open()?;
1326	let imported = storage.chain.import_info;
1327	if let Some(info) = imported {
1328		if request.size > info.allocation_size {
1329			return Err(AllocError::InvalidRequest(
1330				"resource exceeds imported payload",
1331			));
1332		}
1333		if storage.chain.import_kind == super::chain::ImportKind::Collection
1334			&& request.size != info.allocation_size
1335		{
1336			return Err(AllocError::InvalidRequest(
1337				"collection requires exact resource size",
1338			));
1339		}
1340		if let Some(resource) = import_resource(info.resource)
1341			&& binding::associated(allocator, resource)
1342		{
1343			return Err(AllocError::InvalidRequest(
1344				"imported resource already has an allocation",
1345			));
1346		}
1347	}
1348	let claim = imported
1349		.and_then(|info| import_resource(info.resource))
1350		.map(|resource| parallel::claim(allocator, resource, None, None))
1351		.transpose()?
1352		.flatten();
1353	let request = &AllocRequest {
1354		size: imported.map_or(request.size, |info| info.allocation_size),
1355		memory_type_bits: request.memory_type_bits
1356			& imported.map_or(u32::MAX, |info| info.memory_type_bits),
1357		..*request
1358	};
1359	allocator.validate_size(request.size)?;
1360	if !allocator.protected_memory
1361		&& request.required_flags.as_raw() & vk::MemoryPropertyFlagBits::PROTECTED_BIT.as_raw() != 0
1362	{
1363		return Err(AllocError::UnsupportedCapability("protected allocation"));
1364	}
1365	if allocator.native_count >= allocator.allocation_limit as usize {
1366		return Err(AllocError::AllocationLimit);
1367	}
1368	allocator.reserve()?;
1369	if let Some(resource) = imported.and_then(|info| import_resource(info.resource))
1370		&& allocator.parallel.is_none()
1371	{
1372		allocator.associated.try_reserve_for(resource)?;
1373	}
1374	let mut bits = request.memory_type_bits;
1375	let mut last = None;
1376	loop {
1377		let candidate = AllocRequest {
1378			memory_type_bits: bits,
1379			..*request
1380		};
1381		let index = match allocator
1382			.selector
1383			.find_memory_type_with_preferences(&candidate, avoided)
1384		{
1385			Ok(index) => index,
1386			Err(error) => return Err(last.unwrap_or(error)),
1387		};
1388		bits &= !(1 << index);
1389		if let Err(error) = allocator.check_native(request.size, index) {
1390			if error == AllocError::OutOfSpace {
1391				last = Some(error);
1392				continue;
1393			}
1394			return Err(error);
1395		}
1396		let exports = external::allocation_exports(allocator, index, *storage.chain)?;
1397		// SAFETY: matching requirements, admitted chains and reserved bookkeeping.
1398		let handle = match unsafe { allocate_native(allocator, request.size, index, storage) } {
1399			Ok(handle) => handle,
1400			Err(error)
1401				if !storage.chain.imports
1402					&& (matches!(&error, AllocError::Call(native) if recoverable(native))
1403						|| error == AllocError::OutOfSpace) =>
1404			{
1405				last = Some(error);
1406				continue;
1407			}
1408			Err(error) => return Err(error),
1409		};
1410		if handle.is_null() || allocator.contains_memory(handle) {
1411			return Err(
1412				CallError::InvalidOutput("allocator returned a null or already-live memory handle").into(),
1413			);
1414		}
1415		allocator.add_native(request.size, index, handle);
1416		let serial = publish(
1417			allocator,
1418			Region {
1419				handle,
1420				index,
1421				size: request.size,
1422				reserved_size: request.size,
1423				offset: 0,
1424				alignment: request.alignment,
1425				block_size: request.size,
1426				placement: None,
1427				pool: None,
1428				class: ResourceClass::Unknown,
1429			},
1430		);
1431		let live = allocator
1432			.live
1433			.get_mut(&serial)
1434			.expect("new separate allocation");
1435		live.imported = storage.chain.imports;
1436		live.resource = imported.and_then(|info| import_resource(info.resource));
1437		live.export_types = exports;
1438		if let Some(claim) = claim {
1439			claim.commit(serial);
1440		} else if let Some(resource) = live.resource {
1441			allocator.associated.record(resource, None, serial);
1442		}
1443		return Ok(serial);
1444	}
1445}
1446
1447fn recoverable(error: &CallError) -> bool {
1448	matches!(error, CallError::Vulkan(result) if *result == vk::Result::ERROR_OUT_OF_DEVICE_MEMORY || *result == vk::Result::ERROR_OUT_OF_HOST_MEMORY)
1449}
1450
1451// Publication is infallible after tracking reservation and native/placement success.
1452struct Region {
1453	handle: vk::DeviceMemory,
1454	index: u32,
1455	size: u64,
1456	reserved_size: u64,
1457	offset: u64,
1458	alignment: u64,
1459	block_size: u64,
1460	placement: Option<Placement>,
1461	pool: Option<u64>,
1462	class: ResourceClass,
1463}
1464
1465fn memory_flags(allocator: &Allocator<'_>, index: u32) -> vk::MemoryPropertyFlags {
1466	allocator.selector.properties.memory_types[index as usize].property_flags
1467}
1468
1469#[inline]
1470// Returns the published serial; public tokens are built once by `published`.
1471fn publish(allocator: &mut Allocator<'_>, region: Region) -> u64 {
1472	let Region {
1473		handle,
1474		index,
1475		size,
1476		reserved_size,
1477		offset,
1478		alignment,
1479		block_size,
1480		placement,
1481		pool,
1482		class,
1483	} = region;
1484	let serial = allocator.next_serial;
1485	allocator.next_serial += 1;
1486	if let Some(shared) = allocator.parallel_state() {
1487		shared.publish();
1488	}
1489	allocator.budget_operations = allocator.budget_operations.saturating_add(1);
1490	let guard_size = if allocator.debug_settings.margin != 0
1491		&& placement.is_some()
1492		&& guard::eligible(memory_flags(allocator, index))
1493	{
1494		allocator.debug_settings.margin
1495	} else {
1496		0
1497	};
1498	let guard_offset = if allocator.debug_settings.margin != 0
1499		&& placement.is_some()
1500		&& guard::eligible(memory_flags(allocator, index))
1501	{
1502		size.checked_add(3).expect("preflighted guard extent") & !3
1503	} else {
1504		0
1505	};
1506	allocator.live.insert_new_reserved_with(serial, || Live {
1507		serial,
1508		pool,
1509		handle,
1510		size,
1511		reserved_size,
1512		block_size,
1513		memory_type_index: index,
1514		offset,
1515		alignment,
1516		map_count: 0,
1517		persistent: false,
1518		resource: None,
1519		bound: false,
1520		name: None,
1521		user_data: 0,
1522		pinned: false,
1523		can_alias: false,
1524		imported: false,
1525		plane: None,
1526		export_types: vk::ExternalMemoryHandleTypeFlags::empty(),
1527		guard_size,
1528		guard_offset,
1529		debug_ready: false,
1530		class,
1531		placement,
1532		mapping: MappingOwner::default(),
1533		native_users: 0,
1534		pending_binding: None,
1535	});
1536	serial
1537}
1538
1539unsafe fn rollback_allocation(
1540	allocator: &mut Allocator<'_>,
1541	allocation: &mut Allocation,
1542) -> Result<(), AllocError> {
1543	allocator.validate(allocation)?;
1544	// Unpublished storage has no consumer data to diagnose or poison; debug setup
1545	// may itself have failed. Never repeat that failed operation during rollback.
1546	allocator
1547		.live
1548		.get_mut(&allocation.serial)
1549		.expect("unpublished allocation")
1550		.debug_ready = false;
1551	// SAFETY: callers own only new unpublished storage and already destroyed any new resource.
1552	unsafe { free_region(allocator, allocation, true) }
1553}
1554
1555/// Release a separate allocation or pooled placement and invalidate its token.
1556/// # Safety
1557/// Destroy all bound resources and complete every host/GPU access first. This
1558/// is also the low-level escape route after manually destroying a managed buffer.
1559pub unsafe fn free(
1560	allocator: &mut Allocator<'_>,
1561	allocation: &mut Allocation,
1562) -> Result<(), AllocError> {
1563	// SAFETY: the public release contract requires completed resource/host/GPU uses.
1564	unsafe { free_region(allocator, allocation, false) }
1565}
1566
1567// Relocation alone may retire outstanding counted holds: its unsafe commit/
1568// ignore/destroy contract explicitly ends every old pointer use. Ordinary public
1569// free still rejects explicit maps. Keep guard failure before any hold release.
1570unsafe fn free_region(
1571	allocator: &mut Allocator<'_>,
1572	allocation: &mut Allocation,
1573	release_explicit: bool,
1574) -> Result<(), AllocError> {
1575	allocator.ensure_allocation()?;
1576	let live = allocator.validate(allocation)?;
1577	if live.native_users != 0 {
1578		return Err(AllocError::InvalidRequest("native operation in progress"));
1579	}
1580	if live.map_count != 0 && !release_explicit {
1581		return Err(AllocError::AlreadyMapped);
1582	}
1583	let resource = live.resource;
1584	let plane = live.plane;
1585	let pending = live.pending_binding;
1586	let placement = live.placement;
1587	let (map_count, persistent) = (live.map_count, live.persistent);
1588	// Guard checks acquire and release only temporary mapping holds. The
1589	// exclusive allocator borrow keeps this region and its ownership unchanged.
1590	// SAFETY: caller completed all uses and ended the applicable pointer lifetimes.
1591	unsafe { guard::before_free(allocator, allocation) }?;
1592	let holds = map_count
1593		.checked_add(u32::from(persistent))
1594		.ok_or(AllocError::InvalidRange)?;
1595	if holds != 0 {
1596		let device = allocator.device.clone();
1597		// SAFETY: ordinary free rejected explicit maps; relocation ends all counted
1598		// pointer uses. The canonical shared owner releases exactly these holds.
1599		unsafe {
1600			allocator
1601				.mapping_mut(allocation)
1602				.release_many(device.as_device(), allocation.handle, holds)
1603		}?;
1604	}
1605	if let Some(placement) = placement {
1606		let block_index = allocator
1607			.blocks
1608			.iter()
1609			.position(|b| b.handle == allocation.handle)
1610			.expect("live block");
1611		let domain = BlockDomain::new_from_block(&allocator.blocks[block_index]);
1612		let block = &mut allocator.blocks[block_index];
1613		// SAFETY: every hold belonging to this region was released above.
1614		unsafe {
1615			block
1616				.mapping
1617				.write()
1618				.post_free(allocator.device.as_device(), block.handle)
1619		}?;
1620		block
1621			.metadata
1622			.free(placement)
1623			.expect("validated private placement");
1624		if block.defragmentation_hold {
1625			// The exclusive Extensive context will reuse or explicitly release this block.
1626		} else if block.automatic || block.pool.is_some() {
1627			pool::release_block(allocator, block_index);
1628		} else if block.metadata.is_empty() {
1629			debug_assert_eq!(block.mapping.read().references, 0);
1630			// SAFETY: no region holds remain; release the optional cache before native free.
1631			unsafe {
1632				allocator.blocks[block_index]
1633					.mapping
1634					.write()
1635					.clear_cache(allocator.device.as_device(), allocation.handle)
1636			}?;
1637			// SAFETY: all regions and maps ended; caller completed native resources.
1638			unsafe {
1639				allocator
1640					.device
1641					.as_device()
1642					.free_memory(allocation.handle, allocator.callbacks)
1643			}?;
1644			let block = allocator.blocks.remove(block_index);
1645			allocator.remove_native(block.size, block.memory_type_index, block.handle);
1646		}
1647		domain.incrementally_sort(&mut allocator.blocks);
1648	} else {
1649		// SAFETY: checked ownership; caller guarantees resource destruction/completion.
1650		unsafe {
1651			allocator
1652				.device
1653				.as_device()
1654				.free_memory(allocation.handle, allocator.callbacks)
1655		}?;
1656		allocator.remove_native(
1657			allocation.block_size,
1658			allocation.memory_type_index,
1659			allocation.handle,
1660		);
1661	}
1662	allocator.live.discard(&allocation.serial);
1663	if let Some(resource) = resource {
1664		allocator
1665			.associated
1666			.release(resource, plane, allocation.serial);
1667	}
1668	if let Some((resource, plane)) = pending {
1669		allocator
1670			.associated
1671			.release(resource, plane, allocation.serial);
1672	}
1673	if let Some(shared) = allocator.parallel_state() {
1674		shared.retire(resource, plane, allocation.serial);
1675	}
1676	allocator.budget_operations = allocator.budget_operations.saturating_add(1);
1677	allocation.handle = vk::DeviceMemory::NULL;
1678	Ok(())
1679}
1680
1681/// Map one allocation. Distinct regions share a reference-counted block mapping.
1682/// Repeated explicit maps acquire checked holds on the same native mapping.
1683/// # Safety
1684/// Synchronize host/GPU access. Pointers remain valid only while an explicit or
1685/// persistent hold for this allocation remains live. Do not alias Rust references.
1686pub unsafe fn map(
1687	allocator: &mut Allocator<'_>,
1688	allocation: &Allocation,
1689) -> Result<*mut std::ffi::c_void, AllocError> {
1690	let operation = native::MapOperation::prepare(allocator, allocation, true)?;
1691	// SAFETY: caller meets the shared canonical operation's native/byte contract.
1692	let result = unsafe { operation.execute(&mut allocator.mapping_mut(allocation)) };
1693	operation.commit(allocator, result)
1694}
1695
1696/// Release one explicit map; persistent holds and other regions remain mapped.
1697/// # Safety
1698/// Stop using pointers acquired by this explicit hold. This never flushes or waits.
1699pub unsafe fn unmap(
1700	allocator: &mut Allocator<'_>,
1701	allocation: &Allocation,
1702) -> Result<(), AllocError> {
1703	let operation = native::MapOperation::prepare(allocator, allocation, false)?;
1704	// SAFETY: caller ends the checked explicit hold and its pointer uses.
1705	let result = unsafe { operation.execute(&mut allocator.mapping_mut(allocation)) };
1706	operation.commit(allocator, result).map(|_| ())
1707}
1708
1709/// Return a region pointer only if that region has an explicit or persistent hold.
1710/// Does not map, acquire a hold, synchronize, or authorize pointer dereferences.
1711pub fn mapped_pointer(
1712	allocator: &Allocator<'_>,
1713	allocation: &Allocation,
1714) -> Result<Option<*mut std::ffi::c_void>, AllocError> {
1715	let live = allocator.validate(allocation)?;
1716	if live.map_count == 0 && !live.persistent {
1717		return Ok(None);
1718	}
1719	Ok(Some(
1720		allocator
1721			.mapping(allocation)
1722			.pointer
1723			.cast::<u8>()
1724			.wrapping_add(allocation.offset as usize)
1725			.cast(),
1726	))
1727}
1728
1729/// Snapshot one live allocation's binding, actual properties and current mapping.
1730///
1731/// Donor: pinned VMA `GetAllocationInfo` and `vmaGetAllocationMemoryProperties`.
1732/// Adaptation: checked owner/generation, one typed snapshot and per-region map holds.
1733/// Performs no native query, mapping, allocation or synchronization. A pooled
1734/// neighbor's mapping does not expose a pointer for an unmapped region.
1735/// # Errors
1736/// Returns `Closed`, `WrongAllocator` or `UnknownAllocation` before observing stale state.
1737pub fn allocation_info(
1738	allocator: &Allocator<'_>,
1739	allocation: &Allocation,
1740) -> Result<AllocationInfo, AllocError> {
1741	let mapped_pointer = mapped_pointer(allocator, allocation)?;
1742	let live = allocator.validate(allocation)?;
1743	Ok(AllocationInfo {
1744		device_memory: allocation.handle,
1745		offset: allocation.offset,
1746		size: allocation.size,
1747		reserved_size: allocation.reserved_size,
1748		memory_type_index: allocation.memory_type_index,
1749		heap_index: allocation.heap_index,
1750		memory_properties: allocation.flags,
1751		mapped_pointer,
1752		map_count: live.map_count,
1753		native_size: allocation.block_size,
1754		is_separate: live.placement.is_none(),
1755		user_data: live.user_data,
1756	})
1757}
1758
1759/// Calculate local live-region/native-block accounting without querying the driver.
1760pub fn statistics(allocator: &Allocator<'_>) -> AllocatorStats {
1761	AllocatorStats {
1762		allocation_count: allocator.live.len(),
1763		native_allocation_count: allocator.native_count,
1764		pooled_block_count: allocator.blocks.len(),
1765		allocation_bytes: allocator.live.values().map(|v| v.size).sum(),
1766		reserved_bytes: allocator.live.values().map(|v| v.reserved_size).sum(),
1767		native_bytes: allocator.native_bytes,
1768		mapped_native_count: allocator
1769			.blocks
1770			.iter()
1771			.filter(|b| !b.mapping.read().pointer.is_null())
1772			.count()
1773			+ allocator
1774				.live
1775				.values()
1776				.filter(|v| v.placement.is_none() && v.mapping.read().references != 0)
1777				.count(),
1778	}
1779}
1780
1781/// Flush a bounded allocation-relative mapped range, observing atom/end rules.
1782/// # Safety
1783/// Follow Vulkan's host/device synchronization and mapping requirements.
1784pub unsafe fn flush(
1785	allocator: &Allocator<'_>,
1786	allocation: &Allocation,
1787	offset: u64,
1788	size: u64,
1789) -> Result<(), AllocError> {
1790	let live = allocator.validate(allocation)?;
1791	if live.map_count == 0 && !live.persistent {
1792		return Err(AllocError::NotMapped);
1793	}
1794	if let Some(range) = allocation.mapped_range(offset, size)? {
1795		// SAFETY: whole allocation mapped; range is bounded and atom aligned.
1796		unsafe { allocator.device.flush_mapped_memory_ranges(&[range]) }?;
1797	}
1798	Ok(())
1799}
1800
1801/// Invalidate a bounded allocation-relative mapped range; never waits for the GPU.
1802/// # Safety
1803/// Complete device writes first and do not race host accesses.
1804pub unsafe fn invalidate(
1805	allocator: &Allocator<'_>,
1806	allocation: &Allocation,
1807	offset: u64,
1808	size: u64,
1809) -> Result<(), AllocError> {
1810	let live = allocator.validate(allocation)?;
1811	if live.map_count == 0 && !live.persistent {
1812		return Err(AllocError::NotMapped);
1813	}
1814	if let Some(range) = allocation.mapped_range(offset, size)? {
1815		// SAFETY: whole allocation mapped; range is bounded and atom aligned.
1816		unsafe { allocator.device.invalidate_mapped_memory_ranges(&[range]) }?;
1817	}
1818	Ok(())
1819}
1820
1821/// Transactionally create, query, allocate and bind an ordinary buffer.
1822///
1823/// Core 1.1 or enabled KHR dedication uses requirements2 with a dedicated
1824/// requirements output chain and dedicates this separate allocation to the
1825/// buffer, even when dedication is only preferred or unnecessary. Core 1.0
1826/// without dedication uses legacy requirements. Exact PFN spellings are retained.
1827///
1828/// # Safety
1829/// Info/limits/queue-family data and ordinary buffer usage must satisfy Vulkan
1830/// valid usage. Arbitrary chains and sparse/external memory are excluded. Protected memory
1831/// requires captured enabled feature metadata.
1832/// Device-address usage requires captured enabled core/KHR feature metadata.
1833pub unsafe fn create_buffer(
1834	allocator: &mut Allocator<'_>,
1835	info: &vk::BufferCreateInfo,
1836	required: vk::MemoryPropertyFlags,
1837	preferred: vk::MemoryPropertyFlags,
1838) -> Result<(vk::Buffer, Allocation), AllocError> {
1839	let options = AllocationCreateInfo {
1840		required_flags: required,
1841		preferred_flags: preferred,
1842		..Default::default()
1843	};
1844	// SAFETY: same native guarantees with the existing separate-storage policy.
1845	unsafe { create_buffer_with_options(allocator, info, &options) }
1846}
1847
1848/// Create, allocate, optionally map and bind an ordinary buffer transactionally.
1849///
1850/// Only recoverable native allocation OOM retries another compatible memory type.
1851/// Required/preferred dedication and regions larger than the explicit block use
1852/// separate storage. Empty blocks are released immediately, without caching.
1853/// # Errors
1854/// Returns checked policy/identity/limit errors or the preserved native failure.
1855/// # Safety
1856/// Info and queue-family pointers must satisfy native valid usage. Complete uses
1857/// before release. Sparse and capture/replay resources are excluded. Ordinary
1858/// external create/allocation profiles are admitted with caller-validated native
1859/// compatibility. Protected resources and BDA require captured enabled metadata.
1860pub unsafe fn create_buffer_with_options(
1861	allocator: &mut Allocator<'_>,
1862	info: &vk::BufferCreateInfo,
1863	options: &AllocationCreateInfo,
1864) -> Result<(vk::Buffer, Allocation), AllocError> {
1865	// SAFETY: same caller-owned native create records as the canonical operation.
1866	let device_address = unsafe { request::validate_buffer(allocator, info) }?;
1867	allocator.validate_size(info.size)?;
1868	let (mut required, preferred, avoided) =
1869		super::policy::preferences(options, info.usage.as_raw() & !3 != 0, allocator.integrated)?;
1870	required = request::resource_properties(
1871		allocator,
1872		info.flags.as_raw() & vk::BufferCreateFlagBits::PROTECTED_BIT.as_raw() != 0,
1873		required,
1874	)?;
1875	request::validate_options(allocator, options, required)?;
1876	// A null chain borrows the caller's options; keep that common case free of
1877	// the large owned-options result.
1878	let filtered;
1879	let options = if info.p_next.is_null() {
1880		options
1881	} else {
1882		// SAFETY: preceding resource validation checked the complete bounded native chain.
1883		filtered = unsafe { external::resource_options(allocator, options, info.p_next) }?;
1884		filtered.as_ref()
1885	};
1886	if options.pool.is_none()
1887		&& options.policy == AllocationPolicy::Separate
1888		&& allocator.native_count >= allocator.allocation_limit as usize
1889	{
1890		return Err(AllocError::AllocationLimit);
1891	}
1892	if options.persistent_map {
1893		require(allocator.device.fp().map_memory.is_some(), "vkMapMemory")?;
1894		require(
1895			allocator.device.fp().unmap_memory.is_some(),
1896			"vkUnmapMemory",
1897		)?;
1898	}
1899	allocator.buffer_route()?;
1900	let fp = allocator.device.fp();
1901	require(fp.create_buffer.is_some(), "vkCreateBuffer")?;
1902	require(fp.destroy_buffer.is_some(), "vkDestroyBuffer")?;
1903	require(fp.bind_buffer_memory.is_some(), "vkBindBufferMemory")?;
1904	allocator.reserve()?;
1905	// SAFETY: caller guarantees ordinary buffer valid usage; cleanup/metadata preflighted.
1906	let buffer = unsafe { allocator.device.create_buffer(info, allocator.callbacks) }?;
1907	if buffer.is_null() {
1908		return Err(CallError::InvalidOutput("vkCreateBuffer returned a null handle").into());
1909	}
1910	if binding::associated(allocator, Resource::Buffer(buffer)) {
1911		return Err(CallError::InvalidOutput("vkCreateBuffer returned an already-live handle").into());
1912	}
1913	// SAFETY: newly created ordinary resource, exact native requirements and preflighted inverses.
1914	let result = unsafe {
1915		binding::allocate_prepared_resource(
1916			allocator,
1917			Resource::Buffer(buffer),
1918			options,
1919			(required, preferred, avoided),
1920			ResourceClass::Buffer,
1921			device_address,
1922		)
1923	};
1924
1925	let mut allocation = match result {
1926		Ok(serial) => allocator.published(serial),
1927		Err(error) => {
1928			// SAFETY: unbound new buffer, cached immutable destruction PFN preflighted.
1929			unsafe { allocator.device.destroy_buffer(buffer, allocator.callbacks) }
1930				.expect("preflighted cleanup");
1931			return Err(error);
1932		}
1933	};
1934	// SAFETY: new host-visible storage and exact unbound resource/allocation pair;
1935	// publication already claimed and recorded this resource association.
1936	let result =
1937		unsafe { binding::persistent(allocator, &allocation, options) }.and_then(|()| unsafe {
1938			binding::bind_prepared(allocator, Resource::Buffer(buffer), &allocation)
1939		});
1940	if let Err(error) = result {
1941		// SAFETY: new unpublished objects only; inverse operations preflighted.
1942		unsafe {
1943			allocator.release_resource_claim(&allocation);
1944			allocator
1945				.device
1946				.as_device()
1947				.destroy_buffer(buffer, allocator.callbacks)
1948				.expect("preflighted cleanup");
1949			rollback_allocation(allocator, &mut allocation).expect("preflighted allocation rollback");
1950		}
1951		return Err(error);
1952	}
1953
1954	Ok((buffer, allocation))
1955}
1956
1957fn validate_policy(policy: AllocationPolicy) -> Result<(), AllocError> {
1958	match policy {
1959		AllocationPolicy::Pooled { block_size: 0 } => Err(AllocError::InvalidRequest(
1960			"pooled block size must be nonzero",
1961		)),
1962		AllocationPolicy::Adaptive {
1963			block_size,
1964			min_block_size,
1965		} if !block_size.is_power_of_two()
1966			|| !min_block_size.is_power_of_two()
1967			|| min_block_size > block_size =>
1968		{
1969			Err(AllocError::InvalidRequest(
1970				"adaptive block bounds must be ordered powers of two",
1971			))
1972		}
1973		_ => Ok(()),
1974	}
1975}
1976
1977#[derive(Clone, Copy, PartialEq, Eq)]
1978enum Dedication {
1979	None,
1980	Preferred,
1981	Required,
1982}
1983
1984impl Dedication {
1985	fn new(requirements: &vk::MemoryDedicatedRequirements) -> Self {
1986		if requirements.requires_dedicated_allocation != 0 {
1987			Self::Required
1988		} else if requirements.prefers_dedicated_allocation != 0 {
1989			Self::Preferred
1990		} else {
1991			Self::None
1992		}
1993	}
1994}
1995
1996#[derive(Clone, Copy)]
1997struct BlockOptions {
1998	// Exact request/type already checked by custom/default pool admission.
1999	// Consumed once per search; failed attempts still follow normal retry rules.
2000	selected_type: Option<u32>,
2001	// Custom-pool placement admission; default vectors use TLSF without rings.
2002	algorithm: AllocationAlgorithm,
2003	allow_ring: bool,
2004	automatic: bool,
2005	block_size: u64,
2006	strategy: super::PlacementStrategy,
2007	class: ResourceClass,
2008	grow: bool,
2009	pool: Option<u64>,
2010	upper_address: bool,
2011	priority: MemoryPriority,
2012	within_budget: bool,
2013	prefer_mapped: bool,
2014}
2015
2016// Donor: VMA BlockVector existing-block-first and smaller-block OOM retries.
2017// Adaptation: caller-visible finite bounds, no global growth history/hysteresis.
2018unsafe fn allocate_managed(
2019	allocator: &mut Allocator<'_>,
2020	request: &AllocRequest,
2021	storage: StorageOptions<'_>,
2022	dedication_policy: Dedication,
2023	options: &AllocationCreateInfo,
2024	class: ResourceClass,
2025	avoided: vk::MemoryPropertyFlags,
2026) -> Result<u64, AllocError> {
2027	// One pool lookup supplies both its allocation chain and the priority that
2028	// replaces the request priority for every pool-owned storage path.
2029	let (pool_chain, priority) =
2030		options
2031			.pool
2032			.map_or((super::chain::ChainData::EMPTY, storage.priority), |pool| {
2033				let state = &allocator.pools[&pool.serial];
2034				(state.allocation_chain, state.info.priority)
2035			});
2036	let chain = if let Some(chain) = &options.memory_allocate_next {
2037		if !pool_chain.next.is_null() {
2038			return Err(AllocError::InvalidRequest(
2039				"allocation and pool chains cannot be combined",
2040			));
2041		}
2042		&chain.data
2043	} else {
2044		&pool_chain
2045	};
2046	let mut storage = StorageOptions {
2047		chain,
2048		priority,
2049		..storage
2050	};
2051	let mut imported_dedicated = vk::MemoryDedicatedAllocateInfo::new();
2052	if let Some(resource) = storage
2053		.chain
2054		.import_info
2055		.and_then(|info| import_resource(info.resource))
2056	{
2057		if options.can_alias {
2058			return Err(AllocError::InvalidRequest(
2059				"resource-specific imports cannot alias",
2060			));
2061		}
2062		if !allocator
2063			.device
2064			.as_device()
2065			.capabilities()
2066			.is_some_and(|caps| caps.api_version() >= vk::API_VERSION_1_1)
2067			&& !super::resource::extension(allocator.device.as_device(), c"VK_KHR_dedicated_allocation")
2068		{
2069			return Err(AllocError::UnsupportedCapability(
2070				"import dedication requires core 1.1 or KHR_dedicated_allocation",
2071			));
2072		}
2073		if let Some(existing) = storage.dedicated {
2074			let matches = match resource {
2075				Resource::Buffer(buffer) => existing.buffer == buffer && existing.image.is_null(),
2076				Resource::Image(image) => existing.image == image && existing.buffer.is_null(),
2077			};
2078			if !matches {
2079				return Err(AllocError::WrongResource);
2080			}
2081		}
2082		match resource {
2083			Resource::Buffer(buffer) => imported_dedicated.buffer = buffer,
2084			Resource::Image(image) => imported_dedicated.image = image,
2085		}
2086		storage.dedicated = Some(&imported_dedicated);
2087	}
2088	if storage.chain.import_kind == super::chain::ImportKind::Host {
2089		if dedication_policy == Dedication::Required {
2090			return Err(AllocError::InvalidRequest(
2091				"host imports forbid required resource dedication",
2092			));
2093		}
2094		storage.dedicated = None;
2095	}
2096	let class = if options.can_alias {
2097		ResourceClass::Unknown
2098	} else {
2099		class
2100	};
2101	let dedication_policy = if options.can_alias {
2102		if dedication_policy == Dedication::Required {
2103			return Err(AllocError::UnsupportedCapability(
2104				"required resource dedication cannot alias",
2105			));
2106		}
2107		storage.dedicated = None;
2108		storage.device_address = allocator.buffer_device_address;
2109		Dedication::None
2110	} else {
2111		dedication_policy
2112	};
2113	let dedication_policy = if options.never_allocate && dedication_policy == Dedication::Preferred {
2114		Dedication::None
2115	} else {
2116		dedication_policy
2117	};
2118	if options.never_allocate && dedication_policy == Dedication::Required {
2119		return Err(AllocError::OutOfSpace);
2120	}
2121
2122	let forced_separate = options.dedicated_memory || options.memory_allocate_next.is_some();
2123	if forced_separate {
2124		if options.never_allocate || options.upper_address {
2125			return Err(AllocError::InvalidRequest(
2126				"separate allocation conflicts with reuse/upper placement",
2127			));
2128		}
2129		if let Some(pool) = options.pool {
2130			if allocator.pools[&pool.serial].info.block_size != 0 {
2131				return Err(AllocError::UnsupportedCapability(
2132					"fixed pool cannot allocate separate storage",
2133				));
2134			}
2135			let index = allocator.pools[&pool.serial].info.memory_type_index;
2136			let request = AllocRequest {
2137				memory_type_bits: request.memory_type_bits & (1 << index),
2138				..*request
2139			};
2140			// SAFETY: exact compatible pool type and caller's synchronous chain contract.
2141			let serial = unsafe { allocate_storage(allocator, &request, storage, avoided) }?;
2142			allocator
2143				.live
2144				.get_mut(&serial)
2145				.expect("new separate allocation")
2146				.pool = Some(pool.serial);
2147			return Ok(serial);
2148		}
2149		// SAFETY: explicit separate request, admitted tail and original requirements.
2150		return unsafe { allocate_storage(allocator, request, storage, avoided) };
2151	}
2152
2153	if options.pool.is_some() {
2154		// SAFETY: identical native requirements, chain and resource lifetime contract.
2155		return unsafe {
2156			pool::allocate(
2157				allocator,
2158				request,
2159				storage,
2160				dedication_policy,
2161				options,
2162				class,
2163				avoided,
2164			)
2165		};
2166	}
2167	if options.policy == AllocationPolicy::Automatic {
2168		// SAFETY: original native requirements/chain; shared donor automatic policy fixes each candidate type.
2169		return unsafe {
2170			pool::allocate_default(
2171				allocator,
2172				request,
2173				storage,
2174				dedication_policy,
2175				options,
2176				class,
2177				avoided,
2178			)
2179		};
2180	}
2181
2182	if dedication_policy == Dedication::None {
2183		match options.policy {
2184			AllocationPolicy::Pooled { block_size } if request.size <= block_size => {
2185				// SAFETY: matching native requirements and checked resource page separation.
2186				return unsafe {
2187					allocate_pooled(
2188						allocator,
2189						request,
2190						avoided,
2191						BlockOptions {
2192							selected_type: None,
2193							algorithm: AllocationAlgorithm::Tlsf,
2194							allow_ring: false,
2195							automatic: false,
2196							block_size,
2197							strategy: options.strategy,
2198							class,
2199							grow: !options.never_allocate,
2200							pool: None,
2201							upper_address: false,
2202							priority: options.priority,
2203							within_budget: options.within_budget,
2204							prefer_mapped: super::policy::prefers_mapped(options),
2205						},
2206					)
2207				};
2208			}
2209			AllocationPolicy::Adaptive {
2210				block_size,
2211				min_block_size,
2212			} => {
2213				for grow in [false, true] {
2214					if grow && options.never_allocate {
2215						break;
2216					}
2217					let mut size = block_size;
2218					while size >= request.size && size >= min_block_size {
2219						// SAFETY: matching native requirements; failed attempts publish no placement.
2220						match unsafe {
2221							allocate_pooled(
2222								allocator,
2223								request,
2224								avoided,
2225								BlockOptions {
2226									selected_type: None,
2227									algorithm: AllocationAlgorithm::Tlsf,
2228									allow_ring: false,
2229									automatic: false,
2230									block_size: size,
2231									strategy: options.strategy,
2232									class,
2233									grow,
2234									pool: None,
2235									upper_address: false,
2236									priority: options.priority,
2237									within_budget: options.within_budget,
2238									prefer_mapped: super::policy::prefers_mapped(options),
2239								},
2240							)
2241						} {
2242							Ok(serial) => return Ok(serial),
2243							Err(
2244								AllocError::OutOfSpace
2245								| AllocError::AllocationLimit
2246								| AllocError::AllocationTooLarge { .. },
2247							) => {}
2248							Err(AllocError::Call(error)) if recoverable(&error) => {}
2249							Err(error) => return Err(error),
2250						}
2251						if size == min_block_size {
2252							break;
2253						}
2254						size /= 2;
2255					}
2256				}
2257			}
2258			_ => {}
2259		}
2260	}
2261	// SAFETY: unchanged required flags and admitted buffer/image dedication chain.
2262	if options.never_allocate {
2263		return Err(AllocError::OutOfSpace);
2264	}
2265	unsafe { allocate_storage(allocator, request, storage, avoided) }
2266}
2267
2268// Donor: VMA BlockVector existing-block-first policy, explicit-size blocks,
2269// FindMemoryTypeIndex retry, GetMemoryTypeMinAlignment. Adaptations: verified
2270// virtual TLSF bucket geometry, compatible block-vector search, physical-neighbor
2271// granularity and atom-rounded reservation. Default blocks are released when empty;
2272// custom pools retain their minimum and one empty spare through the pool owner.
2273unsafe fn allocate_pooled(
2274	allocator: &mut Allocator<'_>,
2275	request: &AllocRequest,
2276	avoided: vk::MemoryPropertyFlags,
2277	options: BlockOptions,
2278) -> Result<u64, AllocError> {
2279	let BlockOptions {
2280		mut selected_type,
2281		algorithm,
2282		allow_ring,
2283		automatic,
2284		block_size,
2285		strategy,
2286		class,
2287		grow,
2288		pool,
2289		upper_address,
2290		priority,
2291		within_budget,
2292		prefer_mapped,
2293	} = options;
2294	let exact_size = !automatic;
2295	if exact_size {
2296		allocator.validate_size(block_size)?;
2297	}
2298	let mut bits = request.memory_type_bits;
2299	let mut last = None;
2300	loop {
2301		let candidate = AllocRequest {
2302			memory_type_bits: bits,
2303			..*request
2304		};
2305		let index = if let Some(index) = selected_type.take() {
2306			index
2307		} else {
2308			match allocator
2309				.selector
2310				.find_memory_type_with_preferences(&candidate, avoided)
2311			{
2312				Ok(index) => index,
2313				Err(error) => return Err(last.unwrap_or(error)),
2314			}
2315		};
2316		bits &= !(1 << index);
2317		let flags = allocator.selector.properties.memory_types[index as usize].property_flags;
2318		let noncoherent = flags.as_raw()
2319			& (vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT.as_raw()
2320				| vk::MemoryPropertyFlagBits::HOST_COHERENT_BIT.as_raw())
2321			== vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT.as_raw();
2322		let atom = if noncoherent {
2323			allocator.selector.atom
2324		} else {
2325			1
2326		};
2327		let guard_size = if guard::eligible(flags) {
2328			allocator.debug_settings.margin
2329		} else {
2330			0
2331		};
2332		let data_extent = if guard_size != 0 {
2333			request
2334				.size
2335				.checked_add(3)
2336				.map(|size| size & !3)
2337				.ok_or(AllocError::InvalidRange)?
2338		} else {
2339			request.size
2340		};
2341		let alignment = request
2342			.alignment
2343			.max(atom)
2344			.max(if guard_size != 0 { 4 } else { 1 });
2345		if !alignment.is_power_of_two() || !request.alignment.is_power_of_two() || request.size == 0 {
2346			return Err(AllocError::InvalidRequest(
2347				"invalid native buffer requirements",
2348			));
2349		}
2350		let reserved = data_extent
2351			.checked_add(guard_size)
2352			.and_then(|size| size.checked_add(atom - 1))
2353			.map(|v| v & !(atom - 1))
2354			.ok_or(AllocError::InvalidRange)?;
2355		if reserved > block_size {
2356			// The nominal requirement fits, but its private atom extent does not.
2357			last = Some(AllocError::OutOfSpace);
2358			continue;
2359		}
2360		let domain = BlockDomain {
2361			memory_type_index: index,
2362			pool,
2363			automatic,
2364			priority,
2365			exact_size: exact_size.then_some(block_size),
2366		};
2367		// Linear vectors use only their newest block. TLSF MIN_TIME scans
2368		// backward without mapping preference; every other strategy scans
2369		// forward, first in the preferred mapping group, then the other.
2370		let last_block = if algorithm == AllocationAlgorithm::Linear {
2371			allocator
2372				.blocks
2373				.iter()
2374				.rposition(|block| domain.contains(block))
2375		} else {
2376			None
2377		};
2378		let reverse = strategy == super::PlacementStrategy::MinTime;
2379		let mapping_groups = allocator.blocks.len() > 1
2380			&& algorithm == AllocationAlgorithm::Tlsf
2381			&& !reverse
2382			&& flags.as_raw() & vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT.as_raw() != 0;
2383		for group in 0..if mapping_groups { 2 } else { 1 } {
2384			for step in 0..allocator.blocks.len() {
2385				let block_index = if reverse {
2386					allocator.blocks.len() - 1 - step
2387				} else {
2388					step
2389				};
2390				let block = &mut allocator.blocks[block_index];
2391				let is_mapped = block.mapping.is_mapped();
2392				if !domain.contains(block)
2393					|| (algorithm == AllocationAlgorithm::Linear && Some(block_index) != last_block)
2394					|| (mapping_groups && ((group == 0) != (prefer_mapped == is_mapped)))
2395				{
2396					continue;
2397				}
2398				let (placement, offset) = match block.metadata.allocate_resource(
2399					reserved,
2400					alignment,
2401					strategy,
2402					upper_address,
2403					class,
2404				) {
2405					Ok(placement) => placement,
2406					Err(AllocError::OutOfSpace) => continue,
2407					Err(error) => return Err(error),
2408				};
2409				let handle = block.handle;
2410				let actual_size = block.size;
2411				// Block bookkeeping completes before publication, so the token is
2412				// built directly as the return value.
2413				block.mapping.write().post_alloc();
2414				domain.incrementally_sort(&mut allocator.blocks);
2415				return Ok(publish(
2416					allocator,
2417					Region {
2418						handle,
2419						index,
2420						size: request.size,
2421						reserved_size: reserved,
2422						offset,
2423						alignment,
2424						block_size: actual_size,
2425						placement: Some(placement),
2426						pool,
2427						class,
2428					},
2429				));
2430			}
2431		}
2432		if let Some(pool) = pool {
2433			let max = allocator.pools[&pool].info.max_block_count;
2434			if max != 0
2435				&& allocator
2436					.blocks
2437					.iter()
2438					.filter(|block| block.pool == Some(pool))
2439					.count()
2440					>= max
2441			{
2442				return Err(AllocError::OutOfSpace);
2443			}
2444		}
2445		if !grow {
2446			last = Some(AllocError::OutOfSpace);
2447			continue;
2448		}
2449		if let Err(error) = allocator.check_native(block_size, index) {
2450			// A count ceiling prevents growth, not reuse of another compatible type.
2451			if error == AllocError::OutOfSpace || error == AllocError::AllocationLimit {
2452				last = Some(error);
2453				continue;
2454			}
2455			return Err(error);
2456		}
2457		allocator
2458			.blocks
2459			.try_reserve(1)
2460			.map_err(|_| AllocError::HostAllocationFailed)?;
2461		let mut metadata = Metadata::new_native(
2462			block_size,
2463			algorithm,
2464			allow_ring,
2465			allocator.granularity,
2466			allocator.host,
2467		)?;
2468		let (placement, offset) =
2469			metadata.allocate_resource(reserved, alignment, strategy, upper_address, class)?;
2470		let chain = pool.map_or(Default::default(), |serial| {
2471			allocator.pools[&serial].allocation_chain
2472		});
2473		let export_types = external::allocation_exports(allocator, index, chain)?;
2474		// SAFETY: exact ordinary block requirements and admitted native capability metadata.
2475		let handle = match unsafe {
2476			allocate_native(
2477				allocator,
2478				block_size,
2479				index,
2480				StorageOptions {
2481					chain: &chain,
2482					dedicated: None,
2483					device_address: allocator.buffer_device_address,
2484					priority,
2485					within_budget,
2486				},
2487			)
2488		} {
2489			Ok(handle) => handle,
2490			Err(error)
2491				if matches!(&error, AllocError::Call(native) if recoverable(native))
2492					|| error == AllocError::OutOfSpace =>
2493			{
2494				last = Some(error);
2495				continue;
2496			}
2497			Err(error) => return Err(error),
2498		};
2499		if handle.is_null() || allocator.contains_memory(handle) {
2500			return Err(
2501				CallError::InvalidOutput("allocator returned a null or already-live memory handle").into(),
2502			);
2503		}
2504		allocator.blocks.push_reserved(Block {
2505			defragmentation_hold: false,
2506			automatic,
2507			export_types,
2508			priority,
2509			pool,
2510			handle,
2511			size: block_size,
2512			memory_type_index: index,
2513			metadata,
2514			mapping: MappingOwner::new_with_hysteresis(allocator.mapping_hysteresis),
2515		});
2516		allocator.add_native(block_size, index, handle);
2517		allocator
2518			.blocks
2519			.last_mut()
2520			.expect("new pooled block")
2521			.mapping
2522			.write()
2523			.post_alloc();
2524		domain.incrementally_sort(&mut allocator.blocks);
2525		return Ok(publish(
2526			allocator,
2527			Region {
2528				handle,
2529				index,
2530				size: request.size,
2531				reserved_size: reserved,
2532				offset,
2533				alignment,
2534				block_size,
2535				placement: Some(placement),
2536				pool,
2537				class,
2538			},
2539		));
2540	}
2541}
2542
2543/// Destroy a matching resource pair in buffer-before-memory order with matching retained callbacks.
2544/// # Safety
2545/// Complete all host/GPU uses. Neither object may have been externally destroyed.
2546/// The buffer must use this allocator's callbacks; otherwise destroy it directly then free.
2547pub unsafe fn destroy_buffer(
2548	allocator: &mut Allocator<'_>,
2549	buffer: vk::Buffer,
2550	allocation: &mut Allocation,
2551) -> Result<(), AllocError> {
2552	allocator.ensure_allocation()?;
2553	let live = allocator.validate(allocation)?;
2554	if live.native_users != 0 {
2555		return Err(AllocError::InvalidRequest("native operation in progress"));
2556	}
2557	if live.resource != Some(Resource::Buffer(buffer)) {
2558		return Err(AllocError::WrongResource);
2559	}
2560	if live.map_count != 0 {
2561		return Err(AllocError::AlreadyMapped);
2562	}
2563	let debug_ready = live.debug_ready;
2564	// Both transitions must be possible before any native destruction occurs.
2565	require(
2566		allocator.device.fp().destroy_buffer.is_some(),
2567		"vkDestroyBuffer",
2568	)?;
2569	require(allocator.device.fp().free_memory.is_some(), "vkFreeMemory")?;
2570	// SAFETY: association/state validated; caller guarantees exact completion.
2571	unsafe {
2572		guard::before_free(allocator, allocation)?;
2573		allocator.release_resource_claim(allocation);
2574		allocator
2575			.device
2576			.as_device()
2577			.destroy_buffer(buffer, allocator.callbacks)?;
2578		// Only guard initialization sets this flag; skip a second lookup otherwise.
2579		if debug_ready {
2580			allocator
2581				.live
2582				.get_mut(&allocation.serial)
2583				.expect("validated resource allocation")
2584				.debug_ready = false;
2585		}
2586		free(allocator, allocation)
2587	}
2588}
2589
2590#[cfg(test)]
2591#[path = "../../../test/rs/mem/test_lifecycle.rs"]
2592mod tests;
2593
2594/// Create an ordinary image with separate storage and explicit memory properties.
2595/// # Safety
2596/// Caller establishes all native image, create-chain and synchronization valid usage.
2597pub unsafe fn create_image(
2598	allocator: &mut Allocator<'_>,
2599	info: &vk::ImageCreateInfo,
2600	required: vk::MemoryPropertyFlags,
2601	preferred: vk::MemoryPropertyFlags,
2602) -> Result<(vk::Image, Allocation), AllocError> {
2603	let options = AllocationCreateInfo {
2604		required_flags: required,
2605		preferred_flags: preferred,
2606		..Default::default()
2607	};
2608	// SAFETY: same native guarantees and separate-storage policy.
2609	unsafe { create_image_with_options(allocator, info, &options) }
2610}
2611
2612/// Create, query, allocate, optionally map and bind a non-disjoint image transactionally.
2613///
2614/// Donor: pinned VMA `CreateImage`, `GetImageMemoryRequirements`, `AllocateDedicatedMemory`.
2615/// Adaptation: checked neighbor metadata separates incompatible buffer/image pages
2616/// while compatible resources share native blocks. Unknown granularity stays conservative.
2617/// Video-profile and image-format-list chains are forwarded unchanged after admission.
2618/// Sparse, disjoint, external and unknown allocation chains are excluded.
2619/// Aliases require explicit can_alias storage; protected images require enabled metadata.
2620/// # Safety
2621/// Meet image/format/feature and pointer-chain valid usage. A mapped optimal image is not a
2622/// linear pixel array. Callers use native subresource layouts for host access to linear images.
2623pub unsafe fn create_image_with_options(
2624	allocator: &mut Allocator<'_>,
2625	info: &vk::ImageCreateInfo,
2626	options: &AllocationCreateInfo,
2627) -> Result<(vk::Image, Allocation), AllocError> {
2628	// SAFETY: same caller-owned native create records as the canonical operation.
2629	let class = unsafe { request::validate_image(allocator, info) }?;
2630	let (mut required, preferred, avoided) =
2631		super::policy::preferences(options, info.usage.as_raw() & !3 != 0, allocator.integrated)?;
2632	required = request::resource_properties(
2633		allocator,
2634		info.flags.as_raw() & vk::ImageCreateFlagBits::PROTECTED_BIT.as_raw() != 0,
2635		required,
2636	)?;
2637	request::validate_options(allocator, options, required)?;
2638	// A null chain borrows the caller's options; keep that common case free of
2639	// the large owned-options result.
2640	let filtered;
2641	let options = if info.p_next.is_null() {
2642		options
2643	} else {
2644		// SAFETY: preceding resource validation checked the complete bounded native chain.
2645		filtered = unsafe { external::resource_options(allocator, options, info.p_next) }?;
2646		filtered.as_ref()
2647	};
2648	allocator.requirements_route(true)?;
2649	let fp = allocator.device.fp();
2650	require(fp.create_image.is_some(), "vkCreateImage")?;
2651	require(fp.destroy_image.is_some(), "vkDestroyImage")?;
2652	require(fp.bind_image_memory.is_some(), "vkBindImageMemory")?;
2653	if options.persistent_map {
2654		require(fp.map_memory.is_some(), "vkMapMemory")?;
2655		require(fp.unmap_memory.is_some(), "vkUnmapMemory")?;
2656	}
2657	allocator.reserve()?;
2658	// SAFETY: native info valid by caller contract; inverse PFNs and metadata preflighted.
2659	let image = unsafe { allocator.device.create_image(info, allocator.callbacks) }?;
2660	if image.is_null() {
2661		return Err(CallError::InvalidOutput("vkCreateImage returned a null handle").into());
2662	}
2663	if binding::associated(allocator, Resource::Image(image)) {
2664		return Err(CallError::InvalidOutput("vkCreateImage returned an already-live handle").into());
2665	}
2666	// SAFETY: newly created ordinary image with validated creation metadata.
2667	let result = unsafe {
2668		binding::allocate_prepared_resource(
2669			allocator,
2670			Resource::Image(image),
2671			options,
2672			(required, preferred, avoided),
2673			class,
2674			false,
2675		)
2676	};
2677
2678	let mut allocation = match result {
2679		Ok(serial) => allocator.published(serial),
2680		Err(error) => {
2681			// SAFETY: only the unpublished unbound image exists; inverse PFN preflighted.
2682			unsafe { allocator.device.destroy_image(image, allocator.callbacks) }
2683				.expect("preflighted cleanup");
2684			return Err(error);
2685		}
2686	};
2687	// SAFETY: new host-visible storage and matching unbound image/allocation pair;
2688	// publication already claimed and recorded this resource association.
2689	let result =
2690		unsafe { binding::persistent(allocator, &allocation, options) }.and_then(|()| unsafe {
2691			binding::bind_prepared(allocator, Resource::Image(image), &allocation)
2692		});
2693
2694	if let Err(error) = result {
2695		// SAFETY: unpublished objects only; inverse transitions preflighted.
2696		unsafe {
2697			allocator.release_resource_claim(&allocation);
2698			allocator
2699				.device
2700				.as_device()
2701				.destroy_image(image, allocator.callbacks)
2702				.expect("preflighted cleanup");
2703			rollback_allocation(allocator, &mut allocation).expect("preflighted allocation rollback");
2704		}
2705		return Err(error);
2706	}
2707	Ok((image, allocation))
2708}
2709
2710/// Destroy the exact image/token pair with matching retained callbacks before freeing memory.
2711/// # Safety
2712/// Destroy views and complete all native uses first; do not externally destroy this image.
2713/// The image must use this allocator's callbacks; otherwise destroy it directly then free.
2714pub unsafe fn destroy_image(
2715	allocator: &mut Allocator<'_>,
2716	image: vk::Image,
2717	allocation: &mut Allocation,
2718) -> Result<(), AllocError> {
2719	allocator.ensure_allocation()?;
2720	let live = allocator.validate(allocation)?;
2721	if live.native_users != 0 {
2722		return Err(AllocError::InvalidRequest("native operation in progress"));
2723	}
2724	if live.plane.is_some() {
2725		return Err(AllocError::InvalidRequest(
2726			"destroy a disjoint image once, then release every plane allocation",
2727		));
2728	}
2729	if live.resource != Some(Resource::Image(image)) {
2730		return Err(AllocError::WrongResource);
2731	}
2732	if live.map_count != 0 {
2733		return Err(AllocError::AlreadyMapped);
2734	}
2735	let debug_ready = live.debug_ready;
2736	require(
2737		allocator.device.fp().destroy_image.is_some(),
2738		"vkDestroyImage",
2739	)?;
2740	require(allocator.device.fp().free_memory.is_some(), "vkFreeMemory")?;
2741	// SAFETY: checked association/state; caller supplies exact completion.
2742	unsafe {
2743		guard::before_free(allocator, allocation)?;
2744		allocator.release_resource_claim(allocation);
2745		allocator.device.destroy_image(image, allocator.callbacks)?;
2746		// Only guard initialization sets this flag; skip a second lookup otherwise.
2747		if debug_ready {
2748			allocator
2749				.live
2750				.get_mut(&allocation.serial)
2751				.expect("validated resource allocation")
2752				.debug_ready = false;
2753		}
2754		free(allocator, allocation)
2755	}
2756}