diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index a18cd180a6..dd6ef69fbc 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -99,7 +99,7 @@ ;; This should never be called from the outside. (defn- render ([timestamp] - (render timestamp false)) + (render timestamp true)) ([timestamp full] (h/call wasm/internal-module "_render" timestamp full) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index c30f2fa4d5..3ddbd2356a 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -672,6 +672,15 @@ impl RenderState { 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.apply_drawing_to_render_canvas(None); self.process_animation_frame(tree, modifiers, structure, scale_content, timestamp)?; performance::end_measure!("start_render_loop"); @@ -886,7 +895,172 @@ impl RenderState { scale_content: &HashMap, timestamp: i32, ) -> Result<(bool, bool), String> { - self.render_shape_tree_partial_uncached(tree, modifiers, structure, scale_content, timestamp) + let result = self.render_shape_tree_full_uncached(tree, modifiers, structure, scale_content, timestamp); + + let (is_empty, should_stop_rendering) = result?; + if should_stop_rendering { + self.render_in_progress = false; + self.render_is_full = false; + } + + if self.options.is_debug_visible() { + debug::render(self); + } + + debug::render_wasm_label(self); + Ok((is_empty, should_stop_rendering)) + } + + pub fn render_shape_tree_full_uncached( + &mut self, + tree: &mut HashMap, + modifiers: &HashMap, + structure: &HashMap>, + scale_content: &HashMap, + timestamp: i32, + ) -> Result<(bool, bool), String> { + let mut iteration = 0; + let mut is_empty = true; + let scale = self.get_scale(); + let tile_hashmap = &self.tiles; + println!("render_shape_tree_full_uncached::while::start"); + while let Some(node_render_state) = self.pending_nodes.pop() { + println!("render_shape_tree_full_uncached::while::pop {}", self.pending_nodes.len()); + let NodeRenderState { + id: node_id, + visited_children, + clip_bounds, + visited_mask, + mask, + } = node_render_state; + + is_empty = false; + let element = tree.get_mut(&node_id).ok_or( + "Error: Element with root_id {node_render_state.id} not found in the tree." + .to_string(), + )?; + + let tiles = tile_hashmap.get_tiles_of(node_id); + // If the shape is not in the tile set, then we update + // it. + if tiles.is_none() { + self.update_tile_for(element); + } + + // We need to iterate over every tile of the element. + for tile in tiles.unwrap() { + println!("render_shape_tree_full_uncached::while::start {} {}", tile.0, tile.1); + self.current_tile = Some(*tile); + self.render_area = tiles::get_tile_rect(*tile, scale); + + 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, + }); + } + } + } + } + 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(scale); + + 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)); + } + } + + // 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, + }); + + if element.is_recursive() { + let children_clip_bounds = + node_render_state.get_children_clip_bounds(element, modifiers.get(&element.id)); + + let mut children_ids = modified_children_ids(element, structure.get(&element.id)); + + // Z-index ordering on Layouts + if element.has_layout() { + children_ids.sort_by(|id1, id2| { + let z1 = tree.get(id1).map_or_else(|| 0, |s| s.z_index()); + let z2 = tree.get(id2).map_or_else(|| 0, |s| s.z_index()); + z1.cmp(&z2) + }); + } + + 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, + }); + } + } + + // We try to avoid doing too many calls to get_time + if self.should_stop_rendering(iteration, timestamp) { + return Ok((is_empty, true)); + } + iteration += 1; + } + println!("render_shape_tree_full_uncached::while::end"); + Ok((is_empty, self.pending_nodes.is_empty())) } pub fn render_shape_tree_partial_uncached( @@ -899,6 +1073,7 @@ impl RenderState { ) -> Result<(bool, bool), String> { let mut iteration = 0; let mut is_empty = true; + let scale = self.get_scale(); while let Some(node_render_state) = self.pending_nodes.pop() { let NodeRenderState { id: node_id, @@ -939,7 +1114,7 @@ impl RenderState { let is_visible = transformed_element.extrect().intersects(self.render_area) && !transformed_element.hidden - && !transformed_element.visually_insignificant(self.get_scale()); + && !transformed_element.visually_insignificant(scale); if self.options.is_debug_visible() { debug::render_debug_shape(self, &transformed_element, is_visible); @@ -1003,7 +1178,7 @@ impl RenderState { } iteration += 1; } - Ok((is_empty, false)) + Ok((is_empty, self.pending_nodes.is_empty())) } pub fn render_shape_tree_partial( @@ -1221,6 +1396,10 @@ impl RenderState { self.viewbox.zoom() * self.options.dpr() } + pub fn get_scale_mut(&mut self) -> f32 { + self.viewbox.zoom() * self.options.dpr() + } + pub fn get_cached_scale(&self) -> f32 { self.cached_viewbox.zoom() * self.options.dpr() }