freya_performance_plugin/
lib.rs1use std::{
2 collections::HashMap,
3 time::{
4 Duration,
5 Instant,
6 },
7};
8
9use freya_core::prelude::UserEvent;
10use freya_engine::prelude::{
11 Color,
12 FontStyle,
13 Paint,
14 PaintStyle,
15 ParagraphBuilder,
16 ParagraphStyle,
17 Rect,
18 Slant,
19 TextShadow,
20 TextStyle,
21 Weight,
22 Width,
23};
24use freya_winit::{
25 plugins::{
26 FreyaPlugin,
27 Key,
28 Modifiers,
29 PluginEvent,
30 PluginHandle,
31 },
32 reexports::winit::window::WindowId,
33 renderer::{
34 NativeEvent,
35 NativeWindowEvent,
36 NativeWindowEventAction,
37 },
38};
39
40#[derive(Default)]
44pub struct PerformanceOverlayPlugin {
45 enabled: bool,
46 metrics: HashMap<WindowId, WindowMetrics>,
47}
48
49#[derive(Default)]
50struct WindowMetrics {
51 graphics_driver: &'static str,
52
53 frames: Vec<Instant>,
54 fps_historic: Vec<usize>,
55 max_fps: usize,
56
57 started_render: Option<Instant>,
58
59 started_layout: Option<Instant>,
60 finished_layout: Option<Duration>,
61
62 started_tree_updates: Option<Instant>,
63 finished_tree_updates: Option<Duration>,
64
65 started_accessibility_updates: Option<Instant>,
66 finished_accessibility_updates: Option<Duration>,
67
68 started_presenting: Option<Instant>,
69 finished_presenting: Option<Duration>,
70}
71
72impl PerformanceOverlayPlugin {
73 pub fn with_visible(mut self, visible: bool) -> Self {
75 self.enabled = visible;
76 self
77 }
78
79 fn get_metrics(&mut self, id: WindowId) -> &mut WindowMetrics {
80 self.metrics.entry(id).or_default()
81 }
82}
83
84impl FreyaPlugin for PerformanceOverlayPlugin {
85 fn plugin_id(&self) -> &'static str {
86 "freya-performance-overlay"
87 }
88
89 fn on_event(&mut self, event: &mut PluginEvent, handle: PluginHandle) {
90 match event {
91 PluginEvent::KeyboardInput {
92 window,
93 key,
94 modifiers,
95 is_pressed,
96 ..
97 } => {
98 let toggle_modifier = if cfg!(target_os = "macos") {
99 Modifiers::META | Modifiers::SHIFT
100 } else {
101 Modifiers::CONTROL | Modifiers::SHIFT
102 };
103 let is_p = matches!(key, Key::Character(c) if c.eq_ignore_ascii_case("p"));
104 if *is_pressed && is_p && *modifiers == toggle_modifier {
105 self.enabled = !self.enabled;
106 handle.send_event_loop_event(NativeEvent::Window(NativeWindowEvent {
107 window_id: window.id(),
108 action: NativeWindowEventAction::User(UserEvent::RequestRedraw),
109 }));
110 }
111 }
112 PluginEvent::WindowCreated {
113 window,
114 graphics_driver,
115 ..
116 } => {
117 self.get_metrics(window.id()).graphics_driver = graphics_driver;
118 }
119 PluginEvent::AfterRedraw { window, .. } => {
120 let metrics = self.get_metrics(window.id());
121 let now = Instant::now();
122
123 metrics
124 .frames
125 .retain(|frame| now.duration_since(*frame).as_millis() < 1000);
126
127 metrics.frames.push(now);
128 }
129 PluginEvent::BeforePresenting { window, .. } => {
130 self.get_metrics(window.id()).started_presenting = Some(Instant::now())
131 }
132 PluginEvent::AfterPresenting { window, .. } => {
133 let metrics = self.get_metrics(window.id());
134 metrics.finished_presenting = Some(metrics.started_presenting.unwrap().elapsed())
135 }
136 PluginEvent::StartedMeasuringLayout { window, .. } => {
137 self.get_metrics(window.id()).started_layout = Some(Instant::now())
138 }
139 PluginEvent::FinishedMeasuringLayout { window, .. } => {
140 let metrics = self.get_metrics(window.id());
141 metrics.finished_layout = Some(metrics.started_layout.unwrap().elapsed())
142 }
143 PluginEvent::StartedUpdatingTree { window, .. } => {
144 self.get_metrics(window.id()).started_tree_updates = Some(Instant::now())
145 }
146 PluginEvent::FinishedUpdatingTree { window, .. } => {
147 let metrics = self.get_metrics(window.id());
148 metrics.finished_tree_updates =
149 Some(metrics.started_tree_updates.unwrap().elapsed())
150 }
151 PluginEvent::BeforeAccessibility { window, .. } => {
152 self.get_metrics(window.id()).started_accessibility_updates = Some(Instant::now())
153 }
154 PluginEvent::AfterAccessibility { window, .. } => {
155 let metrics = self.get_metrics(window.id());
156 metrics.finished_accessibility_updates =
157 Some(metrics.started_accessibility_updates.unwrap().elapsed())
158 }
159 PluginEvent::BeforeRender { window, .. } => {
160 self.get_metrics(window.id()).started_render = Some(Instant::now())
161 }
162 PluginEvent::AfterRender {
163 window,
164 canvas,
165 font_collection,
166 tree,
167 animation_clock,
168 } => {
169 if !self.enabled {
170 return;
171 }
172 let metrics = self.get_metrics(window.id());
173 let started_render = metrics.started_render.take().unwrap();
174
175 let finished_render = started_render.elapsed();
176 let finished_presenting = metrics.finished_presenting.unwrap_or_default();
177 let finished_layout = metrics.finished_layout.unwrap();
178 let finished_tree_updates = metrics.finished_tree_updates.unwrap_or_default();
179 let finished_accessibility_updates =
180 metrics.finished_accessibility_updates.unwrap_or_default();
181
182 let mut paint = Paint::default();
183 paint.set_anti_alias(true);
184 paint.set_style(PaintStyle::Fill);
185 paint.set_color(Color::from_argb(225, 225, 225, 225));
186
187 canvas.draw_rect(Rect::new(5., 5., 220., 440.), &paint);
188
189 let mut paragraph_builder =
191 ParagraphBuilder::new(&ParagraphStyle::default(), *font_collection);
192 let mut text_style = TextStyle::default();
193 text_style.set_color(Color::from_rgb(63, 255, 0));
194 text_style.add_shadow(TextShadow::new(
195 Color::from_rgb(60, 60, 60),
196 (0.0, 1.0),
197 1.0,
198 ));
199 paragraph_builder.push_style(&text_style);
200
201 add_text(
203 &mut paragraph_builder,
204 format!("{} FPS\n", metrics.frames.len()),
205 30.0,
206 );
207
208 metrics.fps_historic.push(metrics.frames.len());
209 if metrics.fps_historic.len() > 70 {
210 metrics.fps_historic.remove(0);
211 }
212
213 add_text(
215 &mut paragraph_builder,
216 format!(
217 "Rendering: {:.3}ms \n",
218 finished_render.as_secs_f64() * 1000.0
219 ),
220 18.0,
221 );
222
223 add_text(
225 &mut paragraph_builder,
226 format!(
227 "Presenting: {:.3}ms \n",
228 finished_presenting.as_secs_f64() * 1000.0
229 ),
230 18.0,
231 );
232
233 add_text(
235 &mut paragraph_builder,
236 format!("Layout: {:.3}ms \n", finished_layout.as_secs_f64() * 1000.0),
237 18.0,
238 );
239
240 add_text(
242 &mut paragraph_builder,
243 format!(
244 "Tree Updates: {:.3}ms \n",
245 finished_tree_updates.as_secs_f64() * 1000.0
246 ),
247 18.0,
248 );
249
250 add_text(
252 &mut paragraph_builder,
253 format!(
254 "a11y Updates: {:.3}ms \n",
255 finished_accessibility_updates.as_secs_f64() * 1000.0
256 ),
257 18.0,
258 );
259
260 add_text(
262 &mut paragraph_builder,
263 format!("{} Tree Nodes \n", tree.size()),
264 14.0,
265 );
266
267 add_text(
269 &mut paragraph_builder,
270 format!("{} Layout Nodes \n", tree.layout.size()),
271 14.0,
272 );
273
274 add_text(
276 &mut paragraph_builder,
277 format!("Scale Factor: {}x\n", window.scale_factor()),
278 14.0,
279 );
280
281 add_text(
285 &mut paragraph_builder,
286 format!("Animation clock speed: {}x \n", animation_clock.speed()),
287 14.0,
288 );
289
290 add_text(
292 &mut paragraph_builder,
293 format!("Graphics: {} \n", metrics.graphics_driver),
294 14.0,
295 );
296
297 let mut paragraph = paragraph_builder.build();
298 paragraph.layout(f32::MAX);
299 paragraph.paint(canvas, (5.0, 0.0));
300
301 metrics.max_fps = metrics.max_fps.max(
302 metrics
303 .fps_historic
304 .iter()
305 .max()
306 .copied()
307 .unwrap_or_default(),
308 );
309 let start_x = 5.0;
310 let start_y = 290.0 + metrics.max_fps.max(60) as f32;
311
312 for (i, fps) in metrics.fps_historic.iter().enumerate() {
313 let mut paint = Paint::default();
314 paint.set_anti_alias(true);
315 paint.set_style(PaintStyle::Fill);
316 paint.set_color(Color::from_rgb(63, 255, 0));
317 paint.set_stroke_width(3.0);
318
319 let x = start_x + (i * 2) as f32;
320 let y = start_y - *fps as f32 + 2.0;
321 canvas.draw_circle((x, y), 2.0, &paint);
322 }
323 }
324 _ => {}
325 }
326 }
327}
328
329fn add_text(paragraph_builder: &mut ParagraphBuilder, text: String, font_size: f32) {
330 let mut text_style = TextStyle::default();
331 text_style.set_color(Color::from_rgb(25, 225, 35));
332 let font_style = FontStyle::new(Weight::BOLD, Width::EXPANDED, Slant::Upright);
333 text_style.set_font_style(font_style);
334 text_style.add_shadow(TextShadow::new(
335 Color::from_rgb(65, 65, 65),
336 (0.0, 1.0),
337 1.0,
338 ));
339 text_style.set_font_size(font_size);
340 paragraph_builder.push_style(&text_style);
341 paragraph_builder.add_text(text);
342}