embedded_3dgfx/
perfcounter.rs1use crate::hardware_profile;
2use core::fmt::Write;
3use core::mem;
4use heapless::String;
5
6#[cfg(feature = "embassy-time")]
8use embassy_time::Instant;
9
10#[cfg(feature = "embassy-time")]
11fn now_us() -> u64 {
12 Instant::now().as_micros() as u64
13}
14
15#[cfg(all(feature = "std", not(feature = "embassy-time")))]
17fn now_us() -> u64 {
18 extern crate std;
19 use std::time::{SystemTime, UNIX_EPOCH};
20 SystemTime::now()
21 .duration_since(UNIX_EPOCH)
22 .unwrap()
23 .as_micros() as u64
24}
25
26#[cfg(not(any(feature = "std", feature = "embassy-time")))]
29fn now_us() -> u64 {
30 0
33}
34
35#[derive(Debug)]
55pub struct PerformanceCounter {
56 frame_count: u64,
57 text: String<256>,
58 old_text: String<256>,
59 only_fps: bool,
60 start_time_us: u64,
61 last_measurement_time_us: u64,
62 dwt_frame_start_cycles: u32,
63 dwt_last_measurement_cycles: u32,
64}
65
66impl Default for PerformanceCounter {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl PerformanceCounter {
73 pub fn new() -> Self {
74 let now = now_us();
75 Self {
76 frame_count: 0,
77 text: String::new(),
78 old_text: String::new(),
79 only_fps: false,
80 start_time_us: now,
81 last_measurement_time_us: now,
82 dwt_frame_start_cycles: 0,
83 dwt_last_measurement_cycles: 0,
84 }
85 }
86
87 pub fn only_fps(&mut self, only_fps: bool) {
88 self.only_fps = only_fps;
89 }
90
91 pub fn get_frametime(&self) -> u64 {
92 now_us().saturating_sub(self.start_time_us)
93 }
94
95 pub fn start_of_frame(&mut self) {
96 self.frame_count += 1;
97 self.text.clear();
98 self.start_time_us = now_us();
99 self.last_measurement_time_us = self.start_time_us;
100 hardware_profile::init_dwt_cycle_counter();
101 if let Some(cycles) = hardware_profile::read_cycle_counter() {
102 self.dwt_frame_start_cycles = cycles;
103 self.dwt_last_measurement_cycles = cycles;
104 }
105 }
106
107 pub fn add_measurement(&mut self, label: &str) {
108 if self.only_fps {
109 return;
110 }
111 let now = now_us();
112 let duration = now.saturating_sub(self.last_measurement_time_us);
113 if let Some(cycles_now) = hardware_profile::read_cycle_counter() {
114 let sample = hardware_profile::sample_cycles(
115 "perf.measurement",
116 self.dwt_last_measurement_cycles,
117 cycles_now,
118 );
119 hardware_profile::emit_trace(sample);
120 let _ = write!(
121 self.text,
122 "{}: {}us ({} cyc)\n",
123 label, duration, sample.cycles
124 );
125 self.dwt_last_measurement_cycles = cycles_now;
126 } else {
127 let _ = write!(self.text, "{}: {}us\n", label, duration);
128 }
129 self.last_measurement_time_us = now;
130 }
131
132 pub fn discard_measurement(&mut self) {
133 mem::swap(&mut self.old_text, &mut self.text);
134 }
135
136 pub fn print(&mut self) {
137 let total_us = self.get_frametime();
138 let fps = if total_us > 0 {
139 1_000_000 / total_us
140 } else {
141 0
142 };
143 if self.only_fps {
144 let _ = write!(self.text, "fps: {}\n", fps);
145 self.old_text = self.text.clone();
146 return;
147 }
148 if let Some(cycles_now) = hardware_profile::read_cycle_counter() {
149 let total_cycles = cycles_now.wrapping_sub(self.dwt_frame_start_cycles);
150 let sample = hardware_profile::sample_cycles(
151 "perf.frame",
152 self.dwt_frame_start_cycles,
153 cycles_now,
154 );
155 hardware_profile::emit_trace(sample);
156 let _ = write!(
157 self.text,
158 "total: {}us ({} cyc)\nfps: {}\n",
159 total_us, total_cycles, fps
160 );
161 } else {
162 let _ = write!(self.text, "total: {}us\nfps: {}\n", total_us, fps);
163 }
164 self.old_text = self.text.clone();
165 }
166
167 pub fn get_text(&self) -> &str {
168 &self.old_text
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 extern crate std;
175 use super::*;
176
177 #[test]
178 fn test_perfcounter_creation() {
179 let perf = PerformanceCounter::new();
180 assert_eq!(perf.get_text(), "");
181 assert_eq!(perf.frame_count, 0);
182 }
183
184 #[test]
185 fn test_perfcounter_default() {
186 let perf = PerformanceCounter::default();
187 assert_eq!(perf.get_text(), "");
188 }
189
190 #[test]
191 fn test_perfcounter_start_of_frame() {
192 let mut perf = PerformanceCounter::new();
193 perf.start_of_frame();
194 assert_eq!(perf.frame_count, 1);
195
196 perf.start_of_frame();
197 assert_eq!(perf.frame_count, 2);
198 }
199
200 #[test]
201 #[cfg(any(feature = "std", feature = "embassy-time"))]
202 fn test_perfcounter_get_frametime() {
203 let mut perf = PerformanceCounter::new();
204 perf.start_of_frame();
205
206 std::thread::sleep(std::time::Duration::from_micros(100));
208
209 let frametime = perf.get_frametime();
210 assert!(frametime >= 100); }
212
213 #[test]
214 #[cfg(any(feature = "std", feature = "embassy-time"))]
215 fn test_perfcounter_add_measurement() {
216 let mut perf = PerformanceCounter::new();
217 perf.start_of_frame();
218
219 std::thread::sleep(std::time::Duration::from_micros(100));
220 perf.add_measurement("test_label");
221
222 perf.print();
223 let text = perf.get_text();
224
225 assert!(text.contains("test_label"));
227 assert!(text.contains(":"));
229 }
230
231 #[test]
232 fn test_perfcounter_only_fps_mode() {
233 let mut perf = PerformanceCounter::new();
234 perf.only_fps(true);
235 perf.start_of_frame();
236
237 perf.add_measurement("should_be_ignored");
238
239 perf.print();
240 let text = perf.get_text();
241
242 assert!(!text.contains("should_be_ignored"));
244 assert!(text.contains("fps"));
246 }
247
248 #[test]
249 #[cfg(any(feature = "std", feature = "embassy-time"))]
250 fn test_perfcounter_discard_measurement() {
251 let mut perf = PerformanceCounter::new();
252 perf.start_of_frame();
253
254 perf.add_measurement("test1");
255 perf.discard_measurement();
257
258 let text_after = perf.get_text();
260 assert!(text_after.contains("test1"));
261 }
262
263 #[test]
264 fn test_perfcounter_print_includes_fps() {
265 let mut perf = PerformanceCounter::new();
266 perf.start_of_frame();
267
268 std::thread::sleep(std::time::Duration::from_micros(1000));
269
270 perf.print();
271 let text = perf.get_text();
272
273 assert!(text.contains("fps"));
275 assert!(text.contains("total"));
277 }
278
279 #[test]
280 #[cfg(any(feature = "std", feature = "embassy-time"))]
281 fn test_perfcounter_fps_calculation() {
282 let mut perf = PerformanceCounter::new();
283 perf.start_of_frame();
284
285 std::thread::sleep(std::time::Duration::from_millis(10));
286
287 perf.print();
288 let text = perf.get_text();
289
290 assert!(text.len() > 0);
293 }
294
295 #[test]
296 #[cfg(any(feature = "std", feature = "embassy-time"))]
297 fn test_perfcounter_multiple_measurements() {
298 let mut perf = PerformanceCounter::new();
299 perf.start_of_frame();
300
301 std::thread::sleep(std::time::Duration::from_micros(100));
302 perf.add_measurement("step1");
303
304 std::thread::sleep(std::time::Duration::from_micros(100));
305 perf.add_measurement("step2");
306
307 perf.print();
308 let text = perf.get_text();
309
310 assert!(text.contains("step1"));
312 assert!(text.contains("step2"));
313 assert!(text.contains("total"));
314 assert!(text.contains("fps"));
315 }
316}