diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 65ac296895..4ae8be8b17 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -135,6 +135,14 @@ (wasm.mem/free) text))) +(defn ^:export wasmRenderStats + [] + (let [module wasm/internal-module + f (when module (unchecked-get module "_render_stats"))] + (if (fn? f) + (wasm.h/call module "_render_stats") + (js/console.warn "[debug] render-wasm module not ready or missing _render_stats")))) + (defn ^:export wasmAtlasConsole "Logs the current render-wasm atlas as an image in the JS console (if present)." [] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 9f298c3900..3ab9853005 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1035,6 +1035,13 @@ pub extern "C" fn render_shape_pixels( }) } +#[no_mangle] +pub extern "C" fn render_stats() { + with_state!(state, { + state.render_state.print_stats(); + }) +} + fn main() { #[cfg(target_arch = "wasm32")] init_gl!(); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 6272d8d9a3..52da6d8344 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -16,6 +16,7 @@ mod ui; use skia_safe::{self as skia, Matrix, RRect, Rect}; use std::borrow::Cow; use std::collections::HashSet; +use std::collections::HashMap; use gpu_state::GpuState; @@ -289,9 +290,46 @@ fn sort_z_index(tree: ShapesPoolRef, element: &Shape, children_ids: Vec) - } } +struct RenderStats { + pub counts: HashMap, +} + +impl RenderStats { + pub fn new() -> Self { + Self { + counts: HashMap::new() + } + } + + fn count(&mut self, id: Uuid) -> i32 { + let counter = self.counts.entry(id).or_insert(0); + *counter += 1; + *counter + } + + fn clear(&mut self) { + self.counts.clear(); + } + + #[allow(dead_code)] + fn get(&self, id: &Uuid) -> Option<&i32> { + self.counts.get(id) + } + + fn print(&self) { + let mut sum: i32 = 0; + for (&id, &count) in self.counts.iter() { + println!("{}: {}", id, count); + sum += count; + } + println!("{}: {}", self.counts.len(), sum); + } +} + pub(crate) struct RenderState { gpu_state: GpuState, pub options: RenderOptions, + stats: RenderStats, pub surfaces: Surfaces, pub fonts: FontStore, pub viewbox: Viewbox, @@ -377,6 +415,7 @@ impl RenderState { Ok(RenderState { gpu_state: gpu_state.clone(), options, + stats: RenderStats::new(), surfaces, fonts, viewbox, @@ -851,6 +890,8 @@ impl RenderState { outset: Option, target_surface: SurfaceId, ) -> Result<()> { + self.stats.count(shape.id); + let surface_ids = fills_surface_id as u32 | strokes_surface_id as u32 | innershadows_surface_id as u32 @@ -1662,6 +1703,7 @@ impl RenderState { timestamp: i32, sync_render: bool, ) -> Result<()> { + self.stats.clear(); let _start = performance::begin_timed_log!("start_render_loop"); let scale = self.get_scale(); @@ -3338,6 +3380,10 @@ impl RenderState { pub fn set_view(&mut self, zoom: f32, x: f32, y: f32) { self.viewbox.set_all(zoom, x, y); } + + pub fn print_stats(&self) { + self.stats.print(); + } } impl Drop for RenderState { diff --git a/render-wasm/src/render/gpu_state.rs b/render-wasm/src/render/gpu_state.rs index aa62b83817..e1a1fbc02f 100644 --- a/render-wasm/src/render/gpu_state.rs +++ b/render-wasm/src/render/gpu_state.rs @@ -1,5 +1,5 @@ use crate::error::{Error, Result}; -use skia_safe::gpu::{self, gl::FramebufferInfo, gl::TextureInfo, DirectContext}; +use skia_safe::gpu::{self, ganesh::context_options::Enable, gl::FramebufferInfo, gl::TextureInfo, DirectContext, ContextOptions}; use skia_safe::{self as skia, ISize}; #[derive(Debug, Clone)] @@ -13,7 +13,18 @@ impl GpuState { let interface = gpu::gl::Interface::new_native().ok_or(Error::CriticalError( "Failed to create GL interface".to_string(), ))?; - let context = gpu::direct_contexts::make_gl(interface, None).ok_or( + + // We tweak some options to enhance performance. + let mut context_options = ContextOptions::default(); + println!("context_options {:?}", context_options); + // context_options.reduce_ops_task_splitting = Enable::Yes; + context_options.skip_gl_error_checks = Enable::Yes; + // context_options.runtime_program_cache_size = 1024; + // context_options.allow_multiple_glyph_cache_textures = Enable::Yes; + // context_options.allow_path_mask_caching = false; + println!("context_options {:?}", context_options); + + let context = gpu::direct_contexts::make_gl(interface, Some(&context_options)).ok_or( Error::CriticalError("Failed to create GL context".to_string()), )?; let framebuffer_info = { diff --git a/render-wasm/src/render/options.rs b/render-wasm/src/render/options.rs index 40a3125ccd..81cf2da36d 100644 --- a/render-wasm/src/render/options.rs +++ b/render-wasm/src/render/options.rs @@ -7,7 +7,7 @@ const SHOW_WASM_INFO: u32 = 0x08; // Render performance options // This is the extra area used for tile rendering (tiles beyond viewport). // Higher values pre-render more tiles, reducing empty squares during pan but using more memory. -const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 3; +const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1; const MAX_BLOCKING_TIME_MS: i32 = 32; const NODE_BATCH_THRESHOLD: i32 = 3; const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0; diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index c5a3a26bf1..0a1baa4ed2 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -10,6 +10,9 @@ use crate::error::{Error, Result}; use crate::render::filters::compose_filters; use crate::render::{get_dest_rect, get_source_rect}; +#[cfg(target_arch = "wasm32")] +use crate::run_script; + #[allow(clippy::too_many_arguments)] fn draw_stroke_on_rect( canvas: &skia::Canvas, @@ -229,21 +232,21 @@ fn draw_stroke_on_path( if let Some(pt) = path_transform { canvas.concat(pt); } - let skia_path = path.to_skia_path(); + let skia_path = path.to_skia_path(); match stroke.render_kind(is_open) { StrokeKind::Inner => { - draw_inner_stroke_path(canvas, &skia_path, &draw_paint, blur, antialias); + draw_inner_stroke_path(canvas, skia_path, &draw_paint, blur, antialias); } StrokeKind::Center => { - canvas.draw_path(&skia_path, &draw_paint); + canvas.draw_path(skia_path, &draw_paint); } StrokeKind::Outer => { - draw_outer_stroke_path(canvas, &skia_path, &draw_paint, blur, antialias); + draw_outer_stroke_path(canvas, skia_path, &draw_paint, blur, antialias); } } - handle_stroke_caps(&skia_path, stroke, canvas, is_open, paint, blur, antialias); + handle_stroke_caps(skia_path, stroke, canvas, is_open, paint, blur, antialias); canvas.restore_to_count(save_count); } diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index ca7f2a3ef2..1524f73df1 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -463,6 +463,7 @@ impl Surfaces { } pub fn flush_and_submit(&mut self, gpu_state: &mut GpuState, id: SurfaceId) { + println!("flush_and_submit"); let surface = self.get_mut(id); gpu_state.context.flush_and_submit_surface(surface, None); } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 9e17de41ee..cd05bcb46d 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -2,6 +2,9 @@ use skia_safe::{self as skia}; use indexmap::IndexSet; +#[cfg(target_arch = "wasm32")] +use crate::run_script; + use crate::uuid::Uuid; use std::borrow::Cow; use std::cell::{OnceCell, RefCell}; @@ -1349,7 +1352,7 @@ impl Shape { pub fn get_skia_path(&self) -> Option { if let Some(path) = self.shape_type.path() { - let mut skia_path = path.to_skia_path(); + let mut skia_path = path.to_skia_path().clone(); if let Some(path_transform) = self.to_path_transform() { skia_path = skia_path.make_transform(&path_transform); } @@ -1392,6 +1395,8 @@ impl Shape { if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type { if let Some(path) = shape_type.path_mut() { + #[cfg(target_arch = "wasm32")] + run_script!("console.count('path.transform')"); path.transform(transform); } } else if let Type::Text(text) = &mut self.shape_type { diff --git a/render-wasm/src/shapes/paths.rs b/render-wasm/src/shapes/paths.rs index 2debf44958..4d5c285cb2 100644 --- a/render-wasm/src/shapes/paths.rs +++ b/render-wasm/src/shapes/paths.rs @@ -1,7 +1,10 @@ -use skia_safe::{self as skia, Matrix}; - +use std::{ops::Deref, rc::Rc}; +use skia_safe::{self as skia, Matrix, wrapper::ValueWrapper}; use crate::math; +#[cfg(target_arch = "wasm32")] +use crate::run_script; + mod subpaths; type Point = (f32, f32); @@ -19,7 +22,7 @@ impl Segment {} #[derive(Debug, Clone, PartialEq)] pub struct Path { segments: Vec, - skia_path: skia::Path, + pub skia_path: skia::Path, open: bool, } @@ -217,8 +220,8 @@ impl Path { Path::new(segments) } - pub fn to_skia_path(&self) -> skia::Path { - self.skia_path.snapshot() + pub fn to_skia_path(&self) -> &skia::Path { + &self.skia_path } pub fn contains(&self, p: skia::Point) -> bool {