Commit: 38e2d3ed9825721a3f0b5e61611b527c5b527503
Parent: 95864d5a8f74698bdd64409e7efbc83c61341bdb
Author: Randy Palamar
Date: Mon, 4 Nov 2024 00:12:46 -0700
first pass at debug bar chart overlay
Diffstat:
M | debug.c | | | 299 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | debug.h | | | 138 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
M | main.c | | | 57 | +++++++++++++++++++++++++++++++++++++-------------------- |
M | os_unix.c | | | 6 | +++--- |
M | test.c | | | 14 | ++------------ |
M | util.c | | | 31 | ++++++++++++++++++++++++++++--- |
M | util.h | | | 7 | +++++-- |
M | vtgl.c | | | 138 | +++++++++++++++++++------------------------------------------------------------ |
8 files changed, 528 insertions(+), 162 deletions(-)
diff --git a/debug.c b/debug.c
@@ -91,3 +91,302 @@ debug_init(Term *t, Arena a)
{
/* NOTE: this used to be useful but for now its not */
}
+
+static OpenDebugBlock *
+get_open_debug_block(DebugState *ds, DebugEvent *de)
+{
+ OpenDebugBlock *result = ds->first_free_block;
+ if (result) {
+ ds->first_free_block = result->next_free;
+ } else {
+ result = alloc(ds->temp_memory.arena, OpenDebugBlock, 1);
+ }
+ result->next_free = 0;
+ result->parent = 0;
+ result->opening_event = de;
+ return result;
+}
+
+static void
+restart_collation(DebugState *ds, u32 invalid_record_index)
+{
+ end_temp_arena(ds->temp_memory);
+ ds->temp_memory = begin_temp_arena(&ds->memory);
+ ds->first_free_block = 0;
+ ds->record_count = 0;
+ ds->record_bar_scale = 0;
+ ds->record_working_index = invalid_record_index + 1;
+ ds->open_record = 0;
+}
+
+static void
+coalesce_debug_events(DebugState *ds, u32 invalid_snapshot_index, DebugMetadata *metadata)
+{
+ BEGIN_TIMED_BLOCK();
+
+ for (;; ds->record_working_index++) {
+ if (ds->record_working_index == DEBUG_SNAPSHOT_COUNT)
+ ds->record_working_index = 0;
+ if (ds->record_working_index == invalid_snapshot_index)
+ break;
+ u32 record_index = ds->record_working_index;
+ for (u32 j = 0; j < g_debug_table.events_count[record_index]; j++) {
+ DebugEvent *de = g_debug_table.events[record_index] + j;
+
+ if (de->type == DE_FRAME_MARK) {
+ if (ds->open_record) {
+ DebugRecord *or = ds->open_record;
+ or->end_clock = de->clock;
+ or->wall_time = de->wall_clock_time;
+ f32 elapsed_time = or->end_clock - or->start_clock;
+ if (elapsed_time) {
+ f32 record_bar_scale = 1 / elapsed_time;
+ if (record_bar_scale > ds->record_bar_scale)
+ ds->record_bar_scale = record_bar_scale;
+ }
+ ds->record_count++;
+ if (ds->record_count >= DEBUG_SNAPSHOT_COUNT)
+ /* TODO: is this just an error ? */
+ goto end;
+ }
+ ds->open_record = ds->records + ds->record_count;
+ ds->open_record->start_clock = de->clock;
+ ds->open_record->end_clock = 0;
+ ds->open_record->region_count = 0;
+ ds->open_record->wall_time = 0;
+ ds->open_record->first_block = get_open_debug_block(ds, 0);
+ ds->open_record->regions = alloc(&ds->memory, DebugRegion,
+ MAX_DEBUG_REGION_COUNT);
+ } else if (ds->open_record) {
+ DebugRecord *or = ds->open_record;
+ //ASSERT(or == ds->records + record_index);
+ //u64 relative_clock = de->clock - record->start_clock;
+ switch (de->type) {
+ case DE_BEGIN: {
+ OpenDebugBlock *odb = get_open_debug_block(ds, de);
+ odb->starting_record_index = record_index;
+ odb->parent = or->first_block;
+ or->first_block = odb;
+ } break;
+ case DE_END: {
+ if (!or->first_block)
+ break;
+
+ OpenDebugBlock *odb = or->first_block;
+ DebugEvent *oe = odb->opening_event;
+ if (oe->debug_metadata_index != de->debug_metadata_index) {
+ /* TODO: the first block was never closed */
+ INVALID_CODE_PATH;
+ break;
+ }
+
+ if (odb->starting_record_index == record_index) {
+ if (odb->parent->opening_event == 0) {
+ f32 min_t = (oe->clock - or->start_clock);
+ f32 max_t = (de->clock - or->start_clock);
+ if ((de->clock - oe->clock) > 1) {
+ DebugRegion *dr = or->regions + or->region_count++;
+ ASSERT(or->region_count <= MAX_DEBUG_REGION_COUNT);
+ dr->min_t = min_t;
+ dr->max_t = max_t;
+ dr->debug_metadata_index = de->debug_metadata_index;
+ }
+ }
+ } else {
+ /* TODO: this record spans multiple running frames;
+ * need to record all enclosing events */
+ INVALID_CODE_PATH;
+ }
+ odb->next_free = ds->first_free_block;
+ or->first_block = odb->parent;
+ ds->first_free_block = odb;
+ } break;
+ default: INVALID_CODE_PATH;
+ }
+ }
+ }
+ }
+
+end:
+ END_TIMED_BLOCK();
+}
+
+static void
+draw_debug_bar_chart(Term *t, DebugState *ds, RenderCtx *rc, v2 bar_chart_top_left, f32 bar_chart_magnitude)
+{
+ BEGIN_TIMED_BLOCK();
+
+ f32 bar_thick = ds->bar_thickness;
+ f32 bar_pad = 0.5 * ds->bar_thickness;
+ v2 pos = bar_chart_top_left;
+ /* TODO */
+ //f32 secs_scale = bar_chart_magnitude / ds->frame_target_time;
+ f32 target_fps = 60;
+ f32 target_time = 1 / target_fps;
+ f32 secs_scale = bar_chart_magnitude * target_fps;
+
+ /* TODO: store the mouse somewhere */
+ f64 xpos, ypos;
+ glfwGetCursorPos(t->gl.window, &xpos, &ypos);
+ v2 mouse = {.x = xpos, .y = t->gl.window_size.h - ypos};
+
+ /* TODO */
+ v4 fg = normalize_colour((Colour){.rgba = 0x1e9e33ff});
+ v4 colours[] = {
+ (v4){.r = 0.5, .g = 0.0, .b = 0.0, .a = 1.0},
+ (v4){.r = 0.0, .g = 0.5, .b = 0.0, .a = 1.0},
+ (v4){.r = 0.0, .g = 0.0, .b = 0.5, .a = 1.0},
+ (v4){.r = 0.5, .g = 0.5, .b = 0.0, .a = 1.0},
+ (v4){.r = 0.5, .g = 0.0, .b = 0.5, .a = 1.0},
+ (v4){.r = 0.0, .g = 0.5, .b = 0.5, .a = 1.0},
+ };
+
+ pos.y -= bar_thick + bar_pad;
+
+ DebugMetadata *txt_meta = 0;
+ f32 txt_secs = 0;
+
+ Stream txt = stream_alloc(ds->temp_memory.arena, 1024);
+ for (u32 i = 0; i < ds->record_count; i++) {
+ DebugRecord *record = ds->records + i;
+
+ f32 cycs_to_secs = record->wall_time / (f32)(record->end_clock - record->start_clock);
+ f32 scale = cycs_to_secs * secs_scale;
+ pos.y -= bar_thick + bar_pad;
+ for (u32 j = 0; j < record->region_count; j++) {
+ DebugRegion *dr = record->regions + j;
+ f32 cycs = dr->max_t - dr->min_t;
+ f32 time = cycs * scale;
+ Rect r = {.pos = pos, .size = {.h = bar_thick, .w = time}};
+ if (r.size.w > 1.0f) {
+ push_rect(rc, r, colours[dr->debug_metadata_index % ARRAY_COUNT(colours)]);
+ if (point_in_rect(mouse, r)) {
+ txt_meta = debug_metadata + dr->debug_metadata_index;
+ txt_secs = cycs * cycs_to_secs;
+ }
+ pos.x += r.size.w;
+ }
+ }
+ pos.x = bar_chart_top_left.x;
+ //stream_push_f64(&txt, record->wall_time * 1e3, 100);
+ //stream_push_s8(&txt, s8(" [ms]"));
+ stream_push_u64(&txt, record->end_clock - record->start_clock);
+ stream_push_s8(&txt, s8(" [cycs]"));
+ v2 txt_s = measure_text(rc, g_ui_debug_font_id, stream_to_s8(&txt));
+ v2 txt_p = pos;
+ txt_p.x -= txt_s.w + 10;
+ push_s8(rc, txt_p, fg, g_ui_debug_font_id, stream_to_s8(&txt));
+ txt.widx = 0;
+ }
+
+ Rect target_marker = {.pos = pos};
+ target_marker.pos.x += bar_chart_magnitude;
+ target_marker.size.w = 4;
+ target_marker.size.h = ds->record_count * (bar_thick + bar_pad) - bar_pad;
+ push_rect(rc, target_marker, colours[0]);
+
+ stream_push_s8(&txt, s8("Target Time: "));
+ stream_push_f64(&txt, target_time * 1e3, 100);
+ pos.x += bar_chart_magnitude + 8;
+ pos.y = bar_chart_top_left.y - 0.5 * DEBUG_SNAPSHOT_COUNT * (bar_thick + bar_pad);
+ stream_push_s8(&txt, s8(" [ms]"));
+ push_s8(rc, pos, fg, g_ui_debug_font_id, stream_to_s8(&txt));
+
+ if (txt_meta) {
+ txt.widx = 0;
+ stream_push_s8(&txt, c_str_to_s8(txt_meta->function_name));
+ stream_push_s8(&txt, s8(": "));
+ stream_push_f64(&txt, txt_secs * 1e3, 100);
+ stream_push_s8(&txt, s8(" [ms] "));
+ stream_push_s8(&txt, c_str_to_s8(txt_meta->file_name));
+ stream_push_byte(&txt, ':');
+ stream_push_u64(&txt, txt_meta->line_number);
+ push_s8(rc, mouse, fg, g_ui_debug_font_id, stream_to_s8(&txt));
+ }
+
+ END_TIMED_BLOCK();
+}
+
+static void
+draw_debug_overlay(Term *t, RenderCtx *rc)
+{
+ if (!(t->gl.flags & DRAW_DEBUG_OVERLAY))
+ return;
+
+ BEGIN_TIMED_BLOCK();
+
+ DebugState *ds = t->debug_state;
+
+ v2 bar_chart_top_left = t->gl.window_size;
+ bar_chart_top_left.x *= 0.5;
+ draw_debug_bar_chart(t, ds, rc, bar_chart_top_left, 0.25 * t->gl.window_size.w);
+
+ Arena memory = *ds->temp_memory.arena;
+
+ v2 ws = t->gl.window_size;
+
+ static GlyphCacheStats glyph_stats;
+ static Rect r;
+ static f32 max_text_width;
+ static f32 line_height;
+ r.pos.x = ws.w - (max_text_width + 20);
+ r.size.x = max_text_width + 20;
+
+ v4 fg = normalize_colour((Colour){.rgba = 0x1e9e33ff});
+ v4 bg = normalize_colour((Colour){.rgba = 0x090909bf});
+
+ push_rect(rc, r, bg);
+
+ u32 font_id = g_ui_debug_font_id;
+ f32 line_pad = 5;
+ v2 txt_pos = {.x = r.pos.x, .y = ws.h - 20};
+
+ Stream txt = stream_alloc(&memory, 1024);
+ {
+ stream_push_s8(&txt, s8("Render Time: "));
+ stream_push_f64(&txt, dt_for_frame * 1e3, 100);
+ stream_push_s8(&txt, s8(" ms/f"));
+ v2 ts = measure_text(rc, font_id, stream_to_s8(&txt));
+ txt_pos.y = (u32)(txt_pos.y - ts.h - line_pad);
+ push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt));
+
+ if (ts.w > max_text_width) max_text_width = ts.w;
+ if (ts.h > line_height) line_height = ts.h;
+ }
+
+ GlyphCacheStats new_glyph_stats = get_and_clear_glyph_cache_stats(&t->fa.glyph_cache);
+ if (new_glyph_stats.hit_count != 0)
+ glyph_stats = new_glyph_stats;
+ {
+ s8 header = s8("Glyph Cache Stats:");
+ txt_pos.y = (u32)(txt_pos.y - line_height - line_pad);
+ v2 ts = push_s8(rc, txt_pos, fg, font_id, header);
+
+ /* TODO: This doesn't really work when we are redrawing on every frame */
+ static s8 fmts[ARRAY_COUNT(glyph_stats.E)] = {
+ s8(" Hits: "),
+ s8(" Misses: "),
+ s8(" Recycles: "),
+ };
+ for (u32 i = 0; i < ARRAY_COUNT(glyph_stats.E); i++) {
+ txt.widx = 0;
+ stream_push_s8(&txt, fmts[i]);
+ stream_push_u64(&txt, glyph_stats.E[i]);
+ txt_pos.y = (u32)(txt_pos.y - line_height - line_pad);
+ ts = push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt));
+
+ if (ts.w > max_text_width) max_text_width = ts.w;
+ if (ts.h > line_height) line_height = ts.h;
+ }
+ }
+
+ r.pos.y = txt_pos.y - 2 * line_pad;
+ r.size.h = ws.h - r.pos.y;
+
+ END_TIMED_BLOCK();
+}
+
+DEBUG_EXPORT DEBUG_BEGIN_FRAME_FN(debug_begin_frame)
+{
+ FRAME_MARK(wall_clock_elapsed);
+}
diff --git a/debug.h b/debug.h
@@ -1,5 +1,73 @@
+/* See LICENSE for copyright details */
+
+#define MAX_DEBUG_EVENT_COUNT (16 * 65536)
+/* TODO: this is way too high and it only seems to be necessary when resizing */
+#define MAX_DEBUG_REGION_COUNT 80 * 1024
+#define DEBUG_SNAPSHOT_COUNT 16
+
+typedef struct {
+ f32 min_t;
+ f32 max_t;
+ u16 debug_metadata_index;
+} DebugRegion;
+
+typedef __attribute__((aligned(16))) struct {
+ u64 clock;
+ u16 debug_metadata_index;
+ u16 type;
+ union {
+ /* TODO: store core + thread id if we make this multithreaded */
+ struct { u16 core_id, thread_id; };
+ f32 wall_clock_time;
+ };
+} DebugEvent;
+
+typedef struct OpenDebugBlock {
+ DebugEvent *opening_event;
+ struct OpenDebugBlock *parent;
+ struct OpenDebugBlock *next_free;
+ u32 starting_record_index;
+} OpenDebugBlock;
+
+typedef struct {
+ u64 start_clock;
+ u64 end_clock;
+ DebugRegion *regions;
+ u32 region_count;
+ f32 wall_time;
+ OpenDebugBlock *first_block;
+} DebugRecord;
+
+typedef struct {
+ Arena memory;
+ TempArena temp_memory;
+ b32 initialized;
+ b32 paused;
+
+ f32 bar_thickness;
+ f32 record_bar_scale;
+ f32 frame_target_time;
+ f32 cpu_target_time;
+ f32 gpu_target_time;
+ u32 frame_draw_count;
+
+ u32 record_count;
+ u32 record_working_index;
+ DebugRecord records[DEBUG_SNAPSHOT_COUNT];
+ DebugRecord *open_record;
+
+ OpenDebugBlock *first_free_block;
+} DebugState;
+
+#define DEBUG_BEGIN_FRAME_FN(name) void name(DebugState *debug_state, f32 wall_clock_elapsed)
+typedef DEBUG_BEGIN_FRAME_FN(debug_begin_frame_fn);
+
+#define DEBUG_END_FRAME_FN(name) void name(DebugState *debug_state, v2 window_size)
+typedef DEBUG_END_FRAME_FN(debug_end_frame_fn);
+
#ifndef _DEBUG
#define ASSERT(c) do { (void)(c); } while(0)
+#define INVALID_CODE_PATH
#define DEBUG_EXPORT static
#define BEGIN_TIMED_BLOCK(...)
@@ -8,35 +76,73 @@
#define debug_init(...)
#define draw_debug_overlay(...)
+DEBUG_BEGIN_FRAME_FN(debug_begin_frame) {}
+DEBUG_END_FRAME_FN(debug_end_frame) {}
+
#else
#define ASSERT(c) do { if (!(c)) asm("int3; nop"); } while(0)
#define INVALID_CODE_PATH ASSERT(0)
#define DEBUG_EXPORT
-typedef struct {
- u64 cycles;
+enum debug_event_types {
+ DE_INVALID,
+ DE_FRAME_MARK,
+ DE_BEGIN,
+ DE_END,
+};
+typedef struct {
char *file_name;
char *function_name;
u32 line_number;
- u32 hit_count;
-} DebugRecord;
+} DebugMetadata;
+
+typedef struct {
+ DebugEvent events[DEBUG_SNAPSHOT_COUNT][MAX_DEBUG_EVENT_COUNT];
+ u32 events_count[DEBUG_SNAPSHOT_COUNT];
+ u64 event_array_event_index;
+ u32 snapshot_index;
+} DebugTable;
+static DebugTable g_debug_table;
+
+DebugMetadata debug_metadata[];
+
+#define RECORD_DEBUG_EVENT_COMMON(counter, event_type) \
+ u64 event_index = __atomic_fetch_add(&g_debug_table.event_array_event_index, 1, __ATOMIC_RELAXED); \
+ ASSERT((event_index & 0xFFFFFFFF) < MAX_DEBUG_EVENT_COUNT); \
+ DebugEvent *event = g_debug_table.events[event_index >> 32] + (event_index & 0xFFFFFFFF); \
+ event->clock = __rdtsc(); \
+ event->debug_metadata_index = counter; \
+ event->type = event_type
+
+#define RECORD_DEBUG_EVENT(counter, event_type) \
+{ \
+ RECORD_DEBUG_EVENT_COMMON(counter, event_type); \
+}
+
+#define FRAME_MARK(wall_seconds_elapsed) \
+{ \
+ RECORD_DEBUG_EVENT_COMMON(__COUNTER__, DE_FRAME_MARK); \
+ event->wall_clock_time = wall_seconds_elapsed; \
+}
-DebugRecord debug_records[];
-
-#define BEGIN_TIMED_BLOCK(...) \
- u32 __counter = __COUNTER__; \
- { \
- DebugRecord *record = debug_records + __counter; \
- record->cycles -= __rdtsc(); \
- record->file_name = __FILE__; \
- record->function_name = (char *)__FUNCTION__; \
- record->line_number = __LINE__; \
- record->hit_count += 1; \
+#define BEGIN_TIMED_BLOCK(...) \
+ u16 __counter = __COUNTER__; \
+ { \
+ DebugMetadata *meta = debug_metadata + __counter; \
+ meta->file_name = __FILE__; \
+ meta->function_name = (char *)__FUNCTION__; \
+ meta->line_number = __LINE__; \
+ RECORD_DEBUG_EVENT(__counter, DE_BEGIN); \
}
-#define END_TIMED_BLOCK(...) debug_records[__counter].cycles += __rdtsc();
+#define END_TIMED_BLOCK(...) RECORD_DEBUG_EVENT(__counter, DE_END);
+typedef struct Term Term;
+typedef struct RenderCtx RenderCtx;
+static void debug_init(Term *t, Arena a);
+static void dump_lines_to_file(Term *t);
+static void draw_debug_overlay(Term *t, RenderCtx *rc);
#endif
diff --git a/main.c b/main.c
@@ -19,13 +19,18 @@ static char *libname = "./vtgl.so";
static void *libhandle;
typedef void do_terminal_fn(Term *, f32);
-static do_terminal_fn *do_terminal;
-
typedef void init_callbacks_fn(GLCtx *);
-static init_callbacks_fn *init_callbacks;
-
-typedef iv2 init_term_fn(Term *, Arena *, iv2);
-static init_term_fn *init_term;
+typedef iv2 init_term_fn(Term *, Arena *, iv2);
+
+#define LIB_FNS \
+ X(debug_begin_frame) \
+ X(debug_end_frame) \
+ X(do_terminal) \
+ X(init_callbacks) \
+ X(init_term)
+#define X(name) static name ## _fn *name;
+LIB_FNS
+#undef X
static void
load_library(const char *lib, Stream *err)
@@ -37,15 +42,12 @@ load_library(const char *lib, Stream *err)
libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL);
if (!libhandle)
stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl});
- do_terminal = dlsym(libhandle, "do_terminal");
- if (!do_terminal)
- stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), c_str_to_s8(dlerror()), nl});
- init_callbacks = dlsym(libhandle, "init_callbacks");
- if (!init_callbacks)
- stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), c_str_to_s8(dlerror()), nl});
- init_term = dlsym(libhandle, "init_term");
- if (!init_term)
- stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), c_str_to_s8(dlerror()), nl});
+ #define X(name) \
+ name = dlsym(libhandle, #name); \
+ if (!name) stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), \
+ c_str_to_s8(dlerror()), nl});
+ LIB_FNS
+ #undef X
}
static void
@@ -66,6 +68,8 @@ do_debug(GLCtx *gl, Stream *err)
stream_push_s8(err, s8("Reloaded Main Program\n"));
}
+ /* TODO: clear debug memory? */
+
if (err->widx) {
os_write_err_msg(stream_to_s8(err));
err->widx = 0;
@@ -193,7 +197,6 @@ init_window(Term *t, Arena arena, iv2 window_size)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * ARRAY_COUNT(rpb->positions) * sizeof(i32),
element_indices, GL_STATIC_DRAW);
-
t->gl.glyph_bitmap_tex = gen_2D_texture(t->gl.glyph_bitmap_dim, GL_RGBA, GL_NEAREST, 0);
/* NOTE: set pixel 0,0 to white (tile 0,0 is reserved). We can use this texture for
* drawing glyphs from the font cache or for drawing plain rectangles */
@@ -346,6 +349,13 @@ main(i32 argc, char *argv[], char *envp[])
{
Arena memory = os_new_arena(16 * MEGABYTE);
Term term = {0};
+ #ifdef _DEBUG
+ {
+ Arena debug_memory = os_new_arena(128 * MEGABYTE);
+ term.debug_state = alloc_(&debug_memory, sizeof(*term.debug_state), 64, 1);
+ term.debug_state->memory = debug_memory;
+ }
+ #endif
term.error_stream = stream_alloc(&memory, MEGABYTE / 4);
@@ -427,19 +437,26 @@ main(i32 argc, char *argv[], char *envp[])
term.child = os_fork_child("/bin/sh");
- f32 last_time = 0;
+ f64 last_time = os_get_time();
while (!glfwWindowShouldClose(term.gl.window)) {
do_debug(&term.gl, &term.error_stream);
- check_shaders(&term.gl, memory, &term.error_stream);
- f32 current_time = (f32)glfwGetTime();
+ /* TODO: cpu time excluding waiting for the vblank */
+ f64 current_time = os_get_time();
f32 dt = current_time - last_time;
last_time = current_time;
+ debug_begin_frame(term.debug_state, dt);
+
+ check_shaders(&term.gl, memory, &term.error_stream);
+
term.arena_for_frame = memory;
glfwPollEvents();
do_terminal(&term, dt);
+
+ debug_end_frame(term.debug_state, term.gl.window_size);
+
glfwSwapBuffers(term.gl.window);
}
@@ -448,5 +465,5 @@ main(i32 argc, char *argv[], char *envp[])
#ifdef _DEBUG
/* NOTE: shut up compiler warning that can't be disabled */
-DebugRecord debug_records[__COUNTER__];
+DebugMetadata debug_metadata[__COUNTER__];
#endif
diff --git a/os_unix.c b/os_unix.c
@@ -25,12 +25,12 @@ typedef struct {
#define OS_MAP_READ PROT_READ
#define OS_MAP_PRIVATE MAP_PRIVATE
-static u64
+static f64
os_get_time(void)
{
struct timespec ts;
- clock_gettime(CLOCK_REALTIME, &ts);
- u64 result = ts.tv_sec;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ f64 result = ts.tv_sec + ((f64)ts.tv_nsec) * 1e-9;
return result;
}
diff --git a/test.c b/test.c
@@ -32,7 +32,6 @@ KEYBIND_FN(scroll) { return 0; }
KEYBIND_FN(zoom) { return 0; }
#include "terminal.c"
-#include "debug.c"
static void
get_gpu_glyph_index(Arena a, void *b, void *c, u32 cp, u32 d, u32 e, CachedGlyph **cg)
@@ -198,10 +197,7 @@ static TEST_FN(colour_setting)
static TEST_FN(cursor_at_line_boundary)
{
- /* NOTE: two tests here
- * 1. split_raw_input_to_lines should modify cursor style properties
- * 2. split_raw_input_to_lines should not split utf-8 characters into separate lines
- */
+ /* NOTE: Test that lines are not split in the middle of utf-8 characters */
struct test_result result = {.info = __FUNCTION__};
s8 long_line = s8alloc(&arena, 8192);
@@ -245,12 +241,6 @@ static TEST_FN(cursor_at_line_boundary)
result.status &= line_length(lb->buf) > SPLIT_LONG;
- /* NOTE: check that cursor state was transferred */
- Cursor line_end = simulate_line(term, lb->buf);
- result.status &= !mem_cmp(&line_end.style,
- &lb->buf[1].cursor_state,
- sizeof(lb->buf[0].cursor_state));
-
return result;
}
@@ -353,7 +343,7 @@ static u32 failure_count;
#ifdef _DEBUG
/* NOTE: shut up compiler warning that can't be disabled */
-DebugRecord debug_records[__COUNTER__];
+DebugMetadata debug_metadata[__COUNTER__];
#endif
int
diff --git a/util.c b/util.c
@@ -36,6 +36,14 @@ is_valid_range(Range r)
return result;
}
+static b32
+point_in_rect(v2 point, Rect rect)
+{
+ v2 max = {.x = rect.pos.x + rect.size.w, .y = rect.pos.y + rect.size.h};
+ b32 result = BETWEEN(point.x, rect.pos.x, max.x) && BETWEEN(point.y, rect.pos.y, max.y);
+ return result;
+}
+
static Range
normalize_range(Range r)
{
@@ -93,12 +101,29 @@ alloc_(Arena *a, size len, size align, size count)
static Arena
make_arena(Arena *a, size size)
{
- Arena result;
- result.beg = alloc_(a, size, 64, 1);
- result.end = result.beg + size;
+ Arena result = {0};
+ result.beg = alloc_(a, size, 64, 1);
+ result.end = result.beg + size;
return result;
}
+static TempArena
+begin_temp_arena(Arena *a)
+{
+ TempArena result;
+ result.arena = a;
+ result.old_beg = a->beg;
+ return result;
+}
+
+static void
+end_temp_arena(TempArena ta)
+{
+ Arena *a = ta.arena;
+ ASSERT(a->beg >= ta.old_beg);
+ a->beg = ta.old_beg;
+}
+
/* NOTE: This performs wrapping of the ring buffer as needed; since a line could be in
* progress this must also adjust the start and end of the current line */
static void
diff --git a/util.h b/util.h
@@ -92,6 +92,7 @@ typedef union {
} Colour;
typedef struct { u8 *beg, *end; } Arena;
+typedef struct { Arena *arena; u8 *old_beg; } TempArena;
typedef struct { size len; u8 *data; } s8;
#define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s}
@@ -439,14 +440,14 @@ enum terminal_mode {
TM_CRLF = 1 << 3,
};
-typedef struct {
+typedef struct RenderCtx {
RenderPushBuffer *rpb;
GLCtx *gl;
FontAtlas *fa;
Arena a;
} RenderCtx;
-typedef struct {
+typedef struct Term {
GLCtx gl;
FontAtlas fa;
@@ -479,6 +480,8 @@ typedef struct {
char saved_title[1024];
Stream error_stream;
+
+ DebugState *debug_state;
} Term;
static f32 dt_for_frame;
diff --git a/vtgl.c b/vtgl.c
@@ -12,11 +12,6 @@ static u32 get_gpu_glyph_index(Arena, GLCtx *, FontAtlas *, u32, u32, enum face_
#include "terminal.c"
-#ifdef _DEBUG
-#include "debug.c"
-static void draw_debug_overlay(Term *, RenderCtx *);
-#endif
-
#define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba
static v4
@@ -613,6 +608,10 @@ key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods)
dump_lines_to_file(t);
return;
}
+ if (key == GLFW_KEY_F11 && act == GLFW_PRESS) {
+ t->debug_state->paused = !t->debug_state->paused;
+ return;
+ }
if (key == GLFW_KEY_F12 && act == GLFW_PRESS) {
t->gl.flags ^= DRAW_DEBUG_OVERLAY;
return;
@@ -836,9 +835,8 @@ do_terminal(Term *t, f32 dt)
if (os_child_data_available(t->child)) {
RingBuf *rb = &t->views[t->view_idx].log;
if (os_child_exited(t->child)) {
- /* TODO: is there a reason to not immediately exit? */
glfwSetWindowShouldClose(t->gl.window, GL_TRUE);
- return;
+ goto end;
}
t->unprocessed_bytes += os_read_from_child(t->child, t->views + t->view_idx,
t->unprocessed_bytes);
@@ -914,120 +912,48 @@ do_terminal(Term *t, f32 dt)
push_rect_textured(&rc, (Rect){.size = t->gl.window_size}, (v4){0}, 1);
flush_render_push_buffer(&rc);
- /* TODO: update debug info at the start of the frame so that this function can
- * be properly included */
- END_TIMED_BLOCK();
-
/* NOTE: this happens at the end so that ui stuff doesn't go through the post
* processing/effects shader */
glUseProgram(t->gl.programs[SHADER_RECTS]);
draw_debug_overlay(t, &rc);
flush_render_push_buffer(&rc);
+
+end:
+ END_TIMED_BLOCK();
}
#ifdef _DEBUG
-DebugRecord debug_records[__COUNTER__];
-
-static void
-draw_debug_overlay(Term *t, RenderCtx *rc)
-{
- static u64 cycs[ARRAY_COUNT(debug_records)];
- static u32 hits[ARRAY_COUNT(debug_records)];
-
- for (u32 i = 0; i < ARRAY_COUNT(debug_records); i++) {
- DebugRecord *dr = debug_records + i;
- if (dr->cycles == 0)
- continue;
- cycs[i] = dr->cycles;
- hits[i] = dr->hit_count;
- dr->hit_count = 0;
- dr->cycles = 0;
- }
-
- if (!(t->gl.flags & DRAW_DEBUG_OVERLAY))
- return;
-
- v2 ws = t->gl.window_size;
-
- static GlyphCacheStats glyph_stats;
- static Rect r;
- static f32 max_text_width;
- static f32 line_height;
- r.pos.x = ws.w - (max_text_width + 20);
- r.size.x = max_text_width + 20;
-
- v4 fg = normalize_colour((Colour){.rgba = 0x1e9e33ff});
- v4 bg = normalize_colour((Colour){.rgba = 0x090909bf});
-
- push_rect(rc, r, bg);
+#include "debug.c"
- u32 font_id = g_ui_debug_font_id;
- f32 line_pad = 5;
- v2 txt_pos = {.x = r.pos.x, .y = ws.h - 20};
+DebugMetadata debug_metadata[__COUNTER__];
- Stream txt = stream_alloc(&t->arena_for_frame, 1024);
- {
- stream_push_s8(&txt, s8("Render Time: "));
- stream_push_f64(&txt, dt_for_frame * 1e3, 100);
- stream_push_s8(&txt, s8(" ms/f"));
- v2 ts = measure_text(rc, font_id, stream_to_s8(&txt));
- txt_pos.y = (u32)(txt_pos.y - ts.h - line_pad);
- push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt));
+DEBUG_EXPORT DEBUG_END_FRAME_FN(debug_end_frame)
+{
+ g_debug_table.snapshot_index++;
+ if (g_debug_table.snapshot_index == DEBUG_SNAPSHOT_COUNT)
+ g_debug_table.snapshot_index = 0;
+ u64 event_array_event_index = __atomic_exchange_n(&g_debug_table.event_array_event_index,
+ (u64)g_debug_table.snapshot_index << 32,
+ __ATOMIC_ACQUIRE);
- if (ts.w > max_text_width) max_text_width = ts.w;
- if (ts.h > line_height) line_height = ts.h;
- }
+ u32 array_index = event_array_event_index >> 32;
+ u32 event_count = event_array_event_index & 0xFFFFFFFF;
- for (u32 i = 0; i < ARRAY_COUNT(debug_records); i++) {
- if (hits[i] == 0)
- continue;
+ g_debug_table.events_count[array_index] = event_count;
- DebugRecord *dr = debug_records + i;
-
- txt.widx = 0;
- stream_push_s8_left_padded(&txt, c_str_to_s8(dr->function_name), 29);
- stream_push_byte(&txt, ':');
- stream_push_u64_padded(&txt, cycs[i], 10);
- stream_push_s8(&txt, s8(" cycs "));
- stream_push_u64_padded(&txt, hits[i], 5);
- stream_push_s8_left_padded(&txt, hits[i] > 1? s8("hits") : s8("hit"), 5);
- stream_push_s8(&txt, s8(" "));
- stream_push_f64(&txt, (f32)cycs[i]/(f32)hits[i], 100);
- stream_push_s8_left_padded(&txt, s8("cycs/hit"), 80 - txt.widx);
- txt_pos.y = (u32)(txt_pos.y - line_height - line_pad);
- v2 ts = push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt));
-
- if (ts.w > max_text_width) max_text_width = ts.w;
- if (ts.h > line_height) line_height = ts.h;
+ if (!debug_state->initialized) {
+ debug_state->temp_memory = begin_temp_arena(&debug_state->memory);
+ debug_state->initialized = 1;
+ debug_state->bar_thickness = 14;
+ debug_state->frame_target_time = 4e-3;
+ restart_collation(debug_state, g_debug_table.snapshot_index);
}
- GlyphCacheStats new_glyph_stats = get_and_clear_glyph_cache_stats(&t->fa.glyph_cache);
- if (new_glyph_stats.hit_count != 0)
- glyph_stats = new_glyph_stats;
- {
- s8 header = s8("Glyph Cache Stats:");
- txt_pos.y = (u32)(txt_pos.y - line_height - line_pad);
- v2 ts = push_s8(rc, txt_pos, fg, font_id, header);
-
- /* TODO: This doesn't really work when we are redrawing on every frame */
- static s8 fmts[ARRAY_COUNT(glyph_stats.E)] = {
- s8(" Hits: "),
- s8(" Misses: "),
- s8(" Recycles: "),
- };
- for (u32 i = 0; i < ARRAY_COUNT(glyph_stats.E); i++) {
- txt.widx = 0;
- stream_push_s8(&txt, fmts[i]);
- stream_push_u64(&txt, glyph_stats.E[i]);
- txt_pos.y = (u32)(txt_pos.y - line_height - line_pad);
- ts = push_s8(rc, txt_pos, fg, font_id, stream_to_s8(&txt));
-
- if (ts.w > max_text_width) max_text_width = ts.w;
- if (ts.h > line_height) line_height = ts.h;
- }
+ if (!debug_state->paused) {
+ if (debug_state->record_count >= DEBUG_SNAPSHOT_COUNT)
+ restart_collation(debug_state, g_debug_table.snapshot_index);
+ coalesce_debug_events(debug_state, g_debug_table.snapshot_index, debug_metadata);
}
-
- r.pos.y = txt_pos.y - 2 * line_pad;
- r.size.h = ws.h - r.pos.y;
}
+
#endif