Skip to main content

cranpose_render_common/
graph.rs

1use std::{
2    cell::Cell,
3    ops::{Deref, DerefMut, Range},
4    rc::Rc,
5    sync::atomic::{AtomicU32, Ordering},
6};
7
8use cranpose_core::{NodeId, collections::map::HashSet};
9use cranpose_ui::{
10    GraphicsLayer, ModifierNodeSlices, Point, Rect, RenderEffect, TextOverflow, TextStyle,
11    text::{AnnotatedString, RenderString},
12};
13pub use cranpose_ui_graphics::transform::{FrontPolygon, ProjectiveTransform, quad_bounds};
14use cranpose_ui_graphics::{
15    BlendMode, ColorFilter, CommandRecording, DrawPlacement, DrawPrimitive, RecordingSummary,
16};
17
18use crate::{
19    glyph_reach::glyph_bounds,
20    raster_cache::LayerRasterCacheHashes,
21    text_paint::{TextPaint, TextPaintCache},
22};
23
24mod heap_bytes;
25use heap_bytes::layer_heap_bytes;
26mod inline_node;
27pub use inline_node::InlineNode;
28
29/// Reasons a layer is drawn into its own offscreen buffer instead of directly into its parent.
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31pub struct IsolationReasons {
32    /// The layer asked for offscreen compositing.
33    pub explicit_offscreen: bool,
34    /// The layer clips to a shape that is not a rectangle.
35    pub shape_clip: bool,
36    /// The layer applies a render effect to its content.
37    pub effect: bool,
38    /// The layer applies an effect to what lies behind it.
39    pub backdrop: bool,
40    /// The layer's alpha is below one and must apply to its content as a group.
41    pub group_opacity: bool,
42    /// The layer uses a blend mode other than source-over.
43    pub blend_mode: bool,
44}
45
46impl IsolationReasons {
47    /// Whether at least one reason requires the layer to be isolated.
48    pub fn has_any(self) -> bool {
49        self.explicit_offscreen
50            || self.shape_clip
51            || self.effect
52            || self.backdrop
53            || self.group_opacity
54            || self.blend_mode
55    }
56}
57
58/// Whether a renderer may keep the rasterized pixels of a layer between frames.
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
60pub enum CachePolicy {
61    /// The layer is drawn directly each frame.
62    #[default]
63    None,
64    /// The layer is a candidate for a raster cache because it is isolated, scaled or rotated.
65    Auto,
66}
67
68/// Pointer handling attached to a layer.
69#[derive(Clone)]
70pub struct HitTestNode {
71    /// The node's modifier slices, shared rather than copied: they hold its
72    /// pointer inputs and the pointer icon it asks for while hovered. A node
73    /// that only names an icon is still a hit target, which is how a
74    /// decorative panel carries a cursor without handling clicks.
75    pub handlers: Rc<ModifierNodeSlices>,
76}
77
78/// One draw primitive together with its clip.
79#[derive(Clone, Debug, PartialEq)]
80pub struct DrawPrimitiveNode {
81    /// The shape, image or other primitive to draw.
82    pub primitive: DrawPrimitive,
83    /// Rectangle that cuts the primitive, or `None` when it is not clipped.
84    pub clip: Option<Rect>,
85}
86
87/// A run of text to draw, with the layout facts that a renderer needs.
88#[derive(Clone, Debug, PartialEq)]
89pub struct TextPrimitiveNode {
90    /// Layout node that owns the text.
91    pub node_id: NodeId,
92    /// Box of the laid-out text in logical pixels, after alignment and panning.
93    pub rect: Rect,
94    /// Shared so the renderer can hand the same allocation to every draw it
95    /// emits for this node instead of deep-copying the string once per emit.
96    pub text: Rc<AnnotatedString>,
97    /// `text` as the renderer's draws carry it across threads, shared with
98    /// the prepared layout it came from so no frame converts it again.
99    pub render_text: std::sync::Arc<RenderString>,
100    /// The lines and glyphs the text draws, from its top left corner at
101    /// `rect`'s.
102    pub layout: cranpose_text::TextLayout,
103    /// Shared so every frame's draws of this text hand over the same style
104    /// instead of copying it.
105    pub text_style: std::sync::Arc<TextStyle>,
106    /// Resolved font size in logical pixels.
107    pub font_size: f32,
108    /// How the text was laid out to handle text that does not fit.
109    pub overflow: TextOverflow,
110    /// Rectangle that cuts the glyphs: the node's box when the layout overflows it and overflow
111    /// is not visible, or when the text pans; `None` otherwise, as Compose cuts a text.
112    pub clip: Option<Rect>,
113    /// What a renderer reads of `text_style` and `text`'s spans to draw the
114    /// glyphs, so that drawing a plain text does not load the style.
115    pub paint: TextPaintCache,
116}
117
118impl TextPrimitiveNode {
119    /// The paint of the node's glyphs.
120    pub fn paint(&self) -> TextPaint {
121        self.paint.get_or(|| {
122            TextPaint::of(
123                &self.text_style,
124                self.text_style.render_hash(),
125                self.render_text.span_styles(),
126                (self.font_size, &self.layout),
127                (self.render_text.render_hash(), self.render_text.is_empty()),
128            )
129        })
130    }
131
132    /// Conservative bounds for the emitted glyphs and paint: the clip, or
133    /// the layout rectangle and wherever the ink of a glyph, a stroke's
134    /// included, reaches past it. Returns `None` when an unclipped layout or
135    /// style can draw beyond both: a visible overflow, a shadow of the style
136    /// or a span, or a baseline shift.
137    pub fn draw_bounds(&self) -> Option<Rect> {
138        if let Some(clip) = self.clip {
139            return Some(clip);
140        }
141        if matches!(self.overflow, TextOverflow::Visible) {
142            return None;
143        }
144        // The paint, kept in the node, tells this without loading the style.
145        let paint = self.paint();
146        (!paint.casts_shadow && paint.baseline_shift == 0.0)
147            .then(|| glyph_bounds(self.rect, &self.layout, paint.spread))
148    }
149
150    fn draws_within(&self, bounds: Rect) -> bool {
151        self.draw_bounds()
152            .is_some_and(|drawn| rect_within(drawn, bounds))
153    }
154}
155
156/// Whether a primitive draws before or after the children of its layer.
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub enum PrimitivePhase {
159    /// Drawn under the children, like a background.
160    BeforeChildren,
161    /// Drawn over the children, like a foreground or overlay.
162    AfterChildren,
163}
164
165/// The content of a [`PrimitiveEntry`].
166#[derive(Clone, Debug, PartialEq)]
167pub enum PrimitiveNode {
168    /// A draw primitive with its clip.
169    Draw(Box<DrawPrimitiveNode>),
170    /// A text run.
171    Text(Box<TextPrimitiveNode>),
172}
173
174/// One primitive of a layer together with the phase that orders it against the children.
175#[derive(Clone, Debug, PartialEq)]
176pub struct PrimitiveEntry {
177    /// Whether the primitive draws before or after the children of its layer.
178    pub phase: PrimitivePhase,
179    /// The primitive.
180    pub node: PrimitiveNode,
181}
182
183/// Scene properties with implicit defaults and independently mutable clones.
184#[derive(Clone, Debug, Default)]
185pub struct LayerProperties(Option<Box<GraphicsLayer>>);
186
187impl LayerProperties {
188    /// The properties that were set, or `None` when they read as defaults.
189    pub fn get(&self) -> Option<&GraphicsLayer> {
190        self.0.as_deref()
191    }
192
193    /// Whether these properties read as `value`, `None` for the defaults:
194    /// a node without a layer compares nothing.
195    #[inline]
196    pub(crate) fn reads_as(&self, value: Option<&GraphicsLayer>) -> bool {
197        match (self.get(), value) {
198            (None, None) => true,
199            (stored, value) => {
200                stored.unwrap_or(&GraphicsLayer::DEFAULT)
201                    == value.unwrap_or(&GraphicsLayer::DEFAULT)
202            }
203        }
204    }
205
206    pub(crate) fn replace(&mut self, value: Option<GraphicsLayer>) {
207        match (&mut self.0, value) {
208            (Some(current), Some(value)) => **current = value,
209            (storage, value) => *storage = value.map(Box::new),
210        }
211    }
212}
213
214impl Deref for LayerProperties {
215    type Target = GraphicsLayer;
216
217    fn deref(&self) -> &Self::Target {
218        self.0.as_deref().unwrap_or(&GraphicsLayer::DEFAULT)
219    }
220}
221
222impl DerefMut for LayerProperties {
223    fn deref_mut(&mut self) -> &mut Self::Target {
224        self.0.get_or_insert_with(Box::default)
225    }
226}
227
228impl From<GraphicsLayer> for LayerProperties {
229    fn from(value: GraphicsLayer) -> Self {
230        Self(Some(Box::new(value)))
231    }
232}
233
234impl From<Option<GraphicsLayer>> for LayerProperties {
235    fn from(value: Option<GraphicsLayer>) -> Self {
236        Self(value.map(Box::new))
237    }
238}
239
240impl PartialEq for LayerProperties {
241    fn eq(&self, other: &Self) -> bool {
242        self.reads_as(other.get())
243    }
244}
245
246/// A node of the render graph that groups content under one set of layer properties: bounds,
247/// transform, clip, graphics layer and children.
248#[derive(Clone)]
249pub struct LayerNode {
250    /// Layout node of this layer, or `None` for the root and the synthetic wrapper layers.
251    pub node_id: Option<NodeId>,
252    /// Set on the synthetic layer that holds a node's outer draws, those chained
253    /// before its graphics layer, around that node's own layer. Scene updates
254    /// address the node through this id because the node's layer has none of the
255    /// outer draws and the wrapper has no node id of its own.
256    pub wraps: Option<NodeId>,
257    /// The bounds of the node's layer, where its clip cuts, its transforms
258    /// pivot and an offscreen pass draws it.
259    pub local_bounds: Rect,
260    /// The node's own rect, `(0, 0, size)`, where its pointer handlers are,
261    /// when it differs from `local_bounds`: a clip or graphics layer that
262    /// wraps a coordinator a later padding or offset moves bounds the layer
263    /// there instead, as in Compose. `None` when the two agree.
264    pub node_bounds: Option<Rect>,
265    /// Maps the layer's local space to its parent's content space, with the placement, the graphics
266    /// layer transform and the parent's content offset applied.
267    pub transform_to_parent: ProjectiveTransform,
268    /// Offset in logical pixels of the node's content box from the node origin, for example from
269    /// padding. Children are placed relative to it.
270    pub content_offset: Point,
271    /// Whether the layer is inside a motion context such as an animated scroll, where a renderer
272    /// must not snap its content rigidly to the pixel grid.
273    pub motion_context_animated: bool,
274    /// Whether the layer moves its content as one translated block, as a scroll container does,
275    /// so a renderer may snap that content to the pixel grid as a whole.
276    pub translated_content_context: bool,
277    /// Translation in logical pixels of the content when `translated_content_context` is set.
278    pub translated_content_offset: Point,
279    /// Where layout placed this layer within its parent's content, before the
280    /// parent's content offset. Scene updates add these up from the root, with
281    /// each layer's content offset and graphics-layer translation, to find the
282    /// window origin of a subtree they rebuild.
283    pub origin_in_parent: Point,
284    /// The graphics layer properties; they read as defaults when none were set.
285    pub graphics_layer: LayerProperties,
286    /// Whether the layer cuts its content to `local_bounds`.
287    pub clip_to_bounds: bool,
288    /// Pointer handling of the node, or `None` when the node has none.
289    pub hit_test: Option<HitTestNode>,
290    /// Whether this layer or a layer beneath it has a hit test node. Scene building keeps it
291    /// current.
292    pub has_hit_targets: bool,
293    /// [`Self::has_space`], worked out the first time a walk asks and
294    /// forgotten when the layer or its children change.
295    pub space: Cell<Option<bool>>,
296    /// Whether this subtree publishes live window origins (a text field's
297    /// popup anchor, a scroll container's viewport rect). Those sinks are
298    /// written during a full lowering, so the scroll fast path may translate
299    /// a retained subtree in place only when this is false.
300    pub has_origin_sinks: bool,
301    /// [`Self::draws_within_bounds`], worked out the first time a renderer
302    /// asks and forgotten when the layer's children change.
303    pub contained: Cell<Option<bool>>,
304    /// The reasons this layer is drawn through its own offscreen buffer.
305    pub isolation: IsolationReasons,
306    /// Set on a layer that holds part of one draw command's draws, those a
307    /// `DrawScope` transform or clip groups: the phase they paint in among
308    /// the parent's own draws, which they belong to. `None` on the layer of
309    /// a layout node and on the root.
310    pub draw_phase: Option<PrimitivePhase>,
311    /// Whether a renderer may keep this layer's pixels between frames.
312    pub cache_policy: CachePolicy,
313    /// The raster cache hashes, computed the first time a renderer asks and
314    /// kept until the layer or a layer beneath it changes.
315    pub cache_hashes: Cell<Option<LayerRasterCacheHashes>>,
316    /// The scene updates that changed what the layer draws.
317    pub content_changes: ContentChanges,
318    /// The number the layer took when it or a layer beneath it last changed.
319    pub change: LayerChange,
320    /// The number the layer took when it last changed itself: its own
321    /// properties, draws or list of children. A change beneath it leaves
322    /// this number, so a renderer that kept the layer's own items knows they
323    /// hold.
324    pub own_change: LayerChange,
325    /// Content in draw order: primitives, draw runs and child layers.
326    pub children: Vec<RenderNode>,
327}
328
329/// A number a layer takes when it is made and whenever it or a layer beneath
330/// it changes, as its raster cache hashes are forgotten: a renderer that
331/// kept what it built from a subtree knows from an equal number that nothing
332/// in it changed since, save the subtree's placement in its parent.
333#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
334pub struct LayerChange(u32);
335
336impl LayerChange {
337    /// A number no layer took in the last 2^32 changes.
338    fn next() -> Self {
339        static LAST: AtomicU32 = AtomicU32::new(0);
340        Self(LAST.fetch_add(1, Ordering::Relaxed).wrapping_add(1))
341    }
342}
343
344impl Default for LayerChange {
345    fn default() -> Self {
346        Self::next()
347    }
348}
349
350/// The last scene update that changed what a layer draws, and how many
351/// updates in a row up to it did.
352#[derive(Clone, Copy, Debug, Default)]
353pub struct ContentChanges {
354    last_update: u64,
355    run: u32,
356}
357
358impl ContentChanges {
359    fn note(&mut self, update: u64) {
360        if self.run > 0 && self.last_update == update {
361            return;
362        }
363        self.run = if self.run > 0 && self.last_update + 1 == update {
364            self.run + 1
365        } else {
366            1
367        };
368        self.last_update = update;
369    }
370}
371
372impl Default for LayerNode {
373    fn default() -> Self {
374        Self {
375            node_id: None,
376            wraps: None,
377            local_bounds: Rect {
378                x: 0.0,
379                y: 0.0,
380                width: 0.0,
381                height: 0.0,
382            },
383            node_bounds: None,
384            transform_to_parent: ProjectiveTransform::identity(),
385            content_offset: Point::default(),
386            motion_context_animated: false,
387            translated_content_context: false,
388            translated_content_offset: Point::default(),
389            origin_in_parent: Point::default(),
390            graphics_layer: LayerProperties::default(),
391            clip_to_bounds: false,
392            hit_test: None,
393            has_hit_targets: false,
394            space: Cell::new(None),
395            has_origin_sinks: false,
396            contained: Cell::new(None),
397            isolation: IsolationReasons::default(),
398            draw_phase: None,
399            cache_policy: CachePolicy::None,
400            cache_hashes: Cell::new(None),
401            content_changes: ContentChanges::default(),
402            change: LayerChange::default(),
403            own_change: LayerChange::default(),
404            children: Vec::new(),
405        }
406    }
407}
408
409/// How far past its bounds a layer that draws within them may still put
410/// pixels: glyph and edge antialiasing.
411pub const CONTAINED_DRAW_SLACK: f32 = 1.0;
412
413impl LayerNode {
414    /// Every string this layer and the layers below it paint, in draw order.
415    pub fn painted_text(&self) -> Vec<String> {
416        let mut out = Vec::new();
417        collect_painted_text(&self.children, &mut out);
418        out
419    }
420
421    /// Whether everything this layer and its subtree draw lies within its
422    /// `local_bounds`, give or take [`CONTAINED_DRAW_SLACK`], so a renderer
423    /// may skip the whole subtree where those bounds are clipped away.
424    /// Worked out at the first call after a change and kept until the next.
425    pub fn draws_within_bounds(&self) -> bool {
426        if let Some(contained) = self.contained.get() {
427            return contained;
428        }
429        let contained = self.content_draws_within_bounds();
430        self.contained.set(Some(contained));
431        contained
432    }
433
434    /// Whether this layer's content, as [`LayerNode::draws_within_bounds`]
435    /// describes it, stays within its bounds: it clips to them, or its draws,
436    /// its texts and its children placed where they are all fit inside.
437    pub fn content_draws_within_bounds(&self) -> bool {
438        self.visual_clip_rect().is_some()
439            || nodes_draw_within(
440                &self.children,
441                inflate_rect(self.local_bounds, CONTAINED_DRAW_SLACK),
442            )
443    }
444
445    pub(crate) fn refresh_child_facts(&mut self) {
446        self.has_hit_targets = self.hit_test.is_some()
447            || self.children.iter().any(|child| match child {
448                RenderNode::Layer(child_layer) => child_layer.has_hit_targets,
449                RenderNode::Primitive(_) | RenderNode::DrawRun(_) | RenderNode::Inline(_) => false,
450            });
451        self.contained.set(None);
452        self.space.set(None);
453    }
454
455    /// Whether this layer or a layer beneath it is a scene, a mesh surface
456    /// or a 3D transform, so a walk for what lies in 3D skips the subtrees
457    /// without one.
458    pub fn has_space(&self) -> bool {
459        if let Some(known) = self.space.get() {
460            return known;
461        }
462        let found = self.graphics_layer.space.is_some()
463            || self.children.iter().any(|child| match child {
464                RenderNode::Layer(child_layer) => child_layer.has_space(),
465                RenderNode::Primitive(_) | RenderNode::DrawRun(_) | RenderNode::Inline(_) => false,
466            });
467        self.space.set(Some(found));
468        found
469    }
470
471    /// Whether this layer, drawn as a child, puts nothing outside `bounds`:
472    /// it draws within its own bounds, casts no shadow, applies no effect,
473    /// and its bounds placed in its parent lie inside.
474    fn draws_within_parent(&self, bounds: Rect) -> bool {
475        self.draws_within_bounds()
476            && self.graphics_layer.shadow_elevation <= 0.0
477            && self.effect().is_none()
478            && self.backdrop().is_none()
479            && rect_within(
480                self.transform_to_parent
481                    .bounds_for_rect(inflate_rect(self.local_bounds, CONTAINED_DRAW_SLACK)),
482                bounds,
483            )
484    }
485
486    /// The node's own rect, where its pointer handlers are.
487    pub fn node_rect(&self) -> Rect {
488        self.node_bounds.unwrap_or(self.local_bounds)
489    }
490
491    /// The rectangle that cuts the layer's content and pointer hits: its bounds when the layer
492    /// clips to bounds or its graphics layer clips, otherwise `None`.
493    pub fn clip_rect(&self) -> Option<Rect> {
494        (self.clip_to_bounds || self.graphics_layer.clip).then_some(self.local_bounds)
495    }
496
497    /// Where the layer's own shadow is cut: its bounds when it clips its
498    /// content to them. A graphics-layer clip leaves the shadow whole.
499    pub fn shadow_clip(&self) -> Option<Rect> {
500        self.clip_to_bounds.then_some(self.local_bounds)
501    }
502
503    /// Where the layer's drawing is cut: its clip or, when it composites
504    /// through an offscreen buffer for its alpha or by request, its bounds,
505    /// as Compose's layer-sized buffer cuts it. Hit testing takes only
506    /// [`Self::clip_rect`]: alpha hides nothing from a finger.
507    #[inline]
508    pub fn visual_clip_rect(&self) -> Option<Rect> {
509        self.clip_rect().or_else(|| {
510            (self.isolation.group_opacity || self.isolation.explicit_offscreen)
511                .then_some(self.local_bounds)
512        })
513    }
514
515    /// The render effect applied to the layer's content, if any.
516    pub fn effect(&self) -> Option<&RenderEffect> {
517        self.graphics_layer.render_effect.as_ref()
518    }
519
520    /// The effect applied to what lies behind the layer, if any.
521    pub fn backdrop(&self) -> Option<&RenderEffect> {
522        self.graphics_layer.backdrop_effect.as_ref()
523    }
524
525    /// Alpha of the layer from 0.0 (transparent) to 1.0 (opaque).
526    pub fn opacity(&self) -> f32 {
527        self.graphics_layer.alpha
528    }
529
530    /// How the layer composites onto what lies beneath it.
531    pub fn blend_mode(&self) -> BlendMode {
532        self.graphics_layer.blend_mode
533    }
534
535    /// The color filter applied to the layer's content, if any.
536    pub fn color_filter(&self) -> Option<&ColorFilter> {
537        self.graphics_layer.color_filter.as_ref()
538    }
539
540    /// Hash of everything the layer draws into its target: bounds, clip, alpha, children and their
541    /// placement. Equal hashes mean cached pixels are still valid. Computed on first use.
542    pub fn target_content_hash(&self) -> u64 {
543        self.raster_cache_hashes().target_content
544    }
545
546    /// Hash of the layer's content like `target_content_hash`, except that the translated content
547    /// offset is left out, so the hash stays equal while the content only moves.
548    pub fn motion_source_content_hash(&self) -> u64 {
549        crate::graph_hash::layer_motion_source_content_hash(self)
550    }
551
552    /// Hash of the layer's render effect, for keying cached effect output.
553    pub fn effect_hash(&self) -> u64 {
554        self.raster_cache_hashes().effect
555    }
556
557    fn raster_cache_hashes(&self) -> LayerRasterCacheHashes {
558        if let Some(hashes) = self.cache_hashes.get() {
559            return hashes;
560        }
561        let hashes = crate::graph_hash::layer_raster_cache_hashes(self);
562        self.cache_hashes.set(Some(hashes));
563        hashes
564    }
565
566    /// Forgets the raster cache hashes of this layer and every layer beneath
567    /// it, after a change the scene builder did not make: they are computed
568    /// again when asked for.
569    pub fn forget_raster_cache_hashes(&mut self) {
570        self.forget_own_raster_cache_hashes();
571        for child in &mut self.children {
572            if let RenderNode::Layer(child) = child {
573                child.forget_raster_cache_hashes();
574            }
575        }
576    }
577
578    /// Forgets this layer's own raster cache hashes and takes new change
579    /// numbers: a property its hash covers changed.
580    pub(crate) fn forget_own_raster_cache_hashes(&mut self) {
581        self.cache_hashes.set(None);
582        self.change = LayerChange::next();
583        self.own_change = LayerChange::next();
584    }
585
586    /// Records that scene update `update` changed a layer beneath this one,
587    /// which it holds in its place: the hashes and the subtree's change
588    /// number go, the layer's own number stays.
589    pub(crate) fn note_descendant_change(&mut self, update: u64) {
590        self.cache_hashes.set(None);
591        self.change = LayerChange::next();
592        self.content_changes.note(update);
593    }
594
595    /// Records that scene update `update` changed what this layer draws, and
596    /// forgets its hashes.
597    pub(crate) fn note_content_change(&mut self, update: u64) {
598        self.forget_own_raster_cache_hashes();
599        self.content_changes.note(update);
600    }
601
602    /// Whether what this layer draws changed in scene update `update` and in
603    /// the one before it. A raster cache keyed on its content would miss
604    /// again on the next frame, so a renderer neither hashes nor caches it.
605    pub fn content_churns(&self, update: u64) -> bool {
606        self.content_changes.run >= 2 && self.content_changes.last_update == update
607    }
608}
609
610/// A child of a layer in the render graph.
611#[derive(Clone)]
612pub enum RenderNode {
613    /// A single draw or text primitive.
614    Primitive(PrimitiveEntry),
615    /// A whole draw command's primitives as one node. A heavy canvas records
616    /// thousands of primitives per frame; wrapping each in its own
617    /// [`RenderNode`] made the graph rebuild move every one of them twice and
618    /// free seventeen thousand nodes per frame on a stress scene. The run
619    /// keeps the recorded vector intact — semantically it is exactly that
620    /// many consecutive `Primitive` draw entries with no per-primitive clip.
621    DrawRun(DrawRunNode),
622    /// A nested layer.
623    Layer(Box<LayerNode>),
624    /// A layout node drawn in this layer instead of a layer of its own.
625    Inline(Box<InlineNode>),
626}
627
628impl RenderNode {
629    /// The phase the node paints in among its layer's own draws: a
630    /// primitive's or run's, or a draw group layer's. `None` for the layer
631    /// of a layout node, which is a child of the layer.
632    pub fn draw_phase(&self) -> Option<PrimitivePhase> {
633        match self {
634            Self::Primitive(entry) => Some(entry.phase),
635            Self::DrawRun(run) => Some(run.phase),
636            Self::Layer(layer) => layer.draw_phase,
637            Self::Inline(_) => None,
638        }
639    }
640
641    fn draws_after_children(&self) -> bool {
642        self.draw_phase() == Some(PrimitivePhase::AfterChildren)
643    }
644}
645
646/// Visits `children`, a layer's child list, in the order they draw: in list
647/// order, save that the draws after the children wait until every other
648/// node has drawn.
649pub fn for_each_in_draw_order(children: &[RenderNode], mut visit: impl FnMut(&RenderNode)) {
650    let mut first_deferred = children.len();
651    for (index, child) in children.iter().enumerate() {
652        if child.draws_after_children() {
653            first_deferred = first_deferred.min(index);
654        } else {
655            visit(child);
656        }
657    }
658    children[first_deferred..]
659        .iter()
660        .filter(|child| child.draws_after_children())
661        .for_each(visit);
662}
663
664/// Stable identity of one placement of a draw command: its layout node,
665/// command index and placement pass. A `WithContent` command shares one CPU
666/// recording between its two runs, while each placement retains independent
667/// renderer state under this identity.
668#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
669pub struct DrawCommandId {
670    /// Layout node that owns the draw command.
671    pub node_id: NodeId,
672    /// Index of the command among the draw commands of the node.
673    pub command_index: u32,
674    /// Whether the command draws behind the node's content or over it.
675    pub placement: DrawPlacement,
676}
677
678/// A run of recorded draw primitives from one draw command, kept together as one node.
679#[derive(Clone, Debug, PartialEq)]
680pub struct DrawRunNode {
681    /// Whether the run draws before or after the children of its layer.
682    pub phase: PrimitivePhase,
683    /// Which draw command recorded these primitives. `None` only for runs
684    /// with no per-command provenance (hand-built tests).
685    pub command: Option<DrawCommandId>,
686    /// The recording registry keeps a handle per layout node and command,
687    /// so this recording's buffers survive the draw run being dropped and
688    /// the command can record into them on a later update. Nothing mutates
689    /// a recording after construction, which is
690    /// what makes sharing sound.
691    pub recording: Rc<CommandRecording>,
692    /// The segments of the recording this run draws: one placement's part
693    /// of a with-content command, or the whole recording.
694    pub segments: Range<u32>,
695    /// Content facts consumers keep asking per frame, answered while
696    /// recording and never by rescanning.
697    pub summary: DrawRunSummary,
698}
699
700/// What a run contains, answered while recording.
701pub type DrawRunSummary = RecordingSummary;
702
703impl DrawRunNode {
704    /// Creates a run for `primitives` that has no draw command provenance.
705    pub fn new(phase: PrimitivePhase, primitives: Vec<DrawPrimitive>) -> Self {
706        Self::for_command(phase, None, primitives)
707    }
708
709    /// Creates a run for `primitives` recorded by the draw command `command`.
710    pub fn for_command(
711        phase: PrimitivePhase,
712        command: Option<DrawCommandId>,
713        primitives: Vec<DrawPrimitive>,
714    ) -> Self {
715        let recording = CommandRecording::from_primitives(primitives);
716        let segments = recording.all_segments();
717        Self::for_command_shared(phase, command, Rc::new(recording), segments)
718    }
719
720    /// Creates a run over `segments` of an existing shared `recording`, without copying it.
721    pub fn for_command_shared(
722        phase: PrimitivePhase,
723        command: Option<DrawCommandId>,
724        recording: Rc<CommandRecording>,
725        segments: Range<u32>,
726    ) -> Self {
727        let summary = recording.summary_in(&segments);
728        Self {
729            phase,
730            command,
731            recording,
732            segments,
733            summary,
734        }
735    }
736
737    /// The run's primitives, materialised from the recording in order.
738    pub fn primitives(&self) -> impl Iterator<Item = DrawPrimitive> + '_ {
739        self.recording.primitives(self.segments.clone())
740    }
741
742    /// Runs `visit` on the run's primitives in order, as [`Self::primitives`]
743    /// yields them, without the iterator's step between segments.
744    pub fn for_each_primitive(&self, visit: impl FnMut(DrawPrimitive)) {
745        self.recording
746            .for_each_primitive(self.segments.clone(), visit);
747    }
748
749    /// The rect every entry of the run can reach.
750    pub fn coverage_rects(&self) -> impl Iterator<Item = Rect> + '_ {
751        self.recording.coverage_rects(self.segments.clone())
752    }
753
754    /// Number of primitives in the run.
755    pub fn len(&self) -> usize {
756        self.recording.len_in(&self.segments)
757    }
758
759    /// Whether the run holds no primitives.
760    pub fn is_empty(&self) -> bool {
761        self.recording.is_empty_in(&self.segments)
762    }
763}
764
765/// The scene as a tree of layers that a renderer draws.
766#[derive(Clone)]
767pub struct RenderGraph {
768    /// The top layer; every other node of the graph is beneath it.
769    pub root: LayerNode,
770    /// How many scene updates the graph took: the number of the latest.
771    pub update: u64,
772    /// The way to the layer the last scene updates changed alone, for the
773    /// next update that changes it alone.
774    pub(crate) retained_path: Option<crate::scene_builder::RetainedPath>,
775    // The marks of the last walk to the graph's dirty layers.
776    pub(crate) dirty_ancestry: crate::scene_builder::DirtyAncestry,
777}
778
779impl RenderGraph {
780    /// Creates a graph around `root` at update zero.
781    pub fn new(root: LayerNode) -> Self {
782        Self {
783            root,
784            update: 0,
785            retained_path: None,
786            dirty_ancestry: crate::scene_builder::DirtyAncestry::default(),
787        }
788    }
789
790    /// Number of nodes in the graph. A layer counts once and each primitive of a draw run counts on
791    /// its own.
792    pub fn node_count(&self) -> usize {
793        fn count_nodes(nodes: &[RenderNode]) -> usize {
794            nodes
795                .iter()
796                .map(|child| match child {
797                    RenderNode::Primitive(_) => 1,
798                    RenderNode::DrawRun(run) => run.len(),
799                    RenderNode::Layer(child_layer) => 1 + count_nodes(&child_layer.children),
800                    RenderNode::Inline(inline) => 1 + count_nodes(&inline.content),
801                })
802                .sum::<usize>()
803        }
804
805        1 + count_nodes(&self.root.children)
806    }
807
808    /// Estimated heap memory held beneath the root layer, in bytes, for memory reports.
809    pub fn heap_bytes(&self) -> usize {
810        layer_heap_bytes(&self.root)
811    }
812
813    /// Replaces the set with the graph's visual observation owners, retaining its capacity.
814    pub fn collect_retained_visual_observation_nodes(&self, nodes: &mut HashSet<NodeId>) {
815        fn collect(layer: &LayerNode, nodes: &mut HashSet<NodeId>) {
816            if let Some(node_id) = layer.node_id {
817                nodes.insert(node_id);
818            }
819            for child in &layer.children {
820                match child {
821                    RenderNode::DrawRun(run) => {
822                        if let Some(command) = run.command {
823                            nodes.insert(command.node_id);
824                        }
825                    }
826                    RenderNode::Layer(child) => collect(child, nodes),
827                    RenderNode::Inline(inline) => {
828                        nodes.insert(inline.node_id);
829                    }
830                    RenderNode::Primitive(_) => {}
831                }
832            }
833        }
834
835        nodes.clear();
836        collect(&self.root, nodes);
837    }
838}
839
840fn collect_painted_text(nodes: &[RenderNode], out: &mut Vec<String>) {
841    for child in nodes {
842        match child {
843            RenderNode::Primitive(primitive) => {
844                if let PrimitiveNode::Text(text) = &primitive.node {
845                    out.push(text.text.text.clone());
846                }
847            }
848            RenderNode::Layer(child) => collect_painted_text(&child.children, out),
849            RenderNode::Inline(inline) => collect_painted_text(&inline.content, out),
850            RenderNode::DrawRun(run) => {
851                for primitive in run.primitives() {
852                    if let DrawPrimitive::Text(text) = primitive {
853                        out.push(text.text.to_string());
854                    }
855                }
856            }
857        }
858    }
859}
860
861// Whether `nodes`, a layer's children, put nothing outside `bounds` of it.
862fn nodes_draw_within(nodes: &[RenderNode], bounds: Rect) -> bool {
863    nodes.iter().all(|child| match child {
864        RenderNode::DrawRun(run) => {
865            !run.summary.has_shadow
866                && run
867                    .recording
868                    .bounds()
869                    .is_none_or(|drawn| rect_within(drawn, bounds))
870        }
871        RenderNode::Primitive(entry) => match &entry.node {
872            PrimitiveNode::Text(text) => text.draws_within(bounds),
873            PrimitiveNode::Draw(_) => false,
874        },
875        RenderNode::Layer(layer) => layer.draws_within_parent(bounds),
876        RenderNode::Inline(inline) => inline.draws_within_parent(bounds),
877    })
878}
879
880fn inflate_rect(rect: Rect, by: f32) -> Rect {
881    Rect {
882        x: rect.x - by,
883        y: rect.y - by,
884        width: rect.width + by * 2.0,
885        height: rect.height + by * 2.0,
886    }
887}
888
889fn rect_within(inner: Rect, outer: Rect) -> bool {
890    inner.x >= outer.x
891        && inner.y >= outer.y
892        && inner.x + inner.width <= outer.x + outer.width
893        && inner.y + inner.height <= outer.y + outer.height
894}
895
896#[cfg(test)]
897#[path = "tests/graph_tests.rs"]
898mod tests;