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::{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    pub(crate) fn replace(&mut self, value: Option<GraphicsLayer>) {
188        match (&mut self.0, value) {
189            (Some(current), Some(value)) => **current = value,
190            (storage, value) => *storage = value.map(Box::new),
191        }
192    }
193}
194
195impl Deref for LayerProperties {
196    type Target = GraphicsLayer;
197
198    fn deref(&self) -> &Self::Target {
199        self.0.as_deref().unwrap_or(&GraphicsLayer::DEFAULT)
200    }
201}
202
203impl DerefMut for LayerProperties {
204    fn deref_mut(&mut self) -> &mut Self::Target {
205        self.0.get_or_insert_with(Box::default)
206    }
207}
208
209impl From<GraphicsLayer> for LayerProperties {
210    fn from(value: GraphicsLayer) -> Self {
211        Self(Some(Box::new(value)))
212    }
213}
214
215impl From<Option<GraphicsLayer>> for LayerProperties {
216    fn from(value: Option<GraphicsLayer>) -> Self {
217        Self(value.map(Box::new))
218    }
219}
220
221impl PartialEq for LayerProperties {
222    fn eq(&self, other: &Self) -> bool {
223        **self == **other
224    }
225}
226
227/// A node of the render graph that groups content under one set of layer properties: bounds,
228/// transform, clip, graphics layer and children.
229#[derive(Clone)]
230pub struct LayerNode {
231    /// Layout node of this layer, or `None` for the root and the synthetic wrapper layers.
232    pub node_id: Option<NodeId>,
233    /// Set on the synthetic layer that holds a node's outer draws, those chained
234    /// before its graphics layer, around that node's own layer. Scene updates
235    /// address the node through this id because the node's layer has none of the
236    /// outer draws and the wrapper has no node id of its own.
237    pub wraps: Option<NodeId>,
238    /// The bounds of the node's layer, where its clip cuts, its transforms
239    /// pivot and an offscreen pass draws it.
240    pub local_bounds: Rect,
241    /// The node's own rect, `(0, 0, size)`, where its pointer handlers are,
242    /// when it differs from `local_bounds`: a clip or graphics layer that
243    /// wraps a coordinator a later padding or offset moves bounds the layer
244    /// there instead, as in Compose. `None` when the two agree.
245    pub node_bounds: Option<Rect>,
246    /// Maps the layer's local space to its parent's content space, with the placement, the graphics
247    /// layer transform and the parent's content offset applied.
248    pub transform_to_parent: ProjectiveTransform,
249    /// Offset in logical pixels of the node's content box from the node origin, for example from
250    /// padding. Children are placed relative to it.
251    pub content_offset: Point,
252    /// Whether the layer is inside a motion context such as an animated scroll, where a renderer
253    /// must not snap its content rigidly to the pixel grid.
254    pub motion_context_animated: bool,
255    /// Whether the layer moves its content as one translated block, as a scroll container does,
256    /// so a renderer may snap that content to the pixel grid as a whole.
257    pub translated_content_context: bool,
258    /// Translation in logical pixels of the content when `translated_content_context` is set.
259    pub translated_content_offset: Point,
260    /// Where layout placed this layer within its parent's content, before the
261    /// parent's content offset. Scene updates add these up from the root, with
262    /// each layer's content offset and graphics-layer translation, to find the
263    /// window origin of a subtree they rebuild.
264    pub origin_in_parent: Point,
265    /// The graphics layer properties; they read as defaults when none were set.
266    pub graphics_layer: LayerProperties,
267    /// Whether the layer cuts its content to `local_bounds`.
268    pub clip_to_bounds: bool,
269    /// Pointer handling of the node, or `None` when the node has none.
270    pub hit_test: Option<HitTestNode>,
271    /// Whether this layer or a layer beneath it has a hit test node. Scene building keeps it
272    /// current.
273    pub has_hit_targets: bool,
274    /// Whether this subtree publishes live window origins (a text field's
275    /// popup anchor, a scroll container's viewport rect). Those sinks are
276    /// written during a full lowering, so the scroll fast path may translate
277    /// a retained subtree in place only when this is false.
278    pub has_origin_sinks: bool,
279    /// Whether everything this layer and its subtree draw lies within its
280    /// `local_bounds`, give or take [`CONTAINED_DRAW_SLACK`], so a renderer
281    /// may skip the whole subtree where those bounds are clipped away. Scene
282    /// building keeps it current; `false` promises nothing.
283    pub draws_within_bounds: bool,
284    /// The reasons this layer is drawn through its own offscreen buffer.
285    pub isolation: IsolationReasons,
286    /// Whether a renderer may keep this layer's pixels between frames.
287    pub cache_policy: CachePolicy,
288    /// The raster cache hashes, computed the first time a renderer asks and
289    /// kept until the layer or a layer beneath it changes.
290    pub cache_hashes: Cell<Option<LayerRasterCacheHashes>>,
291    /// The scene updates that changed what the layer draws.
292    pub content_changes: ContentChanges,
293    /// The number the layer took when it or a layer beneath it last changed.
294    pub change: LayerChange,
295    /// Content in draw order: primitives, draw runs and child layers.
296    pub children: Vec<RenderNode>,
297}
298
299/// A number a layer takes when it is made and whenever it or a layer beneath
300/// it changes, as its raster cache hashes are forgotten: a renderer that
301/// kept what it built from a subtree knows from an equal number that nothing
302/// in it changed since, save the subtree's placement in its parent.
303#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
304pub struct LayerChange(u32);
305
306impl LayerChange {
307    /// A number no layer took in the last 2^32 changes.
308    fn next() -> Self {
309        static LAST: AtomicU32 = AtomicU32::new(0);
310        Self(LAST.fetch_add(1, Ordering::Relaxed).wrapping_add(1))
311    }
312}
313
314impl Default for LayerChange {
315    fn default() -> Self {
316        Self::next()
317    }
318}
319
320/// The last scene update that changed what a layer draws, and how many
321/// updates in a row up to it did.
322#[derive(Clone, Copy, Debug, Default)]
323pub struct ContentChanges {
324    last_update: u64,
325    run: u32,
326}
327
328impl ContentChanges {
329    fn note(&mut self, update: u64) {
330        if self.run > 0 && self.last_update == update {
331            return;
332        }
333        self.run = if self.run > 0 && self.last_update + 1 == update {
334            self.run + 1
335        } else {
336            1
337        };
338        self.last_update = update;
339    }
340}
341
342impl Default for LayerNode {
343    fn default() -> Self {
344        Self {
345            node_id: None,
346            wraps: None,
347            local_bounds: Rect {
348                x: 0.0,
349                y: 0.0,
350                width: 0.0,
351                height: 0.0,
352            },
353            node_bounds: None,
354            transform_to_parent: ProjectiveTransform::identity(),
355            content_offset: Point::default(),
356            motion_context_animated: false,
357            translated_content_context: false,
358            translated_content_offset: Point::default(),
359            origin_in_parent: Point::default(),
360            graphics_layer: LayerProperties::default(),
361            clip_to_bounds: false,
362            hit_test: None,
363            has_hit_targets: false,
364            has_origin_sinks: false,
365            draws_within_bounds: false,
366            isolation: IsolationReasons::default(),
367            cache_policy: CachePolicy::None,
368            cache_hashes: Cell::new(None),
369            content_changes: ContentChanges::default(),
370            change: LayerChange::default(),
371            children: Vec::new(),
372        }
373    }
374}
375
376/// How far past its bounds a layer that draws within them may still put
377/// pixels: glyph and edge antialiasing.
378pub const CONTAINED_DRAW_SLACK: f32 = 1.0;
379
380impl LayerNode {
381    /// Every string this layer and the layers below it paint, in draw order.
382    pub fn painted_text(&self) -> Vec<String> {
383        let mut out = Vec::new();
384        self.collect_painted_text(&mut out);
385        out
386    }
387
388    fn collect_painted_text(&self, out: &mut Vec<String>) {
389        for child in &self.children {
390            match child {
391                RenderNode::Primitive(primitive) => {
392                    if let PrimitiveNode::Text(text) = &primitive.node {
393                        out.push(text.text.text.clone());
394                    }
395                }
396                RenderNode::Layer(child) => child.collect_painted_text(out),
397                RenderNode::DrawRun(run) => {
398                    for primitive in run.primitives() {
399                        if let DrawPrimitive::Text(text) = primitive {
400                            out.push(text.text.to_string());
401                        }
402                    }
403                }
404            }
405        }
406    }
407
408    /// Whether this layer's content, as [`LayerNode::draws_within_bounds`]
409    /// describes it, stays within its bounds: it clips to them, or its draws,
410    /// its texts and its children placed where they are all fit inside.
411    pub fn content_draws_within_bounds(&self) -> bool {
412        if self.visual_clip_rect().is_some() {
413            return true;
414        }
415        let bounds = inflate_rect(self.local_bounds, CONTAINED_DRAW_SLACK);
416        self.children.iter().all(|child| match child {
417            RenderNode::DrawRun(run) => {
418                !run.summary.has_shadow
419                    && run
420                        .recording
421                        .bounds()
422                        .is_none_or(|drawn| rect_within(drawn, bounds))
423            }
424            RenderNode::Primitive(entry) => match &entry.node {
425                PrimitiveNode::Text(text) => text.draws_within(bounds),
426                PrimitiveNode::Draw(_) => false,
427            },
428            RenderNode::Layer(layer) => layer.draws_within_parent(bounds),
429        })
430    }
431
432    pub(crate) fn refresh_child_facts(&mut self) {
433        self.has_hit_targets = self.hit_test.is_some()
434            || self.children.iter().any(|child| match child {
435                RenderNode::Layer(child_layer) => child_layer.has_hit_targets,
436                RenderNode::Primitive(_) | RenderNode::DrawRun(_) => false,
437            });
438        self.draws_within_bounds = self.content_draws_within_bounds();
439    }
440
441    /// Whether this layer, drawn as a child, puts nothing outside `bounds`:
442    /// it draws within its own bounds, casts no shadow, applies no effect,
443    /// and its bounds placed in its parent lie inside.
444    fn draws_within_parent(&self, bounds: Rect) -> bool {
445        self.draws_within_bounds
446            && self.graphics_layer.shadow_elevation <= 0.0
447            && self.effect().is_none()
448            && self.backdrop().is_none()
449            && rect_within(
450                quad_bounds(
451                    self.transform_to_parent
452                        .map_rect(inflate_rect(self.local_bounds, CONTAINED_DRAW_SLACK)),
453                ),
454                bounds,
455            )
456    }
457
458    /// The node's own rect, where its pointer handlers are.
459    pub fn node_rect(&self) -> Rect {
460        self.node_bounds.unwrap_or(self.local_bounds)
461    }
462
463    /// The rectangle that cuts the layer's content and pointer hits: its bounds when the layer
464    /// clips to bounds or its graphics layer clips, otherwise `None`.
465    pub fn clip_rect(&self) -> Option<Rect> {
466        (self.clip_to_bounds || self.graphics_layer.clip).then_some(self.local_bounds)
467    }
468
469    /// Where the layer's own shadow is cut: its bounds when it clips its
470    /// content to them. A graphics-layer clip leaves the shadow whole.
471    pub fn shadow_clip(&self) -> Option<Rect> {
472        self.clip_to_bounds.then_some(self.local_bounds)
473    }
474
475    /// Where the layer's drawing is cut: its clip or, when it composites
476    /// through an offscreen buffer for its alpha or by request, its bounds,
477    /// as Compose's layer-sized buffer cuts it. Hit testing takes only
478    /// [`Self::clip_rect`]: alpha hides nothing from a finger.
479    pub fn visual_clip_rect(&self) -> Option<Rect> {
480        self.clip_rect().or_else(|| {
481            (self.isolation.group_opacity || self.isolation.explicit_offscreen)
482                .then_some(self.local_bounds)
483        })
484    }
485
486    /// The render effect applied to the layer's content, if any.
487    pub fn effect(&self) -> Option<&RenderEffect> {
488        self.graphics_layer.render_effect.as_ref()
489    }
490
491    /// The effect applied to what lies behind the layer, if any.
492    pub fn backdrop(&self) -> Option<&RenderEffect> {
493        self.graphics_layer.backdrop_effect.as_ref()
494    }
495
496    /// Alpha of the layer from 0.0 (transparent) to 1.0 (opaque).
497    pub fn opacity(&self) -> f32 {
498        self.graphics_layer.alpha
499    }
500
501    /// How the layer composites onto what lies beneath it.
502    pub fn blend_mode(&self) -> BlendMode {
503        self.graphics_layer.blend_mode
504    }
505
506    /// The color filter applied to the layer's content, if any.
507    pub fn color_filter(&self) -> Option<&ColorFilter> {
508        self.graphics_layer.color_filter.as_ref()
509    }
510
511    /// Hash of everything the layer draws into its target: bounds, clip, alpha, children and their
512    /// placement. Equal hashes mean cached pixels are still valid. Computed on first use.
513    pub fn target_content_hash(&self) -> u64 {
514        self.raster_cache_hashes().target_content
515    }
516
517    /// Hash of the layer's content like `target_content_hash`, except that the translated content
518    /// offset is left out, so the hash stays equal while the content only moves.
519    pub fn motion_source_content_hash(&self) -> u64 {
520        crate::graph_hash::layer_motion_source_content_hash(self)
521    }
522
523    /// Hash of the layer's render effect, for keying cached effect output.
524    pub fn effect_hash(&self) -> u64 {
525        self.raster_cache_hashes().effect
526    }
527
528    fn raster_cache_hashes(&self) -> LayerRasterCacheHashes {
529        if let Some(hashes) = self.cache_hashes.get() {
530            return hashes;
531        }
532        let hashes = crate::graph_hash::layer_raster_cache_hashes(self);
533        self.cache_hashes.set(Some(hashes));
534        hashes
535    }
536
537    /// Forgets the raster cache hashes of this layer and every layer beneath
538    /// it, after a change the scene builder did not make: they are computed
539    /// again when asked for.
540    pub fn forget_raster_cache_hashes(&mut self) {
541        self.forget_own_raster_cache_hashes();
542        for child in &mut self.children {
543            if let RenderNode::Layer(child) = child {
544                child.forget_raster_cache_hashes();
545            }
546        }
547    }
548
549    /// Forgets this layer's own raster cache hashes and takes a new change
550    /// number: a property its hash covers, or a layer beneath it, changed.
551    pub(crate) fn forget_own_raster_cache_hashes(&mut self) {
552        self.cache_hashes.set(None);
553        self.change = LayerChange::next();
554    }
555
556    /// Records that scene update `update` changed what this layer draws, and
557    /// forgets its hashes.
558    pub(crate) fn note_content_change(&mut self, update: u64) {
559        self.forget_own_raster_cache_hashes();
560        self.content_changes.note(update);
561    }
562
563    /// Whether what this layer draws changed in scene update `update` and in
564    /// the one before it. A raster cache keyed on its content would miss
565    /// again on the next frame, so a renderer neither hashes nor caches it.
566    pub fn content_churns(&self, update: u64) -> bool {
567        self.content_changes.run >= 2 && self.content_changes.last_update == update
568    }
569}
570
571/// A child of a layer in the render graph.
572#[derive(Clone)]
573pub enum RenderNode {
574    /// A single draw or text primitive.
575    Primitive(PrimitiveEntry),
576    /// A whole draw command's primitives as one node. A heavy canvas records
577    /// thousands of primitives per frame; wrapping each in its own
578    /// [`RenderNode`] made the graph rebuild move every one of them twice and
579    /// free seventeen thousand nodes per frame on a stress scene. The run
580    /// keeps the recorded vector intact — semantically it is exactly that
581    /// many consecutive `Primitive` draw entries with no per-primitive clip.
582    DrawRun(DrawRunNode),
583    /// A nested layer.
584    Layer(Box<LayerNode>),
585}
586
587/// Stable identity of one placement of a draw command: its layout node,
588/// command index and placement pass. A `WithContent` command shares one CPU
589/// recording between its two runs, while each placement retains independent
590/// renderer state under this identity.
591#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
592pub struct DrawCommandId {
593    /// Layout node that owns the draw command.
594    pub node_id: NodeId,
595    /// Index of the command among the draw commands of the node.
596    pub command_index: u32,
597    /// Whether the command draws behind the node's content or over it.
598    pub placement: DrawPlacement,
599}
600
601/// A run of recorded draw primitives from one draw command, kept together as one node.
602#[derive(Clone, Debug, PartialEq)]
603pub struct DrawRunNode {
604    /// Whether the run draws before or after the children of its layer.
605    pub phase: PrimitivePhase,
606    /// Which draw command recorded these primitives. `None` only for runs
607    /// with no per-command provenance (hand-built tests).
608    pub command: Option<DrawCommandId>,
609    /// The recording registry keeps a handle per layout node and command,
610    /// so this recording's buffers survive the draw run being dropped and
611    /// the command can record into them on a later update. Nothing mutates
612    /// a recording after construction, which is
613    /// what makes sharing sound.
614    pub recording: Rc<CommandRecording>,
615    /// The segments of the recording this run draws: one placement's part
616    /// of a with-content command, or the whole recording.
617    pub segments: Range<u32>,
618    /// Content facts consumers keep asking per frame, answered while
619    /// recording and never by rescanning.
620    pub summary: DrawRunSummary,
621}
622
623/// What a run contains, answered while recording.
624pub type DrawRunSummary = RecordingSummary;
625
626impl DrawRunNode {
627    /// Creates a run for `primitives` that has no draw command provenance.
628    pub fn new(phase: PrimitivePhase, primitives: Vec<DrawPrimitive>) -> Self {
629        Self::for_command(phase, None, primitives)
630    }
631
632    /// Creates a run for `primitives` recorded by the draw command `command`.
633    pub fn for_command(
634        phase: PrimitivePhase,
635        command: Option<DrawCommandId>,
636        primitives: Vec<DrawPrimitive>,
637    ) -> Self {
638        let recording = CommandRecording::from_primitives(primitives);
639        let segments = recording.all_segments();
640        Self::for_command_shared(phase, command, Rc::new(recording), segments)
641    }
642
643    /// Creates a run over `segments` of an existing shared `recording`, without copying it.
644    pub fn for_command_shared(
645        phase: PrimitivePhase,
646        command: Option<DrawCommandId>,
647        recording: Rc<CommandRecording>,
648        segments: Range<u32>,
649    ) -> Self {
650        let summary = recording.summary_in(&segments);
651        Self {
652            phase,
653            command,
654            recording,
655            segments,
656            summary,
657        }
658    }
659
660    /// The run's primitives, materialised from the recording in order.
661    pub fn primitives(&self) -> impl Iterator<Item = DrawPrimitive> + '_ {
662        self.recording.primitives(self.segments.clone())
663    }
664
665    /// The rect every entry of the run can reach.
666    pub fn coverage_rects(&self) -> impl Iterator<Item = Rect> + '_ {
667        self.recording.coverage_rects(self.segments.clone())
668    }
669
670    /// Number of primitives in the run.
671    pub fn len(&self) -> usize {
672        self.recording.len_in(&self.segments)
673    }
674
675    /// Whether the run holds no primitives.
676    pub fn is_empty(&self) -> bool {
677        self.recording.is_empty_in(&self.segments)
678    }
679}
680
681/// The scene as a tree of layers that a renderer draws.
682#[derive(Clone)]
683pub struct RenderGraph {
684    /// The top layer; every other node of the graph is beneath it.
685    pub root: LayerNode,
686    /// How many scene updates the graph took: the number of the latest.
687    pub update: u64,
688}
689
690impl RenderGraph {
691    /// Creates a graph around `root` at update zero.
692    pub fn new(root: LayerNode) -> Self {
693        Self { root, update: 0 }
694    }
695
696    /// Number of nodes in the graph. A layer counts once and each primitive of a draw run counts on
697    /// its own.
698    pub fn node_count(&self) -> usize {
699        fn count_layer(layer: &LayerNode) -> usize {
700            1 + layer
701                .children
702                .iter()
703                .map(|child| match child {
704                    RenderNode::Primitive(_) => 1,
705                    RenderNode::DrawRun(run) => run.len(),
706                    RenderNode::Layer(child_layer) => count_layer(child_layer),
707                })
708                .sum::<usize>()
709        }
710
711        count_layer(&self.root)
712    }
713
714    /// Estimated heap memory held beneath the root layer, in bytes, for memory reports.
715    pub fn heap_bytes(&self) -> usize {
716        layer_heap_bytes(&self.root)
717    }
718
719    /// Replaces the set with the graph's visual observation owners, retaining its capacity.
720    pub fn collect_retained_visual_observation_nodes(&self, nodes: &mut HashSet<NodeId>) {
721        fn collect(layer: &LayerNode, nodes: &mut HashSet<NodeId>) {
722            if let Some(node_id) = layer.node_id {
723                nodes.insert(node_id);
724            }
725            for child in &layer.children {
726                match child {
727                    RenderNode::DrawRun(run) => {
728                        if let Some(command) = run.command {
729                            nodes.insert(command.node_id);
730                        }
731                    }
732                    RenderNode::Layer(child) => collect(child, nodes),
733                    RenderNode::Primitive(_) => {}
734                }
735            }
736        }
737
738        nodes.clear();
739        collect(&self.root, nodes);
740    }
741}
742
743fn layer_heap_bytes(layer: &LayerNode) -> usize {
744    size_of::<RenderNode>() * layer.children.capacity()
745        + layer
746            .graphics_layer
747            .0
748            .as_ref()
749            .map_or(0, |_| size_of::<GraphicsLayer>())
750        + layer
751            .children
752            .iter()
753            .map(render_node_heap_bytes)
754            .sum::<usize>()
755}
756
757fn render_node_heap_bytes(node: &RenderNode) -> usize {
758    match node {
759        RenderNode::Primitive(entry) => primitive_entry_heap_bytes(entry),
760        RenderNode::DrawRun(run) => {
761            run.recording.pod_heap_bytes()
762                + std::mem::size_of_val(run.recording.others())
763                + run
764                    .recording
765                    .others()
766                    .iter()
767                    .map(draw_primitive_heap_bytes)
768                    .sum::<usize>()
769        }
770        RenderNode::Layer(layer) => size_of::<LayerNode>() + layer_heap_bytes(layer),
771    }
772}
773
774fn primitive_entry_heap_bytes(entry: &PrimitiveEntry) -> usize {
775    match &entry.node {
776        PrimitiveNode::Draw(draw) => {
777            size_of::<DrawPrimitiveNode>() + draw_primitive_heap_bytes(&draw.primitive)
778        }
779        PrimitiveNode::Text(text) => {
780            size_of::<TextPrimitiveNode>() + annotated_string_heap_bytes(&text.text)
781        }
782    }
783}
784
785fn draw_primitive_heap_bytes(primitive: &DrawPrimitive) -> usize {
786    match primitive {
787        DrawPrimitive::Content
788        | DrawPrimitive::Rect { .. }
789        | DrawPrimitive::RoundRect { .. }
790        | DrawPrimitive::Arc { .. }
791        | DrawPrimitive::Line { .. }
792        | DrawPrimitive::Trapezoid { .. } => 0,
793        DrawPrimitive::Blend { primitive, .. } => {
794            size_of::<DrawPrimitive>() + draw_primitive_heap_bytes(primitive)
795        }
796        DrawPrimitive::Image { .. } => 0,
797        DrawPrimitive::Text(text) => {
798            size_of::<cranpose_ui_graphics::TextPrimitive>()
799                + text.text.len()
800                + text
801                    .style
802                    .font_family
803                    .as_ref()
804                    .map_or(0, std::string::String::capacity)
805        }
806        DrawPrimitive::Shadow(shadow) => shadow_primitive_heap_bytes(shadow),
807    }
808}
809
810fn shadow_primitive_heap_bytes(shadow: &ShadowPrimitive) -> usize {
811    match shadow {
812        ShadowPrimitive::Drop { shape, .. } => {
813            size_of::<DrawPrimitive>() + draw_primitive_heap_bytes(shape)
814        }
815        ShadowPrimitive::Inner { fill, cutout, .. } => {
816            size_of::<DrawPrimitive>() * 2
817                + draw_primitive_heap_bytes(fill)
818                + draw_primitive_heap_bytes(cutout)
819        }
820    }
821}
822
823fn annotated_string_heap_bytes(text: &AnnotatedString) -> usize {
824    text.text.capacity()
825        + text.span_styles.capacity() * size_of::<usize>() * 2
826        + text.paragraph_styles.capacity() * size_of::<usize>() * 2
827        + text.string_annotations.capacity() * size_of::<usize>() * 2
828        + text.link_annotations.capacity() * size_of::<usize>() * 2
829        + text
830            .string_annotations
831            .iter()
832            .map(|annotation| {
833                annotation.item.tag.capacity() + annotation.item.annotation.capacity()
834            })
835            .sum::<usize>()
836        + text
837            .link_annotations
838            .iter()
839            .map(|annotation| match &annotation.item {
840                cranpose_ui::text::LinkAnnotation::Url(url) => url.capacity(),
841                cranpose_ui::text::LinkAnnotation::Clickable { tag, .. } => tag.capacity(),
842            })
843            .sum::<usize>()
844}
845
846fn inflate_rect(rect: Rect, by: f32) -> Rect {
847    Rect {
848        x: rect.x - by,
849        y: rect.y - by,
850        width: rect.width + by * 2.0,
851        height: rect.height + by * 2.0,
852    }
853}
854
855fn rect_within(inner: Rect, outer: Rect) -> bool {
856    inner.x >= outer.x
857        && inner.y >= outer.y
858        && inner.x + inner.width <= outer.x + outer.width
859        && inner.y + inner.height <= outer.y + outer.height
860}
861
862#[cfg(test)]
863#[path = "tests/graph_tests.rs"]
864mod tests;