1use super::Allocator;
7use crate::{
8 self as vk,
9 mem::{AllocError, BudgetSnapshot, BudgetSource, HeapBudget},
10};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub struct TrimStats {
15 pub block_count: usize,
16 pub block_bytes: u64,
17}
18
19impl TrimStats {
20 #[inline]
22 #[must_use]
23 pub fn new() -> Self {
24 Self::default()
25 }
26
27 #[inline]
29 #[must_use]
30 pub fn set_block_count(mut self, value: usize) -> Self {
31 self.block_count = value;
32 self
33 }
34
35 #[inline]
37 #[must_use]
38 pub fn set_block_bytes(mut self, value: u64) -> Self {
39 self.block_bytes = value;
40 self
41 }
42}
43
44impl Allocator<'_> {
45 pub fn set_heap_limit(&mut self, heap: u32, limit: u64) -> Result<(), AllocError> {
47 set_heap_limit(self, heap, limit)
48 }
49
50 pub fn budget_snapshot(&self) -> BudgetSnapshot {
52 budget_snapshot(self)
53 }
54
55 pub fn trim(&mut self) -> Result<TrimStats, AllocError> {
60 trim(self)
61 }
62
63 pub unsafe fn trim_with_budget(
70 &mut self,
71 instance: &vk::Instance,
72 physical: vk::PhysicalDevice,
73 ) -> Result<TrimStats, AllocError> {
74 unsafe { trim_with_budget(self, instance, physical) }
76 }
77
78 pub unsafe fn query_budget(
82 &self,
83 instance: &vk::Instance,
84 physical: vk::PhysicalDevice,
85 ) -> Result<BudgetSnapshot, AllocError> {
86 unsafe { query_budget(self, instance, physical) }
88 }
89}
90
91pub fn trim(allocator: &mut Allocator<'_>) -> Result<TrimStats, AllocError> {
101 allocator.ensure_allocation()?;
102 allocator.ensure_open()?;
103 let mut snapshot = budget_snapshot(allocator);
104 Ok(super::pool::trim_blocks(allocator, &mut snapshot, true))
105}
106
107pub unsafe fn trim_with_budget(
120 allocator: &mut Allocator<'_>,
121 instance: &vk::Instance,
122 physical: vk::PhysicalDevice,
123) -> Result<TrimStats, AllocError> {
124 allocator.ensure_allocation()?;
125 let mut snapshot = unsafe { query_budget(allocator, instance, physical) }?;
127 Ok(super::pool::trim_blocks(allocator, &mut snapshot, false))
128}
129
130pub fn set_heap_limit(
132 allocator: &mut Allocator<'_>,
133 heap: u32,
134 limit: u64,
135) -> Result<(), AllocError> {
136 allocator.ensure_global()?;
137 allocator.ensure_open()?;
138 if heap >= allocator.selector.properties.memory_heap_count {
139 return Err(AllocError::InvalidRequest("absent memory heap"));
140 }
141 let index = heap as usize;
142 if limit > allocator.selector.properties.memory_heaps[index].size
143 || limit < allocator.heap_bytes[index]
144 {
145 return Err(AllocError::InvalidRequest(
146 "heap limit exceeds size or live native bytes",
147 ));
148 }
149 allocator.heap_limits[index] = Some(limit);
150 Ok(())
151}
152
153pub fn budget_snapshot(allocator: &Allocator<'_>) -> BudgetSnapshot {
155 if let Some(shared) = allocator.parallel_state() {
156 return shared.budget_snapshot();
157 }
158 let mut snapshot = BudgetSnapshot {
159 heap_count: allocator.selector.properties.memory_heap_count,
160 ..Default::default()
161 };
162 for (index, heap) in snapshot.heaps[..snapshot.heap_count as usize]
163 .iter_mut()
164 .enumerate()
165 {
166 *heap = HeapBudget {
167 size: allocator.selector.properties.memory_heaps[index].size,
168 limit: allocator.heap_limits[index]
169 .unwrap_or(allocator.selector.properties.memory_heaps[index].size),
170 budget: allocator.heap_limits[index]
171 .unwrap_or(allocator.selector.properties.memory_heaps[index].size),
172 usage: allocator.heap_bytes[index],
173 native_bytes: allocator.heap_bytes[index],
174 };
175 }
176 snapshot
177}
178
179pub unsafe fn query_budget(
183 allocator: &Allocator<'_>,
184 instance: &vk::Instance,
185 physical: vk::PhysicalDevice,
186) -> Result<BudgetSnapshot, AllocError> {
187 allocator.ensure_open()?;
188 let snapshot = budget_snapshot(allocator);
189 if !crate::mem::resource::extension(allocator.device.as_device(), c"VK_EXT_memory_budget") {
190 return Ok(snapshot);
191 }
192 let parent = instance
193 .capabilities()
194 .ok_or(AllocError::UnsupportedCapability(
195 "instance metadata is required",
196 ))?;
197 let capabilities = allocator
198 .device
199 .as_device()
200 .capabilities()
201 .expect("extension requires metadata");
202 if physical.is_null() || !capabilities.matches_instance(parent) {
203 return Err(AllocError::InvalidProperties(
204 "budget context metadata disagree",
205 ));
206 }
207 let mut budget = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::new();
208 let mut output = vk::PhysicalDeviceMemoryProperties2 {
209 p_next: std::ptr::from_mut(&mut budget).cast(),
210 ..Default::default()
211 };
212 unsafe {
214 if parent.instance_api_version() >= vk::API_VERSION_1_1 {
215 instance.get_physical_device_memory_properties2(physical, &mut output)?;
216 } else if parent
217 .instance_extensions()
218 .any(|name| name == c"VK_KHR_get_physical_device_properties2")
219 {
220 instance.get_physical_device_memory_properties2_khr(physical, &mut output)?;
221 } else {
222 return Err(AllocError::UnsupportedCapability(
223 "budget requires properties2",
224 ));
225 }
226 }
227 validate_budget(allocator, &output, &budget)
228}
229
230fn validate_budget(
231 allocator: &Allocator<'_>,
232 output: &vk::PhysicalDeviceMemoryProperties2,
233 budget: &vk::PhysicalDeviceMemoryBudgetPropertiesEXT,
234) -> Result<BudgetSnapshot, AllocError> {
235 let mut snapshot = budget_snapshot(allocator);
236
237 if output.memory_properties.memory_heap_count != snapshot.heap_count {
238 return Err(AllocError::InvalidProperties(
239 "budget heap inventory changed",
240 ));
241 }
242 for (index, heap) in snapshot.heaps[..snapshot.heap_count as usize]
243 .iter_mut()
244 .enumerate()
245 {
246 if output.memory_properties.memory_heaps[index].size != heap.size
247 || budget.heap_budget[index] == 0
248 || budget.heap_budget[index] > heap.size
249 {
250 return Err(AllocError::InvalidProperties("invalid native heap budget"));
251 }
252 heap.budget = budget.heap_budget[index];
253 heap.usage = budget.heap_usage[index];
254 }
255 if budget.heap_budget[snapshot.heap_count as usize..]
256 .iter()
257 .any(|&value| value != 0)
258 || budget.heap_usage[snapshot.heap_count as usize..]
259 .iter()
260 .any(|&value| value != 0)
261 {
262 return Err(AllocError::InvalidProperties(
263 "native budget references absent heaps",
264 ));
265 }
266 snapshot.source = BudgetSource::Extension;
267 Ok(snapshot)
268}
269
270#[derive(Clone, Copy)]
271pub(super) struct BudgetQuery {
272 physical: vk::PhysicalDevice,
273 function: vk::PFN_vkGetPhysicalDeviceMemoryProperties2,
274 name: &'static str,
275}
276
277pub(super) fn capture_query(
278 instance: &vk::Instance,
279 physical: vk::PhysicalDevice,
280) -> Option<BudgetQuery> {
281 let parent = instance.capabilities()?;
282 if parent.instance_api_version() >= vk::API_VERSION_1_1 {
283 Some(BudgetQuery {
284 physical,
285 function: instance.fp().get_physical_device_memory_properties2,
286 name: "vkGetPhysicalDeviceMemoryProperties2",
287 })
288 } else if parent
289 .instance_extensions()
290 .any(|name| name == c"VK_KHR_get_physical_device_properties2")
291 {
292 Some(BudgetQuery {
293 physical,
294 function: instance.fp().get_physical_device_memory_properties2_khr,
295 name: "vkGetPhysicalDeviceMemoryProperties2KHR",
296 })
297 } else {
298 None
299 }
300}
301
302pub fn refresh_budget(allocator: &mut Allocator<'_>) -> Result<BudgetSnapshot, AllocError> {
309 allocator.ensure_global()?;
310 allocator.ensure_open()?;
311 let snapshot =
312 if crate::mem::resource::extension(allocator.device.as_device(), c"VK_EXT_memory_budget") {
313 let query = allocator
314 .budget_query
315 .ok_or(AllocError::UnsupportedCapability(
316 "retained physical budget context is required",
317 ))?;
318 let function = query
319 .function
320 .ok_or(crate::load::CallError::MissingCommand(query.name))?;
321 let mut budget = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::new();
322 let mut output = vk::PhysicalDeviceMemoryProperties2 {
323 p_next: std::ptr::from_mut(&mut budget).cast(),
324 ..Default::default()
325 };
326 unsafe { function(query.physical, &mut output) };
329 validate_budget(allocator, &output, &budget)?
330 } else {
331 budget_snapshot(allocator)
332 };
333 allocator.budget_cache = Some(snapshot);
334 allocator.budget_operations = 0;
335 Ok(snapshot)
336}
337
338pub unsafe fn refresh_budget_with_context(
346 allocator: &mut Allocator<'_>,
347 instance: &vk::Instance,
348 physical: vk::PhysicalDevice,
349) -> Result<BudgetSnapshot, AllocError> {
350 allocator.ensure_global()?;
351 let snapshot = unsafe { query_budget(allocator, instance, physical) }?;
353 allocator.budget_query = capture_query(instance, physical);
354 allocator.budget_cache = Some(snapshot);
355 allocator.budget_operations = 0;
356 Ok(snapshot)
357}
358
359pub fn heap_budgets(allocator: &mut Allocator<'_>) -> Result<BudgetSnapshot, AllocError> {
365 allocator.ensure_open()?;
366 if let Some(shared) = allocator.parallel_state() {
367 return Ok(shared.budget_snapshot());
368 }
369 if allocator.budget_cache.is_none() || allocator.budget_operations >= 30 {
370 refresh_budget(allocator)?;
371 }
372 let mut snapshot = allocator.budget_cache.expect("refreshed owned budget");
373 for (index, heap) in snapshot.heaps[..snapshot.heap_count as usize]
374 .iter_mut()
375 .enumerate()
376 {
377 let external = heap.usage.saturating_sub(heap.native_bytes);
378 heap.native_bytes = allocator.heap_bytes[index];
379 heap.usage = external
380 .checked_add(heap.native_bytes)
381 .ok_or(AllocError::InvalidProperties("budget usage overflow"))?;
382 heap.limit = allocator.heap_limits[index].unwrap_or(heap.size);
383 if snapshot.source == BudgetSource::Local {
384 heap.budget = heap.limit;
385 }
386 }
387 Ok(snapshot)
388}
389
390pub fn set_current_frame_index(
392 allocator: &mut Allocator<'_>,
393 index: u32,
394) -> Result<(), AllocError> {
395 allocator.ensure_global()?;
396 allocator.ensure_open()?;
397 if allocator.current_frame_index != index {
398 allocator.current_frame_index = index;
399 allocator.budget_cache = None;
400 }
401 Ok(())
402}
403
404pub(super) fn admit_native(
405 allocator: &mut Allocator<'_>,
406 index: u32,
407 size: u64,
408) -> Result<(), AllocError> {
409 let snapshot = heap_budgets(allocator)?;
410 let heap =
411 snapshot.heaps[allocator.selector.properties.memory_types[index as usize].heap_index as usize];
412 if heap
413 .usage
414 .checked_add(size)
415 .is_none_or(|usage| usage > heap.budget)
416 {
417 return Err(AllocError::OutOfSpace);
418 }
419 Ok(())
420}
421
422impl Allocator<'_> {
423 pub fn refresh_budget(&mut self) -> Result<BudgetSnapshot, AllocError> {
425 refresh_budget(self)
426 }
427
428 pub unsafe fn refresh_budget_with_context(
432 &mut self,
433 instance: &vk::Instance,
434 physical: vk::PhysicalDevice,
435 ) -> Result<BudgetSnapshot, AllocError> {
436 unsafe { refresh_budget_with_context(self, instance, physical) }
438 }
439
440 pub fn heap_budgets(&mut self) -> Result<BudgetSnapshot, AllocError> {
442 heap_budgets(self)
443 }
444
445 pub fn set_current_frame_index(&mut self, index: u32) -> Result<(), AllocError> {
447 set_current_frame_index(self, index)
448 }
449
450 pub fn current_frame_index(&self) -> u32 {
451 self.current_frame_index
452 }
453}
454
455pub(super) fn validate_profile(allocator: &Allocator<'_>) -> Result<(), AllocError> {
456 if crate::mem::resource::extension(allocator.device.as_device(), c"VK_EXT_memory_budget") {
457 let query = allocator
458 .budget_query
459 .ok_or(AllocError::UnsupportedCapability(
460 "retained physical budget context is required",
461 ))?;
462 if query.function.is_none() {
463 return Err(crate::load::CallError::MissingCommand(query.name).into());
464 }
465 }
466 Ok(())
467}
468
469pub(super) fn cached_pressure(allocator: &Allocator<'_>, heap: usize) -> bool {
473 if let Some(shared) = allocator.parallel_state() {
474 let snapshot = shared.budget_snapshot();
475 return snapshot.heaps[heap].usage >= snapshot.heaps[heap].budget;
476 }
477 let Some(snapshot) = allocator.budget_cache else {
478 return false;
479 };
480 let old = snapshot.heaps[heap];
481 old
482 .usage
483 .saturating_sub(old.native_bytes)
484 .saturating_add(allocator.heap_bytes[heap])
485 >= old.budget
486}