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