From 7dc920c14fd64bbdf6adc328988fd996d847aa26 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Apr 2025 09:45:43 -0700 Subject: [PATCH] font cache: add additional layer of caching for runs --- src/base/base_entry_point.c | 3 + src/font_cache/font_cache.c | 633 ++++++++++++++++++++---------------- src/font_cache/font_cache.h | 56 +++- 3 files changed, 390 insertions(+), 302 deletions(-) diff --git a/src/base/base_entry_point.c b/src/base/base_entry_point.c index 21bbc363..6d489e66 100644 --- a/src/base/base_entry_point.c +++ b/src/base/base_entry_point.c @@ -121,6 +121,9 @@ update(void) { ProfTick(0); ins_atomic_u64_inc_eval(&global_update_tick_idx); +#if defined(FONT_CACHE_H) + fnt_frame(); +#endif #if OS_FEATURE_GRAPHICAL B32 result = frame(); #else diff --git a/src/font_cache/font_cache.c b/src/font_cache/font_cache.c index 52c4aa3a..4070aa3c 100644 --- a/src/font_cache/font_cache.c +++ b/src/font_cache/font_cache.c @@ -24,9 +24,9 @@ fnt_hash_from_string(String8 string) } internal U64 -fnt_little_hash_from_string(String8 string) +fnt_little_hash_from_string(U64 seed, String8 string) { - U64 result = XXH3_64bits(string.str, string.size); + U64 result = XXH3_64bits_withSeed(string.str, string.size, seed); return result; } @@ -65,10 +65,10 @@ internal FP_Handle fnt_handle_from_tag(FNT_Tag tag) { ProfBeginFunction(); - U64 slot_idx = tag.u64[1] % f_state->font_hash_table_size; + U64 slot_idx = tag.u64[1] % fnt_state->font_hash_table_size; FNT_FontHashNode *existing_node = 0; { - for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) + for(FNT_FontHashNode *n = fnt_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) { if(MemoryMatchStruct(&tag, &n->tag)) { @@ -90,10 +90,10 @@ internal FP_Metrics fnt_fp_metrics_from_tag(FNT_Tag tag) { ProfBeginFunction(); - U64 slot_idx = tag.u64[1] % f_state->font_hash_table_size; + U64 slot_idx = tag.u64[1] % fnt_state->font_hash_table_size; FNT_FontHashNode *existing_node = 0; { - for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) + for(FNT_FontHashNode *n = fnt_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) { if(MemoryMatchStruct(&tag, &n->tag)) { @@ -125,12 +125,12 @@ fnt_tag_from_path(String8 path) } //- rjf: tag -> slot index - U64 slot_idx = result.u64[1] % f_state->font_hash_table_size; + U64 slot_idx = result.u64[1] % fnt_state->font_hash_table_size; //- rjf: slot * tag -> existing node FNT_FontHashNode *existing_node = 0; { - for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) + for(FNT_FontHashNode *n = fnt_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) { if(MemoryMatchStruct(&result, &n->tag)) { @@ -144,12 +144,12 @@ fnt_tag_from_path(String8 path) FNT_FontHashNode *new_node = 0; if(existing_node == 0) { - FNT_FontHashSlot *slot = &f_state->font_hash_table[slot_idx]; - new_node = push_array(f_state->permanent_arena, FNT_FontHashNode, 1); + FNT_FontHashSlot *slot = &fnt_state->font_hash_table[slot_idx]; + new_node = push_array(fnt_state->permanent_arena, FNT_FontHashNode, 1); new_node->tag = result; new_node->handle = fp_font_open(path); new_node->metrics = fp_metrics_from_font(new_node->handle); - new_node->path = push_str8_copy(f_state->permanent_arena, path); + new_node->path = push_str8_copy(fnt_state->permanent_arena, path); SLLQueuePush_N(slot->first, slot->last, new_node, hash_next); } @@ -172,12 +172,12 @@ fnt_tag_from_static_data_string(String8 *data_ptr) } //- rjf: tag -> slot index - U64 slot_idx = result.u64[1] % f_state->font_hash_table_size; + U64 slot_idx = result.u64[1] % fnt_state->font_hash_table_size; //- rjf: slot * tag -> existing node FNT_FontHashNode *existing_node = 0; { - for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) + for(FNT_FontHashNode *n = fnt_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) { if(MemoryMatchStruct(&result, &n->tag)) { @@ -191,8 +191,8 @@ fnt_tag_from_static_data_string(String8 *data_ptr) FNT_FontHashNode *new_node = 0; if(existing_node == 0) { - FNT_FontHashSlot *slot = &f_state->font_hash_table[slot_idx]; - new_node = push_array(f_state->permanent_arena, FNT_FontHashNode, 1); + FNT_FontHashSlot *slot = &fnt_state->font_hash_table[slot_idx]; + new_node = push_array(fnt_state->permanent_arena, FNT_FontHashNode, 1); new_node->tag = result; new_node->handle = fp_font_open_from_static_data_string(data_ptr); new_node->metrics = fp_metrics_from_font(new_node->handle); @@ -209,12 +209,12 @@ internal String8 fnt_path_from_tag(FNT_Tag tag) { //- rjf: tag -> slot index - U64 slot_idx = tag.u64[1] % f_state->font_hash_table_size; + U64 slot_idx = tag.u64[1] % fnt_state->font_hash_table_size; //- rjf: slot * tag -> existing node FNT_FontHashNode *existing_node = 0; { - for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) + for(FNT_FontHashNode *n = fnt_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next) { if(MemoryMatchStruct(&tag, &n->tag)) { @@ -514,7 +514,7 @@ fnt_piece_array_copy(Arena *arena, FNT_PieceArray *src) } //////////////////////////////// -//~ rjf: Rasterization Cache +//~ rjf: Cache Usage internal FNT_Hash2StyleRasterCacheNode * fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags) @@ -530,15 +530,15 @@ fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags) *(U64 *)(&size_f64), (U64)flags, }; - style_hash = fnt_little_hash_from_string(str8((U8 *)buffer, sizeof(buffer))); + style_hash = fnt_little_hash_from_string(5381, str8((U8 *)buffer, sizeof(buffer))); } //- rjf: style hash -> style node FNT_Hash2StyleRasterCacheNode *hash2style_node = 0; { ProfBegin("style hash -> style node"); - U64 slot_idx = style_hash%f_state->hash2style_slots_count; - FNT_Hash2StyleRasterCacheSlot *slot = &f_state->hash2style_slots[slot_idx]; + U64 slot_idx = style_hash%fnt_state->hash2style_slots_count; + FNT_Hash2StyleRasterCacheSlot *slot = &fnt_state->hash2style_slots[slot_idx]; for(FNT_Hash2StyleRasterCacheNode *n = slot->first; n != 0; n = n->hash_next) @@ -552,14 +552,14 @@ fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags) if(Unlikely(hash2style_node == 0)) { FNT_Metrics metrics = fnt_metrics_from_tag_size(tag, size); - hash2style_node = push_array(f_state->raster_arena, FNT_Hash2StyleRasterCacheNode, 1); + hash2style_node = push_array(fnt_state->raster_arena, FNT_Hash2StyleRasterCacheNode, 1); DLLPushBack_NP(slot->first, slot->last, hash2style_node, hash_next, hash_prev); hash2style_node->style_hash = style_hash; hash2style_node->ascent = metrics.ascent; hash2style_node->descent = metrics.descent; - hash2style_node->utf8_class1_direct_map = push_array_no_zero(f_state->raster_arena, F_RasterCacheInfo, 256); + hash2style_node->utf8_class1_direct_map = push_array_no_zero(fnt_state->raster_arena, FNT_RasterCacheInfo, 256); hash2style_node->hash2info_slots_count = 1024; - hash2style_node->hash2info_slots = push_array(f_state->raster_arena, FNT_Hash2InfoRasterCacheSlot, hash2style_node->hash2info_slots_count); + hash2style_node->hash2info_slots = push_array(fnt_state->raster_arena, FNT_Hash2InfoRasterCacheSlot, hash2style_node->hash2info_slots_count); } ProfEnd(); } @@ -575,274 +575,321 @@ fnt_push_run_from_string(Arena *arena, FNT_Tag tag, F32 size, F32 base_align_px, //- rjf: map tag/size to style node FNT_Hash2StyleRasterCacheNode *hash2style_node = fnt_hash2style_from_tag_size_flags(tag, size, flags); - //- rjf: decode string & produce run pieces - FNT_PieceChunkList piece_chunks = {0}; - Vec2F32 dim = {0}; - B32 font_handle_mapped_on_miss = 0; - FP_Handle font_handle = {0}; - U64 piece_substring_start_idx = 0; - U64 piece_substring_end_idx = 0; - for(U64 idx = 0; idx <= string.size;) + //- rjf: set up this style's run cache if needed + if(hash2style_node->run_slots_frame_index != fnt_state->frame_index) { - //- rjf: decode next codepoint & get piece substring, or continuation rule - U8 byte = (idx < string.size ? string.str[idx] : 0); - B32 need_another_codepoint = 0; - if(byte == 0) + hash2style_node->run_slots_count = 1024; + hash2style_node->run_slots = push_array(fnt_state->frame_arena, FNT_RunCacheSlot, hash2style_node->run_slots_count); + hash2style_node->run_slots_frame_index = fnt_state->frame_index; + } + + //- rjf: unpack run params + U64 run_hash = fnt_little_hash_from_string(5381, string); + U64 run_slot_idx = run_hash%hash2style_node->run_slots_count; + FNT_RunCacheSlot *run_slot = &hash2style_node->run_slots[run_slot_idx]; + + //- rjf: find existing run node for this string + FNT_RunCacheNode *run_node = 0; + { + for(FNT_RunCacheNode *n = run_slot->first; n != 0; n = n->next) { - idx += 1; - } - else switch(utf8_class[byte>>3]) - { - case 1: + if(str8_match(n->string, string, 0)) { - idx += 1; - piece_substring_end_idx += 1; - need_another_codepoint = 0; - }break; - default: - { - UnicodeDecode decode = utf8_decode(string.str+idx, string.size-idx); - idx += decode.inc; - piece_substring_end_idx += decode.inc; - need_another_codepoint = 0; - }break; - } - - //- rjf: need another codepoint, or have no substring? -> continue - if(need_another_codepoint || piece_substring_end_idx == piece_substring_start_idx) - { - continue; - } - - //- rjf: do not need another codepoint? -> grab substring, bump piece start idx - String8 piece_substring = str8_substr(string, r1u64(piece_substring_start_idx, piece_substring_end_idx)); - piece_substring_start_idx = idx; - piece_substring_end_idx = idx; - - //- rjf: determine if this piece is a tab - if so, use space info to draw - B32 is_tab = (piece_substring.size == 1 && piece_substring.str[0] == '\t'); - if(is_tab) - { - piece_substring = str8_lit(" "); - } - - //- rjf: piece substring -> raster cache info - F_RasterCacheInfo *info = 0; - U64 piece_hash = 0; - { - // rjf: fast path for utf8 class 1 -> direct map - if(piece_substring.size == 1 && hash2style_node->utf8_class1_direct_map_mask[piece_substring.str[0]/64] & (1ull<<(piece_substring.str[0]%64))) - { - info = &hash2style_node->utf8_class1_direct_map[piece_substring.str[0]]; - } - - // rjf: more general, slower path for other glyphs - if(piece_substring.size > 1) - { - piece_hash = fnt_little_hash_from_string(piece_substring); - U64 slot_idx = piece_hash%hash2style_node->hash2info_slots_count; - FNT_Hash2InfoRasterCacheSlot *slot = &hash2style_node->hash2info_slots[slot_idx]; - for(F_Hash2InfoRasterCacheNode *node = slot->first; node != 0; node = node->hash_next) - { - if(node->hash == piece_hash) - { - info = &node->info; - break; - } - } - } - } - - //- rjf: no info found -> miss... fill this hash in the cache - if(info == 0) - { - ProfBegin("no info found -> miss... fill this hash in the cache"); - Temp scratch = scratch_begin(&arena, 1); - - // rjf: grab font handle for this tag if we don't have one already - if(font_handle_mapped_on_miss == 0) - { - font_handle_mapped_on_miss = 1; - - // rjf: tag -> font slot index - U64 font_slot_idx = tag.u64[1] % f_state->font_hash_table_size; - - // rjf: tag * slot -> existing node - FNT_FontHashNode *existing_node = 0; - { - for(FNT_FontHashNode *n = f_state->font_hash_table[font_slot_idx].first; n != 0 ; n = n->hash_next) - { - if(MemoryMatchStruct(&n->tag, &tag)) - { - existing_node = n; - break; - } - } - } - - // rjf: existing node -> font handle - if(existing_node != 0) - { - font_handle = existing_node->handle; - } - } - - // rjf: call into font provider to rasterize this substring - FP_RasterResult raster = {0}; - if(size > 0) - { - FP_RasterFlags fp_flags = 0; - if(flags & FNT_RasterFlag_Smooth) { fp_flags |= FP_RasterFlag_Smooth; } - if(flags & FNT_RasterFlag_Hinted) { fp_flags |= FP_RasterFlag_Hinted; } - raster = fp_raster(scratch.arena, font_handle, floor_f32(size), flags, piece_substring); - } - - // rjf: allocate portion of an atlas to upload the rasterization - S16 chosen_atlas_num = 0; - FNT_Atlas *chosen_atlas = 0; - Rng2S16 chosen_atlas_region = {0}; - if(raster.atlas_dim.x != 0 && raster.atlas_dim.y != 0) - { - U64 num_atlases = 0; - for(FNT_Atlas *atlas = f_state->first_atlas;; atlas = atlas->next, num_atlases += 1) - { - // rjf: create atlas if needed - if(atlas == 0 && num_atlases < 64) - { - atlas = push_array(f_state->raster_arena, FNT_Atlas, 1); - DLLPushBack(f_state->first_atlas, f_state->last_atlas, atlas); - atlas->root_dim = v2s16(1024, 1024); - atlas->root = push_array(f_state->raster_arena, FNT_AtlasRegionNode, 1); - atlas->root->max_free_size[Corner_00] = - atlas->root->max_free_size[Corner_01] = - atlas->root->max_free_size[Corner_10] = - atlas->root->max_free_size[Corner_11] = v2s16(atlas->root_dim.x/2, atlas->root_dim.y/2); - atlas->texture = r_tex2d_alloc(R_ResourceKind_Dynamic, v2s32((S32)atlas->root_dim.x, (S32)atlas->root_dim.y), R_Tex2DFormat_RGBA8, 0); - } - - // rjf: allocate from atlas - if(atlas != 0) - { - Vec2S16 needed_dimensions = v2s16(raster.atlas_dim.x + 2, raster.atlas_dim.y + 2); - chosen_atlas_region = fnt_atlas_region_alloc(f_state->raster_arena, atlas, needed_dimensions); - if(chosen_atlas_region.x1 != chosen_atlas_region.x0) - { - chosen_atlas = atlas; - chosen_atlas_num = (S32)num_atlases; - break; - } - } - else - { - break; - } - } - } - - // rjf: upload rasterization to allocated region of atlas texture memory - if(chosen_atlas != 0) - { - Rng2S32 subregion = - { - chosen_atlas_region.x0, - chosen_atlas_region.y0, - chosen_atlas_region.x0 + raster.atlas_dim.x, - chosen_atlas_region.y0 + raster.atlas_dim.y - }; - r_fill_tex2d_region(chosen_atlas->texture, subregion, raster.atlas); - } - - // rjf: allocate & fill & push node - { - if(piece_substring.size == 1) - { - info = &hash2style_node->utf8_class1_direct_map[piece_substring.str[0]]; - hash2style_node->utf8_class1_direct_map_mask[piece_substring.str[0]/64] |= (1ull<<(piece_substring.str[0]%64)); - } - else - { - U64 slot_idx = piece_hash%hash2style_node->hash2info_slots_count; - FNT_Hash2InfoRasterCacheSlot *slot = &hash2style_node->hash2info_slots[slot_idx]; - F_Hash2InfoRasterCacheNode *node = push_array_no_zero(f_state->raster_arena, F_Hash2InfoRasterCacheNode, 1); - DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev); - node->hash = piece_hash; - info = &node->info; - } - if(info != 0) - { - info->subrect = chosen_atlas_region; - info->atlas_num = chosen_atlas_num; - info->raster_dim = raster.atlas_dim; - info->advance = raster.advance; - } - } - - scratch_end(scratch); - ProfEnd(); - } - - //- rjf: push piece for this raster portion - if(info != 0) - { - // rjf: find atlas - FNT_Atlas *atlas = 0; - { - if(info->subrect.x1 != 0 && info->subrect.y1 != 0) - { - S32 num = 0; - for(FNT_Atlas *a = f_state->first_atlas; a != 0; a = a->next, num += 1) - { - if(info->atlas_num == num) - { - atlas = a; - break; - } - } - } - } - - // rjf: on tabs -> expand advance - F32 advance = info->advance; - if(is_tab) - { - advance = floor_f32(tab_size_px) - mod_f32(floor_f32(base_align_px), floor_f32(tab_size_px)); - } - - // rjf: push piece - { - FNT_Piece *piece = fnt_piece_chunk_list_push_new(arena, &piece_chunks, string.size); - { - piece->texture = atlas ? atlas->texture : r_handle_zero(); - piece->subrect = r2s16p(info->subrect.x0, - info->subrect.y0, - info->subrect.x0 + info->raster_dim.x, - info->subrect.y0 + info->raster_dim.y); - piece->advance = advance; - piece->decode_size = piece_substring.size; - piece->offset = v2s16(0, -(hash2style_node->ascent + hash2style_node->descent)); - } - base_align_px += advance; - dim.x += piece->advance; - dim.y = Max(dim.y, info->raster_dim.y); + run_node = n; + break; } } } - //- rjf: tighten & return + //- rjf: no run node? -> cache miss - compute & build & fill node if possible + B32 run_is_cacheable = 1; FNT_Run run = {0}; + if(run_node) { - if(piece_chunks.node_count == 1) + run = run_node->run; + } + else + ProfScope("no run node? -> cache miss") + ProfScope("compute & build & fill node for '%.*s'", str8_varg(string)) + { + //- rjf: decode string & produce run pieces + FNT_PieceChunkList piece_chunks = {0}; + Vec2F32 dim = {0}; + B32 font_handle_mapped_on_miss = 0; + FP_Handle font_handle = {0}; + U64 piece_substring_start_idx = 0; + U64 piece_substring_end_idx = 0; + for(U64 idx = 0; idx <= string.size;) { - run.pieces.v = piece_chunks.first->v; - run.pieces.count = piece_chunks.first->count; + //- rjf: decode next codepoint & get piece substring, or continuation rule + U8 byte = (idx < string.size ? string.str[idx] : 0); + B32 need_another_codepoint = 0; + if(byte == 0) + { + idx += 1; + } + else switch(utf8_class[byte>>3]) + { + case 1: + { + idx += 1; + piece_substring_end_idx += 1; + need_another_codepoint = 0; + }break; + default: + { + UnicodeDecode decode = utf8_decode(string.str+idx, string.size-idx); + idx += decode.inc; + piece_substring_end_idx += decode.inc; + need_another_codepoint = 0; + }break; + } + + //- rjf: need another codepoint, or have no substring? -> continue + if(need_another_codepoint || piece_substring_end_idx == piece_substring_start_idx) + { + continue; + } + + //- rjf: do not need another codepoint? -> grab substring, bump piece start idx + String8 piece_substring = str8_substr(string, r1u64(piece_substring_start_idx, piece_substring_end_idx)); + piece_substring_start_idx = idx; + piece_substring_end_idx = idx; + + //- rjf: determine if this piece is a tab - if so, use space info to draw + B32 is_tab = (piece_substring.size == 1 && piece_substring.str[0] == '\t'); + if(is_tab) + { + run_is_cacheable = 0; + piece_substring = str8_lit(" "); + } + + //- rjf: piece substring -> raster cache info + FNT_RasterCacheInfo *info = 0; + U64 piece_hash = 0; + { + // rjf: fast path for utf8 class 1 -> direct map + if(piece_substring.size == 1 && hash2style_node->utf8_class1_direct_map_mask[piece_substring.str[0]/64] & (1ull<<(piece_substring.str[0]%64))) + { + info = &hash2style_node->utf8_class1_direct_map[piece_substring.str[0]]; + } + + // rjf: more general, slower path for other glyphs + if(piece_substring.size > 1) + { + piece_hash = fnt_little_hash_from_string(5381, piece_substring); + U64 slot_idx = piece_hash%hash2style_node->hash2info_slots_count; + FNT_Hash2InfoRasterCacheSlot *slot = &hash2style_node->hash2info_slots[slot_idx]; + for(FNT_Hash2InfoRasterCacheNode *node = slot->first; node != 0; node = node->hash_next) + { + if(node->hash == piece_hash) + { + info = &node->info; + break; + } + } + } + } + + //- rjf: no info found -> miss... fill this hash in the cache + if(info == 0) + { + ProfBegin("no info found -> miss... fill this hash in the cache"); + Temp scratch = scratch_begin(&arena, 1); + + // rjf: grab font handle for this tag if we don't have one already + if(font_handle_mapped_on_miss == 0) + { + font_handle_mapped_on_miss = 1; + + // rjf: tag -> font slot index + U64 font_slot_idx = tag.u64[1] % fnt_state->font_hash_table_size; + + // rjf: tag * slot -> existing node + FNT_FontHashNode *existing_node = 0; + { + for(FNT_FontHashNode *n = fnt_state->font_hash_table[font_slot_idx].first; n != 0 ; n = n->hash_next) + { + if(MemoryMatchStruct(&n->tag, &tag)) + { + existing_node = n; + break; + } + } + } + + // rjf: existing node -> font handle + if(existing_node != 0) + { + font_handle = existing_node->handle; + } + } + + // rjf: call into font provider to rasterize this substring + FP_RasterResult raster = {0}; + if(size > 0) + { + FP_RasterFlags fp_flags = 0; + if(flags & FNT_RasterFlag_Smooth) { fp_flags |= FP_RasterFlag_Smooth; } + if(flags & FNT_RasterFlag_Hinted) { fp_flags |= FP_RasterFlag_Hinted; } + raster = fp_raster(scratch.arena, font_handle, floor_f32(size), flags, piece_substring); + } + + // rjf: allocate portion of an atlas to upload the rasterization + S16 chosen_atlas_num = 0; + FNT_Atlas *chosen_atlas = 0; + Rng2S16 chosen_atlas_region = {0}; + if(raster.atlas_dim.x != 0 && raster.atlas_dim.y != 0) + { + U64 num_atlases = 0; + for(FNT_Atlas *atlas = fnt_state->first_atlas;; atlas = atlas->next, num_atlases += 1) + { + // rjf: create atlas if needed + if(atlas == 0 && num_atlases < 64) + { + atlas = push_array(fnt_state->raster_arena, FNT_Atlas, 1); + DLLPushBack(fnt_state->first_atlas, fnt_state->last_atlas, atlas); + atlas->root_dim = v2s16(1024, 1024); + atlas->root = push_array(fnt_state->raster_arena, FNT_AtlasRegionNode, 1); + atlas->root->max_free_size[Corner_00] = + atlas->root->max_free_size[Corner_01] = + atlas->root->max_free_size[Corner_10] = + atlas->root->max_free_size[Corner_11] = v2s16(atlas->root_dim.x/2, atlas->root_dim.y/2); + atlas->texture = r_tex2d_alloc(R_ResourceKind_Dynamic, v2s32((S32)atlas->root_dim.x, (S32)atlas->root_dim.y), R_Tex2DFormat_RGBA8, 0); + } + + // rjf: allocate from atlas + if(atlas != 0) + { + Vec2S16 needed_dimensions = v2s16(raster.atlas_dim.x + 2, raster.atlas_dim.y + 2); + chosen_atlas_region = fnt_atlas_region_alloc(fnt_state->raster_arena, atlas, needed_dimensions); + if(chosen_atlas_region.x1 != chosen_atlas_region.x0) + { + chosen_atlas = atlas; + chosen_atlas_num = (S32)num_atlases; + break; + } + } + else + { + break; + } + } + } + + // rjf: upload rasterization to allocated region of atlas texture memory + if(chosen_atlas != 0) + { + Rng2S32 subregion = + { + chosen_atlas_region.x0, + chosen_atlas_region.y0, + chosen_atlas_region.x0 + raster.atlas_dim.x, + chosen_atlas_region.y0 + raster.atlas_dim.y + }; + r_fill_tex2d_region(chosen_atlas->texture, subregion, raster.atlas); + } + + // rjf: allocate & fill & push node + { + if(piece_substring.size == 1) + { + info = &hash2style_node->utf8_class1_direct_map[piece_substring.str[0]]; + hash2style_node->utf8_class1_direct_map_mask[piece_substring.str[0]/64] |= (1ull<<(piece_substring.str[0]%64)); + } + else + { + U64 slot_idx = piece_hash%hash2style_node->hash2info_slots_count; + FNT_Hash2InfoRasterCacheSlot *slot = &hash2style_node->hash2info_slots[slot_idx]; + FNT_Hash2InfoRasterCacheNode *node = push_array_no_zero(fnt_state->raster_arena, FNT_Hash2InfoRasterCacheNode, 1); + DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev); + node->hash = piece_hash; + info = &node->info; + } + if(info != 0) + { + info->subrect = chosen_atlas_region; + info->atlas_num = chosen_atlas_num; + info->raster_dim = raster.atlas_dim; + info->advance = raster.advance; + } + } + + scratch_end(scratch); + ProfEnd(); + } + + //- rjf: push piece for this raster portion + if(info != 0) + { + // rjf: find atlas + FNT_Atlas *atlas = 0; + { + if(info->subrect.x1 != 0 && info->subrect.y1 != 0) + { + S32 num = 0; + for(FNT_Atlas *a = fnt_state->first_atlas; a != 0; a = a->next, num += 1) + { + if(info->atlas_num == num) + { + atlas = a; + break; + } + } + } + } + + // rjf: on tabs -> expand advance + F32 advance = info->advance; + if(is_tab) + { + advance = floor_f32(tab_size_px) - mod_f32(floor_f32(base_align_px), floor_f32(tab_size_px)); + } + + // rjf: push piece + { + FNT_Piece *piece = fnt_piece_chunk_list_push_new(fnt_state->frame_arena, &piece_chunks, string.size); + { + piece->texture = atlas ? atlas->texture : r_handle_zero(); + piece->subrect = r2s16p(info->subrect.x0, + info->subrect.y0, + info->subrect.x0 + info->raster_dim.x, + info->subrect.y0 + info->raster_dim.y); + piece->advance = advance; + piece->decode_size = piece_substring.size; + piece->offset = v2s16(0, -(hash2style_node->ascent + hash2style_node->descent)); + } + base_align_px += advance; + dim.x += piece->advance; + dim.y = Max(dim.y, info->raster_dim.y); + } + } } - else + + //- rjf: tighten & fill { - run.pieces = fnt_piece_array_from_chunk_list(arena, &piece_chunks); + if(piece_chunks.node_count == 1) + { + run.pieces.v = piece_chunks.first->v; + run.pieces.count = piece_chunks.first->count; + } + else + { + run.pieces = fnt_piece_array_from_chunk_list(fnt_state->frame_arena, &piece_chunks); + } + run.dim = dim; + run.ascent = hash2style_node->ascent; + run.descent = hash2style_node->descent; } - run.dim = dim; - run.ascent = hash2style_node->ascent; - run.descent = hash2style_node->descent; + } + + //- rjf: build node for cacheable runs + if(run_is_cacheable) + { + run_node = push_array(fnt_state->frame_arena, FNT_RunCacheNode, 1); + SLLQueuePush(run_slot->first, run_slot->last, run_node); + run_node->string = push_str8_copy(fnt_state->frame_arena, string); + run_node->run = run; } ProfEnd(); @@ -1057,23 +1104,31 @@ internal void fnt_init(void) { Arena *arena = arena_alloc(); - f_state = push_array(arena, FNT_State, 1); - f_state->permanent_arena = arena; - f_state->raster_arena = arena_alloc(); - f_state->font_hash_table_size = 64; - f_state->font_hash_table = push_array(f_state->permanent_arena, FNT_FontHashSlot, f_state->font_hash_table_size); + fnt_state = push_array(arena, FNT_State, 1); + fnt_state->permanent_arena = arena; + fnt_state->raster_arena = arena_alloc(); + fnt_state->frame_arena = arena_alloc(); + fnt_state->font_hash_table_size = 64; + fnt_state->font_hash_table = push_array(fnt_state->permanent_arena, FNT_FontHashSlot, fnt_state->font_hash_table_size); fnt_reset(); } internal void fnt_reset(void) { - for(FNT_Atlas *a = f_state->first_atlas; a != 0; a = a->next) + for(FNT_Atlas *a = fnt_state->first_atlas; a != 0; a = a->next) { r_tex2d_release(a->texture); } - f_state->first_atlas = f_state->last_atlas = 0; - arena_clear(f_state->raster_arena); - f_state->hash2style_slots_count = 1024; - f_state->hash2style_slots = push_array(f_state->raster_arena, FNT_Hash2StyleRasterCacheSlot, f_state->hash2style_slots_count); + fnt_state->first_atlas = fnt_state->last_atlas = 0; + arena_clear(fnt_state->raster_arena); + fnt_state->hash2style_slots_count = 1024; + fnt_state->hash2style_slots = push_array(fnt_state->raster_arena, FNT_Hash2StyleRasterCacheSlot, fnt_state->hash2style_slots_count); +} + +internal void +fnt_frame(void) +{ + fnt_state->frame_index += 1; + arena_clear(fnt_state->frame_arena); } diff --git a/src/font_cache/font_cache.h b/src/font_cache/font_cache.h index 9f343eca..d1ac1661 100644 --- a/src/font_cache/font_cache.h +++ b/src/font_cache/font_cache.h @@ -93,8 +93,10 @@ struct FNT_FontHashSlot //////////////////////////////// //~ rjf: Rasterization Cache Types -typedef struct F_RasterCacheInfo F_RasterCacheInfo; -struct F_RasterCacheInfo +//- rjf: base glyph rasterization / dimensions cache + +typedef struct FNT_RasterCacheInfo FNT_RasterCacheInfo; +struct FNT_RasterCacheInfo { Rng2S16 subrect; Vec2S16 raster_dim; @@ -102,22 +104,41 @@ struct F_RasterCacheInfo F32 advance; }; -typedef struct F_Hash2InfoRasterCacheNode F_Hash2InfoRasterCacheNode; -struct F_Hash2InfoRasterCacheNode +typedef struct FNT_Hash2InfoRasterCacheNode FNT_Hash2InfoRasterCacheNode; +struct FNT_Hash2InfoRasterCacheNode { - F_Hash2InfoRasterCacheNode *hash_next; - F_Hash2InfoRasterCacheNode *hash_prev; + FNT_Hash2InfoRasterCacheNode *hash_next; + FNT_Hash2InfoRasterCacheNode *hash_prev; U64 hash; - F_RasterCacheInfo info; + FNT_RasterCacheInfo info; }; typedef struct FNT_Hash2InfoRasterCacheSlot FNT_Hash2InfoRasterCacheSlot; struct FNT_Hash2InfoRasterCacheSlot { - F_Hash2InfoRasterCacheNode *first; - F_Hash2InfoRasterCacheNode *last; + FNT_Hash2InfoRasterCacheNode *first; + FNT_Hash2InfoRasterCacheNode *last; }; +//- rjf: run cache (arrangements of many glyphs to represent a full string) + +typedef struct FNT_RunCacheNode FNT_RunCacheNode; +struct FNT_RunCacheNode +{ + FNT_RunCacheNode *next; + String8 string; + FNT_Run run; +}; + +typedef struct FNT_RunCacheSlot FNT_RunCacheSlot; +struct FNT_RunCacheSlot +{ + FNT_RunCacheNode *first; + FNT_RunCacheNode *last; +}; + +//- rjf: style hash -> artifacts/metrics cache + typedef struct FNT_Hash2StyleRasterCacheNode FNT_Hash2StyleRasterCacheNode; struct FNT_Hash2StyleRasterCacheNode { @@ -127,10 +148,13 @@ struct FNT_Hash2StyleRasterCacheNode F32 ascent; F32 descent; F32 column_width; - F_RasterCacheInfo *utf8_class1_direct_map; + FNT_RasterCacheInfo *utf8_class1_direct_map; U64 utf8_class1_direct_map_mask[4]; U64 hash2info_slots_count; FNT_Hash2InfoRasterCacheSlot *hash2info_slots; + U64 run_slots_count; + FNT_RunCacheSlot *run_slots; + U64 run_slots_frame_index; }; typedef struct FNT_Hash2StyleRasterCacheSlot FNT_Hash2StyleRasterCacheSlot; @@ -189,6 +213,8 @@ struct FNT_State { Arena *permanent_arena; Arena *raster_arena; + Arena *frame_arena; + U64 frame_index; // rjf: font table U64 font_hash_table_size; @@ -206,13 +232,13 @@ struct FNT_State //////////////////////////////// //~ rjf: Globals -global FNT_State *f_state = 0; +global FNT_State *fnt_state = 0; //////////////////////////////// //~ rjf: Basic Functions internal U128 fnt_hash_from_string(String8 string); -internal U64 fnt_little_hash_from_string(String8 string); +internal U64 fnt_little_hash_from_string(U64 seed, String8 string); internal Vec2S32 fnt_vertex_from_corner(Corner corner); //////////////////////////////// @@ -241,10 +267,13 @@ internal FNT_PieceArray fnt_piece_array_from_chunk_list(Arena *arena, FNT_PieceC internal FNT_PieceArray fnt_piece_array_copy(Arena *arena, FNT_PieceArray *src); //////////////////////////////// -//~ rjf: Rasterization Cache +//~ rjf: Cache Usage +//- rjf: base cache lookups internal FNT_Hash2StyleRasterCacheNode *fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags); internal FNT_Run fnt_push_run_from_string(Arena *arena, FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, FNT_RasterFlags flags, String8 string); + +//- rjf: helpers internal String8List fnt_wrapped_string_lines_from_font_size_string_max(Arena *arena, FNT_Tag font, F32 size, F32 base_align_px, F32 tab_size_px, String8 string, F32 max); internal Vec2F32 fnt_dim_from_tag_size_string(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8 string); internal Vec2F32 fnt_dim_from_tag_size_string_list(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8List list); @@ -262,5 +291,6 @@ internal F32 fnt_line_height_from_metrics(FNT_Metrics *metrics); internal void fnt_init(void); internal void fnt_reset(void); +internal void fnt_frame(void); #endif // FONT_CACHE_H