From 991e7a81c077219b97634ddaa7f4a1826cf380ee Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 4 Jun 2024 18:44:12 -0400 Subject: [PATCH] Progress on VEFontCache port, only cache_glyph_to_atlas & shape_text_uncached left --- code/font/VEFontCache/LRU.odin | 22 ++- code/font/VEFontCache/VEFontCache.odin | 232 ++++++++++--------------- code/font/VEFontCache/atlas.odin | 171 ++++++++++++++++-- code/font/VEFontCache/draw.odin | 221 ++++++++++++++++++++++- code/font/VEFontCache/mappings.odin | 6 + code/font/VEFontCache/parser.odin | 7 + code/grime/hashmap_chained.odin | 2 +- code/sectr/ui/core/base.odin | 2 +- 8 files changed, 488 insertions(+), 175 deletions(-) diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index 7337068..239da88 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -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 } diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 9d31141..0ff0ce4 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -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() -{ - -} diff --git a/code/font/VEFontCache/atlas.odin b/code/font/VEFontCache/atlas.odin index be9e477..5b32152 100644 --- a/code/font/VEFontCache/atlas.odin +++ b/code/font/VEFontCache/atlas.odin @@ -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 +} diff --git a/code/font/VEFontCache/draw.odin b/code/font/VEFontCache/draw.odin index 5f7d51f..f2fb8b7 100644 --- a/code/font/VEFontCache/draw.odin +++ b/code/font/VEFontCache/draw.odin @@ -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) ) +} diff --git a/code/font/VEFontCache/mappings.odin b/code/font/VEFontCache/mappings.odin index 9d08ca8..8210b9b 100644 --- a/code/font/VEFontCache/mappings.odin +++ b/code/font/VEFontCache/mappings.odin @@ -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, } diff --git a/code/font/VEFontCache/parser.odin b/code/font/VEFontCache/parser.odin index 1f80777..7ff22c9 100644 --- a/code/font/VEFontCache/parser.odin +++ b/code/font/VEFontCache/parser.odin @@ -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 diff --git a/code/grime/hashmap_chained.odin b/code/grime/hashmap_chained.odin index 65a1281..feb426d 100644 --- a/code/grime/hashmap_chained.odin +++ b/code/grime/hashmap_chained.odin @@ -89,7 +89,7 @@ hmap_chained_clear :: proc( using self : HMapChained($Type)) if slot == nil { continue } - for probe_slot = slot.next; probe_slot != nil; probe_slot = probe_slot.next { + for probe_slot := slot.next; probe_slot != nil; probe_slot = probe_slot.next { slot.occupied = false } slot.occupied = false diff --git a/code/sectr/ui/core/base.odin b/code/sectr/ui/core/base.odin index 76d43f0..78c34dd 100644 --- a/code/sectr/ui/core/base.odin +++ b/code/sectr/ui/core/base.odin @@ -257,7 +257,7 @@ ui_key_from_string :: #force_inline proc "contextless" ( value : string ) -> UI_ when USE_RAD_DEBUGGERS_METHOD { hash : u64 for str_byte in transmute([]byte) value { - hash = ((hash << 5) + hash) + u64(str_byte) + hash = ((hash << 8) + hash) + u64(str_byte) } key = cast(UI_Key) hash }