diff --git a/code/font/vefontcache/LRU.odin b/code/font/vefontcache/LRU.odin index 4fcf1e9..49897a1 100644 --- a/code/font/vefontcache/LRU.odin +++ b/code/font/vefontcache/LRU.odin @@ -139,7 +139,7 @@ pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter ) } } -pool_list_move_to_front :: #force_inline proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter ) +pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter ) { using pool @@ -156,7 +156,7 @@ pool_list_move_to_front :: #force_inline proc "contextless" ( pool : ^Pool_List, front = iter } -pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue #no_bounds_check { +pool_list_peek_back :: #force_inline proc ( pool : Pool_List ) -> Pool_ListValue #no_bounds_check { assert( pool.back != - 1 ) value := pool.items[ pool.back ].value return value @@ -212,7 +212,7 @@ 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 : u64, must_find := false ) -> (LRU_Link, bool) { link, success := cache.table[key] return link, success } @@ -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 ) -> u64 { if cache.key_queue.size >= cache.capacity { - evict := pool_list_peek_back( & cache.key_queue ) + evict := pool_list_peek_back( cache.key_queue ) return evict } return 0xFFFFFFFFFFFFFFFF } -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 : u64, must_find := false ) -> i32 { iter, success := lru_find( cache, key, must_find ) if success == false { return -1 @@ -267,7 +267,7 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u } lru_refresh :: proc( cache : ^LRU_Cache, key : u64 ) { - link, success := lru_find( cache, key ) + link, success := lru_find( cache ^, key ) pool_list_erase( & cache.key_queue, link.ptr ) pool_list_push_front( & cache.key_queue, key ) link.ptr = cache.key_queue.front diff --git a/code/font/vefontcache/atlas.odin b/code/font/vefontcache/atlas.odin index a5ade52..e4b57b7 100644 --- a/code/font/vefontcache/atlas.odin +++ b/code/font/vefontcache/atlas.odin @@ -38,56 +38,6 @@ Atlas :: struct { regions : [4] ^Atlas_Region, } -atlas_bbox :: #force_inline proc "contextless" ( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) -{ - profile(#procedure) - switch region - { - case .A: - size.x = f32(atlas.region_a.width) - size.y = f32(atlas.region_a.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: - size.x = f32(atlas.region_b.width) - size.y = 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: - size.x = f32(atlas.region_c.width) - size.y = 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: - size.x = f32(atlas.region_d.width) - size.y = 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 .Ignore, .None, .E: - } - return -} - atlas_region_bbox :: proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2) { size.x = f32(region.width) @@ -135,28 +85,28 @@ 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 ) + next_evict_codepoint := lru_get_next_evicted( region.state ) + // assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF ) - atlas_index = lru_peek( & region.state, next_evict_codepoint, must_find = true ) - assert( atlas_index != -1 ) + atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true ) + // assert( atlas_index != -1 ) evicted := lru_put( & region.state, lru_code, atlas_index ) - assert( evicted == next_evict_codepoint ) + // assert( evicted == next_evict_codepoint ) } assert( lru_get( & region.state, lru_code ) != - 1 ) return } -check_and_reserve_slot_in_atlas :: #force_inline proc( ctx : ^Context, glyph_index : Glyph, +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 ) + // assert( glyph_index != -1 ) if ctx.temp_codepoint_seen_num > i32(cap(ctx.temp_codepoint_seen)) do return @@ -166,10 +116,10 @@ check_and_reserve_slot_in_atlas :: #force_inline proc( ctx : ^Context, glyph_ind 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 ) + next_evict_codepoint := lru_get_next_evicted( region.state ) success : bool found, success = ctx.temp_codepoint_seen[next_evict_codepoint] - assert(success != false) + // assert(success != false) if (found) { return } diff --git a/code/font/vefontcache/draw.odin b/code/font/vefontcache/draw.odin index c783c67..30ba7f0 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -1,13 +1,42 @@ package vefontcache -import "thirdparty:freetype" +import "base:runtime" import "core:slice" +import "thirdparty:freetype" Vertex :: struct { pos : Vec2, u, v : f32, } +GlyphBounds :: struct { + p0, p1 : Vec2 +} + +Glyph_Pack_Entry :: struct { + shape : Parser_Glyph_Shape, + bounds : GlyphBounds, + bounds_size : Vec2, + bounds_size_scaled : Vec2, + over_sample : Vec2, + translate : Vec2, + // scale : Vec2, + region_pos : Vec2, + region_size : Vec2, + lru_code : u64, + atlas_index : i32, + index : Glyph, + // shape_id : i32, + region_kind : Atlas_Region_Kind, + in_atlas : b8, + should_cache : b8, +} + +Glyph_Sub_Pack :: struct { + pack : #soa[]Glyph_Pack_Entry, + num : i32 +} + Draw_Call :: struct { pass : Frame_Buffer_Pass, start_index : u32, @@ -51,6 +80,11 @@ Glyph_Draw_Buffer :: struct { batch_x : i32, clear_draw_list : Draw_List, draw_list : Draw_List, + + glyph_pack : #soa[dynamic]Glyph_Pack_Entry, + oversized : #soa[dynamic]Glyph_Pack_Entry, + to_cache : #soa[dynamic]Glyph_Pack_Entry, + cached : #soa[dynamic]Glyph_Pack_Entry, } blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} ) @@ -246,26 +280,17 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 // } // TODO(Ed): Is it better to cache the glyph vertices for when it must be re-drawn (directly or two atlas)? -cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32 +generate_glyph_pass_draw_list :: #force_inline proc(ctx : ^Context, + glyph_index : Glyph, + glyph_shape : Parser_Glyph_Shape, + curve_quality : f32, + bounds : GlyphBounds, + scale, translate : Vec2 +) -> b32 { profile(#procedure) - if glyph_index == Glyph(0) { - return false - } - // Glyph shape handling are not abstractable between freetype and stb_truetype - // if entry.parser_info.kind == .Freetype { - // result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) - // return result - // } - - shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index) - assert(error == .None) - if len(shape) == 0 { - return false - } - - outside := Vec2{bounds_0.x - 21, bounds_0.y - 33} + outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33} draw := Draw_Call_Default draw.pass = Frame_Buffer_Pass.Glyph @@ -274,13 +299,12 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : path := &ctx.temp_path clear(path) - step := 1.0 / entry.curve_quality - for edge in shape do #partial switch edge.type + step := 1.0 / curve_quality + for edge in glyph_shape do #partial switch edge.type { case .Move: if len(path) > 0 { - // draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) + construct_filled_path(&ctx.draw_list, outside, path[:], scale, translate) clear(path) } fallthrough @@ -294,7 +318,7 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } p2 := Vec2{ f32(edge.x), f32(edge.y) } - for index : f32 = 1; index <= entry.curve_quality; index += 1 { + for index : f32 = 1; index <= curve_quality; index += 1 { alpha := index * step append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } ) } @@ -306,41 +330,33 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } p3 := Vec2{ f32(edge.x), f32(edge.y) } - for index : f32 = 1; index <= entry.curve_quality; index += 1 { + for index : f32 = 1; index <= curve_quality; index += 1 { alpha := index * step append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } ) } } if len(path) > 0 { - // draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) + construct_filled_path(&ctx.draw_list, outside, path[:], scale, translate) } draw.end_index = u32(len(ctx.draw_list.indices)) if draw.end_index > draw.start_index { append( & ctx.draw_list.calls, draw) } - - parser_free_shape(&entry.parser_info, shape) return true } -/* - Called by: - * 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, - font : Font_ID, +cache_glyph_to_atlas :: #force_no_inline proc ( ctx : ^Context, glyph_index : Glyph, + glyph_shape : Parser_Glyph_Shape, bounds : GlyphBounds, bounds_size : Vec2, region_pos : Vec2, region_size : Vec2, lru_code : u64, atlas_index : i32, - entry : ^Entry, + entry : Entry, // region_kind : Atlas_Region_Kind, // region : ^Atlas_Region, over_sample : Vec2 @@ -413,19 +429,48 @@ 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, bounds.p0, bounds.p1, glyph_draw_scale, glyph_draw_translate ) + generate_glyph_pass_draw_list( ctx, glyph_index, glyph_shape, entry.curve_quality, bounds, glyph_draw_scale, glyph_draw_translate ) } -// ve_fontcache_clear_Draw_List -clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) { - clear( & draw_list.calls ) - clear( & draw_list.indices ) - clear( & draw_list.vertices ) +// Constructs a triangle fan to fill a shape using the provided path outside_point represents the center point of the fan. +construct_filled_path :: #force_inline proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, + scale := Vec2 { 1, 1 }, + translate := Vec2 { 0, 0 } +) +{ + profile(#procedure) + v_offset := cast(u32) len(draw_list.vertices) + for point in path { + point := point + point.pos = point.pos * scale + translate + append( & draw_list.vertices, point ) + } + + outside_vertex := cast(u32) len(draw_list.vertices) + { + vertex := Vertex { + pos = outside_point * scale + translate, + u = 0, + v = 0, + } + append( & draw_list.vertices, vertex ) + } + + for index : u32 = 1; index < cast(u32) len(path); index += 1 { + indices := & draw_list.indices + to_add := [3]u32 { + outside_vertex, + v_offset + index - 1, + v_offset + index + } + append( indices, ..to_add[:] ) + } } -directly_draw_massive_glyph :: proc( ctx : ^Context, - entry : ^Entry, +generate_oversized_draw_list :: #force_no_inline proc( ctx : ^Context, + entry : Entry, glyph : Glyph, + glyph_shape : Parser_Glyph_Shape, bounds : GlyphBounds, bounds_size : Vec2, over_sample, position, scale : Vec2 ) @@ -441,7 +486,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, 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.p0, bounds.p1, glyph_draw_scale, glyph_draw_translate ) + generate_glyph_pass_draw_list( ctx, glyph, glyph_shape, entry.curve_quality, bounds, glyph_draw_scale, glyph_draw_translate ) bounds_scaled := bounds_size * entry.size_scale @@ -484,59 +529,9 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, append( & ctx.draw_list.calls, ..calls[:] ) } -// Constructs a triangle fan to fill a shape using the provided path -// outside_point represents the center point of the fan. -// -// Note(Original Author): -// WARNING: doesn't actually append Draw_Call; caller is responsible for actually appending the Draw_Call. -// ve_fontcache_draw_filled_path -draw_filled_path :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, - scale := Vec2 { 1, 1 }, - translate := Vec2 { 0, 0 } - // debug_print_verbose : b32 = false -) #no_bounds_check -{ - profile(#procedure) - // if debug_print_verbose - // { - // log("outline_path:") - // for point in path { - // vec := point.pos * scale + translate - // logf(" %0.2f %0.2f", vec.x, vec.y ) - // } - // } - - v_offset := cast(u32) len(draw_list.vertices) - for point in path { - point := point - point.pos = point.pos * scale + translate - append( & draw_list.vertices, point ) - } - - outside_vertex := cast(u32) len(draw_list.vertices) - { - vertex := Vertex { - pos = outside_point * scale + translate, - u = 0, - v = 0, - } - append( & draw_list.vertices, vertex ) - } - - for index : u32 = 1; index < cast(u32) len(path); index += 1 { - indices := & draw_list.indices - to_add := [3]u32 { - outside_vertex, - v_offset + index - 1, - v_offset + index - } - append( indices, ..to_add[:] ) - } -} - -draw_text_batch :: #force_inline proc (ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, +generate_cached_draw_list :: proc (ctx: ^Context, entry: Entry, shaped: Shaped_Text, // batch_start_idx, batch_end_idx : i32, - glyph_pack : #soa[]GlyphPackEntry, + glyph_pack : #soa[]Glyph_Pack_Entry, position, scale : Vec2, snap_width, snap_height : f32 ) { @@ -575,133 +570,144 @@ draw_text_batch :: #force_inline proc (ctx: ^Context, entry: ^Entry, shaped: ^Sh } } -GlyphBounds :: struct { - p0, p1 : Vec2 -} - -GlyphPackEntry :: struct { - 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, - region_kind : Atlas_Region_Kind, - in_atlas : b8, - should_cache : b8, -} - -Glyph_Sub_Pack :: struct { - pack : #soa[]GlyphPackEntry, - num : i32 -} - -// Helper for draw_text, all raw text content should be confirmed to be either formatting or visible shapes before getting cached. -draw_text_shape :: #force_inline proc( ctx : ^Context, +generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, font : Font_ID, - entry : ^Entry, - shaped : ^Shaped_Text, + entry : Entry, + shaped : Shaped_Text, position, scale : Vec2, snap_width, snap_height : f32 ) -> (cursor_pos : Vec2) #no_bounds_check { profile(#procedure) - oversized : Glyph_Sub_Pack = {} - to_cache : Glyph_Sub_Pack = {} - cached : Glyph_Sub_Pack = {} + atlas := & ctx.atlas + glyph_buffer := & ctx.glyph_buffer + draw_list := & ctx.draw_list + colour := ctx.colour + atlas_glyph_pad := atlas.glyph_padding + atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) } profile_begin("soa allocation") - glyph_pack, glyph_pack_alloc_error := make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator ) - - alloc_error : Allocator_Error - oversized.pack, alloc_error = make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator ) - to_cache.pack, alloc_error = make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator ) - cached.pack, alloc_error = make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator ) + // Make sure the packs are large enough for the shape + non_zero_resize_soa(& glyph_buffer.glyph_pack, len(shaped.glyphs)) + glyph_pack := & glyph_buffer.glyph_pack + oversized := & glyph_buffer.oversized + to_cache := & glyph_buffer.to_cache + cached := & glyph_buffer.cached + append_sub_pack :: #force_inline proc ( pack : ^#soa[dynamic]Glyph_Pack_Entry, entry : Glyph_Pack_Entry ) + { + raw := runtime.raw_soa_footer_dynamic_array(pack) + raw.len += 1 + pack[len(pack) - 1] = entry + } + sub_slice :: #force_inline proc "contextless" ( pack : ^#soa[dynamic]Glyph_Pack_Entry) -> #soa[]Glyph_Pack_Entry { return pack[:] } profile_end() - append_sub_pack :: #force_inline proc "contextless" ( sub : ^Glyph_Sub_Pack, entry : GlyphPackEntry ) + // profile_begin("soa allocation") + // glyph_pack_, glyph_pack_alloc_error := make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator ) + + // oversized_ : Glyph_Sub_Pack = {} + // to_cache_ : Glyph_Sub_Pack = {} + // cached_ : Glyph_Sub_Pack = {} + + // glyph_pack := & glyph_pack_ + // oversized := & oversized_ + // to_cache := & to_cache_ + // cached := & cached_ + + // alloc_error : Allocator_Error + // oversized.pack, alloc_error = make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator ) + // to_cache.pack, alloc_error = make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator ) + // cached.pack, alloc_error = make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator ) + // append_sub_pack :: #force_inline proc "contextless" ( sub : ^Glyph_Sub_Pack, entry : Glyph_Pack_Entry ) + // { + // sub.pack[sub.num] = entry + // sub.num += 1 + // } + // sub_slice :: #force_inline proc "contextless" ( sub : ^Glyph_Sub_Pack) -> #soa[]Glyph_Pack_Entry { return sub.pack[: sub.num] } + // profile_end() + + profile_begin("index") + for & glyph, index in glyph_pack { - sub.pack[sub.num] = entry - sub.num += 1 + // glyph.shape_id = cast(i32) index + glyph.index = shaped.glyphs[ index ] } - sub_slice :: #force_inline proc "contextless" ( sub : Glyph_Sub_Pack) -> #soa[]GlyphPackEntry { return sub.pack[: sub.num] } + profile_end() - atlas := & ctx.atlas - - SOA_Setup: + profile_begin("translate") + for & glyph, index in glyph_pack { - profile("SOA setup") + glyph.translate = position + (shaped.positions[index]) * scale + } + profile_end() - profile_begin("index & translate") - for & glyph, index in glyph_pack - { - glyph.shape_id = cast(i32) index - glyph.index = shaped.glyphs[ index ] + 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 & oversized segregation") + 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("segregation") + for & glyph, index in glyph_pack + { + region := atlas.regions[glyph.region_kind] + if glyph.region_kind == .E { + append_sub_pack(oversized, glyph) + continue } - profile_end() - profile_begin("translate") - for & glyph, index in glyph_pack - { - glyph.translate = position + (shaped.positions[index]) * scale + glyph.atlas_index = lru_get( & region.state, glyph.lru_code ) + glyph.in_atlas, glyph.should_cache = check_and_reserve_slot_in_atlas( ctx ^, glyph.index, glyph.lru_code, & glyph.atlas_index, region ) + glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) + + if glyph.should_cache { + profile("append to_cache") + append_sub_pack(to_cache, glyph) + mark_batch_codepoint_seen(ctx, glyph.lru_code) } - 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 + else { + profile("append cached") + append_sub_pack(cached, glyph) + mark_batch_codepoint_seen(ctx, glyph.lru_code) } - profile_end() + } + profile_end() - profile_begin("region & oversized segregation") - 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("to_cache: font parser shape generation") + for & glyph, index in sub_slice(to_cache) { + error : Allocator_Error + glyph.shape, error = parser_get_glyph_shape(entry.parser_info, glyph.index) + assert(error == .None) + } + profile_end() - profile_begin("caching setup") - for & glyph, index in glyph_pack - { - region := atlas.regions[glyph.region_kind] - if glyph.region_kind == .E { - append_sub_pack(& oversized, glyph) - continue - } - - glyph.atlas_index = lru_get( & region.state, glyph.lru_code ) - glyph.in_atlas, glyph.should_cache = check_and_reserve_slot_in_atlas( ctx, glyph.index, glyph.lru_code, & glyph.atlas_index, region ) - glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) - - if glyph.should_cache { - profile("append to_cache") - append_sub_pack(& to_cache, glyph) - mark_batch_codepoint_seen(ctx, glyph.lru_code) - 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.over_sample ) - continue - } - else { - profile("append cached") - append_sub_pack(& cached, glyph) - mark_batch_codepoint_seen(ctx, glyph.lru_code) - } - } - profile_end() + profile_begin("to_cache: caching to atlas") + for & glyph, index in sub_slice(to_cache) { + cache_glyph_to_atlas( ctx, glyph.index, glyph.shape, glyph.bounds, glyph.bounds_size, glyph.region_pos, glyph.region_size, glyph.lru_code, glyph.atlas_index, entry, glyph.over_sample ) + } + profile_end() + + for & glyph, index in sub_slice(to_cache) + { + parser_free_shape(entry.parser_info, glyph.shape) } - draw_text_batch( ctx, entry, shaped, sub_slice(to_cache), position, scale, snap_width, snap_height ) + generate_cached_draw_list( ctx, entry, shaped, sub_slice(to_cache), position, scale, snap_width, snap_height ) reset_batch_codepoint_state( ctx ) - draw_text_batch( ctx, entry, shaped, sub_slice(cached), position, scale, snap_width , snap_height ) + generate_cached_draw_list( ctx, entry, shaped, sub_slice(cached), position, scale, snap_width , snap_height ) reset_batch_codepoint_state( ctx ) profile_begin("generate oversized glyphs draw_list") @@ -709,7 +715,7 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, for & glyph, index in sub_slice(oversized) { - directly_draw_massive_glyph(ctx, entry, glyph.index, + generate_oversized_draw_list(ctx, entry, glyph.index, glyph.shape, glyph.bounds, glyph.bounds_size, glyph.over_sample, glyph.translate, scale ) @@ -717,6 +723,11 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, reset_batch_codepoint_state( ctx ) profile_end() + + // clear_soa(& glyph_buffer.glyph_pack) + clear_soa(& glyph_buffer.oversized) + clear_soa(& glyph_buffer.to_cache) + clear_soa(& glyph_buffer.cached) cursor_pos = position + shaped.end_cursor_pos * scale return @@ -744,8 +755,15 @@ flush_glyph_buffer_to_atlas :: #force_inline proc( ctx : ^Context ) } } +// ve_fontcache_clear_Draw_List +clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) { + clear( & draw_list.calls ) + clear( & draw_list.indices ) + clear( & draw_list.vertices ) +} + // ve_fontcache_merge_Draw_List -merge_draw_list :: #force_inline proc ( #no_alias dst, src : ^Draw_List ) +merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) { profile(#procedure) error : Allocator_Error diff --git a/code/font/vefontcache/mappings.odin b/code/font/vefontcache/mappings.odin index d22f5b0..be191b2 100644 --- a/code/font/vefontcache/mappings.odin +++ b/code/font/vefontcache/mappings.odin @@ -120,7 +120,7 @@ vec2_64 :: proc { import "../../grime" -DISABLE_PROFILING :: false +DISABLE_PROFILING :: true @(deferred_none = profile_end, disabled = DISABLE_PROFILING) profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index c753c7c..6b27a4f 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -44,6 +44,11 @@ reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) { raw.allocator = allocator } +reload_array_soa :: proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) { + raw := runtime.raw_soa_footer(self) + raw.allocator = allocator +} + reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) { raw := transmute( ^runtime.Raw_Map) self raw.allocator = allocator diff --git a/code/font/vefontcache/parser.odin b/code/font/vefontcache/parser.odin index e656378..8c18033 100644 --- a/code/font/vefontcache/parser.odin +++ b/code/font/vefontcache/parser.odin @@ -132,7 +132,7 @@ parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font return Glyph(-1) } -parser_free_shape :: #force_inline proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape ) +parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape ) { switch font.kind { @@ -140,11 +140,11 @@ parser_free_shape :: #force_inline proc( font : ^Parser_Font_Info, shape : Parse delete(shape) case .STB_TrueType: - stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) + stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) } } -parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 ) +parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 ) { switch font.kind { @@ -170,12 +170,12 @@ parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( fo } case .STB_TrueType: - stbtt.GetCodepointHMetrics( & font.stbtt_info, codepoint, & advance, & to_left_side_glyph ) + stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph ) } return } -parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32 +parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32 { switch font.kind { @@ -198,13 +198,13 @@ parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : ^ } case .STB_TrueType: - kern := stbtt.GetCodepointKernAdvance( & font.stbtt_info, prev_codepoint, codepoint ) + kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint ) return kern } return -1 } -parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info ) -> (ascent, descent, line_gap : i32 ) +parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info ) -> (ascent, descent, line_gap : i32 ) { switch font.kind { @@ -215,12 +215,12 @@ parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^P line_gap = i32(info.height) - (ascent - descent) case .STB_TrueType: - stbtt.GetFontVMetrics( & font.stbtt_info, & ascent, & descent, & line_gap ) + stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap ) } return } -parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds : GlyphBounds) +parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : GlyphBounds) { profile(#procedure) @@ -238,7 +238,7 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info case .STB_TrueType: x0, y0, x1, y1 : i32 - success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) + success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) // assert( success ) bounds_0 = { x0, y0 } @@ -248,7 +248,7 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info return } -parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) +parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) { switch font.kind { @@ -258,7 +258,7 @@ parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_i case .STB_TrueType: stb_shape : [^]stbtt.vertex - nverts := stbtt.GetGlyphShape( & font.stbtt_info, cast(i32) glyph_index, & stb_shape ) + nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape ) shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape shape_raw.data = stb_shape @@ -295,7 +295,7 @@ parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_I return false } -parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32 +parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32 { size_scale := size < 0.0 ? \ parser_scale_for_pixel_height( font, -size ) \ @@ -304,7 +304,7 @@ parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, siz return size_scale } -parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32 +parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32 { switch font.kind { case .Freetype: @@ -313,12 +313,12 @@ parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : ^Pars return size_scale case.STB_TrueType: - return stbtt.ScaleForPixelHeight( & font.stbtt_info, size ) + return stbtt.ScaleForPixelHeight( font.stbtt_info, size ) } return 0 } -parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32 +parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32 { switch font.kind { case .Freetype: @@ -342,7 +342,7 @@ parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font return size_scale case .STB_TrueType: - return stbtt.ScaleForMappingEmToPixels( & font.stbtt_info, size ) + return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size ) } return 0 } diff --git a/code/font/vefontcache/shaped_text.odin b/code/font/vefontcache/shaped_text.odin index eaabde8..123cd1a 100644 --- a/code/font/vefontcache/shaped_text.odin +++ b/code/font/vefontcache/shaped_text.odin @@ -1,6 +1,9 @@ package vefontcache Shaped_Text :: struct { + font : Font_ID, + entry : ^Entry, + glyphs : [dynamic]Glyph, positions : [dynamic]Vec2, end_cursor_pos : Vec2, @@ -19,9 +22,9 @@ shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte } } -ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) +ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) -shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape_text_uncached : ShapedTextUncachedProc ) -> ^Shaped_Text #no_bounds_check +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 @@ -45,23 +48,27 @@ shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, e } else { - next_evict_idx := lru_get_next_evicted( state ) - assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF ) + 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 ) + 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 ) } - shape_entry := & shape_cache.storage[ shape_cache_idx ] - shape_text_uncached( ctx, font, text_utf8, entry, shape_entry ) + storage_entry := & shape_cache.storage[ shape_cache_idx ] + shape_text_uncached( ctx, font, text_utf8, entry, storage_entry ) + + shaped_text = storage_entry ^ + return } - return & shape_cache.storage[ shape_cache_idx ] + shaped_text = shape_cache.storage[ shape_cache_idx ] + return } -shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) +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 ) @@ -70,16 +77,16 @@ shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ clear( & output.glyphs ) clear( & output.positions ) - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) + 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( & 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 ) +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 ) @@ -88,7 +95,7 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s clear( & output.glyphs ) clear( & output.positions ) - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) + 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) @@ -102,7 +109,7 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s for codepoint, index in text_utf8 { if prev_codepoint > 0 { - kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint ) + kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint ) position.x += f32(kern) * entry.size_scale } if codepoint == '\n' @@ -130,7 +137,7 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s }) } - advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) + advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) position.x += f32(advance) * entry.size_scale prev_codepoint = codepoint } diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index 171daba..4be038b 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 :: 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 3933f34..9c1d0a3 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -21,9 +21,13 @@ Entry :: struct { } Entry_Default :: Entry { - id = 0, - used = false, - size = 24.0, + id = 0, + used = false, + curve_quality = 2, + + // TODO(Ed): Remove size information. Its not conceptually needed. + // The size_scale can be computed only the fly when needed for a font. + size = 24.0, size_scale = 1.0, } @@ -147,10 +151,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, glyph_draw_params := Init_Glyph_Draw_Params_Default, shape_cache_params := Init_Shape_Cache_Params_Default, shaper_params := Init_Shaper_Params_Default, - default_curve_quality : u32 = 8 * 2, - entires_reserve : u32 = 8 * 256, - temp_path_reserve : u32 = 8 * 1024, - temp_codepoint_seen_reserve : u32 = 8 * 1024, + default_curve_quality : u32 = 2, + entires_reserve : u32 = 256, + temp_path_reserve : u32 = 1024, + temp_codepoint_seen_reserve : u32 = 1024, ) { assert( ctx != nil, "Must provide a valid context" ) @@ -282,6 +286,15 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, clear_draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 4 ) assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" ) + + glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator ) + oversized, error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator ) + to_cache, error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator ) + cached, error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator ) + // resize_soa(& glyph_pack, 1 * Kilobyte) + // resize_soa(& oversized, 1 * Kilobyte) + // resize_soa(& to_cache, 1 * Kilobyte) + // resize_soa(& cached, 1 * Kilobyte) } parser_init( & parser_ctx, parser_kind ) @@ -325,6 +338,11 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) reload_array( & glyph_buffer.clear_draw_list.indices, allocator ) reload_array( & glyph_buffer.clear_draw_list.vertices, allocator ) + reload_array_soa( & glyph_buffer.glyph_pack, allocator ) + reload_array_soa( & glyph_buffer.oversized, allocator ) + reload_array_soa( & glyph_buffer.to_cache, allocator ) + reload_array_soa( & glyph_buffer.cached, allocator ) + reload_array( & shape_cache.storage, allocator ) } @@ -369,6 +387,11 @@ shutdown :: proc( ctx : ^Context ) delete( glyph_buffer.clear_draw_list.indices ) delete( glyph_buffer.clear_draw_list.calls ) + delete_soa( glyph_buffer.glyph_pack) + delete_soa( glyph_buffer.oversized) + delete_soa( glyph_buffer.to_cache) + delete_soa( glyph_buffer.cached) + shaper_shutdown( & shaper_ctx ) parser_shutdown( & parser_ctx ) } @@ -403,7 +426,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, shaper_info = shaper_load_font( & shaper_ctx, label, data ) size = size_px - size_scale = parser_scale( & parser_info, size ) + size_scale = parser_scale( parser_info, size ) if glyph_curve_quality == 0 { curve_quality = f32(ctx.default_curve_quality) @@ -457,24 +480,54 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : str 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 + position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width + position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height - entry := & ctx.entries[ font ] + entry := ctx.entries[ font ] - ChunkType :: enum u32 { Visible, Formatting } - chunk_kind : ChunkType - chunk_start : int = 0 - chunk_end : int = 0 + shape := shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) + ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) + return true +} - text_utf8_bytes := transmute([]u8) text_utf8 - text_chunk : string +draw_text_no_snap :: #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) ) - text_chunk = transmute(string) text_utf8_bytes[ : ] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_advanced ) - ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) - } + ctx.cursor_pos = {} + + entry := ctx.entries[ font ] + shape := shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) + ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) + return true +} + +// For high performance: Resolve the shape and track it to reduce iteration overhead +draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, shape : Shaped_Text, position, scale : Vec2 ) -> b32 +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + position := position + position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width + position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height + + entry := ctx.entries[ font ] + ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) + return true +} + +// For high performance: Resolve the shape and track it to reduce iteration overhead +draw_text_shape_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, shape : Shaped_Text, position, scale : Vec2 ) -> b32 +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + entry := ctx.entries[ font ] + ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) return true } @@ -522,8 +575,8 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, text_ut assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := &ctx.entries[font] - shaped := shape_text_cached(ctx, font, text_utf8, entry, shape_text_uncached_advanced ) + entry := ctx.entries[font] + shaped := shaper_shape_text_cached(ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) return shaped.size } @@ -533,7 +586,7 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID assert( font >= 0 && int(font) < len(ctx.entries) ) entry := & ctx.entries[ font ] - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) + ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) ascent = (f32(ascent_i32) * entry.size_scale) descent = (f32(descent_i32) * entry.size_scale) @@ -543,6 +596,34 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID //#endregion("metrics") +//#region("shaping") + +shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, allocator := context.allocator ) -> Shaped_Text +{ + entry := ctx.entries[ font ] + return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_from_text_latin ) +} + +shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> Shaped_Text +{ + entry := ctx.entries[ font ] + return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) +} + +// User handled shaped text. Will not be cached +shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, allocator := context.allocator ) -> Shaped_Text +{ + return {} +} + +// User handled shaped text. Will not be cached +shape_text_advanced_uncahed :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, allocator := context.allocator ) -> Shaped_Text +{ + return {} +} + +//#endregion("shaping") + // Can be used with hot-reload clear_atlas_region_caches :: proc(ctx : ^Context) { diff --git a/code/grime/profiler.odin b/code/grime/profiler.odin index a924ffc..654578d 100644 --- a/code/grime/profiler.odin +++ b/code/grime/profiler.odin @@ -15,7 +15,7 @@ set_profiler_module_context :: #force_inline proc "contextless" ( ctx : ^SpallPr Module_Context = ctx } -DISABLE_PROFILING :: false +DISABLE_PROFILING :: true @(deferred_none = profile_end, disabled = DISABLE_PROFILING) profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index 96b64ba..645113a 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -158,7 +158,8 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State font := font if font.key == Font_Default.key do font = default_font - draw_text_string_pos_extent( content, font, size, pos, color ) + shape := shape_text_cached( content, font, size, app_config().font_size_screen_scalar ) + draw_text_shape_pos_extent( shape, font, size, pos, color ) } debug_text :: proc( format : string, args : ..any ) @@ -578,8 +579,8 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_ cam != nil ? cam.position.y : 0, } - screen_size := screen_extent * 2 - screen_scaled := (1.0 / screen_size) + screen_size := screen_extent * 2 + screen_size_norm := (1.0 / screen_size) layer_left : b32 = true for layer_left @@ -646,10 +647,12 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_ text_id += 1 if cam != nil { - draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam_offset, screen_size, screen_scaled, cam.zoom, entry.color ) + // draw_text_shape_pos_extent_zoomed( entry.shape, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color ) + draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color ) } else { draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color ) + // draw_text_string_pos_extent( entry.shape, font, entry.font_size, entry.position, entry.color ) } } @@ -897,34 +900,64 @@ draw_rect_rounded_border :: proc(rect: Range2, radii: [4]f32, border_width: f32, } // Draw text using a string and normalized render coordinates -draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 ) +draw_text_string_pos_norm :: #force_inline proc( text : string, id : FontID, font_size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 ) { state := get_state(); using state width := app_window.extent.x * 2 height := app_window.extent.y * 2 - ve_id, resolved_size := font_provider_resolve_draw_id( id, size * config.font_size_screen_scalar ) + ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size * config.font_size_screen_scalar ) color_norm := normalize_rgba8(color) + screen_size_norm := Vec2{1 / width, 1 / height} + ve.set_colour( & font_provider_ctx.ve_ctx, color_norm ) - ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale * (1 / config.font_size_screen_scalar) ) + ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, text, pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar) ) return } // Draw text using a string and extent-based screen coordinates -draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White ) +draw_text_string_pos_extent :: #force_inline proc( text : string, id : FontID, font_size : f32, pos : Vec2, color := Color_White ) { - // profile(#procedure) + profile(#procedure) state := get_state(); using state screen_size := app_window.extent * 2 render_pos := screen_to_render_pos(pos) normalized_pos := render_pos * (1.0 / screen_size) - draw_text_string_pos_norm( content, id, size, normalized_pos, color ) + draw_text_string_pos_norm( text, id, font_size, normalized_pos, color ) } -draw_text_string_pos_extent_zoomed :: #force_inline proc( content : string, id : FontID, size : f32, pos, cam_offset, screen_size, screen_scaled : Vec2, zoom : f32, color := Color_White ) +// Draw text using a string and normalized render coordinates +draw_text_shape_pos_norm :: #force_inline proc( shape : ShapedText, id : FontID, font_size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 ) { - // profile(#procedure) + state := get_state(); using state + width := app_window.extent.x * 2 + height := app_window.extent.y * 2 + + ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size * config.font_size_screen_scalar ) + color_norm := normalize_rgba8(color) + + screen_size_norm := Vec2{1 / width, 1 / height} + + ve.set_colour( & font_provider_ctx.ve_ctx, color_norm ) + ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, shape, pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar) ) + return +} + +// Draw text using a string and extent-based screen coordinates +draw_text_shape_pos_extent :: #force_inline proc( shape : ShapedText, id : FontID, font_size : f32, pos : Vec2, color := Color_White ) +{ + profile(#procedure) + state := get_state(); using state + screen_size := app_window.extent * 2 + render_pos := screen_to_render_pos(pos) + normalized_pos := render_pos * (1.0 / screen_size) + draw_text_shape_pos_norm( shape, id, font_size, normalized_pos, color ) +} + +draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : FontID, size : f32, pos, cam_offset, screen_size, screen_size_norm : Vec2, zoom : f32, color := Color_White ) +{ + profile(#procedure) state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state. zoom_adjust_size := size * zoom @@ -935,16 +968,16 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( content : string, id : pos_offset := (pos + cam_offset) render_pos := ws_view_to_render_pos(pos) - normalized_pos := render_pos * screen_scaled + normalized_pos := render_pos * screen_size_norm ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size ) - text_scale : Vec2 = screen_scaled + text_scale : Vec2 = screen_size_norm // if config.cam_zoom_mode == .Smooth { f32_resolved_size := f32(resolved_size) diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size - text_scale = diff_scalar * screen_scaled + text_scale = diff_scalar * screen_size_norm text_scale.x = clamp( text_scale.x, 0, screen_size.x ) text_scale.y = clamp( text_scale.y, 0, screen_size.y ) } @@ -954,7 +987,42 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( content : string, id : color_norm := normalize_rgba8(color) ve.set_colour( & font_provider_ctx.ve_ctx, color_norm ) - ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, content, normalized_pos, text_scale ) + ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, text, normalized_pos, text_scale ) +} + +draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id : FontID, size : f32, pos, cam_offset, screen_size, screen_size_norm : Vec2, zoom : f32, color := Color_White ) +{ + profile(#procedure) + state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state. + + zoom_adjust_size := size * zoom + + // Over-sample font-size for any render under a camera + over_sample : f32 = f32(state.config.font_size_canvas_scalar) + zoom_adjust_size *= over_sample + + pos_offset := (pos + cam_offset) + render_pos := ws_view_to_render_pos(pos) + normalized_pos := render_pos * screen_size_norm + + ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size ) + + text_scale : Vec2 = screen_size_norm + // if config.cam_zoom_mode == .Smooth + { + f32_resolved_size := f32(resolved_size) + diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size + text_scale = diff_scalar * screen_size_norm + text_scale.x = clamp( text_scale.x, 0, screen_size.x ) + text_scale.y = clamp( text_scale.y, 0, screen_size.y ) + } + + // Down-sample back + text_scale /= over_sample + + color_norm := normalize_rgba8(color) + ve.set_colour( & font_provider_ctx.ve_ctx, color_norm ) + ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, shape, normalized_pos, text_scale ) } // TODO(Ed): Eventually the workspace will need a viewport for drawing text diff --git a/code/sectr/font/provider.odin b/code/sectr/font/provider.odin index 535b581..36d17fb 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -34,6 +34,8 @@ FontProviderContext :: struct using render : VE_RenderData, } +ShapedText :: ve.Shaped_Text + font_provider_startup :: proc( ctx : ^FontProviderContext ) { profile(#procedure) @@ -116,7 +118,7 @@ font_load :: proc(path_file : string, Font_Use_Default_Size :: f32(0.0) -font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Size ) -> (ve_id :ve.Font_ID, resolved_size : i32) +font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_Use_Default_Size ) -> (ve_id :ve.Font_ID, resolved_size : i32) { provider_data := get_state().font_provider_ctx; using provider_data @@ -130,7 +132,7 @@ font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Siz return } -measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 +measure_text_size :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 { ve_id, size := font_provider_resolve_draw_id( font, font_size ) measured := ve.measure_text_size( & get_state().font_provider_ctx.ve_ctx, ve_id, text ) @@ -143,3 +145,10 @@ get_font_vertical_metrics :: #force_inline proc ( font : FontID, font_size := Fo ascent, descent, line_gap = ve.get_font_vertical_metrics( & get_state().font_provider_ctx.ve_ctx, ve_id ) return } + +shape_text_cached :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, scalar : f32 ) -> ShapedText +{ + ve_id, size := font_provider_resolve_draw_id( font, font_size * scalar ) + shape := ve.shape_text_advanced( & get_state().font_provider_ctx.ve_ctx, ve_id, text ) + return shape +} diff --git a/code/sectr/ui/core/base.odin b/code/sectr/ui/core/base.odin index b1db1d9..6dcf6d9 100644 --- a/code/sectr/ui/core/base.odin +++ b/code/sectr/ui/core/base.odin @@ -58,13 +58,21 @@ UI_ScalarConstraint :: struct { UI_Scalar2 :: [Axis2.Count]UI_Scalar -// UI_BoxFlags_Stack_Size :: 512 -UI_Layout_Stack_Size :: 512 -UI_Style_Stack_Size :: 512 -UI_Parent_Stack_Size :: 512 -// UI_Built_Boxes_Array_Size :: 8 -UI_Built_Boxes_Array_Size :: 56 * Kilobyte -UI_BoxCache_TableSize :: 8 * Kilobyte +// The UI_Box's actual positioning and sizing +// There is an excess of rectangles here for debug puproses. +UI_Computed :: struct { + // anchors : Range2, // Bounds for anchors within parent + // margins : Range2, // Bounds for margins within parent + padding : Range2, // Bounds for padding's starting bounds (will be offset by border if there is one), only here for debug vis + + bounds : Range2, // Bounds for box itself + content : Range2, // Bounds for content (text or children) + text_shape : ShapedText, // Text string processed into shape optimial for processing by text draw_list generator. + text_pos : Vec2, // Position of text within content + text_size : Vec2, // Size of text within content + fresh : b32, // If the auto-layout has been computed for the current frame +} + UI_RenderEntry :: struct { info : UI_RenderBoxInfo, @@ -75,16 +83,6 @@ UI_RenderEntry :: struct { UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry) -// UI_RenderBoxInfo :: struct { -// using computed : UI_Computed, -// using style : UI_Style, -// text : StrRunesPair, -// font_size : UI_Scalar, -// border_width : UI_Scalar, -// label : StrRunesPair, -// layer_signal : b32, -// } - UI_RenderBoxInfo :: struct { bounds : Range2, corner_radii : [Corner.Count]f32, @@ -96,6 +94,7 @@ UI_RenderBoxInfo :: struct { UI_RenderTextInfo :: struct { text : string, + shape : ShapedText, position : Vec2, color : RGBA8, font : FontID, @@ -110,6 +109,14 @@ UI_RenderMethod :: enum u32 { UI_Render_Method :: UI_RenderMethod.Layers +// UI_BoxFlags_Stack_Size :: 512 +UI_Layout_Stack_Size :: 512 +UI_Style_Stack_Size :: 512 +UI_Parent_Stack_Size :: 512 +// UI_Built_Boxes_Array_Size :: 8 +UI_Built_Boxes_Array_Size :: 56 * Kilobyte +UI_BoxCache_TableSize :: 8 * Kilobyte + // TODO(Ed): Rename to UI_Context UI_State :: struct { // TODO(Ed) : Use these? @@ -267,7 +274,20 @@ ui_graph_build_end :: proc( ui : ^UI_State ) for current := ui.root.first; current != nil; current = ui_box_tranverse_next_depth_first( current, bypass_intersection_test = true, ctx = ui ) { - if ! current.computed.fresh { + if ! current.computed.fresh + { + if len(current.text.str) > 0 { + app_window := get_state().app_window + screen_extent := app_window.extent + screen_size := screen_extent * 2 + screen_size_norm := 1 / screen_size + + font_size_screen_scalar := app_config().font_size_screen_scalar + + // over_sample : f32 = f32(get_state().config.font_size_canvas_scalar) + + current.computed.text_shape = shape_text_cached( current.text.str, current.style.font, current.layout.font_size, 1.0 ) + } ui_box_compute_layout( current ) } @@ -288,10 +308,11 @@ ui_graph_build_end :: proc( ui : ^UI_State ) array_append(& ui.render_list_box, entry_box) // TODO(Ed): It may be better to let VEFontCache handle processing empty strings. - if len(current.text.str) > 0 - { + // if len(current.text.str) > 0 + // { entry_text := UI_RenderTextInfo { text = current.text.str, + shape = current.computed.text_shape, position = current.computed.text_pos, color = current.style.text_color, font = current.style.font, @@ -299,7 +320,7 @@ ui_graph_build_end :: proc( ui : ^UI_State ) } entry_text.layer_signal = different_ancestory array_append(& ui.render_list_text, entry_text) - } + // } previous_layer = current.ancestors } diff --git a/code/sectr/ui/core/layout.odin b/code/sectr/ui/core/layout.odin index 6955605..122ab7e 100644 --- a/code/sectr/ui/core/layout.odin +++ b/code/sectr/ui/core/layout.odin @@ -46,21 +46,6 @@ UI_Align_Presets :: UI_Align_Presets_Struct { text_centered = {0.5, 0.5}, } - -// The UI_Box's actual positioning and sizing -// There is an excess of rectangles here for debug puproses. -UI_Computed :: struct { - // anchors : Range2, // Bounds for anchors within parent - // margins : Range2, // Bounds for margins within parent - padding : Range2, // Bounds for padding's starting bounds (will be offset by border if there is one), only here for debug vis - - bounds : Range2, // Bounds for box itself - content : Range2, // Bounds for content (text or children) - text_pos : Vec2, // Position of text within content - text_size : Vec2, // Size of text within content - fresh : b32, // If the auto-layout has been computed for the current frame -} - UI_LayoutDirection_XY :: enum(i32) { Left_To_Right, Right_to_Left, diff --git a/code/sectr/ui/core/layout_compute.odin b/code/sectr/ui/core/layout_compute.odin index 3d2f7ae..cd2ed72 100644 --- a/code/sectr/ui/core/layout_compute.odin +++ b/code/sectr/ui/core/layout_compute.odin @@ -5,7 +5,7 @@ ui_box_compute_layout :: proc( box : ^UI_Box, ancestors_layout_required : b32 = false, root_layout_required : b32 = false ) { - // profile("Layout Box") + profile("Layout Box") state := get_state() ui := state.ui_context using box @@ -73,12 +73,15 @@ ui_box_compute_layout :: proc( box : ^UI_Box, text_size : Vec2 if len(box.text.str) > 0 { - if layout.font_size == computed.text_size.y { - text_size = computed.text_size - } - else { - text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 ) - } + + + text_size = computed.text_shape.size + // if layout.font_size == computed.text_size.y { + // text_size = computed.text_size + // } + // else { + // text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 ) + // } } if size_to_text { diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 99726a2..e3ad369 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -201,9 +201,9 @@ 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 += $flag_optimize_speed # $build_args += $falg_optimize_aggressive $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb