mirror of
https://github.com/penpot/penpot.git
synced 2026-08-05 12:29:00 +00:00
🔧 Use LRU cache for tile surfaces
This commit is contained in:
parent
60bc88a075
commit
7aeefb5b2e
34
render-wasm/Cargo.lock
generated
34
render-wasm/Cargo.lock
generated
@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
@ -8,6 +8,17 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.20"
|
version = "0.7.20"
|
||||||
@ -823,6 +834,15 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@ -901,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.15.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1036,6 +1056,15 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
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]]
|
[[package]]
|
||||||
name = "mac-notification-sys"
|
name = "mac-notification-sys"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@ -1511,6 +1540,7 @@ dependencies = [
|
|||||||
"cargo-watch",
|
"cargo-watch",
|
||||||
"gl",
|
"gl",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"lru",
|
||||||
"skia-safe",
|
"skia-safe",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -11,6 +11,7 @@ name = "render_wasm"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
lru = "0.6"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
gl = "0.14.0"
|
gl = "0.14.0"
|
||||||
indexmap = "2.7.1"
|
indexmap = "2.7.1"
|
||||||
|
|||||||
@ -462,12 +462,10 @@ impl RenderState {
|
|||||||
ey + interest_delta
|
ey + interest_delta
|
||||||
*/
|
*/
|
||||||
self.pending_tiles = vec![];
|
self.pending_tiles = vec![];
|
||||||
self.surfaces.cache_clear_visited();
|
|
||||||
for y in sy..=ey {
|
for y in sy..=ey {
|
||||||
for x in sx..=ex {
|
for x in sx..=ex {
|
||||||
let tile = (x, y);
|
let tile = (x, y);
|
||||||
self.pending_tiles.push(tile);
|
self.pending_tiles.push(tile);
|
||||||
self.surfaces.cache_visit(tile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.current_tile = None;
|
self.current_tile = None;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::view::Viewbox;
|
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 super::{gpu_state::GpuState, tiles::Tile};
|
||||||
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use std::collections::HashMap;
|
use lru::LruCache;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum SurfaceId {
|
pub enum SurfaceId {
|
||||||
@ -22,22 +22,22 @@ pub enum SurfaceId {
|
|||||||
|
|
||||||
pub struct Surfaces {
|
pub struct Surfaces {
|
||||||
// is the final destination surface, the one that it is represented in the canvas element.
|
// is the final destination surface, the one that it is represented in the canvas element.
|
||||||
target: skia::Surface,
|
target: Surface,
|
||||||
// keeps the current render
|
// keeps the current render
|
||||||
current: skia::Surface,
|
current: Surface,
|
||||||
// keeps the current shape's fills
|
// keeps the current shape's fills
|
||||||
shape_fills: skia::Surface,
|
shape_fills: Surface,
|
||||||
// keeps the current shape's strokes
|
// keeps the current shape's strokes
|
||||||
shape_strokes: skia::Surface,
|
shape_strokes: Surface,
|
||||||
// used for rendering shadows
|
// used for rendering shadows
|
||||||
shadow: skia::Surface,
|
shadow: Surface,
|
||||||
// used for new shadow rendering
|
// used for new shadow rendering
|
||||||
drop_shadows: skia::Surface,
|
drop_shadows: Surface,
|
||||||
inner_shadows: skia::Surface,
|
inner_shadows: Surface,
|
||||||
// for drawing the things that are over shadows.
|
// for drawing the things that are over shadows.
|
||||||
overlay: skia::Surface,
|
overlay: Surface,
|
||||||
// for drawing debug info.
|
// for drawing debug info.
|
||||||
debug: skia::Surface,
|
debug: Surface,
|
||||||
// for drawing tiles.
|
// for drawing tiles.
|
||||||
tiles: TileSurfaceCache,
|
tiles: TileSurfaceCache,
|
||||||
sampling_options: skia::SamplingOptions,
|
sampling_options: skia::SamplingOptions,
|
||||||
@ -74,8 +74,11 @@ impl Surfaces {
|
|||||||
const POOL_CAPACITY_THRESHOLD: i32 = 4;
|
const POOL_CAPACITY_THRESHOLD: i32 = 4;
|
||||||
let pool_capacity =
|
let pool_capacity =
|
||||||
(width / tile_dims.width) * (height / tile_dims.height) * POOL_CAPACITY_THRESHOLD;
|
(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(
|
||||||
let tiles = TileSurfaceCache::new(pool);
|
pool_capacity as usize,
|
||||||
|
target.new_surface_with_dimensions(tile_dims).unwrap(),
|
||||||
|
tile_dims,
|
||||||
|
);
|
||||||
Surfaces {
|
Surfaces {
|
||||||
target,
|
target,
|
||||||
current,
|
current,
|
||||||
@ -145,7 +148,7 @@ impl Surfaces {
|
|||||||
.draw(self.canvas(to), (0.0, 0.0), sampling_options, paint);
|
.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 {
|
for id in ids {
|
||||||
let surface = self.get_mut(*id);
|
let surface = self.get_mut(*id);
|
||||||
f(surface);
|
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 {
|
match id {
|
||||||
SurfaceId::Target => &mut self.target,
|
SurfaceId::Target => &mut self.target,
|
||||||
SurfaceId::Current => &mut self.current,
|
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());
|
let dim = (target.width(), target.height());
|
||||||
self.target = target;
|
self.target = target;
|
||||||
self.debug = self.target.new_surface_with_dimensions(dim).unwrap();
|
self.debug = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||||
@ -238,18 +241,10 @@ impl Surfaces {
|
|||||||
.reset_matrix();
|
.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) {
|
pub fn cache_tile_surface(&mut self, tile: Tile, id: SurfaceId, color: skia::Color) {
|
||||||
let sampling_options = self.sampling_options;
|
let sampling_options = self.sampling_options;
|
||||||
let mut tile_surface = self.tiles.get_or_create(tile).unwrap();
|
|
||||||
let margins = self.margins;
|
let margins = self.margins;
|
||||||
|
let mut tile_surface = self.tiles.get_or_create(tile);
|
||||||
let surface = self.get_mut(id);
|
let surface = self.get_mut(id);
|
||||||
tile_surface.canvas().clear(color);
|
tile_surface.canvas().clear(color);
|
||||||
surface.draw(
|
surface.draw(
|
||||||
@ -280,148 +275,52 @@ impl Surfaces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_cached_tiles(&mut self) {
|
pub fn remove_cached_tiles(&mut self) {
|
||||||
self.tiles.clear_grid();
|
self.tiles.clear();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TileSurfaceCache {
|
pub struct TileSurfaceCache {
|
||||||
pool: SurfacePool,
|
surface: Surface,
|
||||||
grid: HashMap<Tile, SurfaceRef>,
|
dims: skia::ISize,
|
||||||
visited: HashMap<Tile, bool>,
|
lru_cache: LruCache<Tile, Surface>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileSurfaceCache {
|
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 {
|
Self {
|
||||||
pool,
|
surface,
|
||||||
grid: HashMap::new(),
|
dims,
|
||||||
visited: HashMap::new(),
|
lru_cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has(&mut self, tile: Tile) -> bool {
|
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> {
|
pub fn get_or_create(&mut self, tile: Tile) -> Surface {
|
||||||
if let Some(surface_ref) = self.pool.allocate() {
|
if let Some(surface) = self.lru_cache.get(&tile) {
|
||||||
self.grid.insert(tile, surface_ref.clone());
|
return surface.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
pub fn get(&mut self, tile: Tile) -> Result<&mut Surface, String> {
|
||||||
Ok(&mut self.grid.get_mut(&tile).unwrap().surface)
|
self.lru_cache
|
||||||
|
.get_mut(&tile)
|
||||||
|
.ok_or_else(|| "Tile not found".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, tile: Tile) -> bool {
|
pub fn remove(&mut self, tile: Tile) -> bool {
|
||||||
if !self.grid.contains_key(&tile) {
|
self.lru_cache.pop(&tile);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.grid.remove(&tile);
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_grid(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.grid.clear();
|
self.lru_cache.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user