Skip to main content

alpha2_render/
graph_plan.rs

1use crate::RenderQualityControls;
2use crate::graph::{GraphError, RenderCacheKey, RenderGraph, RenderNode, RenderNodeId};
3
4/// Compiled execution and lifetime schedule for a render graph.
5#[derive(Clone, Debug)]
6pub struct RenderPlan {
7    pub(crate) graph: u32,
8    pub(crate) order: Vec<RenderNodeId>,
9    pub(crate) last_use: Vec<Option<usize>>,
10    pub(crate) output: RenderNodeId,
11    pub(crate) cache_key: RenderCacheKey,
12    pub(crate) filter_sample_limits: Vec<Option<u32>>,
13    pub(crate) temporal_offsets: Vec<f32>,
14}
15
16impl RenderPlan {
17    /// Reachable nodes in deterministic insertion order.
18    #[must_use]
19    pub fn execution_order(&self) -> &[RenderNodeId] {
20        &self.order
21    }
22
23    /// Returns the execution position after which a node can be released.
24    #[must_use]
25    pub fn last_use(&self, node: RenderNodeId) -> Option<usize> {
26        (node.graph == self.graph)
27            .then(|| self.last_use.get(node.index).copied().flatten())
28            .flatten()
29    }
30
31    /// Returns the structural cache key of the selected output.
32    #[must_use]
33    pub const fn cache_key(&self) -> RenderCacheKey {
34        self.cache_key
35    }
36
37    /// Quality-controlled filter sample limit for one node.
38    #[must_use]
39    pub fn filter_sample_limit(&self, node: RenderNodeId) -> Option<u32> {
40        (node.graph == self.graph)
41            .then(|| self.filter_sample_limits.get(node.index).copied().flatten())
42            .flatten()
43    }
44
45    /// Centered, normalized temporal sampling offsets for motion-aware runners.
46    #[must_use]
47    pub fn temporal_offsets(&self) -> &[f32] {
48        &self.temporal_offsets
49    }
50}
51
52impl RenderGraph<'_> {
53    /// Compiles reachable nodes and their final-use positions.
54    ///
55    /// # Errors
56    /// Returns [`GraphError::MissingOutput`] when no output was selected.
57    pub fn compile(&self) -> Result<RenderPlan, GraphError> {
58        self.compile_with_quality(RenderQualityControls::default())
59    }
60
61    /// Compiles with explicit filter and temporal sampling controls.
62    ///
63    /// # Errors
64    /// Returns [`GraphError::MissingOutput`] when no output was selected.
65    pub fn compile_with_quality(
66        &self,
67        quality: RenderQualityControls,
68    ) -> Result<RenderPlan, GraphError> {
69        let output = self.output.ok_or(GraphError::MissingOutput)?;
70        let mut reachable = vec![false; self.nodes.len()];
71        mark_reachable(output, &self.nodes, &mut reachable);
72        let order = reachable
73            .iter()
74            .enumerate()
75            .filter(|(_, is_reachable)| **is_reachable)
76            .map(|(index, _)| RenderNodeId {
77                graph: self.id,
78                index,
79            })
80            .collect::<Vec<_>>();
81        let mut last_use = vec![None; self.nodes.len()];
82        for (position, node) in order.iter().enumerate() {
83            for dependency in self.nodes[node.index].dependencies() {
84                last_use[dependency.index] = Some(position);
85            }
86        }
87        last_use[output.index] = Some(order.len());
88        let filter_sample_limits = self
89            .nodes
90            .iter()
91            .map(|node| match node {
92                RenderNode::Effect { .. }
93                | RenderNode::Transition { .. }
94                | RenderNode::Generator { .. } => Some(quality.max_filter_samples.max(1)),
95                RenderNode::Source(_)
96                | RenderNode::Composite { .. }
97                | RenderNode::Vector { .. } => None,
98            })
99            .collect();
100        let count = quality.motion_blur_subframes.max(1);
101        #[allow(clippy::cast_precision_loss)]
102        let temporal_offsets = (0..count)
103            .map(|index| (index as f32 + 0.5) / count as f32 - 0.5)
104            .collect();
105        Ok(RenderPlan {
106            graph: self.id,
107            order,
108            last_use,
109            output,
110            cache_key: self.cache_keys[output.index],
111            filter_sample_limits,
112            temporal_offsets,
113        })
114    }
115}
116
117fn mark_reachable(node: RenderNodeId, nodes: &[RenderNode<'_>], reachable: &mut [bool]) {
118    let mut pending = vec![node];
119    while let Some(node) = pending.pop() {
120        if reachable[node.index] {
121            continue;
122        }
123        reachable[node.index] = true;
124        pending.extend(nodes[node.index].dependencies());
125    }
126}