vtgl

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

Commit: 7b68364ff33165061f602e8f79e88ad8d2b8b34f
Parent: 5cfafe14ace64e47eca65e3998ecc0391c915773
Author: Randy Palamar
Date:   Mon, 21 Apr 2025 22:59:25 -0600

core: move render thread spawning/initialization into terminal

Where to place the OS boundary is a little unclear when there is a
second thread doing rendering. For now increase control in the
terminal at the cost of a slightly larger OS API surface.

Diffstat:
Mdebug.c | 12++++++------
Mintrinsics.c | 2++
Mos.h | 42+++++++++++++++++++++++++++++++++---------
Mos_linux_common.c | 60+++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mos_linux_x11.c | 54++++++++++++++++--------------------------------------
Mterminal.c | 4++--
Mutil.c | 123+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mutil.h | 56+++++++++++++++++++++++++++++++++++++++++++-------------
Mvtgl.c | 458++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mvtgl.h | 11++++++-----
10 files changed, 485 insertions(+), 337 deletions(-)

diff --git a/debug.c b/debug.c @@ -23,7 +23,7 @@ dump_lines_to_file(Term *t) Stream out = stream_alloc(&temp_arena, MB(1)); iv2 tsize = {.w = t->size.w, .h = t->size.h}; - iv2 wsize = {.w = t->gl.window_size.w, .h = t->gl.window_size.h}; + iv2 wsize = t->render_thread.gl.window_size; stream_push_s8(&out, s8("Term Info:")); stream_push_s8(&out, s8("\n Size: ")); stream_push_iv2(&out, tsize); stream_push_s8(&out, s8("\n Window Size: ")); stream_push_iv2(&out, wsize); @@ -295,18 +295,18 @@ draw_debug_overlay(TerminalMemory *term_memory, TerminalInput *input, RenderCtx Term *t = term_memory->memory; DebugState *ds = term_memory->debug_memory; - if (!(t->gl.flags & DRAW_DEBUG_OVERLAY)) + if (!(t->render_thread.gl.flags & DRAW_DEBUG_OVERLAY)) return; BEGIN_TIMED_BLOCK(); - v2 bar_chart_top_left = v2_from_iv2(t->gl.window_size); + v2 bar_chart_top_left = v2_from_iv2(t->render_thread.gl.window_size); bar_chart_top_left.x *= 0.5; - draw_debug_bar_chart(t, ds, input, rc, bar_chart_top_left, 0.25 * t->gl.window_size.w); + draw_debug_bar_chart(t, ds, input, rc, bar_chart_top_left, 0.25 * t->render_thread.gl.window_size.w); Arena memory = *ds->temp_memory.arena; - v2 ws = v2_from_iv2(t->gl.window_size); + v2 ws = v2_from_iv2(t->render_thread.gl.window_size); static GlyphCacheStats glyph_stats; static Rect r; @@ -337,7 +337,7 @@ draw_debug_overlay(TerminalMemory *term_memory, TerminalInput *input, RenderCtx if (ts.h > line_height) line_height = ts.h; } - GlyphCacheStats new_glyph_stats = get_and_clear_glyph_cache_stats(&t->fa.glyph_cache); + GlyphCacheStats new_glyph_stats = get_and_clear_glyph_cache_stats(&t->render_thread.fa.glyph_cache); if (new_glyph_stats.hit_count != 0) glyph_stats = new_glyph_stats; { diff --git a/intrinsics.c b/intrinsics.c @@ -2,8 +2,10 @@ #define atomic_and(ptr, n) __atomic_and_fetch(ptr, n, __ATOMIC_RELEASE); #define atomic_fetch_add(ptr, n) __atomic_fetch_add(ptr, n, __ATOMIC_RELEASE); +#define atomic_inc(ptr, n) __atomic_fetch_add(ptr, n, __ATOMIC_ACQ_REL) #define atomic_load(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE) #define atomic_exchange_n(ptr, val) __atomic_exchange_n(ptr, val, __ATOMIC_SEQ_CST) +#define atomic_cas(ptr, cptr, n) __atomic_compare_exchange_n(ptr, cptr, n, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) function FORCE_INLINE u32 clz_u32(u32 a) diff --git a/os.h b/os.h @@ -58,19 +58,43 @@ typedef OS_GET_WINDOW_TITLE_FN(os_get_window_title_fn); #define OS_SET_WINDOW_TITLE_FN(name) void name(s8 title) typedef OS_SET_WINDOW_TITLE_FN(os_set_window_title_fn); +#define OS_THREAD_ENTRY_POINT_FN(name) i64 name(iptr context, i32 *sync) +typedef OS_THREAD_ENTRY_POINT_FN(os_thread_entry_point_fn); + +#define OS_GL_SWAP_BUFFERS_FN(name) void name(iptr window) +typedef OS_GL_SWAP_BUFFERS_FN(os_gl_swap_buffers_fn); + +#define OS_GL_MAKE_CONTEXT_CURRENT_FN(name) void name(iptr window) +typedef OS_GL_MAKE_CONTEXT_CURRENT_FN(os_gl_make_context_current_fn); + +#define OS_SPAWN_THREAD_FN(name) i32 *name(os_thread_entry_point_fn *entry_point, \ + char *thread_name, iptr user_context) +typedef OS_SPAWN_THREAD_FN(os_spawn_thread_fn); + +#define OS_WAIT_ON_VALUE_FN(name) b32 name(i32 *value, i32 current, u32 timeout_ms) +typedef OS_WAIT_ON_VALUE_FN(os_wait_on_value_fn); + +#define OS_WAKE_WAITERS_FN(name) void name(i32 *sync) +typedef OS_WAKE_WAITERS_FN(os_wake_waiters_fn); + #define OS_WRITE_FN(name) b32 name(iptr file, s8 raw) typedef OS_WRITE_FN(os_write_fn); #define OS_FUNCTIONS \ - X(add_file_watch) \ - X(allocate_ring_buffer) \ - X(get_clipboard) \ - X(set_clipboard) \ - X(read_file) \ - X(read) \ - X(set_terminal_size) \ - X(get_window_title) \ - X(set_window_title) \ + X(add_file_watch) \ + X(allocate_ring_buffer) \ + X(get_clipboard) \ + X(set_clipboard) \ + X(read_file) \ + X(read) \ + X(set_terminal_size) \ + X(get_window_title) \ + X(set_window_title) \ + X(gl_swap_buffers) \ + X(gl_make_context_current) \ + X(spawn_thread) \ + X(wait_on_value) \ + X(wake_waiters) \ X(write) typedef struct { diff --git a/os_linux_common.c b/os_linux_common.c @@ -70,12 +70,10 @@ typedef __attribute__((aligned(16))) u8 statx_buffer[256]; struct __attribute__((aligned(16))) stack_base { void (*entry)(struct stack_base *stack); - Arena thread_arena; - void *window; - TerminalMemory *terminal_memory; - TerminalInput *input; - i32 work_futex; - b32 thread_asleep; + os_thread_entry_point_fn *user_entry_point; + iptr user_context; + char name[16]; + i32 futex; }; typedef struct { @@ -118,11 +116,7 @@ typedef struct { Stream error_stream; - struct stack_base *render_stack; - -#ifdef _DEBUG - void *library_handle; -#endif + DEBUG_DECL(void *library_handle); } PlatformCtx; global PlatformCtx linux_ctx; @@ -517,6 +511,50 @@ new_stack(iz capacity) return result; } +function OS_WAIT_ON_VALUE_FN(os_wait_on_value) +{ + iptr timeout = 0; + i64 timespec[2]; + if (timeout_ms != (u32)-1) { + timeout = (iptr)&timespec; + timespec[0] = timeout_ms / 1000; + timespec[1] = (timeout_ms % 1000) * 1000000; + } + return syscall4(SYS_futex, (iptr)value, FUTEX_WAIT, current, timeout) == 0; +} + +function OS_WAKE_WAITERS_FN(os_wake_waiters) +{ + atomic_inc(sync, 1); + syscall4(SYS_futex, (iptr)sync, FUTEX_WAKE, I32_MAX, 0); +} + +function void +thread_entry_point(struct stack_base *stack) +{ + syscall2(SYS_prctl, PR_SET_NAME, (iptr)stack->name); + i64 result = stack->user_entry_point(stack->user_context, &stack->futex); + syscall1(SYS_exit, result); + __builtin_unreachable(); +} + +function OS_SPAWN_THREAD_FN(os_spawn_thread) +{ + struct stack_base *stack = new_stack(KB(256)); + + s8 name = c_str_to_s8(thread_name); + name.len = MIN(name.len, (iz)(sizeof(stack->name) - 1)); + mem_copy(stack->name, name.data, name.len); + + stack->entry = thread_entry_point; + stack->user_entry_point = entry_point; + stack->user_context = user_context; + + new_thread(stack); + + return &stack->futex; +} + function void usage(char *argv0, Stream *err) { diff --git a/os_linux_x11.c b/os_linux_x11.c @@ -28,7 +28,6 @@ i32 XPending(void *display); #define LIB_FNS \ X(vtgl_active_selection) \ X(vtgl_initialize) \ - X(vtgl_render_frame) \ X(vtgl_handle_keys) \ X(vtgl_frame_step) @@ -44,7 +43,7 @@ function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) return; /* NOTE(rnp): spin until render thread finishes its work */ - while (!ctx->render_stack->thread_asleep); + //while (!ctx->render_stack->thread_asleep); ctx->input.executable_reloaded = 1; s8 nl = s8("\n"); @@ -202,7 +201,7 @@ refresh_callback(GLFWwindow *win) ctx->input.window_refreshed = 1; } -function GLFWwindow * +function void * init_window(PlatformCtx *ctx, iv2 window_size) { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); @@ -321,28 +320,14 @@ function OS_SET_WINDOW_TITLE_FN(x11_set_window_title) glfwSetWindowTitle(linux_ctx.window, (c8 *)title.data); } -function void -linux_render_thread_entry(struct stack_base *stack) +function OS_GL_SWAP_BUFFERS_FN(glfw_swap_buffers) { - { - /* NOTE: set thread name */ - char name[16] = "[render]"; - syscall2(SYS_prctl, PR_SET_NAME, (iptr)name); - /* NOTE: halt until main thread is ready to hand off gl context */ - syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0); - - glfwMakeContextCurrent(stack->window); - } - - for (;;) { - stack->thread_asleep = 1; - syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0); - stack->thread_asleep = 0; - vtgl_render_frame(stack->terminal_memory, stack->input, stack->thread_arena); - glfwSwapBuffers(stack->window); - } + glfwSwapBuffers((GLFWwindow *)window); +} - __builtin_unreachable(); +function OS_GL_MAKE_CONTEXT_CURRENT_FN(glfw_make_context_current) +{ + glfwMakeContextCurrent((GLFWwindow *)window); } i32 @@ -400,11 +385,6 @@ main(i32 argc, char *argv[], char *envp[]) stream_reset(&linux_ctx.error_stream, 0); } - linux_ctx.render_stack = new_stack(KB(256)); - linux_ctx.render_stack->entry = linux_render_thread_entry; - linux_ctx.render_stack->thread_asleep = 1; - new_thread(linux_ctx.render_stack); - { Arena tmp = linux_ctx.platform_memory; SLLVariableVector environment_block = parse_environment(&tmp, envp); @@ -441,6 +421,11 @@ main(i32 argc, char *argv[], char *envp[]) linux_ctx.memory.os.set_terminal_size = os_set_terminal_size; linux_ctx.memory.os.get_window_title = x11_get_window_title; linux_ctx.memory.os.set_window_title = x11_set_window_title; + linux_ctx.memory.os.gl_swap_buffers = glfw_swap_buffers; + linux_ctx.memory.os.gl_make_context_current = glfw_make_context_current; + linux_ctx.memory.os.spawn_thread = os_spawn_thread; + linux_ctx.memory.os.wait_on_value = os_wait_on_value; + linux_ctx.memory.os.wake_waiters = os_wake_waiters; linux_ctx.memory.os.write = os_write; linux_ctx.memory.os.path_separator = '/'; @@ -461,7 +446,8 @@ main(i32 argc, char *argv[], char *envp[]) iv2 window_size = {.w = 1280, .h = 720}; linux_ctx.window = init_window(&linux_ctx, window_size); - iv2 requested_size = vtgl_initialize(&linux_ctx.memory, linux_ctx.child.handle, cells, monitor_size); + iv2 requested_size = vtgl_initialize(&linux_ctx.memory, &linux_ctx.input, linux_ctx.window, + linux_ctx.child.handle, cells, monitor_size); if (requested_size.w > 0 && requested_size.h > 0 && (requested_size.w != window_size.w || requested_size.h != window_size.h)) { @@ -483,16 +469,9 @@ main(i32 argc, char *argv[], char *envp[]) ABS(monitor_size.h - window_size.h) / 2); glfwShowWindow(linux_ctx.window); } - glfwMakeContextCurrent(0); linux_ctx.input.window_size = window_size; - linux_ctx.render_stack->input = &linux_ctx.input; - linux_ctx.render_stack->terminal_memory = &linux_ctx.memory; - linux_ctx.render_stack->thread_arena = arena_from_memory_block(os_block_alloc(MB(8))); - linux_ctx.render_stack->window = linux_ctx.window; - syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1); - Range last_sel = {0}; f64 last_time = os_get_time(); while (!glfwWindowShouldClose(linux_ctx.window)) { @@ -505,8 +484,7 @@ main(i32 argc, char *argv[], char *envp[]) last_time = current_time; update_input(&linux_ctx); - if (vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input)) - syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1); + vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input); Range current_sel = vtgl_active_selection(&linux_ctx.memory, 0); if (is_valid_range(current_sel) && !equal_range(current_sel, last_sel)) { diff --git a/terminal.c b/terminal.c @@ -1407,7 +1407,7 @@ blit_lines(Term *t, Arena a) { BEGIN_TIMED_BLOCK(); - ASSERT(t->gl.flags & NEEDS_REFILL); + ASSERT(t->state & TS_NEEDS_REFILL); term_reset(t); TermView *tv = t->views + t->view_idx; @@ -1419,7 +1419,7 @@ blit_lines(Term *t, Arena a) push_line(t, tv->lines.data + line_idx, a); } - t->gl.flags &= ~NEEDS_REFILL; + t->state &= ~TS_NEEDS_REFILL; END_TIMED_BLOCK(); } diff --git a/util.c b/util.c @@ -90,82 +90,99 @@ normalize_range(Range r) return result; } -/* NOTE(rnp): based on nullprogram's lock-free, concurrent, - * generic queue in 32 bits */ -function i32 -work_queue_push(u32 *q, u32 capacity) -{ - ASSERT(ISPOWEROFTWO(capacity)); - u32 r = atomic_load(q); - i32 mask = capacity - 1; - i32 head = r & mask; - i32 tail = (r >> 16) & mask; - i32 next = (head + 1) & mask; - /* NOTE(rnp): prevent an overflow into the tail on commit */ - if (r & 0x8000) atomic_and(q, ~0x8000u); - return next == tail ? -1 : head; +function void +mem_copy(void *restrict dest, void *restrict src, iz len) +{ + ASSERT(len >= 0); + u8 *s = src, *d = dest; + for (; len; len--) *d++ = *s++; } -function void -work_queue_push_commit(u32 *q) +#define zero_struct(s) mem_clear(s, 0, sizeof(typeof(*s))) +function void * +mem_clear(void *p_, u8 c, iz len) { - atomic_fetch_add(q, 1); + u8 *p = p_; + while (len) p[--len] = c; + return p; } -function i32 -work_queue_pop(u32 *q, u32 capacity) +/* NOTE(rnp): based on nullprogram's lock-free, concurrent, + * generic queue in 32 bits */ +function WorkQueueWork * +work_queue_push(WorkQueue *q) { - ASSERT(ISPOWEROFTWO(capacity)); - u32 r = atomic_load(q); - i32 mask = capacity - 1; - i32 head = r & mask; - i32 tail = (r >> 16) & mask; - return head == tail ? -1 : tail; + WorkQueueWork *result = 0; + static_assert(ISPOWEROFTWO(countof(q->items)), "queue capacity must be power of two"); + u64 val = atomic_load(&q->queue); + u64 mask = countof(q->items) - 1; + u32 widx = val & mask; + u32 ridx = (val >> 32) & mask; + u32 next = (widx + 1) & mask; + + /* NOTE(rnp): prevent an overflow into the tail on commit */ + if (val & 0x80000000ULL) + atomic_and(&q->queue, ~0x80000000ULL); + + if (next != ridx) { + result = q->items + widx; + zero_struct(result); + } + + return result; } function void -work_queue_pop_commit(u32 *q) +work_queue_push_commit(WorkQueue *q) { - atomic_fetch_add(q, 0x10000u); + atomic_fetch_add(&q->queue, 1); } -function b32 -work_queue_empty(u32 *q, u32 capacity) +function WorkQueueWork * +work_queue_pop(WorkQueue *q) { - ASSERT(ISPOWEROFTWO(capacity)); - u32 r = atomic_load(q); - i32 mask = capacity - 1; - i32 head = r & mask; - i32 tail = (r >> 16) & mask; - return head == tail; + WorkQueueWork *result = 0; + static_assert(ISPOWEROFTWO(countof(q->items)), "queue capacity must be power of two"); + u64 val = atomic_load(&q->queue); + u64 mask = countof(q->items) - 1; + u32 widx = val & mask; + u32 ridx = (val >> 32) & mask; + + if (ridx != widx) + result = q->items + ridx; + + return result; } function void -work_queue_insert(Term *t, u32 type, void *ctx) +work_queue_pop_commit(WorkQueue *q) { - i32 index = work_queue_push(&t->work_queue, t->work_queue_capacity); - /* NOTE(rnp): if we ever fill this up we need to resize the queue */ - ASSERT(index != -1); - work_queue_push_commit(&t->work_queue); - t->work_queue_items[index].type = type; - t->work_queue_items[index].ctx = ctx; + atomic_fetch_add(&q->queue, 0x100000000ULL); } -function void -mem_copy(void *restrict dest, void *restrict src, iz len) +function b32 +work_queue_empty(WorkQueue *q) { - ASSERT(len >= 0); - u8 *s = src, *d = dest; - for (; len; len--) *d++ = *s++; + static_assert(ISPOWEROFTWO(countof(q->items)), "queue capacity must be power of two"); + u64 r = atomic_load(&q->queue); + u32 mask = countof(q->items) - 1; + u32 head = r & mask; + u32 tail = (r >> 32) & mask; + return head == tail; } -#define zero_struct(s) mem_clear(s, 0, sizeof(typeof(*s))) -function void * -mem_clear(void *p_, u8 c, iz len) +function b32 +work_queue_insert(WorkQueue *q, WorkQueueWorkKind kind, void *ctx) { - u8 *p = p_; - while (len) p[--len] = c; - return p; + WorkQueueWork *work = work_queue_push(q); + 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_queue_push_commit(q); + } + return result; } #define push_struct(a, t) alloc(a, t, 1) diff --git a/util.h b/util.h @@ -200,8 +200,6 @@ typedef struct { enum gl_flags { RESIZE_RENDERER = 1 << 0, - NEEDS_RESIZE = 1 << 1, - NEEDS_REFILL = 1 << 2, DRAW_DEBUG_OVERLAY = 1 << 30, }; @@ -435,31 +433,61 @@ typedef struct RenderCtx { Arena a; } RenderCtx; -enum work_queue_entry_type { - WQ_RELOAD_SHADER, - WQ_RELOAD_ALL_SHADERS, -}; +typedef enum { + WQK_RELOAD_SHADER, + WQK_RELOAD_ALL_SHADERS, +} WorkQueueWorkKind; typedef struct { void *ctx; - enum work_queue_entry_type type; -} work_queue_entry; + WorkQueueWorkKind kind; +} WorkQueueWork; -typedef struct Term { +typedef struct { + union { + u64 queue; + struct {u32 widx, ridx;}; + }; + WorkQueueWork items[1 << 6]; +} WorkQueue; + +typedef struct { GLCtx gl; FontAtlas fa; + WorkQueue work_queue; + + Arena arena; + + OS *os; + /* TODO(rnp): cleanup */ + TerminalInput *input; + TerminalMemory *memory; + + iptr window; + iv2 monitor_size; + + /* TODO(rnp): cleanup */ + Stream error_stream; + + i32 *sync; +} RenderThreadContext; + +typedef enum { + TS_NEEDS_RESIZE = 1 << 0, + TS_NEEDS_REFILL = 1 << 1, +} TerminalState; + +typedef struct Term { Arena arena_for_frame; TempArena temp_arena; - work_queue_entry *work_queue_items; - u32 work_queue_capacity; - u32 work_queue; - InteractionState interaction; Selection selection; + TerminalState state; + ModeState mode; ModeState saved_mode; @@ -490,6 +518,8 @@ typedef struct Term { Stream saved_title; Stream error_stream; + RenderThreadContext render_thread; + OS *os; } Term; diff --git a/vtgl.c b/vtgl.c @@ -6,6 +6,10 @@ * [ ]: refactor: remove os_... includes from vtgl.h * [ ]: 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? */ /* TODO: define this ourselves since we should be loading it at runtime */ @@ -181,7 +185,7 @@ function OS_FILE_WATCH_CALLBACK_FN(queue_shader_reload) { queue_shader_reload_ctx *ctx = user_ctx; ctx->path = path; - work_queue_insert(ctx->t, WQ_RELOAD_SHADER, ctx); + work_queue_insert(&ctx->t->render_thread.work_queue, WQK_RELOAD_SHADER, ctx); } function v4 @@ -209,7 +213,7 @@ get_cell_size(FontAtlas *fa) function v2 get_occupied_size(Term *t) { - v2 cs = get_cell_size(&t->fa); + v2 cs = get_cell_size(&t->render_thread.fa); v2 result = {.x = t->size.w * cs.w, .y = t->size.h * cs.h}; return result; } @@ -217,9 +221,10 @@ get_occupied_size(Term *t) function v2 get_terminal_top_left(Term *t) { + iv2 ws = t->render_thread.gl.window_size; v2 os = get_occupied_size(t); - v2 delta = {.x = t->gl.window_size.w - os.w, .y = t->gl.window_size.h - os.h}; - v2 result = {.x = delta.x / 2, .y = t->gl.window_size.h - delta.y / 2}; + v2 delta = {.x = ws.w - os.w, .y = ws.h - os.h}; + v2 result = {.x = delta.x / 2, .y = ws.h - delta.y / 2}; return result; } @@ -231,7 +236,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->fa); + v2 cs = get_cell_size(&t->render_thread.fa); t->size.w = (i32)(ws.w / cs.w); t->size.h = (i32)(ws.h / cs.h); @@ -247,19 +252,20 @@ resize_terminal(Term *t, OS *os, iv2 window_size) if (!equal_iv2(old_size, t->size)) { t->size = initialize_framebuffer(&t->views[0].fb, t->size); initialize_framebuffer(&t->views[1].fb, t->size); - t->gl.flags |= NEEDS_REFILL; + t->state |= TS_NEEDS_REFILL; } os->set_terminal_size(t->child, t->size.h, t->size.w, ws.w, ws.h); - t->gl.flags |= RESIZE_RENDERER; - t->gl.flags &= ~NEEDS_RESIZE; + /* TODO(rnp): queue renderer resize */ + t->render_thread.gl.flags |= RESIZE_RENDERER; + t->state &= ~TS_NEEDS_RESIZE; } function void resize(Term *t, OS *os, iv2 window_size) { - GLCtx *gl = &t->gl; + GLCtx *gl = &t->render_thread.gl; gl->window_size = window_size; glViewport(0, 0, window_size.w, window_size.h); @@ -281,14 +287,15 @@ resize(Term *t, OS *os, iv2 window_size) LABEL_GL_OBJECT(GL_BUFFER, gl->render_shader_ssbo, s8("RenderCells")); gl->queued_render = 1; - v2 cs = get_cell_size(&t->fa); + FontAtlas *fa = &t->render_thread.fa; + v2 cs = get_cell_size(fa); ShaderParameters *sp = &gl->shader_parameters; sp->cell_size = (iv2){.w = cs.w, .h = cs.h}; - sp->strike_min = (t->fa.info.baseline + 0.40 * t->fa.info.h); - sp->strike_max = (t->fa.info.baseline + 0.48 * t->fa.info.h); - sp->underline_min = (0.89 * t->fa.info.h); - sp->underline_max = (0.96 * t->fa.info.h); + sp->strike_min = (fa->info.baseline + 0.40 * fa->info.h); + sp->strike_max = (fa->info.baseline + 0.48 * fa->info.h); + sp->underline_min = (0.89 * fa->info.h); + sp->underline_max = (0.96 * fa->info.h); sp->top_left_margin = g_term_margin; sp->margin_colour = g_colours.data[g_colours.bgidx].rgba; //sp->margin_colour = 0x7f003f00; @@ -504,6 +511,7 @@ render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena { BEGIN_TIMED_BLOCK(); + RenderThreadContext *ctx = &t->render_thread; TermView *tv = t->views + t->view_idx; iv2 term_size = t->size; /* NOTE: draw whole framebuffer */ @@ -513,7 +521,7 @@ render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena RenderCell *rc = render_buf + (row * term_size.w + col); CachedGlyph *cg; - rc->gpu_glyph = get_gpu_glyph_index(arena, &t->gl, &t->fa, c.cp, 0, + rc->gpu_glyph = get_gpu_glyph_index(arena, &ctx->gl, &ctx->fa, c.cp, 0, c.bg & FS_MASK, &cg); rc->fg = c.fg; rc->bg = c.bg; @@ -545,6 +553,7 @@ render_cursor(Term *t, b32 focused, Arena a) { BEGIN_TIMED_BLOCK(); + RenderThreadContext *ctx = &t->render_thread; iv2 curs = t->cursor.pos; Cell *c = &t->views[t->view_idx].fb.rows[curs.y][curs.x]; RenderCell *rc = alloc(&a, RenderCell, 3); @@ -554,7 +563,7 @@ render_cursor(Term *t, b32 focused, Arena a) iz offset = sizeof(RenderCell) * (curs.y * t->size.w + curs.x); CachedGlyph *cg; - rc[1].gpu_glyph = get_gpu_glyph_index(a, &t->gl, &t->fa, c->cp, 0, c->bg & FS_MASK, &cg); + rc[1].gpu_glyph = get_gpu_glyph_index(a, &ctx->gl, &ctx->fa, c->cp, 0, c->bg & FS_MASK, &cg); rc[1].fg = c->fg; rc[1].bg = c->bg; @@ -587,7 +596,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->fa); + v2 cell_size = get_cell_size(&t->render_thread.fa); v2 top_left = get_terminal_top_left(t); iv2 result; @@ -640,7 +649,7 @@ begin_selection(Term *t, u32 click_count, v2 mouse) case SS_NONE: break; } - t->gl.queued_render = 1; + t->render_thread.gl.queued_render = 1; } function void @@ -679,7 +688,7 @@ update_selection(Term *t, TerminalInput *input) sel->range = normalize_range(sel->range); if (!equal_range(old_range, sel->range)) - t->gl.queued_render = 1; + t->render_thread.gl.queued_render = 1; } KEYBIND_FN(copy) @@ -720,16 +729,16 @@ KEYBIND_FN(scroll) t->scroll_offset += a.i; CLAMP(t->scroll_offset, 0, tv->lines.filled - (t->size.h - 1)); - t->gl.flags |= NEEDS_REFILL; + t->state |= TS_NEEDS_REFILL; return 1; } KEYBIND_FN(zoom) { - shift_font_sizes(&t->fa, a.i); - font_atlas_update(&t->fa, t->gl.glyph_bitmap_dim); - t->gl.flags |= NEEDS_RESIZE; + shift_font_sizes(&t->render_thread.fa, a.i); + font_atlas_update(&t->render_thread.fa, t->render_thread.gl.glyph_bitmap_dim); + t->state |= TS_NEEDS_RESIZE; return 1; } @@ -864,7 +873,7 @@ DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys) return; } if (key == KEY_F12 && action == BUTTON_PRESS) { - t->gl.flags ^= DRAW_DEBUG_OVERLAY; + t->render_thread.gl.flags ^= DRAW_DEBUG_OVERLAY; input->window_refreshed = 1; return; } @@ -1029,6 +1038,7 @@ 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) { @@ -1057,99 +1067,156 @@ gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba) return result; } -DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) +function b32 +try_wait_sync(i32 *sync, i32 timeout_ms, os_wait_on_value_fn *os_wait_on_value) { - Term *t = (Term *)memory->memory; - Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size}; + 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; +} - t->os = &memory->os; +function void +vtgl_render_frame(RenderThreadContext *ctx, TerminalMemory *memory, TerminalInput *input, Arena arena) +{ + BEGIN_TIMED_BLOCK(); - t->cursor.state = CURSOR_NORMAL; - cursor_reset(t); + Term *t = memory->memory; - 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); + dt_for_frame = input->dt; - 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)); + WorkQueueWork *work = work_queue_pop(&ctx->work_queue); + 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); + } break; + case WQK_RELOAD_ALL_SHADERS: { + reload_all_shaders(&ctx->gl, ctx->os, arena); + } break; + default: INVALID_CODE_PATH; + } + work_queue_pop_commit(&ctx->work_queue); + work = work_queue_pop(&ctx->work_queue); + } - t->gl.glyph_bitmap_dim = monitor_size; - init_fonts(&t->fa, &a, t->gl.glyph_bitmap_dim); - selection_clear(&t->selection); - v2 cs = get_cell_size(&t->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}; + /* NOTE: default state which can be overwritten later in the frame */ + /* TODO: if (!t->ui_active) */ + { + t->interaction.hot.type = IS_TERM; + } - t->size = (iv2){.x = 1, .y = 1}; - initialize_framebuffer(&t->views[0].fb, t->size); - initialize_framebuffer(&t->views[1].fb, t->size); + glBindTexture(GL_TEXTURE_2D, ctx->gl.glyph_bitmap_tex); - t->work_queue_items = alloc(&a, typeof(*t->work_queue_items), 1 << 6); - t->work_queue_capacity = 1 << 6; + BEGIN_NAMED_BLOCK(update_render); - queue_shader_reload_ctx *reload_ctxs = alloc(&a, typeof(*reload_ctxs), SHADER_COUNT); + RenderCtx rc = make_render_ctx(&arena, &ctx->gl, &ctx->fa); - 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"), - }; + glUseProgram(ctx->gl.programs[SHADER_RENDER]); + glBindFramebuffer(GL_FRAMEBUFFER, ctx->gl.fb); + clear_colour(); - 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); + /* NOTE(rnp): spin lock here so that we don't have to deal with rescheduling + * a redraw. Remember that this failing is already unlikely. */ + while (atomic_exchange_n(&t->resize_lock, 1) != 0); - 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; + if (ctx->gl.flags & RESIZE_RENDERER) + resize(t, &memory->os, input->window_size); + + if (ctx->gl.queued_render) { + ctx->gl.queued_render = 0; + u32 cell_count = t->size.h * t->size.w; + RenderCell *render_buf = alloc(&arena, RenderCell, cell_count); + render_framebuffer(t, render_buf, input, arena); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, + cell_count * sizeof(*render_buf), render_buf); } + render_cursor(t, input->window_focused, arena); - t->error_stream = stream_alloc(&a, KB(256)); - t->saved_title = stream_alloc(&a, KB(16)); - t->arena_for_frame = a; - t->child = child; + t->resize_lock = 0; + + ShaderParameters *sp = &ctx->gl.shader_parameters; + sp->blink_parameter += 2 * PI * g_blink_speed * dt_for_frame; + if (sp->blink_parameter > 2 * PI) sp->blink_parameter -= 2 * PI; + sp->reverse_video_mask = REVERSE_VIDEO_MASK * !!(t->mode.win & WM_REVERSE); + + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(*sp), sp); + + push_rect_textured(&rc, (Rect){.size = v2_from_iv2(ctx->gl.window_size)}, (v4){0}, 0); + flush_render_push_buffer(&rc); - /* NOTE: Set up OpenGL Render Pipeline */ - glDebugMessageCallback(gl_debug_logger, &t->error_stream); + static f32 param = 0; + static f32 p_scale = 1; + param += p_scale * 0.005 * dt_for_frame; + if (param > 1.0f || param < 0) + p_scale *= -1.0f; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + clear_colour(); + glUseProgram(ctx->gl.programs[SHADER_POST]); + glUniform1i(ctx->gl.post.texslot, ctx->gl.fb_tex_unit); + glUniform1f(ctx->gl.post.param, param); + + push_rect_textured(&rc, (Rect){.size = v2_from_iv2(ctx->gl.window_size)}, (v4){0}, 1); + flush_render_push_buffer(&rc); + END_NAMED_BLOCK(update_render); + + /* 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]); + + BEGIN_NAMED_BLOCK(debug_overlay); + draw_debug_overlay(memory, input, &rc); + END_NAMED_BLOCK(debug_overlay); + + END_TIMED_BLOCK(); +} + +function void +vtgl_render_thread_initialize(RenderThreadContext *ctx) +{ + 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, &t->gl.vao); - glBindVertexArray(t->gl.vao); + glGenVertexArrays(1, &ctx->gl.vao); + glBindVertexArray(ctx->gl.vao); - glGenBuffers(ARRAY_COUNT(t->gl.vbos), t->gl.vbos); + glGenBuffers(ARRAY_COUNT(ctx->gl.vbos), ctx->gl.vbos); - RenderPushBuffer *rpb = NULL; + RenderPushBuffer *rpb = 0; /* NOTE: vertex position buffer */ - glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbos[0]); + 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, t->gl.vbos[1]); + 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, t->gl.vbos[2]); + 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); @@ -1164,155 +1231,142 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) element_indices[i + 4] = 4 * j + 2; element_indices[i + 5] = 4 * j + 3; } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t->gl.vbos[4]); + 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); - t->gl.glyph_bitmap_tex = gen_2D_texture(t->gl.glyph_bitmap_dim, GL_RGBA, GL_NEAREST, 0); + 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, t->gl.glyph_bitmap_tex, s8("Glyph_Bitmap")); + 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); + 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, &t->gl.fb); - glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb); - - t->gl.fb_tex_unit = 1; - glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit); - iv2 ws = monitor_size; - t->gl.fb_tex = gen_2D_texture(ws, GL_RGBA, GL_NEAREST, 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t->gl.fb_tex, 0); - LABEL_GL_OBJECT(GL_TEXTURE, t->gl.fb_tex, s8("Framebuffer_Texture")); - - glGenBuffers(1, &t->gl.render_shader_ubo); - glBindBuffer(GL_UNIFORM_BUFFER, t->gl.render_shader_ubo); + 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, t->gl.render_shader_ubo); - LABEL_GL_OBJECT(GL_BUFFER, t->gl.render_shader_ubo, s8("ShaderParameters")); + 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); - reload_all_shaders(&t->gl, &memory->os, a); - - return requested_size; + reload_all_shaders(&ctx->gl, ctx->os, a); } -DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection) +function OS_THREAD_ENTRY_POINT_FN(vtgl_render_thread_entry) { - Term *t = memory->memory; - Range result = t->selection.range; - if (out) stream_push_selection(out, t->views[t->view_idx].fb.rows, result, t->size.w); - return result; -} - -DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame) -{ - BEGIN_TIMED_BLOCK(); - - Term *t = memory->memory; - - dt_for_frame = input->dt; - - TempArena temp_arena = begin_temp_arena(&arena); + RenderThreadContext *ctx = (RenderThreadContext *)context; - i32 queue_item; - while ((queue_item = work_queue_pop(&t->work_queue, t->work_queue_capacity)) != -1) { - work_queue_pop_commit(&t->work_queue); - work_queue_entry *entry = t->work_queue_items + queue_item; - switch (entry->type) { - case WQ_RELOAD_SHADER: { - queue_shader_reload_ctx *ctx = entry->ctx; - reload_shader(&t->gl, &memory->os, ctx->path, ctx->stage, ctx->info, arena); - } break; - case WQ_RELOAD_ALL_SHADERS: { - reload_all_shaders(&t->gl, &memory->os, arena); - } break; - default: INVALID_CODE_PATH; - } + vtgl_render_thread_initialize(ctx); + for (;;) { + try_wait_sync(sync, -1, ctx->os->wait_on_value); + vtgl_render_frame(ctx, ctx->memory, ctx->input, ctx->arena); + ctx->os->gl_swap_buffers(ctx->window); } + return 0; +} - /* NOTE: default state which can be overwritten later in the frame */ - /* TODO: if (!t->ui_active) */ - { - t->interaction.hot.type = IS_TERM; - } - - glBindTexture(GL_TEXTURE_2D, t->gl.glyph_bitmap_tex); - - BEGIN_NAMED_BLOCK(update_render); - - RenderCtx rc = make_render_ctx(&arena, &t->gl, &t->fa); +DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) +{ + Term *t = (Term *)memory->memory; + Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size}; - glUseProgram(t->gl.programs[SHADER_RENDER]); - glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb); - clear_colour(); + t->os = &memory->os; - /* NOTE(rnp): spin lock here so that we don't have to deal with rescheduling - * a redraw. Remember that this failing is already unlikely. */ - while (atomic_exchange_n(&t->resize_lock, 1) != 0); + t->cursor.state = CURSOR_NORMAL; + cursor_reset(t); - if (t->gl.flags & RESIZE_RENDERER) - resize(t, &memory->os, input->window_size); + 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); - if (t->gl.queued_render) { - t->gl.queued_render = 0; - u32 cell_count = t->size.h * t->size.w; - RenderCell *render_buf = alloc(&arena, RenderCell, cell_count); - render_framebuffer(t, render_buf, input, arena); - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, - cell_count * sizeof(*render_buf), render_buf); - } - render_cursor(t, input->window_focused, arena); + 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)); - t->resize_lock = 0; + 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; - ShaderParameters *sp = &t->gl.shader_parameters; - sp->blink_parameter += 2 * PI * g_blink_speed * dt_for_frame; - if (sp->blink_parameter > 2 * PI) sp->blink_parameter -= 2 * PI; - sp->reverse_video_mask = REVERSE_VIDEO_MASK * !!(t->mode.win & WM_REVERSE); + selection_clear(&t->selection); - glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(*sp), sp); + 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}; - push_rect_textured(&rc, (Rect){.size = v2_from_iv2(t->gl.window_size)}, (v4){0}, 0); - flush_render_push_buffer(&rc); + memory->os.gl_make_context_current(0); - static f32 param = 0; - static f32 p_scale = 1; - param += p_scale * 0.005 * dt_for_frame; - if (param > 1.0f || param < 0) - p_scale *= -1.0f; + 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); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + queue_shader_reload_ctx *reload_ctxs = alloc(&a, typeof(*reload_ctxs), SHADER_COUNT); - clear_colour(); - glUseProgram(t->gl.programs[SHADER_POST]); - glUniform1i(t->gl.post.texslot, t->gl.fb_tex_unit); - glUniform1f(t->gl.post.param, param); + 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"), + }; - push_rect_textured(&rc, (Rect){.size = v2_from_iv2(t->gl.window_size)}, (v4){0}, 1); - flush_render_push_buffer(&rc); - END_NAMED_BLOCK(update_render); + 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); - /* 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]); + 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; + } - BEGIN_NAMED_BLOCK(debug_overlay); - draw_debug_overlay(memory, input, &rc); - END_NAMED_BLOCK(debug_overlay); + t->error_stream = stream_alloc(&a, KB(256)); + t->saved_title = stream_alloc(&a, KB(16)); + t->arena_for_frame = a; + t->child = child; - end_temp_arena(temp_arena); + return requested_size; +} - END_TIMED_BLOCK(); +DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection) +{ + Term *t = memory->memory; + Range result = t->selection.range; + if (out) stream_push_selection(out, t->views[t->view_idx].fb.rows, result, t->size.w); + return result; } DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) @@ -1329,7 +1383,7 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) t->temp_arena = begin_temp_arena(&t->arena_for_frame); - if (t->gl.flags & NEEDS_RESIZE || !equal_iv2(input->window_size, t->gl.window_size)) { + if (t->state & TS_NEEDS_RESIZE || !equal_iv2(input->window_size, t->render_thread.gl.window_size)) { /* NOTE(rnp): we skip the resize this time through so that we don't add * input latency waiting for the render thread to release this lock */ if (atomic_exchange_n(&t->resize_lock, 1) == 0) { @@ -1341,14 +1395,14 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) } if (input->executable_reloaded) { - work_queue_insert(t, WQ_RELOAD_ALL_SHADERS, 0); + work_queue_insert(&t->render_thread.work_queue, WQK_RELOAD_ALL_SHADERS, 0); } BEGIN_NAMED_BLOCK(mouse_and_keyboard_input); if (input->character_input.len) { if (t->scroll_offset) { t->scroll_offset = 0; - t->gl.flags |= NEEDS_REFILL; + t->state |= TS_NEEDS_REFILL; } memory->os.write(t->child, input->character_input); } @@ -1356,9 +1410,9 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) handle_interactions(t, input, &memory->os); END_NAMED_BLOCK(mouse_and_keyboard_input); - if (t->gl.flags & NEEDS_REFILL) { + if (t->state & TS_NEEDS_REFILL) { blit_lines(t, t->arena_for_frame); - t->gl.queued_render = 1; + t->render_thread.gl.queued_render = 1; } BEGIN_NAMED_BLOCK(input_from_child); @@ -1376,7 +1430,7 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) .data = rb->data + (rb->write_index - t->unprocessed_bytes) }; handle_input(t, t->arena_for_frame, raw); - t->gl.queued_render = 1; + t->render_thread.gl.queued_render = 1; } END_NAMED_BLOCK(input_from_child); @@ -1384,8 +1438,12 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) END_TIMED_BLOCK(); - return t->gl.queued_render || input->window_refreshed || t->gl.flags & DRAW_DEBUG_OVERLAY - || !work_queue_empty(&t->work_queue, t->work_queue_capacity); + if (t->render_thread.gl.queued_render || input->window_refreshed || + t->render_thread.gl.flags & DRAW_DEBUG_OVERLAY || + !work_queue_empty(&t->render_thread.work_queue)) + { + t->os->wake_waiters(t->render_thread.sync); + } } #ifdef _DEBUG diff --git a/vtgl.h b/vtgl.h @@ -26,6 +26,7 @@ #define KB(a) ((a) << 10ULL) #define MB(a) ((a) << 20ULL) +#define countof(a) (iz)(sizeof(a) / sizeof(*a)) #define ARRAY_COUNT(a) (iz)(sizeof(a) / sizeof(*a)) #define ABS(a) ((a) < 0 ? (-a) : (a)) #define BETWEEN(x, a, b) ((x) >= (a) && (x) <= (b)) @@ -77,9 +78,11 @@ typedef size_t uz; #ifdef _DEBUG #define ASSERT(c) do { if (!(c)) debugbreak(); } while(0) #define DEBUG_EXPORT +#define DEBUG_DECL(a) a #else #define ASSERT(c) do { (void)(c); } while(0) #define DEBUG_EXPORT static +#define DEBUG_DECL(a) #endif typedef struct { u8 *beg, *end; } Arena; @@ -309,15 +312,13 @@ typedef struct TerminalMemory { /************************************************************/ /* NOTE: functions provided by the terminal to the platform */ /************************************************************/ -#define VTGL_INITIALIZE_FN(name) iv2 name(TerminalMemory *memory, iptr child, iv2 requested_cells, iv2 monitor_size) +#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) b32 name(TerminalMemory *memory, TerminalInput *input) +#define VTGL_FRAME_STEP_FN(name) void name(TerminalMemory *memory, TerminalInput *input) typedef VTGL_FRAME_STEP_FN(vtgl_frame_step_fn); -#define VTGL_RENDER_FRAME_FN(name) void name(TerminalMemory *memory, TerminalInput *input, Arena arena) -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);