Commit: 7451ad886ad12dd0bf84fa6f282433cd5711abaf
Parent: c0aeb541f3ff1298c808165ff98b51986fcf23d9
Author: Randy Palamar
Date: Sun, 20 Oct 2024 12:01:42 -0600
make the glyph cache track gpu tile positions
Diffstat:
M | font.c | | | 129 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ |
M | util.c | | | 7 | +++++++ |
M | util.h | | | 14 | +++++++++----- |
M | vtgl.c | | | 22 | +++++++++------------- |
4 files changed, 115 insertions(+), 57 deletions(-)
diff --git a/font.c b/font.c
@@ -21,6 +21,15 @@ compute_glyph_hash(GlyphCache *gc, u32 cp)
return result;
}
+static uv2
+unpack_gpu_tile_coord(u32 gpu_index)
+{
+ uv2 result;
+ result.x = gpu_index & 0xFFFF;
+ result.y = gpu_index >> 16;
+ return result;
+}
+
static void
recycle_cache(GlyphCache *gc)
{
@@ -64,6 +73,12 @@ pop_free_glyph_entry(GlyphCache *gc)
cg->next_with_same_hash = 0;
cg->uploaded_to_gpu = 0;
+ uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index);
+ u32 base_tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x;
+ for (u32 i = 0; i < cg->tile_count; i++)
+ gc->occupied_tiles[base_tile_index + i] = 0;
+ cg->tile_count = 0;
+
return result;
}
@@ -123,6 +138,7 @@ static u32 *
render_glyph(Arena *a, FontAtlas *fa, u32 cp, enum face_style style, CachedGlyph **out_glyph, u32 *out_idx)
{
BEGIN_TIMED_BLOCK();
+ GlyphCache *gc = &fa->glyph_cache;
u32 *rgba_bitmap = NULL;
/* NOTE: first check if glyph is in the cache and valid */
@@ -136,6 +152,8 @@ render_glyph(Arena *a, FontAtlas *fa, u32 cp, enum face_style style, CachedGlyph
if (cg->uploaded_to_gpu)
goto end;
+ ASSERT(cg->tile_count == 0);
+
i32 glyph_idx = 0;
i32 font_idx = 0;
for (u32 i = 0; i < fa->nfonts; i++) {
@@ -163,7 +181,7 @@ render_glyph(Arena *a, FontAtlas *fa, u32 cp, enum face_style style, CachedGlyph
stbtt_MakeGlyphBitmapSubpixel(*a, &f->font_info, render_buf, width, height,
width, scale, scale, 0, 0, glyph_idx);
- /* NOTE: this looks wierd but some 'wide' glyphs are not actually wide but still
+ /* NOTE: this looks weird but some 'wide' glyphs are not actually wide but still
* need to be displayed as such (eg. ト). x1 can be used to determine this. */
cg->tile_count = (u16)(width + (fa->info.w + 1) / 2) / fa->info.w;
if (cg->tile_count * fa->info.w < x1) cg->tile_count++;
@@ -173,9 +191,43 @@ render_glyph(Arena *a, FontAtlas *fa, u32 cp, enum face_style style, CachedGlyph
i32 out_size = fa->info.h * out_width;
cg->size.w = out_width;
cg->size.h = fa->info.h;
-
ASSERT(out_width >= width);
+ uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index);
+ u32 tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x;
+ if (tile_index != 0 && (tile_coord.x + cg->tile_count < gc->tiles_in_x)) {
+ /* NOTE: try to use the old tile directly */
+ for (u32 i = 1; i < cg->tile_count; i++)
+ if (gc->occupied_tiles[tile_index + i])
+ tile_index = 0;
+ }
+
+ if (!tile_index) {
+ /* NOTE: there may be a fancier way to do this but we will see if this causes
+ * any performance issue in practice */
+ u32 x, y;
+ u8 *occupied = gc->occupied_tiles;
+ for (y = 0; !tile_index && y < gc->tiles_in_y; y++) {
+ for (x = 0; !tile_index && x < gc->tiles_in_x; x++) {
+ if (!occupied[0]) {
+ tile_index = y * gc->tiles_in_x + x;
+ for (u32 i = 1; i < cg->tile_count && i < gc->tiles_in_x - x; i++)
+ if (occupied[i])
+ tile_index = 0;
+ }
+ occupied++;
+ }
+ }
+ tile_coord.x = x - 1;
+ tile_coord.y = y - 1;
+ cg->gpu_tile_index = (tile_coord.y << 16) | (tile_coord.x & 0xFFFF);
+ }
+
+ ASSERT(tile_index);
+
+ for (u32 i = 0; i < cg->tile_count; i++)
+ gc->occupied_tiles[tile_index + i] = 1;
+
rgba_bitmap = alloc(a, u32, out_size);
u32 out_y = (fa->info.h + y0 - fa->info.baseline) * out_width;
for (i32 i = 0; i < height; i++) {
@@ -200,39 +252,29 @@ end:
}
static void
-update_font_metrics(Font *font, FontInfo *info)
-{
- i32 x0, x1, y0, y1;
- stbtt_GetFontBoundingBox(&font->font_info, &x0, &y0, &x1, &y1);
- f32 scale = font->stbtt_scale;
- info->h = scale * (y1 - y0) + 0.5;
- info->w = scale * (x1 - x0) + 0.5;
- info->baseline = -scale * y0 + 0.5;
-}
-
-static void
font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim)
{
GlyphCache *gc = &fa->glyph_cache;
mem_clear(gc->glyphs, 0, sizeof(*gc->glyphs) * gc->cache_len);
mem_clear(gc->hash_table, 0, sizeof(*gc->hash_table) * gc->cache_len);
get_and_clear_glyph_cache_stats(gc);
- update_font_metrics(&fa->fonts[0][FS_NORMAL], &fa->info);
-
- gc->cursor = (iv2){0};
- gc->tiles_in_x = glyph_bitmap_dim.x / fa->info.w;
- ASSERT(gc->tiles_in_x);
-
- u32 x = 0, y = 0;
- for (u32 i = 0; i < gc->cache_len; i++, x++) {
- if (i + 1 < gc->cache_len)
- gc->glyphs[i].next_with_same_hash = i + 1;
- if (x >= gc->tiles_in_x) {
- x = 0;
- y++;
- }
- gc->glyphs[i].gpu_glyph_index = (y << 16) | x;
- }
+
+ Font *font = &fa->fonts[0][FS_NORMAL];
+ i32 x0, x1, y0, y1;
+ stbtt_GetFontBoundingBox(&font->font_info, &x0, &y0, &x1, &y1);
+ f32 scale = font->stbtt_scale;
+ fa->info.h = scale * (y1 - y0) + 0.5;
+ fa->info.w = scale * (x1 - x0) + 0.5;
+ fa->info.baseline = -scale * y0 + 0.5;
+
+ gc->tiles_in_x = (glyph_bitmap_dim.x - 1) / fa->info.w;
+ gc->tiles_in_y = (glyph_bitmap_dim.y - 1) / fa->info.h;
+
+ ASSERT(gc->tiles_in_x && gc->tiles_in_y);
+ ASSERT(gc->tiles_in_x * fa->info.w <= glyph_bitmap_dim.x);
+
+ for (u32 i = 0; i < gc->cache_len - 1; i++)
+ gc->glyphs[i].next_with_same_hash = i + 1;
}
static void
@@ -244,7 +286,7 @@ shift_font_sizes(FontAtlas *fa, i32 size_delta)
if (!f->buf) continue;
i32 newsize = f->fontsize + size_delta;
- CLAMP(newsize, 8, MAX_FONT_SIZE);
+ CLAMP(newsize, MIN_FONT_SIZE, MAX_FONT_SIZE);
f->fontsize = newsize;
f->stbtt_scale = stbtt_ScaleForPixelHeight(&f->font_info, f->fontsize);
}
@@ -252,9 +294,8 @@ shift_font_sizes(FontAtlas *fa, i32 size_delta)
}
static void
-init_fonts(Term *t, Arena *a, iv2 glyph_bitmap_dim)
+init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim)
{
- FontAtlas *fa = &t->fa;
u32 n_fonts = ARRAY_COUNT(g_fonts);
fa->fonts = alloc(a, typeof(*fa->fonts), n_fonts);
@@ -266,11 +307,21 @@ init_fonts(Term *t, Arena *a, iv2 glyph_bitmap_dim)
}
}
- static_assert(ISPOWEROFTWO(GLYPH_CACHE_LEN), "GLYPH_CACHE_LEN must be a power of two!");
- 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);
-
- font_atlas_update(&t->fa, glyph_bitmap_dim);
+ Font *f = &fa->fonts[0][FS_NORMAL];
+ i32 x0, x1, y0, y1;
+ f32 scale = stbtt_ScaleForPixelHeight(&f->font_info, MIN_FONT_SIZE);
+ stbtt_GetFontBoundingBox(&f->font_info, &x0, &y0, &x1, &y1);
+ i32 h = scale * (y1 - y0) + 0.5;
+ i32 w = scale * (x1 - x0) + 0.5;
+ i32 max_tiles_x = glyph_bitmap_dim.x / w;
+ i32 max_tiles_y = glyph_bitmap_dim.y / h;
+ stbtt_ScaleForPixelHeight(&f->font_info, f->fontsize);
+
+ GlyphCache *gc = &fa->glyph_cache;
+ gc->cache_len = round_down_power_of_2(max_tiles_x * max_tiles_y);
+ gc->occupied_tiles = alloc(a, typeof(*gc->occupied_tiles), gc->cache_len);
+ gc->glyphs = alloc(a, typeof(*gc->glyphs), gc->cache_len);
+ gc->hash_table = alloc(a, typeof(*gc->hash_table), gc->cache_len);
+
+ font_atlas_update(fa, glyph_bitmap_dim);
}
diff --git a/util.c b/util.c
@@ -8,6 +8,13 @@ safe_left_shift(u32 n, u32 shift)
return result & 0xFFFFFFFF;
}
+static u32
+round_down_power_of_2(u32 a)
+{
+ u32 result = 0x80000000UL >> _lzcnt_u32(a);
+ return result;
+}
+
static b32
equal_iv2(iv2 a, iv2 b)
{
diff --git a/util.h b/util.h
@@ -285,6 +285,7 @@ typedef struct {
u32 count;
} RenderPushBuffer;
+#define MIN_FONT_SIZE 8
#define MAX_FONT_SIZE 128
#define TEXTURE_GLYPH_COUNT PUSH_BUFFER_CAP
@@ -347,12 +348,15 @@ typedef struct {
} Glyph;
typedef struct {
- uv2 size;
u32 cp;
u32 next_with_same_hash;
+ u32 gpu_tile_index;
+ union {
+ struct { u16 h, w; };
+ struct { u16 x, y; };
+ } size;
u16 prev, next;
- u32 gpu_glyph_index;
- b32 uploaded_to_gpu;
+ u16 uploaded_to_gpu;
u16 tile_count;
} CachedGlyph;
@@ -368,11 +372,11 @@ typedef union {
typedef struct {
CachedGlyph *glyphs;
u32 *hash_table;
+ u8 *occupied_tiles;
u32 cache_len;
GlyphCacheStats stats;
u32 tiles_in_x;
- /* TODO: temporary. this is need this to be able to keep track the end of wide glyphs */
- iv2 cursor;
+ u32 tiles_in_y;
} GlyphCache;
typedef struct {
diff --git a/vtgl.c b/vtgl.c
@@ -160,7 +160,7 @@ get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, enum face_
u32 depth_idx;
CachedGlyph *cg;
u32 *data = render_glyph(&a, fa, codepoint, style, &cg, &depth_idx);
- *glyph_size = cg->size;
+ *glyph_size = (uv2){.h = cg->size.h, .w = cg->size.w};
if (data) {
ASSERT(depth_idx);
glBindTexture(GL_TEXTURE_2D_ARRAY, gl->glyph_tex);
@@ -172,19 +172,15 @@ get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, enum face_
/* TODO: this will be bound beforehand */
glBindTexture(GL_TEXTURE_2D, gl->glyph_bitmap_tex);
- /* TODO: the glyph cache needs to manage the positioning in the texture */
- GlyphCache *gc = &fa->glyph_cache;
- uv2 gpu_position = unpack_gpu_glyph_index(cg->gpu_glyph_index);
- if (gpu_position.x + cg->tile_count - 1 >= gc->tiles_in_x) {
- gpu_position.x = 0;
- gpu_position.y++;
- }
- iv2 glyph_position = get_gpu_texture_position(get_cell_size(fa), gpu_position);
- ASSERT(glyph_position.x < gl->glyph_bitmap_dim.x &&
- glyph_position.y < gl->glyph_bitmap_dim.y);
+ uv2 gpu_position = unpack_gpu_tile_coord(cg->gpu_tile_index);
+ v2 cell_size = get_cell_size(fa);
+ iv2 glyph_position = get_gpu_texture_position(cell_size, gpu_position);
+ ASSERT(glyph_position.x + cell_size.w * cg->tile_count < gl->glyph_bitmap_dim.x);
+ ASSERT(glyph_position.y + cell_size.h < gl->glyph_bitmap_dim.y);
glTexSubImage2D(GL_TEXTURE_2D, 0, glyph_position.x, glyph_position.y,
- cg->size.w, cg->size.h, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ cell_size.w * cg->tile_count, cell_size.h,
+ GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D_ARRAY, gl->glyph_tex);
}
@@ -735,7 +731,7 @@ scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff)
DEBUG_EXPORT iv2
init_term(Term *t, Arena *a, iv2 cells)
{
- init_fonts(t, a, t->gl.glyph_bitmap_dim);
+ init_fonts(&t->fa, a, t->gl.glyph_bitmap_dim);
for (u32 i = 0; i < ARRAY_COUNT(t->saved_cursors); i++) {
cursor_reset(t);
cursor_move_to(t, 0, 0);