Progress on VEFontCache port, only cache_glyph_to_atlas & shape_text_uncached left
This commit is contained in:
		| @@ -18,8 +18,8 @@ PoolList :: struct { | ||||
| 	free_list : Array( PoolListIter ), | ||||
| 	front     : PoolListIter, | ||||
| 	back      : PoolListIter, | ||||
| 	size      : i32, | ||||
| 	capacity  : i32, | ||||
| 	size      : u32, | ||||
| 	capacity  : u32, | ||||
| } | ||||
|  | ||||
| pool_list_init :: proc( pool : ^PoolList, capacity : u32 ) | ||||
| @@ -33,7 +33,7 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32 ) | ||||
| 	assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array") | ||||
| 	array_resize( & pool.items, u64(capacity) ) | ||||
|  | ||||
| 	pool.capacity = i32(capacity) | ||||
| 	pool.capacity = capacity | ||||
|  | ||||
| 	for id in 0 ..< capacity do pool.free_list.data[id] = i32(id) | ||||
| } | ||||
| @@ -61,7 +61,7 @@ pool_list_erase :: proc( pool : ^PoolList, iter : PoolListIter ) | ||||
| { | ||||
| 	using pool | ||||
| 	if size <= 0 do return | ||||
| 	assert( iter >= 0 && iter < capacity ) | ||||
| 	assert( iter >= 0 && iter < i32(capacity) ) | ||||
| 	assert( free_list.num == u64(capacity - size) ) | ||||
|  | ||||
| 	iter_node := & items.data[ iter ] | ||||
| @@ -108,14 +108,15 @@ LRU_Link :: struct { | ||||
| } | ||||
|  | ||||
| LRU_Cache :: struct { | ||||
| 	capacity  : i32, | ||||
| 	capacity  : u32, | ||||
| 	num       : u32, | ||||
| 	table     : HMapChained(LRU_Link), | ||||
| 	key_queue : PoolList, | ||||
| } | ||||
|  | ||||
| LRU_init :: proc( cache : ^LRU_Cache, capacity : u32 ) { | ||||
| 	error : AllocatorError | ||||
| 	cache.capacity     = i32(capacity) | ||||
| 	cache.capacity     = 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") | ||||
|  | ||||
| @@ -167,12 +168,15 @@ LRU_put :: proc( cache : ^LRU_Cache, key : u64,  value : i32 ) -> u64 { | ||||
| 	if cache.key_queue.size >= cache.capacity { | ||||
| 		evict = pool_list_pop_back( & cache.key_queue ) | ||||
| 		hmap_chained_remove( cache.table, evict ) | ||||
| 		cache.num -= 1 | ||||
| 	} | ||||
|  | ||||
| 	pool_list_push_front( & cache.key_queue, key ) | ||||
| 	link := LRU_find( cache, key ) | ||||
| 	link.value = value | ||||
| 	link.ptr    = cache.key_queue.front | ||||
| 	hmap_chained_set( cache.table, key, LRU_Link { | ||||
| 		value = value, | ||||
| 		ptr   = cache.key_queue.front | ||||
| 	}) | ||||
| 	cache.num += 1 | ||||
| 	return evict | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ This port is heavily tied to the grime package in SectrPrototype. | ||||
| TODO(Ed): Make an idiomatic port of this for Odin (or just dupe the data structures...) | ||||
|  | ||||
| Changes: | ||||
| - Support for freetype(WIP), only supports processing true type formatted data however | ||||
| - Support for freetype(WIP) | ||||
| - Font Parser & Glyph Shaper are abstracted to their own interface | ||||
| - Font Face parser info stored separately from entries | ||||
| - ve_fontcache_loadfile not ported (just use odin's core:os or os2), then call load_font | ||||
| @@ -77,8 +77,9 @@ Context :: struct { | ||||
|  | ||||
| 	entries : Array(Entry), | ||||
|  | ||||
| 	temp_path           : Array(Vec2), | ||||
| 	temp_codepoint_seen : HMapChained(bool), | ||||
| 	temp_path               : Array(Vec2), | ||||
| 	temp_codepoint_seen     : HMapChained(bool), | ||||
| 	temp_codepoint_seen_num : u32, | ||||
|  | ||||
| 	snap_width  : u32, | ||||
| 	snap_height : u32, | ||||
| @@ -96,10 +97,19 @@ Context :: struct { | ||||
| 	debug_print_verbose : b32 | ||||
| } | ||||
|  | ||||
| font_key_from_label :: proc( label : string ) -> u64 { | ||||
| get_cursor_pos :: proc( ctx : ^Context                  ) -> Vec2 { return ctx.cursor_pos } | ||||
| set_colour     :: proc( ctx : ^Context, colour : Colour )         { ctx.colour = colour } | ||||
|  | ||||
| font_glyph_lru_code :: #force_inline proc( font : FontID, glyph_index : Glyph ) -> (lru_code : u64) | ||||
| { | ||||
| 	lru_code = u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| font_key_from_label :: #force_inline proc( label : string ) -> u64 { | ||||
| 	hash : u64 | ||||
| 	for str_byte in transmute([]byte) label { | ||||
| 		hash = ((hash << 5) + hash) + u64(str_byte) | ||||
| 		hash = ((hash << 8) + hash) + u64(str_byte) | ||||
| 	} | ||||
| 	return hash | ||||
| } | ||||
| @@ -175,7 +185,7 @@ init :: proc( ctx : ^Context, | ||||
| 	advance_snap_smallfont_size : u32 = 12, | ||||
| 	entires_reserve             : u32 = Kilobyte, | ||||
| 	temp_path_reserve           : u32 = Kilobyte, | ||||
| 	temp_codepoint_seen_reserve : u32 = 4 * Kilobyte, | ||||
| 	temp_codepoint_seen_reserve : u32 = 512, | ||||
| ) | ||||
| { | ||||
| 	assert( ctx != nil, "Must provide a valid context" ) | ||||
| @@ -438,7 +448,7 @@ cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, | ||||
| 	} | ||||
|  | ||||
| 	// Note(Original Author): Figure out scaling so it fits within our box. | ||||
| 	draw : DrawCall | ||||
| 	draw := DrawCall_Default | ||||
| 	draw.pass        = FrameBufferPass.Glyph | ||||
| 	draw.start_index = u32(ctx.draw_list.indices.num) | ||||
|  | ||||
| @@ -503,162 +513,96 @@ cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| decide_codepoint_region :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph | ||||
| ) -> (region : AtlasRegionKind, state : ^LRU_Cache, next_idx : ^u32, over_sample : ^Vec2) | ||||
| { | ||||
| 	if parser_is_glyph_empty( entry.parser_info, glyph_index ) { | ||||
| 		region = .None | ||||
| 	} | ||||
| screenspace_x_form :: proc( position, scale : ^Vec2, width, height : f32 ) { | ||||
| 	scale.x    = (scale.x / width ) * 2.0 | ||||
| 	scale.y    = (scale.y / height) * 2.0 | ||||
| 	position.x = position.x * (2.0 / width) - 1.0 | ||||
| 	position.y = position.y * (2.0 / width) - 1.0 | ||||
| } | ||||
|  | ||||
| textspace_x_form :: proc( position, scale : ^Vec2, width, height : f32 ) { | ||||
| 	position.x /= width | ||||
| 	position.y /= height | ||||
| 	scale.x    /= width | ||||
| 	scale.y    /= height | ||||
| } | ||||
|  | ||||
| cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph ) | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && font < FontID(ctx.entries.num) ) | ||||
| 	entry := & ctx.entries.data[ font ] | ||||
|  | ||||
| 	if glyph_index == 0 do return | ||||
| 	if parser_is_glyph_empty( entry.parser_info, glyph_index ) do return | ||||
|  | ||||
| 	// Get hb_font text metrics. These are unscaled! | ||||
| 	bounds_0, bounds_1 := parser_get_glyph_box( entry.parser_info, glyph_index ) | ||||
| 	bounds_width  := bounds_1.x - bounds_0.x | ||||
| 	bounds_height := bounds_1.y - bounds_0.y | ||||
|  | ||||
| 	atlas := & ctx.atlas | ||||
| 	region, state, next_idx, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
|  | ||||
| 	bounds_width_scaled  := cast(u32) (f32(bounds_width)  * entry.size_scale + 2.0 * f32(atlas.glyph_padding)) | ||||
| 	bounds_height_scaled := cast(u32) (f32(bounds_height) * entry.size_scale + 2.0 * f32(atlas.glyph_padding)) | ||||
| 	// E region is special case and not cached to atlas. | ||||
| 	if region == .None || region == .E do return | ||||
|  | ||||
| 	if bounds_width_scaled <= atlas.region_a.width && bounds_height_scaled <= atlas.region_a.height | ||||
|  | ||||
| } | ||||
|  | ||||
| is_empty :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 | ||||
| { | ||||
| 	if glyph_index == 0 do return true | ||||
| 	if parser_is_glyph_empty( entry.parser_info, glyph_index ) do return true | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| reset_batch_codepoint_state :: proc( ctx : ^Context ) { | ||||
| 	clear( ctx.temp_codepoint_seen ) | ||||
| 	ctx.temp_codepoint_seen_num = 0 | ||||
| } | ||||
|  | ||||
| shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> ^ShapedText | ||||
| { | ||||
| 	ELFhash64 :: proc( hash : ^u64, ptr : ^( $Type), count := 1 ) | ||||
| 	{ | ||||
| 		// Region A for small glyphs. These are good for things such as punctuation. | ||||
| 		region   = .A | ||||
| 		state    = & atlas.region_a.state | ||||
| 		next_idx = & atlas.region_a.next_idx | ||||
| 		x    := u64(0) | ||||
| 		bytes = transmute( [^]byte) ptr | ||||
| 		for index : i32 = 0; index < i32( size_of(Type)); index += 1 { | ||||
| 			(hash^) = ((hash^) << 4 ) + bytes[index] | ||||
| 			x       = (hash^) & 0xF000000000000000 | ||||
| 			if x != 0 { | ||||
| 				(hash^) ~= (x >> 24) | ||||
| 			} | ||||
| 			(hash^) &= ~x | ||||
| 		} | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_b.width && bounds_height_scaled <= atlas.region_b.height | ||||
|   hash        := cast(u64) 0x9f8e00d51d263c24; | ||||
| 	shape_cache := & ctx.shape_cache | ||||
| 	state       := & ctx.shape_cache.state | ||||
|  | ||||
| 	shape_cache_idx := LRU_get( state, hash ) | ||||
| 	if shape_cache_idx == -1 | ||||
| 	{ | ||||
| 		// Region B for tall glyphs. These are good for things such as european alphabets. | ||||
| 		region   = .B | ||||
| 		state    = & atlas.region_b.state | ||||
| 		next_idx = & atlas.region_b.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_c.width && bounds_height_scaled <= atlas.region_c.height | ||||
| 	{ | ||||
| 		// Region C for big glyphs. These are good for things such as asian typography. | ||||
| 		region   = .C | ||||
| 		state    = & atlas.region_c.state | ||||
| 		next_idx = & atlas.region_c.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_d.width && bounds_height_scaled <= atlas.region_d.height | ||||
| 	{ | ||||
| 		// Region D for huge glyphs. These are good for things such as titles and 4k. | ||||
| 		region   = .D | ||||
| 		state    = & atlas.region_d.state | ||||
| 		next_idx = & atlas.region_d.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.buffer_width && bounds_height_scaled <= atlas.buffer_height | ||||
| 	{ | ||||
| 		// Region 'E' for massive glyphs. These are rendered uncached and un-oversampled. | ||||
| 		region   = .E | ||||
| 		state    = nil | ||||
| 		next_idx = nil | ||||
| 		if bounds_width_scaled <= atlas.buffer_width / 2 && bounds_height_scaled <= atlas.buffer_height / 2 | ||||
| 		{ | ||||
| 			(over_sample^) = { 2.0, 2.0 } | ||||
| 		if shape_cache.next_cache_id < i32(state.capacity) { | ||||
| 			shape_cache_idx = shape_cache.next_cache_id | ||||
| 			LRU_put( state, hash, shape_cache_idx ) | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			(over_sample^) = { 1.0, 1.0 } | ||||
| 			next_evict_idx := LRU_get_next_evicted( state ) | ||||
| 			assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF ) | ||||
|  | ||||
| 			shape_cache_idx = LRU_peek( state, next_evict_idx ) | ||||
| 			assert( shape_cache_idx != - 1 ) | ||||
|  | ||||
| 			LRU_put( state, hash, shape_cache_idx ) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	else { | ||||
| 		region = .None | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	assert(state    != nil) | ||||
| 	assert(next_idx != nil) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| flush_glyph_buffer_to_atlas :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| screenspace_x_form :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| textspace_x_form :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| atlas_bbox :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| cache_glyph_to_atlas :: proc() | ||||
| { | ||||
|  | ||||
| 	return & shape_cache.storage.data[ shape_cache_idx ] | ||||
| } | ||||
|  | ||||
| shape_text_uncached :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| ELFhash64 :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| shape_text_cached :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| directly_draw_massive_glyph :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| empty :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| draw_cached_glyph :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| reset_batch_codepoint_state :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| can_batch_glyph :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| draw_text_batch :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| draw_text :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| get_cursor_pos :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| optimize_draw_list :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| set_colour :: proc() | ||||
| { | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,17 +1,5 @@ | ||||
| package VEFontCache | ||||
|  | ||||
| GlyphDrawBuffer :: struct { | ||||
| 	over_sample   : Vec2, | ||||
| 	buffer_batch  : u32, | ||||
| 	buffer_width  : u32, | ||||
| 	buffer_height : u32, | ||||
| 	draw_padding  : u32, | ||||
|  | ||||
| 	update_batch_x  : i32, | ||||
| 	clear_draw_list : DrawList, | ||||
| 	draw_list       : DrawList, | ||||
| } | ||||
|  | ||||
| AtlasRegion :: struct { | ||||
| 	state : LRU_Cache, | ||||
|  | ||||
| @@ -38,3 +26,162 @@ Atlas :: struct { | ||||
|  | ||||
| 	using glyph_update_batch : GlyphDrawBuffer, | ||||
| } | ||||
|  | ||||
| atlas_bbox :: proc( atlas : ^Atlas, region : AtlasRegionKind, local_idx : u32 ) -> (position : Vec2, width, height : f32) | ||||
| { | ||||
| 	switch region | ||||
| 	{ | ||||
| 		case .A: | ||||
| 			width  = f32(atlas.region_a.width) | ||||
| 			height = f32(atlas.region_b.height) | ||||
|  | ||||
| 			position.x = cast(f32) (( local_idx % atlas.region_a.capacity.x ) * atlas.region_a.width) | ||||
| 			position.y = cast(f32) (( local_idx % atlas.region_a.capacity.x ) * atlas.region_a.height) | ||||
|  | ||||
| 			position.x += f32(atlas.region_a.offset.x) | ||||
| 			position.y += f32(atlas.region_a.offset.y) | ||||
|  | ||||
| 		case .B: | ||||
| 			width  = f32(atlas.region_b.width) | ||||
| 			height = f32(atlas.region_b.height) | ||||
|  | ||||
| 			position.x = cast(f32) (( local_idx % atlas.region_b.capacity.x ) * atlas.region_b.width) | ||||
| 			position.y = cast(f32) (( local_idx % atlas.region_b.capacity.x ) * atlas.region_b.height) | ||||
|  | ||||
| 			position.x += f32(atlas.region_b.offset.x) | ||||
| 			position.y += f32(atlas.region_b.offset.y) | ||||
|  | ||||
| 		case .C: | ||||
| 			width  = f32(atlas.region_c.width) | ||||
| 			height = f32(atlas.region_c.height) | ||||
|  | ||||
| 			position.x = cast(f32) (( local_idx % atlas.region_c.capacity.x ) * atlas.region_c.width) | ||||
| 			position.y = cast(f32) (( local_idx % atlas.region_c.capacity.x ) * atlas.region_c.height) | ||||
|  | ||||
| 			position.x += f32(atlas.region_c.offset.x) | ||||
| 			position.y += f32(atlas.region_c.offset.y) | ||||
|  | ||||
| 		case .D: | ||||
| 			width  = f32(atlas.region_d.width) | ||||
| 			height = f32(atlas.region_d.height) | ||||
|  | ||||
| 			position.x = cast(f32) (( local_idx % atlas.region_d.capacity.x ) * atlas.region_d.width) | ||||
| 			position.y = cast(f32) (( local_idx % atlas.region_d.capacity.x ) * atlas.region_d.height) | ||||
|  | ||||
| 			position.x += f32(atlas.region_d.offset.x) | ||||
| 			position.y += f32(atlas.region_d.offset.y) | ||||
|  | ||||
| 		case .None: fallthrough | ||||
| 		case .E: | ||||
| 			assert(false, "What?") | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| can_batch_glyph :: proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph ) -> b32 | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( entry.id == font ) | ||||
|  | ||||
| 	// Decide which atlas to target | ||||
| 	assert( glyph_index != -1 ) | ||||
| 	region, state, next_index, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
|  | ||||
| 	// E region can't batch | ||||
| 	if region == .E || region == .None    do return false | ||||
| 	if ctx.temp_codepoint_seen_num > 1024 do return false | ||||
| 	// Note(Ed): Why 1024? | ||||
|  | ||||
| 	// Is this glyph cached? | ||||
| 	// lru_code    := u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) | ||||
| 	lru_code    := font_glyph_lru_code(font, glyph_index) | ||||
| 	atlas_index := LRU_get( state, lru_code ) | ||||
| 	if atlas_index == - 1 | ||||
| 	{ | ||||
| 		if (next_index^) >= u32(state.capacity) { | ||||
| 			// We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch. | ||||
| 			next_evict_codepoint := LRU_get_next_evicted( state ) | ||||
| 			seen := get( ctx.temp_codepoint_seen, next_evict_codepoint ) | ||||
| 			assert(seen != nil) | ||||
|  | ||||
| 			if (seen^) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		cache_glyph_to_atlas( ctx, font, glyph_index ) | ||||
| 	} | ||||
|  | ||||
| 	assert( LRU_get( state, lru_code ) != 1 ) | ||||
| 	set( ctx.temp_codepoint_seen, lru_code, true ) | ||||
| 	ctx.temp_codepoint_seen_num += 1 | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| decide_codepoint_region :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph | ||||
| ) -> (region : AtlasRegionKind, state : ^LRU_Cache, next_idx : ^u32, over_sample : Vec2) | ||||
| { | ||||
| 	if parser_is_glyph_empty( entry.parser_info, glyph_index ) { | ||||
| 		region = .None | ||||
| 	} | ||||
|  | ||||
| 	bounds_0, bounds_1 := parser_get_glyph_box( entry.parser_info, glyph_index ) | ||||
| 	bounds_width  := bounds_1.x - bounds_0.x | ||||
| 	bounds_height := bounds_1.y - bounds_0.y | ||||
|  | ||||
| 	atlas := & ctx.atlas | ||||
|  | ||||
| 	bounds_width_scaled  := cast(u32) (f32(bounds_width)  * entry.size_scale + 2.0 * f32(atlas.glyph_padding)) | ||||
| 	bounds_height_scaled := cast(u32) (f32(bounds_height) * entry.size_scale + 2.0 * f32(atlas.glyph_padding)) | ||||
|  | ||||
| 	if bounds_width_scaled <= atlas.region_a.width && bounds_height_scaled <= atlas.region_a.height | ||||
| 	{ | ||||
| 		// Region A for small glyphs. These are good for things such as punctuation. | ||||
| 		region   = .A | ||||
| 		state    = & atlas.region_a.state | ||||
| 		next_idx = & atlas.region_a.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_b.width && bounds_height_scaled <= atlas.region_b.height | ||||
| 	{ | ||||
| 		// Region B for tall glyphs. These are good for things such as european alphabets. | ||||
| 		region   = .B | ||||
| 		state    = & atlas.region_b.state | ||||
| 		next_idx = & atlas.region_b.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_c.width && bounds_height_scaled <= atlas.region_c.height | ||||
| 	{ | ||||
| 		// Region C for big glyphs. These are good for things such as asian typography. | ||||
| 		region   = .C | ||||
| 		state    = & atlas.region_c.state | ||||
| 		next_idx = & atlas.region_c.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_d.width && bounds_height_scaled <= atlas.region_d.height | ||||
| 	{ | ||||
| 		// Region D for huge glyphs. These are good for things such as titles and 4k. | ||||
| 		region   = .D | ||||
| 		state    = & atlas.region_d.state | ||||
| 		next_idx = & atlas.region_d.next_idx | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.buffer_width && bounds_height_scaled <= atlas.buffer_height | ||||
| 	{ | ||||
| 		// Region 'E' for massive glyphs. These are rendered uncached and un-oversampled. | ||||
| 		region   = .E | ||||
| 		state    = nil | ||||
| 		next_idx = nil | ||||
| 		if bounds_width_scaled <= atlas.buffer_width / 2 && bounds_height_scaled <= atlas.buffer_height / 2 { | ||||
| 			over_sample = { 2.0, 2.0 } | ||||
| 		} | ||||
| 		else { | ||||
| 			over_sample = { 1.0, 1.0 } | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	else { | ||||
| 		region = .None | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	assert(state    != nil) | ||||
| 	assert(next_idx != nil) | ||||
| 	return | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,5 @@ | ||||
| package VEFontCache | ||||
|  | ||||
| FrameBufferPass :: enum u32 { | ||||
| 	None             = 0, | ||||
| 	Glyph            = 1, | ||||
| 	Atlas            = 2, | ||||
| 	Target           = 3, | ||||
| 	Target_Unchanged = 4, | ||||
| } | ||||
|  | ||||
| DrawCall :: struct { | ||||
| 	pass              : FrameBufferPass, | ||||
| 	start_index       : u32, | ||||
| @@ -32,6 +24,26 @@ DrawList :: struct { | ||||
| 	calls    : Array(DrawCall), | ||||
| } | ||||
|  | ||||
| FrameBufferPass :: enum u32 { | ||||
| 	None             = 0, | ||||
| 	Glyph            = 1, | ||||
| 	Atlas            = 2, | ||||
| 	Target           = 3, | ||||
| 	Target_Unchanged = 4, | ||||
| } | ||||
|  | ||||
| GlyphDrawBuffer :: struct { | ||||
| 	over_sample   : Vec2, | ||||
| 	buffer_batch  : u32, | ||||
| 	buffer_width  : u32, | ||||
| 	buffer_height : u32, | ||||
| 	draw_padding  : u32, | ||||
|  | ||||
| 	update_batch_x  : i32, | ||||
| 	clear_draw_list : DrawList, | ||||
| 	draw_list       : DrawList, | ||||
| } | ||||
|  | ||||
| blit_quad :: proc( draw_list : ^DrawList, p0, p1 : Vec2, uv0, uv1 : Vec2 ) | ||||
| { | ||||
| 	v_offset := cast(u32) draw_list.vertices.num | ||||
| @@ -77,6 +89,87 @@ clear_draw_list :: proc( draw_list : ^DrawList ) { | ||||
| 	clear( draw_list.vertices ) | ||||
| } | ||||
|  | ||||
| directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : u32, over_sample, position, scale : Vec2 ) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, position, scale : Vec2 ) -> b32 | ||||
| { | ||||
| 	// Glyph not in current font | ||||
| 	if glyph_index == 0                                        do return true | ||||
| 	if parser_is_glyph_empty( entry.parser_info, glyph_index ) do return true | ||||
|  | ||||
| 	bounds_0, bounds_1 := parser_get_glyph_box( entry.parser_info, glyph_index ) | ||||
|  | ||||
| 	bounds_width  := bounds_1.x - bounds_0.x | ||||
| 	bounds_height := bounds_1.y - bounds_0.y | ||||
|  | ||||
| 	// Decide which atlas to target | ||||
| 	region, state, next_idx, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
|  | ||||
| 	// E region is special case and not cached to atlas | ||||
| 	if region == .E | ||||
| 	{ | ||||
| 		directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, bounds_width, bounds_height, over_sample, position, scale ) | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Is this codepoint cached? | ||||
| 	// lru_code    := u64(glyph_index) + ( ( 0x100000000 * u64(entry.id) ) & 0xFFFFFFFF00000000 ) | ||||
| 	lru_code    := font_glyph_lru_code(entry.id, glyph_index) | ||||
| 	atlas_index := LRU_get( state, lru_code ) | ||||
| 	if atlas_index == - 1 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	atlas := & ctx.atlas | ||||
|  | ||||
| 	// Figure out the source bounding box in the atlas texture | ||||
| 	position, width, height := atlas_bbox( atlas, region, u32(atlas_index) ) | ||||
|  | ||||
| 	glyph_position := position | ||||
| 	glyph_width    := f32(bounds_width)  * entry.size_scale | ||||
| 	glyph_height   := f32(bounds_height) * entry.size_scale | ||||
|  | ||||
| 	glyph_width  += 2 * f32(atlas.glyph_padding) | ||||
| 	glyph_height += 2 * f32(atlas.glyph_padding) | ||||
| 	glyph_scale  := Vec2 { glyph_width, glyph_height } | ||||
|  | ||||
| 	bounds_0_scaled := Vec2{ f32(bounds_0.x), f32(bounds_0.y) } * entry.size_scale - { 0.5, 0.5 } | ||||
| 	dst := Vec2 { | ||||
| 		position.x + scale.x * bounds_0_scaled.x, | ||||
| 		position.y + scale.y * bounds_0_scaled.y, | ||||
| 	} | ||||
| 	dst_width  := scale.x * glyph_width | ||||
| 	dst_height := scale.y * glyph_height | ||||
| 	dst        -= scale * { f32(atlas.glyph_padding), f32(atlas.glyph_padding) } | ||||
| 	dst_scale  := Vec2 { dst_width, dst_height } | ||||
| 	textspace_x_form( & glyph_position, & dst_scale, f32(atlas.buffer_width), f32(atlas.buffer_height) ) | ||||
|  | ||||
| 	// Add the glyph drawcall | ||||
| 	call := DrawCall_Default | ||||
| 	{ | ||||
| 		using call | ||||
| 		pass        = .Target_Unchanged | ||||
| 		colour      = ctx.colour | ||||
| 		start_index = cast(u32) ctx.draw_list.indices.num | ||||
|  | ||||
| 		blit_quad( & ctx.draw_list, dst, dst + dst_scale, glyph_position, glyph_position + glyph_scale ) | ||||
| 		end_index   = cast(u32) ctx.draw_list.indices.num | ||||
| 	} | ||||
| 	append( & ctx.draw_list.calls, call ) | ||||
|  | ||||
| 	// Clear glyph_update_FBO | ||||
| 	call.pass = .Glyph | ||||
| 	call.start_index       = 0 | ||||
| 	call.end_index         = 0 | ||||
| 	call.clear_before_draw = true | ||||
| 	append( & ctx.draw_list.calls, call ) | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Constructs a triangle fan to fill a shape using the provided path | ||||
| // outside_point represents the center point of the fan. | ||||
| // | ||||
| @@ -125,12 +218,92 @@ draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : [] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32 | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font > 0 && font < FontID(ctx.entries.num) ) | ||||
|  | ||||
| 	shaped := shape_text_cached( ctx, font, text_utf8 ) | ||||
|  | ||||
| 	snap_width  := f32(ctx.snap_width) | ||||
| 	snap_height := f32(ctx.snap_height) | ||||
|  | ||||
| 	position := position | ||||
| 	if ctx.snap_width  > 0 do position.x = (position.x * snap_width  + 0.5) / snap_width | ||||
| 	if ctx.snap_height > 0 do position.y = (position.y * snap_height + 0.5) / snap_height | ||||
|  | ||||
| 	entry := & ctx.entries.data[ font ] | ||||
|  | ||||
| 	batch_start_idx : i32 = 0 | ||||
| 	for index : i32 = 0; index < i32(shaped.glyphs.num); index += 1 | ||||
| 	{ | ||||
| 		glyph_index := shaped.glyphs.data[ index ] | ||||
| 		if is_empty( ctx, entry, glyph_index )              do continue | ||||
| 		if can_batch_glyph( ctx, font, entry, glyph_index ) do continue | ||||
|  | ||||
| 		draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale ) | ||||
| 		reset_batch_codepoint_state( ctx ) | ||||
|  | ||||
| 		cache_glyph_to_atlas( ctx, font, glyph_index ) | ||||
|  | ||||
| 		// lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) | ||||
| 		lru_code := font_glyph_lru_code(font, glyph_index) | ||||
| 		set( ctx.temp_codepoint_seen, lru_code, true ) | ||||
| 		ctx.temp_codepoint_seen_num += 1 | ||||
|  | ||||
| 		batch_start_idx = index | ||||
| 	} | ||||
|  | ||||
| 	draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(shaped.glyphs.num), position, scale ) | ||||
| 	reset_batch_codepoint_state( ctx ) | ||||
| 	ctx.cursor_pos.x = position.x + shaped.end_cursor_pos.x * scale.x | ||||
| 	ctx.cursor_pos.y = position.y + shaped.end_cursor_pos.y * scale.y | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2 ) | ||||
| { | ||||
| 	flush_glyph_buffer_to_atlas( ctx ) | ||||
| 	for index := batch_start_idx; index < batch_end_idx; index += 1 | ||||
| 	{ | ||||
| 		glyph_index       := shaped.glyphs.data[ index ] | ||||
| 		shaped_position   := shaped.positions.data[index] | ||||
| 		glyph_translate_x := position.x + shaped_position.x * scale.x | ||||
| 		glyph_translate_y := position.y + shaped_position.y * scale.y | ||||
| 		glyph_cached      := draw_cached_glyph( ctx, entry, glyph_index, {glyph_translate_x, glyph_translate_y}, scale) | ||||
| 		assert( glyph_cached == true ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ve_fontcache_flush_drawlist | ||||
| flush_draw_list :: proc( ctx : ^Context ) { | ||||
| 	assert( ctx != nil ) | ||||
| 	clear_draw_list( & ctx.draw_list ) | ||||
| } | ||||
|  | ||||
| flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) | ||||
| { | ||||
| 	// Flush drawcalls to draw list | ||||
| 	merge_draw_list( & ctx.draw_list, & ctx.atlas.clear_draw_list ) | ||||
| 	merge_draw_list( & ctx.draw_list, & ctx.atlas.draw_list) | ||||
| 	clear_draw_list( & ctx.atlas.draw_list ) | ||||
| 	clear_draw_list( & ctx.atlas.clear_draw_list ) | ||||
|  | ||||
| 	// Clear glyph_update_FBO | ||||
| 	if ctx.atlas.update_batch_x != 0 | ||||
| 	{ | ||||
| 		call := DrawCall_Default | ||||
| 		call.pass = .Glyph | ||||
| 		call.start_index = 0 | ||||
| 		call.end_index   = 0 | ||||
| 		call.clear_before_draw = true | ||||
|  | ||||
| 		append( & ctx.draw_list.calls, call ) | ||||
| 		ctx.atlas.update_batch_x = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ve_fontcache_drawlist | ||||
| get_draw_list :: proc( ctx : ^Context ) -> ^DrawList { | ||||
| 	assert( ctx != nil ) | ||||
| @@ -164,3 +337,35 @@ merge_draw_list :: proc( dst, src : ^DrawList ) | ||||
| 		assert( error == .None ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| optimize_draw_list :: proc( ctx : ^Context ) | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
|  | ||||
| 	write_index : i32 = 0 | ||||
| 	for index : i32 = 0; index < i32(ctx.draw_list.calls.num); index += 1 | ||||
| 	{ | ||||
| 		assert( write_index <= index ) | ||||
| 		draw_0 := & ctx.draw_list.calls.data[ write_index ] | ||||
| 		draw_1 := & ctx.draw_list.calls.data[ index ] | ||||
|  | ||||
| 		merge : b32 = true | ||||
| 		if draw_0.pass      != draw_1.pass        do merge = false | ||||
| 		if draw_0.end_index != draw_1.start_index do merge = false | ||||
| 		if draw_0.region    != draw_1.region      do merge = false | ||||
| 		if draw_1.clear_before_draw               do merge = false | ||||
| 		if draw_0.colour    != draw_1.colour      do merge = false | ||||
|  | ||||
| 		if merge { | ||||
| 			draw_0.end_index   = draw_1.end_index | ||||
| 			draw_1.start_index = draw_1.end_index | ||||
| 		} | ||||
| 		else { | ||||
| 			write_index += 1 | ||||
| 			draw_2    := & ctx.draw_list.calls.data[ write_index ] | ||||
| 			if write_index != index do draw_2 = draw_1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	resize( & ctx.draw_list.calls, u64(write_index + 1) ) | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,7 @@ array_underlying_slice :: grime.array_underlying_slice | ||||
|  | ||||
| HMapChained :: grime.HMapChained | ||||
|  | ||||
| hmap_chained_clear  :: grime.hmap_chained_clear | ||||
| hmap_chained_init   :: grime.hmap_chained_init | ||||
| hmap_chained_get    :: grime.hmap_chained_get | ||||
| hmap_chained_remove :: grime.hmap_chained_remove | ||||
| @@ -76,6 +77,7 @@ append_at :: proc { | ||||
|  | ||||
| clear :: proc { | ||||
| 	array_clear, | ||||
| 	hmap_chained_clear, | ||||
| } | ||||
|  | ||||
| delete :: proc { | ||||
| @@ -95,6 +97,10 @@ remove_at :: proc { | ||||
| 	array_remove_at, | ||||
| } | ||||
|  | ||||
| resize :: proc { | ||||
| 	array_resize, | ||||
| } | ||||
|  | ||||
| set :: proc { | ||||
| 	hmap_chained_set, | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ Notes: | ||||
|  | ||||
| Freetype will do memory allocations and has an interface the user can implement. | ||||
| That interface is not exposed from this parser but could be added to parser_init. | ||||
|  | ||||
| STB_Truetype has macros for its allocation unfortuantely | ||||
| */ | ||||
|  | ||||
| import "core:c" | ||||
| @@ -383,7 +385,12 @@ parser_get_glyph_box :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> ( | ||||
| 	switch font.kind | ||||
| 	{ | ||||
| 		case .Freetype: | ||||
| 			freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) | ||||
|  | ||||
| 			metrics := font.freetype_info.glyph.metrics | ||||
|  | ||||
| 			bounds_0 = {u32(metrics.hori_bearing_x), u32(metrics.hori_bearing_y - metrics.height)} | ||||
| 			bounds_1 = {u32(metrics.hori_bearing_x + metrics.width), u32(metrics.hori_bearing_y)} | ||||
|  | ||||
| 		case .STB_TrueType: | ||||
| 			x0, y0, x1, y1 : i32 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user