Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,37 @@ use std::num::NonZeroUsize;
use std::sync::Arc;

pub(crate) struct Cache {
tessellation_cache: LruCache<u64, Arc<VertexBuffers<CustomVertex, u16>>>,
tessellation_cache: Option<LruCache<u64, Arc<VertexBuffers<CustomVertex, u16>>>>,
}

impl Cache {
pub(crate) fn new(size: NonZeroUsize) -> Self {
pub(crate) fn new(size: NonZeroUsize, enabled: bool) -> Self {
Self {
tessellation_cache: LruCache::new(size),
tessellation_cache: enabled.then(|| LruCache::new(size)),
}
}

pub fn len(&self) -> usize {
self.tessellation_cache.len()
self.tessellation_cache.as_ref().map_or(0, LruCache::len)
}

pub(crate) fn get_vertex_buffers(
&mut self,
cache_key: &u64,
) -> Option<Arc<VertexBuffers<CustomVertex, u16>>> {
self.tessellation_cache.get(cache_key).cloned()
self.tessellation_cache
.as_mut()
.and_then(|cache| cache.get(cache_key).cloned())
}

pub(crate) fn insert_vertex_buffers(
&mut self,
cache_key: u64,
vertex_buffers: Arc<VertexBuffers<CustomVertex, u16>>,
) {
self.tessellation_cache.put(cache_key, vertex_buffers);
if let Some(cache) = self.tessellation_cache.as_mut() {
cache.put(cache_key, vertex_buffers);
}
}
}

Expand All @@ -45,7 +49,7 @@ mod tests {

#[test]
fn cache_returns_shared_arc_without_cloning_vertex_buffers() {
let mut cache = Cache::new(NonZeroUsize::new(4).unwrap());
let mut cache = Cache::new(NonZeroUsize::new(4).unwrap(), true);
let mut vertex_buffers = VertexBuffers::<CustomVertex, u16>::new();
vertex_buffers.vertices.push(CustomVertex {
position: [0.0, 0.0],
Expand All @@ -61,4 +65,15 @@ mod tests {
let cached_vertex_buffers = cache.get_vertex_buffers(&7).unwrap();
assert!(Arc::ptr_eq(&shared_vertex_buffers, &cached_vertex_buffers));
}

#[test]
fn disabled_cache_never_stores_entries() {
let mut cache = Cache::new(NonZeroUsize::new(4).unwrap(), false);
let vertex_buffers = Arc::new(VertexBuffers::<CustomVertex, u16>::new());

cache.insert_vertex_buffers(7, vertex_buffers);

assert_eq!(cache.len(), 0);
assert!(cache.get_vertex_buffers(&7).is_none());
}
}
33 changes: 33 additions & 0 deletions src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ pub(crate) struct OffscreenTexturePool {
available: Vec<PooledTexture>,
}

pub(crate) struct OffscreenTexturePoolStats {
pub pooled_textures: usize,
pub estimated_color_bytes: u64,
pub estimated_resolve_bytes: u64,
pub estimated_depth_stencil_bytes: u64,
}

/// Maximum number of textures to keep in the pool.
const MAX_POOL_SIZE: usize = 8;

Expand All @@ -142,6 +149,10 @@ impl OffscreenTexturePool {
}
}

pub fn clear(&mut self) {
self.available.clear();
}

/// Return textures for reuse in future frames.
/// Textures that don't match the given active configuration are dropped
/// immediately, and the pool is capped at `MAX_POOL_SIZE`.
Expand Down Expand Up @@ -185,6 +196,28 @@ impl OffscreenTexturePool {
}
}

pub(crate) fn stats(&self) -> OffscreenTexturePoolStats {
let mut estimated_color_bytes = 0_u64;
let mut estimated_resolve_bytes = 0_u64;
let mut estimated_depth_stencil_bytes = 0_u64;

for texture in &self.available {
let pixels = texture.width as u64 * texture.height as u64;
estimated_color_bytes += pixels * 4 * texture.sample_count as u64;
if texture.resolve_texture.is_some() {
estimated_resolve_bytes += pixels * 4;
}
estimated_depth_stencil_bytes += pixels * 4 * texture.sample_count as u64;
}

OffscreenTexturePoolStats {
pooled_textures: self.available.len(),
estimated_color_bytes,
estimated_resolve_bytes,
estimated_depth_stencil_bytes,
}
}
Comment on lines +199 to +219
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Memory estimate assumes 4 B/pixel color & depth/stencil.

estimated_color_bytes multiplies by a hard-coded 4, but pooled textures use the surface config.format (see acquire call sites), which can be e.g. Rgba16Float (8 B) or Rgba8Unorm (4 B). Similarly, the depth/stencil is hard-coded Depth24PlusStencil8 in create_pooled_texture, which on most backends is 4 B — fine in practice but driver‑dependent. Since this is reported via print_memory_usage_info, consider either documenting it as an approximation or deriving the color byte size from color_texture.format().block_copy_size(None) to keep the number meaningful when formats change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/effect.rs` around lines 199 - 219, The stats() implementation in
effect.rs assumes 4 bytes per pixel for color and depth/stencil which is
incorrect for variable formats; update stats() to compute per-texture byte sizes
using each texture's actual format instead of hard-coded 4 (e.g., call
texture.color_texture.format().block_copy_size(None) or equivalent to get
bytes-per-block and multiply by pixel count and sample_count), and similarly
derive the depth/stencil size from the created depth texture's format used in
create_pooled_texture (or document that the numbers are approximate if you
prefer not to query formats); ensure OffscreenTexturePoolStats fields
(estimated_color_bytes, estimated_resolve_bytes, estimated_depth_stencil_bytes)
accumulate these computed sizes and keep pooled_textures = self.available.len()
unchanged so acquire callers relying on format selection are accurately
reflected.


fn create_pooled_texture(
device: &wgpu::Device,
width: u32,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub use gradient::types::{
pub use renderer::MathRect;
pub use renderer::Renderer;
pub use renderer::RendererCreationError;
pub use renderer::RendererOptions;
pub use renderer::TextureLayer;
pub use shape::*;
pub use stroke::Stroke;
Expand Down
30 changes: 30 additions & 0 deletions src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,35 @@ pub type MathRect = lyon::math::Box2D;
// TODO: move to the config/constructor
const MAX_CACHED_SHAPES: usize = 1024;

/// Renderer construction options for runtime-tunable performance behavior.
///
/// `enable_reusable_caches` controls renderer-managed performance caches such as
/// tessellation reuse, gradient LRUs, and texture bind-group reuse.
///
/// Explicit resource stores like `load_shape()` geometry and loaded effects are
/// intentionally not affected, because disabling them would change renderer
/// behavior rather than just cache policy.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RendererOptions {
pub enable_reusable_caches: bool,
}

impl Default for RendererOptions {
fn default() -> Self {
Self {
enable_reusable_caches: true,
}
}
}

impl RendererOptions {
pub const fn without_reusable_caches() -> Self {
Self {
enable_reusable_caches: false,
}
}
}

/// Semantic texture layers for a shape. Background is layer 0, Foreground is layer 1.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum TextureLayer {
Expand Down Expand Up @@ -86,6 +115,7 @@ pub struct Renderer<'a> {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
config: wgpu::SurfaceConfiguration,
renderer_options: RendererOptions,

tessellator: FillTessellator,
buffers_pool_manager: PoolManager,
Expand Down
Loading
Loading