use super::Allocator;
use crate::{
self as vk,
mem::{AllocError, BudgetSnapshot, BudgetSource, HeapBudget},
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TrimStats {
pub block_count: usize,
pub block_bytes: u64,
}
impl TrimStats {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn set_block_count(mut self, value: usize) -> Self {
self.block_count = value;
self
}
#[inline]
#[must_use]
pub fn set_block_bytes(mut self, value: u64) -> Self {
self.block_bytes = value;
self
}
}
impl Allocator<'_> {
pub fn set_heap_limit(&mut self, heap: u32, limit: u64) -> Result<(), AllocError> {
set_heap_limit(self, heap, limit)
}
pub fn budget_snapshot(&self) -> BudgetSnapshot {
budget_snapshot(self)
}
pub fn trim(&mut self) -> Result<TrimStats, AllocError> {
trim(self)
}
pub unsafe fn trim_with_budget(
&mut self,
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Result<TrimStats, AllocError> {
unsafe { trim_with_budget(self, instance, physical) }
}
pub unsafe fn query_budget(
&self,
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Result<BudgetSnapshot, AllocError> {
unsafe { query_budget(self, instance, physical) }
}
}
pub fn trim(allocator: &mut Allocator<'_>) -> Result<TrimStats, AllocError> {
allocator.ensure_allocation()?;
allocator.ensure_open()?;
let mut snapshot = budget_snapshot(allocator);
Ok(super::pool::trim_blocks(allocator, &mut snapshot, true))
}
pub unsafe fn trim_with_budget(
allocator: &mut Allocator<'_>,
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Result<TrimStats, AllocError> {
allocator.ensure_allocation()?;
let mut snapshot = unsafe { query_budget(allocator, instance, physical) }?;
Ok(super::pool::trim_blocks(allocator, &mut snapshot, false))
}
pub fn set_heap_limit(
allocator: &mut Allocator<'_>,
heap: u32,
limit: u64,
) -> Result<(), AllocError> {
allocator.ensure_global()?;
allocator.ensure_open()?;
if heap >= allocator.selector.properties.memory_heap_count {
return Err(AllocError::InvalidRequest("absent memory heap"));
}
let index = heap as usize;
if limit > allocator.selector.properties.memory_heaps[index].size
|| limit < allocator.heap_bytes[index]
{
return Err(AllocError::InvalidRequest(
"heap limit exceeds size or live native bytes",
));
}
allocator.heap_limits[index] = Some(limit);
Ok(())
}
pub fn budget_snapshot(allocator: &Allocator<'_>) -> BudgetSnapshot {
if let Some(shared) = allocator.parallel_state() {
return shared.budget_snapshot();
}
let mut snapshot = BudgetSnapshot {
heap_count: allocator.selector.properties.memory_heap_count,
..Default::default()
};
for (index, heap) in snapshot.heaps[..snapshot.heap_count as usize]
.iter_mut()
.enumerate()
{
*heap = HeapBudget {
size: allocator.selector.properties.memory_heaps[index].size,
limit: allocator.heap_limits[index]
.unwrap_or(allocator.selector.properties.memory_heaps[index].size),
budget: allocator.heap_limits[index]
.unwrap_or(allocator.selector.properties.memory_heaps[index].size),
usage: allocator.heap_bytes[index],
native_bytes: allocator.heap_bytes[index],
};
}
snapshot
}
pub unsafe fn query_budget(
allocator: &Allocator<'_>,
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Result<BudgetSnapshot, AllocError> {
allocator.ensure_open()?;
let snapshot = budget_snapshot(allocator);
if !crate::mem::resource::extension(allocator.device.as_device(), c"VK_EXT_memory_budget") {
return Ok(snapshot);
}
let parent = instance
.capabilities()
.ok_or(AllocError::UnsupportedCapability(
"instance metadata is required",
))?;
let capabilities = allocator
.device
.as_device()
.capabilities()
.expect("extension requires metadata");
if physical.is_null() || !capabilities.matches_instance(parent) {
return Err(AllocError::InvalidProperties(
"budget context metadata disagree",
));
}
let mut budget = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::new();
let mut output = vk::PhysicalDeviceMemoryProperties2 {
p_next: std::ptr::from_mut(&mut budget).cast(),
..Default::default()
};
unsafe {
if parent.instance_api_version() >= vk::API_VERSION_1_1 {
instance.get_physical_device_memory_properties2(physical, &mut output)?;
} else if parent
.instance_extensions()
.any(|name| name == c"VK_KHR_get_physical_device_properties2")
{
instance.get_physical_device_memory_properties2_khr(physical, &mut output)?;
} else {
return Err(AllocError::UnsupportedCapability(
"budget requires properties2",
));
}
}
validate_budget(allocator, &output, &budget)
}
fn validate_budget(
allocator: &Allocator<'_>,
output: &vk::PhysicalDeviceMemoryProperties2,
budget: &vk::PhysicalDeviceMemoryBudgetPropertiesEXT,
) -> Result<BudgetSnapshot, AllocError> {
let mut snapshot = budget_snapshot(allocator);
if output.memory_properties.memory_heap_count != snapshot.heap_count {
return Err(AllocError::InvalidProperties(
"budget heap inventory changed",
));
}
for (index, heap) in snapshot.heaps[..snapshot.heap_count as usize]
.iter_mut()
.enumerate()
{
if output.memory_properties.memory_heaps[index].size != heap.size
|| budget.heap_budget[index] == 0
|| budget.heap_budget[index] > heap.size
{
return Err(AllocError::InvalidProperties("invalid native heap budget"));
}
heap.budget = budget.heap_budget[index];
heap.usage = budget.heap_usage[index];
}
if budget.heap_budget[snapshot.heap_count as usize..]
.iter()
.any(|&value| value != 0)
|| budget.heap_usage[snapshot.heap_count as usize..]
.iter()
.any(|&value| value != 0)
{
return Err(AllocError::InvalidProperties(
"native budget references absent heaps",
));
}
snapshot.source = BudgetSource::Extension;
Ok(snapshot)
}
#[derive(Clone, Copy)]
pub(super) struct BudgetQuery {
physical: vk::PhysicalDevice,
function: vk::PFN_vkGetPhysicalDeviceMemoryProperties2,
name: &'static str,
}
pub(super) fn capture_query(
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Option<BudgetQuery> {
let parent = instance.capabilities()?;
if parent.instance_api_version() >= vk::API_VERSION_1_1 {
Some(BudgetQuery {
physical,
function: instance.fp().get_physical_device_memory_properties2,
name: "vkGetPhysicalDeviceMemoryProperties2",
})
} else if parent
.instance_extensions()
.any(|name| name == c"VK_KHR_get_physical_device_properties2")
{
Some(BudgetQuery {
physical,
function: instance.fp().get_physical_device_memory_properties2_khr,
name: "vkGetPhysicalDeviceMemoryProperties2KHR",
})
} else {
None
}
}
pub fn refresh_budget(allocator: &mut Allocator<'_>) -> Result<BudgetSnapshot, AllocError> {
allocator.ensure_global()?;
allocator.ensure_open()?;
let snapshot =
if crate::mem::resource::extension(allocator.device.as_device(), c"VK_EXT_memory_budget") {
let query = allocator
.budget_query
.ok_or(AllocError::UnsupportedCapability(
"retained physical budget context is required",
))?;
let function = query
.function
.ok_or(crate::load::CallError::MissingCommand(query.name))?;
let mut budget = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::new();
let mut output = vk::PhysicalDeviceMemoryProperties2 {
p_next: std::ptr::from_mut(&mut budget).cast(),
..Default::default()
};
unsafe { function(query.physical, &mut output) };
validate_budget(allocator, &output, &budget)?
} else {
budget_snapshot(allocator)
};
allocator.budget_cache = Some(snapshot);
allocator.budget_operations = 0;
Ok(snapshot)
}
pub unsafe fn refresh_budget_with_context(
allocator: &mut Allocator<'_>,
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Result<BudgetSnapshot, AllocError> {
allocator.ensure_global()?;
let snapshot = unsafe { query_budget(allocator, instance, physical) }?;
allocator.budget_query = capture_query(instance, physical);
allocator.budget_cache = Some(snapshot);
allocator.budget_operations = 0;
Ok(snapshot)
}
pub fn heap_budgets(allocator: &mut Allocator<'_>) -> Result<BudgetSnapshot, AllocError> {
allocator.ensure_open()?;
if let Some(shared) = allocator.parallel_state() {
return Ok(shared.budget_snapshot());
}
if allocator.budget_cache.is_none() || allocator.budget_operations >= 30 {
refresh_budget(allocator)?;
}
let mut snapshot = allocator.budget_cache.expect("refreshed owned budget");
for (index, heap) in snapshot.heaps[..snapshot.heap_count as usize]
.iter_mut()
.enumerate()
{
let external = heap.usage.saturating_sub(heap.native_bytes);
heap.native_bytes = allocator.heap_bytes[index];
heap.usage = external
.checked_add(heap.native_bytes)
.ok_or(AllocError::InvalidProperties("budget usage overflow"))?;
heap.limit = allocator.heap_limits[index].unwrap_or(heap.size);
if snapshot.source == BudgetSource::Local {
heap.budget = heap.limit;
}
}
Ok(snapshot)
}
pub fn set_current_frame_index(
allocator: &mut Allocator<'_>,
index: u32,
) -> Result<(), AllocError> {
allocator.ensure_global()?;
allocator.ensure_open()?;
if allocator.current_frame_index != index {
allocator.current_frame_index = index;
allocator.budget_cache = None;
}
Ok(())
}
pub(super) fn admit_native(
allocator: &mut Allocator<'_>,
index: u32,
size: u64,
) -> Result<(), AllocError> {
let snapshot = heap_budgets(allocator)?;
let heap =
snapshot.heaps[allocator.selector.properties.memory_types[index as usize].heap_index as usize];
if heap
.usage
.checked_add(size)
.is_none_or(|usage| usage > heap.budget)
{
return Err(AllocError::OutOfSpace);
}
Ok(())
}
impl Allocator<'_> {
pub fn refresh_budget(&mut self) -> Result<BudgetSnapshot, AllocError> {
refresh_budget(self)
}
pub unsafe fn refresh_budget_with_context(
&mut self,
instance: &vk::Instance,
physical: vk::PhysicalDevice,
) -> Result<BudgetSnapshot, AllocError> {
unsafe { refresh_budget_with_context(self, instance, physical) }
}
pub fn heap_budgets(&mut self) -> Result<BudgetSnapshot, AllocError> {
heap_budgets(self)
}
pub fn set_current_frame_index(&mut self, index: u32) -> Result<(), AllocError> {
set_current_frame_index(self, index)
}
pub fn current_frame_index(&self) -> u32 {
self.current_frame_index
}
}
pub(super) fn validate_profile(allocator: &Allocator<'_>) -> Result<(), AllocError> {
if crate::mem::resource::extension(allocator.device.as_device(), c"VK_EXT_memory_budget") {
let query = allocator
.budget_query
.ok_or(AllocError::UnsupportedCapability(
"retained physical budget context is required",
))?;
if query.function.is_none() {
return Err(crate::load::CallError::MissingCommand(query.name).into());
}
}
Ok(())
}
pub(super) fn cached_pressure(allocator: &Allocator<'_>, heap: usize) -> bool {
if let Some(shared) = allocator.parallel_state() {
let snapshot = shared.budget_snapshot();
return snapshot.heaps[heap].usage >= snapshot.heaps[heap].budget;
}
let Some(snapshot) = allocator.budget_cache else {
return false;
};
let old = snapshot.heaps[heap];
old
.usage
.saturating_sub(old.native_bytes)
.saturating_add(allocator.heap_bytes[heap])
>= old.budget
}