WIP - VEFontCache: Major changes

* Add back proper batching (busted the rendering for it though..)
* Some reogonzation of definitions and procedure args
* CURRENTLY BROKEN: Something went wrong with the calculations for text positioning..
This commit is contained in:
Edward R. Gonzalez 2025-01-03 23:06:31 -05:00
parent 078d9c8447
commit cb6053395c
13 changed files with 390 additions and 290 deletions

View File

@ -22,6 +22,7 @@ Note: freetype and harfbuzz could technically be gutted if the user removes thei
* Macro defines have been coverted (mostly) to runtime parameters
* Support for hot_reloading
* Curve quality step interpolation for glyph rendering can be set on a per font basis.
* All codepaths heavily changed (its faster)
## TODOs

View File

@ -89,7 +89,13 @@ Frame_Buffer_Pass :: enum u32 {
Target_Uncached = 4,
}
Glyph_Draw_Buffer :: struct {
Glyph_Batch_Cache :: struct {
table : map[u32]b8,
num : i32,
cap : i32,
}
Glyph_Draw_Buffer :: struct{
over_sample : Vec2,
batch : i32,
width : i32,
@ -100,6 +106,10 @@ Glyph_Draw_Buffer :: struct {
clear_draw_list : Draw_List,
draw_list : Draw_List,
// TODO(Ed): Get this working properly again.
batch_cache : Glyph_Batch_Cache,
shape_gen_scratch : [dynamic]Vertex,
glyph_pack : #soa[dynamic]Glyph_Pack_Entry,
oversized : [dynamic]i32,
to_cache : [dynamic]i32,
@ -108,9 +118,7 @@ Glyph_Draw_Buffer :: struct {
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} )
{
profile(#procedure)
// logf("Blitting: xy0: %0.2f, %0.2f xy1: %0.2f, %0.2f uv0: %0.2f, %0.2f uv1: %0.2f, %0.2f",
// p0.x, p0.y, p1.x, p1.y, uv0.x, uv0.y, uv1.x, uv1.y);
// profile(#procedure)
v_offset := cast(u32) len(draw_list.vertices)
quadv : [4]Vertex = {
@ -142,12 +150,14 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1
}
// 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 }
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)
// profile(#procedure)
v_offset := cast(u32) len(draw_list.vertices)
for point in path {
point := point
@ -184,7 +194,6 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V
)
{
profile(#procedure)
outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33}
draw := Draw_Call_Default
@ -266,7 +275,6 @@ cache_glyph_to_atlas :: #force_no_inline proc (
)
{
profile(#procedure)
batch_x := cast(f32) glyph_buf_Batch_x ^
buffer_padding_scaled := glyph_padding * over_sample
buffer_bounds_scale := (bounds_size_scaled) * over_sample
@ -332,36 +340,41 @@ cache_glyph_to_atlas :: #force_no_inline proc (
generate_glyph_pass_draw_list( draw_list, temp_path, glyph_shape, curve_quality, bounds, glyph_transform.scale, glyph_transform.pos )
}
generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
entry : Entry,
shaped : Shaped_Text,
position, target_scale : Vec2,
snap_width, snap_height : f32
generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape : Shaped_Text,
atlas : ^Atlas,
glyph_buffer : ^Glyph_Draw_Buffer,
colour : Colour,
entry : Entry,
font_scale : f32,
target_position : Vec2,
target_scale : Vec2,
snap_width : f32,
snap_height : f32
) -> (cursor_pos : Vec2) #no_bounds_check
{
profile(#procedure)
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_scalar
mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : u32 ) {
cache.table[lru_code] = true
cache.num += 1
}
reset_batch :: #force_inline proc( cache : ^Glyph_Batch_Cache ) {
clear_map( & cache.table )
cache.num = 0
}
atlas := & ctx.atlas
glyph_buffer := & ctx.glyph_buffer
draw_list := & ctx.draw_list
atlas_glyph_pad := atlas.glyph_padding
atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) }
glyph_buffer_size := Vec2 { f32(glyph_buffer.width), f32(glyph_buffer.height) }
profile_begin("soa prep")
// Make sure the packs are large enough for the shape
glyph_pack := & glyph_buffer.glyph_pack
oversized := & glyph_buffer.oversized
to_cache := & glyph_buffer.to_cache
cached := & glyph_buffer.cached
non_zero_resize_soa(glyph_pack, len(shaped.glyphs))
clear(oversized)
clear(to_cache)
clear(cached)
profile_end()
non_zero_resize_soa(glyph_pack, len(shape.glyphs))
append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 )
{
@ -374,7 +387,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
profile_begin("index")
for & glyph, index in glyph_pack
{
glyph.index = shaped.glyphs[ index ]
glyph.index = shape.glyphs[ index ]
glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index)
}
profile_end()
@ -382,7 +395,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
profile_begin("translate")
for & glyph, index in glyph_pack
{
glyph.position = position + (shaped.positions[index]) * target_scale
glyph.position = target_position + (shape.positions[index]) * target_scale
}
profile_end()
@ -390,9 +403,9 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
for & glyph, index in glyph_pack
{
glyph.bounds = parser_get_bounds( entry.parser_info, glyph.index )
glyph.bounds_scaled = { glyph.bounds.p0 * entry.size_scale, glyph.bounds.p1 * entry.size_scale }
glyph.bounds_scaled = { glyph.bounds.p0 * font_scale, glyph.bounds.p1 * font_scale }
glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0
glyph.bounds_size_scaled = glyph.bounds_size * entry.size_scale
glyph.bounds_size_scaled = glyph.bounds_size * font_scale
glyph.scale = glyph.bounds_size_scaled + atlas.glyph_padding
}
profile_end()
@ -406,7 +419,11 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
}
profile_end()
profile_begin("atlas slot resolution & to_cache/cached segregation")
profile_begin("batching")
clear(oversized)
clear(to_cache)
clear(cached)
for & glyph, index in glyph_pack
{
if glyph.region_kind == .None {
@ -428,43 +445,106 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
region := atlas.regions[glyph.region_kind]
glyph.atlas_index = lru_get( & region.state, glyph.lru_code )
to_cache_check:
// Glyphs are prepared in batches based on the capacity of the batch cache.
Prepare_For_Batch:
{
if ctx.temp_codepoint_seen_num <= i32(cap(ctx.temp_codepoint_seen))
{
if glyph.atlas_index == - 1
{
// Check to see if we reached capacity for the atlas
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 )
found, success := ctx.temp_codepoint_seen[next_evict_codepoint]
assert(success != false)
if (found) {
break to_cache_check
}
}
// Determine if we hit the limit for this batch.
if glyph_buffer.batch_cache.num >= glyph_buffer.batch_cache.cap do break Prepare_For_Batch
profile("append to_cache")
glyph.atlas_index = atlas_reserve_slot(region, glyph.lru_code)
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
append_sub_pack(to_cache, cast(i32) index)
mark_batch_codepoint_seen(ctx, glyph.lru_code)
continue
if glyph.atlas_index == - 1
{
// Check to see if we reached capacity for the atlas
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_glyph := lru_get_next_evicted( region.state )
found_take_slow_path, success := glyph_buffer.batch_cache.table[next_evict_glyph]
assert(success != false)
if (found_take_slow_path) {
break Prepare_For_Batch
}
}
profile("append to_cache")
glyph.atlas_index = atlas_reserve_slot(region, glyph.lru_code)
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
append_sub_pack(to_cache, cast(i32) index)
mark_glyph_seen(& glyph_buffer.batch_cache, glyph.lru_code)
continue
}
profile("append cached")
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
append_sub_pack(cached, cast(i32) index)
mark_glyph_seen(& glyph_buffer.batch_cache, glyph.lru_code)
continue
}
profile("append cached")
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
append_sub_pack(cached, cast(i32) index)
mark_batch_codepoint_seen(ctx, glyph.lru_code)
// Batch has been prepared for a set of glyphs time to generate glyphs.
batch_generate_glyphs_draw_list( draw_list, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
atlas,
glyph_buffer,
atlas_size,
glyph_buffer_size,
entry,
colour,
font_scale,
target_scale
)
reset_batch( & glyph_buffer.batch_cache)
clear(oversized)
clear(to_cache)
clear(cached)
}
profile_end()
// Last batch pass
batch_generate_glyphs_draw_list( draw_list, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
atlas,
glyph_buffer,
atlas_size,
glyph_buffer_size,
entry,
colour,
font_scale,
target_scale
)
reset_batch( & glyph_buffer.batch_cache)
clear(oversized)
clear(to_cache)
clear(cached)
cursor_pos = target_position + shape.end_cursor_pos * target_scale
return
}
batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
glyph_pack : ^#soa[dynamic]Glyph_Pack_Entry,
cached : []i32,
to_cache : []i32,
oversized : []i32,
atlas : ^Atlas,
glyph_buffer : ^Glyph_Draw_Buffer,
atlas_size : Vec2,
glyph_buffer_size : Vec2,
entry : Entry,
colour : Colour,
font_scale : Vec2,
target_scale : Vec2,
)
{
profile(#procedure)
when ENABLE_DRAW_TYPE_VIS {
colour := colour
}
profile_begin("transform & quad compute")
for id, index in sub_slice(cached)
for id, index in cached
{
// Quad to for drawing atlas slot to target
glyph := & glyph_pack[id]
@ -475,7 +555,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
quad.src_pos = (glyph.region_pos)
to_target_space( & quad.src_pos, & quad.src_scale, atlas_size )
}
for id, index in sub_slice(to_cache)
for id, index in to_cache
{
glyph := & glyph_pack[id]
@ -489,17 +569,17 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
// The glyph buffer space transform for generate_glyph_pass_draw_list
transform := & glyph.draw_transform
transform.scale = entry.size_scale * glyph_buffer.over_sample
transform.scale = font_scale * glyph_buffer.over_sample
transform.pos = -1 * (glyph.bounds.p0) * transform.scale + atlas.glyph_padding
// Unlike with oversized, this cannot be finished here as its final value is dependent on glyph_buffer.batch_x allocation
}
for id, index in sub_slice(oversized)
for id, index in oversized
{
glyph := & glyph_pack[id]
// The glyph buffer space transform for generate_glyph_pass_draw_list
transform := & glyph.draw_transform
transform.scale = entry.size_scale * glyph.over_sample
transform.scale = font_scale * glyph.over_sample
transform.pos = -1 * glyph.bounds.p0 * transform.scale + vec2(atlas.glyph_padding)
to_glyph_buffer_space( & transform.pos, & transform.scale, glyph_buffer_size )
// Oversized will use a cleared glyph_buffer every time.
@ -516,8 +596,6 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
}
profile_end()
profile_end()
generate_cached_draw_list :: #force_inline proc (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : Colour )
{
profile(#procedure)
@ -540,13 +618,12 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
}
profile_begin("to_cache: caching to atlas")
for id, index in sub_slice(to_cache) {
for id, index in to_cache {
error : Allocator_Error
glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index)
assert(error == .None)
}
for id, index in sub_slice(to_cache)
for id, index in to_cache
{
profile("glyph")
when ENABLE_DRAW_TYPE_VIS {
@ -561,8 +638,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
& glyph_buffer.draw_list,
& glyph_buffer.clear_draw_list,
& glyph_buffer.batch_x,
& ctx.temp_path,
& glyph_buffer.shape_gen_scratch,
glyph.shape,
glyph.bounds_scaled,
@ -580,37 +656,33 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
)
}
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x)
for id, index in sub_slice(to_cache) do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
for id, index in to_cache do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
profile_end()
generate_cached_draw_list( draw_list, glyph_pack[:], sub_slice(to_cache), ctx.colour )
generate_cached_draw_list( draw_list, glyph_pack[:], to_cache, colour )
profile_begin("generate_cached_draw_list: to_cache")
when ENABLE_DRAW_TYPE_VIS {
colour.r = 1.0
colour.g = 1.0
colour.b = 1.0
}
generate_cached_draw_list( draw_list, glyph_pack[:], sub_slice(cached), ctx.colour )
reset_batch_codepoint_state( ctx )
generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour )
profile_end()
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x)
profile_begin("generate oversized glyphs draw_list")
for id, index in sub_slice(oversized) {
for id, index in oversized {
error : Allocator_Error
glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index)
assert(error == .None)
}
for id, index in sub_slice(oversized)
for id, index in oversized
{
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.batch_x)
generate_glyph_pass_draw_list( draw_list, & ctx.temp_path,
generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch,
glyph_pack[id].shape,
entry.curve_quality,
glyph_pack[id].bounds,
@ -631,7 +703,7 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
{
using draw_to_target
pass = .Target_Uncached
colour = ctx.colour
colour = colour
start_index = u32(len(draw_list.indices))
blit_quad( draw_list,
@ -650,13 +722,8 @@ generate_shape_draw_list :: #force_no_inline proc( ctx : ^Context,
}
append( & draw_list.calls, ..calls[:] )
}
profile_begin("font parser shape cleanup")
for id, index in sub_slice(oversized) do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
for id, index in oversized do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
profile_end()
cursor_pos = position + shaped.end_cursor_pos * target_scale
return
}
// Flush the content of the glyph_buffers draw lists to the main draw list

View File

@ -134,4 +134,3 @@ profile_end :: #force_inline proc "contextless" () {
}
//#endregion("Proc overload mappings")

View File

@ -6,14 +6,34 @@ import "core:math"
import core_log "core:log"
reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) {
raw := transmute( ^runtime.Raw_Dynamic_Array) self
raw.allocator = allocator
}
reload_array_soa :: #force_inline proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) {
raw := runtime.raw_soa_footer(self)
raw.allocator = allocator
}
reload_map :: #force_inline proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
raw := transmute( ^runtime.Raw_Map) self
raw.allocator = allocator
}
font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u32) {
lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
return
}
djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) }
djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) }
Colour :: [4]f32
Vec2 :: [2]f32
Vec2i :: [2]i32
Vec2_64 :: [2]f64
djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) }
djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) }
vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }}
vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }}
vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }}
@ -42,36 +62,6 @@ logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc :=
core_log.logf( level, fmt, ..args, location = loc )
}
reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) {
raw := transmute( ^runtime.Raw_Dynamic_Array) self
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
}
font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u32) {
lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
return
}
mark_batch_codepoint_seen :: #force_inline proc "contextless" ( ctx : ^Context, lru_code : u32 ) {
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
}
to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
{
pos := position^

View File

@ -274,8 +274,7 @@ parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_in
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32
{
// switch font.kind
// switch font.kind
// {
// case .Freetype:
// error := freetype.load_glyph( font.freetype_info, cast(u32) glyph_index, { .No_Bitmap, .No_Hinting, .No_Scale } )

View File

@ -23,7 +23,7 @@ Shaped_Text_Cache :: struct {
next_cache_id : i32,
}
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text )
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
Shaper_Kind :: enum {
Naive = 0,
@ -56,7 +56,7 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context )
}
}
shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info)
shaper_load_font :: #force_inline proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info)
{
using info
blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil )
@ -65,7 +65,7 @@ shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte,
return
}
shaper_unload_font :: proc( ctx : ^Shaper_Info )
shaper_unload_font :: #force_inline proc( ctx : ^Shaper_Info )
{
using ctx
if blob != nil do harfbuzz.font_destroy( font )
@ -73,28 +73,37 @@ 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,
ascent, descent, line_gap : i32, size, size_scale : f32 )
shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text )
{
profile(#procedure)
current_script := harfbuzz.Script.UNKNOWN
hb_ucfunc := harfbuzz.unicode_funcs_get_default()
harfbuzz.buffer_clear_contents( ctx.hb_buffer )
assert( info.font != nil )
ascent := f32(ascent)
descent := f32(descent)
line_gap := f32(line_gap)
ascent := entry.ascent
descent := entry.descent
line_gap :=entry.line_gap
max_line_width := f32(0)
line_count := 1
line_height := ((ascent - descent + line_gap) * size_scale)
line_height := ((ascent - descent + line_gap) * font_scale)
position : Vec2
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 )
shape_run :: proc( output : ^Shaped_Text,
entry : Entry,
buffer : harfbuzz.Buffer,
script : harfbuzz.Script,
position : ^Vec2,
max_line_width : ^f32,
line_count : ^int,
font_px_size : f32,
font_scale : f32,
snap_shape_pos : b32,
adv_snap_small_font_threshold : f32
)
{
profile(#procedure)
// Set script and direction. We use the system's default langauge.
@ -105,14 +114,14 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info
// Perform the actual shaping of this run using HarfBuzz.
harfbuzz.buffer_set_content_type( buffer, harfbuzz.Buffer_Content_Type.UNICODE )
harfbuzz.shape( font, buffer, nil, 0 )
harfbuzz.shape( entry.shaper_info.font, buffer, nil, 0 )
// Loop over glyphs and append to output buffer.
glyph_count : u32
glyph_infos := harfbuzz.buffer_get_glyph_infos( buffer, & glyph_count )
glyph_positions := harfbuzz.buffer_get_glyph_positions( buffer, & glyph_count )
line_height := (ascent - descent + line_gap) * size_scale
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
for index : i32; index < i32(glyph_count); index += 1
{
@ -129,13 +138,13 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info
(line_count^) += 1
continue
}
if abs( size ) <= adv_snap_small_font_threshold
if abs( font_px_size ) <= adv_snap_small_font_threshold
{
(position^) = ceil( position^ )
}
glyph_pos := position^
offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * size_scale
offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * font_scale
glyph_pos += offset
if snap_shape_pos {
@ -143,13 +152,13 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info
}
advance := Vec2 {
f32(hb_gposition.x_advance) * size_scale,
f32(hb_gposition.y_advance) * size_scale
f32(hb_gposition.x_advance) * font_scale,
f32(hb_gposition.y_advance) * font_scale
}
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
is_empty := parser_is_glyph_empty(parser_info, glyph_id)
is_empty := parser_is_glyph_empty(entry.parser_info, glyph_id)
if ! is_empty {
append( & output.glyphs, glyph_id )
append( & output.positions, glyph_pos)
@ -181,61 +190,62 @@ shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, parser_info
}
// End current run since we've encountered a script change.
shape_run( parser_info,
ctx.hb_buffer, current_script, info.font, output,
& position, & max_line_width, & line_count,
ascent, descent, line_gap, size, size_scale,
ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold
)
shape_run( output,
entry,
ctx.hb_buffer,
current_script,
& position,
& max_line_width,
& line_count,
font_px_Size,
font_scale,
ctx.snap_glyph_position,
ctx.adv_snap_small_font_threshold
)
harfbuzz.buffer_add( ctx.hb_buffer, hb_codepoint, codepoint == '\n' ? 1 : 0 )
current_script = script
}
// End the last run if needed
shape_run( parser_info,
ctx.hb_buffer, current_script, info.font, output,
& position, & max_line_width, & line_count,
ascent, descent, line_gap, size, size_scale,
ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold
)
shape_run( output,
entry,
ctx.hb_buffer,
current_script,
& position,
& max_line_width,
& line_count,
font_px_Size,
font_scale,
ctx.snap_glyph_position,
ctx.adv_snap_small_font_threshold
)
// Set the final size
output.size.x = max_line_width
output.size.y = f32(line_count) * line_height
return
}
shaper_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 : ^Shaper_Context, entry : Entry, font_px_size : f32, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
clear( & output.glyphs )
clear( & output.positions )
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_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output )
}
shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, output : ^Shaped_Text )
shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
clear( & output.glyphs )
clear( & output.positions )
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
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
line_count : int = 1
max_line_width : f32 = 0
@ -246,7 +256,7 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_
{
if prev_codepoint > 0 {
kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint )
position.x += f32(kern) * entry.size_scale
position.x += f32(kern) * font_scale
}
if codepoint == '\n'
{
@ -258,12 +268,12 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_
prev_codepoint = rune(0)
continue
}
if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold {
if abs( font_px_Size ) <= ctx.adv_snap_small_font_threshold {
position.x = ceil(position.x)
}
glyph_index := parser_find_glyph_index( entry.parser_info, codepoint )
is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index )
glyph_index := parser_find_glyph_index( entry.parser_info, codepoint )
is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index )
if ! is_glyph_empty
{
append( & output.glyphs, glyph_index)
@ -274,7 +284,7 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_
}
advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint )
position.x += f32(advance) * entry.size_scale
position.x += f32(advance) * font_scale
prev_codepoint = codepoint
}
@ -285,7 +295,15 @@ shaper_shape_from_text_latin :: #force_inline proc( ctx : ^Context, font : Font_
output.size.y = f32(line_count) * line_height
}
shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : Entry, shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc ) -> (shaped_text : Shaped_Text)
shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
ctx : ^Shaper_Context,
shape_cache : ^Shaped_Text_Cache,
font : Font_ID,
entry : Entry,
font_px_size : f32,
font_scale : f32,
shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc
) -> (shaped_text : Shaped_Text)
{
profile(#procedure)
font := font
@ -296,8 +314,7 @@ shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID,
shape_lru_code( & lru_code, font_bytes )
shape_lru_code( & lru_code, text_bytes )
shape_cache := & ctx.shape_cache
state := & ctx.shape_cache.state
state := & shape_cache.state
shape_cache_idx := lru_get( state, lru_code )
if shape_cache_idx == -1
@ -319,7 +336,7 @@ shaper_shape_text_cached :: #force_inline proc( ctx : ^Context, font : Font_ID,
}
storage_entry := & shape_cache.storage[ shape_cache_idx ]
shape_text_uncached( ctx, font, text_utf8, entry, storage_entry )
shape_text_uncached( ctx, entry, font_px_size, font_scale, text_utf8, storage_entry )
shaped_text = storage_entry ^
return

View File

@ -8,9 +8,9 @@ package vefontcache
import "base:runtime"
// White: Cached Hit, Red: Cache Miss, Yellow: Oversized
ENABLE_DRAW_TYPE_VIS :: false
ENABLE_DRAW_TYPE_VIS :: true
// See: mappings.odin for profiling hookup
DISABLE_PROFILING :: true
DISABLE_PROFILING :: false
Font_ID :: distinct i32
Glyph :: distinct i32
@ -21,53 +21,52 @@ Entry :: struct {
id : Font_ID,
used : b32,
curve_quality : f32,
size : f32,
size_scale : f32,
ascent : f32,
descent : f32,
line_gap : f32,
}
Entry_Default :: Entry {
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,
curve_quality = 3,
}
Context :: struct {
backing : Allocator,
parser_ctx : Parser_Context,
shaper_ctx : Shaper_Context,
parser_ctx : Parser_Context, // Glyph parser state
shaper_ctx : Shaper_Context, // Text shaper state
// The managed font instances
entries : [dynamic]Entry,
temp_path : [dynamic]Vertex,
temp_codepoint_seen : map[u32]b8,
temp_codepoint_seen_num : i32,
snap_width : f32,
snap_height : f32,
alpha_scalar : f32, // Will apply a multiplier to the colour's alpha which provides some sharpening of the edges.
colour : Colour,
cursor_pos : Vec2,
glyph_buffer : Glyph_Draw_Buffer,
atlas : Atlas,
shape_cache : Shaped_Text_Cache,
draw_list : Draw_List,
// Tracks the offsets for the current layer in a draw_list
draw_layer : struct {
vertices_offset : int,
indices_offset : int,
calls_offset : int,
},
draw_list : Draw_List,
atlas : Atlas,
glyph_buffer : Glyph_Draw_Buffer,
shape_cache : Shaped_Text_Cache,
// Helps with hinting
snap_width : f32,
snap_height : f32,
colour : Colour, // Color used in draw interface
cursor_pos : Vec2,
alpha_scalar : f32, // Will apply a multiplier to the colour's alpha which provides some sharpening of the edges.
// Used by draw interface to super-scale the text by
// upscaling px_size with px_scalar and then down-scaling
// the draw_list result by the same amount.
px_scalar : f32,
default_curve_quality : i32,
use_advanced_shaper : b32,
debug_print : b32,
debug_print_verbose : b32,
@ -115,25 +114,27 @@ Init_Atlas_Params_Default :: Init_Atlas_Params {
}
Init_Glyph_Draw_Params :: struct {
over_sample : Vec2,
buffer_batch : u32,
draw_padding : u32,
over_sample : Vec2,
draw_padding : u32,
shape_gen_scratch_reserve : u32,
buffer_batch : u32,
buffer_batch_glyph_limit : u32, // How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list
}
Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params {
over_sample = Vec2 { 4, 4 },
buffer_batch = 4,
draw_padding = Init_Atlas_Params_Default.glyph_padding,
over_sample = Vec2 { 4, 4 },
draw_padding = Init_Atlas_Params_Default.glyph_padding,
shape_gen_scratch_reserve = 10 * 1024,
buffer_batch = 4,
buffer_batch_glyph_limit = 512,
}
Init_Shaper_Params :: struct {
use_advanced_text_shaper : b32,
snap_glyph_position : b32,
adv_snap_small_font_threshold : u32,
}
Init_Shaper_Params_Default :: Init_Shaper_Params {
use_advanced_text_shaper = true,
snap_glyph_position = true,
adv_snap_small_font_threshold = 0,
}
@ -158,10 +159,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
shape_cache_params := Init_Shape_Cache_Params_Default,
shaper_params := Init_Shaper_Params_Default,
alpha_sharpen := 0.2,
// Curve quality to use for a font when unspecified,
// Affects step size for bezier curve passes in generate_glyph_pass_draw_list
default_curve_quality : u32 = 3,
entires_reserve : u32 = 256,
temp_path_reserve : u32 = 10 * 1024,
temp_codepoint_seen_reserve : u32 = 10 * 1024,
)
{
assert( ctx != nil, "Must provide a valid context" )
@ -172,7 +173,6 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
ctx.colour = { 1, 1, 1, 1 }
use_advanced_shaper = shaper_params.use_advanced_text_shaper
shaper_ctx.adv_snap_small_font_threshold = f32(shaper_params.adv_snap_small_font_threshold)
shaper_ctx.snap_glyph_position = shaper_params.snap_glyph_position
@ -185,12 +185,6 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
entries, error = make( [dynamic]Entry, len = 0, cap = entires_reserve )
assert(error == .None, "VEFontCache.init : Failed to allocate entries")
temp_path, error = make( [dynamic]Vertex, len = 0, cap = temp_path_reserve )
assert(error == .None, "VEFontCache.init : Failed to allocate temp_path")
temp_codepoint_seen, error = make( map[u32]b8, uint(temp_codepoint_seen_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate temp_path")
draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte )
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices")
@ -270,7 +264,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for draw_list" )
}
// Note(From original author): We can actually go over VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH batches due to smart packing!
Glyph_Buffer_Setup:
{
using glyph_buffer
over_sample = glyph_draw_params.over_sample
@ -297,6 +291,14 @@ 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" )
shape_gen_scratch, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch_glyph_limit )
assert(error == .None, "VEFontCache.init : Failed to allocate shape_gen_scratch")
batch_cache.cap = i32(glyph_draw_params.buffer_batch_glyph_limit)
batch_cache.num = 0
batch_cache.table, error = make( map[u32]b8, uint(glyph_draw_params.shape_gen_scratch_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate batch_cache")
glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = 1 * Kilobyte, allocator = context.temp_allocator )
oversized, error = make( [dynamic]i32, len = 0, cap = 1 * Kilobyte, allocator = context.temp_allocator )
to_cache, error = make( [dynamic]i32, len = 0, cap = 1 * Kilobyte, allocator = context.temp_allocator )
@ -315,8 +317,6 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator )
using ctx
reload_array( & entries, allocator )
reload_array( & temp_path, allocator )
reload_map( & ctx.temp_codepoint_seen, allocator )
reload_array( & draw_list.vertices, allocator)
reload_array( & draw_list.indices, allocator )
@ -349,10 +349,12 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator )
reload_array( & glyph_buffer.to_cache, allocator )
reload_array( & glyph_buffer.cached, allocator )
reload_array( & glyph_buffer.shape_gen_scratch, allocator )
reload_map( & glyph_buffer.batch_cache.table, allocator )
reload_array( & shape_cache.storage, allocator )
}
// ve_foncache_shutdown
shutdown :: proc( ctx : ^Context )
{
assert( ctx != nil )
@ -364,8 +366,6 @@ shutdown :: proc( ctx : ^Context )
}
delete( entries )
delete( temp_path )
delete( temp_codepoint_seen )
delete( draw_list.vertices )
delete( draw_list.indices )
@ -398,11 +398,13 @@ shutdown :: proc( ctx : ^Context )
delete( glyph_buffer.to_cache)
delete( glyph_buffer.cached)
delete( glyph_buffer.shape_gen_scratch )
delete( glyph_buffer.batch_cache.table )
shaper_shutdown( & shaper_ctx )
parser_shutdown( & parser_ctx )
}
// ve_fontcache_load
load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID)
{
profile(#procedure)
@ -426,22 +428,23 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32,
entry := & entries[ id ]
{
using entry
used = true
entry.used = true
profile_begin("calling loaders")
parser_info = parser_load_font( & parser_ctx, label, data )
shaper_info = shaper_load_font( & shaper_ctx, label, data )
entry.parser_info = parser_load_font( & parser_ctx, label, data )
entry.shaper_info = shaper_load_font( & shaper_ctx, label, data )
profile_end()
size = size_px
size_scale = parser_scale( parser_info, size )
ascent, descent, line_gap := parser_get_font_vertical_metrics(entry.parser_info)
entry.ascent = f32(ascent)
entry.descent = f32(descent)
entry.line_gap = f32(line_gap)
if glyph_curve_quality == 0 {
curve_quality = f32(ctx.default_curve_quality)
entry.curve_quality = f32(ctx.default_curve_quality)
}
else {
curve_quality = f32(glyph_curve_quality)
entry.curve_quality = f32(glyph_curve_quality)
}
}
entry.id = Font_ID(id)
@ -451,7 +454,6 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32,
return
}
// ve_fontcache_unload
unload_font :: proc( ctx : ^Context, font : Font_ID )
{
assert( ctx != nil )
@ -470,17 +472,17 @@ unload_font :: proc( ctx : ^Context, font : Font_ID )
//#region("drawing")
// ve_fontcache_configure_snap
configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) {
assert( ctx != nil )
ctx.snap_width = f32(snap_width)
ctx.snap_height = f32(snap_height)
}
get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos }
set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour }
get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos }
set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_scalar = scalar }
set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour }
draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32
draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string ) -> b32
{
profile(#procedure)
assert( ctx != nil )
@ -493,14 +495,23 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : str
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 ]
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_scalar
// TODO(Ed): Test this.
// px_size_scalar :: 2
// px_size := px_size * px_size_scalar
// scale := scale / px_size_scalar
entry := ctx.entries[ font ]
font_scale := parser_scale( entry.parser_info, px_size )
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_uncached_advanced )
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height )
shape := shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
ctx.cursor_pos = generate_shape_draw_list( ctx, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
return true
}
draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32
draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string ) -> b32
{
profile(#procedure)
assert( ctx != nil )
@ -509,14 +520,18 @@ draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, text_ut
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, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_scalar
entry := ctx.entries[ font ]
font_scale := parser_scale( entry.parser_info, px_size )
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_latin )
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, 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
// Resolve the shape and track it to reduce iteration overhead
draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text ) -> b32
{
profile(#procedure)
assert( ctx != nil )
@ -525,24 +540,31 @@ draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, shape : S
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, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_scalar
entry := ctx.entries[ font ]
font_scale := parser_scale( entry.parser_info, px_size )
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, 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
// Resolve the shape and track it to reduce iteration overhead
draw_text_shape_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text ) -> b32
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_scalar
entry := ctx.entries[ font ]
ctx.cursor_pos = generate_shape_draw_list( ctx, entry, shape, position, scale, ctx.snap_width, ctx.snap_height )
font_scale := parser_scale( entry.parser_info, px_size )
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height )
return true
}
// ve_fontcache_Draw_List
get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List {
assert( ctx != nil )
if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 )
@ -558,7 +580,6 @@ get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_retur
return
}
// ve_fontcache_flush_Draw_List
flush_draw_list :: #force_inline proc( ctx : ^Context ) {
assert( ctx != nil )
using ctx
@ -580,14 +601,15 @@ flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) {
//#region("metrics")
measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2)
measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> (measured : Vec2)
{
// profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
entry := ctx.entries[font]
shaped := shaper_shape_text_cached(ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
entry := ctx.entries[font]
font_scale := parser_scale( entry.parser_info, px_size )
shaped := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_uncached_advanced )
return shaped.size
}
@ -597,11 +619,11 @@ 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)
line_gap = (f32(line_gap_i32) * entry.size_scale)
ascent = entry.ascent
descent = entry.descent
line_gap = entry.line_gap
return
}
@ -609,20 +631,22 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID
//#region("shaping")
shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, allocator := context.allocator ) -> Shaped_Text
shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text
{
profile(#procedure)
assert( len(text_utf8) > 0 )
entry := ctx.entries[ font ]
return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_from_text_latin )
font_scale := parser_scale( entry.parser_info, px_size )
return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_latin )
}
shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> Shaped_Text
shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text
{
profile(#procedure)
assert( len(text_utf8) > 0 )
entry := ctx.entries[ font ]
return shaper_shape_text_cached( ctx, font, text_utf8, entry, shaper_shape_text_uncached_advanced )
font_scale := parser_scale( entry.parser_info, px_size )
return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_uncached_advanced )
}
// User handled shaped text. Will not be cached

View File

@ -1,5 +1,9 @@
package grime
djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) {
for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value)
}
djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) {
for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value)
}

View File

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

View File

@ -284,7 +284,7 @@ app_color_theme :: #force_inline proc "contextless" () -> AppColorTheme { retur
debug_data :: #force_inline proc "contextless" () -> DebugData { return get_state().debug }
get_frametime :: #force_inline proc "contextless" () -> FrameTime { return get_state().frametime }
get_default_font :: #force_inline proc "contextless" () -> FontID { return get_state().default_font }
get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) }
get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) }
get_ui_context_mut :: #force_inline proc "contextless" () -> ^UI_State { return get_state().ui_context }
set_ui_context :: #force_inline proc "contextless" ( ui : ^UI_State ) { get_state().ui_context = ui }

View File

@ -649,12 +649,10 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
text_enqueued = true
if cam != nil {
// 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 )
}
}
@ -914,7 +912,7 @@ draw_text_string_pos_norm :: #force_inline proc( text : string, id : FontID, fon
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, text, pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar) )
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar), text )
return
}
@ -942,7 +940,7 @@ draw_text_shape_pos_norm :: #force_inline proc( shape : ShapedText, id : FontID,
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) )
ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar), shape )
return
}
@ -960,12 +958,13 @@ draw_text_shape_pos_extent :: #force_inline proc( shape : ShapedText, id : FontI
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.
// state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state.
config := app_config()
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)
over_sample : f32 = f32(config.font_size_canvas_scalar)
zoom_adjust_size *= over_sample
pos_offset := (pos + cam_offset)
@ -973,11 +972,11 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo
normalized_pos := render_pos * screen_size_norm
ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size )
f32_resolved_size := f32(resolved_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 )
@ -988,8 +987,8 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo
text_scale /= over_sample
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, text, normalized_pos, text_scale )
ve.set_colour( & get_state().font_provider_ctx.ve_ctx, color_norm )
ve.draw_text( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), normalized_pos, text_scale, text )
}
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 )
@ -1008,11 +1007,11 @@ draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id
normalized_pos := render_pos * screen_size_norm
ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size )
f32_resolved_size := f32(resolved_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 )
@ -1024,7 +1023,7 @@ draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id
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 )
ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, f32_resolved_size, normalized_pos, text_scale, shape )
}
// TODO(Ed): Eventually the workspace will need a viewport for drawing text

View File

@ -135,7 +135,7 @@ font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_U
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 )
measured := ve.measure_text_size( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text )
return measured
}
@ -149,13 +149,13 @@ get_font_vertical_metrics :: #force_inline proc ( font : FontID, font_size := Fo
shape_text_cached_latin :: #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_latin( & get_state().font_provider_ctx.ve_ctx, ve_id, text )
shape := ve.shape_text_latin( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text )
return shape
}
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 )
shape := ve.shape_text_advanced( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text )
return shape
}

View File

@ -201,10 +201,10 @@ 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 += $falg_optimize_aggressive
$build_args += $falg_optimize_aggressive
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'