diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 7c2f8c6..9d31141 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -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() diff --git a/code/font/VEFontCache/atlas.odin b/code/font/VEFontCache/atlas.odin index 135ce5d..be9e477 100644 --- a/code/font/VEFontCache/atlas.odin +++ b/code/font/VEFontCache/atlas.odin @@ -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, } diff --git a/code/font/VEFontCache/draw.odin b/code/font/VEFontCache/draw.odin index 8b4fe62..5f7d51f 100644 --- a/code/font/VEFontCache/draw.odin +++ b/code/font/VEFontCache/draw.odin @@ -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 ) + } +} diff --git a/code/font/VEFontCache/parser.odin b/code/font/VEFontCache/parser.odin index 1661988..1f80777 100644 --- a/code/font/VEFontCache/parser.odin +++ b/code/font/VEFontCache/parser.odin @@ -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 -} \ No newline at end of file +}