VEFC: ported decide_codepoint_region

This commit is contained in:
Edward R. Gonzalez 2024-06-04 13:30:30 -04:00
parent 38ebed8874
commit 26e53bf327
4 changed files with 320 additions and 163 deletions

View File

@ -11,6 +11,7 @@ Changes:
- Font Parser & Glyph Shaper are abstracted to their own interface
- Font Face parser info stored separately from entries
- ve_fontcache_loadfile not ported (just use odin's core:os or os2), then call load_font
- Macro defines have been made into runtime parameters
*/
package VEFontCache
@ -21,12 +22,13 @@ Colour :: [4]f32
Vec2 :: [2]f32
Vec2i :: [2]u32
AtlasRegionKind :: enum {
A = 0,
B = 1,
C = 2,
D = 3,
E = 4,
AtlasRegionKind :: enum u8 {
None = 0x00,
A = 0x41,
B = 0x42,
C = 0x43,
D = 0x44,
E = 0x45,
}
Vertex :: struct {
@ -34,15 +36,6 @@ Vertex :: struct {
u, v : f32,
}
// GlyphDrawBuffer :: struct {
// over_sample : Vec2,
// batch : i32,
// width : i32,
// height : i32,
// padding : i32,
// }
ShapedText :: struct {
glyphs : Array(Glyph),
positions : Array(Vec2),
@ -97,6 +90,7 @@ Context :: struct {
atlas : Atlas,
shape_cache : ShapedTextCache,
curve_quality : u32,
text_shape_adv : b32,
debug_print_verbose : b32
@ -150,15 +144,15 @@ InitAtlasParams_Default :: InitAtlasParams {
}
InitGlyphDrawParams :: struct {
over_sample : Vec2i,
buffer_batch : u32,
padding : u32,
over_sample : Vec2,
buffer_batch : u32,
draw_padding : u32,
}
InitGlyphDrawParams_Default :: InitGlyphDrawParams {
over_sample = { 4, 4 },
buffer_batch = 4,
padding = InitAtlasParams_Default.glyph_padding,
over_sample = { 4, 4 },
buffer_batch = 4,
draw_padding = InitAtlasParams_Default.glyph_padding,
}
InitShapeCacheParams :: struct {
@ -177,6 +171,7 @@ init :: proc( ctx : ^Context,
atlas_params := InitAtlasParams_Default,
glyph_draw_params := InitGlyphDrawParams_Default,
shape_cache_params := InitShapeCacheParams_Default,
curve_quality : u32 = 6,
advance_snap_smallfont_size : u32 = 12,
entires_reserve : u32 = Kilobyte,
temp_path_reserve : u32 = Kilobyte,
@ -189,6 +184,8 @@ init :: proc( ctx : ^Context,
ctx.backing = allocator
context.allocator = ctx.backing
ctx.curve_quality = curve_quality
error : AllocatorError
entries, error = make( Array(Entry), u64(entires_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate entries")
@ -233,6 +230,10 @@ init :: proc( ctx : ^Context,
init_atlas_region( & atlas.region_c, atlas_params, atlas_params.region_c )
init_atlas_region( & atlas.region_d, atlas_params, atlas_params.region_d )
atlas.width = atlas_params.width
atlas.height = atlas_params.height
atlas.glyph_padding = atlas_params.glyph_padding
atlas.region_b.offset.y = atlas.region_a.size.y
atlas.region_c.offset.x = atlas.region_a.size.x
atlas.region_d.offset.x = atlas.width / 2
@ -251,6 +252,12 @@ init :: proc( ctx : ^Context,
// Note(From original author): We can actually go over VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH batches due to smart packing!
{
using atlas
over_sample = glyph_draw_params.over_sample
buffer_batch = glyph_draw_params.buffer_batch
buffer_width = region_d.width * u32(over_sample.x) * buffer_batch
buffer_height = region_d.height * u32(over_sample.y)
draw_padding = glyph_draw_params.draw_padding
draw_list.calls, error = make( Array(DrawCall), cast(u64) glyph_draw_params.buffer_batch * 2 )
assert( error != .None, "VEFontCache.init : Failed to allocate calls for draw_list" )
@ -348,53 +355,6 @@ configure_snap :: proc( ctx : ^Context, snap_width, snap_height : u32 ) {
ctx.snap_height = snap_height
}
// ve_fontcache_drawlist
get_draw_list :: proc( ctx : ^Context ) -> ^DrawList {
assert( ctx != nil )
return & ctx.draw_list
}
// ve_fontcache_clear_drawlist
clear_draw_list :: proc( draw_list : ^DrawList ) {
clear( draw_list.calls )
clear( draw_list.indices )
clear( draw_list.vertices )
}
// ve_fontcache_merge_drawlist
merge_draw_list :: proc( dst, src : ^DrawList )
{
error : AllocatorError
v_offset := cast(u32) dst.vertices.num
// for index : u32 = 0; index < cast(u32) src.vertices.num; index += 1 {
// error = append( & dst.vertices, src.vertices.data[index] )
// assert( error == .None )
// }
error = append( & dst.vertices, src.vertices )
assert( error == .None )
i_offset := cast(u32) dst.indices.num
for index : u32 = 0; index < cast(u32) src.indices.num; index += 1 {
error = append( & dst.indices, src.indices.data[index] + v_offset )
assert( error == .None )
}
for index : u32 = 0; index < cast(u32) src.calls.num; index += 1 {
src_call := src.calls.data[ index ]
src_call.start_index += i_offset
src_call.end_index += i_offset
append( & dst.calls, src_call )
assert( error == .None )
}
}
// ve_fontcache_flush_drawlist
flush_draw_list :: proc( ctx : ^Context ) {
assert( ctx != nil )
clear_draw_list( & ctx.draw_list )
}
// For a provided alpha value,
// allows the function to calculate the position of a point along the curve at any given fraction of its total length
// ve_fontcache_eval_bezier (quadratic)
@ -422,92 +382,6 @@ eval_point_on_bezier4 :: proc( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2
return point
}
// Constructs a triangle fan to fill a shape using the provided path
// outside_point represents the center point of the fan.
//
// Note(Original Author):
// WARNING: doesn't actually append drawcall; caller is responsible for actually appending the drawcall.
// ve_fontcache_draw_filled_path
draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []Vec2,
scale := Vec2 { 1, 1 },
translate := Vec2 { 0, 0 },
debug_print_verbose : b32 = false
)
{
if debug_print_verbose
{
log("outline_path: \n")
for point in path {
logf(" %.2f %.2f\n", point.x * scale )
}
}
v_offset := cast(u32) draw_list.vertices.num
for point in path {
vertex := Vertex {
pos = point * scale + translate,
u = 0,
v = 0,
}
append( & draw_list.vertices, vertex )
}
outside_vertex := cast(u32) draw_list.vertices.num
{
vertex := Vertex {
pos = outside_point * scale + translate,
u = 0,
v = 0,
}
append( & draw_list.vertices, vertex )
}
for index : u32 = 1; index < u32(len(path)); index += 1 {
indices := & draw_list.indices
append( indices, outside_vertex )
append( indices, v_offset + index - 1 )
append( indices, v_offset + index )
}
}
blit_quad :: proc( draw_list : ^DrawList, p0, p1 : Vec2, uv0, uv1 : Vec2 )
{
v_offset := cast(u32) draw_list.vertices.num
vertex := Vertex {
{p0.x, p0.y},
uv0.x,
uv0.y
}
append( & draw_list.vertices, vertex )
vertex = Vertex {
{p0.x, p1.y},
uv0.x,
uv1.y
}
append( & draw_list.vertices, vertex )
vertex = Vertex {
{p1.x, p0.y},
uv1.x,
uv0.y
}
append( & draw_list.vertices, vertex )
vertex = Vertex {
{p1.x, p1.y},
uv1.x,
uv1.y
}
append( & draw_list.vertices, vertex )
quad_indices : []u32 = {
0, 1, 2,
2, 1, 3
}
for index : i32 = 0; index < 6; index += 1 {
append( & draw_list.indices, v_offset + quad_indices[ index ] )
}
}
cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, translate : Vec2 ) -> b32
{
assert( ctx != nil )
@ -531,7 +405,24 @@ cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale,
if ctx.debug_print_verbose
{
log( "shape: \n")
// for
for vertex in shape
{
if vertex.type == .Move {
logf("move_to %d %d\n", vertex.x, vertex.y )
}
else if vertex.type == .Line {
logf("line_to %d %d\n", vertex.x, vertex.y )
}
else if vertex.type == .Curve {
logf("curve_to %d %d through %d %d\n", vertex.x, vertex.y, vertex.contour_x0, vertex.contour_y0 )
}
else if vertex.type == .Cubic {
logf("cubic_to %d %d through %d %d and %d %d\n",
vertex.x, vertex.y,
vertex.contour_x0, vertex.contour_y0,
vertex.contour_x1, vertex.contour_y1 )
}
}
}
/*
@ -556,16 +447,130 @@ cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale,
// Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions.
path := ctx.temp_path
clear(path)
for vertex in shape {
for edge in shape do switch edge.type
{
case .Move:
if path.num > 0 {
draw_filled_path( & ctx.draw_list, outside, array_to_slice(path), scale, translate )
}
clear(path)
fallthrough
case .Line:
append( & path, Vec2{ f32(edge.x), f32(edge.y) })
case .Curve:
assert( path.num > 0 )
p0 := path.data[ path.num - 1 ]
p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) }
p2 := Vec2{ f32(edge.x), f32(edge.y) }
step := 1.0 / f32(ctx.curve_quality)
alpha := step
for index := i32(0); index < i32(ctx.curve_quality); index += 1 {
append( & path, eval_point_on_bezier3( p0, p1, p2, alpha ))
alpha += step
}
case .Cubic:
assert( path.num > 0 )
p0 := path.data[ path.num - 1]
p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) }
p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) }
p3 := Vec2{ f32(edge.x), f32(edge.y) }
step := 1.0 / f32(ctx.curve_quality)
alpha := step
for index := i32(0); index < i32(ctx.curve_quality); index += 1 {
append( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha ))
alpha += step
}
case .None:
assert(false, "Unknown edge type or invalid")
}
if path.num > 0 {
draw_filled_path( & ctx.draw_list, outside, array_to_slice(path), scale, translate )
}
// Note(Original Author): Apend the draw call
draw.end_index = cast(u32) ctx.draw_list.indices.num
if draw.end_index > draw.start_index {
append(& ctx.draw_list.calls, draw)
}
parser_free_shape( entry.parser_info, shape )
return false
}
decide_codepoint_region :: proc() -> AtlasRegionKind
decide_codepoint_region :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph
) -> (region : AtlasRegionKind, state : ^LRU_Cache, next_idx : ^u32, over_sample : ^Vec2)
{
return {}
if parser_is_glyph_empty( entry.parser_info, glyph_index ) {
region = .None
}
bounds_0, bounds_1 := parser_get_glyph_box( entry.parser_info, glyph_index )
bounds_width := bounds_1.x - bounds_0.x
bounds_height := bounds_1.y - bounds_0.y
atlas := & ctx.atlas
bounds_width_scaled := cast(u32) (f32(bounds_width) * entry.size_scale + 2.0 * f32(atlas.glyph_padding))
bounds_height_scaled := cast(u32) (f32(bounds_height) * entry.size_scale + 2.0 * f32(atlas.glyph_padding))
if bounds_width_scaled <= atlas.region_a.width && bounds_height_scaled <= atlas.region_a.height
{
// Region A for small glyphs. These are good for things such as punctuation.
region = .A
state = & atlas.region_a.state
next_idx = & atlas.region_a.next_idx
}
else if bounds_width_scaled <= atlas.region_b.width && bounds_height_scaled <= atlas.region_b.height
{
// Region B for tall glyphs. These are good for things such as european alphabets.
region = .B
state = & atlas.region_b.state
next_idx = & atlas.region_b.next_idx
}
else if bounds_width_scaled <= atlas.region_c.width && bounds_height_scaled <= atlas.region_c.height
{
// Region C for big glyphs. These are good for things such as asian typography.
region = .C
state = & atlas.region_c.state
next_idx = & atlas.region_c.next_idx
}
else if bounds_width_scaled <= atlas.region_d.width && bounds_height_scaled <= atlas.region_d.height
{
// Region D for huge glyphs. These are good for things such as titles and 4k.
region = .D
state = & atlas.region_d.state
next_idx = & atlas.region_d.next_idx
}
else if bounds_width_scaled <= atlas.buffer_width && bounds_height_scaled <= atlas.buffer_height
{
// Region 'E' for massive glyphs. These are rendered uncached and un-oversampled.
region = .E
state = nil
next_idx = nil
if bounds_width_scaled <= atlas.buffer_width / 2 && bounds_height_scaled <= atlas.buffer_height / 2
{
(over_sample^) = { 2.0, 2.0 }
}
else
{
(over_sample^) = { 1.0, 1.0 }
}
return
}
else {
region = .None
return
}
assert(state != nil)
assert(next_idx != nil)
return
}
flush_glyph_buffer_to_atlas :: proc()

View File

@ -1,6 +1,12 @@
package VEFontCache
GlyphUpdateBatch :: struct {
GlyphDrawBuffer :: struct {
over_sample : Vec2,
buffer_batch : u32,
buffer_width : u32,
buffer_height : u32,
draw_padding : u32,
update_batch_x : i32,
clear_draw_list : DrawList,
draw_list : DrawList,
@ -23,12 +29,12 @@ Atlas :: struct {
width : u32,
height : u32,
glyph_pad : u16,
glyph_padding : u32,
region_a : AtlasRegion,
region_b : AtlasRegion,
region_c : AtlasRegion,
region_d : AtlasRegion,
using glyph_update_batch : GlyphUpdateBatch,
using glyph_update_batch : GlyphDrawBuffer,
}

View File

@ -31,3 +31,136 @@ DrawList :: struct {
indices : Array(u32),
calls : Array(DrawCall),
}
blit_quad :: proc( draw_list : ^DrawList, p0, p1 : Vec2, uv0, uv1 : Vec2 )
{
v_offset := cast(u32) draw_list.vertices.num
vertex := Vertex {
{p0.x, p0.y},
uv0.x,
uv0.y
}
append( & draw_list.vertices, vertex )
vertex = Vertex {
{p0.x, p1.y},
uv0.x,
uv1.y
}
append( & draw_list.vertices, vertex )
vertex = Vertex {
{p1.x, p0.y},
uv1.x,
uv0.y
}
append( & draw_list.vertices, vertex )
vertex = Vertex {
{p1.x, p1.y},
uv1.x,
uv1.y
}
append( & draw_list.vertices, vertex )
quad_indices : []u32 = {
0, 1, 2,
2, 1, 3
}
for index : i32 = 0; index < 6; index += 1 {
append( & draw_list.indices, v_offset + quad_indices[ index ] )
}
}
// ve_fontcache_clear_drawlist
clear_draw_list :: proc( draw_list : ^DrawList ) {
clear( draw_list.calls )
clear( draw_list.indices )
clear( draw_list.vertices )
}
// Constructs a triangle fan to fill a shape using the provided path
// outside_point represents the center point of the fan.
//
// Note(Original Author):
// WARNING: doesn't actually append drawcall; caller is responsible for actually appending the drawcall.
// ve_fontcache_draw_filled_path
draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []Vec2,
scale := Vec2 { 1, 1 },
translate := Vec2 { 0, 0 },
debug_print_verbose : b32 = false
)
{
if debug_print_verbose
{
log("outline_path: \n")
for point in path {
logf(" %.2f %.2f\n", point.x * scale )
}
}
v_offset := cast(u32) draw_list.vertices.num
for point in path {
vertex := Vertex {
pos = point * scale + translate,
u = 0,
v = 0,
}
append( & draw_list.vertices, vertex )
}
outside_vertex := cast(u32) draw_list.vertices.num
{
vertex := Vertex {
pos = outside_point * scale + translate,
u = 0,
v = 0,
}
append( & draw_list.vertices, vertex )
}
for index : u32 = 1; index < u32(len(path)); index += 1 {
indices := & draw_list.indices
append( indices, outside_vertex )
append( indices, v_offset + index - 1 )
append( indices, v_offset + index )
}
}
// ve_fontcache_flush_drawlist
flush_draw_list :: proc( ctx : ^Context ) {
assert( ctx != nil )
clear_draw_list( & ctx.draw_list )
}
// ve_fontcache_drawlist
get_draw_list :: proc( ctx : ^Context ) -> ^DrawList {
assert( ctx != nil )
return & ctx.draw_list
}
// ve_fontcache_merge_drawlist
merge_draw_list :: proc( dst, src : ^DrawList )
{
error : AllocatorError
v_offset := cast(u32) dst.vertices.num
// for index : u32 = 0; index < cast(u32) src.vertices.num; index += 1 {
// error = append( & dst.vertices, src.vertices.data[index] )
// assert( error == .None )
// }
error = append( & dst.vertices, src.vertices )
assert( error == .None )
i_offset := cast(u32) dst.indices.num
for index : u32 = 0; index < cast(u32) src.indices.num; index += 1 {
error = append( & dst.indices, src.indices.data[index] + v_offset )
assert( error == .None )
}
for index : u32 = 0; index < cast(u32) src.calls.num; index += 1 {
src_call := src.calls.data[ index ]
src_call.start_index += i_offset
src_call.end_index += i_offset
append( & dst.calls, src_call )
assert( error == .None )
}
}

View File

@ -380,5 +380,18 @@ parser_free_shape :: proc( font : ^ParserFontInfo, shape : ParserGlyphShape )
parser_get_glyph_box :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i)
{
switch font.kind
{
case .Freetype:
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
}
}