Skip to main content

vk/mem/
services.rs

1//! Pinned VMA 3.4.0 page allocation/free, batched cache maintenance, host-copy
2//! helpers and allocation/pool metadata (license/vma/MIT.txt).
3//! Adaptation: fallible Rust containers, checked tokens/ranges, explicit mapping
4//! holds and cleanup; no implicit synchronization, native Drop or raw-data ownership.
5use super::*;
6
7/// One allocation-relative cache range. Each allocation must own a mapping hold.
8#[derive(Clone, Copy)]
9pub struct CacheRange<'a> {
10	pub allocation: &'a Allocation,
11	pub offset: u64,
12	pub size: u64,
13}
14
15impl<'a> CacheRange<'a> {
16	/// Describe the whole allocation from byte zero without acquiring a mapping hold.
17	#[inline]
18	#[must_use]
19	pub fn new(allocation: &'a Allocation) -> Self {
20		Self {
21			allocation,
22			offset: 0,
23			size: vk::WHOLE_SIZE,
24		}
25	}
26
27	/// Set [`allocation`](Self::allocation) and return the updated record.
28	#[inline]
29	#[must_use]
30	pub fn set_allocation(mut self, value: &'a Allocation) -> Self {
31		self.allocation = value;
32		self
33	}
34
35	/// Set [`offset`](Self::offset) and return the updated record.
36	#[inline]
37	#[must_use]
38	pub fn set_offset(mut self, value: u64) -> Self {
39		self.offset = value;
40		self
41	}
42
43	/// Set [`size`](Self::size) and return the updated record.
44	#[inline]
45	#[must_use]
46	pub fn set_size(mut self, value: u64) -> Self {
47		self.size = value;
48		self
49	}
50}
51
52/// Allocate identical ordinary requirement pages; failure releases new pages only.
53///
54/// Host result storage is reserved before native effects. Generations may advance
55/// after rollback, and normal pool caching policy may retain empty native blocks.
56/// # Errors
57/// Preserves allocation failure; rejects impossible result extents/serial overflow first.
58/// # Safety
59/// Same raw requirements/feature/lifetime contract as `allocate_with_options`.
60pub unsafe fn allocate_many(
61	allocator: &mut Allocator<'_>,
62	requirements: &vk::MemoryRequirements,
63	options: &AllocationCreateInfo,
64	count: usize,
65) -> Result<Vec<Allocation>, AllocError> {
66	if count > 1
67		&& options
68			.memory_allocate_next
69			.is_some_and(|chain| chain.data.imports)
70	{
71		return Err(AllocError::InvalidRequest(
72			"one imported handle cannot back multiple allocations",
73		));
74	}
75	allocator.ensure_open()?;
76	allocator
77		.next_serial
78		.checked_add(u64::try_from(count).map_err(|_| AllocError::SerialExhausted)?)
79		.ok_or(AllocError::SerialExhausted)?;
80	let mut allocations = Vec::new();
81	allocations
82		.try_reserve_exact(count)
83		.map_err(|_| AllocError::HostAllocationFailed)?;
84	for _ in 0..count {
85		// SAFETY: each page is unpublished, unbound ordinary storage with the same native requirements.
86		match unsafe { allocate_with_options(allocator, requirements, options) } {
87			Ok(allocation) => allocations.push(allocation),
88			Err(error) => {
89				for allocation in allocations.iter_mut().rev() {
90					// SAFETY: no caller received any token or mapping pointer; no GPU/native resource use exists.
91					unsafe { rollback_allocation(allocator, allocation) }.expect("unpublished page rollback");
92				}
93				return Err(error);
94			}
95		}
96	}
97	Ok(allocations)
98}
99
100/// Validate an entire release batch before freeing any allocation.
101///
102/// Successful release invalidates every token. Ordinary native cleanup uses
103/// immutable preflighted void PFNs; no host allocation is needed for this batch.
104/// # Errors
105/// Rejects closed, foreign, stale, duplicate or explicitly mapped tokens before effects.
106/// # Safety
107/// Every bound resource must already be destroyed and every CPU/GPU use complete.
108pub unsafe fn free_many(
109	allocator: &mut Allocator<'_>,
110	allocations: &mut [Allocation],
111) -> Result<(), AllocError> {
112	allocator.ensure_open()?;
113	for (index, allocation) in allocations.iter().enumerate() {
114		if allocator.validate(allocation)?.map_count != 0 {
115			return Err(AllocError::AlreadyMapped);
116		}
117		if allocations[..index]
118			.iter()
119			.any(|previous| previous.serial == allocation.serial)
120		{
121			return Err(AllocError::InvalidRequest(
122				"duplicate allocation in release batch",
123			));
124		}
125	}
126	for allocation in allocations {
127		// SAFETY: all identities/holds were checked; caller has completed uses and destroyed resources.
128		unsafe { free(allocator, allocation) }?;
129	}
130	Ok(())
131}
132
133fn cache_ranges<'device>(
134	allocator: &Allocator<'device>,
135	ranges: &[CacheRange<'_>],
136) -> Result<HostVec<'device, vk::MappedMemoryRange<'static>>, AllocError> {
137	allocator.ensure_open()?;
138	let mut native = HostVec::new(allocator.host);
139	native
140		.try_reserve_exact(ranges.len())
141		.map_err(|_| AllocError::HostAllocationFailed)?;
142	for range in ranges {
143		let live = allocator.validate(range.allocation)?;
144		if live.map_count == 0 && !live.persistent {
145			return Err(AllocError::NotMapped);
146		}
147		if let Some(range) = range.allocation.mapped_range(range.offset, range.size)? {
148			native.push_reserved(range);
149		}
150	}
151	u32::try_from(native.len()).map_err(|_| AllocError::InvalidRange)?;
152	Ok(native)
153}
154
155/// Flush checked ranges in one native call; coherent/empty ranges remain checked no-ops.
156/// # Errors
157/// Invalid identity/map/range or host reservation fails before the native call.
158/// # Safety
159/// Match Vulkan host/GPU synchronization and host-write visibility requirements.
160pub unsafe fn flush_many(
161	allocator: &Allocator<'_>,
162	ranges: &[CacheRange<'_>],
163) -> Result<(), AllocError> {
164	let native = cache_ranges(allocator, ranges)?;
165	if !native.is_empty() {
166		// SAFETY: every range belongs to a currently held mapping and satisfies native atom bounds.
167		unsafe { allocator.device.flush_mapped_memory_ranges(&native) }?;
168	}
169	Ok(())
170}
171
172/// Invalidate checked ranges in one native call; never waits for device writes.
173/// # Errors
174/// Same checked identity/map/range and native errors as `flush_many`.
175/// # Safety
176/// Device writes must have completed; synchronize every host/GPU access first.
177pub unsafe fn invalidate_many(
178	allocator: &Allocator<'_>,
179	ranges: &[CacheRange<'_>],
180) -> Result<(), AllocError> {
181	let native = cache_ranges(allocator, ranges)?;
182	if !native.is_empty() {
183		// SAFETY: initialized native ranges are bounded within held native mappings.
184		unsafe { allocator.device.invalidate_mapped_memory_ranges(&native) }?;
185	}
186	Ok(())
187}
188
189fn copy_range(
190	allocator: &Allocator<'_>,
191	allocation: &Allocation,
192	offset: u64,
193	length: usize,
194	write: bool,
195) -> Result<u64, AllocError> {
196	allocator.validate(allocation)?;
197	let size = u64::try_from(length).map_err(|_| AllocError::InvalidRange)?;
198	if offset
199		.checked_add(size)
200		.is_none_or(|end| end > allocation.size)
201	{
202		return Err(AllocError::InvalidRange);
203	}
204	if size != 0 && allocation.mapped_range(offset, size)?.is_some() {
205		if write {
206			require(
207				allocator.device.fp().flush_mapped_memory_ranges.is_some(),
208				"vkFlushMappedMemoryRanges",
209			)?;
210		} else {
211			require(
212				allocator
213					.device
214					.as_device()
215					.fp()
216					.invalidate_mapped_memory_ranges
217					.is_some(),
218				"vkInvalidateMappedMemoryRanges",
219			)?;
220		}
221	}
222	Ok(size)
223}
224
225/// Copy host bytes to an allocation, flush them and release only this helper's map hold.
226///
227/// Existing explicit/persistent maps remain held. Zero bytes still validate
228/// identity and bounds. Does not submit transfers, wait or infer GPU completion.
229/// # Errors
230/// Reports checked identity/range/map/cache errors; a cache failure still releases this map hold.
231/// # Safety
232/// Caller synchronizes all accesses and provides initialized readable source
233/// storage. Destination native bytes must be writable; no live Rust reference may
234/// be invalidated by this write. Resource layout (especially images) remains caller-owned.
235pub unsafe fn copy_to_allocation(
236	allocator: &mut Allocator<'_>,
237	allocation: &Allocation,
238	offset: u64,
239	source: &[u8],
240) -> Result<(), AllocError> {
241	let size = copy_range(allocator, allocation, offset, source.len(), true)?;
242	if size == 0 {
243		return Ok(());
244	}
245	let pointer_offset = usize::try_from(offset).map_err(|_| AllocError::InvalidRange)?;
246	// SAFETY: caller establishes host access and initialization; checked extent is within native storage.
247	let pointer = unsafe { map(allocator, allocation) }?;
248	// SAFETY: Mapping validates native host extent; checked copy range and caller's alias/access contract apply.
249	unsafe {
250		std::ptr::copy(
251			source.as_ptr(),
252			pointer.cast::<u8>().add(pointer_offset),
253			source.len(),
254		)
255	};
256	// SAFETY: this helper's live map covers the checked allocation-relative range.
257	let result = unsafe { flush(allocator, allocation, offset, size) };
258	// SAFETY: helper no longer uses the pointer; preserve other explicit/persistent holds.
259	let cleanup = unsafe { unmap(allocator, allocation) };
260	result.and(cleanup)
261}
262
263/// Invalidate and copy allocation bytes to initialized writable host storage.
264/// # Errors
265/// Checked identity/range/map/cache errors; invalidation failure performs no host copy.
266/// # Safety
267/// Complete device writes and synchronize all host/GPU accesses. Source bytes
268/// must be initialized; destination exclusivity and native image layout remain caller obligations.
269pub unsafe fn copy_from_allocation(
270	allocator: &mut Allocator<'_>,
271	allocation: &Allocation,
272	offset: u64,
273	destination: &mut [u8],
274) -> Result<(), AllocError> {
275	let size = copy_range(allocator, allocation, offset, destination.len(), false)?;
276	if size == 0 {
277		return Ok(());
278	}
279	let pointer_offset = usize::try_from(offset).map_err(|_| AllocError::InvalidRange)?;
280	// SAFETY: caller supplies completed writes and synchronized visible storage.
281	let pointer = unsafe { map(allocator, allocation) }?;
282	// SAFETY: checked allocation-relative range lies within this held mapping.
283	let result = unsafe { invalidate(allocator, allocation, offset, size) };
284	if result.is_ok() {
285		// SAFETY: caller guarantees initialized native bytes and exclusive writable destination.
286		unsafe {
287			std::ptr::copy(
288				pointer.cast::<u8>().add(pointer_offset),
289				destination.as_mut_ptr(),
290				destination.len(),
291			)
292		};
293	}
294	// SAFETY: no helper pointer uses remain; release exactly the acquired hold.
295	let cleanup = unsafe { unmap(allocator, allocation) };
296	result.and(cleanup)
297}
298
299fn name_copy<'callbacks>(
300	host: HostMemory<'callbacks>,
301	name: Option<&str>,
302) -> Result<Option<HostString<'callbacks>>, AllocError> {
303	name
304		.map(|name| HostString::new_from_str(host, name))
305		.transpose()
306}
307
308/// Borrow an allocation's diagnostic name; no native lifetime is acquired.
309pub fn allocation_name<'a>(
310	allocator: &'a Allocator<'_>,
311	allocation: &Allocation,
312) -> Result<Option<&'a str>, AllocError> {
313	Ok(allocator.validate(allocation)?.name.as_deref())
314}
315
316/// Replace a copied UTF-8 diagnostic name transactionally; None clears it.
317/// # Errors
318/// Identity/state or host reservation failure leaves the previous name intact.
319pub fn set_allocation_name(
320	allocator: &mut Allocator<'_>,
321	allocation: &Allocation,
322	name: Option<&str>,
323) -> Result<(), AllocError> {
324	allocator.validate(allocation)?;
325	let name = name_copy(allocator.host, name)?;
326	allocator
327		.live
328		.get_mut(&allocation.serial)
329		.expect("validated allocation")
330		.name = name;
331	Ok(())
332}
333
334/// Set uninterpreted consumer metadata. It is never dereferenced, freed or owned.
335pub fn set_allocation_user_data(
336	allocator: &mut Allocator<'_>,
337	allocation: &Allocation,
338	data: usize,
339) -> Result<(), AllocError> {
340	allocator.validate(allocation)?;
341	allocator
342		.live
343		.get_mut(&allocation.serial)
344		.expect("validated allocation")
345		.user_data = data;
346	Ok(())
347}
348
349/// Borrow a custom pool's copied diagnostic name without native effects.
350pub fn pool_name<'a>(
351	allocator: &'a Allocator<'_>,
352	pool: &Pool,
353) -> Result<Option<&'a str>, AllocError> {
354	Ok(pool::validate(allocator, pool)?.name.as_deref())
355}
356
357/// Replace a pool diagnostic name; metadata allocation failure preserves its old name.
358pub fn set_pool_name(
359	allocator: &mut Allocator<'_>,
360	pool: &Pool,
361	name: Option<&str>,
362) -> Result<(), AllocError> {
363	pool::validate(allocator, pool)?;
364	let name = name_copy(allocator.host, name)?;
365	allocator
366		.pools
367		.get_mut(&pool.serial)
368		.expect("validated pool")
369		.name = name;
370	Ok(())
371}
372
373/// Detailed local byte/count statistics. Empty populations have None extrema.
374#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
375pub struct DetailedStatistics {
376	pub native_count: usize,
377	pub native_bytes: u64,
378	pub allocation_count: usize,
379	pub allocation_bytes: u64,
380	pub reserved_bytes: u64,
381	pub free_range_count: usize,
382	pub free_bytes: u64,
383	pub allocation_size_min: Option<u64>,
384	pub allocation_size_max: Option<u64>,
385	pub free_range_size_min: Option<u64>,
386	pub free_range_size_max: Option<u64>,
387}
388
389/// Explicit diagnostic snapshot, indexed by native memory type and heap.
390#[derive(Clone, Debug, Default, PartialEq, Eq)]
391pub struct MemoryStatistics {
392	pub total: DetailedStatistics,
393	pub memory_types: [DetailedStatistics; 32],
394	pub heaps: [DetailedStatistics; 16],
395}
396
397impl MemoryStatistics {
398	/// Construct the record with its default state.
399	#[inline]
400	#[must_use]
401	pub fn new() -> Self {
402		Self::default()
403	}
404
405	/// Set [`total`](Self::total) and return the updated record.
406	#[inline]
407	#[must_use]
408	pub fn set_total(mut self, value: DetailedStatistics) -> Self {
409		self.total = value;
410		self
411	}
412
413	/// Set [`memory_types`](Self::memory_types) and return the updated record.
414	#[inline]
415	#[must_use]
416	pub fn set_memory_types(mut self, value: [DetailedStatistics; 32]) -> Self {
417		self.memory_types = value;
418		self
419	}
420
421	/// Set [`heaps`](Self::heaps) and return the updated record.
422	#[inline]
423	#[must_use]
424	pub fn set_heaps(mut self, value: [DetailedStatistics; 16]) -> Self {
425		self.heaps = value;
426		self
427	}
428}
429
430impl DetailedStatistics {
431	/// Construct the record with its default state.
432	#[inline]
433	#[must_use]
434	pub fn new() -> Self {
435		Self::default()
436	}
437
438	/// Set [`native_count`](Self::native_count) and return the updated record.
439	#[inline]
440	#[must_use]
441	pub fn set_native_count(mut self, value: usize) -> Self {
442		self.native_count = value;
443		self
444	}
445
446	/// Set [`native_bytes`](Self::native_bytes) and return the updated record.
447	#[inline]
448	#[must_use]
449	pub fn set_native_bytes(mut self, value: u64) -> Self {
450		self.native_bytes = value;
451		self
452	}
453
454	/// Set [`allocation_count`](Self::allocation_count) and return the updated record.
455	#[inline]
456	#[must_use]
457	pub fn set_allocation_count(mut self, value: usize) -> Self {
458		self.allocation_count = value;
459		self
460	}
461
462	/// Set [`allocation_bytes`](Self::allocation_bytes) and return the updated record.
463	#[inline]
464	#[must_use]
465	pub fn set_allocation_bytes(mut self, value: u64) -> Self {
466		self.allocation_bytes = value;
467		self
468	}
469
470	/// Set [`reserved_bytes`](Self::reserved_bytes) and return the updated record.
471	#[inline]
472	#[must_use]
473	pub fn set_reserved_bytes(mut self, value: u64) -> Self {
474		self.reserved_bytes = value;
475		self
476	}
477
478	/// Set [`free_range_count`](Self::free_range_count) and return the updated record.
479	#[inline]
480	#[must_use]
481	pub fn set_free_range_count(mut self, value: usize) -> Self {
482		self.free_range_count = value;
483		self
484	}
485
486	/// Set [`free_bytes`](Self::free_bytes) and return the updated record.
487	#[inline]
488	#[must_use]
489	pub fn set_free_bytes(mut self, value: u64) -> Self {
490		self.free_bytes = value;
491		self
492	}
493
494	/// Set [`allocation_size_min`](Self::allocation_size_min) and return the updated record.
495	#[inline]
496	#[must_use]
497	pub fn set_allocation_size_min(mut self, value: Option<u64>) -> Self {
498		self.allocation_size_min = value;
499		self
500	}
501
502	/// Set [`allocation_size_max`](Self::allocation_size_max) and return the updated record.
503	#[inline]
504	#[must_use]
505	pub fn set_allocation_size_max(mut self, value: Option<u64>) -> Self {
506		self.allocation_size_max = value;
507		self
508	}
509
510	/// Set [`free_range_size_min`](Self::free_range_size_min) and return the updated record.
511	#[inline]
512	#[must_use]
513	pub fn set_free_range_size_min(mut self, value: Option<u64>) -> Self {
514		self.free_range_size_min = value;
515		self
516	}
517
518	/// Set [`free_range_size_max`](Self::free_range_size_max) and return the updated record.
519	#[inline]
520	#[must_use]
521	pub fn set_free_range_size_max(mut self, value: Option<u64>) -> Self {
522		self.free_range_size_max = value;
523		self
524	}
525
526	fn include(&mut self, other: Self) {
527		self.native_count += other.native_count;
528		self.native_bytes += other.native_bytes;
529		self.allocation_count += other.allocation_count;
530		self.allocation_bytes += other.allocation_bytes;
531		self.reserved_bytes += other.reserved_bytes;
532		self.free_range_count += other.free_range_count;
533		self.free_bytes += other.free_bytes;
534		for (target, value, minimum) in [
535			(
536				&mut self.allocation_size_min,
537				other.allocation_size_min,
538				true,
539			),
540			(
541				&mut self.allocation_size_max,
542				other.allocation_size_max,
543				false,
544			),
545			(
546				&mut self.free_range_size_min,
547				other.free_range_size_min,
548				true,
549			),
550			(
551				&mut self.free_range_size_max,
552				other.free_range_size_max,
553				false,
554			),
555		] {
556			if let Some(value) = value {
557				*target = Some(target.map_or(value, |old| {
558					if minimum {
559						old.min(value)
560					} else {
561						old.max(value)
562					}
563				}));
564			}
565		}
566	}
567}
568
569/// Walk actual native blocks and live regions to build detailed local statistics.
570///
571/// Linear free holes are physical space, not a promise of immediate reuse.
572/// Native block bytes, requested bytes and reserved atom padding stay distinct.
573/// No driver query, map, native allocation or GPU synchronization occurs.
574/// # Errors
575/// Closed allocators are rejected.
576pub fn detailed_statistics(allocator: &Allocator<'_>) -> Result<MemoryStatistics, AllocError> {
577	collect_statistics(allocator, None)
578}
579
580/// Walk actual blocks/live regions belonging to one checked custom pool.
581pub fn detailed_pool_statistics(
582	allocator: &Allocator<'_>,
583	pool: &Pool,
584) -> Result<DetailedStatistics, AllocError> {
585	pool::validate(allocator, pool)?;
586	Ok(collect_statistics(allocator, Some(pool.serial))?.total)
587}
588
589fn collect_statistics(
590	allocator: &Allocator<'_>,
591	pool_filter: Option<u64>,
592) -> Result<MemoryStatistics, AllocError> {
593	allocator.ensure_open()?;
594	let mut result = MemoryStatistics::new();
595	for block in &allocator.blocks {
596		if pool_filter.is_some() && block.pool != pool_filter {
597			continue;
598		}
599		let stats = &mut result.memory_types[block.memory_type_index as usize];
600		stats.native_count += 1;
601		stats.native_bytes += block.size;
602		block.metadata.for_each_free(|_, size| {
603			stats.free_range_count += 1;
604			stats.free_bytes += size;
605			stats.free_range_size_min = Some(stats.free_range_size_min.map_or(size, |old| old.min(size)));
606			stats.free_range_size_max = Some(stats.free_range_size_max.map_or(size, |old| old.max(size)));
607		});
608	}
609	for live in allocator.live.values() {
610		if pool_filter.is_some() && live.pool != pool_filter {
611			continue;
612		}
613		let stats = &mut result.memory_types[live.memory_type_index as usize];
614		if live.placement.is_none() {
615			stats.native_count += 1;
616			stats.native_bytes += live.size;
617		}
618		stats.allocation_count += 1;
619		stats.allocation_bytes += live.size;
620		stats.reserved_bytes += live.reserved_size;
621		stats.allocation_size_min = Some(
622			stats
623				.allocation_size_min
624				.map_or(live.size, |old| old.min(live.size)),
625		);
626		stats.allocation_size_max = Some(
627			stats
628				.allocation_size_max
629				.map_or(live.size, |old| old.max(live.size)),
630		);
631	}
632	for index in 0..allocator.selector.properties.memory_type_count as usize {
633		let stats = result.memory_types[index];
634		result.total.include(stats);
635		result.heaps[allocator.selector.properties.memory_types[index].heap_index as usize]
636			.include(stats);
637	}
638	Ok(result)
639}
640
641/// Write ordered local allocator diagnostics as `vk.memory.statistics.v1` JSON.
642///
643/// Detailed mode includes live allocation identifiers, offsets, names, user data
644/// and per-block free spans. No driver budget query, map or synchronization is hidden.
645/// Output can be partial on writer failure; names are copied/escaped as UTF-8 JSON.
646/// # Errors
647/// Returns closed/host-reservation errors before output, or writer kind/OS code on failure.
648pub fn write_statistics_json(
649	allocator: &Allocator<'_>,
650	output: &mut impl std::io::Write,
651	detailed: bool,
652) -> Result<(), AllocError> {
653	let stats = detailed_statistics(allocator)?;
654	let mut allocations = HostVec::new(allocator.host);
655	if detailed {
656		allocations
657			.try_reserve_exact(allocator.live.len())
658			.map_err(|_| AllocError::HostAllocationFailed)?;
659		for entry in allocator.live.iter() {
660			allocations.push_reserved(entry);
661		}
662		allocations.sort_unstable_by_key(|(serial, _)| **serial);
663	}
664	let mut pools = HostVec::new(allocator.host);
665	pools
666		.try_reserve_exact(allocator.pools.len())
667		.map_err(|_| AllocError::HostAllocationFailed)?;
668	for entry in allocator.pools.iter() {
669		pools.push_reserved(entry);
670	}
671	pools.sort_unstable_by_key(|(serial, _)| **serial);
672	fn quote(out: &mut impl std::io::Write, text: &str) -> std::io::Result<()> {
673		out.write_all(b"\"")?;
674		for value in text.chars() {
675			match value {
676				'"' => out.write_all(b"\\\"")?,
677				'\\' => out.write_all(b"\\\\")?,
678				value if value <= '\u{1f}' => write!(out, "\\u{:04x}", u32::from(value))?,
679				value => {
680					let mut bytes = [0; 4];
681					out.write_all(value.encode_utf8(&mut bytes).as_bytes())?;
682				}
683			}
684		}
685		out.write_all(b"\"")
686	}
687	fn number(out: &mut impl std::io::Write, value: Option<u64>) -> std::io::Result<()> {
688		if let Some(value) = value {
689			write!(out, "{value}")
690		} else {
691			out.write_all(b"null")
692		}
693	}
694	fn statistics(out: &mut impl std::io::Write, stats: &DetailedStatistics) -> std::io::Result<()> {
695		write!(
696			out,
697			"{{\"native_count\":{},\"native_bytes\":{},\"allocation_count\":{},\"allocation_bytes\":{},\"reserved_bytes\":{},\"free_range_count\":{},\"free_bytes\":{},\"allocation_size_min\":",
698			stats.native_count,
699			stats.native_bytes,
700			stats.allocation_count,
701			stats.allocation_bytes,
702			stats.reserved_bytes,
703			stats.free_range_count,
704			stats.free_bytes
705		)?;
706		number(out, stats.allocation_size_min)?;
707		out.write_all(b",\"allocation_size_max\":")?;
708		number(out, stats.allocation_size_max)?;
709		out.write_all(b",\"free_range_size_min\":")?;
710		number(out, stats.free_range_size_min)?;
711		out.write_all(b",\"free_range_size_max\":")?;
712		number(out, stats.free_range_size_max)?;
713		out.write_all(b"}")
714	}
715	let written = (|| -> std::io::Result<()> {
716		output.write_all(b"{\"schema\":\"vk.memory.statistics.v1\",\"total\":")?;
717		statistics(output, &stats.total)?;
718		output.write_all(b",\"memory_types\":[")?;
719		for index in 0..allocator.selector.properties.memory_type_count as usize {
720			if index != 0 {
721				output.write_all(b",")?;
722			}
723			statistics(output, &stats.memory_types[index])?;
724		}
725		output.write_all(b"],\"heaps\":[")?;
726		for index in 0..allocator.selector.properties.memory_heap_count as usize {
727			if index != 0 {
728				output.write_all(b",")?;
729			}
730			statistics(output, &stats.heaps[index])?;
731		}
732		output.write_all(b"],\"pools\":[")?;
733		for (index, (serial, pool)) in pools.iter().enumerate() {
734			if index != 0 {
735				output.write_all(b",")?;
736			}
737			write!(
738				output,
739				"{{\"id\":{serial},\"memory_type\":{},\"name\":",
740				pool.info.memory_type_index
741			)?;
742			if let Some(name) = &pool.name {
743				quote(output, name)?;
744			} else {
745				output.write_all(b"null")?;
746			}
747			output.write_all(b"}")?;
748		}
749		output.write_all(b"]")?;
750		if detailed {
751			output.write_all(b",\"allocations\":[")?;
752			for (index, (serial, live)) in allocations.iter().enumerate() {
753				if index != 0 {
754					output.write_all(b",")?;
755				}
756				write!(
757					output,
758					"{{\"id\":{serial},\"offset\":{},\"size\":{},\"reserved_size\":{},\"memory_type\":{},\"user_data\":{},\"map_count\":{},\"persistent\":{},\"pinned\":{},\"name\":",
759					live.offset,
760					live.size,
761					live.reserved_size,
762					live.memory_type_index,
763					live.user_data,
764					live.map_count,
765					live.persistent,
766					live.pinned
767				)?;
768				if let Some(name) = &live.name {
769					quote(output, name)?;
770				} else {
771					output.write_all(b"null")?;
772				}
773				output.write_all(b"}")?;
774			}
775			output.write_all(b"],\"blocks\":[")?;
776			for (index, block) in allocator.blocks.iter().enumerate() {
777				if index != 0 {
778					output.write_all(b",")?;
779				}
780				write!(
781					output,
782					"{{\"memory_type\":{},\"size\":{},\"free_ranges\":[",
783					block.memory_type_index, block.size
784				)?;
785				let mut first = true;
786				let mut error = None;
787				block.metadata.for_each_free(|offset, size| {
788					if error.is_some() {
789						return;
790					}
791					let result = (|| {
792						if !first {
793							output.write_all(b",")?;
794						}
795						first = false;
796						write!(output, "[{offset},{size}]")
797					})();
798					if let Err(failure) = result {
799						error = Some(failure);
800					}
801				});
802				if let Some(error) = error {
803					return Err(error);
804				}
805				output.write_all(b"]}")?;
806			}
807			output.write_all(b"]")?;
808		}
809		output.write_all(b"}\n")
810	})();
811	written.map_err(|error| AllocError::Output {
812		kind: error.kind(),
813		code: error.raw_os_error(),
814	})
815}
816
817impl Allocator<'_> {
818	/// Calculate detailed checked custom-pool native/live/free-span diagnostics.
819	pub fn detailed_pool_statistics(&self, pool: &Pool) -> Result<DetailedStatistics, AllocError> {
820		detailed_pool_statistics(self, pool)
821	}
822
823	/// Stream explicit local JSON diagnostics; detailed mode includes allocations/free spans.
824	pub fn write_statistics_json(
825		&self,
826		output: &mut impl std::io::Write,
827		detailed: bool,
828	) -> Result<(), AllocError> {
829		write_statistics_json(self, output, detailed)
830	}
831
832	/// Calculate explicit per-type/per-heap byte, count, free-span and extent diagnostics.
833	pub fn detailed_statistics(&self) -> Result<MemoryStatistics, AllocError> {
834		detailed_statistics(self)
835	}
836
837	/// Allocate a transactional batch of ordinary raw requirement pages.
838	/// # Safety
839	/// Same requirements/feature/release contract as `mem::allocate_many`.
840	pub unsafe fn allocate_many(
841		&mut self,
842		requirements: &vk::MemoryRequirements,
843		options: &AllocationCreateInfo,
844		count: usize,
845	) -> Result<Vec<Allocation>, AllocError> {
846		// SAFETY: same caller guarantees as the canonical operation.
847		unsafe { allocate_many(self, requirements, options, count) }
848	}
849
850	/// Release a fully prevalidated batch after explicit native resource destruction.
851	/// # Safety
852	/// Same completion/ownership contract as `mem::free_many`.
853	pub unsafe fn free_many(&mut self, allocations: &mut [Allocation]) -> Result<(), AllocError> {
854		// SAFETY: same caller guarantees as the canonical operation.
855		unsafe { free_many(self, allocations) }
856	}
857
858	/// Flush a batch of allocation-relative mapped ranges in one native call.
859	/// # Safety
860	/// Same host/GPU visibility contract as `mem::flush_many`.
861	pub unsafe fn flush_many(&self, ranges: &[CacheRange<'_>]) -> Result<(), AllocError> {
862		// SAFETY: same caller guarantees as the canonical operation.
863		unsafe { flush_many(self, ranges) }
864	}
865
866	/// Invalidate a batch after completion of device writes.
867	/// # Safety
868	/// Same host/GPU visibility contract as `mem::invalidate_many`.
869	pub unsafe fn invalidate_many(&self, ranges: &[CacheRange<'_>]) -> Result<(), AllocError> {
870		// SAFETY: same caller guarantees as the canonical operation.
871		unsafe { invalidate_many(self, ranges) }
872	}
873
874	/// Copy host bytes, flush and balance the helper's explicit map hold.
875	/// # Safety
876	/// Same pointer/access/layout contract as `mem::copy_to_allocation`.
877	pub unsafe fn copy_to_allocation(
878		&mut self,
879		allocation: &Allocation,
880		offset: u64,
881		source: &[u8],
882	) -> Result<(), AllocError> {
883		// SAFETY: same caller guarantees as the canonical operation.
884		unsafe { copy_to_allocation(self, allocation, offset, source) }
885	}
886
887	/// Invalidate and copy bytes to exclusive writable host storage.
888	/// # Safety
889	/// Same completion/initialization contract as `mem::copy_from_allocation`.
890	pub unsafe fn copy_from_allocation(
891		&mut self,
892		allocation: &Allocation,
893		offset: u64,
894		destination: &mut [u8],
895	) -> Result<(), AllocError> {
896		// SAFETY: same caller guarantees as the canonical operation.
897		unsafe { copy_from_allocation(self, allocation, offset, destination) }
898	}
899
900	/// Borrow an allocation's checked diagnostic name.
901	pub fn allocation_name<'a>(
902		&'a self,
903		allocation: &Allocation,
904	) -> Result<Option<&'a str>, AllocError> {
905		allocation_name(self, allocation)
906	}
907
908	/// Replace a copied name; None clears it.
909	pub fn set_allocation_name(
910		&mut self,
911		allocation: &Allocation,
912		name: Option<&str>,
913	) -> Result<(), AllocError> {
914		set_allocation_name(self, allocation, name)
915	}
916
917	/// Set opaque consumer metadata, with no ownership or dereference.
918	pub fn set_allocation_user_data(
919		&mut self,
920		allocation: &Allocation,
921		data: usize,
922	) -> Result<(), AllocError> {
923		set_allocation_user_data(self, allocation, data)
924	}
925
926	/// Borrow a checked pool's diagnostic name.
927	pub fn pool_name<'a>(&'a self, pool: &Pool) -> Result<Option<&'a str>, AllocError> {
928		pool_name(self, pool)
929	}
930
931	/// Replace a copied pool name; None clears it.
932	pub fn set_pool_name(&mut self, pool: &Pool, name: Option<&str>) -> Result<(), AllocError> {
933		set_pool_name(self, pool, name)
934	}
935}
936
937/// Create an ordinary alias buffer at a checked allocation-local offset.
938///
939/// Backing storage must have `can_alias`; no new memory or allocation token is
940/// created. Aliases are caller-owned: destroy every alias before freeing storage.
941/// Original resource association and binding state remain unchanged.
942/// # Errors
943/// Rejects non-aliasable/foreign/stale storage, invalid requirements/offset/type,
944/// required native dedication, unavailable PFNs or native creation/binding failure.
945/// # Safety
946/// Follow Vulkan aliasing, barrier, native resource/feature and callback rules.
947/// Do not access overlapping aliases concurrently. Retain all native lifetimes.
948/// Use `allocation_callbacks()` for matching explicit native alias destruction.
949pub unsafe fn create_aliasing_buffer(
950	allocator: &mut Allocator<'_>,
951	allocation: &Allocation,
952	local_offset: u64,
953	info: &vk::BufferCreateInfo,
954) -> Result<vk::Buffer, AllocError> {
955	if !allocator.validate(allocation)?.can_alias {
956		return Err(AllocError::UnsupportedCapability(
957			"allocation was not created for aliasing",
958		));
959	}
960	// SAFETY: caller supplies valid ordinary native creation records/chains.
961	unsafe { request::validate_buffer(allocator, info) }?;
962	// SAFETY: the preceding validation established bounded readable resource chains.
963	let required_exports = unsafe { external::required_resource_types(info.p_next) };
964	if !allocator.validate(allocation)?.imported
965		&& external::exports(allocator, allocation)?.as_raw() & required_exports.as_raw()
966			!= required_exports.as_raw()
967	{
968		return Err(AllocError::InvalidRequest(
969			"alias resource export types do not match backing memory",
970		));
971	}
972	require(
973		allocator.device.fp().create_buffer.is_some(),
974		"vkCreateBuffer",
975	)?;
976	require(
977		allocator.device.fp().destroy_buffer.is_some(),
978		"vkDestroyBuffer",
979	)?;
980	// SAFETY: validated ordinary create semantics and retained callback lifetime; inverse preflighted.
981	let buffer = unsafe { allocator.device.create_buffer(info, allocator.callbacks) }?;
982	if buffer.is_null() || binding::associated(allocator, Resource::Buffer(buffer)) {
983		return Err(CallError::InvalidOutput("alias buffer is null or already live").into());
984	}
985	// SAFETY: new unbound caller-owned alias and explicitly conservative aliasable storage.
986	if let Err(error) = unsafe {
987		binding::bind_alias(
988			allocator,
989			Resource::Buffer(buffer),
990			allocation,
991			local_offset,
992		)
993	} {
994		// SAFETY: only the new unpublished alias exists; no GPU use or memory ownership is affected.
995		unsafe { allocator.device.destroy_buffer(buffer, allocator.callbacks) }
996			.expect("preflighted alias cleanup");
997		return Err(error);
998	}
999	Ok(buffer)
1000}
1001
1002/// Create a caller-owned ordinary alias image without allocating new native memory.
1003/// # Errors
1004/// Same identity/requirements/binding errors as `create_aliasing_buffer`;
1005/// requires explicit ALIAS_BIT image creation semantics.
1006/// # Safety
1007/// Native image alias/layout/barrier and enabled-feature valid usage apply.
1008/// Destroy all aliases with matching callbacks before releasing backing storage.
1009pub unsafe fn create_aliasing_image(
1010	allocator: &mut Allocator<'_>,
1011	allocation: &Allocation,
1012	local_offset: u64,
1013	info: &vk::ImageCreateInfo,
1014) -> Result<vk::Image, AllocError> {
1015	if !allocator.validate(allocation)?.can_alias {
1016		return Err(AllocError::UnsupportedCapability(
1017			"allocation was not created for aliasing",
1018		));
1019	}
1020	if info.flags.as_raw() & vk::ImageCreateFlagBits::ALIAS_BIT.as_raw() == 0 {
1021		return Err(AllocError::InvalidRequest("alias images require ALIAS_BIT"));
1022	}
1023	// SAFETY: caller supplies valid ordinary alias image records/chains.
1024	unsafe { request::validate_image(allocator, info) }?;
1025	// SAFETY: the preceding validation established bounded readable resource chains.
1026	let required_exports = unsafe { external::required_resource_types(info.p_next) };
1027	if !allocator.validate(allocation)?.imported
1028		&& external::exports(allocator, allocation)?.as_raw() & required_exports.as_raw()
1029			!= required_exports.as_raw()
1030	{
1031		return Err(AllocError::InvalidRequest(
1032			"alias resource export types do not match backing memory",
1033		));
1034	}
1035	require(
1036		allocator.device.fp().create_image.is_some(),
1037		"vkCreateImage",
1038	)?;
1039	require(
1040		allocator.device.fp().destroy_image.is_some(),
1041		"vkDestroyImage",
1042	)?;
1043	// SAFETY: validated ordinary image alias and retained callbacks, inverse preflighted.
1044	let image = unsafe { allocator.device.create_image(info, allocator.callbacks) }?;
1045	if image.is_null() || binding::associated(allocator, Resource::Image(image)) {
1046		return Err(CallError::InvalidOutput("alias image is null or already live").into());
1047	}
1048	// SAFETY: same device, conservative aliasable storage; no new native memory ownership.
1049	if let Err(error) =
1050		unsafe { binding::bind_alias(allocator, Resource::Image(image), allocation, local_offset) }
1051	{
1052		// SAFETY: new unpublished alias only; inverse and callback lifetime preflighted.
1053		unsafe { allocator.device.destroy_image(image, allocator.callbacks) }
1054			.expect("preflighted alias cleanup");
1055		return Err(error);
1056	}
1057	Ok(image)
1058}
1059
1060impl Allocator<'_> {
1061	/// Borrow this allocator's retained native callbacks for matching explicit resource cleanup.
1062	pub fn allocation_callbacks(&self) -> Option<&vk::AllocationCallbacks> {
1063		self.callbacks
1064	}
1065
1066	/// Create and bind a caller-owned buffer alias without allocating memory.
1067	/// # Safety
1068	/// Same alias/barrier/feature/lifetime contract as `mem::create_aliasing_buffer`.
1069	pub unsafe fn create_aliasing_buffer(
1070		&mut self,
1071		allocation: &Allocation,
1072		local_offset: u64,
1073		info: &vk::BufferCreateInfo,
1074	) -> Result<vk::Buffer, AllocError> {
1075		// SAFETY: caller provides the canonical alias and resource valid-usage guarantees.
1076		unsafe { create_aliasing_buffer(self, allocation, local_offset, info) }
1077	}
1078
1079	/// Create and bind a caller-owned image alias without allocating memory.
1080	/// # Safety
1081	/// Same native alias/layout/barrier contract as `mem::create_aliasing_image`.
1082	pub unsafe fn create_aliasing_image(
1083		&mut self,
1084		allocation: &Allocation,
1085		local_offset: u64,
1086		info: &vk::ImageCreateInfo,
1087	) -> Result<vk::Image, AllocError> {
1088		// SAFETY: caller provides the canonical image alias valid-usage guarantees.
1089		unsafe { create_aliasing_image(self, allocation, local_offset, info) }
1090	}
1091}
1092
1093/// Observe captured native identities without inferring manually omitted parent metadata.
1094pub fn allocator_info(
1095	allocator: &Allocator<'_>,
1096) -> Result<super::super::AllocatorInfo, AllocError> {
1097	allocator.ensure_open()?;
1098	Ok(super::super::AllocatorInfo {
1099		device: allocator.device.handle(),
1100		instance: allocator.instance_handle,
1101		physical_device: allocator.physical_device,
1102		api_version: allocator
1103			.device
1104			.as_device()
1105			.capabilities()
1106			.map(|caps| caps.api_version()),
1107	})
1108}
1109
1110/// Borrow validated native memory metadata used by this allocator's selection policy.
1111pub fn memory_properties<'a>(
1112	allocator: &'a Allocator<'_>,
1113) -> Result<&'a vk::PhysicalDeviceMemoryProperties, AllocError> {
1114	allocator.ensure_open()?;
1115	Ok(&allocator.selector.properties)
1116}
1117
1118/// Borrow queried physical-device properties, or None after manual metadata construction.
1119pub fn physical_device_properties<'a>(
1120	allocator: &'a Allocator<'_>,
1121) -> Result<Option<&'a vk::PhysicalDeviceProperties>, AllocError> {
1122	allocator.ensure_open()?;
1123	Ok(allocator.physical_properties.as_ref())
1124}
1125
1126/// Observe one checked native memory type's actual property flags.
1127pub fn memory_type_properties(
1128	allocator: &Allocator<'_>,
1129	index: u32,
1130) -> Result<vk::MemoryPropertyFlags, AllocError> {
1131	allocator.ensure_open()?;
1132	if index >= allocator.selector.properties.memory_type_count {
1133		return Err(AllocError::InvalidRequest("absent memory type"));
1134	}
1135	Ok(allocator.selector.properties.memory_types[index as usize].property_flags)
1136}
1137
1138impl<'device> Allocator<'device> {
1139	/// Register borrowed informative callbacks before acquiring native memory/pools.
1140	/// # Errors
1141	/// Rejects closed allocators or any existing native allocation/pool domain.
1142	/// # Safety
1143	/// User data must remain live for the callback registration. Functions must not
1144	/// unwind, reenter this allocator or violate their host thread/synchronization contract.
1145	pub unsafe fn set_device_memory_callbacks(
1146		&mut self,
1147		callbacks: Option<&'device super::super::DeviceMemoryCallbacks>,
1148	) -> Result<(), AllocError> {
1149		self.ensure_open()?;
1150		self.ensure_global()?;
1151		if self.native_count != 0 || !self.live.is_empty() {
1152			return Err(AllocError::LiveAllocations);
1153		}
1154		if !self.pools.is_empty() {
1155			return Err(AllocError::LivePools);
1156		}
1157		self.device_memory_callbacks = callbacks;
1158		Ok(())
1159	}
1160
1161	/// Query retained native identity metadata, with explicit None for unknown manual parents.
1162	pub fn info(&self) -> Result<super::super::AllocatorInfo, AllocError> {
1163		allocator_info(self)
1164	}
1165
1166	/// Borrow this allocator's validated physical memory properties.
1167	pub fn memory_properties(&self) -> Result<&vk::PhysicalDeviceMemoryProperties, AllocError> {
1168		memory_properties(self)
1169	}
1170
1171	/// Borrow queried physical-device properties if the construction route captured them.
1172	pub fn physical_device_properties(
1173		&self,
1174	) -> Result<Option<&vk::PhysicalDeviceProperties>, AllocError> {
1175		physical_device_properties(self)
1176	}
1177
1178	/// Query one validated native memory type's property flags.
1179	pub fn memory_type_properties(&self, index: u32) -> Result<vk::MemoryPropertyFlags, AllocError> {
1180		memory_type_properties(self, index)
1181	}
1182}