1 | /**************************************************************************/ |
2 | /* editor_profiler.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "editor_profiler.h" |
32 | |
33 | #include "core/os/os.h" |
34 | #include "editor/editor_scale.h" |
35 | #include "editor/editor_settings.h" |
36 | #include "editor/editor_string_names.h" |
37 | #include "scene/resources/image_texture.h" |
38 | |
39 | void EditorProfiler::_make_metric_ptrs(Metric &m) { |
40 | for (int i = 0; i < m.categories.size(); i++) { |
41 | m.category_ptrs[m.categories[i].signature] = &m.categories.write[i]; |
42 | for (int j = 0; j < m.categories[i].items.size(); j++) { |
43 | m.item_ptrs[m.categories[i].items[j].signature] = &m.categories.write[i].items.write[j]; |
44 | } |
45 | } |
46 | } |
47 | |
48 | EditorProfiler::Metric EditorProfiler::_get_frame_metric(int index) { |
49 | return frame_metrics[(frame_metrics.size() + last_metric - (total_metrics - 1) + index) % frame_metrics.size()]; |
50 | } |
51 | |
52 | void EditorProfiler::add_frame_metric(const Metric &p_metric, bool p_final) { |
53 | ++last_metric; |
54 | if (last_metric >= frame_metrics.size()) { |
55 | last_metric = 0; |
56 | } |
57 | |
58 | total_metrics++; |
59 | if (total_metrics > frame_metrics.size()) { |
60 | total_metrics = frame_metrics.size(); |
61 | } |
62 | |
63 | frame_metrics.write[last_metric] = p_metric; |
64 | _make_metric_ptrs(frame_metrics.write[last_metric]); |
65 | |
66 | updating_frame = true; |
67 | clear_button->set_disabled(false); |
68 | cursor_metric_edit->set_editable(true); |
69 | cursor_metric_edit->set_max(p_metric.frame_number); |
70 | cursor_metric_edit->set_min(_get_frame_metric(0).frame_number); |
71 | |
72 | if (!seeking) { |
73 | cursor_metric_edit->set_value(p_metric.frame_number); |
74 | } |
75 | |
76 | updating_frame = false; |
77 | |
78 | if (frame_delay->is_stopped()) { |
79 | frame_delay->set_wait_time(p_final ? 0.1 : 1); |
80 | frame_delay->start(); |
81 | } |
82 | |
83 | if (plot_delay->is_stopped()) { |
84 | plot_delay->set_wait_time(0.1); |
85 | plot_delay->start(); |
86 | } |
87 | } |
88 | |
89 | void EditorProfiler::clear() { |
90 | int metric_size = EDITOR_GET("debugger/profiler_frame_history_size" ); |
91 | metric_size = CLAMP(metric_size, 60, 10000); |
92 | frame_metrics.clear(); |
93 | frame_metrics.resize(metric_size); |
94 | total_metrics = 0; |
95 | last_metric = -1; |
96 | variables->clear(); |
97 | plot_sigs.clear(); |
98 | plot_sigs.insert("physics_frame_time" ); |
99 | plot_sigs.insert("category_frame_time" ); |
100 | |
101 | updating_frame = true; |
102 | cursor_metric_edit->set_min(0); |
103 | cursor_metric_edit->set_max(100); // Doesn't make much sense, but we can't have min == max. Doesn't hurt. |
104 | cursor_metric_edit->set_value(0); |
105 | cursor_metric_edit->set_editable(false); |
106 | updating_frame = false; |
107 | hover_metric = -1; |
108 | seeking = false; |
109 | |
110 | // Ensure button text (start, stop) is correct |
111 | _update_button_text(); |
112 | emit_signal(SNAME("enable_profiling" ), activate->is_pressed()); |
113 | } |
114 | |
115 | static String _get_percent_txt(float p_value, float p_total) { |
116 | if (p_total == 0) { |
117 | p_total = 0.00001; |
118 | } |
119 | |
120 | return TS->format_number(String::num((p_value / p_total) * 100, 1)) + TS->percent_sign(); |
121 | } |
122 | |
123 | String EditorProfiler::_get_time_as_text(const Metric &m, float p_time, int p_calls) { |
124 | const int dmode = display_mode->get_selected(); |
125 | |
126 | if (dmode == DISPLAY_FRAME_TIME) { |
127 | return TS->format_number(rtos(p_time * 1000).pad_decimals(2)) + " " + RTR("ms" ); |
128 | } else if (dmode == DISPLAY_AVERAGE_TIME) { |
129 | if (p_calls == 0) { |
130 | return TS->format_number("0.00" ) + " " + RTR("ms" ); |
131 | } else { |
132 | return TS->format_number(rtos((p_time / p_calls) * 1000).pad_decimals(2)) + " " + RTR("ms" ); |
133 | } |
134 | } else if (dmode == DISPLAY_FRAME_PERCENT) { |
135 | return _get_percent_txt(p_time, m.frame_time); |
136 | } else if (dmode == DISPLAY_PHYSICS_FRAME_PERCENT) { |
137 | return _get_percent_txt(p_time, m.physics_frame_time); |
138 | } |
139 | |
140 | return "err" ; |
141 | } |
142 | |
143 | Color EditorProfiler::_get_color_from_signature(const StringName &p_signature) const { |
144 | Color bc = get_theme_color(SNAME("error_color" ), EditorStringName(Editor)); |
145 | double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF)); |
146 | Color c; |
147 | c.set_hsv(rot, bc.get_s(), bc.get_v()); |
148 | return c.lerp(get_theme_color(SNAME("base_color" ), EditorStringName(Editor)), 0.07); |
149 | } |
150 | |
151 | void EditorProfiler::_item_edited() { |
152 | if (updating_frame) { |
153 | return; |
154 | } |
155 | |
156 | TreeItem *item = variables->get_edited(); |
157 | if (!item) { |
158 | return; |
159 | } |
160 | StringName signature = item->get_metadata(0); |
161 | bool checked = item->is_checked(0); |
162 | |
163 | if (checked) { |
164 | plot_sigs.insert(signature); |
165 | } else { |
166 | plot_sigs.erase(signature); |
167 | } |
168 | |
169 | if (!frame_delay->is_processing()) { |
170 | frame_delay->set_wait_time(0.1); |
171 | frame_delay->start(); |
172 | } |
173 | |
174 | _update_plot(); |
175 | } |
176 | |
177 | void EditorProfiler::_update_plot() { |
178 | const int w = graph->get_size().width; |
179 | const int h = graph->get_size().height; |
180 | bool reset_texture = false; |
181 | const int desired_len = w * h * 4; |
182 | |
183 | if (graph_image.size() != desired_len) { |
184 | reset_texture = true; |
185 | graph_image.resize(desired_len); |
186 | } |
187 | |
188 | uint8_t *wr = graph_image.ptrw(); |
189 | const Color background_color = get_theme_color(SNAME("dark_color_2" ), EditorStringName(Editor)); |
190 | |
191 | // Clear the previous frame and set the background color. |
192 | for (int i = 0; i < desired_len; i += 4) { |
193 | wr[i + 0] = Math::fast_ftoi(background_color.r * 255); |
194 | wr[i + 1] = Math::fast_ftoi(background_color.g * 255); |
195 | wr[i + 2] = Math::fast_ftoi(background_color.b * 255); |
196 | wr[i + 3] = 255; |
197 | } |
198 | |
199 | //find highest value |
200 | |
201 | const bool use_self = display_time->get_selected() == DISPLAY_SELF_TIME; |
202 | float highest = 0; |
203 | |
204 | for (int i = 0; i < total_metrics; i++) { |
205 | const Metric &m = _get_frame_metric(i); |
206 | |
207 | for (const StringName &E : plot_sigs) { |
208 | HashMap<StringName, Metric::Category *>::ConstIterator F = m.category_ptrs.find(E); |
209 | if (F) { |
210 | highest = MAX(F->value->total_time, highest); |
211 | } |
212 | |
213 | HashMap<StringName, Metric::Category::Item *>::ConstIterator G = m.item_ptrs.find(E); |
214 | if (G) { |
215 | if (use_self) { |
216 | highest = MAX(G->value->self, highest); |
217 | } else { |
218 | highest = MAX(G->value->total, highest); |
219 | } |
220 | } |
221 | } |
222 | } |
223 | |
224 | if (highest > 0) { |
225 | //means some data exists.. |
226 | highest *= 1.2; //leave some upper room |
227 | graph_height = highest; |
228 | |
229 | Vector<int> columnv; |
230 | columnv.resize(h * 4); |
231 | |
232 | int *column = columnv.ptrw(); |
233 | |
234 | HashMap<StringName, int> prev_plots; |
235 | |
236 | for (int i = 0; i < total_metrics * w / frame_metrics.size() - 1; i++) { |
237 | for (int j = 0; j < h * 4; j++) { |
238 | column[j] = 0; |
239 | } |
240 | |
241 | int current = i * frame_metrics.size() / w; |
242 | |
243 | for (const StringName &E : plot_sigs) { |
244 | const Metric &m = _get_frame_metric(current); |
245 | |
246 | float value = 0; |
247 | |
248 | HashMap<StringName, Metric::Category *>::ConstIterator F = m.category_ptrs.find(E); |
249 | if (F) { |
250 | value = F->value->total_time; |
251 | } |
252 | |
253 | HashMap<StringName, Metric::Category::Item *>::ConstIterator G = m.item_ptrs.find(E); |
254 | if (G) { |
255 | if (use_self) { |
256 | value = G->value->self; |
257 | } else { |
258 | value = G->value->total; |
259 | } |
260 | } |
261 | |
262 | int plot_pos = CLAMP(int(value * h / highest), 0, h - 1); |
263 | |
264 | int prev_plot = plot_pos; |
265 | HashMap<StringName, int>::Iterator H = prev_plots.find(E); |
266 | if (H) { |
267 | prev_plot = H->value; |
268 | H->value = plot_pos; |
269 | } else { |
270 | prev_plots[E] = plot_pos; |
271 | } |
272 | |
273 | plot_pos = h - plot_pos - 1; |
274 | prev_plot = h - prev_plot - 1; |
275 | |
276 | if (prev_plot > plot_pos) { |
277 | SWAP(prev_plot, plot_pos); |
278 | } |
279 | |
280 | Color col = _get_color_from_signature(E); |
281 | |
282 | for (int j = prev_plot; j <= plot_pos; j++) { |
283 | column[j * 4 + 0] += Math::fast_ftoi(CLAMP(col.r * 255, 0, 255)); |
284 | column[j * 4 + 1] += Math::fast_ftoi(CLAMP(col.g * 255, 0, 255)); |
285 | column[j * 4 + 2] += Math::fast_ftoi(CLAMP(col.b * 255, 0, 255)); |
286 | column[j * 4 + 3] += 1; |
287 | } |
288 | } |
289 | |
290 | for (int j = 0; j < h * 4; j += 4) { |
291 | const int a = column[j + 3]; |
292 | if (a > 0) { |
293 | column[j + 0] /= a; |
294 | column[j + 1] /= a; |
295 | column[j + 2] /= a; |
296 | } |
297 | |
298 | const uint8_t red = uint8_t(column[j + 0]); |
299 | const uint8_t green = uint8_t(column[j + 1]); |
300 | const uint8_t blue = uint8_t(column[j + 2]); |
301 | const bool is_filled = red >= 1 || green >= 1 || blue >= 1; |
302 | const int widx = ((j >> 2) * w + i) * 4; |
303 | |
304 | // If the pixel isn't filled by any profiler line, apply the background color instead. |
305 | wr[widx + 0] = is_filled ? red : Math::fast_ftoi(background_color.r * 255); |
306 | wr[widx + 1] = is_filled ? green : Math::fast_ftoi(background_color.g * 255); |
307 | wr[widx + 2] = is_filled ? blue : Math::fast_ftoi(background_color.b * 255); |
308 | wr[widx + 3] = 255; |
309 | } |
310 | } |
311 | } |
312 | |
313 | Ref<Image> img = Image::create_from_data(w, h, false, Image::FORMAT_RGBA8, graph_image); |
314 | |
315 | if (reset_texture) { |
316 | if (graph_texture.is_null()) { |
317 | graph_texture.instantiate(); |
318 | } |
319 | graph_texture->set_image(img); |
320 | } |
321 | |
322 | graph_texture->update(img); |
323 | |
324 | graph->set_texture(graph_texture); |
325 | graph->queue_redraw(); |
326 | } |
327 | |
328 | void EditorProfiler::_update_frame() { |
329 | int cursor_metric = cursor_metric_edit->get_value() - _get_frame_metric(0).frame_number; |
330 | |
331 | updating_frame = true; |
332 | variables->clear(); |
333 | |
334 | TreeItem *root = variables->create_item(); |
335 | const Metric &m = _get_frame_metric(cursor_metric); |
336 | |
337 | int dtime = display_time->get_selected(); |
338 | |
339 | for (int i = 0; i < m.categories.size(); i++) { |
340 | TreeItem *category = variables->create_item(root); |
341 | category->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
342 | category->set_editable(0, true); |
343 | category->set_metadata(0, m.categories[i].signature); |
344 | category->set_text(0, String(m.categories[i].name)); |
345 | category->set_text(1, _get_time_as_text(m, m.categories[i].total_time, 1)); |
346 | |
347 | if (plot_sigs.has(m.categories[i].signature)) { |
348 | category->set_checked(0, true); |
349 | category->set_custom_color(0, _get_color_from_signature(m.categories[i].signature)); |
350 | } |
351 | |
352 | for (int j = m.categories[i].items.size() - 1; j >= 0; j--) { |
353 | const Metric::Category::Item &it = m.categories[i].items[j]; |
354 | |
355 | TreeItem *item = variables->create_item(category); |
356 | item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); |
357 | item->set_editable(0, true); |
358 | item->set_text(0, it.name); |
359 | item->set_metadata(0, it.signature); |
360 | item->set_metadata(1, it.script); |
361 | item->set_metadata(2, it.line); |
362 | item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_RIGHT); |
363 | item->set_tooltip_text(0, it.name + "\n" + it.script + ":" + itos(it.line)); |
364 | |
365 | float time = dtime == DISPLAY_SELF_TIME ? it.self : it.total; |
366 | |
367 | item->set_text(1, _get_time_as_text(m, time, it.calls)); |
368 | |
369 | item->set_text(2, itos(it.calls)); |
370 | |
371 | if (plot_sigs.has(it.signature)) { |
372 | item->set_checked(0, true); |
373 | item->set_custom_color(0, _get_color_from_signature(it.signature)); |
374 | } |
375 | } |
376 | } |
377 | |
378 | updating_frame = false; |
379 | } |
380 | |
381 | void EditorProfiler::_update_button_text() { |
382 | if (activate->is_pressed()) { |
383 | activate->set_icon(get_editor_theme_icon(SNAME("Stop" ))); |
384 | activate->set_text(TTR("Stop" )); |
385 | } else { |
386 | activate->set_icon(get_editor_theme_icon(SNAME("Play" ))); |
387 | activate->set_text(TTR("Start" )); |
388 | } |
389 | } |
390 | |
391 | void EditorProfiler::_activate_pressed() { |
392 | _update_button_text(); |
393 | |
394 | if (activate->is_pressed()) { |
395 | _clear_pressed(); |
396 | } |
397 | |
398 | emit_signal(SNAME("enable_profiling" ), activate->is_pressed()); |
399 | } |
400 | |
401 | void EditorProfiler::_clear_pressed() { |
402 | clear_button->set_disabled(true); |
403 | clear(); |
404 | _update_plot(); |
405 | } |
406 | |
407 | void EditorProfiler::_notification(int p_what) { |
408 | switch (p_what) { |
409 | case NOTIFICATION_ENTER_TREE: |
410 | case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: |
411 | case NOTIFICATION_THEME_CHANGED: |
412 | case NOTIFICATION_TRANSLATION_CHANGED: { |
413 | activate->set_icon(get_editor_theme_icon(SNAME("Play" ))); |
414 | clear_button->set_icon(get_editor_theme_icon(SNAME("Clear" ))); |
415 | } break; |
416 | } |
417 | } |
418 | |
419 | void EditorProfiler::_graph_tex_draw() { |
420 | if (total_metrics == 0) { |
421 | return; |
422 | } |
423 | if (seeking) { |
424 | int frame = cursor_metric_edit->get_value() - _get_frame_metric(0).frame_number; |
425 | int cur_x = (2 * frame + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1; |
426 | graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.8)); |
427 | } |
428 | if (hover_metric > -1 && hover_metric < total_metrics) { |
429 | int cur_x = (2 * hover_metric + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1; |
430 | graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), Color(1, 1, 1, 0.4)); |
431 | } |
432 | } |
433 | |
434 | void EditorProfiler::_graph_tex_mouse_exit() { |
435 | hover_metric = -1; |
436 | graph->queue_redraw(); |
437 | } |
438 | |
439 | void EditorProfiler::_cursor_metric_changed(double) { |
440 | if (updating_frame) { |
441 | return; |
442 | } |
443 | |
444 | graph->queue_redraw(); |
445 | _update_frame(); |
446 | } |
447 | |
448 | void EditorProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { |
449 | if (last_metric < 0) { |
450 | return; |
451 | } |
452 | |
453 | Ref<InputEventMouse> me = p_ev; |
454 | Ref<InputEventMouseButton> mb = p_ev; |
455 | Ref<InputEventMouseMotion> mm = p_ev; |
456 | |
457 | if ( |
458 | (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) || |
459 | (mm.is_valid())) { |
460 | int x = me->get_position().x - 1; |
461 | x = x * frame_metrics.size() / graph->get_size().width; |
462 | |
463 | hover_metric = x; |
464 | |
465 | if (x < 0) { |
466 | x = 0; |
467 | } |
468 | |
469 | if (x >= frame_metrics.size()) { |
470 | x = frame_metrics.size() - 1; |
471 | } |
472 | |
473 | if (mb.is_valid() || (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { |
474 | updating_frame = true; |
475 | |
476 | if (x < total_metrics) { |
477 | cursor_metric_edit->set_value(_get_frame_metric(x).frame_number); |
478 | } |
479 | updating_frame = false; |
480 | |
481 | if (activate->is_pressed()) { |
482 | if (!seeking) { |
483 | emit_signal(SNAME("break_request" )); |
484 | } |
485 | } |
486 | |
487 | seeking = true; |
488 | |
489 | if (!frame_delay->is_processing()) { |
490 | frame_delay->set_wait_time(0.1); |
491 | frame_delay->start(); |
492 | } |
493 | } |
494 | |
495 | graph->queue_redraw(); |
496 | } |
497 | } |
498 | |
499 | void EditorProfiler::disable_seeking() { |
500 | seeking = false; |
501 | graph->queue_redraw(); |
502 | } |
503 | |
504 | void EditorProfiler::_combo_changed(int) { |
505 | _update_frame(); |
506 | _update_plot(); |
507 | } |
508 | |
509 | void EditorProfiler::_bind_methods() { |
510 | ADD_SIGNAL(MethodInfo("enable_profiling" , PropertyInfo(Variant::BOOL, "enable" ))); |
511 | ADD_SIGNAL(MethodInfo("break_request" )); |
512 | } |
513 | |
514 | void EditorProfiler::set_enabled(bool p_enable, bool p_clear) { |
515 | activate->set_disabled(!p_enable); |
516 | if (p_clear) { |
517 | clear(); |
518 | } |
519 | } |
520 | |
521 | void EditorProfiler::set_pressed(bool p_pressed) { |
522 | activate->set_pressed(p_pressed); |
523 | _update_button_text(); |
524 | } |
525 | |
526 | bool EditorProfiler::is_profiling() { |
527 | return activate->is_pressed(); |
528 | } |
529 | |
530 | Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { |
531 | Vector<Vector<String>> res; |
532 | |
533 | if (frame_metrics.is_empty()) { |
534 | return res; |
535 | } |
536 | |
537 | // Different metrics may contain different number of categories. |
538 | HashSet<StringName> possible_signatures; |
539 | for (int i = 0; i < frame_metrics.size(); i++) { |
540 | const Metric &m = frame_metrics[i]; |
541 | if (!m.valid) { |
542 | continue; |
543 | } |
544 | for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { |
545 | possible_signatures.insert(E.key); |
546 | } |
547 | for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { |
548 | possible_signatures.insert(E.key); |
549 | } |
550 | } |
551 | |
552 | // Generate CSV header and cache indices. |
553 | HashMap<StringName, int> sig_map; |
554 | Vector<String> signatures; |
555 | signatures.resize(possible_signatures.size()); |
556 | int sig_index = 0; |
557 | for (const StringName &E : possible_signatures) { |
558 | signatures.write[sig_index] = E; |
559 | sig_map[E] = sig_index; |
560 | sig_index++; |
561 | } |
562 | res.push_back(signatures); |
563 | |
564 | // values |
565 | Vector<String> values; |
566 | |
567 | int index = last_metric; |
568 | |
569 | for (int i = 0; i < frame_metrics.size(); i++) { |
570 | ++index; |
571 | |
572 | if (index >= frame_metrics.size()) { |
573 | index = 0; |
574 | } |
575 | |
576 | const Metric &m = frame_metrics[index]; |
577 | |
578 | if (!m.valid) { |
579 | continue; |
580 | } |
581 | |
582 | // Don't keep old values since there may be empty cells. |
583 | values.clear(); |
584 | values.resize(possible_signatures.size()); |
585 | |
586 | for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { |
587 | values.write[sig_map[E.key]] = String::num_real(E.value->total_time); |
588 | } |
589 | for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { |
590 | values.write[sig_map[E.key]] = String::num_real(E.value->total); |
591 | } |
592 | |
593 | res.push_back(values); |
594 | } |
595 | |
596 | return res; |
597 | } |
598 | |
599 | EditorProfiler::EditorProfiler() { |
600 | HBoxContainer *hb = memnew(HBoxContainer); |
601 | add_child(hb); |
602 | activate = memnew(Button); |
603 | activate->set_toggle_mode(true); |
604 | activate->set_disabled(true); |
605 | activate->set_text(TTR("Start" )); |
606 | activate->connect("pressed" , callable_mp(this, &EditorProfiler::_activate_pressed)); |
607 | hb->add_child(activate); |
608 | |
609 | clear_button = memnew(Button); |
610 | clear_button->set_text(TTR("Clear" )); |
611 | clear_button->connect("pressed" , callable_mp(this, &EditorProfiler::_clear_pressed)); |
612 | clear_button->set_disabled(true); |
613 | hb->add_child(clear_button); |
614 | |
615 | hb->add_child(memnew(Label(TTR("Measure:" )))); |
616 | |
617 | display_mode = memnew(OptionButton); |
618 | display_mode->add_item(TTR("Frame Time (ms)" )); |
619 | display_mode->add_item(TTR("Average Time (ms)" )); |
620 | display_mode->add_item(TTR("Frame %" )); |
621 | display_mode->add_item(TTR("Physics Frame %" )); |
622 | display_mode->connect("item_selected" , callable_mp(this, &EditorProfiler::_combo_changed)); |
623 | |
624 | hb->add_child(display_mode); |
625 | |
626 | hb->add_child(memnew(Label(TTR("Time:" )))); |
627 | |
628 | display_time = memnew(OptionButton); |
629 | // TRANSLATORS: This is an option in the profiler to display the time spent in a function, including the time spent in other functions called by that function. |
630 | display_time->add_item(TTR("Inclusive" )); |
631 | // TRANSLATORS: This is an option in the profiler to display the time spent in a function, exincluding the time spent in other functions called by that function. |
632 | display_time->add_item(TTR("Self" )); |
633 | display_time->set_tooltip_text(TTR("Inclusive: Includes time from other functions called by this function.\nUse this to spot bottlenecks.\n\nSelf: Only count the time spent in the function itself, not in other functions called by that function.\nUse this to find individual functions to optimize." )); |
634 | display_time->connect("item_selected" , callable_mp(this, &EditorProfiler::_combo_changed)); |
635 | |
636 | hb->add_child(display_time); |
637 | |
638 | hb->add_spacer(); |
639 | |
640 | hb->add_child(memnew(Label(TTR("Frame #:" )))); |
641 | |
642 | cursor_metric_edit = memnew(SpinBox); |
643 | cursor_metric_edit->set_h_size_flags(SIZE_FILL); |
644 | cursor_metric_edit->set_value(0); |
645 | cursor_metric_edit->set_editable(false); |
646 | hb->add_child(cursor_metric_edit); |
647 | cursor_metric_edit->connect("value_changed" , callable_mp(this, &EditorProfiler::_cursor_metric_changed)); |
648 | |
649 | hb->add_theme_constant_override("separation" , 8 * EDSCALE); |
650 | |
651 | h_split = memnew(HSplitContainer); |
652 | add_child(h_split); |
653 | h_split->set_v_size_flags(SIZE_EXPAND_FILL); |
654 | |
655 | variables = memnew(Tree); |
656 | variables->set_custom_minimum_size(Size2(320, 0) * EDSCALE); |
657 | variables->set_hide_folding(true); |
658 | h_split->add_child(variables); |
659 | variables->set_hide_root(true); |
660 | variables->set_columns(3); |
661 | variables->set_column_titles_visible(true); |
662 | variables->set_column_title(0, TTR("Name" )); |
663 | variables->set_column_expand(0, true); |
664 | variables->set_column_clip_content(0, true); |
665 | variables->set_column_custom_minimum_width(0, 60); |
666 | variables->set_column_title(1, TTR("Time" )); |
667 | variables->set_column_expand(1, false); |
668 | variables->set_column_clip_content(1, true); |
669 | variables->set_column_custom_minimum_width(1, 75 * EDSCALE); |
670 | variables->set_column_title(2, TTR("Calls" )); |
671 | variables->set_column_expand(2, false); |
672 | variables->set_column_clip_content(2, true); |
673 | variables->set_column_custom_minimum_width(2, 50 * EDSCALE); |
674 | variables->connect("item_edited" , callable_mp(this, &EditorProfiler::_item_edited)); |
675 | |
676 | graph = memnew(TextureRect); |
677 | graph->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); |
678 | graph->set_mouse_filter(MOUSE_FILTER_STOP); |
679 | graph->connect("draw" , callable_mp(this, &EditorProfiler::_graph_tex_draw)); |
680 | graph->connect("gui_input" , callable_mp(this, &EditorProfiler::_graph_tex_input)); |
681 | graph->connect("mouse_exited" , callable_mp(this, &EditorProfiler::_graph_tex_mouse_exit)); |
682 | |
683 | h_split->add_child(graph); |
684 | graph->set_h_size_flags(SIZE_EXPAND_FILL); |
685 | |
686 | int metric_size = CLAMP(int(EDITOR_GET("debugger/profiler_frame_history_size" )), 60, 10000); |
687 | frame_metrics.resize(metric_size); |
688 | |
689 | frame_delay = memnew(Timer); |
690 | frame_delay->set_wait_time(0.1); |
691 | frame_delay->set_one_shot(true); |
692 | add_child(frame_delay); |
693 | frame_delay->connect("timeout" , callable_mp(this, &EditorProfiler::_update_frame)); |
694 | |
695 | plot_delay = memnew(Timer); |
696 | plot_delay->set_wait_time(0.1); |
697 | plot_delay->set_one_shot(true); |
698 | add_child(plot_delay); |
699 | plot_delay->connect("timeout" , callable_mp(this, &EditorProfiler::_update_plot)); |
700 | |
701 | plot_sigs.insert("physics_frame_time" ); |
702 | plot_sigs.insert("category_frame_time" ); |
703 | } |
704 | |