WIP (Broken) docs and huge changes

This commit is contained in:
Edward R. Gonzalez 2025-01-07 03:06:12 -05:00
parent a9080fe1f3
commit 3a245a1e9b
20 changed files with 725 additions and 438 deletions

View File

@ -1,9 +1,8 @@
VEFontCache Odin Port
VE Text Rendering Library
Copyright 2024 Edward R. Gonzalez
This project is based on Vertex Engine GPU Font Cache
by Xi Chen (https://github.com/hypernewbie/VEFontCache). It has been substantially
rewritten and redesigned for the Odin programming language.
by Xi Chen (https://github.com/hypernewbie/VEFontCache). It has been substantially overhauled from its original implementation.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,

View File

@ -1,7 +1,15 @@
package vefontcache
package vetext
/*
The choice was made to keep the LRU cache implementation as close to the original as possible.
/* Note(Ed):
Original implementation has been changed moderately.
Notably the LRU is now type generic for its key value.
This was done to profile between using u64, u32, and u16.
What ended up happening was using u32 for both the atlas and the shape cache
yielded a several ms save for processing thousands of draw text calls.
There was an attempt at an optimization pass but the directives done here (other than force_inline)
are marginal changes at best.
*/
import "base:runtime"
@ -177,7 +185,7 @@ pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type
return value
}
LRU_Link :: struct {
LRU_Link :: struct #packed {
value : i32,
ptr : Pool_ListIter,
}

View File

@ -1,9 +1,45 @@
# VE Font Cache : Odin Port
# VE Text Rendering Library
This is a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library.
> Vertex Engine GPU Text Rendering Library
https://github.com/user-attachments/assets/b74f1ec1-f980-45df-b604-d6b7d87d40ff
This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language.
Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications.
Since then the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements.
Features:
* Simple and well documented.
* Load and unload fonts at anytime
* Almost entirely configurabe and tunable at runtime!
* Full support for hot-reload
* Clear the caches at any-time!
* Robust quality of life features:
* Tracks text layers!
* Push and pop stack for font, font_size, view, position, scale and zoom!
* Enforce even only font-sizing
* Snap-positining to view for better hinting
* Basic or advanced text shaping via Harfbuzz
* All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu.
* Can hand thousands of draw text calls with very large or small shapes.
* 4-Level Regioned Texture Atlas for caching rendered glyphs
* Text shape caching
* Glyph texture buffer for rendering the text with super-sampling to downsample to the atlas or direct to target screen.
* Super-sample by a font size scalar for sharper glyphs
* All caching backed by an optimized 32-bit LRU indexing cache
* Provides a draw list that is backend agnostic (see [backend](./backend) for usage example).
Upcoming:
* Support for ear-clipping triangulation
* Support for which triangulation method used on a by font basis?
* Multi-threading supported job queue.
* Lift heavy-lifting portion of the library's context into a thread context.
* Synchronize threads by merging their generated layered drawlist into a file draw-list for processing on the user's render thread.
* User defines how context's are distributed for drawing (a basic quandrant basic selector procedure will be provided.)
See: [docs/Readme.md](docs/Readme.md) for the library's interface.
## Building
@ -12,40 +48,7 @@ See [scripts/Readme.md](scripts/Readme.md) for building examples or utilizing th
Currently the scripts provided & the library itself were developed & tested on Windows. There are bash scripts for building on linux (they build on WSL but need additional testing).
The library depends on freetype, harfbuzz, & stb_truetype to build.
Note: freetype and harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet).
The library depends on harfbuzz, & stb_truetype to build.
Note: harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet).
## Changes from orignal
* Font Parser & Glyph shaper are abstracted to their own warpper interface
* ve_fontcache_loadfile not ported (ust use core:os or os2, then call load_font)
* Macro defines have been coverted (mostly) to runtime parameters
* Support for hot_reloading
* Curve quality step interpolation for glyph rendering can be set on a per font basis.
* All codepaths heavily changed (its faster)
## TODOs
### Additional Features:
* Support for freetype (WIP, Currently a mess... and slow)
* Add ability to conditionally compile dependencies (so that the user may not need to resolve those packages).
* Ability to set a draw transform, viewport and projection
* By default the library's position is in unsigned normalized render space
* Could implement a similar design to sokol_gp's interface
### Optimization:
* Check if its better to store the glyph vertices if they need to be re-cached to atlas or directly drawn.
* Look into setting up multi-threading by giving each thread a context
* There is a heavy performance bottleneck in iterating the text/shape/glyphs on the cpu (single-thread) vs the actual rendering *(if doing thousands of drawing commands)*
* draw_text can provide in the context a job list per thread for the user to thenk hookup to their own threading solution to handle.
* Context would need to be segregated into staged data structures for each thread to utilize
* This would need to converge to the singlar draw_list on a per layer basis. The interface expects the user to issue commands single-threaded unless, its assumed the user is going to feed the gpu the commands & data through separate threads as well (not ideal ux).
* How the contexts are given jobs should be left up to the user (can recommend a screen quadrant based approach in demo examples)
Failed Attempts:
* Attempted to chunk the text to more granular 'shapes' from `draw_list` before doing the actual call to `draw_text_shape`. This lead to a larger performance cost due to the additional iteration across the text string.
* Attempted to cache the shape draw_list for future calls. Led to larger performance cost due to additional iteration in the `merge_draw_list`.
* The shapes glyphs must still be traversed to identify if the glyph is cached. This arguably could be handled in `shape_text_uncached`, however that would require a significan't amount of refactoring to identify... (and would be more unergonomic when shapers libs are processing the text)
![image](https://github.com/user-attachments/assets/2f6c0b36-179c-42fe-8903-7640ae3c209e)

View File

@ -1,5 +1,7 @@
package vefontcache
package vetext
// There are only 4 actual regions of the atlas. E represents the atlas_decide_region detecting an oversized glyph.
// Note(Ed): None should never really occur anymore. So its safe to most likely add an assert when its detected.
Atlas_Region_Kind :: enum u8 {
None = 0x00,
A = 0x01,
@ -10,8 +12,13 @@ Atlas_Region_Kind :: enum u8 {
Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call
}
// Note(Ed): Using 16 bit hash had collision failures and no observable performance improvement (tried several 16-bit hashers)
Atlas_Region_Key :: u32
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Atlas.
/* Essentially a sub-atlas of the atlas. There is a state cache per region that tracks the glyph inventory (what slot they occupy).
Unlike the shape cache this one's fixed capacity (natrually) and the next avail slot is tracked.
*/
Atlas_Region :: struct {
state : LRU_Cache(Atlas_Region_Key),
@ -24,6 +31,14 @@ Atlas_Region :: struct {
next_idx : i32,
}
/* There are four regions each succeeding region holds larger sized slots.
The generator pipeline for draw lists utilizes the regions array for info lookup.
Note(Ed):
Padding can techncially be larger than 1, however recently I haven't had any artififact issues...
size_multiplier usage isn't fully resolved. Intent was to further setup over_sampling or just having
a more massive cache for content that used more than the usual common glyphs.
*/
Atlas :: struct {
region_a : Atlas_Region,
region_b : Atlas_Region,
@ -38,7 +53,7 @@ Atlas :: struct {
size : Vec2i,
}
// Hahser for the atlas.
atlas_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, px_size : f32, glyph_index : Glyph ) -> (lru_code : Atlas_Region_Key) {
// lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
font := font

View File

@ -1,10 +1,21 @@
package vefontcache
package vetext
/*
Note(Ed): This may be seperated in the future into another file dedending on how much is involved with supportin ear-clipping triangulation.
*/
import "base:runtime"
import "base:intrinsics"
import "core:slice"
import "thirdparty:freetype"
Glyph_Trianglation_Method :: enum(i32) {
Ear_Clipping,
Triangle_Fanning,
}
Vertex :: struct {
pos : Vec2,
u, v : f32,
@ -65,7 +76,7 @@ Draw_Call :: struct {
end_index : u32,
clear_before_draw : b32,
region : Atlas_Region_Kind,
colour : Colour,
colour : RGBAN,
}
Draw_Call_Default :: Draw_Call {
@ -97,10 +108,12 @@ Glyph_Batch_Cache :: struct {
cap : i32,
}
// The general tracker for a generator pipeline
Glyph_Draw_Buffer :: struct{
over_sample : Vec2,
size : Vec2i,
draw_padding : f32,
over_sample : Vec2,
size : Vec2i,
draw_padding : f32,
snap_glyph_height : f32,
allocated_x : i32, // Space used (horizontally) within the glyph buffer
clear_draw_list : Draw_List,
@ -115,6 +128,7 @@ Glyph_Draw_Buffer :: struct{
cached : [dynamic]i32,
}
// Contructs a
@(optimization_mode="favor_size")
blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} )
{
@ -149,9 +163,9 @@ blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1
return
}
// Constructs a triangle fan to fill a shape using the provided path outside_point represents the center point of the fan.
// Constructs a triangle fan mesh to fill a shape using the provided path outside_point represents the center point of the fan.
@(optimization_mode="favor_size")
construct_filled_path :: proc( draw_list : ^Draw_List,
fill_path_via_fan_triangulation :: proc( draw_list : ^Draw_List,
outside_point : Vec2,
path : []Vertex,
scale := Vec2 { 1, 1 },
@ -187,6 +201,7 @@ construct_filled_path :: proc( draw_list : ^Draw_List,
}
}
// Glyph triangulation generator
@(optimization_mode="favor_size")
generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]Vertex,
glyph_shape : Parser_Glyph_Shape,
@ -209,7 +224,7 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V
{
case .Move:
if len(path) > 0 {
construct_filled_path( draw_list, outside, path[:], scale, translate)
fill_path_via_fan_triangulation( draw_list, outside, path[:], scale, translate)
clear(path)
}
fallthrough
@ -242,7 +257,7 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V
}
if len(path) > 0 {
construct_filled_path(draw_list, outside, path[:], scale, translate)
fill_path_via_fan_triangulation(draw_list, outside, path[:], scale, translate)
}
draw.end_index = u32(len(draw_list.indices))
@ -251,39 +266,63 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V
}
}
generate_shapes_draw_list :: proc ( ctx : ^Context, font : Font_ID, colour : Colour, entry : Entry, px_size, font_scale : f32, position, scale : Vec2, shapes : []Shaped_Text )
// Just a warpper of generate_shape_draw_list for handling an array of shapes
generate_shapes_draw_list :: #force_inline proc ( ctx : ^Context, font : Font_ID, colour : RGBAN, entry : Entry, px_size, font_scale : f32, position, scale : Vec2, shapes : []Shaped_Text )
{
assert(len(shapes) > 0)
for shape in shapes {
ctx.cursor_pos = {}
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
ctx.enable_draw_type_visualization,
colour,
entry,
px_size,
font_scale,
position,
scale,
ctx.snap_width,
ctx.snap_height
)
}
}
@(optimization_mode="favor_size")
/* Core generator pipeline for shapes
If you'd like to make a custom draw procedure, this may either be directly used, or
should be straight forward to make an augmented derivative for a specific codepath.
This procedure has no awareness of layers. That should be handled by a higher-order codepath.
For this level of codepaths what matters is maximizing memory locality for:
* Dealing with shaping (essentially minimizing having to ever deal with it in a hot path if possible)
* Metric resolution of glyphs within a shape
* Note: It may be better to have the shape to track/store the glyph bounds and lru codes
* The shaper has access to the atlas's dependencies and to parser's metrics (including the unscaled bounds).
Pipleine order:
* Resolve atlas lru codes and track shape indexes
* Resolve the glyph's position offset from the target position
* Resolve glyph bounds and scale
* Resolve atlas region the glyph is associated with
* Segregate the glyphs into three slices: oversized, to_cache, cached.
* If oversized is not necessary for your use case and your hitting a bottle neck, remove it in a derivative procedure.
* You have to to be drawing a px font size > ~140 px for it to trigger.
* The atlas can be scaled with the size_multiplier parameter of startup so that it becomes more irrelevant if processing a larger atlas is a non-issue.
* The segregation will not allow slices to exceed the batch_cache capacity of the glyph_buffer (configurable within startup params)
* When The capacity is reached batch_generate_glyphs_draw_list will be called which will do futher compute and then finally draw_list generation.
* This may perform better with smaller shapes vs larger shapes, but having more shapes has a cache lookup penatly so keep that in mind.
*/
generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
atlas : ^Atlas,
glyph_buffer : ^Glyph_Draw_Buffer,
px_scalar : f32,
colour : Colour,
entry : Entry,
px_size : f32,
font_scale : f32,
enable_debug_vis_type : f32,
colour : RGBAN,
entry : Entry,
px_size : f32,
font_scale : f32,
target_position : Vec2,
target_scale : Vec2,
snap_width : f32,
snap_height : f32
) -> (cursor_pos : Vec2) #no_bounds_check
{
profile(#procedure)
@ -425,7 +464,8 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
entry,
colour,
font_scale,
target_scale
target_scale,
enable_debug_vis_type
)
reset_batch( & glyph_buffer.batch_cache)
@ -446,7 +486,8 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
entry,
colour,
font_scale,
target_scale
target_scale,
enable_debug_vis_type
)
}
@ -454,6 +495,19 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
return
}
/*
The glyphs types have been segregated by this point into a batch slice of indices to the glyph_pack
The transform and draw quads are computed first (getting the math done in one spot as possible...)
Some of the math from to_cache pass for glyph generation was not moved over (it could be but I'm not sure its worth it...)
Order: Oversized first, then to_cache, then cached.
Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target.
The compute section will have operations reguarding how many glyphs they made alloate before a flush must occur.
A flush will force one of the following:
* Oversized will have a draw call setup to blit directly from the glyph buffer to the target.
* to_cache will blit the glyphs rendered to the buffer to the atlas.
*/
batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
glyph_pack : ^#soa[dynamic]Glyph_Pack_Entry,
cached : []i32,
@ -465,29 +519,53 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
atlas_size : Vec2,
glyph_buffer_size : Vec2,
entry : Entry,
colour : Colour,
font_scale : Vec2,
target_scale : Vec2,
entry : Entry,
colour : RGBAN,
font_scale : Vec2,
target_scale : Vec2,
enable_debug_vis_type : f32,
) #no_bounds_check
{
profile(#procedure)
when ENABLE_DRAW_TYPE_VIS {
colour := colour
}
colour := colour
profile_begin("glyph buffer transform & draw quads compute")
for id, index in cached
for id, index in oversized
{
// Quad to for drawing atlas slot to target
glyph := & glyph_pack[id]
quad := & glyph.draw_quad
quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0) * target_scale
quad.dst_scale = (glyph.scale) * target_scale
quad.src_scale = (glyph.scale)
quad.src_pos = (glyph.region_pos)
to_target_space( & quad.src_pos, & quad.src_scale, atlas_size )
f32_allocated_x := cast(f32) glyph_buffer.allocated_x
// Resolve how much space this glyph will allocate in the buffer
buffer_size := (glyph.bounds_size_scaled + glyph_buffer.draw_padding) * glyph.over_sample
// Allocate a glyph glyph render target region (FBO)
to_allocate_x := buffer_size.x + 2.0
glyph_buffer.allocated_x += i32(to_allocate_x)
// If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered.
glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x)
glyph.buffer_x = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) )
// Quad to for drawing atlas slot to target
draw_quad := & glyph.draw_quad
glyph_padding := vec2(glyph_buffer.draw_padding)
// Target position (draw_list's target image)
draw_quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0 - glyph_padding) * target_scale
draw_quad.dst_scale = (glyph.bounds_size_scaled + glyph_padding) * target_scale
// The glyph buffer space transform for generate_glyph_pass_draw_list
draw_transform := & glyph.draw_transform
draw_transform.scale = font_scale * glyph.over_sample
draw_transform.pos = -1 * glyph.bounds.p0 * draw_transform.scale + vec2(atlas.glyph_padding)
draw_transform.pos.x += glyph.buffer_x
to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size )
draw_quad.src_pos = Vec2 { glyph.buffer_x, 0 }
draw_quad.src_scale = glyph.bounds_size_scaled * glyph.over_sample + glyph_padding
to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, glyph_buffer_size )
}
for id, index in to_cache
{
@ -526,52 +604,25 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
draw_quad.src_pos = (glyph.region_pos)
to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, atlas_size )
}
for id, index in oversized
for id, index in cached
{
glyph := & glyph_pack[id]
f32_allocated_x := cast(f32) glyph_buffer.allocated_x
// Resolve how much space this glyph will allocate in the buffer
buffer_size := (glyph.bounds_size_scaled + glyph_buffer.draw_padding) * glyph.over_sample
// Allocate a glyph glyph render target region (FBO)
to_allocate_x := buffer_size.x + 2.0
glyph_buffer.allocated_x += i32(to_allocate_x)
// If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered.
glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x)
glyph.buffer_x = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) )
// Quad to for drawing atlas slot to target
draw_quad := & glyph.draw_quad
glyph_padding := vec2(glyph_buffer.draw_padding)
// Target position (draw_list's target image)
draw_quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0 - glyph_padding) * target_scale
draw_quad.dst_scale = (glyph.bounds_size_scaled + glyph_padding) * target_scale
// The glyph buffer space transform for generate_glyph_pass_draw_list
draw_transform := & glyph.draw_transform
draw_transform.scale = font_scale * glyph.over_sample
draw_transform.pos = -1 * glyph.bounds.p0 * draw_transform.scale + vec2(atlas.glyph_padding)
draw_transform.pos.x += glyph.buffer_x
to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size )
draw_quad.src_pos = Vec2 { glyph.buffer_x, 0 }
draw_quad.src_scale = glyph.bounds_size_scaled * glyph.over_sample + glyph_padding
to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, glyph_buffer_size )
glyph := & glyph_pack[id]
quad := & glyph.draw_quad
quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0) * target_scale
quad.dst_scale = (glyph.scale) * target_scale
quad.src_scale = (glyph.scale)
quad.src_pos = (glyph.region_pos)
to_target_space( & quad.src_pos, & quad.src_scale, atlas_size )
}
profile_end()
profile_begin("generate oversized glyphs draw_list")
if len(oversized) > 0
{
when ENABLE_DRAW_TYPE_VIS {
colour.r = 1.0
colour.g = 1.0
colour.b = 0.0
}
colour.r = max(colour.a, enable_debug_vis_type)
colour.g = max(colour.g, enable_debug_vis_type)
colour.b = colour.b * f32(cast(i32) ! b32(cast(i32) enable_debug_vis_type))
for id, index in oversized {
error : Allocator_Error
glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index)
@ -611,12 +662,34 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
append( & draw_list.calls, draw_to_target )
}
if len(oversized) > 0 do flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
for id, index in oversized do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
}
profile_end()
generate_cached_draw_list :: #force_inline proc (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : RGBAN )
{
profile(#procedure)
call := Draw_Call_Default
call.pass = .Target
call.colour = colour
for id, index in sub_pack
{
profile("glyph")
call.start_index = u32(len(draw_list.indices))
quad := glyph_pack[id].draw_quad
blit_quad(draw_list,
quad.dst_pos, quad.dst_pos + quad.dst_scale,
quad.src_pos, quad.src_pos + quad.src_scale
)
call.end_index = u32(len(draw_list.indices))
append(& draw_list.calls, call)
}
}
profile_begin("to_cache: caching to atlas")
if len(to_cache) > 0
{
for id, index in to_cache {
error : Allocator_Error
@ -651,12 +724,12 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
dst_glyph_pos := glyph.region_pos
dst_glyph_size := glyph.bounds_size_scaled + atlas.glyph_padding
// dst_glyph_size.y = ceil(dst_glyph_size.y) // Note(Ed): Seems to improve hinting
dst_glyph_size.y = ceil(dst_glyph_size.y) * glyph_buffer.snap_glyph_height // Note(Ed): Seems to improve hinting
to_glyph_buffer_space( & dst_glyph_pos, & dst_glyph_size, atlas_size )
src_position := Vec2 { glyph.buffer_x, 0 }
src_size := (glyph.bounds_size_scaled + atlas.glyph_padding) * glyph_buffer.over_sample
// src_size.y = ceil(src_size.y) // Note(Ed): Seems to improve hinting
src_size.y = ceil(src_size.y) * glyph_buffer.snap_glyph_height // Note(Ed): Seems to improve hinting
to_target_space( & src_position, & src_size, glyph_buffer_size )
blit_to_atlas : Draw_Call
@ -681,49 +754,28 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
)
}
if len(to_cache) > 0 do flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
profile_begin("generate_cached_draw_list: to_cache")
if enable_debug_vis_tyeps {
colour.r = 1.0
colour.g = 1.0
colour.b = 1.0
}
generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour )
profile_end()
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
for id, index in to_cache do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
}
profile_end()
generate_cached_draw_list :: #force_inline proc (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : Colour )
{
profile(#procedure)
call := Draw_Call_Default
call.pass = .Target
call.colour = colour
for id, index in sub_pack
{
profile("glyph")
call.start_index = u32(len(draw_list.indices))
quad := glyph_pack[id].draw_quad
blit_quad(draw_list,
quad.dst_pos, quad.dst_pos + quad.dst_scale,
quad.src_pos, quad.src_pos + quad.src_scale
)
call.end_index = u32(len(draw_list.indices))
append(& draw_list.calls, call)
}
}
profile_begin("generate_cached_draw_list: to_cache")
when ENABLE_DRAW_TYPE_VIS {
if enable_debug_vis_tyeps {
colour.r = 0.80
colour.g = 0.25
colour.b = 0.25
}
generate_cached_draw_list( draw_list, glyph_pack[:], to_cache, colour )
profile_end()
profile_begin("generate_cached_draw_list: to_cache")
when ENABLE_DRAW_TYPE_VIS {
colour.r = 1.0
colour.g = 1.0
colour.b = 1.0
}
generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour )
profile_end()
}
// Flush the content of the glyph_buffers draw lists to the main draw list
@ -747,7 +799,6 @@ flush_glyph_buffer_draw_list :: proc( #no_alias draw_list, glyph_buffer_draw_lis
(allocated_x ^) = 0
}
// ve_fontcache_clear_Draw_List
@(optimization_mode="favor_size")
clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
clear( & draw_list.calls )
@ -755,7 +806,7 @@ clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
clear( & draw_list.vertices )
}
// ve_fontcache_merge_Draw_List
// Helper used by flush_glyph_buffer_draw_list. Used to append all the content from the src draw list o the destination.
@(optimization_mode="favor_size")
merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) #no_bounds_check
{
@ -783,6 +834,8 @@ merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) #no_bounds_check
}
}
// Naive implmentation to merge passes that are equivalent and the following to be merged (b for can_merge_draw_calls) doesn't have a clear todo.
// Its intended for optimiztion passes to occur on a per-layer basis.
optimize_draw_list :: proc (draw_list: ^Draw_List, call_offset: int) #no_bounds_check
{
profile(#procedure)

View File

@ -1,4 +1,4 @@
package vefontcache
package vetext
when false {
// TODO(Ed): Freetype support

View File

@ -1,4 +1,9 @@
package vefontcache
package vetext
/*
Didn't want to splinter this into more files..
Just a bunch of utilities.
*/
import "base:runtime"
import "core:simd"
@ -6,6 +11,10 @@ import "core:math"
import core_log "core:log"
peek_array :: #force_inline proc "contextless" ( self : [dynamic]$Type ) -> Type {
return self[ len(self) - 1 ]
}
reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) {
raw := transmute( ^runtime.Raw_Dynamic_Array) self
raw.allocator = allocator
@ -43,7 +52,8 @@ to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte {
@(optimization_mode="favor_size")
djb8_hash :: #force_inline proc "contextless" ( hash : ^$Type, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + Type(value) }
Colour :: [4]f32
RGBA8 :: [4]f32
RGBAN :: [4]f32
Vec2 :: [2]f32
Vec2i :: [2]i32
Vec2_64 :: [2]f64

View File

@ -1,12 +1,18 @@
package vefontcache
package vetext
/*
Notes:
This is a minimal wrapper I originally did incase something than stb_truetype is introduced in the future.
Otherwise, its essentially 1:1 with it.
Freetype will do memory allocations and has an interface the user can implement.
That interface is not exposed from this parser but could be added to parser_init.
Freetype isn't really supported and its not a high priority (pretty sure its too slow).
~~Freetype will do memory allocations and has an interface the user can implement.~~
~~That interface is not exposed from this parser but could be added to parser_init.~~
STB_Truetype has macros for its allocation unfortuantely
STB_Truetype:
* Has macros for its allocation unfortuantely.
TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator.
Already wanted to do so anyway to evaluate the shape generation implementation.
*/
import "base:runtime"

View File

@ -1,5 +1,6 @@
package vefontcache
package vetext
import "base:builtin"
import "base:runtime"
import "core:hash"
ginger16 :: hash.ginger16
@ -50,34 +51,34 @@ append_soa :: proc {
}
ceil :: proc {
ceil_f16,
ceil_f16le,
ceil_f16be,
ceil_f32,
ceil_f32le,
ceil_f32be,
ceil_f64,
ceil_f64le,
ceil_f64be,
math.ceil_f16,
math.ceil_f16le,
math.ceil_f16be,
math.ceil_f32,
math.ceil_f32le,
math.ceil_f32be,
math.ceil_f64,
math.ceil_f64le,
math.ceil_f64be,
ceil_vec2,
}
clear :: proc {
clear_dynamic_array,
clear_map,
builtin.clear_dynamic_array,
builtin.clear_map,
}
floor :: proc {
floor_f16,
floor_f16le,
floor_f16be,
floor_f32,
floor_f32le,
floor_f32be,
floor_f64,
floor_f64le,
floor_f64be,
math.floor_f16,
math.floor_f16le,
math.floor_f16be,
math.floor_f32,
math.floor_f32le,
math.floor_f32be,
math.floor_f64,
math.floor_f64le,
math.floor_f64be,
floor_vec2,
}
@ -87,21 +88,30 @@ fill :: proc {
}
make :: proc {
make_dynamic_array,
make_dynamic_array_len,
make_dynamic_array_len_cap,
make_slice,
make_map,
make_map_cap,
builtin.make_dynamic_array,
builtin.make_dynamic_array_len,
builtin.make_dynamic_array_len_cap,
builtin.make_slice,
builtin.make_map,
builtin.make_map_cap,
}
make_soa :: proc {
make_soa_dynamic_array_len_cap,
make_soa_slice,
builtin.make_soa_dynamic_array_len_cap,
builtin.make_soa_slice,
}
peek :: proc {
peek_array,
}
pop_array :: proc {
builtin.pop,
// pop_safe,
}
resize :: proc {
resize_dynamic_array,
builtin.resize_dynamic_array,
}
vec2 :: proc {

View File

@ -1,4 +1,4 @@
package vefontcache
package vetext
/*
Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists and seems to be under active development as an alternative.
*/
@ -8,28 +8,51 @@ import "thirdparty:harfbuzz"
Shape_Key :: u32
/* A text whose codepoints have had their relevant glyphs and
associated data resolved for processing in a draw list generation stage.
Traditionally a shape only refers to resolving which glyph and
its position should be used for rendering.
For this library's case it also involes keeping any content
that does not have to be resolved up once again in a later stage of
preparing it for rendering.
Ideally the user should resolve this shape once and cache/store it on their side.
They have the best ability to avoid costly lookups to streamline
a hot path to only focusing on draw list generation that must be computed every frame.
For ease of use the cache does a relatively good job and only adds a
few hundred nano-seconds to resolve a shape's lookup from its source specification.
If your doing something heavy though (where there is thousands, or tens-of thousands)
your not going to be satisfied with keeping that in the iteration).
*/
Shaped_Text :: struct {
glyphs : [dynamic]Glyph,
positions : [dynamic]Vec2,
// bounds : [dynamic]Range2, // TODO(Ed): Test tracking/resolving the bounds here, its expensive to resolve at the draw list generation stage.
// region_kinds : [dynamic]Atlas_Region_Kind, // TODO(ed): test tracking/resolving the assigne atlas region here, for some reason as ^^.
end_cursor_pos : Vec2,
size : Vec2,
entry : ^Entry,
font : Font_ID,
}
// Ease of use cache, can handle thousands of lookups per frame with ease.
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Shaped_Text.
Shaped_Text_Cache :: struct {
storage : [dynamic]Shaped_Text,
state : LRU_Cache(Shape_Key),
next_cache_id : i32,
}
// Used by shaper_shape_text_cached, allows user to specify their own proc at compile-time without having to rewrite the caching implementation.
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
// Note(Ed): Not used..
Shaper_Kind :: enum {
Naive = 0,
Latin = 0,
Harfbuzz = 1,
}
// Not much here other than just keep track of a harfbuzz var and deciding to keep runtime config here used by the shapers.
Shaper_Context :: struct {
hb_buffer : harfbuzz.Buffer,
@ -37,6 +60,7 @@ Shaper_Context :: struct {
adv_snap_small_font_threshold : f32,
}
// Only used with harbuzz for now. Resolved during load_font for a font Entry.
Shaper_Info :: struct {
blob : harfbuzz.Blob,
face : harfbuzz.Face,
@ -71,6 +95,8 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info )
if info.blob != nil do harfbuzz.blob_destroy( info.blob )
}
// Recommended shaper. Very performant.
// TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal...
@(optimization_mode="favor_size")
shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text )
{
@ -141,7 +167,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry
}
if abs( font_px_size ) <= adv_snap_small_font_threshold
{
(position^) = ceil( position^ )
(position^) = ceil( position^ )
}
glyph_pos := position^
@ -244,6 +270,7 @@ shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context
shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output )
}
// Basic western alphabet based shaping. Not that much faster than harfbuzz if at all.
shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,
entry : Entry,
font_px_Size : f32,
@ -308,6 +335,12 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,
output.size.y = f32(line_count) * line_height
}
// Shapes are tracked by the library's context using the shape cache
// and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped.
// Thus this procedures cost will be proporitonal to how muh text it has to sift through.
// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-120 charactes long
// (and its very fast).
@(optimization_mode="favor_size")
shaper_shape_text_cached :: proc( text_utf8 : string,
ctx : ^Shaper_Context,
shape_cache : ^Shaped_Text_Cache,

View File

@ -3,12 +3,10 @@ A port of (https://github.com/hypernewbie/VEFontCache) to Odin.
See: https://github.com/Ed94/VEFontCache-Odin
*/
package vefontcache
package vetext
import "base:runtime"
// White: Cached Hit, Red: Cache Miss, Yellow: Oversized
ENABLE_DRAW_TYPE_VIS :: false
// See: mappings.odin for profiling hookup
DISABLE_PROFILING :: true
@ -21,12 +19,11 @@ Load_Font_Error :: enum(i32) {
}
Entry :: struct {
parser_info : Parser_Font_Info,
shaper_info : Shaper_Info,
id : Font_ID,
used : b32,
curve_quality : f32,
// snap_glyph_pos :
parser_info : Parser_Font_Info,
shaper_info : Shaper_Info,
id : Font_ID,
used : b32,
curve_quality : f32,
ascent : f32,
descent : f32,
@ -39,6 +36,23 @@ Entry_Default :: Entry {
curve_quality = 3,
}
// Ease of use encapsulation of common fields for a canvas space
Camera :: struct {
view : [dynamic]Vec2,
position : [dynamic]Vec2,
zoom : [dynamic]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,
@ -48,11 +62,14 @@ Context :: struct {
// 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,
atlas : Atlas,
shape_cache : Shaped_Text_Cache,
draw_list : Draw_List,
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,
@ -60,25 +77,35 @@ Context :: struct {
calls_offset : int,
},
debug_print : b32,
debug_print_verbose : b32,
// Note(Ed): Not really used anymore.
// debug_print : b32,
// debug_print_verbose : b32,
//TODO(Ed): Add a push/pop stack for the below
// Helps with hinting
snap_width : f32,
snap_height : f32,
// camera : Camera, // TODO(Ed): Add camera support
colour : Colour, // Color used in draw interface
cursor_pos : Vec2,
alpha_sharpen : f32, // Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges.
// Will enforce even px_size when drawing.
even_size_only : f32,
// Whether or not to snap positioning to the pixel of the view
// Helps with hinting
snap_to_view_extent : b32,
stack : Scope_Stack,
colour : RGBAN, // Color used in draw interface TODO(Ed): use the stack
cursor_pos : Vec2, // TODO(Ed): Review this, no longer used much at all... (still useful I guess)
// 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, // Used to upscale which font size is used to render (improves hinting)
px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged.
default_curve_quality : i32,
// White: Cached Hit, Red: Cache Miss, Yellow: Oversized (Will override user's colors when active)
enable_draw_type_visualization : f32,
default_curve_quality : i32,
}
Init_Atlas_Params :: struct {
@ -92,14 +119,22 @@ Init_Atlas_Params_Default :: Init_Atlas_Params {
}
Init_Glyph_Draw_Params :: struct {
// During the draw list generation stage when blitting to atlas, the quad wil 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,
buffer_glyph_limit : u32, // How many region.D glyphs can be drawn to the glyph render target buffer at once (worst case scenario)
batch_glyph_limit : u32, // How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list
// 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_height = true,
over_sample = 4,
draw_padding = Init_Atlas_Params_Default.glyph_padding,
shape_gen_scratch_reserve = 10 * 1024,
@ -108,23 +143,28 @@ Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params {
}
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 = false,
snap_glyph_position = true,
adv_snap_small_font_threshold = 0,
}
Init_Shape_Cache_Params :: struct {
capacity : u32,
reserve_length : u32,
// 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.
capacity : u32,
reserve : u32,
}
Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params {
capacity = 10 * 1024,
reserve_length = 10 * 1024,
capacity = 10 * 1024,
reserve = 10 * 1024,
}
//#region("lifetime")
@ -143,6 +183,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
// 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,
)
{
assert( ctx != nil, "Must provide a valid context" )
@ -230,10 +271,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1 {
stroage_entry := & shape_cache.storage[idx]
stroage_entry.glyphs, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve_length )
stroage_entry.glyphs, 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" )
stroage_entry.positions, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve_length )
stroage_entry.positions, 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" )
}
}
@ -384,6 +425,34 @@ shutdown :: proc( ctx : ^Context )
parser_shutdown( & ctx.parser_ctx )
}
// 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.glyphs)
clear(& stroage_entry.positions)
}
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)
@ -454,7 +523,7 @@ unload_font :: proc( ctx : ^Context, font : Font_ID )
//#endregion("lifetime")
//#region("drawing")
//#region("scoping")
configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) {
assert( ctx != nil )
@ -462,34 +531,125 @@ configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height :
ctx.snap_height = f32(snap_height)
}
get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos }
set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar }
set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar }
set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour }
/* 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.
The pkg_mapping.odin provides an explicit set of overloads for these:
scope()
push()
pop()
*/
@(deferred_none = 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, font : Font_ID ) { assert(ctx != nil); pop_array(& ctx.stack.font) }
@(deferred_none = 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, px_size : f32 ) { assert(ctx != nil); pop_array(& ctx.stack.font_size) }
@(deferred_none = 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_array(& ctx.stack.colour) }
@(deferred_none = 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_array(& ctx.stack.view) }
@(deferred_none = 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_array( & ctx.stack.position) }
@(deferred_none = pop_scale)
scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { append(& ctx.stack.scale, scale ) }
push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { append(& ctx.stack.scale, scale ) }
pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { pop_array(& ctx.stack.scale) }
@(deferred_none = 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_array(& ctx.stack.zoom) }
@(deferred_none = pop_camera)
scope_camera :: #force_inline proc( ctx : ^Context, camera : Camera ) {
append(& ctx.stack.view, camera.view )
append(& ctx.stack.position, camera.position )
append(& ctx.stack.zoom, camera.zoom )
}
push_camera :: #force_inline proc( ctx : ^Context, camera : Camera ) {
append(& ctx.stack.view, camera.view )
append(& ctx.stack.position, camera.position )
append(& ctx.stack.zoom, camera.zoom )
}
pop_camera :: #force_inline proc( ctx : ^Context ) {
pop_array(& ctx.stack.view )
pop_array(& ctx.stack.position)
pop_array(& ctx.stack.zoom )
}
//#endregion("scoping")
//#region("draw_list generation")
get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos }
// Does nothing when view is 1 or 0.
get_snapped_position :: #force_inline proc "contextless" ( ctx : Context, position : Vec2 ) -> (snapped_position : Vec2) {
snap_width := max(ctx.snap_width, 1)
snap_height := max(ctx.snap_height, 1)
snap_quotient := 1 / Vec2 { snap_width, snap_height }
snapped_position = position
snapped_position.x = ceil(position.x * snap_width ) * snap_quotient.x
snapped_position.y = ceil(position.y * snap_height) * snap_quotient.y
return
}
set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar }
set_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); ctx.colour = colour }
set_draw_type_visualization :: #force_inline proc( ctx : ^Context, should_enable : b32 ) { assert(ctx != nil); ctx.enable_draw_type_visualization = cast(f32) i32(should_enable); }
set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar }
set_snap_glyph_pos :: #force_inline proc( ctx : ^Context, should_snap : b32 ) {
assert(ctx != nil)
ctx.shaper_ctx.snap_glyph_position = should_snap
}
// Non-scoping context. The most fundamental interface-level draw shape procedure.
// (everything else is either batching/pipelining or quality of life warppers)
// Note: Prefer over draw_text_normalized_space if possible to resolve shape once persistently or at least once per frame or non-dirty state.
@(optimization_mode="favor_size")
draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string )
draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context,
font : Font_ID,
px_size : f32,
colour : RGBAN,
view : Vec2,
position : Vec2,
scale : Vec2,
zoom : f32,
shape : Shaped_Text
)
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
assert( len(text_utf8) > 0 )
adjusted_position := get_snapped_position( ctx, position )
entry := ctx.entries[ font ]
ctx.cursor_pos = {}
position := position
position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width
position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_sharpen
adjusted_colour := colour
adjusted_colour.a = 1.0 + ctx.alpha_sharpen
font_scale := parser_scale( entry.parser_info, px_size )
@ -497,141 +657,129 @@ draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32,
downscale := scale * (1 / ctx.px_scalar)
font_scale_upscale := parser_scale( entry.parser_info, px_upscale )
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
adjusted_colour,
entry,
px_upscale,
font_scale_upscale,
position,
downscale,
)
}
// Non-scoping context. The most fundamental interface-level draw text procedure.
// (everything else is either batching/pipelining or quality of life warppers)
// Note: shape lookup can be expensive on high call usage.
draw_text_normalized_space :: #force_inline proc( ctx : ^Context,
font : Font_ID,
px_size : f32,
colour : RGBAN,
view : Vec2,
position : Vec2,
scale : Vec2,
zoom : f32,
text_utf8 : string
)
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
assert( len(text_utf8) > 0 )
assert( ctx.snap_width > 0 && ctx.snap_height > 0 )
ctx.cursor_pos = {}
entry := ctx.entries[ font ]
adjusted_position := get_snapped_position( ctx, position )
colour := ctx.colour
colour.a = 1.0 + ctx.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, px_upscale )
shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
target_px_size,
target_font_scale,
shaper_shape_text_uncached_advanced
)
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
colour,
entry,
px_upscale,
font_scale_upscale,
position,
downscale,
ctx.snap_width,
ctx.snap_height
target_px_size,
target_font_scale,
adjusted_position,
target_scale,
)
}
draw_text_slice :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, texts : []string )
draw_shape :: #force_inline proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) {
// peek_position
}
draw_text :: #force_inline proc( ctx : ^Context, text_utf8 : string, position, scale : Vec2 ) {
}
Text_Layer_Elem :: struct {
text : string,
view : Vec2,
position : Vec2,
scale : Vec2,
font : Font_ID,
px_size : f32,
zoom : f32,
colour : Colour,
}
// Batch a layer of text. Use get_draw_list_layer to process the layer immediately after.
draw_text_layer :: #force_inline proc( ctx : ^Context, layer : []Text_Layer_Elem )
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
assert( len(texts) > 0 )
entry := ctx.entries[ font ]
for elem in layer
{
entry := ctx.entries[ elem.font ]
ctx.cursor_pos = {}
ctx.cursor_pos = {}
position := position
position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width
position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height
adjusted_position := get_snapped_position( ctx, position )
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_sharpen
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_sharpen
font_scale := parser_scale( entry.parser_info, px_size )
font_scale := parser_scale( entry.parser_info, px_size )
px_upscale := px_size * ctx.px_scalar
downscale := scale * (1 / ctx.px_scalar)
font_scale_upscale := parser_scale( entry.parser_info, px_upscale )
px_upscale := px_size * ctx.px_scalar
downscale := scale * (1 / ctx.px_scalar)
font_scale_upscale := parser_scale( entry.parser_info, px_upscale )
shapes := make( []Shaped_Text, len(texts) )
for str, id in texts {
assert( len(str) > 0 )
shape := shaper_shape_text_cached( str, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_upscale,
font_scale_upscale,
shaper_shape_text_uncached_advanced
)
shapes[id] = shape
shapes := make( []Shaped_Text, len(texts) )
for str, id in texts
{
assert( len(str) > 0 )
shape := shaper_shape_text_cached( str, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_upscale,
font_scale_upscale,
shaper_shape_text_uncached_advanced
)
shapes[id] = shape
}
generate_shapes_draw_list(ctx, font, colour, entry, px_upscale, font_scale_upscale, position, downscale, shapes )
}
generate_shapes_draw_list(ctx, font, colour, entry, px_upscale, font_scale_upscale, position, downscale, shapes )
}
// draw_text_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, text_utf8 : string )
// {
// 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 ]
// colour := ctx.colour
// colour.a = 1.0 + ctx.alpha_sharpen
// px_size_upscaled := px_size * ctx.px_scalar
// scale_downsample := scale * (1 / ctx.px_scalar)
// font_scale_upscale := parser_scale( entry.parser_info, px_size_upscaled )
// font_scale := parser_scale( entry.parser_info, -px_size )
// shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size, font_scale, shaper_shape_text_latin )
// ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height )
// }
// Resolve the shape and track it to reduce iteration overhead
@(optimization_mode="favor_size")
draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text )
{
profile(#procedure)
assert( ctx != nil )
assert( font >= 0 && int(font) < len(ctx.entries) )
position := position
position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width
position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height
entry := ctx.entries[ font ]
colour := ctx.colour
colour.a = 1.0 + ctx.alpha_sharpen
font_scale := parser_scale( entry.parser_info, px_size )
px_upscale := px_size * ctx.px_scalar
downscale := scale * (1 / ctx.px_scalar)
font_scale_upscale := parser_scale( entry.parser_info, px_upscale )
ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
colour,
entry,
px_upscale,
font_scale_upscale,
position,
downscale,
ctx.snap_width,
ctx.snap_height
)
}
// Resolve the shape and track it to reduce iteration overhead
// draw_text_shape_no_snap :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, position, scale : Vec2, shape : Shaped_Text )
// {
// profile(#procedure)
// assert( ctx != nil )
// assert( font >= 0 && int(font) < len(ctx.entries) )
// colour := ctx.colour
// colour.a = 1.0 + ctx.alpha_sharpen
// px_size_upscaled := px_size * ctx.px_scalar
// scale := scale * (1 / ctx.px_scalar)
// entry := ctx.entries[ font ]
// font_scale := parser_scale( entry.parser_info, px_size )
// ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, colour, entry, font_scale, position, scale, ctx.snap_width, ctx.snap_height )
// }
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 )
@ -662,10 +810,20 @@ flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) {
ctx.draw_layer.calls_offset = len(ctx.draw_list.calls)
}
//#endregion("drawing")
//#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
measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> (measured : Vec2)
{
// profile(#procedure)
@ -676,11 +834,12 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size
font_scale := parser_scale( entry.parser_info, px_size )
downscale := 1 / ctx.px_scalar
px_size_upscaled := px_size * ctx.px_scalar
font_scale_upscaled := parser_scale( entry.parser_info, px_size_upscaled )
shaped := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size_upscaled, font_scale_upscaled, shaper_shape_text_uncached_advanced )
return shaped.size
shaped := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, font, entry, px_size_upscaled, font_scale_upscaled, shaper_shape_text_uncached_advanced )
return shaped.size * downscale
}
get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 )
@ -689,12 +848,8 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID
assert( font >= 0 && int(font) < len(ctx.entries) )
entry := ctx.entries[ font ]
font_scale := parser_scale( entry.parser_info, px_size )
px_size_upscaled := px_size * ctx.px_scalar
downscale := 1 / px_size_upscaled
font_scale_upscaled := parser_scale( entry.parser_info, px_size_upscaled )
font_scale := parser_scale( entry.parser_info, px_size )
ascent = font_scale * entry.ascent
descent = font_scale * entry.descent
@ -720,7 +875,7 @@ shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size
return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_size,
px_size_upscaled,
font_scale_upscaled,
shaper_shape_text_latin
)
@ -740,13 +895,14 @@ shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si
return shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache,
font,
entry,
px_size,
px_size_upscaled,
font_scale_upscaled,
shaper_shape_text_uncached_advanced
)
}
// User handled shaped text. Will not be cached
// @(disabled = true)
shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape : ^Shaped_Text )
{
assert(false)
@ -754,6 +910,7 @@ shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID,
}
// User handled shaped text. Will not be cached
// @(disabled = true)
shape_text_advanced_uncahed :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape : ^Shaped_Text )
{
assert(false)
@ -761,31 +918,3 @@ shape_text_advanced_uncahed :: #force_inline proc( ctx : ^Context, font : Font_I
}
//#endregion("shaping")
// 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.glyphs)
clear(& stroage_entry.positions)
}
ctx.shape_cache.next_cache_id = 0
}

View File

@ -26,6 +26,9 @@ ui_screen_reload :: proc( screen_ui : ^UI_ScreenState ) {
ui_screen_tick :: proc( screen_ui : ^UI_ScreenState ) {
profile("Screenspace Imgui")
font_provider_set_px_scalar( app_config().text_size_screen_scalar )
// screen_ui.zoom_scale = 1.0
ui_graph_build( screen_ui )
ui_floating_manager( & screen_ui.floating )
ui_floating("Menu Bar", & screen_ui.menu_bar, ui_screen_menu_bar_builder)

View File

@ -496,7 +496,7 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
}
}
Font_Size_Screen_Scalar:
Text_Size_Screen_Scalar:
{
ui_settings_entry_inputbox( & font_size_screen_scalar_input, false, "settings_menu.font_size_screen_scalar", str_intern("Font: Size Screen Scalar"),
UI_TextInput_Policy {
@ -515,17 +515,46 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.font_size_screen_scalar = value
config.text_size_screen_scalar = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.font_size_screen_scalar)))
append( & input_str, to_runes(str_fmt("%v", config.text_size_screen_scalar)))
}
}
Font_Size_Canvas_Scalar:
Text_Snap_Glyph_Positions:
{
ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
disallow_decimal = false,
digit_min = 0,
digit_max = 1,
max_length = 1,
}
)
using font_size_canvas_scalar_input
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0, 1)
config.text_snap_glyph_positions = cast(b32) i32(value)
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar)))
}
}
Text_Size_Canvas_Scalar:
{
ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"),
UI_TextInput_Policy {
@ -544,19 +573,19 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.font_size_canvas_scalar = value
config.text_size_canvas_scalar = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.font_size_canvas_scalar)))
append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar)))
}
}
Text_Alpha_Sharpen:
{
ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"),
ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.text_alpha_sharpen", str_intern("Text: Alpha Sharpen"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
@ -572,14 +601,14 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.font_size_canvas_scalar = value
value = clamp(value, 0.001, 10.0)
config.text_alpha_sharpen = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.font_size_canvas_scalar)))
append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar)))
}
}
}

View File

@ -161,8 +161,10 @@ AppConfig :: struct {
color_theme : AppColorTheme,
font_size_screen_scalar : f32,
font_size_canvas_scalar : f32,
text_snap_glyph_positions : b32,
text_size_screen_scalar : f32,
text_size_canvas_scalar : f32,
text_alpha_sharpen : f32,
}
AppWindow :: struct {

View File

@ -152,8 +152,10 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
color_theme = App_Thm_Dusk
font_size_screen_scalar = 1.0
font_size_canvas_scalar = 1.0
text_snap_glyph_positions = true
text_size_screen_scalar = 2.0
text_size_canvas_scalar = 2.0
text_alpha_sharpen = 0.25
}
Desired_OS_Scheduler_MS :: 1
@ -354,7 +356,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (197).txt", allocator = persistent_slab_allocator())
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (1022).txt", allocator = persistent_slab_allocator())
debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/sokol_gp.h", allocator = persistent_slab_allocator())
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/sokol_gp.h", allocator = persistent_slab_allocator())
// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/ve_fontcache.h", allocator = persistent_slab_allocator())
alloc_error : AllocatorError; success : bool

View File

@ -57,7 +57,7 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In
screen_size := screen_extent * 2
// TODO(Ed): Eventually will be the viewport extents
ve.set_px_scalar( ve_ctx, app_config().font_size_canvas_scalar )
font_provider_set_px_scalar( app_config().text_size_canvas_scalar )
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
// ve.configure_snap( ve_ctx, 0, 0 )
@ -123,7 +123,7 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
screen_size := screen_extent * 2
screen_ratio := screen_size.x * ( 1.0 / screen_size.y )
ve.set_px_scalar( ve_ctx, app_config().font_size_screen_scalar )
font_provider_set_px_scalar( app_config().text_size_canvas_scalar )
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
render_screen_ui( screen_extent, screen_ui, ve_ctx, ve_render )
@ -265,14 +265,6 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
}
}
if true {
zoom_adjust_size := 16 * state.project.workspace.cam.zoom
over_sample := f32(state.config.font_size_canvas_scalar)
debug_text("font_size_canvas_scalar: %v", config.font_size_canvas_scalar)
ve_id, resolved_size := font_provider_resolve_draw_id( default_font, zoom_adjust_size * over_sample )
debug_text("font_size resolved: %v px", resolved_size)
}
render_text_layer( screen_extent, ve_ctx, ve_render )
}
@ -663,6 +655,7 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
if cam != nil {
draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color )
// draw_text_shape_pos_extent_zoomed( entry.shape, font, entry.font_size, entry.position, cam_offset, screen_size, screen_size_norm, cam.zoom, entry.color )
}
else {
draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color )
@ -987,10 +980,6 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo
zoom_adjust_size := size * zoom
// Over-sample font-size for any render under a camera
// over_sample : f32 = f32(config.font_size_canvas_scalar)
// zoom_adjust_size *= over_sample
pos_offset := (pos + cam_offset)
render_pos := ws_view_to_render_pos(pos)
normalized_pos := render_pos * screen_size_norm
@ -1007,11 +996,7 @@ draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : Fo
text_scale.y = clamp( text_scale.y, 0, screen_size.y )
}
// Down-sample back
// text_scale /= over_sample
color_norm := normalize_rgba8(color)
// ve.set_px_scalar( & get_state().font_provider_ctx.ve_ctx, config.font_size_canvas_scalar )
ve.set_colour( & get_state().font_provider_ctx.ve_ctx, color_norm )
ve.draw_text( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), normalized_pos, text_scale, text )
}

View File

@ -302,19 +302,20 @@ update :: proc( delta_time : f64 ) -> b32
// TODO(Ed): We need input buffer so that we can consume input actions based on the UI with priority
font_provider_set_px_scalar( app_config().font_size_screen_scalar )
ui_screen_tick( & get_state().screen_ui )
//region WorkspaceImgui Tick
if true
{
font_provider_set_px_scalar( app_config().font_size_canvas_scalar )
font_provider_set_px_scalar( app_config().text_size_canvas_scalar )
profile("Workspace Imgui")
// Creates the root box node, set its as the first parent.
ui_graph_build( & state.project.workspace.ui )
ui := ui_context
ui.zoom_scale = state.project.workspace.cam.zoom
frame_style_flags : UI_LayoutFlags = {
.Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,

View File

@ -140,6 +140,12 @@ font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_U
return
}
measure_text_shape :: #force_inline proc( shape : ShapedText ) -> Vec2
{
measured := ve.measure_shape_size( & get_state().font_provider_ctx.ve_ctx, shape )
return measured
}
measure_text_size :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2
{
ve_id, size := font_provider_resolve_draw_id( font, font_size )

View File

@ -123,6 +123,8 @@ UI_State :: struct {
// build_arenas : [2]Arena,
// build_arena : ^ Arena,
zoom_scale : f32,
built_box_count : i32,
caches : [2] HMapChained( UI_Box ),
@ -285,15 +287,6 @@ ui_graph_build_end :: proc( ui : ^UI_State )
{
if len(current.text) > 0 {
profile("text shape")
// app_window := get_state().app_window
// screen_extent := app_window.extent
// screen_size := screen_extent * 2
// screen_size_norm := 1 / screen_size
font_size_screen_scalar := app_config().font_size_screen_scalar
// over_sample : f32 = f32(get_state().config.font_size_canvas_scalar)
current.computed.text_shape = shape_text_cached( current.text, current.style.font, current.layout.font_size, 1.0 )
}
ui_box_compute_layout( current )

View File

@ -73,7 +73,7 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
text_size : Vec2
if len(box.text) > 0
{
text_size = computed.text_shape.size
text_size = measure_text_shape( computed.text_shape )
// if layout.font_size == computed.text_size.y {
// text_size = computed.text_size
// }