ferroui_base/rendering/composition/
compositing_renderer.rs1use super::drawing::RenderDataDrawingContext;
2use super::hit_testing::{GeometryCompositionHitTester, ICompositionHitTester, PointCompositionHitTester};
3use super::server::RenderSurfaces;
4use super::{
5 CompositionDrawListVisual, CompositionTarget, CompositionTransparencyLevel, CompositionVisual, Compositor,
6};
7use crate::media::{DrawingContext, Geometry, GeometryHitTestResult, IntersectionResult, MediaContext};
8use crate::reactive::{Disposable, IDisposable};
9use crate::rendering::{
10 IHitTester, IPresentationSource, IRenderer, RendererDiagnostics, SceneInvalidatedEventArgs,
11};
12use crate::threading::DispatcherPriority;
13use crate::utilities::HandlerList;
14use crate::{Point, Rect, Ref, Size, Visual};
15use std::any::{Any, TypeId};
16use std::cell::{Cell, RefCell};
17use std::collections::{HashMap, HashSet};
18use std::rc::{Rc, Weak};
19use std::sync::atomic::{AtomicU64, Ordering};
20
21static NEXT_RENDERER_KEY: AtomicU64 = AtomicU64::new(1);
22
23crate::ferro_thread_local! {
24 static RENDERERS: RefCell<HashMap<u64, Weak<CompositingRenderer>>> = RefCell::new(HashMap::new());
27}
28
29#[derive(Default)]
31struct VisualSet {
32 items: Vec<Ref<Visual>>,
33 keys: HashSet<*const Visual>,
34}
35
36impl VisualSet {
37 fn add(&mut self, visual: &Visual) {
38 if self.keys.insert(visual as *const Visual) {
39 self.items.push(visual.to_ref());
40 }
41 }
42
43 fn contains(&self, visual: &Visual) -> bool {
44 self.keys.contains(&(visual as *const Visual))
45 }
46
47 fn remove(&mut self, visual: &Visual) {
48 let key = visual as *const Visual;
49 if self.keys.remove(&key) {
50 self.items.retain(|item| !std::ptr::eq(&**item, key));
51 }
52 }
53
54 fn is_empty(&self) -> bool {
55 self.items.is_empty()
56 }
57
58 fn clear(&mut self) {
59 self.items.clear();
60 self.keys.clear();
61 }
62}
63
64pub struct CompositingRenderer {
66 this: Weak<CompositingRenderer>,
67 key: u64,
68 root: Weak<dyn IPresentationSource>,
69 compositor: Rc<Compositor>,
70 recorder: RefCell<RenderDataDrawingContext>,
71 dirty: RefCell<VisualSet>,
72 recalculate_children: RefCell<VisualSet>,
73 queued_update: Cell<bool>,
74 queued_scene_invalidation: Cell<bool>,
75 updating: Cell<bool>,
76 is_disposed: Cell<bool>,
77 composition_target: Rc<CompositionTarget>,
78 diagnostics: Rc<RendererDiagnostics>,
79 diagnostics_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
80 scene_invalidated: HandlerList<dyn Fn(&SceneInvalidatedEventArgs)>,
81}
82
83impl CompositingRenderer {
84 pub fn new(
90 root: &Rc<dyn IPresentationSource>,
91 compositor: &Rc<Compositor>,
92 surfaces: RenderSurfaces,
93 ) -> Rc<CompositingRenderer> {
94 Self::with_weak_root(Rc::downgrade(root), compositor, surfaces)
95 }
96
97 pub fn with_weak_root(
100 root: Weak<dyn IPresentationSource>,
101 compositor: &Rc<Compositor>,
102 surfaces: RenderSurfaces,
103 ) -> Rc<CompositingRenderer> {
104 let key = NEXT_RENDERER_KEY.fetch_add(1, Ordering::SeqCst);
105 let renderer = Rc::new_cyclic(|this: &Weak<CompositingRenderer>| CompositingRenderer {
106 this: this.clone(),
107 key,
108 root,
109 compositor: compositor.clone(),
110 recorder: RefCell::new(RenderDataDrawingContext::new(Some(compositor.clone()))),
111 dirty: RefCell::new(VisualSet::default()),
112 recalculate_children: RefCell::new(VisualSet::default()),
113 queued_update: Cell::new(false),
114 queued_scene_invalidation: Cell::new(false),
115 updating: Cell::new(false),
116 is_disposed: Cell::new(false),
117 composition_target: compositor.create_composition_target(surfaces),
118 diagnostics: RendererDiagnostics::new(),
119 diagnostics_subscription: RefCell::new(None),
120 scene_invalidated: HandlerList::new(),
121 });
122 let weak = Rc::downgrade(&renderer);
123 let subscription = renderer.diagnostics.property_changed(move |diagnostics, property_name| {
124 if let Some(renderer) = weak.upgrade() {
125 renderer.on_diagnostics_property_changed(diagnostics, property_name);
126 }
127 });
128 *renderer.diagnostics_subscription.borrow_mut() = Some(subscription);
129 RENDERERS.with(|renderers| {
130 let mut renderers = renderers.borrow_mut();
131 renderers.retain(|_, r| r.strong_count() > 0);
132 renderers.insert(key, Rc::downgrade(&renderer));
133 });
134 renderer
135 }
136
137 pub fn composition_target(&self) -> &Rc<CompositionTarget> {
139 &self.composition_target
140 }
141
142 pub fn compositor_handle(&self) -> &Rc<Compositor> {
144 &self.compositor
145 }
146
147 pub fn set_root(&self, root: Option<Ref<Visual>>) {
150 let visual = root.and_then(|root| root.composition_visual()).map(|visual| (*visual).clone());
151 self.composition_target.set_root(visual);
152 }
153
154 pub fn set_transparency_level(&self, level: CompositionTransparencyLevel) {
156 self.composition_target.set_transparency_level(level);
157 }
158
159 pub fn set_platform_specific_scene_info(&self, info: Option<std::sync::Arc<dyn Any + Send + Sync>>) {
161 self.composition_target.set_platform_specific_scene_info(info);
162 }
163
164 pub fn hit_tester(&self) -> Option<Rc<dyn IHitTester>> {
166 self.this.upgrade().map(|this| this as Rc<dyn IHitTester>)
167 }
168
169 pub fn as_any(&self) -> &dyn Any {
170 self
171 }
172
173 fn on_diagnostics_property_changed(&self, diagnostics: &RendererDiagnostics, property_name: &str) {
174 if property_name == RendererDiagnostics::DEBUG_OVERLAYS_PROPERTY_NAME {
175 self.composition_target.set_debug_overlays(diagnostics.debug_overlays());
176 } else if property_name == RendererDiagnostics::LAST_LAYOUT_PASS_TIMING_PROPERTY_NAME {
177 self.composition_target.set_last_layout_pass_timing(diagnostics.last_layout_pass_timing());
178 }
179 }
180
181 fn queue_update(&self) {
182 if self.queued_update.get() {
183 return;
184 }
185 self.queued_update.set(true);
186 let weak = self.this.clone();
187 self.compositor.request_composition_update(move || {
188 if let Some(renderer) = weak.upgrade() {
189 renderer.update();
190 }
191 });
192 }
193
194 fn composition_filter<'a>(
195 filter: Option<&'a dyn Fn(&Visual) -> bool>,
196 ) -> Option<impl Fn(&Rc<CompositionVisual>) -> bool + 'a> {
197 filter.map(|filter| {
198 move |v: &Rc<CompositionVisual>| match CompositionDrawListVisual::from_visual(v) {
199 Some(dlv) => dlv.visual().is_some_and(|visual| filter(&visual)),
200 None => true,
201 }
202 })
203 }
204
205 fn hit_test_all<H: ICompositionHitTester>(
206 &self,
207 input: &H::Input,
208 root: &Visual,
209 filter: Option<&dyn Fn(&Visual) -> bool>,
210 ) -> Vec<(Ref<Visual>, IntersectionResult)> {
211 let Some(root_visual) = root.composition_visual() else { return Vec::new() };
212 let root_visual: Rc<CompositionVisual> = (*root_visual).clone();
213
214 let f = Self::composition_filter(filter);
215 let f = f.as_ref().map(|f| f as &dyn Fn(&Rc<CompositionVisual>) -> bool);
216
217 let Some(res) = self.composition_target.try_hit_test::<H>(input, Some(&root_visual), f) else {
218 return Vec::new();
219 };
220
221 let mut result = Vec::new();
222 for (intersection, v) in res {
223 if let Some(visual) = CompositionDrawListVisual::from_visual(&v).and_then(|dv| dv.visual()) {
224 if filter.is_none_or(|filter| filter(&visual)) {
225 result.push((visual, intersection));
226 }
227 }
228 }
229 result
230 }
231
232 fn hit_test_first_core<H: ICompositionHitTester>(
233 &self,
234 input: &H::Input,
235 root: &Visual,
236 filter: Option<&dyn Fn(&Visual) -> bool>,
237 ) -> Option<(Ref<Visual>, IntersectionResult)> {
238 let root_visual = root.composition_visual()?;
239 let root_visual: Rc<CompositionVisual> = (*root_visual).clone();
240
241 let f = Self::composition_filter(filter);
242 let f = f.as_ref().map(|f| f as &dyn Fn(&Rc<CompositionVisual>) -> bool);
243 let result_filter = |v: &Rc<CompositionVisual>| CompositionDrawListVisual::from_visual(v).is_some();
244
245 let (hit, intersection) =
246 self.composition_target.try_hit_test_first::<H>(input, Some(&root_visual), f, Some(&result_filter));
247 let visual = CompositionDrawListVisual::from_visual(&hit?)?.visual()?;
248 Some((visual, intersection))
249 }
250
251 fn update_core(&self) {
252 self.queued_update.set(false);
253 let dirty: Vec<Ref<Visual>> = self.dirty.borrow().items.clone();
254 for visual in &dirty {
255 let Some(comp) = visual.composition_visual() else { continue };
256
257 visual.synchronize_composition_properties();
258
259 let draw_list = {
260 struct ResetRecorder<'a>(&'a RefCell<RenderDataDrawingContext>);
261 impl Drop for ResetRecorder<'_> {
262 fn drop(&mut self) {
263 if let Ok(mut recorder) = self.0.try_borrow_mut() {
264 recorder.reset();
265 }
266 }
267 }
268 let _reset = ResetRecorder(&self.recorder);
269 let mut recorder = self.recorder.borrow_mut();
270 {
271 let mut context = DrawingContext::new(&mut *recorder);
272 visual.render(&mut context);
273 }
274 recorder.get_render_results()
275 };
276 comp.set_draw_list(draw_list);
277
278 visual.synchronize_composition_child_visuals();
279 }
280 let recalculate: Vec<Ref<Visual>> = self.recalculate_children.borrow().items.clone();
281 for visual in &recalculate {
282 if !self.dirty.borrow().contains(visual) {
283 visual.synchronize_composition_child_visuals();
284 }
285 }
286 self.dirty.borrow_mut().clear();
287 self.recalculate_children.borrow_mut().clear();
288 if let Some(root) = self.root.upgrade() {
289 self.composition_target.set_size(root.client_size());
290 self.composition_target.set_scaling(root.render_scaling());
291 }
292
293 let commit = self.compositor.request_composition_batch_commit_async();
294 if !self.queued_scene_invalidation.get() {
295 self.queued_scene_invalidation.set(true);
296 let key = self.key;
298 let dispatcher = self.compositor.dispatcher().clone();
299 commit.rendered().on_completed(move || {
300 dispatcher.post(
301 move || {
302 let renderer =
303 RENDERERS.with(|renderers| renderers.borrow().get(&key).and_then(Weak::upgrade));
304 if let Some(renderer) = renderer {
305 renderer.queued_scene_invalidation.set(false);
306 let size = renderer.root.upgrade().map_or(Size::default(), |root| root.client_size());
307 renderer.raise_scene_invalidated(&SceneInvalidatedEventArgs::new(Rect::from_size(size)));
308 }
309 },
310 DispatcherPriority::INPUT,
311 );
312 });
313 }
314 }
315
316 fn raise_scene_invalidated(&self, e: &SceneInvalidatedEventArgs) {
317 if self.scene_invalidated.is_empty() {
318 return;
319 }
320 for (_, handler) in self.scene_invalidated.snapshot().iter() {
321 handler(e);
322 }
323 }
324
325 pub fn trigger_scene_invalidated_for_unit_tests(&self, rect: Rect) {
326 self.raise_scene_invalidated(&SceneInvalidatedEventArgs::new(rect));
327 }
328
329 pub fn has_pending_update_for_unit_tests(&self, visual: &Visual) -> bool {
332 self.dirty.borrow().contains(visual) || self.recalculate_children.borrow().contains(visual)
333 }
334
335 fn update(&self) {
336 if self.updating.get() {
337 return;
338 }
339 if !self.composition_target.is_enabled() {
340 self.queued_update.set(false);
341 return;
342 }
343
344 struct ResetUpdating<'a>(&'a Cell<bool>);
345 impl Drop for ResetUpdating<'_> {
346 fn drop(&mut self) {
347 self.0.set(false);
348 }
349 }
350 self.updating.set(true);
351 let _reset = ResetUpdating(&self.updating);
352 self.update_core();
353 }
354
355 pub fn is_disposed(&self) -> bool {
356 self.is_disposed.get()
357 }
358}
359
360impl IRenderer for CompositingRenderer {
361 fn diagnostics(&self) -> Rc<RendererDiagnostics> {
362 self.diagnostics.clone()
363 }
364
365 fn scene_invalidated(&self, handler: Rc<dyn Fn(&SceneInvalidatedEventArgs)>) -> Rc<dyn IDisposable> {
366 let token = self.scene_invalidated.add(handler);
367 let weak = self.this.clone();
368 Disposable::create(move || {
369 if let Some(this) = weak.upgrade() {
370 this.scene_invalidated.remove(token);
371 }
372 })
373 }
374
375 fn add_dirty(&self, visual: &Visual) {
376 if self.is_disposed.get() {
377 return;
378 }
379 if self.updating.get() {
380 panic!("Visual was invalidated during the render pass");
381 }
382
383 if visual.composition_visual().is_none() {
384 self.dirty.borrow_mut().remove(visual);
386 self.recalculate_children.borrow_mut().remove(visual);
387 } else {
388 self.dirty.borrow_mut().add(visual);
389 }
390
391 self.queue_update();
392 }
393
394 fn recalculate_children(&self, visual: &Visual) {
395 if self.is_disposed.get() {
396 return;
397 }
398 if self.updating.get() {
399 panic!("Visual was invalidated during the render pass");
400 }
401 self.recalculate_children.borrow_mut().add(visual);
402 self.queue_update();
403 }
404
405 fn resized(&self, _size: Size) {}
406
407 fn paint(&self, _rect: Rect) {
408 if self.is_disposed.get() {
409 return;
410 }
411
412 self.queue_update();
413 self.composition_target.request_redraw();
414 MediaContext::instance().immediate_render_requested(&self.compositor);
415 }
416
417 fn start(&self) {
418 if self.is_disposed.get() {
419 return;
420 }
421
422 self.composition_target.set_is_enabled(true);
423 if !self.dirty.borrow().is_empty() || !self.recalculate_children.borrow().is_empty() {
424 self.queue_update();
425 }
426 }
427
428 fn stop(&self) {
429 self.composition_target.set_is_enabled(false);
430 }
431
432 fn try_get_render_interface_feature(
433 &self,
434 feature_type: TypeId,
435 ) -> super::LocalFuture<Option<super::RenderInterfaceFeature>> {
436 self.compositor.try_get_render_interface_feature(feature_type)
437 }
438
439 fn compositor(&self) -> Option<Rc<Compositor>> {
440 Some(self.compositor.clone())
441 }
442
443 fn dispose(&self) {
444 if self.is_disposed.replace(true) {
445 return;
446 }
447 self.dirty.borrow_mut().clear();
448 self.recalculate_children.borrow_mut().clear();
449 for (token, _) in self.scene_invalidated.snapshot().iter() {
450 self.scene_invalidated.remove(*token);
451 }
452 self.stop();
453 self.composition_target.set_root(None);
456 self.composition_target.mark_disposed_out_of_band();
457 MediaContext::instance().sync_dispose_composition_target(&self.compositor, self.composition_target.server());
458 let key = self.key;
459 let _ = RENDERERS.try_with(|renderers| {
460 if let Ok(mut renderers) = renderers.try_borrow_mut() {
461 renderers.remove(&key);
462 }
463 });
464 }
465}
466
467impl IHitTester for CompositingRenderer {
468 fn hit_test(&self, p: Point, root: &Visual, filter: Option<&dyn Fn(&Visual) -> bool>) -> Vec<Ref<Visual>> {
469 self.hit_test_all::<PointCompositionHitTester>(&p, root, filter).into_iter().map(|(visual, _)| visual).collect()
470 }
471
472 fn hit_test_geometry(
473 &self,
474 geometry: &Geometry,
475 root: &Visual,
476 filter: Option<&dyn Fn(&Visual) -> bool>,
477 ) -> Vec<GeometryHitTestResult> {
478 self.hit_test_all::<GeometryCompositionHitTester>(&geometry.to_ref(), root, filter)
479 .into_iter()
480 .map(|(visual, intersection)| GeometryHitTestResult::new(visual, intersection))
481 .collect()
482 }
483
484 fn hit_test_first(&self, p: Point, root: &Visual, filter: Option<&dyn Fn(&Visual) -> bool>) -> Option<Ref<Visual>> {
485 self.hit_test_first_core::<PointCompositionHitTester>(&p, root, filter).map(|(visual, _)| visual)
486 }
487
488 fn hit_test_first_geometry(
489 &self,
490 geometry: &Geometry,
491 root: &Visual,
492 filter: Option<&dyn Fn(&Visual) -> bool>,
493 ) -> Option<GeometryHitTestResult> {
494 self.hit_test_first_core::<GeometryCompositionHitTester>(&geometry.to_ref(), root, filter)
495 .map(|(visual, intersection)| GeometryHitTestResult::new(visual, intersection))
496 }
497}