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