Skip to main content

cranpose_render_common/
graph.rs

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