diff --git a/render-wasm/Cargo.lock b/render-wasm/Cargo.lock index 0416a66974..227f15437b 100644 --- a/render-wasm/Cargo.lock +++ b/render-wasm/Cargo.lock @@ -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", ] diff --git a/render-wasm/Cargo.toml b/render-wasm/Cargo.toml index 835423948e..16cfb0a954 100644 --- a/render-wasm/Cargo.toml +++ b/render-wasm/Cargo.toml @@ -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" diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index f1595aa1dc..bd0d074e41 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -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; diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 154f0f0e7b..2325fb6217 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -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, - 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 { - 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, - visited: HashMap, + surface: Surface, + dims: skia::ISize, + lru_cache: LruCache, } 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 { - 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(); } }