diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 877839971b..5e3e0e20cd 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -623,38 +623,65 @@ impl RenderState { let paint = skia::Paint::default(); - self.surfaces - .draw_into(SurfaceId::TextDropShadows, SurfaceId::Current, Some(&paint)); + // Only draw surfaces that have content (dirty flag optimization) + if self.surfaces.is_dirty(SurfaceId::TextDropShadows) { + self.surfaces + .draw_into(SurfaceId::TextDropShadows, SurfaceId::Current, Some(&paint)); + } - self.surfaces - .draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint)); + if self.surfaces.is_dirty(SurfaceId::Fills) { + self.surfaces + .draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint)); + } let mut render_overlay_below_strokes = false; if let Some(shape) = shape { render_overlay_below_strokes = shape.has_fills(); } - if render_overlay_below_strokes { + if render_overlay_below_strokes && self.surfaces.is_dirty(SurfaceId::InnerShadows) { self.surfaces .draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint)); } - self.surfaces - .draw_into(SurfaceId::Strokes, SurfaceId::Current, Some(&paint)); + if self.surfaces.is_dirty(SurfaceId::Strokes) { + self.surfaces + .draw_into(SurfaceId::Strokes, SurfaceId::Current, Some(&paint)); + } - if !render_overlay_below_strokes { + if !render_overlay_below_strokes && self.surfaces.is_dirty(SurfaceId::InnerShadows) { self.surfaces .draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint)); } + // Only clear surfaces that were actually used (have dirty flag set) let surface_ids = SurfaceId::Strokes as u32 | SurfaceId::Fills as u32 | SurfaceId::InnerShadows as u32 | SurfaceId::TextDropShadows as u32; - self.surfaces.apply_mut(surface_ids, |s| { - s.canvas().clear(skia::Color::TRANSPARENT); - }); + // Build mask of dirty surfaces that need clearing + let mut dirty_surfaces_to_clear = 0u32; + if self.surfaces.is_dirty(SurfaceId::Strokes) { + dirty_surfaces_to_clear |= SurfaceId::Strokes as u32; + } + if self.surfaces.is_dirty(SurfaceId::Fills) { + dirty_surfaces_to_clear |= SurfaceId::Fills as u32; + } + if self.surfaces.is_dirty(SurfaceId::InnerShadows) { + dirty_surfaces_to_clear |= SurfaceId::InnerShadows as u32; + } + if self.surfaces.is_dirty(SurfaceId::TextDropShadows) { + dirty_surfaces_to_clear |= SurfaceId::TextDropShadows as u32; + } + + if dirty_surfaces_to_clear != 0 { + self.surfaces.apply_mut(dirty_surfaces_to_clear, |s| { + s.canvas().clear(skia::Color::TRANSPARENT); + }); + // Clear dirty flags for surfaces we just cleared + self.surfaces.clear_dirty(dirty_surfaces_to_clear); + } } pub fn clear_focus_mode(&mut self) { @@ -796,7 +823,7 @@ impl RenderState { self.surfaces.canvas(fills_surface_id).concat(&matrix); if let Some(svg) = shape.svg.as_ref() { - svg.render(self.surfaces.canvas(fills_surface_id)) + svg.render(self.surfaces.canvas(fills_surface_id)); } else { let font_manager = skia::FontMgr::from(self.fonts().font_provider().clone()); let dom_result = skia::svg::Dom::from_str(&sr.content, font_manager); diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index fe0edbb455..4f36e69838 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -55,6 +55,8 @@ pub struct Surfaces { tiles: TileTextureCache, sampling_options: skia::SamplingOptions, margins: skia::ISize, + // Tracks which surfaces have content (dirty flag bitmask) + dirty_surfaces: u32, } #[allow(dead_code)] @@ -105,6 +107,7 @@ impl Surfaces { tiles, sampling_options, margins, + dirty_surfaces: 0, } } @@ -148,9 +151,40 @@ impl Surfaces { } pub fn canvas(&mut self, id: SurfaceId) -> &skia::Canvas { + // Automatically mark render surfaces as dirty when accessed + // This tracks which surfaces have content for optimization + match id { + SurfaceId::Fills + | SurfaceId::Strokes + | SurfaceId::InnerShadows + | SurfaceId::TextDropShadows => { + self.mark_dirty(id); + } + _ => {} + } self.get_mut(id).canvas() } + /// Marks a surface as having content (dirty) + pub fn mark_dirty(&mut self, id: SurfaceId) { + self.dirty_surfaces |= id as u32; + } + + /// Checks if a surface has content + pub fn is_dirty(&self, id: SurfaceId) -> bool { + (self.dirty_surfaces & id as u32) != 0 + } + + /// Clears the dirty flag for a surface or set of surfaces + pub fn clear_dirty(&mut self, ids: u32) { + self.dirty_surfaces &= !ids; + } + + /// Clears all dirty flags + pub fn clear_all_dirty(&mut self) { + self.dirty_surfaces = 0; + } + pub fn flush_and_submit(&mut self, gpu_state: &mut GpuState, id: SurfaceId) { let surface = self.get_mut(id); gpu_state.context.flush_and_submit_surface(surface, None); @@ -212,18 +246,33 @@ impl Surfaces { pub fn update_render_context(&mut self, render_area: skia::Rect, scale: f32) { let translation = self.get_render_context_translation(render_area, scale); - self.apply_mut( - SurfaceId::Fills as u32 - | SurfaceId::Strokes as u32 - | SurfaceId::InnerShadows as u32 - | SurfaceId::TextDropShadows as u32, - |s| { - let canvas = s.canvas(); - canvas.reset_matrix(); - canvas.scale((scale, scale)); - canvas.translate(translation); - }, - ); + + // When context changes (zoom/pan/tile), clear all render surfaces first + // to remove any residual content from previous tiles, then mark as dirty + // so they get redrawn with new transformations + let surface_ids = SurfaceId::Fills as u32 + | SurfaceId::Strokes as u32 + | SurfaceId::InnerShadows as u32 + | SurfaceId::TextDropShadows as u32; + + // Clear surfaces before updating transformations to remove residual content + self.apply_mut(surface_ids, |s| { + s.canvas().clear(skia::Color::TRANSPARENT); + }); + + // Mark all render surfaces as dirty so they get redrawn + self.mark_dirty(SurfaceId::Fills); + self.mark_dirty(SurfaceId::Strokes); + self.mark_dirty(SurfaceId::InnerShadows); + self.mark_dirty(SurfaceId::TextDropShadows); + + // Update transformations + self.apply_mut(surface_ids, |s| { + let canvas = s.canvas(); + canvas.reset_matrix(); + canvas.scale((scale, scale)); + canvas.translate(translation); + }); } #[inline] @@ -268,15 +317,18 @@ impl Surfaces { } else { self.canvas(id).draw_rect(shape.selrect, paint); } + self.mark_dirty(id); } pub fn draw_circle_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { self.canvas(id).draw_oval(shape.selrect, paint); + self.mark_dirty(id); } pub fn draw_path_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { if let Some(path) = shape.get_skia_path() { self.canvas(id).draw_path(&path, paint); + self.mark_dirty(id); } } @@ -304,6 +356,9 @@ impl Surfaces { self.canvas(SurfaceId::UI) .clear(skia::Color::TRANSPARENT) .reset_matrix(); + + // Clear all dirty flags after reset + self.clear_all_dirty(); } pub fn cache_current_tile_texture(