vtgl

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

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:
Mbuild.sh | 2+-
Mdebug.c | 4+---
Mdebug.h | 4+++-
Mos_unix.c | 21+--------------------
Aplatform_linux_amd64.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplatform_linux_common.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplatform_linux_x11.c | 164+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mterminal.c | 11++++++-----
Mtests/test.c | 5++++-
Mutil.c | 36+++++++++++++++++++++++++++++++++++-
Mutil.h | 5++---
Mvtgl.c | 258+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mvtgl.h | 16++++++++++++----
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);