Skip to main content

ferroui_base/rendering/composition/
compositing_renderer.rs

1use 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    /// The renderers of this thread by key: lets the `Send` continuation of
25    /// a batch find its renderer once it is back on the thread.
26    static RENDERERS: RefCell<HashMap<u64, Weak<CompositingRenderer>>> = RefCell::new(HashMap::new());
27}
28
29/// A set of visuals that keeps the order in which they were added.
30#[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
64/// A renderer that utilizes a [`Compositor`] to render the visual tree.
65pub 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    /// Creates the renderer of the tree hosted by `root`. `surfaces`
85    /// returns the platform surfaces to render to.
86    ///
87    /// The renderer refers to its presentation source weakly: the source
88    /// owns its renderer.
89    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    /// Like [`new`](Self::new), for a presentation source that is still
98    /// being constructed.
99    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    /// The composition target the renderer draws the tree to.
138    pub fn composition_target(&self) -> &Rc<CompositionTarget> {
139        &self.composition_target
140    }
141
142    /// The compositor of the renderer.
143    pub fn compositor_handle(&self) -> &Rc<Compositor> {
144        &self.compositor
145    }
146
147    /// Sets the visual whose composition visual is the root of the
148    /// composition target; `None` detaches the tree.
149    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    /// Sets the transparency level the composition target renders with.
155    pub fn set_transparency_level(&self, level: CompositionTransparencyLevel) {
156        self.composition_target.set_transparency_level(level);
157    }
158
159    /// Sets the platform-specific scene info passed to the render target.
160    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    /// The renderer as the hit tester of its scene.
165    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            // Updated hit-test information is available after full render
297            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    /// Whether the renderer holds `visual` for its next update pass. For
330    /// tests only.
331    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            // Queuing a detached visual would keep it alive for as long as rendering is stopped.
385            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        // The tree is released with the target: the root visual and the
454        // target refer to each other.
455        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}