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