diff --git a/frontend/src/app/main/data/workspace/viewport_wasm.cljs b/frontend/src/app/main/data/workspace/viewport_wasm.cljs index 4115589ab0..1c1caa801b 100644 --- a/frontend/src/app/main/data/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/data/workspace/viewport_wasm.cljs @@ -27,4 +27,4 @@ (defn maybe-view-interaction-end! [state] (when (and (features/active-feature? state "render-wasm/v1") (not (render-context-lost? state))) - (wasm.api/view-interaction-end!))) \ No newline at end of file + (wasm.api/finalize-view-interaction!))) \ No newline at end of file diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index cc0814a6b2..f2a344fc25 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -357,12 +357,15 @@ (def ^:const FRAME_TYPE_NONE 0) ;; This type should never "leak". (def ^:const FRAME_TYPE_PARTIAL 1) ;; A frame needs more render calls to end. (def ^:const FRAME_TYPE_FULL 2) ;; A frame was full. +(def ^:const RENDER-FLAG-SYNC-TILES 4) ;; Rebuild tile index without ending fast mode (pan/zoom pause). (defn- internal-render ([] (internal-render 0)) ([timestamp] - (set! wasm/internal-frame-type (h/call wasm/internal-module "_render" timestamp wasm/internal-frame-type)) + (internal-render timestamp wasm/internal-frame-type)) + ([timestamp flags] + (set! wasm/internal-frame-type (h/call wasm/internal-module "_render" timestamp flags)) (when (= wasm/internal-frame-type FRAME_TYPE_PARTIAL) (request-render "frame-type-partial")))) @@ -1311,20 +1314,27 @@ (perf/end-measure "render-finish") (reset! view-interaction-active? false))) +(defn- view-gesture-active? + "True while a pointer-driven pan or zoom gesture is in progress." + [] + (let [local (get @st/state :workspace-local)] + (or (:panning local) (:zooming local)))) + +(defn finalize-view-interaction! + "Ends the view interaction and triggers a full-quality render." + [] + (view-interaction-end!) + (internal-render 0 0)) + (def render-finish (letfn [(do-render [] ;; Check if context is still initialized before executing ;; to prevent errors when navigating quickly (when (initialized?) - (view-interaction-end!) - ;; Use async _render: visible tiles render synchronously - ;; (no yield), interest-area tiles render progressively - ;; via rAF. _set_view_end already rebuilt the tile - ;; index. For pan, most tiles are cached so the render - ;; completes in the first frame. For zoom, interest- - ;; area tiles (~3 tile margin) don't block the main - ;; thread. - (internal-render)))] + (if (view-gesture-active?) + ;; Pan/zoom pause: render without ending the interaction. + (internal-render 0 RENDER-FLAG-SYNC-TILES) + (finalize-view-interaction!))))] (fns/debounce do-render DEBOUNCE_DELAY_MS))) (defn set-view-box diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index bcdd9bf28a..0aa3fae930 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -123,6 +123,9 @@ pub extern "C" fn render(timestamp: i32, flags: u8) -> Result { } } let is_partial = flags & RenderFlag::Partial as u8 == RenderFlag::Partial as u8; + if flags & RenderFlag::SyncTiles as u8 != 0 { + render_state.preserve_target_during_render = true; + } let frame_type = if is_partial && !render_state.preserve_target_during_render { state .continue_render_loop(timestamp) @@ -355,6 +358,10 @@ pub extern "C" fn set_view_end() -> Result<()> { // instead of re-drawing every visible tile from scratch. render_state.rebuild_tile_index(&state.shapes); } + // Avoid `reset_canvas` on the post-gesture render (pan at stable zoom). + if !render_state.options.is_profile_rebuild_tiles() { + render_state.preserve_target_during_render = true; + } performance::end_measure!("set_view_end"); }); Ok(()) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index cc26d6de45..ee4ebe3bd0 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -54,7 +54,8 @@ pub enum FrameType { pub enum RenderFlag { None = 0, Partial = 1, - Full = 2, + /// Rebuilds the tile index without leaving fast mode. + SyncTiles = 4, } #[derive(Debug)] @@ -2092,6 +2093,10 @@ impl RenderState { let preserve_target = self.preserve_target_during_render; self.preserve_target_during_render = false; + if preserve_target && self.options.is_fast_mode() { + self.rebuild_tile_index(tree); + } + if self.options.is_interactive_transform() { // Keep `Target` as the previous frame and overwrite only the tiles // that changed. This avoids clearing + redrawing an atlas backdrop @@ -3899,7 +3904,11 @@ impl RenderState { let mut all_tiles = HashSet::::new(); let ids = std::mem::take(&mut self.touched_ids); - self.preserve_target_during_render = !ids.is_empty(); + // Pan release sets `preserve_target` in `set_view_end`; don't reset it + // here when no shapes changed, or the next render clears the canvas. + if !ids.is_empty() { + self.preserve_target_during_render = true; + } for shape_id in ids.iter() { if let Some(shape) = tree.get(shape_id) {