Merge pull request #7922 from penpot/elenatorro-12855-improve-pan-rendering

🔧 Improve pan rendering
This commit is contained in:
Alejandro Alonso
2025-12-10 15:58:59 +01:00
committed by GitHub
6 changed files with 162 additions and 18 deletions

View File

@@ -874,22 +874,37 @@
(def render-finish
(letfn [(do-render [ts]
(perf/begin-measure "render-finish")
(h/call wasm/internal-module "_set_view_end")
(render ts))]
(render ts)
(perf/end-measure "render-finish"))]
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
(def render-pan
(fns/throttle render THROTTLE_DELAY_MS))
(letfn [(do-render-pan [ts]
(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]
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y 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)))
(if (mth/close? prev-zoom zoom)
(do (render-pan)
(render-finish))
(do (h/call wasm/internal-module "_render_from_cache" 0)
(render-finish))))
(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")))))
(defn set-object
[objects shape]

View File

@@ -230,20 +230,62 @@ pub extern "C" fn resize_viewbox(width: i32, height: i32) {
#[no_mangle]
pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) {
with_state_mut!(state, {
performance::begin_measure!("set_view");
let render_state = state.render_state_mut();
render_state.set_view(zoom, x, y);
performance::end_measure!("set_view");
});
}
#[cfg(feature = "profile-macros")]
static mut VIEW_INTERACTION_START: i32 = 0;
#[no_mangle]
pub extern "C" fn set_view_start() {
with_state_mut!(state, {
#[cfg(feature = "profile-macros")]
unsafe {
VIEW_INTERACTION_START = performance::get_time();
}
performance::begin_measure!("set_view_start");
state.render_state.options.set_fast_mode(true);
performance::end_measure!("set_view_start");
});
}
#[no_mangle]
pub extern "C" fn set_view_end() {
with_state_mut!(state, {
// We can have renders in progress
let _end_start = performance::begin_timed_log!("set_view_end");
performance::begin_measure!("set_view_end");
state.render_state.options.set_fast_mode(false);
state.render_state.cancel_animation_frame();
if state.render_state.options.is_profile_rebuild_tiles() {
state.rebuild_tiles();
} else {
state.rebuild_tiles_shallow();
let zoom_changed = state.render_state.zoom_changed();
// Only rebuild tile indices when zoom has changed.
// During pan-only operations, shapes stay in the same tiles
// because tile_size = 1/scale * TILE_SIZE (depends only on zoom).
if zoom_changed {
let _rebuild_start = performance::begin_timed_log!("rebuild_tiles");
performance::begin_measure!("set_view_end::rebuild_tiles");
if state.render_state.options.is_profile_rebuild_tiles() {
state.rebuild_tiles();
} else {
state.rebuild_tiles_shallow();
}
performance::end_measure!("set_view_end::rebuild_tiles");
performance::end_timed_log!("rebuild_tiles", _rebuild_start);
}
performance::end_measure!("set_view_end");
performance::end_timed_log!("set_view_end", _end_start);
#[cfg(feature = "profile-macros")]
{
let total_time = performance::get_time() - unsafe { VIEW_INTERACTION_START };
performance::console_log!(
"[PERF] view_interaction (zoom_changed={}): {}ms",
zoom_changed,
total_time
);
}
});
}

View File

@@ -1,2 +1,3 @@
pub const DEBUG_VISIBLE: u32 = 0x01;
pub const PROFILE_REBUILD_TILES: u32 = 0x02;
pub const FAST_MODE: u32 = 0x04;

View File

@@ -1,7 +1,3 @@
#[allow(unused_imports)]
#[cfg(target_arch = "wasm32")]
use crate::get_now;
#[allow(dead_code)]
#[cfg(target_arch = "wasm32")]
pub fn get_time() -> i32 {
@@ -15,6 +11,68 @@ pub fn get_time() -> i32 {
now.elapsed().as_millis() as i32
}
/// Log a message to the browser console (only when profile-macros feature is enabled)
#[macro_export]
macro_rules! console_log {
($($arg:tt)*) => {
#[cfg(all(feature = "profile-macros", target_arch = "wasm32"))]
{
use $crate::run_script;
run_script!(format!("console.log('{}')", format!($($arg)*)));
}
#[cfg(all(feature = "profile-macros", not(target_arch = "wasm32")))]
{
println!($($arg)*);
}
};
}
/// Begin a timed section with logging (only when profile-macros feature is enabled)
/// Returns the start time - store it and pass to end_timed_log!
#[macro_export]
macro_rules! begin_timed_log {
($name:expr) => {{
#[cfg(feature = "profile-macros")]
{
$crate::performance::get_time()
}
#[cfg(not(feature = "profile-macros"))]
{
0.0
}
}};
}
/// End a timed section and log the duration (only when profile-macros feature is enabled)
#[macro_export]
macro_rules! end_timed_log {
($name:expr, $start:expr) => {{
#[cfg(all(feature = "profile-macros", target_arch = "wasm32"))]
{
let duration = $crate::performance::get_time() - $start;
use $crate::run_script;
run_script!(format!(
"console.log('[PERF] {}: {:.2}ms')",
$name, duration
));
}
#[cfg(all(feature = "profile-macros", not(target_arch = "wasm32")))]
{
let duration = $crate::performance::get_time() - $start;
println!("[PERF] {}: {:.2}ms", $name, duration);
}
}};
}
#[allow(unused_imports)]
pub use console_log;
#[allow(unused_imports)]
pub use begin_timed_log;
#[allow(unused_imports)]
pub use end_timed_log;
#[macro_export]
macro_rules! mark {
($name:expr) => {

View File

@@ -928,6 +928,8 @@ impl RenderState {
}
pub fn render_from_cache(&mut self, shapes: ShapesPoolRef) {
let _start = performance::begin_timed_log!("render_from_cache");
performance::begin_measure!("render_from_cache");
let scale = self.get_cached_scale();
if let Some(snapshot) = &self.cached_target_snapshot {
let canvas = self.surfaces.canvas(SurfaceId::Target);
@@ -965,6 +967,8 @@ impl RenderState {
self.flush_and_submit();
}
performance::end_measure!("render_from_cache");
performance::end_timed_log!("render_from_cache", _start);
}
pub fn start_render_loop(
@@ -974,6 +978,7 @@ impl RenderState {
timestamp: i32,
sync_render: bool,
) -> Result<(), String> {
let _start = performance::begin_timed_log!("start_render_loop");
let scale = self.get_scale();
self.tile_viewbox.update(self.viewbox, scale);
@@ -1004,10 +1009,12 @@ impl RenderState {
// FIXME - review debug
// debug::render_debug_tiles_for_viewbox(self);
let _tile_start = performance::begin_timed_log!("tile_cache_update");
performance::begin_measure!("tile_cache");
self.pending_tiles
.update(&self.tile_viewbox, &self.surfaces);
performance::end_measure!("tile_cache");
performance::end_timed_log!("tile_cache_update", _tile_start);
self.pending_nodes.clear();
if self.pending_nodes.capacity() < tree.len() {
@@ -1031,6 +1038,7 @@ impl RenderState {
}
performance::end_measure!("start_render_loop");
performance::end_timed_log!("start_render_loop", _start);
Ok(())
}
@@ -1479,8 +1487,11 @@ impl RenderState {
.surfaces
.get_render_context_translation(self.render_area, scale);
// Skip expensive drop shadow rendering in fast mode (during pan/zoom)
let skip_shadows = self.options.is_fast_mode();
// For text shapes, render drop shadow using text rendering logic
if !matches!(element.shape_type, Type::Text(_)) {
if !skip_shadows && !matches!(element.shape_type, Type::Text(_)) {
// Shadow rendering technique: Two-pass approach for proper opacity handling
//
// The shadow rendering uses a two-pass technique to ensure that overlapping
@@ -2054,6 +2065,10 @@ impl RenderState {
self.cached_viewbox.zoom() * self.options.dpr()
}
pub fn zoom_changed(&self) -> bool {
(self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON
}
pub fn mark_touched(&mut self, uuid: Uuid) {
self.touched_ids.insert(uuid);
}

View File

@@ -15,6 +15,19 @@ impl RenderOptions {
self.flags & options::PROFILE_REBUILD_TILES == options::PROFILE_REBUILD_TILES
}
/// Use fast mode to enable / disable expensive operations
pub fn is_fast_mode(&self) -> bool {
self.flags & options::FAST_MODE == options::FAST_MODE
}
pub fn set_fast_mode(&mut self, enabled: bool) {
if enabled {
self.flags |= options::FAST_MODE;
} else {
self.flags &= !options::FAST_MODE;
}
}
pub fn dpr(&self) -> f32 {
self.dpr.unwrap_or(1.0)
}