Skip to main content

cranpose_render_common/
scene_builder.rs

1use std::{any::Any, rc::Rc};
2
3use cranpose_core::{MemoryApplier, NodeId, collections::map::HashMap};
4use cranpose_ui::{LayoutBox, ModifierNodeSlices, Point, Rect, Size, SubcomposeLayoutNode};
5use cranpose_ui_graphics::GraphicsLayer;
6
7use crate::{
8    NodeUpdate, SceneUpdates,
9    graph::{LayerNode, ProjectiveTransform, RenderGraph, RenderNode},
10    layer_transform::layer_transform_to_parent,
11};
12
13mod dirty_walk;
14pub(crate) use dirty_walk::{DirtyAncestry, RetainedPath};
15use dirty_walk::{update_along_retained_path, walk_dirty_layers};
16mod draw_recording;
17use draw_recording::{
18    DrawPass, DrawTarget, append_draw_nodes, bump_recording_generation, draw_nodes,
19    layer_node_capacity,
20};
21pub(crate) use draw_recording::{RecorderKey, RecorderSlot};
22pub use draw_recording::{clear_command_recordings_for_tests, draw_command_nodes_for_tests};
23mod hit_graph;
24pub(crate) use hit_graph::HitGraphState;
25use hit_graph::rebuild_tracking_hits;
26mod inline_leaf;
27use inline_leaf::{lower_child, update_dirty_inline};
28mod layer_shape;
29use layer_shape::shaped_graphics_layer;
30mod text_node;
31mod translate_geometry;
32use text_node::{TextNodeParts, text_node_from_parts, translate_unwrapped};
33use translate_geometry::{KeptChild, TranslateGeometry, move_kept_child, translate_retained_child};
34mod node_layer;
35use node_layer::{
36    AbsOrigin, LayerHead, LowerContext, PlacedGeometry, assign_layer, children_have_origin_sinks,
37    hit_test_from_slices, layer_and_node_bounds, layer_identity, layout_box_to_snapshot,
38    lower_root_into, modifier_slices_have_origin_sinks, outer_draws, placed_child_geometry,
39    placed_transform, prepare_node_layer, read_node_data, read_placed_node_data,
40    slices_hit_something, take_wrapped_layer, write_node_content, write_node_layer,
41    write_snapshot_layer, write_wrapper,
42};
43mod translate_layer;
44pub(crate) use translate_layer::TranslateScratch;
45use translate_layer::{
46    TranslatedContainer, translate_tracking_hits, translated_container,
47    write_own_content_around_children,
48};
49
50#[derive(Clone, Default)]
51struct BuildNodeSnapshot {
52    node_id: NodeId,
53    placement: Point,
54    size: Size,
55    content_offset: Point,
56    /// The node's modifier slices, shared: its draw commands, handlers and
57    /// text are read from them rather than copied out.
58    slices: Rc<ModifierNodeSlices>,
59    graphics_layer: Option<GraphicsLayer>,
60    children: Vec<Self>,
61}
62
63struct SnapshotNodeData<'a> {
64    layout_state: cranpose_ui::widgets::LayoutState,
65    modifier_slices: Rc<ModifierNodeSlices>,
66    children: &'a [NodeId],
67    window_root: bool,
68}
69
70/// Why a scoped scene update could not be applied, forcing the caller to throw
71/// the render graph away and build it again from the applier.
72///
73/// The reason has to travel with the outcome because the shell picks the
74/// scoped path from the shape of the dirty set, before this code runs, and
75/// logs that choice. A frame that chose the scoped path and then rebuilt the
76/// whole scene is the expensive case, and in the log it reads exactly like a
77/// cheap patch -- so the fallback says so itself.
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum GraphRebuildReason {
80    /// The root was dirty and its replacement layer could not be built.
81    RootLayerUnavailable,
82    /// A dirty subtree's replacement layer could not be built.
83    DirtyLayerUnavailable,
84    /// This many dirty nodes own no layer in the render graph, so the scoped
85    /// walk never reached them. A node enters the graph only as a `LayerNode`;
86    /// a dirty node that never produced one -- or whose layer left the graph
87    /// this frame -- cannot be patched in place.
88    UnmatchedDirtyNodes(usize),
89}
90
91/// The outcome of a scoped scene update.
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum GraphUpdate {
94    /// The dirty nodes were updated in place.
95    Patched,
96    /// The update could not be applied and the graph must be built again, for the given reason.
97    NeedsRebuild(GraphRebuildReason),
98}
99
100/// The outcome of a scoped update together with its effect on hit testing.
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102pub struct GraphUpdateReport {
103    /// Whether the graph was patched or must be rebuilt.
104    pub update: GraphUpdate,
105    /// Whether the update changed hit geometry or handlers, so the hit targets must be collected
106    /// again.
107    pub hit_graph_dirty: bool,
108}
109
110impl GraphUpdateReport {
111    /// Whether the graph was patched in place.
112    pub fn applied(self) -> bool {
113        matches!(self.update, GraphUpdate::Patched)
114    }
115
116    /// Why the graph must be rebuilt, or `None` when it was patched.
117    pub fn rebuild_reason(self) -> Option<GraphRebuildReason> {
118        match self.update {
119            GraphUpdate::Patched => None,
120            GraphUpdate::NeedsRebuild(reason) => Some(reason),
121        }
122    }
123}
124
125fn note_layer_lowered() {
126    #[cfg(test)]
127    crate::thread_state::with(|state| {
128        let count = &state.lowered_layer_count;
129        count.set(count.get() + 1);
130    });
131}
132
133#[cfg(test)]
134fn reset_lowered_layer_count() {
135    crate::thread_state::with(|state| state.lowered_layer_count.set(0));
136}
137
138#[cfg(test)]
139fn lowered_layer_count() -> usize {
140    crate::thread_state::with(|state| state.lowered_layer_count.get())
141}
142
143/// Builds the render graph of the layout box tree rooted at `root`.
144pub fn build_graph_from_layout_tree(root: &LayoutBox, _scale: f32) -> RenderGraph {
145    bump_recording_generation();
146    let root_snapshot = layout_box_to_snapshot(root, None);
147    let mut graph = RenderGraph::new(LayerNode::default());
148    write_snapshot_layer(root_snapshot, LowerContext::ROOT, &mut graph.root);
149    graph
150}
151
152/// Builds the render graph of the layout tree below `root` by reading nodes from `applier`.
153/// Returns `None` when `root` cannot be lowered to a layer.
154pub fn build_graph_from_applier(
155    applier: &MemoryApplier,
156    root: NodeId,
157    _scale: f32,
158) -> Option<RenderGraph> {
159    bump_recording_generation();
160    let mut graph = RenderGraph::new(LayerNode::default());
161    lower_root_into(applier, root, &mut graph.root).then_some(graph)
162}
163
164/// Builds `root`'s graph again, its layers taking the allocations of
165/// `previous`, the graph it replaces.
166pub fn rebuild_graph_from_applier(
167    applier: &MemoryApplier,
168    root: NodeId,
169    scale: f32,
170    previous: Option<RenderGraph>,
171) -> Option<RenderGraph> {
172    let graph = match previous {
173        Some(mut graph) => {
174            release_for_rebuild(&mut graph.root);
175            bump_recording_generation();
176            graph.retained_path = None;
177            graph.update += 1;
178            graph.root.note_content_change(graph.update);
179            lower_root_into(applier, root, &mut graph.root).then_some(graph)
180        }
181        None => build_graph_from_applier(applier, root, scale),
182    };
183    crate::layer_recycling::release();
184    graph
185}
186
187/// Applies `updates` to `graph` in place. Returns `false` when the update could not be applied
188/// and the graph must be built again.
189pub fn update_graph_from_applier(
190    applier: &MemoryApplier,
191    graph: &mut RenderGraph,
192    updates: SceneUpdates<'_>,
193    scale: f32,
194) -> bool {
195    update_graph_from_applier_report(applier, graph, updates, scale).applied()
196}
197
198/// Like `update_graph_from_applier`, and also reports whether the hit targets must be collected
199/// again or why the graph must be rebuilt.
200#[expect(clippy::print_stderr, reason = "CRANPOSE_SCENE_UPDATE_DIAG asks")]
201pub fn update_graph_from_applier_report(
202    applier: &MemoryApplier,
203    graph: &mut RenderGraph,
204    updates: SceneUpdates<'_>,
205    _scale: f32,
206) -> GraphUpdateReport {
207    let report = update_graph_from_applier_report_inner(applier, graph, updates);
208    crate::layer_recycling::release();
209    if let GraphUpdate::NeedsRebuild(reason) = report.update
210        && cranpose_core::DIAGNOSTICS.scene_update_diag.is_set()
211    {
212        eprintln!(
213            "[scene-update-diag] scoped update abandoned, whole scene rebuilt: {reason:?} dirty={}",
214            updates.len()
215        );
216    }
217    report
218}
219
220/// Puts into `dirty` each dirty node with the update it needs, the updates
221/// of a node named more than once merged.
222fn dirty_node_updates(updates: SceneUpdates<'_>, dirty: &mut HashMap<NodeId, NodeUpdate>) {
223    dirty.clear();
224    dirty.reserve(updates.len());
225    for &(id, update) in updates {
226        dirty
227            .entry(id)
228            .and_modify(|kind| *kind = kind.merge(update))
229            .or_insert(update);
230    }
231}
232
233#[expect(clippy::print_stderr, reason = "CRANPOSE_SCENE_UPDATE_DIAG asks")]
234fn update_graph_from_applier_report_inner(
235    applier: &MemoryApplier,
236    graph: &mut RenderGraph,
237    updates: SceneUpdates<'_>,
238) -> GraphUpdateReport {
239    if updates.is_empty() {
240        return GraphUpdateReport {
241            update: GraphUpdate::Patched,
242            hit_graph_dirty: false,
243        };
244    }
245    bump_recording_generation();
246    graph.update += 1;
247    let update = graph.update;
248
249    if cranpose_core::DIAGNOSTICS.scene_update_diag.is_set() {
250        eprintln!("[scene-update-diag] dirty={updates:?}");
251    }
252
253    // The map of the frame before keeps its storage for this one.
254    let mut remaining_dirty_nodes =
255        crate::thread_state::with(|state| state.dirty_node_scratch.take()).unwrap_or_default();
256    dirty_node_updates(updates, &mut remaining_dirty_nodes);
257    let report = update_dirty_nodes(applier, graph, &mut remaining_dirty_nodes, update);
258    remaining_dirty_nodes.clear();
259    crate::thread_state::with(|state| state.dirty_node_scratch.set(Some(remaining_dirty_nodes)));
260    report
261}
262
263/// Updates `graph` for `remaining_dirty_nodes`, removing each node it
264/// updates from the map.
265fn update_dirty_nodes(
266    applier: &MemoryApplier,
267    graph: &mut RenderGraph,
268    remaining_dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
269    update: u64,
270) -> GraphUpdateReport {
271    if let Some(root_id) = layer_identity(&graph.root)
272        && let Some(kind) = remaining_dirty_nodes.remove(&root_id)
273    {
274        if let Some(hit_graph_dirty) = try_update_retained_layer(
275            applier,
276            &mut graph.root,
277            remaining_dirty_nodes,
278            kind,
279            Level::Root,
280            TranslateAncestorContext {
281                inherited_motion_context_animated: false,
282                update,
283                inherited_translated_content_context: false,
284                parent_content_offset: Point::default(),
285                parent_abs: AbsOrigin::ROOT,
286            },
287        ) {
288            if remaining_dirty_nodes.is_empty() {
289                return GraphUpdateReport {
290                    update: GraphUpdate::Patched,
291                    hit_graph_dirty,
292                };
293            }
294            let walked = walk_dirty_layers(applier, graph, remaining_dirty_nodes, update)
295                .map(|(report, _)| report);
296            return GraphUpdateReport {
297                update: classify_walk(applier, walked.is_some(), remaining_dirty_nodes),
298                hit_graph_dirty: hit_graph_dirty || walked.is_none_or(|r| r.hit_graph_dirty),
299            };
300        }
301
302        let Some(hit_graph_dirty) = rebuild_tracking_hits(&mut graph.root, |root| {
303            release_for_rebuild(root);
304            lower_root_into(applier, root_id, root)
305        }) else {
306            return GraphUpdateReport {
307                update: GraphUpdate::NeedsRebuild(GraphRebuildReason::RootLayerUnavailable),
308                hit_graph_dirty: true,
309            };
310        };
311        graph.root.note_content_change(update);
312
313        return GraphUpdateReport {
314            update: GraphUpdate::Patched,
315            hit_graph_dirty,
316        };
317    }
318
319    // An update that does not go along the retained path may move the
320    // layers on it, so only one that does keeps it.
321    if let Some(path) = graph.retained_path.take()
322        && let Some(report) = update_along_retained_path(
323            applier,
324            &mut graph.root,
325            &path,
326            remaining_dirty_nodes,
327            update,
328        )
329    {
330        if report.is_some() {
331            graph.retained_path = Some(RetainedPath { update, ..path });
332        }
333        return match report {
334            Some(report) => GraphUpdateReport {
335                update: GraphUpdate::Patched,
336                hit_graph_dirty: report.hit_graph_dirty,
337            },
338            None => GraphUpdateReport {
339                update: GraphUpdate::NeedsRebuild(GraphRebuildReason::DirtyLayerUnavailable),
340                hit_graph_dirty: true,
341            },
342        };
343    }
344
345    let Some((report, path)) = walk_dirty_layers(applier, graph, remaining_dirty_nodes, update)
346    else {
347        return GraphUpdateReport {
348            update: GraphUpdate::NeedsRebuild(GraphRebuildReason::DirtyLayerUnavailable),
349            hit_graph_dirty: true,
350        };
351    };
352    graph.retained_path = path;
353
354    match classify_walk(applier, true, remaining_dirty_nodes) {
355        GraphUpdate::Patched => GraphUpdateReport {
356            update: GraphUpdate::Patched,
357            hit_graph_dirty: report.hit_graph_dirty,
358        },
359        update => GraphUpdateReport {
360            update,
361            hit_graph_dirty: true,
362        },
363    }
364}
365
366/// The update a walk made. A dirty node the walk did not find needs the whole
367/// scene built again, unless no layer draws it.
368fn classify_walk(
369    applier: &MemoryApplier,
370    walked: bool,
371    remaining_dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
372) -> GraphUpdate {
373    if !walked {
374        return GraphUpdate::NeedsRebuild(GraphRebuildReason::DirtyLayerUnavailable);
375    }
376    remaining_dirty_nodes.retain(|&node, _| is_drawn(applier, node));
377    if !remaining_dirty_nodes.is_empty() {
378        GraphUpdate::NeedsRebuild(GraphRebuildReason::UnmatchedDirtyNodes(
379            remaining_dirty_nodes.len(),
380        ))
381    } else {
382        GraphUpdate::Patched
383    }
384}
385
386/// Whether the scene draws `node`: each subcompose node above it draws the
387/// slot `node` is in. A slot a lazy list keeps for reuse, or composes before
388/// it shows it, stays in the applier's tree without being drawn, and its
389/// nodes still change.
390fn is_drawn(applier: &MemoryApplier, node: NodeId) -> bool {
391    let mut child = node;
392    let mut current = node;
393    while let Some(parent) = applier
394        .get_ref(current)
395        .ok()
396        .and_then(cranpose_core::Node::parent)
397    {
398        let Ok(parent_node) = applier.get_ref(parent) else {
399            return true;
400        };
401        current = parent;
402        if parent_node.is_virtual() {
403            continue;
404        }
405        let parent_node: &dyn Any = parent_node;
406        if let Some(subcompose) = parent_node.downcast_ref::<SubcomposeLayoutNode>()
407            && !subcompose.with_active_children(|children| children.contains(&child))
408        {
409            return false;
410        }
411        child = parent;
412    }
413    true
414}
415
416#[expect(clippy::print_stderr, reason = "CRANPOSE_SCENE_UPDATE_DIAG asks")]
417fn translate_bail(reason: &str) -> bool {
418    if cranpose_core::DIAGNOSTICS.scene_update_diag.is_set() {
419        eprintln!("[scene-update-diag] translate bail: {reason}");
420    }
421    false
422}
423
424#[derive(Clone, Copy)]
425struct TranslateAncestorContext {
426    inherited_motion_context_animated: bool,
427    /// The scene update in progress.
428    update: u64,
429    inherited_translated_content_context: bool,
430    parent_content_offset: Point,
431    parent_abs: AbsOrigin,
432}
433
434// Whether a node lowers as the graph's root, the one node that may be a
435// window root, which then draws at the origin.
436#[derive(Clone, Copy, PartialEq, Eq)]
437enum Level {
438    Root,
439    Child,
440}
441
442// Whether a retained layer's node kept its place in its parent.
443#[derive(Clone, Copy, PartialEq, Eq)]
444enum Position {
445    Kept,
446    Moved,
447}
448
449// Whether a node's layer lies in the wrapper that holds the draws chained
450// before its graphics layer.
451#[derive(Clone, Copy, PartialEq, Eq)]
452enum Wrapping {
453    Wrapped,
454    Bare,
455}
456
457fn try_update_retained_layer(
458    applier: &MemoryApplier,
459    layer: &mut LayerNode,
460    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
461    kind: NodeUpdate,
462    level: Level,
463    ancestors: TranslateAncestorContext,
464) -> Option<bool> {
465    match kind {
466        NodeUpdate::Moved if level == Level::Child => {
467            move_retained_layer(applier, layer, ancestors)
468                .or_else(|| translate_tracking_hits(applier, layer, dirty_nodes, ancestors))
469        }
470        NodeUpdate::Draws if redraw_retained_layer(applier, layer, level, ancestors) => Some(false),
471        NodeUpdate::Content | NodeUpdate::Draws | NodeUpdate::Moved => {
472            translate_tracking_hits(applier, layer, dirty_nodes, ancestors)
473        }
474        NodeUpdate::Layer => {
475            update_layer_properties(applier, layer, level, Position::Kept, ancestors)
476        }
477        NodeUpdate::MovedLayer => {
478            update_layer_properties(applier, layer, level, Position::Moved, ancestors)
479        }
480    }
481}
482
483/// Records the own draws of the node of `container` again around the child
484/// layers it keeps, for a node whose draws read state that changed while its
485/// layout, modifiers and children stayed. False when the layer no longer
486/// fits the node: the content update then takes it.
487fn redraw_retained_layer(
488    applier: &MemoryApplier,
489    container: &mut LayerNode,
490    level: Level,
491    ancestors: TranslateAncestorContext,
492) -> bool {
493    // The wrapper of a node's outer draws is written with its layer.
494    if container.wraps.is_some() {
495        return false;
496    }
497    let Some(node_id) = container.node_id else {
498        return false;
499    };
500    read_placed_node_data(applier, node_id, level, |data| {
501        let placement = container.origin_in_parent;
502        let kept = Position::Kept;
503        if !retained_layer_matches_layout(container, &data, placement, Wrapping::Bare, kept) {
504            return false;
505        }
506        let Ok(plan) = translated_container(
507            container,
508            &data.layout_state,
509            &data.modifier_slices,
510            ancestors,
511            Wrapping::Bare,
512        ) else {
513            return false;
514        };
515        container.node_bounds = plan.node_bounds;
516        crate::layer_recycling::recycle_primitives(container);
517        write_own_content_around_children(
518            container,
519            node_id,
520            &data.modifier_slices,
521            data.layout_state.size(),
522        );
523        container.refresh_child_facts();
524        container.note_content_change(ancestors.update);
525        true
526    })
527    .unwrap_or(false)
528}
529
530/// Moves the layer of a node that only moved in its parent, keeping what it
531/// drew, and returns whether hit geometry changed. A subtree that publishes
532/// window origins publishes them again from where it now lies. `None` when
533/// the layer cannot keep its content: its size changed.
534fn move_retained_layer(
535    applier: &MemoryApplier,
536    layer: &mut LayerNode,
537    ancestors: TranslateAncestorContext,
538) -> Option<bool> {
539    let placed = placed_child_geometry(applier, layer_identity(layer)?)?;
540    if Rect::from_size(placed.size) != layer.node_rect() {
541        return None;
542    }
543    let previous = HitGraphState::of(layer);
544    translate_retained_child(layer, placed.position, ancestors.parent_content_offset);
545    if layer.has_origin_sinks {
546        publish_retained_window_geometry(applier, layer, ancestors.parent_abs);
547    }
548    Some(previous.dirty_against(&HitGraphState::of(layer)))
549}
550
551fn content_layer_mut(container: &mut LayerNode) -> Option<&mut LayerNode> {
552    let Some(node_id) = container.wraps else {
553        return Some(container);
554    };
555    container.children.iter_mut().find_map(|child| match child {
556        RenderNode::Layer(layer) if layer.node_id == Some(node_id) => Some(&mut **layer),
557        _ => None,
558    })
559}
560
561/// Whether `layer` still fits its node's layout, so new layer properties
562/// can update it in place: at `placement`, unless the node moved.
563fn retained_layer_matches_layout(
564    layer: &LayerNode,
565    data: &SnapshotNodeData<'_>,
566    placement: Point,
567    wrapping: Wrapping,
568    position: Position,
569) -> bool {
570    let state = &data.layout_state;
571    let slices = &data.modifier_slices;
572    (position == Position::Moved || state.position() == placement)
573        && Rect::from_size(state.size()) == layer.node_rect()
574        && slices.layer_bounds(state.size()) == layer.local_bounds
575        && state.content_offset() == layer.content_offset
576        && (slices.outer_draw_command_count() > 0) == (wrapping == Wrapping::Wrapped)
577}
578
579fn update_layer_properties(
580    applier: &MemoryApplier,
581    container: &mut LayerNode,
582    level: Level,
583    position: Position,
584    ancestors: TranslateAncestorContext,
585) -> Option<bool> {
586    let node_id = layer_identity(container)?;
587    let wrapping = if container.wraps.is_some() {
588        Wrapping::Wrapped
589    } else {
590        Wrapping::Bare
591    };
592    let wrapped = wrapping == Wrapping::Wrapped;
593    // The wrapper of a node's outer draws holds its placement; a moved
594    // wrapped node is drawn again.
595    if position == Position::Moved && wrapped {
596        return None;
597    }
598    let placement = container.origin_in_parent;
599    let hit_graph_dirty = read_placed_node_data(applier, node_id, level, |data| {
600        let target = content_layer_mut(container)?;
601        let slices = &data.modifier_slices;
602        let state = &data.layout_state;
603        if !retained_layer_matches_layout(target, &data, placement, wrapping, position) {
604            return None;
605        }
606        let context = LowerContext::beneath(ancestors);
607        let previous = HitGraphState::of(target);
608        let (head, child_context) = prepare_node_layer(node_id, slices, state, context);
609        assign_layer(target, head);
610        if wrapped {
611            target.transform_to_parent = layer_transform_to_parent(
612                target.local_bounds,
613                Point::default(),
614                &target.graphics_layer,
615            );
616            target.origin_in_parent = Point::default();
617        }
618        if target.has_origin_sinks
619            && let Some(child_abs) = child_context.parent_abs
620        {
621            for child in &target.children {
622                if let RenderNode::Layer(child) = child {
623                    publish_retained_window_geometry(applier, child, child_abs);
624                }
625            }
626        }
627        Some(previous.dirty_against(&HitGraphState::of(target)))
628    })??;
629    if wrapped {
630        container.refresh_child_facts();
631        container.has_origin_sinks = children_have_origin_sinks(&container.children);
632        container.forget_own_raster_cache_hashes();
633    }
634    Some(hit_graph_dirty)
635}
636
637/// After a scene build or update that changed a layer in 3D, publishes
638/// where the window's scenes show the layers on their surfaces that publish
639/// window origins, as the eye sees them, and leaves each scene's eye the
640/// world it walks in. A flat window asks nothing of its layers.
641pub(crate) fn publish_space(applier: &MemoryApplier, graph: &RenderGraph) {
642    if !crate::thread_state::with(|state| state.space_changed.replace(false)) {
643        return;
644    }
645    publish_space_now(applier, graph);
646}
647
648#[cold]
649#[inline(never)]
650fn publish_space_now(applier: &MemoryApplier, graph: &RenderGraph) {
651    let root = ProjectiveTransform::identity();
652    crate::space::for_each_window_eye(&graph.root, root, &mut |tree, view, to_window| {
653        if let Some(eye) = &tree.cells[0].scene.eye {
654            eye.world.rebuild(|world| view.build_world(tree, world));
655        }
656        for seen in view
657            .surfaces
658            .iter()
659            .filter(|seen| seen.layer.has_origin_sinks)
660        {
661            if let Some(content) = view.content_to_window(seen, to_window) {
662                publish_content_geometry(applier, seen.layer, content);
663            }
664        }
665    });
666}
667
668/// Publishes the window geometry of `layer`, whose local space `to_window`
669/// maps to the window, and of the layers on it that publish window origins,
670/// as far as the next layer in 3D.
671fn publish_content_geometry(
672    applier: &MemoryApplier,
673    layer: &LayerNode,
674    to_window: ProjectiveTransform,
675) {
676    if let Some(node_id) = layer.node_id {
677        read_node_data(applier, node_id, |data| {
678            data.modifier_slices.publish_window_geometry(
679                Point::default(),
680                to_window,
681                data.layout_state.size(),
682            );
683        });
684    }
685    for child in &layer.children {
686        if let RenderNode::Layer(child) = child
687            && child.has_origin_sinks
688            && child.graphics_layer.space.is_none()
689        {
690            publish_content_geometry(applier, child, child.transform_to_parent.then(to_window));
691        }
692    }
693}
694
695fn publish_retained_window_geometry(
696    applier: &MemoryApplier,
697    layer: &LayerNode,
698    parent_abs: AbsOrigin,
699) {
700    if !layer.has_origin_sinks {
701        return;
702    }
703    let child_abs = parent_abs.children_of(layer);
704    if let Some(node_id) = layer.node_id {
705        read_node_data(applier, node_id, |data| {
706            data.modifier_slices.publish_window_geometry(
707                Point {
708                    x: parent_abs.content_origin.x + layer.origin_in_parent.x,
709                    y: parent_abs.content_origin.y + layer.origin_in_parent.y,
710                },
711                child_abs.window_transform,
712                data.layout_state.size(),
713            );
714        });
715    }
716    for child in &layer.children {
717        if let RenderNode::Layer(child) = child {
718            publish_retained_window_geometry(applier, child, child_abs);
719        }
720    }
721}
722
723/// Notes that the scene update changes a layer that is a scene, a surface or
724/// a 3D transform, or lies beneath one: the window's scenes publish where
725/// their layers show and their eyes' worlds again.
726#[cold]
727#[inline(never)]
728fn note_space_change() {
729    crate::thread_state::with(|state| state.space_changed.set(true));
730}
731
732fn release_for_rebuild(layer: &mut LayerNode) {
733    layer.hit_test = None;
734    crate::layer_recycling::recycle_children(layer);
735}
736
737/// Takes `layer`'s node and every node beneath it out of `dirty_nodes`.
738fn forget_dirty_subtree(layer: &LayerNode, dirty_nodes: &mut HashMap<NodeId, NodeUpdate>) {
739    if let Some(node_id) = layer_identity(layer) {
740        dirty_nodes.remove(&node_id);
741    }
742    remove_dirty_descendants(layer, dirty_nodes);
743}
744
745// Takes the node of `node`, a child of a layer, and every node beneath it
746// out of `dirty_nodes`.
747fn forget_dirty_node(node: &RenderNode, dirty_nodes: &mut HashMap<NodeId, NodeUpdate>) {
748    match node {
749        RenderNode::Layer(layer) => forget_dirty_subtree(layer, dirty_nodes),
750        RenderNode::Inline(inline) => {
751            dirty_nodes.remove(&inline.node_id);
752        }
753        RenderNode::Primitive(_) | RenderNode::DrawRun(_) => {}
754    }
755}
756
757fn remove_dirty_descendants(layer: &LayerNode, dirty_nodes: &mut HashMap<NodeId, NodeUpdate>) {
758    for child in &layer.children {
759        match child {
760            RenderNode::Layer(child_layer) => {
761                if let Some(node_id) = child_layer.node_id {
762                    dirty_nodes.remove(&node_id);
763                }
764                remove_dirty_descendants(child_layer, dirty_nodes);
765            }
766            RenderNode::Inline(inline) => {
767                dirty_nodes.remove(&inline.node_id);
768            }
769            RenderNode::Primitive(_) | RenderNode::DrawRun(_) => {}
770        }
771    }
772}
773
774// The node a child of a layer draws: a child layer's or an inline node's,
775// `None` for the layer's own draws.
776fn child_identity(node: &RenderNode) -> Option<NodeId> {
777    match node {
778        RenderNode::Layer(layer) => layer_identity(layer),
779        RenderNode::Inline(inline) => Some(inline.node_id),
780        RenderNode::Primitive(_) | RenderNode::DrawRun(_) => None,
781    }
782}
783
784#[cfg(test)]
785#[path = "tests/scene_builder_tests.rs"]
786mod tests;