mirror of https://github.com/penpot/penpot.git
🔧 Use LRU cache for tile surfaces
This commit is contained in:
parent
60bc88a075
commit
7aeefb5b2e
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue