Finished draft porting pass for VE Font Cache (next is hook to sokol_gfx + runtime testing)

This commit is contained in:
Edward R. Gonzalez 2024-06-05 19:52:25 -04:00
parent d469fd53e8
commit 566a90001b
3 changed files with 314 additions and 135 deletions

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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 := {
}
}
}
}
}