From 4799f6fe0a469863952e7c29f081f738a6751189 Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Fri, 21 Feb 2025 11:04:12 +0100 Subject: [PATCH] :recycle: Refactor rendering surfaces (#5921) --- render-wasm/src/render.rs | 183 +++++++++++++---------------- render-wasm/src/render/debug.rs | 14 ++- render-wasm/src/render/fills.rs | 4 +- render-wasm/src/render/shadows.rs | 30 ++--- render-wasm/src/render/strokes.rs | 4 +- render-wasm/src/render/surfaces.rs | 51 ++++++++ 6 files changed, 160 insertions(+), 126 deletions(-) create mode 100644 render-wasm/src/render/surfaces.rs diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 5c034adcd..9f68cb191 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -13,11 +13,13 @@ mod images; mod options; mod shadows; mod strokes; +mod surfaces; use crate::shapes::{Corners, Kind, Shape}; use cache::CachedSurfaceImage; use gpu_state::GpuState; use options::RenderOptions; +use surfaces::Surfaces; pub use blend::BlendMode; pub use images::*; @@ -53,12 +55,8 @@ pub struct NodeRenderState { pub(crate) struct RenderState { gpu_state: GpuState, pub options: RenderOptions, - pub final_surface: skia::Surface, - pub render_surface: skia::Surface, - pub drawing_surface: skia::Surface, - pub shadow_surface: skia::Surface, - pub overlay_surface: skia::Surface, - pub debug_surface: skia::Surface, + pub surfaces: Surfaces, + pub sampling_options: skia::SamplingOptions, pub font_provider: skia::textlayout::TypefaceFontProvider, pub cached_surface_image: Option, pub viewbox: Viewbox, @@ -77,38 +75,24 @@ impl RenderState { pub fn new(width: i32, height: i32) -> RenderState { // This needs to be done once per WebGL context. let mut gpu_state = GpuState::new(); - let mut final_surface = gpu_state.create_target_surface(width, height); - let render_surface = final_surface - .new_surface_with_dimensions((width, height)) - .unwrap(); - let shadow_surface = final_surface - .new_surface_with_dimensions((width, height)) - .unwrap(); - let overlay_surface = final_surface - .new_surface_with_dimensions((width, height)) - .unwrap(); - let drawing_surface = final_surface - .new_surface_with_dimensions((width, height)) - .unwrap(); - let debug_surface = final_surface - .new_surface_with_dimensions((width, height)) - .unwrap(); + let surfaces = Surfaces::new(&mut gpu_state, (width, height)); let mut font_provider = skia::textlayout::TypefaceFontProvider::new(); let default_font = skia::FontMgr::default() .new_from_data(DEFAULT_FONT_BYTES, None) .expect("Failed to load font"); font_provider.register_typeface(default_font, "robotomono-regular"); + // This is used multiple times everywhere so instead of creating new instances every + // time we reuse this one. + let sampling_options = + skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest); + RenderState { gpu_state, - final_surface, - render_surface, - overlay_surface, - shadow_surface, - drawing_surface, - debug_surface, + surfaces, cached_surface_image: None, font_provider, + sampling_options, options: RenderOptions::default(), viewbox: Viewbox::new(width as f32, height as f32), images: ImageStore::new(), @@ -160,68 +144,52 @@ impl RenderState { let dpr_width = (width as f32 * self.options.dpr()).floor() as i32; let dpr_height = (height as f32 * self.options.dpr()).floor() as i32; - let surface = self.gpu_state.create_target_surface(dpr_width, dpr_height); - self.final_surface = surface; - self.render_surface = self - .final_surface - .new_surface_with_dimensions((dpr_width, dpr_height)) - .unwrap(); - self.overlay_surface = self - .final_surface - .new_surface_with_dimensions((dpr_width, dpr_height)) - .unwrap(); - self.shadow_surface = self - .final_surface - .new_surface_with_dimensions((dpr_width, dpr_height)) - .unwrap(); - self.drawing_surface = self - .final_surface - .new_surface_with_dimensions((dpr_width, dpr_height)) - .unwrap(); - self.debug_surface = self - .final_surface - .new_surface_with_dimensions((dpr_width, dpr_height)) - .unwrap(); - + self.surfaces + .resize(&mut self.gpu_state, dpr_width, dpr_height); self.viewbox.set_wh(width as f32, height as f32); } pub fn flush(&mut self) { self.gpu_state .context - .flush_and_submit_surface(&mut self.final_surface, None); + .flush_and_submit_surface(&mut self.surfaces.target, None); } pub fn reset_canvas(&mut self) { - self.drawing_surface.canvas().restore_to_count(1); - self.render_surface.canvas().restore_to_count(1); - self.drawing_surface + self.surfaces.shape.canvas().restore_to_count(1); + self.surfaces.current.canvas().restore_to_count(1); + self.surfaces + .shape .canvas() .clear(self.background_color) .reset_matrix(); - self.render_surface + self.surfaces + .current .canvas() .clear(self.background_color) .reset_matrix(); - self.shadow_surface + self.surfaces + .shadow .canvas() .clear(self.background_color) .reset_matrix(); - self.overlay_surface + self.surfaces + .overlay .canvas() .clear(self.background_color) .reset_matrix(); - self.debug_surface + self.surfaces + .debug .canvas() .clear(skia::Color::TRANSPARENT) .reset_matrix(); } pub fn apply_render_to_final_canvas(&mut self) { - self.render_surface.draw( - &mut self.final_surface.canvas(), + self.surfaces.current.draw( + &mut self.surfaces.target.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + self.sampling_options, Some(&skia::Paint::default()), ); } @@ -229,36 +197,41 @@ impl RenderState { pub fn apply_drawing_to_render_canvas(&mut self) { self.gpu_state .context - .flush_and_submit_surface(&mut self.drawing_surface, None); + .flush_and_submit_surface(&mut self.surfaces.shape, None); - self.drawing_surface.draw( - &mut self.render_surface.canvas(), + self.surfaces.shape.draw( + &mut self.surfaces.current.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + self.sampling_options, Some(&skia::Paint::default()), ); self.gpu_state .context - .flush_and_submit_surface(&mut self.render_surface, None); + .flush_and_submit_surface(&mut self.surfaces.current, None); self.gpu_state .context - .flush_and_submit_surface(&mut self.overlay_surface, None); - self.overlay_surface.draw( - &mut self.render_surface.canvas(), + .flush_and_submit_surface(&mut self.surfaces.overlay, None); + + self.surfaces.overlay.draw( + &mut self.surfaces.current.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + self.sampling_options, None, ); - self.shadow_surface.canvas().clear(skia::Color::TRANSPARENT); - self.overlay_surface + self.surfaces + .shadow .canvas() .clear(skia::Color::TRANSPARENT); - self.drawing_surface + + self.surfaces + .overlay .canvas() .clear(skia::Color::TRANSPARENT); + + self.surfaces.shape.canvas().clear(skia::Color::TRANSPARENT); } pub fn invalidate_cache_if_needed(&mut self) { @@ -273,16 +246,18 @@ impl RenderState { modifiers: Option<&Matrix>, clip_bounds: Option<(Rect, Option, Matrix)>, ) { - self.drawing_surface.canvas().save(); + self.surfaces.shape.canvas().save(); if let Some((bounds, corners, transform)) = clip_bounds { - self.drawing_surface.canvas().concat(&transform); + self.surfaces.shape.canvas().concat(&transform); if let Some(corners) = corners { let rrect = RRect::new_rect_radii(bounds, &corners); - self.drawing_surface + self.surfaces + .shape .canvas() .clip_rrect(rrect, skia::ClipOp::Intersect, true); } else { - self.drawing_surface + self.surfaces + .shape .canvas() .clip_rect(bounds, skia::ClipOp::Intersect, true); } @@ -292,10 +267,11 @@ impl RenderState { paint.set_style(skia::PaintStyle::Stroke); paint.set_color(skia::Color::from_argb(255, 255, 0, 0)); paint.set_stroke_width(4.); - self.drawing_surface.canvas().draw_rect(bounds, &paint); + self.surfaces.shape.canvas().draw_rect(bounds, &paint); } - self.drawing_surface + self.surfaces + .shape .canvas() .concat(&transform.invert().unwrap_or(Matrix::default())); } @@ -316,17 +292,17 @@ impl RenderState { match &shape.kind { Kind::SVGRaw(sr) => { if let Some(modifiers) = modifiers { - self.drawing_surface.canvas().concat(&modifiers); + self.surfaces.shape.canvas().concat(&modifiers); } - self.drawing_surface.canvas().concat(&matrix); + self.surfaces.shape.canvas().concat(&matrix); if let Some(svg) = shape.svg.as_ref() { - svg.render(self.drawing_surface.canvas()) + svg.render(self.surfaces.shape.canvas()) } else { let font_manager = skia::FontMgr::from(self.font_provider.clone()); let dom_result = skia::svg::Dom::from_str(sr.content.to_string(), font_manager); match dom_result { Ok(dom) => { - dom.render(self.drawing_surface.canvas()); + dom.render(self.surfaces.shape.canvas()); shape.set_svg(dom); } Err(e) => { @@ -336,7 +312,7 @@ impl RenderState { } } _ => { - self.drawing_surface.canvas().concat(&matrix); + self.surfaces.shape.canvas().concat(&matrix); for fill in shape.fills().rev() { fills::render(self, &shape, fill); @@ -365,7 +341,7 @@ impl RenderState { }; self.apply_drawing_to_render_canvas(); - self.drawing_surface.canvas().restore(); + self.surfaces.shape.canvas().restore(); } pub fn start_render_loop( @@ -380,11 +356,12 @@ impl RenderState { } } self.reset_canvas(); - self.drawing_surface.canvas().scale(( + self.surfaces.shape.canvas().scale(( self.viewbox.zoom * self.options.dpr(), self.viewbox.zoom * self.options.dpr(), )); - self.drawing_surface + self.surfaces + .shape .canvas() .translate((self.viewbox.pan_x, self.viewbox.pan_y)); // @@ -446,7 +423,7 @@ impl RenderState { .map_or(true, |img| img.invalid) { self.cached_surface_image = Some(CachedSurfaceImage { - image: self.render_surface.image_snapshot(), + image: self.surfaces.current.image_snapshot(), viewbox: self.viewbox, invalid: false, has_all_shapes: self.render_complete, @@ -475,27 +452,29 @@ impl RenderState { let image = &cached.image; let paint = skia::Paint::default(); - self.final_surface.canvas().save(); - self.drawing_surface.canvas().save(); + self.surfaces.target.canvas().save(); + self.surfaces.shape.canvas().save(); let navigate_zoom = self.viewbox.zoom / cached.viewbox.zoom; let navigate_x = cached.viewbox.zoom * (self.viewbox.pan_x - cached.viewbox.pan_x); let navigate_y = cached.viewbox.zoom * (self.viewbox.pan_y - cached.viewbox.pan_y); - self.final_surface + self.surfaces + .target .canvas() .scale((navigate_zoom, navigate_zoom)); - self.final_surface.canvas().translate(( + self.surfaces.target.canvas().translate(( navigate_x * self.options.dpr(), navigate_y * self.options.dpr(), )); - self.final_surface.canvas().clear(self.background_color); - self.final_surface + self.surfaces.target.canvas().clear(self.background_color); + self.surfaces + .target .canvas() .draw_image(image, (0, 0), Some(&paint)); - self.final_surface.canvas().restore(); - self.drawing_surface.canvas().restore(); + self.surfaces.target.canvas().restore(); + self.surfaces.shape.canvas().restore(); self.flush(); @@ -516,7 +495,7 @@ impl RenderState { if group.masked { let paint = skia::Paint::default(); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); - self.render_surface.canvas().save_layer(&layer_rec); + self.surfaces.current.canvas().save_layer(&layer_rec); } } _ => {} @@ -533,7 +512,7 @@ impl RenderState { let mut mask_paint = skia::Paint::default(); mask_paint.set_blend_mode(skia::BlendMode::DstIn); let mask_rec = skia::canvas::SaveLayerRec::default().paint(&mask_paint); - self.render_surface.canvas().save_layer(&mask_rec); + self.surfaces.current.canvas().save_layer(&mask_rec); } if let Some(image_filter) = element.image_filter(self.viewbox.zoom * self.options.dpr()) { @@ -541,7 +520,7 @@ impl RenderState { } let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); - self.render_surface.canvas().save_layer(&layer_rec); + self.surfaces.current.canvas().save_layer(&layer_rec); } pub fn render_shape_exit(&mut self, element: &mut Shape, visited_mask: bool) { @@ -552,13 +531,13 @@ impl RenderState { match element.kind { Kind::Group(group) => { if group.masked { - self.render_surface.canvas().restore(); + self.surfaces.current.canvas().restore(); } } _ => {} } } - self.render_surface.canvas().restore(); + self.surfaces.current.canvas().restore(); } pub fn render_shape_tree( diff --git a/render-wasm/src/render/debug.rs b/render-wasm/src/render/debug.rs index 2a38d2803..345f02642 100644 --- a/render-wasm/src/render/debug.rs +++ b/render-wasm/src/render/debug.rs @@ -17,13 +17,14 @@ fn render_debug_view(render_state: &mut RenderState) { scaled_rect.set_xywh(x, y, width, height); render_state - .debug_surface + .surfaces + .debug .canvas() .draw_rect(scaled_rect, &paint); } pub fn render_wasm_label(render_state: &mut RenderState) { - let canvas = render_state.render_surface.canvas(); + let canvas = render_state.surfaces.current.canvas(); let skia::ISize { width, height } = canvas.base_layer_size(); let p = skia::Point::new(width as f32 - 100.0, height as f32 - 25.0); @@ -57,7 +58,8 @@ pub fn render_debug_shape(render_state: &mut RenderState, element: &Shape, inter scaled_rect.set_xywh(x, y, width, height); render_state - .debug_surface + .surfaces + .debug .canvas() .draw_rect(scaled_rect, &paint); } @@ -65,10 +67,10 @@ pub fn render_debug_shape(render_state: &mut RenderState, element: &Shape, inter pub fn render(render_state: &mut RenderState) { let paint = skia::Paint::default(); render_debug_view(render_state); - render_state.debug_surface.draw( - &mut render_state.render_surface.canvas(), + render_state.surfaces.debug.draw( + &mut render_state.surfaces.current.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + render_state.sampling_options, Some(&paint), ); } diff --git a/render-wasm/src/render/fills.rs b/render-wasm/src/render/fills.rs index b9e594c7c..b6b3caf84 100644 --- a/render-wasm/src/render/fills.rs +++ b/render-wasm/src/render/fills.rs @@ -15,7 +15,7 @@ fn draw_image_fill_in_container( } let size = image_fill.size(); - let canvas = render_state.drawing_surface.canvas(); + let canvas = render_state.surfaces.shape.canvas(); let kind = &shape.kind; let container = &shape.selrect; let path_transform = shape.to_path_transform(); @@ -91,7 +91,7 @@ fn draw_image_fill_in_container( * This SHOULD be the only public function in this module. */ pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) { - let canvas = render_state.drawing_surface.canvas(); + let canvas = render_state.surfaces.shape.canvas(); let selrect = shape.selrect; let path_transform = shape.to_path_transform(); let kind = &shape.kind; diff --git a/render-wasm/src/render/shadows.rs b/render-wasm/src/render/shadows.rs index bb3a55e04..24ed0cfdd 100644 --- a/render-wasm/src/render/shadows.rs +++ b/render-wasm/src/render/shadows.rs @@ -5,22 +5,23 @@ use crate::shapes::Shadow; pub fn render_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { let shadow_paint = shadow.to_paint(scale); - render_state.drawing_surface.draw( - &mut render_state.shadow_surface.canvas(), + render_state.surfaces.shape.draw( + &mut render_state.surfaces.shadow.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + render_state.sampling_options, Some(&shadow_paint), ); - render_state.shadow_surface.draw( - &mut render_state.render_surface.canvas(), + render_state.surfaces.shadow.draw( + &mut render_state.surfaces.current.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + render_state.sampling_options, Some(&skia::Paint::default()), ); render_state - .shadow_surface + .surfaces + .shadow .canvas() .clear(skia::Color::TRANSPARENT); } @@ -28,22 +29,23 @@ pub fn render_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale pub fn render_inner_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { let shadow_paint = shadow.to_paint(scale); - render_state.drawing_surface.draw( - render_state.shadow_surface.canvas(), + render_state.surfaces.shape.draw( + render_state.surfaces.shadow.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + render_state.sampling_options, Some(&shadow_paint), ); - render_state.shadow_surface.draw( - &mut render_state.overlay_surface.canvas(), + render_state.surfaces.shadow.draw( + &mut render_state.surfaces.overlay.canvas(), (0.0, 0.0), - skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + render_state.sampling_options, None, ); render_state - .shadow_surface + .surfaces + .shadow .canvas() .clear(skia::Color::TRANSPARENT); } diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 32960e196..337177844 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -335,7 +335,7 @@ fn draw_image_stroke_in_container( } let size = image_fill.size(); - let canvas = render_state.drawing_surface.canvas(); + let canvas = render_state.surfaces.shape.canvas(); let kind = &shape.kind; let container = &shape.selrect; let path_transform = shape.to_path_transform(); @@ -429,7 +429,7 @@ fn draw_image_stroke_in_container( * This SHOULD be the only public function in this module. */ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { - let canvas = render_state.drawing_surface.canvas(); + let canvas = render_state.surfaces.shape.canvas(); let dpr_scale = render_state.viewbox.zoom * render_state.options.dpr(); let selrect = shape.selrect; let path_transform = shape.to_path_transform(); diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs new file mode 100644 index 000000000..b7afb7059 --- /dev/null +++ b/render-wasm/src/render/surfaces.rs @@ -0,0 +1,51 @@ +use super::gpu_state::GpuState; +use skia_safe as skia; + +pub struct Surfaces { + // is the final destination surface, the one that it is represented in the canvas element. + pub target: skia::Surface, + // keeps the current render + pub current: skia::Surface, + // keeps the current shape + pub shape: skia::Surface, + // used for rendering shadows + pub shadow: skia::Surface, + // for drawing the things that are over shadows. + pub overlay: skia::Surface, + // for drawing debug info. + pub debug: skia::Surface, +} + +impl Surfaces { + pub fn new(gpu_state: &mut GpuState, (width, height): (i32, i32)) -> Self { + let mut target = gpu_state.create_target_surface(width, height); + let current = target.new_surface_with_dimensions((width, height)).unwrap(); + let shadow = target.new_surface_with_dimensions((width, height)).unwrap(); + let overlay = target.new_surface_with_dimensions((width, height)).unwrap(); + let shape = target.new_surface_with_dimensions((width, height)).unwrap(); + let debug = target.new_surface_with_dimensions((width, height)).unwrap(); + + Surfaces { + target, + current, + shadow, + overlay, + shape, + debug, + } + } + + pub fn set(&mut self, new_surface: skia::Surface) { + let dim = (new_surface.width(), new_surface.height()); + self.target = new_surface; + self.current = self.target.new_surface_with_dimensions(dim).unwrap(); + self.overlay = self.target.new_surface_with_dimensions(dim).unwrap(); + self.shadow = self.target.new_surface_with_dimensions(dim).unwrap(); + self.shape = self.target.new_surface_with_dimensions(dim).unwrap(); + self.debug = self.target.new_surface_with_dimensions(dim).unwrap(); + } + + pub fn resize(&mut self, gpu_state: &mut GpuState, new_width: i32, new_height: i32) { + self.set(gpu_state.create_target_surface(new_width, new_height)); + } +}