vtgl

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

Commit: 74599cfcd30e741feae2c71e80eeb396c5bc8ae1
Author: Randy Palamar
Date:   Sat, 22 Jun 2024 09:55:08 -0600

start doing the thing

Diffstat:
ALICENSE | 13+++++++++++++
Abuild.sh | 15+++++++++++++++
Amain.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos_unix.c | 23+++++++++++++++++++++++
Autil.c | 16++++++++++++++++
Autil.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avtgl.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 284 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,13 @@ +© 2024 Randy Palamar <randy@rnpnr.xyz> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/build.sh b/build.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +cflags="-march=native -ggdb -O0 -Wall" +cflags="$cflags $(pkg-config --cflags glfw3 gl freetype2)" +ldflags="" +ldflags="$ldflags -lfreetype $(pkg-config --static --libs glfw3 gl)" + +# Hot Reloading/Debugging +cflags="$cflags -D_DEBUG" + +libcflags="$cflags -fPIC" +libldflags="$ldflags -shared" + +cc $libcflags vtgl.c -o vtgl.so $libldflags +cc $cflags -o vtgl main.c $ldflags diff --git a/main.c b/main.c @@ -0,0 +1,114 @@ +/* See LICENSE for copyright details */ +#define GL_GLEXT_PROTOTYPES 1 +#include <GL/glcorearb.h> +#include <GL/glext.h> +#include <GLFW/glfw3.h> + +#include "util.h" + +#include "os_unix.c" + +#ifndef _DEBUG +static void do_debug(void) { } +#else +#include <dlfcn.h> +#include <time.h> + +static char *libname = "./vtgl.so"; +static void *libhandle; + +typedef void do_terminal_fn(Term *, Arena); +static do_terminal_fn *do_terminal; + +typedef void init_callbacks_fn(GLCtx *); +static init_callbacks_fn *init_callbacks; + +static void +load_library(const char *lib) +{ + /* NOTE: glibc sucks and will crash if this is NULL */ + if (libhandle) + dlclose(libhandle); + libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL); + if (!libhandle) + fprintf(stderr, "do_debug: dlopen: %s\n", dlerror()); + do_terminal = dlsym(libhandle, "do_terminal"); + if (!do_terminal) + fprintf(stderr, "do_debug: dlsym: %s\n", dlerror()); + init_callbacks = dlsym(libhandle, "init_callbacks"); + if (!init_callbacks) + fprintf(stderr, "do_debug: dlsym: %s\n", dlerror()); +} + +static void +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)) { + 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); + init_callbacks(gl); + } +} + +#endif /* _DEBUG */ + +static void +error_callback(int code, const char *desc) +{ + fprintf(stderr, "GLFW Error (0x%04X): %s\n", code, desc); +} + +static void +init_window(Term *t) +{ + if (!glfwInit()) + die("Failed to init GLFW\n"); + glfwSetErrorCallback(error_callback); + + GLFWmonitor *mon = glfwGetPrimaryMonitor(); + if (!mon) { + glfwTerminate(); + die("Failed to get GLFW monitor\n"); + } + + glfwGetMonitorWorkarea(mon, NULL, NULL, &t->gl.window_size.w, &t->gl.window_size.h); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + 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(); + die("Failed to spawn GLFW window\n"); + } + glfwMakeContextCurrent(t->gl.window); + glfwSetWindowUserPointer(t->gl.window, t); + + /* TODO: swap interval is not needed because we will sleep on waiting for terminal input */ + glfwSwapInterval(1); +} + +i32 +main(void) +{ + Arena memory = {0}; + Term term = {0}; + + init_window(&term); + + while (!glfwWindowShouldClose(term.gl.window)) { + do_debug(&term.gl); + + do_terminal(&term, memory); + } + + return 0; +} diff --git a/os_unix.c b/os_unix.c @@ -0,0 +1,23 @@ +/* See LICENSE for copyright details */ +#include <fcntl.h> +#include <sys/stat.h> + +typedef struct timespec os_filetime; +typedef struct { + size filesize; + os_filetime timestamp; +} os_file_stats; + +static os_file_stats +os_get_file_stats(const 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_is_newer(os_filetime a, os_filetime b) +{ + return (a.tv_sec - b.tv_sec) + (a.tv_nsec - b.tv_nsec); +} diff --git a/util.c b/util.c @@ -0,0 +1,16 @@ +/* See LICENSE for copyright details */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +static void __attribute__((noreturn)) +die(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + exit(1); +} diff --git a/util.h b/util.h @@ -0,0 +1,55 @@ +/* See LICENSE for copyright details */ +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include <stdint.h> +#include <stddef.h> + +#ifdef _DEBUG +#define ASSERT(c) do { if (!(c)) asm("int3; nop"); } while(0) +#define DEBUG_EXPORT +#else +#define ASSERT(c) +#define DEBUG_EXPORT static +#endif + +typedef float f32; +typedef double f64; +typedef uint8_t u8; +typedef int32_t i32; +typedef uint32_t u32; +typedef uint32_t b32; +typedef ptrdiff_t size; + +typedef union { + struct { i32 x, y; }; + struct { i32 w, h; }; + i32 E[2]; +} iv2; + +typedef union { + struct { f32 x, y, z, w; }; + struct { f32 r, g, b, a; }; + f32 E[4]; +} v4; + +typedef union { + /* NOTE: this assumes little endian */ + struct { u8 a, b, g, r; }; + u32 rgba; +} Colour; + +typedef struct { u8 *beg, *end; } Arena; + +typedef struct { + GLFWwindow *window; + iv2 window_size; +} GLCtx; + +typedef struct { + GLCtx gl; +} Term; + +#include "util.c" + +#endif /* _UTIL_H_ */ diff --git a/vtgl.c b/vtgl.c @@ -0,0 +1,48 @@ +/* See LICENSE for copyright details */ +#define GL_GLEXT_PROTOTYPES 1 +#include <GL/glcorearb.h> +#include <GL/glext.h> +#include <GLFW/glfw3.h> + +#include "util.h" + +static v4 +normalized_colour(Colour c) +{ + return (v4){.r = c.r / 255.0f, .g = c.g / 255.0f, .b = c.b / 255.0f, .a = c.a / 255.0f}; +} + +static void +clear_colour(void) +{ + v4 cc = normalized_colour((Colour){.r = 20, .g = 20, .b = 20, .a = 255}); + glClearColor(cc.r, cc.g, cc.b, cc.a); + glClear(GL_COLOR_BUFFER_BIT); +} + +static void +key_callback(GLFWwindow *w, i32 key, i32 sc, i32 act, i32 mods) +{ + if (key == GLFW_KEY_ESCAPE && act == GLFW_PRESS) + glfwSetWindowShouldClose(w, GL_TRUE); +} + +DEBUG_EXPORT void +init_callbacks(GLCtx *gl) +{ + //glfwSetCharCallback(gl->window, char_callback); + //glfwSetFramebufferSizeCallback(gl->window, fb_callback); + glfwSetKeyCallback(gl->window, key_callback); + //glfwSetWindowRefreshCallback(gl->window, refresh_callback); + //glfwSetScrollCallback(gl->window, scroll_callback); +} + +DEBUG_EXPORT void +do_terminal(Term *t, Arena a) +{ + glfwPollEvents(); + + clear_colour(); + + glfwSwapBuffers(t->gl.window); +}