Skip to main content

vello_common/
render_graph.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Render graph for multi-pass rendering with filter effects.
5//!
6//! This module provides a directed acyclic graph (DAG) representation of the rendering pipeline,
7//! enabling efficient multi-pass rendering when layers have filter effects applied.
8//!
9//! # Overview
10//!
11//! The render graph tracks dependencies between rendering operations (nodes) and ensures they
12//! execute in the correct order. Each node represents either:
13//! - The root (backdrop) layer that forms the base for compositing
14//! - A filtered layer that renders geometry, applies effects, and blends the result
15//!
16//! # Key Features
17//!
18//! - **Incremental execution order**: The graph builds its execution order as layers are popped
19//!   during scene encoding, avoiding the need for a full topological sort at render time.
20//! - **Data dependencies**: Edges represent dependencies where one layer's output is consumed
21//!   as input to another operation (e.g., a filter reading from a previous layer's buffer).
22//! - **Efficient traversal**: The pre-computed execution order allows O(1) iteration over nodes
23//!   in dependency order, with children always processed before their parents.
24//!
25//! # Usage Pattern
26//!
27//! 1. During scene encoding, nodes are added via [`RenderGraph::add_node`] as layers are created
28//! 2. When layers with dependencies are popped, edges are added via [`RenderGraph::add_edge`]
29//! 3. As each layer completes, it's recorded in execution order via [`RenderGraph::record_node_for_execution`]
30//! 4. At render time, iterate nodes in order using [`RenderGraph::execution_order`]
31//!
32//! # Example Structure
33//!
34//! ```text
35//! RootLayer (id=0) ← backdrop
36//!     │
37//!     └─→ FilterLayer (id=1, blur filter)
38//!             │
39//!             └─→ FilterLayer (id=2, drop shadow filter)
40//!
41//! Execution order: [2, 1, 0] (deepest children first, then parents)
42//! ```
43//!
44//! In this example, `FilterLayer 2` (drop shadow) is the most deeply nested child, so it
45//! executes first. Then `FilterLayer 1` (blur) executes and can reference `FilterLayer 2`'s output
46//! if needed. Finally, the `RootLayer` composites all filtered results into the final image.
47//!
48//! # Future Improvements
49//!
50//! TODO: Consider using `render_graph` for `FilterGraph` execution for filters. The render graph
51//! infrastructure could potentially be extended to handle the internal DAG structure within
52//! individual filter effects, not just layer-to-layer dependencies.
53
54use crate::coarse::WideTilesBbox;
55use crate::filter_effects::Filter;
56use crate::kurbo::Affine;
57use alloc::vec;
58use alloc::vec::Vec;
59use hashbrown::HashMap;
60
61/// A render graph containing nodes and edges representing the rendering pipeline.
62///
63/// This graph tracks layers with filter effects and their dependencies, enabling
64/// multi-pass rendering where filter outputs can be used as inputs to subsequent operations.
65#[derive(Debug, Clone)]
66pub struct RenderGraph {
67    /// All render operations (layers) in the graph.
68    pub nodes: Vec<RenderNode>,
69    /// Dependencies between nodes, defining execution order.
70    pub edges: Vec<RenderEdge>,
71    /// Counter for generating unique node IDs.
72    next_node_id: NodeId,
73    /// Pre-computed execution order collected during `pop_layer` calls.
74    /// This is built incrementally as layers are popped (children before parents),
75    /// avoiding the need for topological sort at render time.
76    node_execution_order: Vec<NodeId>,
77    /// The root layer node ID, tracked separately since it's never popped.
78    /// This is appended to the execution order when iterating.
79    root_node: Option<NodeId>,
80    /// Flag indicating whether the graph contains any filter layers.
81    /// Set to true when a `FilterLayer` node is added.
82    has_filters: bool,
83}
84
85/// The type of dependency between nodes.
86#[derive(Debug, Clone)]
87pub enum DependencyKind {
88    /// Sequential execution: the `from` node must execute before the `to` node.
89    ///
90    /// The `to` node consumes output from the `from` node via the specified layer.
91    /// For example, a parent filter reads from a child layer buffer produced by a previous operation.
92    Sequential {
93        /// The layer that connects the two nodes, whose output flows from `from` to `to`.
94        layer_id: LayerId,
95    },
96}
97
98impl Default for RenderGraph {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104impl RenderGraph {
105    /// Creates a new empty render graph.
106    ///
107    /// The graph starts with no nodes or edges. Nodes are added via [`add_node`](Self::add_node)
108    /// as layers are created during scene encoding.
109    pub fn new() -> Self {
110        Self {
111            nodes: Vec::new(),
112            edges: Vec::new(),
113            next_node_id: 0,
114            node_execution_order: Vec::new(),
115            root_node: None,
116            has_filters: false,
117        }
118    }
119
120    /// Clears the render graph state while preserving allocated capacity.
121    ///
122    /// This resets the graph to an empty state (equivalent to [`new`](Self::new))
123    /// but reuses the existing memory allocations, avoiding the need to reallocate
124    /// when building a new scene.
125    ///
126    /// After calling `clear()`, the graph will have no nodes or edges, and counters
127    /// will be reset to their initial state.
128    pub fn clear(&mut self) {
129        self.nodes.clear();
130        self.edges.clear();
131        self.next_node_id = 0;
132        self.node_execution_order.clear();
133        self.root_node = None;
134        self.has_filters = false;
135    }
136
137    /// Add a node to the graph and return its ID.
138    ///
139    /// This is called during scene encoding when a new layer is created. The returned ID
140    /// should be stored with the layer to establish edges later when dependencies are known.
141    pub fn add_node(&mut self, kind: RenderNodeKind) -> NodeId {
142        let id = self.next_node_id;
143        self.next_node_id += 1;
144
145        // Track root node separately since it's never popped and executes last
146        if matches!(kind, RenderNodeKind::RootLayer { .. }) {
147            self.root_node = Some(id);
148        }
149
150        // Track if we have any filters to avoid scanning nodes later
151        if matches!(kind, RenderNodeKind::FilterLayer { .. }) {
152            self.has_filters = true;
153        }
154
155        self.nodes.push(RenderNode { id, kind });
156        id
157    }
158
159    /// Add an edge between two nodes, establishing a dependency relationship.
160    ///
161    /// This is called when a layer with dependencies is popped during scene encoding.
162    /// The edge ensures that `from` node executes before `to` node, typically because
163    /// `to` needs to read data produced by `from` (e.g., a filter reading from a layer buffer).
164    pub fn add_edge(&mut self, from: NodeId, to: NodeId, kind: DependencyKind) {
165        self.edges.push(RenderEdge { from, to, kind });
166    }
167
168    /// Record a node as ready for execution in topological order.
169    ///
170    /// This is called from `pop_layer` to incrementally build the execution order
171    /// as layers are popped. Since layers are popped in a depth-first manner (children
172    /// before parents), this naturally builds the correct dependency order without
173    /// requiring a separate topological sort at render time.
174    ///
175    /// # Example
176    ///
177    /// For nested layers A → B → C (where C is the deepest child):
178    /// - C is popped first → recorded first
179    /// - B is popped second → recorded second  
180    /// - A is popped last → recorded last
181    ///
182    /// This gives execution order [C, B, A], which respects dependencies.
183    pub fn record_node_for_execution(&mut self, node_id: NodeId) {
184        self.node_execution_order.push(node_id);
185    }
186
187    /// Check if the graph contains any filter passes.
188    ///
189    /// Returns `true` if any layers have filter effects that need processing.
190    /// This is useful for determining whether to use the multi-pass rendering
191    /// pipeline (with filter support) or a simpler single-pass approach.
192    ///
193    /// This is set when filter nodes are added.
194    pub fn has_filters(&self) -> bool {
195        self.has_filters
196    }
197
198    /// Get an iterator over nodes in execution order.
199    ///
200    /// Returns filter layers followed by the root layer, in the order they should be executed.
201    /// This order is built incrementally during `pop_layer` calls (children before parents),
202    /// avoiding the need for topological sort at render time. Returns an iterator to avoid allocation.
203    pub fn execution_order(&self) -> impl Iterator<Item = NodeId> + '_ {
204        self.node_execution_order
205            .iter()
206            .copied()
207            .chain(self.root_node)
208    }
209
210    /// Topologically sort nodes based on their dependencies using Kahn's algorithm.
211    ///
212    /// Returns nodes in execution order, ensuring all dependencies are satisfied before
213    /// each node is processed. This implements a standard topological sort with cycle detection.
214    ///
215    /// # Performance
216    ///
217    /// This is primarily used for validation and debugging. For normal rendering,
218    /// prefer [`execution_order`](Self::execution_order) which is pre-computed during
219    /// scene encoding and avoids the O(V+E) sorting overhead.
220    ///
221    /// # Panics
222    ///
223    /// Panics if the graph contains a cycle, which should never happen in a valid render graph.
224    #[allow(
225        dead_code,
226        reason = "we don't use currently topological sort but will in the future"
227    )]
228    pub fn topological_sort(&self) -> Vec<NodeId> {
229        // Track how many incoming edges each node has (dependencies it's waiting for)
230        let mut in_degree = vec![0; self.nodes.len()];
231
232        // Map each node to its dependents (nodes that depend on it)
233        let mut adj_list: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
234
235        // Build adjacency list and count incoming edges for each node
236        for edge in &self.edges {
237            adj_list.entry(edge.from).or_default().push(edge.to);
238            in_degree[edge.to] += 1;
239        }
240
241        // Start with nodes that have no dependencies (in-degree = 0)
242        // These are safe to execute immediately
243        let mut queue: Vec<NodeId> = (0..self.nodes.len())
244            .filter(|&i| in_degree[i] == 0)
245            .collect();
246
247        let mut result = Vec::new();
248
249        // Process nodes in dependency order (Kahn's algorithm)
250        while let Some(node_id) = queue.pop() {
251            result.push(node_id);
252
253            // For each node that depends on this one, mark this dependency as satisfied
254            if let Some(neighbors) = adj_list.get(&node_id) {
255                for &neighbor in neighbors {
256                    in_degree[neighbor] -= 1;
257                    // If all dependencies are now satisfied, this node is ready to execute
258                    if in_degree[neighbor] == 0 {
259                        queue.push(neighbor);
260                    }
261                }
262            }
263        }
264
265        // If we didn't process all nodes, there's a cycle (which should never happen)
266        assert_eq!(
267            result.len(),
268            self.nodes.len(),
269            "Cycle detected in render graph"
270        );
271        result
272    }
273}
274
275/// A node in the render graph representing a single render operation.
276#[derive(Debug, Clone)]
277pub struct RenderNode {
278    /// Unique identifier for this node.
279    pub id: NodeId,
280    /// The type of render operation this node performs.
281    pub kind: RenderNodeKind,
282}
283
284/// An edge representing a dependency between two nodes.
285#[derive(Debug, Clone)]
286pub struct RenderEdge {
287    /// The source node that must complete first.
288    pub from: NodeId,
289    /// The destination node that depends on the source.
290    pub to: NodeId,
291    /// The type of dependency relationship.
292    pub kind: DependencyKind,
293}
294
295/// The type of operation a render node performs.
296///
297/// Each variant represents a different kind of rendering pass in the pipeline.
298/// Nodes are executed in dependency order to ensure correct compositing and filtering.
299#[derive(Debug, Clone)]
300pub enum RenderNodeKind {
301    /// The root (backdrop) layer that serves as the base for compositing.
302    ///
303    /// This layer is always present and is rendered last, after all filter layers have
304    /// been processed. It contains all non-filtered geometry and serves as the final
305    /// compositing target.
306    RootLayer {
307        /// ID of the root layer.
308        layer_id: LayerId,
309        /// Bounding box in wide tile coordinates covered by this layer.
310        wtile_bbox: WideTilesBbox,
311    },
312    /// A layer with filter effects applied.
313    ///
314    /// This combines multiple passes: render geometry → apply filter → blend result.
315    /// Layers are added to the render graph only if they have filters. The filter
316    /// output may be consumed by other filter layers or composited into the root layer.
317    FilterLayer {
318        /// ID of this filtered layer.
319        layer_id: LayerId,
320        /// The filter effect to apply.
321        filter: Filter,
322        /// Bounding box in wide tile coordinates containing geometry for this layer.
323        wtile_bbox: WideTilesBbox,
324        /// Transform that was active when the layer was created.
325        /// Used to scale filter parameters based on the current scale/zoom level.
326        transform: Affine,
327    },
328}
329
330/// Unique identifier for a layer in the rendering system.
331///
332/// Layer IDs are assigned by the encoding system and used to track layer buffers
333/// across multiple render passes. A layer may or may not have a corresponding node
334/// in the render graph (only layers with filters create graph nodes).
335pub type LayerId = u32;
336
337/// Unique identifier for a node in the render graph.
338///
339/// Node IDs are assigned sequentially as nodes are added during scene encoding.
340/// They are used to reference nodes when adding edges and during execution order traversal.
341pub type NodeId = usize;
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    use crate::color::AlphaColor;
347
348    #[test]
349    fn new_creates_empty_graph() {
350        let graph = RenderGraph::new();
351        assert_eq!(graph.nodes.len(), 0);
352        assert_eq!(graph.edges.len(), 0);
353        assert!(!graph.has_filters());
354        assert!(graph.root_node.is_none());
355    }
356
357    #[test]
358    fn clear_resets_to_empty_state() {
359        let mut graph = RenderGraph::new();
360        graph.add_node(RenderNodeKind::RootLayer {
361            layer_id: 0,
362            wtile_bbox: dummy_bbox(),
363        });
364        graph.add_node(RenderNodeKind::FilterLayer {
365            layer_id: 1,
366            filter: dummy_filter(),
367            wtile_bbox: dummy_bbox(),
368            transform: Affine::IDENTITY,
369        });
370        graph.record_node_for_execution(1);
371
372        graph.clear();
373
374        assert_eq!(graph.nodes.len(), 0);
375        assert_eq!(graph.edges.len(), 0);
376        assert_eq!(graph.next_node_id, 0);
377        assert!(graph.root_node.is_none());
378        assert!(!graph.has_filters());
379        assert_eq!(graph.execution_order().count(), 0);
380    }
381
382    #[test]
383    fn clear_preserves_capacity() {
384        let mut graph = RenderGraph::new();
385        let from = graph.add_node(RenderNodeKind::RootLayer {
386            layer_id: 0,
387            wtile_bbox: dummy_bbox(),
388        });
389        let to = graph.add_node(RenderNodeKind::RootLayer {
390            layer_id: 1,
391            wtile_bbox: dummy_bbox(),
392        });
393        graph.add_edge(from, to, DependencyKind::Sequential { layer_id: 1 });
394
395        let nodes_capacity = graph.nodes.capacity();
396        let edges_capacity = graph.edges.capacity();
397
398        graph.clear();
399
400        assert!(graph.nodes.capacity() >= nodes_capacity);
401        assert!(graph.edges.capacity() >= edges_capacity);
402    }
403
404    #[test]
405    fn add_node_returns_sequential_ids() {
406        let mut graph = RenderGraph::new();
407        let id0 = graph.add_node(RenderNodeKind::RootLayer {
408            layer_id: 0,
409            wtile_bbox: dummy_bbox(),
410        });
411        let id1 = graph.add_node(RenderNodeKind::FilterLayer {
412            layer_id: 1,
413            filter: dummy_filter(),
414            wtile_bbox: dummy_bbox(),
415            transform: Affine::IDENTITY,
416        });
417
418        assert_eq!(id0, 0);
419        assert_eq!(id1, 1);
420    }
421
422    #[test]
423    fn add_node_tracks_root() {
424        let mut graph = RenderGraph::new();
425        let root_id = graph.add_node(RenderNodeKind::RootLayer {
426            layer_id: 0,
427            wtile_bbox: dummy_bbox(),
428        });
429
430        assert_eq!(graph.root_node, Some(root_id));
431    }
432
433    #[test]
434    fn add_node_filter_sets_has_filters() {
435        let mut graph = RenderGraph::new();
436
437        graph.add_node(RenderNodeKind::FilterLayer {
438            layer_id: 1,
439            filter: dummy_filter(),
440            wtile_bbox: dummy_bbox(),
441            transform: Affine::IDENTITY,
442        });
443
444        assert!(graph.has_filters());
445    }
446
447    #[test]
448    fn add_node_root_does_not_set_has_filters() {
449        let mut graph = RenderGraph::new();
450
451        graph.add_node(RenderNodeKind::RootLayer {
452            layer_id: 0,
453            wtile_bbox: dummy_bbox(),
454        });
455
456        assert!(!graph.has_filters());
457    }
458
459    #[test]
460    fn add_edge_creates_dependency() {
461        let mut graph = RenderGraph::new();
462        let from = graph.add_node(RenderNodeKind::FilterLayer {
463            layer_id: 1,
464            filter: dummy_filter(),
465            wtile_bbox: dummy_bbox(),
466            transform: Affine::IDENTITY,
467        });
468        let to = graph.add_node(RenderNodeKind::RootLayer {
469            layer_id: 0,
470            wtile_bbox: dummy_bbox(),
471        });
472
473        graph.add_edge(from, to, DependencyKind::Sequential { layer_id: 1 });
474
475        assert_eq!(graph.edges.len(), 1);
476        assert_eq!(graph.edges[0].from, from);
477        assert_eq!(graph.edges[0].to, to);
478    }
479
480    #[test]
481    fn add_edge_multiple_from_same_node() {
482        let mut graph = RenderGraph::new();
483        let from = graph.add_node(RenderNodeKind::FilterLayer {
484            layer_id: 1,
485            filter: dummy_filter(),
486            wtile_bbox: dummy_bbox(),
487            transform: Affine::IDENTITY,
488        });
489        let to1 = graph.add_node(RenderNodeKind::FilterLayer {
490            layer_id: 2,
491            filter: dummy_filter(),
492            wtile_bbox: dummy_bbox(),
493            transform: Affine::IDENTITY,
494        });
495        let to2 = graph.add_node(RenderNodeKind::RootLayer {
496            layer_id: 0,
497            wtile_bbox: dummy_bbox(),
498        });
499
500        graph.add_edge(from, to1, DependencyKind::Sequential { layer_id: 1 });
501        graph.add_edge(from, to2, DependencyKind::Sequential { layer_id: 1 });
502
503        assert_eq!(graph.edges.len(), 2);
504    }
505
506    #[test]
507    fn execution_order_includes_recorded_nodes() {
508        let mut graph = RenderGraph::new();
509        let filter = graph.add_node(RenderNodeKind::FilterLayer {
510            layer_id: 1,
511            filter: dummy_filter(),
512            wtile_bbox: dummy_bbox(),
513            transform: Affine::IDENTITY,
514        });
515
516        graph.record_node_for_execution(filter);
517
518        let order: Vec<_> = graph.execution_order().collect();
519        assert_eq!(order, vec![filter]);
520    }
521
522    #[test]
523    fn execution_order_includes_only_root_when_present() {
524        let mut graph = RenderGraph::new();
525        let root = graph.add_node(RenderNodeKind::RootLayer {
526            layer_id: 0,
527            wtile_bbox: dummy_bbox(),
528        });
529
530        let order: Vec<_> = graph.execution_order().collect();
531        assert_eq!(order, vec![root]);
532    }
533
534    #[test]
535    fn execution_order_root_comes_last() {
536        let mut graph = RenderGraph::new();
537        let filter1 = graph.add_node(RenderNodeKind::FilterLayer {
538            layer_id: 1,
539            filter: dummy_filter(),
540            wtile_bbox: dummy_bbox(),
541            transform: Affine::IDENTITY,
542        });
543        let filter2 = graph.add_node(RenderNodeKind::FilterLayer {
544            layer_id: 2,
545            filter: dummy_filter(),
546            wtile_bbox: dummy_bbox(),
547            transform: Affine::IDENTITY,
548        });
549        let root = graph.add_node(RenderNodeKind::RootLayer {
550            layer_id: 0,
551            wtile_bbox: dummy_bbox(),
552        });
553
554        graph.record_node_for_execution(filter2);
555        graph.record_node_for_execution(filter1);
556
557        let order: Vec<_> = graph.execution_order().collect();
558        assert_eq!(order, vec![filter2, filter1, root]);
559    }
560
561    #[test]
562    fn topological_sort_diamond_dependency() {
563        let mut graph = RenderGraph::new();
564        let bottom = graph.add_node(RenderNodeKind::FilterLayer {
565            layer_id: 3,
566            filter: dummy_filter(),
567            wtile_bbox: dummy_bbox(),
568            transform: Affine::IDENTITY,
569        });
570        let left = graph.add_node(RenderNodeKind::FilterLayer {
571            layer_id: 1,
572            filter: dummy_filter(),
573            wtile_bbox: dummy_bbox(),
574            transform: Affine::IDENTITY,
575        });
576        let right = graph.add_node(RenderNodeKind::FilterLayer {
577            layer_id: 2,
578            filter: dummy_filter(),
579            wtile_bbox: dummy_bbox(),
580            transform: Affine::IDENTITY,
581        });
582        let top = graph.add_node(RenderNodeKind::RootLayer {
583            layer_id: 0,
584            wtile_bbox: dummy_bbox(),
585        });
586
587        // Diamond: bottom -> left -> top, bottom -> right -> top
588        graph.add_edge(bottom, left, DependencyKind::Sequential { layer_id: 3 });
589        graph.add_edge(bottom, right, DependencyKind::Sequential { layer_id: 3 });
590        graph.add_edge(left, top, DependencyKind::Sequential { layer_id: 1 });
591        graph.add_edge(right, top, DependencyKind::Sequential { layer_id: 2 });
592
593        let sorted = graph.topological_sort();
594        assert_eq!(sorted, vec![bottom, right, left, top]);
595    }
596
597    fn dummy_filter() -> Filter {
598        Filter::from_primitive(crate::filter_effects::FilterPrimitive::Flood {
599            color: AlphaColor::from_rgba8(0, 0, 0, 255),
600        })
601    }
602
603    fn dummy_bbox() -> WideTilesBbox {
604        WideTilesBbox::new([0, 0, 0, 0])
605    }
606}