Commit: 9aa421f633daa234458ad5f905bc89927f49edda
Parent: a21c5f2b54962555719b33ec62cbee1b5c486ef8
Author: Randy Palamar
Date: Sat, 22 Jun 2024 17:36:32 -0600
add ascii text rendering
Diffstat:
A | font.c | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | frag.glsl | | | 14 | +++++++------- |
M | main.c | | | 6 | ++++-- |
M | os_unix.c | | | 34 | ++++++++++++++++++++++++++++++++++ |
M | util.c | | | 2 | +- |
M | util.h | | | 55 | ++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
M | vtgl.c | | | 96 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
7 files changed, 271 insertions(+), 25 deletions(-)
diff --git a/font.c b/font.c
@@ -0,0 +1,89 @@
+/* See LICENSE for copyright details */
+static void
+init_font(FT_Library *ftlib, Font *f, char *path, u32 fontsize)
+{
+ if (!*ftlib && FT_Init_FreeType(ftlib))
+ die("Failed to init FreeType\n");
+
+ os_mapped_file map = os_map_file(path, OS_MAP_READ, OS_MAP_PRIVATE);
+
+ f->bufsize = map.len;
+ f->buf = map.data;
+
+ if (FT_New_Memory_Face(*ftlib, f->buf, f->bufsize, 0, &f->face))
+ goto err1;
+ if (FT_Set_Pixel_Sizes(f->face, 0, fontsize))
+ goto err2;
+ if (FT_Select_Charmap(f->face, FT_ENCODING_UNICODE))
+ goto err2;
+
+ return;
+err2:
+ FT_Done_Face(f->face);
+err1:
+ os_unmap_file(f->buf, f->bufsize);
+ die("Failed to init font: %s\n", path);
+}
+
+static u8 *
+render(FontAtlas *fa, Glyph *g, u32 cp)
+{
+ FT_GlyphSlot gs = NULL;
+ for (u32 i = 0; i < fa->nfonts; i++) {
+ FT_Face face = fa->fonts[i].face;
+ u32 fcp = FT_Get_Char_Index(face, cp);
+ if (fcp && !FT_Load_Glyph(face, fcp, FT_LOAD_RENDER)) {
+ gs = face->glyph;
+ break;
+ }
+ }
+ if (!gs) {
+ if (FT_Load_Glyph(fa->fonts[0].face, 0, FT_LOAD_RENDER))
+ die("can't load replacement glyph\n");
+ gs = fa->fonts[0].face->glyph;
+ }
+
+ g->size.w = gs->bitmap.width;
+ g->size.h = gs->bitmap.rows;
+ g->delta.x = gs->bitmap_left;
+ g->delta.y = gs->bitmap_top - g->size.h;
+
+ return gs->bitmap.buffer;
+}
+
+static void
+update_font_metrics(Term *t)
+{
+ t->fa.size.h = 0;
+ t->fa.size.w = 0;
+ t->fa.deltay = 0;
+ for (u32 i = ' '; i <= '~'; i++) {
+ Glyph g;
+ render(&t->fa, &g, i);
+ 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;
+ }
+}
+
+static i32
+set_font_sizes(FontAtlas *fa, u32 fontsize)
+{
+ /* invalidate cache first incase a font fails to resize */
+ for (u32 i = 0; i < fa->nfonts; i++)
+ if (FT_Set_Pixel_Sizes(fa->fonts[i].face, 0, fontsize))
+ return -1;
+ return 0;
+}
+
+static void
+init_fonts(Term *t, char **paths, u32 npaths, u32 fontsize, Arena *a)
+{
+ t->fa.fonts = alloc(a, Font, npaths);
+ 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);
+}
diff --git a/frag.glsl b/frag.glsl
@@ -7,9 +7,9 @@ in VS_OUT {
flat int index;
} fs_in;
-//uniform sampler2DArray u_texslot;
-//uniform int u_charmap[512];
-//uniform vec2 u_texscale[512];
+uniform sampler2DArray u_texslot;
+uniform int u_charmap[512];
+uniform vec2 u_texscale[512];
uniform uvec2 u_texcolour[512];
void main()
@@ -17,9 +17,9 @@ 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];
+ 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);
+ float a = u_texscale[fs_in.index].x == 0 ? 0 : texture(u_texslot, tex_coord).r;
+ colour = mix(bg, fg, a);
}
diff --git a/main.c b/main.c
@@ -6,8 +6,6 @@
#include "util.h"
-#include "os_unix.c"
-
#ifndef _DEBUG
static void do_debug(void) { }
#else
@@ -236,7 +234,11 @@ main(void)
Arena memory = os_new_arena(16 * MEGABYTE);
Term term = {0};
+ static char *font_paths[] = {
+ "/usr/share/fonts/gofont/Go-Mono.ttf",
+ };
init_window(&term);
+ init_fonts(&term, font_paths, ARRAY_COUNT(font_paths), 64, &memory);
f32 last_time = 0;
while (!glfwWindowShouldClose(term.gl.window)) {
diff --git a/os_unix.c b/os_unix.c
@@ -9,6 +9,10 @@ typedef struct {
size filesize;
os_filetime timestamp;
} os_file_stats;
+typedef s8 os_mapped_file;
+
+#define OS_MAP_READ PROT_READ
+#define OS_MAP_PRIVATE MAP_PRIVATE
static Arena
os_new_arena(size cap)
@@ -55,3 +59,33 @@ os_read_file(Arena *a, char *name, size filesize)
return text;
}
+
+static os_mapped_file
+os_map_file(char *path, i32 mode, i32 perm)
+{
+ os_mapped_file res = {0};
+
+ i32 open_mode = 0;
+ switch(mode) {
+ case OS_MAP_READ: open_mode = O_RDONLY; break;
+ default: ASSERT(0);
+ }
+
+ i32 fd = open(path, open_mode);
+ os_file_stats fs = os_get_file_stats(path);
+
+ ASSERT(fd > 0 && fs.filesize > 0);
+
+ res.len = fs.filesize;
+ res.data = mmap(NULL, res.len, mode, perm, fd, 0);
+ ASSERT(res.data != MAP_FAILED);
+ close(fd);
+
+ return res;
+}
+
+static void
+os_unmap_file(u8 *data, size len)
+{
+ munmap(data, len);
+}
diff --git a/util.c b/util.c
@@ -27,7 +27,7 @@ __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 padding = -(uintptr_t)a->beg & (align - 1);
size available = a->end - a->beg - padding;
ASSERT(available > 0 && count < available / len);
diff --git a/util.h b/util.h
@@ -19,7 +19,9 @@
#define MEGABYTE (1024ULL * 1024ULL)
-#define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a))
+#define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a))
+#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
typedef float f32;
typedef double f64;
@@ -36,6 +38,12 @@ typedef union {
} iv2;
typedef union {
+ struct { u32 x, y; };
+ struct { u32 w, h; };
+ u32 E[2];
+} uv2;
+
+typedef union {
struct { f32 x, y; };
f32 E[2];
} v2;
@@ -59,7 +67,10 @@ typedef struct { size len; u8 *data; } s8;
#define GL_UNIFORMS \
X(Pmat) \
+ X(charmap) \
X(texcolour) \
+ X(texscale) \
+ X(texslot) \
X(vertoff) \
X(vertscale)
@@ -72,6 +83,8 @@ typedef struct {
GLFWwindow *window;
iv2 window_size;
+ f32 dt;
+
u32 vao, vbo;
u32 glyph_tex;
@@ -79,17 +92,53 @@ typedef struct {
#define X(name) i32 name;
union {
struct { GL_UNIFORMS };
- i32 uniforms[4];
+ i32 uniforms[7];
};
#undef X
u32 flags;
} GLCtx;
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+typedef struct {
+ u8 *buf;
+ i32 bufsize;
+ FT_Face face;
+} Font;
+
+typedef struct {
+ Font *fonts;
+ u32 nfonts;
+ uv2 size;
+ i32 deltay;
+} FontAtlas;
+
typedef struct {
- GLCtx gl;
+ /* 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;
#include "util.c"
+#ifdef __unix__
+#include "os_unix.c"
+#else
+#error Unsupported Platform!
+#endif
+
+#include "font.c"
+
#endif /* _UTIL_H_ */
diff --git a/vtgl.c b/vtgl.c
@@ -6,6 +6,9 @@
#include "util.h"
+#define MAX_FONT_SIZE 128
+#define TEXTURE_GLYPH_COUNT 128
+
#define X(name) "u_"#name,
static char *uniform_names[] = { GL_UNIFORMS };
#undef X
@@ -42,27 +45,96 @@ resize(GLCtx *gl)
}
static void
-update_uniforms(GLCtx *gl)
+update_font_textures(Term *t)
+{
+ update_font_metrics(t);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, t->gl.glyph_tex);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8,
+ MAX_FONT_SIZE, MAX_FONT_SIZE, TEXTURE_GLYPH_COUNT,
+ 0, GL_RED, GL_UNSIGNED_BYTE, 0);
+
+ for (u32 i = ' '; i <= '~'; i++) {
+ Glyph g;
+ u8 *bitmap = render(&t->fa, &g, i);
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0,
+ 0, 0, TEXTURE_GLYPH_COUNT - (i - ' ') - 1,
+ g.size.w, g.size.h, 1,
+ GL_RED, GL_UNSIGNED_BYTE, bitmap);
+ }
+}
+
+static void
+update_uniforms(Term *t)
{
- 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);
+ for (u32 i = 0; i < ARRAY_COUNT(t->gl.uniforms); i++)
+ t->gl.uniforms[i] = glGetUniformLocation(t->gl.program, uniform_names[i]);
+ t->gl.flags &= ~UPDATE_UNIFORMS;
+
+ resize(&t->gl);
+
+ glUniform1i(t->gl.texslot, 0);
+ update_font_textures(t);
}
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);
+ glUniform2fv(gl->vertscale, 1, size.E);
+ glUniform2fv(gl->vertoff, 1, pos.E);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 1);
}
+static void
+draw_cell(Term *t, u32 cp, v2 pos, u32 bg, u32 fg)
+{
+ ASSERT(BETWEEN(cp, ' ', '~'));
+ i32 depth_idx = TEXTURE_GLYPH_COUNT - (cp - ' ') - 1;
+ Glyph g = t->glyph_cache[cp - ' '];
+
+ v2 texscale[2];
+ texscale[0] = (v2){0};
+ texscale[1] = (v2){
+ .x = g.size.w / 128.0f,
+ .y = g.size.h / 128.0f,
+ };
+
+
+ i32 charmap[2];
+ charmap[0] = depth_idx;
+ charmap[1] = depth_idx;
+
+ v2 vertscale[2];
+ v2 vertoff[2];
+ vertscale[0] = (v2){.x = 100, .y = 100};
+ vertoff[0] = 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;
+
+ vertscale[1] = (v2){.x = g.size.w, .y = g.size.h};
+ vertoff[1] = texture_position;
+
+ u32 colours[4];
+ colours[0] = fg;
+ colours[1] = bg;
+ 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);
+
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 2);
+}
+
+
/* NOTE: called when the window was resized */
static void
fb_callback(GLFWwindow *win, i32 w, i32 h)
@@ -94,7 +166,7 @@ DEBUG_EXPORT void
do_terminal(Term *t, Arena a)
{
if (t->gl.flags & UPDATE_UNIFORMS)
- update_uniforms(&t->gl);
+ update_uniforms(t);
if (t->gl.flags & NEEDS_RESIZE)
resize(&t->gl);
@@ -127,6 +199,6 @@ do_terminal(Term *t, Arena a)
}
draw_rectangle(&t->gl, (v2){.x = 20, .y = 20}, (v2){.x = 200, .y = 100}, 0x7f0000ff, 0);
- draw_rectangle(&t->gl, r2pos, r2size, 0x007f00ff, 0);
+ draw_cell(t, 'E', r2pos, 0x007f00ff, 0x000000ff);
draw_rectangle(&t->gl, (v2){.x = 100, .y = 600}, (v2){.x = 50, .y = 100}, 0x00007fff, 0);
}