vtgl

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

Commit: 369f2e36d8cba0ca74369a1ec4497537f62d21b3
Parent: 911e62d70bd3674b0da791e5bb067ded5d5cfb1f
Author: Randy Palamar
Date:   Mon,  2 Sep 2024 13:33:20 -0600

improve robustness of glyph searching/uploading

* When a Bold/Italic Glyph is not found for the font we should
  just try the same font with normal style first before we try a
  new font
* We should just use a variable to check if the glyph has been
  uploaded to the GPU or not. This way we can ensure we don't end
  up drawing an empty texture
* ' ' can have its width adjusted when rendered, that is already
  the slow path.

Diffstat:
Mconfig.def.h | 8+++++---
Mfont.c | 54+++++++++++++++++++++++++++---------------------------
Mutil.h | 7+++++--
Mvtgl.c | 23+++++++++--------------
4 files changed, 46 insertions(+), 46 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -1,15 +1,17 @@ /* See LICENSE for copyright details */ static FontDesc g_fonts[][FS_LAST] = { { - [FS_NORMAL] = {.path = "/usr/share/fonts/gofont/Go-Mono.ttf", .height = 24}, - [FS_BOLD] = {.path = "/usr/share/fonts/gofont/Go-Mono-Bold.ttf", .height = 24}, + [FS_NORMAL] = {.path = "/usr/share/fonts/gofont/Go-Mono.ttf", .height = 24}, + [FS_BOLD] = {.path = "/usr/share/fonts/gofont/Go-Mono-Bold.ttf", .height = 24}, + [FS_ITALIC] = {.path = "/usr/share/fonts/gofont/Go-Mono-Italic.ttf", .height = 24}, + [FS_BOLD_ITALIC] = {.path = "/usr/share/fonts/gofont/Go-Mono-Bold-Italic.ttf", .height = 24}, }, }; /* NOTE: terminal padding in pixels */ static v2 g_term_pad = {.w = 8, .h = 8}; /* NOTE: cell padding in pixels (glyphs will be centered) */ -static v2 g_cell_pad = {.w = 0, .h = 2}; +static v2 g_cell_pad = {.w = 0, .h = 0}; static u8 g_tabstop = 8; diff --git a/font.c b/font.c @@ -45,8 +45,6 @@ recycle_cache(GlyphCache *gc) cg->next_with_same_hash = sentinel->next_with_same_hash; sentinel->next_with_same_hash = last_index; - cg->g.size = (uv2){0}; - gc->stats.recycle_count++; } @@ -64,6 +62,7 @@ pop_free_glyph_entry(GlyphCache *gc) CachedGlyph *cg = gc->glyphs + result; sentinel->next_with_same_hash = cg->next_with_same_hash; cg->next_with_same_hash = 0; + cg->uploaded_to_gpu = 0; return result; } @@ -124,18 +123,17 @@ static u32 glyph_buf[MAX_FONT_SIZE * MAX_FONT_SIZE * sizeof(u32)]; static u8 temp_buf[MAX_FONT_SIZE * MAX_FONT_SIZE * sizeof(u8)]; static u32 * -render_glyph(FontAtlas *fa, u32 cp, enum face_style style, Glyph *out_glyph, u32 *out_idx) +render_glyph(FontAtlas *fa, u32 cp, enum face_style style, CachedGlyph **out_glyph, u32 *out_idx) { /* NOTE: first check if glyph is in the cache and valid */ + /* NOTE: 8 MSB are not used for UTF8 so we can use that to store the style of the glyph */ u32 idx = get_glyph_entry_index(&fa->glyph_cache, cp|(1 << (30 - style))); CachedGlyph *cg = fa->glyph_cache.glyphs + idx; - *out_idx = idx; - /* TODO: implement a way of ensuring the glyph is actually uploaded to the GPU */ - if (cg->g.size.w) { - *out_glyph = cg->g; + *out_idx = idx; + *out_glyph = cg; + if (cg->uploaded_to_gpu) return NULL; - } i32 glyph_idx = 0; i32 font_idx = 0; @@ -143,6 +141,8 @@ render_glyph(FontAtlas *fa, u32 cp, enum face_style style, Glyph *out_glyph, u32 if (!fa->fonts[i][style].buf) style = FS_NORMAL; glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][style].font_info, cp); + if (!glyph_idx) + glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][FS_NORMAL].font_info, cp); if (glyph_idx) { font_idx = i; break; @@ -175,7 +175,10 @@ render_glyph(FontAtlas *fa, u32 cp, enum face_style style, Glyph *out_glyph, u32 rgba_bitmap[i * cg->g.size.w + j] = pixel; } } - *out_glyph = cg->g; + + /* NOTE: ' ' has 0 size in freetype but we need it to have a width! */ + if (cp == ' ') cg->g.size.w = fa->size.w; + return rgba_bitmap; } @@ -188,17 +191,10 @@ update_font_metrics(FontAtlas *fa) fa->size.h = scale * (y1 - y0); fa->size.w = scale * (x1 - x0); fa->deltay = -scale * y0; - - /* NOTE: ' ' has 0 size in freetype but we need it to have a width! */ - /* TODO: technically this is a hack which assumes that ' ' will always be in the cache */ - Glyph g; - u32 index; - render_glyph(fa, ' ', FS_NORMAL, &g, &index); - fa->glyph_cache.glyphs[index].g.size.w = fa->size.w; } static void -initialize_glyph_cache(FontAtlas *fa) +font_atlas_update(FontAtlas *fa) { GlyphCache *gc = &fa->glyph_cache; mem_clear((u8 *)gc->glyphs, 0, sizeof(*gc->glyphs) * gc->cache_len); @@ -206,6 +202,7 @@ initialize_glyph_cache(FontAtlas *fa) for(u32 i = 0; i < gc->cache_len - 1; i++) gc->glyphs[i].next_with_same_hash = i + 1; get_and_clear_glyph_cache_stats(gc); + update_font_metrics(fa); } static i32 @@ -214,6 +211,8 @@ shift_font_sizes(FontAtlas *fa, i32 size_delta) for (u32 i = 0; i < fa->nfonts; i++) { for (u32 j = 0; j < FS_LAST; j++) { Font *f = &fa->fonts[i][j]; + if (!f->buf) continue; + i32 newsize = f->fontsize + size_delta; CLAMP(newsize, 8, MAX_FONT_SIZE); f->fontsize = newsize; @@ -226,22 +225,23 @@ shift_font_sizes(FontAtlas *fa, i32 size_delta) static void init_fonts(Term *t, Arena *a) { - u32 nfontstrs = ARRAY_COUNT(g_fonts); + FontAtlas *fa = &t->fa; + u32 n_fonts = ARRAY_COUNT(g_fonts); - for (u32 i = 0; i < nfontstrs; i++) - t->fa.fonts[i] = alloc(a, Font, FS_LAST); - for (t->fa.nfonts = 0; t->fa.nfonts < nfontstrs; t->fa.nfonts++) { + fa->fonts = alloc(a, typeof(*fa->fonts), n_fonts); + for (fa->nfonts = 0; fa->nfonts < n_fonts; fa->nfonts++) { for (u32 i = 0; i < FS_LAST; i++) { - if (!g_fonts[t->fa.nfonts][i].path) + if (!g_fonts[fa->nfonts][i].path) continue; - init_font(&t->fa, &t->fa.fonts[t->fa.nfonts][i], &g_fonts[t->fa.nfonts][i]); + init_font(fa, &fa->fonts[fa->nfonts][i], &g_fonts[fa->nfonts][i]); } } static_assert(ISPOWEROFTWO(GLYPH_CACHE_LEN), "GLYPH_CACHE_LEN must be a power of two!"); - t->fa.glyph_cache.cache_len = GLYPH_CACHE_LEN; - t->fa.glyph_cache.glyphs = alloc(a, CachedGlyph, t->fa.glyph_cache.cache_len); - t->fa.glyph_cache.hash_table = alloc(a, u32, t->fa.glyph_cache.cache_len); + GlyphCache *gc = &fa->glyph_cache; + gc->cache_len = GLYPH_CACHE_LEN; + gc->glyphs = alloc(a, typeof(*gc->glyphs), gc->cache_len); + gc->hash_table = alloc(a, typeof(*gc->hash_table), gc->cache_len); - initialize_glyph_cache(&t->fa); + font_atlas_update(fa); } diff --git a/util.h b/util.h @@ -290,13 +290,15 @@ typedef struct { } FontDesc; typedef struct { + stbtt_fontinfo font_info; u8 *buf; i32 bufsize; i32 fontsize; f32 stbtt_scale; - stbtt_fontinfo font_info; } Font; +typedef Font FontFamily[FS_LAST]; + #define GLYPH_CACHE_LEN PUSH_BUFFER_CAP typedef struct { /* distance to shift glyph from bounding box origin */ @@ -310,6 +312,7 @@ typedef struct { u32 cp; u32 next_with_same_hash; u16 prev, next; + b32 uploaded_to_gpu; } CachedGlyph; typedef struct { @@ -326,7 +329,7 @@ typedef struct { } GlyphCache; typedef struct { - Font *fonts[FS_LAST]; + FontFamily *fonts; u32 nfonts; uv2 size; i32 deltay; diff --git a/vtgl.c b/vtgl.c @@ -134,19 +134,7 @@ update_font_textures(GLCtx *gl, FontAtlas *fa) MAX_FONT_SIZE, MAX_FONT_SIZE, TEXTURE_GLYPH_COUNT, 0, GL_RED, GL_UNSIGNED_BYTE, 0); - initialize_glyph_cache(fa); - - /* TODO: remove this */ - u32 depth_idx; - Glyph g; - u32 *data = render_glyph(fa, ' ', FS_NORMAL, &g, &depth_idx); - if (data) { - ASSERT(depth_idx); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, depth_idx, - g.size.w, g.size.h, 1, GL_RGBA, - GL_UNSIGNED_BYTE, data); - } - update_font_metrics(fa); + font_atlas_update(fa); } static void @@ -183,13 +171,17 @@ static i32 get_gpu_glyph_index(FontAtlas *fa, u32 codepoint, enum face_style style, Glyph *out_glyph) { u32 depth_idx; - u32 *data = render_glyph(fa, codepoint, style, out_glyph, &depth_idx); + CachedGlyph *cg; + u32 *data = render_glyph(fa, codepoint, style, &cg, &depth_idx); + *out_glyph = cg->g; if (data) { ASSERT(depth_idx); glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, depth_idx, out_glyph->size.w, out_glyph->size.h, 1, GL_RGBA, GL_UNSIGNED_BYTE, data); + cg->uploaded_to_gpu = 1; } + ASSERT(cg->uploaded_to_gpu); return depth_idx; } @@ -590,6 +582,9 @@ fb_callback(GLFWwindow *win, i32 w, i32 h) glBindTexture(GL_TEXTURE_2D, t->gl.fb_tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + /* NOTE: reactive the glyph texture unit */ + glActiveTexture(GL_TEXTURE0); + t->gl.flags |= NEEDS_RESIZE|NEEDS_BLIT; }