🎉 Scaffold drag-overlay fast path

This commit is contained in:
Alejandro Alonso 2026-04-22 07:58:04 +02:00
parent f331325941
commit 3296ca0303
3 changed files with 60 additions and 0 deletions

View File

@ -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");
});

View File

@ -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<drag_overlay::DragOverlay>,
}
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.

View File

@ -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