vtgl

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

Commit: ad6e8ba279987b30591c6bd5948837b438c995da
Parent: ae6b1615a8dfc4dc51e5dac04bba57be2d53bad5
Author: Randy Palamar
Date:   Sun, 23 Jun 2024 09:32:32 -0600

add text drawing at arbitrary position

Diffstat:
Mbuild.sh | 4++--
Mfont.c | 8+++++---
Mutil.h | 28++++++++++++++++------------
Mvtgl.c | 130++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
4 files changed, 118 insertions(+), 52 deletions(-)

diff --git a/build.sh b/build.sh @@ -6,9 +6,9 @@ ldflags="" ldflags="$ldflags -lfreetype $(pkg-config --static --libs glfw3 gl)" # Hot Reloading/Debugging -cflags="$cflags -D_DEBUG" +cflags="$cflags -D_DEBUG -Wno-unused-function" -libcflags="$cflags -fPIC -Wno-unused-function" +libcflags="$cflags -fPIC" libldflags="$ldflags -shared" cc $libcflags vtgl.c -o vtgl.so $libldflags diff --git a/font.c b/font.c @@ -63,8 +63,10 @@ update_font_metrics(Term *t) t->fa.size.h = MAX(t->fa.size.h, g.size.h); t->fa.size.w = MAX(t->fa.size.w, g.size.w); t->fa.deltay = MAX(t->fa.deltay, -g.delta.y); - t->glyph_cache[i - ' '] = g; + t->gl.glyph_cache[i - ' '] = g; } + /* NOTE: ' ' has 0 size in freetype but we need it to have a width! */ + t->gl.glyph_cache[0].size.w = t->fa.size.w; } static i32 @@ -84,6 +86,6 @@ init_fonts(Term *t, char **paths, u32 npaths, u32 fontsize, Arena *a) for (t->fa.nfonts = 0; t->fa.nfonts < npaths; t->fa.nfonts++) init_font(&t->ftlib, t->fa.fonts + t->fa.nfonts, paths[t->fa.nfonts], fontsize); - t->glyph_cache_len = 0x7E - 0x20 + 1; - t->glyph_cache = alloc(a, Glyph, t->glyph_cache_len); + t->gl.glyph_cache_len = 0x7E - 0x20 + 1; + t->gl.glyph_cache = alloc(a, Glyph, t->gl.glyph_cache_len); } diff --git a/util.h b/util.h @@ -20,7 +20,8 @@ #define MEGABYTE (1024ULL * 1024ULL) #define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a)) -#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define BETWEEN(x, a, b) ((x) >= (a) && (x) <= (b)) +#define MIN(a, b) ((a) <= (b) ? (a) : (b)) #define MAX(a, b) ((a) >= (b) ? (a) : (b)) typedef float f32; @@ -54,6 +55,8 @@ typedef union { f32 E[4]; } v4; +typedef struct { v2 pos, size; } Rect; + typedef union { /* NOTE: this assumes little endian */ struct { u8 a, b, g, r; }; @@ -63,7 +66,7 @@ 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 s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} #define GL_UNIFORMS \ X(Pmat) \ @@ -80,13 +83,19 @@ enum gl_flags { }; typedef struct { + /* distance to shift glyph from bounding box origin */ + iv2 delta; + /* rendered glyph bitmap data (pixels) */ + uv2 size; +} Glyph; + +typedef struct { GLFWwindow *window; iv2 window_size; f32 dt; u32 vao, vbo; - u32 glyph_tex; u32 program; #define X(name) i32 name; @@ -97,6 +106,10 @@ typedef struct { #undef X u32 flags; + + u32 glyph_tex; + u32 glyph_cache_len; + Glyph *glyph_cache; } GLCtx; #include <ft2build.h> @@ -116,18 +129,9 @@ typedef struct { } FontAtlas; typedef struct { - /* distance to shift glyph from bounding box origin */ - iv2 delta; - /* rendered glyph bitmap data (pixels) */ - uv2 size; -} Glyph; - -typedef struct { GLCtx gl; FontAtlas fa; - u32 glyph_cache_len; - Glyph *glyph_cache; FT_Library ftlib; } Term; diff --git a/vtgl.c b/vtgl.c @@ -6,7 +6,7 @@ #include "util.h" -#define MAX_FONT_SIZE 128 +#define MAX_FONT_SIZE 128.0f #define TEXTURE_GLYPH_COUNT 128 #define X(name) "u_"#name, @@ -78,22 +78,70 @@ update_uniforms(Term *t) update_font_textures(t); } +static i32 +get_gpu_glyph_index(GLCtx *gl, u32 codepoint, Glyph *out_glyph) +{ + /* TODO: will perform lookup in LRU cache */ + ASSERT(BETWEEN(codepoint, ' ', '~')); + i32 depth_idx = TEXTURE_GLYPH_COUNT - (codepoint - ' ') - 1; + *out_glyph = gl->glyph_cache[codepoint - ' ']; + return depth_idx; +} + +static v2 +measure_text(GLCtx *gl, s8 text, b32 monospaced) +{ + v2 result = {0}; + f32 single_space_width = gl->glyph_cache[0].size.w; + for (size i = 0; i < text.len; i++) { + ASSERT(BETWEEN(text.data[i], ' ', '~')); + Glyph g = gl->glyph_cache[text.data[i] - ' ']; + /* TODO: should we consider offset characters in y? */ + if (g.size.h > result.y) + result.y = g.size.h; + result.x += monospaced? single_space_width : g.size.w; + } + return result; +} + static void -draw_rectangle(GLCtx *gl, v2 pos, v2 size, u32 bg, u32 fg) +push_char(GLCtx *gl, v2 vertscale, v2 vertoff, v2 texscale, uv2 colours, i32 char_idx) { - glUniform2uiv(gl->texcolour, 1, (u32 []){fg, bg}); - glUniform2fv(gl->vertscale, 1, size.E); - glUniform2fv(gl->vertoff, 1, pos.E); + glUniform2uiv(gl->texcolour, 1, colours.E); + glUniform1iv(gl->charmap, 1, &char_idx); + glUniform2fv(gl->texscale, 1, texscale.E); + glUniform2fv(gl->vertscale, 1, vertscale.E); + glUniform2fv(gl->vertoff, 1, vertoff.E); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 1); } static void -draw_cell(Term *t, u32 cp, v2 pos, u32 bg, u32 fg) +draw_text(GLCtx *gl, s8 text, v2 position, Colour colour, b32 monospaced) +{ + f32 single_space_width = gl->glyph_cache[0].size.w; + for (size i = 0; i < text.len; i++) { + Glyph g; + i32 glyph_idx = get_gpu_glyph_index(gl, text.data[i], &g); + v2 texscale = {.x = g.size.w / MAX_FONT_SIZE, .y = g.size.h / MAX_FONT_SIZE}; + v2 vertscale = {.x = g.size.w, .y = g.size.h}; + v2 vertoff = {.x = position.x + g.delta.x, .y = position.y + g.delta.y}; + push_char(gl, vertscale, vertoff, texscale, (uv2){.x = colour.rgba}, glyph_idx); + position.x += monospaced? single_space_width : g.size.w; + } +} + +static void +draw_rectangle(GLCtx *gl, Rect r, Colour colour) { - ASSERT(BETWEEN(cp, ' ', '~')); - i32 depth_idx = TEXTURE_GLYPH_COUNT - (cp - ' ') - 1; - Glyph g = t->glyph_cache[cp - ' ']; + push_char(gl, r.size, r.pos, (v2){0}, (uv2){.y = colour.rgba}, 0); +} + +static void +draw_cell(GLCtx *gl, u32 cp, Rect r, u32 bg, u32 fg) +{ + Glyph g; + i32 depth_idx = get_gpu_glyph_index(gl, cp, &g); v2 texscale[2]; texscale[0] = (v2){0}; @@ -109,12 +157,12 @@ draw_cell(Term *t, u32 cp, v2 pos, u32 bg, u32 fg) v2 vertscale[2]; v2 vertoff[2]; - vertscale[0] = (v2){.x = 100, .y = 100}; - vertoff[0] = pos; + vertscale[0] = r.size; + vertoff[0] = r.pos; v2 texture_position; - texture_position.x = (vertscale[0].x - g.size.w) * 0.5 + pos.x;// + g.delta.x; - texture_position.y = (vertscale[0].y - g.size.h) * 0.5 + pos.y;// + g.delta.y + t->fa.deltay; + texture_position.x = (r.size.x - g.size.w) * 0.5 + r.pos.x;// + g.delta.x; + texture_position.y = (r.size.y - g.size.h) * 0.5 + r.pos.y;// + g.delta.y + t->fa.deltay; vertscale[1] = (v2){.x = g.size.w, .y = g.size.h}; vertoff[1] = texture_position; @@ -125,11 +173,11 @@ draw_cell(Term *t, u32 cp, v2 pos, u32 bg, u32 fg) colours[2] = fg; colours[3] = bg; - glUniform2uiv(t->gl.texcolour, 2, colours); - glUniform1iv(t->gl.charmap, 2, charmap); - glUniform2fv(t->gl.texscale, 2, (f32 *)texscale); - glUniform2fv(t->gl.vertscale, 2, (f32 *)vertscale); - glUniform2fv(t->gl.vertoff, 2, (f32 *)vertoff); + glUniform2uiv(gl->texcolour, 2, colours); + glUniform1iv(gl->charmap, 2, charmap); + glUniform2fv(gl->texscale, 2, (f32 *)texscale); + glUniform2fv(gl->vertscale, 2, (f32 *)vertscale); + glUniform2fv(gl->vertoff, 2, (f32 *)vertoff); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 2); } @@ -173,32 +221,44 @@ do_terminal(Term *t, Arena a) clear_colour(); - v2 r2size = {.x = 100, .y = 100}; - static v2 r2pos = {.x = 50, .y = 300}; + Rect r1 = {.pos = {.x = 20, .y = 20}, .size = {.x = 200, .y = 100}}; + Rect r3 = {.pos = {.x = 100, .y = 600}, .size = {.x = 50, .y = 100}}; + static Rect r2 = {.pos = {.x = 50, .y = 300}, .size = {.x = 100, .y = 100}}; static v2 r2dir = {.x = 1, .y = -1 }; - r2pos.x += r2dir.x * t->gl.dt * 100; - r2pos.y += r2dir.y * t->gl.dt * 140; - if (r2pos.x + r2size.x > (f32)t->gl.window_size.w) { - r2pos.x = (f32)t->gl.window_size.w - r2size.x; - r2dir.x *= -1; + r2.pos.x += r2dir.x * t->gl.dt * 100; + r2.pos.y += r2dir.y * t->gl.dt * 140; + if (r2.pos.x + r2.size.x > (f32)t->gl.window_size.w) { + r2.pos.x = (f32)t->gl.window_size.w - r2.size.x; + r2dir.x *= -1; } - if (r2pos.x < 0) { - r2pos.x = 0; - r2dir.x *= -1; + if (r2.pos.x < 0) { + r2.pos.x = 0; + r2dir.x *= -1; } - if (r2pos.y + r2size.y > (f32)t->gl.window_size.h) { - r2pos.y = (f32)t->gl.window_size.h - r2size.y; + if (r2.pos.y + r2.size.y > (f32)t->gl.window_size.h) { + r2.pos.y = (f32)t->gl.window_size.h - r2.size.y; r2dir.y *= -1; } - if (r2pos.y < 0) { - r2pos.y = 0; + if (r2.pos.y < 0) { + r2.pos.y = 0; r2dir.y *= -1; } - draw_rectangle(&t->gl, (v2){.x = 20, .y = 20}, (v2){.x = 200, .y = 100}, 0x7f0000ff, 0); - draw_cell(t, 'E', r2pos, 0x007f00ff, 0x000000ff); - draw_rectangle(&t->gl, (v2){.x = 100, .y = 600}, (v2){.x = 50, .y = 100}, 0x00007fff, 0); + draw_rectangle(&t->gl, r1, (Colour){.rgba = 0x7f0000ff}); + draw_cell(&t->gl, 'E', r2, 0x007f00ff, 0x000000ff); + draw_rectangle(&t->gl, r3, (Colour){.rgba = 0x00007fff}); + + { + s8 fps = s8alloc(&a, 64); + fps.len = snprintf((char *)fps.data, fps.len, "%0.01f ms/f", t->gl.dt * 1e3); + v2 ts = measure_text(&t->gl, fps, 1); + v2 pos = { + .x = t->gl.window_size.w - ts.x - 10, + .y = t->gl.window_size.h - ts.y - 10 + }; + draw_text(&t->gl, fps, pos, (Colour){.rgba = 0x1e9e33ff}, 1); + } }