vtgl

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

Commit: c59f7f3220bce900dc34675b20d20dc022c7b193
Parent: 7b84d363f59f92a8c04278366957a77dc90d03e2
Author: Randy Palamar
Date:   Fri,  8 Nov 2024 08:02:45 -0700

move shader loading into the terminal code

Diffstat:
Mconfig.def.h | 2++
Mos_unix.c | 59+++++++++++++++++++++++++----------------------------------
Mplatform_linux_x11.c | 123++++---------------------------------------------------------------------------
Mutil.h | 13++++++++++---
Mvtgl.c | 173+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mvtgl.h | 12++++++++++++
6 files changed, 208 insertions(+), 174 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -30,6 +30,8 @@ static u8 g_tabstop = 8; /* NOTE: number of blinks per second */ static f32 g_blink_speed = 1.0f; +static s8 g_shader_path_prefix = s8(""); + static Colour base16_colours[16] = { [0] = { .rgba = 0x000000ff }, /* black */ [1] = { .rgba = 0xaa0000ff }, /* red */ diff --git a/os_unix.c b/os_unix.c @@ -9,13 +9,7 @@ #include <time.h> #include <unistd.h> -typedef struct timespec os_filetime; -typedef s8 os_mapped_file; - -typedef struct { - size filesize; - os_filetime timestamp; -} os_file_stats; +typedef s8 os_mapped_file; typedef struct { iptr handle; @@ -64,18 +58,15 @@ os_new_arena(size cap) return a; } -static os_file_stats -os_get_file_stats(char *name) -{ - struct stat sb = {0}; - stat(name, &sb); - return (os_file_stats){.timestamp = sb.st_mtim, .filesize = sb.st_size}; -} - -static b32 -os_filetime_changed(os_filetime a, os_filetime b) +static PLATFORM_GET_FILESTATS_FN(posix_get_file_stats) { - return ((a.tv_sec - b.tv_sec) + (a.tv_nsec - b.tv_nsec)) != 0; + struct stat sb = {0}; + FileStats result = {0}; + if (stat((c8 *)path, &sb) == 0) { + result.size = sb.st_size; + result.timestamp = sb.st_mtim.tv_sec * 1e9 + sb.st_mtim.tv_nsec; + } + return result; } static u32 @@ -133,21 +124,21 @@ static PLATFORM_READ_FN(posix_read) return total_bytes_read; } -static s8 -os_read_file(Arena *a, char *name, size filesize) +static PLATFORM_READ_FILE_FN(posix_read_file) { - i32 fd = open(name, O_RDONLY); - if (fd < 0) - return (s8){0}; - - s8 text = s8alloc(a, filesize); - size rlen = posix_read(fd, text, 0); - close(fd); + i32 fd = open((c8 *)path, O_RDONLY); + buffer->errors |= fd < 0; - if (text.len != rlen) - return (s8){0}; + if (!buffer->errors) { + s8 text = {.len = buffer->cap - buffer->widx, .data = buffer->buf + buffer->widx}; + size rlen = posix_read(fd, text, 0); + close(fd); + buffer->errors |= text.len != rlen; + if (!buffer->errors) + buffer->widx += rlen; + } - return text; + return !buffer->errors; } static os_mapped_file @@ -161,13 +152,13 @@ os_map_file(char *path, i32 mode, i32 perm) default: ASSERT(0); } - i32 fd = open(path, open_mode); - os_file_stats fs = os_get_file_stats(path); + i32 fd = open(path, open_mode); + FileStats fs = posix_get_file_stats((u8 *)path); if (fd != -1) { - res.data = mmap(NULL, fs.filesize, mode, perm, fd, 0); + res.data = mmap(NULL, fs.size, mode, perm, fd, 0); if (res.data != MAP_FAILED) - res.len = fs.filesize; + res.len = fs.size; close(fd); } diff --git a/platform_linux_x11.c b/platform_linux_x11.c @@ -65,10 +65,10 @@ load_library(const char *lib, Stream *err) static void do_debug(TerminalMemory *t, Stream *err) { - static os_file_stats updated; - os_file_stats test = os_get_file_stats(libname); + static FileStats updated; + FileStats test = posix_get_file_stats((u8 *)libname); - if (os_filetime_changed(test.timestamp, updated.timestamp)) { + if (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 }; @@ -198,114 +198,6 @@ init_window(tmp_user_ctx *ctx, iv2 window_size) 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 update_input(GLFWwindow *win, TerminalInput *input, posix_platform_process child) { @@ -406,10 +298,13 @@ main(i32 argc, char *argv[], char *envp[]) 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; + term_memory.platform_api.get_file_stats = posix_get_file_stats; + term_memory.platform_api.read_file = posix_read_file; term_memory.platform_api.get_clipboard = x11_get_clipboard; term_memory.platform_api.set_clipboard = x11_set_clipboard; term_memory.platform_api.get_window_title = x11_get_window_title; term_memory.platform_api.set_window_title = x11_set_window_title; + term_memory.platform_api.path_separator = '/'; Arena platform_arena = os_new_arena(2 * MEGABYTE); Stream error_stream = stream_alloc(&platform_arena, MEGABYTE / 4); @@ -535,12 +430,6 @@ main(i32 argc, char *argv[], char *envp[]) 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); - } - vtgl_frame_step(&term_memory, &input); Range current_sel = vtgl_active_selection(&term_memory, 0); diff --git a/util.h b/util.h @@ -109,6 +109,11 @@ typedef struct { b32 errors; } Stream; +typedef struct { + size size; + u64 timestamp; +} FileStats; + #define INVALID_FILE (-1) enum file_attribute { FA_READ = 1 << 0, @@ -220,7 +225,6 @@ enum gl_flags { NEEDS_FULL_REFILL = 1 << 2, UPDATE_RENDER_BUFFER = 1 << 3, - UPDATE_POST_UNIFORMS = 1 << 29, DRAW_DEBUG_OVERLAY = 1 << 30, }; @@ -236,7 +240,7 @@ enum shader_stages { SHADER_RENDER, SHADER_RECTS, SHADER_POST, - SHADER_LAST + SHADER_COUNT }; typedef struct { @@ -268,11 +272,14 @@ typedef struct { u32 fb, fb_tex, fb_tex_unit; - u32 programs[SHADER_LAST]; + u32 programs[SHADER_COUNT]; #define X(name) i32 name; struct { GL_POST_UNIFORMS } post; #undef X + /* TODO: os file watcher */ + FileStats fs_stats[SHADER_COUNT], vs_stats[SHADER_COUNT]; + _Alignas(16) ShaderParameters shader_parameters; u32 render_shader_ubo; u32 render_shader_ssbo; diff --git a/vtgl.c b/vtgl.c @@ -15,6 +15,158 @@ static u32 get_gpu_glyph_index(Arena, GLCtx *, FontAtlas *, u32, u32, enum face_ #define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba +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 +update_uniforms(GLCtx *gl, enum shader_stages stage) +{ + switch (stage) { + case SHADER_RENDER: + case SHADER_RECTS: + break; + case SHADER_POST: + #define X(name) gl->post.name = glGetUniformLocation(gl->programs[stage], "u_" #name); + GL_POST_UNIFORMS + #undef X + break; + case SHADER_COUNT: ASSERT(0); break; + } +} + +static void +check_shaders(GLCtx *gl, Arena a, Stream *err, PlatformAPI *platform) +{ + static s8 fs_name[SHADER_COUNT] = { + [SHADER_RENDER] = s8("frag_render.glsl"), + [SHADER_RECTS] = s8("frag_for_rects.glsl"), + [SHADER_POST] = s8("frag_post.glsl"), + }; + static s8 vs_name[SHADER_COUNT] = { + [SHADER_RENDER] = s8("vert_for_rects.glsl"), + [SHADER_RECTS] = s8("vert_for_rects.glsl"), + [SHADER_POST] = s8("vert_for_rects.glsl"), + }; + + static struct { + s8 name; + b32 update_uniforms; + } map[SHADER_COUNT] = { + [SHADER_RENDER] = {.name = s8("Render"), .update_uniforms = 0}, + [SHADER_RECTS] = {.name = s8("Rects"), .update_uniforms = 0}, + [SHADER_POST] = {.name = s8("Post"), .update_uniforms = 1}, + }; + + /* TODO: this is nasty but will be cleaned up with a file watcher */ + Stream vs_path = stream_alloc(&a, 4 * KILOBYTE); + stream_push_s8(&vs_path, g_shader_path_prefix); + if (vs_path.widx && vs_path.buf[vs_path.widx - 1] != platform->path_separator) + stream_push_byte(&vs_path, platform->path_separator); + + Stream fs_path = stream_alloc(&a, 4 * KILOBYTE); + stream_push_s8(&fs_path, g_shader_path_prefix); + if (fs_path.widx && fs_path.buf[fs_path.widx - 1] != platform->path_separator) + stream_push_byte(&fs_path, platform->path_separator); + + u32 fs_sidx = fs_path.widx; + u32 vs_sidx = vs_path.widx; + for (u32 i = 0; i < SHADER_COUNT; i++) { + fs_path.widx = fs_sidx; + vs_path.widx = vs_sidx; + + stream_push_s8(&fs_path, fs_name[i]); + stream_push_byte(&fs_path, 0); + stream_push_s8(&vs_path, vs_name[i]); + stream_push_byte(&vs_path, 0); + + FileStats fs_test = platform->get_file_stats(fs_path.buf); + FileStats vs_test = platform->get_file_stats(vs_path.buf); + + if ((fs_test.timestamp <= gl->fs_stats[i].timestamp) && + (vs_test.timestamp <= gl->vs_stats[i].timestamp)) + continue; + + b32 success = 1; + + Stream vs_text = stream_alloc(&a, vs_test.size); + success &= platform->read_file(vs_path.buf, &vs_text, vs_test.size); + + Stream fs_text = stream_alloc(&a, fs_test.size); + success &= platform->read_file(fs_path.buf, &fs_text, fs_test.size); + + if (success) { + stream_push_s8s(err, 3, (s8 []){s8("Reloading "), map[i].name, s8(" Shader!\n")}); + gl->vs_stats[i] = vs_test; + gl->fs_stats[i] = fs_test; + + u32 program = program_from_shader_text(stream_to_s8(&vs_text), + stream_to_s8(&fs_text), a); + if (!program) + continue; + glDeleteProgram(gl->programs[i]); + gl->programs[i] = program; + if (map[i].update_uniforms) + update_uniforms(gl, i); + 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 v4 normalize_colour(Colour c) { @@ -142,24 +294,6 @@ resize(Term *t, iv2 window_size) gl->flags &= ~NEEDS_RESIZE; } -static void -update_uniforms(Term *t, enum shader_stages stage) -{ - switch (stage) { - case SHADER_RENDER: - case SHADER_RECTS: - break; - case SHADER_POST: - #define X(name) \ - t->gl.post.name = glGetUniformLocation(t->gl.programs[stage], "u_" #name); - GL_POST_UNIFORMS - #undef X - t->gl.flags &= ~UPDATE_POST_UNIFORMS; - break; - case SHADER_LAST: ASSERT(0); break; - } -} - static RenderCtx make_render_ctx(Arena *a, GLCtx *gl, FontAtlas *fa) { @@ -927,8 +1061,7 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) t->temp_arena = begin_temp_arena(&t->arena_for_frame); - if (t->gl.flags & UPDATE_POST_UNIFORMS) - update_uniforms(t, SHADER_POST); + check_shaders(&t->gl, t->arena_for_frame, &t->error_stream, &memory->platform_api); if (!equal_iv2(input->window_size, t->gl.window_size)) t->gl.flags |= NEEDS_RESIZE; diff --git a/vtgl.h b/vtgl.h @@ -8,9 +8,16 @@ 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); +/* TODO: this should possibly just take a stream buffer */ #define PLATFORM_READ_FN(name) size name(iptr file, s8 buffer, size offset) typedef PLATFORM_READ_FN(platform_read_fn); +#define PLATFORM_READ_FILE_FN(name) b32 name(u8 *path, Stream *buffer, size file_size) +typedef PLATFORM_READ_FILE_FN(platform_read_file_fn); + +#define PLATFORM_GET_FILESTATS_FN(name) FileStats name(u8 *path) +typedef PLATFORM_GET_FILESTATS_FN(platform_get_file_stats_fn); + #define PLATFORM_CLIPBOARD_FN(name) b32 name(Stream *buffer, u32 clipboard) typedef PLATFORM_CLIPBOARD_FN(platform_clipboard_fn); @@ -22,11 +29,16 @@ typedef struct { platform_read_fn *read; platform_write_fn *write; + platform_get_file_stats_fn *get_file_stats; + platform_read_file_fn *read_file; + platform_clipboard_fn *get_clipboard; platform_clipboard_fn *set_clipboard; platform_window_title_fn *get_window_title; platform_window_title_fn *set_window_title; + + u8 path_separator; } PlatformAPI; /* NOTE: CLIPBOARD_1 need not be supported on all platforms */