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