mirror of
https://github.com/penpot/penpot.git
synced 2026-01-20 12:20:16 -05:00
Compare commits
1 Commits
develop
...
elenatorro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4b19be10d |
@@ -276,13 +276,10 @@ pub extern "C" fn set_view_end() {
|
||||
performance::end_measure!("set_view_end::rebuild_tiles");
|
||||
performance::end_timed_log!("rebuild_tiles", _rebuild_start);
|
||||
} else {
|
||||
// During pan, we only clear the tile index without
|
||||
// invalidating cached textures, which is more efficient.
|
||||
let _clear_start = performance::begin_timed_log!("clear_tile_index");
|
||||
performance::begin_measure!("set_view_end::clear_tile_index");
|
||||
state.clear_tile_index();
|
||||
performance::end_measure!("set_view_end::clear_tile_index");
|
||||
performance::end_timed_log!("clear_tile_index", _clear_start);
|
||||
// During pan, don't clear anything - tiles remain valid since
|
||||
// tile coordinates are world-based and don't change with pan.
|
||||
// The renderer will use cached tiles where available and only
|
||||
// render new tiles that come into view.
|
||||
}
|
||||
performance::end_measure!("set_view_end");
|
||||
performance::end_timed_log!("set_view_end", _end_start);
|
||||
|
||||
@@ -514,7 +514,7 @@ impl RenderState {
|
||||
self.surfaces.canvas(surface_id).restore();
|
||||
}
|
||||
|
||||
pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect) {
|
||||
pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect, present_to_target: bool) {
|
||||
let tile_rect = self.get_current_aligned_tile_bounds();
|
||||
self.surfaces.cache_current_tile_texture(
|
||||
&self.tile_viewbox,
|
||||
@@ -522,11 +522,95 @@ impl RenderState {
|
||||
&tile_rect,
|
||||
);
|
||||
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
self.current_tile.unwrap(),
|
||||
rect,
|
||||
self.background_color,
|
||||
);
|
||||
if present_to_target {
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
self.current_tile.unwrap(),
|
||||
rect,
|
||||
self.background_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn present_cached_tiles_to_target(&mut self) {
|
||||
// Clear target before presenting buffered tiles.
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::Target)
|
||||
.clear(self.background_color);
|
||||
|
||||
self.present_cached_tiles_to_target_no_clear();
|
||||
}
|
||||
|
||||
/// Presents cached tiles to the target surface without clearing first.
|
||||
/// Used when combining with a snapshot placeholder background.
|
||||
fn present_cached_tiles_to_target_no_clear(&mut self) {
|
||||
let scale = self.get_scale();
|
||||
let offset_x = self.viewbox.area.left * scale;
|
||||
let offset_y = self.viewbox.area.top * scale;
|
||||
let start_tile_x = (offset_x / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
let start_tile_y = (offset_y / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
let delta_x = start_tile_x - offset_x;
|
||||
let delta_y = start_tile_y - offset_y;
|
||||
let tiles::TileRect(start_x, start_y, end_x, end_y) =
|
||||
tiles::get_tiles_for_viewbox(self.viewbox, scale);
|
||||
|
||||
for ty in start_y..=end_y {
|
||||
for tx in start_x..=end_x {
|
||||
let tile = tiles::Tile::from(tx, ty);
|
||||
let has_tile = self.surfaces.has_cached_tile_surface(tile);
|
||||
if has_tile {
|
||||
let aligned = self.get_aligned_tile_bounds(tile);
|
||||
let rect = Rect::from_xywh(
|
||||
aligned.left + delta_x,
|
||||
aligned.top + delta_y,
|
||||
aligned.width(),
|
||||
aligned.height(),
|
||||
);
|
||||
self.surfaces
|
||||
.draw_cached_tile_surface(tile, rect, self.background_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the cached target snapshot transformed to the current view.
|
||||
/// Returns `true` when the snapshot was available and rendered.
|
||||
fn draw_cached_snapshot_placeholder(&mut self) -> bool {
|
||||
let Some(snapshot) = &self.cached_target_snapshot else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let cached_scale = self.get_cached_scale();
|
||||
if cached_scale <= f32::EPSILON {
|
||||
return false;
|
||||
}
|
||||
|
||||
let canvas = self.surfaces.canvas(SurfaceId::Target);
|
||||
canvas.save();
|
||||
|
||||
// Scale and translate the target according to the cached data
|
||||
let navigate_zoom = self.viewbox.zoom / self.cached_viewbox.zoom;
|
||||
|
||||
canvas.scale((navigate_zoom, navigate_zoom));
|
||||
|
||||
let TileRect(start_tile_x, start_tile_y, _, _) =
|
||||
tiles::get_tiles_for_viewbox_with_interest(
|
||||
self.cached_viewbox,
|
||||
VIEWPORT_INTEREST_AREA_THRESHOLD,
|
||||
cached_scale,
|
||||
);
|
||||
let offset_x = self.viewbox.area.left * self.cached_viewbox.zoom * self.options.dpr();
|
||||
let offset_y = self.viewbox.area.top * self.cached_viewbox.zoom * self.options.dpr();
|
||||
|
||||
canvas.translate((
|
||||
(start_tile_x as f32 * tiles::TILE_SIZE) - offset_x,
|
||||
(start_tile_y as f32 * tiles::TILE_SIZE) - offset_y,
|
||||
));
|
||||
|
||||
canvas.clear(self.background_color);
|
||||
canvas.draw_image(snapshot, (0, 0), Some(&skia::Paint::default()));
|
||||
canvas.restore();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>) {
|
||||
@@ -1086,43 +1170,25 @@ 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);
|
||||
canvas.save();
|
||||
|
||||
// Scale and translate the target according to the cached data
|
||||
let navigate_zoom = self.viewbox.zoom / self.cached_viewbox.zoom;
|
||||
|
||||
canvas.scale((navigate_zoom, navigate_zoom));
|
||||
|
||||
let TileRect(start_tile_x, start_tile_y, _, _) =
|
||||
tiles::get_tiles_for_viewbox_with_interest(
|
||||
self.cached_viewbox,
|
||||
VIEWPORT_INTEREST_AREA_THRESHOLD,
|
||||
scale,
|
||||
);
|
||||
let offset_x = self.viewbox.area.left * self.cached_viewbox.zoom * self.options.dpr();
|
||||
let offset_y = self.viewbox.area.top * self.cached_viewbox.zoom * self.options.dpr();
|
||||
|
||||
canvas.translate((
|
||||
(start_tile_x as f32 * tiles::TILE_SIZE) - offset_x,
|
||||
(start_tile_y as f32 * tiles::TILE_SIZE) - offset_y,
|
||||
));
|
||||
|
||||
canvas.clear(self.background_color);
|
||||
canvas.draw_image(snapshot, (0, 0), Some(&skia::Paint::default()));
|
||||
canvas.restore();
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render(self);
|
||||
}
|
||||
|
||||
ui::render(self, shapes);
|
||||
debug::render_wasm_label(self);
|
||||
|
||||
self.flush_and_submit();
|
||||
if self.zoom_changed() {
|
||||
// For zoom: use only the scaled snapshot (original behavior from staging-render).
|
||||
// Mixing with tiles causes flickering due to coordinate misalignment.
|
||||
self.draw_cached_snapshot_placeholder();
|
||||
} else {
|
||||
// For panning: present cached tiles directly.
|
||||
self.present_cached_tiles_to_target();
|
||||
}
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render(self);
|
||||
}
|
||||
|
||||
ui::render(self, shapes);
|
||||
debug::render_wasm_label(self);
|
||||
|
||||
self.flush_and_submit();
|
||||
|
||||
performance::end_measure!("render_from_cache");
|
||||
performance::end_timed_log!("render_from_cache", _start);
|
||||
}
|
||||
@@ -1152,7 +1218,7 @@ impl RenderState {
|
||||
s.canvas().scale((scale, scale));
|
||||
});
|
||||
|
||||
let viewbox_cache_size = get_cache_size(self.viewbox, scale);
|
||||
let viewbox_cache_size = get_cache_size(self.viewbox, scale);
|
||||
let cached_viewbox_cache_size = get_cache_size(self.cached_viewbox, scale);
|
||||
// Only resize cache if the new size is larger than the cached size
|
||||
// This avoids unnecessary surface recreations when the cache size decreases
|
||||
@@ -1965,7 +2031,8 @@ impl RenderState {
|
||||
timestamp: i32,
|
||||
allow_stop: bool,
|
||||
) -> Result<(), String> {
|
||||
let mut should_stop = false;
|
||||
let mut should_stop = false;
|
||||
let present_incrementally = false;
|
||||
let root_ids = {
|
||||
if let Some(shape_id) = base_object {
|
||||
vec![*shape_id]
|
||||
@@ -1981,22 +2048,24 @@ impl RenderState {
|
||||
if let Some(current_tile) = self.current_tile {
|
||||
if self.surfaces.has_cached_tile_surface(current_tile) {
|
||||
performance::begin_measure!("render_shape_tree::cached");
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
current_tile,
|
||||
tile_rect,
|
||||
self.background_color,
|
||||
);
|
||||
performance::end_measure!("render_shape_tree::cached");
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_workspace_current_tile(
|
||||
self,
|
||||
"Cached".to_string(),
|
||||
if present_incrementally {
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
current_tile,
|
||||
tile_rect,
|
||||
self.background_color,
|
||||
);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_workspace_current_tile(
|
||||
self,
|
||||
"Cached".to_string(),
|
||||
current_tile,
|
||||
tile_rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
performance::end_measure!("render_shape_tree::cached");
|
||||
} else {
|
||||
performance::begin_measure!("render_shape_tree::uncached");
|
||||
let (is_empty, early_return) =
|
||||
@@ -2008,7 +2077,7 @@ impl RenderState {
|
||||
performance::end_measure!("render_shape_tree::uncached");
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
if !is_empty {
|
||||
self.apply_render_to_final_canvas(tile_rect);
|
||||
self.apply_render_to_final_canvas(tile_rect, present_incrementally);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_workspace_current_tile(
|
||||
@@ -2063,6 +2132,10 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
|
||||
if !present_incrementally {
|
||||
self.present_cached_tiles_to_target();
|
||||
}
|
||||
|
||||
self.render_in_progress = false;
|
||||
|
||||
self.surfaces.gc();
|
||||
@@ -2172,17 +2245,6 @@ impl RenderState {
|
||||
performance::end_measure!("rebuild_tiles_shallow");
|
||||
}
|
||||
|
||||
/// Clears the tile index without invalidating cached tile textures.
|
||||
/// This is useful when tile positions don't change (e.g., during pan operations)
|
||||
/// but the tile index needs to be synchronized. The cached tile textures remain
|
||||
/// valid since they don't depend on the current view position, only on zoom level.
|
||||
/// This is much more efficient than clearing the entire cache surface.
|
||||
pub fn clear_tile_index(&mut self) {
|
||||
performance::begin_measure!("clear_tile_index");
|
||||
self.surfaces.clear_tiles();
|
||||
performance::end_measure!("clear_tile_index");
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) {
|
||||
performance::begin_measure!("rebuild_tiles");
|
||||
|
||||
|
||||
@@ -397,6 +397,30 @@ impl Surfaces {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache a tile from an existing snapshot (used in round-robin rendering)
|
||||
pub fn cache_tile_from_snapshot(
|
||||
&mut self,
|
||||
tile_viewbox: &TileViewbox,
|
||||
tile: &Tile,
|
||||
snapshot: &skia::Image,
|
||||
tile_rect: &skia::Rect,
|
||||
) {
|
||||
// Crop the snapshot to tile bounds (removing margins)
|
||||
let rect = IRect::from_xywh(
|
||||
self.margins.width,
|
||||
self.margins.height,
|
||||
self.current.width() - TILE_SIZE_MULTIPLIER * self.margins.width,
|
||||
self.current.height() - TILE_SIZE_MULTIPLIER * self.margins.height,
|
||||
);
|
||||
|
||||
if let Some(cropped) = snapshot.make_subset(None, rect) {
|
||||
self.tiles.add(tile_viewbox, tile, cropped.clone());
|
||||
self.cache
|
||||
.canvas()
|
||||
.draw_image_rect(cropped, None, tile_rect, &skia::Paint::default());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_cached_tile_surface(&self, tile: Tile) -> bool {
|
||||
self.tiles.has(tile)
|
||||
}
|
||||
@@ -409,19 +433,36 @@ impl Surfaces {
|
||||
self.tiles.remove(tile);
|
||||
}
|
||||
|
||||
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect, color: skia::Color) {
|
||||
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect, _color: skia::Color) {
|
||||
let image = self.tiles.get(tile).unwrap();
|
||||
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_color(color);
|
||||
|
||||
self.target.canvas().draw_rect(rect, &paint);
|
||||
|
||||
// Draw the cached tile image directly without background rect
|
||||
// The tile image already contains the full tile content including background
|
||||
self.target
|
||||
.canvas()
|
||||
.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
||||
}
|
||||
|
||||
/// Draws the current tile surface directly to the target without caching.
|
||||
/// Used for incremental rendering to show progress as shapes are rendered.
|
||||
pub fn draw_current_to_target(&mut self, tile_rect: &skia::Rect) {
|
||||
let rect = IRect::from_xywh(
|
||||
self.margins.width,
|
||||
self.margins.height,
|
||||
self.current.width() - TILE_SIZE_MULTIPLIER * self.margins.width,
|
||||
self.current.height() - TILE_SIZE_MULTIPLIER * self.margins.height,
|
||||
);
|
||||
|
||||
if let Some(snapshot) = self.current.image_snapshot_with_bounds(rect) {
|
||||
self.target.canvas().draw_image_rect(
|
||||
snapshot,
|
||||
None,
|
||||
tile_rect,
|
||||
&skia::Paint::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_cached_tiles(&mut self, color: skia::Color) {
|
||||
self.tiles.clear();
|
||||
self.cache.canvas().clear(color);
|
||||
@@ -430,6 +471,16 @@ impl Surfaces {
|
||||
pub fn gc(&mut self) {
|
||||
self.tiles.gc();
|
||||
}
|
||||
|
||||
/// Takes a snapshot of the Current surface for later restoration.
|
||||
pub fn snapshot_current(&mut self) -> skia::Image {
|
||||
self.current.image_snapshot()
|
||||
}
|
||||
|
||||
/// Restores a snapshot to the Current surface by drawing it at origin.
|
||||
pub fn restore_snapshot_to_current(&mut self, snapshot: &skia::Image) {
|
||||
self.current.canvas().draw_image(snapshot, (0.0, 0.0), None);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileTextureCache {
|
||||
|
||||
@@ -197,10 +197,6 @@ impl<'a> State<'a> {
|
||||
self.render_state.rebuild_tiles_shallow(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn clear_tile_index(&mut self) {
|
||||
self.render_state.clear_tile_index();
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles(&mut self) {
|
||||
self.render_state.rebuild_tiles_from(&self.shapes, None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user