mirror of
https://github.com/Ed94/VEFontCache-Odin.git
synced 2025-08-04 22:22:43 -07:00
1365 lines
51 KiB
Odin
1365 lines
51 KiB
Odin
/*
|
|
See: https://github.com/Ed94/VEFontCache-Odin
|
|
*/
|
|
package vefontcache
|
|
|
|
// See: mappings.odin for profiling hookup
|
|
DISABLE_PROFILING :: true
|
|
ENABLE_OVERSIZED_GLYPHS :: true
|
|
// White: Cached Hit, Red: Cache Miss, Yellow: Oversized (Will override user's colors enabled)
|
|
ENABLE_DRAW_TYPE_VISUALIZATION :: false
|
|
|
|
Font_ID :: distinct i16
|
|
Glyph :: distinct i32
|
|
|
|
Load_Font_Error :: enum(i32) {
|
|
None,
|
|
Parser_Failed,
|
|
}
|
|
|
|
Entry :: struct {
|
|
parser_info : Parser_Font_Info,
|
|
shaper_info : Shaper_Info,
|
|
id : Font_ID,
|
|
used : b32,
|
|
curve_quality : f32,
|
|
|
|
// TODO(Ed): Move over settings related to (snapping) hinting, oversample, px_scalar, etc;
|
|
// to here as there is no need to restrict the user to have one option for all fonts.
|
|
|
|
ascent : f32,
|
|
descent : f32,
|
|
line_gap : f32,
|
|
}
|
|
|
|
Entry_Default :: Entry {
|
|
id = 0,
|
|
used = false,
|
|
curve_quality = 3,
|
|
}
|
|
|
|
// Ease of use encapsulation of common fields for a canvas space
|
|
VPZ_Transform :: struct {
|
|
view : Vec2,
|
|
position : Vec2,
|
|
zoom : f32,
|
|
}
|
|
|
|
Scope_Stack :: struct {
|
|
font : [dynamic]Font_ID,
|
|
font_size : [dynamic]f32,
|
|
colour : [dynamic]RGBAN,
|
|
view : [dynamic]Vec2,
|
|
position : [dynamic]Vec2,
|
|
scale : [dynamic]Vec2,
|
|
zoom : [dynamic]f32,
|
|
}
|
|
|
|
Context :: struct {
|
|
backing : Allocator,
|
|
|
|
parser_ctx : Parser_Context, // Glyph parser state
|
|
shaper_ctx : Shaper_Context, // Text shaper state
|
|
|
|
// The managed font instances
|
|
entries : [dynamic]Entry,
|
|
|
|
// TODO(Ed): Review these when preparing to handle lifting of working context to a thread context.
|
|
glyph_buffer : Glyph_Draw_Buffer, // -> draw.odin
|
|
atlas : Atlas, // -> atlas.odin
|
|
shape_cache : Shaped_Text_Cache, // -> shaper.doin
|
|
draw_list : Draw_List, // -> draw.odin
|
|
|
|
batch_shapes_buffer : [dynamic]Shaped_Text, // Used for the procs that batch a layer of text.
|
|
|
|
// Tracks the offsets for the current layer in a draw_list
|
|
draw_layer : struct {
|
|
vertices_offset : int,
|
|
indices_offset : int,
|
|
calls_offset : int,
|
|
},
|
|
// See: get_draw_list_layer & flush_draw_list_layer
|
|
|
|
// Note(Ed): Not really used anymore.
|
|
// debug_print : b32,
|
|
// debug_print_verbose : b32,
|
|
|
|
// Whether or not to snap positioning to the pixel of the view
|
|
// Helps with hinting
|
|
snap_to_view_extent : b32,
|
|
|
|
stack : Scope_Stack,
|
|
|
|
cursor_pos : Vec2,
|
|
// Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges.
|
|
// Has a boldening side-effect. If overblown will look smeared.
|
|
alpha_sharpen : f32,
|
|
// 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, // Improves hinting, positioning, etc. Can make zoomed out text too jagged.
|
|
zoom_px_interval : f32, // When using zoom, the size can be locked to to this interval (fixes text width jitter)
|
|
|
|
default_curve_quality : i32,
|
|
}
|
|
|
|
//#region("Init Params")
|
|
|
|
Init_Atlas_Params :: struct {
|
|
size_multiplier : u32, // How much to scale the the atlas size to. (Affects everything, the base is 4096 x 2048 and everything follows from there)
|
|
glyph_padding : u32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
|
|
}
|
|
|
|
Init_Atlas_Params_Default :: Init_Atlas_Params {
|
|
size_multiplier = 1,
|
|
glyph_padding = 1,
|
|
}
|
|
|
|
Init_Glyph_Draw_Params :: struct {
|
|
// During the draw list generation stage when blitting to atlas, the quad's width will be ceil()'d to the closest pixel.
|
|
// Generally its not recommend todo this and is disabled on most renderers, but on rare occasion some fonts benefit from this.
|
|
snap_glyph_width : b32,
|
|
// During the draw list generation stage when blitting to atlas, the quad's height be ceil()'d to the closest pixel.
|
|
snap_glyph_height : b32,
|
|
// Intended to be x16 (4x4) super-sampling from the glyph buffer to the atlas.
|
|
// Oversized glyphs don't use this and instead do 2x or 1x depending on how massive they are.
|
|
over_sample : u32,
|
|
// Best to just keep this the same as glyph_padding for the atlas..
|
|
draw_padding : u32,
|
|
shape_gen_scratch_reserve : u32,
|
|
// How many region.D glyphs can be drawn to the glyph render target buffer at once (worst case scenario)
|
|
buffer_glyph_limit : u32,
|
|
// How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list
|
|
batch_glyph_limit : u32,
|
|
}
|
|
|
|
Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params {
|
|
snap_glyph_width = false,
|
|
snap_glyph_height = true,
|
|
over_sample = 4,
|
|
draw_padding = Init_Atlas_Params_Default.glyph_padding,
|
|
shape_gen_scratch_reserve = 512,
|
|
buffer_glyph_limit = 16,
|
|
batch_glyph_limit = 256,
|
|
}
|
|
|
|
Init_Shaper_Params :: struct {
|
|
// Forces a glyph position to align to a pixel (make sure to use snap_to_view_extent with this or else it won't be preserveds)
|
|
snap_glyph_position : b32,
|
|
// Will use more signficant advance during shaping for fonts
|
|
// Note(Ed): Thinking of removing, doesn't look good often and its an extra condition in the hot-loop.
|
|
adv_snap_small_font_threshold : u32,
|
|
}
|
|
|
|
Init_Shaper_Params_Default :: Init_Shaper_Params {
|
|
snap_glyph_position = true,
|
|
adv_snap_small_font_threshold = 0,
|
|
}
|
|
|
|
Init_Shape_Cache_Params :: struct {
|
|
// Note(Ed): This should mostly just be given the worst-case capacity and reserve at the same time.
|
|
// If memory is a concern it can easily be 256 - 2k if not much text is going to be rendered often.
|
|
// Shapes should really not exceed 1024 glyphs..
|
|
capacity : u32,
|
|
reserve : u32,
|
|
}
|
|
|
|
Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params {
|
|
capacity = 10 * 1024,
|
|
reserve = 128,
|
|
}
|
|
|
|
//#endregion("Init Params")
|
|
|
|
//#region("lifetime")
|
|
|
|
// ve_fontcache_init
|
|
startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // Note(Ed): Only sbt_truetype supported for now.
|
|
allocator := context.allocator,
|
|
atlas_params := Init_Atlas_Params_Default,
|
|
glyph_draw_params := Init_Glyph_Draw_Params_Default,
|
|
shape_cache_params := Init_Shape_Cache_Params_Default,
|
|
shaper_params := Init_Shaper_Params_Default,
|
|
alpha_sharpen : f32 = 0.1,
|
|
px_scalar : f32 = 1.4,
|
|
zoom_px_interval : i32 = 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,
|
|
scope_stack_reserve : u32 = 32,
|
|
)
|
|
{
|
|
assert( ctx != nil, "Must provide a valid context" )
|
|
|
|
ctx.backing = allocator
|
|
context.allocator = ctx.backing
|
|
|
|
ctx.alpha_sharpen = alpha_sharpen
|
|
ctx.px_scalar = px_scalar
|
|
ctx.zoom_px_interval = f32(zoom_px_interval)
|
|
|
|
shaper_ctx := & ctx.shaper_ctx
|
|
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
|
|
|
|
ctx.default_curve_quality = default_curve_quality == 0 ? 3 : i32(default_curve_quality)
|
|
|
|
error : Allocator_Error
|
|
ctx.entries, error = make( [dynamic]Entry, len = 0, cap = entires_reserve )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate entries")
|
|
|
|
ctx.draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices")
|
|
|
|
ctx.draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 16 * Kilobyte )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.indices")
|
|
|
|
ctx.draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.calls")
|
|
|
|
atlas := & ctx.atlas
|
|
Atlas_Setup:
|
|
{
|
|
atlas.size_multiplier = f32(atlas_params.size_multiplier)
|
|
|
|
atlas_size := Vec2i { 4096, 2048 } * i32(atlas.size_multiplier)
|
|
slot_region_a := Vec2i { 32, 32 } * i32(atlas.size_multiplier)
|
|
slot_region_b := Vec2i { 32, 64 } * i32(atlas.size_multiplier)
|
|
slot_region_c := Vec2i { 64, 64 } * i32(atlas.size_multiplier)
|
|
slot_region_d := Vec2i { 128, 128 } * i32(atlas.size_multiplier)
|
|
|
|
init_atlas_region :: proc( region : ^Atlas_Region, atlas_size, slot_size : Vec2i, factor : Vec2i )
|
|
{
|
|
region.next_idx = 0;
|
|
region.slot_size = slot_size
|
|
region.size = atlas_size / factor
|
|
region.capacity = region.size / region.slot_size
|
|
|
|
error : Allocator_Error
|
|
lru_init( & region.state, region.capacity.x * region.capacity.y )
|
|
}
|
|
init_atlas_region( & atlas.region_a, atlas_size, slot_region_a, { 4, 2})
|
|
init_atlas_region( & atlas.region_b, atlas_size, slot_region_b, { 4, 2})
|
|
init_atlas_region( & atlas.region_c, atlas_size, slot_region_c, { 4, 1})
|
|
init_atlas_region( & atlas.region_d, atlas_size, slot_region_d, { 2, 1})
|
|
|
|
atlas.size = atlas_size
|
|
atlas.glyph_padding = f32(atlas_params.glyph_padding)
|
|
|
|
atlas.region_a.offset = {0, 0}
|
|
atlas.region_b.offset.x = 0
|
|
atlas.region_b.offset.y = atlas.region_a.size.y
|
|
atlas.region_c.offset.x = atlas.region_a.size.x
|
|
atlas.region_c.offset.y = 0
|
|
atlas.region_d.offset.x = atlas.size.x / 2
|
|
atlas.region_d.offset.y = 0
|
|
|
|
atlas.regions = {
|
|
nil,
|
|
& atlas.region_a,
|
|
& atlas.region_b,
|
|
& atlas.region_c,
|
|
& atlas.region_d,
|
|
}
|
|
}
|
|
|
|
Shape_Cache_Setup:
|
|
{
|
|
shape_cache := & ctx.shape_cache
|
|
lru_init( & shape_cache.state, i32(shape_cache_params.capacity) )
|
|
|
|
shape_cache.storage, error = make( [dynamic]Shaped_Text, shape_cache_params.capacity )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate shape_cache.storage")
|
|
|
|
for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1
|
|
{
|
|
storage_entry := & shape_cache.storage[idx]
|
|
|
|
storage_entry.glyph, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" )
|
|
|
|
storage_entry.position, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" )
|
|
|
|
storage_entry.visible, error = make( [dynamic]i32, len = 0, cap = shape_cache_params.reserve )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate visible array for shape cache storage" )
|
|
|
|
storage_entry.atlas_lru_code, error = make( [dynamic]Atlas_Key, len = 0, cap = shape_cache_params.reserve )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate atlas_lru_code array for shape cache storage" )
|
|
|
|
storage_entry.region_kind, error = make( [dynamic]Atlas_Region_Kind, len = 0, cap = shape_cache_params.reserve )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate region_kind array for shape cache storage" )
|
|
|
|
storage_entry.bounds, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate bounds array for shape cache storage" )
|
|
}
|
|
}
|
|
|
|
Glyph_Buffer_Setup:
|
|
{
|
|
glyph_buffer := & ctx.glyph_buffer
|
|
glyph_buffer.snap_glyph_width = cast(f32) i32(glyph_draw_params.snap_glyph_width)
|
|
glyph_buffer.snap_glyph_height = cast(f32) i32(glyph_draw_params.snap_glyph_height)
|
|
glyph_buffer.over_sample = { f32(glyph_draw_params.over_sample), f32(glyph_draw_params.over_sample) }
|
|
glyph_buffer.size.x = atlas.region_d.slot_size.x * i32(glyph_buffer.over_sample.x) * i32(glyph_draw_params.buffer_glyph_limit)
|
|
glyph_buffer.size.y = atlas.region_d.slot_size.y * i32(glyph_buffer.over_sample.y)
|
|
glyph_buffer.draw_padding = cast(f32) glyph_draw_params.draw_padding
|
|
|
|
glyph_buffer.draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for glyph_buffer.draw_list" )
|
|
|
|
glyph_buffer.draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 16 * Kilobyte )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate indices for glyph_buffer.draw_list" )
|
|
|
|
glyph_buffer.draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate calls for glyph_buffer.draw_list" )
|
|
|
|
glyph_buffer.clear_draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 2 * Kilobyte )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" )
|
|
|
|
glyph_buffer.clear_draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 4 * Kilobyte )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate calls for indices array for clear_draw_list" )
|
|
|
|
glyph_buffer.clear_draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte )
|
|
assert( error == .None, "VEFontCache.init : Failed to allocate calls for calls for clear_draw_list" )
|
|
|
|
glyph_buffer.shape_gen_scratch, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.shape_gen_scratch_reserve )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate shape_gen_scratch")
|
|
|
|
batch_cache := & glyph_buffer.batch_cache
|
|
batch_cache.cap = i32(glyph_draw_params.batch_glyph_limit)
|
|
batch_cache.num = 0
|
|
batch_cache.table, error = make( map[Atlas_Key]b8, uint(glyph_draw_params.batch_glyph_limit) )
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate batch_cache")
|
|
|
|
glyph_buffer.glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = uint(shape_cache_params.reserve) )
|
|
glyph_buffer.oversized, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) )
|
|
glyph_buffer.to_cache, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) )
|
|
glyph_buffer.cached, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) )
|
|
}
|
|
|
|
parser_init( & ctx.parser_ctx, parser_kind )
|
|
shaper_init( & ctx.shaper_ctx )
|
|
|
|
// Scoping Stack
|
|
{
|
|
stack := & ctx.stack
|
|
|
|
error : Allocator_Error
|
|
stack.font, error = make([dynamic]Font_ID, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.font")
|
|
|
|
stack.font_size, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.font_size")
|
|
|
|
stack.font_size, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.font_size")
|
|
|
|
stack.colour, error = make([dynamic]RGBAN, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.colour")
|
|
|
|
stack.view, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.view")
|
|
|
|
stack.position, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.position")
|
|
|
|
stack.scale, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.scale")
|
|
|
|
stack.zoom, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve)
|
|
assert(error == .None, "VEFontCache.init : Failed to allocate stack.zoom")
|
|
}
|
|
|
|
// Set the default stack values
|
|
// Will be popped on shutdown
|
|
push_colour(ctx, {1, 1, 1, 1})
|
|
push_font_size(ctx, 36)
|
|
push_view(ctx, { 0, 0 })
|
|
push_position(ctx, {0, 0})
|
|
push_scale(ctx, 1.0)
|
|
push_zoom(ctx, 1.0)
|
|
}
|
|
|
|
hot_reload :: proc( ctx : ^Context, allocator : Allocator )
|
|
{
|
|
assert( ctx != nil )
|
|
ctx.backing = allocator
|
|
context.allocator = ctx.backing
|
|
|
|
atlas := & ctx.atlas
|
|
glyph_buffer := & ctx.glyph_buffer
|
|
shape_cache := & ctx.shape_cache
|
|
draw_list := & ctx.draw_list
|
|
|
|
reload_array( & ctx.entries, allocator )
|
|
|
|
reload_array( & glyph_buffer.draw_list.calls, allocator )
|
|
reload_array( & glyph_buffer.draw_list.indices, allocator )
|
|
reload_array( & glyph_buffer.draw_list.vertices, allocator )
|
|
|
|
reload_array( & glyph_buffer.clear_draw_list.calls, allocator )
|
|
reload_array( & glyph_buffer.clear_draw_list.indices, allocator )
|
|
reload_array( & glyph_buffer.clear_draw_list.vertices, allocator )
|
|
|
|
reload_map( & glyph_buffer.batch_cache.table, allocator )
|
|
reload_array( & glyph_buffer.shape_gen_scratch, allocator )
|
|
|
|
reload_array_soa( & glyph_buffer.glyph_pack, allocator )
|
|
reload_array( & glyph_buffer.oversized, allocator )
|
|
reload_array( & glyph_buffer.to_cache, allocator )
|
|
reload_array( & glyph_buffer.cached, allocator )
|
|
|
|
lru_reload( & atlas.region_a.state, allocator)
|
|
lru_reload( & atlas.region_b.state, allocator)
|
|
lru_reload( & atlas.region_c.state, allocator)
|
|
lru_reload( & atlas.region_d.state, allocator)
|
|
|
|
lru_reload( & shape_cache.state, allocator )
|
|
for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 {
|
|
storage_entry := & shape_cache.storage[idx]
|
|
reload_array( & storage_entry.glyph, allocator)
|
|
reload_array( & storage_entry.position, allocator)
|
|
reload_array( & storage_entry.visible, allocator)
|
|
reload_array( & storage_entry.atlas_lru_code, allocator)
|
|
reload_array( & storage_entry.region_kind, allocator)
|
|
reload_array( & storage_entry.bounds, allocator)
|
|
}
|
|
reload_array( & shape_cache.storage, allocator )
|
|
|
|
reload_array( & draw_list.vertices, allocator)
|
|
reload_array( & draw_list.indices, allocator)
|
|
reload_array( & draw_list.calls, allocator)
|
|
|
|
parser_reload(& ctx.parser_ctx, allocator)
|
|
|
|
// Scope Stack
|
|
{
|
|
stack := & ctx.stack
|
|
reload_array(& stack.font, allocator)
|
|
reload_array(& stack.font_size, allocator)
|
|
reload_array(& stack.colour, allocator)
|
|
reload_array(& stack.view, allocator)
|
|
reload_array(& stack.position, allocator)
|
|
reload_array(& stack.scale, allocator)
|
|
reload_array(& stack.zoom, allocator)
|
|
}
|
|
}
|
|
|
|
shutdown :: proc( ctx : ^Context )
|
|
{
|
|
assert( ctx != nil )
|
|
context.allocator = ctx.backing
|
|
|
|
atlas := & ctx.atlas
|
|
glyph_buffer := & ctx.glyph_buffer
|
|
shape_cache := & ctx.shape_cache
|
|
draw_list := & ctx.draw_list
|
|
|
|
pop_colour(ctx)
|
|
pop_font_size(ctx)
|
|
pop_view(ctx)
|
|
pop_position(ctx)
|
|
pop_scale(ctx)
|
|
pop_zoom(ctx)
|
|
|
|
for & entry in ctx.entries {
|
|
unload_font( ctx, entry.id )
|
|
}
|
|
delete( ctx.entries )
|
|
|
|
delete( glyph_buffer.draw_list.vertices )
|
|
delete( glyph_buffer.draw_list.indices )
|
|
delete( glyph_buffer.draw_list.calls )
|
|
|
|
delete( glyph_buffer.clear_draw_list.vertices )
|
|
delete( glyph_buffer.clear_draw_list.indices )
|
|
delete( glyph_buffer.clear_draw_list.calls )
|
|
|
|
delete( glyph_buffer.batch_cache.table )
|
|
delete( glyph_buffer.shape_gen_scratch )
|
|
|
|
delete_soa( glyph_buffer.glyph_pack)
|
|
delete( glyph_buffer.oversized)
|
|
delete( glyph_buffer.to_cache)
|
|
delete( glyph_buffer.cached)
|
|
|
|
lru_free( & atlas.region_a.state )
|
|
lru_free( & atlas.region_b.state )
|
|
lru_free( & atlas.region_c.state )
|
|
lru_free( & atlas.region_d.state )
|
|
|
|
for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 {
|
|
storage_entry := & shape_cache.storage[idx]
|
|
delete( storage_entry.glyph )
|
|
delete( storage_entry.position )
|
|
delete( storage_entry.visible )
|
|
delete( storage_entry.atlas_lru_code)
|
|
delete( storage_entry.region_kind)
|
|
delete( storage_entry.bounds)
|
|
}
|
|
lru_free( & shape_cache.state )
|
|
|
|
delete( draw_list.vertices )
|
|
delete( draw_list.indices )
|
|
delete( draw_list.calls )
|
|
|
|
shaper_shutdown( & ctx.shaper_ctx )
|
|
parser_shutdown( & ctx.parser_ctx )
|
|
|
|
// Scope Stack
|
|
{
|
|
stack := & ctx.stack
|
|
delete(stack.font)
|
|
delete(stack.font_size)
|
|
delete(stack.colour)
|
|
delete(stack.view)
|
|
delete(stack.position)
|
|
delete(stack.scale)
|
|
delete(stack.zoom)
|
|
}
|
|
}
|
|
|
|
load_font :: proc( ctx : ^Context, label : string, data : []byte, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID, error : Load_Font_Error)
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
assert( len(data) > 0 )
|
|
context.allocator = ctx.backing
|
|
|
|
entries := & ctx.entries
|
|
|
|
id : Font_ID = -1
|
|
|
|
for index : i32 = 0; index < i32(len(entries)); index += 1 {
|
|
if entries[index].used do continue
|
|
id = Font_ID(index)
|
|
break
|
|
}
|
|
if id == -1 {
|
|
append_elem( entries, Entry {})
|
|
id = cast(Font_ID) len(entries) - 1
|
|
}
|
|
assert( id >= 0 && id < Font_ID(len(entries)) )
|
|
|
|
entry := & entries[ id ]
|
|
{
|
|
entry.used = true
|
|
|
|
profile_begin("calling loaders")
|
|
parser_error : b32
|
|
entry.parser_info, parser_error = parser_load_font( & ctx.parser_ctx, label, data )
|
|
if parser_error {
|
|
error = .Parser_Failed
|
|
return
|
|
}
|
|
entry.shaper_info = shaper_load_font( & ctx.shaper_ctx, label, data )
|
|
profile_end()
|
|
|
|
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 {
|
|
entry.curve_quality = f32(ctx.default_curve_quality)
|
|
}
|
|
else {
|
|
entry.curve_quality = f32(glyph_curve_quality)
|
|
}
|
|
}
|
|
entry.id = Font_ID(id)
|
|
ctx.entries[ id ].id = Font_ID(id)
|
|
|
|
font_id = Font_ID(id)
|
|
return
|
|
}
|
|
|
|
unload_font :: proc( ctx : ^Context, font : Font_ID )
|
|
{
|
|
assert( ctx != nil )
|
|
assert( font >= 0 && int(font) < len(ctx.entries) )
|
|
context.allocator = ctx.backing
|
|
|
|
entry := & ctx.entries[ font ]
|
|
entry.used = false
|
|
|
|
shaper_unload_font( & entry.shaper_info )
|
|
parser_unload_font( & entry.parser_info )
|
|
}
|
|
|
|
// Can be used with hot-reload
|
|
clear_atlas_region_caches :: proc(ctx : ^Context)
|
|
{
|
|
lru_clear(& ctx.atlas.region_a.state)
|
|
lru_clear(& ctx.atlas.region_b.state)
|
|
lru_clear(& ctx.atlas.region_c.state)
|
|
lru_clear(& ctx.atlas.region_d.state)
|
|
|
|
ctx.atlas.region_a.next_idx = 0
|
|
ctx.atlas.region_b.next_idx = 0
|
|
ctx.atlas.region_c.next_idx = 0
|
|
ctx.atlas.region_d.next_idx = 0
|
|
}
|
|
|
|
// Can be used with hot-reload
|
|
clear_shape_cache :: proc (ctx : ^Context)
|
|
{
|
|
lru_clear(& ctx.shape_cache.state)
|
|
for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 {
|
|
storage_entry := & ctx.shape_cache.storage[idx]
|
|
storage_entry.end_cursor_pos = {}
|
|
storage_entry.size = {}
|
|
clear(& storage_entry.glyph)
|
|
clear(& storage_entry.position)
|
|
}
|
|
ctx.shape_cache.next_cache_id = 0
|
|
}
|
|
|
|
//#endregion("lifetime")
|
|
|
|
//#region("shaping")
|
|
|
|
// For high performance, the user should track the shapes and use the draw list interface on shapes.
|
|
// Doing so avoids cache lookups.
|
|
|
|
shape_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string,
|
|
shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
|
|
) -> Shaped_Text
|
|
{
|
|
profile(#procedure)
|
|
assert( len(text_utf8) > 0 )
|
|
entry := ctx.entries[ font ]
|
|
|
|
target_px_size := px_size * ctx.px_scalar
|
|
target_font_scale := parser_scale( entry.parser_info, target_px_size )
|
|
|
|
return shaper_shape_text_cached( text_utf8,
|
|
& ctx.shaper_ctx,
|
|
& ctx.shape_cache,
|
|
ctx.atlas,
|
|
vec2(ctx.glyph_buffer.size),
|
|
font,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
shaper_proc
|
|
)
|
|
}
|
|
|
|
// User handled shaped text. Will not be cached
|
|
shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text,
|
|
shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
|
|
)
|
|
{
|
|
profile(#procedure)
|
|
assert( len(text_utf8) > 0 )
|
|
entry := ctx.entries[ font ]
|
|
|
|
target_px_size := px_size * ctx.px_scalar
|
|
target_font_scale := parser_scale( entry.parser_info, target_px_size )
|
|
|
|
shaper_proc(& ctx.shaper_ctx,
|
|
ctx.atlas,
|
|
vec2(ctx.glyph_buffer.size),
|
|
font,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
text_utf8,
|
|
shape
|
|
)
|
|
return
|
|
}
|
|
|
|
//#endregion("shaping")
|
|
|
|
//#region("draw_list generation")
|
|
|
|
/* The most basic interface-level draw shape procedure.
|
|
Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied.
|
|
view, position, and scale are expected to be in unsigned normalized space:
|
|
|
|
| +----------------------------------+ (1.0, 1.0)
|
|
| | |
|
|
| | |
|
|
| | Glyph Quad |
|
|
| | +--------+ < scale.y |
|
|
| | | ** | * | |
|
|
| | | * * | **** | |
|
|
| | | **** | * * | |
|
|
| | | * * | **** | |
|
|
| | +--------+--------+.... |
|
|
| | position ^ ^ scale.x |
|
|
| | |
|
|
| | |
|
|
| | |
|
|
| (0.0, 0.0) +----------------------------------+
|
|
|
|
• position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
|
|
<-> scale : Scale the glyph beyond its default scaling from its px_size.
|
|
*/
|
|
@(optimization_mode="favor_size")
|
|
draw_shape_normalized_space :: #force_inline proc( ctx : ^Context,
|
|
colour : RGBAN,
|
|
position : Vec2,
|
|
scale : Vec2,
|
|
shape : Shaped_Text
|
|
)
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
|
|
entry := ctx.entries[ shape.font ]
|
|
|
|
should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
|
|
adjusted_colour := colour
|
|
adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen
|
|
|
|
target_px_size := shape.px_size
|
|
target_scale := scale * (1 / ctx.px_scalar)
|
|
target_font_scale := parser_scale( entry.parser_info, shape.px_size )
|
|
|
|
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
|
|
ctx.px_scalar,
|
|
adjusted_colour,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
position,
|
|
target_scale,
|
|
)
|
|
}
|
|
|
|
/* Non-scoping context. The most basic interface-level draw shape procedure (everything else is quality of life warppers).
|
|
|
|
Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied.
|
|
view, position, and scale are expected to be in unsigned normalized space:
|
|
|
|
| +----------------------------------+ (1.0, 1.0)
|
|
| | |
|
|
| | |
|
|
| | Glyph Quad |
|
|
| | +---------+ < scale.y |
|
|
| | | ** | * | |
|
|
| | | * * | **** | |
|
|
| | | **** | * * | |
|
|
| | | * * | **** | |
|
|
| | +--------+--------+.... |
|
|
| | position ^ ^ scale.x |
|
|
| | |
|
|
| | |
|
|
| | |
|
|
| (0.0, 0.0) +----------------------------------+
|
|
|
|
• position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
|
|
<-> scale : Scale the glyph beyond its default scaling from its px_size.
|
|
*/
|
|
@(optimization_mode = "favor_size")
|
|
draw_text_normalized_space :: proc( ctx : ^Context,
|
|
font : Font_ID,
|
|
px_size : f32,
|
|
colour : RGBAN,
|
|
position : Vec2,
|
|
scale : Vec2,
|
|
text_utf8 : string,
|
|
shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
|
|
)
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
assert( font >= 0 && int(font) < len(ctx.entries) )
|
|
assert( len(text_utf8) > 0 )
|
|
|
|
ctx.cursor_pos = {}
|
|
entry := ctx.entries[ font ]
|
|
|
|
should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
|
|
adjusted_colour := colour
|
|
adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen
|
|
|
|
// Does nothing when px_scalar is 1.0
|
|
target_px_size := px_size * ctx.px_scalar
|
|
target_scale := scale * (1 / ctx.px_scalar)
|
|
target_font_scale := parser_scale( entry.parser_info, target_px_size )
|
|
|
|
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size),
|
|
font,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
shaper_proc
|
|
)
|
|
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
|
|
ctx.px_scalar,
|
|
adjusted_colour,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
position,
|
|
target_scale,
|
|
)
|
|
}
|
|
|
|
/* Equivalent to draw_text_shape_normalized_space, however the coordinate space is expected to be relative to the view.
|
|
view, position, and scale are expected to be in unsigned view space:
|
|
|
|
| +----------------------------------+ (view.x, view.y)
|
|
| | |
|
|
| | |
|
|
| | Glyph Quad |
|
|
| | +---------+ < scale.y |
|
|
| | | ** | * | |
|
|
| | | * * | **** | |
|
|
| | | **** | * * | |
|
|
| | | * * | **** | |
|
|
| | +--------+--------+.... |
|
|
| | position ^ ^ scale.x |
|
|
| | |
|
|
| | |
|
|
| | |
|
|
| (0.0, 0.0) +----------------------------------+
|
|
|
|
□ view : The coordinate space is scaled to the view. Positions will be snapped to it.
|
|
• position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
|
|
<-> scale : Scale the glyph beyond its default scaling from its px_size.
|
|
zoom : Will affect the scale similar to how the zoom on a canvas would behave.
|
|
*/
|
|
// @(optimization_mode="favor_size")
|
|
draw_shape_view_space :: proc( ctx : ^Context,
|
|
colour : RGBAN,
|
|
view : Vec2,
|
|
position : Vec2,
|
|
scale : Vec2,
|
|
zoom : f32,
|
|
shape : Shaped_Text
|
|
)
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
// TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape)
|
|
assert( ctx.px_scalar > 0.0 )
|
|
|
|
entry := ctx.entries[ shape.font ]
|
|
|
|
should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
|
|
adjusted_colour := colour
|
|
adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen
|
|
|
|
px_scalar_quotient := (1 / ctx.px_scalar)
|
|
px_size := shape.px_size * px_scalar_quotient
|
|
|
|
resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )
|
|
target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view )
|
|
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_scale := resolve_px_scalar_size(entry.parser_info, resolved_size, ctx.px_scalar, norm_scale)
|
|
|
|
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
|
|
ctx.px_scalar,
|
|
adjusted_colour,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_position,
|
|
target_scale,
|
|
)
|
|
}
|
|
|
|
/* Equivalent to draw_text_shape_normalized_space, however the coordinate space is expected to be relative to the view.
|
|
view, position, and scale are expected to be in unsigned view space:
|
|
|
|
| +----------------------------------+ (view.x, view.y)
|
|
| | |
|
|
| | |
|
|
| | Glyph Quad |
|
|
| | +---------+ < scale.y |
|
|
| | | ** | * | |
|
|
| | | * * | **** | |
|
|
| | | **** | * * | |
|
|
| | | * * | **** | |
|
|
| | +--------+--------+.... |
|
|
| | position ^ ^ scale.x |
|
|
| | |
|
|
| | |
|
|
| | |
|
|
| (0.0, 0.0) +----------------------------------+
|
|
|
|
□ view : The coordinate space is scaled to the view. Positions will be snapped to it.
|
|
• position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
|
|
<-> scale : Scale the glyph beyond its default scaling from its px_size.
|
|
zoom : Will affect the scale similar to how the zoom on a canvas would behave.
|
|
*/
|
|
// @(optimization_mode = "favor_size")
|
|
draw_text_view_space :: proc(ctx : ^Context,
|
|
font : Font_ID,
|
|
px_size : f32,
|
|
colour : RGBAN,
|
|
view : Vec2,
|
|
position : Vec2,
|
|
scale : Vec2,
|
|
zoom : f32,
|
|
text_utf8 : string,
|
|
shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
|
|
)
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
assert( font >= 0 && int(font) < len(ctx.entries) )
|
|
assert( len(text_utf8) > 0 )
|
|
assert( ctx.px_scalar > 0.0 )
|
|
|
|
ctx.cursor_pos = {}
|
|
entry := ctx.entries[ font ]
|
|
|
|
should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
|
|
adjusted_colour := colour
|
|
adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen
|
|
|
|
resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )
|
|
target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view )
|
|
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_scale := resolve_px_scalar_size(entry.parser_info, resolved_size, ctx.px_scalar, norm_scale)
|
|
|
|
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size),
|
|
font,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
shaper_proc
|
|
)
|
|
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
|
|
ctx.px_scalar,
|
|
adjusted_colour,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_position,
|
|
target_scale,
|
|
)
|
|
}
|
|
|
|
/* Uses the ctx.stack, position and scale are relative to the position and scale on the stack.
|
|
|
|
absolute_position := peek(stack.position) + position
|
|
absolute_scale := peek(stack.scale ) * scale
|
|
|
|
| +-----------------------------------+ (view.x, view.y)
|
|
| | |
|
|
| | |
|
|
| | Glyph Quad absolute |
|
|
| | +---------+ < scale.y |
|
|
| | | ** | * | |
|
|
| | | * * | **** | |
|
|
| | | **** | * * | |
|
|
| | | * * | **** | |
|
|
| | +---------+--------+.... |
|
|
| | absolute ^ ^ absolute |
|
|
| | position scale.x |
|
|
| | |
|
|
| | |
|
|
| | |
|
|
| (0.0, 0.0) +-----------------------------------+
|
|
*/
|
|
// @(optimization_mode = "favor_size")
|
|
draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text )
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
assert( ctx.px_scalar > 0.0 )
|
|
|
|
stack := & ctx.stack
|
|
assert(len(stack.view) > 0)
|
|
assert(len(stack.colour) > 0)
|
|
assert(len(stack.position) > 0)
|
|
assert(len(stack.scale) > 0)
|
|
assert(len(stack.zoom) > 0)
|
|
|
|
// TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape)
|
|
font := peek(stack.font)
|
|
assert( font >= 0 &&int(font) < len(ctx.entries) )
|
|
|
|
view := peek(stack.view);
|
|
|
|
ctx.cursor_pos = {}
|
|
entry := ctx.entries[ font ]
|
|
|
|
colour := peek(stack.colour)
|
|
should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
|
|
adjusted_colour := colour
|
|
adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen
|
|
|
|
px_scalar_quotient := 1 / ctx.px_scalar
|
|
|
|
px_size := shape.px_size * px_scalar_quotient
|
|
zoom := peek(stack.zoom)
|
|
|
|
resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )
|
|
|
|
absolute_position := peek(stack.position) + position
|
|
absolute_scale := peek(stack.scale) * zoom_scale
|
|
|
|
target_position, norm_scale := get_normalized_position_scale( absolute_position, absolute_scale, view )
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_scale := resolve_px_scalar_size(entry.parser_info,resolved_size, ctx.px_scalar, norm_scale)
|
|
|
|
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
|
|
ctx.px_scalar,
|
|
adjusted_colour,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_position,
|
|
target_scale,
|
|
)
|
|
}
|
|
|
|
/* Uses the ctx.stack, position and scale are relative to the position and scale on the stack.
|
|
|
|
absolute_position := peek(stack.position) + position
|
|
absolute_scale := peek(stack.scale ) * scale
|
|
|
|
| +-----------------------------------+ (view.x, view.y)
|
|
| | |
|
|
| | |
|
|
| | Glyph Quad absolute |
|
|
| | +---------+ < scale.y |
|
|
| | | ** | * | |
|
|
| | | * * | **** | |
|
|
| | | **** | * * | |
|
|
| | | * * | **** | |
|
|
| | +---------+--------+.... |
|
|
| | absolute ^ ^ absolute |
|
|
| | position scale.x |
|
|
| | |
|
|
| | |
|
|
| | |
|
|
| (0.0, 0.0) +-----------------------------------+
|
|
*/
|
|
// @(optimization_mode = "favor_size")
|
|
draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string,
|
|
shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
|
|
)
|
|
{
|
|
profile(#procedure)
|
|
assert( ctx != nil )
|
|
assert( len(text_utf8) > 0 )
|
|
assert( ctx.px_scalar > 0.0 )
|
|
|
|
stack := & ctx.stack
|
|
assert(len(stack.font) > 0)
|
|
assert(len(stack.font_size) > 0)
|
|
assert(len(stack.colour) > 0)
|
|
assert(len(stack.view) > 0)
|
|
assert(len(stack.position) > 0)
|
|
assert(len(stack.scale) > 0)
|
|
assert(len(stack.zoom) > 0)
|
|
|
|
font := peek(stack.font)
|
|
assert( font >= 0 &&int(font) < len(ctx.entries) )
|
|
|
|
view := peek(stack.view);
|
|
|
|
ctx.cursor_pos = {}
|
|
entry := ctx.entries[ font ]
|
|
|
|
colour := peek(stack.colour)
|
|
should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
|
|
adjusted_colour := colour
|
|
adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen
|
|
|
|
px_size := peek(stack.font_size)
|
|
zoom := peek(stack.zoom)
|
|
|
|
resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )
|
|
|
|
absolute_position := peek(stack.position) + position
|
|
absolute_scale := peek(stack.scale) * scale
|
|
|
|
target_position, norm_scale := get_normalized_position_scale( absolute_position, absolute_scale, view )
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_scale := resolve_px_scalar_size(entry.parser_info, resolved_size, ctx.px_scalar, norm_scale)
|
|
|
|
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size),
|
|
font,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
shaper_proc
|
|
)
|
|
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
|
|
ctx.px_scalar,
|
|
adjusted_colour,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
target_position,
|
|
target_scale,
|
|
)
|
|
}
|
|
|
|
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 )
|
|
return & ctx.draw_list
|
|
}
|
|
|
|
get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) {
|
|
assert( ctx != nil )
|
|
if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset )
|
|
vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ]
|
|
indices = ctx.draw_list.indices [ ctx.draw_layer.indices_offset : ]
|
|
calls = ctx.draw_list.calls [ ctx.draw_layer.calls_offset : ]
|
|
return
|
|
}
|
|
|
|
flush_draw_list :: #force_inline proc( ctx : ^Context ) {
|
|
assert( ctx != nil )
|
|
clear_draw_list( & ctx.draw_list )
|
|
ctx.draw_layer.vertices_offset = 0
|
|
ctx.draw_layer.indices_offset = 0
|
|
ctx.draw_layer.calls_offset = 0
|
|
}
|
|
|
|
flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) {
|
|
assert( ctx != nil )
|
|
ctx.draw_layer.vertices_offset = len(ctx.draw_list.vertices)
|
|
ctx.draw_layer.indices_offset = len(ctx.draw_list.indices)
|
|
ctx.draw_layer.calls_offset = len(ctx.draw_list.calls)
|
|
}
|
|
|
|
//#endregion("draw_list generation")
|
|
|
|
//#region("metrics")
|
|
|
|
// The metrics follow the convention for providing their values unscaled from ctx.px_scalar
|
|
// Where its assumed when utilizing the draw_list generators or shaping procedures that the shape will be affected by it so it must be handled.
|
|
// If px_scalar is 1.0 no effect is done and its just redundant ops.
|
|
|
|
measure_shape_size :: #force_inline proc( ctx : Context, shape : Shaped_Text ) -> (measured : Vec2) {
|
|
measured = shape.size * (1 / ctx.px_scalar)
|
|
return
|
|
}
|
|
|
|
// Don't use this if you already have the shape instead use measure_shape_size
|
|
@(optimization_mode="favor_size")
|
|
measure_text_size :: proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string,
|
|
shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
|
|
) -> (measured : Vec2)
|
|
{
|
|
// profile(#procedure)
|
|
assert( ctx != nil )
|
|
assert( font >= 0 && int(font) < len(ctx.entries) )
|
|
|
|
entry := ctx.entries[font]
|
|
|
|
target_scale := 1 / ctx.px_scalar
|
|
target_px_size := px_size * ctx.px_scalar
|
|
target_font_scale := parser_scale( entry.parser_info, target_px_size )
|
|
|
|
shaped := shaper_shape_text_cached( text_utf8,
|
|
& ctx.shaper_ctx,
|
|
& ctx.shape_cache,
|
|
ctx.atlas,
|
|
vec2(ctx.glyph_buffer.size),
|
|
font,
|
|
entry,
|
|
target_px_size,
|
|
target_font_scale,
|
|
shaper_proc
|
|
)
|
|
return shaped.size * target_scale
|
|
}
|
|
|
|
get_font_vertical_metrics :: #force_inline proc ( ctx : Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 )
|
|
{
|
|
assert( font >= 0 && int(font) < len(ctx.entries) )
|
|
|
|
entry := ctx.entries[ font ]
|
|
|
|
font_scale := parser_scale( entry.parser_info, px_size )
|
|
|
|
ascent = font_scale * entry.ascent
|
|
descent = font_scale * entry.descent
|
|
line_gap = font_scale * entry.line_gap
|
|
return
|
|
}
|
|
|
|
//#endregion("metrics")
|
|
|
|
//#region("miscellaneous")
|
|
|
|
get_font_entry :: #force_inline proc "contextless" ( ctx : ^Context, font : Font_ID ) -> Entry {
|
|
return ctx.entries[font]
|
|
}
|
|
|
|
get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos }
|
|
|
|
// Will normalize the value of the position and scale based on the provided view.
|
|
// Position will also be snapped to the nearest pixel via ceil.
|
|
// (Does nothing if view is 1 or 0)
|
|
get_normalized_position_scale :: #force_inline proc "contextless" ( position, scale, view : Vec2 ) -> (position_norm, scale_norm : Vec2)
|
|
{
|
|
should_snap := cast(f32) i32(view.x > 0 && view.y > 0)
|
|
view_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) }
|
|
|
|
position_snapped := ceil(position) * view_quotient * should_snap
|
|
position_norm = position * view_quotient
|
|
|
|
position_norm = max(position_snapped, position_norm)
|
|
scale_norm = scale * view_quotient
|
|
return
|
|
}
|
|
|
|
// Used to constrain the px_size used in draw calls.
|
|
resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, interval, min, max : f32 ) -> (resolved_size : f32) {
|
|
interval_quotient := 1.0 / f32(interval)
|
|
interval_size := round(px_size * interval_quotient) * interval
|
|
resolved_size = clamp( interval_size, min, max )
|
|
return
|
|
}
|
|
|
|
// Provides a way to get a "zoom" on the font size and scale, similar conceptually to a canvas UX zoom
|
|
// Does nothing when zoom is 1.0
|
|
resolve_zoom_size_scale :: #force_inline proc "contextless" (
|
|
zoom, px_size : f32, scale : Vec2, interval, min, max : f32, clamp_scale : Vec2
|
|
) -> (resolved_size : f32, zoom_scale : Vec2)
|
|
{
|
|
zoom_px_size := px_size * zoom
|
|
resolved_size = resolve_draw_px_size( zoom_px_size, interval, min, max )
|
|
zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size)
|
|
zoom_scale = zoom_diff_scalar * scale
|
|
zoom_scale.x = clamp(zoom_scale.x, 0, clamp_scale.x)
|
|
zoom_scale.y = clamp(zoom_scale.y, 0, clamp_scale.y)
|
|
return
|
|
}
|
|
|
|
// Get the target pixel, font_scale, and scale for the given font pixel size, and scalar multiple to apply. (Normalized space with norm_scale)
|
|
// To derived norm_scale use: get_normalized_position_scale or just do (scale * (1 / view))
|
|
resolve_px_scalar_size :: #force_inline proc "contextless" ( parser_info : Parser_Font_Info, px_size, px_scalar : f32, norm_scale : Vec2
|
|
) -> (target_px_size, target_font_scale : f32, target_scale : Vec2 )
|
|
{
|
|
// Does nothing when px_scalar is 1.0
|
|
target_px_size = px_size * px_scalar
|
|
target_scale = norm_scale * (1 / px_scalar)
|
|
target_font_scale = parser_scale( parser_info, target_px_size )
|
|
return
|
|
}
|
|
|
|
snap_normalized_position_to_view :: #force_inline proc "contextless" ( position, view : Vec2 ) -> (position_snapped : Vec2)
|
|
{
|
|
should_snap := cast(f32) i32(view.x > 0 && view.y > 0)
|
|
view_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) }
|
|
|
|
position_snapped = ceil(position * view) * view_quotient * should_snap
|
|
position_snapped = max(position, position_snapped)
|
|
return
|
|
}
|
|
|
|
set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar }
|
|
set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar }
|
|
set_zoom_px_interval :: #force_inline proc( ctx : ^Context, interval : i32 ) { assert(ctx != nil); ctx.zoom_px_interval = f32(interval) }
|
|
|
|
// During a shaping pass on text, will snap each glyph's position via ceil.
|
|
set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) {
|
|
assert(ctx != nil)
|
|
ctx.shaper_ctx.snap_glyph_position = should_snap
|
|
}
|
|
|
|
// During to_cache pass within batch_generate_glyphs_draw_list, will snap the quad's size using ceil.
|
|
set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap : b32 ) {
|
|
assert(ctx != nil)
|
|
ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap)
|
|
}
|
|
|
|
//#endregion("miscellaneous")
|
|
|
|
//#region("scope stack")
|
|
|
|
/* Scope stacking ease of use interface.
|
|
|
|
View: Extents in 2D for the relative space the the text is being drawn within.
|
|
Used with snap_to_view_extent to enforce position snapping.
|
|
|
|
Position: Used with a draw procedure that uses relative positioning will offset the incoming position by the given amount.
|
|
Scale : Used with a draw procedure that uses relative scaling, will scale the procedures incoming scale by the given amount.
|
|
Zoom : Used with a draw procedure that uses scaling via zoom, will scale the procedure's incoming font size & scale based on an 'canvas' camera's notion of it.
|
|
*/
|
|
|
|
@(deferred_in = auto_pop_font)
|
|
scope_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) }
|
|
push_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) }
|
|
pop_font :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font) }
|
|
auto_pop_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) }
|
|
|
|
@(deferred_in = auto_pop_font_size)
|
|
scope_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) }
|
|
push_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) }
|
|
pop_font_size :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font_size) }
|
|
auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); pop(& ctx.stack.font_size) }
|
|
|
|
@(deferred_in = auto_pop_colour )
|
|
scope_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) }
|
|
push_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) }
|
|
pop_colour :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.colour) }
|
|
auto_pop_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); pop(& ctx.stack.colour) }
|
|
|
|
@(deferred_in = auto_pop_view)
|
|
scope_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) }
|
|
push_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) }
|
|
pop_view :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.view) }
|
|
auto_pop_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.view) }
|
|
|
|
@(deferred_in = auto_pop_position)
|
|
scope_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) }
|
|
push_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) }
|
|
pop_position :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop( & ctx.stack.position) }
|
|
auto_pop_position :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop( & ctx.stack.position) }
|
|
|
|
@(deferred_in = auto_pop_scale)
|
|
scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) }
|
|
push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) }
|
|
pop_scale :: #force_inline proc( ctx : ^Context, ) { assert(ctx != nil); pop(& ctx.stack.scale) }
|
|
auto_pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.scale) }
|
|
|
|
@(deferred_in = auto_pop_zoom )
|
|
scope_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom ) }
|
|
push_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom) }
|
|
pop_zoom :: #force_inline proc( ctx : ^Context ) { pop(& ctx.stack.zoom) }
|
|
auto_pop_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { pop(& ctx.stack.zoom) }
|
|
|
|
@(deferred_in = auto_pop_vpz)
|
|
scope_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) {
|
|
assert(ctx != nil)
|
|
append(& ctx.stack.view, camera.view )
|
|
append(& ctx.stack.position, camera.position )
|
|
append(& ctx.stack.zoom, camera.zoom )
|
|
}
|
|
push_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) {
|
|
assert(ctx != nil)
|
|
append(& ctx.stack.view, camera.view )
|
|
append(& ctx.stack.position, camera.position )
|
|
append(& ctx.stack.zoom, camera.zoom )
|
|
}
|
|
pop_vpz :: #force_inline proc( ctx : ^Context ) {
|
|
assert(ctx != nil)
|
|
pop(& ctx.stack.view )
|
|
pop(& ctx.stack.position)
|
|
pop(& ctx.stack.zoom )
|
|
}
|
|
auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) {
|
|
assert(ctx != nil)
|
|
pop(& ctx.stack.view )
|
|
pop(& ctx.stack.position)
|
|
pop(& ctx.stack.zoom )
|
|
}
|
|
|
|
//#endregion("scope stack")
|