Starting the process of porting VEFontCache

This commit is contained in:
Edward R. Gonzalez 2024-06-02 17:29:44 -04:00
parent 33ddd420b7
commit f99157aae5
11 changed files with 491 additions and 18 deletions

View File

@ -28,26 +28,32 @@ The dependencies are:
Major 'codebase modules':
* Engine : Main loop, logging, client interface for host, etc
* App : General app config & contextual state
* Engine : client interface for host, tick, update, rendering.
* Has the following definitions: startup, shutdown, reload, tick, clean_frame (which host hooks up to when managing the client dll)
* Env : Core Memory & State definition + orchestration
* Will handle async ops.
* Font Provider : Manages fonts.
* When loading fonts, the provider currently uses raylib to generate bitmap glyth sheets for a range of font sizes at once.
* Goal is to eventually render using SDF shaders.
* Bulk of visualization must be able to render text effectively
* Going to use some form of caching.
* Needs to be able to scale text in-realtime to linear values.
* Grime : Name speaks for itself, stuff not directly related to the target features to iterate upon for the prototype.
* Defining dependency aliases or procedure overload tables, rolling own allocator, data structures, etc.
* Input : All human input related features
* Base input features (polling & related) are platform abstracted from raylib
* Input Events
* Parser : AST generation, editing, and serialization. A 1/3 of this prototype will most likely be this alone.
* Project : Encpasulation of user config/state separate from persistent app config/state as a 'project'
* Manages the codebase (program model database)
* Base input features (polling & related) are platform abstracted from sokol_app
* Entirely user rebindable
* Parsers
* AST generation, editing, and serialization.
* Parsers for different levels of "synatitic & semantic awareness", Formatting -> Domain Specific AST
* Figure out pragmatic transformations between ASTs.
* Project : Encpasulation of user config/context/state separate from persistent app's
* Manages the codebase (database & model view controller)
* Manages workspaces : View compositions of the codebase
* UI : Core graphic user interface framework, AST visualzation & editing, backend visualization
* Will most likely be the bulk of this prototype.
* PIMGUI (Persistent Immediate Mode User Interface)
* Auto-layout
* Supports heavy procedural generation of box widgets
* Viewports
* Docking/Tiling, Floating, Canvas
Due to the nature of the prototype there are 'sub-groups' such as the codebase being its own ordeal as well as the workspace.
They'll be elaborated in their own documentation

View File

@ -0,0 +1,69 @@
package VEFontCache
/*
The choice was made to keep the LUR cache implementation as close to the original as possible.
*/
PoolListIter :: u32
PoolListValue :: u64
PoolListItem :: struct {
prev : PoolListIter,
next : PoolListIter,
value : PoolListValue,
}
PoolList :: struct {
items : Array( PoolListItem ),
free_list : Array( PoolListIter ),
front : PoolListIter,
back : PoolListIter,
size : i32,
capacity : i32,
}
pool_list_init :: proc( pool : ^PoolList, capacity : u32 )
{
error : AllocatorError
pool.items, error = make( Array( PoolListItem ), u64(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array")
pool.free_list, error = make( Array( PoolListIter ), u64(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array")
pool.capacity = i32(capacity)
for id in 0 ..< capacity do pool.free_list.data[id] = id
}
pool_list_push_front :: proc( pool : ^PoolList, value : PoolListValue )
{
using pool
if size >= capacity do return
assert( free_list.num > 0 )
assert( free_list.num == u64(capacity - size) )
id := array_back( free_list )
}
LRU_Link :: struct {
value : i32,
ptr : PoolListIter,
}
LRU_Cache :: struct {
capacity : i32,
table : HMapChained(LRU_Link),
key_queue : PoolList,
}
LRU_init :: proc( cache : ^LRU_Cache, capacity : u32 )
{
error : AllocatorError
cache.capacity = i32(capacity)
cache.table, error = make( HMapChained(LRU_Link), uint(capacity) )
assert( error != .None, "VEFontCache.LRU_init : Failed to allocate cache's table")
}

View File

@ -1,3 +1,204 @@
/*
A port of (https://github.com/hypernewbie/VEFontCache) to Odin.
Status:
This port is heavily tied to the grime package in SectrPrototype.
TODO(Ed): Make an idiomatic port of this for Odin (or just dupe the data structures...)
*/
package VEFontCache
Font_ID :: i64
Glyph :: i32
Colour :: [4]f32
Vec2 :: [2]f32
Vec2i :: [2]u32
AtlasRegionKind :: enum {
A = 0,
B = 1,
C = 2,
D = 3
}
Vertex :: struct {
pos : Vec2,
u, v : f32,
}
// GlyphDrawBuffer :: struct {
// over_sample : Vec2,
// batch : i32,
// width : i32,
// height : i32,
// padding : i32,
// }
ShapedText :: struct {
Glyphs : Array(Glyph),
Positions : Array(Vec2),
end_cursor_pos : Vec2,
}
ShapedTextCache :: struct {
storage : Array(ShapedText),
state : LRU_Cache,
next_cache_id : i32,
}
Entry :: struct {
parser_info : ParserInfo,
shaper_info : ShaperInfo,
id : Font_ID,
used : b32,
size : f32,
size_scale : f32,
}
Entry_Default :: Entry {
id = 0,
used = false,
size = 24.0,
size_scale = 1.0,
}
Context :: struct {
backing : Allocator,
parser_kind : ParserKind,
parser_ctx : ParserContext,
shaper_ctx : ShaperContext,
entries : Array(Entry),
temp_path : Array(Vec2),
temp_codepoint_seen : HMapChained(bool),
snap_width : u32,
snap_height : u32,
colour : Colour,
cursor_pos : Vec2,
draw_list : DrawList,
atlas : Atlas,
shape_cache : ShapedTextCache,
text_shape_adv : b32,
}
Module_Ctx :: Context
InitAtlasRegionParams :: struct {
width : u32,
height : u32,
offset : Vec2i,
}
InitAtlasParams :: struct {
width : u32,
height : u32,
glyph_padding : u32,
region_a : InitAtlasRegionParams,
region_b : InitAtlasRegionParams,
region_c : InitAtlasRegionParams,
region_d : InitAtlasRegionParams,
}
InitAtlasParams_Default :: InitAtlasParams {
width = 4 * Kilobyte,
height = 2 * Kilobyte,
glyph_padding = 1,
}
InitGlyphDrawParams :: struct {
over_sample : Vec2i,
buffer_batch : u32,
padding : u32,
}
InitGlyphDrawParams_Default :: InitGlyphDrawParams {
over_sample = { 4, 4 },
buffer_batch = 4,
padding = InitAtlasParams_Default.glyph_padding,
}
InitShapeCacheParams :: struct {
capacity : u32,
reserve_length : u32,
}
InitShapeCacheParams_Default :: InitShapeCacheParams {
capacity = 256,
reserve_length = 64,
}
init :: proc( ctx : ^Context,
allocator := context.allocator,
atlas_params := InitAtlasParams_Default,
glyph_draw_params := InitGlyphDrawParams_Default,
shape_cache_params := InitShapeCacheParams_Default,
advance_snap_smallfont_size : u32 = 12,
entires_reserve : u32 = Kilobyte,
temp_path_reserve : u32 = Kilobyte,
temp_codepoint_seen_reserve : u32 = 4 * Kilobyte,
)
{
assert( ctx != nil, "Must provide a valid context" )
using ctx
ctx.backing = allocator
context.allocator = ctx.backing
error : AllocatorError
entries, error = make( Array(Entry), u64(entires_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate entries")
temp_path, error = make( Array(Vec2), u64(temp_path_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate temp_path")
temp_codepoint_seen, error = make( HMapChained(bool), uint(temp_codepoint_seen_reserve) )
assert(error == .None, "VEFontCache.init : Failed to allocate temp_path")
draw_list.vertices, error = make( Array(Vertex), 4 * Kilobyte )
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices")
draw_list.indices, error = make( Array(u32), 8 * Kilobyte )
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.indices")
draw_list.calls, error = make( Array(DrawCall), 512 )
assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.calls")
init_atlas_region :: proc( region : ^AtlasRegion, params : InitAtlasParams, region_params : InitAtlasRegionParams ) {
using region
next_idx = 0;
width = region_params.width
height = region_params.height
size = {
params.width / 4,
params.height / 2,
}
capacity = {
size.x / width,
size.y / height,
}
offset = region_params.offset
error : AllocatorError
// state.cache, error = make( HMapChained(LRU_Link), uint(capacity.x * capacity.y) )
// assert( error == .None, "VEFontCache.init_atlas_region : Failed to allocate state.cache")
LRU_init( & state, capacity.x * capacity.y )
}
init_atlas_region( & atlas.region_a, atlas_params, atlas_params.region_a )
init_atlas_region( & atlas.region_b, atlas_params, atlas_params.region_b )
init_atlas_region( & atlas.region_c, atlas_params, atlas_params.region_c )
init_atlas_region( & atlas.region_d, atlas_params, atlas_params.region_d )
}

View File

@ -0,0 +1,34 @@
package VEFontCache
GlyphUpdateBatch :: struct {
update_batch_x : i32,
clear_draw_list : DrawList,
draw_list : DrawList,
}
AtlasRegion :: struct {
state : LRU_Cache,
width : u32,
height : u32,
size : Vec2i,
capacity : Vec2i,
offset : Vec2i,
next_idx : u32,
}
Atlas :: struct {
width : u32,
height : u32,
glyph_pad : u16,
region_a : AtlasRegion,
region_b : AtlasRegion,
region_c : AtlasRegion,
region_d : AtlasRegion,
using glyph_update_batch : GlyphUpdateBatch,
}

View File

@ -0,0 +1,32 @@
package VEFontCache
FrameBufferPass :: enum {
Glyph = 1,
Atlas = 2,
Target = 3,
Target_Unchanged = 4,
}
DrawCall :: struct {
pass : u32,
start_index : u32,
end_index : u32,
clear_before_draw : b32,
region : AtlasRegionKind,
colour : [4]f32,
}
DrawCall_Default :: DrawCall {
pass = 0,
start_index = 0,
end_index = 0,
clear_before_draw = false,
region = .A,
colour = { 1.0, 1.0, 1.0, 1.0 }
}
DrawList :: struct {
vertices : Array(Vertex),
indices : Array(u32),
calls : Array(DrawCall),
}

View File

@ -0,0 +1,84 @@
package VEFontCache
import "core:mem"
Kilobyte :: mem.Kilobyte
Allocator :: mem.Allocator
AllocatorError :: mem.Allocator_Error
import "codebase:grime"
// asserts
ensure :: grime.ensure
verify :: grime.verify
// container
Array :: grime.Array
array_init :: grime.array_init
array_append :: grime.array_append
array_append_at :: grime.array_append_at
array_back :: grime.array_back
array_clear :: grime.array_clear
array_free :: grime.array_free
array_remove_at :: grime.array_remove_at
array_to_slice :: grime.array_to_slice
array_to_slice_cpacity :: grime.array_to_slice_capacity
array_underlying_slice :: grime.array_underlying_slice
HMapChained :: grime.HMapChained
hmap_chained_init :: grime.hmap_chained_init
// Pool :: grime.Pool
StackFixed :: grime.StackFixed
stack_clear :: grime.stack_clear
stack_push :: grime.stack_push
stack_pop :: grime.stack_pop
stack_peek_ref :: grime.stack_peek_ref
stack_peek :: grime.stack_peek
stack_push_contextless :: grime.stack_push_contextless
//#region("Proc overload mappings")
append :: proc {
grime.array_append_array,
grime.array_append_slice,
grime.array_append_value,
}
append_at :: proc {
grime.array_append_at_slice,
grime.array_append_at_value,
}
clear :: proc {
array_clear,
}
delete :: proc {
array_free,
}
make :: proc {
array_init,
hmap_chained_init,
}
remove_at :: proc {
array_remove_at,
}
to_slice :: proc {
array_to_slice,
}
underlying_slice :: proc {
array_underlying_slice,
}
//#endregion("Proc overload mappings")

View File

@ -0,0 +1,19 @@
package VEFontCache
import stbtt "vendor:stb/truetype"
import freetype "thirdparty:freetype"
ParserKind :: enum u32 {
stb_true_type,
freetype,
}
ParserInfo :: struct #raw_union {
stbtt_info : stbtt.fontinfo,
freetype_info : freetype.Face
}
ParserContext :: struct {
ft_library : freetype.Library
}

View File

@ -0,0 +1,14 @@
package VEFontCache
import "thirdparty:harfbuzz"
ShaperContext :: struct {
hb_buffer : harfbuzz.Buffer,
}
ShaperInfo :: struct {
blob : harfbuzz.Blob,
face : harfbuzz.Face,
font : harfbuzz.Font,
}

View File

@ -0,0 +1,3 @@
package sectr
import "codebase:font/VEFontCache"

View File

@ -138,16 +138,24 @@ push-location $path_root
write-host "`nBuilding Sectr Prototype`n"
$package_grime = join-path $path_code 'grime'
$module_host = join-path $path_code 'host'
$module_sectr = join-path $path_code 'sectr'
$path_font = join-path $path_code 'font'
$package_grime = join-path $path_code 'grime'
$package_fstash = join-path $path_font 'fontstash'
$package_VEFontCache = join-path $path_font 'VEFontCache'
$module_host = join-path $path_code 'host'
$module_sectr = join-path $path_code 'sectr'
if ($force){
mark-ModuleDirty $package_fstash
mark-ModuleDirty $package_VEFontCache
mark-ModuleDirty $package_grime
mark-ModuleDirty $module_sectr
mark-ModuleDirty $module_host
}
$pkg_grime_dirty = check-ModuleForChanges $package_grime
$pkg_fstash_dirty = check-ModuleForChanges $package_fstash
$pkg_VEFontCache_dirty = check-ModuleForChanges $package_VEFontCache
$pkg_grime_dirty = check-ModuleForChanges $package_grime
$pkg_collection_codebase = 'codebase=' + $path_code
$pkg_collection_thirdparty = 'thirdparty=' + $path_thirdparty
@ -165,7 +173,7 @@ push-location $path_root
function build-sectr
{
$should_build = (check-ModuleForChanges $module_sectr) -or $pkg_grime_dirty
$should_build = (check-ModuleForChanges $module_sectr) -or $pkg_grime_dirty -or $pkg_fstash_dirty -or $pkg_VEFontCache_dirty
if ( -not( $should_build)) {
write-host 'Skipping sectr build, module up to date'
return $module_unchanged

View File

@ -8,13 +8,15 @@ $path_toolchain = join-path $path_root 'toolchain'
$url_backtrace_repo = 'https://github.com/Ed94/back.git'
$url_freetype = 'https://github.com/Ed94/odin-freetype.git'
$url_harfbuzz = 'https://github.com/Ed94/odin_harfbuzz.git'
$url_ini_parser = 'https://github.com/laytan/odin-ini-parser.git'
$url_odin_repo = 'https://github.com/Ed94/Odin.git'
$url_sokol = 'https://github.com/Ed94/sokol-odin.git'
$url_sokol_tools = 'https://github.com/floooh/sokol-tools-bin.git'
$path_backtrace = join-path $path_thirdparty 'backtrace'
$path_freetype = join-path $path_thirdparty 'freetype'
$path_freetype = join-path $path_thirdparty 'freetype'
$path_harfbuzz = join-path $path_thirdparty 'harfbuzz'
$path_ini_parser = join-path $path_thirdparty 'ini'
$path_odin = join-path $path_toolchain 'Odin'
$path_sokol = join-path $path_thirdparty 'sokol'
@ -85,8 +87,9 @@ function Update-GitRepo
push-location $path_thirdparty
Update-GitRepo -path $path_odin -url $url_odin_repo -build_command '.\scripts\build.ps1'
Update-GitRepo -path $path_sokol -url $url_sokol -build_command '.\build_windows.ps1'
Update-GitRepo -path $path_odin -url $url_odin_repo -build_command '.\scripts\build.ps1'
Update-GitRepo -path $path_sokol -url $url_sokol -build_command '.\build_windows.ps1'
Update-GitRepo -path $path_harfbuzz -url $url_harfbuzz -build_command '.\scripts\build.ps1'
function clone-gitrepo { param( [string] $path, [string] $url )
if (test-path $path) {