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