vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: 38e2d3ed9825721a3f0b5e61611b527c5b527503
Parent: 95864d5a8f74698bdd64409e7efbc83c61341bdb
Author: Randy Palamar
Date:   Mon,  4 Nov 2024 00:12:46 -0700

first pass at debug bar chart overlay

Diffstat:
Mdebug.c | 299+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdebug.h | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mmain.c | 57+++++++++++++++++++++++++++++++++++++--------------------
Mos_unix.c | 6+++---
Mtest.c | 14++------------
Mutil.c | 31++++++++++++++++++++++++++++---
Mutil.h | 7+++++--
Mvtgl.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