diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index e9b07b77b6..f0711768b2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -369,7 +369,8 @@ ;; in the future (when we handle the UI in the render) should be better to ;; have a "wasm.api/pointer-move" function that works as an entry point for ;; all the pointer-move events. - (wasm.api/text-editor-pointer-move (.-x off-pt) (.-y off-pt)) + (when (wasm.api/text-editor-has-focus?) + (wasm.api/text-editor-pointer-move (.-x off-pt) (.-y off-pt))) (rx/push! move-stream pt) (reset! last-position raw-pt) 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/frontend/src/debug.cljs b/frontend/src/debug.cljs index e586dc3d1a..ee3f58b74c 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -31,6 +31,9 @@ [app.main.errors :as errors] [app.main.repo :as rp] [app.main.store :as st] + [app.render-wasm.helpers :as wasm.h] + [app.render-wasm.mem :as wasm.mem] + [app.render-wasm.wasm :as wasm] [app.util.debug :as dbg] [app.util.dom :as dom] [app.util.http :as http] @@ -117,6 +120,43 @@ (js/console.log str (json/->js val)) val)) +(defn- wasm-read-len-prefixed-utf8 + "Reads a `[u32 byte_len][utf8 bytes...]` buffer returned by WASM and frees it. + Returns a JS string (possibly empty)." + [ptr] + (when (and ptr (not (zero? ptr))) + (let [heap-u8 (wasm.mem/get-heap-u8) + heap-u32 (wasm.mem/get-heap-u32) + len (aget heap-u32 (wasm.mem/->offset-32 ptr)) + start (+ ptr 4) + end (+ start len) + decoder (js/TextDecoder. "utf-8") + text (.decode decoder (.subarray heap-u8 start end))] + (wasm.mem/free) + text))) + +(defn ^:export wasmCacheConsole + "Logs the current render-wasm cache surface as an image in the JS console." + [] + (let [module wasm/internal-module + f (when module (unchecked-get module "_debug_cache_console"))] + (if (fn? f) + (wasm.h/call module "_debug_cache_console") + (js/console.warn "[debug] render-wasm module not ready or missing _debug_cache_console")))) + +(defn ^:export wasmCacheBase64 + "Returns the cache surface PNG base64 (empty string if missing/empty)." + [] + (let [module wasm/internal-module + f (when module (unchecked-get module "_debug_cache_base64"))] + (if (fn? f) + (let [ptr (wasm.h/call module "_debug_cache_base64") + s (or (wasm-read-len-prefixed-utf8 ptr) "")] + s) + (do + (js/console.warn "[debug] render-wasm module not ready or missing _debug_cache_base64") + "")))) + (when (exists? js/window) (set! (.-dbg ^js js/window) json/->js) (set! (.-pp ^js js/window) pprint)) 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/debug.rs b/render-wasm/src/render/debug.rs index 624cdaef78..47b739b484 100644 --- a/render-wasm/src/render/debug.rs +++ b/render-wasm/src/render/debug.rs @@ -1,4 +1,7 @@ use super::{tiles, RenderState, SurfaceId}; +use crate::with_state_mut; +use crate::STATE; +use macros::wasm_error; use skia_safe::{self as skia, Rect}; #[cfg(target_arch = "wasm32")] @@ -210,3 +213,13 @@ pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId, run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')")) } } + +#[no_mangle] +#[wasm_error] +#[cfg(target_arch = "wasm32")] +pub extern "C" fn debug_cache_console() -> Result<()> { + with_state_mut!(state, { + console_debug_surface(state.render_state_mut(), SurfaceId::Cache); + }); + Ok(()) +} 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,