Starting the process of porting VEFontCache
This commit is contained in:
parent
33ddd420b7
commit
f99157aae5
26
Readme.md
26
Readme.md
@ -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
|
||||
|
69
code/font/VEFontCache/LRU.odin
Normal file
69
code/font/VEFontCache/LRU.odin
Normal 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")
|
||||
|
||||
}
|
||||
|
||||
|
@ -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 )
|
||||
|
||||
|
||||
}
|
||||
|
34
code/font/VEFontCache/atlas.odin
Normal file
34
code/font/VEFontCache/atlas.odin
Normal 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,
|
||||
}
|
32
code/font/VEFontCache/draw.odin
Normal file
32
code/font/VEFontCache/draw.odin
Normal 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),
|
||||
}
|
84
code/font/VEFontCache/mappings.odin
Normal file
84
code/font/VEFontCache/mappings.odin
Normal 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")
|
19
code/font/VEFontCache/parser.odin
Normal file
19
code/font/VEFontCache/parser.odin
Normal 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
|
||||
}
|
||||
|
14
code/font/VEFontCache/shaper.odin
Normal file
14
code/font/VEFontCache/shaper.odin
Normal 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,
|
||||
}
|
||||
|
3
code/sectr/font/provider_VEFontCache.odin
Normal file
3
code/sectr/font/provider_VEFontCache.odin
Normal file
@ -0,0 +1,3 @@
|
||||
package sectr
|
||||
|
||||
import "codebase:font/VEFontCache"
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user