From 413f544e9c0b14f9ae9c9c55eb175133fa049a42 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 26 Jun 2024 06:01:06 -0400 Subject: [PATCH] VEFontCache: Codepath simplificiation & optimization --- code/font/VEFontCache/LRU.odin | 4 +- code/font/VEFontCache/Readme.md | 2 + code/font/VEFontCache/VEFontCache.odin | 453 +++++++-------- code/font/VEFontCache/atlas.odin | 24 +- code/font/VEFontCache/docs/Readme.md | 74 ++- .../VEFontCache/docs/draw_text_codepaths.pur | Bin 0 -> 48520 bytes code/font/VEFontCache/draw.odin | 522 +++++++++++------- code/font/VEFontCache/misc.odin | 13 + code/font/VEFontCache/shaped_text.odin | 15 +- 9 files changed, 609 insertions(+), 498 deletions(-) create mode 100644 code/font/VEFontCache/docs/draw_text_codepaths.pur diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index ad8e906..6b00752 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -55,7 +55,7 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32, dbg_name : string = "" pool_list_free :: proc( pool : ^PoolList ) { - + // TODO(Ed): Implement } pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) @@ -163,7 +163,7 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : u32, dbg_name : string = "" ) { LRU_free :: proc( cache : ^LRU_Cache ) { - + // TODO(Ed): Implement } LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) diff --git a/code/font/VEFontCache/Readme.md b/code/font/VEFontCache/Readme.md index e469042..d9e78e8 100644 --- a/code/font/VEFontCache/Readme.md +++ b/code/font/VEFontCache/Readme.md @@ -2,6 +2,8 @@ This is a port of the library base on [fork](https://github.com/hypernewbie/VEFontCache) +Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. + TODO (Making it a more idiomatic library): * Use Odin's builtin dynamic arrays diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 65660d4..5b1f7a8 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -28,11 +28,6 @@ vec2_64_from_vec2 :: #force_inline proc( v2 : Vec2 ) -> Vec2_64 { return { f FontID :: distinct i64 Glyph :: distinct i32 -Vertex :: struct { - pos : Vec2, - u, v : f32, -} - Entry :: struct { parser_info : ParserFontInfo, shaper_info : ShaperInfo, @@ -68,6 +63,14 @@ Context :: struct { colour : Colour, cursor_pos : Vec2, + // draw_cursor_pos : Vec2, + + draw_layer : struct { + vertices_offset : int, + indices_offset : int, + calls_offset : int, + }, + draw_list : DrawList, atlas : Atlas, shape_cache : ShapedTextCache, @@ -130,7 +133,6 @@ InitGlyphDrawParams_Default :: InitGlyphDrawParams { over_sample = { 4, 4 }, buffer_batch = 4, draw_padding = InitAtlasParams_Default.glyph_padding, - // draw_padding = InitAtlasParams_Default.glyph_padding, } InitShapeCacheParams :: struct { @@ -139,12 +141,12 @@ InitShapeCacheParams :: struct { } InitShapeCacheParams_Default :: InitShapeCacheParams { - capacity = 256, - reserve_length = 64, + capacity = 1024, + reserve_length = 1024, } // ve_fontcache_init -init :: proc( ctx : ^Context, parser_kind : ParserKind, +startup :: proc( ctx : ^Context, parser_kind : ParserKind, allocator := context.allocator, atlas_params := InitAtlasParams_Default, glyph_draw_params := InitGlyphDrawParams_Default, @@ -272,9 +274,9 @@ init :: proc( ctx : ^Context, parser_kind : ParserKind, hot_reload :: proc( ctx : ^Context, allocator : Allocator ) { + assert( ctx != nil ) ctx.backing = allocator context.allocator = ctx.backing - using ctx reload_array( & entries, allocator ) @@ -323,15 +325,8 @@ shutdown :: proc( ctx : ^Context ) } shaper_shutdown( & shaper_ctx ) -} -#endregion("lifetime") - -// ve_fontcache_configure_snap -configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { - assert( ctx != nil ) - ctx.snap_width = snap_width - ctx.snap_height = snap_height + // TODO(Ed): Finish implementing, there is quite a few resource not released here. } // ve_fontcache_load @@ -395,271 +390,185 @@ unload_font :: proc( ctx : ^Context, font : FontID ) shaper_unload_font( & entry.shaper_info ) } -cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, translate : Vec2 ) -> b32 -{ - // profile(#procedure) +#endregion("lifetime") + +#region("drawing") + +// ve_fontcache_configure_snap +configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := & ctx.entries[ font ] - if glyph_index == Glyph(0) { - // Note(Original Author): Glyph not in current hb_font - return false - } - - // No shpae to retrieve - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - - // Retrieve the shape definition from the parser. - shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index ) - assert( error == .None ) - if len(shape) == 0 { - return false - } - - if ctx.debug_print_verbose - { - log( "shape:") - for vertex in shape - { - if vertex.type == .Move { - logf("move_to %d %d", vertex.x, vertex.y ) - } - else if vertex.type == .Line { - logf("line_to %d %d", vertex.x, vertex.y ) - } - else if vertex.type == .Curve { - logf("curve_to %d %d through %d %d", vertex.x, vertex.y, vertex.contour_x0, vertex.contour_y0 ) - } - else if vertex.type == .Cubic { - logf("cubic_to %d %d through %d %d and %d %d", - vertex.x, vertex.y, - vertex.contour_x0, vertex.contour_y0, - vertex.contour_x1, vertex.contour_y1 ) - } - } - } - - /* - Note(Original Author): - We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner. - Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here. - */ - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - - outside := Vec2 { - f32(bounds_0.x) - 21, - f32(bounds_0.y) - 33, - } - - // Note(Original Author): Figure out scaling so it fits within our box. - draw := DrawCall_Default - draw.pass = FrameBufferPass.Glyph - draw.start_index = u32(len(ctx.draw_list.indices)) - - // Note(Original Author); - // Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac. - // Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions. - path := ctx.temp_path - clear( & path) - for edge in shape do switch edge.type - { - case .Move: - if len(path) > 0 { - draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) - } - clear( & path) - fallthrough - - case .Line: - append_elem( & path, Vec2{ f32(edge.x), f32(edge.y) }) - - case .Curve: - assert( len(path) > 0 ) - p0 := path[ len(path) - 1 ] - p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } - p2 := Vec2{ f32(edge.x), f32(edge.y) } - - step := 1.0 / f32(ctx.curve_quality) - alpha := step - for index := i32(0); index < i32(ctx.curve_quality); index += 1 { - append_elem( & path, eval_point_on_bezier3( p0, p1, p2, alpha )) - alpha += step - } - - case .Cubic: - assert( len(path) > 0 ) - p0 := path[ len(path) - 1] - p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } - p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } - p3 := Vec2{ f32(edge.x), f32(edge.y) } - - step := 1.0 / f32(ctx.curve_quality) - alpha := step - for index := i32(0); index < i32(ctx.curve_quality); index += 1 { - append_elem( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha )) - alpha += step - } - - case .None: - assert(false, "Unknown edge type or invalid") - } - if len(path) > 0 { - draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) - } - - // Note(Original Author): Apend the draw call - draw.end_index = cast(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 -} - -cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph ) -{ - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := & ctx.entries[ font ] - - if glyph_index == 0 do return - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return - - // Get hb_font text metrics. These are unscaled! - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - bounds_width := f32(bounds_1.x - bounds_0.x) - bounds_height := f32(bounds_1.y - bounds_0.y) - - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - - // E region is special case and not cached to atlas. - if region_kind == .None || region_kind == .E do return - - // Grab an atlas LRU cache slot. - lru_code := font_glyph_lru_code( font, glyph_index ) - atlas_index := LRU_get( & region.state, lru_code ) - if atlas_index == -1 - { - if region.next_idx < region.state.capacity - { - evicted := LRU_put( & region.state, lru_code, i32(region.next_idx) ) - atlas_index = i32(region.next_idx) - region.next_idx += 1 - assert( evicted == lru_code ) - } - else - { - 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 ) - - evicted := LRU_put( & region.state, lru_code, atlas_index ) - assert( evicted == next_evict_codepoint ) - } - - assert( LRU_get( & region.state, lru_code ) != - 1 ) - } - - atlas := & ctx.atlas - atlas_width := f32(atlas.width) - atlas_height := f32(atlas.height) - glyph_buffer_width := f32(atlas.buffer_width) - glyph_buffer_height := f32(atlas.buffer_height) - glyph_padding := cast(f32) atlas.glyph_padding - - if ctx.debug_print - { - @static debug_total_cached : i32 = 0 - logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached) - debug_total_cached += 1 - } - - // Draw oversized glyph to update FBO - glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := -1 * Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2( glyph_padding ) - glyph_draw_translate.x = cast(f32) (i32(glyph_draw_translate.x + 0.9999999)) - glyph_draw_translate.y = cast(f32) (i32(glyph_draw_translate.y + 0.9999999)) - - // Allocate a glyph_update_FBO region - gwidth_scaled_px := i32( bounds_width * glyph_draw_scale.x + 1.0 ) + i32(over_sample.x * glyph_padding) - if i32(atlas.update_batch_x + gwidth_scaled_px) >= i32(atlas.buffer_width) { - flush_glyph_buffer_to_atlas( ctx ) - } - - // Calculate the src and destination regions - dst_position, dst_width, dst_height := atlas_bbox( atlas, region_kind, atlas_index ) - dst_glyph_position := dst_position - dst_glyph_width := bounds_width * entry.size_scale - dst_glyph_height := bounds_height * entry.size_scale - dst_glyph_width += glyph_padding - dst_glyph_height += glyph_padding - - dst_size := Vec2 { dst_width, dst_height } - dst_glyph_size := Vec2 { dst_glyph_width, dst_glyph_height } - screenspace_x_form( & dst_glyph_position, & dst_glyph_size, atlas_width, atlas_height ) - screenspace_x_form( & dst_position, & dst_size, atlas_width, atlas_height ) - - src_position := Vec2 { f32(atlas.update_batch_x), 0 } - src_size := Vec2 { - bounds_width * glyph_draw_scale.x, - bounds_height * glyph_draw_scale.y, - } - src_size += over_sample * glyph_padding - textspace_x_form( & src_position, & src_size, glyph_buffer_width, glyph_buffer_height ) - - // Advance glyph_update_batch_x and calculate final glyph drawing transform - glyph_draw_translate.x += f32(atlas.update_batch_x) - atlas.update_batch_x += gwidth_scaled_px - screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height ) - - call : DrawCall - { - // Queue up clear on target region on atlas - using call - pass = .Atlas - region = .Ignore - start_index = cast(u32) len(atlas.clear_draw_list.indices) - blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } ) - end_index = cast(u32) len(atlas.clear_draw_list.indices) - append( & atlas.clear_draw_list.calls, call ) - - // Queue up a blit from glyph_update_FBO to the atlas - region = .None - start_index = cast(u32) len(atlas.draw_list.indices) - blit_quad( & atlas.draw_list, dst_glyph_position, dst_position + dst_glyph_size, src_position, src_position + src_size ) - end_index = cast(u32) len(atlas.draw_list.indices) - append( & atlas.draw_list.calls, call ) - } - - // Render glyph to glyph_update_FBO - cache_glyph( ctx, font, glyph_index, glyph_draw_scale, glyph_draw_translate ) + ctx.snap_width = snap_width + ctx.snap_height = snap_height } get_cursor_pos :: #force_inline proc "contextless" ( ctx : ^Context ) -> Vec2 { return ctx.cursor_pos } set_colour :: #force_inline proc "contextless" ( ctx : ^Context, colour : Colour ) { ctx.colour = colour } -is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 +// TODO(Ed): Change this to be whitespace aware so that we can optimize the caching of shpaes properly. +// Right now the entire text provided to this call is considered a "shape" this is really bad as basically it invalidates caching for large chunks of text +// Instead we should be aware of whitespace tokens and the chunks between them (the whitespace lexer could be abused for this). +// From there we should maek a 'draw text shape' that breaks up the batch text draws for each of the shapes. +draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32 { - if glyph_index == 0 do return true - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - return false + // profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + ctx.cursor_pos = {} + + position := position + snap_width := f32(ctx.snap_width) + snap_height := f32(ctx.snap_height) + if ctx.snap_width > 0 do position.x = cast(f32) cast(u32) (position.x * snap_width + 0.5) / snap_width + if ctx.snap_height > 0 do position.y = cast(f32) cast(u32) (position.y * snap_height + 0.5) / snap_height + + entry := & ctx.entries[ font ] + + last_shaped : ^ShapedText + + ChunkType :: enum u32 { Visible, Formatting } + chunk_kind : ChunkType + chunk_start : int = 0 + chunk_end : int = 0 + + text_utf8_bytes := transmute([]u8) text_utf8 + text_chunk : string + + when true { + text_chunk = transmute(string) text_utf8_bytes[ : ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk, entry ) + ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + last_shaped = shaped + } + } + else { + last_byte_offset : int = 0 + byte_offset : int = 0 + for codepoint, offset in text_utf8 + { + Rune_Space :: ' ' + Rune_Tab :: '\t' + Rune_Carriage_Return :: '\r' + Rune_Line_Feed :: '\n' + // Rune_Tab_Vertical :: '\v' + + byte_offset = offset + + switch codepoint + { + case Rune_Space: fallthrough + case Rune_Tab: fallthrough + case Rune_Line_Feed: fallthrough + case Rune_Carriage_Return: + if chunk_kind == .Formatting { + chunk_end = byte_offset + last_byte_offset = byte_offset + } + else + { + text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk, entry ) + ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Formatting + + last_byte_offset = byte_offset + continue + } + } + + // Visible Chunk + if chunk_kind == .Visible { + chunk_end = byte_offset + last_byte_offset = byte_offset + } + else + { + text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk, entry ) + ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Visible + + last_byte_offset = byte_offset + } + } + + text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk, entry ) + ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Visible + + last_byte_offset = byte_offset + } + return true } +// ve_fontcache_drawlist +get_draw_list :: proc( ctx : ^Context, optimize_before_returning := true ) -> ^DrawList { + assert( ctx != nil ) + if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 ) + + return & ctx.draw_list +} + +get_draw_list_layer :: proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []DrawCall) { + assert( ctx != nil ) + if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset ) + vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ] + indices = ctx.draw_list.indices [ ctx.draw_layer.indices_offset : ] + calls = ctx.draw_list.calls [ ctx.draw_layer.calls_offset : ] + return +} + +// ve_fontcache_flush_drawlist +flush_draw_list :: proc( ctx : ^Context ) { + assert( ctx != nil ) + using ctx + clear_draw_list( & draw_list ) + draw_layer.vertices_offset = 0 + draw_layer.indices_offset = 0 + draw_layer.calls_offset = 0 +} + +flush_draw_list_layer :: proc( ctx : ^Context ) { + assert( ctx != nil ) + using ctx + draw_layer.vertices_offset = len(draw_list.vertices) + draw_layer.indices_offset = len(draw_list.indices) + draw_layer.calls_offset = len(draw_list.calls) +} + +#endregion("drawing") + +#region("metrics") + measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> (measured : Vec2) { // profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - context.allocator = ctx.backing - atlas := ctx.atlas - shaped := shape_text_cached( ctx, font, text_utf8 ) entry := & ctx.entries[ font ] + shaped := shape_text_cached( ctx, font, text_utf8, entry ) padding := cast(f32) atlas.glyph_padding for index : i32 = 0; index < i32(len(shaped.glyphs)); index += 1 @@ -676,3 +585,15 @@ measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) - measured.x = shaped.end_cursor_pos.x return measured } + +get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : FontID ) -> ( ascent, descent, line_gap : i32 ) +{ + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + entry := & ctx.entries[ font ] + ascent, descent, line_gap = parser_get_font_vertical_metrics( & entry.parser_info ) + return +} + +#endregion("metrics") diff --git a/code/font/VEFontCache/atlas.odin b/code/font/VEFontCache/atlas.odin index 3c48193..89abfa6 100644 --- a/code/font/VEFontCache/atlas.odin +++ b/code/font/VEFontCache/atlas.odin @@ -88,25 +88,22 @@ atlas_bbox :: proc( atlas : ^Atlas, region : AtlasRegionKind, local_idx : i32 ) return } -can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph ) -> b32 +can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph, + lru_code : u64, + atlas_index : i32, + region_kind : AtlasRegionKind, + region : ^AtlasRegion, + over_sample : Vec2 +) -> b32 { // profile(#procedure) - assert( ctx != nil ) - assert( entry.id == font ) - - // Decide which atlas to target assert( glyph_index != -1 ) - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) // E region can't batch if region_kind == .E || region_kind == .None do return false if ctx.temp_codepoint_seen_num > 1024 do return false - // Note(Ed): Why 1024? + // TODO(Ed): Why 1024? - // Is this glyph cached? - // lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) - lru_code := font_glyph_lru_code(font, glyph_index) - atlas_index := LRU_get( & region.state, lru_code ) if atlas_index == - 1 { if region.next_idx > u32( region.state.capacity) { @@ -120,12 +117,11 @@ can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^E } } - cache_glyph_to_atlas( ctx, font, glyph_index ) + cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) } assert( LRU_get( & region.state, lru_code ) != -1 ) - ctx.temp_codepoint_seen[lru_code] = true - ctx.temp_codepoint_seen_num += 1 + mark_batch_codepoint_seen( ctx, lru_code) return true } diff --git a/code/font/VEFontCache/docs/Readme.md b/code/font/VEFontCache/docs/Readme.md index 8abed21..9a9f4aa 100644 --- a/code/font/VEFontCache/docs/Readme.md +++ b/code/font/VEFontCache/docs/Readme.md @@ -1,7 +1,73 @@ -# Documentation - -Work in progress... - +# Interface +Notes +--- + +Freetype implementation supports specifying a FT_Memory handle which is a pointer to a FT_MemoryRect. This can be used to define an allocator for the parser. Currently this library does not wrap this interface (yet). If using freetype its recommend to update `parser_init` with the necessary changes to wrap the context's backing allocator for freetype to utilize. + +```c + struct FT_MemoryRec_ + { + void* user; + FT_Alloc_Func alloc; + FT_Free_Func free; + FT_Realloc_Func realloc; + }; + ``` + +### startup + +Initializes a provided context. + +There are a large amount of parameters to tune the library instance to the user's preference. By default, keep in mind the library defaults to utilize stb_truetype as the font parser and harfbuzz (soon...) for the shaper. + +Much of the data structures within the context struct are not fixed-capacity allocations so make sure that the backing allocator utilized can handle it. + +### hot_reload + +The library supports being used in a dynamically loaded module. If this occurs simply make sure to call this procedure with a reference to the backing allocator provided during startup as all dynamic containers tend to lose a proper reference to the allocator's procedure. + +### shutdown + +Release resources from the context. + +### configure_snap + +You'll find this used immediately in draw_text it acts as a way to snap the position of the text to the nearest pixel for the width and height specified. + +If snapping is not desired, set the snap_width and height before calling draw_text to 0. + +## get_cursor_pos + +Will provide the current cursor_pos for the resulting text drawn. + +## set_color + +Sets the color to utilize on `DrawCall`s for FrameBuffer.Target or .Target_Uncached passes + +### get_draw_list + +Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety. +By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +### get_draw_list_layer + +Get the enqueued draw_list for the current "layer". +A layer is considered the slice of the drawlist's content from the last call to `flush_draw_list_layer` onward. +By default, if get_draw_list_layer is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +The draw layer offsets are cleared with `flush_draw_list` + +### flush_draw_list + +Will clear the draw list and draw layer offsets. + +### flush_draw_list_layer + +Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays. + +### measure_text_size + +Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `ShapedText` entry. diff --git a/code/font/VEFontCache/docs/draw_text_codepaths.pur b/code/font/VEFontCache/docs/draw_text_codepaths.pur new file mode 100644 index 0000000000000000000000000000000000000000..061c9b92e9c12bfdb4d990396beaf6d88cf7b206 GIT binary patch literal 48520 zcmeI52_Tf)-}q-DOUOvsmx*jyXYBjF3q@rKjlme(Ff&XfLJ1L3B3q@Bge2LM8C2F} zyMziMSyCkX{~4-Vx7+*P`@X;Xd;kCUKd0tA=bYy(-}5=&=QwAc=Q|Gw1fl`KK~f+X z=$pGj4sF{=l>yGk05L6)GsqRB1f;nDz6?kXqzsfGrznGvAOzqm0lq8{mI1ke6oK<{ zK#C#|lLvCafiQ@dXZ^SJS0IRqfw2Jy3~|Y75zseJa3F(@>Kg9SzM^ z8hW~I+vw@&>9;X5Gj3zp&OlGk#KN?l83JK}Y-8NPx&y)rgdyZgz~qut)O0{W2m?I> zkoq^>`Xdkv9a$zSFbil43z&)pyj}<5-ADkF9hnN*QBiND*#f4eqX$l_LqK3^@Rlt! zG;}oJt>m*?Kwxr)9W1Q0I}cc$zqf0*{C<2Qod8T$P679tO;FnpTLfp{ql0h;igRsL z{9WB3&{k@yEi~ko67u|!)$}8+Iu4NTK zgzfaZx=XPA_51Zc5F-^Bke`YLqzPIFsU-YO4FBrvc5e)SbJ^+B=(q2l24g?>611?} zZA!8639p+^NAR7nUq&KK$`}*E5{fHJ^QqHsEi;BS#@~Iy^N$81`WUO?hn!YyX~$LU zuU2wq2&Q!^tL1xqde!lM3}*9r)cLZ_NK@D2*d&D>j@rS%jMGnVEL}Ms=(0!Kphnj& zbK2ALZU2cC*XGDeF@g8A9gSULb{Fh0_B=JrFVqK#Jb-=qU|*F$phIuEv`1TZjR}_^ z6G}DL&wuZso1B7o2e9x&k?iwld5;=x9r*2X^DKknnwfoCXTPOohdKl$ACg*!lCj;3 z8)$Kpa^5?%h*KMxKN=o*v;WcTeFSf$06{q5sEAt#55)Efp1BZK@=?)iD_dU9>zHm6 zLPqWFp3GvKJhAAgl-S9YyC^o{v{MF97lGF;?*{g$a36GfKA4$(DT!Z9TW*esh`YYS z5BgdZ?1#dXq`v6x=UolnQf)ypToV=2`g5m!>HLc6I_NBP;?`XGo!Q^`Ur%&Ab9dtB z6B-MGREOvAKC6ISs3fq;M?Fd%=znmQmN#6WRzvk>#);Slhq~@+vn$n7&e%XvB8u~k zIFn}^r_wBKA_P1i`WVyoqOR=J0~KWF<;42NRp^WAlzP`Uxx#jPak}OMHw)QnUmU%9 z37INmEMCU0U{u$VULPGXJeMn+k({p)m+M$JB-)p_YSMdF{sXSw**%h5vxVzfw4vPo z$DDQh%gW+uhwDF|=E@MCSn*MmK2>jVAX!#Jiqk}SusJv{A2Nt zTYu|~j$l6Ep>{5>@o8FOSEnS&{K8mL@0v+=W-o zh2ut9k~FvTU1wQ>OTAA}=VU)-#20(EY7eu4{@@PUY=Ieis`m4QQ{kOQ+d&Pj)5Lyt zcXjA5Tv0q&b^c_1l*v&P3S;$P^oYEb+;0WQQr3~YyZPzdi#j>x5_Zq$(%fx|%&hbk3+nU? z1O&v>_4ItA;_1#veezPB9pjJbUqs&dZ6>~+>c8Yu&*h{{(p@-ZHo0Q^GKOi-#(e+1 z$L5#$Ja=h!F%{Lda6Dahq^Nx+7OnhX)v_p8y(aN^J?l+H&$o^M?I-;|#-yNSZl?E0Jy2uio2V$?#~bq*2u2#;Zp|M{J))XV4gAjdNS0 z@GD%&!TF5p%6m?^24{HQt=eyDK2?xfi{#TTR5a2$c^174+tR25zXIGiG>&22Z#?bc# zjPO??ENSL%LT)(3mt^$TOqb0r6o;Bi)eey6tlqK>-py{Gmq_03uPRH{m5;Pe!y?~7E$K4CIw5j6|kM=LYXBxDhBQq>nu=zDZz zi$sprE6xnRvXmO5C`sf4cwaK*qoTO+n_{p6fh3 z{fjJQ$$;G88(?MwCe3q5t%9pmO2`thJIW3r>vT z!sABgM4hAk=0<7<-=b`WO>PYdKkkU&3*d^?h>v`HC|{`&ZiSX$OniEuqnWe10GGATp`M{WU%yj#5B1R*aFzcnDy4Lz1}GAXaN&my zh`nn*f^;k(_`8(^yzR=oBioZ{?@ennN`gYnM7lgx!=f2>Ld}||>S?o)4pwqj}0Hfu8+Q$*J;RJsU^=p(|k=ZXfp5wfit|IrZ9eo;nS*|&rie<4E$t=HI0^opbIM2UF?gv- zJwN*(zAv>zxiGI6ru5EAcD_}Rl~tA{i?MagZm!*|CuVr(*t;55mUWQwT4=f`KB+x= z&h^q0^h$DB2WN1yN~g?Dj=)J9&TGkc&ASCtUvro!jLP}E_-&zh)sU^f!rgxzG}gYa zQL<~FEURXP04zGCt^t?PJK3OyD<9sUvkv0Rz2*^}vg8&ln?B5+yy_U{k}heJbVGf+ z{M*qxM}fYf#ur+jH#bq< z9jmAJY35}mVQCgg6I-_bZMlWy*+_L$@p{Ij)>*@YeTU*2aWDkLN5b5BM55nzAd1nj zY9e|+rgcfQp`N`5l?Kf^Y zGJ59PU1fD}<;pPQySCX13E9GZi#h0y)TnC&|6ugv>FCVC2P?>615~iHuwn(U2vusc zQww>>8lRK3aOzSJd#NF4>B5qB`@QzWjOB~v?5JcOXIVN~64MV`5` zmCCWsnq!)6Zl9wYP`Hdr-XN7#_jqRhmC%KAQ`3d!CwTp7vYUqPY8S{wYK+>8C`d_F z`_%b@>$RkeciXu0vByPfMMs&HN1DwRHB9ZT5B0Vy(m$+eULNBtf;S*^$<-m1TPJ7Y z+FwNX&tSA28n0#E*J~aQ2}zx5(2O5FF3a7Y95=q^jimmRX7%d*`-6dww(tYZ61#Z= zMFiHY-Dc*U10ZgLF6$sm<8VW!iVPR@O~KrQR+?tVnhj$g_XK}3jBP-wN!p|pW!^Vj z239;vq0_-?HoW`a8Jiep(yKg?yVL%p?$wJY0cnaWrW3QT)!90R`y9|IkCCwr zGS3~e!hzlX?~XyK4Q;)8Y^Aw)V!67N^MlR!LW5lTi;!D3ouMgkCwA$K?h_aLCEjkML~Ci1CbsWdXLwV(I4*7wJ2bL#Yzs5@ z-i5IbA>;p=gSufCGQlDpQ}X$J@A8$77_AE&wV#Wx%>A&~4{w$2JE-ZXDJ!w`a0V9% zOhf9kaUu#YZ?nnsk1_&Fjm`=qVua8vc5wjJ z>p*N{(y+|$SO;DHbV*~T;JTc~#0*I@{jj%13Fhpq*fM+nyFl3m$=i}f61ET5EIx*Q zidYi2#g|;2C6w@?jBK7gce&T+i#{!J=;EpO?Y+VIf&BuJtr|@I0`$k^Q%0eX?QDBQ z++tNE9FgP03Dudzx62%65o7b7583%OvcQQDb^ZOXIm~X&aLvS>GnW*4-~)fkf0%!N zitF2Izh@`ZGLFs1wQwgkKh%WE7w?p-!uwq)PZrIv@#>LqA?>NMeVrQ`eOQl@Wvx(#3j5Lf!<_S$5p*8IH?-tg|d-;@T)!Wk}D9V!Pd5N#yA< zx?E%Ek-TUR?8M&m|n2w@ng1f8DGRB{dk6#bpl1trEKdJlZ4Q%30>qjGYaeU>C zxfd=IHk}T3kJS z>cev(Nx2&QTje^XiHd@y=g|2iQQMTI*Tu-*sUBY$=WzSFI_c89RW0G|R5@|BQJrtD z?M389Oo)5x<9GJctEAj(%AfEj6HI4>tymY%V|Hst%t@86=#sqb1p za^Dx89GPwfF5>%qDpjjbR|>+q#-=Ua;I|iQVnfBR_y;a0Ejm!;we@E;j`KQ(jI15j zn)tXN@}YwEa8|kI#b>`RL~{`eEf8>?Tyg3g7y04a8Eq8j)ZVN@{?z0XPK0ujhAA)K z=Z2&-Ocw)J{6`wop*J5Z+U`}J&g2TdY7eY*GJLmsA+p^y9B-Ox9EQe|9Z!MYaJxx@=(f2_0F<*neFSG#$6SmA03~M4a|2&o*8iX zjQSWemHR1wHbh9%c{u|k9P*|_KbQK4j{F#*bnAcpXqolglPPCttt~HO^?6-iv+%04 z1$Ic5H~!+asiytb`>h|_!IWlW>xM##Qli%~Ra!zms~vuq+U>beI~4Yp{gd5L5jV8< zz*?If{ZBhU2*e^@=G-cJk1z}8$>^6$vaC{Em)-Jqby#@SE$;PFy>HWZ*|QY2vjZEQ znnf3L&CP1;MojRaj=Kx%Ia7E-LJgZRHe`F#xt42rL8{h=IZ|$>Kc36!@ZOh!F%F2# zv9LaB6x;z*giq#Qacw3@amS(tM8tHNtf^FNGVNn5?r_ci7FqxQwk z6415G@iVOLyVT`{rR5dQGfG{pM^7vs$;hyTxJdEzonaT6NbEe$=xYc%TujuCfx8a{ zn&@x}25eW=cMjrx>fT~{M_!o0tY?ISPkd)@G}p{FZ>M0a@`&L1z7FnL(RcgG27L}6 zgt$SnZaC0BJ9Or`ZOU9hPMt?^w+cq9UY#c|&0+t&B5TU zy%Y#6O4E0{+@I{k%U7%3mN!V+ebq_YS=zAk*uD3*_m0CA9F>O#Y1TmtR*#tEq>%HA z&K!NF*Vdx$aakE5;ekw=37xi*wy}k~+ncGey$!u$m)&y+Wf)F_VOC{-BO9gJQM0<% z+WZ@G((R4YriJekh{Mx%b?xGpL$}@%x6M6Z?Bfs_xVNw5!YLZRaGu~h&u#BN3K$VM zIm}L{yBIE6^}cUWoxznX=s!bJbvM?l;O9_$(`{ zSUBmj%hKa{*X11Um@KMuo#l3AP4aLf@P90;#cc5+nRcNxH-+Vv#86r0=G9TEvJGUlX;0N#}6HbW)Bo=^R{2Mv82xU?4a@H<6e76dRo9q{WvoA*O5k}eQSi1BR1G~$(C$*W2Fvs}PEm@pVI zCxnzJb%1oz4ns^LrRg8Vf4dI=0#R>N$f$!x9YEmG7$1ZeIpOPXC6SLbe~5!=!*~Vm2fp|mA^l!OsKmaWCjb;BSdW z*03M6jPm^d21JTSAwVHOAwVHOAwVHOAwVHOAwVHOAwVHOA@IMN0D1qvWqUY?Sr70i zjzWM!fI@&mfI@&mfI@&mfI@&mfI@&mfI{FOBM_#^L=B1n?wM~sXae1DhQ{I546z7b zcQg{GheZ&K@vhz~Dpq(b#?w`UEC<*H4BVsN__ryR*3NI@{p#r<@-qw@4zM66UHK9Q zk#E&+xWAD4HS15uFl8!gP&nb=*MmR^SqAcb{NLsOkJEmV`D+4s|G$-r7sM2_ZIbTj z)_fpHaTEd+0%QWGnYIDHMWM+Q?MnVh3N{e%^x2muc76}3?bfr6>!JneO08T2?T-^K~@TbMM+y9kUpTV zE9s8+_L7s4#^JGOBwoq|?;@bFO^uwWq2}(2aM4incEuy0J_v7Dbpbyhk2~J=7+wI1 z#Q5M{eemi6FafCamrO`^1QzECr1|6BB$YPOari(lS13Nv7bs0GD2>G7fChXepk8R` zaVWtZjdzvA`67_6Do|gnt0V!KfmNX=wt1nY{C$w_u1HT;7Znw61lH3P3*`UOstQ!# z2wVXsBk(2bf0hxxkx>okBp{83n)GJJoG~te(2XQ@0jL|$A4xZaH`*&uMa06_)dy;Y z@WF|wZl1uQgIrbMaNlEJ;sjST${nvFCnNJU1G#1u9}LzT;iXC@==-f^KzJ0|M-q?m zRgocA@Fnhy!Q(OBKO}j%y5WCFz;3j%QA*MeFX`-sL3&D}eOv(WDnQF*px>fE)dCu7 z07{>4E&tFba||ByrB4DHICq4vs}s4r6Mz{3@N)s^0A&FEP>6v+E)m8IXZIfXhRVG4Bt_6VCZ=!QQ2S{E6>o4Sag z=pscI{~2BU?$7yu_Q#wGw5g5zDcVTU#(zc|zs-!loD(U}|5LWQ6ao|i6ao|i6ao|i z6ao|i6ao|i6ao|i6axQ$5P3y1cSxH!$MbgBU?SETtA$c}Z-Bi4u zF{yRd@L=DexJDcd0r8PAcOH@Gw;hOLG_0D)T?du(+OB2|5>SMXD?V8h&Zw-n7E>?z zE7vq%T;V%|k%Rn}b{L_BnoEAN^icbaTaJvLxpr4s9bCCG%=oTtwn9R-aNlAMx+68} z8o@sp{dhV$bMV0mGS~nW>@2KU0lcDKsm)F;}di%&bD!C6{ z&L~=r7)5q0WVj~kd_)?E9p+wrXO+9iGgr1!Io4ToOta1Hb94g=mr=uDXD6oI$v0mV*-u>^4O$;;XRi4P*X@64p z>cx|QG{qIuiP=}c%X913LASg1IiOP>BV!w6o;zlR1FvuR-7zS&p{-Ystuz-;ELXR3 zey|x|Xpl>P5pv6>Gc*P6#4erDed1!j#QTmn+Xgfj3(>ggr3qc#>4Fv6#rlBG;S4S^I9Ww~Hcmw0I zHMvEdQj0auz9Y*ID-xb9=hZN-`8kwmbUl==%b9e-<&U)WKe$x_EkZ3XY635V?_>7e zU+KZE;L(xkj&A6%O;qZSgy0~~iWtk{)RVKJhj*|oJ0$3Q5heT)^4^Z&(I$O^+V*_8 zgQNFgto1wZ(O>X8|Nhk;-JJyzGxdG40l?+*e%rW&UBifbt$9};n#OXN0#R#EU?BW2b*MZo^q+yxgu@1WY>5|4w!F4%}i5Ze+`eAR263p3Iv1RuD zcY(4ClD8#|By1n9S$qur6tN_3i!Zr4ODN$(8QDC0?sBis7kygd(8W{l+k1oa1N#Lc zTQ!*a1?Z2-r;I`&+u8PrxW%eSI3mY~6RIiYX%bC}(l z;hKp%XD%u9zz6=6|1kgl6xX-ae$P&*WgMH2YvE38ey9nRFWxCvh4;Hqo-CSS-jrDkBIXrcY+0LnR0Lc~p@->fmHr!!8v&%PZMF*Hb^Ag}e!BlE~9zbh*aRBZGxtI&K;iP-QvRCS=IPbt3zL&0_9zI&nipp5JKq z#Ph8Y%7*Eo&B(i>dJ(GHH3mE7D%GNKJ`$$in@hX>4Gg*f^WQuH+}@sb+qH8dtKxuP*~9kv!6SX z!!r!?=2WOVetiplQAOHS9=E90p033V)!Xn(Gjd6_F&#zE1b0`PWsE-^AHN>HC6~IT zep2_*8`#91){jQ);`quNb1z&bY&soyQLmdOyyd;JACD^u-r(Cy+o*nCr2{z}m$SWy ziY1Dr%;eDyj-tC~()lWu)Q9Inl5#cpx5{-&6BPwZ&!O{4qP8hZuZxkrQ$4;i&f)fT zb<(AIt6IX_sdD0MqdMPQ+l%xUrcy>y-8{=3(oMVFrjchIWc)JSK$NdK05j6Ya9&QZ zEIng^ZOHeP-@iqfQ{T0I1 zT6CbwYwOQy9Orcm8Cg55HSuvl`S|H#&x#HA0F7o{g-?g++ zoKt(V3i(r$PdE|ENgAfSe4iVV(lA{NT=5@iOo!fltZ2Jec{-CT_^N%*I!Jy>>cmS2 zBli%1QOkvUr*8>zu}26`>y;pN|d9cSfEWaQKY+7&DdoDStLZNYi;a z10x*rrbIuN`iGAE7@>6QfBk5g_1u#wXK1Z0FJtw2U0<{Cs{*K1*@2Bt&7zCB=4Lf^BPMuI$K8eX zoGCmZp@vNu8?wFWT+6k*AXV$b94R-`AJ64x~Kvm#3s; zaimOMQVyLJT1{J(EKE84RpGU#`5(rwq^(}*`sV&aewjBz7KW^fd$>E+%Tnz}<%eO>{T~1GcN`I|uPTb#F1f zBQMNg)-%GvC%&^cnrmj8w^J}yc|`DhUkCTB=(~MogFc53Lfjx(Hymi69Xj*eHf63L zr_Lj|TLq(4ug;T~X0fN36!h?r4Dyw8S5OzdcIq(w7I*+sScG0huzpVd)|^6ooo5A? zV|q(2ZtUXIOx>g;(d>rqOT0&hRTIF6GCPdvS*x5Sgor`YhT^9x&X~<@J(PU65wX`f zXizQFK{>8Ei)HS-u}OJ971x7FM!zn>M3XCrQbq-jncKsOz6n@fSogC}4-*1SE+fG? zxo}QmHKGsBH)53QN#NlbX zx_0r)p<8c>+vXlH_Hl>|+}l@j;S`NuI8X4M=eGAB1&j!s9A>A}T@073dfzuaRbA${ ze9TY#{tv6mFOdjwXj`}GGJM-Ooy0IaRZVjI-Z8JZ+w9Qc#v(iC7|Ws7w)>}hUz}Af z&0Twp=f|dc6=3I5)dtN6bw;YVOxbRVxoR*r_Zwvle3q3}ESz-NW$E#}>v9fvOcvF- z&T_l5Ci}&?tL0E_snJ*V*{HrFwy9ZXgL-KW(4+fH<4bl;u-(jeqe2T@X7yES$vO*3 z3hI3^_(VW^n)z2MtNqdUQ(8UM?_QK<-(kbnOa45DJXK|ZgvgS19I z{)rO@N!iPjpE00ifPk1yx5Jpi7_TuZFq{XX6h|RIAwVHOAwVHOAwVHOAwVJUKTaT& zmX=*e2pp!3M>u-{b}z2pI42Yq%lJ_ z6Z6wFDU(<6^{URNng4z zu~DBB9t)Tyk~dBpa(+p4CXeu@FRJ82u?BH`y~hAnpJ;sGW`wMg z0G6+PMPj_YziUz~oP7%|J1;Nz6nH~kvPr570uMB}d8Gb9>aVH49%-bjkE;_F*cxxj zi}3aJLL)ccB;pj{ip2qM6xloxfOZ9Je7+IixOnjH{@%_$2(;H`XWTJZbP%wUMtC{l zkXVeD*S9_ZHQ|9;e@gd4;P6g3M1ZRkdB42b&_|&-W(pj}ABzM`LV<4yzV!g;8eqBlrPYn|o8d3} z_6;0A%0?cPO)dnq3(&c* b32 +{ + // profile(#procedure) + if glyph_index == Glyph(0) { + // Note(Original Author): Glyph not in current hb_font + return false + } + + // No shpae to retrieve + // if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true + + // Retrieve the shape definition from the parser. + shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index ) + assert( error == .None ) + if len(shape) == 0 { + return false + } + + if ctx.debug_print_verbose + { + log( "shape:") + for vertex in shape + { + if vertex.type == .Move { + logf("move_to %d %d", vertex.x, vertex.y ) + } + else if vertex.type == .Line { + logf("line_to %d %d", vertex.x, vertex.y ) + } + else if vertex.type == .Curve { + logf("curve_to %d %d through %d %d", vertex.x, vertex.y, vertex.contour_x0, vertex.contour_y0 ) + } + else if vertex.type == .Cubic { + logf("cubic_to %d %d through %d %d and %d %d", + vertex.x, vertex.y, + vertex.contour_x0, vertex.contour_y0, + vertex.contour_x1, vertex.contour_y1 ) + } + } + } + + /* + Note(Original Author): + We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner. + Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here. + */ + outside := Vec2 { + f32(bounds_0.x) - 21, + f32(bounds_0.y) - 33, + } + + // Note(Original Author): Figure out scaling so it fits within our box. + draw := DrawCall_Default + draw.pass = FrameBufferPass.Glyph + draw.start_index = u32(len(ctx.draw_list.indices)) + + // Note(Original Author); + // Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac. + // Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions. + path := ctx.temp_path + clear( & path) + for edge in shape do switch edge.type + { + case .Move: + if len(path) > 0 { + draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) + } + clear( & path) + fallthrough + + case .Line: + append_elem( & path, Vec2{ f32(edge.x), f32(edge.y) }) + + case .Curve: + assert( len(path) > 0 ) + p0 := path[ len(path) - 1 ] + p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } + p2 := Vec2{ f32(edge.x), f32(edge.y) } + + step := 1.0 / f32(ctx.curve_quality) + alpha := step + for index := i32(0); index < i32(ctx.curve_quality); index += 1 { + append_elem( & path, eval_point_on_bezier3( p0, p1, p2, alpha )) + alpha += step + } + + case .Cubic: + assert( len(path) > 0 ) + p0 := path[ len(path) - 1] + p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } + p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } + p3 := Vec2{ f32(edge.x), f32(edge.y) } + + step := 1.0 / f32(ctx.curve_quality) + alpha := step + for index := i32(0); index < i32(ctx.curve_quality); index += 1 { + append_elem( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha )) + alpha += step + } + + case .None: + assert(false, "Unknown edge type or invalid") + } + if len(path) > 0 { + draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) + } + + // Note(Original Author): Apend the draw call + draw.end_index = cast(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 : FontID, glyph_index : Glyph, lru_code : u64, atlas_index : i32, entry : ^Entry, region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2 ) +{ + // profile(#procedure) + + // Get hb_font text metrics. These are unscaled! + bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) + bounds_width := f32(bounds_1.x - bounds_0.x) + bounds_height := f32(bounds_1.y - bounds_0.y) + + // E region is special case and not cached to atlas. + if region_kind == .None || region_kind == .E do return + + // Grab an atlas LRU cache slot. + atlas_index := atlas_index + if atlas_index == -1 + { + if region.next_idx < region.state.capacity + { + evicted := LRU_put( & region.state, lru_code, i32(region.next_idx) ) + atlas_index = i32(region.next_idx) + region.next_idx += 1 + assert( evicted == lru_code ) + } + else + { + 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 ) + + evicted := LRU_put( & region.state, lru_code, atlas_index ) + assert( evicted == next_evict_codepoint ) + } + + assert( LRU_get( & region.state, lru_code ) != - 1 ) + } + + atlas := & ctx.atlas + atlas_width := f32(atlas.width) + atlas_height := f32(atlas.height) + glyph_buffer_width := f32(atlas.buffer_width) + glyph_buffer_height := f32(atlas.buffer_height) + glyph_padding := cast(f32) atlas.glyph_padding + + if ctx.debug_print + { + @static debug_total_cached : i32 = 0 + logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached) + debug_total_cached += 1 + } + + // Draw oversized glyph to update FBO + glyph_draw_scale := over_sample * entry.size_scale + glyph_draw_translate := -1 * Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2( glyph_padding ) + glyph_draw_translate.x = cast(f32) (i32(glyph_draw_translate.x + 0.9999999)) + glyph_draw_translate.y = cast(f32) (i32(glyph_draw_translate.y + 0.9999999)) + + // Allocate a glyph_update_FBO region + gwidth_scaled_px := i32( bounds_width * glyph_draw_scale.x + 1.0 ) + i32(over_sample.x * glyph_padding) + if i32(atlas.update_batch_x + gwidth_scaled_px) >= i32(atlas.buffer_width) { + flush_glyph_buffer_to_atlas( ctx ) + } + + // Calculate the src and destination regions + dst_position, dst_width, dst_height := atlas_bbox( atlas, region_kind, atlas_index ) + dst_glyph_position := dst_position + dst_glyph_width := bounds_width * entry.size_scale + dst_glyph_height := bounds_height * entry.size_scale + dst_glyph_width += glyph_padding + dst_glyph_height += glyph_padding + + dst_size := Vec2 { dst_width, dst_height } + dst_glyph_size := Vec2 { dst_glyph_width, dst_glyph_height } + screenspace_x_form( & dst_glyph_position, & dst_glyph_size, atlas_width, atlas_height ) + screenspace_x_form( & dst_position, & dst_size, atlas_width, atlas_height ) + + src_position := Vec2 { f32(atlas.update_batch_x), 0 } + src_size := Vec2 { + bounds_width * glyph_draw_scale.x, + bounds_height * glyph_draw_scale.y, + } + src_size += over_sample * glyph_padding + textspace_x_form( & src_position, & src_size, glyph_buffer_width, glyph_buffer_height ) + + // Advance glyph_update_batch_x and calculate final glyph drawing transform + glyph_draw_translate.x += f32(atlas.update_batch_x) + atlas.update_batch_x += gwidth_scaled_px + screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height ) + + call : DrawCall + { + // Queue up clear on target region on atlas + using call + pass = .Atlas + region = .Ignore + start_index = cast(u32) len(atlas.clear_draw_list.indices) + blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } ) + end_index = cast(u32) len(atlas.clear_draw_list.indices) + append( & atlas.clear_draw_list.calls, call ) + + // Queue up a blit from glyph_update_FBO to the atlas + region = .None + start_index = cast(u32) len(atlas.draw_list.indices) + blit_quad( & atlas.draw_list, dst_glyph_position, dst_position + dst_glyph_size, src_position, src_position + src_size ) + end_index = cast(u32) len(atlas.draw_list.indices) + append( & atlas.draw_list.calls, call ) + } + + // Render glyph to glyph_update_FBO + cache_glyph( ctx, font, glyph_index, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate ) +} + // ve_fontcache_clear_drawlist clear_draw_list :: #force_inline proc ( draw_list : ^DrawList ) { clear( & draw_list.calls ) @@ -96,35 +336,42 @@ clear_draw_list :: #force_inline proc ( draw_list : ^DrawList ) { clear( & draw_list.vertices ) } -directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : i32, over_sample, position, scale : Vec2 ) +directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0, bounds_1 : Vec2i, bounds_width, bounds_height : f32, over_sample, position, scale : Vec2 ) { // profile(#procedure) flush_glyph_buffer_to_atlas( ctx ) + glyph_padding := f32(ctx.atlas.glyph_padding) + glyph_buffer_width := f32(ctx.atlas.buffer_width) + glyph_buffer_height := f32(ctx.atlas.buffer_height) + // Draw un-antialiased glyph to update FBO. glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := Vec2{ -f32(bounds_0.x), -f32(bounds_0.y)} * glyph_draw_scale + Vec2{ f32(ctx.atlas.glyph_padding), f32(ctx.atlas.glyph_padding) } - screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) + glyph_draw_translate := -1 * Vec2{ f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2_from_scalar(glyph_padding) + screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height ) - cache_glyph( ctx, entry.id, glyph, glyph_draw_scale, glyph_draw_translate ) + cache_glyph( ctx, entry.id, glyph, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate ) + + glyph_padding_dbl := glyph_padding * 2 + bounds_scaled := Vec2 { bounds_width, bounds_height } * entry.size_scale // Figure out the source rect. glyph_position := Vec2 {} - glyph_width := f32(bounds_width) * entry.size_scale * over_sample.x - glyph_height := f32(bounds_height) * entry.size_scale * over_sample.y - glyph_dst_width := f32(bounds_width) * entry.size_scale - glyph_dst_height := f32(bounds_height) * entry.size_scale - glyph_width += f32(2 * ctx.atlas.glyph_padding) - glyph_height += f32(2 * ctx.atlas.glyph_padding) - glyph_dst_width += f32(2 * ctx.atlas.glyph_padding) - glyph_dst_height += f32(2 * ctx.atlas.glyph_padding) + glyph_width := bounds_scaled.x * over_sample.x + glyph_height := bounds_scaled.y * over_sample.y + glyph_dst_width := bounds_scaled.x + glyph_dst_height := bounds_scaled.y + glyph_height += glyph_padding_dbl + glyph_width += glyph_padding_dbl + glyph_dst_width += glyph_padding_dbl + glyph_dst_height += glyph_padding_dbl // Figure out the destination rect. - bounds_scaled := Vec2 { + bounds_0_scaled := Vec2 { cast(f32) i32(f32(bounds_0.x) * entry.size_scale - 0.5), cast(f32) i32(f32(bounds_0.y) * entry.size_scale - 0.5), } - dst := position + scale * bounds_scaled + dst := position + scale * bounds_0_scaled dst_width := scale.x * glyph_dst_width dst_height := scale.y * glyph_dst_height dst.x -= scale.x * f32(ctx.atlas.draw_padding) @@ -132,7 +379,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly dst_size := Vec2{ dst_width, dst_height } glyph_size := Vec2 { glyph_width, glyph_height } - textspace_x_form( & glyph_position, & glyph_size, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) + textspace_x_form( & glyph_position, & glyph_size, glyph_buffer_width, glyph_buffer_height ) // Add the glyph drawcall. call : DrawCall @@ -154,32 +401,25 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly append( & ctx.draw_list.calls, call ) } -draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, position, scale : Vec2 ) -> b32 +draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, + lru_code : u64, atlas_index : i32, + bounds_0, bounds_1 : Vec2i, + region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2, + position, scale : Vec2 +) -> b32 { // profile(#procedure) - // Glyph not in current font - if glyph_index == 0 do return true - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - bounds_width := f32(bounds_1.x - bounds_0.x) bounds_height := f32(bounds_1.y - bounds_0.y) - // Decide which atlas to target - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - // E region is special case and not cached to atlas if region_kind == .E { - directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, cast(i32) bounds_width, cast(i32) bounds_height, over_sample, position, scale ) + directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, bounds_1, bounds_width, bounds_height, over_sample, position, scale ) return true } // Is this codepoint cached? - // lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(entry.id) ) & 0xFFFFFFFF00000000 ) - lru_code := font_glyph_lru_code(entry.id, glyph_index) - atlas_index := LRU_get( & region.state, lru_code ) if atlas_index == - 1 { return false } @@ -192,8 +432,8 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, // Figure out the source bounding box in the atlas texture glyph_atlas_position, glyph_atlas_width, glyph_atlas_height := atlas_bbox( atlas, region_kind, atlas_index ) - glyph_width := bounds_width * entry.size_scale - glyph_height := bounds_height * entry.size_scale + glyph_width := bounds_width * entry.size_scale + glyph_height := bounds_height * entry.size_scale glyph_width += glyph_padding glyph_height += glyph_padding @@ -204,14 +444,10 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, math.ceil(bounds_0_scaled.x), math.ceil(bounds_0_scaled.y), } - // dst := position * scale * bounds_0_scaled - dst := Vec2 { - position.x + bounds_0_scaled.x * scale.x, - position.y + bounds_0_scaled.y * scale.y, - } + dst := position + bounds_0_scaled * scale + dst -= scale * glyph_padding dst_width := scale.x * glyph_width dst_height := scale.y * glyph_height - dst -= scale * glyph_padding dst_scale := Vec2 { dst_width, dst_height } textspace_x_form( & glyph_atlas_position, & glyph_scale, atlas_width, atlas_height ) @@ -280,187 +516,74 @@ draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : [] } } -// TODO(Ed): Change this to be whitespace aware so that we can optimize the caching of shpaes properly. -// Right now the entire text provided to this call is considered a "shape" this is really bad as basically it invalidates caching for large chunks of text -// Instead we should be aware of whitespace tokens and the chunks between them (the whitespace lexer could be abused for this). -// From there we should maek a 'draw text shape' that breaks up the batch text draws for each of the shapes. -draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32 +draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2, snap_width, snap_height : f32 ) { - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - context.allocator = ctx.backing - - position := position - snap_width := f32(ctx.snap_width) - snap_height := f32(ctx.snap_height) - if ctx.snap_width > 0 do position.x = cast(f32) cast(u32) (position.x * snap_width + 0.5) / snap_width - if ctx.snap_height > 0 do position.y = cast(f32) cast(u32) (position.y * snap_height + 0.5) / snap_height - - entry := & ctx.entries[ font ] - - // entry.size_scale = parser_scale( & entry.parser_info, entry.size ) - - post_shapes_draw_cursor_pos : Vec2 - last_shaped : ^ShapedText - - ChunkType :: enum u32 { Visible, Formatting } - chunk_kind : ChunkType - chunk_start : int = 0 - chunk_end : int = 0 - - text_utf8_bytes := transmute([]u8) text_utf8 - text_chunk : string - - when true { - text_chunk = transmute(string) text_utf8_bytes[ : ] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk ) - post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) - ctx.cursor_pos = post_shapes_draw_cursor_pos - position += shaped.end_cursor_pos - last_shaped = shaped - } - } - else { - last_byte_offset : int = 0 - byte_offset : int = 0 - for codepoint, offset in text_utf8 - { - Rune_Space :: ' ' - Rune_Tab :: '\t' - Rune_Carriage_Return :: '\r' - Rune_Line_Feed :: '\n' - // Rune_Tab_Vertical :: '\v' - - byte_offset = offset - - switch codepoint - { - case Rune_Space: fallthrough - case Rune_Tab: fallthrough - case Rune_Line_Feed: fallthrough - case Rune_Carriage_Return: - if chunk_kind == .Formatting { - chunk_end = byte_offset - last_byte_offset = byte_offset - } - else - { - text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk ) - post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) - ctx.cursor_pos = post_shapes_draw_cursor_pos - position += shaped.end_cursor_pos - last_shaped = shaped - } - - chunk_start = byte_offset - chunk_end = chunk_start - chunk_kind = .Formatting - - last_byte_offset = byte_offset - continue - } - } - - // Visible Chunk - if chunk_kind == .Visible { - chunk_end = byte_offset - last_byte_offset = byte_offset - } - else - { - text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk ) - post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) - ctx.cursor_pos = post_shapes_draw_cursor_pos - position += shaped.end_cursor_pos - last_shaped = shaped - } - - chunk_start = byte_offset - chunk_end = chunk_start - chunk_kind = .Visible - - last_byte_offset = byte_offset - } - } - - text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk ) - post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) - ctx.cursor_pos = post_shapes_draw_cursor_pos - position += shaped.end_cursor_pos - last_shaped = shaped - } - - chunk_start = byte_offset - chunk_end = chunk_start - chunk_kind = .Visible - - last_byte_offset = byte_offset - - ctx.cursor_pos = post_shapes_draw_cursor_pos - } - return true -} - -draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2 ) -{ - flush_glyph_buffer_to_atlas( ctx ) + // flush_glyph_buffer_to_atlas( ctx ) for index := batch_start_idx; index < batch_end_idx; index += 1 { // profile(#procedure) - glyph_index := shaped.glyphs[ index ] - shaped_position := shaped.positions[index] - glyph_translate := position + shaped_position * scale - glyph_cached := draw_cached_glyph( ctx, entry, glyph_index, glyph_translate, scale) + glyph_index := shaped.glyphs[ index ] + + if glyph_index == 0 do continue + if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do continue + + region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) + lru_code := font_glyph_lru_code(entry.id, glyph_index) + atlas_index := cast(i32) -1 + + if region_kind != .E do atlas_index = LRU_get( & region.state, lru_code ) + bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) + + shaped_position := shaped.positions[index] + glyph_translate := position + shaped_position * scale + + glyph_cached := draw_cached_glyph( ctx, + entry, glyph_index, + lru_code, atlas_index, + bounds_0, bounds_1, + region_kind, region, over_sample, + glyph_translate, scale) assert( glyph_cached == true ) } } - // Helper for draw_text, all raw text content should be confirmed to be either formatting or visible shapes before getting cached. draw_text_shape :: proc( ctx : ^Context, font : FontID, entry : ^Entry, shaped : ^ShapedText, position, scale : Vec2, snap_width, snap_height : f32 ) -> (cursor_pos : Vec2) { + // position := position //+ ctx.cursor_pos * scale // profile(#procedure) batch_start_idx : i32 = 0 for index : i32 = 0; index < i32(len(shaped.glyphs)); index += 1 { glyph_index := shaped.glyphs[ index ] - if is_empty( ctx, entry, glyph_index ) do continue - if can_batch_glyph( ctx, font, entry, glyph_index ) do continue + if is_empty( ctx, entry, glyph_index ) do continue + + region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) + lru_code := font_glyph_lru_code(entry.id, glyph_index) + atlas_index := cast(i32) -1 + + if region_kind != .E do atlas_index = LRU_get( & region.state, lru_code ) + if can_batch_glyph( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue // Glyph has not been catched, needs to be directly drawn. - draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale ) + + // First batch the other cached glyphs + flush_glyph_buffer_to_atlas(ctx) + draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale, snap_width, snap_height ) reset_batch_codepoint_state( ctx ) - cache_glyph_to_atlas( ctx, font, glyph_index ) - - lru_code := font_glyph_lru_code(font, glyph_index) - ctx.temp_codepoint_seen[lru_code] = true - ctx.temp_codepoint_seen_num += 1 - + cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) + mark_batch_codepoint_seen( ctx, lru_code) batch_start_idx = index } - draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(len(shaped.glyphs)), position, scale ) + flush_glyph_buffer_to_atlas(ctx) + draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(len(shaped.glyphs)), position, scale, snap_width , snap_height ) reset_batch_codepoint_state( ctx ) - cursor_pos = position + shaped.end_cursor_pos * scale + cursor_pos = shaped.end_cursor_pos return } -// ve_fontcache_flush_drawlist -flush_draw_list :: proc( ctx : ^Context ) { - assert( ctx != nil ) - clear_draw_list( & ctx.draw_list ) -} - flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) { // profile(#procedure) @@ -484,17 +607,6 @@ flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) } } -// ve_fontcache_drawlist -get_draw_list :: proc( ctx : ^Context ) -> ^DrawList { - assert( ctx != nil ) - return & ctx.draw_list -} - -// TODO(Ed): See render.odin's render_text_layer, should provide the ability to get a slice of the draw list to render the latest layer -DrawListLayer :: struct {} -get_draw_list_layer :: proc() -> DrawListLayer { return {} } -flush_layer :: proc( draw_list : ^DrawList ) {} - // ve_fontcache_merge_drawlist merge_draw_list :: proc( dst, src : ^DrawList ) { @@ -526,13 +638,13 @@ merge_draw_list :: proc( dst, src : ^DrawList ) } } -optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : u64 ) +optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : int ) { // profile(#procedure) assert( draw_list != nil ) - write_index : u64 = call_offset - for index : u64 = 1 + call_offset; index < cast(u64) len(draw_list.calls); index += 1 + write_index : int = call_offset + for index : int = 1 + call_offset; index < len(draw_list.calls); index += 1 { assert( write_index <= index ) draw_0 := & draw_list.calls[ write_index ] diff --git a/code/font/VEFontCache/misc.odin b/code/font/VEFontCache/misc.odin index d3f5f5f..860c57a 100644 --- a/code/font/VEFontCache/misc.odin +++ b/code/font/VEFontCache/misc.odin @@ -72,6 +72,19 @@ eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec return { f32(point.x), f32(point.y) } } +is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 +{ + if glyph_index == 0 do return true + if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true + return false +} + +mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 ) +{ + ctx.temp_codepoint_seen[lru_code] = true + ctx.temp_codepoint_seen_num += 1 +} + reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { clear_map( & ctx.temp_codepoint_seen ) ctx.temp_codepoint_seen_num = 0 diff --git a/code/font/VEFontCache/shaped_text.odin b/code/font/VEFontCache/shaped_text.odin index 79cdf5c..cf9e2fe 100644 --- a/code/font/VEFontCache/shaped_text.odin +++ b/code/font/VEFontCache/shaped_text.odin @@ -14,22 +14,24 @@ ShapedTextCache :: struct { next_cache_id : i32, } -shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> ^ShapedText +shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, entry : ^Entry ) -> ^ShapedText { // profile(#procedure) @static buffer : [64 * Kilobyte]byte - font := font + font := font + text_size := len(text_utf8) + sice_end_offset := size_of(FontID) + len(text_utf8) buffer_slice := buffer[:] font_bytes := slice_ptr( transmute(^byte) & font, size_of(FontID) ) copy( buffer_slice, font_bytes ) text_bytes := transmute( []byte) text_utf8 - buffer_slice_post_font := buffer[size_of(FontID) : size_of(FontID) + len(text_utf8) ] + buffer_slice_post_font := buffer[ size_of(FontID) : sice_end_offset ] copy( buffer_slice_post_font, text_bytes ) - hash := shape_lru_hash( transmute(string) buffer[: size_of(FontID) + len(text_utf8)] ) + hash := shape_lru_hash( transmute(string) buffer[: sice_end_offset ] ) shape_cache := & ctx.shape_cache state := & ctx.shape_cache.state @@ -54,20 +56,19 @@ shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) - LRU_put( state, hash, shape_cache_idx ) } - shape_text_uncached( ctx, font, & shape_cache.storage[ shape_cache_idx ], text_utf8 ) + shape_text_uncached( ctx, font, text_utf8, entry, & shape_cache.storage[ shape_cache_idx ] ) } return & shape_cache.storage[ shape_cache_idx ] } -shape_text_uncached :: proc( ctx : ^Context, font : FontID, output : ^ShapedText, text_utf8 : string ) +shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, entry : ^Entry, output : ^ShapedText ) { // profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) use_full_text_shape := ctx.text_shape_adv - entry := & ctx.entries[ font ] clear( & output.glyphs ) clear( & output.positions )