diff --git a/code/font/vefontcache/LRU.odin b/code/font/vefontcache/LRU.odin index 49897a1..8018bc5 100644 --- a/code/font/vefontcache/LRU.odin +++ b/code/font/vefontcache/LRU.odin @@ -7,7 +7,7 @@ The choice was made to keep the LRU cache implementation as close to the origina import "base:runtime" Pool_ListIter :: i32 -Pool_ListValue :: u64 +Pool_ListValue :: u32 Pool_List_Item :: struct { prev : Pool_ListIter, @@ -183,14 +183,14 @@ LRU_Link :: struct { LRU_Cache :: struct { capacity : i32, num : i32, - table : map[u64]LRU_Link, + table : map[u32]LRU_Link, key_queue : Pool_List, } lru_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) { error : Allocator_Error cache.capacity = capacity - cache.table, error = make( map[u64]LRU_Link, uint(capacity) ) + cache.table, error = make( map[u32]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 ) @@ -212,12 +212,12 @@ lru_clear :: proc ( cache : ^LRU_Cache ) { cache.num = 0 } -lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) { +lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, must_find := false ) -> (LRU_Link, bool) { link, success := cache.table[key] return link, success } -lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u64 ) -> i32 #no_bounds_check { +lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u32 ) -> i32 #no_bounds_check { if link, ok := &cache.table[ key ]; ok { pool_list_move_to_front(&cache.key_queue, link.ptr) return link.value @@ -225,15 +225,15 @@ lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u64 ) -> i32 #no_bounds return -1 } -lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache ) -> u64 { +lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache ) -> u32 { if cache.key_queue.size >= cache.capacity { evict := pool_list_peek_back( cache.key_queue ) return evict } - return 0xFFFFFFFFFFFFFFFF + return 0xFFFFFFFF } -lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u64, must_find := false ) -> i32 { +lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, must_find := false ) -> i32 { iter, success := lru_find( cache, key, must_find ) if success == false { return -1 @@ -241,7 +241,7 @@ lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u64, mus return iter.value } -lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 +lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u32, value : i32 ) -> u32 { // profile(#procedure) if link, ok := & cache.table[ key ]; ok { @@ -266,7 +266,7 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u return evict } -lru_refresh :: proc( cache : ^LRU_Cache, key : u64 ) { +lru_refresh :: proc( cache : ^LRU_Cache, key : u32 ) { 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 22aeddc..77f1ea1 100644 --- a/code/font/vefontcache/atlas.odin +++ b/code/font/vefontcache/atlas.odin @@ -68,7 +68,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 : u64 ) -> (atlas_index : i32) +atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u32 ) -> (atlas_index : i32) { if region.next_idx < region.state.capacity { @@ -80,7 +80,7 @@ atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u6 else { next_evict_codepoint := lru_get_next_evicted( region.state ) - assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF ) + assert( next_evict_codepoint != 0xFFFFFFFF) atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true ) assert( atlas_index != -1 ) @@ -92,37 +92,3 @@ atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u6 assert( lru_get( & region.state, lru_code ) != - 1 ) return } - -check_and_reserve_slot_in_atlas :: #force_inline proc( ctx : Context, glyph_index : Glyph, - lru_code : u64, - atlas_index : ^i32, - region : ^Atlas_Region, -) -> (found, should_cache : b8 ) -{ - profile(#procedure) - assert( glyph_index != -1 ) - - if ctx.temp_codepoint_seen_num > i32(cap(ctx.temp_codepoint_seen)) do return - - if (atlas_index ^) == - 1 - { - // Check to see if we reached capacity for the atlas - if region.next_idx > region.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( region.state ) - success : bool - found, success = ctx.temp_codepoint_seen[next_evict_codepoint] - assert(success != false) - if (found) { - return - } - } - - should_cache = true - (atlas_index ^) = atlas_reserve_slot(region, lru_code) - } - - found = true - return -} \ No newline at end of file diff --git a/code/font/vefontcache/draw.odin b/code/font/vefontcache/draw.odin index 36f5230..d0d3019 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -32,7 +32,7 @@ Glyph_Pack_Entry :: struct #packed { position : Vec2, index : Glyph, - lru_code : u64, + lru_code : u32, atlas_index : i32, in_atlas : b8, should_cache : b8, @@ -270,8 +270,8 @@ cache_glyph_to_atlas :: #force_no_inline proc ( profile(#procedure) batch_x := cast(f32) glyph_buf_Batch_x ^ - buffer_padding_scaled := glyph_padding * over_sample - buffer_bounds_scale := bounds_size_scaled * over_sample + buffer_padding_scaled := glyph_padding * over_sample + buffer_bounds_scale := (bounds_size_scaled) * over_sample // Allocate a glyph glyph render target region (FBO) buffer_x_allocation := buffer_bounds_scale.x + buffer_padding_scaled.x + 2.0 @@ -284,7 +284,7 @@ cache_glyph_to_atlas :: #force_no_inline proc ( region_pos := region_pos dst_glyph_position := region_pos - dst_glyph_size := bounds_size_scaled + glyph_padding + dst_glyph_size := (bounds_size_scaled) + glyph_padding dst_size := region_size to_screen_space( & dst_glyph_position, & dst_glyph_size, atlas_size ) to_screen_space( & region_pos, & dst_size, atlas_size ) @@ -334,50 +334,6 @@ cache_glyph_to_atlas :: #force_no_inline proc ( generate_glyph_pass_draw_list( draw_list, temp_path, glyph_shape, curve_quality, bounds, glyph_transform.scale, glyph_transform.pos ) } -generate_oversized_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, temp_path : ^[dynamic]Vertex, - colour : Colour, - curve_quality : f32, - glyph_shape : Parser_Glyph_Shape, - bounds : Range2, - glyph_pass_transform : Transform, - target_quad : Glyph_Draw_Quad, -) -{ - profile(#procedure) - - calls : [2]Draw_Call - draw_to_target := & calls[0] - { - using draw_to_target - pass = .Target_Uncached - colour = colour - start_index = u32(len(draw_list.indices)) - - blit_quad( draw_list, - target_quad.dst_pos, target_quad.dst_pos + target_quad.dst_scale, - target_quad.src_pos, target_quad.src_pos + target_quad.src_scale ) - - end_index = u32(len(draw_list.indices)) - } - clear_glyph_update := & calls[1] - { - // Clear glyph render target (FBO) - clear_glyph_update.pass = .Glyph - clear_glyph_update.start_index = 0 - clear_glyph_update.end_index = 0 - clear_glyph_update.clear_before_draw = true - } - append( & draw_list.calls, ..calls[:] ) - - generate_glyph_pass_draw_list( draw_list, temp_path, - glyph_shape, - curve_quality, - bounds, - glyph_pass_transform.scale, - glyph_pass_transform.pos - ) -} - generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, entry : Entry, shaped : Shaped_Text, @@ -440,11 +396,6 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, glyph.bounds = parser_get_bounds( entry.parser_info, glyph.index ) glyph.bounds_scaled = { glyph.bounds.p0 * entry.size_scale, glyph.bounds.p1 * entry.size_scale } glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0 - } - for & glyph, index in glyph_pack - { - // glyph.bounds_target_scaled = glyph.bounds_scaled * target_scale - glyph.bounds_size_scaled = glyph.bounds_size * entry.size_scale glyph.scale = glyph.bounds_size_scaled + atlas.glyph_padding } @@ -456,7 +407,6 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, for & glyph, index in glyph_pack { glyph.region_kind = atlas_decide_region( atlas ^, glyph_buffer_size, glyph.bounds_size_scaled ) - // glyph.over_sample = glyph_buffer.over_sample } profile_end() @@ -526,10 +476,10 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, // Quad to for drawing atlas slot to target glyph := & glyph_pack[id] quad := & glyph.draw_quad - quad.dst_pos = glyph.position + glyph.bounds_scaled.p0 * target_scale - quad.dst_scale = glyph.scale * target_scale - quad.src_scale = glyph.scale - quad.src_pos = glyph.region_pos + quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0) * target_scale + quad.dst_scale = (glyph.scale) * target_scale + quad.src_scale = (glyph.scale) + quad.src_pos = (glyph.region_pos) to_text_space( & quad.src_pos, & quad.src_scale, atlas_size ) } for id, index in sub_slice(to_cache) @@ -538,16 +488,16 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, // Quad to for drawing atlas slot to target quad := & glyph.draw_quad - quad.dst_pos = glyph.position + glyph.bounds_scaled.p0 * target_scale - quad.dst_scale = glyph.scale * target_scale - quad.src_scale = glyph.scale - quad.src_pos = glyph.region_pos + quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0) * target_scale + quad.dst_scale = (glyph.scale) * target_scale + quad.src_scale = (glyph.scale) + quad.src_pos = (glyph.region_pos) to_text_space( & quad.src_pos, & quad.src_scale, atlas_size ) // The glyph buffer space transform for generate_glyph_pass_draw_list transform := & glyph.draw_transform transform.scale = entry.size_scale * glyph_buffer.over_sample - transform.pos = -1 * glyph.bounds.p0 * transform.scale + atlas.glyph_padding + transform.pos = -1 * (glyph.bounds.p0) * transform.scale + atlas.glyph_padding // Unlike with oversized, this cannot be finished here as its final value is dependent on glyph_buffer.batch_x allocation } for id, index in sub_slice(oversized) @@ -601,14 +551,11 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, } profile_begin("to_cache: caching to atlas") - - // profile_begin("font parser shape generation") for id, index in sub_slice(to_cache) { error : Allocator_Error glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index) assert(error == .None) } - // profile_end() for id, index in sub_slice(to_cache) { @@ -640,21 +587,6 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, glyph.region_size, entry.curve_quality, ) - - // call := Draw_Call_Default - // call.pass = .Target - // call.colour = ctx.colour - - // profile("glyph") - // call.start_index = u32(len(draw_list.indices)) - - // quad := glyph_pack[id].draw_quad - // blit_quad(draw_list, - // quad.dst_pos, quad.dst_pos + quad.dst_scale, - // quad.src_pos, quad.src_pos + quad.src_scale - // ) - // call.end_index = u32(len(draw_list.indices)) - // append(& draw_list.calls, call) } flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) @@ -667,25 +599,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, ctx.colour.r = 1.0 ctx.colour.g = 1.0 ctx.colour.b = 1.0 - // flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) - // generate_cached_draw_list( draw_list, glyph_pack[:], sub_slice(cached), ctx.colour ) - for id, index in sub_slice(cached) - { - call := Draw_Call_Default - call.pass = .Target - call.colour = ctx.colour - - profile("glyph") - call.start_index = u32(len(draw_list.indices)) - - quad := glyph_pack[id].draw_quad - blit_quad(draw_list, - quad.dst_pos, quad.dst_pos + quad.dst_scale, - quad.src_pos, quad.src_pos + quad.src_scale - ) - call.end_index = u32(len(draw_list.indices)) - append(& draw_list.calls, call) - } + generate_cached_draw_list( draw_list, glyph_pack[:], sub_slice(cached), ctx.colour ) reset_batch_codepoint_state( ctx ) profile_end() @@ -698,22 +612,45 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, for id, index in sub_slice(oversized) { + flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) + + generate_glyph_pass_draw_list( draw_list, & ctx.temp_path, + glyph_pack[id].shape, + entry.curve_quality, + glyph_pack[id].bounds, + glyph_pack[id].draw_transform.scale, + glyph_pack[id].draw_transform.pos + ) + ctx.colour.r = 0.0 ctx.colour.g = 0.0 ctx.colour.b = 1.0 - flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) - glyph := glyph_pack[id] - generate_oversized_draw_list( - draw_list, - & ctx.temp_path, - ctx.colour, - entry.curve_quality, - glyph.shape, - glyph.bounds, - glyph.draw_transform, - glyph.draw_quad, - ) + target_quad := glyph_pack[id].draw_quad + + calls : [2]Draw_Call + draw_to_target := & calls[0] + { + using draw_to_target + pass = .Target_Uncached + colour = colour + start_index = u32(len(draw_list.indices)) + + blit_quad( draw_list, + target_quad.dst_pos, target_quad.dst_pos + target_quad.dst_scale, + target_quad.src_pos, target_quad.src_pos + target_quad.src_scale ) + + end_index = u32(len(draw_list.indices)) + } + clear_glyph_update := & calls[1] + { + // Clear glyph render target (FBO) + clear_glyph_update.pass = .Glyph + clear_glyph_update.start_index = 0 + clear_glyph_update.end_index = 0 + clear_glyph_update.clear_before_draw = true + } + append( & draw_list.calls, ..calls[:] ) } profile_end() diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index 6fd805e..3c62271 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -11,6 +11,9 @@ Vec2 :: [2]f32 Vec2i :: [2]i32 Vec2_64 :: [2]f64 +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) } + vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }} vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }} vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }} @@ -54,8 +57,8 @@ reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) { raw.allocator = allocator } -font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u64) { - lru_code = u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) +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 } @@ -66,7 +69,7 @@ is_glyph_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_ind return false } -mark_batch_codepoint_seen :: #force_inline proc "contextless" ( ctx : ^Context, lru_code : u64 ) { +mark_batch_codepoint_seen :: #force_inline proc "contextless" ( ctx : ^Context, lru_code : u32 ) { ctx.temp_codepoint_seen[lru_code] = true ctx.temp_codepoint_seen_num += 1 } @@ -137,11 +140,6 @@ when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS // ve_fontcache_eval_bezier (quadratic) eval_point_on_bezier3 :: #force_inline proc "contextless" ( p0, p1, p2 : Vec2, alpha : f32 ) -> Vec2 { - // p0 := vec2_64(p0) - // p1 := vec2_64(p1) - // p2 := vec2_64(p2) - // alpha := f64(alpha) - weight_start := (1 - alpha) * (1 - alpha) weight_control := 2.0 * (1 - alpha) * alpha weight_end := alpha * alpha @@ -159,12 +157,6 @@ when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS // ve_fontcache_eval_bezier (cubic) eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2 { - // p0 := vec2_64(p0) - // p1 := vec2_64(p1) - // p2 := vec2_64(p2) - // p3 := vec2_64(p3) - // alpha := f64(alpha) - weight_start := (1 - alpha) * (1 - alpha) * (1 - alpha) weight_c_a := 3 * (1 - alpha) * (1 - alpha) * alpha weight_c_b := 3 * (1 - alpha) * alpha * alpha diff --git a/code/font/vefontcache/shaped_text.odin b/code/font/vefontcache/shaped_text.odin deleted file mode 100644 index b439f26..0000000 --- a/code/font/vefontcache/shaped_text.odin +++ /dev/null @@ -1,149 +0,0 @@ -package vefontcache - -Shaped_Text :: struct { - glyphs : [dynamic]Glyph, - positions : [dynamic]Vec2, - end_cursor_pos : Vec2, - size : Vec2, - entry : ^Entry, - font : Font_ID, -} - -Shaped_Text_Cache :: struct { - storage : [dynamic]Shaped_Text, - state : LRU_Cache, - next_cache_id : i32, -} - -shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { - for value in bytes { - (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) - } -} - -ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) - -shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, shape_text_uncached : $ShapedTextUncachedProc ) -> (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 - - lru_code : u64 - shape_lru_hash( & lru_code, font_bytes ) - shape_lru_hash( & lru_code, text_bytes ) - - shape_cache := & ctx.shape_cache - state := & ctx.shape_cache.state - - shape_cache_idx := lru_get( state, lru_code ) - if shape_cache_idx == -1 - { - if shape_cache.next_cache_id < i32(state.capacity) { - shape_cache_idx = shape_cache.next_cache_id - shape_cache.next_cache_id += 1 - evicted := lru_put( state, lru_code, shape_cache_idx ) - } - else - { - next_evict_idx := lru_get_next_evicted( state ^ ) - // assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF ) - - shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true ) - // assert( shape_cache_idx != - 1 ) - - lru_put( state, lru_code, shape_cache_idx ) - } - - storage_entry := & shape_cache.storage[ shape_cache_idx ] - shape_text_uncached( ctx, font, text_utf8, entry, storage_entry ) - - shaped_text = storage_entry ^ - return - } - - shaped_text = shape_cache.storage[ shape_cache_idx ] - return -} - -shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) -{ - profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - clear( & output.glyphs ) - clear( & output.positions ) - - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) - ascent := f32(ascent_i32) - descent := f32(descent_i32) - line_gap := f32(line_gap_i32) - line_height := (ascent - descent + line_gap) * entry.size_scale - - shaper_shape_from_text( & ctx.shaper_ctx, entry.parser_info, entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) -} - -shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) -{ - profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - clear( & output.glyphs ) - clear( & output.positions ) - - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) - ascent := f32(ascent_i32) - descent := f32(descent_i32) - line_gap := f32(line_gap_i32) - line_height := (ascent - descent + line_gap) * entry.size_scale - - line_count : int = 1 - max_line_width : f32 = 0 - position : Vec2 - - prev_codepoint : rune - for codepoint, index in text_utf8 - { - if prev_codepoint > 0 { - kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint ) - position.x += f32(kern) * entry.size_scale - } - if codepoint == '\n' - { - line_count += 1 - max_line_width = max(max_line_width, position.x) - position.x = 0.0 - position.y -= line_height - position.y = position.y - prev_codepoint = rune(0) - continue - } - if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { - position.x = ceil(position.x) - } - - glyph_index := parser_find_glyph_index( entry.parser_info, codepoint ) - is_glyph_empty := parser_is_glyph_empty( entry.parser_info,glyph_index ) - if ! is_glyph_empty - { - append( & output.glyphs, glyph_index) - append( & output.positions, Vec2 { - floor(position.x), - floor(position.y) - }) - } - - advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) - position.x += f32(advance) * entry.size_scale - prev_codepoint = codepoint - } - - output.end_cursor_pos = position - max_line_width = max(max_line_width, position.x) - - output.size.x = max_line_width - output.size.y = f32(line_count) * line_height -} diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index 4be038b..6615a57 100644 --- a/code/font/vefontcache/shaper.odin +++ b/code/font/vefontcache/shaper.odin @@ -6,6 +6,25 @@ 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 + +Shaped_Text :: struct { + glyphs : [dynamic]Glyph, + positions : [dynamic]Vec2, + end_cursor_pos : Vec2, + size : Vec2, + entry : ^Entry, + font : Font_ID, +} + +Shaped_Text_Cache :: struct { + storage : [dynamic]Shaped_Text, + state : LRU_Cache, + next_cache_id : i32, +} + +Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) + Shaper_Kind :: enum { Naive = 0, Harfbuzz = 1, @@ -185,3 +204,127 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info output.size.y = f32(line_count) * line_height return } +shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + clear( & output.glyphs ) + clear( & output.positions ) + + ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) + ascent := f32(ascent_i32) + descent := f32(descent_i32) + line_gap := f32(line_gap_i32) + line_height := (ascent - descent + line_gap) * entry.size_scale + + shaper_shape_from_text( & ctx.shaper_ctx, entry.parser_info, entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) +} + +shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + clear( & output.glyphs ) + clear( & output.positions ) + + ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) + ascent := f32(ascent_i32) + descent := f32(descent_i32) + line_gap := f32(line_gap_i32) + line_height := (ascent - descent + line_gap) * entry.size_scale + + line_count : int = 1 + max_line_width : f32 = 0 + position : Vec2 + + prev_codepoint : rune + for codepoint, index in text_utf8 + { + if prev_codepoint > 0 { + kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint ) + position.x += f32(kern) * entry.size_scale + } + if codepoint == '\n' + { + line_count += 1 + max_line_width = max(max_line_width, position.x) + position.x = 0.0 + position.y -= line_height + position.y = position.y + prev_codepoint = rune(0) + continue + } + if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { + position.x = ceil(position.x) + } + + glyph_index := parser_find_glyph_index( entry.parser_info, codepoint ) + is_glyph_empty := parser_is_glyph_empty( entry.parser_info,glyph_index ) + if ! is_glyph_empty + { + append( & output.glyphs, glyph_index) + append( & output.positions, Vec2 { + floor(position.x), + floor(position.y) + }) + } + + advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) + position.x += f32(advance) * entry.size_scale + prev_codepoint = codepoint + } + + output.end_cursor_pos = position + max_line_width = max(max_line_width, position.x) + + output.size.x = max_line_width + output.size.y = f32(line_count) * line_height +} + +shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc ) -> (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 + + lru_code : u32 + shape_lru_code( & lru_code, font_bytes ) + shape_lru_code( & lru_code, text_bytes ) + + shape_cache := & ctx.shape_cache + state := & ctx.shape_cache.state + + shape_cache_idx := lru_get( state, lru_code ) + if shape_cache_idx == -1 + { + if shape_cache.next_cache_id < i32(state.capacity) { + shape_cache_idx = shape_cache.next_cache_id + shape_cache.next_cache_id += 1 + evicted := lru_put( state, lru_code, shape_cache_idx ) + } + else + { + next_evict_idx := lru_get_next_evicted( state ^ ) + assert( next_evict_idx != 0xFFFFFFFF ) + + shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true ) + assert( shape_cache_idx != - 1 ) + + lru_put( state, lru_code, shape_cache_idx ) + } + + storage_entry := & shape_cache.storage[ shape_cache_idx ] + shape_text_uncached( ctx, font, text_utf8, entry, storage_entry ) + + shaped_text = storage_entry ^ + return + } + + shaped_text = shape_cache.storage[ shape_cache_idx ] + return +} diff --git a/code/font/vefontcache/vefontcache.odin b/code/font/vefontcache/vefontcache.odin index a33c408..8df1016 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -40,7 +40,7 @@ Context :: struct { entries : [dynamic]Entry, temp_path : [dynamic]Vertex, - temp_codepoint_seen : map[u64]b8, + temp_codepoint_seen : map[u32]b8, temp_codepoint_seen_num : i32, snap_width : f32, @@ -183,7 +183,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, temp_path, error = make( [dynamic]Vertex, len = 0, cap = temp_path_reserve ) assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - temp_codepoint_seen, error = make( map[u64]b8, uint(temp_codepoint_seen_reserve) ) + temp_codepoint_seen, error = make( map[u32]b8, uint(temp_codepoint_seen_reserve) ) assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte ) diff --git a/code/sectr/engine/update.odin b/code/sectr/engine/update.odin index 3f98f6b..c8072e9 100644 --- a/code/sectr/engine/update.odin +++ b/code/sectr/engine/update.odin @@ -322,7 +322,7 @@ update :: proc( delta_time : f64 ) -> b32 flags = frame_style_flags, anchor = {}, // alignment = { 0.5, 0.5 }, - font_size = 14, + font_size = 16, text_alignment = { 0.0, 0.0 }, // corner_radii = { 0.2, 0.2, 0.2, 0.2 }, pos = { 0, 0 }, diff --git a/scripts/build.ps1 b/scripts/build.ps1 index ec895c5..99726a2 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -201,10 +201,10 @@ push-location $path_root # $build_args += $flag_micro_architecture_native $build_args += $flag_use_separate_modules $build_args += $flag_thread_count + $CoreCount_Physical - # $build_args += $flag_optimize_none + $build_args += $flag_optimize_none # $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed - $build_args += $falg_optimize_aggressive + # $build_args += $falg_optimize_aggressive $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows'