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};
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.frame_pool,
133 &self.coverage,
134 scene,
135 graph.size,
136 blend_space,
137 )?),
138 RenderNode::Composite { layers, background } => {
139 let inputs = layers
140 .iter()
141 .map(|layer| LayerInput {
142 image: output(&outputs, layer.source),
143 visual: layer.visual,
144 blend_mode: layer.blend_mode,
145 })
146 .collect::<Vec<_>>();
147 Arc::new(composite(
148 &self.workers,
149 &self.frame_pool,
150 &inputs,
151 CompositeSettings {
152 size: graph.size,
153 background: *background,
154 blend_space,
155 max_filter_samples: quality.max_filter_samples,
156 },
157 )?)
158 }
159 };
160 if !matches!(graph.nodes[node.index], RenderNode::Source(_)) {
161 drawn += 1;
162 }
163 if let Some((key, pinned)) = cached.store[node.index] {
164 inserts.push((key, Arc::clone(&image), pinned));
165 }
166 outputs[node.index] = Some(image);
167 high_water = high_water.max(
168 outputs
169 .iter()
170 .flatten()
171 .map(|image| image.retained_bytes())
172 .sum(),
173 );
174 release_inputs(graph, &plan.last_use, &mut outputs, node, position);
175 }
176 let image = outputs[plan.output.index]
177 .take()
178 .ok_or(GraphError::SourceOutput)?;
179 if !inserts.is_empty() {
180 let mut cache = self.lock_cache();
181 for (key, image, pinned) in &inserts {
182 cache.insert(*key, image, *pinned);
183 }
184 }
185 self.stats.graphs.fetch_add(1, Ordering::Relaxed);
186 self.stats.nodes.fetch_add(drawn, Ordering::Relaxed);
187 self.stats.render_micros.fetch_add(
188 u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
189 Ordering::Relaxed,
190 );
191 self.stats
192 .image_high_water_bytes
193 .fetch_max(high_water, Ordering::Relaxed);
194 Ok(image)
195 }
196
197 pub fn render_graph_into_yuv420p(
204 &self,
205 graph: &RenderGraph<'_>,
206 encoding: YuvEncoding,
207 targets: &mut Yuv420pTargets<'_>,
208 ) -> Result<(), RenderError> {
209 let image = self
210 .execute_graph(graph)
211 .map_err(GraphError::into_render_error)?;
212 let started = Instant::now();
213 super::pack::pack_yuv420p(&self.workers, &image, encoding, targets)?;
214 self.stats.render_micros.fetch_add(
215 u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
216 Ordering::Relaxed,
217 );
218 Ok(())
219 }
220
221 fn draw_kernel(
222 &self,
223 graph: &RenderGraph<'_>,
224 outputs: &[Option<Arc<CpuImage>>],
225 node: &KernelNode<'_>,
226 ) -> Result<CpuImage, GraphError> {
227 let entry = self.entry(node.effect).ok_or(RenderError::UnknownEffect)?;
228 let definition = &entry.definition;
229 if definition.kind != node.kind {
230 return Err(GraphError::EffectKindMismatch);
231 }
232 crate::runtime_effect::validate_runtime(node.runtime)?;
233 crate::runtime_effect::encode_params(&definition.params, node.params)?;
234 if node
235 .resources
236 .iter()
237 .any(|(name, _)| !definition.resources.iter().any(|spec| spec.name == name))
238 {
239 return Err(RenderError::InvalidEffectParams.into());
240 }
241 let mut resources: Vec<(&str, &CpuLut)> = Vec::with_capacity(definition.resources.len());
242 for spec in &definition.resources {
243 let id = node
244 .resources
245 .get(&spec.name)
246 .ok_or(RenderError::MissingEffectResource)?;
247 let lut = self
248 .resource(id)
249 .ok_or(RenderError::MissingEffectResource)?;
250 resources.push((spec.name.as_str(), lut));
251 }
252 if node
253 .inputs
254 .iter()
255 .any(|(name, _)| !definition.inputs.iter().any(|spec| spec.name == *name))
256 {
257 return Err(RenderError::InvalidEffectParams.into());
258 }
259 let mut inputs = Vec::with_capacity(definition.inputs.len());
260 for spec in &definition.inputs {
261 let (_, input) = node
262 .inputs
263 .iter()
264 .find(|(name, _)| *name == spec.name)
265 .ok_or(RenderError::MissingEffectInput)?;
266 inputs.push((spec.name.as_str(), output(outputs, *input)));
267 }
268 let transparent = CpuImage::transparent(graph.size);
269 let source_a = node.source_a.map_or(&transparent, |id| output(outputs, id));
270 let source_b = node.source_b.map_or(source_a, |id| output(outputs, id));
271 let context = KernelContext {
272 size: graph.size,
273 definition,
274 params: node.params,
275 runtime: node.runtime,
276 max_filter_samples: self.quality_controls().max_filter_samples,
277 source_a,
278 source_b,
279 inputs: &inputs,
280 resources: &resources,
281 };
282 let kernel = entry.kernel;
283 let matching = [source_a, source_b]
286 .into_iter()
287 .chain(inputs.iter().map(|(_, image)| *image))
288 .all(|image| image.size() == graph.size);
289 let bounds = if matching {
290 kernel.bounds(&context)
291 } else {
292 PixelBox::whole(graph.size)
293 }
294 .intersect(PixelBox::whole(graph.size));
295 if bounds.is_empty() {
296 return Ok(CpuImage::transparent(graph.size));
297 }
298 let width = bounds.width as usize;
299 let mut pixels = self.frame_pool.take(bounds.pixels());
300 self.workers.bands_with(
301 &mut pixels,
302 width,
303 || vec![[0.0_f32; 4]; width],
304 |row, first_row, band| {
305 row.fill([0.0; 4]);
306 for (index, line) in band.chunks_mut(width).enumerate() {
307 #[allow(clippy::cast_possible_truncation)]
308 let y = bounds.y + (first_row + index) as u32;
309 kernel.row(&context, y, bounds.x, row);
310 for (out, value) in line.iter_mut().zip(row.iter()) {
311 *out = half::store4(*value);
312 }
313 }
314 },
315 );
316 Ok(CpuImage::pooled(
317 graph.size,
318 bounds,
319 pixels,
320 &self.frame_pool,
321 ))
322 }
323
324 fn cached_results(
326 &self,
327 graph: &RenderGraph<'_>,
328 order: &[RenderNodeId],
329 output: RenderNodeId,
330 blend_space: f32,
331 ) -> CachedResults {
332 let count = graph.nodes.len();
333 let mut cached = CachedResults {
334 hits: vec![None; count],
335 store: vec![None; count],
336 needed: vec![true; count],
337 };
338 if graph.intents.iter().all(Option::is_none) {
339 return cached;
340 }
341 let mut cache = self.lock_cache();
342 if !cache.enabled() {
343 return cached;
344 }
345 let eligibility = graph.eligibility();
346 let (mut hits, mut misses, mut ineligible) = (0, 0, 0);
347 cached.needed = vec![false; count];
348 cached.needed[output.index] = true;
349 for node in order.iter().rev() {
350 if !cached.needed[node.index] {
351 continue;
352 }
353 if let Some(intent) = graph.intents[node.index] {
354 if eligibility[node.index] == CacheEligibility::Eligible {
355 let key = renderer_node_key(graph.cache_keys[node.index], blend_space);
356 let pinned = intent == CacheIntent::Pin;
357 if let Some(image) = cache.get(key, pinned) {
358 cached.hits[node.index] = Some(image);
359 hits += 1;
360 continue;
361 }
362 cached.store[node.index] = Some((key, pinned));
363 misses += 1;
364 } else if intent != CacheIntent::Bypass {
365 ineligible += 1;
366 }
367 }
368 for input in graph.nodes[node.index].dependencies() {
369 cached.needed[input.index] = true;
370 }
371 }
372 let skipped = order
373 .iter()
374 .filter(|node| {
375 !cached.needed[node.index]
376 && cached.hits[node.index].is_none()
377 && !matches!(graph.nodes[node.index], RenderNode::Source(_))
378 })
379 .count() as u64;
380 cache.record(hits, misses, ineligible, skipped);
381 cached
382 }
383}
384
385struct CachedResults {
386 hits: Vec<Option<Arc<CpuImage>>>,
387 store: Vec<Option<(RenderCacheKey, bool)>>,
388 needed: Vec<bool>,
389}
390
391fn output(outputs: &[Option<Arc<CpuImage>>], node: RenderNodeId) -> &CpuImage {
392 outputs[node.index]
393 .as_deref()
394 .expect("compiled dependency has an output")
395}
396
397fn release_inputs(
399 graph: &RenderGraph<'_>,
400 last_use: &[Option<usize>],
401 outputs: &mut [Option<Arc<CpuImage>>],
402 node: RenderNodeId,
403 position: usize,
404) {
405 for dependency in graph.nodes[node.index].dependencies() {
406 if last_use[dependency.index] == Some(position) {
407 outputs[dependency.index] = None;
408 }
409 }
410}