VEFontCache: Codepath simplificiation & optimization

This commit is contained in:
Edward R. Gonzalez 2024-06-26 06:01:06 -04:00
parent 6f034534f3
commit 413f544e9c
9 changed files with 609 additions and 498 deletions

View File

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

View File

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

View File

@ -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")

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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