From ddfd529993fe771920fed53b9eea28abf2f16185 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 09:52:44 -0500 Subject: [PATCH] Progress on cleanup --- Readme.md | 4 +- vefontcache/atlas.odin | 1 - vefontcache/freetype_wip.odin | 2 +- vefontcache/parser.odin | 239 ++++------------------------------ vefontcache/pkg_mapping.odin | 18 --- vefontcache/profiling.odin | 17 +++ vefontcache/shaper.odin | 11 +- vefontcache/vefontcache.odin | 35 +++-- 8 files changed, 68 insertions(+), 259 deletions(-) create mode 100644 vefontcache/profiling.odin diff --git a/Readme.md b/Readme.md index 9951a30..c73acf9 100644 --- a/Readme.md +++ b/Readme.md @@ -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. diff --git a/vefontcache/atlas.odin b/vefontcache/atlas.odin index ddb95ac..6a0213e 100644 --- a/vefontcache/atlas.odin +++ b/vefontcache/atlas.odin @@ -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. diff --git a/vefontcache/freetype_wip.odin b/vefontcache/freetype_wip.odin index 8458182..d6306a4 100644 --- a/vefontcache/freetype_wip.odin +++ b/vefontcache/freetype_wip.odin @@ -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, diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 6be355a..cef2502 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -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 ) } diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index 9e39d0f..87bb178 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -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") - diff --git a/vefontcache/profiling.odin b/vefontcache/profiling.odin new file mode 100644 index 0000000..5d832f5 --- /dev/null +++ b/vefontcache/profiling.odin @@ -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" () { +} diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index de2ab71..85eca80 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -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 { diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 8770c09..7fb2753 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -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)