From 26ad2d1e492bfd805a5491c665b8fecd6ebd5646 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 3 Jun 2024 01:04:24 -0400 Subject: [PATCH] VEFontCache : Finished porting the LRU --- code/font/VEFontCache/LRU.odin | 127 +++++++++++++++++++++++-- code/font/VEFontCache/VEFontCache.odin | 62 ++++++++++-- code/font/VEFontCache/mappings.odin | 14 ++- code/font/VEFontCache/shaper.odin | 4 + code/grime/array.odin | 6 +- code/grime/hashmap_chained.odin | 4 +- code/grime/mappings.odin | 2 +- thirdparty/harfbuzz | 2 +- 8 files changed, 201 insertions(+), 20 deletions(-) diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index 293f177..7337068 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -1,10 +1,10 @@ package VEFontCache /* -The choice was made to keep the LUR cache implementation as close to the original as possible. +The choice was made to keep the LRU cache implementation as close to the original as possible. */ -PoolListIter :: u32 +PoolListIter :: i32 PoolListValue :: u64 PoolListItem :: struct { @@ -27,13 +27,15 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32 ) error : AllocatorError pool.items, error = make( Array( PoolListItem ), u64(capacity) ) assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array") + array_resize( & pool.items, u64(capacity) ) pool.free_list, error = make( Array( PoolListIter ), u64(capacity) ) assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array") + array_resize( & pool.items, u64(capacity) ) pool.capacity = i32(capacity) - for id in 0 ..< capacity do pool.free_list.data[id] = id + for id in 0 ..< capacity do pool.free_list.data[id] = i32(id) } pool_list_push_front :: proc( pool : ^PoolList, value : PoolListValue ) @@ -44,6 +46,60 @@ pool_list_push_front :: proc( pool : ^PoolList, value : PoolListValue ) assert( free_list.num == u64(capacity - size) ) id := array_back( free_list ) + array_pop( free_list ) + items.data[ id ].prev = -1 + items.data[ id ].next = front + items.data[ id ].value = value + + if front != -1 do items.data[ front ].prev = id + if back != -1 do back = id + front = id + size += 1 +} + +pool_list_erase :: proc( pool : ^PoolList, iter : PoolListIter ) +{ + using pool + if size <= 0 do return + assert( iter >= 0 && iter < capacity ) + assert( free_list.num == u64(capacity - size) ) + + iter_node := & items.data[ iter ] + prev := iter_node.prev + next := iter_node.next + + if iter_node.prev != -1 do items.data[ prev ].next = iter_node.next + if iter_node.next != -1 do items.data[ next ].prev = iter_node.prev + + if front == iter do front = iter_node.next + if back == iter do back = iter_node.prev + + iter_node.prev = -1 + iter_node.next = -1 + iter_node.value = 0 + append( & free_list, iter ) + + size -= 1 + if size == 0 { + back = -1 + front = -1 + } +} + +pool_list_peek_back :: proc ( pool : ^PoolList ) -> PoolListValue { + assert( pool.back != - 1 ) + value := pool.items.data[ pool.back ].value + return value +} + +pool_list_pop_back :: proc( pool : ^PoolList ) -> PoolListValue { + using pool + if size <= 0 do return 0 + assert( back != -1 ) + + value := items.data[ back ].value + pool_list_erase( pool, back ) + return value } LRU_Link :: struct { @@ -57,13 +113,72 @@ LRU_Cache :: struct { key_queue : PoolList, } -LRU_init :: proc( cache : ^LRU_Cache, capacity : u32 ) -{ +LRU_init :: proc( cache : ^LRU_Cache, capacity : u32 ) { error : AllocatorError cache.capacity = i32(capacity) - cache.table, error = make( HMapChained(LRU_Link), uint(capacity) ) + cache.table, error = make( HMapChained(LRU_Link), hmap_closest_prime( uint(capacity)) ) assert( error != .None, "VEFontCache.LRU_init : Failed to allocate cache's table") + pool_list_init( & cache.key_queue, capacity ) } +LRU_find :: proc( cache : ^LRU_Cache, value : u64 ) -> ^LRU_Link { + bytes := transmute( [8]byte ) value + key := fnv64a( bytes[:] ) + link := get( cache.table, key ) + return link +} +LRU_get :: proc( cache : ^LRU_Cache, key : u64 ) -> i32 { + iter := LRU_find( cache, key ) + if iter == nil { + return -1 + } + LRU_refresh( cache, key ) + return iter.value +} + +LRU_get_next_evicted :: proc( cache : ^LRU_Cache ) -> u64 +{ + if cache.key_queue.size >= cache.capacity { + evict := pool_list_peek_back( & cache.key_queue ) + return evict + } + return 0xFFFFFFFFFFFFFFFF +} + +LRU_peek :: proc( cache : ^LRU_Cache, key : u64 ) -> i32 { + iter := LRU_find( cache, key ) + if iter == nil { + return -1 + } + return iter.value +} + +LRU_put :: proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 { + iter := LRU_find( cache, key ) + if iter != nil { + LRU_refresh( cache, key ) + iter.value = value + return key + } + + evict := key + if cache.key_queue.size >= cache.capacity { + evict = pool_list_pop_back( & cache.key_queue ) + hmap_chained_remove( cache.table, evict ) + } + + pool_list_push_front( & cache.key_queue, key ) + link := LRU_find( cache, key ) + link.value = value + link.ptr = cache.key_queue.front + return evict +} + +LRU_refresh :: proc( cache : ^LRU_Cache, key : u64 ) { + link := LRU_find( cache, key ) + pool_list_erase( & cache.key_queue, link.ptr ) + pool_list_push_front( & cache.key_queue, key ) + link.ptr = cache.key_queue.front +} diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index c45853b..30c27ae 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -37,8 +37,8 @@ Vertex :: struct { // } ShapedText :: struct { - Glyphs : Array(Glyph), - Positions : Array(Vec2), + glyphs : Array(Glyph), + positions : Array(Vec2), end_cursor_pos : Vec2, } @@ -94,7 +94,6 @@ Module_Ctx :: Context InitAtlasRegionParams :: struct { width : u32, height : u32, - offset : Vec2i, } InitAtlasParams :: struct { @@ -113,7 +112,22 @@ InitAtlasParams_Default :: InitAtlasParams { height = 2 * Kilobyte, glyph_padding = 1, - + region_a = { + width = 32, + height = 32, + }, + region_b = { + width = 32, + height = 64, + }, + region_c = { + width = 64, + height = 64, + }, + region_d = { + width = 128, + height = 128, + } } InitGlyphDrawParams :: struct { @@ -162,7 +176,7 @@ init :: proc( ctx : ^Context, temp_path, error = make( Array(Vec2), u64(temp_path_reserve) ) assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - temp_codepoint_seen, error = make( HMapChained(bool), uint(temp_codepoint_seen_reserve) ) + temp_codepoint_seen, error = make( HMapChained(bool), hmap_closest_prime( uint(temp_codepoint_seen_reserve)) ) assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") draw_list.vertices, error = make( Array(Vertex), 4 * Kilobyte ) @@ -188,7 +202,6 @@ init :: proc( ctx : ^Context, size.x / width, size.y / height, } - offset = region_params.offset error : AllocatorError // state.cache, error = make( HMapChained(LRU_Link), uint(capacity.x * capacity.y) ) @@ -200,5 +213,42 @@ init :: proc( ctx : ^Context, init_atlas_region( & atlas.region_c, atlas_params, atlas_params.region_c ) init_atlas_region( & atlas.region_d, atlas_params, atlas_params.region_d ) + atlas.region_b.offset.y = atlas.region_a.size.y + atlas.region_c.offset.x = atlas.region_a.size.x + atlas.region_d.offset.x = atlas.width / 2 + LRU_init( & shape_cache.state, shape_cache_params.capacity ) + for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1 { + stroage_entry := & shape_cache.storage.data[idx] + using stroage_entry + glyphs, error = make( Array(Glyph), cast(u64) shape_cache_params.reserve_length ) + assert( error != .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" ) + + positions, error = make( Array(Vec2), cast(u64) shape_cache_params.reserve_length ) + assert( error != .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" ) + } + + // Note(From original author): We can actually go over VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH batches due to smart packing! + { + using atlas + draw_list.calls, error = make( Array(DrawCall), cast(u64) glyph_draw_params.buffer_batch * 2 ) + assert( error != .None, "VEFontCache.init : Failed to allocate calls for draw_list" ) + + draw_list.indices, error = make( Array(u32), cast(u64) glyph_draw_params.buffer_batch * 2 * 6 ) + assert( error != .None, "VEFontCache.init : Failed to allocate indices array for draw_list" ) + + draw_list.vertices, error = make( Array(Vertex), cast(u64) glyph_draw_params.buffer_batch * 2 * 4 ) + assert( error != .None, "VEFontCache.init : Failed to allocate vertices array for draw_list" ) + + clear_draw_list.calls, error = make( Array(DrawCall), cast(u64) glyph_draw_params.buffer_batch * 2 ) + assert( error != .None, "VEFontCache.init : Failed to allocate calls for calls for clear_draw_list" ) + + clear_draw_list.indices, error = make( Array(u32), cast(u64) glyph_draw_params.buffer_batch * 2 * 4 ) + assert( error != .None, "VEFontCache.init : Failed to allocate calls for indices array for clear_draw_list" ) + + clear_draw_list.vertices, error = make( Array(Vertex), cast(u64) glyph_draw_params.buffer_batch * 2 * 4 ) + assert( error != .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" ) + } + + shaper_init( & shaper_ctx ) } diff --git a/code/font/VEFontCache/mappings.odin b/code/font/VEFontCache/mappings.odin index 0de1a43..36eeeb6 100644 --- a/code/font/VEFontCache/mappings.odin +++ b/code/font/VEFontCache/mappings.odin @@ -1,5 +1,8 @@ package VEFontCache +import "core:hash" + fnv64a :: hash.fnv64a + import "core:mem" Kilobyte :: mem.Kilobyte @@ -24,13 +27,18 @@ array_back :: grime.array_back array_clear :: grime.array_clear array_free :: grime.array_free array_remove_at :: grime.array_remove_at +array_pop :: grime.array_pop +array_resize :: grime.array_resize array_to_slice :: grime.array_to_slice array_to_slice_cpacity :: grime.array_to_slice_capacity array_underlying_slice :: grime.array_underlying_slice HMapChained :: grime.HMapChained -hmap_chained_init :: grime.hmap_chained_init +hmap_chained_init :: grime.hmap_chained_init +hmap_chained_get :: grime.hmap_chained_get +hmap_chained_remove :: grime.hmap_chained_remove +hmap_closest_prime :: grime.hmap_closest_prime // Pool :: grime.Pool @@ -64,6 +72,10 @@ delete :: proc { array_free, } +get :: proc { + hmap_chained_get, +} + make :: proc { array_init, hmap_chained_init, diff --git a/code/font/VEFontCache/shaper.odin b/code/font/VEFontCache/shaper.odin index a1e3f12..f334ec0 100644 --- a/code/font/VEFontCache/shaper.odin +++ b/code/font/VEFontCache/shaper.odin @@ -12,3 +12,7 @@ ShaperInfo :: struct { font : harfbuzz.Font, } +shaper_init :: proc( ctx : ^ShaperContext ) +{ + ctx.hb_buffer = harfbuzz.buffer_create() +} diff --git a/code/grime/array.odin b/code/grime/array.odin index be936fe..28891b2 100644 --- a/code/grime/array.odin +++ b/code/grime/array.odin @@ -232,9 +232,9 @@ array_grow :: proc( using self : ^Array( $ Type ), min_capacity : u64 ) -> Alloc return array_set_capacity( self, new_capacity ) } -array_pop :: proc( using self : Array( $ Type ) ) { - verify( num != 0, "Attempted to pop an array with no elements" ) - num -= 1 +array_pop :: proc( self : Array( $ Type ) ) { + verify( self.num != 0, "Attempted to pop an array with no elements" ) + self.num -= 1 } array_remove_at :: proc( using self : Array( $ Type ), id : u64 ) diff --git a/code/grime/hashmap_chained.odin b/code/grime/hashmap_chained.odin index 94c494a..65a1281 100644 --- a/code/grime/hashmap_chained.odin +++ b/code/grime/hashmap_chained.odin @@ -139,7 +139,7 @@ hmap_chained_reload :: proc( self : HMapChained($Type), allocator : Allocator ) // Entries already found to be vacant will not return true hmap_chained_remove :: proc( self : HMapChained($Type), key : u64 ) -> b32 { - surface_slot := lookup[hmap_chained_lookup_id(self, key)] + surface_slot := self.lookup[hmap_chained_lookup_id(self, key)] if surface_slot == nil { return false @@ -150,7 +150,7 @@ hmap_chained_remove :: proc( self : HMapChained($Type), key : u64 ) -> b32 return true } - for slot := surface_slot.next; slot != nil; slot.next + for slot := surface_slot.next; slot != nil; slot = slot.next { if slot.occupied && slot.key == key { slot.occupied = false diff --git a/code/grime/mappings.odin b/code/grime/mappings.odin index f0b221b..e4bee15 100644 --- a/code/grime/mappings.odin +++ b/code/grime/mappings.odin @@ -32,7 +32,7 @@ import c "core:c/libc" import "core:dynlib" import "core:hash" - crc32 :: hash.crc32 + crc32 :: hash.crc32 import "core:hash/xxhash" xxh32 :: xxhash.XXH32 diff --git a/thirdparty/harfbuzz b/thirdparty/harfbuzz index 6781256..5112039 160000 --- a/thirdparty/harfbuzz +++ b/thirdparty/harfbuzz @@ -1 +1 @@ -Subproject commit 6781256b374ddafc42eed265e7b8377099b36919 +Subproject commit 5112039d627bf5f6a683b7aca5bb360e53570d97