From 434e27bbe830a0e84f7f536f3dd073d5c3b0c83a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 9 Apr 2026 16:18:04 +0200 Subject: [PATCH] :tada: Improve panning performance --- .../app/main/ui/workspace/viewport_wasm.cljs | 4 +-- frontend/src/app/render_wasm/api.cljs | 35 +++++-------------- render-wasm/src/render.rs | 12 +++++++ render-wasm/src/render/surfaces.rs | 9 +++++ 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index ab06a89709..b45b7e492c 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -173,7 +173,6 @@ (get base-objects parent-id))))) zoom (d/check-num zoom 1) - prev-zoom (mf/use-ref zoom) drawing-tool (:tool drawing) drawing-obj (:object drawing) @@ -405,8 +404,7 @@ (mf/with-effect [vbox zoom] (when (and @canvas-init? initialized?) - (wasm.api/set-view-box (mf/ref-val prev-zoom) zoom vbox)) - (mf/set-ref-val! prev-zoom zoom)) + (wasm.api/set-view-box zoom vbox))) (mf/with-effect [background] (when (and @canvas-init? initialized?) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index a4993f3503..c3a783040b 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -989,34 +989,17 @@ (render (js/performance.now))))] (fns/debounce do-render DEBOUNCE_DELAY_MS))) -(def render-pan - (letfn [(do-render-pan [ts] - ;; Check if context is still initialized before executing - ;; to prevent errors when navigating quickly - (when wasm/context-initialized? - (perf/begin-measure "render-pan") - (render ts) - (perf/end-measure "render-pan")))] - (fns/throttle do-render-pan THROTTLE_DELAY_MS))) - (defn set-view-box - [prev-zoom zoom vbox] - (let [is-pan (mth/close? prev-zoom zoom)] - (perf/begin-measure "set-view-box") - (h/call wasm/internal-module "_set_view_start") - (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) + [zoom vbox] + (perf/begin-measure "set-view-box") + (h/call wasm/internal-module "_set_view_start") + (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) + (perf/end-measure "set-view-box") - (if is-pan - (do (perf/end-measure "set-view-box") - (perf/begin-measure "set-view-box::pan") - (render-pan) - (render-finish) - (perf/end-measure "set-view-box::pan")) - (do (perf/end-measure "set-view-box") - (perf/begin-measure "set-view-box::zoom") - (h/call wasm/internal-module "_render_from_cache" 0) - (render-finish) - (perf/end-measure "set-view-box::zoom"))))) + (perf/begin-measure "render-from-cache") + (h/call wasm/internal-module "_render_from_cache" 0) + (render-finish) + (perf/end-measure "render-from-cache")) (defn update-text-rect! [id] diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index c66bcdf2b3..acdd436e7c 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -337,6 +337,9 @@ pub(crate) struct RenderState { /// Preview render mode - when true, uses simplified rendering for progressive loading pub preview_mode: bool, pub export_context: Option<(Rect, f32)>, + /// Cleared at the beginning of a render pass; set to true after we clear Cache the first + /// time we are about to blit a tile into Cache for this pass. + pub cache_cleared_this_render: bool, } pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize { @@ -411,6 +414,7 @@ impl RenderState { ignore_nested_blurs: false, preview_mode: false, export_context: None, + cache_cleared_this_render: false, }) } @@ -665,6 +669,13 @@ impl RenderState { } pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect) -> Result<()> { + // Decide *now* (at the first real cache blit) whether we need to clear Cache. + // This avoids clearing Cache on renders that don't actually paint tiles (e.g. hover/UI), + // while still preventing stale pixels from surviving across full-quality renders. + if !self.options.is_fast_mode() && !self.cache_cleared_this_render { + self.surfaces.clear_cache(self.background_color); + self.cache_cleared_this_render = true; + } let tile_rect = self.get_current_aligned_tile_bounds()?; self.surfaces.cache_current_tile_texture( &self.tile_viewbox, @@ -1497,6 +1508,7 @@ impl RenderState { performance::begin_measure!("render"); performance::begin_measure!("start_render_loop"); + self.cache_cleared_this_render = false; self.reset_canvas(); let surface_ids = SurfaceId::Strokes as u32 | SurfaceId::Fills as u32 diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 2ab32d1a61..3b7d4bcbbc 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -533,6 +533,15 @@ impl Surfaces { self.clear_all_dirty(); } + /// Clears the whole cache surface without disturbing its configured transform. + pub fn clear_cache(&mut self, color: skia::Color) { + let canvas = self.cache.canvas(); + canvas.save(); + canvas.reset_matrix(); + canvas.clear(color); + canvas.restore(); + } + pub fn cache_current_tile_texture( &mut self, tile_viewbox: &TileViewbox,