Progress on cleanup

This commit is contained in:
2025-01-10 09:52:44 -05:00
parent 77cf07ce16
commit ddfd529993
8 changed files with 68 additions and 259 deletions

View File

@@ -35,8 +35,8 @@ Upcoming:
* Support for which triangulation method used on a by font basis?
* Multi-threading supported job queue.
* Lift heavy-lifting portion of the library's context into a thread context.
* Synchronize threads by merging their generated layered drawlist into a file draw-list for processing on the user's render thread.
* User defines how context's are distributed for drawing (a basic quandrant basic selector procedure will be provided.)
* Synchronize threads by merging their generated layered draw list into a finished draw-list for processing on the user's render thread.
* User defines how thread context's are distributed for drawing (a basic quandrant based selector procedure will be provided.)
See: [docs/Readme.md](docs/Readme.md) for the library's interface.

View File

@@ -12,7 +12,6 @@ Atlas_Region_Kind :: enum u8 {
Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call
}
// Note(Ed): Using 16 bit hash had collision failures and no observable performance improvement (tried several 16-bit hashers)
Atlas_Key :: u32
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Atlas.

View File

@@ -3,7 +3,7 @@ package vefontcache
when false {
// TODO(Ed): Freetype support
// TODO(Ed): glyph caching cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly...
// TODO(Ed): glyph triangulation cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly...
cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32
{
draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex,

View File

@@ -12,7 +12,7 @@ Freetype isn't really supported and its not a high priority (pretty sure its too
STB_Truetype:
* Has macros for its allocation unfortuantely.
TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator.
Already wanted to do so anyway to evaluate the glyph point shape implementation on its side.
Already wanted to do so anyway to evaluate the shape generation implementation.
*/
import "base:runtime"
@@ -24,7 +24,7 @@ import stbtt "vendor:stb/truetype"
Parser_Kind :: enum u32 {
STB_TrueType,
Freetype,
Freetype, // Currently not implemented.
}
Parser_Font_Info :: struct {
@@ -63,40 +63,16 @@ Parser_Context :: struct {
parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind )
{
// switch kind
// {
// case .Freetype:
// result := freetype.init_free_type( & ctx.ft_library )
// assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" )
// case .STB_TrueType:
// Do nothing intentional
// }
ctx.kind = kind
}
parser_shutdown :: proc( ctx : ^Parser_Context ) {
// TODO(Ed): Implement
// Note: Not necesssary for stb_truetype
}
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info, error : b32)
{
// switch ctx.kind
// {
// case .Freetype:
// when ODIN_OS == .Windows {
// error_status := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info )
// if error != .Ok do error = true
// }
// else when ODIN_OS == .Linux {
// error := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i64) len(data), 0, & font.freetype_info )
// if error_status != .Ok do error = true
// }
// case .STB_TrueType:
error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
// }
font.label = label
font.data = data
@@ -106,122 +82,36 @@ parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte )
parser_unload_font :: proc( font : ^Parser_Font_Info )
{
// switch font.kind {
// case .Freetype:
// error := freetype.done_face( font.freetype_info )
// assert( error == .Ok, "VEFontCache.parser_unload_font: Failed to unload freetype face" )
// case .STB_TrueType:
// Do Nothing
// }
// case .STB_TrueType:
// Do Nothing
}
parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
{
// profile(#procedure)
// switch font.kind
// {
// case .Freetype:
// when ODIN_OS == .Windows {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// return
// case .STB_TrueType:
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
return
// }
// return Glyph(-1)
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
return
}
parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape )
{
// switch font.kind
// {
// case .Freetype:
// delete(shape)
// case .STB_TrueType:
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
// }
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
}
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
{
// switch font.kind
// {
// case .Freetype:
// glyph_index : Glyph
// when ODIN_OS == .Windows {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// if glyph_index != 0
// {
// freetype.load_glyph( font.freetype_info, c.uint(codepoint), { .No_Bitmap, .No_Hinting, .No_Scale } )
// advance = i32(font.freetype_info.glyph.advance.x) >> 6
// to_left_side_glyph = i32(font.freetype_info.glyph.metrics.hori_bearing_x) >> 6
// }
// else
// {
// advance = 0
// to_left_side_glyph = 0
// }
// case .STB_TrueType:
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
// }
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
return
}
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
{
// switch font.kind
// {
// case .Freetype:
// prev_glyph_index : Glyph
// glyph_index : Glyph
// when ODIN_OS == .Windows {
// prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) prev_codepoint )
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) prev_codepoint )
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// if prev_glyph_index != 0 && glyph_index != 0
// {
// kerning : freetype.Vector
// font.freetype_info.driver.clazz.get_kerning( font.freetype_info, transmute(u32) prev_codepoint, transmute(u32) codepoint, & kerning )
// }
// case .STB_TrueType:
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
// }
// return -1
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
}
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
{
// switch font.kind
// {
// case .Freetype:
// info := font.freetype_info
// ascent = i32(info.ascender)
// descent = i32(info.descender)
// line_gap = i32(info.height) - (ascent - descent)
// case .STB_TrueType:
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
// }
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
return
}
@@ -230,122 +120,47 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info,
// profile(#procedure)
bounds_0, bounds_1 : Vec2i
// switch font.kind
// {
// case .Freetype:
// freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } )
x0, y0, x1, y1 : i32
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// metrics := font.freetype_info.glyph.metrics
// bounds_0 = {i32(metrics.hori_bearing_x), i32(metrics.hori_bearing_y - metrics.height)}
// bounds_1 = {i32(metrics.hori_bearing_x + metrics.width), i32(metrics.hori_bearing_y)}
// case .STB_TrueType:
x0, y0, x1, y1 : i32
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// assert( success )
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
// }
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
bounds = { vec2(bounds_0), vec2(bounds_1) }
return
}
parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
{
// switch font.kind
// {
// case .Freetype:
// // TODO(Ed): Don't do this, going a completely different route for handling shapes.
// // This abstraction fails to be time-saving or performant.
// case .STB_TrueType:
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
shape_raw.data = stb_shape
shape_raw.len = int(nverts)
shape_raw.cap = int(nverts)
shape_raw.allocator = runtime.nil_allocator()
error = Allocator_Error.None
// return
// }
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
shape_raw.data = stb_shape
shape_raw.len = int(nverts)
shape_raw.cap = int(nverts)
shape_raw.allocator = runtime.nil_allocator()
error = Allocator_Error.None
return
}
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32
{
// switch font.kind
// {
// case .Freetype:
// error := freetype.load_glyph( font.freetype_info, cast(u32) glyph_index, { .No_Bitmap, .No_Hinting, .No_Scale } )
// if error == .Ok
// {
// if font.freetype_info.glyph.format == .Outline {
// return font.freetype_info.glyph.outline.n_points == 0
// }
// else if font.freetype_info.glyph.format == .Bitmap {
// return font.freetype_info.glyph.bitmap.width == 0 && font.freetype_info.glyph.bitmap.rows == 0;
// }
// }
// return false
// case .STB_TrueType:
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
// }
// return false
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
}
parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// profile(#procedure)
// size_scale := size < 0.0 ? parser_scale_for_pixel_height( font, -size ) : parser_scale_for_mapping_em_to_pixels( font, size )
size_scale := size > 0.0 ? parser_scale_for_pixel_height( font, size ) : parser_scale_for_mapping_em_to_pixels( font, -size )
return size_scale
}
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// switch font.kind {
// case .Freetype:
// freetype.set_pixel_sizes( font.freetype_info, 0, cast(u32) size )
// size_scale := size / cast(f32)font.freetype_info.units_per_em
// return size_scale
// case.STB_TrueType:
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
// }
// return 0
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
}
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// switch font.kind {
// case .Freetype:
// Inches_To_CM :: cast(f32) 2.54
// Points_Per_CM :: cast(f32) 28.3465
// CM_Per_Point :: cast(f32) 1.0 / DPT_DPCM
// CM_Per_Pixel :: cast(f32) 1.0 / DPT_PPCM
// DPT_DPCM :: cast(f32) 72.0 * Inches_To_CM // 182.88 points/dots per cm
// DPT_PPCM :: cast(f32) 96.0 * Inches_To_CM // 243.84 pixels per cm
// DPT_DPI :: cast(f32) 72.0
// // TODO(Ed): Don't assume the dots or pixels per inch.
// system_dpi :: DPT_DPI
// FT_Font_Size_Point_Unit :: 1.0 / 64.0
// FT_Point_10 :: 64.0
// points_per_em := (size / system_dpi ) * DPT_DPI
// freetype.set_char_size( font.freetype_info, 0, cast(freetype.F26Dot6) f32(points_per_em * FT_Point_10), cast(u32) DPT_DPI, cast(u32) DPT_DPI )
// size_scale := f32(f64(size) / cast(f64) font.freetype_info.units_per_em)
// return size_scale
// case .STB_TrueType:
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
// }
// return 0
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
}

View File

@@ -136,22 +136,4 @@ vec2_64 :: proc {
vec2_64_from_vec2,
}
import "../../grime"
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
grime.profile_begin(name, loc)
}
@(disabled = DISABLE_PROFILING)
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
grime.profile_begin(name, loc)
}
@(disabled = DISABLE_PROFILING)
profile_end :: #force_inline proc "contextless" () {
grime.profile_end()
}
//#endregion("Proc overload mappings")

View File

@@ -0,0 +1,17 @@
package vefontcache
// Add profiling hookup here
// import ""
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
}
@(disabled = DISABLE_PROFILING)
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
}
@(disabled = DISABLE_PROFILING)
profile_end :: #force_inline proc "contextless" () {
}

View File

@@ -13,15 +13,14 @@ Shape_Key :: u32
Traditionally a shape only refers to resolving which glyph and
its position should be used for rendering.
For this library's case it also involes keeping any content
that does not have to be resolved once again in the later stage of processing:
For this library's case it also resolves any content that does not have to be done
on a per-frame basis for draw list generation.
* Resolve atlas lru codes
* Resolve glyph bounds and scale
* Resolve atlas region the glyph is associated with.
Ideally the user should resolve this shape once and cache/store it on their side.
They have the best ability to avoid costly lookups to streamline
a hot path to only focusing on draw list generation that must be computed every frame.
They have the best ability to avoid costly lookups.
*/
Shaped_Text :: struct #packed {
glyph : [dynamic]Glyph,
@@ -190,8 +189,8 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry
f32(hb_gposition.x_advance) * font_scale,
f32(hb_gposition.y_advance) * font_scale
}
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
is_empty := parser_is_glyph_empty(entry.parser_info, glyph)
if ! is_empty {

View File

@@ -94,7 +94,8 @@ Context :: struct {
stack : Scope_Stack,
cursor_pos : Vec2, // TODO(Ed): Review this (most likely not being used properly right now)
colour : RGBAN, // Color used in draw interface TODO(Ed): use the stack
cursor_pos : Vec2, // TODO(Ed): Review this, no longer used much at all... (still useful I guess)
// Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges.
// Has a boldening side-effect. If overblown will look smeared.
alpha_sharpen : f32,
@@ -647,11 +648,9 @@ get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { r
get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view : Vec2 ) -> (snapped_position : Vec2) {
snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) }
should_snap := view * snap_quotient
snapped_position = position
snapped_position.x = ceil(position.x * view.x) * snap_quotient.x
snapped_position.y = ceil(position.y * view.y) * snap_quotient.y
snapped_position *= should_snap
snapped_position.x = max(snapped_position.x, position.x)
snapped_position.y = max(snapped_position.y, position.y)
@@ -763,7 +762,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.
@(optimization_mode="favor_size")
// @(optimization_mode="favor_size")
draw_text_shape_view_space :: #force_inline proc( ctx : ^Context,
font : Font_ID,
px_size : f32,
@@ -805,16 +804,16 @@ 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.
@(optimization_mode = "favor_size")
// @(optimization_mode = "favor_size")
draw_text_view_space :: proc(ctx : ^Context,
font : Font_ID,
px_size : f32,
colour : RGBAN,
view : Vec2,
view_position : Vec2,
scale : Vec2,
zoom : f32, // TODO(Ed): Implement Zoom support
text_utf8 : string
font : Font_ID,
px_size : f32,
colour : RGBAN,
view : Vec2,
position : Vec2,
scale : Vec2,
zoom : f32, // TODO(Ed): Implement Zoom support
text_utf8 : string
)
{
profile(#procedure)
@@ -855,7 +854,7 @@ draw_text_view_space :: proc(ctx : ^Context,
)
}
@(optimization_mode = "favor_size")
// @(optimization_mode = "favor_size")
draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text )
{
profile(#procedure)
@@ -908,7 +907,7 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text
)
}
@(optimization_mode = "favor_size")
// @(optimization_mode = "favor_size")
draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string )
{
profile(#procedure)
@@ -1020,7 +1019,7 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size
entry := ctx.entries[font]
downscale := 1 / ctx.px_scalar
target_scale := 1 / ctx.px_scalar
target_px_size := px_size * ctx.px_scalar
target_font_scale := parser_scale( entry.parser_info, target_px_size )
@@ -1035,7 +1034,7 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size
target_font_scale,
shaper_shape_text_uncached_advanced
)
return shaped.size * downscale
return shaped.size * target_scale
}
get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 )
@@ -1102,7 +1101,6 @@ shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si
}
// User handled shaped text. Will not be cached
// @(disabled = true)
shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text )
{
profile(#procedure)
@@ -1125,7 +1123,6 @@ shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID,
}
// User handled shaped text. Will not be cached
// @(disabled = true)
shape_text_advanced_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text )
{
profile(#procedure)