1use std::sync::Arc;
5use std::sync::atomic::Ordering;
6
7use alpha2_media::YuvEncoding;
8use web_time::Instant;
9
10use super::CpuRenderer;
11use super::composite::{CompositeSettings, LayerInput, composite};
12use super::half;
13use super::image::{CpuImage, PixelBox, Pixels};
14use super::kernels::{CpuLut, KernelContext};
15use crate::graph::{RenderGraph, RenderNode, RenderNodeId};
16use crate::graph_cache::renderer_node_key;
17use crate::{
18 CacheEligibility, CacheIntent, EffectId, EffectKind, EffectParams, EffectResources,
19 EffectRuntime, GraphError, RenderCacheKey, RenderError, Yuv420pTargets,
20};
21
22struct KernelNode<'a> {
23 effect: &'a EffectId,
24 kind: EffectKind,
25 source_a: Option<RenderNodeId>,
26 source_b: Option<RenderNodeId>,
27 inputs: Vec<(&'a str, RenderNodeId)>,
28 params: &'a EffectParams<'a>,
29 resources: &'a EffectResources,
30 runtime: EffectRuntime,
31}
32
33impl CpuRenderer {
34 #[allow(clippy::too_many_lines)]
40 pub fn execute_graph(&self, graph: &RenderGraph<'_>) -> Result<Arc<CpuImage>, GraphError> {
41 let started = Instant::now();
42 let quality = self.quality_controls();
43 let plan = graph.compile_with_quality(quality)?;
44 if matches!(graph.nodes[plan.output.index], RenderNode::Source(_)) {
45 return Err(GraphError::SourceOutput);
46 }
47 let blend_space = graph.blend_space.unwrap_or(self.blend_space);
48 let cached =
49 self.cached_results(graph, &plan.order, plan.output, blend_space.shader_value());
50 let mut outputs: Vec<Option<Arc<CpuImage>>> = vec![None; graph.nodes.len()];
51 let mut inserts = Vec::new();
52 let mut drawn = 0_u64;
53 let mut high_water = 0_u64;
54 for (position, node) in plan.order.iter().copied().enumerate() {
55 if let Some(hit) = cached.hits[node.index].clone() {
56 outputs[node.index] = Some(hit);
57 release_inputs(graph, &plan.last_use, &mut outputs, node, position);
58 continue;
59 }
60 if !cached.needed[node.index] {
61 release_inputs(graph, &plan.last_use, &mut outputs, node, position);
62 continue;
63 }
64 let image = match &graph.nodes[node.index] {
65 RenderNode::Source(view) => {
66 let source = view.cpu_source().ok_or(RenderError::InvalidSource)?;
67 source.presented(self)
68 }
69 RenderNode::Effect {
70 effect,
71 source,
72 inputs,
73 params,
74 resources,
75 runtime,
76 } => Arc::new(self.draw_kernel(
77 graph,
78 &outputs,
79 &KernelNode {
80 effect,
81 kind: EffectKind::Effect,
82 source_a: Some(*source),
83 source_b: None,
84 inputs: inputs.iter().collect(),
85 params,
86 resources,
87 runtime: *runtime,
88 },
89 )?),
90 RenderNode::Transition {
91 effect,
92 from,
93 to,
94 params,
95 resources,
96 runtime,
97 } => Arc::new(self.draw_kernel(
98 graph,
99 &outputs,
100 &KernelNode {
101 effect,
102 kind: EffectKind::Transition,
103 source_a: Some(*from),
104 source_b: Some(*to),
105 inputs: Vec::new(),
106 params,
107 resources,
108 runtime: *runtime,
109 },
110 )?),
111 RenderNode::Generator {
112 effect,
113 params,
114 resources,
115 runtime,
116 } => Arc::new(self.draw_kernel(
117 graph,
118 &outputs,
119 &KernelNode {
120 effect,
121 kind: EffectKind::Generator,
122 source_a: None,
123 source_b: None,
124 inputs: Vec::new(),
125 params,
126 resources,
127 runtime: *runtime,
128 },
129 )?),
130 RenderNode::Vector { scene } => Arc::new(super::vector::draw(
131 &self.workers,
132 &self.coverage,
133 scene,
134 graph.size,
135 blend_space,
136 )?),
137 RenderNode::Composite { layers, background } => {
138 let inputs = layers
139 .iter()
140 .map(|layer| LayerInput {
141 image: output(&outputs, layer.source),
142 visual: layer.visual,
143 blend_mode: layer.blend_mode,
144 })
145 .collect::<Vec<_>>();
146 Arc::new(composite(
147 &self.workers,
148 &inputs,
149 CompositeSettings {
150 size: graph.size,
151 background: *background,
152 blend_space,
153 max_filter_samples: quality.max_filter_samples,
154 },
155 )?)
156 }
157 };
158 if !matches!(graph.nodes[node.index], RenderNode::Source(_)) {
159 drawn += 1;
160 }
161 if let Some((key, pinned)) = cached.store[node.index] {
162 inserts.push((key, Arc::clone(&image), pinned));
163 }
164 outputs[node.index] = Some(image);
165 high_water = high_water.max(
166 outputs
167 .iter()
168 .flatten()
169 .map(|image| image.retained_bytes())
170 .sum(),
171 );
172 release_inputs(graph, &plan.last_use, &mut outputs, node, position);
173 }
174 let image = outputs[plan.output.index]
175 .take()
176 .ok_or(GraphError::SourceOutput)?;
177 if !inserts.is_empty() {
178 let mut cache = self.lock_cache();
179 for (key, image, pinned) in &inserts {
180 cache.insert(*key, image, *pinned);
181 }
182 }
183 self.stats.graphs.fetch_add(1, Ordering::Relaxed);
184 self.stats.nodes.fetch_add(drawn, Ordering::Relaxed);
185 self.stats.render_micros.fetch_add(
186 u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
187 Ordering::Relaxed,
188 );
189 self.stats
190 .image_high_water_bytes
191 .fetch_max(high_water, Ordering::Relaxed);
192 Ok(image)
193 }
194
195 pub fn render_graph_into_yuv420p(
202 &self,
203 graph: &RenderGraph<'_>,
204 encoding: YuvEncoding,
205 targets: &mut Yuv420pTargets<'_>,
206 ) -> Result<(), RenderError> {
207 let image = self
208 .execute_graph(graph)
209 .map_err(GraphError::into_render_error)?;
210 let started = Instant::now();
211 super::pack::pack_yuv420p(&self.workers, &image, encoding, targets)?;
212 self.stats.render_micros.fetch_add(
213 u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
214 Ordering::Relaxed,
215 );
216 Ok(())
217 }
218
219 fn draw_kernel(
220 &self,
221 graph: &RenderGraph<'_>,
222 outputs: &[Option<Arc<CpuImage>>],
223 node: &KernelNode<'_>,
224 ) -> Result<CpuImage, GraphError> {
225 let entry = self.entry(node.effect).ok_or(RenderError::UnknownEffect)?;
226 let definition = &entry.definition;
227 if definition.kind != node.kind {
228 return Err(GraphError::EffectKindMismatch);
229 }
230 crate::runtime_effect::validate_runtime(node.runtime)?;
231 crate::runtime_effect::encode_params(&definition.params, node.params)?;
232 if node
233 .resources
234 .iter()
235 .any(|(name, _)| !definition.resources.iter().any(|spec| spec.name == name))
236 {
237 return Err(RenderError::InvalidEffectParams.into());
238 }
239 let mut resources: Vec<(&str, &CpuLut)> = Vec::with_capacity(definition.resources.len());
240 for spec in &definition.resources {
241 let id = node
242 .resources
243 .get(&spec.name)
244 .ok_or(RenderError::MissingEffectResource)?;
245 let lut = self
246 .resource(id)
247 .ok_or(RenderError::MissingEffectResource)?;
248 resources.push((spec.name.as_str(), lut));
249 }
250 if node
251 .inputs
252 .iter()
253 .any(|(name, _)| !definition.inputs.iter().any(|spec| spec.name == *name))
254 {
255 return Err(RenderError::InvalidEffectParams.into());
256 }
257 let mut inputs = Vec::with_capacity(definition.inputs.len());
258 for spec in &definition.inputs {
259 let (_, input) = node
260 .inputs
261 .iter()
262 .find(|(name, _)| *name == spec.name)
263 .ok_or(RenderError::MissingEffectInput)?;
264 inputs.push((spec.name.as_str(), output(outputs, *input)));
265 }
266 let transparent = CpuImage::transparent(graph.size);
267 let source_a = node.source_a.map_or(&transparent, |id| output(outputs, id));
268 let source_b = node.source_b.map_or(source_a, |id| output(outputs, id));
269 let context = KernelContext {
270 size: graph.size,
271 definition,
272 params: node.params,
273 runtime: node.runtime,
274 max_filter_samples: self.quality_controls().max_filter_samples,
275 source_a,
276 source_b,
277 inputs: &inputs,
278 resources: &resources,
279 };
280 let kernel = entry.kernel;
281 let matching = [source_a, source_b]
284 .into_iter()
285 .chain(inputs.iter().map(|(_, image)| *image))
286 .all(|image| image.size() == graph.size);
287 let bounds = if matching {
288 kernel.bounds(&context)
289 } else {
290 PixelBox::whole(graph.size)
291 }
292 .intersect(PixelBox::whole(graph.size));
293 if bounds.is_empty() {
294 return Ok(CpuImage::transparent(graph.size));
295 }
296 let width = bounds.width as usize;
297 let mut pixels = vec![[0_u16; 4]; bounds.pixels()];
298 self.workers.bands(&mut pixels, width, |first_row, band| {
299 let mut row = vec![[0.0_f32; 4]; width];
300 for (index, line) in band.chunks_mut(width).enumerate() {
301 #[allow(clippy::cast_possible_truncation)]
302 let y = bounds.y + (first_row + index) as u32;
303 kernel.row(&context, y, bounds.x, &mut row);
304 for (out, value) in line.iter_mut().zip(&row) {
305 *out = half::store4(*value);
306 }
307 }
308 });
309 Ok(CpuImage::new(graph.size, bounds, Pixels::Half(pixels)))
310 }
311
312 fn cached_results(
314 &self,
315 graph: &RenderGraph<'_>,
316 order: &[RenderNodeId],
317 output: RenderNodeId,
318 blend_space: f32,
319 ) -> CachedResults {
320 let count = graph.nodes.len();
321 let mut cached = CachedResults {
322 hits: vec![None; count],
323 store: vec![None; count],
324 needed: vec![true; count],
325 };
326 if graph.intents.iter().all(Option::is_none) {
327 return cached;
328 }
329 let mut cache = self.lock_cache();
330 if !cache.enabled() {
331 return cached;
332 }
333 let eligibility = graph.eligibility();
334 let (mut hits, mut misses, mut ineligible) = (0, 0, 0);
335 cached.needed = vec![false; count];
336 cached.needed[output.index] = true;
337 for node in order.iter().rev() {
338 if !cached.needed[node.index] {
339 continue;
340 }
341 if let Some(intent) = graph.intents[node.index] {
342 if eligibility[node.index] == CacheEligibility::Eligible {
343 let key = renderer_node_key(graph.cache_keys[node.index], blend_space);
344 let pinned = intent == CacheIntent::Pin;
345 if let Some(image) = cache.get(key, pinned) {
346 cached.hits[node.index] = Some(image);
347 hits += 1;
348 continue;
349 }
350 cached.store[node.index] = Some((key, pinned));
351 misses += 1;
352 } else if intent != CacheIntent::Bypass {
353 ineligible += 1;
354 }
355 }
356 for input in graph.nodes[node.index].dependencies() {
357 cached.needed[input.index] = true;
358 }
359 }
360 let skipped = order
361 .iter()
362 .filter(|node| {
363 !cached.needed[node.index]
364 && cached.hits[node.index].is_none()
365 && !matches!(graph.nodes[node.index], RenderNode::Source(_))
366 })
367 .count() as u64;
368 cache.record(hits, misses, ineligible, skipped);
369 cached
370 }
371}
372
373struct CachedResults {
374 hits: Vec<Option<Arc<CpuImage>>>,
375 store: Vec<Option<(RenderCacheKey, bool)>>,
376 needed: Vec<bool>,
377}
378
379fn output(outputs: &[Option<Arc<CpuImage>>], node: RenderNodeId) -> &CpuImage {
380 outputs[node.index]
381 .as_deref()
382 .expect("compiled dependency has an output")
383}
384
385fn release_inputs(
387 graph: &RenderGraph<'_>,
388 last_use: &[Option<usize>],
389 outputs: &mut [Option<Arc<CpuImage>>],
390 node: RenderNodeId,
391 position: usize,
392) {
393 for dependency in graph.nodes[node.index].dependencies() {
394 if last_use[dependency.index] == Some(position) {
395 outputs[dependency.index] = None;
396 }
397 }
398}