🔧 Use LRU cache for tile surfaces

This commit is contained in:
Elena Torro 2025-03-26 09:58:53 +01:00
parent 60bc88a075
commit 7aeefb5b2e
4 changed files with 77 additions and 149 deletions

34
render-wasm/Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "adler2"
@ -8,6 +8,17 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
@ -823,6 +834,15 @@ dependencies = [
"regex",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
@ -901,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.15.0",
]
[[package]]
@ -1036,6 +1056,15 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lru"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91"
dependencies = [
"hashbrown 0.11.2",
]
[[package]]
name = "mac-notification-sys"
version = "0.6.2"
@ -1511,6 +1540,7 @@ dependencies = [
"cargo-watch",
"gl",
"indexmap",
"lru",
"skia-safe",
"uuid",
]

View File

@ -11,6 +11,7 @@ name = "render_wasm"
path = "src/main.rs"
[dependencies]
lru = "0.6"
base64 = "0.22.1"
gl = "0.14.0"
indexmap = "2.7.1"

View File

@ -462,12 +462,10 @@ impl RenderState {
ey + interest_delta
*/
self.pending_tiles = vec![];
self.surfaces.cache_clear_visited();
for y in sy..=ey {
for x in sx..=ex {
let tile = (x, y);
self.pending_tiles.push(tile);
self.surfaces.cache_visit(tile);
}
}
self.current_tile = None;

View File

@ -1,11 +1,11 @@
use crate::shapes::Shape;
use crate::view::Viewbox;
use skia_safe::{self as skia, Paint, RRect};
use skia_safe::{self as skia, Paint, RRect, Surface};
use super::{gpu_state::GpuState, tiles::Tile};
use base64::{engine::general_purpose, Engine as _};
use std::collections::HashMap;
use lru::LruCache;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SurfaceId {
@ -22,22 +22,22 @@ pub enum SurfaceId {
pub struct Surfaces {
// is the final destination surface, the one that it is represented in the canvas element.
target: skia::Surface,
target: Surface,
// keeps the current render
current: skia::Surface,
current: Surface,
// keeps the current shape's fills
shape_fills: skia::Surface,
shape_fills: Surface,
// keeps the current shape's strokes
shape_strokes: skia::Surface,
shape_strokes: Surface,
// used for rendering shadows
shadow: skia::Surface,
shadow: Surface,
// used for new shadow rendering
drop_shadows: skia::Surface,
inner_shadows: skia::Surface,
drop_shadows: Surface,
inner_shadows: Surface,
// for drawing the things that are over shadows.
overlay: skia::Surface,
overlay: Surface,
// for drawing debug info.
debug: skia::Surface,
debug: Surface,
// for drawing tiles.
tiles: TileSurfaceCache,
sampling_options: skia::SamplingOptions,
@ -74,8 +74,11 @@ impl Surfaces {
const POOL_CAPACITY_THRESHOLD: i32 = 4;
let pool_capacity =
(width / tile_dims.width) * (height / tile_dims.height) * POOL_CAPACITY_THRESHOLD;
let pool = SurfacePool::with_capacity(&mut target, tile_dims, pool_capacity as usize);
let tiles = TileSurfaceCache::new(pool);
let tiles = TileSurfaceCache::new(
pool_capacity as usize,
target.new_surface_with_dimensions(tile_dims).unwrap(),
tile_dims,
);
Surfaces {
target,
current,
@ -145,7 +148,7 @@ impl Surfaces {
.draw(self.canvas(to), (0.0, 0.0), sampling_options, paint);
}
pub fn apply_mut(&mut self, ids: &[SurfaceId], mut f: impl FnMut(&mut skia::Surface) -> ()) {
pub fn apply_mut(&mut self, ids: &[SurfaceId], mut f: impl FnMut(&mut Surface) -> ()) {
for id in ids {
let surface = self.get_mut(*id);
f(surface);
@ -172,7 +175,7 @@ impl Surfaces {
);
}
fn get_mut(&mut self, id: SurfaceId) -> &mut skia::Surface {
fn get_mut(&mut self, id: SurfaceId) -> &mut Surface {
match id {
SurfaceId::Target => &mut self.target,
SurfaceId::Current => &mut self.current,
@ -186,7 +189,7 @@ impl Surfaces {
}
}
fn reset_from_target(&mut self, target: skia::Surface) {
fn reset_from_target(&mut self, target: Surface) {
let dim = (target.width(), target.height());
self.target = target;
self.debug = self.target.new_surface_with_dimensions(dim).unwrap();
@ -238,18 +241,10 @@ impl Surfaces {
.reset_matrix();
}
pub fn cache_clear_visited(&mut self) {
self.tiles.clear_visited();
}
pub fn cache_visit(&mut self, tile: Tile) {
self.tiles.visit(tile);
}
pub fn cache_tile_surface(&mut self, tile: Tile, id: SurfaceId, color: skia::Color) {
let sampling_options = self.sampling_options;
let mut tile_surface = self.tiles.get_or_create(tile).unwrap();
let margins = self.margins;
let mut tile_surface = self.tiles.get_or_create(tile);
let surface = self.get_mut(id);
tile_surface.canvas().clear(color);
surface.draw(
@ -280,148 +275,52 @@ impl Surfaces {
}
pub fn remove_cached_tiles(&mut self) {
self.tiles.clear_grid();
}
}
pub struct SurfaceRef {
pub index: usize,
pub in_use: bool,
pub surface: skia::Surface,
}
impl Clone for SurfaceRef {
fn clone(&self) -> Self {
Self {
index: self.index,
in_use: self.in_use,
surface: self.surface.clone(),
}
}
}
pub struct SurfacePool {
pub surfaces: Vec<SurfaceRef>,
pub index: usize,
}
impl SurfacePool {
pub fn with_capacity(surface: &mut skia::Surface, dims: skia::ISize, capacity: usize) -> Self {
let mut surfaces = Vec::with_capacity(capacity);
for _ in 0..capacity {
surfaces.push(surface.new_surface_with_dimensions(dims).unwrap())
}
Self {
index: 0,
surfaces: surfaces
.into_iter()
.enumerate()
.map(|(index, surface)| SurfaceRef {
index,
in_use: false,
surface: surface,
})
.collect(),
}
}
pub fn clear(&mut self) {
for surface in self.surfaces.iter_mut() {
surface.in_use = false;
}
}
pub fn deallocate(&mut self, surface_ref_to_deallocate: &SurfaceRef) {
let surface_ref = self
.surfaces
.get_mut(surface_ref_to_deallocate.index)
.unwrap();
surface_ref.in_use = false;
}
pub fn allocate(&mut self) -> Option<SurfaceRef> {
let start = self.index;
let len = self.surfaces.len();
loop {
self.index = (self.index + 1) % len;
if self.index == start {
return None;
}
if let Some(surface_ref) = self.surfaces.get_mut(self.index) {
if !surface_ref.in_use {
surface_ref.in_use = true;
return Some(surface_ref.clone());
}
}
}
self.tiles.clear();
}
}
pub struct TileSurfaceCache {
pool: SurfacePool,
grid: HashMap<Tile, SurfaceRef>,
visited: HashMap<Tile, bool>,
surface: Surface,
dims: skia::ISize,
lru_cache: LruCache<Tile, Surface>,
}
impl TileSurfaceCache {
pub fn new(pool: SurfacePool) -> Self {
pub fn new(pool_capacity: usize, surface: Surface, dims: skia::ISize) -> Self {
let lru_cache = LruCache::new(pool_capacity);
Self {
pool,
grid: HashMap::new(),
visited: HashMap::new(),
surface,
dims,
lru_cache,
}
}
pub fn has(&mut self, tile: Tile) -> bool {
return self.grid.contains_key(&tile);
self.lru_cache.contains(&tile)
}
pub fn get_or_create(&mut self, tile: Tile) -> Result<skia::Surface, String> {
if let Some(surface_ref) = self.pool.allocate() {
self.grid.insert(tile, surface_ref.clone());
Ok(surface_ref.surface.clone())
} else {
// TODO: I don't know yet how to improve this but I don't like it. I think
// there should be a better solution.
for (tile, surface_ref) in self.grid.iter() {
if !self.visited.contains_key(tile) {
continue;
}
if !self.visited.get(tile).unwrap() {
self.pool.deallocate(surface_ref);
}
}
if let Some(surface_ref) = self.pool.allocate() {
self.grid.insert(tile, surface_ref.clone());
return Ok(surface_ref.surface.clone());
}
return Err("Not enough surfaces".into());
pub fn get_or_create(&mut self, tile: Tile) -> Surface {
if let Some(surface) = self.lru_cache.get(&tile) {
return surface.clone();
}
let new_surface = self.surface.new_surface_with_dimensions(self.dims).unwrap();
self.lru_cache.put(tile, new_surface.clone());
new_surface
}
pub fn get(&mut self, tile: Tile) -> Result<&mut skia::Surface, String> {
Ok(&mut self.grid.get_mut(&tile).unwrap().surface)
pub fn get(&mut self, tile: Tile) -> Result<&mut Surface, String> {
self.lru_cache
.get_mut(&tile)
.ok_or_else(|| "Tile not found".to_string())
}
pub fn remove(&mut self, tile: Tile) -> bool {
if !self.grid.contains_key(&tile) {
return false;
}
self.grid.remove(&tile);
self.lru_cache.pop(&tile);
true
}
pub fn clear_grid(&mut self) {
self.grid.clear();
self.pool.clear();
}
pub fn clear_visited(&mut self) {
self.visited.clear();
}
pub fn visit(&mut self, tile: Tile) {
self.visited.insert(tile, true);
pub fn clear(&mut self) {
self.lru_cache.clear();
}
}