diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 4394d962cb..400917604e 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -2817,6 +2817,24 @@ impl RenderState { paint.set_color(self.background_color); s.canvas().draw_rect(tile_rect, &paint); }); + // Keep Cache surface coherent for render_from_cache. + if !self.options.is_fast_mode() { + if !self.cache_cleared_this_render { + self.surfaces.clear_cache(self.background_color); + self.cache_cleared_this_render = true; + } + let aligned_rect = self.get_aligned_tile_bounds(current_tile); + self.surfaces.apply_mut(SurfaceId::Cache as u32, |s| { + let mut paint = skia::Paint::default(); + paint.set_color(self.background_color); + s.canvas().draw_rect(aligned_rect, &paint); + }); + } + + // Clear atlas region to transparent so background shows through. + let _ = self + .surfaces + .clear_doc_rect_in_atlas(&mut self.gpu_state, self.render_area); } } } diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 3a1d20d900..e3a9512e08 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -289,6 +289,33 @@ impl Surfaces { Ok(()) } + pub fn clear_doc_rect_in_atlas( + &mut self, + gpu_state: &mut GpuState, + doc_rect: skia::Rect, + ) -> Result<()> { + if doc_rect.is_empty() { + return Ok(()); + } + + self.ensure_atlas_contains(gpu_state, doc_rect)?; + + // Destination is document-space rect mapped into atlas pixel coords. + let dst = skia::Rect::from_xywh( + (doc_rect.left - self.atlas_origin.x) * self.atlas_scale, + (doc_rect.top - self.atlas_origin.y) * self.atlas_scale, + doc_rect.width() * self.atlas_scale, + doc_rect.height() * self.atlas_scale, + ); + + let canvas = self.atlas.canvas(); + canvas.save(); + canvas.clip_rect(dst, None, true); + canvas.clear(skia::Color::TRANSPARENT); + canvas.restore(); + Ok(()) + } + pub fn clear_tiles(&mut self) { self.tiles.clear(); } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 74b81d6336..c624dad43d 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -160,14 +160,24 @@ impl State { // Only remove the children when is being deleted from the owner if shape.parent_id.is_none() || shape.parent_id == Some(parent_id) { - let tiles::TileRect(rsx, rsy, rex, rey) = - self.render_state.get_tiles_for_shape(shape, &self.shapes); - for x in rsx..=rex { - for y in rsy..=rey { - let tile = tiles::Tile(x, y); - self.render_state.remove_cached_tile(tile); - self.render_state.tiles.remove_shape_at(tile, shape.id); - } + // IMPORTANT: + // Do NOT use `get_tiles_for_shape` here. That method intersects the shape + // tiles with the current interest area, which means we'd only invalidate + // the subset currently near the viewport. When the user later pans/zooms + // to reveal previously cached tiles, stale pixels could reappear. + // + // Instead, remove the shape from *all* tiles where it was indexed, and + // drop cached tiles for those entries. + let indexed_tiles: Vec = self + .render_state + .tiles + .get_tiles_of(shape.id) + .map(|t| t.iter().copied().collect()) + .unwrap_or_default(); + + for tile in indexed_tiles { + self.render_state.remove_cached_tile(tile); + self.render_state.tiles.remove_shape_at(tile, shape.id); } if let Some(shape_to_delete) = self.shapes.get(&id) {