freya_performance_plugin/
lib.rs1use 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 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 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 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 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 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 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 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 add_text(
206 &mut paragraph_builder,
207 format!("{} Tree Nodes \n", tree.size()),
208 14.0,
209 );
210
211 add_text(
213 &mut paragraph_builder,
214 format!("{} Layout Nodes \n", tree.layout.size()),
215 14.0,
216 );
217
218 add_text(
220 &mut paragraph_builder,
221 format!("Scale Factor: {}x\n", window.scale_factor()),
222 14.0,
223 );
224
225 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}