vkrust 0.13.2+1.4.365

Rust Vulkan bindings for graphics and GPU compute, with cached dispatch and a native allocator
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
//! Host-only region placement over an integer byte range.
//!
//! No Vulkan memory, buffers, mapping, synchronization or implicit retirement is
//! provided. Consumers own the validity/completion of any resource using these
//! offsets. Tokens are explicit: dropping one does not release its region.
//!
//! ```
//! use vk::mem::{PlacementStrategy, VirtualBlock};
//! let mut block = VirtualBlock::new(4096)?;
//! let mut region = block.allocate(64, 16, PlacementStrategy::Default)?;
//! assert_eq!(block.allocation_info(&region)?.offset, 0);
//! block.free(&mut region)?;
//! # Ok::<(), vk::AllocError>(())
//! ```

use super::{
	AllocError, AllocationAlgorithm,
	host::{HostMemory, HostVec, Identity},
	metadata::{Metadata, Placement},
};
use crate as vk;

/// VMA virtual-TLSF search order; these policies are not latency guarantees.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PlacementStrategy {
	#[default]
	Default,
	MinMemory,
	MinTime,
	MinOffset,
}

/// Generation-checked identity in exactly one virtual block; no implicit free.
pub struct VirtualAllocation {
	owner: Identity,
	placement: Option<Placement>,
}

/// Placement snapshot; valid only while the matching allocation remains live.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VirtualAllocationInfo {
	/// Uninterpreted consumer metadata; no ownership or dereference is inferred.
	pub user_data: usize,
	pub offset: u64,
	pub size: u64,
}

impl VirtualAllocationInfo {
	/// Set [`user_data`](Self::user_data) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_user_data(mut self, value: usize) -> Self {
		self.user_data = value;
		self
	}

	/// Set [`offset`](Self::offset) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_offset(mut self, value: u64) -> Self {
		self.offset = value;
		self
	}

	/// Set [`size`](Self::size) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_size(mut self, value: u64) -> Self {
		self.size = value;
		self
	}
}

/// Host metadata statistics. Alignment holes remain free, not reserved to a token.
/// Linear free spans can include holes that placement cannot immediately reuse.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VirtualBlockStats {
	pub size: u64,
	pub allocation_count: usize,
	pub allocated_bytes: u64,
	pub free_bytes: u64,
	pub free_region_count: usize,
	pub largest_free_region: u64,
}

impl VirtualBlockStats {
	/// Set [`size`](Self::size) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_size(mut self, value: u64) -> Self {
		self.size = value;
		self
	}

	/// Set [`allocation_count`](Self::allocation_count) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_allocation_count(mut self, value: usize) -> Self {
		self.allocation_count = value;
		self
	}

	/// Set [`allocated_bytes`](Self::allocated_bytes) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_allocated_bytes(mut self, value: u64) -> Self {
		self.allocated_bytes = value;
		self
	}

	/// Set [`free_bytes`](Self::free_bytes) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_free_bytes(mut self, value: u64) -> Self {
		self.free_bytes = value;
		self
	}

	/// Set [`free_region_count`](Self::free_region_count) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_free_region_count(mut self, value: usize) -> Self {
		self.free_region_count = value;
		self
	}

	/// Set [`largest_free_region`](Self::largest_free_region) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_largest_free_region(mut self, value: u64) -> Self {
		self.largest_free_region = value;
		self
	}
}

/// Virtual TLSF or linear allocator; owns host metadata and no native objects.
///
/// Mutations require exclusive access. Drop releases host metadata only.
/// Callback construction borrows its allocation/free record through Drop; raw
/// callback user data remains the unsafe constructor caller's responsibility.
/// This is not a Vulkan memory block or a pool with GPU lifetime tracking.
pub struct VirtualBlock<'callbacks> {
	owner: Identity,
	metadata: Metadata<'callbacks>,
}

/// Explicit virtual placement diagnostics; physical holes may not be immediately reusable by linear placement.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VirtualBlockDetails {
	pub statistics: VirtualBlockStats,
	pub allocation_size_min: Option<u64>,
	pub allocation_size_max: Option<u64>,
	pub free_range_size_min: Option<u64>,
	pub free_range_size_max: Option<u64>,
}

impl VirtualBlockDetails {
	/// Set [`statistics`](Self::statistics) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_statistics(mut self, value: VirtualBlockStats) -> Self {
		self.statistics = value;
		self
	}

	/// Set [`allocation_size_min`](Self::allocation_size_min) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_allocation_size_min(mut self, value: Option<u64>) -> Self {
		self.allocation_size_min = value;
		self
	}

	/// Set [`allocation_size_max`](Self::allocation_size_max) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_allocation_size_max(mut self, value: Option<u64>) -> Self {
		self.allocation_size_max = value;
		self
	}

	/// Set [`free_range_size_min`](Self::free_range_size_min) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_free_range_size_min(mut self, value: Option<u64>) -> Self {
		self.free_range_size_min = value;
		self
	}

	/// Set [`free_range_size_max`](Self::free_range_size_max) and return the updated record.
	#[inline]
	#[must_use]
	pub fn set_free_range_size_max(mut self, value: Option<u64>) -> Self {
		self.free_range_size_max = value;
		self
	}
}

/// Set checked per-region opaque metadata without native effects or ownership transfer.
pub fn set_user_data(
	block: &mut VirtualBlock,
	allocation: &VirtualAllocation,
	data: usize,
) -> Result<(), AllocError> {
	allocation_info(block, allocation)?;
	block.metadata.set_user_data(
		allocation.placement.ok_or(AllocError::UnknownAllocation)?,
		data,
	)
}

/// Observe whether every virtual allocation has been explicitly released or cleared.
pub fn is_empty(block: &VirtualBlock) -> bool {
	block.metadata.is_empty()
}

/// Walk virtual placements/free spans for explicit extent statistics.
pub fn detailed_statistics(block: &VirtualBlock) -> VirtualBlockDetails {
	let mut result = VirtualBlockDetails {
		statistics: statistics(block),
		allocation_size_min: None,
		allocation_size_max: None,
		free_range_size_min: None,
		free_range_size_max: None,
	};
	block.metadata.for_each_allocation(|_, size, _| {
		result.allocation_size_min = Some(result.allocation_size_min.map_or(size, |old| old.min(size)));
		result.allocation_size_max = Some(result.allocation_size_max.map_or(size, |old| old.max(size)));
	});
	block.metadata.for_each_free(|_, size| {
		result.free_range_size_min = Some(result.free_range_size_min.map_or(size, |old| old.min(size)));
		result.free_range_size_max = Some(result.free_range_size_max.map_or(size, |old| old.max(size)));
	});
	result
}

/// Write explicit virtual metadata as ordered `vk.memory.virtual.v1` JSON.
/// # Errors
/// Host reservation fails before output; writer errors retain their kind/OS code.
pub fn write_statistics_json(
	block: &VirtualBlock,
	output: &mut impl std::io::Write,
	detailed: bool,
) -> Result<(), AllocError> {
	let stats = statistics(block);
	let mut allocations = HostVec::new(block.metadata.host_memory());
	if detailed {
		allocations
			.try_reserve_exact(stats.allocation_count)
			.map_err(|_| AllocError::HostAllocationFailed)?;
		block
			.metadata
			.for_each_allocation(|offset, size, user_data| {
				allocations.push_reserved((offset, size, user_data))
			});
		allocations.sort_unstable_by_key(|value| value.0);
	}
	let result = (|| -> std::io::Result<()> {
		write!(
			output,
			"{{\"schema\":\"vk.memory.virtual.v1\",\"size\":{},\"allocation_count\":{},\"allocation_bytes\":{},\"free_bytes\":{},\"free_range_count\":{},\"largest_free_range\":{}",
			stats.size,
			stats.allocation_count,
			stats.allocated_bytes,
			stats.free_bytes,
			stats.free_region_count,
			stats.largest_free_region
		)?;
		if detailed {
			output.write_all(b",\"allocations\":[")?;
			for (index, (offset, size, data)) in allocations.iter().enumerate() {
				if index != 0 {
					output.write_all(b",")?;
				}
				write!(
					output,
					"{{\"offset\":{offset},\"size\":{size},\"user_data\":{data}}}"
				)?;
			}
			output.write_all(b"],\"free_ranges\":[")?;
			let mut first = true;
			let mut error = None;
			block.metadata.for_each_free(|offset, size| {
				if error.is_some() {
					return;
				}
				let result = (|| {
					if !first {
						output.write_all(b",")?;
					}
					first = false;
					write!(output, "[{offset},{size}]")
				})();
				if let Err(failure) = result {
					error = Some(failure);
				}
			});
			if let Some(error) = error {
				return Err(error);
			}
			output.write_all(b"]")?;
		}
		output.write_all(b"}\n")
	})();
	result.map_err(|error| AllocError::Output {
		kind: error.kind(),
		code: error.raw_os_error(),
	})
}

impl<'callbacks> VirtualBlock<'callbacks> {
	/// Observe checked virtual emptiness in constant time.
	pub fn is_empty(&self) -> bool {
		is_empty(self)
	}

	/// Set opaque per-allocation metadata, with no native pointer dereference or ownership.
	pub fn set_user_data(
		&mut self,
		allocation: &VirtualAllocation,
		data: usize,
	) -> Result<(), AllocError> {
		set_user_data(self, allocation, data)
	}

	/// Calculate explicit virtual allocation/free-span extrema.
	pub fn detailed_statistics(&self) -> VirtualBlockDetails {
		detailed_statistics(self)
	}

	/// Stream virtual JSON diagnostics; no native memory is accessed.
	pub fn write_statistics_json(
		&self,
		output: &mut impl std::io::Write,
		detailed: bool,
	) -> Result<(), AllocError> {
		write_statistics_json(self, output, detailed)
	}

	pub fn new(size: u64) -> Result<Self, AllocError> {
		Self::new_with_algorithm(size, AllocationAlgorithm::Tlsf)
	}

	/// Create TLSF or linear host-only placement. No native storage is acquired.
	///
	/// ```
	/// use vk::{VirtualBlock, mem::{AllocationAlgorithm, PlacementStrategy}};
	/// let mut block = VirtualBlock::new_with_algorithm(4096, AllocationAlgorithm::Linear)?;
	/// let mut lower = block.allocate(64, 16, PlacementStrategy::Default)?;
	/// let mut upper = block.allocate_upper(64, 16)?;
	/// assert_eq!(block.allocation_info(&lower)?.offset, 0);
	/// assert_eq!(block.allocation_info(&upper)?.offset, 4032);
	/// block.free(&mut upper)?;
	/// block.free(&mut lower)?;
	/// # Ok::<(), vk::AllocError>(())
	/// ```
	/// # Errors
	/// Rejects a zero size or failed host metadata reservation.
	pub fn new_with_algorithm(size: u64, algorithm: AllocationAlgorithm) -> Result<Self, AllocError> {
		Ok(Self {
			owner: Identity::new()?,
			metadata: Metadata::new(size, algorithm, true)?,
		})
	}

	/// Create host-only placement whose metadata uses borrowed Vulkan allocation callbacks.
	///
	/// TLSF/linear buffers, growth and detailed-JSON scratch storage use only this
	/// allocation/free pair, with `SystemAllocationScope::OBJECT`. The block value
	/// stays inline; tokens need no reference-count allocation. Reallocation and
	/// driver internal-allocation notifications are not invoked. Drop releases
	/// host metadata only; no Vulkan device or native memory is involved.
	/// The callback record cannot expire while the block still owns storage:
	/// ```compile_fail,E0597
	/// # use vk::{VirtualBlock, mem::AllocationAlgorithm};
	/// let mut block;
	/// {
	///     let callbacks = vk::AllocationCallbacks::new();
	///     // SAFETY: demonstration of record lifetime; null functions are rejected at runtime.
	///     block = unsafe {
	///         VirtualBlock::new_with_callbacks(1024, AllocationAlgorithm::Tlsf, &callbacks)
	///     }.unwrap();
	/// }
	/// block.clear();
	/// ```
	/// # Errors
	/// Rejects zero size, a missing allocation/free function or failed host storage.
	/// # Safety
	/// Callbacks must return uniquely owned memory of the requested size/alignment,
	/// or null on failure, and free only their own allocations. They must not unwind
	/// or reenter this block. Their code and user data must remain valid through
	/// block Drop and support calls from any thread, including concurrent independent
	/// blocks. Borrowing retains the callback record, not ownership of its raw user data.
	pub unsafe fn new_with_callbacks(
		size: u64,
		algorithm: AllocationAlgorithm,
		callbacks: &'callbacks vk::AllocationCallbacks,
	) -> Result<Self, AllocError> {
		// SAFETY: caller establishes callback ownership/lifetime/thread contracts;
		// this block's metadata retains the borrow through every buffer's Drop.
		let host = unsafe { HostMemory::new_from_callbacks(callbacks) }?;
		Ok(Self {
			owner: Identity::new()?,
			metadata: Metadata::new_with_host(size, algorithm, true, host)?,
		})
	}

	/// Allocate an aligned region. Linear placement ignores the TLSF search strategy.
	#[inline]
	pub fn allocate(
		&mut self,
		size: u64,
		alignment: u64,
		strategy: PlacementStrategy,
	) -> Result<VirtualAllocation, AllocError> {
		allocate(self, size, alignment, strategy)
	}

	/// Allocate downward in a linear block. Active ring mode rejects this request.
	/// # Errors
	/// Rejects TLSF, invalid extents/alignment, exhausted space or host metadata.
	pub fn allocate_upper(
		&mut self,
		size: u64,
		alignment: u64,
	) -> Result<VirtualAllocation, AllocError> {
		allocate_upper(self, size, alignment)
	}

	/// Release a matching live identity. No native resource is destroyed or waited on.
	#[inline]
	pub fn free(&mut self, allocation: &mut VirtualAllocation) -> Result<(), AllocError> {
		free(self, allocation)
	}

	/// Validate identity/generation and return the current offset/size snapshot.
	pub fn allocation_info(
		&self,
		allocation: &VirtualAllocation,
	) -> Result<VirtualAllocationInfo, AllocError> {
		allocation_info(self, allocation)
	}

	/// Invalidate all tokens and restore the full range, retaining host capacity.
	/// Users of previously returned offsets must stop using them before reuse.
	pub fn clear(&mut self) {
		clear(self)
	}

	/// Calculate detailed free-span statistics; this is not a constant-time hot path.
	pub fn statistics(&self) -> VirtualBlockStats {
		statistics(self)
	}
}

/// Allocate a nonzero region with a nonzero power-of-two alignment.
///
/// Failure leaves placements and donor free-list ordering unchanged. Only host
/// metadata is allocated; no Vulkan calls or memory mapping occur.
#[inline]
pub fn allocate(
	block: &mut VirtualBlock,
	size: u64,
	alignment: u64,
	strategy: PlacementStrategy,
) -> Result<VirtualAllocation, AllocError> {
	let placement = block.metadata.allocate(size, alignment, strategy, false)?;
	Ok(VirtualAllocation {
		owner: block.owner,
		placement: Some(placement),
	})
}

/// Allocate downward from the upper end of a linear block.
/// # Errors
/// Rejects TLSF, active ring mode, invalid extents/alignment and exhausted space/metadata.
pub fn allocate_upper(
	block: &mut VirtualBlock,
	size: u64,
	alignment: u64,
) -> Result<VirtualAllocation, AllocError> {
	let placement = block
		.metadata
		.allocate(size, alignment, PlacementStrategy::Default, true)?;
	Ok(VirtualAllocation {
		owner: block.owner,
		placement: Some(placement),
	})
}

/// Reject foreign, freed or stale tokens before reading placement metadata.
pub fn allocation_info(
	block: &VirtualBlock,
	allocation: &VirtualAllocation,
) -> Result<VirtualAllocationInfo, AllocError> {
	if block.owner != allocation.owner {
		return Err(AllocError::WrongAllocator);
	}
	block
		.metadata
		.info(allocation.placement.ok_or(AllocError::UnknownAllocation)?)
}

/// Free a checked live region and invalidate its token; no native effects.
#[inline]
pub fn free(
	block: &mut VirtualBlock,
	allocation: &mut VirtualAllocation,
) -> Result<(), AllocError> {
	if block.owner != allocation.owner {
		return Err(AllocError::WrongAllocator);
	}
	// Metadata::free performs the generation/liveness check before mutation.
	block
		.metadata
		.free(allocation.placement.ok_or(AllocError::UnknownAllocation)?)?;
	allocation.placement = None;
	Ok(())
}

/// Clear all regions, invalidating every prior generation; never submits or waits.
pub fn clear(block: &mut VirtualBlock) {
	block.metadata.clear()
}

/// Calculate allocation bytes, free bytes, free-region count and largest span.
pub fn statistics(block: &VirtualBlock) -> VirtualBlockStats {
	block.metadata.statistics()
}