vtgl

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

Commit: 806951a9deed6716eec91a919d01e70aafb84b8e
Parent: 15c48ce51d89671e39703c1634590433d72aba7e
Author: Randy Palamar
Date:   Wed,  6 Nov 2024 07:40:20 -0700

start rigidizing the terminal code/platform code boundary

its time to start cleaning up the haphazard calling back and forth

Diffstat:
Mbuild.sh | 2+-
Mdebug.c | 18++++++++++++------
Mdebug.h | 9+++++----
Dmain.c | 468-------------------------------------------------------------------------------
Mos_unix.c | 74+++++++++++++++++++++++++++++++++-----------------------------------------
Aplatform_linux_x11.c | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest.c | 4++--
Mutil.c | 16++++++++++++++++
Mutil.h | 11++++++++---
Mvtgl.c | 266++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Avtgl.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 727 insertions(+), 595 deletions(-)

diff --git a/build.sh b/build.sh @@ -39,5 +39,5 @@ testcflags="${testcflags} -Wno-unused-variable -Wno-unused-function -Wno-undefin testldflags="-lm -static" [ ${build_lib} ] && ${cc} ${cflags} -fPIC vtgl.c -o vtgl.so ${ldflags} -shared -${cc} ${cflags} -o vtgl main.c ${ldflags} +${cc} ${cflags} -o vtgl platform_linux_x11.c ${ldflags} ${cc} ${testcflags} -o test test.c ${testldflags} diff --git a/debug.c b/debug.c @@ -42,7 +42,8 @@ dump_lines_to_file(Term *t) s8 l = line_to_s8(line, &tv->log); stream_push_s8(&out, l); if (out.errors) { - os_write(file, stream_to_s8(&out), file_offset); + /* TODO: cleanup */ + posix_write(file, stream_to_s8(&out), file_offset); file_offset += out.widx; out.widx = 0; stream_push_s8(&out, l); @@ -50,7 +51,8 @@ dump_lines_to_file(Term *t) } stream_push_byte(&out, '\n'); - os_write(file, stream_to_s8(&out), file_offset); + /* TODO: cleanup */ + posix_write(file, stream_to_s8(&out), file_offset); os_close(file); } @@ -302,13 +304,13 @@ draw_debug_overlay(Term *t, RenderCtx *rc) DebugState *ds = t->debug_state; - v2 bar_chart_top_left = t->gl.window_size; + v2 bar_chart_top_left = v2_from_iv2(t->gl.window_size); bar_chart_top_left.x *= 0.5; draw_debug_bar_chart(t, ds, rc, bar_chart_top_left, 0.25 * t->gl.window_size.w); Arena memory = *ds->temp_memory.arena; - v2 ws = t->gl.window_size; + v2 ws = v2_from_iv2(t->gl.window_size); static GlyphCacheStats glyph_stats; static Rect r; @@ -372,8 +374,11 @@ draw_debug_overlay(Term *t, RenderCtx *rc) } static void -debug_init(DebugState *ds) +debug_init(TerminalMemory *memory) { + DebugState *ds = memory->debug_memory; + Arena a = {.beg = (u8 *)(ds + 1), .end = memory->debug_memory + memory->debug_memory_size}; + ds->memory = a; ds->temp_memory = begin_temp_arena(&ds->memory); ds->initialized = 1; ds->bar_thickness = 14; @@ -383,7 +388,8 @@ debug_init(DebugState *ds) DEBUG_EXPORT DEBUG_BEGIN_FRAME_FN(debug_begin_frame) { + DebugState *debug_state = memory->debug_memory; if (!debug_state->initialized) - debug_init(debug_state); + debug_init(memory); FRAME_MARK(wall_clock_elapsed); } diff --git a/debug.h b/debug.h @@ -71,10 +71,13 @@ typedef struct { OpenDebugBlock *first_free_block; } DebugState; -#define DEBUG_BEGIN_FRAME_FN(name) void name(DebugState *debug_state, f32 wall_clock_elapsed) +/* TODO: this can probably be removed */ +typedef struct TerminalMemory TerminalMemory; + +#define DEBUG_BEGIN_FRAME_FN(name) void name(TerminalMemory *memory, f32 wall_clock_elapsed) typedef DEBUG_BEGIN_FRAME_FN(debug_begin_frame_fn); -#define DEBUG_END_FRAME_FN(name) void name(DebugState *debug_state, v2 window_size) +#define DEBUG_END_FRAME_FN(name) void name(TerminalMemory *memory) typedef DEBUG_END_FRAME_FN(debug_end_frame_fn); #ifndef _DEBUG @@ -88,7 +91,6 @@ typedef DEBUG_END_FRAME_FN(debug_end_frame_fn); #define BEGIN_NAMED_BLOCK(...) #define END_NAMED_BLOCK(...) -#define debug_init(...) #define draw_debug_overlay(...) DEBUG_BEGIN_FRAME_FN(debug_begin_frame) {} @@ -158,7 +160,6 @@ static DebugTable g_debug_table; typedef struct Term Term; typedef struct RenderCtx RenderCtx; -static void debug_init(DebugState *ds); static void dump_lines_to_file(Term *t); static void draw_debug_overlay(Term *t, RenderCtx *rc); #endif diff --git a/main.c b/main.c @@ -1,468 +0,0 @@ -/* See LICENSE for copyright details */ -#define GL_GLEXT_PROTOTYPES 1 -#include <GLFW/glfw3.h> - -#include "util.h" - -#ifndef VERSION -#define VERSION "unknown" -#endif - -#ifndef _DEBUG -#define do_debug(...) -#include "vtgl.c" -#else -#include <dlfcn.h> -#include <time.h> - -static char *libname = "./vtgl.so"; -static void *libhandle; - -typedef void do_terminal_fn(Term *, f32); -typedef void reset_terminal_fn(Term *); -typedef iv2 init_term_fn(Term *, Arena *, iv2); - -#define LIB_FNS \ - X(debug_begin_frame) \ - X(debug_end_frame) \ - X(do_terminal) \ - X(init_term) \ - X(reset_terminal) - -#define X(name) static name ## _fn *name; -LIB_FNS -#undef X - -static void -load_library(const char *lib, Stream *err) -{ - s8 nl = s8("\n"); - /* NOTE: glibc sucks and will crash if this is NULL */ - if (libhandle) - dlclose(libhandle); - libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL); - if (!libhandle) - stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl}); - #define X(name) \ - name = dlsym(libhandle, #name); \ - if (!name) stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), \ - c_str_to_s8(dlerror()), nl}); - LIB_FNS - #undef X -} - -static void -do_debug(Term *t, Stream *err) -{ - static os_file_stats updated; - os_file_stats test = os_get_file_stats(libname); - - if (os_filetime_changed(test.timestamp, updated.timestamp)) { - updated = test; - /* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */ - struct timespec sleep_time = { .tv_nsec = 100e6 }; - nanosleep(&sleep_time, &sleep_time); - load_library(libname, err); - if (t) reset_terminal(t); - stream_push_s8(err, s8("Reloaded Main Program\n")); - } - - /* TODO: clear debug memory? */ - - if (err->widx) { - os_write_err_msg(stream_to_s8(err)); - err->widx = 0; - } -} - -#endif /* _DEBUG */ - -static 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) { - case GL_DEBUG_SEVERITY_HIGH: stream_push_s8(err, s8("HIGH]: ")); break; - case GL_DEBUG_SEVERITY_MEDIUM: stream_push_s8(err, s8("MEDIUM]: ")); break; - case GL_DEBUG_SEVERITY_LOW: stream_push_s8(err, s8("LOW]: ")); break; - case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break; - default: stream_push_s8(err, s8("INVALID]: ")); break; - } - stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg}); - stream_push_byte(err, '\n'); - os_write_err_msg(stream_to_s8(err)); - err->widx = 0; -} - - -static void -error_callback(int code, const char *desc) -{ - u8 buf[256]; - Stream err = {.cap = sizeof(buf), .buf = buf}; - stream_push_s8(&err, s8("GLFW Error (0x")); - stream_push_hex_u64(&err, code); - stream_push_s8(&err, s8("): ")); - os_write_err_msg(stream_to_s8(&err)); - os_write_err_msg(c_str_to_s8((char *)desc)); - os_write_err_msg(s8("\n")); -} - -static u32 -gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba) -{ - /* TODO: logging */ - u32 result; - glGenTextures(1, &result); - glBindTexture(GL_TEXTURE_2D, result); - glTexImage2D(GL_TEXTURE_2D, 0, format, size.w, size.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - return result; -} - -static void -init_window(Term *t, Arena arena, iv2 window_size) -{ - t->gl.window_size = (v2){.w = window_size.w, .h = window_size.h}; - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - t->gl.window = glfwCreateWindow(t->gl.window_size.w, t->gl.window_size.h, "vtgl", NULL, NULL); - if (!t->gl.window) { - glfwTerminate(); - os_fatal(s8("Failed to spawn GLFW window\n")); - } - glfwMakeContextCurrent(t->gl.window); - glfwSetWindowUserPointer(t->gl.window, t); - glfwSetWindowAttrib(t->gl.window, GLFW_RESIZABLE, GLFW_TRUE); - - /* NOTE: make sure the terminal performs a resize after initialization. The - * terminal code handles this because it also needs to communicate the size - * to the child process */ - t->gl.flags |= NEEDS_RESIZE; - - /* TODO: swap interval is not needed because we will sleep on waiting for terminal input */ - glfwSwapInterval(1); - - /* NOTE: Set up OpenGL Render Pipeline */ - glDebugMessageCallback(gl_debug_logger, &t->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); - - glGenBuffers(ARRAY_COUNT(t->gl.vbos), t->gl.vbos); - - RenderPushBuffer *rpb = NULL; - /* NOTE: vertex position buffer */ - glBindBuffer(GL_ARRAY_BUFFER, t->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]); - 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]); - glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->colours), 0, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 4, GL_FLOAT, 0, 0, 0); - - /* NOTE: fill in element index buffer */ - i32 *element_indices = alloc(&arena, i32, 6 * ARRAY_COUNT(rpb->positions)); - for (i32 i = 0, j = 0; i < 6 * ARRAY_COUNT(rpb->positions); i += 6, j++) { - element_indices[i + 0] = 4 * j; - element_indices[i + 1] = 4 * j + 1; - element_indices[i + 2] = 4 * j + 2; - element_indices[i + 3] = 4 * j; - element_indices[i + 4] = 4 * j + 2; - element_indices[i + 5] = 4 * j + 3; - } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t->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); - /* 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); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - /* 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 = {.x = t->gl.window_size.w, .y = t->gl.window_size.h}; - t->gl.fb_tex = gen_2D_texture(ws, GL_RGBA, GL_NEAREST, 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t->gl.fb_tex, 0); - - 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); - - glActiveTexture(GL_TEXTURE0); -} - -static u32 -compile_shader(Arena a, u32 type, s8 shader) -{ - u32 sid = glCreateShader(type); - - glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len); - glCompileShader(sid); - - i32 res = 0; - glGetShaderiv(sid, GL_COMPILE_STATUS, &res); - if (res != GL_TRUE) { - i32 len; - glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); - s8 err = s8alloc(&a, len); - glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data); - os_write_err_msg(s8("compile_shader: ")); - os_write_err_msg(err); - glDeleteShader(sid); - return 0; - } - - return sid; -} - -static u32 -program_from_shader_text(s8 vertex, s8 fragment, Arena a) -{ - u32 pid = glCreateProgram(); - - u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex); - if (vid == 0) { - glDeleteProgram(pid); - return 0; - } - - u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment); - if (fid == 0) { - glDeleteShader(vid); - glDeleteProgram(pid); - return 0; - } - - glAttachShader(pid, vid); - glAttachShader(pid, fid); - glLinkProgram(pid); - glValidateProgram(pid); - glUseProgram(pid); - glDeleteShader(vid); - glDeleteShader(fid); - - return pid; -} - -static void -check_shaders(GLCtx *gl, Arena a, Stream *err) -{ - static char *fs_name[SHADER_LAST] = { - "frag_render.glsl", - "frag_for_rects.glsl", - "frag_post.glsl", - }; - static char *vs_name[SHADER_LAST] = { - "vert_for_rects.glsl", - "vert_for_rects.glsl", - "vert_for_rects.glsl", - }; - - static os_file_stats fs_stats[SHADER_LAST], vs_stats[SHADER_LAST]; - static struct { - s8 name; - u32 flag; - } map[SHADER_LAST] = { - [SHADER_RENDER] = {.name = s8("Render"), .flag = 0}, - [SHADER_RECTS] = {.name = s8("Rects"), .flag = 0}, - [SHADER_POST] = {.name = s8("Post"), .flag = UPDATE_POST_UNIFORMS}, - }; - - for (u32 i = 0; i < SHADER_LAST; i++) { - os_file_stats fs_test = os_get_file_stats(fs_name[i]); - os_file_stats vs_test = os_get_file_stats(vs_name[i]); - - if (!os_filetime_changed(fs_test.timestamp, fs_stats[i].timestamp) && - !os_filetime_changed(vs_test.timestamp, vs_stats[i].timestamp)) - continue; - - stream_push_s8s(err, 3, (s8 []){s8("Reloading "), map[i].name, s8(" Shader!\n")}); - fs_stats[i] = fs_test; - vs_stats[i] = vs_test; - - s8 vs_text = os_read_file(&a, vs_name[i], vs_stats[i].filesize); - s8 fs_text = os_read_file(&a, fs_name[i], fs_stats[i].filesize); - ASSERT(vs_text.len > 0 && fs_text.len > 0); - - u32 program = program_from_shader_text(vs_text, fs_text, a); - if (!program) - continue; - glDeleteProgram(gl->programs[i]); - gl->programs[i] = program; - gl->flags |= map[i].flag; - stream_push_s8s(err, 2, (s8 []){map[i].name, s8(" Program Updated!\n")}); - } - - if (err->widx) { - os_write_err_msg(stream_to_s8(err)); - err->widx = 0; - } -} - -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)); -} - -i32 -main(i32 argc, char *argv[], char *envp[]) -{ - Arena memory = os_new_arena(16 * MEGABYTE); - Term term = {0}; - #ifdef _DEBUG - { - Arena debug_memory = os_new_arena(128 * MEGABYTE); - term.debug_state = alloc_(&debug_memory, sizeof(*term.debug_state), 64, 1); - term.debug_state->memory = debug_memory; - } - #endif - - term.error_stream = stream_alloc(&memory, MEGABYTE / 4); - - iv2 cells = {.x = -1, .y = -1}; - - char *argv0 = *argv++; - argc--; - struct conversion_result cres; - for (i32 i = 0; i < argc; i++) { - char *arg = argv[i]; - if (!arg || !arg[0]) - usage(argv0, &term.error_stream); - if (arg[0] != '-') - break; - arg++; - switch (arg[0]) { - case 'g': - if (!argv[i + 1]) - usage(argv0, &term.error_stream); - cres = i32_from_cstr(argv[i + 1], 'x'); - if (cres.status == CR_SUCCESS) - cells.w = cres.i; - cres = i32_from_cstr(cres.unparsed, 0); - if (cres.status == CR_SUCCESS) - cells.h = cres.i; - if (cells.w <= 0 || cells.h <= 0) { - stream_push_s8(&term.error_stream, s8("ignoring malformed geometry: ")); - stream_push_s8(&term.error_stream, c_str_to_s8(argv[i + 1])); - stream_push_byte(&term.error_stream, '\n'); - } - argv++; - argc--; - break; - case 'v': - stream_push_s8s(&term.error_stream, 2, - (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")}); - os_fatal(stream_to_s8(&term.error_stream)); - default: - usage(argv0, &term.error_stream); - } - } - if (term.error_stream.widx) { - os_write_err_msg(stream_to_s8(&term.error_stream)); - term.error_stream.widx = 0; - } - - do_debug(NULL, &term.error_stream); - - if (!glfwInit()) - os_fatal(s8("Failed to init GLFW\n")); - glfwSetErrorCallback(error_callback); - - GLFWmonitor *mon = glfwGetPrimaryMonitor(); - if (!mon) { - glfwTerminate(); - os_fatal(s8("Failed to get GLFW monitor\n")); - } - - iv2 ws; - glfwGetMonitorWorkarea(mon, NULL, NULL, &ws.w, &ws.h); - term.gl.glyph_bitmap_dim = ws; - - iv2 requested_size = init_term(&term, &memory, cells); - if (requested_size.w > 0 && requested_size.h > 0) { - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); - ws = requested_size; - } - - init_window(&term, memory, ws); - - os_alloc_ring_buffer(&term.views[0].log, BACKLOG_SIZE); - line_buf_alloc(&term.views[0].lines, &memory, term.views[0].log.buf, term.cursor.style, - BACKLOG_LINES); - os_alloc_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE); - line_buf_alloc(&term.views[1].lines, &memory, term.views[1].log.buf, term.cursor.style, - ALT_BACKLOG_LINES); - - term.child = os_fork_child("/bin/sh"); - - reset_terminal(&term); - - f64 last_time = os_get_time(); - while (!glfwWindowShouldClose(term.gl.window)) { - do_debug(&term, &term.error_stream); - - /* TODO: cpu time excluding waiting for the vblank */ - f64 current_time = os_get_time(); - f32 dt = current_time - last_time; - last_time = current_time; - - debug_begin_frame(term.debug_state, dt); - - check_shaders(&term.gl, memory, &term.error_stream); - - term.arena_for_frame = memory; - - glfwPollEvents(); - - do_terminal(&term, dt); - - debug_end_frame(term.debug_state, term.gl.window_size); - - glfwSwapBuffers(term.gl.window); - } - - return 0; -} - -#ifdef _DEBUG -/* NOTE: shut up compiler warning that can't be disabled */ -DebugMetadata debug_metadata[__COUNTER__]; -#endif diff --git a/os_unix.c b/os_unix.c @@ -18,9 +18,9 @@ typedef struct { } os_file_stats; typedef struct { - i32 fd; - pid_t pid; -} os_child; + iptr handle; + iptr process_id; +} posix_platform_process; #define OS_MAP_READ PROT_READ #define OS_MAP_PRIVATE MAP_PRIVATE @@ -105,8 +105,7 @@ os_open(u8 *name, u32 attr) return result; } -static b32 -os_write(iptr file, s8 raw, size offset) +static PLATFORM_WRITE_FN(posix_write) { size result = pwrite(file, raw.data, raw.len, offset); return result == raw.len; @@ -118,6 +117,19 @@ os_close(iptr file) close(file); } +static PLATFORM_READ_FN(posix_read) +{ + size r = 0, remaining = buffer.len, total_bytes_read = 0; + + do { + remaining -= r; + total_bytes_read += r; + r = read(file, buffer.data + total_bytes_read, remaining); + } while (r != -1 && remaining != 0); + + return total_bytes_read; +} + static s8 os_read_file(Arena *a, char *name, size filesize) { @@ -126,7 +138,7 @@ os_read_file(Arena *a, char *name, size filesize) return (s8){0}; s8 text = s8alloc(a, filesize); - size rlen = read(fd, text.data, text.len); + size rlen = posix_read(fd, text, 0); close(fd); if (text.len != rlen) @@ -159,8 +171,7 @@ os_map_file(char *path, i32 mode, i32 perm) return res; } -static void -os_alloc_ring_buffer(RingBuf *rb, size capacity) +static PLATFORM_ALLOCATE_RING_BUFFER_FN(posix_allocate_ring_buffer) { size pagesize = sysconf(_SC_PAGESIZE); if (capacity % pagesize != 0) @@ -272,7 +283,7 @@ execsh(char *defcmd) _exit(1); } -static os_child +static posix_platform_process os_fork_child(char *cmd) { i32 cfd; @@ -295,72 +306,53 @@ os_fork_child(char *cmd) if (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) == -1) os_fatal(s8("os_fork_child: fcntl: F_SETFL\n")); - return (os_child){ .pid = pid, .fd = cfd }; + return (posix_platform_process){.process_id = pid, .handle = cfd}; } static b32 -os_child_data_available(os_child c) +os_child_data_available(iptr c) { b32 result = 0; struct timespec timeout = {0}; fd_set rfd; FD_ZERO(&rfd); - FD_SET(c.fd, &rfd); + FD_SET(c, &rfd); - pselect(c.fd+1, &rfd, NULL, NULL, &timeout, NULL); + pselect(c + 1, &rfd, NULL, NULL, &timeout, NULL); - result = FD_ISSET(c.fd, &rfd) != 0; + result = FD_ISSET(c, &rfd) != 0; return result; } static b32 -os_child_exited(os_child c) +os_child_exited(iptr pid) { i32 r, status; - r = waitpid(c.pid, &status, WNOHANG); - return r == c.pid && WIFEXITED(status); -} - -static size -os_read_from_child(os_child c, TermView *tv, size unprocessed_bytes) -{ - RingBuf *rb = &tv->log; - size r = 0, total_bytes_read = 0, remaining = rb->cap - unprocessed_bytes; - - ASSERT(rb->widx < rb->cap); - do { - remaining -= r; - total_bytes_read += r; - r = read(c.fd, rb->buf + rb->widx + total_bytes_read, remaining); - } while (r != -1); - ASSERT(total_bytes_read <= rb->cap); - - commit_to_rb(tv, total_bytes_read); - - return total_bytes_read; + r = waitpid(pid, &status, WNOHANG); + return r == pid && WIFEXITED(status); } static void -os_child_put_s8(os_child c, s8 text) +os_child_put_s8(iptr c, s8 text) { - write(c.fd, text.data, text.len); + write(c, text.data, text.len); } static void -os_child_put_char(os_child c, u32 cp) +os_child_put_char(iptr c, u32 cp) { os_child_put_s8(c, utf8_encode(cp)); } static void -os_set_term_size(os_child c, u32 rows, u32 cols, i32 x, i32 y) +os_set_term_size(iptr child, u32 rows, u32 cols, i32 x, i32 y) { struct winsize ws; ws.ws_col = cols; ws.ws_row = rows; ws.ws_xpixel = x; ws.ws_ypixel = y; - if (ioctl(c.fd, TIOCSWINSZ, &ws) < 0) + if (ioctl(child, TIOCSWINSZ, &ws) < 0) os_write_err_msg(s8("os_set_term_size\n")); } diff --git a/platform_linux_x11.c b/platform_linux_x11.c @@ -0,0 +1,399 @@ +/* See LICENSE for copyright details */ +#define GL_GLEXT_PROTOTYPES 1 +#include <GLFW/glfw3.h> + +#include "util.h" + +#include "vtgl.h" + +#ifndef VERSION +#define VERSION "unknown" +#endif + +#ifndef _DEBUG +#define do_debug(...) +#include "vtgl.c" +#else +#include <dlfcn.h> +#include <time.h> + +static char *libname = "./vtgl.so"; +static void *libhandle; + +/* TODO: cleanup */ +typedef void reset_terminal_fn(TerminalMemory *); + +#define LIB_FNS \ + X(debug_begin_frame) \ + X(debug_end_frame) \ + X(reset_terminal) \ + X(vtgl_initialize) \ + X(vtgl_frame_step) + +#define X(name) static name ## _fn *name; +LIB_FNS +#undef X + +static void +load_library(const char *lib, Stream *err) +{ + s8 nl = s8("\n"); + /* NOTE: glibc sucks and will crash if this is NULL */ + if (libhandle) + dlclose(libhandle); + libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL); + if (!libhandle) + stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl}); + #define X(name) \ + name = dlsym(libhandle, #name); \ + if (!name) stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), \ + c_str_to_s8(dlerror()), nl}); + LIB_FNS + #undef X +} + +static void +do_debug(TerminalMemory *t, Stream *err) +{ + static os_file_stats updated; + os_file_stats test = os_get_file_stats(libname); + + if (os_filetime_changed(test.timestamp, updated.timestamp)) { + updated = test; + /* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */ + struct timespec sleep_time = { .tv_nsec = 100e6 }; + nanosleep(&sleep_time, &sleep_time); + load_library(libname, err); + if (t) reset_terminal(t); + stream_push_s8(err, s8("Reloaded Main Program\n")); + } + + /* TODO: clear debug memory? */ + + if (err->widx) { + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; + } +} + +#endif /* _DEBUG */ + +static void +glfw_error_callback(int code, const char *desc) +{ + u8 buf[256]; + Stream err = {.cap = sizeof(buf), .buf = buf}; + stream_push_s8(&err, s8("GLFW Error (0x")); + stream_push_hex_u64(&err, code); + stream_push_s8(&err, s8("): ")); + os_write_err_msg(stream_to_s8(&err)); + os_write_err_msg(c_str_to_s8((char *)desc)); + os_write_err_msg(s8("\n")); +} + +/* NOTE: called when the window was resized */ +static void +fb_callback(GLFWwindow *win, i32 w, i32 h) +{ + //TerminalInput *input = glfwGetWindowUserPointer(win); + tmp_user_ctx *ctx = glfwGetWindowUserPointer(win); + ctx->input->window_size = (iv2){.w = w, .h = h}; +} + +static GLFWwindow * +init_window(tmp_user_ctx *ctx, iv2 window_size) +{ + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + #ifdef _DEBUG + glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE); + #endif + + GLFWwindow *window = glfwCreateWindow(window_size.w, window_size.h, "vtgl", NULL, NULL); + if (!window) { + glfwTerminate(); + os_fatal(s8("Failed to spawn GLFW window\n")); + } + glfwMakeContextCurrent(window); + glfwSetWindowUserPointer(window, ctx); + glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_TRUE); + + /* TODO: swap interval is not needed because we will sleep on waiting for terminal input */ + glfwSwapInterval(1); + + glfwSetFramebufferSizeCallback(window, fb_callback); + + return window; +} + +static u32 +compile_shader(Arena a, u32 type, s8 shader) +{ + u32 sid = glCreateShader(type); + + glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len); + glCompileShader(sid); + + i32 res = 0; + glGetShaderiv(sid, GL_COMPILE_STATUS, &res); + if (res != GL_TRUE) { + i32 len; + glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); + s8 err = s8alloc(&a, len); + glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data); + os_write_err_msg(s8("compile_shader: ")); + os_write_err_msg(err); + glDeleteShader(sid); + return 0; + } + + return sid; +} + +static u32 +program_from_shader_text(s8 vertex, s8 fragment, Arena a) +{ + u32 pid = glCreateProgram(); + + u32 vid = compile_shader(a, GL_VERTEX_SHADER, vertex); + if (vid == 0) { + glDeleteProgram(pid); + return 0; + } + + u32 fid = compile_shader(a, GL_FRAGMENT_SHADER, fragment); + if (fid == 0) { + glDeleteShader(vid); + glDeleteProgram(pid); + return 0; + } + + glAttachShader(pid, vid); + glAttachShader(pid, fid); + glLinkProgram(pid); + glValidateProgram(pid); + glUseProgram(pid); + glDeleteShader(vid); + glDeleteShader(fid); + + return pid; +} + +static void +check_shaders(GLCtx *gl, Arena a, Stream *err) +{ + static char *fs_name[SHADER_LAST] = { + "frag_render.glsl", + "frag_for_rects.glsl", + "frag_post.glsl", + }; + static char *vs_name[SHADER_LAST] = { + "vert_for_rects.glsl", + "vert_for_rects.glsl", + "vert_for_rects.glsl", + }; + + static os_file_stats fs_stats[SHADER_LAST], vs_stats[SHADER_LAST]; + static struct { + s8 name; + u32 flag; + } map[SHADER_LAST] = { + [SHADER_RENDER] = {.name = s8("Render"), .flag = 0}, + [SHADER_RECTS] = {.name = s8("Rects"), .flag = 0}, + [SHADER_POST] = {.name = s8("Post"), .flag = UPDATE_POST_UNIFORMS}, + }; + + for (u32 i = 0; i < SHADER_LAST; i++) { + os_file_stats fs_test = os_get_file_stats(fs_name[i]); + os_file_stats vs_test = os_get_file_stats(vs_name[i]); + + if (!os_filetime_changed(fs_test.timestamp, fs_stats[i].timestamp) && + !os_filetime_changed(vs_test.timestamp, vs_stats[i].timestamp)) + continue; + + stream_push_s8s(err, 3, (s8 []){s8("Reloading "), map[i].name, s8(" Shader!\n")}); + fs_stats[i] = fs_test; + vs_stats[i] = vs_test; + + s8 vs_text = os_read_file(&a, vs_name[i], vs_stats[i].filesize); + s8 fs_text = os_read_file(&a, fs_name[i], fs_stats[i].filesize); + ASSERT(vs_text.len > 0 && fs_text.len > 0); + + u32 program = program_from_shader_text(vs_text, fs_text, a); + if (!program) + continue; + glDeleteProgram(gl->programs[i]); + gl->programs[i] = program; + gl->flags |= map[i].flag; + stream_push_s8s(err, 2, (s8 []){map[i].name, s8(" Program Updated!\n")}); + } + + if (err->widx) { + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; + } +} + +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)); +} + +i32 +main(i32 argc, char *argv[], char *envp[]) +{ + TerminalMemory term_memory = {0}; + { + /* TODO: remove this step */ + Arena tmp = os_new_arena(16 * MEGABYTE); + term_memory.memory = tmp.beg; + term_memory.memory_size = tmp.end - tmp.beg; + } + #ifdef _DEBUG + { + Arena debug_memory = os_new_arena(128 * MEGABYTE); + term_memory.debug_memory = debug_memory.beg; + term_memory.debug_memory_size = debug_memory.end - debug_memory.beg; + } + #endif + + term_memory.platform_api.read = posix_read; + term_memory.platform_api.write = posix_write; + term_memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer; + + Arena error_arena = os_new_arena(MEGABYTE / 4); + Stream error_stream = arena_stream(error_arena); + + iv2 cells = {.x = -1, .y = -1}; + + char *argv0 = *argv++; + argc--; + struct conversion_result cres; + for (i32 i = 0; i < argc; i++) { + char *arg = argv[i]; + if (!arg || !arg[0]) + usage(argv0, &error_stream); + if (arg[0] != '-') + break; + arg++; + switch (arg[0]) { + case 'g': + if (!argv[i + 1]) + usage(argv0, &error_stream); + cres = i32_from_cstr(argv[i + 1], 'x'); + if (cres.status == CR_SUCCESS) + cells.w = cres.i; + cres = i32_from_cstr(cres.unparsed, 0); + if (cres.status == CR_SUCCESS) + cells.h = cres.i; + if (cells.w <= 0 || cells.h <= 0) { + stream_push_s8(&error_stream, s8("ignoring malformed geometry: ")); + stream_push_s8(&error_stream, c_str_to_s8(argv[i + 1])); + stream_push_byte(&error_stream, '\n'); + } + argv++; + argc--; + break; + case 'v': + stream_push_s8s(&error_stream, 2, + (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")}); + os_fatal(stream_to_s8(&error_stream)); + default: + usage(argv0, &error_stream); + } + } + if (error_stream.widx) { + os_write_err_msg(stream_to_s8(&error_stream)); + error_stream.widx = 0; + } + + do_debug(NULL, &error_stream); + + if (!glfwInit()) + os_fatal(s8("Failed to init GLFW\n")); + glfwSetErrorCallback(glfw_error_callback); + + GLFWmonitor *mon = glfwGetPrimaryMonitor(); + if (!mon) { + glfwTerminate(); + os_fatal(s8("Failed to get GLFW monitor\n")); + } + + iv2 ws; + glfwGetMonitorWorkarea(mon, NULL, NULL, &ws.w, &ws.h); + + TerminalInput input = {0}; + + /* TODO: delete !!!! */ + tmp_user_ctx ctx; + ctx.memory = &term_memory; + ctx.input = &input; + + GLFWwindow *window = init_window(&ctx, ws); + + posix_platform_process child = os_fork_child("/bin/sh"); + + iv2 requested_size = vtgl_initialize(&term_memory, child.handle, cells, ws); + if (requested_size.w > 0 && requested_size.h > 0 && + (requested_size.w != ws.w || requested_size.h != ws.h)) + { + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + glfwSetWindowSize(window, requested_size.w, requested_size.h); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + ws = requested_size; + } + + input.window_size = ws; + + /* TODO: remove */ + { + Term *t = term_memory.memory; + t->gl.window = window; + reset_terminal(&term_memory); + } + + f64 last_time = os_get_time(); + while (!glfwWindowShouldClose(window)) { + do_debug(&term_memory, &error_stream); + + /* TODO: cpu time excluding waiting for the vblank */ + f64 current_time = os_get_time(); + input.dt = current_time - last_time; + last_time = current_time; + + debug_begin_frame(&term_memory, input.dt); + + /* TODO: push this into the terminal */ + { + Term *t = term_memory.memory; + check_shaders(&t->gl, t->arena_for_frame, &error_stream); + } + + glfwPollEvents(); + + input.data_available = os_child_data_available(child.handle); + if (os_child_exited(child.process_id)) + break; + + vtgl_frame_step(&term_memory, &input); + + debug_end_frame(&term_memory); + + glfwSwapBuffers(window); + } + + return 0; +} + +#ifdef _DEBUG +/* NOTE: shut up compiler warning that can't be disabled */ +DebugMetadata debug_metadata[__COUNTER__]; +#endif diff --git a/test.c b/test.c @@ -359,10 +359,10 @@ main(void) cursor_alt(&term, 1); } - os_alloc_ring_buffer(&term.views[0].log, BACKLOG_SIZE); + posix_allocate_ring_buffer(&term.views[0].log, BACKLOG_SIZE); line_buf_alloc(&term.views[0].lines, &memory, term.views[0].log.buf, term.cursor.style, BACKLOG_LINES); - os_alloc_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE); + posix_allocate_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE); line_buf_alloc(&term.views[1].lines, &memory, term.views[1].log.buf, term.cursor.style, ALT_BACKLOG_LINES); diff --git a/util.c b/util.c @@ -29,6 +29,13 @@ equal_uv2(uv2 a, uv2 b) return result; } +static v2 +v2_from_iv2(iv2 a) +{ + v2 result = {.x = a.x, .y = a.y}; + return result; +} + static b32 is_valid_range(Range r) { @@ -212,6 +219,15 @@ i32_from_cstr(char *s, char delim) } static Stream +arena_stream(Arena a) +{ + Stream result = {0}; + result.cap = a.end - a.beg; + result.buf = (typeof(result.buf))a.beg; + return result; +} + +static Stream stream_alloc(Arena *a, u32 cap) { Stream result = {0}; diff --git a/util.h b/util.h @@ -46,6 +46,7 @@ typedef float f32; typedef double f64; +typedef char c8; typedef int8_t i8; typedef uint8_t u8; typedef int16_t i16; @@ -260,7 +261,7 @@ typedef struct { typedef struct { GLFWwindow *window; - v2 window_size; + iv2 window_size; u32 vao, vbos[5]; @@ -315,6 +316,9 @@ struct conversion_result { #define STB_STATIC #include "extern/stb_truetype.h" +/* TODO: this can be moved */ +#include "vtgl.h" + #ifdef __unix__ #include "os_unix.c" #else @@ -450,7 +454,8 @@ typedef struct Term { GLCtx gl; FontAtlas fa; - Arena arena_for_frame; + Arena arena_for_frame; + TempArena temp_arena; Selection selection; Cursor cursor; @@ -461,7 +466,7 @@ typedef struct Term { size unprocessed_bytes; - os_child child; + iptr child; uv2 size; /* NOTE: scrolling region */ diff --git a/vtgl.c b/vtgl.c @@ -3,6 +3,7 @@ #include <GLFW/glfw3.h> #include "util.h" + #include "config.h" #include "font.c" @@ -72,10 +73,22 @@ get_terminal_bot_left(Term *t) } static void -resize(Term *t) +resize(Term *t, iv2 window_size) { GLCtx *gl = &t->gl; - v2 ws = gl->window_size; + 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); + + v2 ws = v2_from_iv2(gl->window_size); ws.w -= 2 * g_term_margin.w; ws.h -= 2 * g_term_margin.h; @@ -115,11 +128,12 @@ resize(Term *t) sp->margin_colour = g_colours.data[g_colours.bgidx].rgba; //sp->margin_colour = 0x7f003f00; - sp->term_size_in_pixels = gl->window_size; + sp->term_size_in_pixels = v2_from_iv2(gl->window_size); sp->term_size_in_cells = t->size; gl->flags &= ~NEEDS_RESIZE; } + static void update_uniforms(Term *t, enum shader_stages stage) { @@ -183,22 +197,26 @@ get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, u32 font_i static void flush_render_push_buffer(RenderCtx *rc) { + BEGIN_TIMED_BLOCK(); if (rc->rpb->count > 0) { u32 n = rc->rpb->count; GLCtx *gl = rc->gl; RenderPushBuffer *rpb = rc->rpb; ASSERT((n % 4) == 0); + BEGIN_NAMED_BLOCK(upload); glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[0]); glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->positions), rpb->positions); glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[1]); glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->texture_coordinates), rpb->texture_coordinates); glBindBuffer(GL_ARRAY_BUFFER, gl->vbos[2]); glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(*rpb->colours), rpb->colours); + END_NAMED_BLOCK(upload); glDrawElements(GL_TRIANGLES, 6 * n / 4, GL_UNSIGNED_INT, 0); } rc->rpb->count = 0; + END_TIMED_BLOCK(); } static u32 @@ -586,30 +604,11 @@ KEYBIND_FN(zoom) return 1; } -/* NOTE: called when the window was resized */ -static void -fb_callback(GLFWwindow *win, i32 w, i32 h) -{ - Term *t = glfwGetWindowUserPointer(win); - - t->gl.window_size = (v2){.w = w, .h = h}; - - glViewport(0, 0, w, h); - - glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit); - glBindTexture(GL_TEXTURE_2D, t->gl.fb_tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - - /* NOTE: reactive the glyph texture unit */ - glActiveTexture(GL_TEXTURE0); - - t->gl.flags |= NEEDS_RESIZE; -} - static void key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods) { - Term *t = glfwGetWindowUserPointer(win); + tmp_user_ctx *ctx = glfwGetWindowUserPointer(win); + Term *t = ctx->memory->memory; #ifdef _DEBUG if (key == GLFW_KEY_F1 && act == GLFW_PRESS) { @@ -715,7 +714,8 @@ key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods) static void mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod) { - Term *t = glfwGetWindowUserPointer(win); + tmp_user_ctx *ctx = glfwGetWindowUserPointer(win); + Term *t = ctx->memory->memory; /* TODO: map other mouse buttons */ if (btn != GLFW_MOUSE_BUTTON_LEFT) return; @@ -749,7 +749,9 @@ mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod) static void char_callback(GLFWwindow *win, u32 codepoint) { - Term *t = glfwGetWindowUserPointer(win); + tmp_user_ctx *ctx = glfwGetWindowUserPointer(win); + Term *t = ctx->memory->memory; + if (t->scroll_offset) { t->scroll_offset = 0; t->gl.flags |= NEEDS_FULL_REFILL; @@ -762,7 +764,9 @@ scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff) { (void)xoff; - Term *t = glfwGetWindowUserPointer(win); + tmp_user_ctx *ctx = glfwGetWindowUserPointer(win); + Term *t = ctx->memory->memory; + if (t->mode & TM_ALTSCREEN) { b32 left_shift_state = glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS; b32 right_shift_state = glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS; @@ -783,52 +787,170 @@ scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff) } } -DEBUG_EXPORT iv2 -init_term(Term *t, Arena *a, iv2 cells) +static void +gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) { - init_fonts(&t->fa, a, t->gl.glyph_bitmap_dim); - t->cursor.state = CURSOR_NORMAL; - for (u32 i = 0; i < ARRAY_COUNT(t->saved_cursors); i++) { - cursor_reset(t); - cursor_move_to(t, 0, 0); - cursor_alt(t, 1); + (void)src; (void)type; (void)id; + Stream *err = (Stream *)data; + stream_push_s8(err, s8("[GL Error ")); + switch (lvl) { + case GL_DEBUG_SEVERITY_HIGH: stream_push_s8(err, s8("HIGH]: ")); break; + case GL_DEBUG_SEVERITY_MEDIUM: stream_push_s8(err, s8("MEDIUM]: ")); break; + case GL_DEBUG_SEVERITY_LOW: stream_push_s8(err, s8("LOW]: ")); break; + case GL_DEBUG_SEVERITY_NOTIFICATION: stream_push_s8(err, s8("NOTIFICATION]: ")); break; + default: stream_push_s8(err, s8("INVALID]: ")); break; } - selection_clear(&t->selection); - v2 cs = get_cell_size(&t->fa); - iv2 requested_size = { - .x = cs.x * cells.x + 2 * g_term_margin.x, - .y = cs.y * cells.y + 2 * g_term_margin.y, - }; + stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg}); + stream_push_byte(err, '\n'); + os_write_err_msg(stream_to_s8(err)); + err->widx = 0; +} - return requested_size; +static u32 +gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba) +{ + /* TODO: logging */ + u32 result; + glGenTextures(1, &result); + glBindTexture(GL_TEXTURE_2D, result); + glTexImage2D(GL_TEXTURE_2D, 0, format, size.w, size.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + return result; } DEBUG_EXPORT void -reset_terminal(Term *t) +reset_terminal(TerminalMemory *memory) { + Term *t = memory->memory; glfwSetCharCallback(t->gl.window, char_callback); - glfwSetFramebufferSizeCallback(t->gl.window, fb_callback); glfwSetKeyCallback(t->gl.window, key_callback); glfwSetMouseButtonCallback(t->gl.window, mouse_button_callback); //glfwSetWindowRefreshCallback(t->gl.window, refresh_callback); glfwSetScrollCallback(t->gl.window, scroll_callback); +} + +DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) +{ + Term *t = (Term *)memory->memory; + Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size}; + + t->cursor.state = CURSOR_NORMAL; + cursor_reset(t); + + memory->platform_api.allocate_ring_buffer(&t->views[0].log, BACKLOG_SIZE); + line_buf_alloc(&t->views[0].lines, &a, t->views[0].log.buf, t->cursor.style, BACKLOG_LINES); + memory->platform_api.allocate_ring_buffer(&t->views[1].log, ALT_BACKLOG_SIZE); + line_buf_alloc(&t->views[1].lines, &a, t->views[1].log.buf, t->cursor.style, ALT_BACKLOG_LINES); + + 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, + }; - resize(t); - term_reset(t); + t->size = (uv2){.x = 1, .y = 1}; + t->error_stream = stream_alloc(&a, MEGABYTE / 4); + t->arena_for_frame = a; + t->child = child; + + /* NOTE: Set up OpenGL Render Pipeline */ + glDebugMessageCallback(gl_debug_logger, &t->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); + + glGenBuffers(ARRAY_COUNT(t->gl.vbos), t->gl.vbos); + + RenderPushBuffer *rpb = NULL; + /* NOTE: vertex position buffer */ + glBindBuffer(GL_ARRAY_BUFFER, t->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]); + 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]); + glBufferData(GL_ARRAY_BUFFER, sizeof(rpb->colours), 0, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 4, GL_FLOAT, 0, 0, 0); + + /* NOTE: fill in element index buffer */ + i32 *element_indices = alloc(&a, i32, 6 * ARRAY_COUNT(rpb->positions)); + for (i32 i = 0, j = 0; i < 6 * ARRAY_COUNT(rpb->positions); i += 6, j++) { + element_indices[i + 0] = 4 * j; + element_indices[i + 1] = 4 * j + 1; + element_indices[i + 2] = 4 * j + 2; + element_indices[i + 3] = 4 * j; + element_indices[i + 4] = 4 * j + 2; + element_indices[i + 5] = 4 * j + 3; + } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t->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); + /* 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); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* 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); + + 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); + + glActiveTexture(GL_TEXTURE0); + + return requested_size; } -DEBUG_EXPORT void -do_terminal(Term *t, f32 dt) +DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) { BEGIN_TIMED_BLOCK(); - dt_for_frame = dt; + Term *t = memory->memory; + + dt_for_frame = input->dt; + + t->temp_arena = begin_temp_arena(&t->arena_for_frame); if (t->gl.flags & UPDATE_POST_UNIFORMS) update_uniforms(t, SHADER_POST); + if (!equal_iv2(input->window_size, t->gl.window_size)) + t->gl.flags |= NEEDS_RESIZE; + if (t->gl.flags & NEEDS_RESIZE) - resize(t); + resize(t, input->window_size); /* NOTE: this needs to be bound for blitting lines because that function can * access the font cache. @@ -837,28 +959,27 @@ do_terminal(Term *t, f32 dt) BEGIN_NAMED_BLOCK(input_from_child); - size parsed_lines = 0; - if (os_child_data_available(t->child)) { + if (input->data_available) { RingBuf *rb = &t->views[t->view_idx].log; - if (os_child_exited(t->child)) { - glfwSetWindowShouldClose(t->gl.window, GL_TRUE); - t->gl.flags = 0; - } else { - t->unprocessed_bytes += os_read_from_child(t->child, t->views + t->view_idx, - t->unprocessed_bytes); - 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; - } + 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, parsed_lines); + blit_lines(t, t->arena_for_frame, 0); t->gl.flags |= UPDATE_RENDER_BUFFER; } @@ -899,11 +1020,9 @@ do_terminal(Term *t, f32 dt) glBindBufferBase(GL_UNIFORM_BUFFER, 0, t->gl.render_shader_ubo); glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(*sp), sp); - push_rect_textured(&rc, (Rect){.size = t->gl.window_size}, (v4){0}, 0); + push_rect_textured(&rc, (Rect){.size = v2_from_iv2(t->gl.window_size)}, (v4){0}, 0); flush_render_push_buffer(&rc); - END_NAMED_BLOCK(update_render); - static f32 param = 0; static f32 p_scale = 1; param += p_scale * 0.005 * dt_for_frame; @@ -917,14 +1036,19 @@ do_terminal(Term *t, f32 dt) glUniform1i(t->gl.post.texslot, t->gl.fb_tex_unit); glUniform1f(t->gl.post.param, param); - push_rect_textured(&rc, (Rect){.size = t->gl.window_size}, (v4){0}, 1); + 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); /* NOTE: this happens at the end so that ui stuff doesn't go through the post * processing/effects shader */ + BEGIN_NAMED_BLOCK(debug_overlay); glUseProgram(t->gl.programs[SHADER_RECTS]); draw_debug_overlay(t, &rc); flush_render_push_buffer(&rc); + END_NAMED_BLOCK(debug_overlay); + + end_temp_arena(t->temp_arena); END_TIMED_BLOCK(); } @@ -934,6 +1058,8 @@ do_terminal(Term *t, f32 dt) DEBUG_EXPORT DEBUG_END_FRAME_FN(debug_end_frame) { + DebugState *debug_state = memory->debug_memory; + g_debug_table.snapshot_index++; if (g_debug_table.snapshot_index == MAX_DEBUG_RECORD_COUNT) g_debug_table.snapshot_index = 0; diff --git a/vtgl.h b/vtgl.h @@ -0,0 +1,55 @@ +/* See LICENSE for copyright details */ +#ifndef _VTGL_H_ +#define _VTGL_H_ + +#define PLATFORM_ALLOCATE_RING_BUFFER_FN(name) void name(RingBuf *rb, size capacity) +typedef PLATFORM_ALLOCATE_RING_BUFFER_FN(platform_allocate_ring_buffer_fn); + +#define PLATFORM_WRITE_FN(name) b32 name(iptr file, s8 raw, size offset) +typedef PLATFORM_WRITE_FN(platform_write_fn); + +#define PLATFORM_READ_FN(name) size name(iptr file, s8 buffer, size offset) +typedef PLATFORM_READ_FN(platform_read_fn); + +typedef struct { + platform_allocate_ring_buffer_fn *allocate_ring_buffer; + platform_read_fn *read; + platform_write_fn *write; +} PlatformAPI; + +typedef struct { + b32 data_available; + b32 executable_reloaded; + iv2 window_size; + + f32 dt; +} TerminalInput; + +typedef struct TerminalMemory { + u64 memory_size; + void *memory; + + u64 debug_memory_size; + void *debug_memory; + + iptr child; + + PlatformAPI platform_api; +} TerminalMemory; + +/* TODO: delete this !!!! */ +typedef struct { + TerminalMemory *memory; + TerminalInput *input; +} tmp_user_ctx; + +/************************************************************/ +/* 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) +typedef VTGL_INITIALIZE_FN(vtgl_initialize_fn); + +#define VTGL_FRAME_STEP_FN(name) void name(TerminalMemory *memory, TerminalInput *input) +typedef VTGL_FRAME_STEP_FN(vtgl_frame_step_fn); + +#endif /*_VTGL_H_ */