Skip to main content

vk/mem/
virtual_block.rs

1//! Host-only region placement over an integer byte range.
2//!
3//! No Vulkan memory, buffers, mapping, synchronization or implicit retirement is
4//! provided. Consumers own the validity/completion of any resource using these
5//! offsets. Tokens are explicit: dropping one does not release its region.
6//!
7//! ```
8//! use vk::mem::{PlacementStrategy, VirtualBlock};
9//! let mut block = VirtualBlock::new(4096)?;
10//! let mut region = block.allocate(64, 16, PlacementStrategy::Default)?;
11//! assert_eq!(block.allocation_info(&region)?.offset, 0);
12//! block.free(&mut region)?;
13//! # Ok::<(), vk::AllocError>(())
14//! ```
15
16use super::{
17	AllocError, AllocationAlgorithm,
18	host::{HostMemory, HostVec, Identity},
19	metadata::{Metadata, Placement},
20};
21use crate as vk;
22
23/// VMA virtual-TLSF search order; these policies are not latency guarantees.
24#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub enum PlacementStrategy {
26	#[default]
27	Default,
28	MinMemory,
29	MinTime,
30	MinOffset,
31}
32
33/// Generation-checked identity in exactly one virtual block; no implicit free.
34pub struct VirtualAllocation {
35	owner: Identity,
36	placement: Option<Placement>,
37}
38
39/// Placement snapshot; valid only while the matching allocation remains live.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct VirtualAllocationInfo {
42	/// Uninterpreted consumer metadata; no ownership or dereference is inferred.
43	pub user_data: usize,
44	pub offset: u64,
45	pub size: u64,
46}
47
48impl VirtualAllocationInfo {
49	/// Set [`user_data`](Self::user_data) and return the updated record.
50	#[inline]
51	#[must_use]
52	pub fn set_user_data(mut self, value: usize) -> Self {
53		self.user_data = value;
54		self
55	}
56
57	/// Set [`offset`](Self::offset) and return the updated record.
58	#[inline]
59	#[must_use]
60	pub fn set_offset(mut self, value: u64) -> Self {
61		self.offset = value;
62		self
63	}
64
65	/// Set [`size`](Self::size) and return the updated record.
66	#[inline]
67	#[must_use]
68	pub fn set_size(mut self, value: u64) -> Self {
69		self.size = value;
70		self
71	}
72}
73
74/// Host metadata statistics. Alignment holes remain free, not reserved to a token.
75/// Linear free spans can include holes that placement cannot immediately reuse.
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct VirtualBlockStats {
78	pub size: u64,
79	pub allocation_count: usize,
80	pub allocated_bytes: u64,
81	pub free_bytes: u64,
82	pub free_region_count: usize,
83	pub largest_free_region: u64,
84}
85
86impl VirtualBlockStats {
87	/// Set [`size`](Self::size) and return the updated record.
88	#[inline]
89	#[must_use]
90	pub fn set_size(mut self, value: u64) -> Self {
91		self.size = value;
92		self
93	}
94
95	/// Set [`allocation_count`](Self::allocation_count) and return the updated record.
96	#[inline]
97	#[must_use]
98	pub fn set_allocation_count(mut self, value: usize) -> Self {
99		self.allocation_count = value;
100		self
101	}
102
103	/// Set [`allocated_bytes`](Self::allocated_bytes) and return the updated record.
104	#[inline]
105	#[must_use]
106	pub fn set_allocated_bytes(mut self, value: u64) -> Self {
107		self.allocated_bytes = value;
108		self
109	}
110
111	/// Set [`free_bytes`](Self::free_bytes) and return the updated record.
112	#[inline]
113	#[must_use]
114	pub fn set_free_bytes(mut self, value: u64) -> Self {
115		self.free_bytes = value;
116		self
117	}
118
119	/// Set [`free_region_count`](Self::free_region_count) and return the updated record.
120	#[inline]
121	#[must_use]
122	pub fn set_free_region_count(mut self, value: usize) -> Self {
123		self.free_region_count = value;
124		self
125	}
126
127	/// Set [`largest_free_region`](Self::largest_free_region) and return the updated record.
128	#[inline]
129	#[must_use]
130	pub fn set_largest_free_region(mut self, value: u64) -> Self {
131		self.largest_free_region = value;
132		self
133	}
134}
135
136/// Virtual TLSF or linear allocator; owns host metadata and no native objects.
137///
138/// Mutations require exclusive access. Drop releases host metadata only.
139/// Callback construction borrows its allocation/free record through Drop; raw
140/// callback user data remains the unsafe constructor caller's responsibility.
141/// This is not a Vulkan memory block or a pool with GPU lifetime tracking.
142pub struct VirtualBlock<'callbacks> {
143	owner: Identity,
144	metadata: Metadata<'callbacks>,
145}
146
147/// Explicit virtual placement diagnostics; physical holes may not be immediately reusable by linear placement.
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149pub struct VirtualBlockDetails {
150	pub statistics: VirtualBlockStats,
151	pub allocation_size_min: Option<u64>,
152	pub allocation_size_max: Option<u64>,
153	pub free_range_size_min: Option<u64>,
154	pub free_range_size_max: Option<u64>,
155}
156
157impl VirtualBlockDetails {
158	/// Set [`statistics`](Self::statistics) and return the updated record.
159	#[inline]
160	#[must_use]
161	pub fn set_statistics(mut self, value: VirtualBlockStats) -> Self {
162		self.statistics = value;
163		self
164	}
165
166	/// Set [`allocation_size_min`](Self::allocation_size_min) and return the updated record.
167	#[inline]
168	#[must_use]
169	pub fn set_allocation_size_min(mut self, value: Option<u64>) -> Self {
170		self.allocation_size_min = value;
171		self
172	}
173
174	/// Set [`allocation_size_max`](Self::allocation_size_max) and return the updated record.
175	#[inline]
176	#[must_use]
177	pub fn set_allocation_size_max(mut self, value: Option<u64>) -> Self {
178		self.allocation_size_max = value;
179		self
180	}
181
182	/// Set [`free_range_size_min`](Self::free_range_size_min) and return the updated record.
183	#[inline]
184	#[must_use]
185	pub fn set_free_range_size_min(mut self, value: Option<u64>) -> Self {
186		self.free_range_size_min = value;
187		self
188	}
189
190	/// Set [`free_range_size_max`](Self::free_range_size_max) and return the updated record.
191	#[inline]
192	#[must_use]
193	pub fn set_free_range_size_max(mut self, value: Option<u64>) -> Self {
194		self.free_range_size_max = value;
195		self
196	}
197}
198
199/// Set checked per-region opaque metadata without native effects or ownership transfer.
200pub fn set_user_data(
201	block: &mut VirtualBlock,
202	allocation: &VirtualAllocation,
203	data: usize,
204) -> Result<(), AllocError> {
205	allocation_info(block, allocation)?;
206	block.metadata.set_user_data(
207		allocation.placement.ok_or(AllocError::UnknownAllocation)?,
208		data,
209	)
210}
211
212/// Observe whether every virtual allocation has been explicitly released or cleared.
213pub fn is_empty(block: &VirtualBlock) -> bool {
214	block.metadata.is_empty()
215}
216
217/// Walk virtual placements/free spans for explicit extent statistics.
218pub fn detailed_statistics(block: &VirtualBlock) -> VirtualBlockDetails {
219	let mut result = VirtualBlockDetails {
220		statistics: statistics(block),
221		allocation_size_min: None,
222		allocation_size_max: None,
223		free_range_size_min: None,
224		free_range_size_max: None,
225	};
226	block.metadata.for_each_allocation(|_, size, _| {
227		result.allocation_size_min = Some(result.allocation_size_min.map_or(size, |old| old.min(size)));
228		result.allocation_size_max = Some(result.allocation_size_max.map_or(size, |old| old.max(size)));
229	});
230	block.metadata.for_each_free(|_, size| {
231		result.free_range_size_min = Some(result.free_range_size_min.map_or(size, |old| old.min(size)));
232		result.free_range_size_max = Some(result.free_range_size_max.map_or(size, |old| old.max(size)));
233	});
234	result
235}
236
237/// Write explicit virtual metadata as ordered `vk.memory.virtual.v1` JSON.
238/// # Errors
239/// Host reservation fails before output; writer errors retain their kind/OS code.
240pub fn write_statistics_json(
241	block: &VirtualBlock,
242	output: &mut impl std::io::Write,
243	detailed: bool,
244) -> Result<(), AllocError> {
245	let stats = statistics(block);
246	let mut allocations = HostVec::new(block.metadata.host_memory());
247	if detailed {
248		allocations
249			.try_reserve_exact(stats.allocation_count)
250			.map_err(|_| AllocError::HostAllocationFailed)?;
251		block
252			.metadata
253			.for_each_allocation(|offset, size, user_data| {
254				allocations.push_reserved((offset, size, user_data))
255			});
256		allocations.sort_unstable_by_key(|value| value.0);
257	}
258	let result = (|| -> std::io::Result<()> {
259		write!(
260			output,
261			"{{\"schema\":\"vk.memory.virtual.v1\",\"size\":{},\"allocation_count\":{},\"allocation_bytes\":{},\"free_bytes\":{},\"free_range_count\":{},\"largest_free_range\":{}",
262			stats.size,
263			stats.allocation_count,
264			stats.allocated_bytes,
265			stats.free_bytes,
266			stats.free_region_count,
267			stats.largest_free_region
268		)?;
269		if detailed {
270			output.write_all(b",\"allocations\":[")?;
271			for (index, (offset, size, data)) in allocations.iter().enumerate() {
272				if index != 0 {
273					output.write_all(b",")?;
274				}
275				write!(
276					output,
277					"{{\"offset\":{offset},\"size\":{size},\"user_data\":{data}}}"
278				)?;
279			}
280			output.write_all(b"],\"free_ranges\":[")?;
281			let mut first = true;
282			let mut error = None;
283			block.metadata.for_each_free(|offset, size| {
284				if error.is_some() {
285					return;
286				}
287				let result = (|| {
288					if !first {
289						output.write_all(b",")?;
290					}
291					first = false;
292					write!(output, "[{offset},{size}]")
293				})();
294				if let Err(failure) = result {
295					error = Some(failure);
296				}
297			});
298			if let Some(error) = error {
299				return Err(error);
300			}
301			output.write_all(b"]")?;
302		}
303		output.write_all(b"}\n")
304	})();
305	result.map_err(|error| AllocError::Output {
306		kind: error.kind(),
307		code: error.raw_os_error(),
308	})
309}
310
311impl<'callbacks> VirtualBlock<'callbacks> {
312	/// Observe checked virtual emptiness in constant time.
313	pub fn is_empty(&self) -> bool {
314		is_empty(self)
315	}
316
317	/// Set opaque per-allocation metadata, with no native pointer dereference or ownership.
318	pub fn set_user_data(
319		&mut self,
320		allocation: &VirtualAllocation,
321		data: usize,
322	) -> Result<(), AllocError> {
323		set_user_data(self, allocation, data)
324	}
325
326	/// Calculate explicit virtual allocation/free-span extrema.
327	pub fn detailed_statistics(&self) -> VirtualBlockDetails {
328		detailed_statistics(self)
329	}
330
331	/// Stream virtual JSON diagnostics; no native memory is accessed.
332	pub fn write_statistics_json(
333		&self,
334		output: &mut impl std::io::Write,
335		detailed: bool,
336	) -> Result<(), AllocError> {
337		write_statistics_json(self, output, detailed)
338	}
339
340	pub fn new(size: u64) -> Result<Self, AllocError> {
341		Self::new_with_algorithm(size, AllocationAlgorithm::Tlsf)
342	}
343
344	/// Create TLSF or linear host-only placement. No native storage is acquired.
345	///
346	/// ```
347	/// use vk::{VirtualBlock, mem::{AllocationAlgorithm, PlacementStrategy}};
348	/// let mut block = VirtualBlock::new_with_algorithm(4096, AllocationAlgorithm::Linear)?;
349	/// let mut lower = block.allocate(64, 16, PlacementStrategy::Default)?;
350	/// let mut upper = block.allocate_upper(64, 16)?;
351	/// assert_eq!(block.allocation_info(&lower)?.offset, 0);
352	/// assert_eq!(block.allocation_info(&upper)?.offset, 4032);
353	/// block.free(&mut upper)?;
354	/// block.free(&mut lower)?;
355	/// # Ok::<(), vk::AllocError>(())
356	/// ```
357	/// # Errors
358	/// Rejects a zero size or failed host metadata reservation.
359	pub fn new_with_algorithm(size: u64, algorithm: AllocationAlgorithm) -> Result<Self, AllocError> {
360		Ok(Self {
361			owner: Identity::new()?,
362			metadata: Metadata::new(size, algorithm, true)?,
363		})
364	}
365
366	/// Create host-only placement whose metadata uses borrowed Vulkan allocation callbacks.
367	///
368	/// TLSF/linear buffers, growth and detailed-JSON scratch storage use only this
369	/// allocation/free pair, with `SystemAllocationScope::OBJECT`. The block value
370	/// stays inline; tokens need no reference-count allocation. Reallocation and
371	/// driver internal-allocation notifications are not invoked. Drop releases
372	/// host metadata only; no Vulkan device or native memory is involved.
373	/// The callback record cannot expire while the block still owns storage:
374	/// ```compile_fail,E0597
375	/// # use vk::{VirtualBlock, mem::AllocationAlgorithm};
376	/// let mut block;
377	/// {
378	///     let callbacks = vk::AllocationCallbacks::new();
379	///     // SAFETY: demonstration of record lifetime; null functions are rejected at runtime.
380	///     block = unsafe {
381	///         VirtualBlock::new_with_callbacks(1024, AllocationAlgorithm::Tlsf, &callbacks)
382	///     }.unwrap();
383	/// }
384	/// block.clear();
385	/// ```
386	/// # Errors
387	/// Rejects zero size, a missing allocation/free function or failed host storage.
388	/// # Safety
389	/// Callbacks must return uniquely owned memory of the requested size/alignment,
390	/// or null on failure, and free only their own allocations. They must not unwind
391	/// or reenter this block. Their code and user data must remain valid through
392	/// block Drop and support calls from any thread, including concurrent independent
393	/// blocks. Borrowing retains the callback record, not ownership of its raw user data.
394	pub unsafe fn new_with_callbacks(
395		size: u64,
396		algorithm: AllocationAlgorithm,
397		callbacks: &'callbacks vk::AllocationCallbacks,
398	) -> Result<Self, AllocError> {
399		// SAFETY: caller establishes callback ownership/lifetime/thread contracts;
400		// this block's metadata retains the borrow through every buffer's Drop.
401		let host = unsafe { HostMemory::new_from_callbacks(callbacks) }?;
402		Ok(Self {
403			owner: Identity::new()?,
404			metadata: Metadata::new_with_host(size, algorithm, true, host)?,
405		})
406	}
407
408	/// Allocate an aligned region. Linear placement ignores the TLSF search strategy.
409	#[inline]
410	pub fn allocate(
411		&mut self,
412		size: u64,
413		alignment: u64,
414		strategy: PlacementStrategy,
415	) -> Result<VirtualAllocation, AllocError> {
416		allocate(self, size, alignment, strategy)
417	}
418
419	/// Allocate downward in a linear block. Active ring mode rejects this request.
420	/// # Errors
421	/// Rejects TLSF, invalid extents/alignment, exhausted space or host metadata.
422	pub fn allocate_upper(
423		&mut self,
424		size: u64,
425		alignment: u64,
426	) -> Result<VirtualAllocation, AllocError> {
427		allocate_upper(self, size, alignment)
428	}
429
430	/// Release a matching live identity. No native resource is destroyed or waited on.
431	#[inline]
432	pub fn free(&mut self, allocation: &mut VirtualAllocation) -> Result<(), AllocError> {
433		free(self, allocation)
434	}
435
436	/// Validate identity/generation and return the current offset/size snapshot.
437	pub fn allocation_info(
438		&self,
439		allocation: &VirtualAllocation,
440	) -> Result<VirtualAllocationInfo, AllocError> {
441		allocation_info(self, allocation)
442	}
443
444	/// Invalidate all tokens and restore the full range, retaining host capacity.
445	/// Users of previously returned offsets must stop using them before reuse.
446	pub fn clear(&mut self) {
447		clear(self)
448	}
449
450	/// Calculate detailed free-span statistics; this is not a constant-time hot path.
451	pub fn statistics(&self) -> VirtualBlockStats {
452		statistics(self)
453	}
454}
455
456/// Allocate a nonzero region with a nonzero power-of-two alignment.
457///
458/// Failure leaves placements and donor free-list ordering unchanged. Only host
459/// metadata is allocated; no Vulkan calls or memory mapping occur.
460#[inline]
461pub fn allocate(
462	block: &mut VirtualBlock,
463	size: u64,
464	alignment: u64,
465	strategy: PlacementStrategy,
466) -> Result<VirtualAllocation, AllocError> {
467	let placement = block.metadata.allocate(size, alignment, strategy, false)?;
468	Ok(VirtualAllocation {
469		owner: block.owner,
470		placement: Some(placement),
471	})
472}
473
474/// Allocate downward from the upper end of a linear block.
475/// # Errors
476/// Rejects TLSF, active ring mode, invalid extents/alignment and exhausted space/metadata.
477pub fn allocate_upper(
478	block: &mut VirtualBlock,
479	size: u64,
480	alignment: u64,
481) -> Result<VirtualAllocation, AllocError> {
482	let placement = block
483		.metadata
484		.allocate(size, alignment, PlacementStrategy::Default, true)?;
485	Ok(VirtualAllocation {
486		owner: block.owner,
487		placement: Some(placement),
488	})
489}
490
491/// Reject foreign, freed or stale tokens before reading placement metadata.
492pub fn allocation_info(
493	block: &VirtualBlock,
494	allocation: &VirtualAllocation,
495) -> Result<VirtualAllocationInfo, AllocError> {
496	if block.owner != allocation.owner {
497		return Err(AllocError::WrongAllocator);
498	}
499	block
500		.metadata
501		.info(allocation.placement.ok_or(AllocError::UnknownAllocation)?)
502}
503
504/// Free a checked live region and invalidate its token; no native effects.
505#[inline]
506pub fn free(
507	block: &mut VirtualBlock,
508	allocation: &mut VirtualAllocation,
509) -> Result<(), AllocError> {
510	if block.owner != allocation.owner {
511		return Err(AllocError::WrongAllocator);
512	}
513	// Metadata::free performs the generation/liveness check before mutation.
514	block
515		.metadata
516		.free(allocation.placement.ok_or(AllocError::UnknownAllocation)?)?;
517	allocation.placement = None;
518	Ok(())
519}
520
521/// Clear all regions, invalidating every prior generation; never submits or waits.
522pub fn clear(block: &mut VirtualBlock) {
523	block.metadata.clear()
524}
525
526/// Calculate allocation bytes, free bytes, free-region count and largest span.
527pub fn statistics(block: &VirtualBlock) -> VirtualBlockStats {
528	block.metadata.statistics()
529}