1use std::collections::{HashMap, HashSet};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
7
8use super::image::CpuImage;
9use super::kernels::{CpuKernel, CpuLut, kernel};
10use super::workers::Workers;
11use crate::result_cache::{CachedPicture, ResultCache};
12use crate::{
13 BlendSpace, CacheInvalidation, EffectCatalog, EffectDef, EffectId, GraphError, Lut3dData,
14 RenderCacheKey, RenderControls, RenderError, RenderGraph, RenderQualityControls,
15 ResultCacheStats,
16};
17
18impl CachedPicture for Arc<CpuImage> {
19 fn cached_bytes(&self) -> u64 {
20 self.retained_bytes()
21 }
22}
23
24pub(crate) struct CatalogEntry {
26 pub(crate) definition: EffectDef,
27 pub(crate) reads_time: bool,
28 pub(crate) kernel: &'static dyn CpuKernel,
29}
30
31#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
33pub struct CpuRenderStats {
34 pub graphs: u64,
36 pub nodes: u64,
38 pub render_micros: u64,
40 pub presented_sources: u64,
42 pub image_high_water_bytes: u64,
44}
45
46#[derive(Default)]
47pub(crate) struct StatCounters {
48 pub(crate) graphs: AtomicU64,
49 pub(crate) nodes: AtomicU64,
50 pub(crate) render_micros: AtomicU64,
51 pub(crate) presented_sources: AtomicU64,
52 pub(crate) image_high_water_bytes: AtomicU64,
53}
54
55pub struct CpuRenderer {
66 pub(crate) workers: Workers,
67 pub(crate) blend_space: BlendSpace,
68 quality: Mutex<RenderQualityControls>,
69 cache: Mutex<ResultCache<Arc<CpuImage>>>,
70 catalog: HashMap<EffectId, CatalogEntry>,
71 internal: HashSet<EffectId>,
72 resources: HashMap<String, Arc<CpuLut>>,
73 pub(crate) coverage: Mutex<alpha2_geometry::raster::CoverageCache>,
75 pub(crate) stats: StatCounters,
76 frames: AtomicU64,
77 repeated: AtomicU64,
78}
79
80impl std::fmt::Debug for CpuRenderer {
81 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 formatter
83 .debug_struct("CpuRenderer")
84 .field("threads", &self.workers.threads())
85 .field("blend_space", &self.blend_space)
86 .finish_non_exhaustive()
87 }
88}
89
90impl CpuRenderer {
91 #[must_use]
94 pub fn new(threads: usize) -> Self {
95 let mut catalog = HashMap::new();
96 let mut internal = HashSet::new();
97 for (definition, is_internal) in crate::builtins::internal_effect_definitions()
98 .into_iter()
99 .map(|definition| (definition, true))
100 .chain(
101 crate::builtin_effect_definitions()
102 .into_iter()
103 .chain(crate::builtin_transition_definitions())
104 .map(|definition| (definition, false)),
105 )
106 {
107 let Some(kernel) = kernel(&definition.id, definition.version) else {
108 continue;
109 };
110 let id = EffectId::new(definition.id.clone(), definition.version);
111 if is_internal {
112 internal.insert(id.clone());
113 }
114 catalog.insert(
115 id,
116 CatalogEntry {
117 reads_time: crate::registry::body_reads_time(&definition.wgsl_body),
118 definition,
119 kernel,
120 },
121 );
122 }
123 Self {
124 workers: Workers::new(threads),
125 blend_space: BlendSpace::default(),
126 quality: Mutex::new(RenderQualityControls::default()),
127 cache: Mutex::new(ResultCache::default()),
128 catalog,
129 internal,
130 resources: HashMap::new(),
131 coverage: Mutex::new(alpha2_geometry::raster::CoverageCache::default()),
132 stats: StatCounters::default(),
133 frames: AtomicU64::new(0),
134 repeated: AtomicU64::new(0),
135 }
136 }
137
138 #[must_use]
141 pub const fn with_blend_space(mut self, blend_space: BlendSpace) -> Self {
142 self.blend_space = blend_space;
143 self
144 }
145
146 #[must_use]
148 pub const fn threads(&self) -> usize {
149 self.workers.threads()
150 }
151
152 #[must_use]
154 pub fn quality_controls(&self) -> RenderQualityControls {
155 *self.quality.lock().unwrap_or_else(PoisonError::into_inner)
156 }
157
158 #[must_use]
162 pub fn unsupported_builtins() -> Vec<(String, u32)> {
163 crate::builtin_effect_definitions()
164 .into_iter()
165 .chain(crate::builtin_transition_definitions())
166 .chain(crate::builtins::internal_effect_definitions())
167 .filter(|definition| kernel(&definition.id, definition.version).is_none())
168 .map(|definition| (definition.id, definition.version))
169 .collect()
170 }
171
172 pub fn set_result_cache_budget(&self, budget_bytes: u64) {
175 self.lock_cache().set_budget(budget_bytes);
176 }
177
178 #[must_use]
180 pub fn result_cache_stats(&self) -> ResultCacheStats {
181 self.lock_cache().stats()
182 }
183
184 pub fn clear_result_cache(&self) {
186 self.lock_cache().clear(CacheInvalidation::Explicit);
187 }
188
189 #[must_use]
191 pub fn stats(&self) -> CpuRenderStats {
192 CpuRenderStats {
193 graphs: self.stats.graphs.load(Ordering::Relaxed),
194 nodes: self.stats.nodes.load(Ordering::Relaxed),
195 render_micros: self.stats.render_micros.load(Ordering::Relaxed),
196 presented_sources: self.stats.presented_sources.load(Ordering::Relaxed),
197 image_high_water_bytes: self.stats.image_high_water_bytes.load(Ordering::Relaxed),
198 }
199 }
200
201 #[must_use]
204 pub fn frame_outcomes(&self) -> (u64, u64) {
205 (
206 self.frames.load(Ordering::Relaxed),
207 self.repeated.load(Ordering::Relaxed),
208 )
209 }
210
211 pub fn register_lut3d(
218 &mut self,
219 id: impl Into<String>,
220 data: &Lut3dData,
221 ) -> Result<(), RenderError> {
222 let id = id.into();
223 if id.is_empty() || !data.is_valid(u32::MAX) {
224 return Err(RenderError::InvalidEffectResource);
225 }
226 let values = data
228 .values
229 .iter()
230 .map(|rgb| rgb.map(|value| half::f16::from_f32(value).to_f32()))
231 .collect();
232 self.resources.insert(
233 id,
234 Arc::new(CpuLut {
235 edge: data.edge_length,
236 domain_min: data.domain_min,
237 domain_max: data.domain_max,
238 values,
239 }),
240 );
241 self.lock_cache().clear(CacheInvalidation::EffectResources);
242 Ok(())
243 }
244
245 pub fn clear_effect_resources(&mut self) {
247 self.resources.clear();
248 self.lock_cache().clear(CacheInvalidation::EffectResources);
249 }
250
251 pub fn release_idle_memory(&self) -> u64 {
254 let mut cache = self.lock_cache();
255 let bytes = cache.stats().bytes;
256 cache.clear(CacheInvalidation::Explicit);
257 bytes
258 }
259
260 pub fn unregister_effect_resource(&mut self, id: &str) -> bool {
262 let removed = self.resources.remove(id).is_some();
263 if removed {
264 self.lock_cache().clear(CacheInvalidation::EffectResources);
265 }
266 removed
267 }
268
269 pub(crate) fn resource(&self, id: &str) -> Option<&Arc<CpuLut>> {
270 self.resources.get(id)
271 }
272
273 pub(crate) fn entry(&self, id: &EffectId) -> Option<&CatalogEntry> {
274 self.catalog.get(id)
275 }
276
277 pub(crate) fn lock_cache(&self) -> MutexGuard<'_, ResultCache<Arc<CpuImage>>> {
278 self.cache.lock().unwrap_or_else(PoisonError::into_inner)
279 }
280}
281
282impl EffectCatalog for CpuRenderer {
283 fn effect_definition(&self, id: &EffectId) -> Option<&EffectDef> {
284 self.catalog.get(id).map(|entry| &entry.definition)
285 }
286
287 fn effect_reads_time(&self, id: &EffectId) -> bool {
288 if let Some(passes) = crate::gaussian_blur::expanded_passes(id.as_str()) {
289 return passes
290 .iter()
291 .any(|pass| self.effect_reads_time(&EffectId::from_static(pass, 1)));
292 }
293 self.catalog.get(id).is_none_or(|entry| entry.reads_time)
294 }
295
296 fn project_effect_id(&self, id: &str, version: u32) -> Option<&EffectId> {
297 let key = EffectId::new(id.to_owned(), version);
298 self.catalog
299 .get_key_value(&key)
300 .map(|(id, _)| id)
301 .filter(|id| !self.internal.contains(*id))
302 }
303}
304
305impl RenderControls for CpuRenderer {
306 fn set_quality_controls(&self, max_filter_samples: u32, motion_blur_subframes: u32) {
307 let next = RenderQualityControls {
308 max_filter_samples: max_filter_samples
309 .clamp(1, RenderQualityControls::MAX_FILTER_SAMPLES),
310 motion_blur_subframes: motion_blur_subframes.max(1),
311 };
312 let mut quality = self.quality.lock().unwrap_or_else(PoisonError::into_inner);
313 if *quality != next {
314 *quality = next;
315 drop(quality);
316 self.lock_cache().clear(CacheInvalidation::Quality);
318 }
319 }
320
321 fn set_texture_budget_bytes(&self, _budget_bytes: u64) {}
324
325 fn content_epoch(&self) -> u64 {
326 self.lock_cache().clears()
327 }
328
329 fn record_frame_outcome(&self, presented: bool, repeated: bool) {
330 if presented {
331 self.frames.fetch_add(1, Ordering::Relaxed);
332 }
333 if repeated {
334 self.repeated.fetch_add(1, Ordering::Relaxed);
335 }
336 }
337
338 fn graph_cache_key(&self, graph: &RenderGraph<'_>) -> Result<RenderCacheKey, GraphError> {
339 let quality = self.quality_controls();
340 let plan = graph.compile_with_quality(quality)?;
341 Ok(crate::graph_cache::cpu_renderer_cache_key(
342 plan.cache_key,
343 graph.blend_space.unwrap_or(self.blend_space).shader_value(),
344 quality,
345 ))
346 }
347}