mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-06-13 07:32:23 -07:00
1067 lines
33 KiB
C
1067 lines
33 KiB
C
// Copyright (c) 2024 Epic Games Tools
|
|
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Basic Functions
|
|
|
|
#if !defined(XXH_IMPLEMENTATION)
|
|
# define XXH_IMPLEMENTATION
|
|
# define XXH_STATIC_LINKING_ONLY
|
|
# include "linker/third_party_ext/xxHash/xxhash.h"
|
|
#endif
|
|
|
|
internal U128
|
|
fnt_hash_from_string(String8 string)
|
|
{
|
|
union
|
|
{
|
|
XXH128_hash_t xxhash;
|
|
U128 u128;
|
|
}
|
|
hash;
|
|
hash.xxhash = XXH3_128bits(string.str, string.size);
|
|
return hash.u128;
|
|
}
|
|
|
|
internal U64
|
|
fnt_little_hash_from_string(String8 string)
|
|
{
|
|
U64 result = XXH3_64bits(string.str, string.size);
|
|
return result;
|
|
}
|
|
|
|
internal Vec2S32
|
|
fnt_vertex_from_corner(Corner corner)
|
|
{
|
|
Vec2S32 result = {0};
|
|
switch(corner)
|
|
{
|
|
default: break;
|
|
case Corner_00:{result = v2s32(0, 0);}break;
|
|
case Corner_01:{result = v2s32(0, 1);}break;
|
|
case Corner_10:{result = v2s32(1, 0);}break;
|
|
case Corner_11:{result = v2s32(1, 1);}break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Font Tags
|
|
|
|
internal FNT_Tag
|
|
fnt_tag_zero(void)
|
|
{
|
|
FNT_Tag result = {0};
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
fnt_tag_match(FNT_Tag a, FNT_Tag b)
|
|
{
|
|
return a.u64[0] == b.u64[0] && a.u64[1] == b.u64[1];
|
|
}
|
|
|
|
internal FP_Handle
|
|
fnt_handle_from_tag(FNT_Tag tag)
|
|
{
|
|
ProfBeginFunction();
|
|
U64 slot_idx = tag.u64[1] % f_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)
|
|
{
|
|
if(MemoryMatchStruct(&tag, &n->tag))
|
|
{
|
|
existing_node = n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
FP_Handle result = {0};
|
|
if(existing_node != 0)
|
|
{
|
|
result = existing_node->handle;
|
|
}
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal FP_Metrics
|
|
fnt_fp_metrics_from_tag(FNT_Tag tag)
|
|
{
|
|
ProfBeginFunction();
|
|
U64 slot_idx = tag.u64[1] % f_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)
|
|
{
|
|
if(MemoryMatchStruct(&tag, &n->tag))
|
|
{
|
|
existing_node = n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
FP_Metrics result = {0};
|
|
if(existing_node != 0)
|
|
{
|
|
result = existing_node->metrics;
|
|
}
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal FNT_Tag
|
|
fnt_tag_from_path(String8 path)
|
|
{
|
|
ProfBeginFunction();
|
|
|
|
//- rjf: produce tag from hash of path
|
|
FNT_Tag result = {0};
|
|
{
|
|
U128 hash = fnt_hash_from_string(path);
|
|
MemoryCopy(&result, &hash, sizeof(result));
|
|
result.u64[1] |= bit64;
|
|
}
|
|
|
|
//- rjf: tag -> slot index
|
|
U64 slot_idx = result.u64[1] % f_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)
|
|
{
|
|
if(MemoryMatchStruct(&result, &n->tag))
|
|
{
|
|
existing_node = n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: allocate & push new node if we don't have an existing one
|
|
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->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->arena, path);
|
|
SLLQueuePush_N(slot->first, slot->last, new_node, hash_next);
|
|
}
|
|
|
|
//- rjf: return
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal FNT_Tag
|
|
fnt_tag_from_static_data_string(String8 *data_ptr)
|
|
{
|
|
ProfBeginFunction();
|
|
|
|
//- rjf: produce tag hash of ptr
|
|
FNT_Tag result = {0};
|
|
{
|
|
U128 hash = fnt_hash_from_string(str8((U8 *)&data_ptr, sizeof(String8 *)));
|
|
MemoryCopy(&result, &hash, sizeof(result));
|
|
result.u64[1] &= ~bit64;
|
|
}
|
|
|
|
//- rjf: tag -> slot index
|
|
U64 slot_idx = result.u64[1] % f_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)
|
|
{
|
|
if(MemoryMatchStruct(&result, &n->tag))
|
|
{
|
|
existing_node = n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: allocate & push new node if we don't have an existing one
|
|
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->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);
|
|
new_node->path = str8_lit("");
|
|
SLLQueuePush_N(slot->first, slot->last, new_node, hash_next);
|
|
}
|
|
|
|
//- rjf: return
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
|
|
//- 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)
|
|
{
|
|
if(MemoryMatchStruct(&tag, &n->tag))
|
|
{
|
|
existing_node = n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: existing node -> path
|
|
String8 result = {0};
|
|
if(existing_node != 0)
|
|
{
|
|
result = existing_node->path;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Atlas
|
|
|
|
internal Rng2S16
|
|
fnt_atlas_region_alloc(Arena *arena, FNT_Atlas *atlas, Vec2S16 needed_size)
|
|
{
|
|
ProfBeginFunction();
|
|
|
|
//- rjf: find node with best-fit size
|
|
Vec2S16 region_p0 = {0};
|
|
Vec2S16 region_sz = {0};
|
|
Corner node_corner = Corner_Invalid;
|
|
FNT_AtlasRegionNode *node = 0;
|
|
{
|
|
Vec2S16 n_supported_size = atlas->root_dim;
|
|
for(FNT_AtlasRegionNode *n = atlas->root, *next = 0; n != 0; n = next, next = 0)
|
|
{
|
|
// rjf: we've traversed to a taken node.
|
|
if(n->flags & FNT_AtlasRegionNodeFlag_Taken)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// rjf: calculate if this node can be allocated (all children are non-allocated)
|
|
B32 n_can_be_allocated = (n->num_allocated_descendants == 0);
|
|
|
|
// rjf: fill size
|
|
if(n_can_be_allocated)
|
|
{
|
|
region_sz = n_supported_size;
|
|
}
|
|
|
|
// rjf: calculate size of this node's children
|
|
Vec2S16 child_size = v2s16(n_supported_size.x/2, n_supported_size.y/2);
|
|
|
|
// rjf: find best next child
|
|
FNT_AtlasRegionNode *best_child = 0;
|
|
if(child_size.x >= needed_size.x && child_size.y >= needed_size.y)
|
|
{
|
|
for(Corner corner = (Corner)0; corner < Corner_COUNT; corner = (Corner)(corner+1))
|
|
{
|
|
if(n->children[corner] == 0)
|
|
{
|
|
n->children[corner] = push_array(arena, FNT_AtlasRegionNode, 1);
|
|
n->children[corner]->parent = n;
|
|
n->children[corner]->max_free_size[Corner_00] =
|
|
n->children[corner]->max_free_size[Corner_01] =
|
|
n->children[corner]->max_free_size[Corner_10] =
|
|
n->children[corner]->max_free_size[Corner_11] = v2s16(child_size.x/2, child_size.y/2);
|
|
}
|
|
if(n->max_free_size[corner].x >= needed_size.x &&
|
|
n->max_free_size[corner].y >= needed_size.y)
|
|
{
|
|
best_child = n->children[corner];
|
|
node_corner = corner;
|
|
Vec2S32 side_vertex = fnt_vertex_from_corner(corner);
|
|
region_p0.x += side_vertex.x*child_size.x;
|
|
region_p0.y += side_vertex.y*child_size.y;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// rjf: resolve node to this node if it can be allocated and children
|
|
// don't fit, or keep going to the next best child
|
|
if(n_can_be_allocated && best_child == 0)
|
|
{
|
|
node = n;
|
|
}
|
|
else
|
|
{
|
|
next = best_child;
|
|
n_supported_size = child_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: we're taking the subtree rooted by `node`. mark up all parents
|
|
if(node != 0 && node_corner != Corner_Invalid)
|
|
{
|
|
node->flags |= FNT_AtlasRegionNodeFlag_Taken;
|
|
if(node->parent != 0)
|
|
{
|
|
MemoryZeroStruct(&node->parent->max_free_size[node_corner]);
|
|
}
|
|
for(FNT_AtlasRegionNode *p = node->parent; p != 0; p = p->parent)
|
|
{
|
|
p->num_allocated_descendants += 1;
|
|
FNT_AtlasRegionNode *parent = p->parent;
|
|
if(parent != 0)
|
|
{
|
|
Corner p_corner = (p == parent->children[Corner_00] ? Corner_00 :
|
|
p == parent->children[Corner_01] ? Corner_01 :
|
|
p == parent->children[Corner_10] ? Corner_10 :
|
|
p == parent->children[Corner_11] ? Corner_11 :
|
|
Corner_Invalid);
|
|
if(p_corner == Corner_Invalid)
|
|
{
|
|
InvalidPath;
|
|
}
|
|
parent->max_free_size[p_corner].x = Max(Max(p->max_free_size[Corner_00].x,
|
|
p->max_free_size[Corner_01].x),
|
|
Max(p->max_free_size[Corner_10].x,
|
|
p->max_free_size[Corner_11].x));
|
|
parent->max_free_size[p_corner].y = Max(Max(p->max_free_size[Corner_00].y,
|
|
p->max_free_size[Corner_01].y),
|
|
Max(p->max_free_size[Corner_10].y,
|
|
p->max_free_size[Corner_11].y));
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: fill rectangular region & return
|
|
Rng2S16 result = {0};
|
|
result.p0 = region_p0;
|
|
result.p1 = add_2s16(region_p0, region_sz);
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
fnt_atlas_region_release(FNT_Atlas *atlas, Rng2S16 region)
|
|
{
|
|
ProfBeginFunction();
|
|
|
|
//- rjf: extract region size
|
|
Vec2S16 region_size = v2s16(region.x1 - region.x0, region.y1 - region.y0);
|
|
|
|
//- rjf: map region to associated node
|
|
Vec2S16 calc_region_size = {0};
|
|
FNT_AtlasRegionNode *node = 0;
|
|
Corner node_corner = Corner_Invalid;
|
|
{
|
|
Vec2S16 n_p0 = v2s16(0, 0);
|
|
Vec2S16 n_sz = atlas->root_dim;
|
|
for(FNT_AtlasRegionNode *n = atlas->root, *next = 0; n != 0; n = next)
|
|
{
|
|
// rjf: is the region within this node's boundaries? (either this node, or a descendant)
|
|
if(n_p0.x <= region.p0.x && region.p0.x < n_p0.x+n_sz.x &&
|
|
n_p0.y <= region.p0.y && region.p0.y < n_p0.y+n_sz.y)
|
|
{
|
|
// rjf: check the region against this node
|
|
if(region.p0.x == n_p0.x && region.p0.y == n_p0.y &&
|
|
region_size.x == n_sz.x && region_size.y == n_sz.y)
|
|
{
|
|
node = n;
|
|
calc_region_size = n_sz;
|
|
break;
|
|
}
|
|
// rjf: check the region against children & iterate
|
|
else
|
|
{
|
|
Vec2S16 r_midpoint = v2s16(region.p0.x + region_size.x/2,
|
|
region.p0.y + region_size.y/2);
|
|
Vec2S16 n_midpoint = v2s16(n_p0.x + n_sz.x/2,
|
|
n_p0.y + n_sz.y/2);
|
|
Corner next_corner = Corner_Invalid;
|
|
if(r_midpoint.x <= n_midpoint.x && r_midpoint.y <= n_midpoint.y)
|
|
{
|
|
next_corner = Corner_00;
|
|
}
|
|
else if(r_midpoint.x <= n_midpoint.x && n_midpoint.y <= r_midpoint.y)
|
|
{
|
|
next_corner = Corner_01;
|
|
}
|
|
else if(n_midpoint.x <= r_midpoint.x && r_midpoint.y <= n_midpoint.y)
|
|
{
|
|
next_corner = Corner_10;
|
|
}
|
|
else if(n_midpoint.x <= r_midpoint.x && n_midpoint.y <= r_midpoint.y)
|
|
{
|
|
next_corner = Corner_11;
|
|
}
|
|
next = n->children[next_corner];
|
|
node_corner = next_corner;
|
|
n_sz.x /= 2;
|
|
n_sz.y /= 2;
|
|
Vec2S32 side_vertex = fnt_vertex_from_corner(node_corner);
|
|
n_p0.x += side_vertex.x*n_sz.x;
|
|
n_p0.y += side_vertex.y*n_sz.y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: free node
|
|
if(node != 0 && node_corner != Corner_Invalid)
|
|
{
|
|
node->flags &= ~FNT_AtlasRegionNodeFlag_Taken;
|
|
if(node->parent != 0)
|
|
{
|
|
node->parent->max_free_size[node_corner] = calc_region_size;
|
|
}
|
|
for(FNT_AtlasRegionNode *p = node->parent; p != 0; p = p->parent)
|
|
{
|
|
p->num_allocated_descendants -= 1;
|
|
FNT_AtlasRegionNode *parent = p->parent;
|
|
if(parent != 0)
|
|
{
|
|
Corner p_corner = (p == parent->children[Corner_00] ? Corner_00 :
|
|
p == parent->children[Corner_01] ? Corner_01 :
|
|
p == parent->children[Corner_10] ? Corner_10 :
|
|
p == parent->children[Corner_11] ? Corner_11 :
|
|
Corner_Invalid);
|
|
if(p_corner == Corner_Invalid)
|
|
{
|
|
InvalidPath;
|
|
}
|
|
parent->max_free_size[p_corner].x = Max(Max(p->max_free_size[Corner_00].x,
|
|
p->max_free_size[Corner_01].x),
|
|
Max(p->max_free_size[Corner_10].x,
|
|
p->max_free_size[Corner_11].x));
|
|
parent->max_free_size[p_corner].y = Max(Max(p->max_free_size[Corner_00].y,
|
|
p->max_free_size[Corner_01].y),
|
|
Max(p->max_free_size[Corner_10].y,
|
|
p->max_free_size[Corner_11].y));
|
|
}
|
|
}
|
|
}
|
|
ProfEnd();
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Piece Type Functions
|
|
|
|
internal FNT_Piece *
|
|
fnt_piece_chunk_list_push_new(Arena *arena, FNT_PieceChunkList *list, U64 cap)
|
|
{
|
|
FNT_PieceChunkNode *node = list->last;
|
|
if(node == 0 || node->count >= node->cap)
|
|
{
|
|
node = push_array(arena, FNT_PieceChunkNode, 1);
|
|
node->v = push_array_no_zero(arena, FNT_Piece, cap);
|
|
node->cap = cap;
|
|
SLLQueuePush(list->first, list->last, node);
|
|
list->node_count += 1;
|
|
}
|
|
FNT_Piece *result = node->v + node->count;
|
|
node->count += 1;
|
|
list->total_piece_count += 1;
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
fnt_piece_chunk_list_push(Arena *arena, FNT_PieceChunkList *list, U64 cap, FNT_Piece *piece)
|
|
{
|
|
FNT_Piece *new_piece = fnt_piece_chunk_list_push_new(arena, list, cap);
|
|
MemoryCopyStruct(new_piece, piece);
|
|
}
|
|
|
|
internal FNT_PieceArray
|
|
fnt_piece_array_from_chunk_list(Arena *arena, FNT_PieceChunkList *list)
|
|
{
|
|
FNT_PieceArray array = {0};
|
|
array.count = list->total_piece_count;
|
|
array.v = push_array_no_zero(arena, FNT_Piece, array.count);
|
|
U64 write_idx = 0;
|
|
for(FNT_PieceChunkNode *node = list->first; node != 0; node = node->next)
|
|
{
|
|
MemoryCopy(array.v + write_idx, node->v, node->count * sizeof(FNT_Piece));
|
|
write_idx += node->count;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
internal FNT_PieceArray
|
|
fnt_piece_array_copy(Arena *arena, FNT_PieceArray *src)
|
|
{
|
|
FNT_PieceArray dst = {0};
|
|
dst.count = src->count;
|
|
dst.v = push_array_no_zero(arena, FNT_Piece, dst.count);
|
|
MemoryCopy(dst.v, src->v, sizeof(FNT_Piece)*dst.count);
|
|
return dst;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Rasterization Cache
|
|
|
|
internal FNT_Hash2StyleRasterCacheNode *
|
|
fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags)
|
|
{
|
|
//- rjf: tag * size -> style hash
|
|
U64 style_hash = {0};
|
|
{
|
|
F64 size_f64 = size;
|
|
U64 buffer[] =
|
|
{
|
|
tag.u64[0],
|
|
tag.u64[1],
|
|
*(U64 *)(&size_f64),
|
|
(U64)flags,
|
|
};
|
|
style_hash = fnt_little_hash_from_string(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];
|
|
for(FNT_Hash2StyleRasterCacheNode *n = slot->first;
|
|
n != 0;
|
|
n = n->hash_next)
|
|
{
|
|
if(n->style_hash == style_hash)
|
|
{
|
|
hash2style_node = n;
|
|
break;
|
|
}
|
|
}
|
|
if(Unlikely(hash2style_node == 0))
|
|
{
|
|
FNT_Metrics metrics = fnt_metrics_from_tag_size(tag, size);
|
|
hash2style_node = push_array(f_state->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->arena, F_RasterCacheInfo, 256);
|
|
hash2style_node->hash2info_slots_count = 1024;
|
|
hash2style_node->hash2info_slots = push_array(f_state->arena, FNT_Hash2InfoRasterCacheSlot, hash2style_node->hash2info_slots_count);
|
|
}
|
|
ProfEnd();
|
|
}
|
|
|
|
return hash2style_node;
|
|
}
|
|
|
|
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)
|
|
{
|
|
ProfBeginFunction();
|
|
|
|
//- 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: 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)
|
|
{
|
|
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->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->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->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->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);
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: tighten & return
|
|
FNT_Run run = {0};
|
|
{
|
|
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(arena, &piece_chunks);
|
|
}
|
|
run.dim = dim;
|
|
run.ascent = hash2style_node->ascent;
|
|
run.descent = hash2style_node->descent;
|
|
}
|
|
|
|
ProfEnd();
|
|
return run;
|
|
}
|
|
|
|
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)
|
|
{
|
|
String8List list = {0};
|
|
{
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
FNT_Run run = fnt_push_run_from_string(scratch.arena, font, size, base_align_px, tab_size_px, 0, string);
|
|
F32 off_px = 0;
|
|
U64 off_bytes = 0;
|
|
U64 line_start_off_bytes = 0;
|
|
U64 line_end_off_bytes = 0;
|
|
B32 seeking_word_end = 0;
|
|
F32 word_start_off_px = 0;
|
|
FNT_Piece *last_word_start_piece = 0;
|
|
U64 last_word_start_off_bytes = 0;
|
|
FNT_Piece *pieces_first = run.pieces.v;
|
|
FNT_Piece *pieces_opl = run.pieces.v + run.pieces.count;
|
|
for(FNT_Piece *piece = pieces_first, *next = 0; piece != 0 && piece <= pieces_opl; piece = next)
|
|
{
|
|
if(piece != 0) {next = piece+1;}
|
|
|
|
// rjf: gather info
|
|
U8 byte = off_bytes < string.size ? string.str[off_bytes] : 0;
|
|
F32 advance = (piece != 0) ? piece->advance : 0;
|
|
U64 decode_size = (piece != 0) ? piece->decode_size : 0;
|
|
|
|
// rjf: find start/end of words
|
|
B32 is_first_byte_of_word = 0;
|
|
B32 is_first_space_after_word = 0;
|
|
if(!seeking_word_end && !char_is_space(byte))
|
|
{
|
|
seeking_word_end = 1;
|
|
is_first_byte_of_word = 1;
|
|
last_word_start_off_bytes = off_bytes;
|
|
last_word_start_piece = piece;
|
|
word_start_off_px = off_px;
|
|
}
|
|
else if(seeking_word_end && char_is_space(byte))
|
|
{
|
|
seeking_word_end = 0;
|
|
is_first_space_after_word = 1;
|
|
}
|
|
else if(seeking_word_end && byte == 0)
|
|
{
|
|
is_first_space_after_word = 1;
|
|
}
|
|
|
|
// rjf: determine properties of this advance
|
|
B32 is_illegal = (off_px >= max);
|
|
B32 is_next_illegal = (off_px + advance >= max);
|
|
B32 is_end = (byte == 0);
|
|
|
|
// rjf: legal word end -> extend line
|
|
if(is_first_space_after_word && !is_illegal)
|
|
{
|
|
line_end_off_bytes = off_bytes;
|
|
}
|
|
|
|
// rjf: illegal mid-word split -> wrap mid-word
|
|
if(is_next_illegal && word_start_off_px == 0)
|
|
{
|
|
String8 line = str8(string.str + line_start_off_bytes, off_bytes - line_start_off_bytes);
|
|
line = str8_skip_chop_whitespace(line);
|
|
if(line.size != 0)
|
|
{
|
|
str8_list_push(arena, &list, line);
|
|
}
|
|
off_px = advance;
|
|
line_start_off_bytes = off_bytes;
|
|
line_end_off_bytes = off_bytes;
|
|
word_start_off_px = 0;
|
|
last_word_start_piece = piece;
|
|
last_word_start_off_bytes = off_bytes;
|
|
off_bytes += decode_size;
|
|
}
|
|
|
|
// rjf: illegal word end -> wrap line
|
|
else if(is_first_space_after_word && (is_illegal || is_end))
|
|
{
|
|
String8 line = str8(string.str + line_start_off_bytes, line_end_off_bytes - line_start_off_bytes);
|
|
line = str8_skip_chop_whitespace(line);
|
|
if(line.size != 0)
|
|
{
|
|
str8_list_push(arena, &list, line);
|
|
}
|
|
line_start_off_bytes = line_end_off_bytes;
|
|
if(is_illegal)
|
|
{
|
|
off_px = 0;
|
|
word_start_off_px = 0;
|
|
off_bytes = last_word_start_off_bytes;
|
|
next = last_word_start_piece;
|
|
}
|
|
}
|
|
|
|
// rjf: advance offsets otherwise
|
|
else
|
|
{
|
|
off_px += advance;
|
|
off_bytes += decode_size;
|
|
}
|
|
|
|
// rjf: 0 piece and 0 next -> done
|
|
if(piece == 0 && next == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
scratch_end(scratch);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
internal Vec2F32
|
|
fnt_dim_from_tag_size_string(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8 string)
|
|
{
|
|
ProfBeginFunction();
|
|
Temp scratch = scratch_begin(0, 0);
|
|
Vec2F32 result = {0};
|
|
FNT_Run run = fnt_push_run_from_string(scratch.arena, tag, size, base_align_px, tab_size_px, 0, string);
|
|
result = run.dim;
|
|
scratch_end(scratch);
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal Vec2F32
|
|
fnt_dim_from_tag_size_string_list(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8List list)
|
|
{
|
|
ProfBeginFunction();
|
|
Vec2F32 sum = {0};
|
|
for(String8Node *n = list.first; n != 0; n = n->next)
|
|
{
|
|
Vec2F32 str_dim = fnt_dim_from_tag_size_string(tag, size, base_align_px, tab_size_px, n->string);
|
|
sum.x += str_dim.x;
|
|
sum.y = Max(sum.y, str_dim.y);
|
|
}
|
|
ProfEnd();
|
|
return sum;
|
|
}
|
|
|
|
internal F32
|
|
fnt_column_size_from_tag_size(FNT_Tag tag, F32 size)
|
|
{
|
|
F32 result = fnt_dim_from_tag_size_string(tag, size, 0, 0, str8_lit("H")).x;
|
|
return result;
|
|
}
|
|
|
|
internal U64
|
|
fnt_char_pos_from_tag_size_string_p(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8 string, F32 p)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
U64 best_offset_bytes = 0;
|
|
F32 best_offset_px = inf32();
|
|
U64 offset_bytes = 0;
|
|
F32 offset_px = 0.f;
|
|
FNT_Run run = fnt_push_run_from_string(scratch.arena, tag, size, base_align_px, tab_size_px, 0, string);
|
|
for(U64 idx = 0; idx <= run.pieces.count; idx += 1)
|
|
{
|
|
F32 this_piece_offset_px = abs_f32(offset_px - p);
|
|
if(this_piece_offset_px < best_offset_px)
|
|
{
|
|
best_offset_bytes = offset_bytes;
|
|
best_offset_px = this_piece_offset_px;
|
|
}
|
|
if(idx < run.pieces.count)
|
|
{
|
|
FNT_Piece *piece = &run.pieces.v[idx];
|
|
offset_px += piece->advance;
|
|
offset_bytes += piece->decode_size;
|
|
}
|
|
}
|
|
scratch_end(scratch);
|
|
return best_offset_bytes;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Metrics
|
|
|
|
internal FNT_Metrics
|
|
fnt_metrics_from_tag_size(FNT_Tag tag, F32 size)
|
|
{
|
|
ProfBeginFunction();
|
|
FP_Metrics metrics = fnt_fp_metrics_from_tag(tag);
|
|
FNT_Metrics result = {0};
|
|
{
|
|
result.ascent = floor_f32(size) * metrics.ascent / metrics.design_units_per_em;
|
|
result.descent = floor_f32(size) * metrics.descent / metrics.design_units_per_em;
|
|
result.line_gap = floor_f32(size) * metrics.line_gap / metrics.design_units_per_em;
|
|
result.capital_height = floor_f32(size) * metrics.capital_height / metrics.design_units_per_em;
|
|
}
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal F32
|
|
fnt_line_height_from_metrics(FNT_Metrics *metrics)
|
|
{
|
|
return metrics->ascent + metrics->descent + metrics->line_gap;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Main Calls
|
|
|
|
internal void
|
|
fnt_init(void)
|
|
{
|
|
Arena *arena = arena_alloc();
|
|
f_state = push_array(arena, FNT_State, 1);
|
|
f_state->arena = arena;
|
|
f_state->font_hash_table_size = 64;
|
|
f_state->font_hash_table = push_array(arena, FNT_FontHashSlot, f_state->font_hash_table_size);
|
|
f_state->hash2style_slots_count = 1024;
|
|
f_state->hash2style_slots = push_array(arena, FNT_Hash2StyleRasterCacheSlot, f_state->hash2style_slots_count);
|
|
}
|