vtgl

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

font.c (13961B)


      1 /* See LICENSE for copyright details */
      2 /* NOTE: https://en.wikipedia.org/wiki/DEC_Special_Graphics */
      3 /*                                          | 0x60 - 0x7e
      4  *  "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", | ` - g
      5  *  "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", | h - o
      6  *  "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", | p - w
      7  *  "│", "≤", "≥", "π", "≠", "£", "·",      | x - ~
      8  */
      9 global u16 graphic_0[31] = {
     10         0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
     11         0x2424, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA,
     12         0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C,
     13         0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7,
     14 };
     15 
     16 #define STB_TRUETYPE_IMPLEMENTATION
     17 #define STB_STATIC
     18 #include "extern/stb_truetype.h"
     19 
     20 function b32
     21 init_font(Font *f, char *font_path, i32 font_size)
     22 {
     23 	b32 result = 0;
     24 	os_mapped_file map = os_map_file(font_path, OS_MAP_READ, OS_MAP_PRIVATE);
     25 
     26 	if (map.len) {
     27 		result = stbtt_InitFont(f->font_info, map.data, 0);
     28 		if (result) {
     29 			f->bufsize     = map.len;
     30 			f->buf         = map.data;
     31 			f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(f->font_info, font_size);
     32 		} else {
     33 			/* TODO: leak */
     34 			os_write_err_msg(s8("init_font: failed on: "));
     35 			os_write_err_msg(c_str_to_s8(font_path));
     36 			os_write_err_msg(s8("\n"));
     37 		}
     38 	}
     39 
     40 	return result;
     41 }
     42 
     43 function u32
     44 compute_glyph_hash(GlyphCache *gc, u32 cp)
     45 {
     46 	/* TODO: better hash function! */
     47 	u32 result = (((u64)cp * 1111111111111111111) >> 32) & (gc->cache_len - 1);
     48 	return result;
     49 }
     50 
     51 function uv2
     52 unpack_gpu_tile_coord(u32 gpu_index)
     53 {
     54 	uv2 result;
     55 	result.x = gpu_index & 0xFFFF;
     56 	result.y = gpu_index >> 16;
     57 	return result;
     58 }
     59 
     60 function void
     61 cached_glyph_to_uv(FontAtlas *fa, CachedGlyph *cg, v2 *start, v2 *end, v2 scale)
     62 {
     63 	v2 cs = {.w = fa->info.w, .h = fa->info.h};
     64 	uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index);
     65 	f32 min_x = tile_coord.x * cs.w + cg->x0;
     66 	f32 max_x = min_x + cg->width;
     67 	f32 min_y = tile_coord.y * cs.h + fa->info.h + cg->y0 - fa->info.baseline;
     68 	f32 max_y = min_y + cg->height;
     69 
     70 	*start = (v2){.x = min_x * scale.x, .y = max_y * scale.y};
     71 	*end   = (v2){.x = max_x * scale.x, .y = min_y * scale.y};
     72 }
     73 
     74 function void
     75 recycle_cache(GlyphCache *gc)
     76 {
     77 	CachedGlyph *sentinel = gc->glyphs + 0;
     78 
     79 	ASSERT(sentinel->prev);
     80 
     81 	u32 last_index    = sentinel->prev;
     82 	CachedGlyph *cg   = gc->glyphs + last_index;
     83 	CachedGlyph *prev = gc->glyphs + cg->prev;
     84 	prev->next        = 0;
     85 	sentinel->prev    = cg->prev;
     86 
     87 	u32 hash_slot   = compute_glyph_hash(gc, cg->cp);
     88 	u32 *next_index = gc->hash_table + hash_slot;
     89 	while (*next_index != last_index) {
     90 		ASSERT(*next_index);
     91 		next_index = &gc->glyphs[*next_index].next_with_same_hash;
     92 	}
     93 	ASSERT(*next_index == last_index);
     94 	*next_index                   = cg->next_with_same_hash;
     95 	cg->next_with_same_hash       = sentinel->next_with_same_hash;
     96 	sentinel->next_with_same_hash = last_index;
     97 
     98 	gc->stats.recycle_count++;
     99 }
    100 
    101 function i32
    102 pop_free_glyph_entry(GlyphCache *gc)
    103 {
    104 	CachedGlyph *sentinel = gc->glyphs + 0;
    105 
    106 	if (!sentinel->next_with_same_hash)
    107 		recycle_cache(gc);
    108 
    109 	u32 result = sentinel->next_with_same_hash;
    110 	ASSERT(result);
    111 
    112 	CachedGlyph *cg = gc->glyphs + result;
    113 	sentinel->next_with_same_hash = cg->next_with_same_hash;
    114 	cg->next_with_same_hash       = 0;
    115 	cg->uploaded_to_gpu           = 0;
    116 
    117 	uv2 tile_coord      = unpack_gpu_tile_coord(cg->gpu_tile_index);
    118 	u32 base_tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x;
    119 	for (u32 i = 0; i < cg->tile_count; i++)
    120 		gc->occupied_tiles[base_tile_index + i] = 0;
    121 	cg->tile_count = 0;
    122 
    123 	return result;
    124 }
    125 
    126 function u32
    127 get_glyph_entry_index(GlyphCache *gc, u32 cp)
    128 {
    129 	u32 result    = 0;
    130 	u32 hash_slot = compute_glyph_hash(gc, cp);
    131 	u32 entry_idx = gc->hash_table[hash_slot];
    132 
    133 	CachedGlyph *cg;
    134 	while (entry_idx) {
    135 		cg = gc->glyphs + entry_idx;
    136 		if (cp == cg->cp) {
    137 			result = entry_idx;
    138 			break;
    139 		}
    140 		entry_idx = cg->next_with_same_hash;
    141 	}
    142 
    143 	if (result) {
    144 		CachedGlyph *prev = gc->glyphs + cg->prev;
    145 		CachedGlyph *next = gc->glyphs + cg->next;
    146 		prev->next = cg->next;
    147 		next->prev = cg->prev;
    148 		gc->stats.hit_count++;
    149 	} else {
    150 		result = pop_free_glyph_entry(gc);
    151 		cg     = gc->glyphs + result;
    152 		cg->cp = cp;
    153 		cg->next_with_same_hash   = gc->hash_table[hash_slot];
    154 		gc->hash_table[hash_slot] = result;
    155 		gc->stats.miss_count++;
    156 	}
    157 
    158 	ASSERT(result);
    159 
    160 	CachedGlyph *sentinel  = gc->glyphs + 0;
    161 	CachedGlyph *last_next = gc->glyphs + sentinel->next;
    162 	cg->next               = sentinel->next;
    163 	cg->prev               = 0;
    164 	sentinel->next         = result;
    165 	last_next->prev        = result;
    166 
    167 	return result;
    168 }
    169 
    170 function GlyphCacheStats
    171 get_and_clear_glyph_cache_stats(GlyphCache *gc)
    172 {
    173 	GlyphCacheStats	result = gc->stats;
    174 	gc->stats              = (GlyphCacheStats){0};
    175 	return result;
    176 }
    177 
    178 function u32 *
    179 render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style, CachedGlyph **out_glyph)
    180 {
    181 	BEGIN_TIMED_BLOCK();
    182 	GlyphCache *gc   = &fa->glyph_cache;
    183 	u32 *rgba_bitmap = NULL;
    184 
    185 	/* NOTE: first check if glyph is in the cache and valid */
    186 	/* NOTE: 8 MSB are not used for UTF8 so we can use that to store some extra info:
    187 	 * bits 25,26: the style of the glyph
    188 	 * bits 26-31: requested font_id (limited to first 32 fonts */
    189 	u32 packed_cp   = cp | ((u32)style << 24) | (font_id << 26);
    190 	u32 idx         = get_glyph_entry_index(&fa->glyph_cache, packed_cp);
    191 	CachedGlyph *cg = fa->glyph_cache.glyphs + idx;
    192 
    193 	*out_glyph = cg;
    194 	if (cg->uploaded_to_gpu)
    195 		goto end;
    196 
    197 	ASSERT(cg->tile_count == 0);
    198 
    199 	i32 glyph_idx = 0;
    200 	i32 font_idx  = 0;
    201 	/* NOTE: first try the requested font id */
    202 	if (font_id < fa->nfonts && fa->fonts[font_id][style].buf) {
    203 		glyph_idx = stbtt_FindGlyphIndex(fa->fonts[font_id][style].font_info, cp);
    204 		if (glyph_idx) font_idx = font_id;
    205 	}
    206 
    207 	for (u32 i = 0; !glyph_idx && i < fa->nfonts; i++) {
    208 		u32 test_style = style;
    209 		if (!fa->fonts[i][test_style].buf)
    210 			test_style = FS_NORMAL;
    211 		glyph_idx = stbtt_FindGlyphIndex(fa->fonts[i][test_style].font_info, cp);
    212 		if (!glyph_idx) {
    213 			test_style = FS_NORMAL;
    214 			glyph_idx  = stbtt_FindGlyphIndex(fa->fonts[i][test_style].font_info, cp);
    215 		}
    216 		if (glyph_idx) {
    217 			font_idx = i;
    218 			style    = test_style;
    219 			break;
    220 		}
    221 	}
    222 
    223 	Font *f   = &fa->fonts[font_idx][style];
    224 	f32 scale = f->stbtt_scale;
    225 	i32 x0, y0, x1, y1, advance, left_bearing;
    226 	stbtt_GetGlyphBitmapBoxSubpixel(f->font_info, glyph_idx, scale, scale, 0, 0,
    227 	                                &x0, &y0, &x1, &y1);
    228 
    229 	/* NOTE: this looks weird but some 'wide' glyphs are not actually wide but still
    230 	 * need to be displayed as such (eg. ト). x1 can be used to determine this. */
    231 	cg->tile_count = ((x1 - 1) / (i32)fa->info.w) + 1;
    232 	if (x0 < 0 && x1 + x0 <= fa->info.w)
    233 		cg->tile_count = 1;
    234 	CLAMP(cg->tile_count, 1, 2);
    235 
    236 	i32 height  = y1 - y0;
    237 	i32 width   = x1 - x0;
    238 
    239 	stbtt_GetGlyphHMetrics(f->font_info, glyph_idx, &advance, &left_bearing);
    240 	cg->advance = scale * advance;
    241 	cg->height  = MIN(height, fa->info.h);
    242 	cg->width   = MIN(width, fa->info.w * cg->tile_count);
    243 	cg->x0      = x0;
    244 	cg->y0      = y0;
    245 
    246 	ASSERT(cg->height <= fa->info.h);
    247 	ASSERT(cg->width  <= 2 * fa->info.w);
    248 
    249 	u8 *render_buf = alloc(a, u8, width * height);
    250 	stbtt_MakeGlyphBitmapSubpixel(*a, f->font_info, render_buf, width, height,
    251 	                              width, scale, scale, 0, 0, glyph_idx);
    252 
    253 	uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index);
    254 	u32 tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x;
    255 	if (tile_index != 0 && ((i32)tile_coord.x + cg->tile_count < gc->tiles_in_x)) {
    256 		/* NOTE: try to use the old tile directly */
    257 		if (gc->occupied_tiles[tile_index] ||
    258 		    (cg->tile_count == 2 && gc->occupied_tiles[tile_index + 1]))
    259 			tile_index = 0;
    260 	}
    261 
    262 	if (!tile_index) {
    263 		/* NOTE: there may be a fancier way to do this but we will see if this causes
    264 		 * any performance issue in practice */
    265 		i32 x = 1, y = 1;
    266 		u8 *occupied = gc->occupied_tiles;
    267 		for (y = 0; !tile_index && y < gc->tiles_in_y; y++) {
    268 			for (x = 0; !tile_index && x < gc->tiles_in_x; x++) {
    269 				if (!occupied[0]) {
    270 					tile_index = y * gc->tiles_in_x + x;
    271 					if ((cg->tile_count == 2) && occupied[1])
    272 						tile_index = 0;
    273 				}
    274 				occupied++;
    275 			}
    276 		}
    277 		tile_coord.x = x - 1;
    278 		tile_coord.y = y - 1;
    279 		cg->gpu_tile_index = (tile_coord.y << 16) | (tile_coord.x & 0xFFFF);
    280 	}
    281 
    282 	/* NOTE: tile index 0,0 is reserved */
    283 	ASSERT(cg->gpu_tile_index);
    284 	ASSERT(tile_index);
    285 
    286 	for (u32 i = 0; i < cg->tile_count; i++)
    287 		gc->occupied_tiles[tile_index + i] = 1;
    288 
    289 	i32 out_width = fa->info.w * cg->tile_count;
    290 	i32 out_size  = fa->info.h * out_width;
    291 	ASSERT(out_width >= cg->width);
    292 
    293 	i32 out_y = (fa->info.h + y0 - fa->info.baseline) * out_width;
    294 	out_y = MAX(out_y, 0);
    295 
    296 	height     = cg->height;
    297 	width      = cg->width;
    298 	i32 stride = x1 - x0;
    299 
    300 	i32 xskip = 0;
    301 	if (cg->x0 < 0) xskip = -cg->x0;
    302 
    303 	rgba_bitmap = alloc(a, u32, out_size);
    304 	for (i32 i = 0; i < height; i++) {
    305 		i32 out_x = MAX(cg->x0, 0);
    306 		for (i32 j = xskip; j < width; j++) {
    307 			/* TODO: handled coloured glyphs */
    308 			u32 pixel = 0;
    309 			if (0 /* COLOURED */) {
    310 			} else {
    311 				pixel = (u32)render_buf[i * stride + j] << 24u | 0x00FFFFFFu;
    312 			}
    313 			rgba_bitmap[out_y + out_x] = pixel;
    314 			out_x++;
    315 		}
    316 		out_y += out_width;
    317 	}
    318 
    319 end:
    320 	END_TIMED_BLOCK();
    321 
    322 	return rgba_bitmap;
    323 }
    324 
    325 function FontInfo
    326 compute_font_info(Font *font, u32 font_size)
    327 {
    328 	FontInfo result = {0};
    329 
    330 	local_persist s8 ascii = s8(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    331 	                            "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~");
    332 
    333 	f32 scale      = stbtt_ScaleForMappingEmToPixels(font->font_info, font_size);
    334 	i32 min_y      = 0;
    335 	i32 max_height = 0;
    336 	f32 width      = 0;
    337 	for (iz i = 0; i < ascii.len; i++) {
    338 		u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, ascii.data[i]);
    339 		i32 x0, y0, x1, y1, advance, left_bearing;
    340 		stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0,
    341 		                                &x0, &y0, &x1, &y1);
    342 		stbtt_GetGlyphHMetrics(font->font_info, glyph_idx, &advance, &left_bearing);
    343 
    344 		width      += scale * advance;
    345 		max_height  = MAX(max_height, y1 - y0);
    346 		min_y       = MIN(min_y, y0);
    347 	}
    348 
    349 	u32 graphic_0_count = 0;
    350 	for (iz i = 0; i < ARRAY_COUNT(graphic_0); i++) {
    351 		if (graphic_0[i] == 0)
    352 			continue;
    353 		graphic_0_count++;
    354 
    355 		u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, graphic_0[i]);
    356 		i32 x0, y0, x1, y1, advance, left_bearing;
    357 		stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0,
    358 		                                &x0, &y0, &x1, &y1);
    359 		stbtt_GetGlyphHMetrics(font->font_info, glyph_idx, &advance, &left_bearing);
    360 
    361 		width      += scale * advance;
    362 		max_height  = MAX(max_height, y1 - y0);
    363 		min_y       = MIN(min_y, y0);
    364 	}
    365 
    366 	result.h        = max_height;
    367 	result.w        = (i32)(width / (ascii.len + graphic_0_count) + 1);
    368 	result.baseline = (i32)(result.h + min_y);
    369 
    370 	return result;
    371 }
    372 
    373 function void
    374 font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim)
    375 {
    376 	GlyphCache *gc = &fa->glyph_cache;
    377 	mem_clear(gc->glyphs,         0, sizeof(*gc->glyphs) * gc->cache_len);
    378 	mem_clear(gc->hash_table,     0, sizeof(*gc->hash_table) * gc->cache_len);
    379 	mem_clear(gc->occupied_tiles, 0, sizeof(*gc->occupied_tiles) * gc->cache_len);
    380 	get_and_clear_glyph_cache_stats(gc);
    381 
    382 	/* NOTE: reserve tile 0,0 */
    383 	gc->occupied_tiles[0] = 1;
    384 
    385 	Font *font = &fa->fonts[0][FS_NORMAL];
    386 	fa->info   = compute_font_info(font, g_font_size);
    387 
    388 	gc->tiles_in_x = (glyph_bitmap_dim.x - 1) / fa->info.w - 1;
    389 	gc->tiles_in_y = (glyph_bitmap_dim.y - 1) / fa->info.h - 1;
    390 
    391 	ASSERT(gc->tiles_in_x && gc->tiles_in_y);
    392 	ASSERT(gc->tiles_in_x * fa->info.w <= glyph_bitmap_dim.x);
    393 
    394 	for (u32 i = 0; i < gc->cache_len - 1; i++)
    395 		gc->glyphs[i].next_with_same_hash = i + 1;
    396 }
    397 
    398 function v2
    399 fa_cell_size(FontAtlas *fa)
    400 {
    401 	v2 result = {.w = fa->info.w, .h = fa->info.h};
    402 	return result;
    403 }
    404 
    405 function void
    406 shift_font_sizes(FontAtlas *fa, i32 size_delta)
    407 {
    408 	g_font_size += size_delta;
    409 	if (g_font_size < MIN_FONT_SIZE)
    410 		g_font_size = MIN_FONT_SIZE;
    411 	for (u32 i = 0; i < fa->nfonts; i++) {
    412 		/* TODO: better multi font size support */
    413 		i32 font_size;
    414 		if (i == g_ui_debug_font_id) font_size = g_ui_font_size;
    415 		else                         font_size = g_font_size;
    416 		font_size = MIN(font_size, g_font_size);
    417 		for (u32 j = 0; j < FS_COUNT; j++) {
    418 			Font *f = &fa->fonts[i][j];
    419 			if (!f->buf) continue;
    420 			f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(f->font_info, font_size);
    421 		}
    422 	}
    423 }
    424 
    425 function void
    426 init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim)
    427 {
    428 	fa->nfonts = 0;
    429 	fa->fonts  = alloc(a, typeof(*fa->fonts), ARRAY_COUNT(g_fonts));
    430 	for (u32 j = 0; j < ARRAY_COUNT(g_fonts); j++) {
    431 		i32 font_size;
    432 		if (j == g_ui_debug_font_id) font_size = g_ui_font_size;
    433 		else                         font_size = g_font_size;
    434 		font_size = MIN(font_size, g_font_size);
    435 		b32 result = 0;
    436 		for (u32 i = 0; i < FS_COUNT; i++) {
    437 			if (!g_fonts[j][i])
    438 				continue;
    439 			Font *f = fa->fonts[fa->nfonts] + i;
    440 			f->font_info = alloc(a, typeof(*f->font_info), 1);
    441 			if (init_font(f, g_fonts[j][i], font_size)) {
    442 				result = 1;
    443 			} else {
    444 				os_write_err_msg(s8("failed to load font: "));
    445 				os_write_err_msg(c_str_to_s8(g_fonts[j][i]));
    446 				os_write_err_msg(s8("\n"));
    447 			}
    448 		}
    449 		fa->nfonts += result;
    450 	}
    451 
    452 	if (!fa->nfonts)
    453 		os_fatal(s8("failed to load any fonts!\n"));
    454 
    455 	Font *f = &fa->fonts[0][FS_NORMAL];
    456 	FontInfo info   = compute_font_info(f, MIN_FONT_SIZE);
    457 	i32 max_tiles_x = glyph_bitmap_dim.x / info.w;
    458 	i32 max_tiles_y = glyph_bitmap_dim.y / info.h;
    459 
    460 	GlyphCache *gc     = &fa->glyph_cache;
    461 	gc->cache_len      = round_down_power_of_2(max_tiles_x * max_tiles_y);
    462 	gc->occupied_tiles = alloc(a, typeof(*gc->occupied_tiles), gc->cache_len);
    463 	gc->glyphs         = alloc(a, typeof(*gc->glyphs),         gc->cache_len);
    464 	gc->hash_table     = alloc(a, typeof(*gc->hash_table),     gc->cache_len);
    465 
    466 	font_atlas_update(fa, glyph_bitmap_dim);
    467 }