More cleanup, doc updates

This commit is contained in:
2025-01-10 12:44:53 -05:00
parent 18decf3e46
commit 2eb94e077f
8 changed files with 49 additions and 43 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
build build
thirdparty # thirdparty
.vscode .vscode
# backend/sokol/render_glyph.odin # backend/sokol/render_glyph.odin
# backend/sokol/blit_atlas.odin # backend/sokol/blit_atlas.odin

View File

@@ -1,4 +1,4 @@
VEFontCache Odin Port VEFontCache Odin
Copyright 2024 Edward R. Gonzalez Copyright 2024 Edward R. Gonzalez
This project is based on Vertex Engine GPU Font Cache This project is based on Vertex Engine GPU Font Cache

View File

@@ -43,11 +43,11 @@ pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : s
{ {
error : Allocator_Error error : Allocator_Error
pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) ) 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 ) resize( & pool.items, capacity )
pool.free_list, error = make( [dynamic]Pool_ListIter, len = 0, cap = int(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 ) resize( & pool.free_list, capacity )
pool.capacity = 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) ) assert( length == int(pool.capacity - pool.size) )
id := pool.free_list[ len(pool.free_list) - 1 ] id := pool.free_list[ len(pool.free_list) - 1 ]
if pool.dbg_name != "" { // if pool.dbg_name != "" {
logf("pool_list: back %v", id) // logf("pool_list: back %v", id)
} // }
pop( & pool.free_list ) pop( & pool.free_list )
pool.items[ id ].prev = -1 pool.items[ id ].prev = -1
pool.items[ id ].next = pool.front pool.items[ id ].next = pool.front
pool.items[ id ].value = value pool.items[ id ].value = value
if pool.dbg_name != "" { // if pool.dbg_name != "" {
logf("pool_list: pushed %v into id %v", value, id) // logf("pool_list: pushed %v into id %v", value, id)
} // }
if pool.front != -1 do pool.items[ pool.front ].prev = id if pool.front != -1 do pool.items[ pool.front ].prev = id
if pool.back == -1 do pool.back = id if pool.back == -1 do pool.back = id

View File

@@ -109,7 +109,7 @@ Glyph_Draw_Buffer :: struct{
cached : [dynamic]i32, 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") @(optimization_mode="favor_size")
blit_quad :: #force_inline proc ( draw_list : ^Draw_List, blit_quad :: #force_inline proc ( draw_list : ^Draw_List,
p0 : Vec2 = {0, 0}, p0 : Vec2 = {0, 0},
@@ -279,24 +279,15 @@ generate_shapes_draw_list :: #force_inline proc ( ctx : ^Context,
} }
/* Generator pipeline for shapes /* 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. 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: Pipleine order:
* Resolve the glyph's position offset from the target position * Resolve the glyph's position offset from the target position
* Segregate the glyphs into three slices: oversized, to_cache, cached. * 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. * 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.
* The segregation will not allow slices to exceed the batch_cache capacity of the glyph_buffer (configurable within startup params) * 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. * 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. * 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, generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
atlas : ^Atlas, 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 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...) 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...) 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. 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. 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: 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. * 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") @(optimization_mode = "favor_size")
batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,

View File

@@ -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) 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) } } @(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 // This means a single line is limited to 4k buffer
// Logger_Allocator_Buffer : [4 * Kilobyte]u8 // Logger_Allocator_Buffer : [4 * Kilobyte]u8

View File

@@ -2,10 +2,10 @@ package vefontcache
/* /*
Notes: 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. 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.~~ ~~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.~~ ~~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:c"
import "core:math" import "core:math"
import "core:slice" import "core:slice"
import stbtt "vendor:stb/truetype" import stbtt "thirdparty:stb/truetype"
// import freetype "thirdparty:freetype" // import freetype "thirdparty:freetype"
Parser_Kind :: enum u32 { Parser_Kind :: enum u32 {

View File

@@ -1,6 +1,8 @@
package vefontcache 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" import "core:c"
@@ -165,11 +167,11 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry
if hb_glyph.cluster > 0 if hb_glyph.cluster > 0
{ {
(max_line_width^) = max( max_line_width^, position.x ) (max_line_width^) = max( max_line_width^, position.x )
position.x = 0.0 position.x = 0.0
position.y -= line_height position.y -= line_height
position.y = floor(position.y) position.y = floor(position.y)
(line_count^) += 1 (line_count^) += 1
continue continue
} }
if abs( font_px_size ) <= adv_snap_small_font_threshold 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 // 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. // 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. // 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-120 charactes long // djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-250 charactes long
// (and its very fast). // (and its very fast).
@(optimization_mode="favor_size") @(optimization_mode="favor_size")
shaper_shape_text_cached :: proc( text_utf8 : string, shaper_shape_text_cached :: proc( text_utf8 : string,

View File

@@ -105,6 +105,8 @@ Context :: struct {
px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged.
default_curve_quality : i32, default_curve_quality : i32,
} }
Init_Atlas_Params :: struct { Init_Atlas_Params :: struct {
@@ -657,6 +659,15 @@ get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view
return snapped_position 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_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_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 } 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") // @(optimization_mode="favor_size")
draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, draw_text_shape_view_space :: #force_inline proc( ctx : ^Context,
font : Font_ID, 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") // @(optimization_mode = "favor_size")
draw_text_view_space :: proc(ctx : ^Context, draw_text_view_space :: proc(ctx : ^Context,
font : Font_ID, 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") // @(optimization_mode = "favor_size")
draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) 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") // @(optimization_mode = "favor_size")
draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) 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 stack := & ctx.stack
assert(len(stack.font) > 0) assert(len(stack.font) > 0)
assert(len(stack.view) > 0) assert(len(stack.font_size) > 0)
assert(len(stack.colour) > 0) assert(len(stack.colour) > 0)
assert(len(stack.view) > 0)
assert(len(stack.position) > 0) assert(len(stack.position) > 0)
assert(len(stack.scale) > 0) assert(len(stack.scale) > 0)
assert(len(stack.font_size) > 0)
assert(len(stack.zoom) > 0) assert(len(stack.zoom) > 0)
font := peek(stack.font) font := peek(stack.font)