vello_common 0.0.8

Core data structures and utilities shared across the Vello rendering, including geometry processing and tiling logic.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Render graph for multi-pass rendering with filter effects.
//!
//! This module provides a directed acyclic graph (DAG) representation of the rendering pipeline,
//! enabling efficient multi-pass rendering when layers have filter effects applied.
//!
//! # Overview
//!
//! The render graph tracks dependencies between rendering operations (nodes) and ensures they
//! execute in the correct order. Each node represents either:
//! - The root (backdrop) layer that forms the base for compositing
//! - A filtered layer that renders geometry, applies effects, and blends the result
//!
//! # Key Features
//!
//! - **Incremental execution order**: The graph builds its execution order as layers are popped
//!   during scene encoding, avoiding the need for a full topological sort at render time.
//! - **Data dependencies**: Edges represent dependencies where one layer's output is consumed
//!   as input to another operation (e.g., a filter reading from a previous layer's buffer).
//! - **Efficient traversal**: The pre-computed execution order allows O(1) iteration over nodes
//!   in dependency order, with children always processed before their parents.
//!
//! # Usage Pattern
//!
//! 1. During scene encoding, nodes are added via [`RenderGraph::add_node`] as layers are created
//! 2. When layers with dependencies are popped, edges are added via [`RenderGraph::add_edge`]
//! 3. As each layer completes, it's recorded in execution order via [`RenderGraph::record_node_for_execution`]
//! 4. At render time, iterate nodes in order using [`RenderGraph::execution_order`]
//!
//! # Example Structure
//!
//! ```text
//! RootLayer (id=0) ← backdrop
//!//!     └─→ FilterLayer (id=1, blur filter)
//!//!             └─→ FilterLayer (id=2, drop shadow filter)
//!
//! Execution order: [2, 1, 0] (deepest children first, then parents)
//! ```
//!
//! In this example, `FilterLayer 2` (drop shadow) is the most deeply nested child, so it
//! executes first. Then `FilterLayer 1` (blur) executes and can reference `FilterLayer 2`'s output
//! if needed. Finally, the `RootLayer` composites all filtered results into the final image.
//!
//! # Future Improvements
//!
//! TODO: Consider using `render_graph` for `FilterGraph` execution for filters. The render graph
//! infrastructure could potentially be extended to handle the internal DAG structure within
//! individual filter effects, not just layer-to-layer dependencies.

use crate::coarse::WideTilesBbox;
use crate::filter_effects::Filter;
use crate::kurbo::Affine;
use alloc::vec;
use alloc::vec::Vec;
use hashbrown::HashMap;

/// A render graph containing nodes and edges representing the rendering pipeline.
///
/// This graph tracks layers with filter effects and their dependencies, enabling
/// multi-pass rendering where filter outputs can be used as inputs to subsequent operations.
#[derive(Debug, Clone)]
pub struct RenderGraph {
    /// All render operations (layers) in the graph.
    pub nodes: Vec<RenderNode>,
    /// Dependencies between nodes, defining execution order.
    pub edges: Vec<RenderEdge>,
    /// Counter for generating unique node IDs.
    next_node_id: NodeId,
    /// Pre-computed execution order collected during `pop_layer` calls.
    /// This is built incrementally as layers are popped (children before parents),
    /// avoiding the need for topological sort at render time.
    node_execution_order: Vec<NodeId>,
    /// The root layer node ID, tracked separately since it's never popped.
    /// This is appended to the execution order when iterating.
    root_node: Option<NodeId>,
    /// Flag indicating whether the graph contains any filter layers.
    /// Set to true when a `FilterLayer` node is added.
    has_filters: bool,
}

/// The type of dependency between nodes.
#[derive(Debug, Clone)]
pub enum DependencyKind {
    /// Sequential execution: the `from` node must execute before the `to` node.
    ///
    /// The `to` node consumes output from the `from` node via the specified layer.
    /// For example, a parent filter reads from a child layer buffer produced by a previous operation.
    Sequential {
        /// The layer that connects the two nodes, whose output flows from `from` to `to`.
        layer_id: LayerId,
    },
}

impl Default for RenderGraph {
    fn default() -> Self {
        Self::new()
    }
}

impl RenderGraph {
    /// Creates a new empty render graph.
    ///
    /// The graph starts with no nodes or edges. Nodes are added via [`add_node`](Self::add_node)
    /// as layers are created during scene encoding.
    pub fn new() -> Self {
        Self {
            nodes: Vec::new(),
            edges: Vec::new(),
            next_node_id: 0,
            node_execution_order: Vec::new(),
            root_node: None,
            has_filters: false,
        }
    }

    /// Clears the render graph state while preserving allocated capacity.
    ///
    /// This resets the graph to an empty state (equivalent to [`new`](Self::new))
    /// but reuses the existing memory allocations, avoiding the need to reallocate
    /// when building a new scene.
    ///
    /// After calling `clear()`, the graph will have no nodes or edges, and counters
    /// will be reset to their initial state.
    pub fn clear(&mut self) {
        self.nodes.clear();
        self.edges.clear();
        self.next_node_id = 0;
        self.node_execution_order.clear();
        self.root_node = None;
        self.has_filters = false;
    }

    /// Add a node to the graph and return its ID.
    ///
    /// This is called during scene encoding when a new layer is created. The returned ID
    /// should be stored with the layer to establish edges later when dependencies are known.
    pub fn add_node(&mut self, kind: RenderNodeKind) -> NodeId {
        let id = self.next_node_id;
        self.next_node_id += 1;

        // Track root node separately since it's never popped and executes last
        if matches!(kind, RenderNodeKind::RootLayer { .. }) {
            self.root_node = Some(id);
        }

        // Track if we have any filters to avoid scanning nodes later
        if matches!(kind, RenderNodeKind::FilterLayer { .. }) {
            self.has_filters = true;
        }

        self.nodes.push(RenderNode { id, kind });
        id
    }

    /// Add an edge between two nodes, establishing a dependency relationship.
    ///
    /// This is called when a layer with dependencies is popped during scene encoding.
    /// The edge ensures that `from` node executes before `to` node, typically because
    /// `to` needs to read data produced by `from` (e.g., a filter reading from a layer buffer).
    pub fn add_edge(&mut self, from: NodeId, to: NodeId, kind: DependencyKind) {
        self.edges.push(RenderEdge { from, to, kind });
    }

    /// Record a node as ready for execution in topological order.
    ///
    /// This is called from `pop_layer` to incrementally build the execution order
    /// as layers are popped. Since layers are popped in a depth-first manner (children
    /// before parents), this naturally builds the correct dependency order without
    /// requiring a separate topological sort at render time.
    ///
    /// # Example
    ///
    /// For nested layers A → B → C (where C is the deepest child):
    /// - C is popped first → recorded first
    /// - B is popped second → recorded second  
    /// - A is popped last → recorded last
    ///
    /// This gives execution order [C, B, A], which respects dependencies.
    pub fn record_node_for_execution(&mut self, node_id: NodeId) {
        self.node_execution_order.push(node_id);
    }

    /// Check if the graph contains any filter passes.
    ///
    /// Returns `true` if any layers have filter effects that need processing.
    /// This is useful for determining whether to use the multi-pass rendering
    /// pipeline (with filter support) or a simpler single-pass approach.
    ///
    /// This is set when filter nodes are added.
    pub fn has_filters(&self) -> bool {
        self.has_filters
    }

    /// Get an iterator over nodes in execution order.
    ///
    /// Returns filter layers followed by the root layer, in the order they should be executed.
    /// This order is built incrementally during `pop_layer` calls (children before parents),
    /// avoiding the need for topological sort at render time. Returns an iterator to avoid allocation.
    pub fn execution_order(&self) -> impl Iterator<Item = NodeId> + '_ {
        self.node_execution_order
            .iter()
            .copied()
            .chain(self.root_node)
    }

    /// Topologically sort nodes based on their dependencies using Kahn's algorithm.
    ///
    /// Returns nodes in execution order, ensuring all dependencies are satisfied before
    /// each node is processed. This implements a standard topological sort with cycle detection.
    ///
    /// # Performance
    ///
    /// This is primarily used for validation and debugging. For normal rendering,
    /// prefer [`execution_order`](Self::execution_order) which is pre-computed during
    /// scene encoding and avoids the O(V+E) sorting overhead.
    ///
    /// # Panics
    ///
    /// Panics if the graph contains a cycle, which should never happen in a valid render graph.
    #[allow(
        dead_code,
        reason = "we don't use currently topological sort but will in the future"
    )]
    pub fn topological_sort(&self) -> Vec<NodeId> {
        // Track how many incoming edges each node has (dependencies it's waiting for)
        let mut in_degree = vec![0; self.nodes.len()];

        // Map each node to its dependents (nodes that depend on it)
        let mut adj_list: HashMap<NodeId, Vec<NodeId>> = HashMap::new();

        // Build adjacency list and count incoming edges for each node
        for edge in &self.edges {
            adj_list.entry(edge.from).or_default().push(edge.to);
            in_degree[edge.to] += 1;
        }

        // Start with nodes that have no dependencies (in-degree = 0)
        // These are safe to execute immediately
        let mut queue: Vec<NodeId> = (0..self.nodes.len())
            .filter(|&i| in_degree[i] == 0)
            .collect();

        let mut result = Vec::new();

        // Process nodes in dependency order (Kahn's algorithm)
        while let Some(node_id) = queue.pop() {
            result.push(node_id);

            // For each node that depends on this one, mark this dependency as satisfied
            if let Some(neighbors) = adj_list.get(&node_id) {
                for &neighbor in neighbors {
                    in_degree[neighbor] -= 1;
                    // If all dependencies are now satisfied, this node is ready to execute
                    if in_degree[neighbor] == 0 {
                        queue.push(neighbor);
                    }
                }
            }
        }

        // If we didn't process all nodes, there's a cycle (which should never happen)
        assert_eq!(
            result.len(),
            self.nodes.len(),
            "Cycle detected in render graph"
        );
        result
    }
}

/// A node in the render graph representing a single render operation.
#[derive(Debug, Clone)]
pub struct RenderNode {
    /// Unique identifier for this node.
    pub id: NodeId,
    /// The type of render operation this node performs.
    pub kind: RenderNodeKind,
}

impl RenderNode {
    /// Returns `true` if this node has an empty (zero-area) bounding box.
    pub fn is_empty(&self) -> bool {
        match &self.kind {
            RenderNodeKind::RootLayer { wtile_bbox, .. } => wtile_bbox.is_empty(),
            RenderNodeKind::FilterLayer { wtile_bbox, .. } => wtile_bbox.is_empty(),
        }
    }
}

/// An edge representing a dependency between two nodes.
#[derive(Debug, Clone)]
pub struct RenderEdge {
    /// The source node that must complete first.
    pub from: NodeId,
    /// The destination node that depends on the source.
    pub to: NodeId,
    /// The type of dependency relationship.
    pub kind: DependencyKind,
}

/// The type of operation a render node performs.
///
/// Each variant represents a different kind of rendering pass in the pipeline.
/// Nodes are executed in dependency order to ensure correct compositing and filtering.
#[derive(Debug, Clone)]
pub enum RenderNodeKind {
    /// The root (backdrop) layer that serves as the base for compositing.
    ///
    /// This layer is always present and is rendered last, after all filter layers have
    /// been processed. It contains all non-filtered geometry and serves as the final
    /// compositing target.
    RootLayer {
        /// ID of the root layer.
        layer_id: LayerId,
        /// Bounding box in wide tile coordinates covered by this layer.
        wtile_bbox: WideTilesBbox,
    },
    /// A layer with filter effects applied.
    ///
    /// This combines multiple passes: render geometry → apply filter → blend result.
    /// Layers are added to the render graph only if they have filters. The filter
    /// output may be consumed by other filter layers or composited into the root layer.
    FilterLayer {
        /// ID of this filtered layer.
        layer_id: LayerId,
        /// The filter effect to apply.
        filter: Filter,
        /// Bounding box in wide tile coordinates containing geometry for this layer.
        wtile_bbox: WideTilesBbox,
        /// Transform that was active when the layer was created.
        /// Used to scale filter parameters based on the current scale/zoom level.
        transform: Affine,
    },
}

/// Unique identifier for a layer in the rendering system.
///
/// Layer IDs are assigned by the encoding system and used to track layer buffers
/// across multiple render passes. A layer may or may not have a corresponding node
/// in the render graph (only layers with filters create graph nodes).
pub type LayerId = u32;

/// Unique identifier for a node in the render graph.
///
/// Node IDs are assigned sequentially as nodes are added during scene encoding.
/// They are used to reference nodes when adding edges and during execution order traversal.
pub type NodeId = usize;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::color::AlphaColor;

    #[test]
    fn new_creates_empty_graph() {
        let graph = RenderGraph::new();
        assert_eq!(graph.nodes.len(), 0);
        assert_eq!(graph.edges.len(), 0);
        assert!(!graph.has_filters());
        assert!(graph.root_node.is_none());
    }

    #[test]
    fn clear_resets_to_empty_state() {
        let mut graph = RenderGraph::new();
        graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });
        graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        graph.record_node_for_execution(1);

        graph.clear();

        assert_eq!(graph.nodes.len(), 0);
        assert_eq!(graph.edges.len(), 0);
        assert_eq!(graph.next_node_id, 0);
        assert!(graph.root_node.is_none());
        assert!(!graph.has_filters());
        assert_eq!(graph.execution_order().count(), 0);
    }

    #[test]
    fn clear_preserves_capacity() {
        let mut graph = RenderGraph::new();
        let from = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });
        let to = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 1,
            wtile_bbox: dummy_bbox(),
        });
        graph.add_edge(from, to, DependencyKind::Sequential { layer_id: 1 });

        let nodes_capacity = graph.nodes.capacity();
        let edges_capacity = graph.edges.capacity();

        graph.clear();

        assert!(graph.nodes.capacity() >= nodes_capacity);
        assert!(graph.edges.capacity() >= edges_capacity);
    }

    #[test]
    fn add_node_returns_sequential_ids() {
        let mut graph = RenderGraph::new();
        let id0 = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });
        let id1 = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });

        assert_eq!(id0, 0);
        assert_eq!(id1, 1);
    }

    #[test]
    fn add_node_tracks_root() {
        let mut graph = RenderGraph::new();
        let root_id = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        assert_eq!(graph.root_node, Some(root_id));
    }

    #[test]
    fn add_node_filter_sets_has_filters() {
        let mut graph = RenderGraph::new();

        graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });

        assert!(graph.has_filters());
    }

    #[test]
    fn add_node_root_does_not_set_has_filters() {
        let mut graph = RenderGraph::new();

        graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        assert!(!graph.has_filters());
    }

    #[test]
    fn add_edge_creates_dependency() {
        let mut graph = RenderGraph::new();
        let from = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let to = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        graph.add_edge(from, to, DependencyKind::Sequential { layer_id: 1 });

        assert_eq!(graph.edges.len(), 1);
        assert_eq!(graph.edges[0].from, from);
        assert_eq!(graph.edges[0].to, to);
    }

    #[test]
    fn add_edge_multiple_from_same_node() {
        let mut graph = RenderGraph::new();
        let from = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let to1 = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 2,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let to2 = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        graph.add_edge(from, to1, DependencyKind::Sequential { layer_id: 1 });
        graph.add_edge(from, to2, DependencyKind::Sequential { layer_id: 1 });

        assert_eq!(graph.edges.len(), 2);
    }

    #[test]
    fn execution_order_includes_recorded_nodes() {
        let mut graph = RenderGraph::new();
        let filter = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });

        graph.record_node_for_execution(filter);

        let order: Vec<_> = graph.execution_order().collect();
        assert_eq!(order, vec![filter]);
    }

    #[test]
    fn execution_order_includes_only_root_when_present() {
        let mut graph = RenderGraph::new();
        let root = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        let order: Vec<_> = graph.execution_order().collect();
        assert_eq!(order, vec![root]);
    }

    #[test]
    fn execution_order_root_comes_last() {
        let mut graph = RenderGraph::new();
        let filter1 = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let filter2 = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 2,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let root = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        graph.record_node_for_execution(filter2);
        graph.record_node_for_execution(filter1);

        let order: Vec<_> = graph.execution_order().collect();
        assert_eq!(order, vec![filter2, filter1, root]);
    }

    #[test]
    fn topological_sort_diamond_dependency() {
        let mut graph = RenderGraph::new();
        let bottom = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 3,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let left = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 1,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let right = graph.add_node(RenderNodeKind::FilterLayer {
            layer_id: 2,
            filter: dummy_filter(),
            wtile_bbox: dummy_bbox(),
            transform: Affine::IDENTITY,
        });
        let top = graph.add_node(RenderNodeKind::RootLayer {
            layer_id: 0,
            wtile_bbox: dummy_bbox(),
        });

        // Diamond: bottom -> left -> top, bottom -> right -> top
        graph.add_edge(bottom, left, DependencyKind::Sequential { layer_id: 3 });
        graph.add_edge(bottom, right, DependencyKind::Sequential { layer_id: 3 });
        graph.add_edge(left, top, DependencyKind::Sequential { layer_id: 1 });
        graph.add_edge(right, top, DependencyKind::Sequential { layer_id: 2 });

        let sorted = graph.topological_sort();
        assert_eq!(sorted, vec![bottom, right, left, top]);
    }

    fn dummy_filter() -> Filter {
        Filter::from_primitive(crate::filter_effects::FilterPrimitive::Flood {
            color: AlphaColor::from_rgba8(0, 0, 0, 255),
        })
    }

    fn dummy_bbox() -> WideTilesBbox {
        WideTilesBbox::ZERO
    }
}