vtgl

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

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:
Mfont.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mutil.c | 7+++++++
Mutil.h | 14+++++++++-----
Mvtgl.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);