WIP - VEFontCache: Rendering 3k lines of whitespace ast is 16 ms rn...
This commit is contained in:
parent
560b2a125b
commit
73ba89e7f9
@ -139,7 +139,7 @@ pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
}
|
||||
}
|
||||
|
||||
pool_list_move_to_front :: #force_inline proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
{
|
||||
using pool
|
||||
|
||||
@ -156,7 +156,7 @@ pool_list_move_to_front :: #force_inline proc "contextless" ( pool : ^Pool_List,
|
||||
front = iter
|
||||
}
|
||||
|
||||
pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue #no_bounds_check {
|
||||
pool_list_peek_back :: #force_inline proc ( pool : Pool_List ) -> Pool_ListValue #no_bounds_check {
|
||||
assert( pool.back != - 1 )
|
||||
value := pool.items[ pool.back ].value
|
||||
return value
|
||||
@ -212,7 +212,7 @@ lru_clear :: proc ( cache : ^LRU_Cache ) {
|
||||
cache.num = 0
|
||||
}
|
||||
|
||||
lru_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) {
|
||||
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) {
|
||||
link, success := cache.table[key]
|
||||
return link, success
|
||||
}
|
||||
@ -225,15 +225,15 @@ lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u64 ) -> i32 #no_bounds
|
||||
return -1
|
||||
}
|
||||
|
||||
lru_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 {
|
||||
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache ) -> u64 {
|
||||
if cache.key_queue.size >= cache.capacity {
|
||||
evict := pool_list_peek_back( & cache.key_queue )
|
||||
evict := pool_list_peek_back( cache.key_queue )
|
||||
return evict
|
||||
}
|
||||
return 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
|
||||
lru_peek :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 {
|
||||
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u64, must_find := false ) -> i32 {
|
||||
iter, success := lru_find( cache, key, must_find )
|
||||
if success == false {
|
||||
return -1
|
||||
@ -267,7 +267,7 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u
|
||||
}
|
||||
|
||||
lru_refresh :: proc( cache : ^LRU_Cache, key : u64 ) {
|
||||
link, success := lru_find( cache, key )
|
||||
link, success := lru_find( cache ^, key )
|
||||
pool_list_erase( & cache.key_queue, link.ptr )
|
||||
pool_list_push_front( & cache.key_queue, key )
|
||||
link.ptr = cache.key_queue.front
|
||||
|
@ -38,56 +38,6 @@ Atlas :: struct {
|
||||
regions : [4] ^Atlas_Region,
|
||||
}
|
||||
|
||||
atlas_bbox :: #force_inline proc "contextless" ( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2)
|
||||
{
|
||||
profile(#procedure)
|
||||
switch region
|
||||
{
|
||||
case .A:
|
||||
size.x = f32(atlas.region_a.width)
|
||||
size.y = f32(atlas.region_a.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_a.capacity.x ) * atlas.region_a.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_a.capacity.x ) * atlas.region_a.height)
|
||||
|
||||
position.x += f32(atlas.region_a.offset.x)
|
||||
position.y += f32(atlas.region_a.offset.y)
|
||||
|
||||
case .B:
|
||||
size.x = f32(atlas.region_b.width)
|
||||
size.y = f32(atlas.region_b.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_b.capacity.x ) * atlas.region_b.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_b.capacity.x ) * atlas.region_b.height)
|
||||
|
||||
position.x += f32(atlas.region_b.offset.x)
|
||||
position.y += f32(atlas.region_b.offset.y)
|
||||
|
||||
case .C:
|
||||
size.x = f32(atlas.region_c.width)
|
||||
size.y = f32(atlas.region_c.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_c.capacity.x ) * atlas.region_c.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_c.capacity.x ) * atlas.region_c.height)
|
||||
|
||||
position.x += f32(atlas.region_c.offset.x)
|
||||
position.y += f32(atlas.region_c.offset.y)
|
||||
|
||||
case .D:
|
||||
size.x = f32(atlas.region_d.width)
|
||||
size.y = f32(atlas.region_d.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_d.capacity.x ) * atlas.region_d.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_d.capacity.x ) * atlas.region_d.height)
|
||||
|
||||
position.x += f32(atlas.region_d.offset.x)
|
||||
position.y += f32(atlas.region_d.offset.y)
|
||||
|
||||
case .Ignore, .None, .E:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
atlas_region_bbox :: proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
|
||||
{
|
||||
size.x = f32(region.width)
|
||||
@ -135,28 +85,28 @@ atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u6
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_codepoint := lru_get_next_evicted( & region.state )
|
||||
assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF )
|
||||
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 )
|
||||
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( evicted == next_evict_codepoint )
|
||||
}
|
||||
|
||||
assert( lru_get( & region.state, lru_code ) != - 1 )
|
||||
return
|
||||
}
|
||||
|
||||
check_and_reserve_slot_in_atlas :: #force_inline proc( ctx : ^Context, glyph_index : Glyph,
|
||||
check_and_reserve_slot_in_atlas :: #force_inline proc( ctx : Context, glyph_index : Glyph,
|
||||
lru_code : u64,
|
||||
atlas_index : ^i32,
|
||||
region : ^Atlas_Region,
|
||||
) -> (found, should_cache : b8 )
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( glyph_index != -1 )
|
||||
// assert( glyph_index != -1 )
|
||||
|
||||
if ctx.temp_codepoint_seen_num > i32(cap(ctx.temp_codepoint_seen)) do return
|
||||
|
||||
@ -166,10 +116,10 @@ check_and_reserve_slot_in_atlas :: #force_inline proc( ctx : ^Context, glyph_ind
|
||||
if region.next_idx > region.state.capacity
|
||||
{
|
||||
// We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch.
|
||||
next_evict_codepoint := lru_get_next_evicted( & region.state )
|
||||
next_evict_codepoint := lru_get_next_evicted( region.state )
|
||||
success : bool
|
||||
found, success = ctx.temp_codepoint_seen[next_evict_codepoint]
|
||||
assert(success != false)
|
||||
// assert(success != false)
|
||||
if (found) {
|
||||
return
|
||||
}
|
||||
|
@ -1,13 +1,42 @@
|
||||
package vefontcache
|
||||
|
||||
import "thirdparty:freetype"
|
||||
import "base:runtime"
|
||||
import "core:slice"
|
||||
import "thirdparty:freetype"
|
||||
|
||||
Vertex :: struct {
|
||||
pos : Vec2,
|
||||
u, v : f32,
|
||||
}
|
||||
|
||||
GlyphBounds :: struct {
|
||||
p0, p1 : Vec2
|
||||
}
|
||||
|
||||
Glyph_Pack_Entry :: struct {
|
||||
shape : Parser_Glyph_Shape,
|
||||
bounds : GlyphBounds,
|
||||
bounds_size : Vec2,
|
||||
bounds_size_scaled : Vec2,
|
||||
over_sample : Vec2,
|
||||
translate : Vec2,
|
||||
// scale : Vec2,
|
||||
region_pos : Vec2,
|
||||
region_size : Vec2,
|
||||
lru_code : u64,
|
||||
atlas_index : i32,
|
||||
index : Glyph,
|
||||
// shape_id : i32,
|
||||
region_kind : Atlas_Region_Kind,
|
||||
in_atlas : b8,
|
||||
should_cache : b8,
|
||||
}
|
||||
|
||||
Glyph_Sub_Pack :: struct {
|
||||
pack : #soa[]Glyph_Pack_Entry,
|
||||
num : i32
|
||||
}
|
||||
|
||||
Draw_Call :: struct {
|
||||
pass : Frame_Buffer_Pass,
|
||||
start_index : u32,
|
||||
@ -51,6 +80,11 @@ Glyph_Draw_Buffer :: struct {
|
||||
batch_x : i32,
|
||||
clear_draw_list : Draw_List,
|
||||
draw_list : Draw_List,
|
||||
|
||||
glyph_pack : #soa[dynamic]Glyph_Pack_Entry,
|
||||
oversized : #soa[dynamic]Glyph_Pack_Entry,
|
||||
to_cache : #soa[dynamic]Glyph_Pack_Entry,
|
||||
cached : #soa[dynamic]Glyph_Pack_Entry,
|
||||
}
|
||||
|
||||
blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} )
|
||||
@ -246,26 +280,17 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1
|
||||
// }
|
||||
|
||||
// TODO(Ed): Is it better to cache the glyph vertices for when it must be re-drawn (directly or two atlas)?
|
||||
cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32
|
||||
generate_glyph_pass_draw_list :: #force_inline proc(ctx : ^Context,
|
||||
glyph_index : Glyph,
|
||||
glyph_shape : Parser_Glyph_Shape,
|
||||
curve_quality : f32,
|
||||
bounds : GlyphBounds,
|
||||
scale, translate : Vec2
|
||||
) -> b32
|
||||
{
|
||||
profile(#procedure)
|
||||
if glyph_index == Glyph(0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Glyph shape handling are not abstractable between freetype and stb_truetype
|
||||
// if entry.parser_info.kind == .Freetype {
|
||||
// result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate )
|
||||
// return result
|
||||
// }
|
||||
|
||||
shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index)
|
||||
assert(error == .None)
|
||||
if len(shape) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
outside := Vec2{bounds_0.x - 21, bounds_0.y - 33}
|
||||
outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33}
|
||||
|
||||
draw := Draw_Call_Default
|
||||
draw.pass = Frame_Buffer_Pass.Glyph
|
||||
@ -274,13 +299,12 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry :
|
||||
path := &ctx.temp_path
|
||||
clear(path)
|
||||
|
||||
step := 1.0 / entry.curve_quality
|
||||
for edge in shape do #partial switch edge.type
|
||||
step := 1.0 / curve_quality
|
||||
for edge in glyph_shape do #partial switch edge.type
|
||||
{
|
||||
case .Move:
|
||||
if len(path) > 0 {
|
||||
// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose)
|
||||
draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate)
|
||||
construct_filled_path(&ctx.draw_list, outside, path[:], scale, translate)
|
||||
clear(path)
|
||||
}
|
||||
fallthrough
|
||||
@ -294,7 +318,7 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry :
|
||||
p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) }
|
||||
p2 := Vec2{ f32(edge.x), f32(edge.y) }
|
||||
|
||||
for index : f32 = 1; index <= entry.curve_quality; index += 1 {
|
||||
for index : f32 = 1; index <= curve_quality; index += 1 {
|
||||
alpha := index * step
|
||||
append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } )
|
||||
}
|
||||
@ -306,41 +330,33 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry :
|
||||
p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) }
|
||||
p3 := Vec2{ f32(edge.x), f32(edge.y) }
|
||||
|
||||
for index : f32 = 1; index <= entry.curve_quality; index += 1 {
|
||||
for index : f32 = 1; index <= curve_quality; index += 1 {
|
||||
alpha := index * step
|
||||
append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } )
|
||||
}
|
||||
}
|
||||
|
||||
if len(path) > 0 {
|
||||
// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose)
|
||||
draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate)
|
||||
construct_filled_path(&ctx.draw_list, outside, path[:], scale, translate)
|
||||
}
|
||||
|
||||
draw.end_index = 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 : Font_ID,
|
||||
cache_glyph_to_atlas :: #force_no_inline proc ( ctx : ^Context,
|
||||
glyph_index : Glyph,
|
||||
glyph_shape : Parser_Glyph_Shape,
|
||||
bounds : GlyphBounds,
|
||||
bounds_size : Vec2,
|
||||
region_pos : Vec2,
|
||||
region_size : Vec2,
|
||||
lru_code : u64,
|
||||
atlas_index : i32,
|
||||
entry : ^Entry,
|
||||
entry : Entry,
|
||||
// region_kind : Atlas_Region_Kind,
|
||||
// region : ^Atlas_Region,
|
||||
over_sample : Vec2
|
||||
@ -413,19 +429,48 @@ cache_glyph_to_atlas :: proc ( ctx : ^Context,
|
||||
append( & glyph_buffer.draw_list.calls, blit_to_atlas )
|
||||
|
||||
// Render glyph to glyph render target (FBO)
|
||||
cache_glyph( ctx, font, glyph_index, entry, bounds.p0, bounds.p1, glyph_draw_scale, glyph_draw_translate )
|
||||
generate_glyph_pass_draw_list( ctx, glyph_index, glyph_shape, entry.curve_quality, bounds, glyph_draw_scale, glyph_draw_translate )
|
||||
}
|
||||
|
||||
// ve_fontcache_clear_Draw_List
|
||||
clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
|
||||
clear( & draw_list.calls )
|
||||
clear( & draw_list.indices )
|
||||
clear( & draw_list.vertices )
|
||||
// Constructs a triangle fan to fill a shape using the provided path outside_point represents the center point of the fan.
|
||||
construct_filled_path :: #force_inline proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex,
|
||||
scale := Vec2 { 1, 1 },
|
||||
translate := Vec2 { 0, 0 }
|
||||
)
|
||||
{
|
||||
profile(#procedure)
|
||||
v_offset := cast(u32) len(draw_list.vertices)
|
||||
for point in path {
|
||||
point := point
|
||||
point.pos = point.pos * scale + translate
|
||||
append( & draw_list.vertices, point )
|
||||
}
|
||||
|
||||
outside_vertex := cast(u32) len(draw_list.vertices)
|
||||
{
|
||||
vertex := Vertex {
|
||||
pos = outside_point * scale + translate,
|
||||
u = 0,
|
||||
v = 0,
|
||||
}
|
||||
append( & draw_list.vertices, vertex )
|
||||
}
|
||||
|
||||
for index : u32 = 1; index < cast(u32) len(path); index += 1 {
|
||||
indices := & draw_list.indices
|
||||
to_add := [3]u32 {
|
||||
outside_vertex,
|
||||
v_offset + index - 1,
|
||||
v_offset + index
|
||||
}
|
||||
append( indices, ..to_add[:] )
|
||||
}
|
||||
}
|
||||
|
||||
directly_draw_massive_glyph :: proc( ctx : ^Context,
|
||||
entry : ^Entry,
|
||||
generate_oversized_draw_list :: #force_no_inline proc( ctx : ^Context,
|
||||
entry : Entry,
|
||||
glyph : Glyph,
|
||||
glyph_shape : Parser_Glyph_Shape,
|
||||
bounds : GlyphBounds,
|
||||
bounds_size : Vec2,
|
||||
over_sample, position, scale : Vec2 )
|
||||
@ -441,7 +486,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context,
|
||||
glyph_draw_translate := -1 * bounds.p0 * glyph_draw_scale + vec2_from_scalar(glyph_padding)
|
||||
screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_size )
|
||||
|
||||
cache_glyph( ctx, entry.id, glyph, entry, bounds.p0, bounds.p1, glyph_draw_scale, glyph_draw_translate )
|
||||
generate_glyph_pass_draw_list( ctx, glyph, glyph_shape, entry.curve_quality, bounds, glyph_draw_scale, glyph_draw_translate )
|
||||
|
||||
bounds_scaled := bounds_size * entry.size_scale
|
||||
|
||||
@ -484,59 +529,9 @@ directly_draw_massive_glyph :: proc( ctx : ^Context,
|
||||
append( & ctx.draw_list.calls, ..calls[:] )
|
||||
}
|
||||
|
||||
// Constructs a triangle fan to fill a shape using the provided path
|
||||
// outside_point represents the center point of the fan.
|
||||
//
|
||||
// Note(Original Author):
|
||||
// WARNING: doesn't actually append Draw_Call; caller is responsible for actually appending the Draw_Call.
|
||||
// ve_fontcache_draw_filled_path
|
||||
draw_filled_path :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex,
|
||||
scale := Vec2 { 1, 1 },
|
||||
translate := Vec2 { 0, 0 }
|
||||
// debug_print_verbose : b32 = false
|
||||
) #no_bounds_check
|
||||
{
|
||||
profile(#procedure)
|
||||
// if debug_print_verbose
|
||||
// {
|
||||
// log("outline_path:")
|
||||
// for point in path {
|
||||
// vec := point.pos * scale + translate
|
||||
// logf(" %0.2f %0.2f", vec.x, vec.y )
|
||||
// }
|
||||
// }
|
||||
|
||||
v_offset := cast(u32) len(draw_list.vertices)
|
||||
for point in path {
|
||||
point := point
|
||||
point.pos = point.pos * scale + translate
|
||||
append( & draw_list.vertices, point )
|
||||
}
|
||||
|
||||
outside_vertex := cast(u32) len(draw_list.vertices)
|
||||
{
|
||||
vertex := Vertex {
|
||||
pos = outside_point * scale + translate,
|
||||
u = 0,
|
||||
v = 0,
|
||||
}
|
||||
append( & draw_list.vertices, vertex )
|
||||
}
|
||||
|
||||
for index : u32 = 1; index < cast(u32) len(path); index += 1 {
|
||||
indices := & draw_list.indices
|
||||
to_add := [3]u32 {
|
||||
outside_vertex,
|
||||
v_offset + index - 1,
|
||||
v_offset + index
|
||||
}
|
||||
append( indices, ..to_add[:] )
|
||||
}
|
||||
}
|
||||
|
||||
draw_text_batch :: #force_inline proc (ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text,
|
||||
generate_cached_draw_list :: proc (ctx: ^Context, entry: Entry, shaped: Shaped_Text,
|
||||
// batch_start_idx, batch_end_idx : i32,
|
||||
glyph_pack : #soa[]GlyphPackEntry,
|
||||
glyph_pack : #soa[]Glyph_Pack_Entry,
|
||||
position, scale : Vec2,
|
||||
snap_width, snap_height : f32 )
|
||||
{
|
||||
@ -575,133 +570,144 @@ draw_text_batch :: #force_inline proc (ctx: ^Context, entry: ^Entry, shaped: ^Sh
|
||||
}
|
||||
}
|
||||
|
||||
GlyphBounds :: struct {
|
||||
p0, p1 : Vec2
|
||||
}
|
||||
|
||||
GlyphPackEntry :: struct {
|
||||
bounds : GlyphBounds,
|
||||
bounds_size : Vec2,
|
||||
over_sample : Vec2,
|
||||
translate : Vec2,
|
||||
region_pos : Vec2,
|
||||
region_size : Vec2,
|
||||
lru_code : u64,
|
||||
atlas_index : i32,
|
||||
index : Glyph,
|
||||
shape_id : i32,
|
||||
region_kind : Atlas_Region_Kind,
|
||||
in_atlas : b8,
|
||||
should_cache : b8,
|
||||
}
|
||||
|
||||
Glyph_Sub_Pack :: struct {
|
||||
pack : #soa[]GlyphPackEntry,
|
||||
num : i32
|
||||
}
|
||||
|
||||
// Helper for draw_text, all raw text content should be confirmed to be either formatting or visible shapes before getting cached.
|
||||
draw_text_shape :: #force_inline proc( ctx : ^Context,
|
||||
generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
|
||||
font : Font_ID,
|
||||
entry : ^Entry,
|
||||
shaped : ^Shaped_Text,
|
||||
entry : Entry,
|
||||
shaped : Shaped_Text,
|
||||
position, scale : Vec2,
|
||||
snap_width, snap_height : f32
|
||||
) -> (cursor_pos : Vec2) #no_bounds_check
|
||||
{
|
||||
profile(#procedure)
|
||||
|
||||
oversized : Glyph_Sub_Pack = {}
|
||||
to_cache : Glyph_Sub_Pack = {}
|
||||
cached : Glyph_Sub_Pack = {}
|
||||
atlas := & ctx.atlas
|
||||
glyph_buffer := & ctx.glyph_buffer
|
||||
draw_list := & ctx.draw_list
|
||||
colour := ctx.colour
|
||||
atlas_glyph_pad := atlas.glyph_padding
|
||||
atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) }
|
||||
|
||||
profile_begin("soa allocation")
|
||||
glyph_pack, glyph_pack_alloc_error := make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
|
||||
alloc_error : Allocator_Error
|
||||
oversized.pack, alloc_error = make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
to_cache.pack, alloc_error = make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
cached.pack, alloc_error = make_soa( #soa[]GlyphPackEntry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
// Make sure the packs are large enough for the shape
|
||||
non_zero_resize_soa(& glyph_buffer.glyph_pack, len(shaped.glyphs))
|
||||
glyph_pack := & glyph_buffer.glyph_pack
|
||||
oversized := & glyph_buffer.oversized
|
||||
to_cache := & glyph_buffer.to_cache
|
||||
cached := & glyph_buffer.cached
|
||||
append_sub_pack :: #force_inline proc ( pack : ^#soa[dynamic]Glyph_Pack_Entry, entry : Glyph_Pack_Entry )
|
||||
{
|
||||
raw := runtime.raw_soa_footer_dynamic_array(pack)
|
||||
raw.len += 1
|
||||
pack[len(pack) - 1] = entry
|
||||
}
|
||||
sub_slice :: #force_inline proc "contextless" ( pack : ^#soa[dynamic]Glyph_Pack_Entry) -> #soa[]Glyph_Pack_Entry { return pack[:] }
|
||||
profile_end()
|
||||
|
||||
append_sub_pack :: #force_inline proc "contextless" ( sub : ^Glyph_Sub_Pack, entry : GlyphPackEntry )
|
||||
// profile_begin("soa allocation")
|
||||
// glyph_pack_, glyph_pack_alloc_error := make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
|
||||
// oversized_ : Glyph_Sub_Pack = {}
|
||||
// to_cache_ : Glyph_Sub_Pack = {}
|
||||
// cached_ : Glyph_Sub_Pack = {}
|
||||
|
||||
// glyph_pack := & glyph_pack_
|
||||
// oversized := & oversized_
|
||||
// to_cache := & to_cache_
|
||||
// cached := & cached_
|
||||
|
||||
// alloc_error : Allocator_Error
|
||||
// oversized.pack, alloc_error = make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
// to_cache.pack, alloc_error = make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
// cached.pack, alloc_error = make_soa( #soa[]Glyph_Pack_Entry, len(shaped.glyphs), allocator = context.temp_allocator )
|
||||
// append_sub_pack :: #force_inline proc "contextless" ( sub : ^Glyph_Sub_Pack, entry : Glyph_Pack_Entry )
|
||||
// {
|
||||
// sub.pack[sub.num] = entry
|
||||
// sub.num += 1
|
||||
// }
|
||||
// sub_slice :: #force_inline proc "contextless" ( sub : ^Glyph_Sub_Pack) -> #soa[]Glyph_Pack_Entry { return sub.pack[: sub.num] }
|
||||
// profile_end()
|
||||
|
||||
profile_begin("index")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
sub.pack[sub.num] = entry
|
||||
sub.num += 1
|
||||
// glyph.shape_id = cast(i32) index
|
||||
glyph.index = shaped.glyphs[ index ]
|
||||
}
|
||||
sub_slice :: #force_inline proc "contextless" ( sub : Glyph_Sub_Pack) -> #soa[]GlyphPackEntry { return sub.pack[: sub.num] }
|
||||
profile_end()
|
||||
|
||||
atlas := & ctx.atlas
|
||||
|
||||
SOA_Setup:
|
||||
profile_begin("translate")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
profile("SOA setup")
|
||||
glyph.translate = position + (shaped.positions[index]) * scale
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("index & translate")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
glyph.shape_id = cast(i32) index
|
||||
glyph.index = shaped.glyphs[ index ]
|
||||
profile_begin("bounds")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index)
|
||||
glyph.bounds = parser_get_bounds( entry.parser_info, glyph.index )
|
||||
glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("region & oversized segregation")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
glyph.region_kind,
|
||||
glyph.over_sample = decide_codepoint_region( ctx.atlas, ctx.glyph_buffer, entry.size_scale, glyph.index, glyph.bounds_size )
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("segregation")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
region := atlas.regions[glyph.region_kind]
|
||||
if glyph.region_kind == .E {
|
||||
append_sub_pack(oversized, glyph)
|
||||
continue
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("translate")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
glyph.translate = position + (shaped.positions[index]) * scale
|
||||
glyph.atlas_index = lru_get( & region.state, glyph.lru_code )
|
||||
glyph.in_atlas, glyph.should_cache = check_and_reserve_slot_in_atlas( ctx ^, glyph.index, glyph.lru_code, & glyph.atlas_index, region )
|
||||
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
|
||||
|
||||
if glyph.should_cache {
|
||||
profile("append to_cache")
|
||||
append_sub_pack(to_cache, glyph)
|
||||
mark_batch_codepoint_seen(ctx, glyph.lru_code)
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("bounds")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index)
|
||||
glyph.bounds = parser_get_bounds( & entry.parser_info, glyph.index )
|
||||
glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0
|
||||
else {
|
||||
profile("append cached")
|
||||
append_sub_pack(cached, glyph)
|
||||
mark_batch_codepoint_seen(ctx, glyph.lru_code)
|
||||
}
|
||||
profile_end()
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("region & oversized segregation")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
glyph.region_kind,
|
||||
glyph.over_sample = decide_codepoint_region( ctx.atlas, ctx.glyph_buffer, entry.size_scale, glyph.index, glyph.bounds_size )
|
||||
}
|
||||
profile_end()
|
||||
profile_begin("to_cache: font parser shape generation")
|
||||
for & glyph, index in sub_slice(to_cache) {
|
||||
error : Allocator_Error
|
||||
glyph.shape, error = parser_get_glyph_shape(entry.parser_info, glyph.index)
|
||||
assert(error == .None)
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("caching setup")
|
||||
for & glyph, index in glyph_pack
|
||||
{
|
||||
region := atlas.regions[glyph.region_kind]
|
||||
if glyph.region_kind == .E {
|
||||
append_sub_pack(& oversized, glyph)
|
||||
continue
|
||||
}
|
||||
|
||||
glyph.atlas_index = lru_get( & region.state, glyph.lru_code )
|
||||
glyph.in_atlas, glyph.should_cache = check_and_reserve_slot_in_atlas( ctx, glyph.index, glyph.lru_code, & glyph.atlas_index, region )
|
||||
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
|
||||
|
||||
if glyph.should_cache {
|
||||
profile("append to_cache")
|
||||
append_sub_pack(& to_cache, glyph)
|
||||
mark_batch_codepoint_seen(ctx, glyph.lru_code)
|
||||
cache_glyph_to_atlas( ctx, font, glyph.index, glyph.bounds, glyph.bounds_size, glyph.region_pos, glyph.region_size, glyph.lru_code, glyph.atlas_index, entry, glyph.over_sample )
|
||||
continue
|
||||
}
|
||||
else {
|
||||
profile("append cached")
|
||||
append_sub_pack(& cached, glyph)
|
||||
mark_batch_codepoint_seen(ctx, glyph.lru_code)
|
||||
}
|
||||
}
|
||||
profile_end()
|
||||
profile_begin("to_cache: caching to atlas")
|
||||
for & glyph, index in sub_slice(to_cache) {
|
||||
cache_glyph_to_atlas( ctx, glyph.index, glyph.shape, glyph.bounds, glyph.bounds_size, glyph.region_pos, glyph.region_size, glyph.lru_code, glyph.atlas_index, entry, glyph.over_sample )
|
||||
}
|
||||
profile_end()
|
||||
|
||||
for & glyph, index in sub_slice(to_cache)
|
||||
{
|
||||
parser_free_shape(entry.parser_info, glyph.shape)
|
||||
}
|
||||
|
||||
draw_text_batch( ctx, entry, shaped, sub_slice(to_cache), position, scale, snap_width, snap_height )
|
||||
generate_cached_draw_list( ctx, entry, shaped, sub_slice(to_cache), position, scale, snap_width, snap_height )
|
||||
reset_batch_codepoint_state( ctx )
|
||||
|
||||
draw_text_batch( ctx, entry, shaped, sub_slice(cached), position, scale, snap_width , snap_height )
|
||||
generate_cached_draw_list( ctx, entry, shaped, sub_slice(cached), position, scale, snap_width , snap_height )
|
||||
reset_batch_codepoint_state( ctx )
|
||||
|
||||
profile_begin("generate oversized glyphs draw_list")
|
||||
@ -709,7 +715,7 @@ draw_text_shape :: #force_inline proc( ctx : ^Context,
|
||||
|
||||
for & glyph, index in sub_slice(oversized)
|
||||
{
|
||||
directly_draw_massive_glyph(ctx, entry, glyph.index,
|
||||
generate_oversized_draw_list(ctx, entry, glyph.index, glyph.shape,
|
||||
glyph.bounds,
|
||||
glyph.bounds_size,
|
||||
glyph.over_sample, glyph.translate, scale )
|
||||
@ -717,6 +723,11 @@ draw_text_shape :: #force_inline proc( ctx : ^Context,
|
||||
reset_batch_codepoint_state( ctx )
|
||||
|
||||
profile_end()
|
||||
|
||||
// clear_soa(& glyph_buffer.glyph_pack)
|
||||
clear_soa(& glyph_buffer.oversized)
|
||||
clear_soa(& glyph_buffer.to_cache)
|
||||
clear_soa(& glyph_buffer.cached)
|
||||
|
||||
cursor_pos = position + shaped.end_cursor_pos * scale
|
||||
return
|
||||
@ -744,8 +755,15 @@ flush_glyph_buffer_to_atlas :: #force_inline proc( ctx : ^Context )
|
||||
}
|
||||
}
|
||||
|
||||
// ve_fontcache_clear_Draw_List
|
||||
clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
|
||||
clear( & draw_list.calls )
|
||||
clear( & draw_list.indices )
|
||||
clear( & draw_list.vertices )
|
||||
}
|
||||
|
||||
// ve_fontcache_merge_Draw_List
|
||||
merge_draw_list :: #force_inline proc ( #no_alias dst, src : ^Draw_List )
|
||||
merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List )
|
||||
{
|
||||
profile(#procedure)
|
||||
error : Allocator_Error
|
||||
|
@ -120,7 +120,7 @@ vec2_64 :: proc {
|
||||
import "../../grime"
|
||||
|
||||
|
||||
DISABLE_PROFILING :: false
|
||||
DISABLE_PROFILING :: true
|
||||
|
||||
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
|
||||
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
|
@ -44,6 +44,11 @@ reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) {
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_array_soa :: proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := runtime.raw_soa_footer(self)
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Map) self
|
||||
raw.allocator = allocator
|
||||
|
@ -132,7 +132,7 @@ parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font
|
||||
return Glyph(-1)
|
||||
}
|
||||
|
||||
parser_free_shape :: #force_inline proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape )
|
||||
parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
@ -140,11 +140,11 @@ parser_free_shape :: #force_inline proc( font : ^Parser_Font_Info, shape : Parse
|
||||
delete(shape)
|
||||
|
||||
case .STB_TrueType:
|
||||
stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
|
||||
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
|
||||
}
|
||||
}
|
||||
|
||||
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
|
||||
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
@ -170,12 +170,12 @@ parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( fo
|
||||
}
|
||||
|
||||
case .STB_TrueType:
|
||||
stbtt.GetCodepointHMetrics( & font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
|
||||
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
|
||||
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
@ -198,13 +198,13 @@ parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : ^
|
||||
}
|
||||
|
||||
case .STB_TrueType:
|
||||
kern := stbtt.GetCodepointKernAdvance( & font.stbtt_info, prev_codepoint, codepoint )
|
||||
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
|
||||
return kern
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
|
||||
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
@ -215,12 +215,12 @@ parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^P
|
||||
line_gap = i32(info.height) - (ascent - descent)
|
||||
|
||||
case .STB_TrueType:
|
||||
stbtt.GetFontVMetrics( & font.stbtt_info, & ascent, & descent, & line_gap )
|
||||
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds : GlyphBounds)
|
||||
parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : GlyphBounds)
|
||||
{
|
||||
profile(#procedure)
|
||||
|
||||
@ -238,7 +238,7 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info
|
||||
|
||||
case .STB_TrueType:
|
||||
x0, y0, x1, y1 : i32
|
||||
success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
|
||||
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
|
||||
// assert( success )
|
||||
|
||||
bounds_0 = { x0, y0 }
|
||||
@ -248,7 +248,7 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : ^Parser_Font_Info
|
||||
return
|
||||
}
|
||||
|
||||
parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
|
||||
parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
@ -258,7 +258,7 @@ parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_i
|
||||
|
||||
case .STB_TrueType:
|
||||
stb_shape : [^]stbtt.vertex
|
||||
nverts := stbtt.GetGlyphShape( & font.stbtt_info, cast(i32) glyph_index, & stb_shape )
|
||||
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
|
||||
|
||||
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
|
||||
shape_raw.data = stb_shape
|
||||
@ -295,7 +295,7 @@ parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_I
|
||||
return false
|
||||
}
|
||||
|
||||
parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
|
||||
parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
|
||||
{
|
||||
size_scale := size < 0.0 ? \
|
||||
parser_scale_for_pixel_height( font, -size ) \
|
||||
@ -304,7 +304,7 @@ parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, siz
|
||||
return size_scale
|
||||
}
|
||||
|
||||
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
|
||||
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
|
||||
{
|
||||
switch font.kind {
|
||||
case .Freetype:
|
||||
@ -313,12 +313,12 @@ parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : ^Pars
|
||||
return size_scale
|
||||
|
||||
case.STB_TrueType:
|
||||
return stbtt.ScaleForPixelHeight( & font.stbtt_info, size )
|
||||
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
|
||||
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
|
||||
{
|
||||
switch font.kind {
|
||||
case .Freetype:
|
||||
@ -342,7 +342,7 @@ parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font
|
||||
return size_scale
|
||||
|
||||
case .STB_TrueType:
|
||||
return stbtt.ScaleForMappingEmToPixels( & font.stbtt_info, size )
|
||||
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package vefontcache
|
||||
|
||||
Shaped_Text :: struct {
|
||||
font : Font_ID,
|
||||
entry : ^Entry,
|
||||
|
||||
glyphs : [dynamic]Glyph,
|
||||
positions : [dynamic]Vec2,
|
||||
end_cursor_pos : Vec2,
|
||||
@ -19,9 +22,9 @@ shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte
|
||||
}
|
||||
}
|
||||
|
||||
ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text )
|
||||
ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text )
|
||||
|
||||
shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape_text_uncached : ShapedTextUncachedProc ) -> ^Shaped_Text #no_bounds_check
|
||||
shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, shape_text_uncached : $ShapedTextUncachedProc ) -> (shaped_text : Shaped_Text)
|
||||
{
|
||||
profile(#procedure)
|
||||
font := font
|
||||
@ -45,23 +48,27 @@ shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, e
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_idx := lru_get_next_evicted( state )
|
||||
assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF )
|
||||
next_evict_idx := lru_get_next_evicted( state ^ )
|
||||
// assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF )
|
||||
|
||||
shape_cache_idx = lru_peek( state, next_evict_idx, must_find = true )
|
||||
assert( shape_cache_idx != - 1 )
|
||||
shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true )
|
||||
// assert( shape_cache_idx != - 1 )
|
||||
|
||||
lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
|
||||
shape_entry := & shape_cache.storage[ shape_cache_idx ]
|
||||
shape_text_uncached( ctx, font, text_utf8, entry, shape_entry )
|
||||
storage_entry := & shape_cache.storage[ shape_cache_idx ]
|
||||
shape_text_uncached( ctx, font, text_utf8, entry, storage_entry )
|
||||
|
||||
shaped_text = storage_entry ^
|
||||
return
|
||||
}
|
||||
|
||||
return & shape_cache.storage[ shape_cache_idx ]
|
||||
shaped_text = shape_cache.storage[ shape_cache_idx ]
|
||||
return
|
||||
}
|
||||
|
||||
shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text )
|
||||
shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text )
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
@ -70,16 +77,16 @@ shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_
|
||||
clear( & output.glyphs )
|
||||
clear( & output.positions )
|
||||
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info )
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info )
|
||||
ascent := f32(ascent_i32)
|
||||
descent := f32(descent_i32)
|
||||
line_gap := f32(line_gap_i32)
|
||||
line_height := (ascent - descent + line_gap) * entry.size_scale
|
||||
|
||||
shaper_shape_from_text( & ctx.shaper_ctx, entry.parser_info, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale )
|
||||
shaper_shape_from_text( & ctx.shaper_ctx, entry.parser_info, entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale )
|
||||
}
|
||||
|
||||
shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text )
|
||||
shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text )
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
@ -88,7 +95,7 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s
|
||||
clear( & output.glyphs )
|
||||
clear( & output.positions )
|
||||
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info )
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info )
|
||||
ascent := f32(ascent_i32)
|
||||
descent := f32(descent_i32)
|
||||
line_gap := f32(line_gap_i32)
|
||||
@ -102,7 +109,7 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s
|
||||
for codepoint, index in text_utf8
|
||||
{
|
||||
if prev_codepoint > 0 {
|
||||
kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint )
|
||||
kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint )
|
||||
position.x += f32(kern) * entry.size_scale
|
||||
}
|
||||
if codepoint == '\n'
|
||||
@ -130,7 +137,7 @@ shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : s
|
||||
})
|
||||
}
|
||||
|
||||
advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint )
|
||||
advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint )
|
||||
position.x += f32(advance) * entry.size_scale
|
||||
prev_codepoint = codepoint
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ shaper_unload_font :: proc( ctx : ^Shaper_Info )
|
||||
if blob != nil do harfbuzz.blob_destroy( blob )
|
||||
}
|
||||
|
||||
shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info : Parser_Font_Info, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string,
|
||||
shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info : Parser_Font_Info, info : Shaper_Info, output :^Shaped_Text, text_utf8 : string,
|
||||
ascent, descent, line_gap : i32, size, size_scale : f32 )
|
||||
{
|
||||
profile(#procedure)
|
||||
@ -72,7 +72,7 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info
|
||||
line_height := ((ascent - descent + line_gap) * size_scale)
|
||||
|
||||
position : Vec2
|
||||
shape_run :: #force_inline proc( parser_info : Parser_Font_Info, buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text,
|
||||
shape_run :: proc( parser_info : Parser_Font_Info, buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text,
|
||||
position : ^Vec2, max_line_width: ^f32, line_count: ^int,
|
||||
ascent, descent, line_gap, size, size_scale: f32,
|
||||
snap_shape_pos : b32, adv_snap_small_font_threshold : f32 )
|
||||
|
@ -21,9 +21,13 @@ Entry :: struct {
|
||||
}
|
||||
|
||||
Entry_Default :: Entry {
|
||||
id = 0,
|
||||
used = false,
|
||||
size = 24.0,
|
||||
id = 0,
|
||||
used = false,
|
||||
curve_quality = 2,
|
||||
|
||||
// TODO(Ed): Remove size information. Its not conceptually needed.
|
||||
// The size_scale can be computed only the fly when needed for a font.
|
||||
size = 24.0,
|
||||
size_scale = 1.0,
|
||||
}
|
||||
|
||||
@ -147,10 +151,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
|
||||
glyph_draw_params := Init_Glyph_Draw_Params_Default,
|
||||
shape_cache_params := Init_Shape_Cache_Params_Default,
|
||||
shaper_params := Init_Shaper_Params_Default,
|
||||
default_curve_quality : u32 = 8 * 2,
|
||||
entires_reserve : u32 = 8 * 256,
|
||||
temp_path_reserve : u32 = 8 * 1024,
|
||||
temp_codepoint_seen_reserve : u32 = 8 * 1024,
|
||||
default_curve_quality : u32 = 2,
|
||||
entires_reserve : u32 = 256,
|
||||
temp_path_reserve : u32 = 1024,
|
||||
temp_codepoint_seen_reserve : u32 = 1024,
|
||||
)
|
||||
{
|
||||
assert( ctx != nil, "Must provide a valid context" )
|
||||
@ -282,6 +286,15 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
|
||||
|
||||
clear_draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 4 )
|
||||
assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" )
|
||||
|
||||
glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator )
|
||||
oversized, error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator )
|
||||
to_cache, error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator )
|
||||
cached, error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator )
|
||||
// resize_soa(& glyph_pack, 1 * Kilobyte)
|
||||
// resize_soa(& oversized, 1 * Kilobyte)
|
||||
// resize_soa(& to_cache, 1 * Kilobyte)
|
||||
// resize_soa(& cached, 1 * Kilobyte)
|
||||
}
|
||||
|
||||
parser_init( & parser_ctx, parser_kind )
|
||||
@ -325,6 +338,11 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator )
|
||||
reload_array( & glyph_buffer.clear_draw_list.indices, allocator )
|
||||
reload_array( & glyph_buffer.clear_draw_list.vertices, allocator )
|
||||
|
||||
reload_array_soa( & glyph_buffer.glyph_pack, allocator )
|
||||
reload_array_soa( & glyph_buffer.oversized, allocator )
|
||||
reload_array_soa( & glyph_buffer.to_cache, allocator )
|
||||
reload_array_soa( & glyph_buffer.cached, allocator )
|
||||
|
||||
reload_array( & shape_cache.storage, allocator )
|
||||
}
|
||||
|
||||
@ -369,6 +387,11 @@ shutdown :: proc( ctx : ^Context )
|
||||
delete( glyph_buffer.clear_draw_list.indices )
|
||||
delete( glyph_buffer.clear_draw_list.calls )
|
||||
|
||||
delete_soa( glyph_buffer.glyph_pack)
|
||||
delete_soa( glyph_buffer.oversized)
|
||||
delete_soa( glyph_buffer.to_cache)
|
||||
delete_soa( glyph_buffer.cached)
|
||||
|
||||
shaper_shutdown( & shaper_ctx )
|
||||
parser_shutdown( & parser_ctx )
|
||||
}
|
||||
@ -403,7 +426,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32,
|
||||
shaper_info = shaper_load_font( & shaper_ctx, label, data )
|
||||
|
||||
size = size_px
|
||||
size_scale = parser_scale( & parser_info, size )
|
||||
size_scale = parser_scale( parser_info, size )
|
||||
|
||||
if glyph_curve_quality == 0 {
|
||||
curve_quality = f32(ctx.default_curve_quality)
|
||||
@ -457,24 +480,54 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : str
|
||||
ctx.cursor_pos = {}
|
||||
|
||||
position := position
|
||||
if ctx.snap_width > 0 do position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width
|
||||
if ctx.snap_height > 0 do position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height
|
||||
position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width
|
||||
position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height
|
||||
|
||||
entry := & ctx.entries[ font ]
|
||||
entry := ctx.entries[ font ]
|
||||
|
||||
ChunkType :: enum u32 { Visible, Formatting }
|
||||
chunk_kind : ChunkType
|
||||
chunk_start : int = 0
|
||||
chunk_end : int = 0
|
||||
shape := shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
|
||||
ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
|
||||
return true
|
||||
}
|
||||
|
||||
text_utf8_bytes := transmute([]u8) text_utf8
|
||||
text_chunk : string
|
||||
draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
text_chunk = transmute(string) text_utf8_bytes[ : ]
|
||||
if len(text_chunk) > 0 {
|
||||
shaped := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_advanced )
|
||||
ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height )
|
||||
}
|
||||
ctx.cursor_pos = {}
|
||||
|
||||
entry := ctx.entries[ font ]
|
||||
shape := shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
|
||||
ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
|
||||
return true
|
||||
}
|
||||
|
||||
// For high performance: Resolve the shape and track it to reduce iteration overhead
|
||||
draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, shape : Shaped_Text, position, scale : Vec2 ) -> b32
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
position := position
|
||||
position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width
|
||||
position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height
|
||||
|
||||
entry := ctx.entries[ font ]
|
||||
ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
|
||||
return true
|
||||
}
|
||||
|
||||
// For high performance: Resolve the shape and track it to reduce iteration overhead
|
||||
draw_text_shape_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, shape : Shaped_Text, position, scale : Vec2 ) -> b32
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
entry := ctx.entries[ font ]
|
||||
ctx.cursor_pos = generate_shape_draw_list( ctx, font, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
|
||||
return true
|
||||
}
|
||||
|
||||
@ -522,8 +575,8 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, text_ut
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
entry := &ctx.entries[font]
|
||||
shaped := shape_text_cached(ctx, font, text_utf8, entry, shape_text_uncached_advanced )
|
||||
entry := ctx.entries[font]
|
||||
shaped := shaper_shape_text_cached(ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
|
||||
return shaped.size
|
||||
}
|
||||
|
||||
@ -533,7 +586,7 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
entry := & ctx.entries[ font ]
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info )
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( entry.parser_info )
|
||||
|
||||
ascent = (f32(ascent_i32) * entry.size_scale)
|
||||
descent = (f32(descent_i32) * entry.size_scale)
|
||||
@ -543,6 +596,34 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID
|
||||
|
||||
//#endregion("metrics")
|
||||
|
||||
//#region("shaping")
|
||||
|
||||
shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, allocator := context.allocator ) -> Shaped_Text
|
||||
{
|
||||
entry := ctx.entries[ font ]
|
||||
return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_from_text_latin )
|
||||
}
|
||||
|
||||
shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> Shaped_Text
|
||||
{
|
||||
entry := ctx.entries[ font ]
|
||||
return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
|
||||
}
|
||||
|
||||
// User handled shaped text. Will not be cached
|
||||
shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, allocator := context.allocator ) -> Shaped_Text
|
||||
{
|
||||
return {}
|
||||
}
|
||||
|
||||
// User handled shaped text. Will not be cached
|
||||
shape_text_advanced_uncahed :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, allocator := context.allocator ) -> Shaped_Text
|
||||
{
|
||||
return {}
|
||||
}
|
||||
|
||||
//#endregion("shaping")
|
||||
|
||||
// Can be used with hot-reload
|
||||
clear_atlas_region_caches :: proc(ctx : ^Context)
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ set_profiler_module_context :: #force_inline proc "contextless" ( ctx : ^SpallPr
|
||||
Module_Context = ctx
|
||||
}
|
||||
|
||||
DISABLE_PROFILING :: false
|
||||
DISABLE_PROFILING :: true
|
||||
|
||||
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
|
||||
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
|
@ -158,7 +158,8 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
|
||||
|
||||
font := font
|
||||
if font.key == Font_Default.key do font = default_font
|
||||
draw_text_string_pos_extent( content, font, size, pos, color )
|
||||
shape := shape_text_cached( content, font, size, app_config().font_size_screen_scalar )
|
||||
draw_text_shape_pos_extent( shape, font, size, pos, color )
|
||||
}
|
||||
|
||||
debug_text :: proc( format : string, args : ..any )
|
||||
@ -578,8 +579,8 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
|
||||
cam != nil ? cam.position.y : 0,
|
||||
}
|
||||
|
||||
screen_size := screen_extent * 2
|
||||
screen_scaled := (1.0 / screen_size)
|
||||
screen_size := screen_extent * 2
|
||||
screen_size_norm := (1.0 / screen_size)
|
||||
|
||||
layer_left : b32 = true
|
||||
for layer_left
|
||||
@ -646,10 +647,12 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
|
||||
text_id += 1
|
||||
|
||||
if cam != nil {
|
||||
draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam_offset, screen_size, screen_scaled, cam.zoom, entry.color )
|
||||
// draw_text_shape_pos_extent_zoomed( entry.shape, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color )
|
||||
draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color )
|
||||
}
|
||||
else {
|
||||
draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color )
|
||||
// draw_text_string_pos_extent( entry.shape, font, entry.font_size, entry.position, entry.color )
|
||||
}
|
||||
}
|
||||
|
||||
@ -897,34 +900,64 @@ draw_rect_rounded_border :: proc(rect: Range2, radii: [4]f32, border_width: f32,
|
||||
}
|
||||
|
||||
// Draw text using a string and normalized render coordinates
|
||||
draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 )
|
||||
draw_text_string_pos_norm :: #force_inline proc( text : string, id : FontID, font_size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 )
|
||||
{
|
||||
state := get_state(); using state
|
||||
width := app_window.extent.x * 2
|
||||
height := app_window.extent.y * 2
|
||||
|
||||
ve_id, resolved_size := font_provider_resolve_draw_id( id, size * config.font_size_screen_scalar )
|
||||
ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size * config.font_size_screen_scalar )
|
||||
color_norm := normalize_rgba8(color)
|
||||
|
||||
screen_size_norm := Vec2{1 / width, 1 / height}
|
||||
|
||||
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
|
||||
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale * (1 / config.font_size_screen_scalar) )
|
||||
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, text, pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar) )
|
||||
return
|
||||
}
|
||||
|
||||
// Draw text using a string and extent-based screen coordinates
|
||||
draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White )
|
||||
draw_text_string_pos_extent :: #force_inline proc( text : string, id : FontID, font_size : f32, pos : Vec2, color := Color_White )
|
||||
{
|
||||
// profile(#procedure)
|
||||
profile(#procedure)
|
||||
state := get_state(); using state
|
||||
screen_size := app_window.extent * 2
|
||||
render_pos := screen_to_render_pos(pos)
|
||||
normalized_pos := render_pos * (1.0 / screen_size)
|
||||
draw_text_string_pos_norm( content, id, size, normalized_pos, color )
|
||||
draw_text_string_pos_norm( text, id, font_size, normalized_pos, color )
|
||||
}
|
||||
|
||||
draw_text_string_pos_extent_zoomed :: #force_inline proc( content : string, id : FontID, size : f32, pos, cam_offset, screen_size, screen_scaled : Vec2, zoom : f32, color := Color_White )
|
||||
// Draw text using a string and normalized render coordinates
|
||||
draw_text_shape_pos_norm :: #force_inline proc( shape : ShapedText, id : FontID, font_size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 )
|
||||
{
|
||||
// profile(#procedure)
|
||||
state := get_state(); using state
|
||||
width := app_window.extent.x * 2
|
||||
height := app_window.extent.y * 2
|
||||
|
||||
ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size * config.font_size_screen_scalar )
|
||||
color_norm := normalize_rgba8(color)
|
||||
|
||||
screen_size_norm := Vec2{1 / width, 1 / height}
|
||||
|
||||
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
|
||||
ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, shape, pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar) )
|
||||
return
|
||||
}
|
||||
|
||||
// Draw text using a string and extent-based screen coordinates
|
||||
draw_text_shape_pos_extent :: #force_inline proc( shape : ShapedText, id : FontID, font_size : f32, pos : Vec2, color := Color_White )
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state(); using state
|
||||
screen_size := app_window.extent * 2
|
||||
render_pos := screen_to_render_pos(pos)
|
||||
normalized_pos := render_pos * (1.0 / screen_size)
|
||||
draw_text_shape_pos_norm( shape, id, font_size, normalized_pos, color )
|
||||
}
|
||||
|
||||
draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : FontID, size : f32, pos, cam_offset, screen_size, screen_size_norm : Vec2, zoom : f32, color := Color_White )
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state.
|
||||
|
||||
zoom_adjust_size := size * zoom
|
||||
@ -935,16 +968,16 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( content : string, id :
|
||||
|
||||
pos_offset := (pos + cam_offset)
|
||||
render_pos := ws_view_to_render_pos(pos)
|
||||
normalized_pos := render_pos * screen_scaled
|
||||
normalized_pos := render_pos * screen_size_norm
|
||||
|
||||
ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size )
|
||||
|
||||
text_scale : Vec2 = screen_scaled
|
||||
text_scale : Vec2 = screen_size_norm
|
||||
// if config.cam_zoom_mode == .Smooth
|
||||
{
|
||||
f32_resolved_size := f32(resolved_size)
|
||||
diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size
|
||||
text_scale = diff_scalar * screen_scaled
|
||||
text_scale = diff_scalar * screen_size_norm
|
||||
text_scale.x = clamp( text_scale.x, 0, screen_size.x )
|
||||
text_scale.y = clamp( text_scale.y, 0, screen_size.y )
|
||||
}
|
||||
@ -954,7 +987,42 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( content : string, id :
|
||||
|
||||
color_norm := normalize_rgba8(color)
|
||||
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
|
||||
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, content, normalized_pos, text_scale )
|
||||
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, text, normalized_pos, text_scale )
|
||||
}
|
||||
|
||||
draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id : FontID, size : f32, pos, cam_offset, screen_size, screen_size_norm : Vec2, zoom : f32, color := Color_White )
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state.
|
||||
|
||||
zoom_adjust_size := size * zoom
|
||||
|
||||
// Over-sample font-size for any render under a camera
|
||||
over_sample : f32 = f32(state.config.font_size_canvas_scalar)
|
||||
zoom_adjust_size *= over_sample
|
||||
|
||||
pos_offset := (pos + cam_offset)
|
||||
render_pos := ws_view_to_render_pos(pos)
|
||||
normalized_pos := render_pos * screen_size_norm
|
||||
|
||||
ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size )
|
||||
|
||||
text_scale : Vec2 = screen_size_norm
|
||||
// if config.cam_zoom_mode == .Smooth
|
||||
{
|
||||
f32_resolved_size := f32(resolved_size)
|
||||
diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size
|
||||
text_scale = diff_scalar * screen_size_norm
|
||||
text_scale.x = clamp( text_scale.x, 0, screen_size.x )
|
||||
text_scale.y = clamp( text_scale.y, 0, screen_size.y )
|
||||
}
|
||||
|
||||
// Down-sample back
|
||||
text_scale /= over_sample
|
||||
|
||||
color_norm := normalize_rgba8(color)
|
||||
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
|
||||
ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, shape, normalized_pos, text_scale )
|
||||
}
|
||||
|
||||
// TODO(Ed): Eventually the workspace will need a viewport for drawing text
|
||||
|
@ -34,6 +34,8 @@ FontProviderContext :: struct
|
||||
using render : VE_RenderData,
|
||||
}
|
||||
|
||||
ShapedText :: ve.Shaped_Text
|
||||
|
||||
font_provider_startup :: proc( ctx : ^FontProviderContext )
|
||||
{
|
||||
profile(#procedure)
|
||||
@ -116,7 +118,7 @@ font_load :: proc(path_file : string,
|
||||
|
||||
Font_Use_Default_Size :: f32(0.0)
|
||||
|
||||
font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Size ) -> (ve_id :ve.Font_ID, resolved_size : i32)
|
||||
font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_Use_Default_Size ) -> (ve_id :ve.Font_ID, resolved_size : i32)
|
||||
{
|
||||
provider_data := get_state().font_provider_ctx; using provider_data
|
||||
|
||||
@ -130,7 +132,7 @@ font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Siz
|
||||
return
|
||||
}
|
||||
|
||||
measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2
|
||||
measure_text_size :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2
|
||||
{
|
||||
ve_id, size := font_provider_resolve_draw_id( font, font_size )
|
||||
measured := ve.measure_text_size( & get_state().font_provider_ctx.ve_ctx, ve_id, text )
|
||||
@ -143,3 +145,10 @@ get_font_vertical_metrics :: #force_inline proc ( font : FontID, font_size := Fo
|
||||
ascent, descent, line_gap = ve.get_font_vertical_metrics( & get_state().font_provider_ctx.ve_ctx, ve_id )
|
||||
return
|
||||
}
|
||||
|
||||
shape_text_cached :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, scalar : f32 ) -> ShapedText
|
||||
{
|
||||
ve_id, size := font_provider_resolve_draw_id( font, font_size * scalar )
|
||||
shape := ve.shape_text_advanced( & get_state().font_provider_ctx.ve_ctx, ve_id, text )
|
||||
return shape
|
||||
}
|
||||
|
@ -58,13 +58,21 @@ UI_ScalarConstraint :: struct {
|
||||
|
||||
UI_Scalar2 :: [Axis2.Count]UI_Scalar
|
||||
|
||||
// UI_BoxFlags_Stack_Size :: 512
|
||||
UI_Layout_Stack_Size :: 512
|
||||
UI_Style_Stack_Size :: 512
|
||||
UI_Parent_Stack_Size :: 512
|
||||
// UI_Built_Boxes_Array_Size :: 8
|
||||
UI_Built_Boxes_Array_Size :: 56 * Kilobyte
|
||||
UI_BoxCache_TableSize :: 8 * Kilobyte
|
||||
// The UI_Box's actual positioning and sizing
|
||||
// There is an excess of rectangles here for debug puproses.
|
||||
UI_Computed :: struct {
|
||||
// anchors : Range2, // Bounds for anchors within parent
|
||||
// margins : Range2, // Bounds for margins within parent
|
||||
padding : Range2, // Bounds for padding's starting bounds (will be offset by border if there is one), only here for debug vis
|
||||
|
||||
bounds : Range2, // Bounds for box itself
|
||||
content : Range2, // Bounds for content (text or children)
|
||||
text_shape : ShapedText, // Text string processed into shape optimial for processing by text draw_list generator.
|
||||
text_pos : Vec2, // Position of text within content
|
||||
text_size : Vec2, // Size of text within content
|
||||
fresh : b32, // If the auto-layout has been computed for the current frame
|
||||
}
|
||||
|
||||
|
||||
UI_RenderEntry :: struct {
|
||||
info : UI_RenderBoxInfo,
|
||||
@ -75,16 +83,6 @@ UI_RenderEntry :: struct {
|
||||
|
||||
UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry)
|
||||
|
||||
// UI_RenderBoxInfo :: struct {
|
||||
// using computed : UI_Computed,
|
||||
// using style : UI_Style,
|
||||
// text : StrRunesPair,
|
||||
// font_size : UI_Scalar,
|
||||
// border_width : UI_Scalar,
|
||||
// label : StrRunesPair,
|
||||
// layer_signal : b32,
|
||||
// }
|
||||
|
||||
UI_RenderBoxInfo :: struct {
|
||||
bounds : Range2,
|
||||
corner_radii : [Corner.Count]f32,
|
||||
@ -96,6 +94,7 @@ UI_RenderBoxInfo :: struct {
|
||||
|
||||
UI_RenderTextInfo :: struct {
|
||||
text : string,
|
||||
shape : ShapedText,
|
||||
position : Vec2,
|
||||
color : RGBA8,
|
||||
font : FontID,
|
||||
@ -110,6 +109,14 @@ UI_RenderMethod :: enum u32 {
|
||||
|
||||
UI_Render_Method :: UI_RenderMethod.Layers
|
||||
|
||||
// UI_BoxFlags_Stack_Size :: 512
|
||||
UI_Layout_Stack_Size :: 512
|
||||
UI_Style_Stack_Size :: 512
|
||||
UI_Parent_Stack_Size :: 512
|
||||
// UI_Built_Boxes_Array_Size :: 8
|
||||
UI_Built_Boxes_Array_Size :: 56 * Kilobyte
|
||||
UI_BoxCache_TableSize :: 8 * Kilobyte
|
||||
|
||||
// TODO(Ed): Rename to UI_Context
|
||||
UI_State :: struct {
|
||||
// TODO(Ed) : Use these?
|
||||
@ -267,7 +274,20 @@ ui_graph_build_end :: proc( ui : ^UI_State )
|
||||
for current := ui.root.first; current != nil;
|
||||
current = ui_box_tranverse_next_depth_first( current, bypass_intersection_test = true, ctx = ui )
|
||||
{
|
||||
if ! current.computed.fresh {
|
||||
if ! current.computed.fresh
|
||||
{
|
||||
if len(current.text.str) > 0 {
|
||||
app_window := get_state().app_window
|
||||
screen_extent := app_window.extent
|
||||
screen_size := screen_extent * 2
|
||||
screen_size_norm := 1 / screen_size
|
||||
|
||||
font_size_screen_scalar := app_config().font_size_screen_scalar
|
||||
|
||||
// over_sample : f32 = f32(get_state().config.font_size_canvas_scalar)
|
||||
|
||||
current.computed.text_shape = shape_text_cached( current.text.str, current.style.font, current.layout.font_size, 1.0 )
|
||||
}
|
||||
ui_box_compute_layout( current )
|
||||
}
|
||||
|
||||
@ -288,10 +308,11 @@ ui_graph_build_end :: proc( ui : ^UI_State )
|
||||
array_append(& ui.render_list_box, entry_box)
|
||||
|
||||
// TODO(Ed): It may be better to let VEFontCache handle processing empty strings.
|
||||
if len(current.text.str) > 0
|
||||
{
|
||||
// if len(current.text.str) > 0
|
||||
// {
|
||||
entry_text := UI_RenderTextInfo {
|
||||
text = current.text.str,
|
||||
shape = current.computed.text_shape,
|
||||
position = current.computed.text_pos,
|
||||
color = current.style.text_color,
|
||||
font = current.style.font,
|
||||
@ -299,7 +320,7 @@ ui_graph_build_end :: proc( ui : ^UI_State )
|
||||
}
|
||||
entry_text.layer_signal = different_ancestory
|
||||
array_append(& ui.render_list_text, entry_text)
|
||||
}
|
||||
// }
|
||||
|
||||
previous_layer = current.ancestors
|
||||
}
|
||||
|
@ -46,21 +46,6 @@ UI_Align_Presets :: UI_Align_Presets_Struct {
|
||||
text_centered = {0.5, 0.5},
|
||||
}
|
||||
|
||||
|
||||
// The UI_Box's actual positioning and sizing
|
||||
// There is an excess of rectangles here for debug puproses.
|
||||
UI_Computed :: struct {
|
||||
// anchors : Range2, // Bounds for anchors within parent
|
||||
// margins : Range2, // Bounds for margins within parent
|
||||
padding : Range2, // Bounds for padding's starting bounds (will be offset by border if there is one), only here for debug vis
|
||||
|
||||
bounds : Range2, // Bounds for box itself
|
||||
content : Range2, // Bounds for content (text or children)
|
||||
text_pos : Vec2, // Position of text within content
|
||||
text_size : Vec2, // Size of text within content
|
||||
fresh : b32, // If the auto-layout has been computed for the current frame
|
||||
}
|
||||
|
||||
UI_LayoutDirection_XY :: enum(i32) {
|
||||
Left_To_Right,
|
||||
Right_to_Left,
|
||||
|
@ -5,7 +5,7 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
|
||||
ancestors_layout_required : b32 = false,
|
||||
root_layout_required : b32 = false )
|
||||
{
|
||||
// profile("Layout Box")
|
||||
profile("Layout Box")
|
||||
state := get_state()
|
||||
ui := state.ui_context
|
||||
using box
|
||||
@ -73,12 +73,15 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
|
||||
text_size : Vec2
|
||||
if len(box.text.str) > 0
|
||||
{
|
||||
if layout.font_size == computed.text_size.y {
|
||||
text_size = computed.text_size
|
||||
}
|
||||
else {
|
||||
text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 )
|
||||
}
|
||||
|
||||
|
||||
text_size = computed.text_shape.size
|
||||
// if layout.font_size == computed.text_size.y {
|
||||
// text_size = computed.text_size
|
||||
// }
|
||||
// else {
|
||||
// text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 )
|
||||
// }
|
||||
}
|
||||
|
||||
if size_to_text {
|
||||
|
@ -201,9 +201,9 @@ push-location $path_root
|
||||
# $build_args += $flag_micro_architecture_native
|
||||
$build_args += $flag_use_separate_modules
|
||||
$build_args += $flag_thread_count + $CoreCount_Physical
|
||||
$build_args += $flag_optimize_none
|
||||
# $build_args += $flag_optimize_none
|
||||
# $build_args += $flag_optimize_minimal
|
||||
# $build_args += $flag_optimize_speed
|
||||
$build_args += $flag_optimize_speed
|
||||
# $build_args += $falg_optimize_aggressive
|
||||
$build_args += $flag_debug
|
||||
$build_args += $flag_pdb_name + $pdb
|
||||
|
Loading…
Reference in New Issue
Block a user