Merge pull request #6526 from penpot/superalex-improve-zoom-performance-and-behaviour

🐛 Fix zoom performance and behaviour
This commit is contained in:
Aitor Moreno 2025-05-22 12:15:38 +02:00 committed by GitHub
commit d54a7d0401
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 60 additions and 30 deletions

View file

@ -181,6 +181,18 @@ export function set_parent(id) {
Module._set_parent(...buffer); Module._set_parent(...buffer);
} }
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
const debouncedRender = debounce(() => {
Module._render(Date.now());
}, 100);
export function setupInteraction(canvas) { export function setupInteraction(canvas) {
canvas.addEventListener("wheel", (e) => { canvas.addEventListener("wheel", (e) => {
e.preventDefault(); e.preventDefault();
@ -191,7 +203,8 @@ export function setupInteraction(canvas) {
offsetX -= (mouseX - offsetX) * (zoomFactor - 1); offsetX -= (mouseX - offsetX) * (zoomFactor - 1);
offsetY -= (mouseY - offsetY) * (zoomFactor - 1); offsetY -= (mouseY - offsetY) * (zoomFactor - 1);
Module._set_view(scale, offsetX, offsetY); Module._set_view(scale, offsetX, offsetY);
Module._render(Date.now()); Module._render_from_cache();
debouncedRender();
}); });
canvas.addEventListener("mousedown", (e) => { canvas.addEventListener("mousedown", (e) => {
@ -209,7 +222,8 @@ export function setupInteraction(canvas) {
lastX = e.offsetX; lastX = e.offsetX;
lastY = e.offsetY; lastY = e.offsetY;
Module._set_view(scale, offsetX, offsetY); Module._set_view(scale, offsetX, offsetY);
Module._render(Date.now()); Module._render_from_cache();
debouncedRender();
} }
}); });

View file

@ -30,8 +30,8 @@
[app.render-wasm.serializers.fills :as sr-fills] [app.render-wasm.serializers.fills :as sr-fills]
[app.render-wasm.wasm :as wasm] [app.render-wasm.wasm :as wasm]
[app.util.debug :as dbg] [app.util.debug :as dbg]
[app.util.functions :as fns]
[app.util.http :as http] [app.util.http :as http]
[app.util.perf :as uperf]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[promesa.core :as p] [promesa.core :as p]
@ -101,6 +101,8 @@
(h/call wasm/internal-module "_render" timestamp) (h/call wasm/internal-module "_render" timestamp)
(set! wasm/internal-frame-id nil)) (set! wasm/internal-frame-id nil))
(def debounce-render (fns/debounce render 100))
(defn cancel-render (defn cancel-render
[_] [_]
(when wasm/internal-frame-id (when wasm/internal-frame-id
@ -662,7 +664,8 @@
(defn set-view-box (defn set-view-box
[zoom vbox] [zoom vbox]
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox)))
(render (uperf/now))) (h/call wasm/internal-module "_render_from_cache")
(debounce-render))
(defn clear-drawing-cache [] (defn clear-drawing-cache []
(h/call wasm/internal-module "_clear_drawing_cache")) (h/call wasm/internal-module "_clear_drawing_cache"))

View file

@ -91,9 +91,19 @@ pub extern "C" fn set_canvas_background(raw_color: u32) {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render(timestamp: i32) { pub extern "C" fn render(_: i32) {
with_state!(state, { with_state!(state, {
state.start_render_loop(timestamp).expect("Error rendering"); state
.start_render_loop(performance::get_time())
.expect("Error rendering");
});
}
#[no_mangle]
pub extern "C" fn render_from_cache(_: i32) {
with_state!(state, {
let render_state = state.render_state();
render_state.render_from_cache();
}); });
} }
@ -137,17 +147,16 @@ pub extern "C" fn resize_viewbox(width: i32, height: i32) {
pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) { pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) {
with_state!(state, { with_state!(state, {
let render_state = state.render_state(); let render_state = state.render_state();
let zoom_changed = zoom != render_state.viewbox.zoom;
render_state.viewbox.set_all(zoom, x, y); render_state.viewbox.set_all(zoom, x, y);
if zoom_changed { with_state!(state, {
with_state!(state, { // We can have renders in progress
if state.render_state.options.is_profile_rebuild_tiles() { state.render_state.cancel_animation_frame();
state.rebuild_tiles(); if state.render_state.options.is_profile_rebuild_tiles() {
} else { state.rebuild_tiles();
state.rebuild_tiles_shallow(); } else {
} state.rebuild_tiles_shallow();
}); }
} });
}); });
} }

View file

@ -494,8 +494,16 @@ impl RenderState {
.update_render_context(self.render_area, self.get_scale()); .update_render_context(self.render_area, self.get_scale());
} }
fn render_from_cache(&mut self) { pub fn cancel_animation_frame(&mut self) {
let scale = self.get_scale(); if self.render_in_progress {
if let Some(frame_id) = self.render_request_id {
wapi::cancel_animation_frame!(frame_id);
}
}
}
pub fn render_from_cache(&mut self) {
let scale = self.get_cached_scale();
if let Some(snapshot) = &self.cached_target_snapshot { if let Some(snapshot) = &self.cached_target_snapshot {
let canvas = self.surfaces.canvas(SurfaceId::Target); let canvas = self.surfaces.canvas(SurfaceId::Target);
canvas.save(); canvas.save();
@ -525,6 +533,7 @@ impl RenderState {
canvas.clear(self.background_color); canvas.clear(self.background_color);
canvas.draw_image(snapshot, (0, 0), Some(&skia::Paint::default())); canvas.draw_image(snapshot, (0, 0), Some(&skia::Paint::default()));
canvas.restore(); canvas.restore();
self.flush_and_submit();
} }
} }
@ -535,19 +544,12 @@ impl RenderState {
structure: &HashMap<Uuid, Vec<StructureEntry>>, structure: &HashMap<Uuid, Vec<StructureEntry>>,
timestamp: i32, timestamp: i32,
) -> Result<(), String> { ) -> Result<(), String> {
if self.render_in_progress { let scale = self.get_scale();
if let Some(frame_id) = self.render_request_id { self.tile_viewbox.update(self.viewbox, scale);
wapi::cancel_animation_frame!(frame_id);
}
}
performance::begin_measure!("render"); performance::begin_measure!("render");
performance::begin_measure!("start_render_loop"); performance::begin_measure!("start_render_loop");
// If we have cached data let's do a fast render from it
self.render_from_cache();
let scale = self.get_scale();
self.reset_canvas(); self.reset_canvas();
self.surfaces.apply_mut( self.surfaces.apply_mut(
&[ &[
@ -601,9 +603,7 @@ impl RenderState {
self.flush_and_submit(); self.flush_and_submit();
if self.render_in_progress { if self.render_in_progress {
if let Some(frame_id) = self.render_request_id { self.cancel_animation_frame();
wapi::cancel_animation_frame!(frame_id);
}
self.render_request_id = Some(wapi::request_animation_frame!()); self.render_request_id = Some(wapi::request_animation_frame!());
} else { } else {
performance::end_measure!("render"); performance::end_measure!("render");
@ -1041,4 +1041,8 @@ impl RenderState {
pub fn get_scale(&self) -> f32 { pub fn get_scale(&self) -> f32 {
self.viewbox.zoom() * self.options.dpr() self.viewbox.zoom() * self.options.dpr()
} }
pub fn get_cached_scale(&self) -> f32 {
self.cached_viewbox.zoom() * self.options.dpr()
}
} }