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_tree_updates: Option<Instant>,
49    finished_tree_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: &mut 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_tree_updates = Some(Instant::now())
93            }
94            PluginEvent::FinishedUpdatingTree { window, .. } => {
95                let metrics = self.get_metrics(window.id());
96                metrics.finished_tree_updates =
97                    Some(metrics.started_tree_updates.unwrap().elapsed())
98            }
99            PluginEvent::BeforeAccessibility { window, .. } => {
100                self.get_metrics(window.id()).started_accessibility_updates = Some(Instant::now())
101            }
102            PluginEvent::AfterAccessibility { window, .. } => {
103                let metrics = self.get_metrics(window.id());
104                metrics.finished_accessibility_updates =
105                    Some(metrics.started_accessibility_updates.unwrap().elapsed())
106            }
107            PluginEvent::BeforeRender { window, .. } => {
108                self.get_metrics(window.id()).started_render = Some(Instant::now())
109            }
110            PluginEvent::AfterRender {
111                window,
112                canvas,
113                font_collection,
114                tree,
115                animation_clock,
116            } => {
117                let metrics = self.get_metrics(window.id());
118                let started_render = metrics.started_render.take().unwrap();
119
120                let finished_render = started_render.elapsed();
121                let finished_presenting = metrics.finished_presenting.unwrap_or_default();
122                let finished_layout = metrics.finished_layout.unwrap();
123                let finished_tree_updates = metrics.finished_tree_updates.unwrap_or_default();
124                let finished_accessibility_updates =
125                    metrics.finished_accessibility_updates.unwrap_or_default();
126
127                let mut paint = Paint::default();
128                paint.set_anti_alias(true);
129                paint.set_style(PaintStyle::Fill);
130                paint.set_color(Color::from_argb(225, 225, 225, 225));
131
132                canvas.draw_rect(Rect::new(5., 5., 220., 440.), &paint);
133
134                // Render the texts
135                let mut paragraph_builder =
136                    ParagraphBuilder::new(&ParagraphStyle::default(), *font_collection);
137                let mut text_style = TextStyle::default();
138                text_style.set_color(Color::from_rgb(63, 255, 0));
139                text_style.add_shadow(TextShadow::new(
140                    Color::from_rgb(60, 60, 60),
141                    (0.0, 1.0),
142                    1.0,
143                ));
144                paragraph_builder.push_style(&text_style);
145
146                // FPS
147                add_text(
148                    &mut paragraph_builder,
149                    format!("{} FPS\n", metrics.frames.len()),
150                    30.0,
151                );
152
153                metrics.fps_historic.push(metrics.frames.len());
154                if metrics.fps_historic.len() > 70 {
155                    metrics.fps_historic.remove(0);
156                }
157
158                // Rendering time
159                add_text(
160                    &mut paragraph_builder,
161                    format!(
162                        "Rendering: {:.3}ms \n",
163                        finished_render.as_secs_f64() * 1000.0
164                    ),
165                    18.0,
166                );
167
168                // Presenting time
169                add_text(
170                    &mut paragraph_builder,
171                    format!(
172                        "Presenting: {:.3}ms \n",
173                        finished_presenting.as_secs_f64() * 1000.0
174                    ),
175                    18.0,
176                );
177
178                // Layout time
179                add_text(
180                    &mut paragraph_builder,
181                    format!("Layout: {:.3}ms \n", finished_layout.as_secs_f64() * 1000.0),
182                    18.0,
183                );
184
185                // Tree updates time
186                add_text(
187                    &mut paragraph_builder,
188                    format!(
189                        "Tree Updates: {:.3}ms \n",
190                        finished_tree_updates.as_secs_f64() * 1000.0
191                    ),
192                    18.0,
193                );
194
195                // Tree updates time
196                add_text(
197                    &mut paragraph_builder,
198                    format!(
199                        "a11y Updates: {:.3}ms \n",
200                        finished_accessibility_updates.as_secs_f64() * 1000.0
201                    ),
202                    18.0,
203                );
204
205                // Tree size
206                add_text(
207                    &mut paragraph_builder,
208                    format!("{} Tree Nodes \n", tree.size()),
209                    14.0,
210                );
211
212                // Layout size
213                add_text(
214                    &mut paragraph_builder,
215                    format!("{} Layout Nodes \n", tree.layout.size()),
216                    14.0,
217                );
218
219                // Scale Factor
220                add_text(
221                    &mut paragraph_builder,
222                    format!("Scale Factor: {}x\n", window.scale_factor()),
223                    14.0,
224                );
225
226                // TODO: Also track events measurement
227
228                // Animation clock speed
229                add_text(
230                    &mut paragraph_builder,
231                    format!("Animation clock speed: {}x \n", animation_clock.speed()),
232                    14.0,
233                );
234
235                let mut paragraph = paragraph_builder.build();
236                paragraph.layout(f32::MAX);
237                paragraph.paint(canvas, (5.0, 0.0));
238
239                metrics.max_fps = metrics.max_fps.max(
240                    metrics
241                        .fps_historic
242                        .iter()
243                        .max()
244                        .copied()
245                        .unwrap_or_default(),
246                );
247                let start_x = 5.0;
248                let start_y = 290.0 + metrics.max_fps.max(60) as f32;
249
250                for (i, fps) in metrics.fps_historic.iter().enumerate() {
251                    let mut paint = Paint::default();
252                    paint.set_anti_alias(true);
253                    paint.set_style(PaintStyle::Fill);
254                    paint.set_color(Color::from_rgb(63, 255, 0));
255                    paint.set_stroke_width(3.0);
256
257                    let x = start_x + (i * 2) as f32;
258                    let y = start_y - *fps as f32 + 2.0;
259                    canvas.draw_circle((x, y), 2.0, &paint);
260                }
261            }
262            _ => {}
263        }
264    }
265}
266
267fn add_text(paragraph_builder: &mut ParagraphBuilder, text: String, font_size: f32) {
268    let mut text_style = TextStyle::default();
269    text_style.set_color(Color::from_rgb(25, 225, 35));
270    let font_style = FontStyle::new(Weight::BOLD, Width::EXPANDED, Slant::Upright);
271    text_style.set_font_style(font_style);
272    text_style.add_shadow(TextShadow::new(
273        Color::from_rgb(65, 65, 65),
274        (0.0, 1.0),
275        1.0,
276    ));
277    text_style.set_font_size(font_size);
278    paragraph_builder.push_style(&text_style);
279    paragraph_builder.add_text(text);
280}