WIP (Broken) docs and huge changes
This commit is contained in:
parent
a9080fe1f3
commit
3a245a1e9b
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package vefontcache
|
||||
package vetext
|
||||
|
||||
when false {
|
||||
// TODO(Ed): Freetype support
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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
|
||||
// }
|
||||
|
Loading…
x
Reference in New Issue
Block a user