diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 0be80c8c8a..a68d3d1b24 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -1599,6 +1599,42 @@ (when (and (number? n) (not (js/isNaN n)) (pos? n)) n)))) +(defn- wasm-blur-downscale-threshold-from-route-params + "Reads optional `aa_threshold` query param from the router" + [] + (when-let [raw (let [p (rt/get-params @st/state)] + (:blur_downscale_threshold p))] + (let [n (if (string? raw) (js/parseFloat raw) raw)] + (when (and (number? n) (not (js/isNaN n)) (pos? n)) + n)))) + +(defn- wasm-max-blocking-time-ms-from-route-params + "Reads optional `aa_threshold` query param from the router" + [] + (when-let [raw (let [p (rt/get-params @st/state)] + (:max_blocking_time_ms p))] + (let [n (if (string? raw) (js/parseInt raw 10) raw)] + (when (and (number? n) (not (js/isNaN n)) (pos? n)) + n)))) + +(defn- wasm-node-batch-threshold-from-route-params + "Reads optional `aa_threshold` query param from the router" + [] + (when-let [raw (let [p (rt/get-params @st/state)] + (:node_batch_threshold p))] + (let [n (if (string? raw) (js/parseInt raw 10) raw)] + (when (and (number? n) (not (js/isNaN n)) (pos? n)) + n)))) + +(defn- wasm-viewport-interest-area-threshold-from-route-params + "Reads optional `aa_threshold` query param from the router" + [] + (when-let [raw (let [p (rt/get-params @st/state)] + (:viewport_interest_area_threshold p))] + (let [n (if (string? raw) (js/parseInt raw 10) raw)] + (when (and (number? n) (not (js/isNaN n)) (pos? n)) + n)))) + (defn set-canvas-size [canvas] (let [width (or (.-clientWidth ^js canvas) (.-width ^js canvas)) @@ -1636,6 +1672,14 @@ (h/call wasm/internal-module "_set_render_options" flags dpr) (when-let [t (wasm-aa-threshold-from-route-params)] (h/call wasm/internal-module "_set_antialias_threshold" t)) + (when-let [t (wasm-viewport-interest-area-threshold-from-route-params)] + (h/call wasm/internal-module "_set_viewport_interest_area_threshold" t)) + (when-let [t (wasm-max-blocking-time-ms-from-route-params)] + (h/call wasm/internal-module "_set_max_blocking_time_ms" t)) + (when-let [t (wasm-node-batch-threshold-from-route-params)] + (h/call wasm/internal-module "_set_node_batch_threshold" t)) + (when-let [t (wasm-blur-downscale-threshold-from-route-params)] + (h/call wasm/internal-module "_set_blur_downscale_threshold" t)) (when-let [max-tex (webgl/max-texture-size context)] (h/call wasm/internal-module "_set_max_atlas_texture_size" max-tex)) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 7a030e114d..957955d3e0 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -145,6 +145,48 @@ pub extern "C" fn set_render_options(debug: u32, dpr: f32) -> Result<()> { Ok(()) } +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_viewport_interest_area_threshold( + viewport_interest_area_threshold: i32, +) -> Result<()> { + with_state_mut!(state, { + let render_state = state.render_state_mut(); + render_state.set_viewport_interest_area_threshold(viewport_interest_area_threshold); + }); + Ok(()) +} + +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_max_blocking_time_ms(max_blocking_time_ms: i32) -> Result<()> { + with_state_mut!(state, { + let render_state = state.render_state_mut(); + render_state.set_max_blocking_time_ms(max_blocking_time_ms); + }); + Ok(()) +} + +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_node_batch_threshold(node_batch_threshold: i32) -> Result<()> { + with_state_mut!(state, { + let render_state = state.render_state_mut(); + render_state.set_node_batch_threshold(node_batch_threshold); + }); + Ok(()) +} + +#[no_mangle] +#[wasm_error] +pub extern "C" fn set_blur_downscale_threshold(blur_downscale_threshold: f32) -> Result<()> { + with_state_mut!(state, { + let render_state = state.render_state_mut(); + render_state.set_blur_downscale_threshold(blur_downscale_threshold); + }); + Ok(()) +} + #[no_mangle] #[wasm_error] pub extern "C" fn set_antialias_threshold(threshold: f32) -> Result<()> { diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 8684e0f112..6272d8d9a3 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -37,15 +37,6 @@ use crate::wapi; pub use fonts::*; pub use images::*; -// 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 MAX_BLOCKING_TIME_MS: i32 = 32; -const NODE_BATCH_THRESHOLD: i32 = 3; - -const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0; - type ClipStack = Vec<(Rect, Option, Matrix)>; #[derive(Debug)] @@ -345,9 +336,8 @@ pub(crate) struct RenderState { pub cache_cleared_this_render: bool, } -pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize { +pub fn get_cache_size(viewbox: Viewbox, scale: f32, interest: i32) -> skia::ISize { // First we retrieve the extended area of the viewport that we could render. - let interest = VIEWPORT_INTEREST_AREA_THRESHOLD; let TileRect(isx, isy, iex, iey) = tiles::get_tiles_for_viewbox_with_interest(viewbox, interest, scale); @@ -382,10 +372,11 @@ impl RenderState { let viewbox = Viewbox::new(width as f32, height as f32); let tiles = tiles::TileHashMap::new(); + let options = RenderOptions::default(); Ok(RenderState { gpu_state: gpu_state.clone(), - options: RenderOptions::default(), + options, surfaces, fonts, viewbox, @@ -402,7 +393,7 @@ impl RenderState { tiles, tile_viewbox: tiles::TileViewbox::new_with_interest( viewbox, - VIEWPORT_INTEREST_AREA_THRESHOLD, + options.viewport_interest_area_threshold, 1.0, ), pending_tiles: PendingTiles::new_empty(), @@ -631,6 +622,22 @@ impl RenderState { self.options.set_antialias_threshold(value); } + pub fn set_viewport_interest_area_threshold(&mut self, value: i32) { + self.options.set_viewport_interest_area_threshold(value); + } + + pub fn set_node_batch_threshold(&mut self, value: i32) { + self.options.set_node_batch_threshold(value); + } + + pub fn set_max_blocking_time_ms(&mut self, value: i32) { + self.options.set_max_blocking_time_ms(value); + } + + pub fn set_blur_downscale_threshold(&mut self, value: f32) { + self.options.set_blur_downscale_threshold(value); + } + pub fn set_background_color(&mut self, color: skia::Color) { self.background_color = color; } @@ -1495,7 +1502,7 @@ impl RenderState { // Scale and translate the target according to the cached data let navigate_zoom = self.viewbox.zoom / self.cached_viewbox.zoom; - let interest = VIEWPORT_INTEREST_AREA_THRESHOLD; + let interest = self.options.viewport_interest_area_threshold; let TileRect(start_tile_x, start_tile_y, _, _) = tiles::get_tiles_for_viewbox_with_interest( self.cached_viewbox, @@ -1692,15 +1699,25 @@ impl RenderState { s.canvas().scale((scale, scale)); }); - let viewbox_cache_size = get_cache_size(self.viewbox, scale); - let cached_viewbox_cache_size = get_cache_size(self.cached_viewbox, scale); + let viewbox_cache_size = get_cache_size( + self.viewbox, + scale, + self.options.viewport_interest_area_threshold, + ); + let cached_viewbox_cache_size = get_cache_size( + self.cached_viewbox, + scale, + self.options.viewport_interest_area_threshold, + ); // Only resize cache if the new size is larger than the cached size // This avoids unnecessary surface recreations when the cache size decreases if viewbox_cache_size.width > cached_viewbox_cache_size.width || viewbox_cache_size.height > cached_viewbox_cache_size.height { - self.surfaces - .resize_cache(viewbox_cache_size, VIEWPORT_INTEREST_AREA_THRESHOLD)?; + self.surfaces.resize_cache( + viewbox_cache_size, + self.options.viewport_interest_area_threshold, + )?; } // FIXME - review debug @@ -1909,10 +1926,10 @@ impl RenderState { #[inline] pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool { - if iteration % NODE_BATCH_THRESHOLD != 0 { + if iteration % self.options.node_batch_threshold != 0 { return false; } - if performance::get_time() - timestamp <= MAX_BLOCKING_TIME_MS { + if performance::get_time() - timestamp <= self.options.max_blocking_time_ms { return false; } @@ -2303,9 +2320,10 @@ impl RenderState { // Bounds above were computed from the original sigma so filter surface coverage is correct. // Maximum downscale is 1/BLUR_DOWNSCALE_THRESHOLD (i.e. 8x): beyond that the // filter surface becomes too small and quality degrades noticeably. - const MIN_BLUR_DOWNSCALE: f32 = 1.0 / BLUR_DOWNSCALE_THRESHOLD; - let blur_downscale = if shadow.blur > BLUR_DOWNSCALE_THRESHOLD { - (BLUR_DOWNSCALE_THRESHOLD / shadow.blur).max(MIN_BLUR_DOWNSCALE) + let blur_downscale_threshold: f32 = self.options.blur_downscale_threshold; + let min_blur_downscale: f32 = 1.0 / blur_downscale_threshold; + let blur_downscale = if shadow.blur > blur_downscale_threshold { + (blur_downscale_threshold / shadow.blur).max(min_blur_downscale) } else { 1.0 }; diff --git a/render-wasm/src/render/options.rs b/render-wasm/src/render/options.rs index f6072f964f..40a3125ccd 100644 --- a/render-wasm/src/render/options.rs +++ b/render-wasm/src/render/options.rs @@ -4,6 +4,14 @@ const PROFILE_REBUILD_TILES: u32 = 0x02; const TEXT_EDITOR_V3: u32 = 0x04; 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 MAX_BLOCKING_TIME_MS: i32 = 32; +const NODE_BATCH_THRESHOLD: i32 = 3; +const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0; +const ANTIALIAS_THRESHOLD: f32 = 7.0; #[derive(Debug, Copy, Clone, PartialEq)] pub struct RenderOptions { pub flags: u32, @@ -16,6 +24,10 @@ pub struct RenderOptions { interactive_transform: bool, /// Minimum on-screen size (CSS px at 1:1 zoom) above which vector antialiasing is enabled. pub antialias_threshold: f32, + pub viewport_interest_area_threshold: i32, + pub max_blocking_time_ms: i32, + pub node_batch_threshold: i32, + pub blur_downscale_threshold: f32, } impl Default for RenderOptions { @@ -25,7 +37,11 @@ impl Default for RenderOptions { dpr: None, fast_mode: false, interactive_transform: false, - antialias_threshold: 7.0, + antialias_threshold: ANTIALIAS_THRESHOLD, + viewport_interest_area_threshold: VIEWPORT_INTEREST_AREA_THRESHOLD, + max_blocking_time_ms: MAX_BLOCKING_TIME_MS, + node_batch_threshold: NODE_BATCH_THRESHOLD, + blur_downscale_threshold: BLUR_DOWNSCALE_THRESHOLD, } } } @@ -85,4 +101,28 @@ impl RenderOptions { self.antialias_threshold = value; } } + + pub fn set_blur_downscale_threshold(&mut self, value: f32) { + if value.is_finite() && value > 0.0 { + self.blur_downscale_threshold = value; + } + } + + pub fn set_viewport_interest_area_threshold(&mut self, value: i32) { + if value >= 0 { + self.viewport_interest_area_threshold = value; + } + } + + pub fn set_node_batch_threshold(&mut self, value: i32) { + if value > 0 { + self.node_batch_threshold = value; + } + } + + pub fn set_max_blocking_time_ms(&mut self, value: i32) { + if value > 0 { + self.max_blocking_time_ms = value; + } + } }