Skip to main content

tessera_ui/
render_graph.rs

1//! Render graph fragments and nodes for Tessera.
2//!
3//! ## Usage
4//!
5//! Build per-component render fragments and merge them into a frame graph.
6
7use std::{
8    any::TypeId,
9    sync::{Arc, OnceLock, Weak},
10};
11
12use smallvec::SmallVec;
13
14use crate::{
15    Command, ComputeCommand, DrawCommand, DrawRegion, FloatRect, LocalToWindow, SampleRegion,
16    component_tree::SceneRope,
17    px::{Px, PxPosition, PxRect, PxSize},
18};
19
20/// Local drawing dimensions and their precise placement in the render target.
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct DrawGeometry {
23    /// Unscaled dimensions used by command coordinates, radii and strokes.
24    pub local_size: PxSize,
25    /// Mapping from command-local pixels to render-target pixels.
26    pub local_to_window: LocalToWindow,
27}
28
29impl DrawGeometry {
30    /// Creates drawing geometry with an explicit local-to-target mapping.
31    #[must_use]
32    pub const fn new(local_size: PxSize, local_to_window: LocalToWindow) -> Self {
33        Self {
34            local_size,
35            local_to_window,
36        }
37    }
38
39    /// Creates unscaled geometry at a target position.
40    #[must_use]
41    pub fn from_position_size(position: PxPosition, local_size: PxSize) -> Self {
42        Self::new(
43            local_size,
44            LocalToWindow::new(1.0, position).expect("finite draw position"),
45        )
46    }
47
48    /// Returns the precise target origin.
49    #[must_use]
50    pub fn window_origin(self) -> PxPosition {
51        self.local_to_window.map_point(PxPosition::ZERO)
52    }
53
54    /// Returns target dimensions without integer rounding.
55    #[must_use]
56    pub fn window_size(self) -> [f32; 2] {
57        self.local_to_window.map_size(self.local_size)
58    }
59
60    /// Returns precise target bounds.
61    #[must_use]
62    pub fn window_bounds(self) -> FloatRect {
63        self.padded_window_bounds(crate::PaddingRect::ZERO)
64    }
65
66    /// Maps local padding to target bounds without rounding individual edges.
67    #[must_use]
68    pub fn padded_window_bounds(self, padding: crate::PaddingRect) -> FloatRect {
69        self.local_to_window.map_rect(FloatRect {
70            x: -padding.left.to_f32(),
71            y: -padding.top.to_f32(),
72            width: self.local_size.width.to_f32() + padding.left.to_f32() + padding.right.to_f32(),
73            height: self.local_size.height.to_f32()
74                + padding.top.to_f32()
75                + padding.bottom.to_f32(),
76        })
77    }
78}
79
80/// A single render op inside a fragment.
81#[derive(Clone)]
82pub struct RenderFragmentOp {
83    /// The command to execute.
84    pub command: Command,
85    /// Type identifier used for batching.
86    pub type_id: TypeId,
87    /// Optional size override for this op.
88    pub size_override: Option<PxSize>,
89    /// Optional offset of this op from its node's origin.
90    pub position_override: Option<PxPosition>,
91}
92
93/// A per-component render fragment.
94#[derive(Default, Clone)]
95pub struct RenderFragment {
96    ops: Option<Arc<SmallVec<[RenderFragmentOp; 1]>>>,
97}
98
99impl RenderFragment {
100    /// Returns fragment ops in insertion order.
101    #[must_use]
102    pub fn ops(&self) -> &[RenderFragmentOp] {
103        self.ops.as_deref().map_or(&[], SmallVec::as_slice)
104    }
105
106    /// Returns fragment ops in insertion order for local position overrides.
107    #[must_use]
108    pub fn ops_mut(&mut self) -> &mut [RenderFragmentOp] {
109        self.ops
110            .as_mut()
111            .map_or(&mut [], |ops| Arc::make_mut(ops).as_mut_slice())
112    }
113
114    /// Adds a draw command.
115    pub fn push_draw_command<C: DrawCommand + 'static>(&mut self, command: C) -> u32 {
116        self.push_op(Command::Draw(Box::new(command)), TypeId::of::<C>())
117    }
118
119    /// Adds a compute command.
120    pub fn push_compute_command<C: ComputeCommand + 'static>(&mut self, command: C) -> u32 {
121        self.push_op(Command::Compute(Box::new(command)), TypeId::of::<C>())
122    }
123
124    fn push_op(&mut self, command: Command, type_id: TypeId) -> u32 {
125        let ops = Arc::make_mut(self.ops.get_or_insert_with(Arc::default));
126        let index = ops.len() as u32;
127        ops.push(RenderFragmentOp {
128            command,
129            type_id,
130            size_override: None,
131            position_override: None,
132        });
133        index
134    }
135
136    /// Clears all ops in the fragment.
137    pub fn clear(&mut self) {
138        if let Some(ops) = self.ops.as_mut().and_then(Arc::get_mut) {
139            ops.clear();
140        } else {
141            self.ops = None;
142        }
143    }
144
145    pub(crate) fn shares_ops(&self, other: &Self) -> bool {
146        match (&self.ops, &other.ops) {
147            (Some(a), Some(b)) => Arc::ptr_eq(a, b),
148            (None, None) => true,
149            _ => false,
150        }
151    }
152
153    pub(crate) fn supports_partial_redraw(&self) -> bool {
154        self.ops().iter().all(|op| partial_redraw_safe(&op.command))
155    }
156}
157
158/// A render op after merging into the frame graph.
159#[derive(Clone)]
160pub struct RenderGraphOp {
161    /// The command to execute.
162    pub command: Command,
163    /// Type identifier used for batching and ordering.
164    pub type_id: TypeId,
165    /// Local drawing dimensions and their precise target mapping.
166    pub geometry: DrawGeometry,
167    /// Opacity multiplier applied during record.
168    pub opacity: f32,
169    /// Sequence index used to preserve authoring order.
170    pub sequence_index: usize,
171}
172
173/// Frame-level render graph.
174#[derive(Clone, Default)]
175pub struct RenderGraph {
176    data: Arc<RenderGraphData>,
177    damage: Option<PxRect>,
178    previous: Option<Weak<RenderGraphData>>,
179}
180
181#[derive(Default)]
182struct RenderGraphData {
183    order_plan: OnceLock<Arc<RenderOrderPlan>>,
184    scene: Option<Arc<SceneRope>>,
185    flat: OnceLock<FlatRenderGraph>,
186}
187
188#[derive(Default)]
189struct FlatRenderGraph {
190    ops: Vec<RenderGraphOp>,
191    partial_redraw: bool,
192}
193
194impl RenderGraphData {
195    fn flat(&self) -> &FlatRenderGraph {
196        self.flat.get_or_init(|| {
197            let Some(scene) = &self.scene else {
198                return FlatRenderGraph::default();
199            };
200            let mut builder = RenderGraphBuilder::new();
201            scene.emit(&mut builder, None);
202            builder.into_flat()
203        })
204    }
205
206    fn supports_partial_redraw(&self) -> bool {
207        self.flat
208            .get()
209            .map_or(self.scene.is_some(), |flat| flat.partial_redraw)
210    }
211}
212
213struct RenderOrderPlan {
214    inputs: Box<[OpInfo]>,
215    indices: Box<[usize]>,
216}
217
218impl RenderOrderPlan {
219    fn new(ops: &[RenderGraphOp]) -> Self {
220        let inputs: Box<[_]> = ops.iter().map(OpInfo::new).collect();
221        let indices = order_indices(ops, &inputs).into_boxed_slice();
222        Self { inputs, indices }
223    }
224
225    fn matches(&self, ops: &[RenderGraphOp]) -> bool {
226        self.inputs.len() == ops.len()
227            && self
228                .inputs
229                .iter()
230                .zip(ops)
231                .all(|(input, op)| *input == OpInfo::new(op))
232    }
233}
234
235impl RenderGraph {
236    pub(crate) fn from_partial_scene(scene: Arc<SceneRope>) -> Self {
237        Self {
238            data: Arc::new(RenderGraphData {
239                scene: Some(scene),
240                ..RenderGraphData::default()
241            }),
242            damage: None,
243            previous: None,
244        }
245    }
246
247    pub(crate) fn with_damage(mut self, damage: Option<PxRect>, previous: Option<&Self>) -> Self {
248        self.damage = damage;
249        self.previous = previous.map(|graph| Arc::downgrade(&graph.data));
250        self
251    }
252
253    pub(crate) fn damage_since(&self, previous: Option<&Self>) -> Option<PxRect> {
254        let previous = previous?;
255        if !self.data.supports_partial_redraw() || !previous.data.supports_partial_redraw() {
256            return None;
257        }
258        if Arc::ptr_eq(&self.data, &previous.data) {
259            return Some(PxRect::ZERO);
260        }
261        if self
262            .previous
263            .as_ref()
264            .is_some_and(|data| std::ptr::eq(data.as_ptr(), Arc::as_ptr(&previous.data)))
265        {
266            self.damage
267        } else {
268            // A frame was skipped after the scene journals advanced.
269            None
270        }
271    }
272
273    pub(crate) fn for_damage(&self, damage: PxRect) -> Option<Self> {
274        let scene = self.data.scene.as_ref()?;
275        let mut builder = RenderGraphBuilder::new();
276        scene.emit(&mut builder, Some(damage));
277        Some(builder.finish())
278    }
279
280    pub(crate) fn supports_partial_redraw(&self) -> bool {
281        self.data.supports_partial_redraw()
282    }
283
284    /// Returns ops in the graph.
285    #[must_use]
286    pub fn ops(&self) -> &[RenderGraphOp] {
287        &self.data.flat().ops
288    }
289
290    fn order_plan(&self, previous: Option<&Self>) -> &Arc<RenderOrderPlan> {
291        self.data.order_plan.get_or_init(|| {
292            if let Some(plan) = previous.and_then(|graph| graph.data.order_plan.get())
293                && plan.matches(self.ops())
294            {
295                Arc::clone(plan)
296            } else {
297                Arc::new(RenderOrderPlan::new(self.ops()))
298            }
299        })
300    }
301
302    pub(crate) fn ordered_ops(
303        &self,
304        previous: Option<&Self>,
305    ) -> impl Iterator<Item = &RenderGraphOp> {
306        let ops = self.ops();
307        self.order_plan(previous)
308            .indices
309            .iter()
310            .map(|&index| &ops[index])
311    }
312}
313
314/// Builder for a frame-level render graph.
315pub(crate) struct RenderGraphBuilder {
316    ops: Vec<RenderGraphOp>,
317}
318
319impl RenderGraphBuilder {
320    /// Creates a new render graph builder.
321    pub(crate) fn new() -> Self {
322        Self { ops: Vec::new() }
323    }
324
325    fn push_state(&mut self, command: Command) {
326        self.ops.push(RenderGraphOp {
327            command,
328            type_id: TypeId::of::<Command>(),
329            geometry: DrawGeometry::new(PxSize::ZERO, LocalToWindow::IDENTITY),
330            opacity: 1.0,
331            sequence_index: self.ops.len(),
332        });
333    }
334
335    /// Pushes a clip push op into the graph.
336    pub(crate) fn push_clip_push(&mut self, rect: PxRect) {
337        self.push_state(Command::ClipPush(rect));
338    }
339
340    /// Pushes a shape clip op into the graph.
341    pub(crate) fn push_shape_clip_push(&mut self, region: crate::ClipRegion) {
342        self.push_state(Command::ShapeClipPush(region));
343    }
344
345    /// Pushes a clip pop op into the graph.
346    pub(crate) fn push_clip_pop(&mut self) {
347        self.push_state(Command::ClipPop);
348    }
349
350    /// Appends a fragment into the frame graph.
351    pub(crate) fn append_fragment(
352        &mut self,
353        fragment: &RenderFragment,
354        geometry: DrawGeometry,
355        opacity: f32,
356    ) {
357        for op in fragment.ops() {
358            let mut command = op.command.clone();
359            if let Command::Draw(ref mut command) = command {
360                command.apply_opacity(opacity);
361            }
362            let mapping = geometry
363                .local_to_window
364                .with_local_origin(op.position_override.unwrap_or(PxPosition::ZERO));
365            self.ops.push(RenderGraphOp {
366                command,
367                type_id: op.type_id,
368                geometry: DrawGeometry::new(
369                    op.size_override.unwrap_or(geometry.local_size),
370                    mapping,
371                ),
372                opacity,
373                sequence_index: self.ops.len(),
374            });
375        }
376    }
377
378    /// Finishes graph construction.
379    pub(crate) fn finish(self) -> RenderGraph {
380        RenderGraph {
381            data: Arc::new(RenderGraphData {
382                flat: OnceLock::from(self.into_flat()),
383                ..RenderGraphData::default()
384            }),
385            damage: None,
386            previous: None,
387        }
388    }
389
390    fn into_flat(self) -> FlatRenderGraph {
391        let partial_redraw = self.ops.iter().all(|op| partial_redraw_safe(&op.command));
392        FlatRenderGraph {
393            ops: self.ops,
394            partial_redraw,
395        }
396    }
397}
398
399fn partial_redraw_safe(command: &Command) -> bool {
400    match command {
401        Command::Draw(command) => {
402            command.sample_region().is_none() && command.supports_partial_redraw()
403        }
404        Command::Compute(_) => false,
405        _ => true,
406    }
407}
408mod ordering;
409#[cfg(test)]
410mod retained_order_tests;
411
412use ordering::order_indices;
413
414#[cfg(test)]
415fn order_ops(ops: Vec<RenderGraphOp>) -> Vec<RenderGraphOp> {
416    let infos: Vec<_> = ops.iter().map(OpInfo::new).collect();
417    order_indices(&ops, &infos)
418        .into_iter()
419        .map(|index| ops[index].clone())
420        .collect()
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
424enum OpCategory {
425    ContinuationDraw,
426    BarrierDraw,
427    Compute,
428    StateChange,
429}
430
431#[derive(PartialEq, Eq)]
432struct OpInfo {
433    category: OpCategory,
434    command_kind: std::mem::Discriminant<Command>,
435    type_id: TypeId,
436    sequence_index: usize,
437    read_rect: Option<PxRect>,
438    write_rect: Option<PxRect>,
439}
440
441impl OpInfo {
442    fn new(op: &RenderGraphOp) -> Self {
443        let category = match &op.command {
444            Command::Draw(command) => {
445                if command.sample_region().is_some() {
446                    OpCategory::BarrierDraw
447                } else {
448                    OpCategory::ContinuationDraw
449                }
450            }
451            Command::Compute(_) => OpCategory::Compute,
452            Command::ClipPush(_) | Command::ShapeClipPush(_) | Command::ClipPop => {
453                OpCategory::StateChange
454            }
455        };
456
457        Self {
458            category,
459            command_kind: std::mem::discriminant(&op.command),
460            type_id: op.type_id,
461            sequence_index: op.sequence_index,
462            read_rect: scene_read_rect(op),
463            write_rect: scene_write_rect(op),
464        }
465    }
466}
467
468fn scene_read_rect(op: &RenderGraphOp) -> Option<PxRect> {
469    match &op.command {
470        Command::Draw(command) => command
471            .sample_region()
472            .map(|region| sample_region_rect(region, op.geometry)),
473        Command::Compute(command) => Some(sample_region_rect(command.barrier(), op.geometry)),
474        Command::ClipPush(_) | Command::ShapeClipPush(_) | Command::ClipPop => None,
475    }
476}
477
478fn scene_write_rect(op: &RenderGraphOp) -> Option<PxRect> {
479    match &op.command {
480        Command::Draw(command) => Some(
481            command
482                .ordering_rect(op.geometry)
483                .unwrap_or_else(|| draw_region_rect(command.draw_region(), op.geometry)),
484        ),
485        Command::Compute(_) => Some(op.geometry.window_bounds().outward()),
486        Command::ClipPush(_) | Command::ShapeClipPush(_) | Command::ClipPop => None,
487    }
488}
489
490fn sample_region_rect(region: SampleRegion, geometry: DrawGeometry) -> PxRect {
491    match region {
492        SampleRegion::Global => PxRect::new(Px::ZERO, Px::ZERO, Px::MAX, Px::MAX),
493        SampleRegion::PaddedLocal(padding) => geometry.padded_window_bounds(padding).outward(),
494        SampleRegion::Absolute(rect) => rect,
495    }
496}
497
498fn draw_region_rect(region: DrawRegion, geometry: DrawGeometry) -> PxRect {
499    match region {
500        DrawRegion::Global => PxRect::new(Px::ZERO, Px::ZERO, Px::MAX, Px::MAX),
501        DrawRegion::PaddedLocal(padding) => geometry.padded_window_bounds(padding).outward(),
502        DrawRegion::Absolute(rect) => rect,
503    }
504}
505
506fn is_state_change(op: &RenderGraphOp) -> bool {
507    matches!(
508        op.command,
509        Command::ClipPush(_) | Command::ShapeClipPush(_) | Command::ClipPop
510    )
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use crate::{
517        PaddingRect, Px, PxPosition, PxSize, SampleRegion, renderer::drawer::command::DrawCommand,
518    };
519
520    #[test]
521    fn fragment_overrides_map_through_the_node_transform_once() {
522        let _runtime_session = crate::RuntimeSession::new();
523        let _runtime_scope = _runtime_session.enter();
524        let mut fragment = RenderFragment::default();
525        fragment.push_draw_command(ProbeDraw {
526            id: 1,
527            sample: None,
528        });
529        for op in fragment.ops_mut() {
530            op.size_override = Some(PxSize::new(Px(64), Px(32)));
531            op.position_override = Some(PxPosition::from_f32_xy(4.0, 8.0));
532        }
533        let mut builder = RenderGraphBuilder::new();
534        builder.append_fragment(
535            &fragment,
536            DrawGeometry::new(
537                PxSize::new(Px(240), Px(56)),
538                LocalToWindow::new(0.625, PxPosition::from_f32_xy(10.25, 20.5)).unwrap(),
539            ),
540            1.0,
541        );
542        let graph = builder.finish();
543        let scene = &graph.ops()[0];
544        assert_eq!(scene.geometry.local_size, PxSize::new(Px(64), Px(32)));
545        assert_eq!(
546            scene.geometry.window_origin(),
547            PxPosition::from_f32_xy(12.75, 25.5)
548        );
549        assert_eq!(scene.geometry.window_size(), [40.0, 20.0]);
550    }
551
552    #[derive(Clone)]
553    struct ProbeDraw {
554        id: u32,
555        sample: Option<SampleRegion>,
556    }
557
558    impl DrawCommand for ProbeDraw {
559        fn supports_partial_redraw(&self) -> bool {
560            true
561        }
562
563        fn sample_region(&self) -> Option<SampleRegion> {
564            self.sample
565        }
566
567        fn apply_opacity(&mut self, _opacity: f32) {}
568    }
569
570    #[test]
571    fn damage_requires_the_rendered_predecessor_and_safe_contents_in_both_frames() {
572        #[derive(Clone)]
573        struct ResourceDraw {
574            _resource: Arc<()>,
575        }
576
577        impl DrawCommand for ResourceDraw {
578            fn supports_partial_redraw(&self) -> bool {
579                true
580            }
581
582            fn apply_opacity(&mut self, _: f32) {}
583        }
584
585        #[derive(Clone)]
586        struct UntrackedResource;
587
588        impl DrawCommand for UntrackedResource {
589            fn apply_opacity(&mut self, _: f32) {}
590        }
591
592        #[derive(Clone)]
593        struct Filter;
594
595        impl ComputeCommand for Filter {
596            fn barrier(&self) -> SampleRegion {
597                SampleRegion::Global
598            }
599        }
600
601        let geometry =
602            DrawGeometry::from_position_size(PxPosition::ZERO, PxSize::new(Px(8), Px(8)));
603        let resource = Arc::new(());
604        let resource_lifetime = Arc::downgrade(&resource);
605        let first = {
606            let mut fragment = RenderFragment::default();
607            fragment.push_draw_command(ResourceDraw {
608                _resource: resource,
609            });
610            let mut builder = RenderGraphBuilder::new();
611            builder.append_fragment(&fragment, geometry, 1.0);
612            builder.finish()
613        };
614        let damage = PxRect::new(Px(2), Px(3), Px(4), Px(5));
615        let second = RenderGraphBuilder::new()
616            .finish()
617            .with_damage(Some(damage), Some(&first));
618        assert_eq!(
619            second.damage_since(None),
620            None,
621            "new targets need all pixels"
622        );
623        assert_eq!(second.damage_since(Some(&first)), Some(damage));
624        assert_eq!(second.damage_since(Some(&second)), Some(PxRect::ZERO));
625        let third = RenderGraphBuilder::new()
626            .finish()
627            .with_damage(Some(damage), Some(&second));
628        assert_eq!(
629            third.damage_since(Some(&first)),
630            None,
631            "skipping a scene update loses its damage"
632        );
633
634        let sampled = RenderGraphBuilder {
635            ops: vec![padded_sample_op(1, 0, 0, 0, 10, 10, 8)],
636        }
637        .finish()
638        .with_damage(Some(damage), Some(&first));
639        assert_eq!(
640            sampled.damage_since(Some(&first)),
641            None,
642            "background effects need full replay"
643        );
644        let removed = RenderGraphBuilder::new()
645            .finish()
646            .with_damage(Some(damage), Some(&sampled));
647        assert_eq!(
648            removed.damage_since(Some(&sampled)),
649            None,
650            "removing an effect must discard its output too"
651        );
652        let mut fragment = RenderFragment::default();
653        fragment.push_draw_command(UntrackedResource);
654        let mut builder = RenderGraphBuilder::new();
655        builder.append_fragment(&fragment, geometry, 1.0);
656        let unknown = builder.finish().with_damage(Some(damage), Some(&first));
657        assert_eq!(
658            unknown.damage_since(Some(&first)),
659            None,
660            "custom commands must explicitly opt in"
661        );
662        fragment.clear();
663        fragment.push_compute_command(Filter);
664        let mut builder = RenderGraphBuilder::new();
665        builder.append_fragment(&fragment, geometry, 1.0);
666        let computed = builder.finish().with_damage(Some(damage), Some(&first));
667        assert_eq!(
668            computed.damage_since(Some(&first)),
669            None,
670            "compute work must fully reconstruct its input scene"
671        );
672        drop(first);
673        assert!(
674            resource_lifetime.upgrade().is_none(),
675            "damage history must not keep prior draw resources alive"
676        );
677    }
678
679    fn draw_op(id: u32, sequence: usize, x: i32, y: i32, w: i32, h: i32) -> RenderGraphOp {
680        RenderGraphOp {
681            command: Command::Draw(Box::new(ProbeDraw { id, sample: None })),
682            type_id: TypeId::of::<ProbeDraw>(),
683            geometry: DrawGeometry::from_position_size(
684                PxPosition::new(Px::new(x), Px::new(y)),
685                PxSize::new(Px::new(w), Px::new(h)),
686            ),
687            opacity: 1.0,
688            sequence_index: sequence,
689        }
690    }
691
692    fn padded_sample_op(
693        id: u32,
694        sequence: usize,
695        x: i32,
696        y: i32,
697        w: i32,
698        h: i32,
699        pad: i32,
700    ) -> RenderGraphOp {
701        let mut op = draw_op(id, sequence, x, y, w, h);
702        if let Command::Draw(command) = &mut op.command {
703            let probe = command.downcast_mut::<ProbeDraw>().expect("probe draw");
704            probe.sample = Some(SampleRegion::PaddedLocal(PaddingRect::uniform(Px::new(
705                pad,
706            ))));
707        }
708        op
709    }
710
711    fn clip_push(sequence: usize) -> RenderGraphOp {
712        RenderGraphOp {
713            command: Command::ClipPush(PxRect::new(Px::ZERO, Px::ZERO, Px::new(10), Px::new(10))),
714            type_id: TypeId::of::<Command>(),
715            geometry: DrawGeometry::new(PxSize::ZERO, LocalToWindow::IDENTITY),
716            opacity: 1.0,
717            sequence_index: sequence,
718        }
719    }
720
721    fn clip_pop(sequence: usize) -> RenderGraphOp {
722        RenderGraphOp {
723            command: Command::ClipPop,
724            type_id: TypeId::of::<Command>(),
725            geometry: DrawGeometry::new(PxSize::ZERO, LocalToWindow::IDENTITY),
726            opacity: 1.0,
727            sequence_index: sequence,
728        }
729    }
730
731    fn probe_id(op: &RenderGraphOp) -> u32 {
732        match &op.command {
733            Command::Draw(command) => command.downcast_ref::<ProbeDraw>().expect("probe").id,
734            _ => u32::MAX,
735        }
736    }
737
738    #[test]
739    fn disjoint_clip_siblings_keep_every_draw_in_its_original_clip_scope() {
740        let _runtime_session = crate::RuntimeSession::new();
741        let _runtime_scope = _runtime_session.enter();
742        let mut ops = Vec::new();
743        let mut sequence = 0usize;
744        for clip in 0..8 {
745            ops.push(clip_push(sequence));
746            sequence += 1;
747            let x = (clip as i32) * 100;
748            ops.push(draw_op(clip * 2, sequence, x, 0, 20, 20));
749            sequence += 1;
750            ops.push(draw_op(clip * 2 + 1, sequence, x + 5, 5, 20, 20));
751            sequence += 1;
752            ops.push(clip_pop(sequence));
753            sequence += 1;
754        }
755
756        let ordered = order_ops(ops);
757        assert_eq!(ordered.len(), 32);
758        for (scope, commands) in ordered.chunks_exact(4).enumerate() {
759            assert!(matches!(commands[0].command, Command::ClipPush(_)));
760            assert_eq!(probe_id(&commands[1]), (scope * 2) as u32);
761            assert_eq!(probe_id(&commands[2]), (scope * 2 + 1) as u32);
762            assert!(matches!(commands[3].command, Command::ClipPop));
763        }
764    }
765
766    #[test]
767    fn padded_local_sampling_cannot_move_before_the_draw_it_samples() {
768        let _runtime_session = crate::RuntimeSession::new();
769        let _runtime_scope = _runtime_session.enter();
770        let mut ops = vec![draw_op(1, 0, 14, 0, 10, 10)];
771        for i in 1..24 {
772            ops.push(draw_op(i as u32 + 2, i, i as i32 * 50, 0, 10, 10));
773        }
774        ops.push(padded_sample_op(2, 24, 0, 0, 10, 10, 8));
775
776        let ordered = order_ops(ops);
777        assert_eq!(probe_id(&ordered[0]), 1);
778        assert_eq!(probe_id(&ordered[1]), 2);
779    }
780
781    #[test]
782    fn touching_a_sample_region_does_not_prevent_reordering() {
783        let _runtime_session = crate::RuntimeSession::new();
784        let _runtime_scope = _runtime_session.enter();
785        let earlier = draw_op(1, 0, 18, 0, 10, 10);
786        let later = padded_sample_op(2, 1, 0, 0, 10, 10, 8);
787        let ordered = order_ops(vec![earlier, later]);
788        assert_eq!(probe_id(&ordered[0]), 2);
789        assert_eq!(probe_id(&ordered[1]), 1);
790    }
791
792    #[test]
793    fn ready_draws_continue_a_batch_until_an_overlapping_draw_blocks_it() {
794        let _runtime_session = crate::RuntimeSession::new();
795        let _runtime_scope = _runtime_session.enter();
796        let mut ops = vec![
797            draw_op(1, 0, 0, 0, 10, 10),
798            draw_op(2, 1, 20, 0, 10, 10),
799            draw_op(3, 2, 40, 0, 10, 10),
800            draw_op(4, 3, 60, 0, 10, 10),
801            draw_op(5, 4, 20, 0, 10, 10),
802            draw_op(6, 5, 80, 0, 10, 10),
803        ];
804        for index in [1, 3, 5] {
805            ops[index].type_id = TypeId::of::<u32>();
806        }
807
808        let ordered = order_ops(ops);
809        let ids: Vec<_> = ordered.iter().map(probe_id).collect();
810        assert_eq!(ids, [1, 3, 2, 4, 6, 5]);
811    }
812
813    #[test]
814    fn densely_overlapping_draws_fall_back_to_author_order() {
815        let _runtime_session = crate::RuntimeSession::new();
816        let _runtime_scope = _runtime_session.enter();
817        let mut ops = vec![draw_op(0, 0, 100, 0, 10, 10)];
818        ops.push(padded_sample_op(1, 1, 200, 0, 10, 10, 5));
819        for i in 2..1026 {
820            ops.push(draw_op(i as u32, i, 0, 0, 10, 10));
821        }
822
823        let ordered = order_ops(ops);
824        assert!(ordered.iter().map(probe_id).eq(0..1026));
825    }
826
827    #[test]
828    fn unclosed_clip_falls_back_to_sequence() {
829        let _runtime_session = crate::RuntimeSession::new();
830        let _runtime_scope = _runtime_session.enter();
831        let ops = vec![
832            clip_push(0),
833            draw_op(7, 1, 0, 0, 10, 10),
834            draw_op(8, 2, 0, 0, 10, 10),
835        ];
836        let ordered = order_ops(ops);
837        assert_eq!(probe_id(&ordered[1]), 7);
838        assert_eq!(probe_id(&ordered[2]), 8);
839        assert!(matches!(ordered[0].command, Command::ClipPush(_)));
840    }
841}