VEFontCache: Codepath simplificiation & optimization
This commit is contained in:
parent
6f034534f3
commit
413f544e9c
@ -55,7 +55,7 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32, dbg_name : string = ""
|
||||
|
||||
pool_list_free :: proc( pool : ^PoolList )
|
||||
{
|
||||
|
||||
// TODO(Ed): Implement
|
||||
}
|
||||
|
||||
pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator )
|
||||
@ -163,7 +163,7 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : u32, dbg_name : string = "" ) {
|
||||
|
||||
LRU_free :: proc( cache : ^LRU_Cache )
|
||||
{
|
||||
|
||||
// TODO(Ed): Implement
|
||||
}
|
||||
|
||||
LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator )
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
This is a port of the library base on [fork](https://github.com/hypernewbie/VEFontCache)
|
||||
|
||||
Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications.
|
||||
|
||||
TODO (Making it a more idiomatic library):
|
||||
|
||||
* Use Odin's builtin dynamic arrays
|
||||
|
@ -28,11 +28,6 @@ vec2_64_from_vec2 :: #force_inline proc( v2 : Vec2 ) -> Vec2_64 { return { f
|
||||
FontID :: distinct i64
|
||||
Glyph :: distinct i32
|
||||
|
||||
Vertex :: struct {
|
||||
pos : Vec2,
|
||||
u, v : f32,
|
||||
}
|
||||
|
||||
Entry :: struct {
|
||||
parser_info : ParserFontInfo,
|
||||
shaper_info : ShaperInfo,
|
||||
@ -68,6 +63,14 @@ Context :: struct {
|
||||
colour : Colour,
|
||||
cursor_pos : Vec2,
|
||||
|
||||
// draw_cursor_pos : Vec2,
|
||||
|
||||
draw_layer : struct {
|
||||
vertices_offset : int,
|
||||
indices_offset : int,
|
||||
calls_offset : int,
|
||||
},
|
||||
|
||||
draw_list : DrawList,
|
||||
atlas : Atlas,
|
||||
shape_cache : ShapedTextCache,
|
||||
@ -130,7 +133,6 @@ InitGlyphDrawParams_Default :: InitGlyphDrawParams {
|
||||
over_sample = { 4, 4 },
|
||||
buffer_batch = 4,
|
||||
draw_padding = InitAtlasParams_Default.glyph_padding,
|
||||
// draw_padding = InitAtlasParams_Default.glyph_padding,
|
||||
}
|
||||
|
||||
InitShapeCacheParams :: struct {
|
||||
@ -139,12 +141,12 @@ InitShapeCacheParams :: struct {
|
||||
}
|
||||
|
||||
InitShapeCacheParams_Default :: InitShapeCacheParams {
|
||||
capacity = 256,
|
||||
reserve_length = 64,
|
||||
capacity = 1024,
|
||||
reserve_length = 1024,
|
||||
}
|
||||
|
||||
// ve_fontcache_init
|
||||
init :: proc( ctx : ^Context, parser_kind : ParserKind,
|
||||
startup :: proc( ctx : ^Context, parser_kind : ParserKind,
|
||||
allocator := context.allocator,
|
||||
atlas_params := InitAtlasParams_Default,
|
||||
glyph_draw_params := InitGlyphDrawParams_Default,
|
||||
@ -272,9 +274,9 @@ init :: proc( ctx : ^Context, parser_kind : ParserKind,
|
||||
|
||||
hot_reload :: proc( ctx : ^Context, allocator : Allocator )
|
||||
{
|
||||
assert( ctx != nil )
|
||||
ctx.backing = allocator
|
||||
context.allocator = ctx.backing
|
||||
|
||||
using ctx
|
||||
|
||||
reload_array( & entries, allocator )
|
||||
@ -323,15 +325,8 @@ shutdown :: proc( ctx : ^Context )
|
||||
}
|
||||
|
||||
shaper_shutdown( & shaper_ctx )
|
||||
}
|
||||
|
||||
#endregion("lifetime")
|
||||
|
||||
// ve_fontcache_configure_snap
|
||||
configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) {
|
||||
assert( ctx != nil )
|
||||
ctx.snap_width = snap_width
|
||||
ctx.snap_height = snap_height
|
||||
// TODO(Ed): Finish implementing, there is quite a few resource not released here.
|
||||
}
|
||||
|
||||
// ve_fontcache_load
|
||||
@ -395,271 +390,185 @@ unload_font :: proc( ctx : ^Context, font : FontID )
|
||||
shaper_unload_font( & entry.shaper_info )
|
||||
}
|
||||
|
||||
cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, translate : Vec2 ) -> b32
|
||||
{
|
||||
// profile(#procedure)
|
||||
#endregion("lifetime")
|
||||
|
||||
#region("drawing")
|
||||
|
||||
// ve_fontcache_configure_snap
|
||||
configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) {
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
entry := & ctx.entries[ font ]
|
||||
if glyph_index == Glyph(0) {
|
||||
// Note(Original Author): Glyph not in current hb_font
|
||||
return false
|
||||
}
|
||||
|
||||
// No shpae to retrieve
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
|
||||
// Retrieve the shape definition from the parser.
|
||||
shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index )
|
||||
assert( error == .None )
|
||||
if len(shape) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if ctx.debug_print_verbose
|
||||
{
|
||||
log( "shape:")
|
||||
for vertex in shape
|
||||
{
|
||||
if vertex.type == .Move {
|
||||
logf("move_to %d %d", vertex.x, vertex.y )
|
||||
}
|
||||
else if vertex.type == .Line {
|
||||
logf("line_to %d %d", vertex.x, vertex.y )
|
||||
}
|
||||
else if vertex.type == .Curve {
|
||||
logf("curve_to %d %d through %d %d", 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",
|
||||
vertex.x, vertex.y,
|
||||
vertex.contour_x0, vertex.contour_y0,
|
||||
vertex.contour_x1, vertex.contour_y1 )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Note(Original Author):
|
||||
We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner.
|
||||
Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here.
|
||||
*/
|
||||
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
|
||||
|
||||
outside := Vec2 {
|
||||
f32(bounds_0.x) - 21,
|
||||
f32(bounds_0.y) - 33,
|
||||
}
|
||||
|
||||
// Note(Original Author): Figure out scaling so it fits within our box.
|
||||
draw := DrawCall_Default
|
||||
draw.pass = FrameBufferPass.Glyph
|
||||
draw.start_index = u32(len(ctx.draw_list.indices))
|
||||
|
||||
// Note(Original Author);
|
||||
// Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac.
|
||||
// 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 edge in shape do switch edge.type
|
||||
{
|
||||
case .Move:
|
||||
if len(path) > 0 {
|
||||
draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose )
|
||||
}
|
||||
clear( & path)
|
||||
fallthrough
|
||||
|
||||
case .Line:
|
||||
append_elem( & path, Vec2{ f32(edge.x), f32(edge.y) })
|
||||
|
||||
case .Curve:
|
||||
assert( len(path) > 0 )
|
||||
p0 := path[ len(path) - 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_elem( & path, eval_point_on_bezier3( p0, p1, p2, alpha ))
|
||||
alpha += step
|
||||
}
|
||||
|
||||
case .Cubic:
|
||||
assert( len(path) > 0 )
|
||||
p0 := path[ len(path) - 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_elem( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha ))
|
||||
alpha += step
|
||||
}
|
||||
|
||||
case .None:
|
||||
assert(false, "Unknown edge type or invalid")
|
||||
}
|
||||
if len(path) > 0 {
|
||||
draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose )
|
||||
}
|
||||
|
||||
// Note(Original Author): Apend the draw call
|
||||
draw.end_index = cast(u32) len(ctx.draw_list.indices)
|
||||
if draw.end_index > draw.start_index {
|
||||
append(& ctx.draw_list.calls, draw)
|
||||
}
|
||||
|
||||
parser_free_shape( & entry.parser_info, shape )
|
||||
return true
|
||||
}
|
||||
|
||||
cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph )
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
entry := & ctx.entries[ font ]
|
||||
|
||||
if glyph_index == 0 do return
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return
|
||||
|
||||
// Get hb_font text metrics. These are unscaled!
|
||||
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
|
||||
bounds_width := f32(bounds_1.x - bounds_0.x)
|
||||
bounds_height := f32(bounds_1.y - bounds_0.y)
|
||||
|
||||
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
|
||||
|
||||
// E region is special case and not cached to atlas.
|
||||
if region_kind == .None || region_kind == .E do return
|
||||
|
||||
// Grab an atlas LRU cache slot.
|
||||
lru_code := font_glyph_lru_code( font, glyph_index )
|
||||
atlas_index := LRU_get( & region.state, lru_code )
|
||||
if atlas_index == -1
|
||||
{
|
||||
if region.next_idx < region.state.capacity
|
||||
{
|
||||
evicted := LRU_put( & region.state, lru_code, i32(region.next_idx) )
|
||||
atlas_index = i32(region.next_idx)
|
||||
region.next_idx += 1
|
||||
assert( evicted == lru_code )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_codepoint := LRU_get_next_evicted( & region.state )
|
||||
assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF )
|
||||
|
||||
atlas_index = LRU_peek( & region.state, next_evict_codepoint, must_find = true )
|
||||
assert( atlas_index != -1 )
|
||||
|
||||
evicted := LRU_put( & region.state, lru_code, atlas_index )
|
||||
assert( evicted == next_evict_codepoint )
|
||||
}
|
||||
|
||||
assert( LRU_get( & region.state, lru_code ) != - 1 )
|
||||
}
|
||||
|
||||
atlas := & ctx.atlas
|
||||
atlas_width := f32(atlas.width)
|
||||
atlas_height := f32(atlas.height)
|
||||
glyph_buffer_width := f32(atlas.buffer_width)
|
||||
glyph_buffer_height := f32(atlas.buffer_height)
|
||||
glyph_padding := cast(f32) atlas.glyph_padding
|
||||
|
||||
if ctx.debug_print
|
||||
{
|
||||
@static debug_total_cached : i32 = 0
|
||||
logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached)
|
||||
debug_total_cached += 1
|
||||
}
|
||||
|
||||
// Draw oversized glyph to update FBO
|
||||
glyph_draw_scale := over_sample * entry.size_scale
|
||||
glyph_draw_translate := -1 * Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2( 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 := i32( bounds_width * glyph_draw_scale.x + 1.0 ) + i32(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, atlas_index )
|
||||
dst_glyph_position := dst_position
|
||||
dst_glyph_width := bounds_width * entry.size_scale
|
||||
dst_glyph_height := bounds_height * entry.size_scale
|
||||
dst_glyph_width += glyph_padding
|
||||
dst_glyph_height += 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, atlas_width, atlas_height )
|
||||
screenspace_x_form( & dst_position, & dst_size, atlas_width, atlas_height )
|
||||
|
||||
src_position := Vec2 { f32(atlas.update_batch_x), 0 }
|
||||
src_size := Vec2 {
|
||||
bounds_width * glyph_draw_scale.x,
|
||||
bounds_height * glyph_draw_scale.y,
|
||||
}
|
||||
src_size += over_sample * glyph_padding
|
||||
textspace_x_form( & src_position, & src_size, glyph_buffer_width, glyph_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, glyph_buffer_width, glyph_buffer_height )
|
||||
|
||||
call : DrawCall
|
||||
{
|
||||
// Queue up clear on target region on atlas
|
||||
using call
|
||||
pass = .Atlas
|
||||
region = .Ignore
|
||||
start_index = cast(u32) len(atlas.clear_draw_list.indices)
|
||||
blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } )
|
||||
end_index = cast(u32) len(atlas.clear_draw_list.indices)
|
||||
append( & atlas.clear_draw_list.calls, call )
|
||||
|
||||
// Queue up a blit from glyph_update_FBO to the atlas
|
||||
region = .None
|
||||
start_index = cast(u32) len(atlas.draw_list.indices)
|
||||
blit_quad( & atlas.draw_list, dst_glyph_position, dst_position + dst_glyph_size, src_position, src_position + src_size )
|
||||
end_index = cast(u32) len(atlas.draw_list.indices)
|
||||
append( & atlas.draw_list.calls, call )
|
||||
}
|
||||
|
||||
// Render glyph to glyph_update_FBO
|
||||
cache_glyph( ctx, font, glyph_index, glyph_draw_scale, glyph_draw_translate )
|
||||
ctx.snap_width = snap_width
|
||||
ctx.snap_height = snap_height
|
||||
}
|
||||
|
||||
get_cursor_pos :: #force_inline proc "contextless" ( ctx : ^Context ) -> Vec2 { return ctx.cursor_pos }
|
||||
set_colour :: #force_inline proc "contextless" ( ctx : ^Context, colour : Colour ) { ctx.colour = colour }
|
||||
|
||||
is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32
|
||||
// TODO(Ed): Change this to be whitespace aware so that we can optimize the caching of shpaes properly.
|
||||
// Right now the entire text provided to this call is considered a "shape" this is really bad as basically it invalidates caching for large chunks of text
|
||||
// Instead we should be aware of whitespace tokens and the chunks between them (the whitespace lexer could be abused for this).
|
||||
// From there we should maek a 'draw text shape' that breaks up the batch text draws for each of the shapes.
|
||||
draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32
|
||||
{
|
||||
if glyph_index == 0 do return true
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
return false
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
ctx.cursor_pos = {}
|
||||
|
||||
position := position
|
||||
snap_width := f32(ctx.snap_width)
|
||||
snap_height := f32(ctx.snap_height)
|
||||
if ctx.snap_width > 0 do position.x = cast(f32) cast(u32) (position.x * snap_width + 0.5) / snap_width
|
||||
if ctx.snap_height > 0 do position.y = cast(f32) cast(u32) (position.y * snap_height + 0.5) / snap_height
|
||||
|
||||
entry := & ctx.entries[ font ]
|
||||
|
||||
last_shaped : ^ShapedText
|
||||
|
||||
ChunkType :: enum u32 { Visible, Formatting }
|
||||
chunk_kind : ChunkType
|
||||
chunk_start : int = 0
|
||||
chunk_end : int = 0
|
||||
|
||||
text_utf8_bytes := transmute([]u8) text_utf8
|
||||
text_chunk : string
|
||||
|
||||
when true {
|
||||
text_chunk = transmute(string) text_utf8_bytes[ : ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk, entry )
|
||||
ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
last_shaped = shaped
|
||||
}
|
||||
}
|
||||
else {
|
||||
last_byte_offset : int = 0
|
||||
byte_offset : int = 0
|
||||
for codepoint, offset in text_utf8
|
||||
{
|
||||
Rune_Space :: ' '
|
||||
Rune_Tab :: '\t'
|
||||
Rune_Carriage_Return :: '\r'
|
||||
Rune_Line_Feed :: '\n'
|
||||
// Rune_Tab_Vertical :: '\v'
|
||||
|
||||
byte_offset = offset
|
||||
|
||||
switch codepoint
|
||||
{
|
||||
case Rune_Space: fallthrough
|
||||
case Rune_Tab: fallthrough
|
||||
case Rune_Line_Feed: fallthrough
|
||||
case Rune_Carriage_Return:
|
||||
if chunk_kind == .Formatting {
|
||||
chunk_end = byte_offset
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
else
|
||||
{
|
||||
text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk, entry )
|
||||
ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
last_shaped = shaped
|
||||
}
|
||||
|
||||
chunk_start = byte_offset
|
||||
chunk_end = chunk_start
|
||||
chunk_kind = .Formatting
|
||||
|
||||
last_byte_offset = byte_offset
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Visible Chunk
|
||||
if chunk_kind == .Visible {
|
||||
chunk_end = byte_offset
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
else
|
||||
{
|
||||
text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk, entry )
|
||||
ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
last_shaped = shaped
|
||||
}
|
||||
|
||||
chunk_start = byte_offset
|
||||
chunk_end = chunk_start
|
||||
chunk_kind = .Visible
|
||||
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
}
|
||||
|
||||
text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk, entry )
|
||||
ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
last_shaped = shaped
|
||||
}
|
||||
|
||||
chunk_start = byte_offset
|
||||
chunk_end = chunk_start
|
||||
chunk_kind = .Visible
|
||||
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ve_fontcache_drawlist
|
||||
get_draw_list :: proc( ctx : ^Context, optimize_before_returning := true ) -> ^DrawList {
|
||||
assert( ctx != nil )
|
||||
if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 )
|
||||
|
||||
return & ctx.draw_list
|
||||
}
|
||||
|
||||
get_draw_list_layer :: proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []DrawCall) {
|
||||
assert( ctx != nil )
|
||||
if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset )
|
||||
vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ]
|
||||
indices = ctx.draw_list.indices [ ctx.draw_layer.indices_offset : ]
|
||||
calls = ctx.draw_list.calls [ ctx.draw_layer.calls_offset : ]
|
||||
return
|
||||
}
|
||||
|
||||
// ve_fontcache_flush_drawlist
|
||||
flush_draw_list :: proc( ctx : ^Context ) {
|
||||
assert( ctx != nil )
|
||||
using ctx
|
||||
clear_draw_list( & draw_list )
|
||||
draw_layer.vertices_offset = 0
|
||||
draw_layer.indices_offset = 0
|
||||
draw_layer.calls_offset = 0
|
||||
}
|
||||
|
||||
flush_draw_list_layer :: proc( ctx : ^Context ) {
|
||||
assert( ctx != nil )
|
||||
using ctx
|
||||
draw_layer.vertices_offset = len(draw_list.vertices)
|
||||
draw_layer.indices_offset = len(draw_list.indices)
|
||||
draw_layer.calls_offset = len(draw_list.calls)
|
||||
}
|
||||
|
||||
#endregion("drawing")
|
||||
|
||||
#region("metrics")
|
||||
|
||||
measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> (measured : Vec2)
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
context.allocator = ctx.backing
|
||||
|
||||
atlas := ctx.atlas
|
||||
shaped := shape_text_cached( ctx, font, text_utf8 )
|
||||
entry := & ctx.entries[ font ]
|
||||
shaped := shape_text_cached( ctx, font, text_utf8, entry )
|
||||
padding := cast(f32) atlas.glyph_padding
|
||||
|
||||
for index : i32 = 0; index < i32(len(shaped.glyphs)); index += 1
|
||||
@ -676,3 +585,15 @@ measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -
|
||||
measured.x = shaped.end_cursor_pos.x
|
||||
return measured
|
||||
}
|
||||
|
||||
get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : FontID ) -> ( ascent, descent, line_gap : i32 )
|
||||
{
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
entry := & ctx.entries[ font ]
|
||||
ascent, descent, line_gap = parser_get_font_vertical_metrics( & entry.parser_info )
|
||||
return
|
||||
}
|
||||
|
||||
#endregion("metrics")
|
||||
|
@ -88,25 +88,22 @@ atlas_bbox :: proc( atlas : ^Atlas, region : AtlasRegionKind, local_idx : i32 )
|
||||
return
|
||||
}
|
||||
|
||||
can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph ) -> b32
|
||||
can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph,
|
||||
lru_code : u64,
|
||||
atlas_index : i32,
|
||||
region_kind : AtlasRegionKind,
|
||||
region : ^AtlasRegion,
|
||||
over_sample : Vec2
|
||||
) -> b32
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( entry.id == font )
|
||||
|
||||
// Decide which atlas to target
|
||||
assert( glyph_index != -1 )
|
||||
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
|
||||
|
||||
// E region can't batch
|
||||
if region_kind == .E || region_kind == .None do return false
|
||||
if ctx.temp_codepoint_seen_num > 1024 do return false
|
||||
// Note(Ed): Why 1024?
|
||||
// TODO(Ed): Why 1024?
|
||||
|
||||
// Is this glyph cached?
|
||||
// lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 )
|
||||
lru_code := font_glyph_lru_code(font, glyph_index)
|
||||
atlas_index := LRU_get( & region.state, lru_code )
|
||||
if atlas_index == - 1
|
||||
{
|
||||
if region.next_idx > u32( region.state.capacity) {
|
||||
@ -120,12 +117,11 @@ can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^E
|
||||
}
|
||||
}
|
||||
|
||||
cache_glyph_to_atlas( ctx, font, glyph_index )
|
||||
cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample )
|
||||
}
|
||||
|
||||
assert( LRU_get( & region.state, lru_code ) != -1 )
|
||||
ctx.temp_codepoint_seen[lru_code] = true
|
||||
ctx.temp_codepoint_seen_num += 1
|
||||
mark_batch_codepoint_seen( ctx, lru_code)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,73 @@
|
||||
# Documentation
|
||||
|
||||
Work in progress...
|
||||
|
||||
# Interface
|
||||
|
||||
Notes
|
||||
---
|
||||
|
||||
Freetype implementation supports specifying a FT_Memory handle which is a pointer to a FT_MemoryRect. This can be used to define an allocator for the parser. Currently this library does not wrap this interface (yet). If using freetype its recommend to update `parser_init` with the necessary changes to wrap the context's backing allocator for freetype to utilize.
|
||||
|
||||
```c
|
||||
struct FT_MemoryRec_
|
||||
{
|
||||
void* user;
|
||||
FT_Alloc_Func alloc;
|
||||
FT_Free_Func free;
|
||||
FT_Realloc_Func realloc;
|
||||
};
|
||||
```
|
||||
|
||||
### startup
|
||||
|
||||
Initializes a provided context.
|
||||
|
||||
There are a large amount of parameters to tune the library instance to the user's preference. By default, keep in mind the library defaults to utilize stb_truetype as the font parser and harfbuzz (soon...) for the shaper.
|
||||
|
||||
Much of the data structures within the context struct are not fixed-capacity allocations so make sure that the backing allocator utilized can handle it.
|
||||
|
||||
### hot_reload
|
||||
|
||||
The library supports being used in a dynamically loaded module. If this occurs simply make sure to call this procedure with a reference to the backing allocator provided during startup as all dynamic containers tend to lose a proper reference to the allocator's procedure.
|
||||
|
||||
### shutdown
|
||||
|
||||
Release resources from the context.
|
||||
|
||||
### configure_snap
|
||||
|
||||
You'll find this used immediately in draw_text it acts as a way to snap the position of the text to the nearest pixel for the width and height specified.
|
||||
|
||||
If snapping is not desired, set the snap_width and height before calling draw_text to 0.
|
||||
|
||||
## get_cursor_pos
|
||||
|
||||
Will provide the current cursor_pos for the resulting text drawn.
|
||||
|
||||
## set_color
|
||||
|
||||
Sets the color to utilize on `DrawCall`s for FrameBuffer.Target or .Target_Uncached passes
|
||||
|
||||
### get_draw_list
|
||||
|
||||
Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety.
|
||||
By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments.
|
||||
|
||||
### get_draw_list_layer
|
||||
|
||||
Get the enqueued draw_list for the current "layer".
|
||||
A layer is considered the slice of the drawlist's content from the last call to `flush_draw_list_layer` onward.
|
||||
By default, if get_draw_list_layer is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments.
|
||||
|
||||
The draw layer offsets are cleared with `flush_draw_list`
|
||||
|
||||
### flush_draw_list
|
||||
|
||||
Will clear the draw list and draw layer offsets.
|
||||
|
||||
### flush_draw_list_layer
|
||||
|
||||
Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays.
|
||||
|
||||
### measure_text_size
|
||||
|
||||
Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `ShapedText` entry.
|
||||
|
||||
|
||||
|
BIN
code/font/VEFontCache/docs/draw_text_codepaths.pur
Normal file
BIN
code/font/VEFontCache/docs/draw_text_codepaths.pur
Normal file
Binary file not shown.
@ -2,6 +2,11 @@ package VEFontCache
|
||||
|
||||
import "core:math"
|
||||
|
||||
Vertex :: struct {
|
||||
pos : Vec2,
|
||||
u, v : f32,
|
||||
}
|
||||
|
||||
DrawCall :: struct {
|
||||
pass : FrameBufferPass,
|
||||
start_index : u32,
|
||||
@ -89,6 +94,241 @@ blit_quad :: proc( draw_list : ^DrawList, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}
|
||||
return
|
||||
}
|
||||
|
||||
cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2i, scale, translate : Vec2 ) -> b32
|
||||
{
|
||||
// profile(#procedure)
|
||||
if glyph_index == Glyph(0) {
|
||||
// Note(Original Author): Glyph not in current hb_font
|
||||
return false
|
||||
}
|
||||
|
||||
// No shpae to retrieve
|
||||
// if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
|
||||
// Retrieve the shape definition from the parser.
|
||||
shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index )
|
||||
assert( error == .None )
|
||||
if len(shape) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if ctx.debug_print_verbose
|
||||
{
|
||||
log( "shape:")
|
||||
for vertex in shape
|
||||
{
|
||||
if vertex.type == .Move {
|
||||
logf("move_to %d %d", vertex.x, vertex.y )
|
||||
}
|
||||
else if vertex.type == .Line {
|
||||
logf("line_to %d %d", vertex.x, vertex.y )
|
||||
}
|
||||
else if vertex.type == .Curve {
|
||||
logf("curve_to %d %d through %d %d", 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",
|
||||
vertex.x, vertex.y,
|
||||
vertex.contour_x0, vertex.contour_y0,
|
||||
vertex.contour_x1, vertex.contour_y1 )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Note(Original Author):
|
||||
We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner.
|
||||
Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here.
|
||||
*/
|
||||
outside := Vec2 {
|
||||
f32(bounds_0.x) - 21,
|
||||
f32(bounds_0.y) - 33,
|
||||
}
|
||||
|
||||
// Note(Original Author): Figure out scaling so it fits within our box.
|
||||
draw := DrawCall_Default
|
||||
draw.pass = FrameBufferPass.Glyph
|
||||
draw.start_index = u32(len(ctx.draw_list.indices))
|
||||
|
||||
// Note(Original Author);
|
||||
// Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac.
|
||||
// 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 edge in shape do switch edge.type
|
||||
{
|
||||
case .Move:
|
||||
if len(path) > 0 {
|
||||
draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose )
|
||||
}
|
||||
clear( & path)
|
||||
fallthrough
|
||||
|
||||
case .Line:
|
||||
append_elem( & path, Vec2{ f32(edge.x), f32(edge.y) })
|
||||
|
||||
case .Curve:
|
||||
assert( len(path) > 0 )
|
||||
p0 := path[ len(path) - 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_elem( & path, eval_point_on_bezier3( p0, p1, p2, alpha ))
|
||||
alpha += step
|
||||
}
|
||||
|
||||
case .Cubic:
|
||||
assert( len(path) > 0 )
|
||||
p0 := path[ len(path) - 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_elem( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha ))
|
||||
alpha += step
|
||||
}
|
||||
|
||||
case .None:
|
||||
assert(false, "Unknown edge type or invalid")
|
||||
}
|
||||
if len(path) > 0 {
|
||||
draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose )
|
||||
}
|
||||
|
||||
// Note(Original Author): Apend the draw call
|
||||
draw.end_index = cast(u32) len(ctx.draw_list.indices)
|
||||
if draw.end_index > draw.start_index {
|
||||
append(& ctx.draw_list.calls, draw)
|
||||
}
|
||||
|
||||
parser_free_shape( & entry.parser_info, shape )
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
Called by:
|
||||
* can_batch_glyph : If it determines that the glyph was not detected and we haven't reached capacity in the atlas
|
||||
* draw_text_shape : Glyph
|
||||
*/
|
||||
cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, lru_code : u64, atlas_index : i32, entry : ^Entry, region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2 )
|
||||
{
|
||||
// profile(#procedure)
|
||||
|
||||
// Get hb_font text metrics. These are unscaled!
|
||||
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
|
||||
bounds_width := f32(bounds_1.x - bounds_0.x)
|
||||
bounds_height := f32(bounds_1.y - bounds_0.y)
|
||||
|
||||
// E region is special case and not cached to atlas.
|
||||
if region_kind == .None || region_kind == .E do return
|
||||
|
||||
// Grab an atlas LRU cache slot.
|
||||
atlas_index := atlas_index
|
||||
if atlas_index == -1
|
||||
{
|
||||
if region.next_idx < region.state.capacity
|
||||
{
|
||||
evicted := LRU_put( & region.state, lru_code, i32(region.next_idx) )
|
||||
atlas_index = i32(region.next_idx)
|
||||
region.next_idx += 1
|
||||
assert( evicted == lru_code )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_codepoint := LRU_get_next_evicted( & region.state )
|
||||
assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF )
|
||||
|
||||
atlas_index = LRU_peek( & region.state, next_evict_codepoint, must_find = true )
|
||||
assert( atlas_index != -1 )
|
||||
|
||||
evicted := LRU_put( & region.state, lru_code, atlas_index )
|
||||
assert( evicted == next_evict_codepoint )
|
||||
}
|
||||
|
||||
assert( LRU_get( & region.state, lru_code ) != - 1 )
|
||||
}
|
||||
|
||||
atlas := & ctx.atlas
|
||||
atlas_width := f32(atlas.width)
|
||||
atlas_height := f32(atlas.height)
|
||||
glyph_buffer_width := f32(atlas.buffer_width)
|
||||
glyph_buffer_height := f32(atlas.buffer_height)
|
||||
glyph_padding := cast(f32) atlas.glyph_padding
|
||||
|
||||
if ctx.debug_print
|
||||
{
|
||||
@static debug_total_cached : i32 = 0
|
||||
logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached)
|
||||
debug_total_cached += 1
|
||||
}
|
||||
|
||||
// Draw oversized glyph to update FBO
|
||||
glyph_draw_scale := over_sample * entry.size_scale
|
||||
glyph_draw_translate := -1 * Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2( 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 := i32( bounds_width * glyph_draw_scale.x + 1.0 ) + i32(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, atlas_index )
|
||||
dst_glyph_position := dst_position
|
||||
dst_glyph_width := bounds_width * entry.size_scale
|
||||
dst_glyph_height := bounds_height * entry.size_scale
|
||||
dst_glyph_width += glyph_padding
|
||||
dst_glyph_height += 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, atlas_width, atlas_height )
|
||||
screenspace_x_form( & dst_position, & dst_size, atlas_width, atlas_height )
|
||||
|
||||
src_position := Vec2 { f32(atlas.update_batch_x), 0 }
|
||||
src_size := Vec2 {
|
||||
bounds_width * glyph_draw_scale.x,
|
||||
bounds_height * glyph_draw_scale.y,
|
||||
}
|
||||
src_size += over_sample * glyph_padding
|
||||
textspace_x_form( & src_position, & src_size, glyph_buffer_width, glyph_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, glyph_buffer_width, glyph_buffer_height )
|
||||
|
||||
call : DrawCall
|
||||
{
|
||||
// Queue up clear on target region on atlas
|
||||
using call
|
||||
pass = .Atlas
|
||||
region = .Ignore
|
||||
start_index = cast(u32) len(atlas.clear_draw_list.indices)
|
||||
blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } )
|
||||
end_index = cast(u32) len(atlas.clear_draw_list.indices)
|
||||
append( & atlas.clear_draw_list.calls, call )
|
||||
|
||||
// Queue up a blit from glyph_update_FBO to the atlas
|
||||
region = .None
|
||||
start_index = cast(u32) len(atlas.draw_list.indices)
|
||||
blit_quad( & atlas.draw_list, dst_glyph_position, dst_position + dst_glyph_size, src_position, src_position + src_size )
|
||||
end_index = cast(u32) len(atlas.draw_list.indices)
|
||||
append( & atlas.draw_list.calls, call )
|
||||
}
|
||||
|
||||
// Render glyph to glyph_update_FBO
|
||||
cache_glyph( ctx, font, glyph_index, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate )
|
||||
}
|
||||
|
||||
// ve_fontcache_clear_drawlist
|
||||
clear_draw_list :: #force_inline proc ( draw_list : ^DrawList ) {
|
||||
clear( & draw_list.calls )
|
||||
@ -96,35 +336,42 @@ clear_draw_list :: #force_inline proc ( draw_list : ^DrawList ) {
|
||||
clear( & draw_list.vertices )
|
||||
}
|
||||
|
||||
directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : i32, over_sample, position, scale : Vec2 )
|
||||
directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0, bounds_1 : Vec2i, bounds_width, bounds_height : f32, over_sample, position, scale : Vec2 )
|
||||
{
|
||||
// profile(#procedure)
|
||||
flush_glyph_buffer_to_atlas( ctx )
|
||||
|
||||
glyph_padding := f32(ctx.atlas.glyph_padding)
|
||||
glyph_buffer_width := f32(ctx.atlas.buffer_width)
|
||||
glyph_buffer_height := f32(ctx.atlas.buffer_height)
|
||||
|
||||
// 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) )
|
||||
glyph_draw_translate := -1 * Vec2{ f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2_from_scalar(glyph_padding)
|
||||
screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height )
|
||||
|
||||
cache_glyph( ctx, entry.id, glyph, glyph_draw_scale, glyph_draw_translate )
|
||||
cache_glyph( ctx, entry.id, glyph, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate )
|
||||
|
||||
glyph_padding_dbl := glyph_padding * 2
|
||||
bounds_scaled := Vec2 { bounds_width, bounds_height } * entry.size_scale
|
||||
|
||||
// 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)
|
||||
glyph_dst_width += f32(2 * ctx.atlas.glyph_padding)
|
||||
glyph_dst_height += f32(2 * ctx.atlas.glyph_padding)
|
||||
glyph_width := bounds_scaled.x * over_sample.x
|
||||
glyph_height := bounds_scaled.y * over_sample.y
|
||||
glyph_dst_width := bounds_scaled.x
|
||||
glyph_dst_height := bounds_scaled.y
|
||||
glyph_height += glyph_padding_dbl
|
||||
glyph_width += glyph_padding_dbl
|
||||
glyph_dst_width += glyph_padding_dbl
|
||||
glyph_dst_height += glyph_padding_dbl
|
||||
|
||||
// Figure out the destination rect.
|
||||
bounds_scaled := Vec2 {
|
||||
bounds_0_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 := position + scale * bounds_0_scaled
|
||||
dst_width := scale.x * glyph_dst_width
|
||||
dst_height := scale.y * glyph_dst_height
|
||||
dst.x -= scale.x * f32(ctx.atlas.draw_padding)
|
||||
@ -132,7 +379,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly
|
||||
dst_size := Vec2{ dst_width, dst_height }
|
||||
|
||||
glyph_size := Vec2 { glyph_width, glyph_height }
|
||||
textspace_x_form( & glyph_position, & glyph_size, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) )
|
||||
textspace_x_form( & glyph_position, & glyph_size, glyph_buffer_width, glyph_buffer_height )
|
||||
|
||||
// Add the glyph drawcall.
|
||||
call : DrawCall
|
||||
@ -154,32 +401,25 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly
|
||||
append( & ctx.draw_list.calls, call )
|
||||
}
|
||||
|
||||
draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, position, scale : Vec2 ) -> b32
|
||||
draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph,
|
||||
lru_code : u64, atlas_index : i32,
|
||||
bounds_0, bounds_1 : Vec2i,
|
||||
region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2,
|
||||
position, scale : Vec2
|
||||
) -> b32
|
||||
{
|
||||
// profile(#procedure)
|
||||
// Glyph not in current font
|
||||
if glyph_index == 0 do return true
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
|
||||
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
|
||||
|
||||
bounds_width := f32(bounds_1.x - bounds_0.x)
|
||||
bounds_height := f32(bounds_1.y - bounds_0.y)
|
||||
|
||||
// Decide which atlas to target
|
||||
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
|
||||
|
||||
// E region is special case and not cached to atlas
|
||||
if region_kind == .E
|
||||
{
|
||||
directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, cast(i32) bounds_width, cast(i32) bounds_height, over_sample, position, scale )
|
||||
directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, bounds_1, bounds_width, bounds_height, over_sample, position, scale )
|
||||
return true
|
||||
}
|
||||
|
||||
// Is this codepoint cached?
|
||||
// lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(entry.id) ) & 0xFFFFFFFF00000000 )
|
||||
lru_code := font_glyph_lru_code(entry.id, glyph_index)
|
||||
atlas_index := LRU_get( & region.state, lru_code )
|
||||
if atlas_index == - 1 {
|
||||
return false
|
||||
}
|
||||
@ -192,8 +432,8 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph,
|
||||
// Figure out the source bounding box in the atlas texture
|
||||
glyph_atlas_position, glyph_atlas_width, glyph_atlas_height := atlas_bbox( atlas, region_kind, atlas_index )
|
||||
|
||||
glyph_width := bounds_width * entry.size_scale
|
||||
glyph_height := bounds_height * entry.size_scale
|
||||
glyph_width := bounds_width * entry.size_scale
|
||||
glyph_height := bounds_height * entry.size_scale
|
||||
|
||||
glyph_width += glyph_padding
|
||||
glyph_height += glyph_padding
|
||||
@ -204,14 +444,10 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph,
|
||||
math.ceil(bounds_0_scaled.x),
|
||||
math.ceil(bounds_0_scaled.y),
|
||||
}
|
||||
// dst := position * scale * bounds_0_scaled
|
||||
dst := Vec2 {
|
||||
position.x + bounds_0_scaled.x * scale.x,
|
||||
position.y + bounds_0_scaled.y * scale.y,
|
||||
}
|
||||
dst := position + bounds_0_scaled * scale
|
||||
dst -= scale * glyph_padding
|
||||
dst_width := scale.x * glyph_width
|
||||
dst_height := scale.y * glyph_height
|
||||
dst -= scale * glyph_padding
|
||||
dst_scale := Vec2 { dst_width, dst_height }
|
||||
|
||||
textspace_x_form( & glyph_atlas_position, & glyph_scale, atlas_width, atlas_height )
|
||||
@ -280,187 +516,74 @@ draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(Ed): Change this to be whitespace aware so that we can optimize the caching of shpaes properly.
|
||||
// Right now the entire text provided to this call is considered a "shape" this is really bad as basically it invalidates caching for large chunks of text
|
||||
// Instead we should be aware of whitespace tokens and the chunks between them (the whitespace lexer could be abused for this).
|
||||
// From there we should maek a 'draw text shape' that breaks up the batch text draws for each of the shapes.
|
||||
draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32
|
||||
draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2, snap_width, snap_height : f32 )
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
context.allocator = ctx.backing
|
||||
|
||||
position := position
|
||||
snap_width := f32(ctx.snap_width)
|
||||
snap_height := f32(ctx.snap_height)
|
||||
if ctx.snap_width > 0 do position.x = cast(f32) cast(u32) (position.x * snap_width + 0.5) / snap_width
|
||||
if ctx.snap_height > 0 do position.y = cast(f32) cast(u32) (position.y * snap_height + 0.5) / snap_height
|
||||
|
||||
entry := & ctx.entries[ font ]
|
||||
|
||||
// entry.size_scale = parser_scale( & entry.parser_info, entry.size )
|
||||
|
||||
post_shapes_draw_cursor_pos : Vec2
|
||||
last_shaped : ^ShapedText
|
||||
|
||||
ChunkType :: enum u32 { Visible, Formatting }
|
||||
chunk_kind : ChunkType
|
||||
chunk_start : int = 0
|
||||
chunk_end : int = 0
|
||||
|
||||
text_utf8_bytes := transmute([]u8) text_utf8
|
||||
text_chunk : string
|
||||
|
||||
when true {
|
||||
text_chunk = transmute(string) text_utf8_bytes[ : ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk )
|
||||
post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
ctx.cursor_pos = post_shapes_draw_cursor_pos
|
||||
position += shaped.end_cursor_pos
|
||||
last_shaped = shaped
|
||||
}
|
||||
}
|
||||
else {
|
||||
last_byte_offset : int = 0
|
||||
byte_offset : int = 0
|
||||
for codepoint, offset in text_utf8
|
||||
{
|
||||
Rune_Space :: ' '
|
||||
Rune_Tab :: '\t'
|
||||
Rune_Carriage_Return :: '\r'
|
||||
Rune_Line_Feed :: '\n'
|
||||
// Rune_Tab_Vertical :: '\v'
|
||||
|
||||
byte_offset = offset
|
||||
|
||||
switch codepoint
|
||||
{
|
||||
case Rune_Space: fallthrough
|
||||
case Rune_Tab: fallthrough
|
||||
case Rune_Line_Feed: fallthrough
|
||||
case Rune_Carriage_Return:
|
||||
if chunk_kind == .Formatting {
|
||||
chunk_end = byte_offset
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
else
|
||||
{
|
||||
text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk )
|
||||
post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
ctx.cursor_pos = post_shapes_draw_cursor_pos
|
||||
position += shaped.end_cursor_pos
|
||||
last_shaped = shaped
|
||||
}
|
||||
|
||||
chunk_start = byte_offset
|
||||
chunk_end = chunk_start
|
||||
chunk_kind = .Formatting
|
||||
|
||||
last_byte_offset = byte_offset
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Visible Chunk
|
||||
if chunk_kind == .Visible {
|
||||
chunk_end = byte_offset
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
else
|
||||
{
|
||||
text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk )
|
||||
post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
ctx.cursor_pos = post_shapes_draw_cursor_pos
|
||||
position += shaped.end_cursor_pos
|
||||
last_shaped = shaped
|
||||
}
|
||||
|
||||
chunk_start = byte_offset
|
||||
chunk_end = chunk_start
|
||||
chunk_kind = .Visible
|
||||
|
||||
last_byte_offset = byte_offset
|
||||
}
|
||||
}
|
||||
|
||||
text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk )
|
||||
post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height )
|
||||
ctx.cursor_pos = post_shapes_draw_cursor_pos
|
||||
position += shaped.end_cursor_pos
|
||||
last_shaped = shaped
|
||||
}
|
||||
|
||||
chunk_start = byte_offset
|
||||
chunk_end = chunk_start
|
||||
chunk_kind = .Visible
|
||||
|
||||
last_byte_offset = byte_offset
|
||||
|
||||
ctx.cursor_pos = post_shapes_draw_cursor_pos
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2 )
|
||||
{
|
||||
flush_glyph_buffer_to_atlas( ctx )
|
||||
// flush_glyph_buffer_to_atlas( ctx )
|
||||
for index := batch_start_idx; index < batch_end_idx; index += 1
|
||||
{
|
||||
// profile(#procedure)
|
||||
glyph_index := shaped.glyphs[ index ]
|
||||
shaped_position := shaped.positions[index]
|
||||
glyph_translate := position + shaped_position * scale
|
||||
glyph_cached := draw_cached_glyph( ctx, entry, glyph_index, glyph_translate, scale)
|
||||
glyph_index := shaped.glyphs[ index ]
|
||||
|
||||
if glyph_index == 0 do continue
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do continue
|
||||
|
||||
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
|
||||
lru_code := font_glyph_lru_code(entry.id, glyph_index)
|
||||
atlas_index := cast(i32) -1
|
||||
|
||||
if region_kind != .E do atlas_index = LRU_get( & region.state, lru_code )
|
||||
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
|
||||
|
||||
shaped_position := shaped.positions[index]
|
||||
glyph_translate := position + shaped_position * scale
|
||||
|
||||
glyph_cached := draw_cached_glyph( ctx,
|
||||
entry, glyph_index,
|
||||
lru_code, atlas_index,
|
||||
bounds_0, bounds_1,
|
||||
region_kind, region, over_sample,
|
||||
glyph_translate, scale)
|
||||
assert( glyph_cached == true )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper for draw_text, all raw text content should be confirmed to be either formatting or visible shapes before getting cached.
|
||||
draw_text_shape :: proc( ctx : ^Context, font : FontID, entry : ^Entry, shaped : ^ShapedText, position, scale : Vec2, snap_width, snap_height : f32 ) -> (cursor_pos : Vec2)
|
||||
{
|
||||
// position := position //+ ctx.cursor_pos * scale
|
||||
// profile(#procedure)
|
||||
batch_start_idx : i32 = 0
|
||||
for index : i32 = 0; index < i32(len(shaped.glyphs)); index += 1
|
||||
{
|
||||
glyph_index := shaped.glyphs[ index ]
|
||||
if is_empty( ctx, entry, glyph_index ) do continue
|
||||
if can_batch_glyph( ctx, font, entry, glyph_index ) do continue
|
||||
if is_empty( ctx, entry, glyph_index ) do continue
|
||||
|
||||
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
|
||||
lru_code := font_glyph_lru_code(entry.id, glyph_index)
|
||||
atlas_index := cast(i32) -1
|
||||
|
||||
if region_kind != .E do atlas_index = LRU_get( & region.state, lru_code )
|
||||
if can_batch_glyph( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue
|
||||
|
||||
// Glyph has not been catched, needs to be directly drawn.
|
||||
draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale )
|
||||
|
||||
// First batch the other cached glyphs
|
||||
flush_glyph_buffer_to_atlas(ctx)
|
||||
draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale, snap_width, snap_height )
|
||||
reset_batch_codepoint_state( ctx )
|
||||
|
||||
cache_glyph_to_atlas( ctx, font, glyph_index )
|
||||
|
||||
lru_code := font_glyph_lru_code(font, glyph_index)
|
||||
ctx.temp_codepoint_seen[lru_code] = true
|
||||
ctx.temp_codepoint_seen_num += 1
|
||||
|
||||
cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample )
|
||||
mark_batch_codepoint_seen( ctx, lru_code)
|
||||
batch_start_idx = index
|
||||
}
|
||||
|
||||
draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(len(shaped.glyphs)), position, scale )
|
||||
flush_glyph_buffer_to_atlas(ctx)
|
||||
draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(len(shaped.glyphs)), position, scale, snap_width , snap_height )
|
||||
reset_batch_codepoint_state( ctx )
|
||||
cursor_pos = position + shaped.end_cursor_pos * scale
|
||||
cursor_pos = shaped.end_cursor_pos
|
||||
return
|
||||
}
|
||||
|
||||
// ve_fontcache_flush_drawlist
|
||||
flush_draw_list :: proc( ctx : ^Context ) {
|
||||
assert( ctx != nil )
|
||||
clear_draw_list( & ctx.draw_list )
|
||||
}
|
||||
|
||||
flush_glyph_buffer_to_atlas :: proc( ctx : ^Context )
|
||||
{
|
||||
// profile(#procedure)
|
||||
@ -484,17 +607,6 @@ flush_glyph_buffer_to_atlas :: proc( ctx : ^Context )
|
||||
}
|
||||
}
|
||||
|
||||
// ve_fontcache_drawlist
|
||||
get_draw_list :: proc( ctx : ^Context ) -> ^DrawList {
|
||||
assert( ctx != nil )
|
||||
return & ctx.draw_list
|
||||
}
|
||||
|
||||
// TODO(Ed): See render.odin's render_text_layer, should provide the ability to get a slice of the draw list to render the latest layer
|
||||
DrawListLayer :: struct {}
|
||||
get_draw_list_layer :: proc() -> DrawListLayer { return {} }
|
||||
flush_layer :: proc( draw_list : ^DrawList ) {}
|
||||
|
||||
// ve_fontcache_merge_drawlist
|
||||
merge_draw_list :: proc( dst, src : ^DrawList )
|
||||
{
|
||||
@ -526,13 +638,13 @@ merge_draw_list :: proc( dst, src : ^DrawList )
|
||||
}
|
||||
}
|
||||
|
||||
optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : u64 )
|
||||
optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : int )
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( draw_list != nil )
|
||||
|
||||
write_index : u64 = call_offset
|
||||
for index : u64 = 1 + call_offset; index < cast(u64) len(draw_list.calls); index += 1
|
||||
write_index : int = call_offset
|
||||
for index : int = 1 + call_offset; index < len(draw_list.calls); index += 1
|
||||
{
|
||||
assert( write_index <= index )
|
||||
draw_0 := & draw_list.calls[ write_index ]
|
||||
|
@ -72,6 +72,19 @@ eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec
|
||||
return { f32(point.x), f32(point.y) }
|
||||
}
|
||||
|
||||
is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32
|
||||
{
|
||||
if glyph_index == 0 do return true
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
return false
|
||||
}
|
||||
|
||||
mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 )
|
||||
{
|
||||
ctx.temp_codepoint_seen[lru_code] = true
|
||||
ctx.temp_codepoint_seen_num += 1
|
||||
}
|
||||
|
||||
reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) {
|
||||
clear_map( & ctx.temp_codepoint_seen )
|
||||
ctx.temp_codepoint_seen_num = 0
|
||||
|
@ -14,22 +14,24 @@ ShapedTextCache :: struct {
|
||||
next_cache_id : i32,
|
||||
}
|
||||
|
||||
shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> ^ShapedText
|
||||
shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, entry : ^Entry ) -> ^ShapedText
|
||||
{
|
||||
// profile(#procedure)
|
||||
@static buffer : [64 * Kilobyte]byte
|
||||
|
||||
font := font
|
||||
font := font
|
||||
text_size := len(text_utf8)
|
||||
sice_end_offset := size_of(FontID) + len(text_utf8)
|
||||
|
||||
buffer_slice := buffer[:]
|
||||
font_bytes := slice_ptr( transmute(^byte) & font, size_of(FontID) )
|
||||
copy( buffer_slice, font_bytes )
|
||||
|
||||
text_bytes := transmute( []byte) text_utf8
|
||||
buffer_slice_post_font := buffer[size_of(FontID) : size_of(FontID) + len(text_utf8) ]
|
||||
buffer_slice_post_font := buffer[ size_of(FontID) : sice_end_offset ]
|
||||
copy( buffer_slice_post_font, text_bytes )
|
||||
|
||||
hash := shape_lru_hash( transmute(string) buffer[: size_of(FontID) + len(text_utf8)] )
|
||||
hash := shape_lru_hash( transmute(string) buffer[: sice_end_offset ] )
|
||||
|
||||
shape_cache := & ctx.shape_cache
|
||||
state := & ctx.shape_cache.state
|
||||
@ -54,20 +56,19 @@ shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -
|
||||
LRU_put( state, hash, shape_cache_idx )
|
||||
}
|
||||
|
||||
shape_text_uncached( ctx, font, & shape_cache.storage[ shape_cache_idx ], text_utf8 )
|
||||
shape_text_uncached( ctx, font, text_utf8, entry, & shape_cache.storage[ shape_cache_idx ] )
|
||||
}
|
||||
|
||||
return & shape_cache.storage[ shape_cache_idx ]
|
||||
}
|
||||
|
||||
shape_text_uncached :: proc( ctx : ^Context, font : FontID, output : ^ShapedText, text_utf8 : string )
|
||||
shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, entry : ^Entry, output : ^ShapedText )
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
use_full_text_shape := ctx.text_shape_adv
|
||||
entry := & ctx.entries[ font ]
|
||||
|
||||
clear( & output.glyphs )
|
||||
clear( & output.positions )
|
||||
|
Loading…
Reference in New Issue
Block a user