#[derive(Debug, Clone, Copy, Default)]
pub struct TextureMemoryStats {
pub used_bytes: u64,
pub texture_count: u32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ResidentBytes {
pub mesh_bytes: u64,
pub texture_bytes: u64,
pub gaussian_splat_bytes: u64,
pub mc_volume_bytes: u64,
pub scivis_bytes: u64,
}
impl ResidentBytes {
pub fn total(&self) -> u64 {
self.mesh_bytes
+ self.texture_bytes
+ self.gaussian_splat_bytes
+ self.mc_volume_bytes
+ self.scivis_bytes
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VramBudget {
pub total_bytes: u64,
pub available_bytes: Option<u64>,
}
impl crate::resources::DeviceResources {
pub fn resident_bytes(&self) -> crate::resources::types::ResidentBytes {
let scivis_bytes = self.content.polyline_store.allocated_bytes()
+ self.content.streamtube_store.allocated_bytes()
+ self.content.tube_store.allocated_bytes()
+ self.content.ribbon_store.allocated_bytes()
+ self.content.point_cloud_store.allocated_bytes()
+ self.content.glyph_set_store.allocated_bytes()
+ self.content.tensor_glyph_set_store.allocated_bytes()
+ self.content.sprite_set_store.allocated_bytes()
+ self.content.sprite_instance_set_store.allocated_bytes();
crate::resources::types::ResidentBytes {
mesh_bytes: self.mesh_store.allocated_bytes(),
texture_bytes: self.content.textures.allocated_bytes(),
gaussian_splat_bytes: self.content.gaussian_splat_store.allocated_bytes(),
mc_volume_bytes: self.mc_volume_resident_bytes(),
scivis_bytes,
}
}
pub fn vram_budget(
&self,
device: &wgpu::Device,
) -> Option<crate::resources::types::VramBudget> {
vram_budget(device)
}
}
pub fn vram_budget(device: &wgpu::Device) -> Option<VramBudget> {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
vram_budget_metal(device)
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
{
vram_budget_vulkan(device)
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn vram_budget_metal(device: &wgpu::Device) -> Option<VramBudget> {
let hal_device = unsafe { device.as_hal::<wgpu::hal::api::Metal>() }?;
let raw = hal_device.raw_device().lock();
let total = raw.recommended_max_working_set_size();
let used = raw.current_allocated_size() as u64;
Some(VramBudget {
total_bytes: total,
available_bytes: Some(total.saturating_sub(used)),
})
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
fn vram_budget_vulkan(device: &wgpu::Device) -> Option<VramBudget> {
let hal_device = unsafe { device.as_hal::<wgpu::hal::api::Vulkan>() }?;
let phys = hal_device.raw_physical_device();
let instance = hal_device.shared_instance().raw_instance();
let props = unsafe { instance.get_physical_device_memory_properties(phys) };
let total: u64 = props.memory_heaps[..props.memory_heap_count as usize]
.iter()
.filter(|heap| heap.flags.contains(ash::vk::MemoryHeapFlags::DEVICE_LOCAL))
.map(|heap| heap.size)
.sum();
if total == 0 {
return None;
}
Some(VramBudget {
total_bytes: total,
available_bytes: None,
})
}
#[cfg(test)]
mod tests {
use super::vram_budget;
fn try_make_device() -> Option<wgpu::Device> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: None,
force_fallback_adapter: false,
}))
.ok()?;
pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default()))
.ok()
.map(|(device, _queue)| device)
}
#[test]
fn vram_budget_reports_a_consistent_total() {
let Some(device) = try_make_device() else {
eprintln!("skipping: no wgpu adapter available");
return;
};
if let Some(budget) = vram_budget(&device) {
assert!(
budget.total_bytes > 0,
"total device-local VRAM must be non-zero"
);
if let Some(available) = budget.available_bytes {
assert!(
available <= budget.total_bytes,
"available VRAM must not exceed the total"
);
}
}
let resources =
crate::DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
assert_eq!(resources.vram_budget(&device), vram_budget(&device));
}
}