diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 9f298c3900..9bcf0529ed 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -461,6 +461,23 @@ pub extern "C" fn set_modifiers_start() -> Result<()> { Ok(()) } +/// Toggle the drag-overlay fast path. When enabled (default), the +/// renderer bypasses the tile walker during interactive transforms by +/// compositing cached snapshots on top of a hole-punched atlas. Turn +/// OFF to fall back to the legacy per-tile walk (useful to isolate +/// regressions or for screenshots). +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_drag_overlay_enabled(enabled: bool) -> Result<()> { + with_state_mut!(state, { + state.render_state.options.set_drag_overlay(enabled); + if !enabled { + state.render_state.drag_overlay = None; + } + }); + Ok(()) +} + /// Leave interactive transform mode and cancel any pending async /// render scheduled under it. The caller is responsible for triggering /// a final full-quality render (typically via `_render`) once the @@ -473,6 +490,7 @@ pub extern "C" fn set_modifiers_end() -> Result<()> { let opts = &mut state.render_state.options; opts.set_fast_mode(false); opts.set_interactive_transform(false); + state.render_state.drag_overlay = None; state.render_state.cancel_animation_frame(); performance::end_measure!("set_modifiers_end"); }); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 6272d8d9a3..240bc16a0a 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1,4 +1,5 @@ mod debug; +pub mod drag_overlay; mod fills; pub mod filters; mod fonts; @@ -334,6 +335,14 @@ pub(crate) struct RenderState { /// 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, + /// Fast-path state for interactive drag / resize / rotate gestures. + /// `None` outside gestures. When populated, the walker excludes the + /// shapes in `shape_ids` while rebuilding the atlas backdrop, and the + /// hot render path blits the cached snapshots on top of the + /// hole-punched atlas in a single pass. See `drag_overlay` module + /// docs for the full flow. Disabled entirely when + /// `options.is_drag_overlay()` is `false`. + pub drag_overlay: Option, } pub fn get_cache_size(viewbox: Viewbox, scale: f32, interest: i32) -> skia::ISize { @@ -407,6 +416,7 @@ impl RenderState { preview_mode: false, export_context: None, cache_cleared_this_render: false, + drag_overlay: None, }) } @@ -2604,6 +2614,19 @@ impl RenderState { is_empty = false; + // Drag-overlay hole-punch: while the overlay is being built (snapshots + // captured but the backdrop atlas not yet refreshed), skip every node + // that is part of the selection so the atlas stores a clean backdrop + // without the dragged shapes. Exports and already-ready overlays must + // render every node normally. + if !export { + if let Some(overlay) = self.drag_overlay.as_ref() { + if !overlay.backdrop_ready && overlay.shape_ids.contains(&node_id) { + continue; + } + } + } + let Some(element) = tree.get(&node_id) else { // The shape isn't available yet (likely still streaming in from WASM). // Skip it for this pass; a subsequent render will pick it up once present. diff --git a/render-wasm/src/render/options.rs b/render-wasm/src/render/options.rs index 40a3125ccd..4128a03fb4 100644 --- a/render-wasm/src/render/options.rs +++ b/render-wasm/src/render/options.rs @@ -22,6 +22,13 @@ pub struct RenderOptions { /// keeps per-frame flushing enabled (unlike pan/zoom, where /// `render_from_cache` drives target presentation). interactive_transform: bool, + /// When ON, interactive transforms take a fast overlay path: the + /// selected shapes are snapshotted once at gesture start, the atlas + /// is hole-punched below them, and per-frame we blit the cached + /// backdrop + snapshots with the current modifier matrix instead of + /// walking the tile tree. Safe to toggle OFF to fall back to the + /// tile-walker path. + drag_overlay: bool, /// Minimum on-screen size (CSS px at 1:1 zoom) above which vector antialiasing is enabled. pub antialias_threshold: f32, pub viewport_interest_area_threshold: i32, @@ -37,6 +44,7 @@ impl Default for RenderOptions { dpr: None, fast_mode: false, interactive_transform: false, + drag_overlay: true, antialias_threshold: ANTIALIAS_THRESHOLD, viewport_interest_area_threshold: VIEWPORT_INTEREST_AREA_THRESHOLD, max_blocking_time_ms: MAX_BLOCKING_TIME_MS, @@ -76,6 +84,17 @@ impl RenderOptions { self.interactive_transform = enabled; } + /// Drag overlay is a fast path taken while `interactive_transform` + /// is active: selected shapes are cached once and composited over a + /// hole-punched atlas backdrop, bypassing the tile walker entirely. + pub fn is_drag_overlay(&self) -> bool { + self.drag_overlay + } + + pub fn set_drag_overlay(&mut self, enabled: bool) { + self.drag_overlay = enabled; + } + /// True only when the viewport is the one being moved (pan/zoom) /// and the dedicated `render_from_cache` path owns Target /// presentation. In this mode `process_animation_frame` must not