mirror of
https://github.com/Ed94/VEFontCache-Odin.git
synced 2025-08-05 14:42:42 -07:00
More cleanup, doc updates
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user