diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 2e22109c8..3ca24e625 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -18,7 +18,6 @@ [app.main.render :as render] [app.render-wasm.helpers :as h] [app.util.debug :as dbg] - [app.util.functions :as fns] [app.util.http :as http] [app.util.webapi :as wapi] [beicon.v2.core :as rx] @@ -522,13 +521,13 @@ (h/call internal-module "_add_shape_shadow" rgba blur spread x y (translate-shadow-style style) hidden) (recur (inc index))))))) -(def debounce-render (fns/debounce render 100)) - (defn set-view-box [zoom vbox] (h/call internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) - (h/call internal-module "_render_from_cache") - (debounce-render)) + (render nil)) + +(defn clear-cache [] + (h/call internal-module "_clear_cache")) (defn set-objects [objects] @@ -589,6 +588,7 @@ (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] (recur (inc index) (into pending pending')))) pending))] + (clear-cache) (request-render "set-objects") (when-let [pending (seq pending)] (->> (rx/from pending) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index e4117d366..f5debea17 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -142,6 +142,7 @@ ;; when something synced with wasm ;; is modified, we need to request ;; a new render. + (api/clear-cache) (api/request-render "set-wasm-attrs"))) (defn- impl-assoc diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 20e44f2f5..0e6b7efa3 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -47,6 +47,13 @@ pub extern "C" fn clean_up() { mem::free_bytes(); } +#[no_mangle] +pub extern "C" fn clear_cache() { + let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); + let render_state = state.render_state(); + render_state.clear_cache(); +} + #[no_mangle] pub extern "C" fn set_render_options(debug: u32, dpr: f32) { let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); @@ -98,7 +105,9 @@ pub extern "C" fn resize_viewbox(width: i32, height: i32) { #[no_mangle] pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) { let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); - state.render_state().viewbox.set_all(zoom, x, y); + let render_state = state.render_state(); + render_state.invalidate_cache_if_needed(); + render_state.viewbox.set_all(zoom, x, y); } #[no_mangle] diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 0ee39e67b..4ce8204f9 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1,3 +1,4 @@ +use skia::Contains; use skia_safe as skia; use std::collections::HashMap; use uuid::Uuid; @@ -71,6 +72,7 @@ pub(crate) struct RenderState { pub render_in_progress: bool, // Stack of nodes pending to be rendered. pub pending_nodes: Vec, + pub render_complete: bool, } impl RenderState { @@ -112,6 +114,7 @@ impl RenderState { render_request_id: None, render_in_progress: false, pending_nodes: vec![], + render_complete: true, } } @@ -236,6 +239,12 @@ impl RenderState { .clear(skia::Color::TRANSPARENT); } + pub fn invalidate_cache_if_needed(&mut self) { + if let Some(ref mut cached_surface_image) = self.cached_surface_image { + cached_surface_image.invalidate_if_dirty(&self.viewbox); + } + } + pub fn render_shape( &mut self, shape: &mut Shape, @@ -325,6 +334,7 @@ impl RenderState { }]; self.render_in_progress = true; self.process_animation_frame(tree, modifiers, timestamp)?; + self.render_complete = true; Ok(()) } @@ -359,22 +369,41 @@ impl RenderState { } // self.render_in_progress can have changed - if !self.render_in_progress { + if self.render_in_progress { + if self.cached_surface_image.is_some() { + self.render_from_cache()?; + } + return Ok(()); + } + + // Chech if cached_surface_image is not set or is invalid + if self + .cached_surface_image + .as_ref() + .map_or(true, |img| img.invalid) + { self.cached_surface_image = Some(CachedSurfaceImage { image: self.render_surface.image_snapshot(), viewbox: self.viewbox, + invalid: false, + has_all_shapes: self.render_complete, }); - if self.options.is_debug_visible() { - self.render_debug(); - } - - debug::render_wasm_label(self); - self.apply_render_to_final_canvas(); - self.flush(); } + + if self.options.is_debug_visible() { + self.render_debug(); + } + + debug::render_wasm_label(self); + self.apply_render_to_final_canvas(); + self.flush(); Ok(()) } + pub fn clear_cache(&mut self) { + self.cached_surface_image = None; + } + pub fn render_from_cache(&mut self) -> Result<(), String> { let cached = self .cached_surface_image @@ -397,6 +426,7 @@ impl RenderState { navigate_x * self.options.dpr(), navigate_y * self.options.dpr(), )); + self.final_surface.canvas().clear(self.background_color); self.final_surface .canvas() .draw_image(image, (0, 0), Some(&paint)); @@ -437,6 +467,7 @@ impl RenderState { .to_string(), )?; + let render_complete = self.viewbox.area.contains(element.bounds()); if visited_children { if !visited_mask { match element.kind { @@ -488,6 +519,7 @@ impl RenderState { if !node_render_state.id.is_nil() { if !element.bounds().intersects(self.viewbox.area) || element.hidden() { debug::render_debug_shape(self, element, false); + self.render_complete = render_complete; continue; } else { debug::render_debug_shape(self, element, true); diff --git a/render-wasm/src/render/cache.rs b/render-wasm/src/render/cache.rs index 3b5d3f245..04c0ec643 100644 --- a/render-wasm/src/render/cache.rs +++ b/render-wasm/src/render/cache.rs @@ -1,6 +1,18 @@ use super::{Image, Viewbox}; +use skia::Contains; +use skia_safe as skia; pub(crate) struct CachedSurfaceImage { pub image: Image, pub viewbox: Viewbox, + pub invalid: bool, + pub has_all_shapes: bool, +} + +impl CachedSurfaceImage { + pub fn invalidate_if_dirty(&mut self, viewbox: &Viewbox) { + if !self.has_all_shapes && !self.viewbox.area.contains(viewbox.area) { + self.invalid = true; + } + } }