Commit: 80f2a42dfebe74ca4d6a8f7488c3b7424d88b2b9
Parent: 4c5dbf0d04ad4f9c38566da51daaa4096d91495e
Author: Randy Palamar
Date: Sat, 24 Aug 2024 10:55:26 -0600
rework glyph rendering to allow for cache insertion
Diffstat:
5 files changed, 86 insertions(+), 46 deletions(-)
diff --git a/font.c b/font.c
@@ -52,9 +52,17 @@ err_fc1:
return 0;
}
-static u8 *
-render(FontAtlas *fa, Glyph *g, u32 cp)
+static u32 *
+render_glyph(FontAtlas *fa, Arena a, u32 cp, Glyph *out_glyph, i32 *out_idx)
{
+ /* NOTE: first check if glyph is in the cache and valid */
+ ASSERT(BETWEEN(cp, ' ', '~'));
+ *out_idx = cp - ' ';
+ if (fa->glyph_cache[cp - ' '].size.w > 0) {
+ *out_glyph = fa->glyph_cache[cp - ' '];
+ return NULL;
+ }
+
FT_GlyphSlot gs = NULL;
for (u32 i = 0; i < fa->nfonts; i++) {
FT_Face face = fa->fonts[i].face;
@@ -70,12 +78,22 @@ render(FontAtlas *fa, Glyph *g, u32 cp)
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;
+ out_glyph->size.w = gs->bitmap.width;
+ out_glyph->size.h = gs->bitmap.rows;
+ out_glyph->delta.x = gs->bitmap_left;
+ out_glyph->delta.y = gs->bitmap_top - out_glyph->size.h;
+
+ u32 *rgba_bitmap = alloc(&a, u32, MAX_FONT_SIZE * MAX_FONT_SIZE);
+ for (u32 i = 0; i < out_glyph->size.h; i++) {
+ for (u32 j = 0; j < out_glyph->size.w; j++) {
+ /* TODO: handled coloured glyphs */
+ /* TODO: byte order of colour should be swapped to match GL_RBGA */
+ Colour pixel = {.r = gs->bitmap.buffer[i * out_glyph->size.w + j]};
+ rgba_bitmap[i * MAX_FONT_SIZE + j] = pixel.rgba;
+ }
+ }
- return gs->bitmap.buffer;
+ return rgba_bitmap;
}
static void
@@ -86,14 +104,21 @@ update_font_metrics(Term *t)
t->fa.deltay = 0;
for (u32 i = ' '; i <= '~'; i++) {
Glyph g;
- render(&t->fa, &g, i);
+ i32 _unused;
+ render_glyph(&t->fa, t->arena_for_frame, i, &g, &_unused);
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->gl.glyph_cache[i - ' '] = g;
+ t->fa.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;
+ t->fa.glyph_cache[0].size.w = t->fa.size.w;
+}
+
+static void
+invalidate_font_cache(FontAtlas *fa)
+{
+ mem_clear((u8 *)fa->glyph_cache, 0, sizeof(*fa->glyph_cache) * fa->glyph_cache_len);
}
static i32
@@ -127,7 +152,6 @@ init_fonts(Term *t, Arena *a)
die("init_fonts: no valid font patterns\n");
}
- t->gl.glyph_cache_len = 0x7E - 0x20 + 1;
- t->gl.glyph_cache = alloc(a, Glyph, t->gl.glyph_cache_len);
- update_font_metrics(t);
+ t->fa.glyph_cache_len = 0x7E - 0x20 + 1;
+ t->fa.glyph_cache = alloc(a, Glyph, t->fa.glyph_cache_len);
}
diff --git a/frag_render.glsl b/frag_render.glsl
@@ -8,9 +8,9 @@ in VS_OUT {
} fs_in;
uniform sampler2DArray u_texslot;
-uniform int u_charmap[256];
-uniform vec2 u_texscale[256];
-uniform uvec2 u_texcolour[256];
+uniform int u_charmap[512];
+uniform vec2 u_texscale[512];
+uniform uvec2 u_texcolour[512];
void main()
{
@@ -20,6 +20,6 @@ void main()
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;
+ float a = u_texscale[fs_in.index].x == 0 ? 0 : texture(u_texslot, tex_coord).a;
colour = mix(bg, fg, a);
}
diff --git a/util.h b/util.h
@@ -230,12 +230,12 @@ typedef struct {
u32 mode;
u32 glyph_tex;
- u32 glyph_cache_len;
- Glyph *glyph_cache;
} GLCtx;
-/* NOTE: must match count in render shaders */
-#define PUSH_BUFFER_CAP 256
+/* NOTE: This must match the number in the shaders & the number glyphs in
+ * the gpu glyph cache. By doing this we ensure that filling a single push buffer
+ * will not evict a needed glyph texture from the GPU */
+#define PUSH_BUFFER_CAP 512
typedef struct {
v2 vertscales[PUSH_BUFFER_CAP];
v2 vertoffsets[PUSH_BUFFER_CAP];
@@ -249,7 +249,7 @@ typedef struct {
#include FT_FREETYPE_H
#include <fontconfig/fontconfig.h>
-#define MAX_FONT_SIZE 128.0f
+#define MAX_FONT_SIZE 128
#include "util.c"
@@ -273,6 +273,8 @@ typedef struct {
u32 nfonts;
uv2 size;
i32 deltay;
+ Glyph *glyph_cache;
+ u32 glyph_cache_len;
} FontAtlas;
typedef union {
diff --git a/vert_render.glsl b/vert_render.glsl
@@ -3,8 +3,8 @@
in vec2 position;
uniform mat4 u_Pmat;
-uniform vec2 u_vertscale[256];
-uniform vec2 u_vertoff[256];
+uniform vec2 u_vertscale[512];
+uniform vec2 u_vertoff[512];
out VS_OUT {
vec2 tex_coord;
diff --git a/vtgl.c b/vtgl.c
@@ -117,22 +117,25 @@ resize(Term *t)
static void
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);
+ invalidate_font_cache(&t->fa);
+
for (u32 i = ' '; i <= '~'; i++) {
+ i32 depth_idx;
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);
+ u32 *data = render_glyph(&t->fa, t->arena_for_frame, i, &g, &depth_idx);
+ if (data) {
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, depth_idx,
+ MAX_FONT_SIZE, MAX_FONT_SIZE, 1, GL_RGBA,
+ GL_UNSIGNED_BYTE, data);
+ }
}
+ update_font_metrics(t);
}
static void
@@ -161,16 +164,22 @@ update_uniforms(Term *t, enum shader_stages stage)
}
set_projection_matrix(&t->gl);
+ /* TODO: this doesn't need to be called so often */
update_font_textures(t);
}
static i32
-get_gpu_glyph_index(GLCtx *gl, u32 codepoint, Glyph *out_glyph)
+get_gpu_glyph_index(Term *t, 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 - ' '];
+ i32 depth_idx;
+ u32 *data = render_glyph(&t->fa, t->arena_for_frame, codepoint, out_glyph, &depth_idx);
+ if (data) {
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, depth_idx,
+ MAX_FONT_SIZE, MAX_FONT_SIZE, 1, GL_RGBA,
+ GL_UNSIGNED_BYTE, data);
+ }
return depth_idx;
}
@@ -178,6 +187,7 @@ static v2
measure_text(GLCtx *gl, s8 text, b32 monospaced)
{
v2 result = {0};
+#if 0
f32 single_space_width = gl->glyph_cache[0].size.w;
for (size i = 0; i < text.len; i++) {
ASSERT(BETWEEN(text.data[i], ' ', '~'));
@@ -187,6 +197,7 @@ measure_text(GLCtx *gl, s8 text, b32 monospaced)
result.y = g.size.h;
result.x += monospaced? single_space_width : g.size.w;
}
+#endif
return result;
}
@@ -241,6 +252,7 @@ push_empty_cell_rect(RenderPushBuffer *rpb, Term *t, u32 minrow, u32 maxrow, u32
static void
draw_text(RenderPushBuffer *rpb, GLCtx *gl, s8 text, v2 position, Colour colour, b32 monospaced)
{
+ #if 0
f32 single_space_width = gl->glyph_cache[0].size.w;
for (size i = 0; i < text.len; i++) {
Glyph g;
@@ -254,6 +266,7 @@ draw_text(RenderPushBuffer *rpb, GLCtx *gl, s8 text, v2 position, Colour colour,
push_char(rpb, gl, vertscale, vertoff, texscale, (uv2){.x = colour.rgba}, glyph_idx);
position.x += monospaced? single_space_width : g.size.w;
}
+ #endif
}
static void
@@ -262,15 +275,16 @@ draw_rectangle(RenderPushBuffer *rpb, GLCtx *gl, Rect r, Colour colour)
push_char(rpb, gl, r.size, r.pos, (v2){0}, (uv2){.y = colour.rgba}, 0);
}
+/* TODO: this doesn't need to take the whole Term * */
static void
-push_cell(RenderPushBuffer *rpb, GLCtx *gl, Cell c, Rect r, f32 font_text_dy)
+push_cell(RenderPushBuffer *rpb, Term *t, Cell c, Rect r, f32 font_text_dy)
{
- u32 idx = get_render_push_buffer_idx(rpb, gl, 2);
+ u32 idx = get_render_push_buffer_idx(rpb, &t->gl, 2);
Glyph g;
/* TODO: is defaulting to space correct? */
u32 cp = c.cp? c.cp : ' ';
- i32 depth_idx = get_gpu_glyph_index(gl, cp, &g);
+ i32 depth_idx = get_gpu_glyph_index(t, cp, &g);
rpb->vertscales[idx + 0] = r.size;
rpb->vertscales[idx + 1] = (v2){.x = g.size.w, .y = g.size.h};
@@ -283,8 +297,8 @@ push_cell(RenderPushBuffer *rpb, GLCtx *gl, Cell c, Rect r, f32 font_text_dy)
rpb->texscales[idx + 0] = (v2){0};
rpb->texscales[idx + 1] = (v2){
- .x = g.size.w / MAX_FONT_SIZE,
- .y = g.size.h / MAX_FONT_SIZE,
+ .x = g.size.w / (f32)MAX_FONT_SIZE,
+ .y = g.size.h / (f32)MAX_FONT_SIZE,
};
rpb->charmap[idx + 0] = depth_idx;
@@ -317,7 +331,7 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
Rect cr = {.pos = {.x = tl.x, .y = tl.y - cs.h}, .size = cs};
for (u32 r = 0; r < t->size.h; r++) {
for (u32 c = 0; c < t->size.w; c++) {
- push_cell(rpb, &t->gl, tv->fb.rows[r][c], cr, t->fa.deltay);
+ push_cell(rpb, t, tv->fb.rows[r][c], cr, t->fa.deltay);
cr.pos.x += cs.w;
}
cr.pos.x = tl.x;
@@ -334,7 +348,7 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
};
Cell cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x];
cursor.style.attr ^= ATTR_INVERSE;
- push_cell(rpb, &t->gl, cursor, cr, t->fa.deltay);
+ push_cell(rpb, t, cursor, cr, t->fa.deltay);
}
/* NOTE: draw selection if active */
@@ -352,7 +366,7 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
for (; curs.x < t->size.w; curs.x++) {
Cell cell = tv->fb.rows[curs.y][curs.x];
cell.style.attr ^= ATTR_INVERSE;
- push_cell(rpb, &t->gl, cell, cr, t->fa.deltay);
+ push_cell(rpb, t, cell, cr, t->fa.deltay);
cr.pos.x += cs.w;
}
curs.x = 0;
@@ -363,7 +377,7 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
for (; curs.x <= end.x; curs.x++) {
Cell cell = tv->fb.rows[curs.y][curs.x];
cell.style.attr ^= ATTR_INVERSE;
- push_cell(rpb, &t->gl, cell, cr, t->fa.deltay);
+ push_cell(rpb, t, cell, cr, t->fa.deltay);
cr.pos.x += cs.w;
}
}
@@ -718,14 +732,14 @@ do_terminal(Term *t)
static f32 last_frame_time;
f32 frame_start_time = (f32)glfwGetTime();
- if (t->gl.flags & NEEDS_RESIZE)
- resize(t);
-
if (t->gl.flags & UPDATE_RENDER_UNIFORMS)
update_uniforms(t, SHADER_RENDER);
if (t->gl.flags & UPDATE_POST_UNIFORMS)
update_uniforms(t, SHADER_POST);
+ if (t->gl.flags & NEEDS_RESIZE)
+ resize(t);
+
size parsed_lines = 0;
if (os_child_data_available(t->child)) {
RingBuf *rb = &t->views[t->view_idx].log;