mirror of
https://github.com/penpot/penpot.git
synced 2025-05-31 08:06:11 +02:00
🎉 Improve tile caching
This commit is contained in:
parent
f3d13005b2
commit
5c7a1fb407
5 changed files with 95 additions and 225 deletions
|
@ -146,7 +146,7 @@ impl RenderState {
|
|||
}
|
||||
|
||||
pub fn add_image(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> {
|
||||
self.images.add(id, image_data)
|
||||
self.images.add(id, image_data, &mut self.gpu_state.context)
|
||||
}
|
||||
|
||||
pub fn has_image(&mut self, id: &Uuid) -> bool {
|
||||
|
@ -193,9 +193,7 @@ impl RenderState {
|
|||
let x = self.current_tile.unwrap().0;
|
||||
let y = self.current_tile.unwrap().1;
|
||||
|
||||
// This caches the current surface into the corresponding tile.
|
||||
self.surfaces
|
||||
.cache_tile_surface((x, y), SurfaceId::Current, self.background_color);
|
||||
self.surfaces.cache_current_tile_texture((x, y));
|
||||
|
||||
self.surfaces
|
||||
.draw_cached_tile_surface(self.current_tile.unwrap(), rect);
|
||||
|
|
|
@ -171,15 +171,6 @@ pub fn render(render_state: &mut RenderState) {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(dead_code)]
|
||||
pub fn console_debug_tile_surface(render_state: &mut RenderState, tile: tiles::Tile) {
|
||||
let base64_image = render_state.surfaces.base64_snapshot_tile(tile);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(dead_code)]
|
||||
pub fn console_debug_surface(render_state: &mut RenderState, id: SurfaceId) {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::math::Rect as MathRect;
|
||||
use crate::uuid::Uuid;
|
||||
|
||||
use skia_safe as skia;
|
||||
use skia_safe::gpu::{surfaces, Budgeted, DirectContext};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type Image = skia::Image;
|
||||
|
@ -15,11 +18,41 @@ impl ImageStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> {
|
||||
let image_data = skia::Data::new_copy(image_data);
|
||||
pub fn add(
|
||||
&mut self,
|
||||
id: Uuid,
|
||||
image_data: &[u8],
|
||||
context: &mut DirectContext,
|
||||
) -> Result<(), String> {
|
||||
let image_data = unsafe { skia::Data::new_bytes(image_data) };
|
||||
let image = Image::from_encoded(image_data).ok_or("Error decoding image data")?;
|
||||
|
||||
self.images.insert(id, image);
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
|
||||
let image_info = skia::ImageInfo::new_n32_premul((width, height), None);
|
||||
let mut surface = surfaces::render_target(
|
||||
context,
|
||||
Budgeted::Yes,
|
||||
&image_info,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.ok_or("Can't create GPU surface")?;
|
||||
|
||||
let dest_rect = MathRect::from_xywh(0.0, 0.0, width as f32, height as f32);
|
||||
|
||||
surface
|
||||
.canvas()
|
||||
.draw_image_rect(&image, None, dest_rect, &skia::Paint::default());
|
||||
|
||||
let gpu_image = surface.image_snapshot();
|
||||
|
||||
// This way we store the image as a texture
|
||||
self.images.insert(id, gpu_image);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use crate::shapes::Shape;
|
||||
use crate::view::Viewbox;
|
||||
use skia_safe::{self as skia, Paint, RRect};
|
||||
use skia_safe::{self as skia, IRect, Paint, RRect};
|
||||
|
||||
use super::{gpu_state::GpuState, tiles::Tile};
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const POOL_CAPACITY_MINIMUM: i32 = 32;
|
||||
const POOL_CAPACITY_THRESHOLD: i32 = 4;
|
||||
const TEXTURES_CACHE_CAPACITY: usize = 512;
|
||||
const TEXTURES_BATCH_DELETE: usize = 32;
|
||||
// This is the amount of extra space we're going to give to all the surfaces to render shapes.
|
||||
// If it's too big it could affect performance.
|
||||
const TILE_SIZE_MULTIPLIER: i32 = 2;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum SurfaceId {
|
||||
|
@ -37,7 +40,7 @@ pub struct Surfaces {
|
|||
// for drawing debug info.
|
||||
debug: skia::Surface,
|
||||
// for drawing tiles.
|
||||
tiles: TileSurfaceCache,
|
||||
tiles: TileTextureCache,
|
||||
sampling_options: skia::SamplingOptions,
|
||||
margins: skia::ISize,
|
||||
}
|
||||
|
@ -50,13 +53,9 @@ impl Surfaces {
|
|||
sampling_options: skia::SamplingOptions,
|
||||
tile_dims: skia::ISize,
|
||||
) -> Self {
|
||||
// This is the amount of extra space we're going
|
||||
// to give to all the surfaces to render shapes.
|
||||
// If it's too big it could affect performance.
|
||||
let extra_tile_size = 2;
|
||||
let extra_tile_dims = skia::ISize::new(
|
||||
tile_dims.width * extra_tile_size,
|
||||
tile_dims.height * extra_tile_size,
|
||||
tile_dims.width * TILE_SIZE_MULTIPLIER,
|
||||
tile_dims.height * TILE_SIZE_MULTIPLIER,
|
||||
);
|
||||
let margins = skia::ISize::new(extra_tile_dims.width / 4, extra_tile_dims.height / 4);
|
||||
|
||||
|
@ -68,12 +67,7 @@ impl Surfaces {
|
|||
let shape_strokes = target.new_surface_with_dimensions(extra_tile_dims).unwrap();
|
||||
let debug = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
|
||||
let pool_capacity =
|
||||
((width / tile_dims.width) * (height / tile_dims.height) * POOL_CAPACITY_THRESHOLD)
|
||||
.max(POOL_CAPACITY_MINIMUM);
|
||||
|
||||
let pool = SurfacePool::with_capacity(&mut target, tile_dims, pool_capacity as usize);
|
||||
let tiles = TileSurfaceCache::new(pool);
|
||||
let tiles = TileTextureCache::new();
|
||||
Surfaces {
|
||||
target,
|
||||
current,
|
||||
|
@ -82,8 +76,8 @@ impl Surfaces {
|
|||
shape_fills,
|
||||
shape_strokes,
|
||||
debug,
|
||||
sampling_options,
|
||||
tiles,
|
||||
sampling_options,
|
||||
margins,
|
||||
}
|
||||
}
|
||||
|
@ -92,16 +86,6 @@ impl Surfaces {
|
|||
self.reset_from_target(gpu_state.create_target_surface(new_width, new_height));
|
||||
}
|
||||
|
||||
pub fn base64_snapshot_tile(&mut self, tile: Tile) -> String {
|
||||
let surface = self.tiles.get(tile).unwrap();
|
||||
let image = surface.image_snapshot();
|
||||
let mut context = surface.direct_context();
|
||||
let encoded_image = image
|
||||
.encode(context.as_mut(), skia::EncodedImageFormat::PNG, None)
|
||||
.unwrap();
|
||||
general_purpose::STANDARD.encode(&encoded_image.as_bytes())
|
||||
}
|
||||
|
||||
pub fn base64_snapshot(&mut self, id: SurfaceId) -> String {
|
||||
let surface = self.get_mut(id);
|
||||
let image = surface.image_snapshot();
|
||||
|
@ -238,26 +222,19 @@ impl Surfaces {
|
|||
self.tiles.visit(tile);
|
||||
}
|
||||
|
||||
pub fn cache_visited_amount(&self) -> usize {
|
||||
self.tiles.visited_amount()
|
||||
}
|
||||
|
||||
pub fn cache_visited_capacity(&self) -> usize {
|
||||
self.tiles.visited_capacity()
|
||||
}
|
||||
|
||||
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 surface = self.get_mut(id);
|
||||
tile_surface.canvas().clear(color);
|
||||
surface.draw(
|
||||
tile_surface.canvas(),
|
||||
(-margins.width, -margins.height),
|
||||
sampling_options,
|
||||
Some(&skia::Paint::default()),
|
||||
pub fn cache_current_tile_texture(&mut self, tile: Tile) {
|
||||
let snapshot = self.current.image_snapshot();
|
||||
let rect = IRect::from_xywh(
|
||||
self.margins.width,
|
||||
self.margins.height,
|
||||
snapshot.width() - TILE_SIZE_MULTIPLIER * self.margins.width,
|
||||
snapshot.height() - TILE_SIZE_MULTIPLIER * self.margins.height,
|
||||
);
|
||||
|
||||
let mut context = self.current.direct_context();
|
||||
if let Some(snapshot) = snapshot.make_subset(&mut context, &rect) {
|
||||
self.tiles.add(tile, snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_cached_tile_surface(&mut self, tile: Tile) -> bool {
|
||||
|
@ -269,127 +246,25 @@ impl Surfaces {
|
|||
}
|
||||
|
||||
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect) {
|
||||
let sampling_options = self.sampling_options;
|
||||
let tile_surface = self.tiles.get(tile).unwrap();
|
||||
tile_surface.draw(
|
||||
self.target.canvas(),
|
||||
(rect.x(), rect.y()),
|
||||
sampling_options,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
let image = self.tiles.get(tile).unwrap();
|
||||
self.target
|
||||
.canvas()
|
||||
.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
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 capacity(&self) -> usize {
|
||||
self.surfaces.len()
|
||||
}
|
||||
|
||||
pub fn available(&self) -> usize {
|
||||
let mut available: usize = 0;
|
||||
for surface_ref in self.surfaces.iter() {
|
||||
if surface_ref.in_use == false {
|
||||
available += 1;
|
||||
}
|
||||
}
|
||||
available
|
||||
}
|
||||
|
||||
pub fn deallocate(&mut self, surface_ref_to_deallocate: &SurfaceRef) {
|
||||
let surface_ref = self
|
||||
.surfaces
|
||||
.get_mut(surface_ref_to_deallocate.index)
|
||||
.unwrap();
|
||||
|
||||
// This could happen when the "clear" method of the pool is called.
|
||||
if surface_ref.in_use == false {
|
||||
return;
|
||||
}
|
||||
surface_ref.in_use = false;
|
||||
self.index = surface_ref_to_deallocate.index;
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self) -> Option<SurfaceRef> {
|
||||
let start = self.index;
|
||||
let len = self.surfaces.len();
|
||||
loop {
|
||||
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.index = (self.index + 1) % len;
|
||||
if self.index == start {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileSurfaceCache {
|
||||
pool: SurfacePool,
|
||||
grid: HashMap<Tile, SurfaceRef>,
|
||||
pub struct TileTextureCache {
|
||||
grid: HashMap<Tile, skia::Image>,
|
||||
visited: HashMap<Tile, bool>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TileSurfaceCache {
|
||||
pub fn new(pool: SurfacePool) -> Self {
|
||||
impl TileTextureCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool,
|
||||
grid: HashMap::new(),
|
||||
visited: HashMap::new(),
|
||||
}
|
||||
|
@ -405,66 +280,40 @@ impl TileSurfaceCache {
|
|||
}
|
||||
}
|
||||
|
||||
fn try_get_or_create(&mut self, tile: Tile) -> Result<skia::Surface, String> {
|
||||
// TODO: I don't know yet how to improve this but I don't like it. I think
|
||||
// there should be a better solution.
|
||||
let mut marked = vec![];
|
||||
for (tile, surface_ref) in self.grid.iter_mut() {
|
||||
let exists_as_visited = self.visited.contains_key(tile);
|
||||
if !exists_as_visited {
|
||||
marked.push(tile.clone());
|
||||
self.pool.deallocate(surface_ref);
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_visited = self.visited.get(tile).unwrap();
|
||||
if !*is_visited {
|
||||
marked.push(tile.clone());
|
||||
self.pool.deallocate(surface_ref);
|
||||
}
|
||||
pub fn add(&mut self, tile: Tile, image: skia::Image) {
|
||||
if self.grid.len() > TEXTURES_CACHE_CAPACITY {
|
||||
let marked: Vec<_> = self
|
||||
.grid
|
||||
.iter_mut()
|
||||
.filter_map(|(tile, _)| {
|
||||
if !self.visited.contains_key(tile) {
|
||||
Some(tile.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(TEXTURES_BATCH_DELETE)
|
||||
.collect();
|
||||
self.remove_list(marked);
|
||||
}
|
||||
|
||||
self.remove_list(marked);
|
||||
|
||||
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());
|
||||
self.grid.insert(tile, image);
|
||||
}
|
||||
|
||||
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());
|
||||
return Ok(surface_ref.surface.clone());
|
||||
}
|
||||
self.try_get_or_create(tile)
|
||||
}
|
||||
|
||||
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 skia::Image, String> {
|
||||
let image = self.grid.get_mut(&tile).unwrap();
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, tile: Tile) -> bool {
|
||||
if !self.grid.contains_key(&tile) {
|
||||
return false;
|
||||
}
|
||||
let surface_ref_to_deallocate = self.grid.remove(&tile);
|
||||
self.pool.deallocate(&surface_ref_to_deallocate.unwrap());
|
||||
self.grid.remove(&tile);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn clear_grid(&mut self) {
|
||||
pub fn clear(&mut self) {
|
||||
self.grid.clear();
|
||||
self.pool.clear();
|
||||
}
|
||||
|
||||
pub fn visited_amount(&self) -> usize {
|
||||
self.visited.len()
|
||||
}
|
||||
|
||||
pub fn visited_capacity(&self) -> usize {
|
||||
self.visited.capacity()
|
||||
}
|
||||
|
||||
pub fn clear_visited(&mut self) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue