| 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 |  | 
|---|