WIP - VEFontCahe: More progress on optimizing codepaths with SOA

This commit is contained in:
Edward R. Gonzalez 2025-01-01 22:11:41 -05:00
parent dd1752f84b
commit 4adbfaa9fd
11 changed files with 251 additions and 283 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 )

View File

@ -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 )

View File

@ -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)
}

View File

@ -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, "" }