From a666300f4167e7ba349149c177e6c2d8d5c4048d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 29 Jun 2024 20:33:07 -0400 Subject: [PATCH] VEFontCache: Remove draw_cached_glyph, formatting No longer needed as draw_text_batched handles it --- code/font/VEFontCache/LRU.odin | 21 +-- code/font/VEFontCache/Readme.md | 13 +- code/font/VEFontCache/VEFontCache.odin | 5 +- code/font/VEFontCache/docs/Readme.md | 2 + code/font/VEFontCache/draw.odin | 188 +++++++------------------ code/font/VEFontCache/misc.odin | 2 +- code/font/VEFontCache/parser.odin | 3 +- 7 files changed, 70 insertions(+), 164 deletions(-) diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index ea97a6b..0104c76 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -53,13 +53,11 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32, dbg_name : string = "" back = -1 } -pool_list_free :: proc( pool : ^PoolList ) -{ +pool_list_free :: proc( pool : ^PoolList ) { // TODO(Ed): Implement } -pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) -{ +pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) { reload_array( & pool.items, allocator ) reload_array( & pool.free_list, allocator ) } @@ -175,23 +173,15 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : u32, dbg_name : string = "" ) { pool_list_init( & cache.key_queue, capacity, dbg_name = dbg_name ) } -LRU_free :: proc( cache : ^LRU_Cache ) -{ +LRU_free :: proc( cache : ^LRU_Cache ) { // TODO(Ed): Implement } -LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) -{ +LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) { reload_map( & cache.table, allocator ) pool_list_reload( & cache.key_queue, allocator ) } -LRU_hash_key :: #force_inline proc( key : u64 ) -> ( hash : u64 ) { - bytes := transmute( [8]byte ) key - hash = fnv64a( bytes[:] ) - return -} - LRU_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) { link, success := cache.table[key] return link, success @@ -205,8 +195,7 @@ LRU_get :: #force_inline proc( cache: ^LRU_Cache, key : u64 ) -> i32 { return -1 } -LRU_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 -{ +LRU_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 { if cache.key_queue.size >= cache.capacity { evict := pool_list_peek_back( & cache.key_queue ) return evict diff --git a/code/font/VEFontCache/Readme.md b/code/font/VEFontCache/Readme.md index f6690a9..5158e46 100644 --- a/code/font/VEFontCache/Readme.md +++ b/code/font/VEFontCache/Readme.md @@ -6,9 +6,16 @@ Its original purpose was for use in game engines, however its rendeirng quality See: [docs/Readme.md](docs/Readme.md) for the library's interface +## Changes from orignal + +* Font Parser & Glyph shaper are abstracted to their own interface +* Font face parser info encapsulated in parser_info struct. +* ve_fontcache_loadfile not ported (ust use core:os or os2, then call load_font) +* Macro defines have been coverted (mostly) to runtime parameters + ## TODOs -### (Making it a more idiomatic library): +### Thirdparty support: * Setup freetype, harfbuzz, depedency management within the library @@ -28,6 +35,7 @@ See: [docs/Readme.md](docs/Readme.md) for the library's interface * Support for harfbuzz * Ability to set a draw transform, viewport and projection * By default the library's position is in unsigned normalized render space + * Could implement a similar design to sokol_gp's interface * Allow curve_quality to be set on a per-font basis ### Optimization: @@ -43,9 +51,10 @@ See: [docs/Readme.md](docs/Readme.md) for the library's interface * glyph_draw_buffer * shape_cache * This would need to converge to the singlar draw_list on a per layer basis (then user reqeusts a draw_list layer there could a yield to wait for the jobs to finish); if the interface expects the user to issue the commands single-threaded unless, we just assume the user is going to feed the gpu the commands & data through separate threads as well (not ideal ux). + * How the contexts are given jobs should be left up to the user (can recommend a screen quadrant based approach in demo examples) Failed Attempts: * Attempted to chunk the text to more granular 'shapes' from `draw_list` before doing the actual call to `draw_text_shape`. This lead to a larger performance cost due to the additional iteration across the text string. -* Attempted to cache the shape draw_list for future calls. Led to larger performance cost due to additional iteration in the `merge_draw_list`. +* Attempted to cache the shape draw_list for future calls. Led to larger performance cost due to additional iteration in the `merge_draw_list`. * The shapes glyphs must still be traversed to identify if the glyph is cached. This arguably could be handled in `shape_text_uncached`, however that would require a significan't amount of refactoring to identify... (and would be more unergonomic when shapers libs are processing the text) diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index d6bff85..05c1085 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -1,14 +1,11 @@ /* A port of (https://github.com/hypernewbie/VEFontCache) to Odin. -Status: -This port is heavily tied to the grime package in SectrPrototype. - Changes: - Font Parser & Glyph Shaper are abstracted to their own interface - Font Face parser info stored separately from entries - ve_fontcache_loadfile not ported (just use odin's core:os or os2), then call load_font -- Macro defines have been made into runtime parameters +- Macro defines have been made (mostly) into runtime parameters */ package VEFontCache diff --git a/code/font/VEFontCache/docs/Readme.md b/code/font/VEFontCache/docs/Readme.md index 7a3c91a..010f9ce 100644 --- a/code/font/VEFontCache/docs/Readme.md +++ b/code/font/VEFontCache/docs/Readme.md @@ -15,6 +15,8 @@ Freetype implementation supports specifying a FT_Memory handle which is a pointe }; ``` +This library (seems) to perform best if the text commands are fed in 'whitespace aware chunks', where instead of feeding it entire blobs of text, the user identfies the "words" in the text and feeding the visible and whitespce chunks derived from this to draw_text as separate calls. This improves the caching of the text shapes. The downside is there has to be a time where the text is parsed into tokens beforehand so that the this iteration does not have to occur continously. + ### startup Initializes a provided context. diff --git a/code/font/VEFontCache/draw.odin b/code/font/VEFontCache/draw.odin index 1e8ad8c..3d4f9ba 100644 --- a/code/font/VEFontCache/draw.odin +++ b/code/font/VEFontCache/draw.odin @@ -106,23 +106,8 @@ cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : path := &ctx.temp_path clear(path) - append_bezier_curve :: #force_inline proc(path: ^[dynamic]Vertex, p0, p1, p2: Vec2, quality: u32) { - step := 1.0 / f32(quality) - for index := u32(1); index <= quality; index += 1 { - alpha := f32(index) * step - append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } ) - } - } - - append_bezier_curve_cubic :: #force_inline proc(path: ^[dynamic]Vertex, p0, p1, p2, p3: Vec2, quality: u32) { - step := 1.0 / f32(quality) - for index := u32(1); index <= quality; index += 1 { - alpha := f32(index) * step - append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } ) - } - } - - for edge in shape do #partial switch edge.type { + for edge in shape do #partial switch edge.type + { case .Move: if len(path) > 0 { draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) @@ -138,7 +123,11 @@ cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : p0 := path[ len(path) - 1].pos p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } p2 := Vec2{ f32(edge.x), f32(edge.y) } - append_bezier_curve( path, p0, p1, p2, ctx.curve_quality ) + step := 1.0 / f32(quality) + for index := u32(1); index <= quality; index += 1 { + alpha := f32(index) * step + append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } ) + } case .Cubic: assert( len(path) > 0) @@ -146,7 +135,11 @@ cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : 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) } - append_bezier_curve_cubic( path, p0, p1, p2, p3, ctx.curve_quality ) + step := 1.0 / f32(quality) + for index := u32(1); index <= quality; index += 1 { + alpha := f32(index) * step + append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } ) + } } if len(path) > 0 { @@ -405,90 +398,6 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, append( & ctx.draw_list.calls, ..calls[:] ) } -draw_cached_glyph :: proc( ctx : ^Context, shaped : ^ShapedText, - entry : ^Entry, - glyph_index : Glyph, - lru_code : u64, - atlas_index : i32, - bounds_0, bounds_1 : Vec2, - region_kind : AtlasRegionKind, - region : ^AtlasRegion, - over_sample : Vec2, - position, scale : Vec2 -) -> b32 -{ - // profile(#procedure) - bounds_size := Vec2 { - f32(bounds_1.x - bounds_0.x), - f32(bounds_1.y - bounds_0.y), - } - - // E region is special case and not cached to atlas - if region_kind == .E - { - directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, bounds_1, bounds_size, over_sample, position, scale ) - return true - } - - // Is this codepoint cached? - if atlas_index == - 1 { - return false - } - - atlas := & ctx.atlas - atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) } - glyph_padding := f32(atlas.glyph_padding) - - // Figure out the source bounding box in the atlas texture - slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) - - glyph_scale := bounds_size * entry.size_scale + glyph_padding - - bounds_0_scaled := bounds_0 * entry.size_scale //- { 0.5, 0.5 } - bounds_0_scaled = ceil(bounds_0_scaled) - - dst := position + (bounds_0_scaled - glyph_padding) * scale - dst_scale := glyph_scale * scale - - textspace_x_form( & slot_position, & glyph_scale, atlas_size ) - - // Shape call setup - when false - { - call := DrawCall_Default - { - using call - pass = .Target - colour = ctx.colour - start_index = cast(u32) len(shaped.draw_list.indices) - - blit_quad( & shaped.draw_list, - dst, dst + dst_scale, - slot_position, slot_position + glyph_scale ) - end_index = cast(u32) len(shaped.draw_list.indices) - } - append( & shaped.draw_list.calls, call ) - } - else - { - // Add the glyph drawcall - call := DrawCall_Default - { - using call - pass = .Target - colour = ctx.colour - start_index = cast(u32) len(ctx.draw_list.indices) - - blit_quad( & ctx.draw_list, - dst, dst + dst_scale, - slot_position, slot_position + glyph_scale ) - end_index = cast(u32) len(ctx.draw_list.indices) - } - append( & ctx.draw_list.calls, call ) - } - return true -} - // Constructs a triangle fan to fill a shape using the provided path // outside_point represents the center point of the fan. // @@ -551,49 +460,50 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^ShapedText, for index := batch_start_idx; index < batch_end_idx; index += 1 { - glyph_index := shaped.glyphs[index] + glyph_index := shaped.glyphs[index] - if glyph_index == 0 || parser_is_glyph_empty( & entry.parser_info, glyph_index) do continue + if glyph_index == 0 || 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 := region_kind != .E ? LRU_get( & region.state, lru_code ) : -1 - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - vbounds_0 := vec2(bounds_0) - vbounds_1 := vec2(bounds_1) - bounds_size := Vec2 { vbounds_1.x - vbounds_0.x, vbounds_1.y - vbounds_0.y } + region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) + lru_code := font_glyph_lru_code( entry.id, glyph_index ) + atlas_index := region_kind != .E ? LRU_get( & region.state, lru_code ) : -1 + bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) + vbounds_0 := vec2(bounds_0) + vbounds_1 := vec2(bounds_1) + bounds_size := Vec2 { vbounds_1.x - vbounds_0.x, vbounds_1.y - vbounds_0.y } - shaped_position := shaped.positions[index] - glyph_translate := position + shaped_position * scale + shaped_position := shaped.positions[index] + glyph_translate := position + shaped_position * scale - if region_kind == .E - { - directly_draw_massive_glyph(ctx, entry, glyph_index, - vbounds_0, vbounds_1, - bounds_size, - over_sample, glyph_translate, scale ) - } - else if atlas_index != -1 - { - slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) - glyph_scale := bounds_size * entry.size_scale + glyph_padding - bounds_0_scaled := ceil( vbounds_0 * entry.size_scale ) - dst := glyph_translate + (bounds_0_scaled - glyph_padding) * scale - dst_scale := glyph_scale * scale - textspace_x_form( & slot_position, & glyph_scale, atlas_size ) + if region_kind == .E + { + directly_draw_massive_glyph(ctx, entry, glyph_index, + vbounds_0, vbounds_1, + bounds_size, + over_sample, glyph_translate, scale ) + } + else if atlas_index != -1 + { + // Draw cacxhed glyph + slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) + glyph_scale := bounds_size * entry.size_scale + glyph_padding + bounds_0_scaled := ceil( vbounds_0 * entry.size_scale ) + dst := glyph_translate + (bounds_0_scaled - glyph_padding) * scale + dst_scale := glyph_scale * scale + textspace_x_form( & slot_position, & glyph_scale, atlas_size ) - call := DrawCall_Default - call.pass = .Target - call.colour = ctx.colour - call.start_index = u32(len(ctx.draw_list.indices)) + call := DrawCall_Default + call.pass = .Target + call.colour = ctx.colour + call.start_index = u32(len(ctx.draw_list.indices)) - blit_quad(&ctx.draw_list, - dst, dst + dst_scale, - slot_position, slot_position + glyph_scale ) + blit_quad(&ctx.draw_list, + dst, dst + dst_scale, + slot_position, slot_position + glyph_scale ) - call.end_index = u32(len(ctx.draw_list.indices)) - append(&ctx.draw_list.calls, call) - } + call.end_index = u32(len(ctx.draw_list.indices)) + append(&ctx.draw_list.calls, call) + } } } diff --git a/code/font/VEFontCache/misc.odin b/code/font/VEFontCache/misc.odin index cba3de4..7c4e498 100644 --- a/code/font/VEFontCache/misc.odin +++ b/code/font/VEFontCache/misc.odin @@ -120,7 +120,7 @@ textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, } } -Use_SIMD_For_Bezier_Ops :: true +Use_SIMD_For_Bezier_Ops :: false when ! Use_SIMD_For_Bezier_Ops { diff --git a/code/font/VEFontCache/parser.odin b/code/font/VEFontCache/parser.odin index 52796ec..5d7cb74 100644 --- a/code/font/VEFontCache/parser.odin +++ b/code/font/VEFontCache/parser.odin @@ -73,8 +73,7 @@ parser_init :: proc( ctx : ^ParserContext ) // assert( error == .None, "VEFontCache.parser_init: Failed to allocate fonts array" ) } -parser_shutdown :: proc( ctx : ^ParserContext ) -{ +parser_shutdown :: proc( ctx : ^ParserContext ) { // TODO(Ed): Implement }