Skip to main content

ferroui_base/rendering/composition/server/
composition_target_overlays.rs

1use super::{DiagnosticTextRenderer, FpsCounter, FrameTimeGraph, ServerCompositionTarget};
2use crate::media::immutable::ImmutableSolidColorBrush;
3use crate::media::{BoxShadows, Colors};
4use crate::platform::IDrawingContextImpl;
5use crate::rendering::{LayoutPassTiming, RendererDebugOverlays};
6use crate::{Matrix, Rect, RoundedRect, Size};
7use std::cell::{Cell, OnceCell};
8use std::rc::Rc;
9use std::time::Duration;
10
11/// The debug overlays of a composition target: the frame counter and the
12/// time graphs.
13///
14/// Time is read from the compositor clock.
15#[derive(Default)]
16pub struct CompositionTargetOverlays {
17    fps_counter: OnceCell<Option<FpsCounter>>,
18    render_time_graph: OnceCell<Option<FrameTimeGraph>>,
19    compositor_update_time_graph: OnceCell<Option<FrameTimeGraph>>,
20    update_time_graph: OnceCell<Option<FrameTimeGraph>>,
21    layout_time_graph: OnceCell<Option<FrameTimeGraph>>,
22    old_fps_counter_rect: Cell<Option<Rect>>,
23    update_started: Cell<Duration>,
24    debug_overlays: Cell<RendererDebugOverlays>,
25    /// The compositor of the target: where the services of the overlays
26    /// come from.
27    compositor: std::rc::Weak<super::ServerCompositor>,
28}
29
30impl CompositionTargetOverlays {
31    pub fn new(compositor: &Rc<super::ServerCompositor>) -> Self {
32        Self { compositor: Rc::downgrade(compositor), ..Self::default() }
33    }
34
35    fn now(target: &ServerCompositionTarget) -> Duration {
36        target.compositor().map_or(Duration::ZERO, |compositor| compositor.clock_elapsed())
37    }
38
39    fn fps_counter(&self, target: &ServerCompositionTarget) -> Option<&FpsCounter> {
40        self.fps_counter
41            .get_or_init(|| self.diagnostic_text_renderer().map(|renderer| FpsCounter::new(renderer, Self::now(target))))
42            .as_ref()
43    }
44
45    fn layout_time_graph(&self) -> Option<&FrameTimeGraph> {
46        self.layout_time_graph.get_or_init(|| self.create_time_graph("Layout")).as_ref()
47    }
48
49    fn render_time_graph(&self) -> Option<&FrameTimeGraph> {
50        self.render_time_graph.get_or_init(|| self.create_time_graph("Render")).as_ref()
51    }
52
53    fn compositor_update_time_graph(&self) -> Option<&FrameTimeGraph> {
54        self.compositor_update_time_graph.get_or_init(|| self.create_time_graph("GUpdate")).as_ref()
55    }
56
57    fn update_time_graph(&self) -> Option<&FrameTimeGraph> {
58        self.update_time_graph.get_or_init(|| self.create_time_graph("TUpdate")).as_ref()
59    }
60
61    fn diagnostic_text_renderer(&self) -> Option<Rc<DiagnosticTextRenderer>> {
62        self.compositor.upgrade()?.diagnostic_text_renderer()
63    }
64
65    pub fn require_layer(&self) -> bool {
66        self.debug_overlays.get().intersects(RendererDebugOverlays::DIRTY_RECTS)
67    }
68
69    fn create_time_graph(&self, title: &str) -> Option<FrameTimeGraph> {
70        let renderer = self.diagnostic_text_renderer()?;
71        if !self.compositor.upgrade()?.render_interface().has_platform_render_interface() {
72            return None;
73        }
74        Some(FrameTimeGraph::with_render_interface(
75            super::frame_time_graph::GraphRenderInterface::Compositor(self.compositor.clone()),
76            360,
77            Size::new(360.0, 64.0),
78            1000.0 / 60.0,
79            title,
80            renderer,
81        ))
82    }
83
84    pub fn on_changed(&self, debug_overlays: RendererDebugOverlays) {
85        self.debug_overlays.set(debug_overlays);
86        self.old_fps_counter_rect.set(None);
87
88        if !debug_overlays.contains(RendererDebugOverlays::FPS) {
89            if let Some(Some(counter)) = self.fps_counter.get() {
90                counter.reset();
91            }
92        }
93
94        if !debug_overlays.contains(RendererDebugOverlays::LAYOUT_TIME_GRAPH) {
95            if let Some(Some(graph)) = self.layout_time_graph.get() {
96                graph.reset();
97            }
98        }
99
100        if !debug_overlays.contains(RendererDebugOverlays::RENDER_TIME_GRAPH) {
101            for graph in [&self.render_time_graph, &self.compositor_update_time_graph, &self.update_time_graph] {
102                if let Some(Some(graph)) = graph.get() {
103                    graph.reset();
104                }
105            }
106        }
107    }
108
109    fn capture_timing(&self) -> bool {
110        self.debug_overlays.get().contains(RendererDebugOverlays::RENDER_TIME_GRAPH)
111    }
112
113    fn elapsed_ms(&self, target: &ServerCompositionTarget) -> f64 {
114        Self::now(target).saturating_sub(self.update_started.get()).as_secs_f64() * 1000.0
115    }
116
117    pub fn draw(&self, target: &ServerCompositionTarget, target_context: &mut dyn IDrawingContextImpl, has_layer: bool) {
118        let debug_overlays = self.debug_overlays.get();
119        if debug_overlays != RendererDebugOverlays::NONE {
120            if self.capture_timing() {
121                let elapsed = self.elapsed_ms(target);
122                if let Some(graph) = self.render_time_graph() {
123                    graph.add_frame_value(elapsed);
124                }
125            }
126
127            if debug_overlays.contains(RendererDebugOverlays::DIRTY_RECTS) {
128                target.dirty_rects().visualize(target_context);
129            }
130
131            let scaling = target.scaling();
132            target_context.set_transform(Matrix::create_scale(scaling, scaling));
133
134            self.draw_overlays(target, target_context, has_layer, target.size());
135        }
136    }
137
138    pub fn mark_update_call_start(&self, target: &ServerCompositionTarget) {
139        if self.capture_timing() {
140            self.update_started.set(Self::now(target));
141        }
142    }
143
144    pub fn mark_update_call_end(&self, target: &ServerCompositionTarget) {
145        if self.capture_timing() {
146            let elapsed = self.elapsed_ms(target);
147            if let Some(graph) = self.update_time_graph() {
148                graph.add_frame_value(elapsed);
149            }
150        }
151    }
152
153    pub fn record_global_compositor_update_time(&self, _target: &ServerCompositionTarget, elapsed: Duration) {
154        if self.capture_timing() {
155            if let Some(graph) = self.compositor_update_time_graph() {
156                graph.add_frame_value(elapsed.as_secs_f64() * 1000.0);
157            }
158        }
159    }
160
161    fn draw_overlays(
162        &self,
163        target: &ServerCompositionTarget,
164        target_context: &mut dyn IDrawingContextImpl,
165        has_layer: bool,
166        logical_size: Size,
167    ) {
168        let debug_overlays = self.debug_overlays.get();
169        if debug_overlays.contains(RendererDebugOverlays::FPS) {
170            if let Some(counter) = self.fps_counter(target) {
171                // The compositor has no batch pools to report the memory of.
172                let rect = counter.render_fps(
173                    target_context,
174                    None,
175                    target.visited_visuals(),
176                    target.rendered_visuals(),
177                    has_layer,
178                    self.old_fps_counter_rect.get(),
179                    Self::now(target),
180                );
181                self.old_fps_counter_rect.set(Some(rect));
182            }
183        }
184
185        let mut top = 0.0;
186        let base_transform = target_context.transform();
187
188        let mut draw_time_graph = |graph: Option<&FrameTimeGraph>, target_context: &mut dyn IDrawingContextImpl| {
189            let Some(graph) = graph else { return };
190
191            let left = logical_size.width - graph.size().width - 8.0;
192            top += 8.0;
193            if !has_layer {
194                target_context.draw_rectangle(
195                    Some(&ImmutableSolidColorBrush::new(Colors::WHITE)),
196                    None,
197                    RoundedRect::from_rect(Rect::new(left, top, graph.size().width, graph.size().height)),
198                    &BoxShadows::default(),
199                );
200            }
201            target_context.set_transform(Matrix::create_translation(left, top) * base_transform);
202            graph.render(target_context);
203            target_context.set_transform(base_transform);
204            top += graph.size().height;
205        };
206
207        if debug_overlays.contains(RendererDebugOverlays::LAYOUT_TIME_GRAPH) {
208            draw_time_graph(self.layout_time_graph(), target_context);
209        }
210
211        if debug_overlays.contains(RendererDebugOverlays::RENDER_TIME_GRAPH) {
212            draw_time_graph(self.render_time_graph(), target_context);
213            draw_time_graph(self.compositor_update_time_graph(), target_context);
214            draw_time_graph(self.update_time_graph(), target_context);
215        }
216    }
217
218    pub fn on_last_layout_pass_timing_changed(&self, _target: &ServerCompositionTarget, last_layout_pass_timing: LayoutPassTiming) {
219        if self.debug_overlays.get().contains(RendererDebugOverlays::LAYOUT_TIME_GRAPH) {
220            if let Some(graph) = self.layout_time_graph() {
221                graph.add_frame_value(last_layout_pass_timing.elapsed.as_secs_f64() * 1000.0);
222            }
223        }
224    }
225}