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