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