From 4e98dfb99f88da1d50df984c4ccc2b5a183e5c86 Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Fri, 8 May 2026 11:10:14 +0200 Subject: [PATCH] :recycle: Refactor GpuState and RenderState * :recycle: Refactor GpuState * :recycle: Refactor RenderState * :wrench: Tweak some _build_env options --- render-wasm/_build_env | 2 + render-wasm/src/main.rs | 210 ++++++++++++++------------- render-wasm/src/render.rs | 31 ++-- render-wasm/src/render/debug.rs | 24 ++- render-wasm/src/render/gpu_state.rs | 2 +- render-wasm/src/render/images.rs | 7 +- render-wasm/src/render/surfaces.rs | 24 +-- render-wasm/src/shapes/text_paths.rs | 27 ++-- render-wasm/src/state.rs | 88 +++++------ render-wasm/src/utils.rs | 5 +- render-wasm/src/wasm/fills/image.rs | 9 +- render-wasm/src/wasm/fonts.rs | 35 ++--- render-wasm/src/wasm/layouts/grid.rs | 13 +- render-wasm/src/wasm/text_editor.rs | 24 +-- 14 files changed, 232 insertions(+), 269 deletions(-) diff --git a/render-wasm/_build_env b/render-wasm/_build_env index 8fb4ede1be..b54af40d34 100644 --- a/render-wasm/_build_env +++ b/render-wasm/_build_env @@ -42,6 +42,8 @@ export EMCC_CFLAGS="--no-entry \ -sEXPORTED_RUNTIME_METHODS=GL,UTF8ToString,stringToUTF8,HEAPU8,HEAP32,HEAPU32,HEAPF32 \ -sENVIRONMENT=web \ -sMODULARIZE=1 \ + -sDISABLE_EXCEPTION_CATCHING=1 \ + -sFILESYSTEM=0 \ -sEXPORT_ES6=1"; export EM_CACHE="/tmp/emsdk_cache"; diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 380e0e1f47..bd6e26d1fa 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -22,6 +22,7 @@ use crate::state::TextEditorState; use macros::wasm_error; use math::{Bounds, Matrix}; use mem::SerializableResult; +use render::{gpu_state::GpuState, RenderState}; use shapes::{StructureEntry, StructureEntryType, TransformEntry}; use skia_safe as skia; use state::State; @@ -39,6 +40,28 @@ pub fn get_text_editor_state() -> &'static mut TextEditorState { } } +/// GPU State. +static mut GPU_STATE: *mut GpuState = std::ptr::null_mut(); + +#[inline(always)] +pub(crate) fn get_gpu_state() -> &'static mut GpuState { + unsafe { + debug_assert!(!GPU_STATE.is_null(), "GPU State is null"); + &mut *GPU_STATE + } +} + +/// Render State. +static mut RENDER_STATE: *mut RenderState = std::ptr::null_mut(); + +#[inline(always)] +pub(crate) fn get_render_state() -> &'static mut RenderState { + unsafe { + debug_assert!(!RENDER_STATE.is_null(), "Render State is null"); + &mut *RENDER_STATE + } +} + // FIXME: These with_state* macros should be using our CriticalError instead of expect. // But to do that, we need to not use them at domain-level (i.e. in business logic), just // in the context of the wasm call. @@ -111,11 +134,30 @@ macro_rules! with_state_mut_current_shape { }; } +/// Initializes GPU. +fn gpu_init() { + unsafe { + let gpu_state = GpuState::try_new().expect("Cannot initialize GPU State"); + GPU_STATE = Box::into_raw(Box::new(gpu_state)); + } +} + +/// Initializes RenderState. +fn render_init(width: i32, height: i32) { + unsafe { + let render_state = + RenderState::try_new(width, height).expect("Cannot intialize RenderState"); + RENDER_STATE = Box::into_raw(Box::new(render_state)); + } +} + #[no_mangle] #[wasm_error] pub extern "C" fn init(width: i32, height: i32) -> Result<()> { - let state_box = Box::new(State::try_new(width, height)?); + gpu_init(); + render_init(width, height); unsafe { + let state_box = Box::new(State::new()); STATE = Some(state_box); TEXT_EDITOR_STATE = Box::into_raw(Box::new(TextEditorState::new())); } @@ -134,12 +176,10 @@ pub extern "C" fn set_browser(browser: u8) -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn clean_up() -> Result<()> { - with_state_mut!(state, { - // Cancel the current animation frame if it exists so - // it won't try to render without context - let render_state = state.render_state_mut(); - render_state.cancel_animation_frame(); - }); + // Cancel the current animation frame if it exists so + // it won't try to render without context + let render_state = get_render_state(); + render_state.cancel_animation_frame(); unsafe { STATE = None } mem::free_bytes()?; Ok(()) @@ -148,11 +188,9 @@ pub extern "C" fn clean_up() -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn set_render_options(debug: u32, dpr: f32) -> Result<()> { - with_state_mut!(state, { - let render_state = state.render_state_mut(); - render_state.set_debug_flags(debug); - render_state.set_dpr(dpr)?; - }); + let render_state = get_render_state(); + render_state.set_debug_flags(debug); + render_state.set_dpr(dpr)?; Ok(()) } @@ -161,61 +199,48 @@ pub extern "C" fn set_render_options(debug: u32, dpr: f32) -> Result<()> { 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); - }); + let render_state = get_render_state(); + 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); - }); + let render_state = get_render_state(); + 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); - }); + let render_state = get_render_state(); + 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); - }); + let render_state = get_render_state(); + render_state.set_blur_downscale_threshold(blur_downscale_threshold); Ok(()) } #[no_mangle] #[wasm_error] pub extern "C" fn set_antialias_threshold(threshold: f32) -> Result<()> { - with_state_mut!(state, { - state.render_state_mut().set_antialias_threshold(threshold); - }); + get_render_state().set_antialias_threshold(threshold); Ok(()) } #[no_mangle] #[wasm_error] pub extern "C" fn set_max_atlas_texture_size(max_px: i32) -> Result<()> { - with_state_mut!(state, { - state - .render_state_mut() - .surfaces - .set_max_atlas_texture_size(max_px); - }); + get_render_state() + .surfaces + .set_max_atlas_texture_size(max_px); Ok(()) } @@ -241,7 +266,7 @@ pub extern "C" fn render(timestamp: i32) -> Result<()> { // interactive_transform; we do it once here, with the current // modifier set, so the cost is paid once per rAF rather than // once per pointer move. - if state.render_state.options.is_interactive_transform() { + if get_render_state().options.is_interactive_transform() { let ids = state.shapes.modifier_ids(); if !ids.is_empty() { state.rebuild_modifier_tiles(ids)?; @@ -311,9 +336,7 @@ pub extern "C" fn render_from_cache(_: i32) -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn set_preview_mode(enabled: bool) -> Result<()> { - with_state_mut!(state, { - state.render_state.set_preview_mode(enabled); - }); + get_render_state().set_preview_mode(enabled); Ok(()) } @@ -356,9 +379,7 @@ pub extern "C" fn end_loading() -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn render_loading_overlay() -> Result<()> { - with_state_mut!(state, { - state.render_state.render_loading_overlay(); - }); + get_render_state().render_loading_overlay(); Ok(()) } @@ -375,30 +396,24 @@ pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn reset_canvas() -> Result<()> { - with_state_mut!(state, { - state.render_state_mut().reset_canvas(); - }); + get_render_state().reset_canvas(); Ok(()) } #[no_mangle] #[wasm_error] pub extern "C" fn resize_viewbox(width: i32, height: i32) -> Result<()> { - with_state_mut!(state, { - state.resize(width, height)?; - }); + get_render_state().resize(width, height)?; Ok(()) } #[no_mangle] #[wasm_error] pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) -> Result<()> { - with_state_mut!(state, { - performance::begin_measure!("set_view"); - let render_state = state.render_state_mut(); - render_state.set_view(zoom, x, y); - performance::end_measure!("set_view"); - }); + performance::begin_measure!("set_view"); + let render_state = get_render_state(); + render_state.set_view(zoom, x, y); + performance::end_measure!("set_view"); Ok(()) } @@ -408,15 +423,13 @@ static mut VIEW_INTERACTION_START: i32 = 0; #[no_mangle] #[wasm_error] pub extern "C" fn set_view_start() -> Result<()> { - with_state_mut!(state, { - #[cfg(feature = "profile-macros")] - unsafe { - VIEW_INTERACTION_START = performance::get_time(); - } - performance::begin_measure!("set_view_start"); - state.render_state.options.set_fast_mode(true); - performance::end_measure!("set_view_start"); - }); + #[cfg(feature = "profile-macros")] + unsafe { + VIEW_INTERACTION_START = performance::get_time(); + } + performance::begin_measure!("set_view_start"); + get_render_state().options.set_fast_mode(true); + performance::end_measure!("set_view_start"); Ok(()) } @@ -430,32 +443,32 @@ pub extern "C" fn set_view_start() -> Result<()> { pub extern "C" fn set_view_end() -> Result<()> { with_state_mut!(state, { performance::begin_measure!("set_view_end"); - state.render_state.options.set_fast_mode(false); - state.render_state.cancel_animation_frame(); + let render_state = get_render_state(); + render_state.options.set_fast_mode(false); + render_state.cancel_animation_frame(); - let scale = state.render_state.get_scale(); - state - .render_state + let scale = render_state.get_scale(); + render_state .tile_viewbox - .update(state.render_state.viewbox, scale); + .update(render_state.viewbox, scale); - if state.render_state.options.is_profile_rebuild_tiles() { + if render_state.options.is_profile_rebuild_tiles() { state.rebuild_tiles(); - } else if state.render_state.zoom_changed() { + } else if render_state.zoom_changed() { // Zoom changed: tile sizes differ so all cached tile // textures are invalid (wrong scale). Rebuild the tile // index and clear the tile texture cache, but *preserve* // the cache canvas so render_from_cache can show a scaled // preview of the old content while new tiles render. - state.render_state.rebuild_tile_index(&state.shapes); - state.render_state.surfaces.invalidate_tile_cache(); + render_state.rebuild_tile_index(&state.shapes); + render_state.surfaces.invalidate_tile_cache(); } else { // Pure pan at the same zoom level: tile contents have not // changed — only the viewport position moved. Update the // tile index (which tiles are in the interest area) but // keep cached tile textures so the render can blit them // instead of re-drawing every visible tile from scratch. - state.render_state.rebuild_tile_index(&state.shapes); + render_state.rebuild_tile_index(&state.shapes); } performance::end_measure!("set_view_end"); }); @@ -470,12 +483,11 @@ pub extern "C" fn set_view_end() -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn set_modifiers_start() -> Result<()> { - with_state_mut!(state, { - performance::begin_measure!("set_modifiers_start"); - state.render_state.options.set_fast_mode(true); - state.render_state.options.set_interactive_transform(true); - performance::end_measure!("set_modifiers_start"); - }); + performance::begin_measure!("set_modifiers_start"); + let render_state = get_render_state(); + render_state.options.set_fast_mode(true); + render_state.options.set_interactive_transform(true); + performance::end_measure!("set_modifiers_start"); Ok(()) } @@ -486,13 +498,12 @@ pub extern "C" fn set_modifiers_start() -> Result<()> { #[no_mangle] #[wasm_error] pub extern "C" fn set_modifiers_end() -> Result<()> { - with_state_mut!(state, { - performance::begin_measure!("set_modifiers_end"); - state.render_state.options.set_fast_mode(false); - state.render_state.options.set_interactive_transform(false); - state.render_state.cancel_animation_frame(); - performance::end_measure!("set_modifiers_end"); - }); + performance::begin_measure!("set_modifiers_end"); + let render_state = get_render_state(); + render_state.options.set_fast_mode(false); + render_state.options.set_interactive_transform(false); + render_state.cancel_animation_frame(); + performance::end_measure!("set_modifiers_end"); Ok(()) } @@ -808,11 +819,9 @@ pub extern "C" fn is_image_cached( d: u32, is_thumbnail: bool, ) -> Result { - with_state_mut!(state, { - let id = uuid_from_u32_quartet(a, b, c, d); - let result = state.render_state().has_image(&id, is_thumbnail); - Ok(result) - }) + let id = uuid_from_u32_quartet(a, b, c, d); + let result = get_render_state().has_image(&id, is_thumbnail); + Ok(result) } #[no_mangle] @@ -961,15 +970,14 @@ pub extern "C" fn set_structure_modifiers() -> Result<()> { #[wasm_error] pub extern "C" fn clean_modifiers() -> Result<()> { with_state_mut!(state, { + let render_state = get_render_state(); let prev_modifier_ids = state.shapes.clean_all(); // Skip the tile-cache cleanup during interactive transform: the // per-rAF `rebuild_modifier_tiles` in `render()` already evicts // the same tiles for the active modifier set, so the eviction // here is redundant and doubles the per-emission cost. - if !prev_modifier_ids.is_empty() && !state.render_state.options.is_interactive_transform() { - state - .render_state - .update_tiles_shapes(&prev_modifier_ids, &mut state.shapes)?; + if !prev_modifier_ids.is_empty() && !render_state.options.is_interactive_transform() { + render_state.update_tiles_shapes(&prev_modifier_ids, &mut state.shapes)?; } }); Ok(()) @@ -995,7 +1003,7 @@ pub extern "C" fn set_modifiers() -> Result<()> { with_state_mut!(state, { state.set_modifiers(modifiers); // TO CHECK - if !state.render_state.options.is_interactive_transform() { + if !get_render_state().options.is_interactive_transform() { state.rebuild_modifier_tiles(ids)?; } }); @@ -1061,9 +1069,7 @@ pub extern "C" fn render_shape_pixels( #[no_mangle] pub extern "C" fn render_stats() { - with_state!(state, { - state.render_state.print_stats(); - }) + get_render_state().print_stats(); } fn main() { diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 97564b7ac8..3df32255b5 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -2,7 +2,7 @@ mod debug; mod fills; pub mod filters; mod fonts; -mod gpu_state; +pub mod gpu_state; pub mod grid_layout; mod images; mod options; @@ -17,13 +17,10 @@ use skia_safe::{self as skia, Matrix, RRect, Rect}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; -use gpu_state::GpuState; - use options::RenderOptions; pub use surfaces::{SurfaceId, Surfaces}; use crate::error::{Error, Result}; -use crate::performance; use crate::shapes::{ all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, StrokeKind, TextContent, Type, @@ -33,6 +30,7 @@ use crate::tiles::{self, PendingTiles, TileRect}; use crate::uuid::Uuid; use crate::view::Viewbox; use crate::wapi; +use crate::{get_gpu_state, performance}; pub use fonts::*; pub use images::*; @@ -327,7 +325,6 @@ impl RenderStats { } pub(crate) struct RenderState { - gpu_state: GpuState, pub options: RenderOptions, stats: RenderStats, pub surfaces: Surfaces, @@ -492,13 +489,11 @@ impl RenderState { pub fn try_new(width: i32, height: i32) -> Result { // This needs to be done once per WebGL context. - let mut gpu_state = GpuState::try_new()?; let sampling_options = skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest); let fonts = FontStore::try_new()?; let surfaces = Surfaces::try_new( - &mut gpu_state, (width, height), sampling_options, tiles::get_tile_dimensions(), @@ -511,15 +506,14 @@ impl RenderState { let tiles = tiles::TileHashMap::new(); let options = RenderOptions::default(); - Ok(RenderState { - gpu_state: gpu_state.clone(), + Ok(Self { options, stats: RenderStats::new(), surfaces, fonts, viewbox, cached_viewbox: Viewbox::new(0., 0.), - images: ImageStore::new(gpu_state.context.clone()), + images: ImageStore::new(), background_color: skia::Color::TRANSPARENT, render_request_id: None, render_in_progress: false, @@ -801,8 +795,7 @@ impl RenderState { pub fn resize(&mut self, width: i32, height: i32) -> Result<()> { let dpr_width = (width as f32 * self.options.dpr).floor() as i32; let dpr_height = (height as f32 * self.options.dpr).floor() as i32; - self.surfaces - .resize(&mut self.gpu_state, dpr_width, dpr_height)?; + self.surfaces.resize(dpr_width, dpr_height)?; self.viewbox.set_wh(width as f32, height as f32); self.tile_viewbox.update(self.viewbox, self.get_scale()); @@ -810,8 +803,7 @@ impl RenderState { } pub fn flush_and_submit(&mut self) { - self.surfaces - .flush_and_submit(&mut self.gpu_state, SurfaceId::Target); + self.surfaces.flush_and_submit(SurfaceId::Target); } pub fn reset_canvas(&mut self) { @@ -890,7 +882,6 @@ impl RenderState { .as_ref() .ok_or(Error::CriticalError("Current tile not found".to_string()))?; self.surfaces.cache_current_tile_texture( - &mut self.gpu_state, &self.tile_viewbox, ¤t_tile, &tile_rect, @@ -2203,13 +2194,12 @@ impl RenderState { // Clear export context so get_scale() returns to workspace zoom. self.export_context = None; - self.surfaces - .flush_and_submit(&mut self.gpu_state, target_surface); + self.surfaces.flush_and_submit(target_surface); let image = self.surfaces.snapshot(target_surface); let data = image .encode( - &mut self.gpu_state.context, + Some(&mut get_gpu_state().context), skia::EncodedImageFormat::PNG, 100, ) @@ -3590,8 +3580,7 @@ impl RenderState { } pub fn remove_cached_tile(&mut self, tile: tiles::Tile) { - self.surfaces - .remove_cached_tile_surface(&mut self.gpu_state, tile); + self.surfaces.remove_cached_tile_surface(tile); } /// Rebuild the tile index (shape→tile mapping) for all top-level shapes. @@ -3790,6 +3779,6 @@ impl RenderState { impl Drop for RenderState { fn drop(&mut self) { - self.gpu_state.context.free_gpu_resources(); + get_gpu_state().context.free_gpu_resources(); } } diff --git a/render-wasm/src/render/debug.rs b/render-wasm/src/render/debug.rs index f374e32af3..266a9043de 100644 --- a/render-wasm/src/render/debug.rs +++ b/render-wasm/src/render/debug.rs @@ -1,7 +1,11 @@ use super::{tiles, RenderState, SurfaceId}; -use crate::with_state_mut; -use crate::STATE; + +#[cfg(target_arch = "wasm32")] use macros::wasm_error; + +#[cfg(target_arch = "wasm32")] +use crate::get_render_state; + use skia_safe::{self as skia, Rect}; #[cfg(target_arch = "wasm32")] @@ -227,9 +231,7 @@ pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId, #[wasm_error] #[cfg(target_arch = "wasm32")] pub extern "C" fn debug_cache_console() -> Result<()> { - with_state_mut!(state, { - console_debug_surface(state.render_state_mut(), SurfaceId::Cache); - }); + console_debug_surface(get_render_state(), SurfaceId::Cache); Ok(()) } @@ -237,9 +239,7 @@ pub extern "C" fn debug_cache_console() -> Result<()> { #[wasm_error] #[cfg(target_arch = "wasm32")] pub extern "C" fn debug_cache_base64() -> Result<()> { - with_state_mut!(state, { - console_debug_surface_base64(state.render_state_mut(), SurfaceId::Cache); - }); + console_debug_surface_base64(get_render_state(), SurfaceId::Cache); Ok(()) } @@ -247,9 +247,7 @@ pub extern "C" fn debug_cache_base64() -> Result<()> { #[wasm_error] #[cfg(target_arch = "wasm32")] pub extern "C" fn debug_atlas_console() -> Result<()> { - with_state_mut!(state, { - console_debug_surface(state.render_state_mut(), SurfaceId::Atlas); - }); + console_debug_surface(get_render_state(), SurfaceId::Atlas); Ok(()) } @@ -257,8 +255,6 @@ pub extern "C" fn debug_atlas_console() -> Result<()> { #[wasm_error] #[cfg(target_arch = "wasm32")] pub extern "C" fn debug_atlas_base64() -> Result<()> { - with_state_mut!(state, { - console_debug_surface_base64(state.render_state_mut(), SurfaceId::Atlas); - }); + console_debug_surface_base64(get_render_state(), SurfaceId::Atlas); Ok(()) } diff --git a/render-wasm/src/render/gpu_state.rs b/render-wasm/src/render/gpu_state.rs index 77f8816435..47acd7a26b 100644 --- a/render-wasm/src/render/gpu_state.rs +++ b/render-wasm/src/render/gpu_state.rs @@ -41,7 +41,7 @@ impl GpuState { } }; - Ok(GpuState { + Ok(Self { context, framebuffer_info, }) diff --git a/render-wasm/src/render/images.rs b/render-wasm/src/render/images.rs index e1c66b2a51..b7a25388dc 100644 --- a/render-wasm/src/render/images.rs +++ b/render-wasm/src/render/images.rs @@ -3,6 +3,7 @@ use crate::shapes::ImageFill; use crate::uuid::Uuid; use crate::error::Result; +use crate::get_gpu_state; use skia_safe::gpu::{surfaces, Budgeted, DirectContext}; use skia_safe::{self as skia, Codec, ISize}; use std::collections::HashMap; @@ -143,10 +144,12 @@ fn decode_image(context: &mut Box, raw_data: &[u8]) -> Option Self { + pub fn new() -> Self { + let gpu_state = get_gpu_state(); + let context = &gpu_state.context; Self { images: HashMap::with_capacity(2048), - context: Box::new(context), + context: Box::new(context.clone()), } } diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 688428f5eb..f4a3456e9b 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -1,7 +1,7 @@ use crate::error::{Error, Result}; -use crate::performance; use crate::shapes::Shape; use crate::view::Viewbox; +use crate::{get_gpu_state, performance}; use skia_safe::{self as skia, IRect, Paint, RRect}; @@ -101,11 +101,12 @@ pub struct Surfaces { #[allow(dead_code)] impl Surfaces { pub fn try_new( - gpu_state: &mut GpuState, (width, height): (i32, i32), sampling_options: skia::SamplingOptions, tile_dims: skia::ISize, ) -> Result { + let gpu_state = get_gpu_state(); + let extra_tile_dims = skia::ISize::new( tile_dims.width * TILE_SIZE_MULTIPLIER, tile_dims.height * TILE_SIZE_MULTIPLIER, @@ -140,7 +141,7 @@ impl Surfaces { atlas.canvas().clear(skia::Color::TRANSPARENT); let tiles = TileTextureCache::new(); - Ok(Surfaces { + Ok(Self { target, filter, cache, @@ -445,12 +446,9 @@ impl Surfaces { self.margins } - pub fn resize( - &mut self, - gpu_state: &mut GpuState, - new_width: i32, - new_height: i32, - ) -> Result<()> { + pub fn resize(&mut self, new_width: i32, new_height: i32) -> Result<()> { + let gpu_state = get_gpu_state(); + self.reset_from_target(gpu_state.create_target_surface(new_width, new_height)?)?; Ok(()) } @@ -547,7 +545,8 @@ impl Surfaces { self.dirty_surfaces = 0; } - pub fn flush_and_submit(&mut self, gpu_state: &mut GpuState, id: SurfaceId) { + pub fn flush_and_submit(&mut self, id: SurfaceId) { + let gpu_state = get_gpu_state(); let surface = self.get_mut(id); gpu_state.context.flush_and_submit_surface(surface, None); } @@ -946,13 +945,13 @@ impl Surfaces { pub fn cache_current_tile_texture( &mut self, - gpu_state: &mut GpuState, tile_viewbox: &TileViewbox, tile: &Tile, tile_rect: &skia::Rect, skip_cache_surface: bool, tile_doc_rect: skia::Rect, ) { + let gpu_state = get_gpu_state(); let rect = IRect::from_xywh( self.margins.width, self.margins.height, @@ -985,7 +984,8 @@ impl Surfaces { self.tiles.has(tile) } - pub fn remove_cached_tile_surface(&mut self, gpu_state: &mut GpuState, tile: Tile) { + pub fn remove_cached_tile_surface(&mut self, tile: Tile) { + let gpu_state = get_gpu_state(); // Mark tile as invalid // Old content stays visible until new tile overwrites it atomically, // preventing flickering during tile re-renders. diff --git a/render-wasm/src/shapes/text_paths.rs b/render-wasm/src/shapes/text_paths.rs index 238207152b..38cf30226f 100644 --- a/render-wasm/src/shapes/text_paths.rs +++ b/render-wasm/src/shapes/text_paths.rs @@ -1,11 +1,10 @@ +use crate::get_render_state; use crate::shapes::text::TextContent; use skia_safe::{ self as skia, textlayout::Paragraph as SkiaParagraph, FontMetrics, Point, Rect, TextBlob, }; use std::ops::Deref; -use crate::{with_state_mut, STATE}; - pub struct TextPaths(TextContent); // Note: This class is not being currently used. @@ -173,20 +172,18 @@ impl TextPaths { blob_offset_x: f32, blob_offset_y: f32, ) -> Option<(skia::Path, skia::Rect)> { - with_state_mut!(state, { - let utf16_text = span_text.encode_utf16().collect::>(); - let text = unsafe { skia_safe::as_utf16_unchecked(&utf16_text) }; - let emoji_font = state.render_state.fonts().get_emoji_font(font.size()); - let use_font = emoji_font.as_ref().unwrap_or(font); + let utf16_text = span_text.encode_utf16().collect::>(); + let text = unsafe { skia_safe::as_utf16_unchecked(&utf16_text) }; + let emoji_font = get_render_state().fonts().get_emoji_font(font.size()); + let use_font = emoji_font.as_ref().unwrap_or(font); - if let Some(mut text_blob) = TextBlob::from_text(text, use_font) { - let path = SkiaParagraph::get_path(&mut text_blob); - let d = Point::new(blob_offset_x, blob_offset_y); - let offset_path = path.with_offset(d); - let bounds = text_blob.bounds(); - return Some((offset_path, *bounds)); - } - }); + if let Some(mut text_blob) = TextBlob::from_text(text, use_font) { + let path = SkiaParagraph::get_path(&mut text_blob); + let d = Point::new(blob_offset_x, blob_offset_y); + let offset_path = path.with_offset(d); + let bounds = text_blob.bounds(); + return Some((offset_path, *bounds)); + } None } } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 1ff3e2889b..9591294abf 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -7,10 +7,9 @@ pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef}; pub use text_editor::*; use crate::error::{Error, Result}; -use crate::render::RenderState; -use crate::shapes::{modifiers::grid_layout::grid_cell_data, Shape}; -use crate::tiles; +use crate::shapes::{grid_layout::grid_cell_data, Shape}; use crate::uuid::Uuid; +use crate::{get_render_state, tiles}; /// This struct holds the state of the Rust application between JS calls. /// @@ -18,7 +17,6 @@ use crate::uuid::Uuid; /// Note that rust-skia data structures are not thread safe, so a state /// must not be shared between different Web Workers. pub(crate) struct State { - pub render_state: RenderState, pub current_id: Option, pub current_browser: u8, pub shapes: ShapesPool, @@ -28,16 +26,14 @@ pub(crate) struct State { } impl State { - pub fn try_new(width: i32, height: i32) -> Result { - Ok(State { - render_state: RenderState::try_new(width, height)?, - // text_editor_state: TextEditorState::new(), + pub fn new() -> Self { + Self { current_id: None, current_browser: 0, shapes: ShapesPool::new(), saved_shapes: None, loading: false, - }) + } } // Creates a new temporary shapes pool. @@ -64,30 +60,16 @@ impl State { Ok(self) } - pub fn resize(&mut self, width: i32, height: i32) -> Result<()> { - self.render_state.resize(width, height) - } - - pub fn render_state_mut(&mut self) -> &mut RenderState { - &mut self.render_state - } - - pub fn render_state(&self) -> &RenderState { - &self.render_state - } - pub fn render_from_cache(&mut self) { - self.render_state.render_from_cache(&self.shapes); + get_render_state().render_from_cache(&self.shapes); } pub fn render_sync(&mut self, timestamp: i32) -> Result<()> { - self.render_state - .start_render_loop(None, &self.shapes, timestamp, true) + get_render_state().start_render_loop(None, &self.shapes, timestamp, true) } pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<()> { - self.render_state - .start_render_loop(Some(id), &self.shapes, timestamp, true) + get_render_state().start_render_loop(Some(id), &self.shapes, timestamp, true) } pub fn render_shape_pixels( @@ -96,36 +78,34 @@ impl State { scale: f32, timestamp: i32, ) -> Result<(Vec, i32, i32)> { - self.render_state - .render_shape_pixels(id, &self.shapes, scale, timestamp) + get_render_state().render_shape_pixels(id, &self.shapes, scale, timestamp) } pub fn start_render_loop(&mut self, timestamp: i32) -> Result<()> { + let render_state = get_render_state(); // If zoom changed (e.g. interrupted zoom render followed by pan), the // tile index may be stale for the new viewport position. Rebuild the // index so shapes are mapped to the correct tiles. We use // rebuild_tile_index (NOT rebuild_tiles_shallow) to preserve the tile // texture cache — otherwise cached tiles with shadows/blur would be // cleared and re-rendered in fast mode without effects. - if self.render_state.zoom_changed() { - self.render_state.rebuild_tile_index(&self.shapes); + if render_state.zoom_changed() { + render_state.rebuild_tile_index(&self.shapes); } - self.render_state - .start_render_loop(None, &self.shapes, timestamp, false) + render_state.start_render_loop(None, &self.shapes, timestamp, false) } pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<()> { - self.render_state - .process_animation_frame(None, &self.shapes, timestamp) + get_render_state().process_animation_frame(None, &self.shapes, timestamp) } pub fn clear_focus_mode(&mut self) { - self.render_state.clear_focus_mode(); + get_render_state().clear_focus_mode(); } pub fn set_focus_mode(&mut self, shapes: Vec) { - self.render_state.set_focus_mode(shapes); + get_render_state().set_focus_mode(shapes); } pub fn init_shapes_pool(&mut self, capacity: usize) { @@ -140,6 +120,8 @@ impl State { } pub fn delete_shape_children(&mut self, parent_id: Uuid, id: Uuid) { + let render_state = get_render_state(); + // We don't really do a self.shapes.remove so that redo/undo keep working let Some(shape) = self.shapes.get(&id) else { return; @@ -155,16 +137,15 @@ impl State { // // 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 + let indexed_tiles: Vec = 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); + render_state.remove_cached_tile(tile); + render_state.tiles.remove_shape_at(tile, shape.id); } if let Some(shape_to_delete) = self.shapes.get(&id) { @@ -173,8 +154,8 @@ impl State { if let Some(shape_to_delete) = self.shapes.get_mut(&shape_id) { shape_to_delete.set_deleted(true); } - if self.render_state.show_grid == Some(shape_id) { - self.render_state.show_grid = None; + if render_state.show_grid == Some(shape_id) { + render_state.show_grid = None; } } } @@ -190,7 +171,7 @@ impl State { } pub fn set_background_color(&mut self, color: skia::Color) { - self.render_state.set_background_color(color); + get_render_state().set_background_color(color); } pub fn set_browser(&mut self, browser: u8) { @@ -225,33 +206,32 @@ impl State { } pub fn rebuild_tiles_shallow(&mut self) { - self.render_state.rebuild_tiles_shallow(&self.shapes); + get_render_state().rebuild_tiles_shallow(&self.shapes); } pub fn rebuild_tiles(&mut self) { - self.render_state.rebuild_tiles_from(&self.shapes, None); + get_render_state().rebuild_tiles_from(&self.shapes, None); } pub fn rebuild_tiles_from(&mut self, base_id: Option<&Uuid>) { - self.render_state.rebuild_tiles_from(&self.shapes, base_id); + get_render_state().rebuild_tiles_from(&self.shapes, base_id); } pub fn rebuild_touched_tiles(&mut self) { - self.render_state.rebuild_touched_tiles(&self.shapes); + get_render_state().rebuild_touched_tiles(&self.shapes); } pub fn render_preview(&mut self, timestamp: i32) { - let _ = self.render_state.render_preview(&self.shapes, timestamp); + let _ = get_render_state().render_preview(&self.shapes, timestamp); } pub fn rebuild_modifier_tiles(&mut self, ids: Vec) -> Result<()> { // Index-based storage is safe - self.render_state - .rebuild_modifier_tiles(&mut self.shapes, ids) + get_render_state().rebuild_modifier_tiles(&mut self.shapes, ids) } pub fn font_collection(&self) -> &FontCollection { - self.render_state.fonts().font_collection() + get_render_state().fonts().font_collection() } pub fn get_grid_coords(&self, pos_x: f32, pos_y: f32) -> Option<(i32, i32)> { @@ -284,16 +264,18 @@ impl State { } pub fn touch_current(&mut self) { + let render_state = get_render_state(); if !self.loading { if let Some(current_id) = self.current_id { - self.render_state.mark_touched(current_id); + render_state.mark_touched(current_id); } } } pub fn touch_shape(&mut self, id: Uuid) { + let render_state = get_render_state(); if !self.loading { - self.render_state.mark_touched(id); + render_state.mark_touched(id); } } } diff --git a/render-wasm/src/utils.rs b/render-wasm/src/utils.rs index 63a031d761..5d5c9a4c10 100644 --- a/render-wasm/src/utils.rs +++ b/render-wasm/src/utils.rs @@ -1,3 +1,4 @@ +use crate::get_render_state; use crate::skia::textlayout::FontCollection; use crate::skia::Image; use crate::uuid::Uuid; @@ -25,12 +26,12 @@ pub fn uuid_from_u32(id: [u32; 4]) -> Uuid { } pub fn get_image(image_id: &Uuid) -> Option<&Image> { - with_state_mut!(state, { state.render_state_mut().images.get(image_id) }) + get_render_state().images.get(image_id) } // FIXME: move to a different place ? pub fn get_fallback_fonts() -> &'static HashSet { - with_state_mut!(state, { state.render_state().fonts().get_fallback() }) + get_render_state().fonts().get_fallback() } pub fn get_font_collection() -> &'static FontCollection { diff --git a/render-wasm/src/wasm/fills/image.rs b/render-wasm/src/wasm/fills/image.rs index 9200861f8a..5d4db56a71 100644 --- a/render-wasm/src/wasm/fills/image.rs +++ b/render-wasm/src/wasm/fills/image.rs @@ -1,4 +1,5 @@ use crate::error::{Error, Result}; +use crate::get_render_state; use crate::mem; use crate::shapes::Fill; use crate::state::State; @@ -106,11 +107,7 @@ pub extern "C" fn store_image() -> Result<()> { let image_bytes = &bytes[IMAGE_HEADER_SIZE..]; with_state_mut!(state, { - if let Err(msg) = - state - .render_state_mut() - .add_image(ids.image_id, is_thumbnail, image_bytes) - { + if let Err(msg) = get_render_state().add_image(ids.image_id, is_thumbnail, image_bytes) { eprintln!("{}", msg); } touch_shapes_with_image(state, ids.image_id); @@ -180,7 +177,7 @@ pub extern "C" fn store_image_from_texture() -> Result<()> { ); with_state_mut!(state, { - if let Err(msg) = state.render_state_mut().add_image_from_gl_texture( + if let Err(msg) = get_render_state().add_image_from_gl_texture( ids.image_id, is_thumbnail, texture_id, diff --git a/render-wasm/src/wasm/fonts.rs b/render-wasm/src/wasm/fonts.rs index f4723e20c3..21c4e6a797 100644 --- a/render-wasm/src/wasm/fonts.rs +++ b/render-wasm/src/wasm/fonts.rs @@ -1,10 +1,9 @@ use macros::{wasm_error, ToJs}; +use crate::get_render_state; use crate::mem; use crate::shapes::{FontFamily, FontStyle}; use crate::utils::uuid_from_u32_quartet; -use crate::with_state_mut; -use crate::STATE; #[derive(Debug, PartialEq, Clone, Copy, ToJs)] #[repr(u8)] @@ -41,20 +40,16 @@ pub extern "C" fn store_font( is_emoji: bool, is_fallback: bool, ) -> Result<()> { - with_state_mut!(state, { - let id = uuid_from_u32_quartet(a, b, c, d); - let font_bytes = mem::bytes(); - let font_style = RawFontStyle::from(style); + let id = uuid_from_u32_quartet(a, b, c, d); + let font_bytes = mem::bytes(); + let font_style = RawFontStyle::from(style); - let family = FontFamily::new(id, weight, font_style.into()); - let _ = - state - .render_state_mut() - .fonts_mut() - .add(family, &font_bytes, is_emoji, is_fallback); + let family = FontFamily::new(id, weight, font_style.into()); + let _ = get_render_state() + .fonts_mut() + .add(family, &font_bytes, is_emoji, is_fallback); - mem::free_bytes()?; - }); + mem::free_bytes()?; Ok(()) } @@ -68,12 +63,10 @@ pub extern "C" fn is_font_uploaded( style: u8, is_emoji: bool, ) -> bool { - with_state_mut!(state, { - let id = uuid_from_u32_quartet(a, b, c, d); - let font_style = RawFontStyle::from(style); - let family = FontFamily::new(id, weight, font_style.into()); - let res = state.render_state().fonts().has_family(&family, is_emoji); + let id = uuid_from_u32_quartet(a, b, c, d); + let font_style = RawFontStyle::from(style); + let family = FontFamily::new(id, weight, font_style.into()); + let res = get_render_state().fonts().has_family(&family, is_emoji); - res - }) + res } diff --git a/render-wasm/src/wasm/layouts/grid.rs b/render-wasm/src/wasm/layouts/grid.rs index aac2fd1928..87a7e45f28 100644 --- a/render-wasm/src/wasm/layouts/grid.rs +++ b/render-wasm/src/wasm/layouts/grid.rs @@ -1,9 +1,10 @@ use macros::{wasm_error, ToJs}; +use crate::get_render_state; use crate::mem; use crate::shapes::{GridCell, GridDirection, GridTrack, GridTrackType}; use crate::uuid::Uuid; -use crate::{uuid_from_u32_quartet, with_current_shape_mut, with_state, with_state_mut, STATE}; +use crate::{uuid_from_u32_quartet, with_current_shape_mut, with_state, STATE}; use super::align; @@ -241,17 +242,13 @@ pub extern "C" fn set_grid_cells() -> Result<()> { #[no_mangle] pub extern "C" fn show_grid(a: u32, b: u32, c: u32, d: u32) { - with_state_mut!(state, { - let id = uuid_from_u32_quartet(a, b, c, d); - state.render_state.show_grid = Some(id); - }); + let id = uuid_from_u32_quartet(a, b, c, d); + get_render_state().show_grid = Some(id); } #[no_mangle] pub extern "C" fn hide_grid() { - with_state_mut!(state, { - state.render_state.show_grid = None; - }); + get_render_state().show_grid = None; } #[no_mangle] diff --git a/render-wasm/src/wasm/text_editor.rs b/render-wasm/src/wasm/text_editor.rs index 08a5b9500b..10853c98e5 100644 --- a/render-wasm/src/wasm/text_editor.rs +++ b/render-wasm/src/wasm/text_editor.rs @@ -2,7 +2,6 @@ use macros::{wasm_error, ToJs}; use crate::get_text_editor_state; use crate::math::{Matrix, Point, Rect}; -use crate::mem; use crate::render::text_editor as text_editor_render; use crate::render::SurfaceId; use crate::shapes::{Shape, TextAlign, TextContent, TextPositionWithAffinity, Type, VerticalAlign}; @@ -13,6 +12,7 @@ use crate::wasm::fills::RawFillData; use crate::wasm::text::{ helpers as text_helpers, RawTextAlign, RawTextDecoration, RawTextDirection, RawTextTransform, }; +use crate::{get_render_state, mem}; use crate::{with_state, with_state_mut, STATE}; use skia_safe::Color; @@ -287,7 +287,7 @@ pub extern "C" fn text_editor_set_cursor_from_point(x: f32, y: f32) { return; } - let view_matrix: Matrix = state.render_state.viewbox.get_matrix(); + let view_matrix: Matrix = get_render_state().viewbox.get_matrix(); let point = Point::new(x, y); let Some(shape_id) = get_text_editor_state().active_shape_id else { return; @@ -368,7 +368,7 @@ pub extern "C" fn text_editor_composition_end() -> Result<()> { get_text_editor_state().push_event(crate::state::TextEditorEvent::ContentChanged); get_text_editor_state().push_event(crate::state::TextEditorEvent::NeedsLayout); - state.render_state.mark_touched(shape_id); + get_render_state().mark_touched(shape_id); get_text_editor_state().composition.end(); }); @@ -420,7 +420,7 @@ pub extern "C" fn text_editor_composition_update() -> Result<()> { get_text_editor_state().push_event(crate::state::TextEditorEvent::ContentChanged); get_text_editor_state().push_event(crate::state::TextEditorEvent::NeedsLayout); - state.render_state.mark_touched(shape_id); + get_render_state().mark_touched(shape_id); }); crate::mem::free_bytes()?; @@ -489,7 +489,7 @@ pub extern "C" fn text_editor_insert_text() -> Result<()> { get_text_editor_state().push_event(TextEditorEvent::ContentChanged); get_text_editor_state().push_event(TextEditorEvent::NeedsLayout); - state.render_state.mark_touched(shape_id); + get_render_state().mark_touched(shape_id); }); crate::mem::free_bytes()?; @@ -516,7 +516,7 @@ pub extern "C" fn text_editor_delete_backward(word_boundary: bool) { }; get_text_editor_state().delete_backward(text_content, word_boundary); - state.render_state.mark_touched(shape_id); + get_render_state().mark_touched(shape_id); }); } @@ -540,7 +540,7 @@ pub extern "C" fn text_editor_delete_forward(word_boundary: bool) { }; get_text_editor_state().delete_forward(text_content, word_boundary); - state.render_state.mark_touched(shape_id); + get_render_state().mark_touched(shape_id); }); } @@ -564,7 +564,7 @@ pub extern "C" fn text_editor_insert_paragraph() { }; get_text_editor_state().insert_paragraph(text_content); - state.render_state.mark_touched(shape_id); + get_render_state().mark_touched(shape_id); }); } @@ -880,16 +880,16 @@ pub extern "C" fn text_editor_render_overlay() { return; }; - let canvas = state.render_state.surfaces.canvas(SurfaceId::Target); - let viewbox = state.render_state.viewbox; + let canvas = get_render_state().surfaces.canvas(SurfaceId::Target); + let viewbox = get_render_state().viewbox; text_editor_render::render_overlay( canvas, &viewbox, - &state.render_state.options, + &get_render_state().options, get_text_editor_state(), shape, ); - state.render_state.flush_and_submit(); + get_render_state().flush_and_submit(); }); }