Commit: ab202f5425cc5cd5e2d5f9141d304ccc40e92a7e
Parent: c14801bdafaf974c2c4f52b2b5b8b9b5be02c6ec
Author: Randy Palamar
Date: Thu, 28 Nov 2024 21:46:17 -0700
move rendering to a second thread
much less battery drain; bugs yes but this is a needed checkpoint
Diffstat:
13 files changed, 540 insertions(+), 215 deletions(-)
diff --git a/build.sh b/build.sh
@@ -12,7 +12,7 @@ cflags="-march=native -Wall -DVERSION=\"${version}\""
#cflags="${cflags} -fproc-stat-report"
#cflags="${cflags} -Rpass-missed=.*"
#cflags="${cflags} -fsanitize=address,undefined"
-ldflags="-lm -lGL -lglfw"
+ldflags="-lm -lGL -lglfw -lX11"
[ ! -s "./config.h" ] && cp config.def.h config.h
diff --git a/debug.c b/debug.c
@@ -388,14 +388,12 @@ debug_init(TerminalMemory *memory)
}
static void
-debug_frame_end(TerminalMemory *memory, TerminalInput *input, RenderCtx *rc)
+debug_frame_end(TerminalMemory *memory)
{
DebugState *debug_state = memory->debug_memory;
if (!debug_state->initialized)
debug_init(memory);
- draw_debug_overlay(memory, input, rc);
-
g_debug_table.snapshot_index++;
if (g_debug_table.snapshot_index == MAX_DEBUG_RECORD_COUNT)
g_debug_table.snapshot_index = 0;
diff --git a/debug.h b/debug.h
@@ -85,6 +85,7 @@ typedef struct {
#define END_NAMED_BLOCK(...)
#define debug_frame_end(...)
+#define draw_debug_overlay(...)
#else
@@ -152,6 +153,7 @@ typedef struct TerminalInput TerminalInput;
typedef struct Term Term;
static void dump_lines_to_file(Term *t);
-static void debug_frame_end(TerminalMemory *memory, TerminalInput *input, RenderCtx *rc);
+static void draw_debug_overlay(TerminalMemory *term_memory, TerminalInput *input, RenderCtx *rc);
+static void debug_frame_end(TerminalMemory *memory);
#endif
diff --git a/os_unix.c b/os_unix.c
@@ -40,26 +40,7 @@ os_fatal(s8 msg)
{
os_write_err_msg(msg);
_exit(1);
-}
-
-static MemoryBlock
-posix_alloc(size requested_size)
-{
- MemoryBlock result = {0};
-
- size page_size = sysconf(_SC_PAGESIZE);
- size alloc_size = requested_size;
-
- if (alloc_size % page_size!= 0)
- alloc_size += page_size - alloc_size % page_size;
-
- void *memory = mmap(0, alloc_size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
- if (memory != MAP_FAILED) {
- result.memory = memory;
- result.size = alloc_size;
- }
-
- return result;
+ __builtin_unreachable();
}
static PLATFORM_GET_FILESTATS_FN(posix_get_file_stats)
diff --git a/platform_linux_amd64.c b/platform_linux_amd64.c
@@ -0,0 +1,143 @@
+/* See LICENSE for license details. */
+
+#ifndef asm
+#ifdef __asm
+#define asm __asm
+#else
+#define asm __asm__
+#endif
+#endif
+
+#define SYS_read 0
+#define SYS_write 1
+#define SYS_open 2
+#define SYS_close 3
+#define SYS_stat 4
+#define SYS_mmap 9
+#define SYS_clone 56
+#define SYS_exit 60
+#define SYS_prctl 157
+#define SYS_futex 202
+#define SYS_getdents64 217
+
+#define PAGE_SIZE 4096
+
+#define STAT_BUF_SIZE 144
+#define STAT_SIZE_OFF 48
+
+#define DIRENT_RECLEN_OFF 16
+#define DIRENT_TYPE_OFF 18
+#define DIRENT_NAME_OFF 19
+
+typedef signed long i64;
+typedef unsigned long u64;
+
+static i64
+syscall1(i64 n, i64 a1)
+{
+ i64 result;
+ asm volatile ("syscall"
+ : "=a"(result)
+ : "a"(n), "D"(a1)
+ : "rcx", "r11", "memory"
+ );
+ return result;
+}
+
+static i64
+syscall2(i64 n, i64 a1, i64 a2)
+{
+ i64 result;
+ asm volatile ("syscall"
+ : "=a"(result)
+ : "a"(n), "D"(a1), "S"(a2)
+ : "rcx", "r11", "memory"
+ );
+ return result;
+}
+
+static i64
+syscall3(i64 n, i64 a1, i64 a2, i64 a3)
+{
+ i64 result;
+ asm volatile ("syscall"
+ : "=a"(result)
+ : "a"(n), "D"(a1), "S"(a2), "d"(a3)
+ : "rcx", "r11", "memory"
+ );
+ return result;
+}
+
+static i64
+syscall4(i64 n, i64 a1, i64 a2, i64 a3, i64 a4)
+{
+ i64 result;
+ register i64 r10 asm("r10") = a4;
+ asm volatile ("syscall"
+ : "=a"(result)
+ : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10)
+ : "rcx", "r11", "memory"
+ );
+ return result;
+}
+
+
+static i64
+syscall5(i64 n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5)
+{
+ i64 result;
+ register i64 r10 asm("r10") = a4;
+ register i64 r9 asm("r9") = a5;
+ asm volatile ("syscall"
+ : "=a"(result)
+ : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r9)
+ : "rcx", "r11", "memory"
+ );
+ return result;
+}
+
+static i64
+syscall6(i64 n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5, i64 a6)
+{
+ i64 result;
+ register i64 r10 asm("r10") = a4;
+ register i64 r8 asm("r8") = a5;
+ register i64 r9 asm("r9") = a6;
+ asm volatile ("syscall"
+ : "=a"(result)
+ : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9)
+ : "rcx", "r11", "memory"
+ );
+ return result;
+}
+
+/* NOTE: graciously taken from nullprogram (Chris Wellons) */
+__attribute__((naked))
+static i64 clone_thread(void *stack_base)
+{
+ asm volatile (
+ "mov %%rdi, %%rsi\n" // arg2 = stack
+ "mov $0x50F00, %%edi\n" // arg1 = clone flags (VM|FS|FILES|SIGHAND|THREAD|SYSVMEM)
+ "mov $56, %%eax\n" // SYS_clone
+ "syscall\n"
+ "mov %%rsp, %%rdi\n" // entry point argument
+ "ret\n"
+ : : : "rax", "rcx", "rsi", "rdi", "r11", "memory"
+ );
+}
+
+#include "platform_linux_common.c"
+
+#if 0
+asm (
+ ".intel_syntax noprefix\n"
+ ".global _start\n"
+ "_start:\n"
+ " mov edi, DWORD PTR [rsp]\n"
+ " lea rsi, [rsp+8]\n"
+ " lea rdx, [rsi+rdi*8+8]\n"
+ " call linux_main\n"
+ " ud2\n"
+ ".att_syntax\n"
+);
+#endif
diff --git a/platform_linux_common.c b/platform_linux_common.c
@@ -0,0 +1,86 @@
+#define FUTEX_WAIT 0
+#define FUTEX_WAKE 1
+
+#define PR_SET_NAME 15
+
+#define PROT_RW 0x03
+
+#define MAP_PRIVATE 0x02
+#define MAP_FIXED 0x10
+#define MAP_ANON 0x20
+
+#include "util.h"
+#include "vtgl.h"
+
+#ifndef VERSION
+#define VERSION "unknown"
+#endif
+
+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;
+};
+
+typedef struct {
+ platform_file_watch_callback_fn *fn;
+ u8 *path;
+ void *user_ctx;
+ i32 inode;
+ i32 handle;
+} linux_file_watch;
+
+#include <sys/inotify.h>
+
+#define LINUX_INOTIFY_MASK (IN_CLOSE|IN_MODIFY)
+
+static MemoryBlock
+linux_block_alloc(size requested_size)
+{
+ MemoryBlock result = {0};
+
+ size alloc_size = requested_size;
+ if (alloc_size % PAGE_SIZE != 0)
+ alloc_size += PAGE_SIZE - alloc_size % PAGE_SIZE;
+
+ i64 memory = syscall6(SYS_mmap, 0, alloc_size, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0);
+ if (memory <= -4096UL) {
+ result.memory = (void *)memory;
+ result.size = alloc_size;
+ }
+
+ return result;
+}
+
+static struct stack_base *
+new_stack(size capacity)
+{
+ i64 p = syscall6(SYS_mmap, 0, capacity, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0);
+ if (p > -4096UL)
+ os_fatal(s8("new_stack: mmap failed\n"));
+ i64 count = capacity / sizeof(struct stack_base);
+ /* NOTE: remember the stack grows down; we want to start at the highest address */
+ struct stack_base *result = (struct stack_base *)p + count - 1;
+ return result;
+}
+
+static void
+button_action(ButtonState *button, b32 pressed)
+{
+ if (pressed != button->ended_down)
+ button->transitions++;
+ button->ended_down = pressed;
+}
+
+static void
+usage(char *argv0, Stream *err)
+{
+ stream_push_s8(err, s8("usage: "));
+ stream_push_s8(err, c_str_to_s8(argv0));
+ stream_push_s8(err, s8(" [-v] [-g COLxROW]\n"));
+ os_fatal(stream_to_s8(err));
+}
+
diff --git a/platform_linux_x11.c b/platform_linux_x11.c
@@ -12,20 +12,11 @@ typedef void *Window;
#define GLFW_NATIVE_INCLUDE_NONE
#include <GLFW/glfw3native.h>
-#include "util.h"
-#include "vtgl.h"
+/* TODO: main should be defined in this file instead */
+#include "platform_linux_amd64.c"
-#ifndef VERSION
-#define VERSION "unknown"
-#endif
-
-typedef struct {
- platform_file_watch_callback_fn *fn;
- u8 *path;
- void *user_ctx;
- i32 inode;
- i32 handle;
-} linux_file_watch;
+i32 XConnectionNumber(void *display);
+i32 XPending(void *display);
typedef struct {
Arena platform_memory;
@@ -38,6 +29,7 @@ typedef struct {
posix_platform_process child;
i32 inotify_fd;
+ i32 x_fd;
linux_file_watch file_watches[32];
i32 file_watch_count;
@@ -63,6 +55,7 @@ static PlatformCtx linux_ctx;
#define LIB_FNS \
X(vtgl_active_selection) \
X(vtgl_initialize) \
+ X(vtgl_render_frame) \
X(vtgl_handle_keys) \
X(vtgl_frame_step)
@@ -100,18 +93,6 @@ static PLATFORM_FILE_WATCH_CALLBACK_FN(debug_reload_library)
}
#endif /* _DEBUG */
-#include <sys/inotify.h>
-
-#define LINUX_INOTIFY_MASK (IN_CLOSE|IN_MODIFY)
-
-static void
-button_action(ButtonState *button, b32 pressed)
-{
- if (pressed != button->ended_down)
- button->transitions++;
- button->ended_down = pressed;
-}
-
static void
glfw_error_callback(int code, const char *desc)
{
@@ -228,6 +209,22 @@ mouse_button_callback(GLFWwindow *win, i32 button, i32 action, i32 modifiers)
}
}
+static void
+focus_callback(GLFWwindow *win, i32 focused)
+{
+ PlatformCtx *ctx = glfwGetWindowUserPointer(win);
+ ctx->input.window_focused = focused;
+ /* NOTE: force a refresh as well when the focus changes */
+ ctx->input.window_refreshed = 1;
+}
+
+static void
+refresh_callback(GLFWwindow *win)
+{
+ PlatformCtx *ctx = glfwGetWindowUserPointer(win);
+ ctx->input.window_refreshed = 1;
+}
+
static GLFWwindow *
init_window(PlatformCtx *ctx, iv2 window_size)
{
@@ -261,6 +258,10 @@ init_window(PlatformCtx *ctx, iv2 window_size)
glfwSetKeyCallback(window, key_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetScrollCallback(window, scroll_callback);
+ glfwSetWindowFocusCallback(window, focus_callback);
+ glfwSetWindowRefreshCallback(window, refresh_callback);
+
+ ctx->x_fd = XConnectionNumber(glfwGetX11Display());
return window;
}
@@ -308,6 +309,7 @@ update_input(PlatformCtx *ctx)
{
TerminalInput *input = &ctx->input;
ctx->input.executable_reloaded = 0;
+ ctx->input.window_refreshed = 0;
/* NOTE: mouse */
input->last_mouse = input->mouse;
@@ -323,27 +325,24 @@ update_input(PlatformCtx *ctx)
ctx->char_stream.widx = 0;
- /* TODO: we need to maintain the order events occured in; instead of doing this
- * we should have some sort of event list that the terminal can pull off of to
- * process these in order. */
- glfwPollEvents();
+ struct timespec timeout = {.tv_nsec = 25e6};
- /* TODO: pselect should include x11 fd after removing glfw */
- /* TODO: for now this is a poll but we could roll some wait time into it as well */
- struct timespec timeout = {0};
fd_set rfd;
FD_ZERO(&rfd);
FD_SET(ctx->child.handle, &rfd);
FD_SET(ctx->inotify_fd, &rfd);
+ FD_SET(ctx->x_fd, &rfd);
i32 max_fd = MAX(ctx->inotify_fd, ctx->child.handle);
+ max_fd = MAX(max_fd, ctx->x_fd);
pselect(max_fd + 1, &rfd, NULL, NULL, &timeout, NULL);
input->data_available = FD_ISSET(ctx->child.handle, &rfd) != 0;
- if (FD_ISSET(ctx->inotify_fd, &rfd)) {
+ if (FD_ISSET(ctx->inotify_fd, &rfd))
dispatch_file_watch_events(ctx);
- }
+
+ glfwPollEvents();
/* NOTE: char_stream was filled in char callback */
input->character_input = stream_to_s8(&ctx->char_stream);
@@ -413,43 +412,33 @@ static PLATFORM_ADD_FILE_WATCH_FN(linux_add_file_watch)
linux_ctx.file_watches[idx].user_ctx = user_ctx;
}
+#include <stdio.h>
static void
-usage(char *argv0, Stream *err)
+linux_render_thread_entry(struct stack_base *stack)
{
- stream_push_s8(err, s8("usage: "));
- stream_push_s8(err, c_str_to_s8(argv0));
- stream_push_s8(err, s8(" [-v] [-g COLxROW]\n"));
- os_fatal(stream_to_s8(err));
+ {
+ /* 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 (;;) {
+ syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0);
+ vtgl_render_frame(stack->terminal_memory, stack->input, stack->thread_arena);
+ glfwSwapBuffers(stack->window);
+ }
+
+ __builtin_unreachable();
}
i32
main(i32 argc, char *argv[], char *envp[])
{
- {
- MemoryBlock terminal_memory = posix_alloc(32 * MEGABYTE);
- linux_ctx.memory.memory = terminal_memory.memory;
- linux_ctx.memory.memory_size = terminal_memory.size;
-#ifdef _DEBUG
- MemoryBlock debug_memory = posix_alloc(128 * MEGABYTE);
- linux_ctx.memory.debug_memory = debug_memory.memory;
- linux_ctx.memory.debug_memory_size = debug_memory.size;
-#endif
- }
-
- linux_ctx.memory.platform_api.add_file_watch = linux_add_file_watch;
- linux_ctx.memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer;
- linux_ctx.memory.platform_api.get_clipboard = x11_get_clipboard;
- linux_ctx.memory.platform_api.set_clipboard = x11_set_clipboard;
- linux_ctx.memory.platform_api.get_file_stats = posix_get_file_stats;
- linux_ctx.memory.platform_api.read_file = posix_read_file;
- linux_ctx.memory.platform_api.read = posix_read;
- linux_ctx.memory.platform_api.set_terminal_size = posix_set_terminal_size;
- linux_ctx.memory.platform_api.get_window_title = x11_get_window_title;
- linux_ctx.memory.platform_api.set_window_title = x11_set_window_title;
- linux_ctx.memory.platform_api.write = posix_write;
- linux_ctx.memory.platform_api.path_separator = '/';
-
- linux_ctx.platform_memory = arena_from_memory_block(posix_alloc(2 * MEGABYTE));
+ linux_ctx.platform_memory = arena_from_memory_block(linux_block_alloc(MB(2)));
linux_ctx.error_stream = stream_alloc(&linux_ctx.platform_memory, MEGABYTE / 4);
iv2 cells = {.x = -1, .y = -1};
@@ -495,6 +484,21 @@ main(i32 argc, char *argv[], char *envp[])
linux_ctx.error_stream.widx = 0;
}
+ struct stack_base *render_stack = new_stack(MB(4));
+ render_stack->entry = linux_render_thread_entry;
+ clone_thread(render_stack);
+
+ {
+ MemoryBlock terminal_memory = linux_block_alloc(MB(32));
+ linux_ctx.memory.memory = terminal_memory.memory;
+ linux_ctx.memory.memory_size = terminal_memory.size;
+#ifdef _DEBUG
+ MemoryBlock debug_memory = linux_block_alloc(MB(128));
+ linux_ctx.memory.debug_memory = debug_memory.memory;
+ linux_ctx.memory.debug_memory_size = debug_memory.size;
+#endif
+ }
+
linux_ctx.inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
#ifdef _DEBUG
@@ -502,6 +506,19 @@ main(i32 argc, char *argv[], char *envp[])
linux_add_file_watch((u8 *)DEBUG_LIB_NAME, debug_reload_library, &linux_ctx);
#endif
+ linux_ctx.memory.platform_api.add_file_watch = linux_add_file_watch;
+ linux_ctx.memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer;
+ linux_ctx.memory.platform_api.get_clipboard = x11_get_clipboard;
+ linux_ctx.memory.platform_api.set_clipboard = x11_set_clipboard;
+ linux_ctx.memory.platform_api.get_file_stats = posix_get_file_stats;
+ linux_ctx.memory.platform_api.read_file = posix_read_file;
+ linux_ctx.memory.platform_api.read = posix_read;
+ linux_ctx.memory.platform_api.set_terminal_size = posix_set_terminal_size;
+ linux_ctx.memory.platform_api.get_window_title = x11_get_window_title;
+ linux_ctx.memory.platform_api.set_window_title = x11_set_window_title;
+ linux_ctx.memory.platform_api.write = posix_write;
+ linux_ctx.memory.platform_api.path_separator = '/';
+
if (!glfwInit())
os_fatal(s8("Failed to init GLFW\n"));
glfwSetErrorCallback(glfw_error_callback);
@@ -542,23 +559,30 @@ 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;
- Range last_sel = {0};
- f64 last_time = os_get_time();
+ render_stack->input = &linux_ctx.input;
+ render_stack->terminal_memory = &linux_ctx.memory;
+ render_stack->thread_arena = arena_from_memory_block(linux_block_alloc(MB(8)));
+ render_stack->window = linux_ctx.window;
+ syscall3(SYS_futex, (iptr)&render_stack->work_futex, FUTEX_WAKE, 1);
+
+ Range last_sel = {0};
+ f64 last_time = os_get_time();
while (!glfwWindowShouldClose(linux_ctx.window)) {
if (os_child_exited(linux_ctx.child.process_id))
break;
- update_input(&linux_ctx);
-
/* TODO: cpu time excluding waiting for the vblank */
f64 current_time = os_get_time();
linux_ctx.input.dt = current_time - last_time;
last_time = current_time;
- vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input);
+ update_input(&linux_ctx);
+ if (vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input))
+ syscall3(SYS_futex, (iptr)&render_stack->work_futex, FUTEX_WAKE, 1);
Range current_sel = vtgl_active_selection(&linux_ctx.memory, 0);
if (is_valid_range(current_sel) && !equal_range(current_sel, last_sel)) {
@@ -569,8 +593,6 @@ main(i32 argc, char *argv[], char *envp[])
glfwSetX11SelectionString((c8 *)buf.buf);
last_sel = current_sel;
}
-
- glfwSwapBuffers(linux_ctx.window);
}
return 0;
diff --git a/terminal.c b/terminal.c
@@ -1,4 +1,8 @@
/* See LICENSE for copyright details */
+
+/* TODO: build own wide char tables */
+#include <wchar.h>
+
static const u8 utf8overhangmask[32] = {
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
@@ -1214,11 +1218,8 @@ push_normal_cp(Term *t, TermView *tv, u32 cp)
u32 width = 1;
if (cp > 0x7F) {
- /* TODO: this is obviously complete crap but wcwidth from libc doesn't
- * actually work so we must check from the rendered glyph */
- CachedGlyph *cg;
- get_gpu_glyph_index(t->arena_for_frame, &t->gl, &t->fa, cp, 0, 0, &cg);
- width = cg->tile_count;
+ width = wcwidth(cp);
+ ASSERT(width != -1);
}
/* NOTE: make this '>=' for fun in vis */
diff --git a/tests/test.c b/tests/test.c
@@ -2,6 +2,9 @@
#include "util.h"
#include "config.h"
+/* TODO: properly use platform layer according to os */
+#include "../platform_linux_amd64.c"
+
/* NOTE: stubs for stuff we aren't testing */
static void get_gpu_glyph_index(Arena, void *, void *, u32, u32, u32, CachedGlyph **);
@@ -331,7 +334,7 @@ static TEST_FN(working_ringbuffer)
int
main(void)
{
- Arena memory = arena_from_memory_block(posix_alloc(32 * MEGABYTE));
+ Arena memory = arena_from_memory_block(linux_block_alloc(32 * MEGABYTE));
Term term = {0};
u32 failure_count = 0;
diff --git a/util.c b/util.c
@@ -15,6 +15,22 @@ round_down_power_of_2(u32 a)
return result;
}
+static v2
+sub_v2(v2 a, v2 b)
+{
+ v2 result;
+ result.x = a.x - b.x;
+ result.y = a.y - b.y;
+ return result;
+}
+
+static f32
+length_v2(v2 a)
+{
+ f32 result = a.x * a.x + a.y * a.y;
+ return result;
+}
+
static b32
equal_iv2(iv2 a, iv2 b)
{
@@ -103,7 +119,7 @@ alloc_(Arena *a, size len, size align, size count)
{
size padding = -(uintptr_t)a->beg & (align - 1);
size available = a->end - a->beg - padding;
- if (available <= 0 || available / len <= count) {
+ if (available <= 0 || available / len < count) {
ASSERT(0);
}
@@ -409,6 +425,24 @@ selection_next(SelectionIterator *s)
return result;
}
+static b32
+any_mouse_down(TerminalInput *input)
+{
+ b32 result = input->keys[MOUSE_LEFT].ended_down ||
+ input->keys[MOUSE_RIGHT].ended_down ||
+ input->keys[MOUSE_MIDDLE].ended_down;
+ return result;
+}
+
+static b32
+all_mouse_up(TerminalInput *input)
+{
+ b32 result = !input->keys[MOUSE_LEFT].ended_down &&
+ !input->keys[MOUSE_RIGHT].ended_down &&
+ !input->keys[MOUSE_MIDDLE].ended_down;
+ return result;
+}
+
static s8
utf8_encode(u32 cp)
{
diff --git a/util.h b/util.h
@@ -22,6 +22,7 @@
#define PI 3.1415926535897932384f
#define KILOBYTE (1024ULL)
#define MEGABYTE (1024ULL * 1024ULL)
+#define MB(a) ((a) << 20ULL)
#define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a))
#define ABS(a) ((a) < 0 ? (-a) : (a))
@@ -390,15 +391,13 @@ typedef struct {
#include <immintrin.h>
+#include "vtgl.h"
#include "util.c"
#define STB_TRUETYPE_IMPLEMENTATION
#define STB_STATIC
#include "extern/stb_truetype.h"
-/* TODO: this can be moved */
-#include "vtgl.h"
-
#ifdef __unix__
#include "os_unix.c"
#else
diff --git a/vtgl.c b/vtgl.c
@@ -8,12 +8,10 @@
#include "config.h"
#include "font.c"
-
-/* TODO this should be removed */
-static u32 get_gpu_glyph_index(Arena, GLCtx *, FontAtlas *, u32, u32, enum face_style, CachedGlyph **);
-
#include "terminal.c"
+#define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);}
+
#define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba
#define VERTEX_SHADER_TEXT \
@@ -242,26 +240,14 @@ get_terminal_top_left(Term *t)
}
static void
-resize(Term *t, PlatformAPI *platform, iv2 window_size)
+resize_terminal(Term *t, PlatformAPI *platform, iv2 window_size)
{
- GLCtx *gl = &t->gl;
- gl->window_size = window_size;
-
- glViewport(0, 0, window_size.w, window_size.h);
-
- glActiveTexture(GL_TEXTURE0 + gl->fb_tex_unit);
- glBindTexture(GL_TEXTURE_2D, gl->fb_tex);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_size.w, window_size.h, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, 0);
-
- /* NOTE: reactive the glyph texture unit */
- glActiveTexture(GL_TEXTURE0);
+ iv2 old_size = t->size;
- v2 ws = v2_from_iv2(gl->window_size);
+ v2 ws = v2_from_iv2(window_size);
ws.w -= 2 * g_term_margin.w;
ws.h -= 2 * g_term_margin.h;
- iv2 old_size = t->size;
v2 cs = get_cell_size(&t->fa);
t->size.w = (i32)(ws.w / cs.w);
t->size.h = (i32)(ws.h / cs.h);
@@ -278,18 +264,38 @@ resize(Term *t, PlatformAPI *platform, 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);
- gl->flags |= NEEDS_FULL_REFILL;
-
- u32 buffer_size = t->size.w * t->size.h * sizeof(RenderCell);
- glDeleteBuffers(1, &gl->render_shader_ssbo);
- glGenBuffers(1, &gl->render_shader_ssbo);
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, gl->render_shader_ssbo);
- glBufferData(GL_SHADER_STORAGE_BUFFER, buffer_size, 0, GL_DYNAMIC_DRAW);
- glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, gl->render_shader_ssbo);
- gl->flags |= UPDATE_RENDER_BUFFER;
+ t->gl.flags |= NEEDS_FULL_REFILL;
}
platform->set_terminal_size(t->child, t->size.h, t->size.w, ws.w, ws.h);
+}
+
+static void
+resize(Term *t, PlatformAPI *platform, iv2 window_size)
+{
+ GLCtx *gl = &t->gl;
+ gl->window_size = window_size;
+
+ glViewport(0, 0, window_size.w, window_size.h);
+
+ glActiveTexture(GL_TEXTURE0 + gl->fb_tex_unit);
+ glBindTexture(GL_TEXTURE_2D, gl->fb_tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_size.w, window_size.h, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, 0);
+
+ /* NOTE: reactive the glyph texture unit */
+ glActiveTexture(GL_TEXTURE0);
+
+ u32 buffer_size = t->size.w * t->size.h * sizeof(RenderCell);
+ glDeleteBuffers(1, &gl->render_shader_ssbo);
+ glGenBuffers(1, &gl->render_shader_ssbo);
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, gl->render_shader_ssbo);
+ glBufferData(GL_SHADER_STORAGE_BUFFER, buffer_size, 0, GL_DYNAMIC_DRAW);
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, gl->render_shader_ssbo);
+ LABEL_GL_OBJECT(GL_BUFFER, gl->render_shader_ssbo, s8("RenderCells"));
+ gl->flags |= UPDATE_RENDER_BUFFER;
+
+ v2 cs = get_cell_size(&t->fa);
ShaderParameters *sp = &gl->shader_parameters;
sp->cell_size = (iv2){.w = cs.w, .h = cs.h};
@@ -501,7 +507,7 @@ measure_text(RenderCtx *rc, u32 font_id, s8 text)
* to handle all necessary padding (window and cell). Outside of here everyone should
* simply care about the terminal in terms of rows and columns (t->size). */
static void
-render_framebuffer(Term *t, RenderCell *render_buf)
+render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena arena)
{
BEGIN_TIMED_BLOCK();
@@ -514,8 +520,8 @@ render_framebuffer(Term *t, RenderCell *render_buf)
RenderCell *rc = render_buf + (row * t->size.w + col);
CachedGlyph *cg;
- rc->gpu_glyph = get_gpu_glyph_index(t->arena_for_frame, &t->gl, &t->fa,
- c->cp, 0, c->bg & FS_MASK, &cg);
+ rc->gpu_glyph = get_gpu_glyph_index(arena, &t->gl, &t->fa, c->cp, 0,
+ c->bg & FS_MASK, &cg);
rc->fg = c->fg;
rc->bg = c->bg;
@@ -541,26 +547,50 @@ render_framebuffer(Term *t, RenderCell *render_buf)
}
}
+ END_TIMED_BLOCK();
+}
+
+static void
+render_cursor(Term *t, b32 focused, Arena a)
+{
+ BEGIN_TIMED_BLOCK();
+
+ iv2 curs = t->cursor.pos;
+ Cell *c = &t->views[t->view_idx].fb.rows[curs.y][curs.x];
+ RenderCell *rc = alloc(&a, RenderCell, 3);
+
+ size rc_off = 1;
+ size length = sizeof(RenderCell);
+ size 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].fg = c->fg;
+ rc[1].bg = c->bg;
+
/* NOTE: draw cursor */
- if (!(t->mode.win & WM_HIDECURSOR) && t->scroll_offset == 0) {
- iv2 curs = t->cursor.pos;
- Cell *c = &tv->fb.rows[curs.y][curs.x];
- RenderCell *rc = render_buf + curs.y * t->size.w + curs.x;
- rc[0].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
- if ((t->mode.term & TM_ALTSCREEN) == 0)
- rc[0].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
+ if (focused && (!(t->mode.win & WM_HIDECURSOR) && t->scroll_offset == 0)) {
+ rc[1].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
+ //if ((t->mode.term & TM_ALTSCREEN) == 0)
+ // rc[1].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
if (c->bg & ATTR_WIDE) {
- rc[1].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
- if ((t->mode.term & TM_ALTSCREEN) == 0)
- rc[1].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
+ length *= 2;
+ rc[2].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
+ //if ((t->mode.term & TM_ALTSCREEN) == 0)
+ // rc[2].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
} else if (c->bg & ATTR_WDUMMY) {
- rc[-1].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
- if ((t->mode.term & TM_ALTSCREEN) == 0)
- rc[-1].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
+ rc_off = 0;
+ length *= 2;
+ offset -= sizeof(RenderCell);
+ rc[0].fg ^= SHADER_PACK_ATTR(ATTR_INVERSE);
+ //if ((t->mode.term & TM_ALTSCREEN) == 0)
+ // rc[0].fg |= SHADER_PACK_ATTR(ATTR_BLINK);
}
}
+ glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, length, rc + rc_off);
+
END_TIMED_BLOCK();
}
@@ -709,8 +739,6 @@ 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;
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, t->gl.glyph_bitmap_dim.w, t->gl.glyph_bitmap_dim.h,
- 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
return 1;
}
@@ -795,10 +823,7 @@ static b32
terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 click_count)
{
- b32 should_end_interaction = !input->keys[MOUSE_LEFT].ended_down &&
- !input->keys[MOUSE_MIDDLE].ended_down &&
- !input->keys[MOUSE_RIGHT].ended_down;
-
+ b32 should_end_interaction = all_mouse_up(input);
if (t->mode.win & WM_MOUSE_MASK) {
if (t->mode.win & WM_MOUSE_TRK)
report_mouse(t, input, should_end_interaction, 0);
@@ -1161,6 +1186,7 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize)
* 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"));
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -1175,11 +1201,13 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize)
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);
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"));
glActiveTexture(GL_TEXTURE0);
@@ -1198,7 +1226,7 @@ DEBUG_EXPORT VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection)
return result;
}
-DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step)
+DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame)
{
FRAME_MARK(input->dt);
@@ -1208,85 +1236,45 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step)
dt_for_frame = input->dt;
- t->temp_arena = begin_temp_arena(&t->arena_for_frame);
-
- if (input->executable_reloaded) {
- reload_all_shaders(memory);
- }
+ TempArena temp_arena = begin_temp_arena(&arena);
if (t->gl.flags & NEEDS_RESIZE || !equal_iv2(input->window_size, t->gl.window_size))
resize(t, &memory->platform_api, input->window_size);
- /* NOTE: handle this stuff first since it is based on what the user saw last frame */
- BEGIN_NAMED_BLOCK(mouse_and_keyboard_input);
- if (input->character_input.len) {
- if (t->scroll_offset) {
- t->scroll_offset = 0;
- t->gl.flags |= NEEDS_FULL_REFILL;
- }
- memory->platform_api.write(t->child, input->character_input, 0);
+ if (input->executable_reloaded) {
+ reload_all_shaders(memory);
}
- handle_interactions(t, input, &memory->platform_api);
-
/* NOTE: default state which can be overwritten later in the frame */
/* TODO: if (!t->ui_active) */
+ {
t->interaction.hot.type = IS_TERM;
t->interaction.hot.var = (Variable){.value = t};
-
- END_NAMED_BLOCK(mouse_and_keyboard_input);
-
- /* NOTE: this needs to be bound for blitting lines because that function can
- * access the font cache.
- * TODO: cleanup */
- glBindTexture(GL_TEXTURE_2D, t->gl.glyph_bitmap_tex);
-
- BEGIN_NAMED_BLOCK(input_from_child);
-
- if (input->data_available) {
- RingBuf *rb = &t->views[t->view_idx].log;
- s8 buffer = {.len = rb->cap - t->unprocessed_bytes, .data = rb->buf + rb->widx};
-
- size bytes_read = memory->platform_api.read(t->child, buffer, 0);
- ASSERT(bytes_read <= rb->cap);
- commit_to_rb(t->views + t->view_idx, bytes_read);
-
- t->unprocessed_bytes += bytes_read;
- s8 raw = {
- .len = t->unprocessed_bytes,
- .data = rb->buf + (rb->widx - t->unprocessed_bytes)
- };
- handle_input(t, t->arena_for_frame, raw);
- t->gl.flags |= UPDATE_RENDER_BUFFER;
}
- END_NAMED_BLOCK(input_from_child);
+ glBindTexture(GL_TEXTURE_2D, t->gl.glyph_bitmap_tex);
- if (t->gl.flags & (NEEDS_REFILL|NEEDS_FULL_REFILL)) {
- blit_lines(t, t->arena_for_frame, 0);
- t->gl.flags |= UPDATE_RENDER_BUFFER;
- }
+ BEGIN_NAMED_BLOCK(update_render);
set_projection_matrix(&t->gl);
- BEGIN_NAMED_BLOCK(update_render);
-
- RenderCtx rc = make_render_ctx(&t->arena_for_frame, &t->gl, &t->fa);
+ RenderCtx rc = make_render_ctx(&arena, &t->gl, &t->fa);
glUseProgram(t->gl.programs[SHADER_RENDER]);
glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
clear_colour();
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, t->gl.render_shader_ssbo);
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, t->gl.render_shader_ssbo);
+
if (t->gl.flags & UPDATE_RENDER_BUFFER) {
u32 cell_count = t->size.h * t->size.w;
- RenderCell *render_buf = alloc(&t->arena_for_frame, RenderCell, cell_count);
- render_framebuffer(t, render_buf);
-
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, t->gl.render_shader_ssbo);
- glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, t->gl.render_shader_ssbo);
+ 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);
t->gl.flags &= ~UPDATE_RENDER_BUFFER;
}
+ render_cursor(t, input->window_focused, arena);
ShaderParameters *sp = &t->gl.shader_parameters;
sp->blink_parameter += 2 * PI * g_blink_speed * dt_for_frame;
@@ -1327,12 +1315,72 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step)
glUseProgram(t->gl.programs[SHADER_RECTS]);
BEGIN_NAMED_BLOCK(debug_overlay);
- debug_frame_end(memory, input, &rc);
+ draw_debug_overlay(memory, input, &rc);
END_NAMED_BLOCK(debug_overlay);
+ end_temp_arena(temp_arena);
+
+ END_TIMED_BLOCK();
+}
+
+DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step)
+{
+ BEGIN_TIMED_BLOCK();
+
+ Term *t = memory->memory;
+
+ dt_for_frame = input->dt;
+
+ 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))
+ resize_terminal(t, &memory->platform_api, input->window_size);
+
+ BEGIN_NAMED_BLOCK(mouse_and_keyboard_input);
+ if (input->character_input.len) {
+ if (t->scroll_offset) {
+ t->scroll_offset = 0;
+ t->gl.flags |= NEEDS_FULL_REFILL;
+ }
+ memory->platform_api.write(t->child, input->character_input, 0);
+ }
+
+ handle_interactions(t, input, &memory->platform_api);
+ END_NAMED_BLOCK(mouse_and_keyboard_input);
+
+ BEGIN_NAMED_BLOCK(input_from_child);
+ if (input->data_available) {
+ RingBuf *rb = &t->views[t->view_idx].log;
+ s8 buffer = {.len = rb->cap - t->unprocessed_bytes, .data = rb->buf + rb->widx};
+
+ size bytes_read = memory->platform_api.read(t->child, buffer, 0);
+ ASSERT(bytes_read <= rb->cap);
+ commit_to_rb(t->views + t->view_idx, bytes_read);
+
+ t->unprocessed_bytes += bytes_read;
+ s8 raw = {
+ .len = t->unprocessed_bytes,
+ .data = rb->buf + (rb->widx - t->unprocessed_bytes)
+ };
+ handle_input(t, t->arena_for_frame, raw);
+ t->gl.flags |= UPDATE_RENDER_BUFFER;
+ }
+ END_NAMED_BLOCK(input_from_child);
+
+ if (t->gl.flags & (NEEDS_REFILL|NEEDS_FULL_REFILL)) {
+ blit_lines(t, t->arena_for_frame, 0);
+ t->gl.flags |= UPDATE_RENDER_BUFFER;
+ }
+
end_temp_arena(t->temp_arena);
+ BEGIN_NAMED_BLOCK(debug_end_frame);
+ debug_frame_end(memory);
+ END_NAMED_BLOCK(debug_end_frame);
+
END_TIMED_BLOCK();
+
+ return (t->gl.flags & UPDATE_RENDER_BUFFER) || input->window_refreshed;
}
#ifdef _DEBUG
diff --git a/vtgl.h b/vtgl.h
@@ -214,15 +214,20 @@ typedef struct {
} ButtonState;
typedef struct TerminalInput {
+ ButtonState keys[INPUT_KEY_COUNT];
+
+ iv2 window_size;
+
b32 data_available;
b32 executable_reloaded;
- iv2 window_size;
+ b32 window_refreshed;
+ b32 window_focused;
+
+ u32 modifiers;
v2 mouse;
v2 last_mouse;
v2 mouse_scroll;
- ButtonState keys[INPUT_KEY_COUNT];
- u32 modifiers;
/* TODO: do we want the 32bit codepoints instead? */
s8 character_input;
@@ -246,9 +251,12 @@ typedef struct TerminalMemory {
#define VTGL_INITIALIZE_FN(name) iv2 name(TerminalMemory *memory, 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)
+#define VTGL_FRAME_STEP_FN(name) b32 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);