From 566a90001b044c445ed3027b1870058ed1b01d65 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 5 Jun 2024 19:52:25 -0400 Subject: [PATCH] Finished draft porting pass for VE Font Cache (next is hook to sokol_gfx + runtime testing) --- code/font/VEFontCache/VEFontCache.odin | 152 +++++++++++-- code/font/VEFontCache/atlas.odin | 2 +- code/font/VEFontCache/parser.odin | 295 +++++++++++++++---------- 3 files changed, 314 insertions(+), 135 deletions(-) diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 8f4951b..d82569a 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -15,6 +15,8 @@ Changes: */ package VEFontCache +import "core:math" + Advance_Snap_Smallfont_Size :: 12 FontID :: distinct i64 @@ -25,12 +27,13 @@ Vec2 :: [2]f32 Vec2i :: [2]u32 AtlasRegionKind :: enum u8 { - None = 0x00, - A = 0x41, - B = 0x42, - C = 0x43, - D = 0x44, - E = 0x45, + None = 0x00, + A = 0x41, + B = 0x42, + C = 0x43, + D = 0x44, + E = 0x45, + Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call } Vertex :: struct { @@ -571,6 +574,9 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph assert( LRU_get( & region.state, lru_code ) != - 1 ) } + atlas := & ctx.atlas + glyph_padding := cast(f32) atlas.glyph_padding + if ctx.debug_print { @static debug_total_cached : i32 = 0 @@ -580,29 +586,72 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph // Draw oversized glyph to update FBO glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + Vec2{ f32(ctx.atlas.glyph_padding), f32(ctx.atlas.glyph_padding) } + glyph_draw_translate := Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + Vec2{ glyph_padding, glyph_padding } glyph_draw_translate.x = cast(f32) (i32(glyph_draw_translate.x + 0.9999999)) glyph_draw_translate.y = cast(f32) (i32(glyph_draw_translate.y + 0.9999999)) // Allocate a glyph_update_FBO region - // gwidth_scaled_px = + gwidth_scaled_px := i32( f32(bounds_width) * f32(glyph_draw_scale.x) + 1.0 ) + i32(2 * over_sample.x * glyph_padding) + if i32(atlas.update_batch_x + gwidth_scaled_px) >= i32(atlas.buffer_width) { + flush_glyph_buffer_to_atlas( ctx ) + } // Calculate the src and destination regions + dst_position, dst_width, dst_height := atlas_bbox( atlas, region_kind, u32(atlas_index) ) + dst_glyph_position := dst_position //+ { glyph_padding, glyph_padding } + dst_glyph_width := f32(bounds_width) * entry.size_scale + dst_glyph_height := f32(bounds_height) * entry.size_scale + // dst_glyph_position -= { glyph_padding, glyph_padding } + dst_glyph_width += 2 * glyph_padding + dst_glyph_height += 2 * glyph_padding + + dst_size := Vec2 { dst_width, dst_height } + dst_glyph_size := Vec2 { dst_glyph_width, dst_glyph_height } + screenspace_x_form( & dst_glyph_position, & dst_glyph_size, f32(atlas.buffer_width), f32(atlas.buffer_height) ) + screenspace_x_form( & dst_position, & dst_size, f32(atlas.buffer_width), f32(atlas.buffer_height) ) + + src_position := Vec2 { f32(atlas.update_batch_x), 0 } + src_size := Vec2 { + f32(bounds_width) * glyph_draw_scale.x, + f32(bounds_height) * glyph_draw_scale.y, + } + src_size += Vec2{1,1} * 2 * over_sample * glyph_padding + textspace_x_form( & src_position, & src_size, f32(atlas.buffer_width), f32(atlas.buffer_height) ) // Advance glyph_update_batch_x and calculate final glyph drawing transform + glyph_draw_translate.x += f32(atlas.update_batch_x) + atlas.update_batch_x += gwidth_scaled_px + screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, f32(atlas.buffer_width), f32(atlas.buffer_height)) - // Queue up clear on target region on atlas + call : DrawCall + { + // Queue up clear on target region on atlas + using call + pass = .Atlas + region = .Ignore + start_index = u32(atlas.clear_draw_list.indices.num) + blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } ) + end_index = u32(atlas.clear_draw_list.indices.num) + append( & atlas.clear_draw_list.calls, call ) + + // Queue up a blit from glyph_update_FBO to the atlas + region = .None + start_index = u32(atlas.draw_list.indices.num) + blit_quad( & atlas.draw_list, dst_glyph_position, dst_glyph_position + dst_glyph_size, src_position, src_position + src_size ) + end_index = u32(atlas.draw_list.indices.num) + append( & atlas.draw_list.calls, call ) + } - // Queue up a blit from glyph_update_FBO to the atlas // Render glyph to glyph_update_FBO - // cache_glyph( ) + cache_glyph( ctx, font, glyph_index, glyph_draw_scale, glyph_draw_translate ) } directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : u32, over_sample, position, scale : Vec2 ) { flush_glyph_buffer_to_atlas( ctx ) + // Draw un-antialiased glyph to update FBO. glyph_draw_scale := over_sample * entry.size_scale glyph_draw_translate := - Vec2{ f32(bounds_0.x), f32(bounds_0.y)} * glyph_draw_scale + Vec2{ f32(ctx.atlas.glyph_padding), f32(ctx.atlas.glyph_padding) } screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) @@ -610,12 +659,46 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly cache_glyph( ctx, entry.id, glyph, glyph_draw_scale, glyph_draw_translate ) // Figure out the source rect. + glyph_position := Vec2 {} + glyph_width := f32(bounds_width) * entry.size_scale * over_sample.x + glyph_height := f32(bounds_height) * entry.size_scale * over_sample.y + glyph_dst_width := f32(bounds_width) * entry.size_scale + glyph_dst_height := f32(bounds_height) * entry.size_scale + glyph_width += f32(2 * ctx.atlas.glyph_padding) + glyph_height += f32(2 * ctx.atlas.glyph_padding) // Figure out the destination rect. + bounds_scaled := Vec2 { + cast(f32) i32(f32(bounds_0.x) * entry.size_scale - 0.5), + cast(f32) i32(f32(bounds_0.y) * entry.size_scale - 0.5), + } + dst := position + scale * bounds_scaled + dst_width := scale.x * glyph_dst_width + dst_height := scale.y * glyph_dst_height + dst.x -= scale.x * f32(ctx.atlas.glyph_padding) + dst.y -= scale.y * f32(ctx.atlas.glyph_padding) + + glyph_size := Vec2 { glyph_width, glyph_height } + textspace_x_form( & glyph_position, & glyph_size, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) // Add the glyph drawcall. + call : DrawCall + { + using call + pass = .Target_Unchanged + colour = ctx.colour + start_index = u32(ctx.draw_list.indices.num) + blit_quad( & ctx.draw_list, dst, dst + { dst_width, dst_height }, glyph_position, glyph_position + glyph_size ) + end_index = u32(ctx.draw_list.indices.num) + append( & ctx.draw_list.calls, call ) + } // Clear glyph_update_FBO. + call.pass = .Glyph + call.start_index = 0 + call.end_index = 0 + call.clear_before_draw = true + append( & ctx.draw_list.calls, call ) } is_empty :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 @@ -689,13 +772,52 @@ shape_text_uncached :: proc( ctx : ^Context, font : FontID, output : ^ShapedText if use_full_text_shape { assert( entry.shaper_info != nil ) - shaper_shape_from_text( & ctx.shaper_ctx, entry.shaper_info, output, text_utf8, ascent, descent, line_gap, entry.size, entry.size_scale ) return } + else + { + ascent := f32(ascent) + descent := f32(descent) + line_gap := f32(line_gap) - // We use our own fallback dumbass text shaping. - // WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. + // Note(Original Author): + // We use our own fallback dumbass text shaping. + // WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. - + position : Vec2 + advance : i32 = 0 + to_left_side_glyph : i32 = 0 + + prev_codepoint : rune + for codepoint in text_utf8 + { + if prev_codepoint > 0 { + kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint ) + position.x += f32(kern) * entry.size_scale + } + if codepoint == '\n' + { + position.x = 0.0 + position.y -= (ascent - descent + line_gap) * entry.size_scale + position.y = cast(f32) i32( position.y + 0.5 ) + prev_codepoint = rune(0) + continue + } + if math.abs( entry.size ) <= Advance_Snap_Smallfont_Size { + position.x = math.ceil( position.x ) + } + + append( & output.glyphs, parser_find_glyph_index( entry.parser_info, codepoint )) + advance, to_left_side_glyph = parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) + + append( & output.positions, Vec2 { + cast(f32) i32(position.x + 0.5), + position.y + }) + + position.x += f32(advance) * entry.size_scale + prev_codepoint = codepoint + } + } } diff --git a/code/font/VEFontCache/atlas.odin b/code/font/VEFontCache/atlas.odin index 66aeb7d..d166149 100644 --- a/code/font/VEFontCache/atlas.odin +++ b/code/font/VEFontCache/atlas.odin @@ -71,9 +71,9 @@ atlas_bbox :: proc( atlas : ^Atlas, region : AtlasRegionKind, local_idx : u32 ) position.x += f32(atlas.region_d.offset.x) position.y += f32(atlas.region_d.offset.y) + case .Ignore: fallthrough case .None: fallthrough case .E: - assert(false, "What?") } return } diff --git a/code/font/VEFontCache/parser.odin b/code/font/VEFontCache/parser.odin index 750eeb1..69e69d9 100644 --- a/code/font/VEFontCache/parser.odin +++ b/code/font/VEFontCache/parser.odin @@ -106,6 +106,75 @@ parser_unload_font :: proc( font : ^ParserFontInfo ) } } +parser_find_glyph_index :: proc( font : ^ParserFontInfo, codepoint : rune ) -> (glyph_index : Glyph) +{ + switch font.kind + { + case .Freetype: + glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint ) + + case .STB_TrueType: + glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( & font.stbtt_info, codepoint ) + } + return Glyph(-1) +} + +parser_free_shape :: proc( font : ^ParserFontInfo, shape : ParserGlyphShape ) +{ + switch font.kind + { + case .Freetype: + delete( array_underlying_slice(shape) ) + + case .STB_TrueType: + stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) + } +} + +parser_get_codepoint_horizontal_metrics :: proc( font : ^ParserFontInfo, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 ) +{ + switch font.kind + { + case .Freetype: + glyph_index := transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) 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 ) + } + return +} + +parser_get_codepoint_kern_advance :: proc( font : ^ParserFontInfo, prev_codepoint, codepoint : rune ) -> i32 +{ + switch font.kind + { + case .Freetype: + 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 ) + 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 +} + parser_get_font_vertical_metrics :: proc( font : ^ParserFontInfo ) -> (ascent, descent, line_gap : i32 ) { switch font.kind @@ -118,115 +187,27 @@ parser_get_font_vertical_metrics :: proc( font : ^ParserFontInfo ) -> (ascent, d return } -parser_scale_for_pixel_height :: #force_inline proc( font : ^ParserFontInfo, 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 -} - -parser_scale_for_mapping_em_to_pixels :: proc( font : ^ParserFontInfo, 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 := size / cast(f32) font.freetype_info.units_per_em; - return size_scale - - case .STB_TrueType: - return stbtt.ScaleForMappingEmToPixels( & font.stbtt_info, size ) - } - return 0 -} - -parser_is_glyph_empty :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> b32 +parser_get_glyph_box :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) { 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 + freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) + + metrics := font.freetype_info.glyph.metrics + + bounds_0 = {u32(metrics.hori_bearing_x), u32(metrics.hori_bearing_y - metrics.height)} + bounds_1 = {u32(metrics.hori_bearing_x + metrics.width), u32(metrics.hori_bearing_y)} case .STB_TrueType: - return stbtt.IsGlyphEmpty( & font.stbtt_info, cast(c.int) glyph_index ) + x0, y0, x1, y1 : i32 + success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) + assert( success ) + + bounds_0 = { u32(x0), u32(y0) } + bounds_1 = { u32(x1), u32(y1) } } - return false -} - -when false { -parser_convert_conic_to_cubic_freetype :: proc( vertices : Array(ParserGlyphVertex), p0, p1, p2 : freetype.Vector, tolerance : f32 ) -{ - scratch : [Kilobyte * 4]u8 - scratch_arena : Arena; arena_init(& scratch_arena, scratch[:]) - - points, error := make( Array(freetype.Vector), 256, allocator = arena_allocator( &scratch_arena) ) - assert(error != .None) - - append( & points, p0) - append( & points, p1) - append( & points, p2) - - to_float : f32 = 1.0 / 64.0 - control_conv :: f32(2.0 / 3.0) // Conic to cubic control point distance - - for ; points.num > 1; { - p0 := points.data[0] - p1 := points.data[1] - p2 := points.data[2] - - fp0 := Vec2{ f32(p0.x), f32(p0.y) } * to_float - fp1 := Vec2{ f32(p1.x), f32(p1.y) } * to_float - fp2 := Vec2{ f32(p2.x), f32(p2.y) } * to_float - - delta_x := fp0.x - 2 * fp1.x + fp2.x; - delta_y := fp0.y - 2 * fp1.y + fp2.y; - distance := math.sqrt(delta_x * delta_x + delta_y * delta_y); - - if distance <= tolerance - { - control1 := { - - } - } - else - { - control2 := { - - } - } - } -} + return } parser_get_glyph_shape :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> (shape : ParserGlyphShape, error : AllocatorError) @@ -380,37 +361,113 @@ parser_get_glyph_shape :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> return } -parser_free_shape :: proc( font : ^ParserFontInfo, shape : ParserGlyphShape ) +parser_is_glyph_empty :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> b32 { switch font.kind { case .Freetype: - delete( array_underlying_slice(shape) ) + 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: - stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) + return stbtt.IsGlyphEmpty( & font.stbtt_info, cast(c.int) glyph_index ) } + return false } -parser_get_glyph_box :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) +parser_scale_for_pixel_height :: #force_inline proc( font : ^ParserFontInfo, size : f32 ) -> f32 { - switch font.kind - { + switch font.kind { case .Freetype: - freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) + 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 - metrics := font.freetype_info.glyph.metrics + case.STB_TrueType: + return stbtt.ScaleForPixelHeight( & font.stbtt_info, size ) + } + return 0 +} - bounds_0 = {u32(metrics.hori_bearing_x), u32(metrics.hori_bearing_y - metrics.height)} - bounds_1 = {u32(metrics.hori_bearing_x + metrics.width), u32(metrics.hori_bearing_y)} +parser_scale_for_mapping_em_to_pixels :: proc( font : ^ParserFontInfo, 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 := size / cast(f32) font.freetype_info.units_per_em; + return size_scale 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 = { u32(x0), u32(y0) } - bounds_1 = { u32(x1), u32(y1) } + return stbtt.ScaleForMappingEmToPixels( & font.stbtt_info, size ) } - return + return 0 +} + +when false { +parser_convert_conic_to_cubic_freetype :: proc( vertices : Array(ParserGlyphVertex), p0, p1, p2 : freetype.Vector, tolerance : f32 ) +{ + scratch : [Kilobyte * 4]u8 + scratch_arena : Arena; arena_init(& scratch_arena, scratch[:]) + + points, error := make( Array(freetype.Vector), 256, allocator = arena_allocator( &scratch_arena) ) + assert(error != .None) + + append( & points, p0) + append( & points, p1) + append( & points, p2) + + to_float : f32 = 1.0 / 64.0 + control_conv :: f32(2.0 / 3.0) // Conic to cubic control point distance + + for ; points.num > 1; { + p0 := points.data[0] + p1 := points.data[1] + p2 := points.data[2] + + fp0 := Vec2{ f32(p0.x), f32(p0.y) } * to_float + fp1 := Vec2{ f32(p1.x), f32(p1.y) } * to_float + fp2 := Vec2{ f32(p2.x), f32(p2.y) } * to_float + + delta_x := fp0.x - 2 * fp1.x + fp2.x; + delta_y := fp0.y - 2 * fp1.y + fp2.y; + distance := math.sqrt(delta_x * delta_x + delta_y * delta_y); + + if distance <= tolerance + { + control1 := { + + } + } + else + { + control2 := { + + } + } + } +} }