From 80ea8b8b47c2bff79569b478a31dd2cf36f46b7a Mon Sep 17 00:00:00 2001 From: Elena Torro Date: Fri, 17 Apr 2026 11:36:40 +0200 Subject: [PATCH] wip --- render-wasm/src/render.rs | 28 +++++++++++++++++++++++++++- render-wasm/src/render/surfaces.rs | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index e1897ae09b..aab064509f 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -2794,8 +2794,13 @@ impl RenderState { } } else { performance::begin_measure!("render_shape_tree::uncached"); + // At this point, interest-area tiles can still yield. + let tile_is_visible = self + .current_tile + .is_some_and(|t| self.tile_viewbox.is_visible(&t)); + let can_stop = allow_stop && !tile_is_visible; let (is_empty, early_return) = self - .render_shape_tree_partial_uncached(tree, timestamp, allow_stop, false)?; + .render_shape_tree_partial_uncached(tree, timestamp, can_stop, false)?; if early_return { return Ok(()); @@ -2866,6 +2871,18 @@ impl RenderState { })); } } + + // Only yield if the next tile is NOT visible. + let next_is_visible = self + .current_tile + .is_some_and(|t| self.tile_viewbox.is_visible(&t)); + + if allow_stop + && !next_is_visible + && performance::get_time() - timestamp > MAX_BLOCKING_TIME_MS + { + return Ok(()); + } } else { should_stop = true; } @@ -3043,6 +3060,15 @@ impl RenderState { } pub fn remove_cached_tile(&mut self, tile: tiles::Tile) { + let scale = self.get_scale(); + let tile_size = tiles::get_tile_size(scale); + let doc_rect = skia::Rect::from_xywh( + tile.x() as f32 * tile_size, + tile.y() as f32 * tile_size, + tile_size, + tile_size, + ); + self.surfaces.invalidate_atlas_rect(doc_rect); self.surfaces.remove_cached_tile_surface(tile); } diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 3a1d20d900..3156841b82 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -297,6 +297,27 @@ impl Surfaces { self.atlas_size.width > 0 && self.atlas_size.height > 0 } + /// Clears a document-space rect inside the persistent atlas. Called when + /// a tile cache entry is invalidated so that a zoom-out fast-mode preview + /// (which paints from the atlas) doesn't show the old pixels of deleted + /// or moved shapes. The cleared region becomes transparent; the next tile + /// render at any zoom will repopulate it via `blit_tile_image_into_atlas`. + pub fn invalidate_atlas_rect(&mut self, doc_rect: skia::Rect) { + if !self.has_atlas() || doc_rect.is_empty() { + return; + } + let scale = self.atlas_scale.max(0.01); + let atlas_rect = skia::Rect::from_xywh( + (doc_rect.left - self.atlas_origin.x) * scale, + (doc_rect.top - self.atlas_origin.y) * scale, + doc_rect.width() * scale, + doc_rect.height() * scale, + ); + let mut paint = skia::Paint::default(); + paint.set_blend_mode(skia::BlendMode::Clear); + self.atlas.canvas().draw_rect(atlas_rect, &paint); + } + /// Draw the persistent atlas onto the target using the current viewbox transform. /// Intended for fast pan/zoom-out previews (avoids per-tile composition). pub fn draw_atlas_to_target(&mut self, viewbox: Viewbox, dpr: f32, background: skia::Color) {