#ifndef SLOTH_PROFILER_C #define SLOTH_PROFILER_C ////////////////////////////////////////////////////////////////////////////// // Context Cracking #if defined(__clang__) # define SP_COMPILER_CLANG 1 # if defined(__APPLE__) && defined(__MACH__) # define SP_OS_MAC 1 # elif defined(__gnu_linux__) # define SP_OS_LINUX 1 # elif defined(_WIN32) # define SP_OS_WINDOWS 1 # else # error The compiler/platform combo is not supported # endif #elif defined(__GNUC__) || defined(__GNUG__) # define SP_COMPILER_GCC 1 # if defined(__gnu_linux__) # define SP_OS_LINUX 1 # else # error The compiler/platform combo is not supported # endif #endif ////////////////////////////////////////////////////////////////////////////// #ifndef SLOTH_PROFILER_FUNCTION # define SLOTH_PROFILER_FUNCTION #endif #ifndef sp_assert # define sp_assert(v) #endif #ifndef SP_U8 # define SP_U8 unsigned char #endif #ifndef SP_S32 # define SP_S32 int #endif #ifndef SP_U32 # define SP_U32 unsigned int #endif #ifndef SP_U64 # define SP_U64 unsigned long long int #endif #ifndef SP_R64 # define SP_R64 double #endif #ifndef SP_MALLOC # define SP_MALLOC(type, count) (type*)malloc(sizeof(type) * (count)) #endif #ifndef SP_REALLOC # define SP_REALLOC(base, type, old_count, new_count) (type*)realloc((void*)(base), (new_count) * sizeof(type)) #endif ////////////////////////////////////////////////////////////////////////////// // Scope Instrumentation #if !SP_COMPILER_CLANG && !SP_COMPILER_GCC # error sloth_profiler.c currently relies on a defer implementation that utilizes clang/gcc extensions. # error The compiler you are using is not supported. #endif #ifndef defer static inline void defer_cleanup(void (^*b)(void)) { (*b)(); } # define defer_merge(a,b) a##b # define defer_varname(a) defer_merge(defer_scopevar_, a) # define defer __attribute__((cleanup(defer_cleanup))) void (^defer_varname(__COUNTER__))(void) = #endif // Macros for easy #undef if not in debug mode // NOTE: These are the preferred way to call the sloth_profiler functions #define SLOTH_PROFILER_FRAME_BEGIN() sp_frame_begin() #define SLOTH_PROFILER_FRAME_END() sp_frame_end() #define SLOTH_PROFILER_SCOPE sp_scope_begin((char*)__FUNCTION__, (char*)__FILE__, (SP_S32)__LINE__); defer ^{ sp_scope_end(); }; SLOTH_PROFILER_FUNCTION void sp_init(Sloth_Ctx* sloth, SP_U32 frames_cap); SLOTH_PROFILER_FUNCTION void sp_frame_begin(); SLOTH_PROFILER_FUNCTION void sp_frame_end(); SLOTH_PROFILER_FUNCTION void sp_scope_begin(char* function, char* file, SP_S32 line); SLOTH_PROFILER_FUNCTION void sp_scope_end(); SLOTH_PROFILER_FUNCTION void sp_draw(); // Platform Time Keeping Implementations typedef struct Sloth_Profiler_Ticks Sloth_Profiler_Ticks; struct Sloth_Profiler_Ticks { SP_U64 value; }; SLOTH_PROFILER_FUNCTION void sp_ticks_os_init(); SLOTH_PROFILER_FUNCTION Sloth_Profiler_Ticks sp_ticks_now(); SLOTH_PROFILER_FUNCTION Sloth_Profiler_Ticks sp_ticks_elapsed(Sloth_Profiler_Ticks start, Sloth_Profiler_Ticks end); SLOTH_PROFILER_FUNCTION SP_R64 sp_ticks_to_seconds(Sloth_Profiler_Ticks ticks); SLOTH_PROFILER_FUNCTION SP_R64 sp_ticks_to_milliseconds(Sloth_Profiler_Ticks ticks); ////////////////////////////////////////////////////////////////////////////// // IMPLEMENTATION #if defined(SLOTH_PROFILER_IMPLEMENTATION) static const SP_U64 SP_NANOS_PER_SEC = 1000000000ULL; typedef struct Sloth_Profiler_Scope_Id Sloth_Profiler_Scope_Id; struct Sloth_Profiler_Scope_Id { SP_U32 value; }; // a record of a single time a scope was called typedef struct Sloth_Profiler_Scope_Call Sloth_Profiler_Scope_Call; struct Sloth_Profiler_Scope_Call { Sloth_Profiler_Scope_Id id; char* name; // temp Sloth_Profiler_Ticks start; Sloth_Profiler_Ticks end; SP_U32 parent; SP_U32 first_child; SP_U32 last_child; SP_U32 next_sibling; }; // A record of a particular scope, across all call so that scope typedef struct Sloth_Profiler_Scope Sloth_Profiler_Scope; struct Sloth_Profiler_Scope { char* name; char* file; SP_U32 line; Sloth_Profiler_Ticks ticks_longest; Sloth_Profiler_Ticks ticks_shortest; Sloth_Profiler_Ticks ticks_average; SP_U64 total_count; SP_U64 count_last_frame; }; typedef struct Sloth_Profiler_Frame Sloth_Profiler_Frame; struct Sloth_Profiler_Frame { Sloth_Profiler_Scope_Call* calls; SP_U32 calls_len; SP_U32 calls_cap; SP_U32 parent_cur; Sloth_Profiler_Ticks start; Sloth_Profiler_Ticks end; }; enum { SlothProfiler_Recording = 0, SlothProfiler_WaitingToBeginRecording = 1, SlothProfiler_Paused = 2 }; typedef struct Sloth_Profiler_Ctx Sloth_Profiler_Ctx; struct Sloth_Profiler_Ctx { // hashtable of scopes Sloth_Hashtable scope_ids; Sloth_Profiler_Scope* scopes; SP_U32 scopes_cap; SP_U32 scopes_len; // ring buffer of stored frames Sloth_Profiler_Frame* frames; SP_U32 frames_cap; SP_U32 frame_at; Sloth_U32 depth; // UI SP_U32 ui_frame; SP_U8 recording; SP_R64 visible_min; SP_R64 visible_max; }; // a pointer to the applications Sloth_Ctx // the profiler doesn't assume it owns this data static Sloth_Ctx* sp_ctx_; static Sloth_Profiler_Ctx* sp_pctx_; static SP_R64 sp_ticks_per_second_; static SP_R64 sp_ticks_per_millisecond_; SLOTH_PROFILER_FUNCTION void sp_init(Sloth_Ctx* sloth, SP_U32 frames_cap) { sp_ctx_ = sloth; sp_pctx_ = SP_MALLOC(Sloth_Profiler_Ctx, 1); *sp_pctx_ = (Sloth_Profiler_Ctx){ // Sloth_Hashtable has a default size of 2048 .scope_ids = {}, .scopes = SP_MALLOC(Sloth_Profiler_Scope, 2048), .scopes_cap = 2048, .scopes_len = 0, .frames = SP_MALLOC(Sloth_Profiler_Frame, frames_cap), .frames_cap = frames_cap, .frame_at = 0, }; for (SP_U32 i = 0; i < sp_pctx_->frames_cap; i++) { sp_pctx_->frames[i] = (Sloth_Profiler_Frame){}; } sp_ticks_os_init(); } SLOTH_PROFILER_FUNCTION Sloth_Profiler_Frame* sp_get_frame_cur() { Sloth_Profiler_Frame* frame = sp_pctx_->frames + sp_pctx_->frame_at; return frame; } SLOTH_PROFILER_FUNCTION void sp_begin_recording_force() { sp_pctx_->recording = SlothProfiler_Recording; } SLOTH_PROFILER_FUNCTION void sp_begin_recording() { sp_pctx_->recording = SlothProfiler_WaitingToBeginRecording; } SLOTH_PROFILER_FUNCTION void sp_pause_recording() { sp_pctx_->recording = SlothProfiler_Paused; } SLOTH_PROFILER_FUNCTION Sloth_Profiler_Scope_Call* sp_frame_push_call(Sloth_Profiler_Frame* frame, SP_U32* index_out) { if (frame->calls_len >= frame->calls_cap) { SP_U32 calls_cap = frame->calls_cap * 2; if (calls_cap == 0) calls_cap = 1024; frame->calls = SP_REALLOC(frame->calls, Sloth_Profiler_Scope_Call, frame->calls_cap, calls_cap); frame->calls_cap = calls_cap; } SP_U32 index = frame->calls_len++; Sloth_Profiler_Scope_Call* call = frame->calls + index; if (index_out) *index_out = index; return call; } #define TEMP_MAX_DEPTH 8 SLOTH_PROFILER_FUNCTION void sp_frame_begin() { if (sp_pctx_->recording == SlothProfiler_Paused) return; sp_pctx_->recording = SlothProfiler_Recording; Sloth_Profiler_Frame* frame = sp_get_frame_cur(); frame->start = sp_ticks_now(); frame->calls_len = 0; frame->parent_cur = 0; sp_pctx_->depth = 0; SP_U32 index = 1234; // garbage value Sloth_Profiler_Scope_Call* root = sp_frame_push_call(frame, &index); *root = (Sloth_Profiler_Scope_Call){ .name = "root", .id.value = 0, .start = sp_ticks_now(), .parent = 0, .first_child = 0, .last_child = 0, .next_sibling = 0, }; sloth_assert(index == 0); } SLOTH_PROFILER_FUNCTION void sp_frame_end() { if (sp_pctx_->recording != SlothProfiler_Recording) return; Sloth_Profiler_Frame* frame = sp_pctx_->frames + sp_pctx_->frame_at; frame->end = sp_ticks_now(); frame->calls[0].end = frame->end; // TODO: Frame Cleanup sp_pctx_->frame_at++; if (sp_pctx_->frame_at >= sp_pctx_->frames_cap) { sp_pctx_->frame_at = 0; } } SLOTH_PROFILER_FUNCTION void sp_scope_begin(char* function, char* file, SP_S32 line) { if (sp_pctx_->recording != SlothProfiler_Recording) return; sp_pctx_->depth += 1; if (sp_pctx_->depth > TEMP_MAX_DEPTH) return; // Hash the function name // djb2 hash - http://www.cse.yorku.ca/~oz/hash.html Sloth_Profiler_Scope_Id id = { .value = 5381 }; for (char* at = function; *at != 0; at++) { id.value = ((id.value << 5) + id.value) + (Sloth_U8)at[0]; } // Register the scope if needed if (!sloth_hashtable_get(&sp_pctx_->scope_ids, id.value)) { SP_U64 index = (Sloth_U64)sp_pctx_->scopes_len++; Sloth_Profiler_Scope* scope = sp_pctx_->scopes + index; *scope = (Sloth_Profiler_Scope){ .name = function, .file = file, .line = line, .ticks_longest = 0, .ticks_shortest = 0, .ticks_average = 0, .total_count = 0, .count_last_frame = 0, }; sloth_hashtable_add(&sp_pctx_->scope_ids, id.value, (Sloth_U8*)scope); } Sloth_Profiler_Frame* frame = sp_get_frame_cur(); SP_U32 index = 0; Sloth_Profiler_Scope_Call* call = sp_frame_push_call(frame, &index); *call = (Sloth_Profiler_Scope_Call){ .id = id, .name = function, .start = sp_ticks_now(), .parent = frame->parent_cur, .first_child = 0, .last_child = 0, .next_sibling = 0, }; if (index > 0) { Sloth_Profiler_Scope_Call* parent = frame->calls + frame->parent_cur; if (parent->first_child != 0) { Sloth_Profiler_Scope_Call* last_sib = frame->calls + parent->last_child; last_sib->next_sibling = index; sloth_assert(parent->last_child != index); } else { frame->calls[index - 1].first_child = index; } parent->last_child = index; } frame->parent_cur = index; } SLOTH_PROFILER_FUNCTION void sp_scope_end() { if (sp_pctx_->recording != SlothProfiler_Recording) return; sp_pctx_->depth -= 1; if (sp_pctx_->depth > TEMP_MAX_DEPTH) return; Sloth_Profiler_Frame* frame = sp_get_frame_cur(); Sloth_Profiler_Scope_Call* call = frame->calls + frame->parent_cur; call->end = sp_ticks_now(); frame->parent_cur = call->parent; } SLOTH_PROFILER_FUNCTION Sloth_Profiler_Ticks sp_ticks_elapsed(Sloth_Profiler_Ticks start, Sloth_Profiler_Ticks end) { sloth_assert(end.value >= start.value); Sloth_Profiler_Ticks result = { .value = end.value - start.value }; return result; } SLOTH_PROFILER_FUNCTION SP_R64 sp_ticks_to_seconds(Sloth_Profiler_Ticks ticks) { SP_R64 result = (SP_R64)ticks.value / sp_ticks_per_second_; return result; } SLOTH_PROFILER_FUNCTION SP_R64 sp_ticks_to_milliseconds(Sloth_Profiler_Ticks ticks) { SP_R64 result = (SP_R64)ticks.value / sp_ticks_per_millisecond_; return result; } #if defined(SP_OS_MAC) static SP_U64 sp_osx_start_time_absolute_ = 0; static mach_timebase_info_data_t sp_osx_mach_time_info_ = {}; SLOTH_PROFILER_FUNCTION void sp_ticks_os_init() { sp_osx_start_time_absolute_ = mach_absolute_time(); mach_timebase_info(&sp_osx_mach_time_info_); SP_R64 numer = (SP_R64)sp_osx_mach_time_info_.numer; SP_R64 denom = (SP_R64)sp_osx_mach_time_info_.denom; SP_R64 nps = (SP_R64)SP_NANOS_PER_SEC; sp_ticks_per_second_ = (numer / denom) * nps; sp_ticks_per_millisecond_ = (numer / denom) * 1000 * 1000; } SLOTH_PROFILER_FUNCTION Sloth_Profiler_Ticks sp_ticks_now() { Sloth_Profiler_Ticks result = { .value = mach_absolute_time() - sp_osx_start_time_absolute_ }; return result; } #elif defined(SP_OS_LINUX) SLOTH_PROFILER_FUNCTION Sloth_Profiler_Ticks sp_ticks_now() { } #elif defined(SP_OS_WINDOWS) SLOTH_PROFILER_FUNCTION void sp_ticks_os_init() { LARGE_INTEGER freq; if (!QueryPerformanceFrequency(&freq)) { SLOTH_PROFILER_ERROR("Unable to QueryPerformanceCounter"); } sp_ticks_per_second = (SP_U64)freq.QuadPart; } SLOTH_PROFILER_FUNCTION Sloth_Profiler_Ticks sp_ticks_now() { LARGE_INTEGER t; if (!QueryPerformanceCounter(&t)) { SLOTH_PROFILER_ERROR("Unable to QueryPerformanceCounter"); } Sloth_Profiler_Ticks result = { .value = (SP_U64)t.QuadPart, }; return result; } #endif // SP_OS_* //////////////////////////////////////////////////////// // Visualizer Interface void sp_popup_f(char* fmt, ...) { Sloth_Widget_Desc desc = { .layout = { .width = sloth_size_text_content(), .height = sloth_size_text_content(), .margin = sloth_size_box_uniform_pixels(8), .position = { .kind = Sloth_LayoutPosition_FixedOnScreen, .left = sloth_size_pixels(sp_ctx_->mouse_pos.x), .top = sloth_size_pixels(sp_ctx_->mouse_pos.y), .z = -0.5f, }, }, .style = { .color_bg = 0x000000FF, .color_text = 0xFFFFFFFF, }, .input = { .flags = Sloth_WidgetInput_DoNotCaptureMouse, }, }; va_list args; va_start(args, fmt); sloth_push_widget_v(sp_ctx_, desc, fmt, args); sloth_pop_widget(sp_ctx_); va_end(args); } bool sp_button_f(char* fmt, ...) { bool result = false; Sloth_Widget_Desc desc = { .layout = { .width = sloth_size_text_content(), .height = sloth_size_text_content(), .margin = sloth_size_box_uniform_pixels(8), }, .style = { .color_bg = 0x000000FF, .color_text = 0xFFFFFFFF, }, }; va_list args; va_start(args, fmt); Sloth_Widget_Result r = sloth_push_widget_v(sp_ctx_, desc, fmt, args); if (sloth_ids_equal(r.widget->id, sp_ctx_->hot_widget)) { r.widget->style.color_bg = 0xFFFFFFFF; r.widget->style.color_text = 0x000000FF; } if (r.clicked) { result = true; r.widget->style.color_bg = 0x00FFFFFF; r.widget->style.color_text = 0x000000FF; } sloth_pop_widget(sp_ctx_); va_end(args); return result; } void sp_h_scope_slider(char* name, SP_R64* cur_min, SP_R64* cur_max) { Sloth_Widget_Desc desc = { .layout = { .width = sloth_size_percent_parent(1), .height = sloth_size_pixels(16), }, .style = { .color_bg = 0x000000FF, .color_outline = 0xFFFFFFFF, .outline_thickness = 1, }, }; Sloth_Widget_Desc handle_desc = { .layout = { .width = sloth_size_pixels(16), .height = sloth_size_pixels(16), .position.kind = Sloth_LayoutPosition_FixedInParent }, .style = { .color_bg = 0x888888FF, }, .input.flags = Sloth_WidgetInput_Draggable }; sloth_push_widget_f(sp_ctx_, desc, "###slider_%s", name); { handle_desc.layout.position.left = sloth_size_percent_parent(*cur_min); Sloth_Widget_Result r0 = sloth_push_widget_f(sp_ctx_, handle_desc, "###slider_%s_min", name); sloth_pop_widget(sp_ctx_); if (r0.held) { r0.widget->style.color_bg = 0x00FFFF00; SP_R64 new_min_pct = *cur_min + r0.drag_offset_percent_parent.x; new_min_pct = Sloth_Clamp(0, new_min_pct, 1); Sloth_Widget_Desc drag_desc = handle_desc; drag_desc.layout.position.left = sloth_size_percent_parent(new_min_pct); drag_desc.input.flags = Sloth_WidgetInput_DoNotCaptureMouse; sloth_push_widget_f(sp_ctx_, drag_desc, "###slider_moving_%s_min", name); sloth_pop_widget(sp_ctx_); } if (r0.released) { Sloth_R32 new_min_pct = (*cur_min + r0.drag_offset_percent_parent.x); *cur_min = new_min_pct = Sloth_Clamp(0, new_min_pct, 1); } handle_desc.layout.position.left = (Sloth_Size){}; handle_desc.layout.position.right = sloth_size_percent_parent(*cur_max); Sloth_Widget_Result r1 = sloth_push_widget_f(sp_ctx_, handle_desc, "###slider_%s_max", name); sloth_pop_widget(sp_ctx_); if (r1.held) { r1.widget->style.color_bg = 0x00FFFF00; SP_R64 new_max_pct = *cur_max + r1.drag_offset_percent_parent.x; new_max_pct = Sloth_Clamp(0, new_max_pct, 1); Sloth_Widget_Desc drag_desc = handle_desc; drag_desc.layout.position.right = sloth_size_percent_parent(new_max_pct); drag_desc.input.flags = Sloth_WidgetInput_DoNotCaptureMouse; sloth_push_widget_f(sp_ctx_, drag_desc, "###slider_moving_%s_max", name); sloth_pop_widget(sp_ctx_); } if (r1.released) { Sloth_R32 new_max_pct = (*cur_max + r1.drag_offset_percent_parent.x); *cur_max = new_max_pct = Sloth_Clamp(0, new_max_pct, 1); } } sloth_pop_widget(sp_ctx_); } bool sp_frame_bar(SP_U32 frame_i) { bool pause_recording = false; Sloth_Widget_Desc border_desc = { .layout = { .width = sloth_size_percent_parent(1), .height = sloth_size_children_sum(), .margin = sloth_size_box_uniform_pixels(4), .direction = Sloth_LayoutDirection_LeftToRight, // TODO //.child_h_gap = sloth_size_pixels(4), }, .style = { .color_outline = 0xFFFFFFFF, .outline_thickness = 4, }, }; Sloth_Widget_Desc frame_box_desc = { .layout = { .width = sloth_size_percent_parent(1.0f / sp_pctx_->frames_cap), .height = sloth_size_pixels(64), }, .style = { .color_bg = 0xFFFFFFFF, .color_outline = 0x000000FF, .outline_thickness = 1, }, }; sloth_push_widget(sp_ctx_, border_desc, "###frame_bar"); { Sloth_Widget_Result r; SP_U32 popup_frame = sp_pctx_->frames_cap; for (SP_U32 i = 0; i < sp_pctx_->frames_cap; i++) { r = sloth_push_widget_f(sp_ctx_, frame_box_desc, "###frame_bar_%d", i); if (i == frame_i) { r.widget->style.color_bg = 0x00FFFFFF; } if (sloth_ids_equal(r.widget->id, sp_ctx_->hot_widget)) { r.widget->style.color_bg = 0x00FF00FF; popup_frame = i; if (sloth_mouse_button_is_down(sp_ctx_->mouse_button_l)) { pause_recording = true; sp_pctx_->ui_frame = i; } } sloth_pop_widget(sp_ctx_); } if (popup_frame < sp_pctx_->frames_cap) { sp_popup_f("Frame: %d##popup", popup_frame); } } sloth_pop_widget(sp_ctx_); border_desc.style.outline_thickness = 0; sloth_push_widget(sp_ctx_, border_desc, "###frame_bar_btns"); { if (sp_button_f("Resume Recording")) { sp_begin_recording(); } if (sp_button_f("Pause Recording")) { pause_recording = true; sp_pctx_->ui_frame = sp_pctx_->frame_at - 1; } } sloth_pop_widget(sp_ctx_); return pause_recording; } void sp_frame_header(Sloth_Profiler_Frame* frame, SP_U32 frame_i) { Sloth_Widget_Desc header_desc = { .layout = { .width = sloth_size_percent_parent(1), .height = sloth_size_text_content(), .margin = sloth_size_box_uniform_pixels(4), }, .style = { .color_outline = 0xFFFFFFFF, .color_text = 0xFFFFFFFF, .outline_thickness = 1, }, }; bool mouse_down = sloth_mouse_button_is_down(sp_ctx_->mouse_button_l); Sloth_Profiler_Ticks ticks = sp_ticks_elapsed(frame->start, frame->end); SP_R64 ms = sp_ticks_to_milliseconds(ticks); sloth_push_widget_f(sp_ctx_, header_desc, "Frame: %d\nCalls: %d\nTicks: %lld\nTime: %fms###profiler_frame_header", frame_i, frame->calls_len, ticks.value, ms); sloth_pop_widget(sp_ctx_); } Sloth_U32 sp_calls_visualized = 0; void sp_flame_graph_call(Sloth_Profiler_Frame* frame, SP_U32 call_index, Sloth_R32 visible_width_pixels, Sloth_Profiler_Ticks visible_start, Sloth_Profiler_Ticks visible_duration, Sloth_U32 depth) { Sloth_Widget_Desc scope_desc = { .layout = { .height = sloth_size_pixels(16), .position = { .kind = Sloth_LayoutPosition_FixedInParent, .top = sloth_size_pixels(16 * depth), }, }, .style = { .color_outline = 0x00FF00FF, .outline_thickness = 1, }, }; Sloth_Profiler_Scope_Id last_unused_id = {}; Sloth_Profiler_Ticks unused_run_start = {}; do { Sloth_Profiler_Scope_Call call = frame->calls[call_index]; Sloth_Profiler_Scope* scope = (Sloth_Profiler_Scope*)sloth_hashtable_get(&sp_pctx_->scope_ids, call.id.value); char* name = "root"; if (scope) name = scope->name; bool is_after_visible = call.start.value > (visible_start.value + visible_duration.value); bool is_before_visible = call.end.value < visible_start.value; if (is_after_visible || is_before_visible) { call_index = call.next_sibling; continue; } Sloth_Profiler_Ticks call_duration = sp_ticks_elapsed(call.start, call.end); SP_R64 call_pct_visible_duration = (SP_R64)call_duration.value / (SP_R64)visible_duration.value; // See if this block is next to more blocks of the same call // and collapse them if they're too small to see individually SP_R64 min_draw_pct_width = 0.05f; if (call_pct_visible_duration <= min_draw_pct_width) { bool is_in_run = (last_unused_id.value == call.id.value); bool is_end_of_run = false; if (call.next_sibling != 0) { Sloth_Profiler_Scope_Call next_call = frame->calls[call.next_sibling]; is_end_of_run = (next_call.id.value != last_unused_id.value); } if (is_in_run && is_end_of_run) { call_duration = sp_ticks_elapsed(unused_run_start, call.end); call_pct_visible_duration = (SP_R64)call_duration.value / (SP_R64)visible_duration.value; } if (is_end_of_run) { last_unused_id.value = 0; } } if (call_pct_visible_duration > min_draw_pct_width) { Sloth_Profiler_Ticks call_offset_start, call_offset_end; if (call.start.value > visible_start.value) { call_offset_start = sp_ticks_elapsed(visible_start, call.start); } else { call_offset_start.value = 0; } if (call.end.value < visible_start.value + visible_duration.value) { call_offset_end = sp_ticks_elapsed(visible_start, call.end); } else { call_offset_end.value = visible_duration.value; } SP_R64 call_pct_start = (SP_R64)call_offset_start.value / (SP_R64)visible_duration.value; SP_R64 call_pct_end = (SP_R64)call_offset_end.value / (SP_R64)visible_duration.value; sloth_assert(call_pct_start >= 0 && call_pct_start <= 1 && call_pct_end >= 0 && call_pct_end <= 1); SP_R64 call_pct_duration = call_pct_end - call_pct_start; scope_desc.layout.width = sloth_size_pixels(visible_width_pixels * call_pct_duration); scope_desc.layout.position.left = sloth_size_pixels(visible_width_pixels * call_pct_start); Sloth_Widget_Desc scope_bar_desc = { .layout = { .width = sloth_size_percent_parent(call_pct_duration), .height = sloth_size_pixels(16), }, .style = { .color_outline = 0x00FF00FF, .outline_thickness = 1, }, }; Sloth_Widget_Result r = sloth_push_widget_f(sp_ctx_, scope_desc, "###%s_%d_fg_bar", name, sp_calls_visualized++); { if (sloth_ids_equal(r.widget->id, sp_ctx_->hot_widget)) { Sloth_Profiler_Ticks t = sp_ticks_elapsed(call.start, call.end); sp_popup_f("%s - %lld###fg_popup", name, t.value); } } sloth_pop_widget(sp_ctx_); if (call.first_child != 0) { sp_flame_graph_call(frame, call.first_child, visible_width_pixels, visible_start, visible_duration, depth + 1); } } else if (last_unused_id.value != call.id.value) { unused_run_start = call.start; last_unused_id = call.id; } sloth_assert(call.next_sibling == 0 || call.next_sibling > call_index); sloth_assert(call.next_sibling < frame->calls_len); call_index = call.next_sibling; } while (call_index != 0); } void sp_flame_graph(Sloth_Profiler_Frame* frame, Sloth_R32 pixel_width) { Sloth_Widget_Desc fg_desc = { .layout = { .width = sloth_size_percent_parent(1), .height = sloth_size_pixels(128), .margin = sloth_size_box_uniform_pixels(4), }, .style = { .color_outline = 0xFFFFFFFF, .outline_thickness = 1, }, }; sp_calls_visualized = 0; Sloth_Profiler_Ticks frame_duration = sp_ticks_elapsed(frame->start, frame->end); Sloth_Profiler_Ticks frame_start = { .value = frame->start.value + (frame_duration.value * sp_pctx_->visible_min), }; frame_duration.value *= ((1.0f - sp_pctx_->visible_max) - sp_pctx_->visible_min); sloth_push_widget_f(sp_ctx_, fg_desc, "###profiler_flame_graph"); if (frame->calls) { sp_flame_graph_call(frame, 0, pixel_width, frame_start, frame_duration, 0); } sloth_pop_widget(sp_ctx_); sp_h_scope_slider("flame_graph_slider", &sp_pctx_->visible_min, &sp_pctx_->visible_max); } SLOTH_PROFILER_FUNCTION void sp_draw() { if (!sp_pctx_) return; SP_U32 frame_i = sp_pctx_->frame_at - 1; if (sp_pctx_->recording == SlothProfiler_Paused) frame_i = sp_pctx_->ui_frame; if (frame_i > sp_pctx_->frames_cap) frame_i = sp_pctx_->frames_cap - 1; bool pause_recording = false; bool was_recording = sp_pctx_->recording == SlothProfiler_Recording; if (was_recording) sp_pause_recording(); Sloth_Profiler_Frame* frame = sp_pctx_->frames + frame_i; Sloth_Widget_Desc bg_desc = { .layout = { .width = sloth_size_pixels(800), .height = sloth_size_pixels(600), .margin = sloth_size_box_uniform_pixels(16), .position = { .kind = Sloth_LayoutPosition_FixedOnScreen, .left = sloth_size_pixels(32), .top = sloth_size_pixels(48), }, }, .style = { .color_bg = 0x333333FF, }, }; sloth_push_widget(sp_ctx_, bg_desc, "###profiler_root"); { pause_recording = sp_frame_bar(frame_i); sp_frame_header(frame, frame_i); sp_flame_graph(frame, 800 - 32); } sloth_pop_widget(sp_ctx_); if (pause_recording) { sp_pause_recording(); } else if (was_recording) { sp_begin_recording_force(); } } #endif // SLOTH_PROFILER_IMPLEMENTATION #endif // SLOTH_PROFILER_C