vtgl

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

Commit: 9ba075f585cbbe92375a653b80b8206af4b0890c
Parent: 5232c4700ed13bd370bba4300d754cdc00beadb1
Author: Randy Palamar
Date:   Sat, 26 Apr 2025 09:00:05 -0600

debug: ensure render thread is running valid code when hot reloading

Diffstat:
Mos_linux_common.c | 4+---
Mos_linux_x11.c | 33++++++++++++++++-----------------
Mutil.h | 7+++++--
Mvtgl.c | 158++-----------------------------------------------------------------------------
Mvtgl.h | 13+++++++++----
Mvtgl_static.c | 166++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 198 insertions(+), 183 deletions(-)

diff --git a/os_linux_common.c b/os_linux_common.c @@ -61,9 +61,7 @@ typedef __attribute__((aligned(16))) u8 statx_buffer[256]; #define TIOCSPTLCK 0x40045431 /* (un)lock pty */ #define TIOCGPTN 0x80045430 /* get pty number */ -#ifndef VERSION -#define VERSION "unknown" -#endif +#define OS_PATH_SEPERATOR "/" #define OS_MAP_READ PROT_READ #define OS_MAP_PRIVATE MAP_PRIVATE diff --git a/os_linux_x11.c b/os_linux_x11.c @@ -21,20 +21,27 @@ i32 XPending(void *display); #define do_debug(...) #include "vtgl.c" #else -#include <dlfcn.h> - -#define DEBUG_LIB_NAME "./vtgl.so" - #define DEBUG_LIB_FUNCTIONS \ - X(vtgl_active_selection) \ - X(vtgl_handle_keys) \ - X(vtgl_frame_step) \ - X(vtgl_render_thread_entry) + X(vtgl_active_selection) \ + X(vtgl_frame_step) \ + X(vtgl_handle_keys) \ + X(vtgl_render_frame) #define X(name) global name ## _fn *name; DEBUG_LIB_FUNCTIONS #undef X +#include "config.h" +#include "font.c" +#endif + +#include "vtgl_static.c" + +#ifdef _DEBUG +#include <dlfcn.h> + +#define DEBUG_LIB_NAME "./vtgl.so" + function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) { PlatformCtx *ctx = user_ctx; @@ -42,8 +49,7 @@ function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) if (ctx->input.executable_reloaded) return; - /* NOTE(rnp): spin until render thread finishes its work */ - //while (!ctx->render_stack->thread_asleep); + vtgl_wait_complete_work(&ctx->memory); ctx->input.executable_reloaded = 1; s8 nl = s8("\n"); @@ -68,15 +74,8 @@ function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) stream_reset(&ctx->error_stream, 0); } -#include "config.h" -#include "font.c" - #endif /* _DEBUG */ -#define OS_PATH_SEPERATOR "/" - -#include "vtgl_static.c" - function void glfw_error_callback(int code, const char *desc) { diff --git a/util.h b/util.h @@ -480,8 +480,7 @@ typedef struct { Arena arena; /* TODO(rnp): cleanup */ - TerminalInput *input; - TerminalMemory *memory; + TerminalInput *input; iptr window; iv2 monitor_size; @@ -492,6 +491,8 @@ typedef struct { i32 *sync; ShaderReloadContext shader_reload_contexts[SID_LAST]; + + b32 running; } RenderThreadContext; typedef enum { @@ -544,6 +545,8 @@ typedef struct { OS *os; } Term; +#define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);} + global f32 dt_for_frame; #endif /* _UTIL_H_ */ diff --git a/vtgl.c b/vtgl.c @@ -23,8 +23,6 @@ #include "font.c" #include "terminal.c" -#define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);} - #define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba #define VERTEX_SHADER_TEXT \ @@ -1003,63 +1001,14 @@ handle_interactions(Term *t, TerminalInput *input, OS *os) } } -function void -gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) -{ - (void)src; (void)type; (void)id; - - Stream *err = (Stream *)data; - stream_push_s8(err, s8("[GL Error ")); - switch (lvl) { - case GL_DEBUG_SEVERITY_HIGH: stream_push_s8(err, s8("HIGH]: ")); break; - case GL_DEBUG_SEVERITY_MEDIUM: stream_push_s8(err, s8("MEDIUM]: ")); break; - case GL_DEBUG_SEVERITY_LOW: stream_push_s8(err, s8("LOW]: ")); break; - case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break; - default: stream_push_s8(err, s8("INVALID]: ")); break; - } - stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg}); - stream_push_byte(err, '\n'); - os_write_err_msg(stream_to_s8(err)); - stream_reset(err, 0); -} - -function u32 -gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba) -{ - /* TODO: logging */ - u32 result; - glGenTextures(1, &result); - glBindTexture(GL_TEXTURE_2D, result); - glTexImage2D(GL_TEXTURE_2D, 0, format, size.w, size.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - return result; -} - -function b32 -try_wait_sync(i32 *sync, i32 timeout_ms, os_wait_on_value_fn *os_wait_on_value) -{ - b32 result = 0; - for (;;) { - i32 current = atomic_load(sync); - if (current && atomic_cas(sync, &current, 0)) { - result = 1; - break; - } - if (!timeout_ms || !os_wait_on_value(sync, 0, timeout_ms)) - break; - } - return result; -} - -function void -vtgl_render_frame(OS *os, RenderThreadContext *ctx, TerminalMemory *memory, - TerminalInput *input, Arena arena) +DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame) { BEGIN_TIMED_BLOCK(); Term *t = memory->memory; + RenderThreadContext *ctx = &t->render_thread; + Arena arena = ctx->arena; dt_for_frame = input->dt; WorkQueueWork *work = work_queue_pop(&ctx->work_queue); @@ -1152,107 +1101,6 @@ vtgl_render_frame(OS *os, RenderThreadContext *ctx, TerminalMemory *memory, END_TIMED_BLOCK(); } -function void -vtgl_render_thread_initialize(OS *os, RenderThreadContext *ctx) -{ - os->gl_make_context_current(ctx->window); - - ctx->error_stream = stream_alloc(&ctx->arena, KB(1)); - - Arena a = ctx->arena; - - glDebugMessageCallback(gl_debug_logger, &ctx->error_stream); - glEnable(GL_DEBUG_OUTPUT); - /* NOTE: shut up useless shader compilation statistics */ - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, - GL_DEBUG_SEVERITY_NOTIFICATION, - 0, 0, GL_FALSE); - - glGenVertexArrays(1, &ctx->gl.vao); - glBindVertexArray(ctx->gl.vao); - - glGenBuffers(ARRAY_COUNT(ctx->gl.vbos), ctx->gl.vbos); - - RenderPushBuffer *rpb = 0; - /* NOTE: vertex position buffer */ - glBindBuffer(GL_ARRAY_BUFFER, ctx->gl.vbos[0]); - glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->positions), 0, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0); - - /* NOTE: vertex texture coordinate buffer */ - glBindBuffer(GL_ARRAY_BUFFER, ctx->gl.vbos[1]); - glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->texture_coordinates), 0, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0); - - /* NOTE: vertex colour buffer */ - glBindBuffer(GL_ARRAY_BUFFER, ctx->gl.vbos[2]); - glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->colours), 0, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 4, GL_FLOAT, 0, 0, 0); - - /* NOTE: fill in element index buffer */ - i32 *element_indices = alloc(&a, i32, 6 * ARRAY_COUNT(rpb->positions)); - for (i32 i = 0, j = 0; i < 6 * ARRAY_COUNT(rpb->positions); i += 6, j++) { - element_indices[i + 0] = 4 * j; - element_indices[i + 1] = 4 * j + 1; - element_indices[i + 2] = 4 * j + 2; - element_indices[i + 3] = 4 * j; - element_indices[i + 4] = 4 * j + 2; - element_indices[i + 5] = 4 * j + 3; - } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ctx->gl.vbos[4]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * ARRAY_COUNT(rpb->positions) * sizeof(i32), - element_indices, GL_STATIC_DRAW); - - ctx->gl.glyph_bitmap_tex = gen_2D_texture(ctx->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 */ - u32 white = 0xFFFFFFFF; - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &white); - LABEL_GL_OBJECT(GL_TEXTURE, ctx->gl.glyph_bitmap_tex, s8("Glyph_Bitmap")); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); - - /* NOTE: Generate an intermediate framebuffer for rendering to. This - * allows for additional post processing via a second shader stage */ - glGenFramebuffers(1, &ctx->gl.fb); - glBindFramebuffer(GL_FRAMEBUFFER, ctx->gl.fb); - - ctx->gl.fb_tex_unit = 1; - glActiveTexture(GL_TEXTURE0 + ctx->gl.fb_tex_unit); - iv2 ws = ctx->monitor_size; - ctx->gl.fb_tex = gen_2D_texture(ws, GL_RGBA, GL_NEAREST, 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ctx->gl.fb_tex, 0); - LABEL_GL_OBJECT(GL_TEXTURE, ctx->gl.fb_tex, s8("Framebuffer_Texture")); - - glGenBuffers(1, &ctx->gl.render_shader_ubo); - glBindBuffer(GL_UNIFORM_BUFFER, ctx->gl.render_shader_ubo); - glBufferData(GL_UNIFORM_BUFFER, sizeof(ShaderParameters), 0, GL_DYNAMIC_DRAW); - glBindBufferBase(GL_UNIFORM_BUFFER, 0, ctx->gl.render_shader_ubo); - LABEL_GL_OBJECT(GL_BUFFER, ctx->gl.render_shader_ubo, s8("ShaderParameters")); - - glActiveTexture(GL_TEXTURE0); -} - -DEBUG_EXPORT OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry) -{ - RenderThreadContext *ctx = (RenderThreadContext *)context; - - vtgl_render_thread_initialize(os, ctx); - for (;;) { - try_wait_sync(sync, -1, os->wait_on_value); - vtgl_render_frame(os, ctx, ctx->memory, ctx->input, ctx->arena); - os->gl_swap_buffers(ctx->window); - } - return 0; -} - DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection) { Term *t = memory->memory; diff --git a/vtgl.h b/vtgl.h @@ -2,6 +2,10 @@ #ifndef _VTGL_H_ #define _VTGL_H_ +#ifndef VERSION +#define VERSION "unknown" +#endif + #include <stddef.h> #include <stdint.h> @@ -276,7 +280,7 @@ typedef struct { u8 ended_down; } ButtonState; -typedef struct TerminalInput { +typedef struct { ButtonState keys[INPUT_KEY_COUNT]; iv2 window_size; @@ -299,7 +303,7 @@ typedef struct TerminalInput { f32 dt; } TerminalInput; -typedef struct TerminalMemory { +typedef struct { u64 memory_size; void *memory; @@ -313,6 +317,9 @@ typedef struct TerminalMemory { #define VTGL_FRAME_STEP_FN(name) void name(OS *os, TerminalMemory *memory, TerminalInput *input) typedef VTGL_FRAME_STEP_FN(vtgl_frame_step_fn); +#define VTGL_RENDER_FRAME_FN(name) void name(OS *os, TerminalMemory *memory, TerminalInput *input) +typedef VTGL_RENDER_FRAME_FN(vtgl_render_frame_fn); + #define VTGL_ACTIVE_SELECTION_FN(name) Range name(TerminalMemory *memory, Stream *out) typedef VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection_fn); @@ -321,8 +328,6 @@ typedef VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection_fn); InputModifier modifiers) typedef VTGL_HANDLE_KEYS_FN(vtgl_handle_keys_fn); -typedef OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry_fn); - #include "util.h" #include "util.c" diff --git a/vtgl_static.c b/vtgl_static.c @@ -3,6 +3,16 @@ #define static_path_join(a, b) (a OS_PATH_SEPERATOR b) +#ifdef _DEBUG +/* NOTE(rnp): spins until render thread finishes its work */ +function void +vtgl_wait_complete_work(TerminalMemory *memory) +{ + RenderThreadContext *ctx = &((Term *)memory->memory)->render_thread; + while (ctx->running); +} +#endif + function OS_FILE_WATCH_CALLBACK_FN(queue_shader_reload) { ShaderReloadContext *src = user_ctx; @@ -14,6 +24,159 @@ function OS_FILE_WATCH_CALLBACK_FN(queue_shader_reload) } } +function void +gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) +{ + (void)src; (void)type; (void)id; + + Stream *err = (Stream *)data; + stream_push_s8(err, s8("[GL Error ")); + switch (lvl) { + case GL_DEBUG_SEVERITY_HIGH: stream_push_s8(err, s8("HIGH]: ")); break; + case GL_DEBUG_SEVERITY_MEDIUM: stream_push_s8(err, s8("MEDIUM]: ")); break; + case GL_DEBUG_SEVERITY_LOW: stream_push_s8(err, s8("LOW]: ")); break; + case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break; + default: stream_push_s8(err, s8("INVALID]: ")); break; + } + stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg}); + stream_push_byte(err, '\n'); + os_write_err_msg(stream_to_s8(err)); + stream_reset(err, 0); +} + +function u32 +gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba) +{ + /* TODO: logging */ + u32 result; + glGenTextures(1, &result); + glBindTexture(GL_TEXTURE_2D, result); + glTexImage2D(GL_TEXTURE_2D, 0, format, size.w, size.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + return result; +} + +function b32 +try_wait_sync(i32 *sync, i32 timeout_ms, os_wait_on_value_fn *os_wait_on_value) +{ + b32 result = 0; + for (;;) { + i32 current = atomic_load(sync); + if (current && atomic_cas(sync, &current, 0)) { + result = 1; + break; + } + if (!timeout_ms || !os_wait_on_value(sync, 0, timeout_ms)) + break; + } + return result; +} + + +function void +vtgl_render_thread_initialize(OS *os, RenderThreadContext *ctx) +{ + os->gl_make_context_current(ctx->window); + + ctx->error_stream = stream_alloc(&ctx->arena, KB(1)); + + Arena a = ctx->arena; + + glDebugMessageCallback(gl_debug_logger, &ctx->error_stream); + glEnable(GL_DEBUG_OUTPUT); + /* NOTE: shut up useless shader compilation statistics */ + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, + GL_DEBUG_SEVERITY_NOTIFICATION, + 0, 0, GL_FALSE); + + glGenVertexArrays(1, &ctx->gl.vao); + glBindVertexArray(ctx->gl.vao); + + glGenBuffers(ARRAY_COUNT(ctx->gl.vbos), ctx->gl.vbos); + + RenderPushBuffer *rpb = 0; + /* NOTE: vertex position buffer */ + glBindBuffer(GL_ARRAY_BUFFER, ctx->gl.vbos[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->positions), 0, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0); + + /* NOTE: vertex texture coordinate buffer */ + glBindBuffer(GL_ARRAY_BUFFER, ctx->gl.vbos[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->texture_coordinates), 0, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0); + + /* NOTE: vertex colour buffer */ + glBindBuffer(GL_ARRAY_BUFFER, ctx->gl.vbos[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->colours), 0, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 4, GL_FLOAT, 0, 0, 0); + + /* NOTE: fill in element index buffer */ + i32 *element_indices = alloc(&a, i32, 6 * ARRAY_COUNT(rpb->positions)); + for (i32 i = 0, j = 0; i < 6 * ARRAY_COUNT(rpb->positions); i += 6, j++) { + element_indices[i + 0] = 4 * j; + element_indices[i + 1] = 4 * j + 1; + element_indices[i + 2] = 4 * j + 2; + element_indices[i + 3] = 4 * j; + element_indices[i + 4] = 4 * j + 2; + element_indices[i + 5] = 4 * j + 3; + } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ctx->gl.vbos[4]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * ARRAY_COUNT(rpb->positions) * sizeof(i32), + element_indices, GL_STATIC_DRAW); + + ctx->gl.glyph_bitmap_tex = gen_2D_texture(ctx->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 */ + u32 white = 0xFFFFFFFF; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &white); + LABEL_GL_OBJECT(GL_TEXTURE, ctx->gl.glyph_bitmap_tex, s8("Glyph_Bitmap")); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + /* NOTE: Generate an intermediate framebuffer for rendering to. This + * allows for additional post processing via a second shader stage */ + glGenFramebuffers(1, &ctx->gl.fb); + glBindFramebuffer(GL_FRAMEBUFFER, ctx->gl.fb); + + ctx->gl.fb_tex_unit = 1; + glActiveTexture(GL_TEXTURE0 + ctx->gl.fb_tex_unit); + iv2 ws = ctx->monitor_size; + ctx->gl.fb_tex = gen_2D_texture(ws, GL_RGBA, GL_NEAREST, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ctx->gl.fb_tex, 0); + LABEL_GL_OBJECT(GL_TEXTURE, ctx->gl.fb_tex, s8("Framebuffer_Texture")); + + glGenBuffers(1, &ctx->gl.render_shader_ubo); + glBindBuffer(GL_UNIFORM_BUFFER, ctx->gl.render_shader_ubo); + glBufferData(GL_UNIFORM_BUFFER, sizeof(ShaderParameters), 0, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, ctx->gl.render_shader_ubo); + LABEL_GL_OBJECT(GL_BUFFER, ctx->gl.render_shader_ubo, s8("ShaderParameters")); + + glActiveTexture(GL_TEXTURE0); +} + +function OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry) +{ + TerminalMemory *memory = (TerminalMemory *)context; + RenderThreadContext *ctx = &((Term *)memory->memory)->render_thread; + vtgl_render_thread_initialize(os, ctx); + for (;;) { + ctx->running = 0; + try_wait_sync(sync, -1, os->wait_on_value); + ctx->running = 1; + vtgl_render_frame(os, memory, ctx->input); + os->gl_swap_buffers(ctx->window); + } + return 0; +} + function iv2 vtgl_initialize(OS *os, TerminalMemory *memory, TerminalInput *input, void *window, iptr child, iv2 requested_cells, iv2 monitor_size) @@ -40,7 +203,6 @@ vtgl_initialize(OS *os, TerminalMemory *memory, TerminalInput *input, void *wind RenderThreadContext *rtc = &t->render_thread; rtc->arena = sub_arena(&a, MB(8)); rtc->input = input; - rtc->memory = memory; rtc->window = (iptr)window; rtc->monitor_size = monitor_size; rtc->gl.glyph_bitmap_dim = monitor_size; @@ -73,7 +235,7 @@ vtgl_initialize(OS *os, TerminalMemory *memory, TerminalInput *input, void *wind } os->gl_make_context_current(0); - rtc->sync = os->spawn_thread(os, vtgl_render_thread_entry, "[render]", (iptr)rtc); + rtc->sync = os->spawn_thread(os, vtgl_render_thread_entry, "[render]", (iptr)memory); t->size = (iv2){.x = 1, .y = 1}; t->state |= TS_NEEDS_RESIZE;