Commit: 75e230d9bf89f16182a24b180d4f84bd23552927
Parent: 74599cfcd30e741feae2c71e80eeb396c5bc8ae1
Author: Randy Palamar
Date: Sat, 22 Jun 2024 14:52:44 -0600
set up gl render pipeline
Diffstat:
M | build.sh | | | 2 | +- |
A | frag.glsl | | | 25 | +++++++++++++++++++++++++ |
M | main.c | | | 141 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
M | os_unix.c | | | 40 | +++++++++++++++++++++++++++++++++++++--- |
M | util.c | | | 27 | +++++++++++++++++++++++++++ |
M | util.h | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
A | vert.glsl | | | 31 | +++++++++++++++++++++++++++++++ |
M | vtgl.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);
}