This commit is contained in:
Aitor Moreno 2025-06-17 13:31:19 +02:00
parent ee3bf72034
commit 4274db11e3
2 changed files with 221 additions and 147 deletions

View File

@ -98,6 +98,9 @@
;; This should never be called from the outside.
(defn- render
([]
(render (.now js/performance)))
([timestamp]
(render timestamp false))

View File

@ -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<NodeRenderState>,
}
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<Uuid>) {
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<NodeRenderState> {
self.list.pop()
}
pub fn prepare(&mut self, tree: &mut HashMap<Uuid, &mut Shape>) {
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<Corners>, 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<NodeRenderState>,
pending_nodes: PendingNodes,
pub current_tile: Option<tiles::Tile>,
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<tiles::Tile>> {
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<Uuid, Matrix>,
_structure: &HashMap<Uuid, Vec<StructureEntry>>,
scale_content: &HashMap<Uuid, f32>,
) {
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<tiles::Tile>,
modifiers: &HashMap<Uuid, Matrix>,
structure: &HashMap<Uuid, Vec<StructureEntry>>,
scale_content: &HashMap<Uuid, f32>,
) {
// 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<Uuid, &mut Shape>,
@ -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<Shape> = 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<Shape> = 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 {