Files
VEFontCache-Odin/vefontcache/vefontcache.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