VEFontCache : Finished porting the LRU
This commit is contained in:
		| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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 ) | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -12,3 +12,7 @@ ShaperInfo :: struct { | ||||
| 	font : harfbuzz.Font, | ||||
| } | ||||
|  | ||||
| shaper_init :: proc( ctx : ^ShaperContext ) | ||||
| { | ||||
| 	ctx.hb_buffer = harfbuzz.buffer_create() | ||||
| } | ||||
|   | ||||
| @@ -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 ) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user