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:
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;
 }