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:
M | debug.c | | | 12 | ++++++------ |
M | intrinsics.c | | | 2 | ++ |
M | os.h | | | 42 | +++++++++++++++++++++++++++++++++--------- |
M | os_linux_common.c | | | 60 | +++++++++++++++++++++++++++++++++++++++++++++++++----------- |
M | os_linux_x11.c | | | 54 | ++++++++++++++++-------------------------------------- |
M | terminal.c | | | 4 | ++-- |
M | util.c | | | 123 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
M | util.h | | | 56 | +++++++++++++++++++++++++++++++++++++++++++------------- |
M | vtgl.c | | | 458 | ++++++++++++++++++++++++++++++++++++++++++++----------------------------------- |
M | vtgl.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)×pec;
+ 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, ¤t, 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);