#1244: render caches bounded by bytes, evicting the least recently used instead of clearing

Text geometry, clips, clip paints, effect art, brushes, live effects, pattern tiles and freeform
grids were capped by entry count and cleared wholesale on overflow, so a document past the cap
(e.g. more than 4,096 type objects) recomputed every entry on every frame. A FrameCache keeps
per-entry byte sizes and last-used frames, drops the least recently used entries of earlier
frames, and sweeps stale ones. Text geometry keeps its glyph outlines once instead of twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
This commit is contained in:
arhammusheer
2026-10-12 11:02:17 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 454f1737a3
commit c4214be9d7
8 changed files with 498 additions and 104 deletions
+15 -12
View File
@@ -12,7 +12,6 @@
//! dimmed copies of it at the neighbouring tile positions and the tile edge.
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
use vectorcraft_doc::pattern::{Overlap, PatternDef, TileType};
@@ -22,6 +21,7 @@ use vello_cpu::peniko::{Extend, ImageQuality, ImageSampler};
use vello_cpu::{Pixmap, RenderContext};
use crate::ink::Ink;
use crate::lru::{self, FrameCache};
use crate::{Frame, RenderOptions, Rendered, Renderer};
/// Largest super-tile raster side (pixels).
@@ -36,14 +36,19 @@ struct TileEntry {
pixmap: Arc<Pixmap>,
}
#[derive(Default)]
struct PatternCache {
/// Per ink ([`Ink`] as index).
tiles: [HashMap<String, TileEntry>; 3],
/// Per ink ([`Ink`] as index), each within [`lru::PATTERN_BUDGET`], least recently used first out.
tiles: [FrameCache<String, TileEntry>; 3],
renderer: Option<Box<Renderer>>,
depth: u32,
}
impl Default for PatternCache {
fn default() -> Self {
Self { tiles: std::array::from_fn(|_| FrameCache::new(lru::PATTERN_BUDGET, usize::MAX)), renderer: None, depth: 0 }
}
}
thread_local! {
static CACHE: RefCell<PatternCache> = RefCell::new(PatternCache::default());
}
@@ -137,10 +142,10 @@ pub(crate) fn set_pattern_paint(ctx: &mut RenderContext, name: &str, xf: Affine,
let scale = raster_scale(s, (pw, ph));
let params = (def.tile, def.tile_type, def.overlap);
let hit = CACHE.with(|c| {
c.borrow().tiles[ink as usize]
.get(name)
.filter(|e| e.scale == scale && e.params == params && same_art(&e.art, &def.art))
.map(|e| (e.pixmap.clone(), e.sx, e.sy))
let mut c = c.borrow_mut();
let tiles = &mut c.tiles[ink as usize];
let t = tiles.tick();
tiles.get(name, t).filter(|e| e.scale == scale && e.params == params && same_art(&e.art, &def.art)).map(|e| (e.pixmap.clone(), e.sx, e.sy))
});
let (pm, sx, sy) = match hit {
Some(h) => h,
@@ -156,10 +161,8 @@ pub(crate) fn set_pattern_paint(ctx: &mut RenderContext, name: &str, xf: Affine,
CACHE.with(|c| {
let mut c = c.borrow_mut();
let tiles = &mut c.tiles[ink as usize];
if tiles.len() > 64 {
tiles.clear();
}
tiles.insert(name.to_string(), TileEntry { art: def.art.clone(), params, scale, sx, sy, pixmap: pm.clone() });
let (t, bytes) = (tiles.tick(), size_of::<TileEntry>() + w as usize * h as usize * 4);
tiles.insert(name.to_string(), TileEntry { art: def.art.clone(), params, scale, sx, sy, pixmap: pm.clone() }, bytes, t);
});
(pm, sx, sy)
}