Skip to main content

lumen_engine/gpu/
binding.rs

1use std::collections::HashSet;
2
3use crate::{
4    composition::Composition,
5    error::RenderError,
6    expr::ExpressionContext,
7    media::MediaStore,
8    node::{NodeId, NodeKind, NodeParamEvalContext, NodeParams, PortRef},
9};
10
11use super::{BoundFrame, CompiledComposition, FramePortRef};
12
13#[derive(Debug)]
14pub struct FrameBindContext<'a> {
15    composition: &'a Composition,
16    frame: u32,
17    media: Option<&'a dyn MediaStore>,
18}
19
20impl<'a> FrameBindContext<'a> {
21    pub fn new(composition: &'a Composition, frame: u32) -> Self {
22        Self {
23            composition,
24            frame,
25            media: None,
26        }
27    }
28
29    pub fn with_media<M: MediaStore>(
30        composition: &'a Composition,
31        frame: u32,
32        media: &'a M,
33    ) -> Self {
34        Self {
35            composition,
36            frame,
37            media: Some(media),
38        }
39    }
40
41    pub fn bind(&self, compiled: &CompiledComposition) -> crate::Result<BoundFrame> {
42        tracing::trace!(
43            target: "lumen_bind",
44            frame = self.frame,
45            nodes = compiled.compiled_nodes.len(),
46            "bind compiled frame"
47        );
48        let mut bound = BoundFrame::new();
49        let output = media_output_port(self.composition)?;
50        let mut visited = HashSet::new();
51        self.bind_port(compiled, &output, self.frame, &mut visited, &mut bound)?;
52        Ok(bound)
53    }
54
55    fn bind_port(
56        &self,
57        compiled: &CompiledComposition,
58        port: &PortRef,
59        frame: u32,
60        visited: &mut HashSet<FramePortRef>,
61        bound: &mut BoundFrame,
62    ) -> crate::Result<()> {
63        if port.is_empty() || !visited.insert(FramePortRef::new(port.clone(), frame)) {
64            return Ok(());
65        }
66
67        let node = self
68            .composition
69            .graph
70            .nodes
71            .get(&port.id)
72            .ok_or(RenderError::MissingNode {
73                frame,
74                node_id: port.id,
75            })?;
76
77        let frame_ctx = self.with_frame(frame);
78        for input in frame_ctx.frame_inputs(node)? {
79            self.bind_port(compiled, &input.port, input.frame, visited, bound)?;
80        }
81
82        if let Some(compiled_node) = compiled.compiled_nodes.get(&port.id) {
83            tracing::trace!(
84                target: "lumen_bind",
85                frame = self.frame,
86                binding_frame = frame,
87                node_id = port.id.0,
88                "bind compiled node"
89            );
90            compiled_node.bind(&frame_ctx, bound)?;
91        }
92
93        Ok(())
94    }
95
96    fn with_frame(&self, frame: u32) -> Self {
97        Self {
98            composition: self.composition,
99            frame,
100            media: self.media,
101        }
102    }
103
104    fn frame_inputs(&self, node: &NodeKind) -> crate::Result<Vec<FramePortRef>> {
105        match node {
106            NodeKind::MediaOutput(node) => {
107                Ok(vec![FramePortRef::new(node.source.clone(), self.frame)])
108            }
109            NodeKind::TimeRemap(node) => {
110                let params = node.params.eval(&NodeParamEvalContext {
111                    node_id: node.id,
112                    expr: &self.expr_context(node.id, "params"),
113                })?;
114                let settings = crate::node::processing::time_remap::TimeRemapSettings {
115                    frame: params.frame,
116                    loop_enabled: params.loop_enabled,
117                    loop_start: params.loop_start,
118                    loop_end: params.loop_end,
119                };
120                Ok(vec![FramePortRef::new(
121                    node.source.clone(),
122                    crate::node::processing::time_remap::remap_frame(settings),
123                )])
124            }
125            NodeKind::Switch(node) => {
126                let params = node.params.eval(&NodeParamEvalContext {
127                    node_id: node.id,
128                    expr: &self.expr_context(node.id, "params"),
129                })?;
130                let selected =
131                    (params.selected_layer >= 0).then_some(params.selected_layer as usize);
132                Ok(selected
133                    .and_then(|index| node.layers.get(index))
134                    .map(|port| vec![FramePortRef::new(port.clone(), self.frame)])
135                    .unwrap_or_default())
136            }
137            _ => Ok(node
138                .input_ports()
139                .into_iter()
140                .map(|port| FramePortRef::new(port, self.frame))
141                .collect()),
142        }
143    }
144
145    pub(crate) fn frame(&self) -> u32 {
146        self.frame
147    }
148
149    pub(crate) fn media(&self) -> Option<&dyn MediaStore> {
150        self.media
151    }
152
153    pub(crate) fn expr_context(
154        &self,
155        node_id: NodeId,
156        property_path: &str,
157    ) -> ExpressionContext<'_> {
158        ExpressionContext {
159            frame: self.frame,
160            fps: self.composition.timeline.fps,
161            width: self.composition.render_settings.width,
162            height: self.composition.render_settings.height,
163            duration_frames: self.composition.timeline.duration_frames,
164            path: Some(format!("{node_id}.{property_path}")),
165            graph: Some(&self.composition.graph),
166        }
167    }
168}
169
170fn media_output_port(composition: &Composition) -> crate::Result<PortRef> {
171    let mut outputs = composition
172        .graph
173        .nodes
174        .iter()
175        .filter_map(|(node_id, node)| matches!(node, NodeKind::MediaOutput(_)).then_some(*node_id));
176    let Some(output) = outputs.next() else {
177        return Err(crate::error::GraphValidationError::MissingMediaOutput.into());
178    };
179    if outputs.next().is_some() {
180        return Err(crate::error::GraphValidationError::MultipleMediaOutputs { count: 2 }.into());
181    }
182    Ok(PortRef::new(output, "output".to_string()))
183}