From b78a544aa8570703f4af65ed11b337654dcc6a6b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 11:29:48 -0500 Subject: [PATCH] Progress on documentation --- docs/Readme.md | 140 ++++++--- docs/guide_.architecturemd | 28 ++ docs/guide_backend.md | 7 + examples/sokol_demo/sokol_demo.odin | 2 +- thirdparty/stb/lib/stb_truetype.lib | Bin 380552 -> 380552 bytes vefontcache/pkg_mapping.odin | 2 +- vefontcache/shaper.odin | 11 +- vefontcache/vefontcache.odin | 446 ++++++++++++++-------------- 8 files changed, 363 insertions(+), 273 deletions(-) create mode 100644 docs/guide_.architecturemd create mode 100644 docs/guide_backend.md diff --git a/docs/Readme.md b/docs/Readme.md index 09bbb5f..97797c1 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -14,12 +14,20 @@ Much of the data structures within the context struct are not fixed-capacity all The library supports being used in a dynamically loaded module. If its hot-reloaded simply make sure to call this procedure with a reference to the backing allocator provided during startup as all dynamic containers tend to lose a proper reference to the allocator's procedure. -Call clear_atlas_region_caches & clear_shape_cache to reset the library's shape and glyph cache state to force a re-render. +Call `clear_atlas_region_caches` & `clear_shape_cache` to reset the library's shape and glyph cache state to force a re-render. ### shutdown Release resources from the context. +### clear_atlas_region_caches + +Clears the LRU caches of regions A-D of the Atlas & sets their next_idx to 0. Effectively will force a re-cache of all previously rendered glyphs. Shape configuration for the glyph will remain unchanged unless clear_shape_cache is also called. + +### clear_shape_cache + +Clears the LRU cache of the shaper along with clearing all existing storage entries. Effectively will force a re-cache of previously cached text shapes (Does not recache their rendered glyphs). + ### load_font Will load an instance of a font. The user needs to load the file's bytes themselves, the font entry (Entry :: struct) will by tracked by the library. The user will be given a font_id which is a direct index for the entry in the tracked array. @@ -28,23 +36,55 @@ Will load an instance of a font. The user needs to load the file's bytes themsel Will free an entry, (parser and shaper resources also freed) -## Scoping Context interface +## Shaping -These are a set of push & pop pairs of functions that operator ont he context's stack containers. They are used with the draw_shape and draw_text procedures. This mainly for quick scratch usage where the user wants to directly compose a large amount of text without having a UI framework directly handle the text backend. +Ideally the user should track the shapes themselves in a time-scale beyond the per-frame draw call. This avoids having to do caching/lookups of the shope. -* font -* font_size -* colour: Linear colour. -* view: Width and height of the 2D area the text will be drawn within. -* position: Uses relative positioning will offset the incoming position by the given amount. -* scale: Uses relative scaling, will scale the procedures incoming scale by the given amount. -* zoom: Affects scaling, will scale the procedure's incoming font size & scale based on an *UX canvas camera's* notion of it. +### shape_text -Procedure types: +Will shape the text using the `shaper_proc` arugment (user overloadable). Shape will be cached by the library. -* `scope_`: push with a defer pop -* `push_` -* `pop_` +### shape_text_uncached + +Will shape the text using the `shaper_proc` arugment (user overloadable). +Shape will NOT be cached by the library. Use this if you want to roll your own solution for tracking shapes. + +## Draw list generation + +### get_draw_list + +Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety. +By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +### get_draw_list_layer + +Get the enqueued draw_list for the current "layer". +A layer is considered the slice of the `Draw_List`'s content from the last call to `flush_draw_list_layer` onward. +By default, if `get_draw_list_layer` is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +The draw layer offsets are cleared with `flush_draw_list` + +### flush_draw_list + +Will clear the draw list and draw layer offsets. + +### flush_draw_list_layer + +Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays. + +## Metrics + +### measure_shape_size + +This provide's the shape size scaled down by the ctx.px_scale to get intended usage size. Size is equivalent to `measure_text_size`. + +### measure_text_size + +Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `Shaped_Text` entry. + +### get_font_vertical_metrics + +A wrapper for `parser_get_font_vertical_metrics`. Will provide the ascent, descent, and line_gap for a font entry. ## Miscellaneous @@ -62,62 +102,68 @@ Does nothing if view is 1 or 0 This is used by draw via view relative space procedures to normalize it to the intended space for the render pass. -## resolve_draw_px_size +### resolve_draw_px_size -Used to constrain the px_size used in draw calls. +Used to constrain the px_size used in `resolve_zoom_size_scale`. The view relative space and scoping stack-based procedures support zoom. When utilizing zoom their is a nasty jitter that will occur if the user smoothly goes across different font sizes because the spacing can drastically change between even and odd font-sizes. This is applied to enforce the font sticks to a specific interval. -For the provided procedures that utilize it, they reference the context's zoom_px_interval. It can be set with `set_zoom_px_interval` and the default value is 2. +The library uses the context's zoom_px_interval as the reference interval in the draw procedures. It can be set with `set_zoom_px_interval` and the default value is 2. -## resolve_zoom_size_scale +### resolve_zoom_size_scale +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 +Uses `resolve_draw_px_size` to constrain which font size is used for the zoom. -### configure_snap +### set_alpha_scalar -You'll find this used immediately in draw_text it acts as a way to snap the position of the text to the nearest pixel for the width and height specified. +This is an artifact feature of the current shader, it *may* be removed in the future... Increasing the alpha of the colour draw with above 1.0 increases the edge contrast of the glyph shape. -If snapping is not desired, set the snap_width and height before calling draw_text to 0. +For the value to be added to the colour, the alph of the text must already be at 1.0 or greater. +### set_px_scalar -### set_color +This another "super-scalar" applied to rendering glyphs. In each draw procedure the following is computed before passing the values to the shaper and draw list generation passes: -Sets the color to utilize on `Draw_Call`s for FrameBuffer.Target or .Target_Uncached passes +```go +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 ) +``` -### get_draw_list +Essentially, `ctx.px_scalar` is used to upscale the px_size by its value and then downscale the render target scale back the indended size. Doing so provides better shape positioning and futher improves text hinting. The downside is that small text tends to become more jagged (as its really hitting the limits of of how well the shader can blend those edges at that resolution). -Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety. -By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. +This will most likely be preserved with future shader upgrades, however it will most likely not be as necessary as it is right now to achieve crisp text. -### get_draw_list_layer +### set_zoom_px_interval -Get the enqueued draw_list for the current "layer". -A layer is considered the slice of the `Draw_List`'s content from the last call to `flush_draw_list_layer` onward. -By default, if `get_draw_list_layer` is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. +Used with by draw procedures with `resolve_draw_px_size` & `resolve_zoom_size_scale`. Provides the interval to use when constraining the px_size to a specific set of values when using zoom scaling. -The draw layer offsets are cleared with `flush_draw_list` +### set_snap_glyph_shape_position -### flush_draw_list +During the shaping pass, the position of each glyph can be rounded up to the integer to (ussually) allow better hinting. -Will clear the draw list and draw layer offsets. +### set_snap_glyph_render_height -### flush_draw_list_layer +During the draw list generation pass, the position of each glyph when blitting to atlas can have teh quad size rounded up to the integer. +Can yield better hinting but may significantly stretch the glyphs at small scales. -Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays. +## Scope Stack -### measure_text_size +These are a set of push & pop pairs of functions that operator ont he context's stack containers. They are used with the draw_shape and draw_text procedures. This mainly for quick scratch usage where the user wants to directly compose a large amount of text without having a UI framework directly handle the text backend. -Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `Shaped_Text` entry. +* font +* font_size +* colour: Linear colour. +* view: Width and height of the 2D area the text will be drawn within. +* position: Uses relative positioning will offset the incoming position by the given amount. +* scale: Uses relative scaling, will scale the procedures incoming scale by the given amount. +* zoom: Affects scaling, will scale the procedure's incoming font size & scale based on an *UX canvas camera's* notion of it. -### get_font_vertical_metrics +Procedure types: -A wrapper for `parser_get_font_vertical_metrics`. Will provide the ascent, descent, and line_gap for a font entry. - -### clear_atlas_region_caches - -Clears the LRU caches of regions A-D of the Atlas & sets their next_idx to 0. Effectively will force a re-cache of all previously rendered glyphs. Shape configuration for the glyph will remain unchanged unless clear_shape_cache is also called. - -### clear_shape_cache - -Clears the LRU cache of the shaper along with clearing all existing storage entries. Effectively will force a re-cache of previously cached text shapes (Does not recache their rendered glyphs). +* `scope_`: push with a defer pop +* `push_` +* `pop_` diff --git a/docs/guide_.architecturemd b/docs/guide_.architecturemd new file mode 100644 index 0000000..9893ee5 --- /dev/null +++ b/docs/guide_.architecturemd @@ -0,0 +1,28 @@ +# Architecture + +The purpose of this library to really allieviate four issues with one encapsulation: + +* font parsing +* text codepoint shaping +* glyph shape triangulation +* glyph draw-list generation + +Shaping text, getting metrics for the glyphs, triangulating glyphs, and anti-aliasing their render are expensive todo per frame. So anything related to that compute that may be cached, will be. + +There are two cache types used: + +* shape cache (shape_cache.state) +* atlas region cache (Atlas_Region.state) + +The shape cache stores all data for a piece of text that will be utilized in a draw call that is not dependent on a specific position & scale (and is faster to lookup vs compute per draw call). So far these are the text shaping itself, and per-glyph infos: atlas_lru_code (atlas key), atlas region resolution, & glyph bounds. +The atlas region cache tracks what slots have glyphs rendered to the texture atlas. This essentially is caching of triangulation and super-sampling compute. + +All caching uses the [LRU.odin](../vefontcache/LRU.odin) + +## Codepaths + +## Library Lifetime + +## Draw List Generation + +The base draw list generation pipepline provided by the library allows the user to batch whatever the want into a single "layer". However diff --git a/docs/guide_backend.md b/docs/guide_backend.md new file mode 100644 index 0000000..8360f74 --- /dev/null +++ b/docs/guide_backend.md @@ -0,0 +1,7 @@ +# Backend Guide + +The end-user needs adapt this library for hookup into their own codebase. As an example they may see the [examples](../examples/) and [backend](../backend/) for working code of what this guide will go over. + +When rendering text, the two products the user has to deal with: The text to draw and their "layering". Similar to UIs text should be drawn in layer batches, where each layer can represent a pass on some arbitrary set of distictions between the other layers. + + diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index c600d32..85988ad 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -236,7 +236,7 @@ init :: proc "c" () glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, px_scalar = 1.6, - alpha_sharpen = 0.4, + alpha_sharpen = 0.35, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index 4217b43a23ccabe1873cfc7c74981b97e6aebf01..bc37f121124d31e11ea0ebda195bbf87be6966ec 100644 GIT binary patch delta 89 zcmeB}E8a0ze1Z&{nSrU9x$$HLJMoQ{4{f2`%?fs6R!CBlgDVUnT%*Zd6>3Z`>YF!J Xv~Q|l1Y#y2W(H!G?VBoCuZ02twxJ#1 delta 89 zcmeB}E8a0ze1Z&{skw=nso7)&JMoQ{4{f2`%?fs6R!CBlgDVVS+^z~WrUPuvn=0Bj URWJfE6A&{4G0XN%6|C1n0jroDSO5S3 diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index 62cb6e1..a684efc 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -48,7 +48,7 @@ append :: proc { } append_soa :: proc { - append_soa_elem + append_soa_elem, } ceil :: proc { diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index dc6d51e..6ebb3b9 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -104,6 +104,7 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info ) if info.blob != nil do harfbuzz.blob_destroy( info.blob ) } +// TODO(Ed): Allow the user to override snap_glyph_position of the shaper context on a per-call basis (as a param) // Recommended shaper. Very performant. // TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal... @(optimization_mode="favor_size") @@ -299,6 +300,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, return } +// TODO(Ed): Allow the user to override snap_glyph_position of the shaper context on a per-call basis (as an param) // Basic western alphabet based shaping. Not that much faster than harfbuzz if at all. shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, atlas : Atlas, @@ -347,11 +349,12 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index ) if ! is_glyph_empty { + if ctx.snap_glyph_position { + position.x = ceil(position.x) + position.y = ceil(position.y) + } append( & output.glyph, glyph_index) - append( & output.position, Vec2 { - ceil(position.x), - ceil(position.y) - }) + append( & output.position, position) } advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 6703e71..c0b009c 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -64,27 +64,25 @@ Context :: struct { entries : [dynamic]Entry, // TODO(Ed): Review these when preparing to handle lifting of working context to a thread context. - glyph_buffer : Glyph_Draw_Buffer, - atlas : Atlas, - shape_cache : Shaped_Text_Cache, - draw_list : Draw_List, + 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 { + 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, - snap_width : f32, - snap_height : f32, - // Will enforce even px_size when drawing. even_size_only : f32, @@ -94,7 +92,7 @@ Context :: struct { stack : Scope_Stack, - cursor_pos : Vec2, // TODO(Ed): Review this, no longer used much at all... (still useful I guess) + 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, @@ -107,6 +105,8 @@ Context :: struct { 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__scaled for choosing which atlas region. @@ -133,7 +133,7 @@ Init_Glyph_Draw_Params :: struct { } Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { - snap_glyph_height = false, + snap_glyph_height = true, over_sample = 4, draw_padding = Init_Atlas_Params_Default.glyph_padding, shape_gen_scratch_reserve = 512, @@ -150,7 +150,7 @@ Init_Shaper_Params :: struct { } Init_Shaper_Params_Default :: Init_Shaper_Params { - snap_glyph_position = false, + snap_glyph_position = true, adv_snap_small_font_threshold = 0, } @@ -167,6 +167,8 @@ Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params { reserve = 128, } +//#endregion("Init Params") + //#region("lifetime") // ve_fontcache_init @@ -176,15 +178,15 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N 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.0, - px_scalar : f32 = 1, + alpha_sharpen : f32 = 0.35, + px_scalar : f32 = 1.6, 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 = 128, + scope_stack_reserve : u32 = 32, ) { assert( ctx != nil, "Must provide a valid context" ) @@ -517,34 +519,6 @@ shutdown :: proc( ctx : ^Context ) } } -// 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 { - stroage_entry := & ctx.shape_cache.storage[idx] - stroage_entry.end_cursor_pos = {} - stroage_entry.size = {} - clear(& stroage_entry.glyph) - clear(& stroage_entry.position) - } - ctx.shape_cache.next_cache_id = 0 -} - load_font :: proc( ctx : ^Context, label : string, data : []byte, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID, error : Load_Font_Error) { profile(#procedure) @@ -613,153 +587,90 @@ unload_font :: proc( ctx : ^Context, font : Font_ID ) shaper_unload_font( & entry.shaper_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 { + stroage_entry := & ctx.shape_cache.storage[idx] + stroage_entry.end_cursor_pos = {} + stroage_entry.size = {} + clear(& stroage_entry.glyph) + clear(& stroage_entry.position) + } + ctx.shape_cache.next_cache_id = 0 +} + //#endregion("lifetime") -//#region("scoping") +//#region("shaping") -/* Scope stacking ease of use interface. +// For high performance, the user should track the shapes and use the draw list interface on shapes. +// Doing so avoids cache lookups. -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("scoping") - -//#region("misc") - -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) +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 { - snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } - should_snap := view * snap_quotient + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] - snapped_position := position - snapped_position.x = ceil(position.x * view.x) * snap_quotient.x - snapped_position.y = ceil(position.y * view.y) * snap_quotient.y + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) - snapped_position *= should_snap - snapped_position.x = max(snapped_position.x, position.x) - snapped_position.y = max(snapped_position.y, position.y) - - position_norm = snapped_position - scale_norm = scale * snap_quotient - return + 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 + ) } -// 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) +// 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 +) { - 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) + 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), + entry, + target_px_size, + target_font_scale, + text_utf8, + shape + ) 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) -} - -//#endreigon("misc") +//#endregion("shaping") //#region("draw_list generation") @@ -1289,55 +1200,150 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID //#endregion("metrics") -//#region("shaping") +//#region("miscellaneous") -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 +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) { - profile(#procedure) - assert( len(text_utf8) > 0 ) - entry := ctx.entries[ font ] + snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } + should_snap := view * snap_quotient - target_px_size := px_size * ctx.px_scalar - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + snapped_position := position + snapped_position.x = ceil(position.x * view.x) * snap_quotient.x + snapped_position.y = ceil(position.y * view.y) * snap_quotient.y - 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 - ) -} + snapped_position *= should_snap + snapped_position.x = max(snapped_position.x, position.x) + snapped_position.y = max(snapped_position.y, position.y) - -// 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), - entry, - target_px_size, - target_font_scale, - text_utf8, - shape - ) + position_norm = snapped_position + scale_norm = scale * snap_quotient return } -//#endregion("shaping") +// 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 +} + +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("misc") + +//#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")