ferroui_base/rendering/composition/server/
composition_target_overlays.rs1use 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#[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 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 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}