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 scale_factor = window.scale_factor() as f32;
174 let started_render = metrics.started_render.take().unwrap();
175
176 canvas.save();
177 canvas.scale((scale_factor, scale_factor));
178
179 let finished_render = started_render.elapsed();
180 let finished_presenting = metrics.finished_presenting.unwrap_or_default();
181 let finished_layout = metrics.finished_layout.unwrap();
182 let finished_tree_updates = metrics.finished_tree_updates.unwrap_or_default();
183 let finished_accessibility_updates =
184 metrics.finished_accessibility_updates.unwrap_or_default();
185
186 let mut paint = Paint::default();
187 paint.set_anti_alias(true);
188 paint.set_style(PaintStyle::Fill);
189 paint.set_color(Color::from_argb(225, 225, 225, 225));
190
191 canvas.draw_rect(Rect::new(5., 5., 220., 440.), &paint);
192
193 let mut paragraph_builder =
195 ParagraphBuilder::new(&ParagraphStyle::default(), *font_collection);
196 let mut text_style = TextStyle::default();
197 text_style.set_color(Color::from_rgb(63, 255, 0));
198 text_style.add_shadow(TextShadow::new(
199 Color::from_rgb(60, 60, 60),
200 (0.0, 1.0),
201 1.0,
202 ));
203 paragraph_builder.push_style(&text_style);
204
205 add_text(
207 &mut paragraph_builder,
208 format!("{} FPS\n", metrics.frames.len()),
209 30.0,
210 );
211
212 metrics.fps_historic.push(metrics.frames.len());
213 if metrics.fps_historic.len() > 70 {
214 metrics.fps_historic.remove(0);
215 }
216
217 add_text(
219 &mut paragraph_builder,
220 format!(
221 "Rendering: {:.3}ms \n",
222 finished_render.as_secs_f64() * 1000.0
223 ),
224 18.0,
225 );
226
227 add_text(
229 &mut paragraph_builder,
230 format!(
231 "Presenting: {:.3}ms \n",
232 finished_presenting.as_secs_f64() * 1000.0
233 ),
234 18.0,
235 );
236
237 add_text(
239 &mut paragraph_builder,
240 format!("Layout: {:.3}ms \n", finished_layout.as_secs_f64() * 1000.0),
241 18.0,
242 );
243
244 add_text(
246 &mut paragraph_builder,
247 format!(
248 "Tree Updates: {:.3}ms \n",
249 finished_tree_updates.as_secs_f64() * 1000.0
250 ),
251 18.0,
252 );
253
254 add_text(
256 &mut paragraph_builder,
257 format!(
258 "a11y Updates: {:.3}ms \n",
259 finished_accessibility_updates.as_secs_f64() * 1000.0
260 ),
261 18.0,
262 );
263
264 add_text(
266 &mut paragraph_builder,
267 format!("{} Tree Nodes \n", tree.size()),
268 14.0,
269 );
270
271 add_text(
273 &mut paragraph_builder,
274 format!("{} Layout Nodes \n", tree.layout.size()),
275 14.0,
276 );
277
278 add_text(
280 &mut paragraph_builder,
281 format!("Scale Factor: {}x\n", window.scale_factor()),
282 14.0,
283 );
284
285 add_text(
289 &mut paragraph_builder,
290 format!("Animation clock speed: {}x \n", animation_clock.speed()),
291 14.0,
292 );
293
294 add_text(
296 &mut paragraph_builder,
297 format!("Graphics: {} \n", metrics.graphics_driver),
298 14.0,
299 );
300
301 let mut paragraph = paragraph_builder.build();
302 paragraph.layout(f32::MAX);
303 paragraph.paint(canvas, (5.0, 0.0));
304
305 metrics.max_fps = metrics.max_fps.max(
306 metrics
307 .fps_historic
308 .iter()
309 .max()
310 .copied()
311 .unwrap_or_default(),
312 );
313 let start_x = 5.0;
314 let start_y = 290.0 + metrics.max_fps.max(60) as f32;
315
316 for (i, fps) in metrics.fps_historic.iter().enumerate() {
317 let mut paint = Paint::default();
318 paint.set_anti_alias(true);
319 paint.set_style(PaintStyle::Fill);
320 paint.set_color(Color::from_rgb(63, 255, 0));
321 paint.set_stroke_width(3.0);
322
323 let x = start_x + (i * 2) as f32;
324 let y = start_y - *fps as f32 + 2.0;
325 canvas.draw_circle((x, y), 2.0, &paint);
326 }
327
328 canvas.restore();
329 }
330 _ => {}
331 }
332 }
333}
334
335fn add_text(paragraph_builder: &mut ParagraphBuilder, text: String, font_size: f32) {
336 let mut text_style = TextStyle::default();
337 text_style.set_color(Color::from_rgb(25, 225, 35));
338 let font_style = FontStyle::new(Weight::BOLD, Width::EXPANDED, Slant::Upright);
339 text_style.set_font_style(font_style);
340 text_style.add_shadow(TextShadow::new(
341 Color::from_rgb(65, 65, 65),
342 (0.0, 1.0),
343 1.0,
344 ));
345 text_style.set_font_size(font_size);
346 paragraph_builder.push_style(&text_style);
347 paragraph_builder.add_text(text);
348}