diff --git a/src/cache.rs b/src/cache.rs index 89158ce..7b15f7a 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,6 +1,8 @@ +use crate::util::{hash_map_capacity_bytes, vector_capacity_bytes, MemoryUsage}; use crate::vertex::CustomVertex; use ahash::{HashMap, HashMapExt}; use lyon::tessellation::VertexBuffers; +use std::collections::HashSet; use std::num::NonZeroUsize; use std::sync::Arc; @@ -65,6 +67,47 @@ impl Cache { std::mem::swap(&mut self.previous_frame, &mut self.current_frame); self.current_frame.clear(); } + + pub(crate) fn memory_usage( + &self, + visited_tessellations: &mut HashSet<*const CachedTessellation>, + ) -> MemoryUsage { + let mut usage = MemoryUsage { + cpu_bytes: hash_map_capacity_bytes(&self.previous_frame) + .saturating_add(hash_map_capacity_bytes(&self.current_frame)), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + + for tessellation in self + .previous_frame + .values() + .chain(self.current_frame.values()) + { + usage.cpu_bytes = usage + .cpu_bytes + .saturating_add(cached_tessellation_heap_bytes( + tessellation, + visited_tessellations, + )); + } + + usage + } +} + +pub(crate) fn cached_tessellation_heap_bytes( + tessellation: &Arc, + visited_tessellations: &mut HashSet<*const CachedTessellation>, +) -> u64 { + if !visited_tessellations.insert(Arc::as_ptr(tessellation)) { + return 0; + } + + std::mem::size_of::() as u64 + + std::mem::size_of::>() as u64 + + vector_capacity_bytes(&tessellation.vertex_buffers.vertices) + + vector_capacity_bytes(&tessellation.vertex_buffers.indices) } #[cfg(test)] diff --git a/src/effect.rs b/src/effect.rs index 894476b..eca8da8 100644 --- a/src/effect.rs +++ b/src/effect.rs @@ -14,6 +14,8 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hash; use std::sync::OnceLock; +use crate::util::{texture_memory_size, vector_capacity_bytes, MemoryUsage}; + // ── Error type ─────────────────────────────────────────────────────────────── /// Errors that can occur when working with the effect system. @@ -154,6 +156,16 @@ pub(crate) struct LoadedEffect { pub params_bind_group_layout: Option, } +impl LoadedEffect { + pub(crate) fn memory_usage(&self) -> MemoryUsage { + MemoryUsage { + cpu_bytes: vector_capacity_bytes(&self.passes), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + } + } +} + // ── Per-node effect instance ───────────────────────────────────────────────── /// A per-node effect instance. Stored in a HashMap on the Renderer, @@ -178,6 +190,27 @@ pub(crate) struct EffectInstance { pub backdrop_texture_id: Option, } +impl EffectInstance { + pub(crate) fn memory_usage(&self) -> MemoryUsage { + let mut usage = MemoryUsage { + cpu_bytes: vector_capacity_bytes(&self.params), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + + if let Some(params_buffer) = &self.params_buffer { + usage.gpu_buffer_bytes = usage.gpu_buffer_bytes.saturating_add(params_buffer.size()); + } + if let Some(backdrop_material_params_buffer) = &self.backdrop_material_params_buffer { + usage.gpu_buffer_bytes = usage + .gpu_buffer_bytes + .saturating_add(backdrop_material_params_buffer.size()); + } + + usage + } +} + // ── Offscreen texture pool ─────────────────────────────────────────────────── /// A pooled offscreen texture with color, optional depth/stencil, and optional MSAA resolve @@ -191,9 +224,44 @@ pub(crate) struct PooledTexture { pub resolve_view: Option, pub width: u32, pub height: u32, + pub format: wgpu::TextureFormat, pub sample_count: u32, } +impl PooledTexture { + pub(crate) fn memory_usage(&self) -> MemoryUsage { + let color_size = + texture_memory_size(self.color_texture.size(), self.format, self.sample_count, 1); + let resolve_size = self + .resolve_texture + .as_ref() + .map(|texture| texture_memory_size(texture.size(), self.format, 1, 1)) + .unwrap_or(0); + let depth_stencil_size = if self.depth_stencil_view.is_some() { + texture_memory_size( + wgpu::Extent3d { + width: self.width, + height: self.height, + depth_or_array_layers: 1, + }, + wgpu::TextureFormat::Depth24PlusStencil8, + self.sample_count, + 1, + ) + } else { + 0 + }; + + MemoryUsage { + cpu_bytes: 0, + gpu_buffer_bytes: 0, + gpu_texture_bytes: color_size + .saturating_add(resolve_size) + .saturating_add(depth_stencil_size), + } + } +} + /// Pool of reusable offscreen textures for effect compositing. /// At frame start, all textures move back to `available`. pub(crate) struct OffscreenTexturePool { @@ -232,6 +300,20 @@ impl OffscreenTexturePool { } } + pub(crate) fn memory_usage(&self) -> MemoryUsage { + let mut usage = MemoryUsage { + cpu_bytes: vector_capacity_bytes(&self.available), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + + for texture in &self.available { + usage.add(texture.memory_usage()); + } + + usage + } + /// Acquire a texture matching the given dimensions and sample count, plus a depth/stencil /// attachment for render passes that write depth or stencil. pub fn acquire_with_depth( @@ -363,6 +445,7 @@ impl OffscreenTexturePool { resolve_view, width, height, + format, sample_count, } } diff --git a/src/gradient/normalize.rs b/src/gradient/normalize.rs index b09d8c9..82724c3 100644 --- a/src/gradient/normalize.rs +++ b/src/gradient/normalize.rs @@ -46,6 +46,23 @@ pub(crate) struct NormalizedGradient { } impl NormalizedGradient { + pub(crate) fn heap_bytes(&self) -> u64 { + let stops_bytes = if self.stops.spilled() { + (self.stops.capacity() as u64) + .saturating_mul(std::mem::size_of::() as u64) + } else { + 0 + }; + let segments_bytes = if self.segments.spilled() { + (self.segments.capacity() as u64) + .saturating_mul(std::mem::size_of::() as u64) + } else { + 0 + }; + + stops_bytes.saturating_add(segments_bytes) + } + pub(crate) fn from_common(common: &GradientCommonDesc, kind: GradientKind) -> Self { let is_conic = kind == GradientKind::Conic; diff --git a/src/gradient/types.rs b/src/gradient/types.rs index c049798..d3f1493 100644 --- a/src/gradient/types.rs +++ b/src/gradient/types.rs @@ -530,6 +530,13 @@ impl Fill { _ => None, } } + + pub(crate) fn heap_bytes(&self) -> u64 { + match self { + Fill::Solid(_) => 0, + Fill::Gradient(gradient) => gradient.heap_bytes(), + } + } } impl From for Fill { @@ -606,6 +613,10 @@ pub(crate) struct GradientData { } impl Gradient { + pub(crate) fn heap_bytes(&self) -> u64 { + self.data.heap_bytes() + } + pub fn new(desc: GradientDesc) -> Result { match desc { GradientDesc::Linear(d) => Self::linear(d), @@ -815,6 +826,43 @@ impl Gradient { } } +impl GradientData { + pub(crate) fn heap_bytes(&self) -> u64 { + self.ramp_cache_key + .heap_bytes() + .saturating_add(self.ramp.heap_bytes()) + } +} + +impl GradientRampCacheKey { + fn heap_bytes(&self) -> u64 { + if self.stops.spilled() { + (self.stops.capacity() as u64) + .saturating_mul(std::mem::size_of::() as u64) + } else { + 0 + } + } +} + +impl GradientRamp { + fn heap_bytes(&self) -> u64 { + match self { + GradientRamp::Constant(_) => 0, + GradientRamp::Pending(source) => { + std::mem::size_of::() as u64 + source.heap_bytes() + } + GradientRamp::Sampled(_) => std::mem::size_of::<[[f32; 4]; RAMP_RESOLUTION]>() as u64, + } + } +} + +impl GradientRampSource { + fn heap_bytes(&self) -> u64 { + self.normalized.heap_bytes() + } +} + impl HueComponentKey { fn from_hue_component(hue_component: HueComponent) -> Self { match hue_component { diff --git a/src/renderer/construction.rs b/src/renderer/construction.rs index 6cce5b9..a8adca5 100644 --- a/src/renderer/construction.rs +++ b/src/renderer/construction.rs @@ -1,4 +1,10 @@ +use std::collections::HashSet; + use super::*; +use crate::cache::{cached_tessellation_heap_bytes, CachedTessellation}; +use crate::util::{ + hash_map_capacity_bytes, texture_memory_size, vector_capacity_bytes, MemoryUsage, +}; use tracing::{info, warn}; fn pick_surface_format(surface_formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat { @@ -47,6 +53,21 @@ fn pick_alpha_mode(alpha_modes: &[CompositeAlphaMode], transparent: bool) -> Com } } +#[cfg(test)] +mod tests { + use super::Renderer; + + #[test] + fn format_memory_usage_size_uses_compact_binary_units() { + assert_eq!(Renderer::format_memory_usage_size(0), "0B"); + assert_eq!(Renderer::format_memory_usage_size(1024), "1.00KB"); + assert_eq!( + Renderer::format_memory_usage_size((18.91_f64 * 1024.0 * 1024.0).round() as u64), + "18.91MB" + ); + } +} + /// Errors that can occur when creating a [`Renderer`] via /// [`Renderer::try_new_headless`]. #[derive(Debug, thiserror::Error)] @@ -357,7 +378,226 @@ impl<'a> Renderer<'a> { Ok(renderer) } + pub fn format_memory_usage_size(bytes: u64) -> String { + const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; + let mut value = bytes as f64; + let mut unit_index = 0; + + while value >= 1024.0 && unit_index + 1 < UNITS.len() { + value /= 1024.0; + unit_index += 1; + } + + if unit_index == 0 { + format!("{bytes}B") + } else { + format!("{value:.2}{}", UNITS[unit_index]) + } + } + + pub fn print_memory_usage_info_human_readable(&self) { + self.print_memory_usage_info_with_format(true); + } + + pub fn print_total_memory_usage_info(&self) { + let total_usage = self.total_memory_usage(); + let gpu_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(total_usage.gpu_texture_bytes); + + println!( + "CPU total RAM consumed by the renderer: {}, GPU total consumed {}", + Self::format_memory_usage_size(total_usage.cpu_bytes), + Self::format_memory_usage_size(gpu_bytes) + ); + } + pub fn print_memory_usage_info(&self) { + self.print_memory_usage_info_with_format(false); + } + + fn total_memory_usage(&self) -> MemoryUsage { + let mut total_usage = MemoryUsage { + cpu_bytes: std::mem::size_of_val(self) as u64, + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + let mut visited_tessellations: HashSet<*const CachedTessellation> = HashSet::new(); + + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(vector_capacity_bytes(&self.temp_vertices)) + .saturating_add(vector_capacity_bytes(&self.temp_indices)) + .saturating_add(vector_capacity_bytes(&self.temp_instance_transforms)) + .saturating_add(vector_capacity_bytes(&self.temp_instance_colors)) + .saturating_add(vector_capacity_bytes(&self.temp_instance_metadata)); + + Self::add_optional_buffer_usage(&self.aggregated_vertex_buffer, &mut total_usage); + Self::add_optional_buffer_usage(&self.aggregated_index_buffer, &mut total_usage); + Self::add_optional_buffer_usage( + &self.aggregated_instance_transform_buffer, + &mut total_usage, + ); + Self::add_optional_buffer_usage(&self.aggregated_instance_color_buffer, &mut total_usage); + Self::add_optional_buffer_usage( + &self.aggregated_instance_metadata_buffer, + &mut total_usage, + ); + Self::add_optional_buffer_usage(&self.identity_instance_transform_buffer, &mut total_usage); + Self::add_optional_buffer_usage(&self.identity_instance_color_buffer, &mut total_usage); + Self::add_optional_buffer_usage(&self.identity_instance_metadata_buffer, &mut total_usage); + + Self::add_optional_buffer_usage(&self.argb_input_buffer, &mut total_usage); + Self::add_optional_buffer_usage(&self.argb_output_storage_buffer, &mut total_usage); + Self::add_optional_buffer_usage(&self.argb_readback_buffer, &mut total_usage); + Self::add_optional_buffer_usage(&self.argb_params_buffer, &mut total_usage); + Self::add_optional_texture_usage( + &self.argb_offscreen_texture, + self.config.format, + 1, + &mut total_usage, + ); + + Self::add_optional_texture_usage( + &self.rtb_offscreen_texture, + self.config.format, + 1, + &mut total_usage, + ); + Self::add_optional_buffer_usage(&self.rtb_readback_buffer, &mut total_usage); + + total_usage.gpu_buffer_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(self.and_uniform_buffer.size()) + .saturating_add(self.decrementing_uniform_buffer.size()); + + Self::add_optional_texture_usage( + &self.msaa_color_texture, + self.config.format, + self.msaa_sample_count, + &mut total_usage, + ); + Self::add_optional_texture_usage( + &self.depth_stencil_texture, + wgpu::TextureFormat::Depth24PlusStencil8, + self.msaa_sample_count, + &mut total_usage, + ); + + if self.surface.is_some() { + let surface_texture_bytes = texture_memory_size( + wgpu::Extent3d { + width: self.config.width, + height: self.config.height, + depth_or_array_layers: 1, + }, + self.config.format, + 1, + 1, + ) + .saturating_mul(self.config.desired_maximum_frame_latency as u64); + total_usage.gpu_texture_bytes = total_usage + .gpu_texture_bytes + .saturating_add(surface_texture_bytes); + } + + let default_texture_bytes = texture_memory_size( + wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + wgpu::TextureFormat::Rgba8UnormSrgb, + 1, + 1, + ) + .saturating_mul(3); + let default_backdrop_params_bytes = + std::mem::size_of::() as u64; + total_usage.gpu_texture_bytes = total_usage + .gpu_texture_bytes + .saturating_add(default_texture_bytes); + total_usage.gpu_buffer_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(default_backdrop_params_bytes); + + let draw_tree_bytes = (self.draw_tree.len() as u64).saturating_mul( + std::mem::size_of::() as u64 + + std::mem::size_of::>() as u64 + + std::mem::size_of::>() as u64, + ); + total_usage.cpu_bytes = total_usage.cpu_bytes.saturating_add(draw_tree_bytes); + + for (_, draw_command) in self.draw_tree.iter() { + if let DrawCommand::CachedShape(cached_shape) = draw_command { + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(cached_shape.cpu_heap_bytes()) + .saturating_add(cached_tessellation_heap_bytes( + &cached_shape.cached_shape.tessellation, + &mut visited_tessellations, + )); + total_usage.gpu_buffer_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(cached_shape.gpu_buffer_bytes()); + } + } + + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(hash_map_capacity_bytes(&self.metadata_to_clips)) + .saturating_add(hash_map_capacity_bytes(&self.geometry_dedup_map)) + .saturating_add(hash_map_capacity_bytes(&self.shape_cache)); + + for cached_shape in self.shape_cache.values() { + total_usage.cpu_bytes = + total_usage + .cpu_bytes + .saturating_add(cached_tessellation_heap_bytes( + &cached_shape.tessellation, + &mut visited_tessellations, + )); + } + + total_usage.add(self.texture_manager.memory_usage()); + total_usage.add( + self.buffers_pool_manager + .memory_usage(&mut visited_tessellations), + ); + + let mut effects_usage = MemoryUsage { + cpu_bytes: hash_map_capacity_bytes(&self.loaded_effects) + .saturating_add(hash_map_capacity_bytes(&self.group_effects)) + .saturating_add(hash_map_capacity_bytes(&self.backdrop_effects)), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + for loaded_effect in self.loaded_effects.values() { + effects_usage.add(loaded_effect.memory_usage()); + } + for effect_instance in self + .group_effects + .values() + .chain(self.backdrop_effects.values()) + { + effects_usage.add(effect_instance.memory_usage()); + } + total_usage.add(effects_usage); + total_usage.add(self.offscreen_texture_pool.memory_usage()); + total_usage.add(self.scratch.memory_usage()); + + total_usage + } + + fn print_memory_usage_info_with_format(&self, human_readable_only: bool) { + let memory_value = |bytes| Self::memory_value(bytes, human_readable_only); + let mut total_usage = MemoryUsage { + cpu_bytes: std::mem::size_of_val(self) as u64, + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + let mut visited_tessellations: HashSet<*const CachedTessellation> = HashSet::new(); + println!("=== Memory Usage Info ==="); println!("Cached shapes: {}", self.shape_cache.len()); @@ -366,125 +606,475 @@ impl<'a> Renderer<'a> { "Metadata to clips mappings: {}", self.metadata_to_clips.len() ); + println!( + "Renderer CPU inline fields: {}", + memory_value(std::mem::size_of_val(self) as u64) + ); println!("\n--- Temporary Vectors ---"); + let temp_vertices_bytes = vector_capacity_bytes(&self.temp_vertices); + total_usage.cpu_bytes = total_usage.cpu_bytes.saturating_add(temp_vertices_bytes); println!( - "Temp vertices: {} items, {} capacity, ~{} bytes", + "Temp vertices: {} items, {} capacity, {}", self.temp_vertices.len(), self.temp_vertices.capacity(), - self.temp_vertices.capacity() * std::mem::size_of::() + memory_value(temp_vertices_bytes) ); + let temp_indices_bytes = vector_capacity_bytes(&self.temp_indices); + total_usage.cpu_bytes = total_usage.cpu_bytes.saturating_add(temp_indices_bytes); println!( - "Temp indices: {} items, {} capacity, ~{} bytes", + "Temp indices: {} items, {} capacity, {}", self.temp_indices.len(), self.temp_indices.capacity(), - self.temp_indices.capacity() * std::mem::size_of::() + memory_value(temp_indices_bytes) ); + let temp_instance_transform_bytes = vector_capacity_bytes(&self.temp_instance_transforms); + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(temp_instance_transform_bytes); println!( - "Temp instance transforms: {} items, {} capacity, ~{} bytes", + "Temp instance transforms: {} items, {} capacity, {}", self.temp_instance_transforms.len(), self.temp_instance_transforms.capacity(), - self.temp_instance_transforms.capacity() * std::mem::size_of::() + memory_value(temp_instance_transform_bytes) ); + let temp_instance_color_bytes = vector_capacity_bytes(&self.temp_instance_colors); + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(temp_instance_color_bytes); println!( - "Temp instance colors: {} items, {} capacity, ~{} bytes", + "Temp instance colors: {} items, {} capacity, {}", self.temp_instance_colors.len(), self.temp_instance_colors.capacity(), - self.temp_instance_colors.capacity() * std::mem::size_of::() + memory_value(temp_instance_color_bytes) ); + let temp_instance_metadata_bytes = vector_capacity_bytes(&self.temp_instance_metadata); + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(temp_instance_metadata_bytes); println!( - "Temp instance metadata: {} items, {} capacity, ~{} bytes", + "Temp instance metadata: {} items, {} capacity, {}", self.temp_instance_metadata.len(), self.temp_instance_metadata.capacity(), - self.temp_instance_metadata.capacity() * std::mem::size_of::() + memory_value(temp_instance_metadata_bytes) ); println!("\n--- GPU Buffers ---"); - if let Some(buf) = &self.aggregated_vertex_buffer { - println!("Aggregated vertex buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.aggregated_index_buffer { - println!("Aggregated index buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.aggregated_instance_transform_buffer { - println!("Aggregated instance transform buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.aggregated_instance_color_buffer { - println!("Aggregated instance color buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.aggregated_instance_metadata_buffer { - println!("Aggregated instance metadata buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.identity_instance_transform_buffer { - println!("Identity instance transform buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.identity_instance_color_buffer { - println!("Identity instance color buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.identity_instance_metadata_buffer { - println!("Identity instance metadata buffer: {} bytes", buf.size()); - } + Self::print_optional_buffer( + "Aggregated vertex buffer", + &self.aggregated_vertex_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Aggregated index buffer", + &self.aggregated_index_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Aggregated instance transform buffer", + &self.aggregated_instance_transform_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Aggregated instance color buffer", + &self.aggregated_instance_color_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Aggregated instance metadata buffer", + &self.aggregated_instance_metadata_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Identity instance transform buffer", + &self.identity_instance_transform_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Identity instance color buffer", + &self.identity_instance_color_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "Identity instance metadata buffer", + &self.identity_instance_metadata_buffer, + &mut total_usage, + &memory_value, + ); println!("\n--- ARGB Compute Buffers ---"); - if let Some(buf) = &self.argb_input_buffer { + if let Some(buffer) = &self.argb_input_buffer { + total_usage.gpu_buffer_bytes = + total_usage.gpu_buffer_bytes.saturating_add(buffer.size()); println!( - "ARGB input buffer: {} bytes (cached size: {})", - buf.size(), - self.argb_input_buffer_size + "ARGB input buffer: {} (cached size: {})", + memory_value(buffer.size()), + memory_value(self.argb_input_buffer_size) ); } - if let Some(buf) = &self.argb_output_storage_buffer { + if let Some(buffer) = &self.argb_output_storage_buffer { + total_usage.gpu_buffer_bytes = + total_usage.gpu_buffer_bytes.saturating_add(buffer.size()); println!( - "ARGB output storage buffer: {} bytes (cached size: {})", - buf.size(), - self.argb_output_buffer_size + "ARGB output storage buffer: {} (cached size: {})", + memory_value(buffer.size()), + memory_value(self.argb_output_buffer_size) ); } - if let Some(buf) = &self.argb_readback_buffer { - println!("ARGB readback buffer: {} bytes", buf.size()); - } - if let Some(buf) = &self.argb_params_buffer { - println!("ARGB params buffer: {} bytes", buf.size()); - } - if let Some(tex) = &self.argb_offscreen_texture { - let size = tex.size(); + Self::print_optional_buffer( + "ARGB readback buffer", + &self.argb_readback_buffer, + &mut total_usage, + &memory_value, + ); + Self::print_optional_buffer( + "ARGB params buffer", + &self.argb_params_buffer, + &mut total_usage, + &memory_value, + ); + if let Some(texture) = &self.argb_offscreen_texture { + let texture_bytes = texture_memory_size(texture.size(), self.config.format, 1, 1); + total_usage.gpu_texture_bytes = + total_usage.gpu_texture_bytes.saturating_add(texture_bytes); + let size = texture.size(); println!( - "ARGB offscreen texture: {}x{} (cached: {}x{})", - size.width, size.height, self.argb_cached_width, self.argb_cached_height + "ARGB offscreen texture: {}x{} (cached: {}x{}), {}", + size.width, + size.height, + self.argb_cached_width, + self.argb_cached_height, + memory_value(texture_bytes) ); } println!("\n--- Render-to-Buffer Caches ---"); - if let Some(tex) = &self.rtb_offscreen_texture { - let size = tex.size(); + if let Some(texture) = &self.rtb_offscreen_texture { + let texture_bytes = texture_memory_size(texture.size(), self.config.format, 1, 1); + total_usage.gpu_texture_bytes = + total_usage.gpu_texture_bytes.saturating_add(texture_bytes); + let size = texture.size(); println!( - "RTB offscreen texture: {}x{} (cached: {}x{})", - size.width, size.height, self.rtb_cached_width, self.rtb_cached_height + "RTB offscreen texture: {}x{} (cached: {}x{}), {}", + size.width, + size.height, + self.rtb_cached_width, + self.rtb_cached_height, + memory_value(texture_bytes) ); } - if let Some(buf) = &self.rtb_readback_buffer { - println!("RTB readback buffer: {} bytes", buf.size()); - } + Self::print_optional_buffer( + "RTB readback buffer", + &self.rtb_readback_buffer, + &mut total_usage, + &memory_value, + ); println!("\n--- Uniform Buffers ---"); + total_usage.gpu_buffer_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(self.and_uniform_buffer.size()) + .saturating_add(self.decrementing_uniform_buffer.size()); + println!( + "AND uniform buffer: {}", + memory_value(self.and_uniform_buffer.size()) + ); + println!( + "Decrementing uniform buffer: {}", + memory_value(self.decrementing_uniform_buffer.size()) + ); + + println!("\n--- Renderer Textures ---"); + if let Some(texture) = &self.msaa_color_texture { + let texture_bytes = texture_memory_size( + texture.size(), + self.config.format, + self.msaa_sample_count, + 1, + ); + total_usage.gpu_texture_bytes = + total_usage.gpu_texture_bytes.saturating_add(texture_bytes); + println!( + "MSAA color texture: {} samples, {}", + self.msaa_sample_count, + memory_value(texture_bytes) + ); + } + if let Some(texture) = &self.depth_stencil_texture { + let texture_bytes = texture_memory_size( + texture.size(), + wgpu::TextureFormat::Depth24PlusStencil8, + self.msaa_sample_count, + 1, + ); + total_usage.gpu_texture_bytes = + total_usage.gpu_texture_bytes.saturating_add(texture_bytes); + println!("Depth/stencil texture: {}", memory_value(texture_bytes)); + } + if self.surface.is_some() { + let surface_texture_bytes = texture_memory_size( + wgpu::Extent3d { + width: self.config.width, + height: self.config.height, + depth_or_array_layers: 1, + }, + self.config.format, + 1, + 1, + ) + .saturating_mul(self.config.desired_maximum_frame_latency as u64); + total_usage.gpu_texture_bytes = total_usage + .gpu_texture_bytes + .saturating_add(surface_texture_bytes); + println!( + "Surface texture chain estimate: {}", + memory_value(surface_texture_bytes) + ); + } + let default_texture_bytes = texture_memory_size( + wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + wgpu::TextureFormat::Rgba8UnormSrgb, + 1, + 1, + ) + .saturating_mul(3); + let default_backdrop_params_bytes = + std::mem::size_of::() as u64; + total_usage.gpu_texture_bytes = total_usage + .gpu_texture_bytes + .saturating_add(default_texture_bytes); + total_usage.gpu_buffer_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(default_backdrop_params_bytes); println!( - "AND uniform buffer: {} bytes", - self.and_uniform_buffer.size() + "Default bind-group textures: {}", + memory_value(default_texture_bytes) ); println!( - "Decrementing uniform buffer: {} bytes", - self.decrementing_uniform_buffer.size() + "Default backdrop material buffer: {}", + memory_value(default_backdrop_params_bytes) + ); + + println!("\n--- Draw Data and CPU Caches ---"); + let draw_tree_bytes = (self.draw_tree.len() as u64).saturating_mul( + std::mem::size_of::() as u64 + + std::mem::size_of::>() as u64 + + std::mem::size_of::>() as u64, + ); + total_usage.cpu_bytes = total_usage.cpu_bytes.saturating_add(draw_tree_bytes); + println!("Draw tree node payloads: {}", memory_value(draw_tree_bytes)); + + let mut draw_command_heap_bytes = 0_u64; + let mut draw_command_gpu_buffer_bytes = 0_u64; + for (_, draw_command) in self.draw_tree.iter() { + if let DrawCommand::CachedShape(cached_shape) = draw_command { + draw_command_heap_bytes = + draw_command_heap_bytes.saturating_add(cached_shape.cpu_heap_bytes()); + draw_command_heap_bytes = + draw_command_heap_bytes.saturating_add(cached_tessellation_heap_bytes( + &cached_shape.cached_shape.tessellation, + &mut visited_tessellations, + )); + draw_command_gpu_buffer_bytes = + draw_command_gpu_buffer_bytes.saturating_add(cached_shape.gpu_buffer_bytes()); + } + } + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(draw_command_heap_bytes); + total_usage.gpu_buffer_bytes = total_usage + .gpu_buffer_bytes + .saturating_add(draw_command_gpu_buffer_bytes); + println!( + "Draw command heap payloads: {}", + memory_value(draw_command_heap_bytes) + ); + println!( + "Draw command GPU buffers: {}", + memory_value(draw_command_gpu_buffer_bytes) + ); + + let metadata_to_clips_bytes = hash_map_capacity_bytes(&self.metadata_to_clips); + let geometry_dedup_map_bytes = hash_map_capacity_bytes(&self.geometry_dedup_map); + let shape_cache_map_bytes = hash_map_capacity_bytes(&self.shape_cache); + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(metadata_to_clips_bytes) + .saturating_add(geometry_dedup_map_bytes) + .saturating_add(shape_cache_map_bytes); + println!( + "Metadata to clips map: {}", + memory_value(metadata_to_clips_bytes) + ); + println!( + "Geometry dedup map: {}", + memory_value(geometry_dedup_map_bytes) + ); + println!("Shape cache map: {}", memory_value(shape_cache_map_bytes)); + + let mut shape_cache_tessellation_bytes = 0_u64; + for cached_shape in self.shape_cache.values() { + shape_cache_tessellation_bytes = + shape_cache_tessellation_bytes.saturating_add(cached_tessellation_heap_bytes( + &cached_shape.tessellation, + &mut visited_tessellations, + )); + } + total_usage.cpu_bytes = total_usage + .cpu_bytes + .saturating_add(shape_cache_tessellation_bytes); + println!( + "Unique cached tessellations: {}", + memory_value(shape_cache_tessellation_bytes) ); println!("\n--- Texture Manager ---"); - println!("{:?}", self.texture_manager.size()); + let texture_manager_usage = self.texture_manager.memory_usage(); + total_usage.add(texture_manager_usage); + let (texture_count, texture_bind_group_count) = self.texture_manager.size(); + println!( + "Textures: {}, bind groups: {}, CPU {}, GPU textures {}", + texture_count, + texture_bind_group_count, + memory_value(texture_manager_usage.cpu_bytes), + memory_value(texture_manager_usage.gpu_texture_bytes) + ); println!("\n--- Buffer Pool Manager ---"); self.buffers_pool_manager.print_sizes(); + let buffer_pool_usage = self + .buffers_pool_manager + .memory_usage(&mut visited_tessellations); + total_usage.add(buffer_pool_usage); + println!( + "Buffer pool CPU: {}, GPU buffers: {}, GPU textures: {}", + memory_value(buffer_pool_usage.cpu_bytes), + memory_value(buffer_pool_usage.gpu_buffer_bytes), + memory_value(buffer_pool_usage.gpu_texture_bytes) + ); + + println!("\n--- Effects ---"); + let mut effects_usage = MemoryUsage { + cpu_bytes: hash_map_capacity_bytes(&self.loaded_effects) + .saturating_add(hash_map_capacity_bytes(&self.group_effects)) + .saturating_add(hash_map_capacity_bytes(&self.backdrop_effects)), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + for loaded_effect in self.loaded_effects.values() { + effects_usage.add(loaded_effect.memory_usage()); + } + for effect_instance in self + .group_effects + .values() + .chain(self.backdrop_effects.values()) + { + effects_usage.add(effect_instance.memory_usage()); + } + total_usage.add(effects_usage); + println!( + "Loaded effects: {}, group instances: {}, backdrop instances: {}", + self.loaded_effects.len(), + self.group_effects.len(), + self.backdrop_effects.len() + ); + println!( + "Effects CPU: {}, GPU buffers: {}", + memory_value(effects_usage.cpu_bytes), + memory_value(effects_usage.gpu_buffer_bytes) + ); + + let offscreen_pool_usage = self.offscreen_texture_pool.memory_usage(); + total_usage.add(offscreen_pool_usage); + println!( + "Offscreen texture pool CPU: {}, GPU textures: {}", + memory_value(offscreen_pool_usage.cpu_bytes), + memory_value(offscreen_pool_usage.gpu_texture_bytes) + ); + + println!("\n--- Renderer Scratch ---"); + let scratch_usage = self.scratch.memory_usage(); + total_usage.add(scratch_usage); + println!( + "Scratch CPU: {}, GPU textures: {}", + memory_value(scratch_usage.cpu_bytes), + memory_value(scratch_usage.gpu_texture_bytes) + ); + + println!("\n--- Totals ---"); + println!( + "CPU-side tracked memory: {}", + memory_value(total_usage.cpu_bytes) + ); + println!( + "GPU buffer memory: {}", + memory_value(total_usage.gpu_buffer_bytes) + ); + println!( + "GPU texture memory: {}", + memory_value(total_usage.gpu_texture_bytes) + ); + println!("Tracked total: {}", memory_value(total_usage.total_bytes())); + println!( + "Note: WGPU driver-private allocations for pipelines, bind groups, samplers, device, queue, and surface internals are not exposed by wgpu." + ); println!("========================="); } + fn memory_value(bytes: u64, human_readable_only: bool) -> String { + let formatted = Self::format_memory_usage_size(bytes); + if human_readable_only { + formatted + } else { + format!("{bytes} bytes ({formatted})") + } + } + + fn print_optional_buffer( + label: &str, + buffer: &Option, + total_usage: &mut MemoryUsage, + memory_value: &impl Fn(u64) -> String, + ) { + if let Some(buffer) = buffer { + total_usage.gpu_buffer_bytes = + total_usage.gpu_buffer_bytes.saturating_add(buffer.size()); + println!("{label}: {}", memory_value(buffer.size())); + } + } + + fn add_optional_buffer_usage(buffer: &Option, total_usage: &mut MemoryUsage) { + if let Some(buffer) = buffer { + total_usage.gpu_buffer_bytes = + total_usage.gpu_buffer_bytes.saturating_add(buffer.size()); + } + } + + fn add_optional_texture_usage( + texture: &Option, + format: wgpu::TextureFormat, + sample_count: u32, + total_usage: &mut MemoryUsage, + ) { + if let Some(texture) = texture { + total_usage.gpu_texture_bytes = total_usage + .gpu_texture_bytes + .saturating_add(texture_memory_size(texture.size(), format, sample_count, 1)); + } + } + fn create_default_shape_texture_bind_group( device: &Arc, queue: &Arc, diff --git a/src/renderer/traversal.rs b/src/renderer/traversal.rs index dcce336..925935a 100644 --- a/src/renderer/traversal.rs +++ b/src/renderer/traversal.rs @@ -30,6 +30,11 @@ impl TraversalScratch { pub(super) fn events(&self) -> &[TraversalEvent] { &self.events } + + pub(super) fn cpu_heap_bytes(&self) -> u64 { + crate::util::vector_capacity_bytes(&self.events) + .saturating_add(crate::util::vector_capacity_bytes(&self.skipped_stack)) + } } pub(super) fn subtree_has_backdrop_effects( diff --git a/src/renderer/types.rs b/src/renderer/types.rs index f1b6c10..0af74f2 100644 --- a/src/renderer/types.rs +++ b/src/renderer/types.rs @@ -5,7 +5,7 @@ use ahash::{HashMap, HashMapExt}; use crate::effect::{self, LoadedEffect}; use crate::shape::{CachedShapeDrawData, DrawShapeCommand}; use crate::texture_manager::TextureManager; -use crate::util::GradientCache; +use crate::util::{hash_map_capacity_bytes, vector_capacity_bytes, GradientCache, MemoryUsage}; use crate::vertex::InstanceTransform; use super::traversal::TraversalScratch; @@ -423,6 +423,35 @@ impl RendererScratch { trim_vector_if_needed(&mut self.readback_bytes, MAX_READBACK_BYTES_CAPACITY); self.traversal_scratch.trim_to_policy(); } + + pub(super) fn memory_usage(&self) -> MemoryUsage { + let mut usage = MemoryUsage { + cpu_bytes: hash_map_capacity_bytes(&self.effect_results) + .saturating_add(vector_capacity_bytes(&self.effect_node_ids)) + .saturating_add(vector_capacity_bytes(&self.textures_to_recycle)) + .saturating_add(vector_capacity_bytes(&self.effect_output_textures)) + .saturating_add(vector_capacity_bytes(&self.stencil_stack)) + .saturating_add(vector_capacity_bytes(&self.skipped_stack)) + .saturating_add(vector_capacity_bytes(&self.scissor_stack)) + .saturating_add(vector_capacity_bytes(&self.clip_kind_stack)) + .saturating_add(vector_capacity_bytes(&self.backdrop_work_textures)) + .saturating_add(vector_capacity_bytes(&self.readback_bytes)) + .saturating_add(self.traversal_scratch.cpu_heap_bytes()), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + + for texture in self + .textures_to_recycle + .iter() + .chain(self.effect_output_textures.iter()) + .chain(self.backdrop_work_textures.iter()) + { + usage.add(texture.memory_usage()); + } + + usage + } } pub(super) fn trim_vector_if_needed(values: &mut Vec, max_capacity: usize) { diff --git a/src/shape.rs b/src/shape.rs index dfb40e2..22f03fe 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -36,7 +36,9 @@ use crate::cache::CachedTessellation; use crate::gradient::types::Fill; -use crate::util::{GradientCache, PoolManager}; +use crate::util::{ + hash_map_capacity_bytes, vector_capacity_bytes, GradientCache, MemoryUsage, PoolManager, +}; use crate::vertex::{CustomVertex, InstanceTransform}; use crate::{Color, Stroke}; use ahash::AHashMap; @@ -585,6 +587,23 @@ impl AaFringeScratch { self.boundary_edges.shrink_to_fit(); self.triangle_stack.shrink_to_fit(); } + + pub(crate) fn memory_usage(&self) -> MemoryUsage { + MemoryUsage { + cpu_bytes: hash_map_capacity_bytes(&self.edge_use_counts) + .saturating_add(hash_map_capacity_bytes(&self.edge_owners)) + .saturating_add(hash_map_capacity_bytes(&self.incident_triangles_by_vertex)) + .saturating_add(hash_map_capacity_bytes(&self.triangle_adjacency)) + .saturating_add(hash_map_capacity_bytes(&self.visited_triangles)) + .saturating_add(hash_map_capacity_bytes(&self.triangle_component_map)) + .saturating_add(hash_map_capacity_bytes(&self.boundary_corner_normals)) + .saturating_add(hash_map_capacity_bytes(&self.outer_vertex_indices)) + .saturating_add(vector_capacity_bytes(&self.boundary_edges)) + .saturating_add(vector_capacity_bytes(&self.triangle_stack)), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + } + } } fn normalized_float_bits(value: f32) -> u32 { @@ -1245,6 +1264,17 @@ impl CachedShapeDrawData { } } + pub(crate) fn cpu_heap_bytes(&self) -> u64 { + self.fill.as_ref().map(Fill::heap_bytes).unwrap_or_default() + } + + pub(crate) fn gpu_buffer_bytes(&self) -> u64 { + self.backdrop_material_params_buffer + .as_ref() + .map(wgpu::Buffer::size) + .unwrap_or_default() + } + pub fn refresh_gradient_bind_group( &mut self, gradient_cache: &mut GradientCache, diff --git a/src/texture_manager.rs b/src/texture_manager.rs index 580932a..326d01c 100644 --- a/src/texture_manager.rs +++ b/src/texture_manager.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock}; +use crate::util::{hash_map_capacity_bytes, texture_memory_size, MemoryUsage}; + #[derive(Debug, thiserror::Error)] pub enum TextureManagerError { #[error("Texture {0} not found")] @@ -98,6 +100,27 @@ impl TextureManager { ) } + pub(crate) fn memory_usage(&self) -> MemoryUsage { + let texture_storage = self.texture_storage.read().unwrap(); + let texture_storage_cpu_bytes = hash_map_capacity_bytes(&texture_storage); + let texture_gpu_bytes = texture_storage + .values() + .map(|texture| { + texture_memory_size(texture.size(), wgpu::TextureFormat::Rgba8UnormSrgb, 1, 1) + }) + .sum(); + drop(texture_storage); + + let shape_bind_group_cache = self.shape_bind_group_cache.read().unwrap(); + let bind_group_cache_cpu_bytes = hash_map_capacity_bytes(&shape_bind_group_cache); + + MemoryUsage { + cpu_bytes: texture_storage_cpu_bytes.saturating_add(bind_group_cache_cpu_bytes), + gpu_texture_bytes: texture_gpu_bytes, + gpu_buffer_bytes: 0, + } + } + fn create_sampler(device: &wgpu::Device) -> wgpu::Sampler { device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, diff --git a/src/util.rs b/src/util.rs index 3a33fd5..75931ff 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -use crate::cache::Cache; +use crate::cache::{Cache, CachedTessellation}; use crate::gradient::gpu::{ create_default_ramp_texture, create_ramp_texture, GpuGradientColorParams, GpuMaterialParams, }; @@ -8,6 +8,8 @@ use crate::shape::AaFringeScratch; use crate::vertex::CustomVertex; use lru::LruCache; use lyon::tessellation::VertexBuffers; +use std::collections::HashSet; +use std::hash::{BuildHasher, Hash}; use std::num::NonZeroUsize; use std::sync::Arc; @@ -15,6 +17,106 @@ use std::sync::Arc; const MAX_GRADIENT_RAMP_CACHE_SIZE: usize = 256; const MAX_GRADIENT_BIND_GROUP_CACHE_SIZE: usize = 1024; +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct MemoryUsage { + pub(crate) cpu_bytes: u64, + pub(crate) gpu_buffer_bytes: u64, + pub(crate) gpu_texture_bytes: u64, +} + +impl MemoryUsage { + pub(crate) fn total_bytes(self) -> u64 { + self.cpu_bytes + .saturating_add(self.gpu_buffer_bytes) + .saturating_add(self.gpu_texture_bytes) + } + + pub(crate) fn add(&mut self, other: Self) { + self.cpu_bytes = self.cpu_bytes.saturating_add(other.cpu_bytes); + self.gpu_buffer_bytes = self.gpu_buffer_bytes.saturating_add(other.gpu_buffer_bytes); + self.gpu_texture_bytes = self + .gpu_texture_bytes + .saturating_add(other.gpu_texture_bytes); + } +} + +pub(crate) fn vector_capacity_bytes(values: &Vec) -> u64 { + (values.capacity() as u64).saturating_mul(std::mem::size_of::() as u64) +} + +pub(crate) fn hash_map_capacity_bytes(values: &std::collections::HashMap) -> u64 +where + S: BuildHasher, +{ + (values.capacity() as u64).saturating_mul( + std::mem::size_of::() + .saturating_add(std::mem::size_of::()) + .saturating_add(std::mem::size_of::()) + .saturating_add(1) as u64, + ) +} + +pub(crate) fn lru_cache_capacity_bytes(values: &lru::LruCache) -> u64 +where + K: Eq + Hash, + S: BuildHasher, +{ + let hash_map_slots = (values.cap().get() as u64).saturating_mul( + std::mem::size_of::() + .saturating_add(std::mem::size_of::<*const ()>()) + .saturating_add(1) as u64, + ); + let linked_entries = (values.len() as u64).saturating_mul( + std::mem::size_of::() + .saturating_add(std::mem::size_of::()) + .saturating_add(std::mem::size_of::<*const ()>() * 2) as u64, + ); + + hash_map_slots.saturating_add(linked_entries) +} + +pub(crate) fn texture_memory_size( + size: wgpu::Extent3d, + format: wgpu::TextureFormat, + sample_count: u32, + mip_level_count: u32, +) -> u64 { + let bytes_per_block = texture_format_bytes_per_block(format); + let (block_width, block_height) = format.block_dimensions(); + let mut total = 0_u64; + let mut width = size.width.max(1); + let mut height = size.height.max(1); + let mut depth_or_layers = size.depth_or_array_layers.max(1); + + for _ in 0..mip_level_count.max(1) { + let blocks_wide = width.div_ceil(block_width.max(1)) as u64; + let blocks_high = height.div_ceil(block_height.max(1)) as u64; + total = total.saturating_add( + blocks_wide + .saturating_mul(blocks_high) + .saturating_mul(depth_or_layers as u64) + .saturating_mul(bytes_per_block as u64) + .saturating_mul(sample_count.max(1) as u64), + ); + + width = (width / 2).max(1); + height = (height / 2).max(1); + depth_or_layers = (depth_or_layers / 2).max(1); + } + + total +} + +fn texture_format_bytes_per_block(format: wgpu::TextureFormat) -> u32 { + format.block_copy_size(None).unwrap_or(match format { + // WebGPU leaves these as implementation-defined. Current desktop backends store + // Depth24Plus as a 32-bit depth allocation and Depth24PlusStencil8 as D24S8. + wgpu::TextureFormat::Depth24Plus | wgpu::TextureFormat::Depth24PlusStencil8 => 4, + wgpu::TextureFormat::Depth32FloatStencil8 => 8, + _ => 4, + }) +} + pub fn normalize_rgba_color(color: &[u8; 4]) -> [f32; 4] { [ srgb_u8_to_linear(color[0]), @@ -94,10 +196,30 @@ struct CachedGradientRampTexture { view: Arc, } +impl CachedGradientRampTexture { + fn memory_usage(&self) -> MemoryUsage { + MemoryUsage { + cpu_bytes: 0, + gpu_buffer_bytes: 0, + gpu_texture_bytes: texture_memory_size( + self._texture.size(), + wgpu::TextureFormat::Rgba32Float, + 1, + 1, + ), + } + } +} + +struct CachedGradientBindGroup { + bind_group: Arc, + params_buffer: wgpu::Buffer, +} + pub(crate) struct GradientCache { ramps: LruCache, ramp_textures: LruCache>, - bind_groups: LruCache>, + bind_groups: LruCache>, default_ramp_texture: Option>, } @@ -204,7 +326,7 @@ impl GradientCache { }; if let Some(bind_group) = self.bind_groups.get(&cache_key) { - return bind_group.clone(); + return bind_group.bind_group.clone(); } let ramp_texture = if gradient_data.is_constant { @@ -238,8 +360,12 @@ impl GradientCache { }, ], })); + let cached_bind_group = Arc::new(CachedGradientBindGroup { + bind_group: bind_group.clone(), + params_buffer, + }); - self.bind_groups.put(cache_key, bind_group.clone()); + self.bind_groups.put(cache_key, cached_bind_group); bind_group } @@ -291,6 +417,30 @@ impl GradientCache { fn trim(&mut self) {} + pub(crate) fn memory_usage(&self) -> MemoryUsage { + let mut usage = MemoryUsage { + cpu_bytes: lru_cache_capacity_bytes(&self.ramps) + .saturating_add(lru_cache_capacity_bytes(&self.ramp_textures)) + .saturating_add(lru_cache_capacity_bytes(&self.bind_groups)), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + + for (_, ramp_texture) in self.ramp_textures.iter() { + usage.add(ramp_texture.memory_usage()); + } + if let Some(default_ramp_texture) = &self.default_ramp_texture { + usage.add(default_ramp_texture.memory_usage()); + } + for (_, cached_bind_group) in self.bind_groups.iter() { + usage.gpu_buffer_bytes = usage + .gpu_buffer_bytes + .saturating_add(cached_bind_group.params_buffer.size()); + } + + usage + } + fn print_sizes(&self) { println!("Gradient ramps: {}", self.ramps.len()); println!("Gradient ramp textures: {}", self.ramp_textures.len()); @@ -319,6 +469,23 @@ impl LyonVertexBuffersPool { } } + fn memory_usage(&self) -> MemoryUsage { + let mut usage = MemoryUsage { + cpu_bytes: vector_capacity_bytes(&self.vertex_buffers), + gpu_buffer_bytes: 0, + gpu_texture_bytes: 0, + }; + + for vertex_buffers in &self.vertex_buffers { + usage.cpu_bytes = usage + .cpu_bytes + .saturating_add(vector_capacity_bytes(&vertex_buffers.vertices)) + .saturating_add(vector_capacity_bytes(&vertex_buffers.indices)); + } + + usage + } + // pub fn return_vertex_buffers(&mut self, mut vertex_buffers: VertexBuffers) { // vertex_buffers.vertices.clear(); // vertex_buffers.indices.clear(); @@ -350,6 +517,17 @@ impl PoolManager { self.gradient_cache.trim(); } + pub(crate) fn memory_usage( + &self, + visited_tessellations: &mut HashSet<*const CachedTessellation>, + ) -> MemoryUsage { + let mut usage = self.lyon_vertex_buffers_pool.memory_usage(); + usage.add(self.tessellation_cache.memory_usage(visited_tessellations)); + usage.add(self.aa_fringe_scratch.memory_usage()); + usage.add(self.gradient_cache.memory_usage()); + usage + } + pub fn print_sizes(&self) { println!("Pool sizes:"); println!("Vertex buffers: {}", self.lyon_vertex_buffers_pool.len());