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:
M | build.sh | | | 2 | +- |
M | debug.c | | | 18 | ++++++++++++------ |
M | debug.h | | | 9 | +++++---- |
D | main.c | | | 468 | ------------------------------------------------------------------------------- |
M | os_unix.c | | | 74 | +++++++++++++++++++++++++++++++++----------------------------------------- |
A | platform_linux_x11.c | | | 399 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | test.c | | | 4 | ++-- |
M | util.c | | | 16 | ++++++++++++++++ |
M | util.h | | | 11 | ++++++++--- |
M | vtgl.c | | | 266 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
A | vtgl.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_ */