LRU cache now as templated key type

Was testing to see if 16-bit cach emade a diff for the glyphs (it did not)
This commit is contained in:
Edward R. Gonzalez 2025-01-06 21:18:39 -05:00
parent bf38087d8e
commit a9080fe1f3
12 changed files with 185 additions and 120 deletions

View File

@ -6,17 +6,23 @@ The choice was made to keep the LRU cache implementation as close to the origina
import "base:runtime"
Pool_ListIter :: i32
Pool_ListValue :: u32
// 16-bit hashing was attempted, however it seems to get collisions with djb8_hash_16
Pool_List_Item :: struct {
LRU_Fail_Mask_16 :: 0xFFFF
LRU_Fail_Mask_32 :: 0xFFFFFFFF
LRU_Fail_Mask_64 :: 0xFFFFFFFFFFFFFFFF
Pool_ListIter :: i32
// Pool_ListValue :: LRU_Key
Pool_List_Item :: struct( $V_Type : typeid ) #packed {
prev : Pool_ListIter,
next : Pool_ListIter,
value : Pool_ListValue,
value : V_Type,
}
Pool_List :: struct {
items : [dynamic]Pool_List_Item,
Pool_List :: struct( $V_Type : typeid) {
items : [dynamic]Pool_List_Item(V_Type),
free_list : [dynamic]Pool_ListIter,
front : Pool_ListIter,
back : Pool_ListIter,
@ -25,10 +31,10 @@ Pool_List :: struct {
dbg_name : string,
}
pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = "" )
pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : string = "" )
{
error : Allocator_Error
pool.items, error = make( [dynamic]Pool_List_Item, int(capacity) )
pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array")
resize( & pool.items, capacity )
@ -52,17 +58,17 @@ pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = "
pool.back = -1
}
pool_list_free :: proc( pool : ^Pool_List ) {
pool_list_free :: proc( pool : ^Pool_List($V_Type) ) {
delete( pool.items)
delete( pool.free_list)
}
pool_list_reload :: proc( pool : ^Pool_List, allocator : Allocator ) {
pool_list_reload :: proc( pool : ^Pool_List($V_Type), allocator : Allocator ) {
reload_array( & pool.items, allocator )
reload_array( & pool.free_list, allocator )
}
pool_list_clear :: proc( pool: ^Pool_List )
pool_list_clear :: proc( pool: ^Pool_List($V_Type) )
{
clear(& pool.items)
clear(& pool.free_list)
@ -82,7 +88,8 @@ pool_list_clear :: proc( pool: ^Pool_List )
pool.size = 0
}
pool_list_push_front :: proc( pool : ^Pool_List, value : Pool_ListValue )
@(optimization_mode="favor_size")
pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_bounds_check
{
if pool.size >= pool.capacity do return
@ -108,7 +115,8 @@ pool_list_push_front :: proc( pool : ^Pool_List, value : Pool_ListValue )
pool.size += 1
}
pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter )
@(optimization_mode="favor_size")
pool_list_erase :: proc( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
{
if pool.size <= 0 do return
assert( iter >= 0 && iter < i32(pool.capacity) )
@ -136,7 +144,8 @@ pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter )
}
}
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter )
@(optimization_mode="favor_size")
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
{
if pool.front == iter do return
@ -151,13 +160,15 @@ pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List, iter : Pool_L
pool.front = iter
}
pool_list_peek_back :: #force_inline proc ( pool : Pool_List ) -> Pool_ListValue #no_bounds_check {
@(optimization_mode="favor_size")
pool_list_peek_back :: #force_inline proc ( pool : Pool_List($V_Type) ) -> V_Type #no_bounds_check {
assert( pool.back != - 1 )
value := pool.items[ pool.back ].value
return value
}
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue {
@(optimization_mode="favor_size")
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type #no_bounds_check {
if pool.size <= 0 do return 0
assert( pool.back != -1 )
@ -167,52 +178,50 @@ pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue
}
LRU_Link :: struct {
pad_top : u64,
value : i32,
ptr : Pool_ListIter,
pad_bottom : u64,
}
LRU_Cache :: struct {
LRU_Cache :: struct( $Key_Type : typeid ) {
capacity : i32,
num : i32,
table : map[u32]LRU_Link,
key_queue : Pool_List,
table : map[Key_Type]LRU_Link,
key_queue : Pool_List(Key_Type),
}
lru_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) {
lru_init :: proc( cache : ^LRU_Cache($Key_Type), capacity : i32, dbg_name : string = "" ) {
error : Allocator_Error
cache.capacity = capacity
cache.table, error = make( map[u32]LRU_Link, uint(capacity) )
cache.table, error = make( map[Key_Type]LRU_Link, uint(capacity) )
assert( error == .None, "VEFontCache.lru_init : Failed to allocate cache's table")
pool_list_init( & cache.key_queue, capacity, dbg_name = dbg_name )
}
}
lru_free :: proc( cache : ^LRU_Cache ) {
lru_free :: proc( cache : ^LRU_Cache($Key_Type) ) {
pool_list_free( & cache.key_queue )
delete( cache.table )
}
lru_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) {
lru_reload :: #force_inline proc( cache : ^LRU_Cache($Key_Type), allocator : Allocator ) {
reload_map( & cache.table, allocator )
pool_list_reload( & cache.key_queue, allocator )
}
lru_clear :: proc ( cache : ^LRU_Cache ) {
lru_clear :: proc ( cache : ^LRU_Cache($Key_Type) ) {
pool_list_clear( & cache.key_queue )
clear(& cache.table)
cache.num = 0
}
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, must_find := false ) -> (LRU_Link, bool) {
@(optimization_mode="favor_size")
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> (LRU_Link, bool) #no_bounds_check {
link, success := cache.table[key]
return link, success
}
lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u32 ) -> i32 #no_bounds_check {
@(optimization_mode="favor_size")
lru_get :: #force_inline proc ( cache: ^LRU_Cache($Key_Type), key : Key_Type ) -> i32 #no_bounds_check {
if link, ok := &cache.table[ key ]; ok {
pool_list_move_to_front(&cache.key_queue, link.ptr)
return link.value
@ -220,15 +229,17 @@ lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u32 ) -> i32 #no_bounds
return -1
}
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache ) -> u32 {
@(optimization_mode="favor_size")
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache($Key_Type) ) -> Key_Type #no_bounds_check {
if cache.key_queue.size >= cache.capacity {
evict := pool_list_peek_back( cache.key_queue )
return evict
}
return 0xFFFFFFFF
return ~Key_Type(0)
}
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, must_find := false ) -> i32 {
@(optimization_mode="favor_size")
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> i32 #no_bounds_check {
iter, success := lru_find( cache, key, must_find )
if success == false {
return -1
@ -236,7 +247,8 @@ lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, mus
return iter.value
}
lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u32, value : i32 ) -> u32
@(optimization_mode="favor_size")
lru_put :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type, value : i32 ) -> Key_Type #no_bounds_check
{
// profile(#procedure)
if link, ok := & cache.table[ key ]; ok {
@ -261,7 +273,7 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u32, value : i32 ) -> u
return evict
}
lru_refresh :: proc( cache : ^LRU_Cache, key : u32 ) {
lru_refresh :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type ) {
link, success := lru_find( cache ^, key )
pool_list_erase( & cache.key_queue, link.ptr )
pool_list_push_front( & cache.key_queue, key )

View File

@ -10,8 +10,10 @@ Atlas_Region_Kind :: enum u8 {
Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call
}
Atlas_Region_Key :: u32
Atlas_Region :: struct {
state : LRU_Cache,
state : LRU_Cache(Atlas_Region_Key),
size : Vec2i,
capacity : Vec2i,
@ -36,7 +38,19 @@ Atlas :: struct {
size : Vec2i,
}
atlas_region_bbox :: proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
atlas_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, px_size : f32, glyph_index : Glyph ) -> (lru_code : Atlas_Region_Key) {
// lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
font := font
glyph_index := glyph_index
px_size := px_size
djb8_hash( & lru_code, to_bytes( & font) )
djb8_hash( & lru_code, to_bytes( & glyph_index ) )
djb8_hash( & lru_code, to_bytes( & px_size ) )
return
}
atlas_region_bbox :: #force_inline proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
{
size = vec2(region.slot_size.x)
@ -65,7 +79,7 @@ atlas_decide_region :: #force_inline proc "contextless" (atlas : Atlas, glyph_bu
}
// Grab an atlas LRU cache slot.
atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u32 ) -> (atlas_index : i32)
atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : Atlas_Region_Key ) -> (atlas_index : i32)
{
if region.next_idx < region.state.capacity
{
@ -77,7 +91,7 @@ atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u3
else
{
next_evict_codepoint := lru_get_next_evicted( region.state )
assert( next_evict_codepoint != 0xFFFFFFFF)
assert( next_evict_codepoint != LRU_Fail_Mask_16)
atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true )
assert( atlas_index != -1 )

View File

@ -34,7 +34,7 @@ Glyph_Pack_Entry :: struct {
position : Vec2,
index : Glyph,
lru_code : u32,
lru_code : Atlas_Region_Key,
atlas_index : i32,
in_atlas : b8,
should_cache : b8,
@ -92,7 +92,7 @@ Frame_Buffer_Pass :: enum u32 {
}
Glyph_Batch_Cache :: struct {
table : map[u32]b8,
table : map[Atlas_Region_Key]b8,
num : i32,
cap : i32,
}
@ -115,6 +115,7 @@ Glyph_Draw_Buffer :: struct{
cached : [dynamic]i32,
}
@(optimization_mode="favor_size")
blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} )
{
// profile(#procedure)
@ -149,12 +150,13 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1
}
// Constructs a triangle fan to fill a shape using the provided path outside_point represents the center point of the fan.
construct_filled_path :: #force_inline proc( draw_list : ^Draw_List,
@(optimization_mode="favor_size")
construct_filled_path :: proc( draw_list : ^Draw_List,
outside_point : Vec2,
path : []Vertex,
scale := Vec2 { 1, 1 },
translate := Vec2 { 0, 0 }
)
) #no_bounds_check
{
// profile(#procedure)
v_offset := cast(u32) len(draw_list.vertices)
@ -185,12 +187,13 @@ construct_filled_path :: #force_inline proc( draw_list : ^Draw_List,
}
}
@(optimization_mode="favor_size")
generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]Vertex,
glyph_shape : Parser_Glyph_Shape,
curve_quality : f32,
bounds : Range2,
translate, scale : Vec2
)
) #no_bounds_check
{
profile(#procedure)
outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33}
@ -248,14 +251,15 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V
}
}
generate_shapes_draw_list :: proc ( ctx : ^Context, font : Font_ID, colour : Colour, entry : Entry, font_scale : f32, position, scale : Vec2, shapes : []Shaped_Text )
generate_shapes_draw_list :: proc ( ctx : ^Context, font : Font_ID, colour : Colour, entry : Entry, px_size, font_scale : f32, position, scale : Vec2, shapes : []Shaped_Text )
{
assert(len(shapes) > 0)
for shape in shapes {
ctx.cursor_pos = {}
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
colour,
entry,
entry,
px_size,
font_scale,
position,
scale,
@ -265,13 +269,15 @@ generate_shapes_draw_list :: proc ( ctx : ^Context, font : Font_ID, colour : Col
}
}
generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape : Shaped_Text,
@(optimization_mode="favor_size")
generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
atlas : ^Atlas,
glyph_buffer : ^Glyph_Draw_Buffer,
px_scalar : f32,
colour : Colour,
entry : Entry,
px_size : f32,
font_scale : f32,
target_position : Vec2,
@ -282,10 +288,10 @@ generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape
{
profile(#procedure)
font_scale := font_scale * px_scalar
target_scale := target_scale / px_scalar
// font_scale := font_scale * px_scalar
// target_scale := target_scale / px_scalar
mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : u32 ) {
mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : Atlas_Region_Key ) {
cache.table[lru_code] = true
cache.num += 1
}
@ -317,7 +323,7 @@ generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape
for & glyph, index in glyph_pack
{
glyph.index = shape.glyphs[ index ]
glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index)
glyph.lru_code = atlas_glyph_lru_code(entry.id, px_size, glyph.index)
}
profile_end()
@ -463,7 +469,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
colour : Colour,
font_scale : Vec2,
target_scale : Vec2,
)
) #no_bounds_check
{
profile(#procedure)
@ -559,7 +565,6 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
}
profile_end()
profile_begin("generate oversized glyphs draw_list")
{
when ENABLE_DRAW_TYPE_VIS {
@ -722,7 +727,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
}
// Flush the content of the glyph_buffers draw lists to the main draw list
flush_glyph_buffer_draw_list :: #force_inline proc( #no_alias draw_list, glyph_buffer_draw_list, glyph_buffer_clear_draw_list : ^Draw_List, allocated_x : ^i32 )
flush_glyph_buffer_draw_list :: proc( #no_alias draw_list, glyph_buffer_draw_list, glyph_buffer_clear_draw_list : ^Draw_List, allocated_x : ^i32 )
{
profile(#procedure)
// if len(glyph_buffer_clear_draw_list.calls) == 0 || len(glyph_buffer_draw_list.calls) == 0 do return
@ -743,6 +748,7 @@ flush_glyph_buffer_draw_list :: #force_inline proc( #no_alias draw_list, glyph_b
}
// ve_fontcache_clear_Draw_List
@(optimization_mode="favor_size")
clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
clear( & draw_list.calls )
clear( & draw_list.indices )
@ -750,7 +756,8 @@ clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
}
// ve_fontcache_merge_Draw_List
merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List )
@(optimization_mode="favor_size")
merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) #no_bounds_check
{
profile(#procedure)
error : Allocator_Error
@ -776,7 +783,7 @@ merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List )
}
}
optimize_draw_list :: #force_inline proc (draw_list: ^Draw_List, call_offset: int)
optimize_draw_list :: proc (draw_list: ^Draw_List, call_offset: int) #no_bounds_check
{
profile(#procedure)
assert(draw_list != nil)

View File

@ -2,7 +2,7 @@ package vefontcache
import "base:runtime"
import "core:hash"
fnv64a :: hash.fnv64a
ginger16 :: hash.ginger16
import "core:math"
ceil_f16 :: math.ceil_f16
ceil_f16le :: math.ceil_f16le

View File

@ -21,13 +21,27 @@ reload_map :: #force_inline proc( self : ^map [$KeyType] $EntryType, allocator :
raw.allocator = allocator
}
font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u32) {
lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
return
}
to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte { return slice_ptr( transmute(^byte) typed_data, size_of(Type) ) }
djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) }
djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) }
// Provides the nearest prime number value for the given capacity
// closest_prime :: proc( capacity : uint ) -> uint
// {
// prime_table : []uint = {
// 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
// 49157, 98317, 196613, 393241, 786433, 1572869, 3145739,
// 6291469, 12582917, 25165843, 50331653, 100663319,
// 201326611, 402653189, 805306457, 1610612741, 3221225473, 6442450941
// };
// for slot in prime_table {
// if slot >= capacity {
// return slot
// }
// }
// return prime_table[len(prime_table) - 1]
// }
@(optimization_mode="favor_size")
djb8_hash :: #force_inline proc "contextless" ( hash : ^$Type, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + Type(value) }
Colour :: [4]f32
Vec2 :: [2]f32
@ -44,24 +58,25 @@ vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2
// This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators.
// This means a single line is limited to 4k buffer
Logger_Allocator_Buffer : [4 * Kilobyte]u8
// Logger_Allocator_Buffer : [4 * Kilobyte]u8
log :: proc( msg : string, level := core_log.Level.Info, loc := #caller_location ) {
temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
context.allocator = arena_allocator(& temp_arena)
context.temp_allocator = arena_allocator(& temp_arena)
// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
// context.allocator = arena_allocator(& temp_arena)
// context.temp_allocator = arena_allocator(& temp_arena)
core_log.log( level, msg, location = loc )
}
logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc := #caller_location ) {
temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
context.allocator = arena_allocator(& temp_arena)
context.temp_allocator = arena_allocator(& temp_arena)
// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
// context.allocator = arena_allocator(& temp_arena)
// context.temp_allocator = arena_allocator(& temp_arena)
core_log.logf( level, fmt, ..args, location = loc )
}
@(optimization_mode="favor_size")
to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
{
pos := position^
@ -75,6 +90,7 @@ to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position,
(scale^) = scale_32
}
@(optimization_mode="favor_size")
to_target_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
{
quotient : Vec2 = 1.0 / size
@ -126,14 +142,17 @@ else
{
Vec2_SIMD :: simd.f32x4
@(optimization_mode="favor_size")
vec2_to_simd :: #force_inline proc "contextless" (v: Vec2) -> Vec2_SIMD {
return Vec2_SIMD{v.x, v.y, 0, 0}
}
@(optimization_mode="favor_size")
simd_to_vec2 :: #force_inline proc "contextless" (v: Vec2_SIMD) -> Vec2 {
return Vec2{ simd.extract(v, 0), simd.extract(v, 1) }
}
@(optimization_mode="favor_size")
eval_point_on_bezier3 :: #force_inline proc "contextless" (p0, p1, p2: Vec2, alpha: f32) -> Vec2
{
simd_p0 := vec2_to_simd(p0)
@ -157,6 +176,7 @@ else
return simd_to_vec2(result)
}
@(optimization_mode="favor_size")
eval_point_on_bezier4 :: #force_inline proc "contextless" (p0, p1, p2, p3: Vec2, alpha: f32) -> Vec2
{
simd_p0 := vec2_to_simd(p0)

View File

@ -6,7 +6,7 @@ Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists
import "core:c"
import "thirdparty:harfbuzz"
shape_lru_code :: djb8_hash_32
Shape_Key :: u32
Shaped_Text :: struct {
glyphs : [dynamic]Glyph,
@ -19,7 +19,7 @@ Shaped_Text :: struct {
Shaped_Text_Cache :: struct {
storage : [dynamic]Shaped_Text,
state : LRU_Cache,
state : LRU_Cache(Shape_Key),
next_cache_id : i32,
}
@ -71,7 +71,8 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info )
if info.blob != nil do harfbuzz.blob_destroy( info.blob )
}
shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text )
@(optimization_mode="favor_size")
shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text )
{
profile(#procedure)
current_script := harfbuzz.Script.UNKNOWN
@ -87,6 +88,8 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
line_height := ((ascent - descent + line_gap) * font_scale)
position : Vec2
@(optimization_mode="favor_size")
shape_run :: proc( output : ^Shaped_Text,
entry : Entry,
buffer : harfbuzz.Buffer,
@ -241,7 +244,7 @@ shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context
shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output )
}
shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context,
shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,
entry : Entry,
font_px_Size : f32,
font_scale : f32,
@ -305,7 +308,7 @@ shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context,
output.size.y = f32(line_count) * line_height
}
shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
shaper_shape_text_cached :: proc( text_utf8 : string,
ctx : ^Shaper_Context,
shape_cache : ^Shaped_Text_Cache,
font : Font_ID,
@ -316,13 +319,16 @@ shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
) -> (shaped_text : Shaped_Text)
{
profile(#procedure)
font := font
font_bytes := slice_ptr( transmute(^byte) & font, size_of(Font_ID) )
text_bytes := transmute( []byte) text_utf8
font := font
font_px_size := font_px_size
font_bytes := to_bytes( & font )
size_bytes := to_bytes( & font_px_size )
text_bytes := transmute( []byte) text_utf8
lru_code : u32
shape_lru_code( & lru_code, font_bytes )
shape_lru_code( & lru_code, text_bytes )
lru_code : Shape_Key
djb8_hash( & lru_code, font_bytes )
djb8_hash( & lru_code, size_bytes )
djb8_hash( & lru_code, text_bytes )
state := & shape_cache.state
@ -337,7 +343,7 @@ shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
else
{
next_evict_idx := lru_get_next_evicted( state ^ )
assert( next_evict_idx != 0xFFFFFFFF )
assert( next_evict_idx != LRU_Fail_Mask_32 )
shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true )
assert( shape_cache_idx != - 1 )

View File

@ -87,7 +87,7 @@ Init_Atlas_Params :: struct {
}
Init_Atlas_Params_Default :: Init_Atlas_Params {
size_multiplier = 2,
size_multiplier = 1,
glyph_padding = 1,
}
@ -273,7 +273,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
batch_cache := & glyph_buffer.batch_cache
batch_cache.cap = i32(glyph_draw_params.batch_glyph_limit)
batch_cache.num = 0
batch_cache.table, error = make( map[u32]b8, uint(glyph_draw_params.shape_gen_scratch_reserve) )
batch_cache.table, error = make( map[Atlas_Region_Key]b8, uint(glyph_draw_params.shape_gen_scratch_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate batch_cache")
glyph_buffer.glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte )
@ -472,6 +472,7 @@ set_snap_glyph_pos :: #force_inline proc( ctx : ^Context, should_snap : b32 ) {
ctx.shaper_ctx.snap_glyph_position = should_snap
}
@(optimization_mode="favor_size")
draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string )
{
profile(#procedure)
@ -499,16 +500,17 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32,
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_size,
font_scale,
px_upscale,
font_scale_upscale,
shaper_shape_text_uncached_advanced
)
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
colour,
entry,
font_scale,
px_upscale,
font_scale_upscale,
position,
scale,
downscale,
ctx.snap_width,
ctx.snap_height
)
@ -543,14 +545,15 @@ draw_text_slice :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size :
assert( len(str) > 0 )
shape := shaper_shape_text_cached( str, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
entry,
px_upscale,
font_scale,
font_scale_upscale,
shaper_shape_text_uncached_advanced
)
shapes[id] = shape
}
generate_shapes_draw_list(ctx, font, colour, entry, font_scale, position, scale, shapes )
generate_shapes_draw_list(ctx, font, colour, entry, px_upscale, font_scale_upscale, position, downscale, shapes )
}
@ -578,6 +581,7 @@ draw_text_slice :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size :
// }
// Resolve the shape and track it to reduce iteration overhead
@(optimization_mode="favor_size")
draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text )
{
profile(#procedure)
@ -601,9 +605,10 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size :
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
colour,
entry,
font_scale,
px_upscale,
font_scale_upscale,
position,
scale,
downscale,
ctx.snap_width,
ctx.snap_height
)
@ -668,11 +673,12 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size
assert( font >= 0 && int(font) < len(ctx.entries) )
entry := ctx.entries[font]
font_scale := parser_scale( entry.parser_info, px_size )
px_size_upscaled := px_size * ctx.px_scalar
font_scale_upscaled := parser_scale( entry.parser_info, px_size_upscaled )
// font_scale := parser_scale( entry.parser_info, px_size )
shaped := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size_upscaled, font_scale_upscaled, shaper_shape_text_uncached_advanced )
return shaped.size
}
@ -714,7 +720,7 @@ shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size
return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_size_upscaled,
px_size,
font_scale_upscaled,
shaper_shape_text_latin
)
@ -734,7 +740,7 @@ shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si
return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_size_upscaled,
px_size,
font_scale_upscaled,
shaper_shape_text_uncached_advanced
)

View File

@ -35,7 +35,8 @@ import "core:container/queue"
import "core:dynlib"
import "core:hash"
crc32 :: hash.crc32
ginger16 :: hash.ginger16
crc32 :: hash.crc32
import "core:hash/xxhash"
xxh32 :: xxhash.XXH32

View File

@ -15,7 +15,7 @@ set_profiler_module_context :: #force_inline proc "contextless" ( ctx : ^SpallPr
Module_Context = ctx
}
DISABLE_PROFILING :: true
DISABLE_PROFILING :: false
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {

View File

@ -354,8 +354,8 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (197).txt", allocator = persistent_slab_allocator())
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (1022).txt", allocator = persistent_slab_allocator())
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/sokol_gp.h", allocator = persistent_slab_allocator())
debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/ve_fontcache.h", allocator = persistent_slab_allocator())
debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/sokol_gp.h", allocator = persistent_slab_allocator())
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/ve_fontcache.h", allocator = persistent_slab_allocator())
alloc_error : AllocatorError; success : bool
debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_slab_allocator() )
@ -528,8 +528,8 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32
debug.draw_ui_padding_bounds = false
debug.draw_ui_content_bounds = false
font_provider_set_alpha_sharpen(0.15)
font_provider_set_snap_glyph_pos(false)
font_provider_set_alpha_sharpen(0.25)
font_provider_set_snap_glyph_pos(true)
// config.engine_refresh_hz = 165

View File

@ -57,6 +57,7 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In
screen_size := screen_extent * 2
// TODO(Ed): Eventually will be the viewport extents
ve.set_px_scalar( ve_ctx, app_config().font_size_canvas_scalar )
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
// ve.configure_snap( ve_ctx, 0, 0 )
@ -122,6 +123,7 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
screen_size := screen_extent * 2
screen_ratio := screen_size.x * ( 1.0 / screen_size.y )
ve.set_px_scalar( ve_ctx, app_config().font_size_screen_scalar )
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
render_screen_ui( screen_extent, screen_ui, ve_ctx, ve_render )
@ -917,12 +919,18 @@ draw_text_string_pos_norm :: #force_inline proc( text : string, id : FontID, fon
width := app_window.extent.x * 2
height := app_window.extent.y * 2
ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size )
// over_sample : f32 = f32(config.font_size_screen_scalar)
target_size := font_size
// target_size *= over_sample
ve_id, resolved_size := font_provider_resolve_draw_id( id, target_size )
color_norm := normalize_rgba8(color)
screen_size_norm := 1 / Vec2 { width, height }
draw_scale := screen_size_norm * scale
// draw_scale /= over_sample
// ve.set_px_scalar( & font_provider_ctx.ve_ctx, config.font_size_screen_scalar )
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
@ -938,6 +946,7 @@ draw_text_string_pos_extent :: #force_inline proc( text : string, id : FontID, f
screen_size := app_window.extent * 2
render_pos := screen_to_render_pos(pos)
normalized_pos := render_pos * (1.0 / screen_size)
draw_text_string_pos_norm( text, id, font_size, normalized_pos, color )
}

View File

@ -23,7 +23,8 @@ FontID :: struct {
FontDef :: struct {
path_file : string,
default_size : i32,
size_table : [Font_Largest_Px_Size / Font_Size_Interval] ve.Font_ID,
ve_id : ve.Font_ID
// size_table : [Font_Largest_Px_Size / Font_Size_Interval] ve.Font_ID,
}
FontProviderContext :: struct
@ -76,6 +77,7 @@ font_load :: proc(path_file : string,
profile(msg)
log(msg)
font_data, read_succeded : = os.read_entire_file( path_file, persistent_allocator() )
verify( b32(read_succeded), str_fmt("Failed to read font file for: %v", path_file) )
font_data_size := cast(i32) len(font_data)
@ -103,14 +105,8 @@ font_load :: proc(path_file : string,
def.path_file = path_file
def.default_size = default_size
for font_size : i32 = clamp( Font_Size_Interval, 2, Font_Size_Interval ); font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval
{
// logf("Loading at size %v", font_size)
id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval)
ve_id := & def.size_table[id - 1]
ve_ret_id, error := ve.load_font( & ve_ctx, desired_id, font_data )
(ve_id^) = ve_ret_id
}
error : ve.Load_Font_Error
def.ve_id, error = ve.load_font( & ve_ctx, desired_id, font_data )
fid := FontID { key, desired_id }
return fid
@ -118,20 +114,14 @@ font_load :: proc(path_file : string,
font_provider_set_alpha_sharpen :: #force_inline proc( scalar : f32 ) {
ve.set_alpha_scalar( & get_state().font_provider_ctx.ve_ctx, scalar )
// ve.clear_atlas_region_caches(& ctx.ve_ctx)
// ve.clear_shape_cache(& ctx.ve_ctx)
}
font_provider_set_px_scalar :: #force_inline proc( scalar : f32 ) {
ve.set_px_scalar( & get_state().font_provider_ctx.ve_ctx, scalar )
// ve.clear_atlas_region_caches(& ctx.ve_ctx)
// ve.clear_shape_cache(& ctx.ve_ctx)
}
font_provider_set_snap_glyph_pos :: #force_inline proc( should_snap : b32 ) {
ve.set_snap_glyph_pos( & get_state().font_provider_ctx.ve_ctx, should_snap )
// ve.clear_atlas_region_caches(& ctx.ve_ctx)
// ve.clear_shape_cache(& ctx.ve_ctx)
}
Font_Use_Default_Size :: f32(0.0)
@ -146,7 +136,7 @@ font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_U
resolved_size = clamp( i32( even_size), 2, Font_Largest_Px_Size )
id := (resolved_size / Font_Size_Interval) + (resolved_size % Font_Size_Interval)
ve_id = def.size_table[ id - 1 ]
ve_id = def.ve_id
return
}