lumen_engine/gpu/
binding.rs1use 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}