vtgl

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

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:
Mfont.c | 50+++++++++++++++++++++++++++++++++++++-------------
Mfrag_render.glsl | 8++++----
Mutil.h | 12+++++++-----
Mvert_render.glsl | 4++--
Mvtgl.c | 58++++++++++++++++++++++++++++++++++++----------------------
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;