Lumenarium/src_v2/libs/sloth/utils/sloth_profiler.c

913 lines
26 KiB
C
Raw Normal View History

2022-08-08 09:39:42 +00:00
#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