diff --git a/.gitignore b/.gitignore index dafaa3c..fb8d7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ build -thirdparty +# thirdparty .vscode # backend/sokol/render_glyph.odin # backend/sokol/blit_atlas.odin diff --git a/LICENSE.md b/LICENSE.md index a602421..5652bec 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -VEFontCache Odin Port +VEFontCache Odin Copyright 2024 Edward R. Gonzalez This project is based on Vertex Engine GPU Font Cache diff --git a/vefontcache/LRU.odin b/vefontcache/LRU.odin index 20e18a3..5381890 100644 --- a/vefontcache/LRU.odin +++ b/vefontcache/LRU.odin @@ -43,11 +43,11 @@ pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : s { error : Allocator_Error pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) ) - assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array") + assert( error == .None, "VEFontCache.pool_list_inits: Failed to allocate items array") resize( & pool.items, capacity ) pool.free_list, error = make( [dynamic]Pool_ListIter, len = 0, cap = int(capacity) ) - assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array") + assert( error == .None, "VEFontCache.pool_list_init: Failed to allocate free_list array") resize( & pool.free_list, capacity ) pool.capacity = capacity @@ -106,16 +106,16 @@ pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_b assert( length == int(pool.capacity - pool.size) ) id := pool.free_list[ len(pool.free_list) - 1 ] - if pool.dbg_name != "" { - logf("pool_list: back %v", id) - } + // if pool.dbg_name != "" { + // logf("pool_list: back %v", id) + // } pop( & pool.free_list ) pool.items[ id ].prev = -1 pool.items[ id ].next = pool.front pool.items[ id ].value = value - if pool.dbg_name != "" { - logf("pool_list: pushed %v into id %v", value, id) - } + // if pool.dbg_name != "" { + // logf("pool_list: pushed %v into id %v", value, id) + // } if pool.front != -1 do pool.items[ pool.front ].prev = id if pool.back == -1 do pool.back = id diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 69a1888..9d91ba5 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -109,7 +109,7 @@ Glyph_Draw_Buffer :: struct{ cached : [dynamic]i32, } -// Contructs a quad mesh for bliting a texture from one render target (src uv0 & 1) to the destination rendertarget (p0, p1) +// Contructs a quad mesh for bliting a texture from source render target (src uv0 & 1) to the destination render target (p0, p1) @(optimization_mode="favor_size") blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, @@ -279,24 +279,15 @@ generate_shapes_draw_list :: #force_inline proc ( ctx : ^Context, } /* Generator pipeline for shapes - - If you'd like to make a custom draw procedure, this can either be used directly or - modified to create an augmented derivative for a specific code path. - This procedure has no awareness of layers. That should be handled by a higher-order codepath. - For this level of codepaths what matters is maximizing memory locality for: - * Dealing with shaping (essentially minimizing having to ever deal with it in a hot path if possible) - * Dealing with atlas regioning (the expensive region resolution & parser calls are done on the shape pass) Pipleine order: * Resolve the glyph's position offset from the target position * Segregate the glyphs into three slices: oversized, to_cache, cached. - * If oversized is not necessary for your use case and your hitting a bottleneck, omit it with setting ENABLE_OVERSIZED_GLYPHS to false. - * You have to to be drawing a px font size > ~140 px for it to trigger. - * The atlas can be scaled with the size_multiplier parameter of startup so that it becomes more irrelevant if processing a larger atlas is a non-issue. + * If oversized is not necessary for your use case and your hitting a bottleneck, omit it with setting ENABLE_OVERSIZED_GLYPHS to false. * The segregation will not allow slices to exceed the batch_cache capacity of the glyph_buffer (configurable within startup params) - * When The capacity is reached batch_generate_glyphs_draw_list will be called which will do futher compute and then finally draw_list generation. - * This may perform better with smaller shapes vs larger shapes, but having more shapes has a cache lookup penatly so keep that in mind. + * When The capacity is reached batch_generate_glyphs_draw_list will be called which will do futher compute and then finally draw_list generation. + * This may perform better with smaller shapes vs larger shapes, but having more shapes has a cache lookup penatly (if done per frame) so keep that in mind. */ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, atlas : ^Atlas, @@ -463,16 +454,16 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, /* The glyphs types have been segregated by this point into a batch slice of indices to the glyph_pack - The transform and draw quads are computed first (getting the math done in one spot as possible...) - Some of the math from to_cache pass for glyph generation was not moved over (it could be but I'm not sure its worth it...) + The transform and draw quads are computed first (getting the math done in one spot as possible) + Some of the math from to_cache pass for glyph generation was not moved over (it could be but I'm not sure its worth it) Order: Oversized first, then to_cache, then cached. Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target. - The compute section will have operations reguarding how many glyphs they may alloate before a flush must occur. + The compute section will have operations regarding how many glyphs they may alloate before a flush must occur. A flush will force one of the following: * Oversized will have a draw call setup to blit directly from the glyph buffer to the target. - * to_cache will blit the glyphs rendered to the buffer to the atlas. + * to_cache will blit the glyphs rendered from the buffer to the atlas. */ @(optimization_mode = "favor_size") batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, diff --git a/vefontcache/misc.odin b/vefontcache/misc.odin index 7826400..af16f86 100644 --- a/vefontcache/misc.odin +++ b/vefontcache/misc.odin @@ -61,7 +61,7 @@ vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2 @(require_results) ceil_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { ceil_f32(v.x), ceil_f32(v.y) } } @(require_results) floor_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { floor_f32(v.x), floor_f32(v.y) } } -// This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators. +// This buffer is used below excluisvely to prevent any allocator recursion when verbose logging from allocators. // This means a single line is limited to 4k buffer // Logger_Allocator_Buffer : [4 * Kilobyte]u8 diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index cef2502..70a3959 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -2,10 +2,10 @@ package vefontcache /* Notes: -This is a minimal wrapper I originally did incase something than stb_truetype is introduced in the future. +This is a minimal wrapper I originally did incase a font parser other than stb_truetype is introduced in the future. Otherwise, its essentially 1:1 with it. -Freetype isn't really supported and its not a high priority (pretty sure its too slow). +Freetype isn't really supported and its not a high priority. ~~Freetype will do memory allocations and has an interface the user can implement.~~ ~~That interface is not exposed from this parser but could be added to parser_init.~~ @@ -19,7 +19,7 @@ import "base:runtime" import "core:c" import "core:math" import "core:slice" -import stbtt "vendor:stb/truetype" +import stbtt "thirdparty:stb/truetype" // import freetype "thirdparty:freetype" Parser_Kind :: enum u32 { diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index 85eca80..5412349 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -1,6 +1,8 @@ package vefontcache /* -Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists and seems to be under active development as an alternative. +Note(Ed): The only reason I didn't directly use harfbuzz is: +https://github.com/saidwho12/hamza +and seems to be under active development as an alternative. */ import "core:c" @@ -165,11 +167,11 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry if hb_glyph.cluster > 0 { - (max_line_width^) = max( max_line_width^, position.x ) - position.x = 0.0 - position.y -= line_height - position.y = floor(position.y) - (line_count^) += 1 + (max_line_width^) = max( max_line_width^, position.x ) + position.x = 0.0 + position.y -= line_height + position.y = floor(position.y) + (line_count^) += 1 continue } if abs( font_px_size ) <= adv_snap_small_font_threshold @@ -394,8 +396,8 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, // Shapes are tracked by the library's context using the shape cache // and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped. -// Thus this procedures cost will be proporitonal to how muh text it has to sift through. -// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-120 charactes long +// Thus this procedures cost will be proporitonal to how much text it has to sift through. +// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-250 charactes long // (and its very fast). @(optimization_mode="favor_size") shaper_shape_text_cached :: proc( text_utf8 : string, diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 7fb2753..59e2964 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -105,6 +105,8 @@ Context :: struct { px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. default_curve_quality : i32, + + } Init_Atlas_Params :: struct { @@ -657,6 +659,15 @@ get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view return snapped_position } +resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size, interval, min, max : f32 ) -> (resolved_size : f32) +{ + interval_quotient := 1.0 / f32(interval) + size := px_size == 0.0 ? default_size : px_size + even_size := math.round(size * interval_quotient) * interval + resolved_size = clamp( even_size, min, max ) + return +} + set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } set_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); ctx.colour = colour } set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } @@ -761,7 +772,7 @@ draw_text_normalized_space :: proc( ctx : ^Context, ) } -// Equivalent to draw_text_shape_normalized_space, however position's units is scaled to view and must be normalized. +// Equivalent to draw_text_shape_normalized_space, however position's unit convention is expected to be relative to the view // @(optimization_mode="favor_size") draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, font : Font_ID, @@ -803,7 +814,7 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, ) } -// Equivalent to draw_text_normalized_space, however position's units is scaled to view and must be normalized. +// Equivalent to draw_text_shape_normalized_space, however position's unit convention is expected to be relative to the view // @(optimization_mode = "favor_size") draw_text_view_space :: proc(ctx : ^Context, font : Font_ID, @@ -854,6 +865,7 @@ draw_text_view_space :: proc(ctx : ^Context, ) } +// Uses the ctx.stack, position and scale are relative to the position and scale on the stack. // @(optimization_mode = "favor_size") draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) { @@ -907,6 +919,7 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) } +// Uses the ctx.stack, position and scale are relative to the position and scale on the stack. // @(optimization_mode = "favor_size") draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) { @@ -916,11 +929,11 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) stack := & ctx.stack assert(len(stack.font) > 0) - assert(len(stack.view) > 0) + assert(len(stack.font_size) > 0) assert(len(stack.colour) > 0) + assert(len(stack.view) > 0) assert(len(stack.position) > 0) assert(len(stack.scale) > 0) - assert(len(stack.font_size) > 0) assert(len(stack.zoom) > 0) font := peek(stack.font)