1use macroquad::{experimental::collections::storage, telemetry};
2
3use macroquad::prelude::*;
4
5use macroquad::ui::{hash, root_ui, widgets::Window, Ui};
6
7pub struct ProfilerState {
8 fps_buffer: Vec<f32>,
9 frames_buffer: Vec<telemetry::Frame>,
10 selected_frame: Option<telemetry::Frame>,
11 profiler_window_opened: bool,
12 paused: bool,
13}
14
15pub struct ProfilerParams {
16 pub fps_counter_pos: Vec2,
17}
18
19impl Default for ProfilerParams {
20 fn default() -> ProfilerParams {
21 ProfilerParams {
22 fps_counter_pos: vec2(10., 10.),
23 }
24 }
25}
26
27const FPS_BUFFER_CAPACITY: usize = 100;
28const FRAMES_BUFFER_CAPACITY: usize = 400;
29
30fn profiler_window(ui: &mut Ui, state: &mut ProfilerState) {
31 fn zone_ui(ui: &mut Ui, zone: &telemetry::Zone, n: usize) {
32 let label = format!(
33 "{}: {:.4}ms {:.1}(1/t)",
34 zone.name,
35 zone.duration * 1000.0,
36 1.0 / zone.duration
37 );
38 if zone.children.len() != 0 {
39 ui.tree_node(hash!(hash!(), n), &label, |ui| {
40 for (m, zone) in zone.children.iter().enumerate() {
41 zone_ui(ui, zone, n * 1000 + m + 1);
42 }
43 });
44 } else {
45 ui.label(None, &label);
46 }
47 }
48
49 let mut canvas = ui.canvas();
50 let w = 515.0;
51 let h = 40.0;
52 let pos = canvas.request_space(vec2(w, h));
53
54 let rect = Rect::new(pos.x, pos.y, w, h);
55 canvas.rect(rect, Color::new(0.5, 0.5, 0.5, 1.0), None);
56
57 let (mouse_x, mouse_y) = mouse_position();
58
59 let mut selected_frame = None;
60
61 if rect.contains(vec2(mouse_x, mouse_y)) && state.frames_buffer.len() >= 1 {
63 let x = ((mouse_x - pos.x - 2.) / w * FRAMES_BUFFER_CAPACITY as f32) as i32;
64
65 let min = clamp(x - 2, 0, state.frames_buffer.len() as i32 - 1) as usize;
66 let max = clamp(x + 3, 0, state.frames_buffer.len() as i32 - 1) as usize;
67
68 selected_frame = state.frames_buffer[min..max]
69 .iter()
70 .enumerate()
71 .max_by(|(_, a), (_, b)| a.full_frame_time.partial_cmp(&b.full_frame_time).unwrap())
72 .map(|(n, _)| n + min);
73 }
74
75 if let Some(frame) = selected_frame {
76 if is_mouse_button_down(MouseButton::Left) {
77 state.selected_frame = state.frames_buffer[frame].try_clone();
78 }
79 }
80 for (n, frame) in state.frames_buffer.iter().enumerate() {
81 let x = n as f32 / FRAMES_BUFFER_CAPACITY as f32 * (w - 2.);
82 let selected = selected_frame.map_or(false, |selected| n == selected);
83 let color = if selected {
84 Color::new(1.0, 1.0, 0.0, 1.0)
85 } else if frame.full_frame_time < 1.0 / 58.0 {
86 Color::new(0.6, 0.6, 1.0, 1.0)
87 } else if frame.full_frame_time < 1.0 / 25.0 {
88 Color::new(0.3, 0.3, 0.8, 1.0)
89 } else {
90 Color::new(0.2, 0.2, 0.6, 1.0)
91 };
92 let t = macroquad::math::clamp(frame.full_frame_time * 1000.0, 0.0, h);
93
94 canvas.line(
95 vec2(pos.x + x + 2., pos.y + h - 1.0),
96 vec2(pos.x + x + 2., pos.y + h - t),
97 color,
98 );
99 }
100
101 if let Some(frame) = state
102 .selected_frame
103 .as_ref()
104 .or_else(|| state.frames_buffer.get(0))
105 {
106 ui.label(
107 None,
108 &format!(
109 "Full frame time: {:.3}ms {:.1}(1/t)",
110 frame.full_frame_time * 1000.0,
111 (1.0 / frame.full_frame_time)
112 ),
113 );
114 }
115
116 if state.paused {
117 if ui.button(None, "resume") {
118 state.paused = false;
119 }
120 } else {
121 if ui.button(None, "pause") {
122 state.paused = true;
123 }
124 }
125 if state.selected_frame.is_some() {
126 ui.same_line(100.0);
127 if ui.button(None, "deselect frame") {
128 state.selected_frame = None;
129 }
130 }
131
132 let frame = state
133 .selected_frame
134 .as_ref()
135 .or_else(|| state.frames_buffer.get(0));
136
137 ui.separator();
138 ui.group(hash!(), vec2(355., 300.), |ui| {
139 if let Some(frame) = frame {
140 for (n, zone) in frame.zones.iter().enumerate() {
141 zone_ui(ui, zone, n + 1);
142 }
143 }
144 });
145 ui.group(hash!(), vec2(153., 300.), |ui| {
146 let queries = telemetry::gpu_queries();
147
148 for query in queries {
149 let t = query.1 as f64 / 1_000_000_000.0;
150 ui.label(
151 None,
152 &format!("{}: {:.3}ms {:.1}(1/t)", query.0, t * 1000.0, 1.0 / t),
153 );
154 }
155 });
156 if ui.button(None, "sample gpu") {
157 telemetry::sample_gpu_queries();
158 }
159}
160
161pub fn profiler(params: ProfilerParams) {
162 telemetry::pause_gl_capture();
163 if storage::try_get::<ProfilerState>().is_none() {
164 storage::store(ProfilerState {
165 fps_buffer: vec![],
166 frames_buffer: vec![],
167 profiler_window_opened: false,
168 selected_frame: None,
169 paused: false,
170 })
171 }
172 let mut state = storage::get_mut::<ProfilerState>();
173
174 let frame = telemetry::frame();
175
176 if state.paused == false && state.profiler_window_opened {
177 state.frames_buffer.insert(0, frame);
178 }
179 let time = get_frame_time();
180 state.fps_buffer.insert(0, time);
181
182 state.fps_buffer.truncate(FPS_BUFFER_CAPACITY);
183 state.frames_buffer.truncate(FRAMES_BUFFER_CAPACITY);
184
185 push_camera_state();
186 set_default_camera();
187 let mut sum = 0.0;
188 for (x, time) in state.fps_buffer.iter().enumerate() {
189 draw_line(
190 x as f32 + params.fps_counter_pos.x,
191 params.fps_counter_pos.y + 100.0,
192 x as f32 + params.fps_counter_pos.x,
193 params.fps_counter_pos.y + 100.0 - (time * 2000.0).min(100.0),
194 1.0,
195 BLUE,
196 );
197 sum += time;
198 }
199
200 let selectable_rect = Rect::new(
201 params.fps_counter_pos.x,
202 params.fps_counter_pos.y + 40.0,
203 100.0,
204 100.0,
205 );
206
207 if selectable_rect.contains(mouse_position().into()) {
208 draw_rectangle(
209 selectable_rect.x,
210 selectable_rect.y,
211 100.0,
212 100.0,
213 Color::new(1.0, 1.0, 1.0, 0.4),
214 );
215 if is_mouse_button_pressed(MouseButton::Left) {
216 state.profiler_window_opened ^= true;
217 if state.profiler_window_opened {
218 telemetry::enable();
219 } else {
220 telemetry::disable();
221 }
222 }
223 }
224
225 draw_text(
226 &format!("{:.1}", 1.0 / (sum / state.fps_buffer.len() as f32)),
227 params.fps_counter_pos.x,
228 params.fps_counter_pos.y + 100.0,
229 30.0,
230 WHITE,
231 );
232
233 if state.profiler_window_opened {
234 Window::new(
235 hash!(),
236 vec2(params.fps_counter_pos.x, params.fps_counter_pos.y + 150.0),
237 vec2(525., 450.),
238 )
239 .ui(&mut *root_ui(), |ui| {
240 let tab = ui.tabbar(
241 hash!(),
242 vec2(300.0, 20.0),
243 &["profiler", "scene", "frame", "log"],
244 );
245
246 match tab {
247 0 => profiler_window(ui, &mut state),
248 1 => ui.label(
249 None,
250 &format!(
251 "scene allocated memory: {:.1} kb",
252 (telemetry::scene_allocated_memory() as f32) / 1000.0
253 ),
254 ),
255 2 => {
256 let drawcalls = telemetry::drawcalls();
257 ui.label(None, &format!("Draw calls: {}", drawcalls.len()));
258 for telemetry::DrawCallTelemetry { indices_count, .. } in &drawcalls {
259 ui.same_line(0.0);
260
261 ui.label(None, &format!("{}", indices_count));
262 ui.same_line(0.0);
263 }
264 ui.label(None, " ");
265
266 for telemetry::DrawCallTelemetry {
267 indices_count,
268 texture,
269 } in &drawcalls
270 {
271 ui.label(None, &format!("{}", *indices_count));
272 ui.same_line(0.0);
273 ui.texture(Texture2D::from_miniquad_texture(*texture), 100., 100.0);
274 ui.same_line(0.0);
275 }
276 ui.label(None, " ");
277
278 if ui.button(None, "Capture frame") {
279 telemetry::capture_frame();
280 }
281 }
282 3 => {
283 for label in telemetry::strings() {
284 ui.label(None, &label);
285 }
286 }
287 _ => unreachable!(),
288 }
289 });
290 }
291 pop_camera_state();
292
293 telemetry::resume_gl_capture();
294}