#include "editor_profiler.h" #include "editor_settings.h" #include "os/os.h" void EditorProfiler::_make_metric_ptrs(Metric& m) { for(int i=0;i<m.categories.size();i++) { m.category_ptrs[m.categories[i].signature]=&m.categories[i]; for(int j=0;j<m.categories[i].items.size();j++) { m.item_ptrs[m.categories[i].items[j].signature]=&m.categories[i].items[j]; } } } void EditorProfiler::add_frame_metric(const Metric& p_metric,bool p_final) { ++last_metric; if (last_metric>=frame_metrics.size()) last_metric=0; frame_metrics[last_metric]=p_metric; _make_metric_ptrs(frame_metrics[last_metric]); updating_frame=true; cursor_metric_edit->set_max(frame_metrics[last_metric].frame_number); cursor_metric_edit->set_min(MAX(frame_metrics[last_metric].frame_number-frame_metrics.size(),0)); if (!seeking) { cursor_metric_edit->set_val(frame_metrics[last_metric].frame_number); if (hover_metric!=-1) { hover_metric++; if (hover_metric>=frame_metrics.size()) { hover_metric=0; } } } updating_frame=false; if (!frame_delay->is_processing()) { frame_delay->set_wait_time(p_final?0.1:1); frame_delay->start(); } if (!plot_delay->is_processing()) { plot_delay->set_wait_time(0.1); plot_delay->start(); } } void EditorProfiler::clear() { int metric_size=EditorSettings::get_singleton()->get("debugger/profiler_frame_history_size"); metric_size = CLAMP(metric_size,60,1024); frame_metrics.clear(); frame_metrics.resize(metric_size); last_metric=-1; variables->clear(); //activate->set_pressed(false); plot_sigs.clear(); plot_sigs.insert("fixed_frame_time"); plot_sigs.insert("category_frame_time"); updating_frame=true; cursor_metric_edit->set_min(0); cursor_metric_edit->set_max(0); cursor_metric_edit->set_val(0); updating_frame=false; hover_metric=-1; seeking=false; } static String _get_percent_txt(float p_value,float p_total) { if (p_total==0) p_total=0.00001; return String::num((p_value/p_total)*100,1)+"%"; } String EditorProfiler::_get_time_as_text(Metric &m,float p_time,int p_calls) { int dmode = display_mode->get_selected(); if (dmode==DISPLAY_FRAME_TIME) { return rtos(p_time); } else if (dmode==DISPLAY_AVERAGE_TIME) { if (p_calls==0) return "0"; else return rtos(p_time/p_calls); } else if (dmode==DISPLAY_FRAME_PERCENT) { return _get_percent_txt(p_time,m.frame_time); } else if (dmode==DISPLAY_FIXED_FRAME_PERCENT) { return _get_percent_txt(p_time,m.fixed_frame_time); } return "err"; } Color EditorProfiler::_get_color_from_signature(const StringName& p_signature) const { double rot = ABS(double(p_signature.hash())/double(0x7FFFFFFF)); Color c; c.set_hsv(rot,1,1); return c; } void EditorProfiler::_item_edited() { if (updating_frame) return; TreeItem *item=variables->get_edited(); if (!item) return; StringName signature=item->get_metadata(0); bool checked=item->is_checked(0); if (checked) plot_sigs.insert(signature); else plot_sigs.erase(signature); if (!frame_delay->is_processing()) { frame_delay->set_wait_time(0.1); frame_delay->start(); } } void EditorProfiler::_update_plot() { int w = graph->get_size().width; int h = graph->get_size().height; bool reset_texture=false; int desired_len = w * h * 4; if (graph_image.size()!=desired_len) { reset_texture=true; graph_image.resize(desired_len); } DVector<uint8_t>::Write wr = graph_image.write(); //clear for(int i=0;i<desired_len;i+=4) { wr[i+0]=0; wr[i+1]=0; wr[i+2]=0; wr[i+3]=255; } //find highest value bool use_self = display_time->get_selected()==DISPLAY_SELF_TIME; float highest=0; for(int i=0;i<frame_metrics.size();i++) { Metric &m = frame_metrics[i]; if (!m.valid) continue; for (Set<StringName>::Element *E=plot_sigs.front();E;E=E->next()) { Map<StringName,Metric::Category*>::Element *F=m.category_ptrs.find(E->get()); if (F) { highest=MAX(F->get()->total_time,highest); } Map<StringName,Metric::Category::Item*>::Element *G=m.item_ptrs.find(E->get()); if (G) { if (use_self) { highest=MAX(G->get()->self,highest); } else { highest=MAX(G->get()->total,highest); } } } } if (highest>0) { //means some data exists.. highest*=1.2; //leave some upper room graph_height=highest; Vector<int> columnv; columnv.resize(h*4); int *column = columnv.ptr(); Map<StringName,int> plot_prev; //Map<StringName,int> plot_max; uint64_t time = OS::get_singleton()->get_ticks_usec(); for(int i=0;i<w;i++) { for(int j=0;j<h*4;j++) { column[j]=0; } int current = i*frame_metrics.size()/w; int next = (i+1)*frame_metrics.size()/w; if (next>frame_metrics.size()) { next=frame_metrics.size(); } if (next==current) next=current+1; //just because for loop must work for (Set<StringName>::Element *E=plot_sigs.front();E;E=E->next()) { int plot_pos=-1; for(int j=current;j<next;j++) { //wrap int idx = last_metric+1+j; while( idx >= frame_metrics.size() ) { idx-=frame_metrics.size(); } //get Metric &m = frame_metrics[idx]; if (m.valid==false) continue; //skip because invalid float value=0; Map<StringName,Metric::Category*>::Element *F=m.category_ptrs.find(E->get()); if (F) { value=F->get()->total_time; } Map<StringName,Metric::Category::Item*>::Element *G=m.item_ptrs.find(E->get()); if (G) { if (use_self) { value=G->get()->self; } else { value=G->get()->total; } } plot_pos = MAX( CLAMP(int(value*h/highest),0,h-1), plot_pos ); } int prev_plot=plot_pos; Map<StringName,int>::Element *H=plot_prev.find(E->get()); if (H) { prev_plot=H->get(); H->get()=plot_pos; } else { plot_prev[E->get()]=plot_pos; } if (plot_pos==-1 && prev_plot==-1) { //don't bother drawing continue; } if (prev_plot!=-1 && plot_pos==-1) { plot_pos=prev_plot; } if (prev_plot==-1 && plot_pos!=-1) { prev_plot=plot_pos; } plot_pos = h- plot_pos -1; prev_plot = h- prev_plot -1; if (prev_plot > plot_pos) { SWAP(prev_plot,plot_pos); } Color col = _get_color_from_signature(E->get()); for(int j=prev_plot;j<=plot_pos;j++) { column[j*4+0]+=Math::fast_ftoi(CLAMP(col.r*255,0,255)); column[j*4+1]+=Math::fast_ftoi(CLAMP(col.g*255,0,255)); column[j*4+2]+=Math::fast_ftoi(CLAMP(col.b*255,0,255)); column[j*4+3]+=1; } } for(int j=0;j<h*4;j+=4) { int a = column[j+3]; if (a>0) { column[j+0]/=a; column[j+1]/=a; column[j+2]/=a; } uint8_t r = uint8_t(column[j+0]); uint8_t g = uint8_t(column[j+1]); uint8_t b = uint8_t(column[j+2]); int widx = ((j>>2)*w+i)*4; wr[widx+0]=r; wr[widx+1]=g; wr[widx+2]=b; wr[widx+3]=255; } } time = OS::get_singleton()->get_ticks_usec() - time; //print_line("Taken: "+rtos(USEC_TO_SEC(time))); } wr = DVector<uint8_t>::Write(); Image img(w,h,0,Image::FORMAT_RGBA,graph_image); if (reset_texture) { if (graph_texture.is_null()) { graph_texture.instance(); } graph_texture->create(img.get_width(),img.get_height(),img.get_format(),Texture::FLAG_VIDEO_SURFACE); } graph_texture->set_data(img);; graph->set_texture(graph_texture); graph->update(); } void EditorProfiler::_update_frame() { int cursor_metric = _get_cursor_index(); ERR_FAIL_INDEX(cursor_metric,frame_metrics.size()); updating_frame=true; variables->clear(); TreeItem* root = variables->create_item(); Metric &m = frame_metrics[cursor_metric]; int dtime = display_time->get_selected(); for(int i=0;i<m.categories.size();i++) { TreeItem *category = variables->create_item(root); category->set_cell_mode(0,TreeItem::CELL_MODE_CHECK); category->set_editable(0,true); category->set_metadata(0,m.categories[i].signature); category->set_text(0,String(m.categories[i].name)); category->set_text(1,_get_time_as_text(m,m.categories[i].total_time,1)); if (plot_sigs.has(m.categories[i].signature)) { category->set_checked(0,true); category->set_custom_bg_color(0,Color(0,0,0)); category->set_custom_color(0,_get_color_from_signature(m.categories[i].signature)); } for(int j=0;j<m.categories[i].items.size();j++) { Metric::Category::Item &it = m.categories[i].items[j]; TreeItem *item = variables->create_item(category); item->set_cell_mode(0,TreeItem::CELL_MODE_CHECK); item->set_editable(0,true); item->set_text(0,it.name); item->set_metadata(0,it.signature); item->set_metadata(1,it.script); item->set_metadata(2,it.line); item->set_tooltip(0,it.script+":"+itos(it.line)); float time = dtime == DISPLAY_SELF_TIME ? it.self : it.total; item->set_text(1,_get_time_as_text(m,time,it.calls)); item->set_text(2,itos(it.calls)); if (plot_sigs.has(it.signature)) { item->set_checked(0,true); item->set_custom_bg_color(0,Color(0,0,0)); item->set_custom_color(0,_get_color_from_signature(it.signature)); } } } updating_frame=false; } void EditorProfiler::_activate_pressed() { if (activate->is_pressed()) { clear(); activate->set_icon(get_icon("Stop","EditorIcons")); activate->set_text(TTR("Stop Profiling")); } else { activate->set_icon(get_icon("Play","EditorIcons")); activate->set_text(TTR("Start Profiling")); } emit_signal("enable_profiling",activate->is_pressed()); } void EditorProfiler::_notification(int p_what) { if (p_what==NOTIFICATION_ENTER_TREE) { activate->set_icon(get_icon("Play","EditorIcons")); } } void EditorProfiler::_graph_tex_draw() { if (last_metric<0) return; if (seeking) { int max_frames = frame_metrics.size(); int frame = cursor_metric_edit->get_val() - (frame_metrics[last_metric].frame_number-max_frames+1); if (frame<0) frame=0; int cur_x = frame * graph->get_size().x / max_frames; graph->draw_line(Vector2(cur_x,0),Vector2(cur_x,graph->get_size().y),Color(1,1,1,0.8)); } if (hover_metric!=-1 && frame_metrics[hover_metric].valid) { int max_frames = frame_metrics.size(); int frame = frame_metrics[hover_metric].frame_number - (frame_metrics[last_metric].frame_number-max_frames+1); if (frame<0) frame=0; int cur_x = frame * graph->get_size().x / max_frames; graph->draw_line(Vector2(cur_x,0),Vector2(cur_x,graph->get_size().y),Color(1,1,1,0.4)); } } void EditorProfiler::_graph_tex_mouse_exit() { hover_metric=-1; graph->update(); } void EditorProfiler::_cursor_metric_changed(double) { if (updating_frame) return; graph->update(); _update_frame(); } void EditorProfiler::_graph_tex_input(const InputEvent& p_ev){ if (last_metric<0) return; if ( (p_ev.type==InputEvent::MOUSE_BUTTON && p_ev.mouse_button.button_index==BUTTON_LEFT && p_ev.mouse_button.pressed) || (p_ev.type==InputEvent::MOUSE_MOTION) ) { int x = p_ev.mouse_button.x; x=x*frame_metrics.size()/graph->get_size().width; bool show_hover = x>=0 && x<frame_metrics.size(); if (x<0) { x=0; } if (x>=frame_metrics.size()) { x=frame_metrics.size()-1; } int metric=frame_metrics.size()-x-1; metric = last_metric-metric; while(metric<0) { metric+=frame_metrics.size(); } if (show_hover) { hover_metric=metric; } else { hover_metric=-1; } if (p_ev.type==InputEvent::MOUSE_BUTTON || p_ev.mouse_motion.button_mask&BUTTON_MASK_LEFT) { //cursor_metric=x; updating_frame=true; //metric may be invalid, so look for closest metric that is valid, this makes snap feel better bool valid=false; for(int i=0;i<frame_metrics.size();i++) { if (frame_metrics[metric].valid) { valid=true; break; } metric++; if (metric>=frame_metrics.size()) metric=0; } if (valid) cursor_metric_edit->set_val(frame_metrics[metric].frame_number); updating_frame=false; if (activate->is_pressed()) { if (!seeking) { emit_signal("break_request"); } } seeking=true; if (!frame_delay->is_processing()) { frame_delay->set_wait_time(0.1); frame_delay->start(); } } graph->update(); } } int EditorProfiler::_get_cursor_index() const { if (last_metric<0) return 0; if (!frame_metrics[last_metric].valid) return 0; int diff = (frame_metrics[last_metric].frame_number-cursor_metric_edit->get_val()); int idx = last_metric - diff; while (idx<0) { idx+=frame_metrics.size(); } return idx; } void EditorProfiler::disable_seeking() { seeking=false; graph->update(); } void EditorProfiler::_combo_changed(int) { _update_frame(); _update_plot(); } void EditorProfiler::_bind_methods() { ObjectTypeDB::bind_method(_MD("_update_frame"),&EditorProfiler::_update_frame); ObjectTypeDB::bind_method(_MD("_update_plot"),&EditorProfiler::_update_plot); ObjectTypeDB::bind_method(_MD("_activate_pressed"),&EditorProfiler::_activate_pressed); ObjectTypeDB::bind_method(_MD("_graph_tex_draw"),&EditorProfiler::_graph_tex_draw); ObjectTypeDB::bind_method(_MD("_graph_tex_input"),&EditorProfiler::_graph_tex_input); ObjectTypeDB::bind_method(_MD("_graph_tex_mouse_exit"),&EditorProfiler::_graph_tex_mouse_exit); ObjectTypeDB::bind_method(_MD("_cursor_metric_changed"),&EditorProfiler::_cursor_metric_changed); ObjectTypeDB::bind_method(_MD("_combo_changed"),&EditorProfiler::_combo_changed); ObjectTypeDB::bind_method(_MD("_item_edited"),&EditorProfiler::_item_edited); ADD_SIGNAL( MethodInfo("enable_profiling",PropertyInfo(Variant::BOOL,"enable"))); ADD_SIGNAL( MethodInfo("break_request")); } void EditorProfiler::set_enabled(bool p_enable) { activate->set_disabled(!p_enable); } bool EditorProfiler::is_profiling() { return activate->is_pressed(); } EditorProfiler::EditorProfiler() { HBoxContainer *hb = memnew( HBoxContainer ); add_child(hb); activate = memnew( Button ); activate->set_toggle_mode(true); activate->set_text(TTR("Start Profiling")); activate->connect("pressed",this,"_activate_pressed"); hb->add_child(activate); hb->add_child( memnew( Label(TTR("Measure:") ) ) ); display_mode = memnew( OptionButton ); display_mode->add_item(TTR("Frame Time (sec)")); display_mode->add_item(TTR("Average Time (sec)")); display_mode->add_item(TTR("Frame %")); display_mode->add_item(TTR("Fixed Frame %")); display_mode->connect("item_selected",this,"_combo_changed"); hb->add_child( display_mode ); hb->add_child( memnew( Label(TTR("Time:") ) ) ); display_time = memnew( OptionButton ); display_time->add_item(TTR("Inclusive")); display_time->add_item(TTR("Self")); display_time->connect("item_selected",this,"_combo_changed"); hb->add_child(display_time); hb->add_spacer(); hb->add_child( memnew( Label(TTR("Frame #:") ) ) ); cursor_metric_edit = memnew( SpinBox ); cursor_metric_edit->set_h_size_flags(SIZE_FILL); hb->add_child(cursor_metric_edit); cursor_metric_edit->connect("value_changed",this,"_cursor_metric_changed"); hb->add_constant_override("separation",8); h_split = memnew( HSplitContainer ); add_child(h_split); h_split->set_v_size_flags(SIZE_EXPAND_FILL); variables = memnew( Tree ); variables->set_custom_minimum_size(Size2(300,0)); variables->set_hide_folding(true); h_split->add_child(variables); variables->set_hide_root(true); variables->set_columns(3); variables->set_column_titles_visible(true); variables->set_column_title(0,"Name"); variables->set_column_expand(0,true); variables->set_column_min_width(0,60); variables->set_column_title(1,"Time"); variables->set_column_expand(1,false); variables->set_column_min_width(1,60); variables->set_column_title(2,"Calls"); variables->set_column_expand(2,false); variables->set_column_min_width(2,60); variables->connect("item_edited",this,"_item_edited"); graph = memnew( TextureFrame ); graph->set_expand(true); graph->set_stop_mouse(true); graph->set_ignore_mouse(false); graph->connect("draw",this,"_graph_tex_draw"); graph->connect("input_event",this,"_graph_tex_input"); graph->connect("mouse_exit",this,"_graph_tex_mouse_exit"); h_split->add_child(graph); graph->set_h_size_flags(SIZE_EXPAND_FILL); add_constant_override("separation",3); int metric_size=CLAMP(int(EDITOR_DEF("debugger/profiler_frame_history_size",600)),60,1024); frame_metrics.resize(metric_size); last_metric=-1; // cursor_metric=-1; hover_metric=-1; EDITOR_DEF("debugger/profiler_frame_max_functions",64); //display_mode=DISPLAY_FRAME_TIME; frame_delay = memnew( Timer ); frame_delay->set_wait_time(0.1); frame_delay->set_one_shot(true); add_child(frame_delay); frame_delay->connect("timeout",this,"_update_frame"); plot_delay = memnew( Timer ); plot_delay->set_wait_time(0.1); plot_delay->set_one_shot(true); add_child(plot_delay); plot_delay->connect("timeout",this,"_update_plot"); plot_sigs.insert("fixed_frame_time"); plot_sigs.insert("category_frame_time"); seeking=false; graph_height=1; // activate->set_disabled(true); }