diff --git a/code/font/vefontcache/LRU.odin b/code/font/vefontcache/LRU.odin index 24e907d..e3c5ded 100644 --- a/code/font/vefontcache/LRU.odin +++ b/code/font/vefontcache/LRU.odin @@ -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 ) diff --git a/code/font/vefontcache/atlas.odin b/code/font/vefontcache/atlas.odin index 1a4f36d..36a7113 100644 --- a/code/font/vefontcache/atlas.odin +++ b/code/font/vefontcache/atlas.odin @@ -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 ) diff --git a/code/font/vefontcache/draw.odin b/code/font/vefontcache/draw.odin index 6190350..9ba98bf 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -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) diff --git a/code/font/vefontcache/mappings.odin b/code/font/vefontcache/mappings.odin index 6d306f3..3a0eee2 100644 --- a/code/font/vefontcache/mappings.odin +++ b/code/font/vefontcache/mappings.odin @@ -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 diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index ce768e3..f2bf1f7 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -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) diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index 24d606d..653b693 100644 --- a/code/font/vefontcache/shaper.odin +++ b/code/font/vefontcache/shaper.odin @@ -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 ) diff --git a/code/font/vefontcache/vefontcache.odin b/code/font/vefontcache/vefontcache.odin index 4cfcedd..914a00e 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -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 ) diff --git a/code/grime/mappings.odin b/code/grime/mappings.odin index 1c0f4ba..bb6c84f 100644 --- a/code/grime/mappings.odin +++ b/code/grime/mappings.odin @@ -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 diff --git a/code/grime/profiler.odin b/code/grime/profiler.odin index 2fcc5f7..3a3a27c 100644 --- a/code/grime/profiler.odin +++ b/code/grime/profiler.odin @@ -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 ) { diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index 64bcc4a..f114e49 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -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 diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index e094f19..708905c 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -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 ) } diff --git a/code/sectr/font/provider.odin b/code/sectr/font/provider.odin index cae3e42..3f6fa0f 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -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 }