From cb6053395c7a49e1b16b9b89f7920ddf9edfc145 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 3 Jan 2025 23:06:31 -0500 Subject: [PATCH] WIP - VEFontCache: Major changes * Add back proper batching (busted the rendering for it though..) * Some reogonzation of definitions and procedure args * CURRENTLY BROKEN: Something went wrong with the calculations for text positioning.. --- code/font/vefontcache/Readme.md | 1 + code/font/vefontcache/draw.odin | 239 ++++++++++++++++--------- code/font/vefontcache/mappings.odin | 1 - code/font/vefontcache/misc.odin | 56 +++--- code/font/vefontcache/parser.odin | 3 +- code/font/vefontcache/shaper.odin | 135 ++++++++------ code/font/vefontcache/vefontcache.odin | 206 +++++++++++---------- code/grime/hashing.odin | 4 + code/grime/profiler.odin | 2 +- code/sectr/app/state.odin | 2 +- code/sectr/engine/render.odin | 21 ++- code/sectr/font/provider.odin | 6 +- scripts/build.ps1 | 4 +- 13 files changed, 390 insertions(+), 290 deletions(-) diff --git a/code/font/vefontcache/Readme.md b/code/font/vefontcache/Readme.md index fba2547..d83c753 100644 --- a/code/font/vefontcache/Readme.md +++ b/code/font/vefontcache/Readme.md @@ -22,6 +22,7 @@ Note: freetype and harfbuzz could technically be gutted if the user removes thei * Macro defines have been coverted (mostly) to runtime parameters * Support for hot_reloading * Curve quality step interpolation for glyph rendering can be set on a per font basis. +* All codepaths heavily changed (its faster) ## TODOs diff --git a/code/font/vefontcache/draw.odin b/code/font/vefontcache/draw.odin index 88d32f1..276d273 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -89,7 +89,13 @@ Frame_Buffer_Pass :: enum u32 { Target_Uncached = 4, } -Glyph_Draw_Buffer :: struct { +Glyph_Batch_Cache :: struct { + table : map[u32]b8, + num : i32, + cap : i32, +} + +Glyph_Draw_Buffer :: struct{ over_sample : Vec2, batch : i32, width : i32, @@ -100,6 +106,10 @@ Glyph_Draw_Buffer :: struct { clear_draw_list : Draw_List, draw_list : Draw_List, + // TODO(Ed): Get this working properly again. + batch_cache : Glyph_Batch_Cache, + shape_gen_scratch : [dynamic]Vertex, + glyph_pack : #soa[dynamic]Glyph_Pack_Entry, oversized : [dynamic]i32, to_cache : [dynamic]i32, @@ -108,9 +118,7 @@ Glyph_Draw_Buffer :: struct { 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} ) { - profile(#procedure) - // logf("Blitting: xy0: %0.2f, %0.2f xy1: %0.2f, %0.2f uv0: %0.2f, %0.2f uv1: %0.2f, %0.2f", - // p0.x, p0.y, p1.x, p1.y, uv0.x, uv0.y, uv1.x, uv1.y); + // profile(#procedure) v_offset := cast(u32) len(draw_list.vertices) quadv : [4]Vertex = { @@ -142,12 +150,14 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 } // 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 } +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) + // profile(#procedure) v_offset := cast(u32) len(draw_list.vertices) for point in path { point := point @@ -184,7 +194,6 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V ) { profile(#procedure) - outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33} draw := Draw_Call_Default @@ -266,7 +275,6 @@ cache_glyph_to_atlas :: #force_no_inline proc ( ) { profile(#procedure) - batch_x := cast(f32) glyph_buf_Batch_x ^ buffer_padding_scaled := glyph_padding * over_sample buffer_bounds_scale := (bounds_size_scaled) * over_sample @@ -332,36 +340,41 @@ cache_glyph_to_atlas :: #force_no_inline proc ( generate_glyph_pass_draw_list( draw_list, temp_path, glyph_shape, curve_quality, bounds, glyph_transform.scale, glyph_transform.pos ) } -generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, - entry : Entry, - shaped : Shaped_Text, - position, target_scale : Vec2, - snap_width, snap_height : f32 +generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape : Shaped_Text, + atlas : ^Atlas, + glyph_buffer : ^Glyph_Draw_Buffer, + + colour : Colour, + entry : Entry, + font_scale : f32, + + target_position : Vec2, + target_scale : Vec2, + snap_width : f32, + snap_height : f32 ) -> (cursor_pos : Vec2) #no_bounds_check { profile(#procedure) - colour := ctx.colour - colour.a = 1.0 + ctx.alpha_scalar + mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : u32 ) { + cache.table[lru_code] = true + cache.num += 1 + } + reset_batch :: #force_inline proc( cache : ^Glyph_Batch_Cache ) { + clear_map( & cache.table ) + cache.num = 0 + } - atlas := & ctx.atlas - glyph_buffer := & ctx.glyph_buffer - draw_list := & ctx.draw_list atlas_glyph_pad := atlas.glyph_padding atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) } glyph_buffer_size := Vec2 { f32(glyph_buffer.width), f32(glyph_buffer.height) } - profile_begin("soa prep") // Make sure the packs are large enough for the shape glyph_pack := & glyph_buffer.glyph_pack oversized := & glyph_buffer.oversized to_cache := & glyph_buffer.to_cache cached := & glyph_buffer.cached - non_zero_resize_soa(glyph_pack, len(shaped.glyphs)) - clear(oversized) - clear(to_cache) - clear(cached) - profile_end() + non_zero_resize_soa(glyph_pack, len(shape.glyphs)) append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 ) { @@ -374,7 +387,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, profile_begin("index") for & glyph, index in glyph_pack { - glyph.index = shaped.glyphs[ index ] + glyph.index = shape.glyphs[ index ] glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index) } profile_end() @@ -382,7 +395,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, profile_begin("translate") for & glyph, index in glyph_pack { - glyph.position = position + (shaped.positions[index]) * target_scale + glyph.position = target_position + (shape.positions[index]) * target_scale } profile_end() @@ -390,9 +403,9 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, for & glyph, index in glyph_pack { glyph.bounds = parser_get_bounds( entry.parser_info, glyph.index ) - glyph.bounds_scaled = { glyph.bounds.p0 * entry.size_scale, glyph.bounds.p1 * entry.size_scale } + glyph.bounds_scaled = { glyph.bounds.p0 * font_scale, glyph.bounds.p1 * font_scale } glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0 - glyph.bounds_size_scaled = glyph.bounds_size * entry.size_scale + glyph.bounds_size_scaled = glyph.bounds_size * font_scale glyph.scale = glyph.bounds_size_scaled + atlas.glyph_padding } profile_end() @@ -406,7 +419,11 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, } profile_end() - profile_begin("atlas slot resolution & to_cache/cached segregation") + profile_begin("batching") + clear(oversized) + clear(to_cache) + clear(cached) + for & glyph, index in glyph_pack { if glyph.region_kind == .None { @@ -428,43 +445,106 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, region := atlas.regions[glyph.region_kind] glyph.atlas_index = lru_get( & region.state, glyph.lru_code ) - to_cache_check: + // Glyphs are prepared in batches based on the capacity of the batch cache. + Prepare_For_Batch: { - if ctx.temp_codepoint_seen_num <= i32(cap(ctx.temp_codepoint_seen)) - { - if glyph.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 ) - found, success := ctx.temp_codepoint_seen[next_evict_codepoint] - assert(success != false) - if (found) { - break to_cache_check - } - } + // Determine if we hit the limit for this batch. + if glyph_buffer.batch_cache.num >= glyph_buffer.batch_cache.cap do break Prepare_For_Batch - profile("append to_cache") - glyph.atlas_index = atlas_reserve_slot(region, glyph.lru_code) - glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) - append_sub_pack(to_cache, cast(i32) index) - mark_batch_codepoint_seen(ctx, glyph.lru_code) - continue + if glyph.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_glyph := lru_get_next_evicted( region.state ) + found_take_slow_path, success := glyph_buffer.batch_cache.table[next_evict_glyph] + assert(success != false) + if (found_take_slow_path) { + break Prepare_For_Batch + } } + + profile("append to_cache") + glyph.atlas_index = atlas_reserve_slot(region, glyph.lru_code) + glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) + append_sub_pack(to_cache, cast(i32) index) + mark_glyph_seen(& glyph_buffer.batch_cache, glyph.lru_code) + continue } + + profile("append cached") + glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) + append_sub_pack(cached, cast(i32) index) + mark_glyph_seen(& glyph_buffer.batch_cache, glyph.lru_code) + continue } - profile("append cached") - glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) - append_sub_pack(cached, cast(i32) index) - mark_batch_codepoint_seen(ctx, glyph.lru_code) + // Batch has been prepared for a set of glyphs time to generate glyphs. + batch_generate_glyphs_draw_list( draw_list, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized), + atlas, + glyph_buffer, + atlas_size, + glyph_buffer_size, + entry, + colour, + font_scale, + target_scale + ) + + reset_batch( & glyph_buffer.batch_cache) + clear(oversized) + clear(to_cache) + clear(cached) } profile_end() + // Last batch pass + batch_generate_glyphs_draw_list( draw_list, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized), + atlas, + glyph_buffer, + atlas_size, + glyph_buffer_size, + entry, + colour, + font_scale, + target_scale + ) + + reset_batch( & glyph_buffer.batch_cache) + clear(oversized) + clear(to_cache) + clear(cached) + + cursor_pos = target_position + shape.end_cursor_pos * target_scale + return +} + +batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, + glyph_pack : ^#soa[dynamic]Glyph_Pack_Entry, + cached : []i32, + to_cache : []i32, + oversized : []i32, + + atlas : ^Atlas, + glyph_buffer : ^Glyph_Draw_Buffer, + atlas_size : Vec2, + glyph_buffer_size : Vec2, + + entry : Entry, + colour : Colour, + font_scale : Vec2, + target_scale : Vec2, +) +{ + profile(#procedure) + + when ENABLE_DRAW_TYPE_VIS { + colour := colour + } + profile_begin("transform & quad compute") - for id, index in sub_slice(cached) + for id, index in cached { // Quad to for drawing atlas slot to target glyph := & glyph_pack[id] @@ -475,7 +555,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, quad.src_pos = (glyph.region_pos) to_target_space( & quad.src_pos, & quad.src_scale, atlas_size ) } - for id, index in sub_slice(to_cache) + for id, index in to_cache { glyph := & glyph_pack[id] @@ -489,17 +569,17 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, // The glyph buffer space transform for generate_glyph_pass_draw_list transform := & glyph.draw_transform - transform.scale = entry.size_scale * glyph_buffer.over_sample + transform.scale = font_scale * glyph_buffer.over_sample transform.pos = -1 * (glyph.bounds.p0) * transform.scale + atlas.glyph_padding // Unlike with oversized, this cannot be finished here as its final value is dependent on glyph_buffer.batch_x allocation } - for id, index in sub_slice(oversized) + for id, index in oversized { glyph := & glyph_pack[id] // The glyph buffer space transform for generate_glyph_pass_draw_list transform := & glyph.draw_transform - transform.scale = entry.size_scale * glyph.over_sample + transform.scale = font_scale * glyph.over_sample transform.pos = -1 * glyph.bounds.p0 * transform.scale + vec2(atlas.glyph_padding) to_glyph_buffer_space( & transform.pos, & transform.scale, glyph_buffer_size ) // Oversized will use a cleared glyph_buffer every time. @@ -516,8 +596,6 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, } profile_end() - profile_end() - generate_cached_draw_list :: #force_inline proc (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : Colour ) { profile(#procedure) @@ -540,13 +618,12 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, } profile_begin("to_cache: caching to atlas") - for id, index in sub_slice(to_cache) { + for id, index in to_cache { error : Allocator_Error glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index) assert(error == .None) } - - for id, index in sub_slice(to_cache) + for id, index in to_cache { profile("glyph") when ENABLE_DRAW_TYPE_VIS { @@ -561,8 +638,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x, - - & ctx.temp_path, + & glyph_buffer.shape_gen_scratch, glyph.shape, glyph.bounds_scaled, @@ -580,37 +656,33 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, ) } flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) - - for id, index in sub_slice(to_cache) do parser_free_shape(entry.parser_info, glyph_pack[id].shape) + for id, index in to_cache do parser_free_shape(entry.parser_info, glyph_pack[id].shape) profile_end() - generate_cached_draw_list( draw_list, glyph_pack[:], sub_slice(to_cache), ctx.colour ) + generate_cached_draw_list( draw_list, glyph_pack[:], to_cache, colour ) profile_begin("generate_cached_draw_list: to_cache") - when ENABLE_DRAW_TYPE_VIS { colour.r = 1.0 colour.g = 1.0 colour.b = 1.0 } - generate_cached_draw_list( draw_list, glyph_pack[:], sub_slice(cached), ctx.colour ) - reset_batch_codepoint_state( ctx ) + generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour ) profile_end() flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) profile_begin("generate oversized glyphs draw_list") - for id, index in sub_slice(oversized) { + for id, index in oversized { error : Allocator_Error glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index) assert(error == .None) } - - for id, index in sub_slice(oversized) + for id, index in oversized { flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x) - generate_glyph_pass_draw_list( draw_list, & ctx.temp_path, + generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, glyph_pack[id].shape, entry.curve_quality, glyph_pack[id].bounds, @@ -631,7 +703,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, { using draw_to_target pass = .Target_Uncached - colour = ctx.colour + colour = colour start_index = u32(len(draw_list.indices)) blit_quad( draw_list, @@ -650,13 +722,8 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context, } append( & draw_list.calls, ..calls[:] ) } - - profile_begin("font parser shape cleanup") - for id, index in sub_slice(oversized) do parser_free_shape(entry.parser_info, glyph_pack[id].shape) + for id, index in oversized do parser_free_shape(entry.parser_info, glyph_pack[id].shape) profile_end() - - cursor_pos = position + shaped.end_cursor_pos * target_scale - return } // Flush the content of the glyph_buffers draw lists to the main draw list diff --git a/code/font/vefontcache/mappings.odin b/code/font/vefontcache/mappings.odin index 8f8a275..bd61b8d 100644 --- a/code/font/vefontcache/mappings.odin +++ b/code/font/vefontcache/mappings.odin @@ -134,4 +134,3 @@ profile_end :: #force_inline proc "contextless" () { } //#endregion("Proc overload mappings") - diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index ebc79ca..ce768e3 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -6,14 +6,34 @@ import "core:math" import core_log "core:log" +reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) { + raw := transmute( ^runtime.Raw_Dynamic_Array) self + raw.allocator = allocator +} + +reload_array_soa :: #force_inline proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) { + raw := runtime.raw_soa_footer(self) + raw.allocator = allocator +} + +reload_map :: #force_inline proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) { + raw := transmute( ^runtime.Raw_Map) self + raw.allocator = allocator +} + +font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u32) { + lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 ) + return +} + +djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) } +djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) } + Colour :: [4]f32 Vec2 :: [2]f32 Vec2i :: [2]i32 Vec2_64 :: [2]f64 -djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) } -djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) } - vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }} vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }} vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }} @@ -42,36 +62,6 @@ logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc := core_log.logf( level, fmt, ..args, location = loc ) } -reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) { - raw := transmute( ^runtime.Raw_Dynamic_Array) self - 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 -} - -font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u32) { - lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 ) - return -} - -mark_batch_codepoint_seen :: #force_inline proc "contextless" ( ctx : ^Context, lru_code : u32 ) { - ctx.temp_codepoint_seen[lru_code] = true - ctx.temp_codepoint_seen_num += 1 -} - -reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { - clear_map( & ctx.temp_codepoint_seen ) - ctx.temp_codepoint_seen_num = 0 -} - to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) { pos := position^ diff --git a/code/font/vefontcache/parser.odin b/code/font/vefontcache/parser.odin index 4da0b62..e3e5f84 100644 --- a/code/font/vefontcache/parser.odin +++ b/code/font/vefontcache/parser.odin @@ -274,8 +274,7 @@ parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_in parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32 { - - // switch font.kind + // switch font.kind // { // case .Freetype: // error := freetype.load_glyph( font.freetype_info, cast(u32) glyph_index, { .No_Bitmap, .No_Hinting, .No_Scale } ) diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index a388300..7cda16b 100644 --- a/code/font/vefontcache/shaper.odin +++ b/code/font/vefontcache/shaper.odin @@ -23,7 +23,7 @@ Shaped_Text_Cache :: struct { next_cache_id : i32, } -Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) +Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text ) Shaper_Kind :: enum { Naive = 0, @@ -56,7 +56,7 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context ) } } -shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info) +shaper_load_font :: #force_inline proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info) { using info blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil ) @@ -65,7 +65,7 @@ shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, return } -shaper_unload_font :: proc( ctx : ^Shaper_Info ) +shaper_unload_font :: #force_inline proc( ctx : ^Shaper_Info ) { using ctx if blob != nil do harfbuzz.font_destroy( font ) @@ -73,28 +73,37 @@ 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, - ascent, descent, line_gap : i32, size, size_scale : f32 ) +shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text ) { profile(#procedure) current_script := harfbuzz.Script.UNKNOWN hb_ucfunc := harfbuzz.unicode_funcs_get_default() harfbuzz.buffer_clear_contents( ctx.hb_buffer ) - assert( info.font != nil ) - - ascent := f32(ascent) - descent := f32(descent) - line_gap := f32(line_gap) + ascent := entry.ascent + descent := entry.descent + line_gap :=entry.line_gap + max_line_width := f32(0) line_count := 1 - line_height := ((ascent - descent + line_gap) * size_scale) + line_height := ((ascent - descent + line_gap) * font_scale) position : Vec2 - 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 ) + shape_run :: proc( output : ^Shaped_Text, + entry : Entry, + buffer : harfbuzz.Buffer, + script : harfbuzz.Script, + + position : ^Vec2, + max_line_width : ^f32, + line_count : ^int, + + font_px_size : f32, + font_scale : f32, + + snap_shape_pos : b32, + adv_snap_small_font_threshold : f32 + ) { profile(#procedure) // Set script and direction. We use the system's default langauge. @@ -105,14 +114,14 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info // Perform the actual shaping of this run using HarfBuzz. harfbuzz.buffer_set_content_type( buffer, harfbuzz.Buffer_Content_Type.UNICODE ) - harfbuzz.shape( font, buffer, nil, 0 ) + harfbuzz.shape( entry.shaper_info.font, buffer, nil, 0 ) // Loop over glyphs and append to output buffer. glyph_count : u32 glyph_infos := harfbuzz.buffer_get_glyph_infos( buffer, & glyph_count ) glyph_positions := harfbuzz.buffer_get_glyph_positions( buffer, & glyph_count ) - line_height := (ascent - descent + line_gap) * size_scale + line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale for index : i32; index < i32(glyph_count); index += 1 { @@ -129,13 +138,13 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info (line_count^) += 1 continue } - if abs( size ) <= adv_snap_small_font_threshold + if abs( font_px_size ) <= adv_snap_small_font_threshold { (position^) = ceil( position^ ) } glyph_pos := position^ - offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * size_scale + offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * font_scale glyph_pos += offset if snap_shape_pos { @@ -143,13 +152,13 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info } advance := Vec2 { - f32(hb_gposition.x_advance) * size_scale, - f32(hb_gposition.y_advance) * size_scale + f32(hb_gposition.x_advance) * font_scale, + f32(hb_gposition.y_advance) * font_scale } (position^) += advance (max_line_width^) = max(max_line_width^, position.x) - is_empty := parser_is_glyph_empty(parser_info, glyph_id) + is_empty := parser_is_glyph_empty(entry.parser_info, glyph_id) if ! is_empty { append( & output.glyphs, glyph_id ) append( & output.positions, glyph_pos) @@ -181,61 +190,62 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info } // End current run since we've encountered a script change. - shape_run( parser_info, - ctx.hb_buffer, current_script, info.font, output, - & position, & max_line_width, & line_count, - ascent, descent, line_gap, size, size_scale, - ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold - ) + shape_run( output, + entry, + ctx.hb_buffer, + current_script, + & position, + & max_line_width, + & line_count, + font_px_Size, + font_scale, + ctx.snap_glyph_position, + ctx.adv_snap_small_font_threshold + ) harfbuzz.buffer_add( ctx.hb_buffer, hb_codepoint, codepoint == '\n' ? 1 : 0 ) current_script = script } // End the last run if needed - shape_run( parser_info, - ctx.hb_buffer, current_script, info.font, output, - & position, & max_line_width, & line_count, - ascent, descent, line_gap, size, size_scale, - ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold - ) + shape_run( output, + entry, + ctx.hb_buffer, + current_script, + & position, + & max_line_width, + & line_count, + font_px_Size, + font_scale, + ctx.snap_glyph_position, + ctx.adv_snap_small_font_threshold + ) // Set the final size output.size.x = max_line_width output.size.y = f32(line_count) * line_height return } -shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) + +shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context, entry : Entry, font_px_size : f32, font_scale : f32, text_utf8 : string, output : ^Shaped_Text ) { profile(#procedure) assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) clear( & output.glyphs ) clear( & output.positions ) - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) - ascent := f32(ascent_i32) - descent := f32(descent_i32) - line_gap := f32(line_gap_i32) - line_height := (ascent - descent + line_gap) * entry.size_scale - - shaper_shape_from_text( & ctx.shaper_ctx, entry.parser_info, entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) + shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output ) } -shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text ) +shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text ) { profile(#procedure) assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) clear( & output.glyphs ) clear( & output.positions ) - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info ) - ascent := f32(ascent_i32) - descent := f32(descent_i32) - line_gap := f32(line_gap_i32) - line_height := (ascent - descent + line_gap) * entry.size_scale + line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale line_count : int = 1 max_line_width : f32 = 0 @@ -246,7 +256,7 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ { if prev_codepoint > 0 { kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint ) - position.x += f32(kern) * entry.size_scale + position.x += f32(kern) * font_scale } if codepoint == '\n' { @@ -258,12 +268,12 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ prev_codepoint = rune(0) continue } - if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { + if abs( font_px_Size ) <= ctx.adv_snap_small_font_threshold { position.x = ceil(position.x) } - glyph_index := parser_find_glyph_index( entry.parser_info, codepoint ) - is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index ) + 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) @@ -274,7 +284,7 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ } advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) - position.x += f32(advance) * entry.size_scale + position.x += f32(advance) * font_scale prev_codepoint = codepoint } @@ -285,7 +295,15 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ output.size.y = f32(line_count) * line_height } -shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc ) -> (shaped_text : Shaped_Text) +shaper_shape_text_cached :: #force_inline proc( text_utf8 : string, + ctx : ^Shaper_Context, + shape_cache : ^Shaped_Text_Cache, + font : Font_ID, + entry : Entry, + font_px_size : f32, + font_scale : f32, + shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc +) -> (shaped_text : Shaped_Text) { profile(#procedure) font := font @@ -296,8 +314,7 @@ shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, shape_lru_code( & lru_code, font_bytes ) shape_lru_code( & lru_code, text_bytes ) - shape_cache := & ctx.shape_cache - state := & ctx.shape_cache.state + state := & shape_cache.state shape_cache_idx := lru_get( state, lru_code ) if shape_cache_idx == -1 @@ -319,7 +336,7 @@ shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, } storage_entry := & shape_cache.storage[ shape_cache_idx ] - shape_text_uncached( ctx, font, text_utf8, entry, storage_entry ) + shape_text_uncached( ctx, entry, font_px_size, font_scale, text_utf8, storage_entry ) shaped_text = storage_entry ^ return diff --git a/code/font/vefontcache/vefontcache.odin b/code/font/vefontcache/vefontcache.odin index 29c0fc9..4572d3c 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -8,9 +8,9 @@ package vefontcache import "base:runtime" // White: Cached Hit, Red: Cache Miss, Yellow: Oversized -ENABLE_DRAW_TYPE_VIS :: false +ENABLE_DRAW_TYPE_VIS :: true // See: mappings.odin for profiling hookup -DISABLE_PROFILING :: true +DISABLE_PROFILING :: false Font_ID :: distinct i32 Glyph :: distinct i32 @@ -21,53 +21,52 @@ Entry :: struct { id : Font_ID, used : b32, curve_quality : f32, - size : f32, - size_scale : f32, + + ascent : f32, + descent : f32, + line_gap : f32, } Entry_Default :: Entry { 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, + curve_quality = 3, } Context :: struct { backing : Allocator, - parser_ctx : Parser_Context, - shaper_ctx : Shaper_Context, + parser_ctx : Parser_Context, // Glyph parser state + shaper_ctx : Shaper_Context, // Text shaper state + // The managed font instances entries : [dynamic]Entry, - temp_path : [dynamic]Vertex, - temp_codepoint_seen : map[u32]b8, - temp_codepoint_seen_num : i32, - - snap_width : f32, - snap_height : f32, - - alpha_scalar : f32, // Will apply a multiplier to the colour's alpha which provides some sharpening of the edges. - colour : Colour, - cursor_pos : Vec2, + glyph_buffer : Glyph_Draw_Buffer, + atlas : Atlas, + shape_cache : Shaped_Text_Cache, + draw_list : Draw_List, + // Tracks the offsets for the current layer in a draw_list draw_layer : struct { vertices_offset : int, indices_offset : int, calls_offset : int, }, - draw_list : Draw_List, - atlas : Atlas, - glyph_buffer : Glyph_Draw_Buffer, - shape_cache : Shaped_Text_Cache, + // Helps with hinting + snap_width : f32, + snap_height : f32, + + colour : Colour, // Color used in draw interface + cursor_pos : Vec2, + alpha_scalar : f32, // Will apply a multiplier to the colour's alpha which provides some sharpening of the edges. + // Used by draw interface to super-scale the text by + // upscaling px_size with px_scalar and then down-scaling + // the draw_list result by the same amount. + px_scalar : f32, default_curve_quality : i32, - use_advanced_shaper : b32, debug_print : b32, debug_print_verbose : b32, @@ -115,25 +114,27 @@ Init_Atlas_Params_Default :: Init_Atlas_Params { } Init_Glyph_Draw_Params :: struct { - over_sample : Vec2, - buffer_batch : u32, - draw_padding : u32, + over_sample : Vec2, + draw_padding : u32, + shape_gen_scratch_reserve : u32, + buffer_batch : u32, + buffer_batch_glyph_limit : u32, // How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list } Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { - over_sample = Vec2 { 4, 4 }, - buffer_batch = 4, - draw_padding = Init_Atlas_Params_Default.glyph_padding, + over_sample = Vec2 { 4, 4 }, + draw_padding = Init_Atlas_Params_Default.glyph_padding, + shape_gen_scratch_reserve = 10 * 1024, + buffer_batch = 4, + buffer_batch_glyph_limit = 512, } Init_Shaper_Params :: struct { - use_advanced_text_shaper : b32, snap_glyph_position : b32, adv_snap_small_font_threshold : u32, } Init_Shaper_Params_Default :: Init_Shaper_Params { - use_advanced_text_shaper = true, snap_glyph_position = true, adv_snap_small_font_threshold = 0, } @@ -158,10 +159,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, shape_cache_params := Init_Shape_Cache_Params_Default, shaper_params := Init_Shaper_Params_Default, alpha_sharpen := 0.2, + // Curve quality to use for a font when unspecified, + // Affects step size for bezier curve passes in generate_glyph_pass_draw_list default_curve_quality : u32 = 3, entires_reserve : u32 = 256, - temp_path_reserve : u32 = 10 * 1024, - temp_codepoint_seen_reserve : u32 = 10 * 1024, ) { assert( ctx != nil, "Must provide a valid context" ) @@ -172,7 +173,6 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, ctx.colour = { 1, 1, 1, 1 } - use_advanced_shaper = shaper_params.use_advanced_text_shaper shaper_ctx.adv_snap_small_font_threshold = f32(shaper_params.adv_snap_small_font_threshold) shaper_ctx.snap_glyph_position = shaper_params.snap_glyph_position @@ -185,12 +185,6 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, entries, error = make( [dynamic]Entry, len = 0, cap = entires_reserve ) assert(error == .None, "VEFontCache.init : Failed to allocate entries") - temp_path, error = make( [dynamic]Vertex, len = 0, cap = temp_path_reserve ) - assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - - temp_codepoint_seen, error = make( map[u32]b8, uint(temp_codepoint_seen_reserve) ) - assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices") @@ -270,7 +264,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for draw_list" ) } - // Note(From original author): We can actually go over VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH batches due to smart packing! + Glyph_Buffer_Setup: { using glyph_buffer over_sample = glyph_draw_params.over_sample @@ -297,6 +291,14 @@ 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" ) + shape_gen_scratch, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch_glyph_limit ) + assert(error == .None, "VEFontCache.init : Failed to allocate shape_gen_scratch") + + batch_cache.cap = i32(glyph_draw_params.buffer_batch_glyph_limit) + batch_cache.num = 0 + batch_cache.table, error = make( map[u32]b8, uint(glyph_draw_params.shape_gen_scratch_reserve) ) + assert(error == .None, "VEFontCache.init : Failed to allocate batch_cache") + glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator ) oversized, error = make( [dynamic]i32, len = 0, cap = 1 * Kilobyte, allocator = context.temp_allocator ) to_cache, error = make( [dynamic]i32, len = 0, cap = 1 * Kilobyte, allocator = context.temp_allocator ) @@ -315,8 +317,6 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) using ctx reload_array( & entries, allocator ) - reload_array( & temp_path, allocator ) - reload_map( & ctx.temp_codepoint_seen, allocator ) reload_array( & draw_list.vertices, allocator) reload_array( & draw_list.indices, allocator ) @@ -349,10 +349,12 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) reload_array( & glyph_buffer.to_cache, allocator ) reload_array( & glyph_buffer.cached, allocator ) + reload_array( & glyph_buffer.shape_gen_scratch, allocator ) + reload_map( & glyph_buffer.batch_cache.table, allocator ) + reload_array( & shape_cache.storage, allocator ) } -// ve_foncache_shutdown shutdown :: proc( ctx : ^Context ) { assert( ctx != nil ) @@ -364,8 +366,6 @@ shutdown :: proc( ctx : ^Context ) } delete( entries ) - delete( temp_path ) - delete( temp_codepoint_seen ) delete( draw_list.vertices ) delete( draw_list.indices ) @@ -398,11 +398,13 @@ shutdown :: proc( ctx : ^Context ) delete( glyph_buffer.to_cache) delete( glyph_buffer.cached) + delete( glyph_buffer.shape_gen_scratch ) + delete( glyph_buffer.batch_cache.table ) + shaper_shutdown( & shaper_ctx ) parser_shutdown( & parser_ctx ) } -// ve_fontcache_load load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID) { profile(#procedure) @@ -426,22 +428,23 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, entry := & entries[ id ] { - using entry - used = true + entry.used = true profile_begin("calling loaders") - parser_info = parser_load_font( & parser_ctx, label, data ) - shaper_info = shaper_load_font( & shaper_ctx, label, data ) + entry.parser_info = parser_load_font( & parser_ctx, label, data ) + entry.shaper_info = shaper_load_font( & shaper_ctx, label, data ) profile_end() - size = size_px - size_scale = parser_scale( parser_info, size ) + ascent, descent, line_gap := parser_get_font_vertical_metrics(entry.parser_info) + entry.ascent = f32(ascent) + entry.descent = f32(descent) + entry.line_gap = f32(line_gap) if glyph_curve_quality == 0 { - curve_quality = f32(ctx.default_curve_quality) + entry.curve_quality = f32(ctx.default_curve_quality) } else { - curve_quality = f32(glyph_curve_quality) + entry.curve_quality = f32(glyph_curve_quality) } } entry.id = Font_ID(id) @@ -451,7 +454,6 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, return } -// ve_fontcache_unload unload_font :: proc( ctx : ^Context, font : Font_ID ) { assert( ctx != nil ) @@ -470,17 +472,17 @@ unload_font :: proc( ctx : ^Context, font : Font_ID ) //#region("drawing") -// ve_fontcache_configure_snap configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { assert( ctx != nil ) ctx.snap_width = f32(snap_width) ctx.snap_height = f32(snap_height) } -get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos } -set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour } +get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos } +set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_scalar = scalar } +set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour } -draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 +draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string ) -> b32 { profile(#procedure) assert( ctx != nil ) @@ -493,14 +495,23 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : str 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 ] + colour := ctx.colour + colour.a = 1.0 + ctx.alpha_scalar + + // TODO(Ed): Test this. + // px_size_scalar :: 2 + // px_size := px_size * px_size_scalar + // scale := scale / px_size_scalar + + entry := ctx.entries[ font ] + font_scale := parser_scale( entry.parser_info, px_size ) + shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_uncached_advanced ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height ) - shape := shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) - ctx.cursor_pos = generate_shape_draw_list( ctx, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) return true } -draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 +draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string ) -> b32 { profile(#procedure) assert( ctx != nil ) @@ -509,14 +520,18 @@ draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, text_ut 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, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) + colour := ctx.colour + colour.a = 1.0 + ctx.alpha_scalar + + entry := ctx.entries[ font ] + font_scale := parser_scale( entry.parser_info, px_size ) + shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_latin ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, 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 +// Resolve the shape and track it to reduce iteration overhead +draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text ) -> b32 { profile(#procedure) assert( ctx != nil ) @@ -525,24 +540,31 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, shape : S 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, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) + colour := ctx.colour + colour.a = 1.0 + ctx.alpha_scalar + + entry := ctx.entries[ font ] + font_scale := parser_scale( entry.parser_info, px_size ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, 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 +// Resolve the shape and track it to reduce iteration overhead +draw_text_shape_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text ) -> b32 { profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) + colour := ctx.colour + colour.a = 1.0 + ctx.alpha_scalar + entry := ctx.entries[ font ] - ctx.cursor_pos = generate_shape_draw_list( ctx, entry, shape, position, scale, ctx.snap_width, ctx.snap_height ) + font_scale := parser_scale( entry.parser_info, px_size ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, 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 ) if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 ) @@ -558,7 +580,6 @@ get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_retur return } -// ve_fontcache_flush_Draw_List flush_draw_list :: #force_inline proc( ctx : ^Context ) { assert( ctx != nil ) using ctx @@ -580,14 +601,15 @@ flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) { //#region("metrics") -measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2) +measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> (measured : Vec2) { // profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := ctx.entries[font] - shaped := shaper_shape_text_cached(ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) + entry := ctx.entries[font] + font_scale := parser_scale( entry.parser_info, px_size ) + shaped := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_uncached_advanced ) return shaped.size } @@ -597,11 +619,11 @@ 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) - line_gap = (f32(line_gap_i32) * entry.size_scale) + ascent = entry.ascent + descent = entry.descent + line_gap = entry.line_gap return } @@ -609,20 +631,22 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID //#region("shaping") -shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, allocator := context.allocator ) -> Shaped_Text +shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text { profile(#procedure) assert( len(text_utf8) > 0 ) entry := ctx.entries[ font ] - return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_from_text_latin ) + font_scale := parser_scale( entry.parser_info, px_size ) + return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_latin ) } -shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> Shaped_Text +shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text { profile(#procedure) assert( len(text_utf8) > 0 ) entry := ctx.entries[ font ] - return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced ) + font_scale := parser_scale( entry.parser_info, px_size ) + return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_uncached_advanced ) } // User handled shaped text. Will not be cached diff --git a/code/grime/hashing.odin b/code/grime/hashing.odin index 41fa323..715bcb8 100644 --- a/code/grime/hashing.odin +++ b/code/grime/hashing.odin @@ -1,5 +1,9 @@ package grime +djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { + for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) +} + djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) } 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/app/state.odin b/code/sectr/app/state.odin index 3435a13..d96a857 100644 --- a/code/sectr/app/state.odin +++ b/code/sectr/app/state.odin @@ -284,7 +284,7 @@ app_color_theme :: #force_inline proc "contextless" () -> AppColorTheme { retur debug_data :: #force_inline proc "contextless" () -> DebugData { return get_state().debug } get_frametime :: #force_inline proc "contextless" () -> FrameTime { return get_state().frametime } get_default_font :: #force_inline proc "contextless" () -> FontID { return get_state().default_font } -get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) } +get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) } get_ui_context_mut :: #force_inline proc "contextless" () -> ^UI_State { return get_state().ui_context } set_ui_context :: #force_inline proc "contextless" ( ui : ^UI_State ) { get_state().ui_context = ui } diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index fe57b61..753b036 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -649,12 +649,10 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_ text_enqueued = true if cam != nil { - // 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 ) } } @@ -914,7 +912,7 @@ draw_text_string_pos_norm :: #force_inline proc( text : string, id : FontID, fon 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, text, pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar) ) + ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar), text ) return } @@ -942,7 +940,7 @@ draw_text_shape_pos_norm :: #force_inline proc( shape : ShapedText, id : FontID, 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) ) + ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar), shape ) return } @@ -960,12 +958,13 @@ draw_text_shape_pos_extent :: #force_inline proc( shape : ShapedText, id : FontI 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. + // state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state. + config := app_config() 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) + over_sample : f32 = f32(config.font_size_canvas_scalar) zoom_adjust_size *= over_sample pos_offset := (pos + cam_offset) @@ -973,11 +972,11 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo normalized_pos := render_pos * screen_size_norm ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size ) + f32_resolved_size := f32(resolved_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 ) @@ -988,8 +987,8 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo text_scale /= over_sample 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, text, normalized_pos, text_scale ) + ve.set_colour( & get_state().font_provider_ctx.ve_ctx, color_norm ) + ve.draw_text( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), normalized_pos, text_scale, text ) } 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 ) @@ -1008,11 +1007,11 @@ draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id normalized_pos := render_pos * screen_size_norm ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size ) + f32_resolved_size := f32(resolved_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 ) @@ -1024,7 +1023,7 @@ draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id 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 ) + ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, f32_resolved_size, normalized_pos, text_scale, shape ) } // 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 47e317f..3c8648e 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -135,7 +135,7 @@ font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_U 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 ) + measured := ve.measure_text_size( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text ) return measured } @@ -149,13 +149,13 @@ get_font_vertical_metrics :: #force_inline proc ( font : FontID, font_size := Fo shape_text_cached_latin :: #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_latin( & get_state().font_provider_ctx.ve_ctx, ve_id, text ) + shape := ve.shape_text_latin( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text ) return shape } 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 ) + shape := ve.shape_text_advanced( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text ) return shape } diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 99726a2..ec895c5 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -201,10 +201,10 @@ push-location $path_root # $build_args += $flag_micro_architecture_native $build_args += $flag_use_separate_modules $build_args += $flag_thread_count + $CoreCount_Physical - $build_args += $flag_optimize_none + # $build_args += $flag_optimize_none # $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed - # $build_args += $falg_optimize_aggressive + $build_args += $falg_optimize_aggressive $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows'