vtgl

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

Commit: ba72915bf16cf84ddee5f940c76783e64318d191
Parent: 953277e43538e5d3e19699ee27917982e90edd36
Author: Randy Palamar
Date:   Tue, 22 Apr 2025 20:35:48 -0600

core: refactor shader reloading and terminal initialization

Terminal initialization only happens once so it doesn't need to be
in the debug shared library.

shader reloading needs a stable function pointer for the file
watch callback and thus needed to be partially moved out of the
the debug shared library.

Diffstat:
Mconfig.def.h | 2+-
Mfont.c | 8+++++++-
Mos_linux_x11.c | 26+++++++++++++++++---------
Mterminal.c | 7-------
Mutil.c | 19+++++++++++++++++--
Mutil.h | 39+++++++++++++++++++++++++++++++--------
Mvtgl.c | 275++++++++++++++++++++++++-------------------------------------------------------
Mvtgl.h | 8++------
Avtgl_static.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 247 insertions(+), 225 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -33,7 +33,7 @@ global u8 g_tabstop = 8; /* NOTE: number of blinks per second */ global f32 g_blink_speed = 1.0f; -global s8 g_shader_path_prefix = s8(""); +#define SHADER_PATH_PREFIX "" global Colour base16_colours[16] = { [0] = { .rgba = 0x000000ff }, /* black */ diff --git a/font.c b/font.c @@ -71,7 +71,6 @@ cached_glyph_to_uv(FontAtlas *fa, CachedGlyph *cg, v2 *start, v2 *end, v2 scale) *end = (v2){.x = max_x * scale.x, .y = min_y * scale.y}; } - function void recycle_cache(GlyphCache *gc) { @@ -396,6 +395,13 @@ font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim) gc->glyphs[i].next_with_same_hash = i + 1; } +function v2 +fa_cell_size(FontAtlas *fa) +{ + v2 result = {.w = fa->info.w, .h = fa->info.h}; + return result; +} + function void shift_font_sizes(FontAtlas *fa, i32 size_delta) { diff --git a/os_linux_x11.c b/os_linux_x11.c @@ -25,14 +25,14 @@ i32 XPending(void *display); #define DEBUG_LIB_NAME "./vtgl.so" -#define LIB_FNS \ - X(vtgl_active_selection) \ - X(vtgl_initialize) \ - X(vtgl_handle_keys) \ - X(vtgl_frame_step) - -#define X(name) static name ## _fn *name; -LIB_FNS +#define DEBUG_LIB_FUNCTIONS \ + X(vtgl_active_selection) \ + X(vtgl_handle_keys) \ + X(vtgl_frame_step) \ + X(vtgl_render_thread_entry) + +#define X(name) global name ## _fn *name; +DEBUG_LIB_FUNCTIONS #undef X function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) @@ -59,7 +59,7 @@ function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) name = dlsym(ctx->library_handle, #name); \ if (!name) stream_push_s8s(&ctx->error_stream, 3, (s8 []){s8("dlsym: "), \ c_str_to_s8(dlerror()), nl}); - LIB_FNS + DEBUG_LIB_FUNCTIONS #undef X stream_push_s8(&ctx->error_stream, s8("Reloaded Main Program\n")); @@ -67,8 +67,16 @@ function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) os_write_err_msg(stream_to_s8(&ctx->error_stream)); 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/terminal.c b/terminal.c @@ -157,13 +157,6 @@ feed_line(LineBuffer *lb, u8 *position, CellStyle cursor_state) } function void -selection_clear(Selection *s) -{ - s->range.end = INVALID_RANGE_END; - s->state = SS_NONE; -} - -function void selection_scroll(Term *t, i32 origin, i32 n) { Selection *s = &t->selection; diff --git a/util.c b/util.c @@ -178,8 +178,8 @@ work_queue_insert(WorkQueue *q, WorkQueueWorkKind kind, void *ctx) b32 result = 0; /* NOTE(rnp): if we ever fill this up we need to resize the queue */ if (work) { - work->kind = kind; - work->ctx = ctx; + work->kind = kind; + work->generic = ctx; work_queue_push_commit(q); } return result; @@ -396,6 +396,14 @@ stream_reset(Stream *s, iz index) if (!s->errors) s->count = index; } +function void +stream_commit(Stream *s, iz count) +{ + s->errors |= !BETWEEN(s->count + count, 0, s->capacity); + if (!s->errors) + s->count += count; +} + function Stream stream_alloc(Arena *a, i32 capacity) { @@ -534,6 +542,13 @@ stream_push_f64(Stream *s, f64 f, i64 prec) } } +function void +selection_clear(Selection *s) +{ + s->range.end = INVALID_RANGE_END; + s->state = SS_NONE; +} + function SelectionIterator selection_iterator(Range s, Row *rows, u32 term_width) { diff --git a/util.h b/util.h @@ -204,12 +204,18 @@ enum gl_flags { DRAW_DEBUG_OVERLAY = 1 << 30, }; -enum shader_stages { - SHADER_RENDER, - SHADER_RECTS, - SHADER_POST, - SHADER_COUNT -}; +/* X(enumerant, file_name) */ +#define FRAGMENT_SHADERS \ + X(RENDER, "frag_render") \ + X(RECTS, "frag_for_rects") \ + X(POST, "frag_post") + +typedef enum { + #define X(en, fn) SID_ ##en, + FRAGMENT_SHADERS + #undef X + SID_LAST +} ShaderID; typedef struct { iv2 cell_size; @@ -232,6 +238,9 @@ typedef struct { u32 _pad[1]; } ShaderParameters; +static_assert((sizeof(ShaderParameters) & 15) == 0, + "sizeof(ShaderParameters) must be a multiple of 16"); + typedef struct { iv2 window_size; @@ -239,7 +248,7 @@ typedef struct { u32 fb, fb_tex, fb_tex_unit; - u32 programs[SHADER_COUNT]; + ShaderID programs[SID_LAST]; #define X(name) i32 name; struct { GL_POST_UNIFORMS } post; #undef X @@ -426,6 +435,8 @@ enum charsets { CS_GRAPHIC0, }; +typedef struct ShaderReloadContext ShaderReloadContext; + typedef struct RenderCtx { RenderPushBuffer *rpb; GLCtx *gl; @@ -439,7 +450,10 @@ typedef enum { } WorkQueueWorkKind; typedef struct { - void *ctx; + union { + void *generic; + ShaderReloadContext *shader_reload_context; + }; WorkQueueWorkKind kind; } WorkQueueWork; @@ -451,6 +465,13 @@ typedef struct { WorkQueueWork items[1 << 6]; } WorkQueue; +struct ShaderReloadContext { + WorkQueue *work_queue; + s8 path; + s8 label; + ShaderID shader; +}; + typedef struct { GLCtx gl; FontAtlas fa; @@ -471,6 +492,8 @@ typedef struct { Stream error_stream; i32 *sync; + + ShaderReloadContext shader_reload_contexts[SID_LAST]; } RenderThreadContext; typedef enum { diff --git a/vtgl.c b/vtgl.c @@ -7,7 +7,6 @@ * [ ]: refactor: stream_ensure_terminator * [ ]: refactor: push_s8 (into arena); rename push_s8 to draw_s8 * [ ]: refactor: resize renderer work item - * [ ]: refactor: reload shader shouldn't need so much context * [ ]: refactor: remove queued_render (just use a work item) * [ ]: refactor: why is there interaction code in both threads? */ @@ -65,127 +64,105 @@ set_projection_matrix(GLCtx *gl, u32 stage) } function u32 -compile_shader(Arena a, u32 type, s8 shader) +compile_shader(Arena a, u32 type, s8 shader, s8 name) { - u32 sid = glCreateShader(type); - - glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len); - glCompileShader(sid); + u32 result = glCreateShader(type); + glShaderSource(result, 1, (const char **)&shader.data, (int *)&shader.len); + glCompileShader(result); i32 res = 0; - glGetShaderiv(sid, GL_COMPILE_STATUS, &res); + glGetShaderiv(result, GL_COMPILE_STATUS, &res); if (res != GL_TRUE) { - i32 len; - glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); - s8 err = s8alloc(&a, len); - glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data); - os_write_err_msg(s8("compile_shader: ")); - os_write_err_msg(err); - glDeleteShader(sid); - return 0; + Stream s = arena_stream(a); + stream_push_s8(&s, name); + stream_push_s8(&s, s8(": failed to compile\n")); + + i32 len = 0, out_len = 0; + glGetShaderiv(result, GL_INFO_LOG_LENGTH, &len); + glGetShaderInfoLog(result, len, &out_len, (c8 *)(s.data + s.count)); + stream_commit(&s, out_len); + os_write_err_msg(stream_to_s8(&s)); + glDeleteShader(result); + result = 0; } - return sid; + return result; } function u32 -program_from_shader_text(s8 vertex, s8 fragment, Arena a) +link_shader_program(Arena a, u32 *shader_ids, u32 shader_id_count) { - u32 pid = glCreateProgram(); - - u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex); - if (vid == 0) { - glDeleteProgram(pid); - return 0; + u32 result = glCreateProgram(); + for (u32 i = 0; i < shader_id_count; i++) + glAttachShader(result, shader_ids[i]); + glLinkProgram(result); + + i32 success = 0; + glGetProgramiv(result, GL_LINK_STATUS, &success); + if (success == GL_FALSE) { + Stream s = arena_stream(a); + stream_push_s8(&s, s8("shader link error: ")); + + i32 len = 0; + glGetProgramInfoLog(result, s.capacity - s.count, &len, (c8 *)(s.data + s.count)); + stream_commit(&s, len); + os_write_err_msg(stream_to_s8(&s)); + glDeleteProgram(result); + result = 0; } - u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment); - if (fid == 0) { - glDeleteShader(vid); - glDeleteProgram(pid); - return 0; - } + return result; +} - glAttachShader(pid, vid); - glAttachShader(pid, fid); - glLinkProgram(pid); - glValidateProgram(pid); - glUseProgram(pid); - glDeleteShader(vid); - glDeleteShader(fid); +function u32 +load_shader(Arena arena, s8 vertex_text, s8 fragment_text, s8 info, s8 label) +{ + u32 result = 0; + u32 vs_id = compile_shader(arena, GL_VERTEX_SHADER, vertex_text, s8("Vertex Shader")); + u32 fs_id = compile_shader(arena, GL_FRAGMENT_SHADER, fragment_text, info); + + if (vs_id && fs_id) result = link_shader_program(arena, (u32 []){vs_id, fs_id}, 2); + glDeleteShader(vs_id); + glDeleteShader(fs_id); + + if (result) { + Stream s = arena_stream(arena); + stream_push_s8(&s, s8("loaded: ")); + stream_push_s8(&s, info); + stream_push_byte(&s, '\n'); + os_write_err_msg(stream_to_s8(&s)); + LABEL_GL_OBJECT(GL_PROGRAM, result, label); + } - return pid; + return result; } -typedef struct { - Term *t; - u8 *path; - s8 info; - u32 stage; -} queue_shader_reload_ctx; - function void -update_uniforms(GLCtx *gl, enum shader_stages stage) +update_uniforms(GLCtx *gl, ShaderID stage) { switch (stage) { - case SHADER_RENDER: - case SHADER_RECTS: - break; - case SHADER_POST: + case SID_POST: { #define X(name) gl->post.name = glGetUniformLocation(gl->programs[stage], "u_" #name); GL_POST_UNIFORMS #undef X - break; - case SHADER_COUNT: ASSERT(0); break; + } break; + default: break; } } function void -reload_shader(GLCtx *gl, OS *os, u8 *path, u32 stage, s8 info, Arena a) +reload_shader(OS *os, GLCtx *gl, ShaderReloadContext *src, Arena a) { - s8 fs_text = os->read_file(path, &a); + s8 fs_text = os->read_file(src->path.data, &a); if (fs_text.len) { - u32 program = program_from_shader_text(s8(VERTEX_SHADER_TEXT), fs_text, a); - if (program) { - glDeleteProgram(gl->programs[stage]); - gl->programs[stage] = program; - update_uniforms(gl, stage); - set_projection_matrix(gl, stage); + u32 new_program = load_shader(a, s8(VERTEX_SHADER_TEXT), fs_text, src->path, src->label); + if (new_program) { + glDeleteProgram(gl->programs[src->shader]); + gl->programs[src->shader] = new_program; + update_uniforms(gl, src->shader); + set_projection_matrix(gl, src->shader); } } - if (info.len) os_write_err_msg(info); -} - -global s8 fs_name[SHADER_COUNT] = { - [SHADER_RENDER] = s8("frag_render.glsl"), - [SHADER_RECTS] = s8("frag_for_rects.glsl"), - [SHADER_POST] = s8("frag_post.glsl"), -}; - -function void -reload_all_shaders(GLCtx *gl, OS *os, Arena a) -{ - Stream fs_path = stream_alloc(&a, KB(4)); - stream_push_s8(&fs_path, g_shader_path_prefix); - if (fs_path.count && fs_path.data[fs_path.count - 1] != os->path_separator) - stream_push_byte(&fs_path, os->path_separator); - - i32 sidx = fs_path.count; - for (u32 i = 0; i < SHADER_COUNT; i++) { - stream_push_s8(&fs_path, fs_name[i]); - stream_push_byte(&fs_path, 0); - reload_shader(gl, os, fs_path.data, i, (s8){0}, a); - stream_reset(&fs_path, sidx); - } - - os_write_err_msg(s8("Reloaded Shaders\n")); -} - -function OS_FILE_WATCH_CALLBACK_FN(queue_shader_reload) -{ - queue_shader_reload_ctx *ctx = user_ctx; - ctx->path = path; - work_queue_insert(&ctx->t->render_thread.work_queue, WQK_RELOAD_SHADER, ctx); } function v4 @@ -204,16 +181,9 @@ clear_colour(void) } function v2 -get_cell_size(FontAtlas *fa) -{ - v2 result = {.w = fa->info.w, .h = fa->info.h}; - return result; -} - -function v2 get_occupied_size(Term *t) { - v2 cs = get_cell_size(&t->render_thread.fa); + v2 cs = fa_cell_size(&t->render_thread.fa); v2 result = {.x = t->size.w * cs.w, .y = t->size.h * cs.h}; return result; } @@ -236,7 +206,7 @@ resize_terminal(Term *t, OS *os, iv2 window_size) ws.h -= 2 * g_term_margin.h; iv2 old_size = t->size; - v2 cs = get_cell_size(&t->render_thread.fa); + v2 cs = fa_cell_size(&t->render_thread.fa); t->size.w = (i32)(ws.w / cs.w); t->size.h = (i32)(ws.h / cs.h); @@ -289,7 +259,7 @@ resize(Term *t, OS *os, iv2 window_size) FontAtlas *fa = &t->render_thread.fa; - v2 cs = get_cell_size(fa); + v2 cs = fa_cell_size(fa); ShaderParameters *sp = &gl->shader_parameters; sp->cell_size = (iv2){.w = cs.w, .h = cs.h}; sp->strike_min = (fa->info.baseline + 0.40 * fa->info.h); @@ -303,7 +273,7 @@ resize(Term *t, OS *os, iv2 window_size) sp->term_size_in_pixels = gl->window_size; sp->term_size_in_cells = t->size; - for (u32 i = 0; i < SHADER_COUNT; i++) + for (u32 i = 0; i < SID_LAST; i++) set_projection_matrix(gl, i); gl->flags &= ~RESIZE_RENDERER; @@ -337,7 +307,7 @@ get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, u32 font_i CachedGlyph *cg = *out; cg->uploaded_to_gpu = 1; - v2 cell_size = get_cell_size(fa); + v2 cell_size = fa_cell_size(fa); iv2 glyph_position = get_gpu_texture_position(cell_size, cg->gpu_tile_index); ASSERT(glyph_position.x + cell_size.w * cg->tile_count < gl->glyph_bitmap_dim.x); ASSERT(glyph_position.y + cell_size.h < gl->glyph_bitmap_dim.y); @@ -596,7 +566,7 @@ render_cursor(Term *t, b32 focused, Arena a) function iv2 mouse_to_cell_space(Term *t, v2 mouse) { - v2 cell_size = get_cell_size(&t->render_thread.fa); + v2 cell_size = fa_cell_size(&t->render_thread.fa); v2 top_left = get_terminal_top_left(t); iv2 result; @@ -1096,11 +1066,13 @@ vtgl_render_frame(RenderThreadContext *ctx, TerminalMemory *memory, TerminalInpu while (work) { switch (work->kind) { case WQK_RELOAD_SHADER: { - queue_shader_reload_ctx *qsr = work->ctx; - reload_shader(&ctx->gl, ctx->os, qsr->path, qsr->stage, qsr->info, arena); + reload_shader(ctx->os, &ctx->gl, work->shader_reload_context, arena); } break; case WQK_RELOAD_ALL_SHADERS: { - reload_all_shaders(&ctx->gl, ctx->os, arena); + for (i32 i = 0; i < SID_LAST; i++) { + reload_shader(ctx->os, &ctx->gl, ctx->shader_reload_contexts + i, + arena); + } } break; default: INVALID_CODE_PATH; } @@ -1120,7 +1092,7 @@ vtgl_render_frame(RenderThreadContext *ctx, TerminalMemory *memory, TerminalInpu RenderCtx rc = make_render_ctx(&arena, &ctx->gl, &ctx->fa); - glUseProgram(ctx->gl.programs[SHADER_RENDER]); + glUseProgram(ctx->gl.programs[SID_RENDER]); glBindFramebuffer(GL_FRAMEBUFFER, ctx->gl.fb); clear_colour(); @@ -1162,7 +1134,7 @@ vtgl_render_frame(RenderThreadContext *ctx, TerminalMemory *memory, TerminalInpu glBindFramebuffer(GL_FRAMEBUFFER, 0); clear_colour(); - glUseProgram(ctx->gl.programs[SHADER_POST]); + glUseProgram(ctx->gl.programs[SID_POST]); glUniform1i(ctx->gl.post.texslot, ctx->gl.fb_tex_unit); glUniform1f(ctx->gl.post.param, param); @@ -1172,7 +1144,7 @@ vtgl_render_frame(RenderThreadContext *ctx, TerminalMemory *memory, TerminalInpu /* NOTE: this happens at the end so that ui stuff doesn't go through the post * processing/effects shader */ - glUseProgram(ctx->gl.programs[SHADER_RECTS]); + glUseProgram(ctx->gl.programs[SID_RECTS]); BEGIN_NAMED_BLOCK(debug_overlay); draw_debug_overlay(memory, input, &rc); @@ -1267,11 +1239,9 @@ vtgl_render_thread_initialize(RenderThreadContext *ctx) LABEL_GL_OBJECT(GL_BUFFER, ctx->gl.render_shader_ubo, s8("ShaderParameters")); glActiveTexture(GL_TEXTURE0); - - reload_all_shaders(&ctx->gl, ctx->os, a); } -function OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry) +DEBUG_EXPORT OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry) { RenderThreadContext *ctx = (RenderThreadContext *)context; @@ -1284,83 +1254,6 @@ function OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry) return 0; } -DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) -{ - Term *t = (Term *)memory->memory; - Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size}; - - t->os = &memory->os; - - t->cursor.state = CURSOR_NORMAL; - cursor_reset(t); - - t->views[0].log = memory->os.allocate_ring_buffer(BACKLOG_SIZE); - t->views[0].lines = line_buffer_alloc(&a, t->views[0].log.data, t->cursor.style, BACKLOG_LINES); - t->views[1].log = memory->os.allocate_ring_buffer(ALT_BACKLOG_SIZE); - t->views[1].lines = line_buffer_alloc(&a, t->views[1].log.data, t->cursor.style, ALT_BACKLOG_LINES); - - t->views[0].fb.backing_store = memory_block_from_arena(&a, MB(2)); - t->views[1].fb.backing_store = memory_block_from_arena(&a, MB(2)); - - RenderThreadContext *rtc = &t->render_thread; - rtc->arena = sub_arena(&a, MB(8)); - rtc->os = &memory->os; - rtc->input = input; - rtc->memory = memory; - rtc->window = (iptr)window; - rtc->monitor_size = monitor_size; - rtc->gl.glyph_bitmap_dim = monitor_size; - - selection_clear(&t->selection); - - init_fonts(&rtc->fa, &a, rtc->gl.glyph_bitmap_dim); - v2 cs = get_cell_size(&rtc->fa); - iv2 requested_size = { - .x = cs.x * requested_cells.x + 2 * g_term_margin.x, - .y = cs.y * requested_cells.y + 2 * g_term_margin.y, - }; - if (requested_cells.x < 0 || requested_cells.y < 0) - requested_size = (iv2){.x = -1, .y = -1}; - - memory->os.gl_make_context_current(0); - - rtc->sync = memory->os.spawn_thread(vtgl_render_thread_entry, "[render]", (iptr)rtc); - t->size = (iv2){.x = 1, .y = 1}; - initialize_framebuffer(&t->views[0].fb, t->size); - initialize_framebuffer(&t->views[1].fb, t->size); - - queue_shader_reload_ctx *reload_ctxs = alloc(&a, typeof(*reload_ctxs), SHADER_COUNT); - - s8 shader_infos[SHADER_COUNT] = { - [SHADER_POST] = s8("Post Processing Shader Reloaded!\n"), - [SHADER_RECTS] = s8("UI Shader Reloaded!\n"), - [SHADER_RENDER] = s8("Render Shader Reloaded!\n"), - }; - - for (u32 i = 0; i < SHADER_COUNT; i++) { - Stream path = arena_stream(a); - stream_push_s8(&path, g_shader_path_prefix); - if (path.count && path.data[path.count - 1] != memory->os.path_separator) - stream_push_byte(&path, memory->os.path_separator); - - queue_shader_reload_ctx *src = reload_ctxs + i; - src->info = shader_infos[i]; - src->stage = i; - src->t = t; - stream_push_s8(&path, fs_name[i]); - stream_push_byte(&path, 0); - memory->os.add_file_watch(path.data, queue_shader_reload, src); - a.beg = path.data + path.count; - } - - t->error_stream = stream_alloc(&a, KB(256)); - t->saved_title = stream_alloc(&a, KB(16)); - t->arena_for_frame = a; - t->child = child; - - return requested_size; -} - DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection) { Term *t = memory->memory; diff --git a/vtgl.h b/vtgl.h @@ -81,7 +81,7 @@ typedef size_t uz; #define DEBUG_DECL(a) a #else #define ASSERT(c) do { (void)(c); } while(0) -#define DEBUG_EXPORT static +#define DEBUG_EXPORT function #define DEBUG_DECL(a) #endif @@ -312,10 +312,6 @@ typedef struct TerminalMemory { /************************************************************/ /* NOTE: functions provided by the terminal to the platform */ /************************************************************/ -#define VTGL_INITIALIZE_FN(name) iv2 name(TerminalMemory *memory, TerminalInput *input, void *window, \ - iptr child, iv2 requested_cells, iv2 monitor_size) -typedef VTGL_INITIALIZE_FN(vtgl_initialize_fn); - #define VTGL_FRAME_STEP_FN(name) void name(TerminalMemory *memory, TerminalInput *input) typedef VTGL_FRAME_STEP_FN(vtgl_frame_step_fn); @@ -326,7 +322,7 @@ typedef VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection_fn); KeyboardKey key, ButtonAction action, InputModifier modifiers) typedef VTGL_HANDLE_KEYS_FN(vtgl_handle_keys_fn); -#include "os.h" +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 @@ -0,0 +1,88 @@ +/* See LICENSE for copyright details */ +#include "vtgl.h" + +#define static_path_join(a, b) (a OS_PATH_SEPERATOR b) + +function OS_FILE_WATCH_CALLBACK_FN(queue_shader_reload) +{ + ShaderReloadContext *src = user_ctx; + WorkQueueWork *work = work_queue_push(src->work_queue); + if (work) { + work->kind = WQK_RELOAD_SHADER; + work->shader_reload_context = src; + work_queue_push_commit(src->work_queue); + } +} + +function iv2 +vtgl_initialize(TerminalMemory *memory, TerminalInput *input, void *window, iptr child, + iv2 requested_cells, iv2 monitor_size) +{ + Term *t = (Term *)memory->memory; + Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size}; + + t->os = &memory->os; + + t->cursor.state = CURSOR_NORMAL; + t->cursor.style.fg = g_colours.data[g_colours.fgidx]; + t->cursor.style.bg = g_colours.data[g_colours.bgidx]; + + t->views[0].log = memory->os.allocate_ring_buffer(BACKLOG_SIZE); + t->views[0].lines = line_buffer_alloc(&a, t->views[0].log.data, t->cursor.style, BACKLOG_LINES); + t->views[1].log = memory->os.allocate_ring_buffer(ALT_BACKLOG_SIZE); + t->views[1].lines = line_buffer_alloc(&a, t->views[1].log.data, t->cursor.style, ALT_BACKLOG_LINES); + + t->views[0].fb.backing_store = memory_block_from_arena(&a, MB(2)); + t->views[1].fb.backing_store = memory_block_from_arena(&a, MB(2)); + + selection_clear(&t->selection); + + RenderThreadContext *rtc = &t->render_thread; + rtc->arena = sub_arena(&a, MB(8)); + rtc->os = &memory->os; + rtc->input = input; + rtc->memory = memory; + rtc->window = (iptr)window; + rtc->monitor_size = monitor_size; + rtc->gl.glyph_bitmap_dim = monitor_size; + + init_fonts(&rtc->fa, &a, rtc->gl.glyph_bitmap_dim); + + v2 cs = fa_cell_size(&rtc->fa); + iv2 requested_size = { + .x = cs.x * requested_cells.x + 2 * g_term_margin.x, + .y = cs.y * requested_cells.y + 2 * g_term_margin.y, + }; + if (requested_cells.x < 0 || requested_cells.y < 0) + requested_size = (iv2){.x = -1, .y = -1}; + + #define X(en, sn) do { \ + ShaderReloadContext *src = rtc->shader_reload_contexts + SID_ ##en; \ + src->work_queue = &rtc->work_queue; \ + src->label = s8(#en); \ + src->path = s8(static_path_join(SHADER_PATH_PREFIX, sn ".glsl")); \ + src->shader = SID_ ## en; \ + memory->os.add_file_watch(src->path.data, queue_shader_reload, src); \ + } while (0); + FRAGMENT_SHADERS + #undef X + + WorkQueueWork *work = work_queue_push(&rtc->work_queue); + if (work) { + work->kind = WQK_RELOAD_ALL_SHADERS; + work_queue_push_commit(&rtc->work_queue); + } + + memory->os.gl_make_context_current(0); + rtc->sync = memory->os.spawn_thread(vtgl_render_thread_entry, "[render]", (iptr)rtc); + + t->size = (iv2){.x = 1, .y = 1}; + t->state |= TS_NEEDS_RESIZE; + + t->error_stream = stream_alloc(&a, KB(256)); + t->saved_title = stream_alloc(&a, KB(16)); + t->arena_for_frame = a; + t->child = child; + + return requested_size; +}