From 4274db11e39004d4fb90e5c4cb79265611f48c71 Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Tue, 17 Jun 2025 13:31:19 +0200 Subject: [PATCH] WIP --- frontend/src/app/render_wasm/api.cljs | 3 + render-wasm/src/render.rs | 365 +++++++++++++++----------- 2 files changed, 221 insertions(+), 147 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index a18cd180a6..3b75da397c 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -98,6 +98,9 @@ ;; This should never be called from the outside. (defn- render + ([] + (render (.now js/performance))) + ([timestamp] (render timestamp false)) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 521fa80816..e4e52a05a0 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -37,6 +37,100 @@ const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1; const MAX_BLOCKING_TIME_MS: i32 = 32; const NODE_BATCH_THRESHOLD: i32 = 10; +pub struct PendingNodes { + pub list: Vec, +} + +impl PendingNodes { + pub fn new_empty() -> Self { + Self { list: vec![] } + } + + pub fn is_empty(&self) -> bool { + self.list.is_empty() + } + + pub fn len(&self) -> usize { + self.list.len() + } + + pub fn extend(&mut self, valid_ids: Vec) { + self.list + .extend(valid_ids.into_iter().map(|id| NodeRenderState { + id, + visited_children: false, + clip_bounds: None, + visited_mask: false, + mask: false, + })); + } + + pub fn next(&mut self) -> Option { + self.list.pop() + } + + pub fn prepare(&mut self, tree: &mut HashMap) { + self.list.clear(); + if self.list.capacity() < tree.len() { + self.list.reserve(tree.len() - self.list.capacity()); + } + } + + pub fn add_root(&mut self) { + self.list.push(NodeRenderState { + id: Uuid::nil(), + visited_children: false, + clip_bounds: None, + visited_mask: false, + mask: false, + }); + } + + pub fn add_child( + &mut self, + child_id: &Uuid, + children_clip_bounds: Option<(Rect, Option, Matrix)>, + ) { + self.list.push(NodeRenderState { + id: *child_id, + visited_children: false, + clip_bounds: children_clip_bounds, + visited_mask: false, + mask: false, + }); + } + + pub fn add_children_safeguard(&mut self, element: &Shape, mask: bool) { + // Set the node as visited_children before processing children + self.list.push(NodeRenderState { + id: element.id, + visited_children: true, + clip_bounds: None, + visited_mask: false, + mask, + }); + } + + pub fn add_mask(&mut self, element: &Shape) { + self.list.push(NodeRenderState { + id: element.id, + visited_children: true, + clip_bounds: None, + visited_mask: true, + mask: false, + }); + if let Some(&mask_id) = element.mask_id() { + self.list.push(NodeRenderState { + id: mask_id, + visited_children: false, + clip_bounds: None, + visited_mask: false, + mask: true, + }); + } + } +} + pub struct NodeRenderState { pub id: Uuid, // We use this bool to keep that we've traversed all the children inside this node. @@ -165,7 +259,7 @@ pub(crate) struct RenderState { pub render_in_progress: bool, pub render_is_full: bool, // Stack of nodes pending to be rendered. - pending_nodes: Vec, + pending_nodes: PendingNodes, pub current_tile: Option, pub sampling_options: skia::SamplingOptions, pub render_area: Rect, @@ -236,7 +330,7 @@ impl RenderState { render_request_id: None, render_in_progress: false, render_is_full: false, - pending_nodes: vec![], + pending_nodes: PendingNodes::new_empty(), current_tile: None, sampling_options, render_area: Rect::new_empty(), @@ -565,9 +659,13 @@ impl RenderState { }); } - pub fn update_render_context(&mut self, tile: tiles::Tile) { - self.current_tile = Some(tile); - self.render_area = tiles::get_tile_rect(tile, self.get_scale()); + pub fn get_tiles_of(&mut self, id: Uuid) -> Option<&HashSet> { + self.tiles.get_tiles_of(id) + } + + pub fn update_render_context(&mut self, tile: &tiles::Tile) { + self.current_tile = Some(*tile); + self.render_area = tiles::get_tile_rect(*tile, self.get_scale()); self.surfaces .update_render_context(self.render_area, self.get_scale()); } @@ -663,23 +761,13 @@ impl RenderState { self.pending_tiles.update(&self.tile_viewbox); performance::end_measure!("tile_cache"); - self.pending_nodes.clear(); - if self.pending_nodes.capacity() < tree.len() { - self.pending_nodes - .reserve(tree.len() - self.pending_nodes.capacity()); - } + self.pending_nodes.prepare(tree); // reorder by distance to the center. self.current_tile = None; self.render_in_progress = true; self.render_is_full = full; if self.render_is_full { - self.pending_nodes.push(NodeRenderState { - id: Uuid::nil(), - visited_children: false, - clip_bounds: None, - visited_mask: false, - mask: false, - }); + self.pending_nodes.add_root(); } self.apply_drawing_to_render_canvas(None); self.process_animation_frame(tree, modifiers, structure, scale_content, timestamp)?; @@ -799,22 +887,7 @@ impl RenderState { // the blend mode 'destination-in') the content // of the group and the mask. if group.masked { - self.pending_nodes.push(NodeRenderState { - id: element.id, - visited_children: true, - clip_bounds: None, - visited_mask: true, - mask: false, - }); - if let Some(&mask_id) = element.mask_id() { - self.pending_nodes.push(NodeRenderState { - id: mask_id, - visited_children: false, - clip_bounds: None, - visited_mask: false, - mask: true, - }); - } + self.pending_nodes.add_mask(element); } } } @@ -923,6 +996,66 @@ impl RenderState { Ok((is_empty, should_stop_rendering)) } + pub fn render_shape_tree_full_uncached_shape_tile( + &mut self, + element: &mut Shape, + node_render_state: &NodeRenderState, + tile: &tiles::Tile, + modifiers: &HashMap, + _structure: &HashMap>, + scale_content: &HashMap, + ) { + let NodeRenderState { + id: _node_id, + visited_children: _visited_children, + clip_bounds, + visited_mask: _visited_mask, + mask, + } = *node_render_state; + + self.update_render_context(tile); + + self.render_shape_enter(element, mask); + if !node_render_state.id.is_nil() { + self.render_shape( + element, + modifiers.get(&element.id), + scale_content.get(&element.id), + clip_bounds, + ); + } else { + self.apply_drawing_to_render_canvas(Some(element)); + } + } + + pub fn render_shape_tree_full_uncached_shape_tiles( + &mut self, + element: &mut Shape, + node_render_state: &NodeRenderState, + tiles: &HashSet, + modifiers: &HashMap, + structure: &HashMap>, + scale_content: &HashMap, + ) { + // We need to iterate over every tile of the element. + for tile in tiles.iter() { + println!( + "render_shape_tree_full_uncached::for::start {} {}", + tile.0, tile.1 + ); + self.render_shape_tree_full_uncached_shape_tile( + element, + node_render_state, + tile, + modifiers, + structure, + scale_content, + ); + + println!("render_shape_tree_full_uncached::for::end"); + } + } + pub fn render_shape_tree_full_uncached( &mut self, tree: &mut HashMap, @@ -934,7 +1067,7 @@ impl RenderState { let mut iteration = 0; let mut is_empty = true; println!("render_shape_tree_full_uncached::while::start"); - while let Some(node_render_state) = self.pending_nodes.pop() { + while let Some(node_render_state) = self.pending_nodes.next() { println!( "render_shape_tree_full_uncached::while::pop {}", self.pending_nodes.len() @@ -942,7 +1075,7 @@ impl RenderState { let NodeRenderState { id: node_id, visited_children, - clip_bounds, + clip_bounds: _clip_bounds, visited_mask, mask, } = node_render_state; @@ -953,94 +1086,60 @@ impl RenderState { .to_string(), )?; + if !node_render_state.id.is_nil() { + let mut transformed_element: Cow = Cow::Borrowed(element); + + if let Some(modifier) = modifiers.get(&node_id) { + transformed_element.to_mut().apply_transform(modifier); + } + + let is_visible = transformed_element.extrect().intersects(self.render_area) + && !transformed_element.hidden + && !transformed_element.visually_insignificant(self.get_scale_mut()); + + if self.options.is_debug_visible() { + debug::render_debug_shape(self, &transformed_element, is_visible); + } + + if !is_visible { + continue; + } + } + // If the shape is not in the tile set, then we update // it. - if self.tiles.get_tiles_of(node_id).is_none() { + if self.get_tiles_of(node_id).is_none() { self.update_tile_for(element); } - let tiles = self.tiles.get_tiles_of(node_id).as_mut().unwrap(); - // We need to iterate over every tile of the element. - for tile in tiles.iter() { - println!( - "render_shape_tree_full_uncached::for::start {} {}", - tile.0, tile.1 - ); - self.update_render_context(*tile); - if visited_children { - if !visited_mask { - if let Type::Group(group) = element.shape_type { - // When we're dealing with masked groups we need to - // do a separate extra step to draw the mask (the last - // element of a masked group) and blend (using - // the blend mode 'destination-in') the content - // of the group and the mask. - if group.masked { - self.pending_nodes.push(NodeRenderState { - id: node_id, - visited_children: true, - clip_bounds: None, - visited_mask: true, - mask: false, - }); - if let Some(&mask_id) = element.mask_id() { - self.pending_nodes.push(NodeRenderState { - id: mask_id, - visited_children: false, - clip_bounds: None, - visited_mask: false, - mask: true, - }); - } - } + let mut tiles_opt = self.get_tiles_of(element.id); + let tiles = tiles_opt.as_mut().unwrap().clone(); + self.render_shape_tree_full_uncached_shape_tiles( + element, + &node_render_state, + &tiles, + modifiers, + structure, + scale_content, + ); + if visited_children { + if !visited_mask { + if let Type::Group(group) = element.shape_type { + // When we're dealing with masked groups we need to + // do a separate extra step to draw the mask (the last + // element of a masked group) and blend (using + // the blend mode 'destination-in') the content + // of the group and the mask. + if group.masked { + self.pending_nodes.add_mask(element); } } - self.render_shape_exit(element, visited_mask); - continue; } - - if !node_render_state.id.is_nil() { - let mut transformed_element: Cow = Cow::Borrowed(element); - - if let Some(modifier) = modifiers.get(&node_id) { - transformed_element.to_mut().apply_transform(modifier); - } - - let is_visible = transformed_element.extrect().intersects(self.render_area) - && !transformed_element.hidden - && !transformed_element.visually_insignificant(self.get_scale_mut()); - - if self.options.is_debug_visible() { - debug::render_debug_shape(self, &transformed_element, is_visible); - } - - if !is_visible { - continue; - } - } - - self.render_shape_enter(element, mask); - if !node_render_state.id.is_nil() { - self.render_shape( - element, - modifiers.get(&element.id), - scale_content.get(&element.id), - clip_bounds, - ); - } else { - self.apply_drawing_to_render_canvas(Some(element)); - } - println!("render_shape_tree_full_uncached::for::end"); + self.render_shape_exit(element, visited_mask); + continue; } - // Set the node as visited_children before processing children - self.pending_nodes.push(NodeRenderState { - id: node_id, - visited_children: true, - clip_bounds: None, - visited_mask: false, - mask, - }); + self.pending_nodes.add_children_safeguard(element, mask); if element.is_recursive() { let children_clip_bounds = @@ -1058,13 +1157,7 @@ impl RenderState { } for child_id in children_ids.iter() { - self.pending_nodes.push(NodeRenderState { - id: *child_id, - visited_children: false, - clip_bounds: children_clip_bounds, - visited_mask: false, - mask: false, - }); + self.pending_nodes.add_child(child_id, children_clip_bounds); } } @@ -1089,7 +1182,7 @@ impl RenderState { let mut iteration = 0; let mut is_empty = true; let scale = self.get_scale(); - while let Some(node_render_state) = self.pending_nodes.pop() { + while let Some(node_render_state) = self.pending_nodes.next() { let NodeRenderState { id: node_id, visited_children, @@ -1152,13 +1245,7 @@ impl RenderState { } // Set the node as visited_children before processing children - self.pending_nodes.push(NodeRenderState { - id: node_id, - visited_children: true, - clip_bounds: None, - visited_mask: false, - mask, - }); + self.pending_nodes.add_children_safeguard(element, mask); if element.is_recursive() { let children_clip_bounds = @@ -1177,13 +1264,7 @@ impl RenderState { } for child_id in children_ids.iter() { - self.pending_nodes.push(NodeRenderState { - id: *child_id, - visited_children: false, - clip_bounds: children_clip_bounds, - visited_mask: false, - mask: false, - }); + self.pending_nodes.add_child(child_id, children_clip_bounds); } } @@ -1264,7 +1345,7 @@ impl RenderState { // If we finish processing every node rendering is complete // let's check if there are more pending nodes if let Some(next_tile) = self.pending_tiles.pop() { - self.update_render_context(next_tile); + self.update_render_context(&next_tile); if !self.surfaces.has_cached_tile_surface(next_tile) { if let Some(ids) = self.tiles.get_shapes_at(next_tile) { @@ -1273,19 +1354,9 @@ impl RenderState { .iter() .filter_map(|id| root_ids.get(id).map(|_| *id)) .collect(); - // These shapes for the tile should be ordered as they are in the parent node valid_ids.sort_by_key(|id| root_ids.get_index_of(id)); - - self.pending_nodes.extend(valid_ids.into_iter().map(|id| { - NodeRenderState { - id, - visited_children: false, - clip_bounds: None, - visited_mask: false, - mask: false, - } - })); + self.pending_nodes.extend(valid_ids); } } } else {