diff --git a/code/font/vefontcache/LRU.odin b/code/font/vefontcache/LRU.odin index 0cf422b..4fcf1e9 100644 --- a/code/font/vefontcache/LRU.odin +++ b/code/font/vefontcache/LRU.odin @@ -243,7 +243,7 @@ lru_peek :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, mu lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 { - profile(#procedure) + // profile(#procedure) if link, ok := & cache.table[ key ]; ok { pool_list_move_to_front( & cache.key_queue, link.ptr ) link.value = value diff --git a/code/font/vefontcache/atlas.odin b/code/font/vefontcache/atlas.odin index 6290b59..2f8d7c8 100644 --- a/code/font/vefontcache/atlas.odin +++ b/code/font/vefontcache/atlas.odin @@ -34,6 +34,8 @@ Atlas :: struct { region_b : Atlas_Region, region_c : Atlas_Region, region_d : Atlas_Region, + + regions : [4] ^Atlas_Region, } atlas_bbox :: #force_inline proc "contextless" ( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) @@ -86,32 +88,27 @@ atlas_bbox :: #force_inline proc "contextless" ( atlas : ^Atlas, region : Atlas_ return } -decide_codepoint_region :: #force_inline proc (ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2) +atlas_region_bbox :: proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2) +{ + size.x = f32(region.width) + size.y = f32(region.height) + + position.x = cast(f32) (( local_idx % region.capacity.x ) * region.width) + position.y = cast(f32) (( local_idx / region.capacity.x ) * region.height) + + position.x += f32(region.offset.x) + position.y += f32(region.offset.y) + return +} + +decide_codepoint_region :: #force_inline proc (atlas : Atlas, glyph_buffer : Glyph_Draw_Buffer, size_scale : f32, glyph_index : Glyph, bounds_size : Vec2 ) -> (region_kind : Atlas_Region_Kind, over_sample : Vec2) { profile(#procedure) - if parser_is_glyph_empty(&entry.parser_info, glyph_index) { - return .None, nil, {} - } + glyph_padding_dbl := atlas.glyph_padding * 2 + bounds_size_scaled := bounds_size * size_scale + glyph_padding_dbl - bounds_0, bounds_1 := parser_get_glyph_box(&entry.parser_info, glyph_index) - bounds_size := vec2(bounds_1) - vec2(bounds_0) - - atlas := & ctx.atlas - glyph_buffer := & ctx.glyph_buffer - glyph_padding_dbl := atlas.glyph_padding * 2 - - bounds_size_scaled := bounds_size * entry.size_scale * atlas.glyph_over_scalar + glyph_padding_dbl - - // Use a lookup table for faster region selection - region_lookup := [4]struct { kind: Atlas_Region_Kind, region: ^Atlas_Region } { - { .A, & atlas.region_a }, - { .B, & atlas.region_b }, - { .C, & atlas.region_c }, - { .D, & atlas.region_d }, - } - - for region in region_lookup do if bounds_size_scaled.x <= f32(region.region.width) && bounds_size_scaled.y <= f32(region.region.height) { - return region.kind, region.region, glyph_buffer.over_sample + for kind in 0 ..< 4 do if bounds_size_scaled.x <= f32( atlas.regions[kind].width) && bounds_size_scaled.y <= f32(atlas.regions[kind].height) { + return cast(Atlas_Region_Kind) kind, glyph_buffer.over_sample } if bounds_size_scaled.x <= f32(glyph_buffer.width) \ @@ -121,9 +118,9 @@ decide_codepoint_region :: #force_inline proc (ctx : ^Context, entry : ^Entry, g bounds_size_scaled.y <= f32(glyph_buffer.height / 2) ? \ {2.0, 2.0} \ : {1.0, 1.0} - return .E, nil, over_sample + return .E, over_sample } - return .None, nil, {} + return .None, {} } // Grab an atlas LRU cache slot. @@ -151,3 +148,40 @@ 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, font : Font_ID, entry : ^Entry, glyph_index : Glyph, + lru_code : u64, + atlas_index : ^i32, + region_kind : Atlas_Region_Kind, + region : ^Atlas_Region, + over_sample : Vec2 +) -> (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 52df64d..20086ac 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -331,31 +331,23 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : * can_batch_glyph : If it determines that the glyph was not detected and we haven't reached capacity in the atlas * draw_text_shape : Glyph */ -cache_glyph_to_atlas :: proc( ctx : ^Context, +cache_glyph_to_atlas :: proc ( ctx : ^Context, font : Font_ID, glyph_index : Glyph, + bounds : GlyphBounds, + bounds_size : Vec2, + region_pos : Vec2, + region_size : Vec2, lru_code : u64, atlas_index : i32, entry : ^Entry, region_kind : Atlas_Region_Kind, region : ^Atlas_Region, - over_sample : Vec2 ) + over_sample : Vec2 +) { profile(#procedure) - // Get hb_font text metrics. These are unscaled! - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - vbounds_0 := vec2(bounds_0) - vbounds_1 := vec2(bounds_1) - bounds_size := vbounds_1 - vbounds_0 - - // E region is special case and not cached to atlas. - if region_kind == .None || region_kind == .E do return - - atlas_index := atlas_index - // TODO(Ed): Try to make sure this is resolve always - if atlas_index == -1 do atlas_index = atlas_reserve_slot( region, lru_code ) - atlas := & ctx.atlas glyph_buffer := & ctx.glyph_buffer atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) } @@ -372,7 +364,7 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, // Draw oversized glyph to glyph render target (FBO) glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := -1 * vbounds_0 * glyph_draw_scale + vec2( glyph_padding ) + glyph_draw_translate := -1 * bounds.p0 * glyph_draw_scale + vec2( glyph_padding ) // Allocate a glyph glyph render target region (FBO) gwidth_scaled_px := bounds_size.x * glyph_draw_scale.x + over_sample.x * glyph_padding + 1.0 @@ -380,14 +372,13 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, flush_glyph_buffer_to_atlas( ctx ) } - // Calculate the src and destination regions - slot_position, slot_size := atlas_bbox( atlas, region_kind, atlas_index ) + region_pos := region_pos - dst_glyph_position := slot_position + dst_glyph_position := region_pos dst_glyph_size := ceil(bounds_size * entry.size_scale + glyph_padding) - dst_size := (slot_size) + dst_size := (region_size) screenspace_x_form( & dst_glyph_position, & dst_glyph_size, atlas_size ) - screenspace_x_form( & slot_position, & dst_size, atlas_size ) + screenspace_x_form( & region_pos, & dst_size, atlas_size ) src_position := Vec2 { f32(glyph_buffer.batch_x), 0 } src_size := (bounds_size * glyph_draw_scale + over_sample * glyph_padding) @@ -406,7 +397,7 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, start_index = cast(u32) len(glyph_buffer.clear_draw_list.indices) blit_quad( & glyph_buffer.clear_draw_list, - slot_position, slot_position + dst_size, + region_pos, region_pos + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } ) end_index = cast(u32) len(glyph_buffer.clear_draw_list.indices) @@ -420,7 +411,7 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, start_index = cast(u32) len(glyph_buffer.draw_list.indices) blit_quad( & glyph_buffer.draw_list, - dst_glyph_position, slot_position + dst_glyph_size, + dst_glyph_position, region_pos + dst_glyph_size, src_position, src_position + src_size ) end_index = cast(u32) len(glyph_buffer.draw_list.indices) @@ -430,49 +421,7 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, append( & glyph_buffer.draw_list.calls, blit_to_atlas ) // Render glyph to glyph render target (FBO) - cache_glyph( ctx, font, glyph_index, entry, vbounds_0, vbounds_1, glyph_draw_scale, glyph_draw_translate ) -} - -// If the glyuph is found in the atlas, nothing occurs, otherwise, the glyph call is setup to catch it to the atlas -check_glyph_in_atlas :: #force_inline proc( ctx : ^Context, font : Font_ID, entry : ^Entry, glyph_index : Glyph, - lru_code : u64, - atlas_index : i32, - region_kind : Atlas_Region_Kind, - region : ^Atlas_Region, - over_sample : Vec2 -) -> (seen, should_cache : b8) -{ - profile(#procedure) - assert( glyph_index != -1 ) - - // E region can't batch - if region_kind == .E || region_kind == .None do return - 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 - seen, success = ctx.temp_codepoint_seen[next_evict_codepoint] - assert(success != false) - - if (seen) { - return - } - } - - should_cache = true - cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) - } - - assert( lru_get( & region.state, lru_code ) != -1 ) - mark_batch_codepoint_seen( ctx, lru_code) - seen = true - return + cache_glyph( ctx, font, glyph_index, entry, bounds.p0, bounds.p1, glyph_draw_scale, glyph_draw_translate ) } // ve_fontcache_clear_Draw_List @@ -485,7 +434,7 @@ clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) { directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, - bounds_0, bounds_1 : Vec2, + bounds : GlyphBounds, bounds_size : Vec2, over_sample, position, scale : Vec2 ) { @@ -495,24 +444,22 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, glyph_padding := f32(ctx.atlas.glyph_padding) glyph_buffer_size := Vec2 { f32(ctx.glyph_buffer.width), f32(ctx.glyph_buffer.height) } - // Draw un-antialiased glyph to update FBO. + // Draw un-antialiased glyph to draw_buffer glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := -1 * bounds_0 * glyph_draw_scale + vec2_from_scalar(glyph_padding) + glyph_draw_translate := -1 * bounds.p0 * glyph_draw_scale + vec2_from_scalar(glyph_padding) screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_size ) - cache_glyph( ctx, entry.id, glyph, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate ) + cache_glyph( ctx, entry.id, glyph, entry, bounds.p0, bounds.p1, glyph_draw_scale, glyph_draw_translate ) - glyph_padding_dbl := glyph_padding * 2 - bounds_scaled := bounds_size * entry.size_scale + bounds_scaled := bounds_size * entry.size_scale // Figure out the source rect. glyph_position := Vec2 {} - glyph_size := vec2(glyph_padding) - glyph_dst_size := glyph_size + bounds_scaled - glyph_size += bounds_scaled * over_sample + glyph_size := glyph_padding + bounds_scaled * over_sample + glyph_dst_size := glyph_padding + bounds_scaled // Figure out the destination rect. - bounds_0_scaled := (bounds_0 * entry.size_scale) + bounds_0_scaled := (bounds.p0 * entry.size_scale) dst := position + scale * bounds_0_scaled - glyph_padding * scale dst_size := glyph_dst_size * scale textspace_x_form( & glyph_position, & glyph_size, glyph_buffer_size ) @@ -595,8 +542,9 @@ draw_filled_path :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : [ } } -draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, - batch_start_idx, batch_end_idx : i32, +draw_text_batch :: #force_inline proc (ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, + // batch_start_idx, batch_end_idx : i32, + glyph_pack : #soa[]GlyphPackEntry, position, scale : Vec2, snap_width, snap_height : f32 ) { @@ -607,61 +555,66 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, atlas_size := Vec2{ f32(atlas.width), f32(atlas.height) } glyph_padding := atlas.glyph_padding - for index := batch_start_idx; index < batch_end_idx; index += 1 + // for glyph, index in glyph_pack + // { + // profile("oversized") + // if glyph.region_kind != .E do continue + // directly_draw_massive_glyph(ctx, entry, glyph.index, + // glyph.bounds, + // glyph.bounds_size, + // glyph.over_sample, glyph.translate, scale ) + // } + + for glyph, index in glyph_pack { - profile("glyph") - glyph_index := shaped.glyphs[index] + // profile_begin("oversized") + // if glyph.region_kind == .E + // { + // directly_draw_massive_glyph(ctx, entry, glyph.index, + // glyph.bounds, + // glyph.bounds_size, + // glyph.over_sample, glyph.translate, scale ) + // continue + // } + // profile_end() - if glyph_index == 0 || parser_is_glyph_empty( & entry.parser_info, glyph_index) do continue + profile("cached") - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - lru_code := font_glyph_lru_code( entry.id, glyph_index ) - atlas_index := region_kind != .E ? lru_get( & region.state, lru_code ) : -1 - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - vbounds_0 := vec2(bounds_0) - vbounds_1 := vec2(bounds_1) - bounds_size := vbounds_1 - vbounds_0 + glyph_scale := glyph.bounds_size * entry.size_scale + glyph_padding + bounds_0_scaled := ceil(glyph.bounds.p0 * entry.size_scale - 0.5 ) + dst_pos := glyph.translate + bounds_0_scaled * scale + dst_scale := glyph_scale * scale + src_pos := glyph.region_pos - shaped_position := shaped.positions[index] - glyph_translate := position + (shaped_position) * scale + textspace_x_form( & src_pos, & glyph_scale, atlas_size ) - if region_kind == .E - { - directly_draw_massive_glyph(ctx, entry, glyph_index, - vbounds_0, vbounds_1, - bounds_size, - over_sample, glyph_translate, scale ) - } - else if atlas_index != -1 - { - profile("derive manual") - call := Draw_Call_Default - call.pass = .Target - call.colour = ctx.colour - call.start_index = u32(len(ctx.draw_list.indices)) + call := Draw_Call_Default + call.pass = .Target + call.colour = ctx.colour + call.start_index = u32(len(ctx.draw_list.indices)) - // Draw cached glyph - slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) - glyph_scale := bounds_size * entry.size_scale + glyph_padding - bounds_0_scaled := ceil(vbounds_0 * entry.size_scale - 0.5 ) - dst := glyph_translate + bounds_0_scaled * scale - dst_scale := glyph_scale * scale + blit_quad(& ctx.draw_list, + dst_pos, dst_pos + dst_scale, + src_pos, src_pos + glyph_scale ) - textspace_x_form( & slot_position, & glyph_scale, atlas_size ) - blit_quad(&ctx.draw_list, - dst, dst + dst_scale, - slot_position, slot_position + glyph_scale ) - call.end_index = u32(len(ctx.draw_list.indices)) + call.end_index = u32(len(ctx.draw_list.indices)) - append(&ctx.draw_list.calls, call) - } + append(&ctx.draw_list.calls, call) } } +GlyphBounds :: struct { + p0, p1 : Vec2 +} + GlyphPackEntry :: struct { - lru_code : u64, - region : ^Atlas_Region, + bounds : GlyphBounds, + bounds_size : Vec2, over_sample : Vec2, + translate : Vec2, + region_pos : Vec2, + region_size : Vec2, + lru_code : u64, atlas_index : i32, index : Glyph, shape_id : i32, @@ -681,68 +634,91 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, { profile(#procedure) - glyph_pack, pack_alloc_eror := make_soa(#soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator) + glyph_pack, glyph_pack_alloc_error := make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator ) + oversized_pack, oversized_pack_alloc_error := make_soa( #soa[dynamic]GlyphPackEntry, length = 0, capacity = len(shaped.glyphs), allocator = context.temp_allocator ) - profile_begin("SOA glyph pack processing") + atlas := & ctx.atlas + + profile_begin("SOA setup") + + profile_begin("index & translate") for & glyph, index in glyph_pack { glyph.shape_id = cast(i32) index glyph.index = shaped.glyphs[ index ] } - // for & glyph, index in glyph_pack - // { - // glyph.region_kind, - // glyph.region, - // glyph.over_sample = decide_codepoint_region( ctx, entry, glyph.index ) - // } - // for & glyph, index in glyph_pack - // { - // glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index) - // } - // for & glyph, index in glyph_pack - // { - // glyph.atlas_index = -1 - // if glyph.region_kind != .E do glyph.atlas_index = lru_get( & glyph.region.state, glyph.lru_code ) - // } - // for & glyph, index in glyph_pack - // { - // glyph.in_atlas, glyph.should_cache = check_glyph_in_atlas( ctx, font, entry, glyph.index, glyph.lru_code, glyph.atlas_index, glyph.region_kind, glyph.region, glyph.over_sample ) - // } - // for & glyph, index in glyph_pack - // { - // if ! glyph.should_cache do continue - // cache_glyph_to_atlas(ctx, font, glyph.index, glyph.lru_code, glyph.atlas_index, entry, glyph.region_kind, glyph.region, glyph.over_sample) - // } - // for & glyph, index in glyph_pack - // { - // if ! glyph.in_atlas do continue - - // assert( lru_get( & glyph.region.state, glyph.lru_code ) != -1 ) - // mark_batch_codepoint_seen( ctx, glyph.lru_code) - // } profile_end() + profile_begin("translate") + for & glyph, index in glyph_pack + { + glyph.translate = position + (shaped.positions[index]) * scale + } + profile_end() + + profile_begin("bounds") + for & glyph, index in glyph_pack + { + glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index) + glyph.bounds = parser_get_bounds( & entry.parser_info, glyph.index ) + glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0 + } + profile_end() + + profile_begin("region") + for & glyph, index in glyph_pack + { + glyph.region_kind, + glyph.over_sample = decide_codepoint_region( ctx.atlas, ctx.glyph_buffer, entry.size_scale, glyph.index, glyph.bounds_size ) + } + profile_end() + + profile_begin("atlas region slot check & reservation") + for & glyph, index in glyph_pack + { + region := atlas.regions[glyph.region_kind] + + if glyph.region_kind == .E do continue + + glyph.atlas_index = lru_get( & region.state, glyph.lru_code ) + glyph.in_atlas, glyph.should_cache = check_and_reserve_slot_in_atlas( ctx, font, entry, glyph.index, glyph.lru_code, & glyph.atlas_index, glyph.region_kind, region, glyph.over_sample ) + glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) + } + profile_end() + + profile_begin("caching to atlas") + for glyph, index in glyph_pack + { + if glyph.region_kind == .E do continue + if ! glyph.should_cache do continue + cache_glyph_to_atlas(ctx, font, glyph.index, glyph.bounds, glyph.bounds_size, glyph.region_pos, glyph.region_size, glyph.lru_code, glyph.atlas_index, entry, glyph.region_kind, atlas.regions[glyph.region_kind], glyph.over_sample) + } + profile_end() + + profile_end() + + + // First batch the other cached glyphs + // flush_glyph_buffer_to_atlas(ctx) + // draw_text_batch( ctx, entry, shaped, glyph_pack[batch_start_idx : index], position, scale, snap_width, snap_height ) + // reset_batch_codepoint_state( ctx ) + + // Prepare uncached glyphs for caching batch_start_idx : i32 = 0 - for & glyph, index in glyph_pack + for glyph, index in glyph_pack { - // if is_glyph_empty( ctx, entry, glyph.index ) do continue + profile("caching glyph") - glyph.region_kind, glyph.region, glyph.over_sample = decide_codepoint_region( ctx, entry, glyph.index ) + if glyph.region_kind == .E { + append_soa( & oversized_pack, glyph ) + continue + } - glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index) - - glyph.atlas_index = -1 - if glyph.region_kind != .E do glyph.atlas_index = lru_get( & glyph.region.state, glyph.lru_code ) - - glyph.in_atlas, glyph.should_cache = check_glyph_in_atlas( ctx, font, entry, glyph.index, glyph.lru_code, glyph.atlas_index, glyph.region_kind, glyph.region, glyph.over_sample ) - // if glyph.should_cache { - // cache_glyph_to_atlas(ctx, font, glyph.index, glyph.lru_code, glyph.atlas_index, entry, glyph.region_kind, glyph.region, glyph.over_sample) - // glyph.atlas_index = atlas_reserve_slot(glyph.region, glyph.lru_code) - // } if glyph.in_atlas { - // assert( lru_get( & glyph.region.state, glyph.lru_code ) != -1 ) - // mark_batch_codepoint_seen( ctx, glyph.lru_code) + profile("glyph in atlas") + // assert( lru_get( & atlas.regions[glyph.region_kind].state, glyph.lru_code ) != -1 ) + mark_batch_codepoint_seen( ctx, glyph.lru_code) continue } @@ -750,63 +726,32 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, // First batch the other cached glyphs // flush_glyph_buffer_to_atlas(ctx) - draw_text_batch( ctx, entry, shaped, batch_start_idx, glyph.shape_id, position, scale, snap_width, snap_height ) - reset_batch_codepoint_state( ctx ) + // draw_text_batch( ctx, entry, shaped, glyph_pack[batch_start_idx : index], position, scale, snap_width, snap_height ) + // reset_batch_codepoint_state( ctx ) - cache_glyph_to_atlas( ctx, font, glyph.index, glyph.lru_code, glyph.atlas_index, entry, glyph.region_kind, glyph.region, glyph.over_sample ) + cache_glyph_to_atlas( ctx, font, glyph.index, glyph.bounds, glyph.bounds_size, glyph.region_pos, glyph.region_size, glyph.lru_code, glyph.atlas_index, entry, glyph.region_kind, atlas.regions[glyph.region_kind], glyph.over_sample ) mark_batch_codepoint_seen( ctx, glyph.lru_code) - batch_start_idx = 1 + batch_start_idx = glyph.shape_id } - draw_text_batch( ctx, entry, shaped, batch_start_idx, cast(i32) len(shaped.glyphs), position, scale, snap_width , snap_height ) + draw_text_batch( ctx, entry, shaped, glyph_pack[batch_start_idx : len(glyph_pack)], position, scale, snap_width , snap_height ) reset_batch_codepoint_state( ctx ) - cursor_pos = position + shaped.end_cursor_pos * scale - return -} + profile_begin("draw oversized glyphs") + flush_glyph_buffer_to_atlas(ctx) -// Helper for draw_text_latin_mono -draw_text_mono_latin_batch :: #force_inline proc( ctx : ^Context, - font : Font_ID, - entry : ^Entry, - shaped : ^Shaped_Text, - position, scale : Vec2, - snap_width, snap_height : f32 -) -> (cursor_pos : Vec2) #no_bounds_check -{ - profile(#procedure) - batch_start_idx : i32 = 0 - for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1 + for & glyph, index in oversized_pack { - glyph_index := shaped.glyphs[ index ] - if is_glyph_empty( ctx, entry, glyph_index ) do continue - - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - lru_code := font_glyph_lru_code(entry.id, glyph_index) - atlas_index := cast(i32) -1 - - if region_kind != .E do atlas_index = lru_get( & region.state, lru_code ) - - in_atlas, should_cache := check_glyph_in_atlas( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) - if in_atlas do continue - if should_cache do cache_glyph_to_atlas(ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) - - // We can no longer directly append the shape as it has missing glyphs in the atlas - - // First batch the other cached glyphs - // flush_glyph_buffer_to_atlas(ctx) - draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale, snap_width, snap_height ) - reset_batch_codepoint_state( ctx ) - - cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) - mark_batch_codepoint_seen( ctx, lru_code) - batch_start_idx = index + directly_draw_massive_glyph(ctx, entry, glyph.index, + glyph.bounds, + glyph.bounds_size, + glyph.over_sample, glyph.translate, scale ) } - draw_text_batch( ctx, entry, shaped, batch_start_idx, cast(i32) len(shaped.glyphs), position, scale, snap_width , snap_height ) reset_batch_codepoint_state( ctx ) - + profile_end() + cursor_pos = position + shaped.end_cursor_pos * scale return } diff --git a/code/font/vefontcache/mappings.odin b/code/font/vefontcache/mappings.odin index 50262e6..d22f5b0 100644 --- a/code/font/vefontcache/mappings.odin +++ b/code/font/vefontcache/mappings.odin @@ -1,5 +1,6 @@ package vefontcache +import "base:runtime" import "core:hash" fnv64a :: hash.fnv64a import "core:math" @@ -45,6 +46,10 @@ append :: proc { append_elem_string, } +append_soa :: proc { + append_soa_elem +} + ceil :: proc { math.ceil_f16, math.ceil_f16le, @@ -91,6 +96,7 @@ make :: proc { } make_soa :: proc { + make_soa_dynamic_array_len_cap, make_soa_slice, } @@ -113,6 +119,7 @@ vec2_64 :: proc { import "../../grime" + DISABLE_PROFILING :: false @(deferred_none = profile_end, disabled = DISABLE_PROFILING) diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index ccf174a..6c8d194 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -57,16 +57,17 @@ font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_ is_glyph_empty :: #force_inline 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 + if parser_is_glyph_empty( entry.parser_info, glyph_index ) do return true return false } -mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 ) { +mark_batch_codepoint_seen :: #force_inline proc "contextless" ( ctx : ^Context, lru_code : u64 ) { ctx.temp_codepoint_seen[lru_code] = true ctx.temp_codepoint_seen_num += 1 } reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { + profile(#procedure) clear_map( & ctx.temp_codepoint_seen ) ctx.temp_codepoint_seen_num = 0 } diff --git a/code/font/vefontcache/parser.odin b/code/font/vefontcache/parser.odin index 3e2b0a0..e656378 100644 --- a/code/font/vefontcache/parser.odin +++ b/code/font/vefontcache/parser.odin @@ -111,7 +111,7 @@ parser_unload_font :: proc( font : ^Parser_Font_Info ) } } -parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph) +parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph) { profile(#procedure) switch font.kind @@ -126,7 +126,7 @@ parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Fon return case .STB_TrueType: - glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( & font.stbtt_info, codepoint ) + glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint ) return } return Glyph(-1) @@ -220,12 +220,14 @@ parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^P return } -parser_get_glyph_box :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) +parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds : GlyphBounds) { profile(#procedure) + + bounds_0, bounds_1 : Vec2i + switch font.kind { - case .Freetype: freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) @@ -242,6 +244,7 @@ parser_get_glyph_box :: #force_inline proc "contextless" ( font : ^Parser_Font_I bounds_0 = { x0, y0 } bounds_1 = { x1, y1 } } + bounds = { vec2(bounds_0), vec2(bounds_1) } return } @@ -269,7 +272,7 @@ parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_i return } -parser_is_glyph_empty :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> b32 +parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32 { switch font.kind { @@ -287,7 +290,7 @@ parser_is_glyph_empty :: #force_inline proc "contextless" ( font : ^Parser_Font_ return false case .STB_TrueType: - return stbtt.IsGlyphEmpty( & font.stbtt_info, cast(c.int) glyph_index ) + return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index ) } return false } diff --git a/code/font/vefontcache/shaped_text.odin b/code/font/vefontcache/shaped_text.odin index d267eb6..eaabde8 100644 --- a/code/font/vefontcache/shaped_text.odin +++ b/code/font/vefontcache/shaped_text.odin @@ -76,7 +76,7 @@ shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ 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( & ctx.shaper_ctx, entry.parser_info, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) } shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) @@ -119,8 +119,8 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s 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 ) + 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) diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index 0df1f25..171daba 100644 --- a/code/font/vefontcache/shaper.odin +++ b/code/font/vefontcache/shaper.odin @@ -54,7 +54,7 @@ shaper_unload_font :: proc( ctx : ^Shaper_Info ) if blob != nil do harfbuzz.blob_destroy( blob ) } -shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info : ^Parser_Font_Info, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, +shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info : Parser_Font_Info, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, ascent, descent, line_gap : i32, size, size_scale : f32 ) { profile(#procedure) @@ -72,7 +72,7 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info line_height := ((ascent - descent + line_gap) * size_scale) position : Vec2 - shape_run :: #force_inline proc( parser_info : ^Parser_Font_Info, buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, + shape_run :: #force_inline proc( parser_info : Parser_Font_Info, buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, position : ^Vec2, max_line_width: ^f32, line_count: ^int, ascent, descent, line_gap, size, size_scale: f32, snap_shape_pos : b32, adv_snap_small_font_threshold : f32 ) diff --git a/code/font/vefontcache/vefontcache.odin b/code/font/vefontcache/vefontcache.odin index c5ac171..3933f34 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -225,6 +225,13 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, atlas.region_d.offset.x = atlas.width / 2 atlas.region_d.offset.y = 0 + atlas.regions = { + & atlas.region_a, + & atlas.region_b, + & atlas.region_c, + & atlas.region_d, + } + lru_init( & shape_cache.state, i32(shape_cache_params.capacity) ) shape_cache.storage, error = make( [dynamic]Shaped_Text, shape_cache_params.capacity ) @@ -471,36 +478,6 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : str return true } -draw_text_mono_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 -{ - profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - ctx.cursor_pos = {} - - position := position - if ctx.snap_width > 0 do position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width - if ctx.snap_height > 0 do position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height - - entry := & ctx.entries[ font ] - - ChunkType :: enum u32 { Visible, Formatting } - chunk_kind : ChunkType - chunk_start : int = 0 - chunk_end : int = 0 - - text_utf8_bytes := transmute([]u8) text_utf8 - text_chunk : string - - text_chunk = transmute(string) text_utf8_bytes[ : ] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_latin ) - ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) - } - return true -} - // ve_fontcache_Draw_List get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { assert( ctx != nil ) diff --git a/code/grime/profiler.odin b/code/grime/profiler.odin index 3a3a27c..a924ffc 100644 --- a/code/grime/profiler.odin +++ b/code/grime/profiler.odin @@ -31,3 +31,4 @@ profile_begin :: #force_inline proc "contextless" ( name : string, loc := #calle profile_end :: #force_inline proc "contextless" () { spall._buffer_end( & Module_Context.ctx, & Module_Context.buffer) } + diff --git a/code/sectr/font/provider.odin b/code/sectr/font/provider.odin index 4dea357..535b581 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -6,7 +6,7 @@ import ve "codebase:font/VEFontCache" import sokol_gfx "thirdparty:sokol/gfx" Font_Provider_Use_Freetype :: false -Font_Largest_Px_Size :: 72 +Font_Largest_Px_Size :: 152 Font_Size_Interval :: 2 Font_Default :: FontID { 0, "" }