vtgl

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

Commit: 75e230d9bf89f16182a24b180d4f84bd23552927
Parent: 74599cfcd30e741feae2c71e80eeb396c5bc8ae1
Author: Randy Palamar
Date:   Sat, 22 Jun 2024 14:52:44 -0600

set up gl render pipeline

Diffstat:
Mbuild.sh | 2+-
Afrag.glsl | 25+++++++++++++++++++++++++
Mmain.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mos_unix.c | 40+++++++++++++++++++++++++++++++++++++---
Mutil.c | 27+++++++++++++++++++++++++++
Mutil.h | 40++++++++++++++++++++++++++++++++++++++++
Avert.glsl | 31+++++++++++++++++++++++++++++++
Mvtgl.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
8 files changed, 363 insertions(+), 10 deletions(-)

diff --git a/build.sh b/build.sh @@ -8,7 +8,7 @@ ldflags="$ldflags -lfreetype $(pkg-config --static --libs glfw3 gl)" # Hot Reloading/Debugging cflags="$cflags -D_DEBUG" -libcflags="$cflags -fPIC" +libcflags="$cflags -fPIC -Wno-unused-function" libldflags="$ldflags -shared" cc $libcflags vtgl.c -o vtgl.so $libldflags diff --git a/frag.glsl b/frag.glsl @@ -0,0 +1,25 @@ +#version 460 core + +out vec4 colour; + +in VS_OUT { + vec2 tex_coord; + flat int index; +} fs_in; + +//uniform sampler2DArray u_texslot; +//uniform int u_charmap[512]; +//uniform vec2 u_texscale[512]; +uniform uvec2 u_texcolour[512]; + +void main() +{ + vec4 fg = unpackUnorm4x8(u_texcolour[fs_in.index].x).wzyx; + vec4 bg = unpackUnorm4x8(u_texcolour[fs_in.index].y).wzyx; + + //vec3 tex_coord = vec3(fs_in.tex_coord.xy, u_charmap[fs_in.index]); + //tex_coord.xy *= u_texscale[fs_in.index]; + + //float a = u_texscale[fs_in.index].x == 0 ? 0 : texture(u_texslot, tex_coord).r; + colour = mix(bg, fg, fs_in.tex_coord.x); +} diff --git a/main.c b/main.c @@ -46,7 +46,7 @@ do_debug(GLCtx *gl) static os_file_stats updated; os_file_stats test = os_get_file_stats(libname); - if (os_filetime_is_newer(test.timestamp, updated.timestamp)) { + 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 }; @@ -59,6 +59,23 @@ do_debug(GLCtx *gl) #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; (void)data; + fputs("[GL Error ", stderr); + switch (lvl) { + case GL_DEBUG_SEVERITY_HIGH: fputs("HIGH]: ", stderr); break; + case GL_DEBUG_SEVERITY_MEDIUM: fputs("MEDIUM]: ", stderr); break; + case GL_DEBUG_SEVERITY_LOW: fputs("LOW]: ", stderr); break; + case GL_DEBUG_SEVERITY_NOTIFICATION: fputs("NOTIFICATION]: ", stderr); break; + default: fputs("INVALID]: ", stderr); break; + } + fwrite(msg, 1, len, stderr); + fputc('\n', stderr); +} + + +static void error_callback(int code, const char *desc) { fprintf(stderr, "GLFW Error (0x%04X): %s\n", code, desc); @@ -94,20 +111,140 @@ init_window(Term *t) /* 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, NULL); + 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(1, &t->gl.vbo); + glBindBuffer(GL_ARRAY_BUFFER, t->gl.vbo); + + f32 verts[] = { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + }; + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); + glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &t->gl.glyph_tex); + glBindTexture(GL_TEXTURE_2D_ARRAY, t->gl.glyph_tex); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +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); + fputs("compile_shader: ", stderr); + fwrite(err.data, 1, err.len, stderr); + 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) { + 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) +{ + static char *fs_name = "frag.glsl"; + static char *vs_name = "vert.glsl"; + static os_file_stats fs_stats, vs_stats; + os_file_stats fs_test = os_get_file_stats(fs_name); + os_file_stats vs_test = os_get_file_stats(vs_name); + + if (os_filetime_changed(fs_test.timestamp, fs_stats.timestamp) || + os_filetime_changed(vs_test.timestamp, vs_stats.timestamp)) { + fprintf(stderr, "Reloading Shaders!\n"); + fs_stats = fs_test; + vs_stats = vs_test; + + s8 vs_text = os_read_file(&a, vs_name, vs_stats.filesize); + s8 fs_text = os_read_file(&a, fs_name, fs_stats.filesize); + ASSERT(vs_text.len > 0 && fs_text.len > 0); + + u32 program = program_from_shader_text(vs_text, fs_text, a); + if (program) { + glDeleteProgram(gl->program); + gl->program = program; + gl->flags |= UPDATE_UNIFORMS; + fprintf(stderr, "Program Updated!\n"); + } + } } i32 main(void) { - Arena memory = {0}; + Arena memory = os_new_arena(16 * MEGABYTE); Term term = {0}; init_window(&term); while (!glfwWindowShouldClose(term.gl.window)) { do_debug(&term.gl); + check_shaders(&term.gl, memory); + glfwPollEvents(); do_terminal(&term, memory); + glfwSwapBuffers(term.gl.window); } return 0; diff --git a/os_unix.c b/os_unix.c @@ -1,6 +1,8 @@ /* See LICENSE for copyright details */ #include <fcntl.h> +#include <sys/mman.h> #include <sys/stat.h> +#include <unistd.h> typedef struct timespec os_filetime; typedef struct { @@ -8,8 +10,24 @@ typedef struct { os_filetime timestamp; } os_file_stats; +static Arena +os_new_arena(size cap) +{ + Arena a; + + size pagesize = sysconf(_SC_PAGESIZE); + if (cap % pagesize != 0) + cap += pagesize - cap % pagesize; + + a.beg = mmap(0, cap, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (a.beg == MAP_FAILED) + return (Arena){0}; + a.end = a.beg + cap; + return a; +} + static os_file_stats -os_get_file_stats(const char *name) +os_get_file_stats(char *name) { struct stat sb = {0}; stat(name, &sb); @@ -17,7 +35,23 @@ os_get_file_stats(const char *name) } static b32 -os_filetime_is_newer(os_filetime a, os_filetime b) +os_filetime_changed(os_filetime a, os_filetime b) { - return (a.tv_sec - b.tv_sec) + (a.tv_nsec - b.tv_nsec); + return ((a.tv_sec - b.tv_sec) + (a.tv_nsec - b.tv_nsec)) != 0; +} + +static s8 +os_read_file(Arena *a, char *name, size filesize) +{ + i32 fd = open(name, O_RDONLY); + if (fd < 0) + return (s8){0}; + + s8 text = s8alloc(a, filesize); + size rlen = read(fd, text.data, text.len); + + if (text.len != rlen) + return (s8){0}; + + return text; } diff --git a/util.c b/util.c @@ -14,3 +14,30 @@ die(char *fmt, ...) exit(1); } + +static void * +mem_clear(u8 *p, u8 c, size len) +{ + while (len) p[--len] = c; + return p; +} + +#define alloc(a, t, n) (t *)alloc_(a, sizeof(t), _Alignof(t), n) +__attribute((malloc, alloc_size(4, 2), alloc_align(3))) +static void * +alloc_(Arena *a, size len, size align, size count) +{ + size padding = -(uintptr_t)a->beg * (align - 1); + size available = a->end - a->beg - padding; + ASSERT(available > 0 && count < available / len); + + void *p = a->beg + padding; + a->beg += padding + count * len; + return mem_clear(p, 0, count * len); +} + +static s8 +s8alloc(Arena *a, size len) +{ + return (s8){.len = len, .data = alloc(a, u8, len)}; +} diff --git a/util.h b/util.h @@ -13,6 +13,14 @@ #define DEBUG_EXPORT static #endif +#ifndef static_assert +#define static_assert _Static_assert +#endif + +#define MEGABYTE (1024ULL * 1024ULL) + +#define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a)) + typedef float f32; typedef double f64; typedef uint8_t u8; @@ -28,6 +36,11 @@ typedef union { } iv2; typedef union { + struct { f32 x, y; }; + f32 E[2]; +} v2; + +typedef union { struct { f32 x, y, z, w; }; struct { f32 r, g, b, a; }; f32 E[4]; @@ -41,9 +54,36 @@ typedef union { typedef struct { u8 *beg, *end; } Arena; +typedef struct { size len; u8 *data; } s8; +#define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = s} + +#define GL_UNIFORMS \ + X(Pmat) \ + X(texcolour) \ + X(vertoff) \ + X(vertscale) + +enum gl_flags { + NEEDS_RESIZE = 1 << 0, + UPDATE_UNIFORMS = 1 << 30, +}; + typedef struct { GLFWwindow *window; iv2 window_size; + + u32 vao, vbo; + u32 glyph_tex; + + u32 program; + #define X(name) i32 name; + union { + struct { GL_UNIFORMS }; + i32 uniforms[4]; + }; + #undef X + + u32 flags; } GLCtx; typedef struct { diff --git a/vert.glsl b/vert.glsl @@ -0,0 +1,31 @@ +#version 460 core + +in vec2 position; + +uniform mat4 u_Pmat; +uniform vec2 u_vertscale[512]; +uniform vec2 u_vertoff[512]; + +out VS_OUT { + vec2 tex_coord; + flat int index; +} vs_out; + +void main() +{ + vec2 scale = u_vertscale[gl_InstanceID]; + vec2 offset = u_vertoff[gl_InstanceID]; + + mat4 transform = mat4( + scale.x, 0, 0, 0, + 0, scale.y, 0, 0, + 0, 0, 1, 0, + offset.x, offset.y, 0, 1 + ); + + gl_Position = u_Pmat * transform * vec4(position, 0.0, 1.0); + + vs_out.tex_coord = position; + vs_out.tex_coord.y = 1.0 - vs_out.tex_coord.y; + vs_out.index = gl_InstanceID; +} diff --git a/vtgl.c b/vtgl.c @@ -6,6 +6,12 @@ #include "util.h" +#define X(name) "u_"#name, +static char *uniform_names[] = { GL_UNIFORMS }; +#undef X +static_assert(ARRAY_COUNT(uniform_names) == ARRAY_COUNT(((GLCtx *)0)->uniforms), + "GLCtx.uniforms must be same length as GL_UNIFORMS\n"); + static v4 normalized_colour(Colour c) { @@ -15,12 +21,59 @@ normalized_colour(Colour c) static void clear_colour(void) { - v4 cc = normalized_colour((Colour){.r = 20, .g = 20, .b = 20, .a = 255}); + v4 cc = normalized_colour((Colour){.r = 64, .g = 64, .b = 64, .a = 255}); glClearColor(cc.r, cc.g, cc.b, cc.a); glClear(GL_COLOR_BUFFER_BIT); } static void +resize(GLCtx *gl) +{ + iv2 ws = gl->window_size; + glViewport(0, 0, ws.w, ws.h); + f32 pmat[4 * 4] = { + 2.0 / ws.w, 0.0, 0.0, -1.0, + 0.0, 2.0 / ws.h, 0.0, -1.0, + 0.0, 0.0, -1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + }; + glUniformMatrix4fv(gl->Pmat, 1, GL_TRUE, pmat); + gl->flags &= ~NEEDS_RESIZE; +} + +static void +update_uniforms(GLCtx *gl) +{ + for (u32 i = 0; i < ARRAY_COUNT(gl->uniforms); i++) + gl->uniforms[i] = glGetUniformLocation(gl->program, uniform_names[i]); + gl->flags &= ~UPDATE_UNIFORMS; + resize(gl); +} + +static void +draw_rectangle(GLCtx *gl, v2 pos, v2 size, u32 bg, u32 fg) +{ + glUniform2uiv(gl->texcolour, 1, (u32 []){fg, bg}); + //glUniform1iv(glctx.u_charmap, count, charmap); + + //glUniform2fv(glctx.u_texscale, count, (f32 *)texscale); + glUniform2fv(gl->vertscale, 1, size.E); + glUniform2fv(gl->vertoff, 1, pos.E); + + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 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.w = w; + t->gl.window_size.h = h; + t->gl.flags |= NEEDS_RESIZE; +} + +static void key_callback(GLFWwindow *w, i32 key, i32 sc, i32 act, i32 mods) { if (key == GLFW_KEY_ESCAPE && act == GLFW_PRESS) @@ -31,7 +84,7 @@ DEBUG_EXPORT void init_callbacks(GLCtx *gl) { //glfwSetCharCallback(gl->window, char_callback); - //glfwSetFramebufferSizeCallback(gl->window, fb_callback); + glfwSetFramebufferSizeCallback(gl->window, fb_callback); glfwSetKeyCallback(gl->window, key_callback); //glfwSetWindowRefreshCallback(gl->window, refresh_callback); //glfwSetScrollCallback(gl->window, scroll_callback); @@ -40,9 +93,15 @@ init_callbacks(GLCtx *gl) DEBUG_EXPORT void do_terminal(Term *t, Arena a) { - glfwPollEvents(); + if (t->gl.flags & UPDATE_UNIFORMS) + update_uniforms(&t->gl); + + if (t->gl.flags & NEEDS_RESIZE) + resize(&t->gl); clear_colour(); - glfwSwapBuffers(t->gl.window); + draw_rectangle(&t->gl, (v2){.x = 20, .y = 20}, (v2){.x = 200, .y = 100}, 0x7f0000ff, 0); + draw_rectangle(&t->gl, (v2){.x = 50, .y = 300}, (v2){.x = 100, .y = 100}, 0x007f00ff, 0); + draw_rectangle(&t->gl, (v2){.x = 100, .y = 600}, (v2){.x = 50, .y = 100}, 0x00007fff, 0); }