Commit: ad6e8ba279987b30591c6bd5948837b438c995da
Parent: ae6b1615a8dfc4dc51e5dac04bba57be2d53bad5
Author: Randy Palamar
Date: Sun, 23 Jun 2024 09:32:32 -0600
add text drawing at arbitrary position
Diffstat:
M | build.sh | | | 4 | ++-- |
M | font.c | | | 8 | +++++--- |
M | util.h | | | 28 | ++++++++++++++++------------ |
M | vtgl.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);
+ }
}