freya_performance_plugin/
lib.rs

1use std::{
2    collections::HashMap,
3    time::{
4        Duration,
5        Instant,
6    },
7};
8
9use freya_engine::prelude::{
10    Color,
11    FontStyle,
12    Paint,
13    PaintStyle,
14    ParagraphBuilder,
15    ParagraphStyle,
16    Rect,
17    Slant,
18    TextShadow,
19    TextStyle,
20    Weight,
21    Width,
22};
23use freya_winit::{
24    plugins::{
25        FreyaPlugin,
26        PluginEvent,
27        PluginHandle,
28    },
29    reexports::winit::window::WindowId,
30};
31
32#[derive(Default)]
33pub struct PerformanceOverlayPlugin {
34    metrics: HashMap<WindowId, WindowMetrics>,
35}
36
37#[derive(Default)]
38struct WindowMetrics {
39    frames: Vec<Instant>,
40    fps_historic: Vec<usize>,
41    max_fps: usize,
42
43    started_render: Option<Instant>,
44
45    started_layout: Option<Instant>,
46    finished_layout: Option<Duration>,
47
48    started_dom_updates: Option<Instant>,
49    finished_dom_updates: Option<Duration>,
50
51    started_accessibility_updates: Option<Instant>,
52    finished_accessibility_updates: Option<Duration>,
53
54    started_presenting: Option<Instant>,
55    finished_presenting: Option<Duration>,
56}
57
58impl PerformanceOverlayPlugin {
59    fn get_metrics(&mut self, id: WindowId) -> &mut WindowMetrics {
60        self.metrics.entry(id).or_default()
61    }
62}
63
64impl FreyaPlugin for PerformanceOverlayPlugin {
65    fn on_event(&mut self, event: &PluginEvent, _handle: PluginHandle) {
66        match event {
67            PluginEvent::AfterRedraw { window, .. } => {
68                let metrics = self.get_metrics(window.id());
69                let now = Instant::now();
70
71                metrics
72                    .frames
73                    .retain(|frame| now.duration_since(*frame).as_millis() < 1000);
74
75                metrics.frames.push(now);
76            }
77            PluginEvent::BeforePresenting { window, .. } => {
78                self.get_metrics(window.id()).started_presenting = Some(Instant::now())
79            }
80            PluginEvent::AfterPresenting { window, .. } => {
81                let metrics = self.get_metrics(window.id());
82                metrics.finished_presenting = Some(metrics.started_presenting.unwrap().elapsed())
83            }
84            PluginEvent::StartedMeasuringLayout { window, .. } => {
85                self.get_metrics(window.id()).started_layout = Some(Instant::now())
86            }
87            PluginEvent::FinishedMeasuringLayout { window, .. } => {
88                let metrics = self.get_metrics(window.id());
89                metrics.finished_layout = Some(metrics.started_layout.unwrap().elapsed())
90            }
91            PluginEvent::StartedUpdatingTree { window, .. } => {
92                self.get_metrics(window.id()).started_dom_updates = Some(Instant::now())
93            }
94            PluginEvent::FinishedUpdatingTree { window, .. } => {
95                let metrics = self.get_metrics(window.id());
96                metrics.finished_dom_updates = Some(metrics.started_dom_updates.unwrap().elapsed())
97            }
98            PluginEvent::BeforeAccessibility { window, .. } => {
99                self.get_metrics(window.id()).started_accessibility_updates = Some(Instant::now())
100            }
101            PluginEvent::AfterAccessibility { window, .. } => {
102                let metrics = self.get_metrics(window.id());
103                metrics.finished_accessibility_updates =
104                    Some(metrics.started_accessibility_updates.unwrap().elapsed())
105            }
106            PluginEvent::BeforeRender { window, .. } => {
107                self.get_metrics(window.id()).started_render = Some(Instant::now())
108            }
109            PluginEvent::AfterRender {
110                window,
111                canvas,
112                font_collection,
113                tree,
114                animation_clock,
115            } => {
116                let metrics = self.get_metrics(window.id());
117                let started_render = metrics.started_render.take().unwrap();
118
119                let finished_render = started_render.elapsed();
120                let finished_presenting = metrics.finished_presenting.unwrap_or_default();
121                let finished_layout = metrics.finished_layout.unwrap();
122                let finished_dom_updates = metrics.finished_dom_updates.unwrap_or_default();
123                let finished_accessibility_updates =
124                    metrics.finished_accessibility_updates.unwrap_or_default();
125
126                let mut paint = Paint::default();
127                paint.set_anti_alias(true);
128                paint.set_style(PaintStyle::Fill);
129                paint.set_color(Color::from_argb(225, 225, 225, 225));
130
131                canvas.draw_rect(Rect::new(5., 5., 220., 440.), &paint);
132
133                // Render the texts
134                let mut paragraph_builder =
135                    ParagraphBuilder::new(&ParagraphStyle::default(), *font_collection);
136                let mut text_style = TextStyle::default();
137                text_style.set_color(Color::from_rgb(63, 255, 0));
138                text_style.add_shadow(TextShadow::new(
139                    Color::from_rgb(60, 60, 60),
140                    (0.0, 1.0),
141                    1.0,
142                ));
143                paragraph_builder.push_style(&text_style);
144
145                // FPS
146                add_text(
147                    &mut paragraph_builder,
148                    format!("{} FPS\n", metrics.frames.len()),
149                    30.0,
150                );
151
152                metrics.fps_historic.push(metrics.frames.len());
153                if metrics.fps_historic.len() > 70 {
154                    metrics.fps_historic.remove(0);
155                }
156
157                // Rendering time
158                add_text(
159                    &mut paragraph_builder,
160                    format!(
161                        "Rendering: {:.3}ms \n",
162                        finished_render.as_secs_f64() * 1000.0
163                    ),
164                    18.0,
165                );
166
167                // Presenting time
168                add_text(
169                    &mut paragraph_builder,
170                    format!(
171                        "Presenting: {:.3}ms \n",
172                        finished_presenting.as_secs_f64() * 1000.0
173                    ),
174                    18.0,
175                );
176
177                // Layout time
178                add_text(
179                    &mut paragraph_builder,
180                    format!("Layout: {:.3}ms \n", finished_layout.as_secs_f64() * 1000.0),
181                    18.0,
182                );
183
184                // Tree updates time
185                add_text(
186                    &mut paragraph_builder,
187                    format!(
188                        "Tree Updates: {:.3}ms \n",
189                        finished_dom_updates.as_secs_f64() * 1000.0
190                    ),
191                    18.0,
192                );
193
194                // Tree updates time
195                add_text(
196                    &mut paragraph_builder,
197                    format!(
198                        "a11y Updates: {:.3}ms \n",
199                        finished_accessibility_updates.as_secs_f64() * 1000.0
200                    ),
201                    18.0,
202                );
203
204                // Tree size
205                add_text(
206                    &mut paragraph_builder,
207                    format!("{} Tree Nodes \n", tree.size()),
208                    14.0,
209                );
210
211                // Layout size
212                add_text(
213                    &mut paragraph_builder,
214                    format!("{} Layout Nodes \n", tree.layout.size()),
215                    14.0,
216                );
217
218                // Scale Factor
219                add_text(
220                    &mut paragraph_builder,
221                    format!("Scale Factor: {}x\n", window.scale_factor()),
222                    14.0,
223                );
224
225                // TODO: Also track events measurement
226
227                // Animation clock speed
228                add_text(
229                    &mut paragraph_builder,
230                    format!("Animation clock speed: {}x \n", animation_clock.speed()),
231                    14.0,
232                );
233
234                let mut paragraph = paragraph_builder.build();
235                paragraph.layout(f32::MAX);
236                paragraph.paint(canvas, (5.0, 0.0));
237
238                metrics.max_fps = metrics.max_fps.max(
239                    metrics
240                        .fps_historic
241                        .iter()
242                        .max()
243                        .copied()
244                        .unwrap_or_default(),
245                );
246                let start_x = 5.0;
247                let start_y = 290.0 + metrics.max_fps.max(60) as f32;
248
249                for (i, fps) in metrics.fps_historic.iter().enumerate() {
250                    let mut paint = Paint::default();
251                    paint.set_anti_alias(true);
252                    paint.set_style(PaintStyle::Fill);
253                    paint.set_color(Color::from_rgb(63, 255, 0));
254                    paint.set_stroke_width(3.0);
255
256                    let x = start_x + (i * 2) as f32;
257                    let y = start_y - *fps as f32 + 2.0;
258                    canvas.draw_circle((x, y), 2.0, &paint);
259                }
260            }
261            _ => {}
262        }
263    }
264}
265
266fn add_text(paragraph_builder: &mut ParagraphBuilder, text: String, font_size: f32) {
267    let mut text_style = TextStyle::default();
268    text_style.set_color(Color::from_rgb(25, 225, 35));
269    let font_style = FontStyle::new(Weight::BOLD, Width::EXPANDED, Slant::Upright);
270    text_style.set_font_style(font_style);
271    text_style.add_shadow(TextShadow::new(
272        Color::from_rgb(65, 65, 65),
273        (0.0, 1.0),
274        1.0,
275    ));
276    text_style.set_font_size(font_size);
277    paragraph_builder.push_style(&text_style);
278    paragraph_builder.add_text(text);
279}