Compare commits

...

41 Commits

Author SHA1 Message Date
Ed_
ff91e41da9 convert all region/endregion directives to the comment signature used with editor plugins 2025-06-30 09:26:17 -04:00
Ed_
74567ae98a adding some stuff from watl but not ready to use yet 2025-06-28 20:57:05 -04:00
Ed_
cf7151a1ce misc changes
not worth comment ing on...
2025-06-28 20:56:49 -04:00
Ed_
bf5ecd0e0d adjust build script to odin_sectr.exe (renamed when compiler builds) 2025-06-28 20:56:11 -04:00
Ed_
54db9a7d57 misc updates to dependencies
removed freetype, updated vefontcache to latest and sokol + sokol gp
2025-06-26 23:27:05 -04:00
Ed_
3fd4e139d9 gitignore fixes 2025-06-26 22:15:43 -04:00
Ed_
01e989adc8 update gitignore 2025-06-26 21:46:32 -04:00
Ed_
29130cb367 old stuff
Planning to come back to this and eval some state.
Not ready to fully come back still out learning from the past.
2025-06-26 21:44:30 -04:00
Ed_
5b0878d14d update to latest vefontcache 2025-02-13 19:47:19 -05:00
Ed_
85dbaa37b9 updating to latest VEFontCache... tested 10k draw call target (worked) 2025-02-13 19:12:13 -05:00
Ed_
0f5f9c18b1 Update readme, build scripts
Add incremental build check for stb truetype lib
2025-02-01 09:29:31 -05:00
Ed_
07cd28226f update to latest 2025-01-13 20:44:07 -05:00
Ed_
0cd2d84c64 Simplified text rendering code (since its now much of the heavily lifting is all on VEFontCache) 2025-01-13 01:08:02 -05:00
Ed_
7680290650 vefontcache fixes 2025-01-13 00:55:42 -05:00
Ed_
fd424c94bb Fixed bug wth vefoncache storage_entry.visible, added building stb_truetype to dep update 2025-01-12 22:03:38 -05:00
Ed_
9d5ac7b0d2 got it to compile with vefontcache changes, runtime issues.. 2025-01-12 16:41:55 -05:00
Ed_
9da0e73d3b Misc changes to engine and shaders 2025-01-12 14:01:11 -05:00
Ed_
bc47b37a46 Update vefontcache to latest 2025-01-12 14:00:58 -05:00
Ed_
a869ebab69 Add custom stb_truetype package/lib to thirdparty for vefontcache update 2025-01-12 14:00:43 -05:00
Ed_
22cf5c653b Update readme 2025-01-10 11:01:57 -05:00
Ed_
e23935db5b More cleanup, preparing VEFontCache for public repo 2025-01-10 09:32:19 -05:00
Ed_
50dd6130c8 Working towards getting the library to an alpha release state 2025-01-10 01:54:18 -05:00
Ed_
488e5ba67f shaper_shape_text_latin was not resolving atlas info and bounds + lru poollist touchup 2025-01-09 23:53:59 -05:00
Ed_
9ab7bf78c6 made draw type vis a compile time option
Didn't want to deal with the branchless math trial and error...
2025-01-09 23:48:43 -05:00
Ed_
b5fdc02b7d Updates to client api settings and memory suage of sokol_gp 2025-01-09 23:37:15 -05:00
Ed_
c114624eee fixes for: Atlas-Region B clear-region on caching glyph, batching, & tuning default values for performance
Need to fix the debug vis for the library
2025-01-09 23:36:39 -05:00
Ed_
08a8b4b823 Insane perfomrance after tuning the batch and caches. 2025-01-09 14:54:59 -05:00
Ed_
b4abde1094 Misc: VFontCache perf and features, exposing config on prototype side... 2025-01-09 13:23:35 -05:00
Ed_
ce84652417 More offloading to shaper, seeing if its better todo some math in loop... 2025-01-08 08:38:06 -05:00
Ed_
18d8735c54 Preparing to attempt to offload various metric calculations for a shape's glyphs to the shape itself from the draw list generator 2025-01-07 22:24:23 -05:00
Ed_
fa627b4c4a LRU proper casts id to Pool_ListIter (don't assume i32) 2025-01-07 22:23:24 -05:00
Ed_
6010dd1590 Fix for blitting 2025-01-07 22:10:06 -05:00
Ed_
7dee697103 partially restoring old order to try to identity the regression with blitting to atlas... 2025-01-07 17:52:42 -05:00
Ed_
6e01c39899 Builds again has on text rendering (makes sense) 2025-01-07 10:17:46 -05:00
Ed_
3a245a1e9b WIP (Broken) docs and huge changes 2025-01-07 03:06:12 -05:00
Ed_
a9080fe1f3 LRU cache now as templated key type
Was testing to see if 16-bit cach emade a diff for the glyphs (it did not)
2025-01-06 21:18:39 -05:00
Ed_
bf38087d8e Remove rune tracking for string cache, + vecache deharcoding atlas...
Shapers as well
2025-01-06 17:21:16 -05:00
Ed_
0350a0c282 Remove rune tracking for string cache, + vecache changes
Getting ready to de-hardcode vefontcache shaders
2025-01-06 14:12:55 -05:00
Ed_
f1f98ffafb Removed usage of procedure using statements from vefontcache 2025-01-06 11:23:30 -05:00
Ed_
840e6053ff WIP - VEFontCache: Working on getting font size usage and super-sampling via scaling working on library side... 2025-01-06 11:00:55 -05:00
Ed_
c0b439bc30 Update readme and build script 2025-01-06 01:20:27 -05:00
82 changed files with 43268 additions and 2505 deletions

View File

@ -21,7 +21,6 @@ indent_style = tab
indent_size = 2
charset = utf-8
[*.{natvis, natstepfilter}]
indent_style = tab
indent_size = 4

33
.gitignore vendored
View File

@ -1,13 +1,28 @@
build/**
*.exe
# thirdparty/**
logs
.ark
logs*.zip
code_flattened
Sectr.sublime-project
Sectr.sublime-workspace
# binaries
build/**
*.exe
# folders
assets/TX-02-1WN9N6Q8
thirdparty/backtrace
thirdparty/harfbuzz
thirdparty/ini
thirdparty/sokol
thirdparty/sokol-tools
toolchain/**
# logs
logs
logs*.zip
# toolchain
.ark
ols.json
.vscode/settings.json
thirdparty
# toolchain
*.spall
sectr.user

View File

@ -16,45 +16,50 @@ https://github.com/user-attachments/assets/0a895478-4a04-4ac6-a0ac-5355ff87ef4e
The dependencies are:
* Odin Compiler (Slightly custom [fork](https://github.com/Ed94/Odin))
* Added #region, #endregion directives support for editors
* I added support for 'monlithic packages' or 'uniform-across-subdirectories packages'. It allows me to organize the main package with sub-directories.
* Odin repo's base, core, and vendor(raylib) libaries
* An ini parser
* Added the ability to debug using statements on structs (fields get dumped to the stack as ptr refs)
* Remove implicit assignments for container allocators in the Base and Core packages
* I did not enjoy bug hunting a memory corruption because I mistakenly didn't properly initialize a core container with their designated initiatizer: new, make, or init.
* See fork Readme for which procedures were changed..
* Odin repo's base, core, and some of vendor
* [VEFontCache-Odin](https://github.com/Ed94/VEFontCache-Odin): Text rendering & shaping library created for this prototype
* [stb_truetype-odin](https://github.com/Ed94/stb_truetype-odin): Variant of the stb/truetype package in odin's vendor collection made for VEFontCache-Odin
* [harfbuzz-odin](https://github.com/Ed94/harfbuzz-odin): Custom repo with tailor made bindings for VEFontCache-Odin
* [sokol-odin (Sectr Fork)](https://github.com/Ed94/sokol-odin)
* [sokol-tools](https://github.com/floooh/sokol-tools)
* Powershell (if you want to use my build scripts)
* backtrace (not used yet)
* freetype (not used yet)
* harfbuzz
* sokol
* sokol-tools
* Powershell (if you want to use my build scripts)
* Eventually some config parser (maybe I'll use metadesk, or [ini](https://github.com/laytan/odin-ini-parser))
The project is so far in a "codebase boostrapping" phase. Most the work being done right now is setting up high perfomrance linear zoom rendering for text and UI.
The project is so far in a "codebase boostrapping" phase. Most the work being done right now is setting up high performance linear zoom rendering for text and UI.
Text has recently hit sufficient peformance targets, and now inital UX has become the focus.
The project's is organized into 2 modules sectr_host & sectr.
The project's is organized into 2 runtime modules sectr_host & sectr.
The host module loads the main module & its memory. Hot-reloading it's dll when it detects a change.
Major 'codebase modules':
Codebase organization:
* App : General app config & contextual state
* Engine : client interface for host, tick, update, rendering.
* App: General app config, state, and operations.
* 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)
* Will handle async ops.
* Font Provider : Manages fonts.
* 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.
* Font Provider: Manages fonts.
* Bulk of implementation maintained as a separate library: [VEFontCache-Odin](https://github.com/Ed94/VEFontCache-Odin)
* 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
* Input: All human input related features
* Base input features (polling & related) are platform abstracted from sokol_app
* Entirely user rebindable
* Parsers
* Math: The usual for 2D/3D.
* 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
* 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
* UI: Core graphic user interface framework, AST visualzation & editing, backend visualization
* PIMGUI (Persistent Immediate Mode User Interface)
* Auto-layout
* Supports heavy procedural generation of box widgets
@ -72,3 +77,42 @@ They'll be elaborated in their own documentation
![img](docs/assets/sectr_host_2024-05-11_22-34-15.png)
![img](docs/assets/sectr_host_2024-05-15_03-32-36.png)
![img](docs/assets/Code_2024-05-21_23-15-16.gif)
## Notes
Due to bug with custom ols click file in root of sectr to get full symbol reflection setup on the monolithic package.
For support for regions - grab a region extension and use the following regex:
VS-Code Explicit Folding:
```json
"explicitFolding.rules": {
"odin": [
{
"beginRegex": "region\\b",
"endRegex": "endregion\\b"
},
{
"beginRegex": "{",
"endRegex": "}"
},
{
"beginRegex": "\\[",
"endRegex": "\\]"
},
{
"beginRegex": "\\(",
"endRegex": "\\)"
},
{
"beginRegex": "\"",
"endRegex": "\""
},
{
"beginRegex": "/\\*",
"endRegex": "\\*/"
}
]
},
```

View File

@ -1,4 +1,4 @@
VEFontCache Odin Port
VEFontCache Odin
Copyright 2024 Edward R. Gonzalez
This project is based on Vertex Engine GPU Font Cache

View File

@ -1,22 +1,34 @@
package vefontcache
/*
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"
// 16-bit hashing was attempted, however it seems to get collisions with djb8_hash_16
LRU_Fail_Mask_16 :: 0xFFFF
LRU_Fail_Mask_32 :: 0xFFFFFFFF
LRU_Fail_Mask_64 :: 0xFFFFFFFFFFFFFFFF
Pool_ListIter :: i32
Pool_ListValue :: u32
Pool_List_Item :: struct {
Pool_List_Item :: struct( $V_Type : typeid ) #packed {
// Pool_List_Item :: struct( $V_Type : typeid ) {
prev : Pool_ListIter,
next : Pool_ListIter,
value : Pool_ListValue,
value : V_Type,
}
Pool_List :: struct {
items : [dynamic]Pool_List_Item,
Pool_List :: struct( $V_Type : typeid) {
items : [dynamic]Pool_List_Item(V_Type),
free_list : [dynamic]Pool_ListIter,
front : Pool_ListIter,
back : Pool_ListIter,
@ -25,145 +37,144 @@ Pool_List :: struct {
dbg_name : string,
}
pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = "" )
pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : string = "" )
{
error : Allocator_Error
pool.items, error = make( [dynamic]Pool_List_Item, int(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array")
pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) )
assert( error == .None, "VEFontCache.pool_list_inits: Failed to allocate items array")
resize( & pool.items, capacity )
pool.free_list, error = make( [dynamic]Pool_ListIter, len = 0, cap = int(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array")
assert( error == .None, "VEFontCache.pool_list_init: Failed to allocate free_list array")
resize( & pool.free_list, capacity )
pool.capacity = capacity
pool.dbg_name = dbg_name
using pool
for id in 0 ..< capacity {
free_list[id] = i32(id)
items[id] = {
for id in 0 ..< pool.capacity {
pool.free_list[id] = Pool_ListIter(id)
pool.items[id] = {
prev = -1,
next = -1,
}
}
front = -1
back = -1
pool.front = -1
pool.back = -1
}
pool_list_free :: proc( pool : ^Pool_List ) {
pool_list_free :: proc( pool : ^Pool_List($V_Type) ) {
delete( pool.items)
delete( pool.free_list)
}
pool_list_reload :: proc( pool : ^Pool_List, allocator : Allocator ) {
pool_list_reload :: proc( pool : ^Pool_List($V_Type), allocator : Allocator ) {
reload_array( & pool.items, allocator )
reload_array( & pool.free_list, allocator )
}
pool_list_clear :: proc( pool: ^Pool_List )
pool_list_clear :: proc( pool: ^Pool_List($V_Type) )
{
using pool
clear(& items)
clear(& free_list)
clear(& pool.items)
clear(& pool.free_list)
resize( & pool.items, cap(pool.items) )
resize( & pool.free_list, cap(pool.free_list) )
for id in 0 ..< capacity {
free_list[id] = i32(id)
items[id] = {
for id in 0 ..< pool.capacity {
pool.free_list[id] = Pool_ListIter(id)
pool.items[id] = {
prev = -1,
next = -1,
}
}
front = -1
back = -1
size = 0
pool.front = -1
pool.back = -1
pool.size = 0
}
pool_list_push_front :: proc( pool : ^Pool_List, value : Pool_ListValue )
@(optimization_mode="favor_size")
pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_bounds_check
{
using pool
if size >= capacity do return
if pool.size >= pool.capacity do return
length := len(free_list)
length := len(pool.free_list)
assert( length > 0 )
assert( length == int(capacity - size) )
assert( length == int(pool.capacity - pool.size) )
id := free_list[ len(free_list) - 1 ]
if pool.dbg_name != "" {
logf("pool_list: back %v", id)
}
pop( & free_list )
items[ id ].prev = -1
items[ id ].next = front
items[ id ].value = value
if pool.dbg_name != "" {
logf("pool_list: pushed %v into id %v", value, id)
}
id := pool.free_list[ len(pool.free_list) - 1 ]
// if pool.dbg_name != "" {
// logf("pool_list: back %v", id)
// }
pop( & pool.free_list )
pool.items[ id ].prev = -1
pool.items[ id ].next = pool.front
pool.items[ id ].value = value
// if pool.dbg_name != "" {
// logf("pool_list: pushed %v into id %v", value, id)
// }
if front != -1 do items[ front ].prev = id
if back == -1 do back = id
front = id
size += 1
if pool.front != -1 do pool.items[ pool.front ].prev = id
if pool.back == -1 do pool.back = id
pool.front = id
pool.size += 1
}
pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter )
@(optimization_mode="favor_size")
pool_list_erase :: proc( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
{
using pool
if size <= 0 do return
assert( iter >= 0 && iter < i32(capacity) )
assert( len(free_list) == int(capacity - size) )
if pool.size <= 0 do return
assert( iter >= 0 && iter < Pool_ListIter(pool.capacity) )
assert( len(pool.free_list) == int(pool.capacity - pool.size) )
iter_node := & items[ iter ]
iter_node := & pool.items[ iter ]
prev := iter_node.prev
next := iter_node.next
if iter_node.prev != -1 do items[ prev ].next = iter_node.next
if iter_node.next != -1 do items[ next ].prev = iter_node.prev
if iter_node.prev != -1 do pool.items[ prev ].next = iter_node.next
if iter_node.next != -1 do pool.items[ next ].prev = iter_node.prev
if front == iter do front = iter_node.next
if back == iter do back = iter_node.prev
if pool.front == iter do pool.front = iter_node.next
if pool.back == iter do pool.back = iter_node.prev
iter_node.prev = -1
iter_node.next = -1
iter_node.value = 0
append( & free_list, iter )
append( & pool.free_list, iter )
size -= 1
if size == 0 {
back = -1
front = -1
pool.size -= 1
if pool.size == 0 {
pool.back = -1
pool.front = -1
}
}
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter )
@(optimization_mode="favor_size")
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
{
using pool
if pool.front == iter do return
if front == iter do return
item := & pool.items[iter]
if item.prev != -1 do pool.items[ item.prev ].next = item.next
if item.next != -1 do pool.items[ item.next ].prev = item.prev
if pool.back == iter do pool.back = item.prev
item := & items[iter]
if item.prev != -1 do items[ item.prev ].next = item.next
if item.next != -1 do items[ item.next ].prev = item.prev
if back == iter do back = item.prev
item.prev = -1
item.next = front
items[ front ].prev = iter
front = iter
item.prev = -1
item.next = pool.front
pool.items[ pool.front ].prev = iter
pool.front = iter
}
pool_list_peek_back :: #force_inline proc ( pool : Pool_List ) -> Pool_ListValue #no_bounds_check {
@(optimization_mode="favor_size")
pool_list_peek_back :: #force_inline proc ( pool : Pool_List($V_Type) ) -> V_Type #no_bounds_check {
assert( pool.back != - 1 )
value := pool.items[ pool.back ].value
return value
}
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue {
@(optimization_mode="favor_size")
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type #no_bounds_check {
if pool.size <= 0 do return 0
assert( pool.back != -1 )
@ -172,53 +183,51 @@ pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue
return value
}
LRU_Link :: struct {
pad_top : u64,
LRU_Link :: struct #packed {
value : i32,
ptr : Pool_ListIter,
pad_bottom : u64,
}
LRU_Cache :: struct {
LRU_Cache :: struct( $Key_Type : typeid ) {
capacity : i32,
num : i32,
table : map[u32]LRU_Link,
key_queue : Pool_List,
table : map[Key_Type]LRU_Link,
key_queue : Pool_List(Key_Type),
}
lru_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) {
lru_init :: proc( cache : ^LRU_Cache($Key_Type), capacity : i32, dbg_name : string = "" ) {
error : Allocator_Error
cache.capacity = capacity
cache.table, error = make( map[u32]LRU_Link, uint(capacity) )
cache.table, error = make( map[Key_Type]LRU_Link, uint(capacity) )
assert( error == .None, "VEFontCache.lru_init : Failed to allocate cache's table")
pool_list_init( & cache.key_queue, capacity, dbg_name = dbg_name )
}
}
lru_free :: proc( cache : ^LRU_Cache ) {
lru_free :: proc( cache : ^LRU_Cache($Key_Type) ) {
pool_list_free( & cache.key_queue )
delete( cache.table )
}
lru_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) {
lru_reload :: #force_inline proc( cache : ^LRU_Cache($Key_Type), allocator : Allocator ) {
reload_map( & cache.table, allocator )
pool_list_reload( & cache.key_queue, allocator )
}
lru_clear :: proc ( cache : ^LRU_Cache ) {
lru_clear :: proc ( cache : ^LRU_Cache($Key_Type) ) {
pool_list_clear( & cache.key_queue )
clear(& cache.table)
cache.num = 0
}
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, must_find := false ) -> (LRU_Link, bool) {
@(optimization_mode="favor_size")
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> (LRU_Link, bool) #no_bounds_check {
link, success := cache.table[key]
return link, success
}
lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u32 ) -> i32 #no_bounds_check {
@(optimization_mode="favor_size")
lru_get :: #force_inline proc ( cache: ^LRU_Cache($Key_Type), key : Key_Type ) -> i32 #no_bounds_check {
if link, ok := &cache.table[ key ]; ok {
pool_list_move_to_front(&cache.key_queue, link.ptr)
return link.value
@ -226,15 +235,17 @@ lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u32 ) -> i32 #no_bounds
return -1
}
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache ) -> u32 {
@(optimization_mode="favor_size")
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache($Key_Type) ) -> Key_Type #no_bounds_check {
if cache.key_queue.size >= cache.capacity {
evict := pool_list_peek_back( cache.key_queue )
return evict
}
return 0xFFFFFFFF
return ~Key_Type(0)
}
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, must_find := false ) -> i32 {
@(optimization_mode="favor_size")
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> i32 #no_bounds_check {
iter, success := lru_find( cache, key, must_find )
if success == false {
return -1
@ -242,7 +253,8 @@ lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache, key : u32, mus
return iter.value
}
lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u32, value : i32 ) -> u32
@(optimization_mode="favor_size")
lru_put :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type, value : i32 ) -> Key_Type #no_bounds_check
{
// profile(#procedure)
if link, ok := & cache.table[ key ]; ok {
@ -267,7 +279,7 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u32, value : i32 ) -> u
return evict
}
lru_refresh :: proc( cache : ^LRU_Cache, key : u32 ) {
lru_refresh :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type ) {
link, success := lru_find( cache ^, key )
pool_list_erase( & cache.key_queue, link.ptr )
pool_list_push_front( & cache.key_queue, key )

View File

@ -1,51 +0,0 @@
# VE Font Cache : Odin Port
This is a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library.
Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications.
See: [docs/Readme.md](docs/Readme.md) for the library's interface.
## Building
See [scripts/Readme.md](scripts/Readme.md) for building examples or utilizing the provided backends.
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).
## 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)

View File

@ -1,5 +1,7 @@
package vefontcache
// 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,19 +12,32 @@ Atlas_Region_Kind :: enum u8 {
Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call
}
Atlas_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,
state : LRU_Cache(Atlas_Key),
size : Vec2i,
capacity : Vec2i,
offset : Vec2i,
width : i32,
height : i32,
slot_size : Vec2i,
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,
@ -31,33 +46,49 @@ Atlas :: struct {
regions : [5] ^Atlas_Region,
glyph_padding : f32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
glyph_over_scalar : f32, // Scalar to apply to bounds_<width/height>_scaled for choosing which atlas region.
glyph_padding : f32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
size_multiplier : f32, // Grows all text by this multiple.
width : i32,
height : i32,
size : Vec2i,
}
atlas_region_bbox :: proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
{
size.x = f32(region.width)
size.y = f32(region.height)
// Hahser for the atlas.
@(optimization_mode="favor_size")
atlas_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, px_size : f32, glyph_index : Glyph ) -> (lru_code : Atlas_Key) {
// lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
font := font
glyph_index := glyph_index
px_size := px_size
djb8_hash( & lru_code, to_bytes( & font) )
djb8_hash( & lru_code, to_bytes( & glyph_index ) )
djb8_hash( & lru_code, to_bytes( & px_size ) )
return
}
position.x = cast(f32) (( local_idx % region.capacity.x ) * region.width)
position.y = cast(f32) (( local_idx / region.capacity.x ) * region.height)
@(optimization_mode="favor_size")
atlas_region_bbox :: #force_inline proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
{
size = vec2(region.slot_size)
position.x = cast(f32) (( local_idx % region.capacity.x ) * region.slot_size.x)
position.y = cast(f32) (( local_idx / region.capacity.x ) * region.slot_size.y)
position.x += f32(region.offset.x)
position.y += f32(region.offset.y)
return
}
@(optimization_mode="favor_size")
atlas_decide_region :: #force_inline proc "contextless" (atlas : Atlas, glyph_buffer_size : Vec2, bounds_size_scaled : Vec2 ) -> (region_kind : Atlas_Region_Kind)
{
profile(#procedure)
// profile(#procedure)
glyph_padding_dbl := atlas.glyph_padding * 2
padded_bounds := bounds_size_scaled + glyph_padding_dbl
for kind in 1 ..= 4 do if padded_bounds.x <= f32( atlas.regions[kind].width) && padded_bounds.y <= f32(atlas.regions[kind].height) {
for kind in 1 ..= 4 do if
padded_bounds.x <= f32(atlas.regions[kind].slot_size.x) &&
padded_bounds.y <= f32(atlas.regions[kind].slot_size.y)
{
return cast(Atlas_Region_Kind) kind
}
@ -68,7 +99,8 @@ atlas_decide_region :: #force_inline proc "contextless" (atlas : Atlas, glyph_bu
}
// Grab an atlas LRU cache slot.
atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u32 ) -> (atlas_index : i32)
@(optimization_mode="favor_size")
atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : Atlas_Key ) -> (atlas_index : i32)
{
if region.next_idx < region.state.capacity
{
@ -80,7 +112,7 @@ atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : u3
else
{
next_evict_codepoint := lru_get_next_evicted( region.state )
assert( next_evict_codepoint != 0xFFFFFFFF)
assert( next_evict_codepoint != LRU_Fail_Mask_16)
atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true )
assert( atlas_index != -1 )

View File

@ -1,23 +1,6 @@
# Interface
Notes
---
The freetype setup is not finished. Specifically due to cache_glyph_freetype not parsing the glyph outline data structure properly.
Freetype supports specifying a FT_Memory handle which is a pointer to a FT_MemoryRect. This can be used to define an allocator for the parser. Currently this library does not wrap this interface (yet). If using freetype its recommend to update `parser_init` with the necessary changes to wrap the context's backing allocator for freetype to utilize.
```c
struct FT_MemoryRec_
{
void* user;
FT_Alloc_Func alloc;
FT_Free_Func free;
FT_Realloc_Func realloc;
};
```
This library (seems) to perform best if the text commands are fed in 'whitespace aware chunks', where instead of feeding it entire blobs of text, the user identfies "words" in the text and feeding the visible and whitespce chunks derived from this to draw_text as separate calls. It improves the caching of the text shapes. The downside is there has to be a time where the text is parsed into tokens beforehand so that the this iteration does not have to occur continously.
## Lifetime
### startup
@ -31,30 +14,68 @@ Much of the data structures within the context struct are not fixed-capacity all
The library supports being used in a dynamically loaded module. If its hot-reloaded simply make sure to call this procedure with a reference to the backing allocator provided during startup as all dynamic containers tend to lose a proper reference to the allocator's procedure.
Call `clear_atlas_region_caches` & `clear_shape_cache` to reset the library's shape and glyph cache state to force a re-render.
### shutdown
Release resources from the context.
### configure_snap
### clear_atlas_region_caches
You'll find this used immediately in draw_text it acts as a way to snap the position of the text to the nearest pixel for the width and height specified.
Clears the LRU caches of regions A-D of the Atlas & sets their next_idx to 0. Effectively will force a re-cache of all previously rendered glyphs. Shape configuration for the glyph will remain unchanged unless clear_shape_cache is also called.
If snapping is not desired, set the snap_width and height before calling draw_text to 0.
### clear_shape_cache
### get_cursor_pos
Clears the LRU cache of the shaper along with clearing all existing storage entries. Effectively will force a re-cache of previously cached text shapes (Does not recache their rendered glyphs).
Will provide the current cursor_pos for the resulting text drawn.
### load_font
### set_color
Will load an instance of a font. The user needs to load the file's bytes themselves, the font entry (Entry :: struct) will by tracked by the library. The user will be given a font_id which is a direct index for the entry in the tracked array.
Sets the color to utilize on `Draw_Call`s for FrameBuffer.Target or .Target_Uncached passes
### unload_font
Will free an entry, (parser and shaper resources also freed)
## Shaping
Ideally the user should track the shapes themselves in a time-scale beyond the per-frame draw call. This avoids having to do caching/lookups of the shope.
### shape_text
Will shape the text using the `shaper_proc` arugment (user overloadable). Shape will be cached by the library.
### shape_text_uncached
Will shape the text using the `shaper_proc` arugment (user overloadable).
Shape will NOT be cached by the library. Use this if you want to roll your own solution for tracking shapes.
## Draw list generation
### draw_text procedures
There a total of six procedures, 3 for shapes, 3 for text:
* `draw_shape_normalized_space`
* `draw_shape_view_space`
* `draw_shape`
* `draw_text_normalized_space`
* `draw_text_view_space`
* `draw_text`
The normalized space procedures are the `baseline` interface draw procedures. They expec the position, and scale provided to operate with an unsigned normalized space where the bottom left is 0.0, 0.0 and the top right is 1.0, 1.0.
The view space will normalize the position and scale for the user based on the provided view and zoom. The coordinate system is still unsigned just scaled to the view's size.
The non-suffix named procedures use the scope stack to derive the position and scale the user provides a relative position and scale for the text that will be adjusted to the scope's view, position, scale, & zoom.
See the comment above each of the procedures for diagrams.
### get_draw_list
Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety.
By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments.
### get_draw_list_layer
### get_draw_list_layer
Get the enqueued draw_list for the current "layer".
A layer is considered the slice of the `Draw_List`'s content from the last call to `flush_draw_list_layer` onward.
@ -70,6 +91,12 @@ Will clear the draw list and draw layer offsets.
Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays.
## Metrics
### measure_shape_size
This provide's the shape size scaled down by the ctx.px_scale to get intended usage size. Size is equivalent to `measure_text_size`.
### measure_text_size
Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `Shaped_Text` entry.
@ -77,3 +104,85 @@ Provides a Vec2 the width and height occupied by the provided text string. The y
### get_font_vertical_metrics
A wrapper for `parser_get_font_vertical_metrics`. Will provide the ascent, descent, and line_gap for a font entry.
## Miscellaneous
Stuff used by the draw list generation interface or just getters and setters.
### get_cursor_pos
Will provide the current cursor_pos for the resulting text drawn.
### get_normalized_position_scale
Will normalize the value of the position and scale based on the provided view.
Position will also be snapped to the nearest pixel via ceil.
Does nothing if view is 1 or 0
This is used by draw via view relative space procedures to normalize it to the intended space for the render pass.
### resolve_draw_px_size
Used to constrain the px_size used in `resolve_zoom_size_scale`.
The view relative space and scoping stack-based procedures support zoom. When utilizing zoom their is a nasty jitter that will occur if the user smoothly goes across different font sizes because the spacing can drastically change between even and odd font-sizes. This is applied to enforce the font sticks to a specific interval.
The library uses the context's zoom_px_interval as the reference interval in the draw procedures. It can be set with `set_zoom_px_interval` and the default value is 2.
### resolve_zoom_size_scale
Provides a way to get a "zoom" on the font size and scale, similar conceptually to a canvas UX zoom
Does nothing when zoom is 1.0
Uses `resolve_draw_px_size` to constrain which font size is used for the zoom.
### set_alpha_scalar
This is an artifact feature of the current shader, it *may* be removed in the future... Increasing the alpha of the colour draw with above 1.0 increases the edge contrast of the glyph shape.
For the value to be added to the colour, the alph of the text must already be at 1.0 or greater.
### set_px_scalar
This another "super-scalar" applied to rendering glyphs. In each draw procedure the following is computed before passing the values to the shaper and draw list generation passes:
```go
target_px_size := px_size * ctx.px_scalar
target_scale := scale * (1 / ctx.px_scalar)
target_font_scale := parser_scale( entry.parser_info, target_px_size )
```
Essentially, `ctx.px_scalar` is used to upscale the px_size by its value and then downscale the render target scale back the indended size. Doing so provides better shape positioning and futher improves text hinting. The downside is that small text tends to become more jagged (as its really hitting the limits of of how well the shader can blend those edges at that resolution).
This will most likely be preserved with future shader upgrades, however it will most likely not be as necessary as it is right now to achieve crisp text.
### set_zoom_px_interval
Used with by draw procedures with `resolve_draw_px_size` & `resolve_zoom_size_scale`. Provides the interval to use when constraining the px_size to a specific set of values when using zoom scaling.
### set_snap_glyph_shape_position
During the shaping pass, the position of each glyph can be rounded up to the integer to (ussually) allow better hinting.
### set_snap_glyph_render_height
During the draw list generation pass, the position of each glyph when blitting to atlas can have teh quad size rounded up to the integer.
Can yield better hinting but may significantly stretch the glyphs at small scales.
## Scope Stack
These are a set of push & pop pairs of functions that operator ont he context's stack containers. They are used with the draw_shape and draw_text procedures. This mainly for quick scratch usage where the user wants to directly compose a large amount of text without having a UI framework directly handle the text backend.
* font
* font_size
* colour: Linear colour.
* view: Width and height of the 2D area the text will be drawn within.
* position: Uses relative positioning will offset the incoming position by the given amount.
* scale: Uses relative scaling, will scale the procedures incoming scale by the given amount.
* zoom: Affects scaling, will scale the procedure's incoming font size & scale based on an *UX canvas camera's* notion of it.
Procedure types:
* `scope_<stack_option>`: push with a defer pop
* `push_<stack_option>`
* `pop_<stack_option>`

View File

@ -0,0 +1,234 @@
# Guide: Architecture
Overview of the package design and code-path layout.
---
The purpose of this library is to alleviate four key challenges with one encapsulating package:
* Font parsing
* Text codepoint shaping
* Glyph shape triangulation
* Glyph draw-list generation
Shaping text, getting metrics for glyphs, triangulating glyphs, and anti-aliasing their render are expensive operations to perform per frame. Therefore, any compute operations that can be cached, will be.
There are two cache types used:
* Shape cache (`Shaped_Text_Cache.state`)
* Atlas region cache (`Atlas_Region.state`)
The shape cache stores all data for a piece of text that will be utilized in a draw call that is not dependent on a specific position & scale (and is faster to lookup vs compute per draw call).
The atlas region cache tracks what slots have glyphs rendered to the texture atlas. This essentially caches triangulation and super-sampling computations.
All caching uses [LRU.odin](../vefontcache/LRU.odin)
## Code Paths
### Lifetime
The library lifetime is straightforward: you have a startup procedure that should be called during your usual app initialization. From there you may either choose to manually shut it down or let the OS clean it up.
If hot-reload is desired, you just need to call hot_reload with the context's backing allocator to refresh the procedure references. After the DLL has been reloaded, these should be the only aspects that have been scrambled.
Usually when hot-reloading the library for tuning or major changes, you'd also want to clear the caches. Simply call `clear_atlas_region_caches` & `clear_shape_cache` right after.
Ideally, there should be zero dynamic allocation on a per-frame basis as long as the reserves for the dynamic containers are never exceeded. It's acceptable if they do exceed as their memory locality is so large their distance in the pages to load into CPU cache won't matter - it just needs to be a low incidence.
### Shaping Pass
If using the library's cache, `shaper_shape_text_cached` handles the hashing and lookup. As long as a shape is found, it will not enter the uncached code path. By default, this library uses `shaper_shape_harfbuzz` as the `shape_text_uncached` procedure.
Shapes are cached using the following parameters to hash a key:
* font: Font_ID
* font_size: f32
* the text itself: string
All shapers fulfill the following interface:
```odin
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context,
atlas : Atlas,
glyph_buffer_size : Vec2,
font : Font_ID,
entry : Entry,
font_px_Size : f32,
font_scale : f32,
text_utf8 : string,
output : ^Shaped_Text
)
```
Which will resolve the output `Shaped_Text`. It has the following definition:
```odin
Shaped_Text :: struct #packed {
glyph : [dynamic]Glyph,
position : [dynamic]Vec2,
visible : [dynamic]i16,
atlas_lru_code : [dynamic]Atlas_Key,
region_kind : [dynamic]Atlas_Region_Kind,
bounds : [dynamic]Range2,
end_cursor_pos : Vec2,
size : Vec2,
font : Font_ID,
px_size : f32,
}
```
The result of the shaping process is the glyphs and their positions for the the shape; historically resembling whats known as a *Slug* of prepared text for printing. The end position of where the user's "cursor" would be is also recorded which provided the end position of the shape. The size of the shape is also resolved here, which if using px_scalar must be downscaled. `measure_shape_size` does the downscaling for the user.
`visible` tracks which of the glyphs will actually be relevant for the draw_list pass. This is to avoid a conditional jump during the draw list gen pass. When accessing glyph or position during the draw_list gen, they will use visible's relative index.
The font and px_size is tracked here as well so they user does not need to provide it to the library's interface and related.
As stated under the main heading of this guide, the the following are within shaped text so that they may be resolved outside of the draw list generation (see: `generate_shape_draw_list`):
* atlas_lru_code
* region_kind
* bounds
These are the same length as the `visible` array, so indexing those will not need to use visibile's relative index.
`shaper_shape_text_latin` does naive shaping by utilizing the codepoint's kern_advance and detecting newlines.
`shaper_shape_harfbuzz` is an actual shaping *engine*. Here is the general idea of how the library utilizes it for shaping:
1. Reset the state of the hb_buffer
2. Determine the line height
3. Go through the codepoints: (for each)
1. Determine the codepoint's script
2. If the script is netural (Uknown, Inherited, or of Common type), the script has not changed, or this is the first codepoint of the shape we can add the codepoint to the buffer.
3. Otherwise we will have to start a shaping run if we do encounter a significant script change. After, we can add the codepoint to the post-run-cleared hb_buffer.
4. This continues until all codepoints have been processed.
4. We do a final shape run after iterating to make sure all codepoints have been processed.
5. Set the size of the shape: X is max line width, Y is line height multiplied by the line count.
6. Resolve the atlas_lru_code, region_kind, and bounds for all visible glyphs
7. Store the font and px_size information.
The `shape_run` procedure within does the following:
1. Setup the buffer for the batch
2. Have harfbuzz shape the buffer
3. Extract glyph infos and positions from the buffer.
4. Iterate through all glyphs
1. If the hb_glyph cluster is > 0, we need to treat it as the indication of a newline glyph. ***(We update position and skip)***
2. Update positioning and other metrics and append output shape's glyph and position.
3. If the glyph is visible we append it to shape's visible (harfbuzz must specify it as not .nodef, and parser must identify it as non-empty)
5. We update the output.end_cursor_pos with the last position processed by the iteration
6. Clear the hb_buffer's contents to prepare for a possible upcoming shape run.
**Note on shape_run.4: The iteration doesn't preserve tracking the clusters, so that information is lost.**
*In the future cluster tracking may be added if its found to be important for high level text features beyond rendering.*
**Note on shape_run.4.1: Don't know if the glyph signifiying newline should be preserved**
See [Harfbuzz documentation](https://harfbuzz.github.io) for additional information.
There are other shapers out there:
* [hamza](https://github.com/saidwho12/hamza): A notable C library that could be setup with bindings.
***Note: Monospace fonts may have a much more trivial shaper (however for fonts with ligatures this may not be the case)***
***They should only need the kern advance of a single glyph as they're all the same. ligatures (I believe) should preserve this kern advance.***
### Draw List Generation
All interface draw text procedures will ultimately call `generate_shape_draw_list`. If the draw procedure is given text, it will call `shaper_shape_text_cached` the text immediately before calling it.
Its implementation uses a batched-pipeline approach where its goal is to populate three arrays behavings as queues:
* oversized: For drawing oversized glyphs
* to_cache: For glyphs that need triangulation & rendering to glyph buffer then blitting to atlas.
* cache: For glyphs that are already cached in the atlas and just need to be blit to the render target.
And then sent those off to `batch_generate_glyphs_draw_list` for further actual generation to be done. The size of a batch is determined by the capacity of the glyph_buffer's `batch_cache`. This can be set in `glyph_draw_params` for startup.
`glyph_buffer.glyph_pack` is utilized by both `generate_shape_draw_list` and `batch_generate_glyphs_draw_list` to various computed data in an SOA data structure for the glyphs.
generate_shape_draw_list outline:
1. Prepare glyph_pack, oversized, to_cache, cached, and reset the batch cache
* `glyph_pack` is resized to to the length of `shape.visible`
* The other arrays populated have their reserved set to that length as well (they will not bounds check capacity on append)
2. Iterate through the shape.visible and resolve glyph_pack's positions.
3. Iterate through shape.visible this time for final region resolution and segregation of glyphs to their appropriate queue.
1. If the glyphs assigned region is `.E` its oversized. The `oversample` used for rendering to render target will either be 2x or 1x depending on how huge it is.
2. The following glyphs are checked to see if their assigned region has the glyph `cached`.
1. If it does, its just appended to cached and marked as seen in the `batch_cache`.
2. If its doesn't then a slot is reserved for within the atlas's region and the glyph is appended to `to_cache`.
3. For either case the atlas_region_bbox is computed.
3. After a batch has been resolved, `batch_generate_glyphs_draw_list` is called.
4. If there is an partially filled batch (the usual case), batch_generate_glyphs_draw_list will be called for it.
5. The cursor_pos is updated with the shape's end cursor position adjusted for the target space.
batch_generate_glyphs_draw_list outline:
The batch is organized into three major stages:
1. glyph transform & draw quads compute
2. glyph_buffer draw list generation (`oversized` & `to_cache`)
3. blit-from-atlas to render target draw list generation (`to_cache` & `cached`)
Glyph transform & draw quads compute does an iteration for each of the 3 arrays.
Nearly all the math for all three is done there *except* for `to_cache`, which does its blitting compute in its glyph_buffer draw-list gen pass.
glyph_buffer draw list generation paths for `oversized` and `to_cache` are unique to each.
For `oversized`:
1. Allocate glyph shapes
2. Iterate oversized:
1. Flush the glyph buffer if flagged todo so (reached glyph allocation limit)
2. Call `generate_glyph_pass_draw_list` for trianglation and rendering to buffer.
3. blit quad.
3. flush the glyph buffer's draw list.
4. free glyph shapes
For `to_cached`:
1. Allocate glyph shapes
2. Iterate to_cache:
1. Flush the glyph buffer if flagged todo so (reached glyph allocation limit)
2. Compute & blit quads for clearing the atlas region and blitting from the buffer to the atlas.
3. Call `generate_glyph_pass_draw_list` for trianglation and rendering to buffer.
3. flush the glyph buffer's draw list.
4. free glyph shapes
5. Do blits from atlas to draw list.
`cached` only needs to blit from the atlas to the render target.
`generate_glyph_pass_draw_list`: sets up the draw call for glyph to the glyph buffer. Currently it also handles triangulation as well. For now the shape triangulation is rudimentary and uses triangle fanning. Eventually it would be nice to offer alternative modes that can be specified on a per-font basis.
`flush_glyph_buffer_draw_list`: Will merge the draw_lists contents of the glyph buffer over to the library's general draw_list, the clear the buffer's draw lists.
### On Layering
The base draw list generation pippline provided by the library allows the user to batch whatever they want into a single "layer".
However, the user most likely would want take into consideration: font instances, font size, colors; these are things that may benefit from having shared locality during a layer batch. Overlaping text benefits from the user to handle the ordering via layers.
Layers (so far) are just a set of offssets tracked by the library's `Context.draw_layer` struct. When `flush_draw_list_layer` is called, the offsets are set to the current length of the draw list. This allows the rendering backend to retrieve the latest set of vertices, indices, and calls to render on a per-layer basis with: `get_draw_list_layer`.
Importantly, this leads to the following pattern when enuquing a layer to render:
1. Begin render pass
2. For codepath that will deal with text layers
1. Process user-level code-path that calls the draw text interface, populating the draw list layer (usually a for loop)
2. After iteration on the layer is complete, render the text layer
1. grab the draw list layer
2. flush the layer so the draw list offsets are reset
3. Repeat until all layers for the codepath are exhausted.
There is consideration to instead explicitly have a draw list with more contextual information of the start and end of each layer. So that batching can be orchestrated in an isolated section of their pipeline.
This would involve just tracking *slices* of thier draw-list that represents layers:
```odin
Draw_List_Layer :: struct {
vertices : []Vertex,
indices : []u32,
calls : []Draw_Call,
}
```
Eventually the library may provide this since adding that feature is relatively cheap and and a low line-count addition to the interface.
There should be little to no perfomrance loss from doing so as the iteration size is two large of a surface area to matter (so its just pipeline ergonomics)

View File

@ -0,0 +1,68 @@
# Guide: Backend
The end-user needs to adapt this library to hook into their own codebase. For reference, they can check the [examples](../examples/) and [backend](../backend/) directories for working code that demonstrates what this guide covers.
When rendering text, users need to handle two main aspects: the text to draw and its "layering". Similar to UIs, text should be drawn in layer batches, where each layer can represent a pass with arbitrary distinctions from other layers.
The following components are required:
* Vertex and Index Buffers for glyph meshes
* Glyph shader for rendering glyphs to the glyph buffer
* Atlas shader for blitting upscaled glyph quads from the glyph buffer to an atlas region slot (downsampled)
* "Screen or Target" shader for blitting glyph quads from the atlas to a render target or swapchain
* The glyph, atlas, and target image buffers
Currently, the library doesn't support sub-pixel AA, so we're only rendering to R8 images.
## Rendering Passes
There are four passes that need to be handled when rendering a draw list:
* Glyph: Rendering a glyph mesh to the glyph buffer
* Atlas: Blitting a glyph quad from the glyph buffer to an atlas slot
* Target: Blitting from the atlas image to the target image
* Target_Uncached: Blitting from the glyph buffer image to the target image
The Target & Target_Uncached passes can technically be handled in the same case. The user just needs to swap between using the atlas image and the glyph buffer image. This is how the backend_soko.odin's `render_text_layer` has these passes set up.
## Vertex Buffer Layout
The vertex buffer has the following layout for all passes:
* `[2]f32` for positions
* `[2]f32` for texture coords (Offset is naturally `[2]f32`)
* Total stride: `[4]f32`
---
The index buffer is a simple u32 stream.
For quad mesh layout details, see `blit_quad` in [draw.odin](../vefontcache/draw.odin).
For glyph shape triangulation meshes, the library currently only uses a triangle fanning technique, implemented in `fill_path_via_fan_triangulation` within [draw.odin](../vefontcache/draw.odin). Eventually, the library will support other modes on a per-font basis.
## UV Coordinate Conventions (GLSL vs HLSL)
DirectX, Metal, and Vulkan consider the top-left corner as (0, 0), where the Y axis increases downward (traditional screenspace). This library follows OpenGL's convention, where (0, 0) is at the bottom-left (Y goes up).
Adjust the UV coordinates in your shader accordingly:
```c
#if !OpenGL
uv = vec2(v_texture.x, 1.0 - v_texture.y);
#else
uv = vec2(v_texture.x, v_texture.y);
#endif
```
Eventually, the library will support both conventions as a comp-time conditional.
## Retrieving & Processing the layer
`get_draw_list_layer` will provide the layer's vertex, index, and draw call slices. Unless the default is overwritten, it will call `optimize_draw_list` before returning the slices (profile to see whats better for your use case).
Once those are retrived, call `flush_draw_list_layer` to update the layer offsets tracked by the library's `Context`.
The vertex and index slices just needed to be appended to your backend's vertex and index buffers.
The draw calls need to be iterated with a switch statement for the aforementioned pass types. Within the case you can construct the enqueue the passes.
---

View File

@ -1,114 +0,0 @@
# Notice
This is the original readme fo the C++ implementation by Xi Chen.
# VE Font Cache is a single header-only GPU font rendering library designed for game engines.
It aims to:
* Be fast and simple to integrate.
* Take advantage of modern GPU power.
* Be backend agnostic and easy to port to any API such as Vulkan, DirectX, OpenGL.
* Load TTF & OTF file formats directly.
* Use only runtime cache with no offline calculation.
* Render glyphs at reasonable quality at a wide range of font sizes.
* Support a good amount of internationalisation. そうですね!
* Support cached text shaping with HarfBuzz with simple Latin-style fallback.
* Load and unload fonts at any time.
# How it works
Glyphs are GPU rasterised with 16x supersampling. This method is a simplification of "Easy Scalable Text Rendering on the GPU",
by Evan Wallace, making use of XOR blending. Bézier curves are handled via brute force triangle tessellation; even 6 triangles per
curve only generates < 300 triangles, which is nothing for modern GPUs! This avoids complex frag shader for reasonable quality.
![Wireframe with GPU XOR blending](images/wireframe.png)
Texture atlas caching uses naïve grid placement; this wastes a lot of space but ensures interchangeable cache slots allowing for
straight up LRU ( Least Recently Used ) caching scheme to be employed.
The font atlas is a single 4k x 2k R8 texture divided into 4 regions:
```
2k
--------------------
| | |
| A | |
| | | 2
|---------| C | k
| | |
1k | B | |
| | |
--------------------
| |
| |
| | 2
| D | k
| |
| |
| |
--------------------
Region A = 32x32 caches, 1024 glyphs
Region B = 32x64 caches, 512 glyphs
Region C = 64x64 caches, 512 glyphs
Region D = 128x128 caches, 256 glyphs
```
Region A is designed for small glyphs, Region B is for tall glyphs, Region C is for large glyphs, and Region D for huge glyphs.
Glyphs are first rendered to an intermediate 2k x 512px R8 texture. This allows for minimum 4 Region D glyphs supersampled at
4 x 4 = 16x supersampling, and 8 Region C glyphs similarly. A simple 16-tap box downsample shader is then used to blit from this
intermediate texture to the final atlas location.
The atlas texture looks something like this:
![Wireframe with GPU XOR blending](images/atlas_small.png)
# Usage
Pseudo-code demonstrating simple usage:
```cpp
#define VE_FONTCACHE_IMPL
#include "../ve_fontcache.h"
static std::vector< uint8_t > buffer;
ve_fontcache_init( &cache );
ve_fontcache_configure_snap( &cache, width, height );
print_font = ve_fontcache_loadfile( &cache, "fonts/NotoSansJP-Light.otf", buffer, 19.0f );
ve_fontcache_draw_text( &cache, print_font, u8"hello world", 0, 0, 1.0f / width, 1.0f / height );
```
These header files need to be copied to your project:
```
ve_fontcache.h
utf8.h
stb_truetype.h
```
Except HarfBuzz, that's all the required dependencies. That said it's strongly recommended
to use HarfBuzz ( TODO: HarfBuzz not supported yet, coming soon!! ) over the default utf8.h latin
fallback text shaper.
## Integration with rendering backend
VEFontCache is largely backend agnostic. Currently the demo project uses OpenGL 3.3 for Windows.
That said it's designed to be integrated with VE, a Vulkan engine.
Please read the "How to plug into rendering API" section in ve_fontcache.h for more documentation
on how to implement your own backend to plumb this directly into your engine!
# Screenshots
![Screenshot 1](images/ve_fontcache_demo1.png)
![Screenshot 2](images/ve_fontcache_demo2.png)
![Screenshot 3](images/raincode.png)
![Screenshot 4](images/ve_fontcache_pressure_test.gif)
# Similar projects and links
Here are links to some awesome similar and related projects:
* fontstash - https://github.com/memononen/fontstash
* stb_truetype ( has font rasterisation itself ) - https://github.com/nothings/stb/blob/master/stb_truetype.h
* slug - http://sluglibrary.com/
* pathfinder - https://github.com/pcwalton/pathfinder
* https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac

View File

@ -1,24 +1,21 @@
package vefontcache
import "base:runtime"
import "base:intrinsics"
import "core:slice"
import "thirdparty:freetype"
/*
Note(Ed): This may be seperated in the future into another file dedending on how much is involved with supportin ear-clipping triangulation.
*/
// import "thirdparty:freetype"
Glyph_Trianglation_Method :: enum(i32) {
Ear_Clipping,
Triangle_Fanning,
}
Vertex :: struct {
pos : Vec2,
u, v : f32,
}
Transform :: struct {
pos : Vec2,
scale : Vec2,
}
Range2 :: struct {
p0, p1 : Vec2,
}
Glyph_Bounds_Mat :: matrix[2, 2] f32
Glyph_Draw_Quad :: struct {
@ -30,31 +27,15 @@ Glyph_Draw_Quad :: struct {
// This is used by generate_shape_draw_list & batch_generate_glyphs_draw_list
// to track relevant glyph data in soa format for pipelined processing
Glyph_Pack_Entry :: struct {
Glyph_Pack_Entry :: struct #packed {
position : Vec2,
index : Glyph,
lru_code : u32,
atlas_index : i32,
in_atlas : b8,
should_cache : b8,
region_kind : Atlas_Region_Kind,
region_pos : Vec2,
region_size : Vec2,
bounds : Range2,
bounds_scaled : Range2,
bounds_size : Vec2,
bounds_size_scaled : Vec2,
over_sample : Vec2,
scale : Vec2,
shape : Parser_Glyph_Shape,
draw_transform : Transform,
over_sample : Vec2, // Only used for oversized glyphs
shape : Parser_Glyph_Shape,
draw_transform : Transform,
draw_quad : Glyph_Draw_Quad,
draw_atlas_quad : Glyph_Draw_Quad,
draw_quad_clear : Glyph_Draw_Quad,
buffer_x : f32,
flush_glyph_buffer : b8,
}
@ -65,7 +46,7 @@ Draw_Call :: struct {
end_index : u32,
clear_before_draw : b32,
region : Atlas_Region_Kind,
colour : Colour,
colour : RGBAN,
}
Draw_Call_Default :: Draw_Call {
@ -92,23 +73,25 @@ Frame_Buffer_Pass :: enum u32 {
}
Glyph_Batch_Cache :: struct {
table : map[u32]b8,
table : map[Atlas_Key]b8,
num : i32,
cap : i32,
}
// The general tracker for a generator pipeline
Glyph_Draw_Buffer :: struct{
over_sample : Vec2,
width : i32,
height : i32,
draw_padding : f32,
over_sample : Vec2,
size : Vec2i,
draw_padding : f32,
snap_glyph_height : f32,
snap_glyph_width : f32,
allocated_x : i32, // Space used (horizontally) within the glyph buffer
clear_draw_list : Draw_List,
draw_list : Draw_List,
batch_cache : Glyph_Batch_Cache,
shape_gen_scratch : [dynamic]Vertex,
shape_gen_scratch : [dynamic]Vertex, // Used during triangulating a glyph into a mesh.
glyph_pack : #soa[dynamic]Glyph_Pack_Entry,
oversized : [dynamic]i32,
@ -116,7 +99,14 @@ Glyph_Draw_Buffer :: struct{
cached : [dynamic]i32,
}
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} )
// Contructs a quad mesh for bliting a texture from source render target (src uv0 & 1) to the destination render target (p0, p1)
@(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}
)
{
// profile(#procedure)
v_offset := cast(u32) len(draw_list.vertices)
@ -149,13 +139,14 @@ 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.
construct_filled_path :: #force_inline proc( draw_list : ^Draw_List,
// 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")
fill_path_via_fan_triangulation :: proc( draw_list : ^Draw_List,
outside_point : Vec2,
path : []Vertex,
scale := Vec2 { 1, 1 },
translate := Vec2 { 0, 0 }
)
) #no_bounds_check
{
// profile(#procedure)
v_offset := cast(u32) len(draw_list.vertices)
@ -186,12 +177,14 @@ construct_filled_path :: #force_inline 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,
curve_quality : f32,
bounds : Range2,
translate, scale : Vec2
)
) #no_bounds_check
{
profile(#procedure)
outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33}
@ -207,7 +200,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
@ -240,7 +233,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))
@ -249,23 +242,60 @@ generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]V
}
}
generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape : 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 : f32,
font_scale : f32,
position : Vec2,
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,
colour,
entry,
px_size,
font_scale,
position,
scale,
)
}
}
/* Generator pipeline for shapes
This procedure has no awareness of layers. That should be handled by a higher-order codepath.
Pipleine order:
* Resolve the glyph's position offset from the target position
* Segregate the glyphs into three slices: oversized, to_cache, cached.
* If oversized is not necessary for your use case and your hitting a bottleneck, omit it with setting ENABLE_OVERSIZED_GLYPHS to false.
* 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 (if done per frame) 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,
font_scale : 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)
mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : u32 ) {
mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : Atlas_Key ) {
cache.table[lru_code] = true
cache.num += 1
}
@ -275,123 +305,112 @@ generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape
}
atlas_glyph_pad := atlas.glyph_padding
atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) }
glyph_buffer_size := Vec2 { f32(glyph_buffer.width), f32(glyph_buffer.height) }
atlas_size := vec2(atlas.size)
glyph_buffer_size := vec2(glyph_buffer.size)
// Make sure the packs are large enough for the shape
glyph_pack := & glyph_buffer.glyph_pack
oversized := & glyph_buffer.oversized
to_cache := & glyph_buffer.to_cache
cached := & glyph_buffer.cached
non_zero_resize_soa(glyph_pack, len(shape.glyphs))
append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 )
{
raw := cast(^runtime.Raw_Dynamic_Array) pack
raw.len += 1
pack[len(pack) - 1] = entry
}
sub_slice :: #force_inline proc "contextless" ( pack : ^[dynamic]i32) -> []i32 { return pack[:] }
profile_begin("index")
for & glyph, index in glyph_pack
{
glyph.index = shape.glyphs[ index ]
glyph.lru_code = font_glyph_lru_code(entry.id, glyph.index)
}
profile_end()
profile_begin("translate")
for & glyph, index in glyph_pack
{
glyph.position = target_position + (shape.positions[index]) * target_scale
}
profile_end()
profile_begin("bounds")
for & glyph, index in glyph_pack
{
glyph.bounds = parser_get_bounds( entry.parser_info, glyph.index )
glyph.bounds_scaled = { glyph.bounds.p0 * font_scale, glyph.bounds.p1 * font_scale }
glyph.bounds_size = glyph.bounds.p1 - glyph.bounds.p0
glyph.bounds_size_scaled = glyph.bounds_size * font_scale
glyph.scale = glyph.bounds_size_scaled + atlas.glyph_padding
}
profile_end()
glyph_padding_dbl := atlas.glyph_padding * 2
profile_begin("region")
for & glyph, index in glyph_pack
{
glyph.region_kind = atlas_decide_region( atlas ^, glyph_buffer_size, glyph.bounds_size_scaled )
}
profile_end()
profile_begin("batching")
resize_soa_non_zero(glyph_pack, len(shape.visible))
profile_begin("batching & segregating glyphs")
// We do any reservation up front as appending to the array's will not check.
reserve(oversized, len(shape.visible))
reserve(to_cache, len(shape.visible))
reserve(cached, len(shape.visible))
clear(oversized)
clear(to_cache)
clear(cached)
reset_batch( & glyph_buffer.batch_cache)
append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 )
{
raw := cast(^Raw_Dynamic_Array) pack
raw.len += 1
pack[len(pack) - 1] = entry
}
sub_slice :: #force_inline proc "contextless" ( pack : ^[dynamic]i32) -> []i32 { return pack[:] }
profile_begin("translate")
for & glyph, index in glyph_pack {
// Throughout the draw list generation vis_id will need to be used over index as
// not all glyphs or positions for the shape are visibly rendered.
vis_id := shape.visible[index]
glyph.position = target_position + (shape.position[vis_id]) * target_scale
}
profile_end()
for & glyph, index in glyph_pack
{
if glyph.region_kind == .None {
assert(false, "FAILED TO ASSGIN REGION")
continue
}
if glyph.region_kind == .E
{
glyph.over_sample = \
glyph.bounds_size_scaled.x <= glyph_buffer_size.x / 2 &&
glyph.bounds_size_scaled.y <= glyph_buffer_size.y / 2 ? \
{2.0, 2.0} \
: {1.0, 1.0}
append_sub_pack(oversized, cast(i32) index)
continue
}
// atlas_lru_code, region_kind, and bounds are all 1:1 with shape.visible
atlas_key := shape.atlas_lru_code[index]
region_kind := shape.region_kind[index]
bounds := shape.bounds[index]
bounds_size_scaled := size(bounds) * font_scale
assert(region_kind != .None, "FAILED TO ASSGIN REGION")
when ENABLE_OVERSIZED_GLYPHS
{
if region_kind == .E
{
glyph.over_sample = \
bounds_size_scaled.x <= glyph_buffer_size.x / 2 &&
bounds_size_scaled.y <= glyph_buffer_size.y / 2 ? \
{2.0, 2.0} \
: {1.0, 1.0}
append_sub_pack(oversized, cast(i32) index)
continue
}
}
glyph.over_sample = glyph_buffer.over_sample
region := atlas.regions[glyph.region_kind]
glyph.atlas_index = lru_get( & region.state, glyph.lru_code )
region := atlas.regions[region_kind]
glyph.atlas_index = lru_get( & region.state, atlas_key )
// Glyphs are prepared in batches based on the capacity of the batch cache.
Prepare_For_Batch:
{
// Determine if we hit the limit for this batch.
if glyph_buffer.batch_cache.num >= glyph_buffer.batch_cache.cap do break Prepare_For_Batch
pack := cached
found_take_slow_path : b8
success : bool
// Determine if we hit the limit for this batch.
if glyph.atlas_index == - 1
{
// Check to see if we reached capacity for the atlas
if region.next_idx > region.state.capacity
if region.next_idx > region.state.capacity
{
// We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch.
next_evict_glyph := lru_get_next_evicted( region.state )
found_take_slow_path, success := glyph_buffer.batch_cache.table[next_evict_glyph]
found_take_slow_path, success = glyph_buffer.batch_cache.table[next_evict_glyph]
assert(success != false)
// TODO(Ed): This might not be needed with the new pipeline/batching
if (found_take_slow_path) {
break Prepare_For_Batch
}
}
profile("append to_cache")
glyph.atlas_index = atlas_reserve_slot(region, glyph.lru_code)
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
append_sub_pack(to_cache, cast(i32) index)
mark_glyph_seen(& glyph_buffer.batch_cache, glyph.lru_code)
continue
// profile_begin("glyph needs caching")
glyph.atlas_index = atlas_reserve_slot(region, atlas_key)
pack = to_cache
// profile_end()
}
profile("append cached")
// profile("append cached")
glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
append_sub_pack(cached, cast(i32) index)
mark_glyph_seen(& glyph_buffer.batch_cache, glyph.lru_code)
mark_glyph_seen(& glyph_buffer.batch_cache, atlas_key)
append_sub_pack(pack, cast(i32) index)
// TODO(Ed): This might not be needed with the new pipeline/batching
// if (found_take_slow_path) {
// break Prepare_For_Batch
// }
if glyph_buffer.batch_cache.num >= glyph_buffer.batch_cache.cap do break Prepare_For_Batch
continue
}
// Batch has been prepared for a set of glyphs time to generate glyphs.
batch_generate_glyphs_draw_list( draw_list, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
batch_generate_glyphs_draw_list( draw_list, shape, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
atlas,
glyph_buffer,
atlas_size,
@ -412,7 +431,7 @@ generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape
if len(oversized) > 0 || glyph_buffer.batch_cache.num > 0
{
// Last batch pass
batch_generate_glyphs_draw_list( draw_list, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
batch_generate_glyphs_draw_list( draw_list, shape, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
atlas,
glyph_buffer,
atlas_size,
@ -420,7 +439,7 @@ generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape
entry,
colour,
font_scale,
target_scale
target_scale,
)
}
@ -428,7 +447,26 @@ generate_shape_draw_list :: #force_no_inline proc( draw_list : ^Draw_List, shape
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.
Important: These slices store ids for glyph_pack which matches shape.visible in index.
shape.position and shape.glyph DO NOT.
There are only two places this matters for: getting glyph shapes when doing glyph pass generation for oversized and to_cache iterations.
Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target.
The compute section will have operations regarding how many glyphs they may 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 from the buffer to the atlas.
*/
@(optimization_mode="favor_size")
batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
shape : Shaped_Text,
glyph_pack : ^#soa[dynamic]Glyph_Pack_Entry,
cached : []i32,
to_cache : []i32,
@ -439,49 +477,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,
) #no_bounds_check
{
profile(#procedure)
colour := colour
when ENABLE_DRAW_TYPE_VIS {
colour := colour
}
profile_begin("glyph buffer transform & draw quads compute")
profile_begin("glyph transform & draw quads compute")
for id, index in cached
{
// Quad to for drawing atlas slot to target
glyph := & glyph_pack[id]
glyph := & glyph_pack[id]
bounds := shape.bounds[id]
bounds_scaled := mul(bounds, font_scale)
glyph_scale := size(bounds_scaled) + atlas.glyph_padding
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)
quad.dst_pos = glyph.position + (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 )
}
for id, index in to_cache
{
glyph := & glyph_pack[id]
glyph := & glyph_pack[id]
bounds := shape.bounds[id]
bounds_scaled := mul(bounds, font_scale)
glyph_scale := size(bounds_scaled) + glyph_buffer.draw_padding
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_buffer.over_sample + glyph.over_sample
buffer_size := glyph_scale * glyph_buffer.over_sample
// Allocate a glyph glyph render target region (FBO)
to_allocate_x := buffer_size.x
to_allocate_x := buffer_size.x + 4.0
// 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 = glyph.flush_glyph_buffer ? 0 : f32_allocated_x
glyph.buffer_x = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) )
// The glyph buffer space transform for generate_glyph_pass_draw_list
draw_transform := & glyph.draw_transform
draw_transform.scale = font_scale * glyph_buffer.over_sample
draw_transform.pos = -1 * (glyph.bounds.p0) * draw_transform.scale + atlas.glyph_padding
draw_transform.pos = -1 * (bounds.p0) * draw_transform.scale + glyph_buffer.draw_padding
draw_transform.pos.x += glyph.buffer_x
to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size )
@ -492,84 +534,84 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
draw_quad := & glyph.draw_quad
// Destination (draw_list's target image)
draw_quad.dst_pos = glyph.position + (glyph.bounds_scaled.p0) * target_scale
draw_quad.dst_scale = (glyph.scale) * target_scale
draw_quad.dst_pos = glyph.position + (bounds_scaled.p0) * target_scale
draw_quad.dst_scale = (glyph_scale) * target_scale
// UV Coordinates for sampling the atlas
draw_quad.src_scale = (glyph.scale)
draw_quad.src_scale = (glyph_scale)
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
when ENABLE_OVERSIZED_GLYPHS do for id, index in oversized
{
glyph := & glyph_pack[id]
glyph_padding := vec2(glyph_buffer.draw_padding)
glyph := & glyph_pack[id]
bounds := shape.bounds[id]
bounds_scaled := mul(bounds, font_scale)
bounds_size_scaled := size(bounds_scaled)
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 + glyph.over_sample
buffer_size := (bounds_size_scaled + glyph_padding) * glyph.over_sample
// Allocate a glyph glyph render target region (FBO)
to_allocate_x := buffer_size.x
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 ) )
glyph.buffer_x = glyph.flush_glyph_buffer ? 0 : f32_allocated_x
// }
// for id, index in oversized
// {
// glyph := & glyph_pack[id]
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
draw_quad.dst_pos = glyph.position + (bounds_scaled.p0 - glyph_padding) * target_scale
draw_quad.dst_scale = (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 := & glyph.draw_transform
draw_transform.scale = font_scale * glyph.over_sample
draw_transform.pos = -1 * 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
draw_quad.src_scale = bounds_size_scaled * glyph.over_sample + glyph_padding
to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, glyph_buffer_size )
}
profile_end()
profile_begin("generate oversized glyphs draw_list")
profile_begin("gen oversized glyphs draw_list")
when ENABLE_OVERSIZED_GLYPHS do if len(oversized) > 0
{
when ENABLE_DRAW_TYPE_VIS {
when ENABLE_DRAW_TYPE_VISUALIZATION {
colour.r = 1.0
colour.g = 1.0
colour.b = 0.0
}
for id, index in oversized {
for pack_id, index in oversized {
vis_id := shape.visible[pack_id]
error : Allocator_Error
glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index)
glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id])
assert(error == .None)
assert(glyph_pack[pack_id].shape != nil)
}
for id, index in oversized
{
glyph := & glyph_pack[id]
glyph := & glyph_pack[id]
bounds := shape.bounds[id]
if glyph.flush_glyph_buffer do flush_glyph_buffer_draw_list(draw_list,
& glyph_buffer.draw_list,
& glyph_buffer.clear_draw_list,
& glyph_buffer.allocated_x
)
generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch,
glyph_pack[id].shape,
entry.curve_quality,
glyph_pack[id].bounds,
bounds,
glyph_pack[id].draw_transform.pos,
glyph_pack[id].draw_transform.scale
)
@ -591,99 +633,24 @@ 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)
for id, index in oversized do parser_free_shape(entry.parser_info, glyph_pack[id].shape)
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
for pack_id, index in oversized {
assert(glyph_pack[pack_id].shape != nil)
parser_free_shape(entry.parser_info, glyph_pack[pack_id].shape)
}
}
profile_end()
profile_begin("to_cache: caching to atlas")
{
for id, index in to_cache {
error : Allocator_Error
glyph_pack[id].shape, error = parser_get_glyph_shape(entry.parser_info, glyph_pack[id].index)
assert(error == .None)
}
for id, index in to_cache
{
profile("glyph")
glyph := & glyph_pack[id]
if glyph.flush_glyph_buffer do flush_glyph_buffer_draw_list( draw_list,
& glyph_buffer.draw_list,
& glyph_buffer.clear_draw_list,
& glyph_buffer.allocated_x
)
dst_region_pos := glyph.region_pos
dst_region_size := glyph.region_size
to_glyph_buffer_space( & dst_region_pos, & dst_region_size, atlas_size )
clear_target_region : Draw_Call
{
using clear_target_region
pass = .Atlas
region = .Ignore
start_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
blit_quad( & glyph_buffer.clear_draw_list,
dst_region_pos, dst_region_pos + dst_region_size,
{ 1.0, 1.0 }, { 1.0, 1.0 }
)
end_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
}
dst_glyph_pos := glyph.region_pos
dst_glyph_size := (glyph.bounds_size_scaled) + atlas.glyph_padding
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
to_target_space( & src_position, & src_size, glyph_buffer_size )
blit_to_atlas : Draw_Call
{
using blit_to_atlas
pass = .Atlas
region = .None
start_index = cast(u32) len(glyph_buffer.draw_list.indices)
blit_quad( & glyph_buffer.draw_list,
dst_glyph_pos, dst_glyph_pos + dst_glyph_size,
src_position, src_position + src_size )
end_index = cast(u32) len(glyph_buffer.draw_list.indices)
}
append( & glyph_buffer.clear_draw_list.calls, clear_target_region )
append( & glyph_buffer.draw_list.calls, blit_to_atlas )
// Render glyph to glyph render target (FBO)
generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch,
glyph.shape,
entry.curve_quality,
glyph.bounds,
glyph.draw_transform.pos,
glyph.draw_transform.scale
)
}
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)
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 )
@(optimization_mode="favor_size")
generate_blit_from_atlas_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
call := Draw_Call_Default
call.pass = .Target
call.colour = colour
for id, index in sub_pack
{
profile("glyph")
// profile("glyph")
call.start_index = u32(len(draw_list.indices))
quad := glyph_pack[id].draw_quad
@ -696,32 +663,110 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
}
}
profile_begin("generate_cached_draw_list: to_cache")
when ENABLE_DRAW_TYPE_VIS {
colour.r = 0.80
colour.g = 0.25
colour.b = 0.25
profile_begin("to_cache: caching to atlas")
if len(to_cache) > 0
{
for pack_id, index in to_cache {
vis_id := shape.visible[pack_id]
error : Allocator_Error
glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id])
assert(error == .None)
assert(glyph_pack[pack_id].shape != nil)
}
for id, index in to_cache
{
// profile("glyph")
glyph := & glyph_pack[id]
bounds := shape.bounds[id]
bounds_scaled := mul(bounds, font_scale)
bounds_size_scaled := size(bounds_scaled)
if glyph.flush_glyph_buffer do flush_glyph_buffer_draw_list( draw_list,
& glyph_buffer.draw_list,
& glyph_buffer.clear_draw_list,
& glyph_buffer.allocated_x
)
dst_region_pos := glyph.region_pos
dst_region_size := glyph.region_size
to_glyph_buffer_space( & dst_region_pos, & dst_region_size, atlas_size )
clear_target_region : Draw_Call
clear_target_region.pass = .Atlas
clear_target_region.region = .Ignore
clear_target_region.start_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
blit_quad( & glyph_buffer.clear_draw_list,
dst_region_pos, dst_region_pos + dst_region_size,
{ 1.0, 1.0 }, { 1.0, 1.0 }
)
clear_target_region.end_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
dst_glyph_pos := glyph.region_pos
dst_glyph_size := bounds_size_scaled + atlas.glyph_padding
dst_glyph_size.x = max(dst_glyph_size.x, ceil(dst_glyph_size.x) * glyph_buffer.snap_glyph_width) // Note(Ed): Can (in specific cases, rare.) improve hinting
dst_glyph_size.y = max(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 := (bounds_size_scaled + atlas.glyph_padding) * glyph_buffer.over_sample
src_size.x = max(src_size.x, ceil(src_size.x) * glyph_buffer.snap_glyph_width) // Note(Ed): Can (in specific cases, rare.) improve hinting
src_size.y = max(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
blit_to_atlas.pass = .Atlas
blit_to_atlas.region = .None
blit_to_atlas.start_index = cast(u32) len(glyph_buffer.draw_list.indices)
blit_quad( & glyph_buffer.draw_list,
dst_glyph_pos, dst_glyph_pos + dst_glyph_size,
src_position, src_position + src_size )
blit_to_atlas.end_index = cast(u32) len(glyph_buffer.draw_list.indices)
append( & glyph_buffer.clear_draw_list.calls, clear_target_region )
append( & glyph_buffer.draw_list.calls, blit_to_atlas )
// Render glyph to glyph render target (FBO)
generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch,
glyph.shape,
entry.curve_quality,
bounds,
glyph.draw_transform.pos,
glyph.draw_transform.scale
)
}
flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
for pack_id, index in to_cache {
assert(glyph_pack[pack_id].shape != nil)
parser_free_shape(entry.parser_info, glyph_pack[pack_id].shape)
}
profile_begin("gen_cached_draw_list: to_cache")
when ENABLE_DRAW_TYPE_VISUALIZATION {
colour.r = 1.0
colour.g = 0.0
colour.b = 0.0
}
generate_blit_from_atlas_draw_list( draw_list, glyph_pack[:], to_cache, colour )
profile_end()
}
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
profile_begin("gen_cached_draw_list: cached")
when ENABLE_DRAW_TYPE_VISUALIZATION {
colour.r = 0.5
colour.g = 0.5
colour.b = 0.5
}
generate_cached_draw_list( draw_list, glyph_pack[:], cached, colour )
generate_blit_from_atlas_draw_list( draw_list, glyph_pack[:], cached, colour )
profile_end()
}
// Flush the content of the glyph_buffers draw lists to the main draw list
flush_glyph_buffer_draw_list :: #force_inline proc( #no_alias draw_list, glyph_buffer_draw_list, glyph_buffer_clear_draw_list : ^Draw_List, allocated_x : ^i32 )
flush_glyph_buffer_draw_list :: proc( #no_alias draw_list, glyph_buffer_draw_list, glyph_buffer_clear_draw_list : ^Draw_List, allocated_x : ^i32 )
{
profile(#procedure)
// if len(glyph_buffer_clear_draw_list.calls) == 0 || len(glyph_buffer_draw_list.calls) == 0 do return
// Flush Draw_Calls to draw list
merge_draw_list( draw_list, glyph_buffer_clear_draw_list )
merge_draw_list( draw_list, glyph_buffer_draw_list)
clear_draw_list( glyph_buffer_draw_list )
@ -736,15 +781,16 @@ flush_glyph_buffer_draw_list :: #force_inline proc( #no_alias draw_list, glyph_b
(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 )
clear( & draw_list.indices )
clear( & draw_list.vertices )
}
// ve_fontcache_merge_Draw_List
merge_draw_list :: proc ( #no_alias dst, src : ^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
{
profile(#procedure)
error : Allocator_Error
@ -770,7 +816,9 @@ merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List )
}
}
optimize_draw_list :: #force_inline proc (draw_list: ^Draw_List, call_offset: int)
// 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)
assert(draw_list != nil)

View File

@ -1,163 +0,0 @@
package vefontcache
when false {
// TODO(Ed): Freetype support
// TODO(Ed): glyph caching cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly...
cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32
{
draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex,
scale := Vec2 { 1, 1 },
translate := Vec2 { 0, 0 },
debug_print_verbose : b32 = false
)
{
if debug_print_verbose {
log("outline_path:")
for point in path {
vec := point.pos * scale + translate
logf(" %0.2f %0.2f", vec.x, vec.y )
}
}
v_offset := cast(u32) len(draw_list.vertices)
for point in path
{
transformed_point := Vertex {
pos = point.pos * scale + translate,
u = 0,
v = 0
}
append( & draw_list.vertices, transformed_point )
}
if len(path) > 2
{
indices := & draw_list.indices
for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 {
to_add := [3]u32 {
v_offset,
v_offset + index,
v_offset + index + 1
}
append( indices, ..to_add[:] )
}
// Close the path by connecting the last vertex to the first two
to_add := [3]u32 {
v_offset,
v_offset + cast(u32)(len(path) - 1),
v_offset + 1
}
append( indices, ..to_add[:] )
}
}
if glyph_index == Glyph(0) {
return false
}
face := entry.parser_info.freetype_info
error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale})
if error != .Ok {
return false
}
glyph := face.glyph
if glyph.format != .Outline {
return false
}
outline := &glyph.outline
if outline.n_points == 0 {
return false
}
draw := Draw_Call_Default
draw.pass = Frame_Buffer_Pass.Glyph
draw.start_index = cast(u32) len(ctx.draw_list.indices)
contours := slice.from_ptr(cast( [^]i16) outline.contours, int(outline.n_contours))
points := slice.from_ptr(cast( [^]freetype.Vector) outline.points, int(outline.n_points))
tags := slice.from_ptr(cast( [^]u8) outline.tags, int(outline.n_points))
path := &ctx.temp_path
clear(path)
outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 }
start_index: int = 0
for contour_index in 0 ..< int(outline.n_contours)
{
end_index := int(contours[contour_index]) + 1
prev_point : Vec2
first_point : Vec2
for idx := start_index; idx < end_index; idx += 1
{
current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) }
if ( tags[idx] & 1 ) == 0
{
// If current point is off-curve
if (idx == start_index || (tags[ idx - 1 ] & 1) != 0)
{
// current is the first or following an on-curve point
prev_point = current_pos
}
else
{
// current and previous are off-curve, calculate midpoint
midpoint := (prev_point + current_pos) * 0.5
append( path, Vertex { pos = midpoint } ) // Add midpoint as on-curve point
if idx < end_index - 1
{
// perform interp from prev_point to current_pos via midpoint
step := 1.0 / entry.curve_quality
for alpha : f32 = 0.0; alpha <= 1.0; alpha += step
{
bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha )
append( path, Vertex{ pos = bezier_point } )
}
}
prev_point = current_pos
}
}
else
{
if idx == start_index {
first_point = current_pos
}
if prev_point != (Vec2{}) {
// there was an off-curve point before this
append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled
}
append(path, Vertex{ pos = current_pos})
prev_point = {}
}
}
// ensure the contour is closed
if path[0].pos != path[ len(path) - 1 ].pos {
append(path, Vertex{pos = path[0].pos})
}
draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate)
// draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose)
clear(path)
start_index = end_index
}
if len(path) > 0 {
// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose)
draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate)
}
draw.end_index = cast(u32) len(ctx.draw_list.indices)
if draw.end_index > draw.start_index {
append( & ctx.draw_list.calls, draw)
}
return true
}
}

View File

@ -1,39 +1,56 @@
package vefontcache
import "base:runtime"
/*
Didn't want to splinter this into more files..
Just a bunch of utilities.
*/
import "core:simd"
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 := transmute( ^Raw_Dynamic_Array) self
raw.allocator = allocator
}
reload_array_soa :: #force_inline proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) {
raw := runtime.raw_soa_footer(self)
raw := raw_soa_footer(self)
raw.allocator = allocator
}
reload_map :: #force_inline proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
raw := transmute( ^runtime.Raw_Map) self
raw := transmute( ^Raw_Map) self
raw.allocator = allocator
}
font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u32) {
lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
return
}
to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte { return slice_ptr( transmute(^byte) typed_data, size_of(Type) ) }
djb8_hash_32 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) }
djb8_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) }
@(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]u8
RGBAN :: [4]f32
Vec2 :: [2]f32
Vec2i :: [2]i32
Vec2_64 :: [2]f64
Transform :: struct {
pos : Vec2,
scale : Vec2,
}
Range2 :: struct {
p0, p1 : Vec2,
}
mul_range2_vec2 :: #force_inline proc "contextless" ( range : Range2, v : Vec2 ) -> Range2 { return { range.p0 * v, range.p1 * v } }
size_range2 :: #force_inline proc "contextless" ( range : Range2 ) -> Vec2 { return range.p1 - range.p0 }
vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }}
vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }}
vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }}
@ -42,26 +59,27 @@ vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2
@(require_results) ceil_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { ceil_f32(v.x), ceil_f32(v.y) } }
@(require_results) floor_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { floor_f32(v.x), floor_f32(v.y) } }
// This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators.
// This buffer is used below excluisvely to prevent any allocator recursion when verbose logging from allocators.
// This means a single line is limited to 4k buffer
Logger_Allocator_Buffer : [4 * Kilobyte]u8
// Logger_Allocator_Buffer : [4 * Kilobyte]u8
log :: proc( msg : string, level := core_log.Level.Info, loc := #caller_location ) {
temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
context.allocator = arena_allocator(& temp_arena)
context.temp_allocator = arena_allocator(& temp_arena)
// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
// context.allocator = arena_allocator(& temp_arena)
// context.temp_allocator = arena_allocator(& temp_arena)
core_log.log( level, msg, location = loc )
}
logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc := #caller_location ) {
temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
context.allocator = arena_allocator(& temp_arena)
context.temp_allocator = arena_allocator(& temp_arena)
// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:])
// context.allocator = arena_allocator(& temp_arena)
// context.temp_allocator = arena_allocator(& temp_arena)
core_log.logf( level, fmt, ..args, location = loc )
}
@(optimization_mode="favor_size")
to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
{
pos := position^
@ -75,6 +93,7 @@ to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position,
(scale^) = scale_32
}
@(optimization_mode="favor_size")
to_target_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
{
quotient : Vec2 = 1.0 / size
@ -126,14 +145,17 @@ else
{
Vec2_SIMD :: simd.f32x4
@(optimization_mode="favor_size")
vec2_to_simd :: #force_inline proc "contextless" (v: Vec2) -> Vec2_SIMD {
return Vec2_SIMD{v.x, v.y, 0, 0}
}
@(optimization_mode="favor_size")
simd_to_vec2 :: #force_inline proc "contextless" (v: Vec2_SIMD) -> Vec2 {
return Vec2{ simd.extract(v, 0), simd.extract(v, 1) }
}
@(optimization_mode="favor_size")
eval_point_on_bezier3 :: #force_inline proc "contextless" (p0, p1, p2: Vec2, alpha: f32) -> Vec2
{
simd_p0 := vec2_to_simd(p0)
@ -157,6 +179,7 @@ else
return simd_to_vec2(result)
}
@(optimization_mode="favor_size")
eval_point_on_bezier4 :: #force_inline proc "contextless" (p0, p1, p2, p3: Vec2, alpha: f32) -> Vec2
{
simd_p0 := vec2_to_simd(p0)

View File

@ -2,32 +2,33 @@ package vefontcache
/*
Notes:
This is a minimal wrapper I originally did incase a font parser other 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.
~~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:
* Added ability to set the stb_truetype allocator for STBTT_MALLOC and STBTT_FREE.
* Changed procedure signatures to pass the font_info struct by immutable ptr (#by_ptr)
when the C equivalent has their parameter as `const*`.
*/
import "base:runtime"
import "core:c"
import "core:math"
import "core:slice"
import stbtt "vendor:stb/truetype"
import stbtt "thirdparty:stb/truetype"
// import freetype "thirdparty:freetype"
Parser_Kind :: enum u32 {
STB_TrueType,
Freetype,
Freetype, // Currently not implemented.
}
Parser_Font_Info :: struct {
label : string,
kind : Parser_Kind,
using _ : struct #raw_union {
stbtt_info : stbtt.fontinfo,
// freetype_info : freetype.Face
},
stbtt_info : stbtt.fontinfo,
// freetype_info : freetype.Face
data : []byte,
}
@ -51,47 +52,55 @@ Parser_Glyph_Vertex :: struct {
Parser_Glyph_Shape :: [dynamic]Parser_Glyph_Vertex
Parser_Context :: struct {
kind : Parser_Kind,
lib_backing : Allocator,
kind : Parser_Kind,
// ft_library : freetype.Library,
}
parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind )
parser_stbtt_allocator_proc :: proc(
allocator_data : rawptr,
type : stbtt.zpl_allocator_type,
size : c.ssize_t,
alignment : c.ssize_t,
old_memory : rawptr,
old_size : c.ssize_t,
flags : c.ulonglong
) -> rawptr
{
// switch kind
// {
// case .Freetype:
// result := freetype.init_free_type( & ctx.ft_library )
// assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" )
allocator := transmute(^Allocator) allocator_data
result, error := allocator.procedure( allocator.data, cast(Allocator_Mode) type, cast(int) size, cast(int) alignment, old_memory, cast(int) old_size )
assert(error == .None)
// case .STB_TrueType:
// Do nothing intentional
// }
if type == .Alloc || type == .Resize {
raw := transmute(Raw_Slice) result
// assert(raw.len > 0, "Allocation is 0 bytes?")
return transmute(rawptr) raw.data
}
else do return nil
}
ctx.kind = kind
parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind, allocator := context.allocator )
{
ctx.kind = kind
ctx.lib_backing = allocator
stbtt_allocator := stbtt.zpl_allocator { parser_stbtt_allocator_proc, & ctx.lib_backing }
stbtt.SetAllocator( stbtt_allocator )
}
parser_reload :: proc( ctx : ^Parser_Context, allocator := context.allocator) {
ctx.lib_backing = allocator
stbtt_allocator := stbtt.zpl_allocator { parser_stbtt_allocator_proc, & ctx.lib_backing }
stbtt.SetAllocator( stbtt_allocator )
}
parser_shutdown :: proc( ctx : ^Parser_Context ) {
// TODO(Ed): Implement
// Note: Not necesssary for stb_truetype
}
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info)
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info, error : b32)
{
// switch ctx.kind
// {
// case .Freetype:
// when ODIN_OS == .Windows {
// error := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info )
// if error != .Ok do return
// }
// else when ODIN_OS == .Linux {
// error := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i64) len(data), 0, & font.freetype_info )
// if error != .Ok do return
// }
// case .STB_TrueType:
success := stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
if ! success do return
// }
error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
font.label = label
font.data = data
@ -101,249 +110,87 @@ parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte )
parser_unload_font :: proc( font : ^Parser_Font_Info )
{
// switch font.kind {
// case .Freetype:
// error := freetype.done_face( font.freetype_info )
// assert( error == .Ok, "VEFontCache.parser_unload_font: Failed to unload freetype face" )
// case .STB_TrueType:
// Do Nothing
// }
// case .STB_TrueType:
// Do Nothing
}
parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
{
profile(#procedure)
// switch font.kind
// {
// case .Freetype:
// when ODIN_OS == .Windows {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// return
// case .STB_TrueType:
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
return
// }
// return Glyph(-1)
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
return
}
parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape )
{
// switch font.kind
// {
// case .Freetype:
// delete(shape)
// case .STB_TrueType:
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
// }
shape := shape
shape_raw := transmute( ^Raw_Dynamic_Array) & shape
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) shape_raw.data )
}
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
{
// switch font.kind
// {
// case .Freetype:
// glyph_index : Glyph
// when ODIN_OS == .Windows {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// if glyph_index != 0
// {
// freetype.load_glyph( font.freetype_info, c.uint(codepoint), { .No_Bitmap, .No_Hinting, .No_Scale } )
// advance = i32(font.freetype_info.glyph.advance.x) >> 6
// to_left_side_glyph = i32(font.freetype_info.glyph.metrics.hori_bearing_x) >> 6
// }
// else
// {
// advance = 0
// to_left_side_glyph = 0
// }
// case .STB_TrueType:
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
// }
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
return
}
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
{
// switch font.kind
// {
// case .Freetype:
// prev_glyph_index : Glyph
// glyph_index : Glyph
// when ODIN_OS == .Windows {
// prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) prev_codepoint )
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) prev_codepoint )
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// if prev_glyph_index != 0 && glyph_index != 0
// {
// kerning : freetype.Vector
// font.freetype_info.driver.clazz.get_kerning( font.freetype_info, transmute(u32) prev_codepoint, transmute(u32) codepoint, & kerning )
// }
// case .STB_TrueType:
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
// }
// return -1
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
}
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
{
// switch font.kind
// {
// case .Freetype:
// info := font.freetype_info
// ascent = i32(info.ascender)
// descent = i32(info.descender)
// line_gap = i32(info.height) - (ascent - descent)
// case .STB_TrueType:
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
// }
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
return
}
parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : Range2)
{
profile(#procedure)
// profile(#procedure)
bounds_0, bounds_1 : Vec2i
// switch font.kind
// {
// case .Freetype:
// freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } )
x0, y0, x1, y1 : i32
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// metrics := font.freetype_info.glyph.metrics
// bounds_0 = {i32(metrics.hori_bearing_x), i32(metrics.hori_bearing_y - metrics.height)}
// bounds_1 = {i32(metrics.hori_bearing_x + metrics.width), i32(metrics.hori_bearing_y)}
// case .STB_TrueType:
x0, y0, x1, y1 : i32
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// assert( success )
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
// }
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
bounds = { vec2(bounds_0), vec2(bounds_1) }
return
}
parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
{
// switch font.kind
// {
// case .Freetype:
// // TODO(Ed): Don't do this, going a completely different route for handling shapes.
// // This abstraction fails to be time-saving or performant.
// case .STB_TrueType:
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
shape_raw.data = stb_shape
shape_raw.len = int(nverts)
shape_raw.cap = int(nverts)
shape_raw.allocator = runtime.nil_allocator()
error = Allocator_Error.None
// return
// }
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
shape_raw := transmute( ^Raw_Dynamic_Array) & shape
shape_raw.data = stb_shape
shape_raw.len = int(nverts)
shape_raw.cap = int(nverts)
shape_raw.allocator = nil_allocator()
error = Allocator_Error.None
return
}
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32
{
// switch font.kind
// {
// case .Freetype:
// error := freetype.load_glyph( font.freetype_info, cast(u32) glyph_index, { .No_Bitmap, .No_Hinting, .No_Scale } )
// if error == .Ok
// {
// if font.freetype_info.glyph.format == .Outline {
// return font.freetype_info.glyph.outline.n_points == 0
// }
// else if font.freetype_info.glyph.format == .Bitmap {
// return font.freetype_info.glyph.bitmap.width == 0 && font.freetype_info.glyph.bitmap.rows == 0;
// }
// }
// return false
// case .STB_TrueType:
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
// }
// return false
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
}
parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
profile(#procedure)
size_scale := size < 0.0 ? \
parser_scale_for_pixel_height( font, -size ) \
: parser_scale_for_mapping_em_to_pixels( font, size )
// size_scale = 1.0
// profile(#procedure)
size_scale := size > 0.0 ? parser_scale_for_mapping_em_to_pixels( font, size ) : parser_scale_for_pixel_height( font, -size )
return size_scale
}
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// switch font.kind {
// case .Freetype:
// freetype.set_pixel_sizes( font.freetype_info, 0, cast(u32) size )
// size_scale := size / cast(f32)font.freetype_info.units_per_em
// return size_scale
// case.STB_TrueType:
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
// }
// return 0
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
}
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// switch font.kind {
// case .Freetype:
// Inches_To_CM :: cast(f32) 2.54
// Points_Per_CM :: cast(f32) 28.3465
// CM_Per_Point :: cast(f32) 1.0 / DPT_DPCM
// CM_Per_Pixel :: cast(f32) 1.0 / DPT_PPCM
// DPT_DPCM :: cast(f32) 72.0 * Inches_To_CM // 182.88 points/dots per cm
// DPT_PPCM :: cast(f32) 96.0 * Inches_To_CM // 243.84 pixels per cm
// DPT_DPI :: cast(f32) 72.0
// // TODO(Ed): Don't assume the dots or pixels per inch.
// system_dpi :: DPT_DPI
// FT_Font_Size_Point_Unit :: 1.0 / 64.0
// FT_Point_10 :: 64.0
// points_per_em := (size / system_dpi ) * DPT_DPI
// freetype.set_char_size( font.freetype_info, 0, cast(freetype.F26Dot6) f32(points_per_em * FT_Point_10), cast(u32) DPT_DPI, cast(u32) DPT_DPI )
// size_scale := f32(f64(size) / cast(f64) font.freetype_info.units_per_em)
// return size_scale
// case .STB_TrueType:
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
// }
// return 0
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
}

View File

@ -1,8 +1,15 @@
package vefontcache
import "base:builtin"
resize_soa_non_zero :: non_zero_resize_soa
import "base:runtime"
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
Raw_Map :: runtime.Raw_Map
Raw_Slice :: runtime.Raw_Slice
raw_soa_footer :: runtime.raw_soa_footer
nil_allocator :: runtime.nil_allocator
import "core:hash"
fnv64a :: hash.fnv64a
ginger16 :: hash.ginger16
import "core:math"
ceil_f16 :: math.ceil_f16
ceil_f16le :: math.ceil_f16le
@ -30,6 +37,7 @@ import "core:mem"
Allocator :: mem.Allocator
Allocator_Error :: mem.Allocator_Error
Allocator_Mode :: mem.Allocator_Mode
Arena :: mem.Arena
arena_allocator :: mem.arena_allocator
@ -46,38 +54,38 @@ append :: proc {
}
append_soa :: proc {
append_soa_elem
append_soa_elem,
}
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,
}
@ -86,21 +94,43 @@ fill :: proc {
slice.fill,
}
max :: proc {
linalg.max_single,
linalg.max_double,
}
make :: proc {
make_dynamic_array,
make_dynamic_array_len,
make_dynamic_array_len_cap,
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,
}
mul :: proc {
mul_range2_vec2,
}
peek :: proc {
peek_array,
}
resize :: proc {
resize_dynamic_array,
builtin.resize_dynamic_array,
}
round :: proc {
math.round_f32,
}
size :: proc {
size_range2,
}
vec2 :: proc {
@ -116,22 +146,4 @@ vec2_64 :: proc {
vec2_64_from_vec2,
}
import "../../grime"
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
grime.profile_begin(name, loc)
}
@(disabled = DISABLE_PROFILING)
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
grime.profile_begin(name, loc)
}
@(disabled = DISABLE_PROFILING)
profile_end :: #force_inline proc "contextless" () {
grime.profile_end()
}
//#endregion("Proc overload mappings")

View File

@ -0,0 +1,17 @@
package vefontcache
// Add profiling hookup here
// import ""
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
}
@(disabled = DISABLE_PROFILING)
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
}
@(disabled = DISABLE_PROFILING)
profile_end :: #force_inline proc "contextless" () {
}

View File

@ -1,35 +1,69 @@
package vefontcache
/*
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.
Note(Ed): The only reason I didn't directly use harfbuzz is:
https://github.com/saidwho12/hamza
and seems to be under active development as an alternative.
*/
import "core:c"
import "thirdparty:harfbuzz"
shape_lru_code :: djb8_hash_32
Shape_Key :: u32
Shaped_Text :: struct {
glyphs : [dynamic]Glyph,
positions : [dynamic]Vec2,
end_cursor_pos : Vec2,
size : Vec2,
entry : ^Entry,
font : Font_ID,
/* 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 resolves any content that does not have to be done
on a per-frame basis for draw list generation:
* atlas lru codes
* glyph bounds and scale
* atlas region the glyph is associated with.
Ideally the user should resolve this shape once and cache/store it on their side.
They have the best ability to avoid costly lookups.
*/
Shaped_Text :: struct #packed {
glyph : [dynamic]Glyph,
position : [dynamic]Vec2,
visible : [dynamic]i32,
atlas_lru_code : [dynamic]Atlas_Key,
region_kind : [dynamic]Atlas_Region_Kind,
bounds : [dynamic]Range2,
end_cursor_pos : Vec2,
size : Vec2,
font : Font_ID,
px_size : f32,
}
// 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,
state : LRU_Cache(Shape_Key),
next_cache_id : i32,
}
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
// 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,
atlas : Atlas,
glyph_buffer_size : Vec2,
font : Font_ID,
entry : Entry,
font_px_Size : f32,
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 +71,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,
@ -49,7 +84,7 @@ shaper_init :: proc( ctx : ^Shaper_Context )
assert( ctx.hb_buffer != nil, "VEFontCache.shaper_init: Failed to create harfbuzz buffer")
}
shaper_shutdown :: proc( ctx : ^Shaper_Context )
shaper_shutdown :: proc( ctx : ^Shaper_Context )
{
if ctx.hb_buffer != nil {
harfbuzz.buffer_destroy( ctx.hb_buffer )
@ -58,37 +93,56 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context )
shaper_load_font :: #force_inline proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info)
{
using info
blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil )
face = harfbuzz.face_create( blob, 0 )
font = harfbuzz.font_create( face )
info.blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil )
info.face = harfbuzz.face_create( info.blob, 0 )
info.font = harfbuzz.font_create( info.face )
return
}
shaper_unload_font :: #force_inline proc( ctx : ^Shaper_Info )
shaper_unload_font :: #force_inline proc( info : ^Shaper_Info )
{
using ctx
if blob != nil do harfbuzz.font_destroy( font )
if face != nil do harfbuzz.face_destroy( face )
if blob != nil do harfbuzz.blob_destroy( blob )
if info.font != nil do harfbuzz.font_destroy( info.font )
if info.face != nil do harfbuzz.face_destroy( info.face )
if info.blob != nil do harfbuzz.blob_destroy( info.blob )
}
shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text )
// TODO(Ed): Allow the user to override snap_glyph_position of the shaper context on a per-call basis (as a param)
// Recommended shaper. Very performant.
// TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal...
@(optimization_mode="favor_size")
shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context,
atlas : Atlas,
glyph_buffer_size : Vec2,
font : Font_ID,
entry : Entry,
font_px_size : f32,
font_scale : f32,
text_utf8 : string,
output : ^Shaped_Text
)
{
profile(#procedure)
assert( ctx != nil )
clear( & output.glyph )
clear( & output.position )
clear( & output.visible )
current_script := harfbuzz.Script.UNKNOWN
hb_ucfunc := harfbuzz.unicode_funcs_get_default()
harfbuzz.buffer_clear_contents( ctx.hb_buffer )
ascent := entry.ascent
descent := entry.descent
line_gap :=entry.line_gap
line_gap := entry.line_gap
max_line_width := f32(0)
line_count := 1
line_height := ((ascent - descent + line_gap) * font_scale)
position : Vec2
@(optimization_mode="favor_size")
shape_run :: proc( output : ^Shaped_Text,
entry : Entry,
buffer : harfbuzz.Buffer,
@ -106,11 +160,9 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
)
{
profile(#procedure)
// Set script and direction. We use the system's default langauge.
// script = HB_SCRIPT_LATIN
harfbuzz.buffer_set_script( buffer, script )
harfbuzz.buffer_set_script ( buffer, script )
harfbuzz.buffer_set_direction( buffer, harfbuzz.script_get_horizontal_direction( script ))
harfbuzz.buffer_set_language( buffer, harfbuzz.language_get_default() )
harfbuzz.buffer_set_language ( buffer, harfbuzz.language_get_default() )
// Perform the actual shaping of this run using HarfBuzz.
harfbuzz.buffer_set_content_type( buffer, harfbuzz.Buffer_Content_Type.UNICODE )
@ -123,23 +175,25 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
last_cluster := u32(0)
for index : i32; index < i32(glyph_count); index += 1
{
hb_glyph := glyph_infos[ index ]
hb_glyph := glyph_infos [ index ]
hb_gposition := glyph_positions[ index ]
glyph_id := cast(Glyph) hb_glyph.codepoint
glyph := cast(Glyph) hb_glyph.codepoint
if hb_glyph.cluster > 0
{
(max_line_width^) = max( max_line_width^, position.x )
position.x = 0.0
position.y -= line_height
position.y = floor(position.y)
(line_count^) += 1
(max_line_width^) = max( max_line_width^, position.x )
position.x = 0.0
position.y -= line_height
position.y = floor(position.y)
(line_count^) += 1
last_cluster = hb_glyph.cluster
continue
}
if abs( font_px_size ) <= adv_snap_small_font_threshold
{
if abs( font_px_size ) <= adv_snap_small_font_threshold {
(position^) = ceil( position^ )
}
@ -155,13 +209,18 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
f32(hb_gposition.x_advance) * font_scale,
f32(hb_gposition.y_advance) * font_scale
}
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
is_empty := parser_is_glyph_empty(entry.parser_info, glyph_id)
if ! is_empty {
append( & output.glyphs, glyph_id )
append( & output.positions, glyph_pos)
// We track all glyphs so that user can use the shape for navigation purposes.
append( & output.glyph, glyph )
append( & output.position, glyph_pos)
// We don't accept all glyphs for rendering, harfbuzz preserves positions of non-visible codepoints (as .notdef glyphs)
// We also double check to make sure the glyph isn't detected for drawing by the parser.
visible_glyph := glyph != 0 && ! parser_is_glyph_empty(entry.parser_info, glyph)
if visible_glyph {
append( & output.visible, cast(i32) len(output.glyph) - 1 )
}
}
@ -182,14 +241,23 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
// Can we continue the current run?
ScriptKind :: harfbuzz.Script
special_script : b32 = script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON
if special_script || script == current_script || byte_offset == 0 {
// These scripts don't break runs because they don't represent script transitions - they adapt to their context.
// Maintaining the current shaping run for these scripts ensures correct processing of marks, numbers,
// and punctuation within the primary text flow.
is_neutral_script := script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON
// Essentially if the script is neutral, or the same as current,
// or this is the first codepoint: add it to the buffer and continue the loop.
if is_neutral_script \
|| script == current_script \
|| byte_offset == 0
{
harfbuzz.buffer_add( ctx.hb_buffer, hb_codepoint, codepoint == '\n' ? 1 : 0 )
current_script = special_script ? current_script : script
current_script = is_neutral_script ? current_script : script
continue
}
// End current run since we've encountered a script change.
// End current run since we've encountred a significant script change.
shape_run( output,
entry,
ctx.hb_buffer,
@ -197,7 +265,7 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
& position,
& max_line_width,
& line_count,
font_px_Size,
font_px_size,
font_scale,
ctx.snap_glyph_position,
ctx.adv_snap_small_font_threshold
@ -214,7 +282,7 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
& position,
& max_line_width,
& line_count,
font_px_Size,
font_px_size,
font_scale,
ctx.snap_glyph_position,
ctx.adv_snap_small_font_threshold
@ -223,27 +291,56 @@ shaper_shape_harfbuzz :: #force_inline proc( ctx : ^Shaper_Context, text_utf8 :
// Set the final size
output.size.x = max_line_width
output.size.y = f32(line_count) * line_height
// Resolve each glyphs: bounds, atlas lru, and the atlas region as we have everything we need now.
resize( & output.atlas_lru_code, len(output.visible) )
resize( & output.region_kind, len(output.visible) )
resize( & output.bounds, len(output.visible) )
profile_begin("atlas_lru_code")
for vis_id, index in output.visible {
glyph_id := output.glyph[vis_id]
output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, glyph_id)
// atlas_lru_code is 1:1 with visible index
}
profile_end()
profile_begin("bounds & region")
for vis_id, index in output.visible {
glyph_id := output.glyph[vis_id]
bounds := & output.bounds[index]
(bounds ^) = parser_get_bounds( entry.parser_info, glyph_id )
bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale
output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled )
// bounds & region_kind are 1:1 with visible index
}
profile_end()
output.font = font
output.px_size = font_px_size
return
}
shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context, entry : Entry, font_px_size : f32, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
{
profile(#procedure)
assert( ctx != nil )
clear( & output.glyphs )
clear( & output.positions )
shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output )
}
shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context, entry : Entry, font_px_Size, font_scale : f32, text_utf8 : string, output : ^Shaped_Text )
// TODO(Ed): Allow the user to override snap_glyph_position of the shaper context on a per-call basis (as an param)
// Basic western alphabet based shaping. Not that much faster than harfbuzz if at all.
shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,
atlas : Atlas,
glyph_buffer_size : Vec2,
font : Font_ID,
entry : Entry,
font_px_size : f32,
font_scale : f32,
text_utf8 : string,
output : ^Shaped_Text
)
{
profile(#procedure)
assert( ctx != nil )
clear( & output.glyphs )
clear( & output.positions )
clear( & output.glyph )
clear( & output.position )
clear( & output.visible )
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
@ -268,19 +365,22 @@ shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context, entry : En
prev_codepoint = rune(0)
continue
}
if abs( font_px_Size ) <= ctx.adv_snap_small_font_threshold {
if abs( font_px_size ) <= ctx.adv_snap_small_font_threshold {
position.x = ceil(position.x)
}
glyph_index := parser_find_glyph_index( entry.parser_info, codepoint )
is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index )
if ! is_glyph_empty
{
append( & output.glyphs, glyph_index)
append( & output.positions, Vec2 {
floor(position.x),
floor(position.y)
})
if ctx.snap_glyph_position {
position.x = ceil(position.x)
position.y = ceil(position.y)
}
append( & output.glyph, glyph_index)
append( & output.position, position)
if ! is_glyph_empty {
append( & output.visible, cast(i32) len(output.glyph) - 1 )
}
advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint )
@ -293,11 +393,47 @@ shaper_shape_text_latin :: #force_inline proc( ctx : ^Shaper_Context, entry : En
output.size.x = max_line_width
output.size.y = f32(line_count) * line_height
// Resolve each glyphs: bounds, atlas lru, and the atlas region as we have everything we need now.
resize( & output.atlas_lru_code, len(output.glyph) )
resize( & output.region_kind, len(output.glyph) )
resize( & output.bounds, len(output.glyph) )
profile_begin("atlas_lru_code")
for vis_id, index in output.visible {
glyph_id := output.glyph[vis_id]
output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, glyph_id)
// atlas_lru_code is 1:1 with visible index
}
profile_end()
profile_begin("bounds & region")
for vis_id, index in output.visible {
glyph_id := output.glyph[vis_id]
bounds := & output.bounds[index]
(bounds ^) = parser_get_bounds( entry.parser_info, glyph_id )
bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale
output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled )
// bounds & region_kind are 1:1 with visible index
}
profile_end()
output.font = font
output.px_size = font_px_size
}
shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
// 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 much text it has to sift through.
// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-250 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,
atlas : Atlas,
glyph_buffer_size : Vec2,
font : Font_ID,
entry : Entry,
font_px_size : f32,
@ -306,28 +442,31 @@ shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
) -> (shaped_text : Shaped_Text)
{
profile(#procedure)
font := font
font_bytes := slice_ptr( transmute(^byte) & font, size_of(Font_ID) )
text_bytes := transmute( []byte) text_utf8
font := font
font_px_size := font_px_size
font_bytes := to_bytes( & font )
size_bytes := to_bytes( & font_px_size )
text_bytes := transmute( []byte) text_utf8
lru_code : u32
shape_lru_code( & lru_code, font_bytes )
shape_lru_code( & lru_code, text_bytes )
lru_code : Shape_Key
djb8_hash( & lru_code, font_bytes )
djb8_hash( & lru_code, size_bytes )
djb8_hash( & lru_code, text_bytes )
state := & shape_cache.state
shape_cache_idx := lru_get( state, lru_code )
if shape_cache_idx == -1
{
if shape_cache.next_cache_id < i32(state.capacity) {
if shape_cache.next_cache_id < i32(state.capacity){
shape_cache_idx = shape_cache.next_cache_id
shape_cache.next_cache_id += 1
evicted := lru_put( state, lru_code, shape_cache_idx )
}
else
else
{
next_evict_idx := lru_get_next_evicted( state ^ )
assert( next_evict_idx != 0xFFFFFFFF )
assert( next_evict_idx != LRU_Fail_Mask_32 )
shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true )
assert( shape_cache_idx != - 1 )
@ -336,7 +475,7 @@ shaper_shape_text_cached :: #force_inline proc( text_utf8 : string,
}
storage_entry := & shape_cache.storage[ shape_cache_idx ]
shape_text_uncached( ctx, entry, font_px_size, font_scale, text_utf8, storage_entry )
shape_text_uncached( ctx, atlas, glyph_buffer_size, font, entry, font_px_size, font_scale, text_utf8, storage_entry )
shaped_text = storage_entry ^
return

File diff suppressed because it is too large Load Diff

View File

@ -107,7 +107,7 @@ hmap_chained_clear :: proc( using self : HMapChained($Type))
continue
}
for probe_slot := slot.next; probe_slot != nil; probe_slot = probe_slot.next {
slot.occupied = false
probe_slot.occupied = false
}
slot.occupied = false
}

View File

@ -0,0 +1,48 @@
package grime
when (false) {
KT1L_Slot :: struct($Type: typeid) {
key: u64,
value: Type,
}
KT1L_Meta :: struct {
slot_size: uintptr,
kt_value_offset: uintptr,
type_width: uintptr,
type_name: string,
}
kt1l_populate_slice_a2_Slice_Byte :: proc(kt: ^[]byte, backing: Allocator = context.allocator, values: []byte, num_values: int, m: KT1L_Meta) {
assert(kt != nil)
if num_values == 0 { return }
table_size_bytes := num_values * int(m.slot_size)
err : AllocatorError
kt^, err = alloc_bytes(table_size_bytes, allocator = backing)
slice_assert(kt ^)
kt_raw : Raw_Slice = transmute(Raw_Slice) kt^
for cursor in 0 ..< cast(uintptr) num_values {
slot_offset := cursor * m.slot_size
slot_cursor := uintptr(kt_raw.data) + slot_offset
slot_key := cast(^u64) slot_cursor
slot_value := transmute([]byte) Raw_Slice { cast([^]byte) (slot_cursor + m.kt_value_offset), int(m.type_width)}
a2_offset := cursor * m.type_width * 2
a2_cursor := uintptr(& values[a2_offset])
a2_key := (transmute(^[]byte) a2_cursor) ^
a2_value := transmute([]byte) Raw_Slice { rawptr(a2_cursor + m.type_width), int(m.type_width) }
copy(slot_value, a2_value)
slot_key^ = 0; hash64_djb8(slot_key, a2_key)
}
kt_raw.len = num_values
}
kt1l_populate_slice_a2 :: proc($Type: typeid, kt: ^[]KT1L_Slot(Type), backing: AllocatorInfo, values: [][2]Type) {
assert(kt != nil)
values_bytes := transmute([]byte) Raw_Slice{data = raw_data(values), len = len(values) * size_of([2]Type)}
kt1l_populate_slice_a2_Slice_Byte(transmute(^[]byte) kt, backing, values_bytes, len(values), {
slot_size = size_of(KT1L_Slot(Type)),
kt_value_offset = offset_of(KT1L_Slot(Type), KT1L_Slot(Type).value),
type_width = size_of(Type),
type_name = #type_string(Type),
})
}
}

View File

@ -0,0 +1 @@
package grime

View File

@ -43,27 +43,17 @@ DLL_NodeFull :: struct ( $ Type : typeid ) {
first, last : ^Type,
prev, next : ^Type,
}
// I have specific members commented out here as the RAD Debugger currently doesn't support transparently exposing using members of a struct (yet).
DLL_NodePN :: struct ( $ Type : typeid ) {
// using _ : struct {
prev, next : ^Type,
// },
// using _ : struct {
// left, right : ^Type,
// },
prev, next : ^Type,
}
DLL_NodeFL :: struct ( $ Type : typeid ) {
// using _ : struct {
first, last : ^Type,
// },
// TODO(Ed): Review this
// using _ : struct {
// bottom, top: ^Type,
// },
first, last : ^Type,
}
DLL_NodeBT :: struct ($Type: typeid) {
bottom, top: ^Type,
}
DLL_NodeLR :: struct ($Type: typeid) {
left, right: ^Type,
}
type_is_node :: #force_inline proc "contextless" ( $ Type : typeid ) -> bool

View File

@ -21,6 +21,7 @@ import "base:runtime"
Exabyte :: runtime.Exabyte
resize_non_zeroed :: runtime.non_zero_mem_resize
SourceCodeLocation :: runtime.Source_Code_Location
Raw_Slice :: runtime.Raw_Slice
//#endregion("base")
@ -35,7 +36,8 @@ import "core:container/queue"
import "core:dynlib"
import "core:hash"
crc32 :: hash.crc32
ginger16 :: hash.ginger16
crc32 :: hash.crc32
import "core:hash/xxhash"
xxh32 :: xxhash.XXH32

View File

@ -31,4 +31,3 @@ profile_begin :: #force_inline proc "contextless" ( name : string, loc := #calle
profile_end :: #force_inline proc "contextless" () {
spall._buffer_end( & Module_Context.ctx, & Module_Context.buffer)
}

View File

@ -18,10 +18,7 @@ import "core:strings"
StringKey :: distinct u64
RunesCached :: []rune
// TODO(Ed): There doesn't seem to be a need for caching the runes.
// It seems like no one has had a bottleneck just iterating through the code points on demand when needed.
// So we should problably scrap storing them that way.
// Note(Ed): No longer using for caching but could still be useful in the future
StrRunesPair :: struct {
str : string,
runes : []rune,
@ -29,9 +26,11 @@ StrRunesPair :: struct {
to_str_runes_pair_via_string :: #force_inline proc ( content : string ) -> StrRunesPair { return { content, to_runes(content) } }
to_str_runes_pair_via_runes :: #force_inline proc ( content : []rune ) -> StrRunesPair { return { to_string(content), content } }
StrCached :: string
StringCache :: struct {
slab : Slab,
table : HMapChained(StrRunesPair),
table : HMapChained(StrCached),
}
// This is the default string cache for the runtime module.
@ -44,9 +43,9 @@ str_cache_init :: proc( table_allocator, slabs_allocator : Allocator ) -> (cache
policy : SlabPolicy
policy_ptr := & policy
// push( policy_ptr, SlabSizeClass { 64 * Kilobyte, 8, alignment })
// push( policy_ptr, SlabSizeClass { 64 * Kilobyte, 16, alignment })
// push( policy_ptr, SlabSizeClass { 128 * Kilobyte, 32, alignment })
push( policy_ptr, SlabSizeClass { 64 * Kilobyte, 8, alignment })
push( policy_ptr, SlabSizeClass { 64 * Kilobyte, 16, alignment })
push( policy_ptr, SlabSizeClass { 128 * Kilobyte, 32, alignment })
push( policy_ptr, SlabSizeClass { 640 * Kilobyte, 64, alignment })
push( policy_ptr, SlabSizeClass { 64 * Kilobyte, 128, alignment })
push( policy_ptr, SlabSizeClass { 64 * Kilobyte, 256, alignment })
@ -71,7 +70,7 @@ str_cache_init :: proc( table_allocator, slabs_allocator : Allocator ) -> (cache
cache.slab, alloc_error = slab_init( & policy, allocator = slabs_allocator, dbg_name = dbg_name )
verify(alloc_error == .None, "Failed to initialize the string cache" )
cache.table, alloc_error = make( HMapChained(StrRunesPair), 1 * Kilo, table_allocator, dbg_name = dbg_name )
cache.table, alloc_error = make( HMapChained(StrCached), 4 * Kilo, table_allocator, dbg_name = dbg_name )
return
}
@ -82,9 +81,9 @@ str_cache_reload :: #force_inline proc ( cache : ^StringCache, table_allocator,
str_cache_set_module_ctx :: #force_inline proc "contextless" ( cache : ^StringCache ) { Module_String_Cache = cache }
str_intern_key :: #force_inline proc( content : string ) -> StringKey { return cast(StringKey) crc32( transmute([]byte) content ) }
str_intern_lookup :: #force_inline proc( key : StringKey ) -> (^StrRunesPair) { return hmap_chained_get( Module_String_Cache.table, transmute(u64) key ) }
str_intern_lookup :: #force_inline proc( key : StringKey ) -> (^StrCached) { return hmap_chained_get( Module_String_Cache.table, transmute(u64) key ) }
str_intern :: proc( content : string ) -> StrRunesPair
str_intern :: #force_inline proc( content : string ) -> StrCached
{
// profile(#procedure)
cache := Module_String_Cache
@ -100,19 +99,14 @@ str_intern :: proc( content : string ) -> StrRunesPair
copy_non_overlapping( raw_data(str_mem), raw_data(content), length )
runes : []rune
runes, alloc_error = to_runes( content, slab_allocator(cache.slab) )
verify( alloc_error == .None, "String cache had a backing allocator error" )
// slab_validate_pools( cache.slab.backing )
result, alloc_error = hmap_chained_set( cache.table, transmute(u64) key, StrRunesPair { transmute(string) str_mem, runes } )
result, alloc_error = hmap_chained_set( cache.table, transmute(u64) key, transmute(StrCached) str_mem )
verify( alloc_error == .None, "String cache had a backing allocator error" )
// slab_validate_pools( cache.slab.backing )
return (result ^)
}
str_intern_fmt :: #force_inline proc( format : string, args : ..any, allocator := context.allocator ) -> StrRunesPair {
str_intern_fmt :: #force_inline proc( format : string, args : ..any, allocator := context.allocator ) -> StrCached {
return str_intern(str_fmt(format, args, allocator = allocator))
}

View File

@ -3,9 +3,16 @@
package grime
str_fmt_kt1l :: proc() {
// str_format :: proc ( format : string, tokens : ..args ) {
}
// }
str_tfmt_backed :: proc() {
}
str_tfmt_tmp :: proc() {
}

View File

@ -1,10 +1,11 @@
/*
Odin's virtual arena allocator doesn't do what I ideally want for allocation resizing.
(It was also a nice exercise along with making the other allocators)
~~Odin's virtual arena allocator doesn't do what I ideally want for allocation resizing.~~
It was also a nice exercise along with making the other allocators)
So this is a virtual memory backed arena allocator designed
to take advantage of one large contigous reserve of memory.
With the expectation that resizes with its interface will only occur using the last allocated block.
Note(Ed): Odin's mem allocator now has that feature
All virtual address space memory for this application is managed by a virtual arena.
No other part of the program will directly touch the vitual memory interface direclty other than it.

View File

@ -275,7 +275,7 @@ sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, lo
fmt_backing : [16 * Kilobyte] u8
persistent_backing : [2 * Megabyte] byte
persistent_backing : [32 * Megabyte] byte
transient_backing : [32 * Megabyte] byte
main :: proc()
@ -292,7 +292,7 @@ main :: proc()
// Setup profiling
profiler : SpallProfiler
{
buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE * 4)
profiler.ctx = spall.context_create("sectr.spall")
profiler.buffer = spall.buffer_create(buffer_backing)
}

View File

@ -4,7 +4,7 @@ package sectr
import sokol_gfx "thirdparty:sokol/gfx"
DebugData :: struct {
ScratchData :: struct {
square_size : i32,
square_pos : Vec2,
@ -33,11 +33,4 @@ DebugData :: struct {
path_lorem : string,
lorem_content : []byte,
lorem_parse : PWS_ParseResult,
gfx_clear_demo_pass_action : sokol_gfx.Pass_Action,
gfx_tri_demo_state : struct {
pipeline : sokol_gfx.Pipeline,
bindings : sokol_gfx.Bindings,
pass_action : sokol_gfx.Pass_Action,
},
}

View File

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

View File

@ -10,8 +10,11 @@ UI_SettingsMenu :: struct
zoom_smooth_sensitivity_input : UI_TextInputBox,
zoom_digital_sensitivity_input : UI_TextInputBox,
zoom_scroll_delta_scale_input : UI_TextInputBox,
font_size_canvas_scalar_input : UI_TextInputBox,
font_size_screen_scalar_input : UI_TextInputBox,
text_snap_glyph_shape_posiiton : UI_TextInputBox,
text_snap_glyph_render_height : UI_TextInputBox,
text_size_canvas_scalar_input : UI_TextInputBox,
text_size_screen_scalar_input : UI_TextInputBox,
text_alpha_sharpen : UI_TextInputBox,
cfg_drop_down : UI_DropDown,
zoom_mode_drop_down : UI_DropDown,
@ -41,14 +44,14 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
setup_container:
{
using container
if ! is_maximized
if ! is_maximized
{
layout.flags = {
// .Size_To_Content,
.Fixed_Width, .Fixed_Height,
.Fixed_Width, .Fixed_Height,
// .Min_Size_To_Content_Y,
.Fixed_Position_X, .Fixed_Position_Y,
.Origin_At_Anchor_Center
.Fixed_Position_X, .Fixed_Position_Y,
.Origin_At_Anchor_Center
}
layout.pos = pos
layout.size = range2( size, {})
@ -58,17 +61,17 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
layout.flags = {.Origin_At_Anchor_Center }
layout.pos = {}
}
dragged := ui_resizable_handles( & container, & pos, & size)
should_raise |= dragged
// TODO(Ed): This demonstrated a minimum viable-size window to content, however we still need to support a scroll box and switch this window to that.
old_vbox := ui_box_from_key(get_ui_context_mut().prev_cache, ui_key_from_string("Settings Menu: VBox"))
if old_vbox != nil
{
vbox_children_bounds := ui_compute_children_overall_bounds(old_vbox)
joined_size := size_range2( vbox_children_bounds )
if ! dragged
if ! dragged
{
// TODO(Ed): Figure out what this value is
extra_padding :: 3
@ -90,7 +93,7 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
ui_parent(vbox)
Frame_Bar:
{
{
scope(theme_window_bar)
frame_bar := ui_hbox(.Left_To_Right, "Settings Menu: Frame Bar", { .Mouse_Clickable })
{
@ -137,10 +140,10 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
if ! dd_app_config.is_open do break app_config_closed
ui_size_to_content_y( dd_app_config.vbox)
ui_settings_entry_inputbox :: proc( input_box : ^UI_TextInputBox, is_even : bool, label : string, setting_title : StrRunesPair, input_policy : UI_TextInput_Policy )
ui_settings_entry_inputbox :: proc( input_box : ^UI_TextInputBox, is_even : bool, label : string, setting_title : StrCached, input_policy : UI_TextInput_Policy )
{
scope( theme_table_row(is_even))
hb := ui_hbox(.Left_To_Right, str_intern_fmt("%v.hb", label).str); {
hb := ui_hbox(.Left_To_Right, str_fmt("%v.hb", label)); {
using hb
layout.size.min = {0, 25}
@ -149,14 +152,14 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
}
scope(theme_text)
title := ui_text(str_intern_fmt("%v.title", label).str, setting_title); {
title := ui_text(str_fmt("%v.title", label), setting_title); {
using title
layout.anchor.ratio.x = 1.0
layout.margins.left = 10
layout.font_size = 12
}
ui_text_input_box( input_box, str_intern_fmt("%v.input_box", label).str, allocator = persistent_slab_allocator(), policy = input_policy )
ui_text_input_box( input_box, str_fmt("%v.input_box", label), policy = input_policy, allocator = persistent_slab_allocator() )
{
using input_box
layout.flags |= {.Fixed_Width}
@ -243,9 +246,9 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
digits_only = true
disallow_leading_zeros = false
disallow_decimal = false
digit_min = 0.001
digit_min = 0.00001
digit_max = 1.0
max_length = 6
max_length = 7
}
ui_text_input_box( & min_zoom_inputbox, "settings_menu.cam_min_zoom.input_box", allocator = persistent_slab_allocator() )
{
@ -260,7 +263,7 @@ 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.0001, 1.0)
value = clamp(value, 0.000001, 1.0)
config.cam_min_zoom = value
}
}
@ -357,7 +360,7 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
{
ui_parent(dd_app_config.vbox)
scope(theme_button)
btn := ui_button(str_intern_fmt("settings_menu.cam_zoom_mode.%s.btn", entry).str)
btn := ui_button(str_fmt("settings_menu.cam_zoom_mode.%s.btn", entry))
{
using btn
layout.size.min = {100, 25}
@ -368,7 +371,7 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
ui_parent(btn)
scope(theme_text)
text_widget := ui_text(str_intern_fmt("settings_menu.cam_zoom_mode.%s.text", entry).str, str_intern_fmt("%s", entry))
text_widget := ui_text(str_fmt("settings_menu.cam_zoom_mode.%s.text", entry), str_intern_fmt("%s", entry))
}
if btn.pressed {
@ -496,38 +499,77 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
}
}
Font_Size_Screen_Scalar:
Text_Snap_Glyph_Shape_Position:
{
ui_settings_entry_inputbox( & font_size_screen_scalar_input, false, "settings_menu.font_size_screen_scalar", str_intern("Font: Size Screen Scalar"),
ui_settings_entry_inputbox( & text_snap_glyph_shape_posiiton, false, "settings_menu.text_snap_glyph_shape_posiiton", str_intern("Text: Snap Glyph Shape Position"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
disallow_decimal = false,
digit_min = 0.01,
digit_max = 9999,
max_length = 5,
digit_min = 0,
digit_max = 1,
max_length = 1,
}
)
using font_size_screen_scalar_input
using text_snap_glyph_shape_posiiton
if was_active
{
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
value = clamp(value, 0, 1)
value_b32 := cast(b32) i32(value)
if config.text_snap_glyph_shape_position != value_b32 {
font_provider_flush_caches()
font_provider_set_snap_glyph_shape_position( value_b32 )
config.text_snap_glyph_shape_position = value_b32
}
}
}
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", i32(config.text_snap_glyph_shape_position) ) ))
}
}
Font_Size_Canvas_Scalar:
Text_Snap_Glyph_Render_Height:
{
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( & text_snap_glyph_render_height, false, "settings_menu.text_snap_glyph_render_height", str_intern("Text: Snap Glyph Render Height"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
disallow_decimal = false,
digit_min = 0,
digit_max = 1,
max_length = 1,
}
)
using text_snap_glyph_render_height
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0, 1)
value_b32 := cast(b32) i32(value)
if config.text_snap_glyph_render_height != value_b32 {
font_provider_flush_caches()
font_provider_set_snap_glyph_render_height( value_b32 )
config.text_snap_glyph_render_height = value_b32
}
}
}
else
{
clear( input_str )
append( & input_str, to_runes( str_fmt("%v", i32(config.text_snap_glyph_render_height) ) ))
}
}
Text_Size_Screen_Scalar:
{
ui_settings_entry_inputbox( & text_size_screen_scalar_input, false, "settings_menu.text_size_screen_scalar", str_intern("Text: Size Screen Scalar"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
@ -537,20 +579,79 @@ ui_settings_menu_builder :: proc( captures : rawptr = nil ) -> ( should_raise :
max_length = 5,
}
)
using font_size_canvas_scalar_input
using text_size_screen_scalar_input
if was_active
{
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_screen_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_screen_scalar)))
}
}
Text_Size_Canvas_Scalar:
{
ui_settings_entry_inputbox( & text_size_canvas_scalar_input, false, "settings_menu.text_size_canvas_scalar", str_intern("Text: Size Canvas Scalar"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
disallow_decimal = false,
digit_min = 0.01,
digit_max = 9999,
max_length = 5,
}
)
using text_size_canvas_scalar_input
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.text_size_canvas_scalar = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.text_size_canvas_scalar)))
}
}
Text_Alpha_Sharpen:
{
ui_settings_entry_inputbox( & text_alpha_sharpen, false, "settings_menu.text_alpha_sharpen", str_intern("Text: Alpha Sharpen"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
disallow_decimal = false,
digit_min = 0.001,
digit_max = 999,
max_length = 4,
}
)
using text_alpha_sharpen
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0, 10.0)
config.text_alpha_sharpen = value
font_provider_set_alpha_sharpen(value)
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.text_alpha_sharpen)))
}
}
}

View File

@ -8,7 +8,7 @@ import "core:os"
Str_App_State := "App State"
#region("Memory")
//region Memory
Memory_App : Memory
@ -133,9 +133,9 @@ MemoryConfig :: struct {
commit_initial_filebuffer : uint,
}
#endregion("Memory")
//endregion Memory
#region("State")
//region State
// ALl nobs available for this application
AppConfig :: struct {
@ -161,8 +161,11 @@ AppConfig :: struct {
color_theme : AppColorTheme,
font_size_screen_scalar : f32,
font_size_canvas_scalar : f32,
text_snap_glyph_shape_position : b32,
text_snap_glyph_render_height : b32,
text_size_screen_scalar : f32,
text_size_canvas_scalar : f32,
text_alpha_sharpen : f32,
}
AppWindow :: struct {
@ -221,7 +224,7 @@ State :: struct {
staged_input_events : Array(InputEvent),
// TODO(Ed): Add a multi-threaded guard for accessing or mutating staged_input_events.
debug : DebugData,
debug : ScratchData,
project : Project,
@ -252,7 +255,6 @@ State :: struct {
font_rec_mono_semicasual_reg : FontID,
default_font : FontID,
// Context tracking
// These are used as implicit contextual states when doing immediate mode interfaces
// or for event callbacks that need their context assigned
@ -279,14 +281,15 @@ frametime_delta32 :: #force_inline proc "contextless" () -> f32 {
return cast(f32) get_state().frametime.delta_ms
}
app_config :: #force_inline proc "contextless" () -> AppConfig { return get_state().config }
app_color_theme :: #force_inline proc "contextless" () -> AppColorTheme { return get_state().config.color_theme }
debug_data :: #force_inline proc "contextless" () -> DebugData { return get_state().debug }
get_frametime :: #force_inline proc "contextless" () -> FrameTime { return get_state().frametime }
get_default_font :: #force_inline proc "contextless" () -> FontID { return get_state().default_font }
get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) }
app_config :: #force_inline proc "contextless" () -> AppConfig { return get_state().config }
app_color_theme :: #force_inline proc "contextless" () -> AppColorTheme { return get_state().config.color_theme }
debug_data :: #force_inline proc "contextless" () -> ScratchData { return get_state().debug }
get_frametime :: #force_inline proc "contextless" () -> FrameTime { return get_state().frametime }
get_default_font :: #force_inline proc "contextless" () -> FontID { return get_state().default_font }
get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) }
get_screen_extent :: #force_inline proc "contextless" () -> Extents2 { return get_state().app_window.extent }
get_ui_context_mut :: #force_inline proc "contextless" () -> ^UI_State { return get_state().ui_context }
set_ui_context :: #force_inline proc "contextless" ( ui : ^UI_State ) { get_state().ui_context = ui }
#endregion("State")
//endregion State

View File

@ -89,6 +89,8 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
push( policy_ptr, SlabSizeClass { 32 * Megabyte, 32 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 64 * Megabyte, 64 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 256 * Megabyte, 256 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 512 * Megabyte, 512 * Megabyte, alignment })
// Anything above 128 meg needs to have its own setup looked into.
alloc_error : AllocatorError
@ -136,13 +138,13 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
resolution_height = 600
refresh_rate = 0
cam_min_zoom = 0.025
cam_min_zoom = 0.001
cam_max_zoom = 5.0
cam_zoom_mode = .Digital
cam_zoom_mode = .Smooth
cam_zoom_smooth_snappiness = 4.0
cam_zoom_sensitivity_smooth = 0.5
cam_zoom_sensitivity_digital = 0.25
cam_zoom_scroll_delta_scale = 0.25
cam_zoom_sensitivity_smooth = 2.0
engine_refresh_hz = 0
@ -152,8 +154,11 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
color_theme = App_Thm_Dusk
font_size_screen_scalar = 2.0
font_size_canvas_scalar = 2.0
text_snap_glyph_shape_position = false
text_snap_glyph_render_height = false
text_size_screen_scalar = 1.4
text_size_canvas_scalar = 1.4
text_alpha_sharpen = 0.1
}
Desired_OS_Scheduler_MS :: 1
@ -254,8 +259,8 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// Setup sokol_gp
{
desc := sokol_gp.Desc {
max_vertices = 2 * Mega + 640 * Kilo,
max_commands = 1 * Mega,
max_vertices = 1 * Mega,
max_commands = 500 * Kilo,
}
sokol_gp.setup(desc)
verify( cast(b32) sokol_gp.is_valid(), "Failed to setup sokol gp (graphics painter)" )
@ -265,43 +270,46 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
if true
{
font_provider_startup( & font_provider_ctx )
path_rec_mono_semicasual_reg := strings.concatenate( { Path_Assets, "RecMonoSemicasual-Regular-1.084.ttf" })
font_rec_mono_semicasual_reg = font_load( path_rec_mono_semicasual_reg, 16.0, "RecMonoSemiCasual_Regular" )
// path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } )
// font_squidgy_slimes = font_load( path_squidgy_slimes, 32.0, "Squidgy_Slime" )
// Load default font
// path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } )
// font_firacode = font_load( path_firacode, 16.0, "FiraCode" )
path_fira_cousine := strings.concatenate( { Path_Assets, "FiraCousine-Regular.ttf" } )
font_fira_cousine = font_load( path_fira_cousine, 16.0, "Fira Cousine" )
// path_open_sans := strings.concatenate( { Path_Assets, "OpenSans-Regular.ttf" } )
// font_open_sans = font_load( path_open_sans, 16.0, "OpenSans" )
// path_noto_sans := strings.concatenate( { Path_Assets, "NotoSans-Regular.ttf" } )
// font_noto_sans = font_load( path_noto_sans, 16.0, "NotoSans" )
// path_neodgm_code := strings.concatenate( { Path_Assets, "neodgm_code.ttf"} )
// font_neodgm_code = font_load( path_neodgm_code, 32.0, "NeoDunggeunmo Code" )
// path_rec_mono_linear := strings.concatenate( { Path_Assets, "RecMonoLinear-Regular-1.084.ttf" })
// font_rec_mono_linear = font_load( path_rec_mono_linear, 16.0, "RecMonoLinear Regular" )
path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } )
font_firacode = font_load( path_firacode, "FiraCode", 16.0 )
// path_roboto_regular := strings.concatenate( { Path_Assets, "Roboto-Regular.ttf"} )
// font_roboto_regular = font_load( path_roboto_regular, 32.0, "Roboto Regular" )
// font_roboto_regular = font_load( path_roboto_regular, "Roboto Regular", 32.0 )
// path_fira_cousine := strings.concatenate( { Path_Assets, "FiraCousine-Regular.ttf" } )
// font_fira_cousine = font_load( path_fira_cousine, "Fira Cousine", 16.0 )
default_font = font_firacode
// Aysnc load the others
// path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
// font_arial_unicode_ms = font_load( path_arial_unicode_ms, "Arial_Unicode_MS", 16.0 )
// path_neodgm_code := strings.concatenate( { Path_Assets, "neodgm_code.ttf"} )
// font_neodgm_code = font_load( path_neodgm_code, "NeoDunggeunmo Code", 32.0 )
// path_noto_sans := strings.concatenate( { Path_Assets, "NotoSans-Regular.ttf" } )
// font_noto_sans = font_load( path_noto_sans, "NotoSans", 16.0 )
// path_open_sans := strings.concatenate( { Path_Assets, "OpenSans-Regular.ttf" } )
// font_open_sans = font_load( path_open_sans, "OpenSans", 16.0 )
// path_rec_mono_linear := strings.concatenate( { Path_Assets, "RecMonoLinear-Regular-1.084.ttf" })
// font_rec_mono_linear = font_load( path_rec_mono_linear, "RecMonoLinear Regular", 32.0 )
// path_roboto_mono_regular := strings.concatenate( { Path_Assets, "RobotoMono-Regular.ttf"} )
// font_roboto_mono_regular = font_load( path_roboto_mono_regular, 32.0, "Roboto Mono Regular" )
// font_roboto_mono_regular = font_load( path_roboto_mono_regular, "Roboto Mono Regular", 32.0 )
// path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
// font_arial_unicode_ms = font_load( path_arial_unicode_ms, 16.0, "Arial_Unicode_MS" )
// path_rec_mono_semicasual_reg := strings.concatenate( { Path_Assets, "RecMonoSemicasual-Regular-1.084.ttf" })
// font_rec_mono_semicasual_reg = font_load( path_rec_mono_semicasual_reg, "RecMonoSemiCasual_Regular", 16 )
// path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
// font_arial_unicode_ms = font_load( path_arial_unicode_ms, 16.0, "Arial_Unicode_MS" )
// path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } )
// font_squidgy_slimes = font_load( path_squidgy_slimes, "Squidgy_Slime", 32.0 )
default_font = font_fira_cousine
log( "Default font loaded" )
}
@ -348,18 +356,21 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// }
// Setup workspace UI state
ui_startup( & workspace.ui, cache_table_size = 8 * Kilo, cache_allocator = persistent_slab_allocator() )
ui_startup( & workspace.ui, cache_table_size = 64 * Kilo, cache_allocator = persistent_slab_allocator() )
}
// 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/ve_fontcache.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/sokol_gl.h", allocator = persistent_slab_allocator())
debug.path_lorem = str_fmt("C:\\projects\\SectrPrototype\\examples\\gencpp_singleheader.hpp", allocator = persistent_slab_allocator())
alloc_error : AllocatorError; success : bool
debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_slab_allocator() )
debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_allocator() )
assert(success)
debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) debug.lorem_content, persistent_slab_allocator() )
debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) debug.lorem_content, persistent_allocator() )
verify( alloc_error == .None, "Faield to parse due to allocation failure" )
// Render texture test
@ -451,6 +462,9 @@ hot_reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_
slab_reload( persistent_slab, persistent_allocator() )
str_cache_reload( & string_cache, persistent_allocator(), persistent_allocator() )
str_cache_set_module_ctx( & string_cache )
// input_reload()
{
using input_events
@ -463,9 +477,6 @@ hot_reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_
font_provider_reload( & font_provider_ctx )
str_cache_reload( & string_cache, persistent_allocator(), persistent_allocator() )
str_cache_set_module_ctx( & string_cache )
slab_reload( frame_slab, frame_allocator())
slab_reload( transient_slab, transient_allocator())
@ -520,8 +531,6 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32
context.allocator = frame_slab_allocator()
context.temp_allocator = transient_allocator()
// rl.PollInputEvents()
config := & get_state().config
debug := & get_state().debug
@ -532,8 +541,8 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32
// config.engine_refresh_hz = 165
// config.color_theme = App_Thm_Light
config.color_theme = App_Thm_Dusk
// config.color_theme = App_Thm_Dark
// config.color_theme = App_Thm_Dusk
config.color_theme = App_Thm_Dark
sokol_width := sokol_app.widthf()
sokol_height := sokol_app.heightf()
@ -549,7 +558,6 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32
should_close |= update( host_delta_time_ms )
render()
// rl.SwapScreenBuffer()
return should_close
}

View File

@ -6,7 +6,7 @@ import str "core:strings"
import sokol_app "thirdparty:sokol/app"
#region("Sokol App")
//region Sokol App
sokol_app_init_callback :: proc "c" () {
context = get_state().sokol_context
@ -230,9 +230,9 @@ sokol_app_event_callback :: proc "c" (sokol_event : ^sokol_app.Event)
}
}
#endregion("Sokol App")
//endregion Sokol App
#region("Sokol GFX")
//region Sokol GFX
sokol_gfx_alloc :: proc "c" ( size : uint, user_data : rawptr ) -> rawptr {
context = get_state().sokol_context
@ -277,4 +277,4 @@ sokol_gfx_log_callback :: proc "c" (
log_fmt( "%-80s %s::%v", cloned_msg, cloned_tag, line_nr, level = odin_level )
}
#endregion("Sokol GFX")
//endregion Sokol GFX

View File

@ -56,9 +56,7 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In
cam_zoom_ratio := 1.0 / cam.zoom
screen_size := screen_extent * 2
// TODO(Ed): Eventually will be the viewport extents
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
// ve.configure_snap( ve_ctx, 0, 0 )
font_provider_set_px_scalar( app_config().text_size_canvas_scalar )
Render_Debug:
{
@ -116,13 +114,13 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In
}
}
render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State, ve_ctx : ^ve.Context, ve_render : VE_RenderData, config : AppConfig, debug : ^DebugData )
render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State, ve_ctx : ^ve.Context, ve_render : VE_RenderData, config : AppConfig, debug : ^ScratchData )
{
profile(#procedure)
screen_size := screen_extent * 2
screen_ratio := screen_size.x * ( 1.0 / screen_size.y )
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
font_provider_set_px_scalar( app_config().text_size_screen_scalar )
render_screen_ui( screen_extent, screen_ui, ve_ctx, ve_render )
@ -158,8 +156,8 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
font := font
if font.key == Font_Default.key do font = default_font
shape := shape_text_cached( content, font, size, app_config().font_size_screen_scalar )
draw_text_shape_pos_extent( shape, font, size, pos, color )
shape := shape_text_cached( content, font, size )
ve.draw_shape_view_space(& get_state().font_provider_ctx.ve_ctx, normalize_rgba8(color), get_screen_extent() * 2, screen_to_render_pos(pos), 1.0, 1.0, shape)
}
debug_text :: proc( format : string, args : ..any )
@ -174,14 +172,14 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
position.y -= debug.draw_debug_text_y
content := str_fmt( format, ..args )
text_size := measure_text_size( content, default_font, 14.0, 0.0 )
text_size := measure_text_size( content, default_font, 12.0, 0.0 )
debug_draw_text( content, position, 12.0 )
debug.draw_debug_text_y += text_size.y
}
profile("debug_text_vis")
if true {
fps_size : f32 = 14.0
fps_size : f32 = 20.0
fps_msg := str_fmt( "FPS: %0.2f", fps_avg)
fps_msg_size := measure_text_size( fps_msg, default_font, fps_size, 0.0 )
fps_msg_pos := screen_get_corners().top_right - { fps_msg_size.x, fps_msg_size.y }
@ -238,11 +236,11 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
hot_box := ui_box_from_key( ui.curr_cache, ui.hot )
active_box := ui_box_from_key( ui.curr_cache, ui.active )
if hot_box != nil {
debug_text("Worksapce Hot Box : %v", hot_box.label.str )
debug_text("Worksapce Hot Box : %v", hot_box.label )
debug_text("Workspace Hot Range2: %v", hot_box.computed.bounds.pts)
}
if active_box != nil{
debug_text("Workspace Active Box: %v", active_box.label.str )
debug_text("Workspace Active Box: %v", active_box.label )
}
}
@ -255,22 +253,14 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
hot_box := ui_box_from_key( ui.curr_cache, ui.hot )
active_box := ui_box_from_key( ui.curr_cache, ui.active )
if hot_box != nil {
debug_text("Hot Box : %v", hot_box.label.str )
debug_text("Hot Box : %v", hot_box.label )
debug_text("Hot Range2: %v", hot_box.computed.bounds.pts)
}
if active_box != nil{
debug_text("Active Box: %v", active_box.label.str )
debug_text("Active Box: %v", active_box.label )
}
}
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 )
}
@ -286,7 +276,6 @@ render_screen_ui :: proc( screen_extent : Extents2, ui : ^UI_State, ve_ctx : ^ve
render_list_box := array_to_slice( ui.render_list_box )
render_list_text := array_to_slice( ui.render_list_text )
render_ui_via_box_list( render_list_box, render_list_text, screen_extent, ve_ctx, ve_render )
// render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render )
}
when UI_Render_Method == .Depth_First
{
@ -294,165 +283,6 @@ render_screen_ui :: proc( screen_extent : Extents2, ui : ^UI_State, ve_ctx : ^ve
}
}
render_text_layer :: proc( screen_extent : Vec2, ve_ctx : ^ve.Context, render : VE_RenderData )
{
profile("VEFontCache: render text layer")
using render
Bindings :: gfx.Bindings
Range :: gfx.Range
ShaderStage :: gfx.Shader_Stage
vbuf_layer_slice, ibuf_layer_slice, calls_layer_slice := ve.get_draw_list_layer( ve_ctx )
vbuf_ve_range := Range{ raw_data(vbuf_layer_slice), cast(uint) len(vbuf_layer_slice) * size_of(ve.Vertex) }
ibuf_ve_range := Range{ raw_data(ibuf_layer_slice), cast(uint) len(ibuf_layer_slice) * size_of(u32) }
gfx.append_buffer( draw_list_vbuf, vbuf_ve_range )
gfx.append_buffer( draw_list_ibuf, ibuf_ve_range )
ve.flush_draw_list_layer( ve_ctx )
screen_width := u32(screen_extent.x * 2)
screen_height := u32(screen_extent.y * 2)
for & draw_call in calls_layer_slice
{
watch := draw_call
// profile("VEFontCache: draw call")
num_indices := draw_call.end_index - draw_call.start_index
switch draw_call.pass
{
// 1. Do the glyph rendering pass
// Glyphs are first rendered to an intermediate 2k x 512px R8 texture
case .Glyph:
// profile("VEFontCache: draw call: glyph")
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
width := ve_ctx.glyph_buffer.width
height := ve_ctx.glyph_buffer.height
pass := glyph_pass
if draw_call.clear_before_draw {
pass.action.colors[0].load_action = .CLEAR
pass.action.colors[0].clear_value.a = 1.0
}
gfx.begin_pass( pass )
gfx.apply_viewport ( 0,0, width, height, origin_top_left = true )
gfx.apply_scissor_rect( 0,0, width, height, origin_top_left = true )
gfx.apply_pipeline( glyph_pipeline )
bindings := Bindings {
vertex_buffers = {
0 = draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = draw_list_ibuf,
index_buffer_offset = 0,
}
gfx.apply_bindings( bindings )
// 2. Do the atlas rendering pass
// A simple 16-tap box downsample shader is then used to blit from this intermediate texture to the final atlas location
case .Atlas:
// profile("VEFontCache: draw call: atlas")
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
width := ve_ctx.atlas.width
height := ve_ctx.atlas.height
pass := atlas_pass
if draw_call.clear_before_draw {
pass.action.colors[0].load_action = .CLEAR
pass.action.colors[0].clear_value.a = 1.0
}
gfx.begin_pass( pass )
gfx.apply_viewport ( 0, 0, width, height, origin_top_left = true )
gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true )
gfx.apply_pipeline( atlas_pipeline )
fs_uniform := Ve_Blit_Atlas_Fs_Params { region = cast(i32) draw_call.region }
gfx.apply_uniforms( UB_ve_blit_atlas_fs_params, Range { & fs_uniform, size_of(Ve_Blit_Atlas_Fs_Params) })
gfx.apply_bindings(Bindings {
vertex_buffers = {
0 = draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = draw_list_ibuf,
index_buffer_offset = 0,
images = { IMG_ve_blit_atlas_src_texture = glyph_rt_color, },
samplers = { SMP_ve_blit_atlas_src_sampler = glyph_rt_sampler, },
})
// 3. Use the atlas to then render the text.
case .None, .Target, .Target_Uncached:
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
// profile("VEFontCache: draw call: target")
pass := screen_pass
pass.swapchain = sokol_glue.swapchain()
gfx.begin_pass( pass )
gfx.apply_viewport ( 0, 0, screen_width, screen_height, origin_top_left = true )
gfx.apply_scissor_rect( 0, 0, screen_width, screen_height, origin_top_left = true )
gfx.apply_pipeline( screen_pipeline )
src_rt := atlas_rt_color
src_sampler := atlas_rt_sampler
fs_target_uniform := Ve_Draw_Text_Fs_Params {
down_sample = 0,
colour = draw_call.colour,
}
if draw_call.pass == .Target_Uncached {
fs_target_uniform.down_sample = 1
src_rt = glyph_rt_color
src_sampler = glyph_rt_sampler
}
gfx.apply_uniforms( UB_ve_draw_text_fs_params, Range { & fs_target_uniform, size_of(Ve_Draw_Text_Fs_Params) })
gfx.apply_bindings(Bindings {
vertex_buffers = {
0 = draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = draw_list_ibuf,
index_buffer_offset = 0,
images = { IMG_ve_draw_text_src_texture = src_rt, },
samplers = { SMP_ve_draw_text_src_sampler = src_sampler, },
})
}
if num_indices != 0 {
gfx.draw( draw_call.start_index, num_indices, 1 )
}
gfx.end_pass()
}
}
when false {
render_ui_via_box_tree :: proc( ui : ^UI_State, screen_extent : Vec2, ve_ctx : ^ve.Context, ve_render : VE_RenderData, cam : ^Camera = nil )
{
@ -579,8 +409,7 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
cam != nil ? cam.position.y : 0,
}
screen_size := screen_extent * 2
screen_size_norm := (1.0 / screen_size)
screen_size := screen_extent * 2
layer_left : b32 = true
for layer_left
@ -590,7 +419,7 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
box_layer_done : b32 = false
for box_id < cast(i32) len(box_list) && ! box_layer_done
{
profile("GP_Render")
// profile("GP_Render")
box_layer_done = b32(box_id > 0) && box_list[ box_id - 1 ].layer_signal
entry := box_list[box_id]
@ -629,7 +458,7 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
}
if shape_enqueued {
profile("render ui box_layer")
// profile("render ui box_layer")
render_flush_gp()
shape_enqueued = false
}
@ -638,7 +467,7 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
text_layer_done : b32 = false
for text_id < cast(i32) len(text_list) && ! text_layer_done
{
profile("Text_Render")
// profile("Text_Render")
entry := text_list[text_id]
font := entry.font.key != 0 ? entry.font : default_font
@ -648,11 +477,16 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
if len(entry.text) == 0 do continue
text_enqueued = true
ve_id := font_provider_font_def(entry.font)
color := normalize_rgba8(entry.color)
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 )
canvas_position := ws_view_to_render_pos(entry.position)
ve.draw_text_view_space(ve_ctx, ve_id, entry.font_size, color, screen_size, canvas_position, 1.0, cam.zoom, entry.text )
}
else {
draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color )
screen_position := screen_to_render_pos(entry.position)
ve.draw_text_view_space(ve_ctx, ve_id, entry.font_size, color, screen_size, screen_position, 1.0, 1.0, entry.text)
}
}
@ -666,63 +500,190 @@ render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_
}
}
when false {
render_gp_layer :: proc ( )
render_text_layer :: proc( screen_extent : Vec2, ve_ctx : ^ve.Context, render : VE_RenderData )
{
profile("GP_Render")
profile("VEFontCache: render text layer")
using render
shape_enqueued : b32 = false
box_layer_done : b32 = false
for box_id < cast(i32) len(box_list) && ! box_layer_done
Bindings :: gfx.Bindings
Range :: gfx.Range
ShaderStage :: gfx.Shader_Stage
vbuf_layer_slice, ibuf_layer_slice, calls_layer_slice := ve.get_draw_list_layer( ve_ctx )
vbuf_ve_range := Range{ raw_data(vbuf_layer_slice), cast(uint) len(vbuf_layer_slice) * size_of(ve.Vertex) }
ibuf_ve_range := Range{ raw_data(ibuf_layer_slice), cast(uint) len(ibuf_layer_slice) * size_of(u32) }
gfx.append_buffer( draw_list_vbuf, vbuf_ve_range )
gfx.append_buffer( draw_list_ibuf, ibuf_ve_range )
ve.flush_draw_list_layer( ve_ctx )
screen_width := u32(screen_extent.x * 2)
screen_height := u32(screen_extent.y * 2)
atlas := & ve_ctx.atlas
glyph_buffer := & ve_ctx.glyph_buffer
atlas_size : Vec2 = vec2(atlas.size)
glyph_buf_size : Vec2 = vec2(glyph_buffer.size)
for & draw_call in calls_layer_slice
{
profile("GP_Render")
box_layer_done = b32(box_id > 0) && box_list[ box_id - 1 ].layer_signal
watch := draw_call
// profile("VEFontCache: draw call")
entry := box_list[box_id]
num_indices := draw_call.end_index - draw_call.start_index
corner_radii_total : f32 = 0
for radius in entry.corner_radii do corner_radii_total += radius
if entry.bg_color.a != 0
switch draw_call.pass
{
render_set_color( entry.bg_color )
if corner_radii_total > 0 do draw_rect_rounded( entry.bounds, entry.corner_radii, 16 )
else do draw_rect( entry.bounds)
shape_enqueued = true
// 1. Do the glyph rendering pass
// Glyphs are first rendered to an intermediate 2k x 512px R8 texture
case .Glyph:
// profile("VEFontCache: draw call: glyph")
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
width := ve_ctx.glyph_buffer.size.x
height := ve_ctx.glyph_buffer.size.y
pass := glyph_pass
if draw_call.clear_before_draw {
pass.action.colors[0].load_action = .CLEAR
pass.action.colors[0].clear_value.a = 1.0
}
gfx.begin_pass( pass )
gfx.apply_viewport ( 0,0, width, height, origin_top_left = true )
gfx.apply_scissor_rect( 0,0, width, height, origin_top_left = true )
gfx.apply_pipeline( glyph_pipeline )
bindings := Bindings {
vertex_buffers = {
0 = draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = draw_list_ibuf,
index_buffer_offset = 0,
}
gfx.apply_bindings( bindings )
// 2. Do the atlas rendering pass
// A simple 16-tap box downsample shader is then used to blit from this intermediate texture to the final atlas location
case .Atlas:
// profile("VEFontCache: draw call: atlas")
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
width := ve_ctx.atlas.size.x
height := ve_ctx.atlas.size.y
pass := atlas_pass
if draw_call.clear_before_draw {
pass.action.colors[0].load_action = .CLEAR
pass.action.colors[0].clear_value.a = 1.0
}
gfx.begin_pass( pass )
gfx.apply_viewport ( 0, 0, width, height, origin_top_left = true )
gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true )
gfx.apply_pipeline( atlas_pipeline )
fs_uniform := Ve_Blit_Atlas_Fs_Params {
glyph_buffer_size = glyph_buf_size,
over_sample = glyph_buffer.over_sample.x,
region = cast(i32) draw_call.region,
}
gfx.apply_uniforms( UB_ve_blit_atlas_fs_params, Range { & fs_uniform, size_of(Ve_Blit_Atlas_Fs_Params) })
gfx.apply_bindings(Bindings {
vertex_buffers = {
0 = draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = draw_list_ibuf,
index_buffer_offset = 0,
images = { IMG_ve_blit_atlas_src_texture = glyph_rt_color, },
samplers = { SMP_ve_blit_atlas_src_sampler = glyph_rt_sampler, },
})
// 3. Use the atlas to then render the text.
case .None, .Target, .Target_Uncached:
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
// profile("VEFontCache: draw call: target")
pass := screen_pass
pass.swapchain = sokol_glue.swapchain()
gfx.begin_pass( pass )
gfx.apply_viewport ( 0, 0, screen_width, screen_height, origin_top_left = true )
gfx.apply_scissor_rect( 0, 0, screen_width, screen_height, origin_top_left = true )
gfx.apply_pipeline( screen_pipeline )
src_rt := atlas_rt_color
src_sampler := atlas_rt_sampler
fs_target_uniform := Ve_Draw_Text_Fs_Params {
// glyph_buffer_size = glyph_buf_size,
over_sample = glyph_buffer.over_sample.x,
colour = draw_call.colour,
}
if draw_call.pass == .Target_Uncached {
// fs_target_uniform.over_sample = 1.0
src_rt = glyph_rt_color
src_sampler = glyph_rt_sampler
}
gfx.apply_uniforms( UB_ve_draw_text_fs_params, Range { & fs_target_uniform, size_of(Ve_Draw_Text_Fs_Params) })
gfx.apply_bindings(Bindings {
vertex_buffers = {
0 = draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = draw_list_ibuf,
index_buffer_offset = 0,
images = { IMG_ve_draw_text_src_texture = src_rt, },
samplers = { SMP_ve_draw_text_src_sampler = src_sampler, },
})
}
if entry.border_color.a != 0 && entry.border_width > 0
{
render_set_color( entry.border_color )
if corner_radii_total > 0 do draw_rect_rounded_border( entry.bounds, entry.corner_radii, entry.border_width, 16 )
else do draw_rect_border( entry.bounds, entry.border_width )
shape_enqueued = true
if num_indices != 0 {
gfx.draw( draw_call.start_index, num_indices, 1 )
}
if debug.draw_ui_box_bounds_points
{
render_set_color(Color_Red)
draw_filled_circle(entry.bounds.min.x, entry.bounds.min.y, circle_radius, 24)
render_set_color(Color_Blue)
draw_filled_circle(entry.bounds.max.x, entry.bounds.max.y, circle_radius, 24)
shape_enqueued = true
}
box_id += 1
}
box_id += 1
if shape_enqueued {
profile("render ui box_layer")
render_flush_gp()
gfx.end_pass()
}
}
//region Helpers
draw_shape :: proc(color : RGBAN, screen_size, position, scale : Vec2, zoom : f32, shape : ShapedText)
{
ve_ctx := & get_state().font_provider_ctx.ve_ctx
ve.draw_shape_view_space(ve_ctx, color, screen_size, position, scale, zoom, shape )
}
#region("Helpers")
draw_text :: proc(font : FontID, px_size : f32, color : RGBAN, screen_size, position, scale : Vec2, zoom : f32, text : string)
{
ve_ctx := & get_state().font_provider_ctx.ve_ctx
ve_id := font_provider_font_def(font)
ve.draw_text_view_space(ve_ctx, ve_id, px_size, color, screen_size, position, scale, zoom, text )
}
draw_filled_circle :: proc(x, y, radius: f32, edges: int)
{
@ -899,133 +860,6 @@ draw_rect_rounded_border :: proc(rect: Range2, radii: [4]f32, border_width: f32,
draw_corner_border(rect.max.x - bottom_right, rect.max.y - bottom_right, bottom_right, max(bottom_right - border_width, 0), 0, segments)
}
// Draw text using a string and normalized render coordinates
draw_text_string_pos_norm :: #force_inline proc( text : string, id : FontID, font_size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 )
{
state := get_state(); using state
width := app_window.extent.x * 2
height := app_window.extent.y * 2
ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size * config.font_size_screen_scalar )
color_norm := normalize_rgba8(color)
screen_size_norm := Vec2{1 / width, 1 / height}
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar), text )
return
}
// Draw text using a string and extent-based screen coordinates
draw_text_string_pos_extent :: #force_inline proc( text : string, id : FontID, font_size : f32, pos : Vec2, color := Color_White )
{
profile(#procedure)
state := get_state(); using state
screen_size := app_window.extent * 2
render_pos := screen_to_render_pos(pos)
normalized_pos := render_pos * (1.0 / screen_size)
draw_text_string_pos_norm( text, id, font_size, normalized_pos, color )
}
// Draw text using a string and normalized render coordinates
draw_text_shape_pos_norm :: #force_inline proc( shape : ShapedText, id : FontID, font_size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 )
{
state := get_state(); using state
width := app_window.extent.x * 2
height := app_window.extent.y * 2
ve_id, resolved_size := font_provider_resolve_draw_id( id, font_size * config.font_size_screen_scalar )
color_norm := normalize_rgba8(color)
screen_size_norm := Vec2 { 1 / width, 1 / height }
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, f32(resolved_size), pos, screen_size_norm * scale * (1 / config.font_size_screen_scalar), shape )
return
}
// Draw text using a string and extent-based screen coordinates
draw_text_shape_pos_extent :: #force_inline proc( shape : ShapedText, id : FontID, font_size : f32, pos : Vec2, color := Color_White )
{
profile(#procedure)
state := get_state(); using state
screen_size := app_window.extent * 2
render_pos := screen_to_render_pos(pos)
normalized_pos := render_pos * (1.0 / screen_size)
draw_text_shape_pos_norm( shape, id, font_size, normalized_pos, color )
}
draw_text_string_pos_extent_zoomed :: #force_inline proc( text : string, id : FontID, size : f32, pos, cam_offset, screen_size, screen_size_norm : Vec2, zoom : f32, color := Color_White )
{
profile(#procedure)
// state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state.
config := app_config()
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
ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size )
f32_resolved_size := f32(resolved_size)
text_scale : Vec2 = screen_size_norm
// if config.cam_zoom_mode == .Smooth
{
diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size
text_scale = diff_scalar * screen_size_norm
text_scale.x = clamp( text_scale.x, 0, screen_size.x )
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_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 )
}
draw_text_shape_pos_extent_zoomed :: #force_inline proc( shape : ShapedText, id : FontID, size : f32, pos, cam_offset, screen_size, screen_size_norm : Vec2, zoom : f32, color := Color_White )
{
profile(#procedure)
state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state.
zoom_adjust_size := size * zoom
// Over-sample font-size for any render under a camera
over_sample : f32 = f32(state.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
ve_id, resolved_size := font_provider_resolve_draw_id( id, zoom_adjust_size )
f32_resolved_size := f32(resolved_size)
text_scale : Vec2 = screen_size_norm
// if config.cam_zoom_mode == .Smooth
{
diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size
text_scale = diff_scalar * screen_size_norm
text_scale.x = clamp( text_scale.x, 0, screen_size.x )
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_colour( & font_provider_ctx.ve_ctx, color_norm )
ve.draw_text_shape( & font_provider_ctx.ve_ctx, ve_id, f32_resolved_size, normalized_pos, text_scale, shape )
}
// TODO(Ed): Eventually the workspace will need a viewport for drawing text
render_flush_gp :: #force_inline proc()
@ -1056,4 +890,4 @@ render_set_view_space :: #force_inline proc( extent : Extents2 )
gp.project( -extent.x, extent.x, extent.y, -extent.y )
}
#endregion("Helpers")
//endregion Helpers

View File

@ -302,17 +302,21 @@ 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().text_size_screen_scalar )
ui_screen_tick( & get_state().screen_ui )
//region WorkspaceImgui Tick
if true
{
font_provider_set_px_scalar( app_config().text_size_canvas_scalar )
profile("Workspace Imgui")
// Creates the root box node, set its as the first parent.
ui_graph_build( & state.project.workspace.ui )
ui := ui_context
ui.zoom_scale = state.project.workspace.cam.zoom
frame_style_flags : UI_LayoutFlags = {
.Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,

View File

@ -23,7 +23,8 @@ FontID :: struct {
FontDef :: struct {
path_file : string,
default_size : i32,
size_table : [Font_Largest_Px_Size / Font_Size_Interval] ve.Font_ID,
ve_id : ve.Font_ID
// size_table : [Font_Largest_Px_Size / Font_Size_Interval] ve.Font_ID,
}
FontProviderContext :: struct
@ -46,28 +47,35 @@ font_provider_startup :: proc( ctx : ^FontProviderContext )
verify( error == AllocatorError.None, "Failed to allocate font_cache" )
ve.startup( & ve_ctx, .STB_TrueType, allocator = persistent_slab_allocator() )
ve_ctx.glyph_buffer.over_sample = { 4,4 }
// ve_ctx.glyph_buffer.over_sample = { 4,4 }
log("VEFontCached initialized")
font_provider_setup_sokol_gfx_objects( & render, ve_ctx )
}
font_provider_reload :: proc( ctx : ^FontProviderContext )
{
ctx.ve_ctx.glyph_buffer.over_sample = { 4,4 } * 1.0
// ctx.ve_ctx.glyph_buffer.over_sample = { 4,4 } * 2
hmap_chained_reload( ctx.font_cache, persistent_allocator())
ve.hot_reload( & ctx.ve_ctx, persistent_slab_allocator() )
ve.clear_atlas_region_caches(& ctx.ve_ctx)
ve.clear_shape_cache(& ctx.ve_ctx)
}
font_provider_flush_caches :: proc()
{
ve_ctx := & get_state().font_provider_ctx.ve_ctx
ve.clear_atlas_region_caches(ve_ctx)
ve.clear_shape_cache(ve_ctx)
}
font_provider_shutdown :: proc( ctx : ^FontProviderContext )
{
ve.shutdown( & ctx.ve_ctx )
}
font_load :: proc(path_file : string,
desired_id : string = Font_Load_Gen_ID,
default_size : i32 = Font_Load_Use_Default_Size,
desired_id : string = Font_Load_Gen_ID
) -> FontID
{
provider_data := & get_state().font_provider_ctx; using provider_data
@ -76,6 +84,7 @@ font_load :: proc(path_file : string,
profile(msg)
log(msg)
font_data, read_succeded : = os.read_entire_file( path_file, persistent_allocator() )
verify( b32(read_succeded), str_fmt("Failed to read font file for: %v", path_file) )
font_data_size := cast(i32) len(font_data)
@ -103,59 +112,67 @@ font_load :: proc(path_file : string,
def.path_file = path_file
def.default_size = default_size
for font_size : i32 = clamp( Font_Size_Interval, 2, Font_Size_Interval ); font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval
{
// logf("Loading at size %v", font_size)
id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval)
ve_id := & def.size_table[id - 1]
ve_ret_id := ve.load_font( & ve_ctx, desired_id, font_data, f32(font_size) )
(ve_id^) = ve_ret_id
}
error : ve.Load_Font_Error
def.ve_id, error = ve.load_font( & ve_ctx, desired_id, font_data )
fid := FontID { key, desired_id }
return fid
}
font_provider_set_alpha_sharpen :: #force_inline proc( scalar : f32 ) {
ve.set_alpha_scalar( & get_state().font_provider_ctx.ve_ctx, scalar )
}
font_provider_set_px_scalar :: #force_inline proc( scalar : f32 ) {
ve.set_px_scalar( & get_state().font_provider_ctx.ve_ctx, scalar )
}
font_provider_set_snap_glyph_shape_position :: #force_inline proc( should_snap : b32 ) {
ve.set_snap_glyph_shape_position( & get_state().font_provider_ctx.ve_ctx, should_snap )
}
font_provider_set_snap_glyph_render_height :: #force_inline proc( should_snap : b32 ) {
ve.set_snap_glyph_render_height( & get_state().font_provider_ctx.ve_ctx, should_snap )
}
Font_Use_Default_Size :: f32(0.0)
font_provider_resolve_draw_id :: #force_inline proc( id : FontID, size := Font_Use_Default_Size ) -> (ve_id :ve.Font_ID, resolved_size : i32)
font_provider_font_def :: #force_inline proc( font : FontID) -> ve.Font_ID {
provider := & get_state().font_provider_ctx;
def := hmap_chained_get( provider.font_cache, font.key )
return def.ve_id
}
measure_text_shape :: #force_inline proc( shape : ShapedText ) -> Vec2
{
provider_data := get_state().font_provider_ctx; using provider_data
def := hmap_chained_get( font_cache, id.key )
size := size == 0.0 ? f32(def.default_size) : size
even_size := math.round(size * (1.0 / f32(Font_Size_Interval))) * f32(Font_Size_Interval)
resolved_size = clamp( i32( even_size), 2, Font_Largest_Px_Size )
id := (resolved_size / Font_Size_Interval) + (resolved_size % Font_Size_Interval)
ve_id = def.size_table[ id - 1 ]
return
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 )
measured := ve.measure_text_size( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text )
ve_id := font_provider_font_def(font)
measured := ve.measure_text_size( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(font_size), text )
return measured
}
get_font_vertical_metrics :: #force_inline proc ( font : FontID, font_size := Font_Use_Default_Size ) -> ( ascent, descent, line_gap : f32 )
{
ve_id, size := font_provider_resolve_draw_id( font, font_size )
ascent, descent, line_gap = ve.get_font_vertical_metrics( & get_state().font_provider_ctx.ve_ctx, ve_id, font_size )
ve_id := font_provider_font_def(font)
ascent, descent, line_gap = ve.get_font_vertical_metrics( get_state().font_provider_ctx.ve_ctx, ve_id, font_size )
return
}
shape_text_cached_latin :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, scalar : f32 ) -> ShapedText
shape_text_cached_latin :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size ) -> ShapedText
{
ve_id, size := font_provider_resolve_draw_id( font, font_size * scalar )
shape := ve.shape_text_latin( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text )
ve_id := font_provider_font_def(font)
shape := ve.shape_text( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(font_size), text, ve.shaper_shape_text_latin )
return shape
}
shape_text_cached :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size, scalar : f32 ) -> ShapedText
shape_text_cached :: #force_inline proc( text : string, font : FontID, font_size := Font_Use_Default_Size ) -> ShapedText
{
ve_id, size := font_provider_resolve_draw_id( font, font_size * scalar )
shape := ve.shape_text_advanced( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(size), text )
ve_id := font_provider_font_def(font)
shape := ve.shape_text( & get_state().font_provider_ctx.ve_ctx, ve_id, f32(font_size), text )
return shape
}

View File

@ -12,13 +12,13 @@ VE_RenderData :: struct {
atlas_shader : sokol_gfx.Shader,
screen_shader : sokol_gfx.Shader,
// 2k x 512, R8
// ve.glyph_buffer.(width, height), R8
glyph_rt_color : sokol_gfx.Image,
glyph_rt_depth : sokol_gfx.Image,
// glyph_rt_resolve : sokol_gfx.Image,
glyph_rt_sampler : sokol_gfx.Sampler,
// 4k x 2k, R8
// ve.atlas.(width, height), R8
atlas_rt_color : sokol_gfx.Image,
atlas_rt_depth : sokol_gfx.Image,
// atlas_rt_resolve : sokol_gfx.Image,
@ -42,11 +42,10 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
BlendState :: sokol_gfx.Blend_State
BorderColor :: sokol_gfx.Border_Color
BufferDesciption :: sokol_gfx.Buffer_Desc
BufferUsage :: sokol_gfx.Usage
BufferType :: sokol_gfx.Buffer_Type
ColorTargetState :: sokol_gfx.Color_Target_State
Filter :: sokol_gfx.Filter
ImageDesc :: sokol_gfx.Image_Desc
ImageUsage :: sokol_gfx.Image_Usage
PassAction :: sokol_gfx.Pass_Action
Range :: sokol_gfx.Range
ResourceState :: sokol_gfx.Resource_State
@ -67,16 +66,14 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
screen_shader = sokol_gfx.make_shader(ve_draw_text_shader_desc(backend) )
draw_list_vbuf = sokol_gfx.make_buffer( BufferDesciption {
size = size_of([4]f32) * 2 * Mega,
usage = BufferUsage.STREAM,
type = BufferType.VERTEXBUFFER,
size = size_of([4]f32) * 4 * Mega,
usage = {vertex_buffer = true, stream_update = true},
})
verify( sokol_gfx.query_buffer_state( draw_list_vbuf) < ResourceState.FAILED, "Failed to make draw_list_vbuf" )
draw_list_ibuf = sokol_gfx.make_buffer( BufferDesciption {
size = size_of(u32) * 1 * Mega,
usage = BufferUsage.STREAM,
type = BufferType.INDEXBUFFER,
size = size_of(u32) * 6 * Mega,
usage = {index_buffer = true, stream_update = true},
})
verify( sokol_gfx.query_buffer_state( draw_list_ibuf) < ResourceState.FAILED, "Failed to make draw_list_iubuf" )
@ -141,12 +138,11 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
{
glyph_rt_color = sokol_gfx.make_image( ImageDesc {
type = ._2D,
render_target = true,
width = i32(ve_ctx.glyph_buffer.width),
height = i32(ve_ctx.glyph_buffer.height),
usage = ImageUsage { render_attachment = true, immutable = true },
width = i32(ve_ctx.glyph_buffer.size.x),
height = i32(ve_ctx.glyph_buffer.size.y),
num_slices = 1,
num_mipmaps = 1,
usage = .IMMUTABLE,
pixel_format = .R8,
sample_count = 1,
// TODO(Ed): Setup labels for debug tracing/logging
@ -156,12 +152,11 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
glyph_rt_depth = sokol_gfx.make_image( ImageDesc {
type = ._2D,
render_target = true,
width = i32(ve_ctx.glyph_buffer.width),
height = i32(ve_ctx.glyph_buffer.height),
usage = ImageUsage { render_attachment = true, immutable = true },
width = i32(ve_ctx.glyph_buffer.size.x),
height = i32(ve_ctx.glyph_buffer.size.y),
num_slices = 1,
num_mipmaps = 1,
usage = .IMMUTABLE,
pixel_format = .DEPTH,
sample_count = 1,
})
@ -172,8 +167,8 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
mipmap_filter = Filter.NEAREST,
wrap_u = .CLAMP_TO_EDGE,
wrap_v = .CLAMP_TO_EDGE,
min_lod = -1000.0,
max_lod = 1000.0,
min_lod = -1.0,
max_lod = 1.0,
border_color = BorderColor.OPAQUE_BLACK,
compare = .NEVER,
max_anisotropy = 1,
@ -278,12 +273,11 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
{
atlas_rt_color = sokol_gfx.make_image( ImageDesc {
type = ._2D,
render_target = true,
width = i32(ve_ctx.atlas.width),
height = i32(ve_ctx.atlas.height),
usage = { render_attachment = true, immutable = true },
width = i32(ve_ctx.atlas.size.x),
height = i32(ve_ctx.atlas.size.y),
num_slices = 1,
num_mipmaps = 1,
usage = .IMMUTABLE,
pixel_format = .R8,
sample_count = 1,
// TODO(Ed): Setup labels for debug tracing/logging
@ -293,12 +287,11 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
atlas_rt_depth = sokol_gfx.make_image( ImageDesc {
type = ._2D,
render_target = true,
width = i32(ve_ctx.atlas.width),
height = i32(ve_ctx.atlas.height),
usage = { render_attachment = true, immutable = true },
width = i32(ve_ctx.atlas.size.x),
height = i32(ve_ctx.atlas.size.y),
num_slices = 1,
num_mipmaps = 1,
usage = .IMMUTABLE,
pixel_format = .DEPTH,
sample_count = 1,
})
@ -307,14 +300,14 @@ font_provider_setup_sokol_gfx_objects :: proc( ctx : ^VE_RenderData, ve_ctx : ve
atlas_rt_sampler = sokol_gfx.make_sampler( SamplerDescription {
min_filter = Image_Filter,
mag_filter = Image_Filter,
mipmap_filter = Filter.LINEAR,
mipmap_filter = Filter.NEAREST,
wrap_u = .CLAMP_TO_EDGE,
wrap_v = .CLAMP_TO_EDGE,
min_lod = -1000.0,
max_lod = 1000.0,
min_lod = -1.0,
max_lod = 1.0,
border_color = BorderColor.OPAQUE_BLACK,
compare = .NEVER,
max_anisotropy = 16,
max_anisotropy = 1,
})
verify( sokol_gfx.query_sampler_state( atlas_rt_sampler) < ResourceState.FAILED, "Failed to make atlas_rt_sampler" )

View File

@ -1,7 +1,7 @@
package sectr
#region("base")
//region base
import "base:builtin"
copy :: builtin.copy
@ -25,9 +25,9 @@ import "base:runtime"
SourceCodeLocation :: runtime.Source_Code_Location
debug_trap :: runtime.debug_trap
#endregion("base")
//endregion base
#region("core")
//region core
import c "core:c/libc"
@ -153,14 +153,14 @@ import "core:unicode/utf8"
runes_to_string :: utf8.runes_to_string
// string_to_runes :: utf8.string_to_runes
#endregion("core")
//endregion core
import "thirdparty:backtrace"
StackTraceData :: backtrace.Trace_Const
stacktrace :: backtrace.trace
stacktrace_lines :: backtrace.lines
#region("codebase")
//region codebase
import "codebase:grime"
// asserts
@ -305,8 +305,8 @@ import "codebase:grime"
to_bytes :: grime.to_bytes
// strings
StrRunesPair :: grime.StrRunesPair
StringCache :: grime.StringCache
StrCached :: grime.StrCached
StringCache :: grime.StringCache
str_cache_init :: grime.str_cache_init
str_cache_reload :: grime.str_cache_reload
@ -345,9 +345,9 @@ import "codebase:grime"
varena_allocator :: grime.varena_allocator
#endregion("codebase")
//endregion codebase
#region("Procedure overload mappings")
//region Procedure overload mappings
// This has to be done on a per-module basis.
@ -643,6 +643,14 @@ to_str_runes_pair :: proc {
to_str_runes_pair_via_string,
}
vec2 :: proc {
vec2_from_f32s,
vec2_from_scalar,
// vec2_64_from_vec2,
vec2_from_vec2i,
vec2i_from_vec2
}
vec3 :: proc {
vec3_via_f32s,
bivec3_to_vec3,
@ -709,4 +717,4 @@ wedge :: proc {
wedge_bivec3,
}
#endregion("Proc overload mappings")
//endregion Proc overload mappings

View File

@ -73,7 +73,7 @@ Vec3i :: [3]i32
vec2i_to_vec2 :: #force_inline proc "contextless" (v : Vec2i) -> Vec2 {return transmute(Vec2) v}
vec3i_to_vec3 :: #force_inline proc "contextless" (v : Vec3i) -> Vec3 {return transmute(Vec3) v}
#region("Range2")
//region Range2
// TODO(Ed): I rather keep the different usages as different types, then type coerece their procedure mappings
// to support the base p0, p1 range
@ -148,4 +148,4 @@ size_range2 :: #force_inline proc "contextless" ( value : Range2 ) -> Vec2 {
return { abs( value.p1.x - value.p0.x ), abs( value.p0.y - value.p1.y ) }
}
#endregion("Range2")
//endregion Range2

View File

@ -17,7 +17,12 @@ Rotor2 :: struct {
rotor2_to_complex64 :: #force_inline proc( rotor : Rotor2 ) -> complex64 { return transmute(complex64) rotor; }
vec2 :: #force_inline proc "contextless" ( x, y : f32 ) -> Vec2 { return {x, y} }
vec2_from_f32s :: #force_inline proc "contextless" ( x, y : f32 ) -> Vec2 { return {x, y} }
vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }}
vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }}
vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2i { return { i32(v2.x), i32(v2.y) }}
// vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }}
dot_vec2 :: proc "contextless" ( a, b : Vec2 ) -> (s : f32) {
x := a.x * b.x

View File

@ -212,8 +212,8 @@ screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 {
// Centered screen space to conventional screen space used for rendering
screen_to_render_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
screen_extent := transmute(Vec2) get_state().app_window.extent
return pos * {1, 1} + { screen_extent.x, screen_extent.y }
screen_extent := transmute(Vec2) get_screen_extent()
return pos + screen_extent
}
// TODO(Ed): These should assume a cam_context or have the ability to provide it in params

View File

@ -63,7 +63,7 @@ PWS_LexResult :: struct {
PWS_Token :: struct {
type : PWS_TokenType,
line, column : u32,
content : StrRunesPair,
content : StrCached,
}
PWS_AST_Type :: enum u32 {
@ -80,7 +80,7 @@ PWS_AST :: struct {
type : PWS_AST_Type,
line, column : u32,
content : StrRunesPair,
content : StrCached,
}
PWS_ParseError :: struct {
@ -89,9 +89,9 @@ PWS_ParseError :: struct {
}
PWS_ParseError_Max :: 32
PWS_TokenArray_ReserveSize :: 128
PWS_NodeArray_ReserveSize :: 32 * Kilobyte
PWS_LineArray_ReserveSize :: 32 * Kilobyte
PWS_TokenArray_ReserveSize :: 4 * Kilobyte
PWS_NodeArray_ReserveSize :: 64 * Kilobyte
PWS_LineArray_ReserveSize :: 64 * Kilobyte
// TODO(Ed) : The ast arrays should be handled by a slab allocator dedicated to PWS_ASTs
// This can grow in undeterministic ways, persistent will get very polluted otherwise.

View File

@ -15,8 +15,8 @@ ProjectConfig :: struct {
}
Project :: struct {
path : StrRunesPair,
name : StrRunesPair,
path : StrCached,
name : StrCached,
config : ProjectConfig,
codebase : CodeBase,

View File

@ -163,12 +163,12 @@ project_save :: proc( project : ^ Project, archive : ^ ArchiveData = nil )
}
project_serialize( project, archive )
if ! os.is_dir( project.path.str ) {
os.make_directory( project.path.str )
verify( cast(b32) os.is_dir( project.path.str ), "Failed to create project path for saving" )
if ! os.is_dir( project.path) {
os.make_directory( project.path )
verify( cast(b32) os.is_dir( project.path ), "Failed to create project path for saving" )
}
os.write_entire_file( str_tmp_from_any( project.path.str, project.name.str, ".sectr_proj", sep = ""), archive.data )
os.write_entire_file( str_tmp_from_any( project.path, project.name, ".sectr_proj", sep = ""), archive.data )
}
project_load :: proc( path : string, project : ^ Project, archive : ^ ArchiveData = nil )

View File

@ -7,7 +7,7 @@ or frame tiling towards the application's screenspace.
package sectr
Workspace :: struct {
name : StrRunesPair,
name : StrCached,
cam : Camera,
zoom_target : f32,

View File

@ -37,8 +37,9 @@ IMG_ve_blit_atlas_src_texture :: 0
SMP_ve_blit_atlas_src_sampler :: 0
Ve_Blit_Atlas_Fs_Params :: struct #align(16) {
using _: struct #packed {
glyph_buffer_size: [2]f32,
over_sample: f32,
region: i32,
_: [12]u8,
},
}
/*
@ -127,7 +128,9 @@ ve_blit_atlas_vs_source_hlsl4 := [705]u8 {
/*
cbuffer ve_blit_atlas_fs_params : register(b0)
{
int _88_region : packoffset(c0);
float2 _20_glyph_buffer_size : packoffset(c0);
float _20_over_sample : packoffset(c0.z);
int _20_region : packoffset(c0.w);
};
Texture2D<float4> ve_blit_atlas_src_texture : register(t0);
@ -146,43 +149,53 @@ ve_blit_atlas_vs_source_hlsl4 := [705]u8 {
float4 frag_color : SV_Target0;
};
float down_sample(float2 uv_1, float2 texture_size)
float down_sample_to_texture(float2 uv_1, float2 texture_size)
{
return 0.25f * (((ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, uv_1).x + ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, mad(float2(0.0f, 1.0f), texture_size, uv_1)).x) + ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, mad(float2(1.0f, 0.0f), texture_size, uv_1)).x) + ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, uv_1 + texture_size).x);
return (1.0f / _20_over_sample) * (((ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, uv_1).x + ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, mad(float2(0.0f, 1.0f), texture_size, uv_1)).x) + ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, mad(float2(1.0f, 0.0f), texture_size, uv_1)).x) + ve_blit_atlas_src_texture.Sample(ve_blit_atlas_src_sampler, uv_1 + texture_size).x);
}
void frag_main()
{
bool _93 = _88_region == 0;
bool _101;
if (!_93)
float2 _98 = 1.0f.xx / _20_glyph_buffer_size;
bool _104 = _20_region == 0;
bool _111;
if (!_104)
{
_101 = _88_region == 1;
_111 = _20_region == 1;
}
else
{
_101 = _93;
_111 = _104;
}
bool _109;
if (!_101)
bool _118;
if (!_111)
{
_109 = _88_region == 2;
_118 = _20_region == 2;
}
else
{
_109 = _101;
_118 = _111;
}
if (_109)
bool _126;
if (!_118)
{
float2 param = uv + float2(-0.00048828125f, -0.0029296875f);
float2 param_1 = float2(0.00048828125f, 0.001953125f);
float2 param_2 = uv + float2(0.000244140625f, -0.0029296875f);
float2 param_3 = float2(0.00048828125f, 0.001953125f);
float2 param_4 = uv + float2(-0.000732421875f, 0.0009765625f);
float2 param_5 = float2(0.00048828125f, 0.001953125f);
float2 param_6 = uv + float2(0.000244140625f, 0.0009765625f);
float2 param_7 = float2(0.00048828125f, 0.001953125f);
frag_color = float4(1.0f, 1.0f, 1.0f, 0.25f * (((down_sample(param, param_1) + down_sample(param_2, param_3)) + down_sample(param_4, param_5)) + down_sample(param_6, param_7)));
_126 = _20_region == 4;
}
else
{
_126 = _118;
}
if (_126)
{
float2 param = uv + (float2(-1.0f, -1.5f) / _20_glyph_buffer_size);
float2 param_1 = _98;
float2 param_2 = uv + (float2(0.5f, -1.5f) / _20_glyph_buffer_size);
float2 param_3 = _98;
float2 param_4 = uv + (float2(-1.5f, 0.5f) / _20_glyph_buffer_size);
float2 param_5 = _98;
float2 param_6 = uv + (0.5f.xx / _20_glyph_buffer_size);
float2 param_7 = _98;
frag_color = float4(1.0f, 1.0f, 1.0f, (1.0f / _20_over_sample) * (((down_sample_to_texture(param, param_1) + down_sample_to_texture(param_2, param_3)) + down_sample_to_texture(param_4, param_5)) + down_sample_to_texture(param_6, param_7)));
}
else
{
@ -200,141 +213,156 @@ ve_blit_atlas_vs_source_hlsl4 := [705]u8 {
}
*/
@(private="file")
ve_blit_atlas_fs_source_hlsl4 := [2140]u8 {
ve_blit_atlas_fs_source_hlsl4 := [2383]u8 {
0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,
0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,
0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x62,0x30,0x29,0x0a,0x7b,
0x0a,0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x5f,0x38,0x38,0x5f,0x72,0x65,0x67,
0x69,0x6f,0x6e,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,
0x28,0x63,0x30,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x54,0x65,0x78,0x74,0x75,0x72,
0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34,0x3e,0x20,0x76,0x65,0x5f,0x62,
0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x32,0x30,0x5f,
0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,
0x65,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,
0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x32,
0x30,0x5f,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3a,0x20,
0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x2e,0x7a,0x29,
0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x5f,0x32,0x30,0x5f,0x72,0x65,
0x67,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,
0x74,0x28,0x63,0x30,0x2e,0x77,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x54,0x65,0x78,
0x74,0x75,0x72,0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34,0x3e,0x20,0x76,
0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,
0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,
0x74,0x65,0x72,0x28,0x74,0x30,0x29,0x3b,0x0a,0x53,0x61,0x6d,0x70,0x6c,0x65,0x72,
0x53,0x74,0x61,0x74,0x65,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,
0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,
0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,
0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,
0x76,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,
0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,
0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,
0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,
0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,
0x44,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,
0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,
0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,
0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x53,0x56,0x5f,0x54,
0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x6c,0x6f,0x61,
0x74,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f,
0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,
0x75,0x76,0x5f,0x31,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,
0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,
0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x28,0x31,0x2e,0x30,0x66,0x20,0x2f,0x20,
0x5f,0x32,0x30,0x5f,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x29,
0x20,0x2a,0x20,0x28,0x28,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,
0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,
0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,
0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,
0x2c,0x20,0x75,0x76,0x5f,0x31,0x29,0x2e,0x78,0x20,0x2b,0x20,0x76,0x65,0x5f,0x62,
0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,
0x78,0x74,0x75,0x72,0x65,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,
0x28,0x74,0x30,0x29,0x3b,0x0a,0x53,0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,0x74,0x61,
0x74,0x65,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,
0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x3a,0x20,0x72,
0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,0x0a,0x73,0x74,
0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,0x0a,
0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,
0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,
0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,
0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,
0x20,0x75,0x76,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,
0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,
0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,
0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,
0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,
0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,
0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x66,0x6c,0x6f,0x61,0x74,
0x32,0x20,0x75,0x76,0x5f,0x31,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,
0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x29,0x0a,0x7b,0x0a,0x20,
0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x30,0x2e,0x32,0x35,0x66,0x20,
0x2a,0x20,0x28,0x28,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,
0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x53,
0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,
0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,
0x20,0x75,0x76,0x5f,0x31,0x29,0x2e,0x78,0x20,0x2b,0x20,0x76,0x65,0x5f,0x62,0x6c,
0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,
0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,
0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,
0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,0x28,0x66,0x6c,0x6f,0x61,0x74,
0x32,0x28,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x2c,0x20,0x74,
0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x5f,
0x31,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,
0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,
0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,
0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,
0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,
0x31,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x29,0x2c,0x20,0x74,0x65,0x78,
0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x5f,0x31,0x29,
0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,
0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,
0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,
0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,
0x72,0x2c,0x20,0x75,0x76,0x5f,0x31,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,
0x65,0x5f,0x73,0x69,0x7a,0x65,0x29,0x2e,0x78,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x76,
0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,
0x7b,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x39,0x33,0x20,0x3d,
0x20,0x5f,0x38,0x38,0x5f,0x72,0x65,0x67,0x69,0x6f,0x6e,0x20,0x3d,0x3d,0x20,0x30,
0x3b,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x31,0x30,0x31,0x3b,
0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x21,0x5f,0x39,0x33,0x29,0x0a,0x20,
0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x31,0x30,
0x31,0x20,0x3d,0x20,0x5f,0x38,0x38,0x5f,0x72,0x65,0x67,0x69,0x6f,0x6e,0x20,0x3d,
0x3d,0x20,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x65,
0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x5f,0x31,0x30,0x31,0x20,0x3d,0x20,0x5f,0x39,0x33,0x3b,0x0a,0x20,0x20,
0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x31,0x30,
0x39,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x21,0x5f,0x31,0x30,0x31,
0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x5f,0x31,0x30,0x39,0x20,0x3d,0x20,0x5f,0x38,0x38,0x5f,0x72,0x65,0x67,0x69,0x6f,
0x6e,0x20,0x3d,0x3d,0x20,0x32,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,
0x20,0x20,0x65,0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x5f,0x31,0x30,0x39,0x20,0x3d,0x20,0x5f,0x31,0x30,0x31,
0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,
0x5f,0x31,0x30,0x39,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,
0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,
0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x66,0x2c,0x20,
0x2d,0x30,0x2e,0x30,0x30,0x32,0x39,0x32,0x39,0x36,0x38,0x37,0x35,0x66,0x29,0x3b,
0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,
0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,
0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x66,0x2c,
0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x66,0x29,0x3b,0x0a,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,
0x61,0x72,0x61,0x6d,0x5f,0x32,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,
0x36,0x32,0x35,0x66,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x32,0x39,0x32,0x39,0x36,
0x38,0x37,0x35,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,
0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x33,0x20,0x3d,0x20,
0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,
0x38,0x31,0x32,0x35,0x66,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,
0x32,0x35,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x20,0x3d,0x20,0x75,
0x76,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x30,0x2e,0x30,0x30,
0x30,0x37,0x33,0x32,0x34,0x32,0x31,0x38,0x37,0x35,0x66,0x2c,0x20,0x30,0x2e,0x30,
0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,
0x6d,0x5f,0x35,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30,
0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x66,0x2c,0x20,0x30,0x2e,0x30,
0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,
0x5f,0x36,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,
0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x66,
0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x66,0x29,
0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,
0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,
0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,0x28,0x66,0x6c,0x6f,0x61,
0x74,0x32,0x28,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x2c,0x20,
0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,
0x5f,0x31,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,
0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,
0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,0x6c,
0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,
0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,
0x28,0x31,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x29,0x2c,0x20,0x74,0x65,
0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x5f,0x31,
0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,
0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,
0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,
0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,
0x65,0x72,0x2c,0x20,0x75,0x76,0x5f,0x31,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,
0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x29,0x2e,0x78,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,
0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,
0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x39,
0x38,0x20,0x3d,0x20,0x31,0x2e,0x30,0x66,0x2e,0x78,0x78,0x20,0x2f,0x20,0x5f,0x32,
0x30,0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,
0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x31,
0x30,0x34,0x20,0x3d,0x20,0x5f,0x32,0x30,0x5f,0x72,0x65,0x67,0x69,0x6f,0x6e,0x20,
0x3d,0x3d,0x20,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,
0x31,0x31,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x21,0x5f,0x31,
0x30,0x34,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x5f,0x31,0x31,0x31,0x20,0x3d,0x20,0x5f,0x32,0x30,0x5f,0x72,0x65,0x67,
0x69,0x6f,0x6e,0x20,0x3d,0x3d,0x20,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,
0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x31,0x31,0x31,0x20,0x3d,0x20,0x5f,0x31,
0x30,0x34,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,
0x6f,0x6c,0x20,0x5f,0x31,0x31,0x38,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,
0x28,0x21,0x5f,0x31,0x31,0x31,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x31,0x31,0x38,0x20,0x3d,0x20,0x5f,0x32,0x30,
0x5f,0x72,0x65,0x67,0x69,0x6f,0x6e,0x20,0x3d,0x3d,0x20,0x32,0x3b,0x0a,0x20,0x20,
0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,
0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x31,0x31,0x38,0x20,
0x3d,0x20,0x5f,0x31,0x31,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,
0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x31,0x32,0x36,0x3b,0x0a,0x20,0x20,0x20,
0x20,0x69,0x66,0x20,0x28,0x21,0x5f,0x31,0x31,0x38,0x29,0x0a,0x20,0x20,0x20,0x20,
0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x31,0x32,0x36,0x20,0x3d,
0x20,0x5f,0x32,0x30,0x5f,0x72,0x65,0x67,0x69,0x6f,0x6e,0x20,0x3d,0x3d,0x20,0x34,
0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,
0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,
0x31,0x32,0x36,0x20,0x3d,0x20,0x5f,0x31,0x31,0x38,0x3b,0x0a,0x20,0x20,0x20,0x20,
0x7d,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x5f,0x31,0x32,0x36,0x29,0x0a,
0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x75,0x76,0x20,
0x2b,0x20,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x31,0x2e,0x30,0x66,0x2c,
0x20,0x2d,0x31,0x2e,0x35,0x66,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x5f,0x67,0x6c,
0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x29,
0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,
0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,
0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x66,
0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x66,0x29,0x3b,
0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,
0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x31,0x2e,0x30,
0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x30,
0x2e,0x32,0x35,0x66,0x20,0x2a,0x20,0x28,0x28,0x28,0x64,0x6f,0x77,0x6e,0x5f,0x73,
0x61,0x6d,0x70,0x6c,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x2c,0x20,0x70,0x61,0x72,
0x61,0x6d,0x5f,0x31,0x29,0x20,0x2b,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,
0x70,0x6c,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32,0x2c,0x20,0x70,0x61,0x72,
0x61,0x6d,0x5f,0x33,0x29,0x29,0x20,0x2b,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,
0x6d,0x70,0x6c,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x2c,0x20,0x70,0x61,
0x72,0x61,0x6d,0x5f,0x35,0x29,0x29,0x20,0x2b,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,
0x61,0x6d,0x70,0x6c,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x2c,0x20,0x70,
0x61,0x72,0x61,0x6d,0x5f,0x37,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,
0x0a,0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,
0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x30,0x2e,0x30,0x66,
0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,
0x30,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x7d,0x0a,0x0a,0x53,0x50,
0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,
0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,
0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,
0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,
0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x75,0x76,0x3b,0x0a,
0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,
0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,
0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,
0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,
0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,
0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,
0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,
0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00,
0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x5f,0x39,0x38,0x3b,0x0a,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,
0x61,0x72,0x61,0x6d,0x5f,0x32,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x66,
0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x35,0x66,0x2c,0x20,0x2d,0x31,0x2e,0x35,
0x66,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,
0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x29,0x3b,0x0a,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,
0x6d,0x5f,0x33,0x20,0x3d,0x20,0x5f,0x39,0x38,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,
0x34,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,
0x28,0x2d,0x31,0x2e,0x35,0x66,0x2c,0x20,0x30,0x2e,0x35,0x66,0x29,0x20,0x2f,0x20,
0x5f,0x32,0x30,0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,
0x5f,0x73,0x69,0x7a,0x65,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x35,0x20,0x3d,
0x20,0x5f,0x39,0x38,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3d,0x20,0x75,
0x76,0x20,0x2b,0x20,0x28,0x30,0x2e,0x35,0x66,0x2e,0x78,0x78,0x20,0x2f,0x20,0x5f,
0x32,0x30,0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,
0x73,0x69,0x7a,0x65,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,
0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3d,0x20,
0x5f,0x39,0x38,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x72,0x61,
0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,
0x28,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,
0x66,0x2c,0x20,0x28,0x31,0x2e,0x30,0x66,0x20,0x2f,0x20,0x5f,0x32,0x30,0x5f,0x6f,
0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x29,0x20,0x2a,0x20,0x28,0x28,
0x28,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f,0x5f,
0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x2c,0x20,0x70,
0x61,0x72,0x61,0x6d,0x5f,0x31,0x29,0x20,0x2b,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,
0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,
0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32,0x2c,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,
0x33,0x29,0x29,0x20,0x2b,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,
0x65,0x5f,0x74,0x6f,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x70,0x61,0x72,
0x61,0x6d,0x5f,0x34,0x2c,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x35,0x29,0x29,0x20,
0x2b,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f,
0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,
0x2c,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,
0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,
0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,
0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x30,
0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,
0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x7d,0x0a,
0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,
0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,
0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,
0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,
0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x75,
0x76,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,
0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,
0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,
0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,
0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,
0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,
0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,
0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00,
}
ve_blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc {
desc: sg.Shader_Desc

View File

@ -15,34 +15,36 @@ layout(binding = 0) uniform texture2D ve_blit_atlas_src_texture;
layout(binding = 0) uniform sampler ve_blit_atlas_src_sampler;
layout(binding = 0) uniform ve_blit_atlas_fs_params {
int region;
vec2 glyph_buffer_size;
float over_sample;
int region;
};
float down_sample( vec2 uv, vec2 texture_size )
float down_sample_to_texture( vec2 uv, vec2 texture_size )
{
float down_sample_scale = 1.0f / 4.0f;
float down_sample = 1.0f / over_sample;
float value =
texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 0.0f, 0.0f ) * texture_size ).x * down_sample_scale
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 0.0f, 1.0f ) * texture_size ).x * down_sample_scale
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 1.0f, 0.0f ) * texture_size ).x * down_sample_scale
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 1.0f, 1.0f ) * texture_size ).x * down_sample_scale;
texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 0.0f, 0.0f ) * texture_size ).x * down_sample
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 0.0f, 1.0f ) * texture_size ).x * down_sample
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 1.0f, 0.0f ) * texture_size ).x * down_sample
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 1.0f, 1.0f ) * texture_size ).x * down_sample;
return value;
}
void main()
{
// TODO(Ed): The original author made these consts, I want to instead expose as uniforms...
const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); // VEFontCache.Context.buffer_width/buffer_height
if ( region == 0 || region == 1 || region == 2 )
const vec2 texture_size = 1.0f / glyph_buffer_size;
if ( region == 0 || region == 1 || region == 2 || region == 4 )
{
float down_sample_scale = 1.0f / 4.0f;
float down_sample = 1.0f / over_sample;
float alpha =
down_sample( uv + vec2( -1.0f, -1.5f ) * texture_size, texture_size ) * down_sample_scale
+ down_sample( uv + vec2( 0.5f, -1.5f ) * texture_size, texture_size ) * down_sample_scale
+ down_sample( uv + vec2( -1.5f, 0.5f ) * texture_size, texture_size ) * down_sample_scale
+ down_sample( uv + vec2( 0.5f, 0.5f ) * texture_size, texture_size ) * down_sample_scale;
down_sample_to_texture( uv + vec2( -1.0f, -1.5f ) * texture_size, texture_size ) * down_sample
+ down_sample_to_texture( uv + vec2( 0.5f, -1.5f ) * texture_size, texture_size ) * down_sample
+ down_sample_to_texture( uv + vec2( -1.5f, 0.5f ) * texture_size, texture_size ) * down_sample
+ down_sample_to_texture( uv + vec2( 0.5f, 0.5f ) * texture_size, texture_size ) * down_sample;
frag_color = vec4( 1.0f, 1.0f, 1.0f, alpha );
}
else

View File

@ -37,8 +37,9 @@ IMG_ve_draw_text_src_texture :: 0
SMP_ve_draw_text_src_sampler :: 0
Ve_Draw_Text_Fs_Params :: struct #align(16) {
using _: struct #packed {
down_sample: i32,
_: [12]u8,
glyph_buffer_size: [2]f32,
over_sample: f32,
_: [4]u8,
colour: [4]f32,
},
}
@ -129,8 +130,9 @@ ve_draw_text_vs_source_hlsl4 := [724]u8 {
/*
cbuffer ve_draw_text_fs_params : register(b0)
{
int _31_down_sample : packoffset(c0);
float4 _31_colour : packoffset(c1);
float2 _32_glyph_buffer_size : packoffset(c0);
float _32_over_sample : packoffset(c0.z);
float4 _32_colour : packoffset(c1);
};
Texture2D<float4> ve_draw_text_src_texture : register(t0);
@ -151,12 +153,7 @@ ve_draw_text_vs_source_hlsl4 := [724]u8 {
void frag_main()
{
float alpha = ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, uv).x;
if (_31_down_sample == 1)
{
alpha = 0.25f * (((ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, uv + float2(-0.000244140625f, -0.0009765625f)).x + ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, uv + float2(-0.000244140625f, 0.0009765625f)).x) + ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, uv + float2(0.000244140625f, -0.0009765625f)).x) + ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, uv + float2(0.000244140625f, 0.0009765625f)).x);
}
frag_color = float4(_31_colour.xyz, _31_colour.w * alpha);
frag_color = float4(_32_colour.xyz, _32_colour.w * ((1.0f / _32_over_sample) * (((ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, mad((-0.5f).xx, _32_glyph_buffer_size, uv)).x + ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, mad(float2(-0.5f, 0.5f), _32_glyph_buffer_size, uv)).x) + ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, mad(float2(0.5f, -0.5f), _32_glyph_buffer_size, uv)).x) + ve_draw_text_src_texture.Sample(ve_draw_text_src_sampler, mad(0.5f.xx, _32_glyph_buffer_size, uv)).x)));
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
@ -169,89 +166,84 @@ ve_draw_text_vs_source_hlsl4 := [724]u8 {
}
*/
@(private="file")
ve_draw_text_fs_source_hlsl4 := [1296]u8 {
ve_draw_text_fs_source_hlsl4 := [1231]u8 {
0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,
0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x3a,
0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x62,0x30,0x29,0x0a,0x7b,0x0a,
0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x5f,0x33,0x31,0x5f,0x64,0x6f,0x77,0x6e,
0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,
0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x34,0x20,0x5f,0x33,0x31,0x5f,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x20,
0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x31,0x29,
0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x3c,
0x66,0x6c,0x6f,0x61,0x74,0x34,0x3e,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,
0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,
0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x74,0x30,0x29,0x3b,
0x0a,0x53,0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,0x74,0x61,0x74,0x65,0x20,0x76,0x65,
0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,
0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,
0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,
0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,
0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,
0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,
0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x3a,0x20,
0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,
0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,
0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,
0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,
0x20,0x3a,0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,
0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,
0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,
0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,
0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,
0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,
0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,
0x2c,0x20,0x75,0x76,0x29,0x2e,0x78,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,
0x28,0x5f,0x33,0x31,0x5f,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,
0x20,0x3d,0x3d,0x20,0x31,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20,0x30,0x2e,0x32,
0x35,0x66,0x20,0x2a,0x20,0x28,0x28,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,
0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,
0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,
0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,
0x2c,0x20,0x75,0x76,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x30,
0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x66,0x2c,0x20,
0x2d,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x66,0x29,0x29,
0x2e,0x78,0x20,0x2b,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x33,0x32,0x5f,0x67,
0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,
0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x30,
0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x33,0x32,
0x5f,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3a,0x20,0x70,
0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x2e,0x7a,0x29,0x3b,
0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x5f,0x33,0x32,0x5f,
0x63,0x6f,0x6c,0x6f,0x75,0x72,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,
0x73,0x65,0x74,0x28,0x63,0x31,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x54,0x65,0x78,
0x74,0x75,0x72,0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34,0x3e,0x20,0x76,
0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,
0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,
0x65,0x72,0x28,0x74,0x30,0x29,0x3b,0x0a,0x53,0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,
0x74,0x61,0x74,0x65,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,
0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x3a,0x20,
0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,0x0a,0x73,
0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,
0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,
0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,
0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,
0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,
0x32,0x20,0x75,0x76,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,
0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,
0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,
0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,
0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x53,0x56,0x5f,0x54,0x61,0x72,
0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,
0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,
0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x34,0x28,0x5f,0x33,0x32,0x5f,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,
0x78,0x79,0x7a,0x2c,0x20,0x5f,0x33,0x32,0x5f,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,
0x77,0x20,0x2a,0x20,0x28,0x28,0x31,0x2e,0x30,0x66,0x20,0x2f,0x20,0x5f,0x33,0x32,
0x5f,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x29,0x20,0x2a,0x20,
0x28,0x28,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,
0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,
0x6c,0x65,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,
0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,
0x28,0x28,0x2d,0x30,0x2e,0x35,0x66,0x29,0x2e,0x78,0x78,0x2c,0x20,0x5f,0x33,0x32,
0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,
0x7a,0x65,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,0x78,0x20,0x2b,0x20,0x76,0x65,0x5f,
0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,
0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,
0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,
0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,0x28,0x66,0x6c,0x6f,0x61,0x74,
0x32,0x28,0x2d,0x30,0x2e,0x35,0x66,0x2c,0x20,0x30,0x2e,0x35,0x66,0x29,0x2c,0x20,
0x5f,0x33,0x32,0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,
0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,
0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,
0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,
0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,
0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,0x61,0x64,0x28,0x66,
0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x35,0x66,0x2c,0x20,0x2d,0x30,0x2e,0x35,
0x66,0x29,0x2c,0x20,0x5f,0x33,0x32,0x5f,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,
0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,
0x78,0x29,0x20,0x2b,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,
0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,
0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,
0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x75,
0x76,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x30,0x2e,0x30,0x30,
0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x66,0x2c,0x20,0x30,0x2e,0x30,
0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x66,0x29,0x29,0x2e,0x78,0x29,0x20,
0x2b,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,
0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,
0x65,0x28,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,
0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x75,0x76,0x20,0x2b,
0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,
0x31,0x34,0x30,0x36,0x32,0x35,0x66,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x30,0x39,
0x37,0x36,0x35,0x36,0x32,0x35,0x66,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x76,
0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,
0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x76,
0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,
0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x75,0x76,0x20,0x2b,0x20,0x66,0x6c,
0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,
0x36,0x32,0x35,0x66,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,
0x32,0x35,0x66,0x29,0x29,0x2e,0x78,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,
0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,
0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x33,0x31,0x5f,0x63,0x6f,0x6c,0x6f,
0x75,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x5f,0x33,0x31,0x5f,0x63,0x6f,0x6c,0x6f,
0x75,0x72,0x2e,0x77,0x20,0x2a,0x20,0x61,0x6c,0x70,0x68,0x61,0x29,0x3b,0x0a,0x7d,
0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,
0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,
0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,
0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x75,
0x76,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,
0x75,0x76,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,
0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,
0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,
0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,
0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,
0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,
0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,
0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00,
0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x6d,
0x61,0x64,0x28,0x30,0x2e,0x35,0x66,0x2e,0x78,0x78,0x2c,0x20,0x5f,0x33,0x32,0x5f,
0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,
0x65,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,0x78,0x29,0x29,0x29,0x3b,0x0a,0x7d,0x0a,
0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,
0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,
0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,
0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,
0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x75,
0x76,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,
0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,
0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,
0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,
0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,
0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,
0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,
0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00,
}
ve_draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc {
desc: sg.Shader_Desc

View File

@ -10,7 +10,11 @@ out vec2 uv;
void main()
{
uv = vec2( v_texture.x, 1 - v_texture.y );
#if SOKOL_GLSL
uv = vec2( v_texture.x, v_texture.y );
#else
uv = vec2( v_texture.x, 1.0 - v_texture.y );
#endif
gl_Position = vec4( v_position * 2.0f - 1.0f, 0.0f, 1.0f );
}
@end
@ -23,25 +27,23 @@ layout(binding = 0) uniform texture2D ve_draw_text_src_texture;
layout(binding = 0) uniform sampler ve_draw_text_src_sampler;
layout(binding = 0) uniform ve_draw_text_fs_params {
int down_sample;
vec4 colour;
vec2 glyph_buffer_size;
float over_sample;
vec4 colour;
};
void main()
{
float alpha = texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler ), uv ).x;
if ( down_sample == 1 )
{
// TODO(Ed): The original author made these consts, I want to instead expose as uniforms...
const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); // VEFontCache.Context.buffer_width/buffer_height
const float down_sample_scale = 1.0f / 4.0f;
alpha =
(texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( -0.5f, -0.5f) * texture_size ).x * down_sample_scale)
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( -0.5f, 0.5f) * texture_size ).x * down_sample_scale)
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( 0.5f, -0.5f) * texture_size ).x * down_sample_scale)
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( 0.5f, 0.5f) * texture_size ).x * down_sample_scale);
}
const vec2 texture_size = glyph_buffer_size;
const float down_sample = 1.0f / over_sample;
alpha =
(texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( -0.5f, -0.5f) * texture_size ).x * down_sample)
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( -0.5f, 0.5f) * texture_size ).x * down_sample)
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( 0.5f, -0.5f) * texture_size ).x * down_sample)
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( 0.5f, 0.5f) * texture_size ).x * down_sample);
frag_color = vec4( colour.xyz, colour.a * alpha );
}
@end

View File

@ -1,10 +1,13 @@
in vec2 v_position;
in vec2 v_texture;
// in vec4 v_elem;
out vec2 uv;
void main()
{
uv = vec2( v_texture.x, 1 - v_texture.y );
#if SOKOL_GLSL
uv = vec2( v_texture.x, v_texture.y );
#else
uv = vec2( v_texture.x, 1.0 - v_texture.y );
#endif
gl_Position = vec4( v_position, 0.0, 1.0 );
}

View File

@ -114,8 +114,8 @@ UI_Layout_Stack_Size :: 512
UI_Style_Stack_Size :: 512
UI_Parent_Stack_Size :: 512
// UI_Built_Boxes_Array_Size :: 8
UI_Built_Boxes_Array_Size :: 56 * Kilobyte
UI_BoxCache_TableSize :: 8 * Kilobyte
UI_Built_Boxes_Array_Size :: 128 * Kilobyte
UI_BoxCache_TableSize :: 32 * Kilobyte
// TODO(Ed): Rename to UI_Context
UI_State :: struct {
@ -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 ),
@ -166,7 +168,7 @@ UI_State :: struct {
last_invalid_input_time : Time,
}
#region("Lifetime")
//region Lifetime
ui_startup :: proc( ui : ^ UI_State, spacial_indexing_method : UI_SpacialIndexingMethod = .QuadTree, cache_allocator : Allocator, cache_table_size : uint )
{
@ -237,7 +239,7 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
}
ui.built_box_count = 0
root = ui_box_make( {}, str_intern(str_fmt("%s: root#001", ui == & state.screen_ui ? "Screen" : "Workspace" )).str)
root = ui_box_make( {}, str_intern(str_fmt("%s: root#001", ui == & state.screen_ui ? "Screen" : "Workspace" )))
if ui == & state.screen_ui {
root.layout.size = range2(Vec2(state.app_window.extent) * 2, {})
}
@ -283,17 +285,9 @@ ui_graph_build_end :: proc( ui : ^UI_State )
if ! current.computed.fresh
{
if len(current.text.str) > 0 {
// 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_latin( current.text.str, current.style.font, current.layout.font_size, 1.0 )
if len(current.text) > 0 {
profile("text shape")
current.computed.text_shape = shape_text_cached( current.text, current.style.font, current.layout.font_size )
}
ui_box_compute_layout( current )
}
@ -302,6 +296,8 @@ ui_graph_build_end :: proc( ui : ^UI_State )
continue
}
profile("render queue resolution")
different_ancestory := b8(current.ancestors != previous_layer)
entry_box := UI_RenderBoxInfo {
@ -318,7 +314,7 @@ ui_graph_build_end :: proc( ui : ^UI_State )
// if len(current.text.str) > 0
// {
entry_text := UI_RenderTextInfo {
text = current.text.str,
text = current.text,
shape = current.computed.text_shape,
position = current.computed.text_pos,
color = current.style.text_color,
@ -366,9 +362,9 @@ ui_render_entry_tranverse :: proc( entry : ^UI_RenderEntry ) -> ^UI_RenderEntry
@(deferred_in = ui_graph_build_end)
ui_graph_build :: #force_inline proc( ui : ^ UI_State ) { ui_graph_build_begin( ui ) }
#endregion("Lifetime")
//endregion Lifetime
#region("Caching")
//region Caching
// Mainly referenced from RAD Debugger
// TODO(Ed): Need to setup the proper hashing convention for strings the other reference imguis use.
@ -385,7 +381,7 @@ ui_hash_part_from_key_string :: proc ( content : string ) -> string {
ui_key_from_string :: #force_inline proc "contextless" ( value : string ) -> UI_Key
{
// profile(#procedure)
USE_RAD_DEBUGGERS_METHOD :: true
USE_RAD_DEBUGGERS_METHOD :: false
key : UI_Key
@ -403,7 +399,7 @@ ui_key_from_string :: #force_inline proc "contextless" ( value : string ) -> UI_
return key
}
#endregion("Caching")
//endregion Caching
ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()

View File

@ -22,8 +22,8 @@ UI_NavLinks :: struct {
UI_Box :: struct {
// Cache ID
key : UI_Key,
label : StrRunesPair,
text : StrRunesPair,
label : StrCached,
text : StrCached,
// Regenerated per frame.
nav : UI_NavLinks,

View File

@ -194,21 +194,21 @@ ui_layout_pop :: #force_inline proc() { pop( &
ui_set_layout :: #force_inline proc( layout : UI_Layout, preset : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.layout_combo_stack).array[preset] = layout }
ui_size_to_content_xy :: proc ( box : ^UI_Box) {
ui_size_to_content_xy :: #force_inline proc ( box : ^UI_Box) {
using box
children_bounds := ui_compute_children_overall_bounds(box)
layout.size.min = size_range2(children_bounds)
layout.flags |= { .Fixed_Width, .Fixed_Height }
}
ui_size_to_content_x :: proc ( box : ^UI_Box) {
ui_size_to_content_x :: #force_inline proc ( box : ^UI_Box) {
using box
children_bounds := ui_compute_children_overall_bounds(box)
layout.size.min.x = size_range2(children_bounds).x
layout.flags |= { .Fixed_Width }
}
ui_size_to_content_y :: proc ( box : ^UI_Box) {
ui_size_to_content_y :: #force_inline proc ( box : ^UI_Box) {
using box
children_bounds := ui_compute_children_overall_bounds(box)
layout.size.min.y = size_range2(children_bounds).y

View File

@ -71,9 +71,9 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
adjusted_size.y = max( adjusted_max_size_y, layout.size.min.y)
text_size : Vec2
if len(box.text.str) > 0
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
// }
@ -187,7 +187,7 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
computed.content = content_bounds
// 8. Text position & size
if len(box.text.str) > 0
if len(box.text) > 0
{
ascent, descent, line_gap := get_font_vertical_metrics(style.font, layout.font_size)

View File

@ -22,7 +22,7 @@ ui_floating_startup :: proc( self : ^UI_FloatingManager, build_queue_cap, tracke
error : AllocatorError
queue_dbg_name := str_intern(str_fmt("%s: build_queue", dbg_name))
self.build_queue, error = make( Array(UI_Floating), build_queue_cap, dbg_name = queue_dbg_name.str, allocator = allocator )
self.build_queue, error = make( Array(UI_Floating), build_queue_cap, dbg_name = queue_dbg_name, allocator = allocator )
if error != AllocatorError.None
{
ensure(false, "Failed to allocate the build_queue")

View File

@ -61,8 +61,8 @@ test_draggable :: proc()
draggable.layout.pos = debug.draggable_box_pos
draggable.layout.size.min = debug.draggable_box_size
draggable.text = { str_fmt("%v", debug.draggable_box_pos), {} }
draggable.text.runes = to_runes(draggable.text.str)
draggable.text = str_intern_fmt("%v", debug.draggable_box_pos)
// draggable.text.runes = to_runes(draggable.text)
}
test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_Style )
@ -190,11 +190,14 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
// index := 0
widgets : Array(UI_Widget)
// widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 8 )
widgets, alloc_error = make( Array(UI_Widget), 8 * Kilobyte )
widgets, alloc_error = make( Array(UI_Widget), 64 * Kilobyte )
widgets_ptr := & widgets
label_id := 0
builder : StringBuilder
str.builder_init_len_cap( & builder, len = 0, cap = 64 * Kilobyte )
line_id := 0
for line in array_to_slice( debug.lorem_parse.lines )
{
@ -205,11 +208,17 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
profile("line")
ui_layout( text_layout )
line_hbox := ui_widget(str_fmt( "line %v", line_id ), {.Mouse_Clickable})
if line_hbox.key == ui.hot && false
// profile_begin("label fmt")
str.builder_reset( & builder)
label := str_fmt_builder( & builder, "line %d", line_id )
// profile_end()
line_hbox := ui_widget(label, {.Mouse_Clickable})
if false && line_hbox.key == ui.hot
{
line_hbox.text = StrRunesPair {}
line_hbox.text = StrCached {}
ui_parent(line_hbox)
chunk_layout := text_layout
@ -230,33 +239,34 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
#partial switch head.type
{
case .Visible:
label := str_intern( str_fmt( "%v %v", head.content.str, label_id ))
widget = ui_text( label.str, head.content )
label := str_fmt( "%v %v", head.content, label_id )
widget = ui_text( label, head.content )
label_id += 1
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
case .Spaces:
label := str_intern( str_fmt( "%v %v", "space", label_id ))
widget = ui_text_spaces( label.str )
label := str_fmt( "%v %v", "space", label_id )
widget = ui_text_spaces( label )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
// for idx in 1 ..< len( head.content.runes )
// {
// TODO(Ed): VIRTUAL WHITESPACE
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
// }
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
case .Tabs:
label := str_intern( str_fmt( "%v %v", "tab", label_id ))
widget = ui_text_tabs( label.str )
label := str_fmt( "%v %v", "tab", label_id )
widget = ui_text_tabs( label )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
// for idx in 1 ..< len( head.content.runes )
// {
// TODO(Ed): VIRTUAL WHITESPACE
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
// }
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
}
@ -268,30 +278,31 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
}
else
{
builder_backing : [16 * Kilobyte] byte
builder := str.builder_from_bytes( builder_backing[:] )
// profile("line (single-box)")
line_hbox.layout.flags |= { .Size_To_Text }
str.builder_reset( & builder)
head := line.first.next
for ; head != nil;
{
str.write_string( & builder, head.content.str )
// profile("write ast node")
str.write_string( & builder, head.content )
head = head.next
}
// profile("intern")
line_hbox.text = str_intern( to_string( builder ) )
// if len(line_hbox.text.str) == 0 {
// line_hbox.text = str_intern( " " )
// }
}
if len(line_hbox.text.str) > 0 {
if len(line_hbox.text) > 0 {
// profile("append actual")
array_append( widgets_ptr, line_hbox )
text_layout.pos.x = text_layout.pos.x
text_layout.pos.y -= size_range2(line_hbox.computed.bounds).y
}
else {
// profile("end")
widget := & widgets.data[ widgets.num - 1 ]
if widget.box != nil {
text_layout.pos.y -= size_range2( widget.computed.bounds ).y

View File

@ -3,22 +3,19 @@ package sectr
import "base:runtime"
import lalg "core:math/linalg"
// Problably cursed way to setup a 'scope' for a widget
// ui_build :: #force_inline proc( captures : $Type, $maker : #type proc(captures : Type) -> $ReturnType ) -> ReturnType { return maker(captures) }
UI_Widget :: struct {
using box : ^UI_Box,
using signal : UI_Signal,
}
ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
ui_widget :: #force_inline proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
{
widget.box = ui_box_make( flags, label )
widget.signal = ui_signal_from_box( widget.box )
return
}
ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
ui_button :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
{
btn_flags := UI_BoxFlags { .Mouse_Clickable }
btn.box = ui_box_make( btn_flags | flags, label )
@ -26,7 +23,8 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge
return
}
#region("Drop Down")
//region Drop Down
UI_DropDown :: struct {
btn : UI_Widget,
title : UI_Widget,
@ -36,7 +34,7 @@ UI_DropDown :: struct {
}
@(deferred_out = ui_drop_down_end_auto)
ui_drop_down :: proc( drop_down : ^UI_DropDown, label : string, title_text : StrRunesPair,
ui_drop_down :: proc( drop_down : ^UI_DropDown, label : string, title_text : StrCached,
direction := UI_LayoutDirection_Y.Top_To_Bottom,
btn_flags := UI_BoxFlags{},
vb_flags := UI_BoxFlags{},
@ -54,7 +52,7 @@ ui_drop_down :: proc( drop_down : ^UI_DropDown, label : string, title_text : Str
}
// Its assumed that the drop down has a vertical box parent already pushed
ui_drop_down_begin :: proc( drop_down : ^UI_DropDown, label : string, title_text : StrRunesPair,
ui_drop_down_begin :: proc( drop_down : ^UI_DropDown, label : string, title_text : StrCached,
direction := UI_LayoutDirection_Y.Top_To_Bottom,
btn_flags := UI_BoxFlags{},
vb_flags := UI_BoxFlags{},
@ -68,7 +66,7 @@ ui_drop_down_begin :: proc( drop_down : ^UI_DropDown, label : string, title_text
if btn_theme == nil do push(theme_drop_down_btn)
else do push(btn_theme ^)
defer ui_theme_pop()
btn = ui_button( str_intern_fmt("%s.btn", label).str );
btn = ui_button( str_fmt("%s.btn", label) );
{
btn.layout.padding.left = 4
ui_parent(btn)
@ -76,7 +74,7 @@ ui_drop_down_begin :: proc( drop_down : ^UI_DropDown, label : string, title_text
if title_theme == nil do push(theme_text)
else do push(title_theme ^)
defer ui_theme_pop()
title = ui_text( str_intern_fmt("%s.btn.title", label).str, title_text)
title = ui_text( str_fmt("%s.btn.title", label), title_text)
}
if btn.pressed {
@ -91,7 +89,7 @@ ui_drop_down_begin :: proc( drop_down : ^UI_DropDown, label : string, title_text
if vb_parent != nil {
ui_parent_push(vb_parent)
}
vbox = ui_vbox_begin( direction, str_intern_fmt("%v.vbox", label).str, flags = {.Mouse_Clickable}, compute_layout = vb_compute_layout )
vbox = ui_vbox_begin( direction, str_fmt("%v.vbox", label), flags = {.Mouse_Clickable}, compute_layout = vb_compute_layout )
vbox.layout.anchor.ratio.y = 1.0
if vb_parent != nil {
@ -109,9 +107,10 @@ ui_drop_down_end_auto :: proc( drop_down : ^UI_DropDown) {
ui_vbox_end(drop_down.vbox, compute_layout = false)
ui_parent_pop()
}
#endregion("Drop Down")
#region("Horizontal Box")
//endregion Drop Down
//region Horizontal Box
/*
Horizontal Boxes automatically manage a collection of widgets and
attempt to slot them adjacent to each other along the x-axis.
@ -169,9 +168,10 @@ ui_hbox_end_auto :: #force_inline proc( direction : UI_LayoutDirection_X, label
ui_hbox_end(hbox, width_ref)
ui_parent_pop()
}
#endregion("Horizontal Box")
//endregion Horizontal Box
//region Resizable
#region("Resizable")
// Parameterized widget def for ui_resizable_handles
UI_Resizable :: struct {
using widget : UI_Widget,
@ -328,7 +328,7 @@ ui_resizable_handles :: #force_no_inline proc( parent : ^UI_Widget, pos : ^Vec2,
name :: proc( label : string ) -> string {
parent_label := (transmute(^string) context.user_ptr) ^
return str_intern(str_fmt("%v.%v", parent_label, label )).str
return str_fmt("%v.%v", parent_label, label )
}
context.user_ptr = & parent.label
@ -500,8 +500,10 @@ ui_resizable_handles :: #force_no_inline proc( parent : ^UI_Widget, pos : ^Vec2,
// if drag_signal && compute_layout do ui_box_compute_layout(parent)
return
}
#endregion("Resizable")
//endregion Resizable
//region Text
ui_spacer :: proc( label : string ) -> (widget : UI_Widget) {
widget.box = ui_box_make( {.Mouse_Clickable}, label )
widget.signal = ui_signal_from_box( widget.box )
@ -522,10 +524,9 @@ ui_scroll_box :: proc( label : string, flags : UI_BoxFlags ) -> (scroll_box : UI
return
}
#region("Text")
ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget
ui_text :: #force_inline proc( label : string, content : StrCached, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
profile(#procedure)
state := get_state(); using state
box := ui_box_make( flags, label )
@ -535,9 +536,9 @@ ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {
return { box, signal }
}
ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
ui_text_spaces :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
profile(#procedure)
state := get_state(); using state
// TODO(Ed) : Move this somwhere in state.
@ -550,9 +551,9 @@ ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
return { box, signal }
}
ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
ui_text_tabs :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
profile(#procedure)
state := get_state(); using state
// TODO(Ed) : Move this somwhere in state.
@ -569,9 +570,10 @@ ui_text_wrap_panel :: proc( parent : ^UI_Widget )
{
fatal("NOT IMPLEMENTED")
}
#endregion("Text")
//endregion Text
//region Text Input
#region("Text Input")
UI_TextInput_Policy :: struct {
disallow_decimal : b32,
disallow_leading_zeros : b32,
@ -595,8 +597,8 @@ ui_text_input_box_reload :: #force_inline proc ( text_box : ^UI_TextInputBox, al
ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string,
flags : UI_BoxFlags = {.Mouse_Clickable, .Focusable, .Click_To_Focus},
allocator := context.allocator,
policy : UI_TextInput_Policy = {}
policy : UI_TextInput_Policy = {},
allocator : Allocator,
)
{
// state := get_state()
@ -619,6 +621,7 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string,
input_str, error = make( Array(rune), Kilo, allocator )
ensure(error == AllocatorError.None, "Failed to allocate array for input_str of input_box")
}
input_str.backing = allocator // Always assign allocator for hot-reload purposes
if active
{
@ -706,19 +709,19 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string,
ui_parent(text_input_box)
name :: proc( label : string ) -> string {
parent_label := (transmute(^string) context.user_ptr) ^
return str_intern(str_fmt("%v: %v", parent_label, label )).str
return str_intern(str_fmt("%v: %v", parent_label, label ))
}
context.user_ptr = & parent.label
// TODO(Ed): Allow for left and center alignment of text
value_txt : UI_Widget; {
scope(theme_text)
value_txt = ui_text(name("input_str"), to_str_runes_pair(array_to_slice(input_str)))
value_txt = ui_text(name("input_str"), str_intern(to_string(array_to_slice(input_str))))
using value_txt
layout.alignment = {0.0, 0.0}
layout.text_alignment = {1.0, 0.5}
layout.anchor.left = 0.0
layout.size.min = cast(Vec2) measure_text_size( text.str, style.font, layout.font_size, 0 )
layout.size.min = cast(Vec2) measure_text_size( text, style.font, layout.font_size, 0 )
if active {
ui_parent(value_txt)
@ -743,9 +746,9 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string,
}
}
}
#endregion("Text Input")
//endregion Text Input
#region("Vertical Box")
//region Vertical Box
/*
Vertical Boxes automatically manage a collection of widgets and
attempt to slot them adjacent to each other along the y-axis.
@ -802,4 +805,4 @@ ui_vbox :: #force_inline proc( direction : UI_LayoutDirection_Y, label : string,
ui_parent_push(vbox.widget)
return
}
#endregion("Vertical Box")
//endregion Vertical Box

View File

@ -27,7 +27,7 @@ UI_Window_ChildLayout :: enum(i32) {
@(deferred_in=ui_window_end_auto)
ui_window :: proc (window : ^UI_Window, label : string,
title : StrRunesPair = {},
title : StrCached = {},
closable := true,
maximizable := true,
draggable := true,
@ -41,7 +41,7 @@ ui_window :: proc (window : ^UI_Window, label : string,
}
ui_window_begin :: proc( window : ^UI_Window, label : string,
title : StrRunesPair = {},
title : StrCached = {},
closable := true,
maximizable := true,
draggable := true,
@ -83,7 +83,7 @@ ui_window_begin :: proc( window : ^UI_Window, label : string,
scope(theme_transparent)
vb = ui_vbox(.Top_To_Bottom, str_fmt_tmp("%s.vb", label))
if len(title.str) > 0 || closable || maximizable || draggable {
if len(title) > 0 || closable || maximizable || draggable {
dragged, maximized, closed = ui_window_bar(window, title, closable, maximizable, draggable)
}
@ -109,7 +109,7 @@ ui_window_end :: proc (window : ^UI_Window)
}
ui_window_end_auto :: proc( window : ^UI_Window, label : string,
title : StrRunesPair = {},
title : StrCached = {},
closable := true,
maximizable := true,
draggable := true,
@ -121,7 +121,7 @@ ui_window_end_auto :: proc( window : ^UI_Window, label : string,
}
ui_window_bar :: proc( window : ^UI_Window,
title : StrRunesPair = {},
title : StrCached = {},
closable := true,
maximizable := true,
draggable := true,
@ -132,13 +132,13 @@ ui_window_bar :: proc( window : ^UI_Window,
draggable_flag : UI_BoxFlags = draggable ? {.Mouse_Clickable} : {}
scope(theme_window_bar)
bar = ui_hbox(.Left_To_Right, str_fmt_tmp("%s.bar", frame.label.str), draggable_flag);
bar = ui_hbox(.Left_To_Right, str_fmt_tmp("%s.bar", frame.label), draggable_flag);
ui_parent(bar)
if len(title.str) > 0
if len(title) > 0
{
scope(theme_text)
tile_text = ui_text( str_fmt_tmp("%s.title_text", bar.label.str), title, {.Disabled}); {
tile_text = ui_text( str_fmt_tmp("%s.title_text", bar.label), title, {.Disabled}); {
using tile_text
layout.anchor.ratio.x = 1.0
layout.margins = { 0, 0, 15, 0}
@ -149,7 +149,7 @@ ui_window_bar :: proc( window : ^UI_Window,
scope(theme_window_bar_btn)
if maximizable
{
maximize_btn = ui_button( str_fmt_tmp("%v.maximize_btn", bar.label.str) ); {
maximize_btn = ui_button( str_fmt_tmp("%v.maximize_btn", bar.label) ); {
using maximize_btn
if maximize_btn.pressed {
is_maximized = ~is_maximized

File diff suppressed because it is too large Load Diff

4811
examples/sokol_gl.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -94,6 +94,7 @@ $flag_sanitize_address = '-sanitize:address'
$flag_sanitize_memory = '-sanitize:memory'
$flag_sanitize_thread = '-sanitize:thread'
$flag_subsystem = '-subsystem:'
$flag_show_debug_messages = '-show-debug-messages'
$flag_show_timings = '-show-timings'
$flag_show_more_timings = '-show-more-timings'
$flag_show_system_calls = '-show-system-calls'
@ -117,7 +118,7 @@ $msvc_link_default_base_address = 0x180000000
push-location $path_root
$update_deps = join-path $path_scripts 'update_deps.ps1'
$odin_compiler = join-path $path_odin 'odin.exe'
$odin_compiler = join-path $path_odin 'odin_sectr.exe'
function Invoke-WithColorCodedOutput { param( [scriptblock] $command )
& $command 2>&1 | ForEach-Object {
@ -140,19 +141,25 @@ push-location $path_root
$path_font = join-path $path_code 'font'
$package_grime = join-path $path_code 'grime'
$package_VEFontCache = join-path $path_font 'VEFontCache'
$module_host = join-path $path_code 'host'
$module_sectr = join-path $path_code 'sectr'
$module_scripts = $PSScriptRoot
$package_grime = join-path $path_code 'grime'
$package_VEFontCache = join-path $path_font 'VEFontCache'
$package_stb_truetype = join-path $path_thirdparty 'stb'
$module_host = join-path $path_code 'host'
$module_sectr = join-path $path_code 'sectr'
if ($force){
mark-ModuleDirty $module_scripts
mark-ModuleDirty $package_VEFontCache
mark-ModuleDirty $package_stb_truetype
mark-ModuleDirty $package_grime
mark-ModuleDirty $module_sectr
mark-ModuleDirty $module_host
}
$pkg_VEFontCache_dirty = check-ModuleForChanges $package_VEFontCache
$pkg_grime_dirty = check-ModuleForChanges $package_grime
$module_scripts_dirty = check-ModuleForChanges $module_scripts
$pkg_VEFontCache_dirty = check-ModuleForChanges $package_VEFontCache
$pkg_stb_truetype_dirty = check-ModuleForChanges $package_stb_truetype
$pkg_grime_dirty = check-ModuleForChanges $package_grime
$pkg_collection_codebase = 'codebase=' + $path_code
$pkg_collection_thirdparty = 'thirdparty=' + $path_thirdparty
@ -170,7 +177,7 @@ push-location $path_root
function build-sectr
{
$should_build = (check-ModuleForChanges $module_sectr) -or $pkg_grime_dirty -or $pkg_VEFontCache_dirty
$should_build = (check-ModuleForChanges $module_sectr) -or $pkg_grime_dirty -or $pkg_VEFontCache_dirty -or $pkg_stb_truetype_dirty -or $module_scripts_dirty
if ( -not( $should_build)) {
write-host 'Skipping sectr build, module up to date'
return $module_unchanged
@ -193,7 +200,7 @@ push-location $path_root
$build_args = @()
$build_args += $command_build
$build_args += './sectr'
$build_args += 'sectr'
$build_args += $flag_build_mode_dll
$build_args += $flag_output_path + $module_dll
$build_args += ($flag_collection + $pkg_collection_codebase)
@ -203,9 +210,9 @@ push-location $path_root
$build_args += $flag_thread_count + $CoreCount_Physical
# $build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
$build_args += $falg_optimize_aggressive
$build_args += $flag_debug
$build_args += $flag_optimize_speed
# $build_args += $falg_optimize_aggressive
# $build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'
# $build_args += $flag_show_system_calls
@ -217,6 +224,11 @@ push-location $path_root
$build_args += ($flag_max_error_count + '10')
# $build_args += $flag_sanitize_address
# $build_args += $flag_sanitize_memory
# $build_args += $flag_show_debug_messages
# foreach ($arg in $build_args) {
# write-host `t $arg -ForegroundColor Cyan
# }
if ( Test-Path $module_dll) {
$module_dll_pre_build_hash = get-filehash -path $module_dll -Algorithm MD5
@ -277,8 +289,8 @@ push-location $path_root
# $build_args += $flag_micro_architecture_native
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
# $build_args += $flag_optimize_none
$build_args += $flag_optimize_minimal
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
# $build_args += $falg_optimize_aggressive
$build_args += $flag_debug
@ -293,6 +305,10 @@ push-location $path_root
$build_args += ($flag_max_error_count + '10')
# $build_args += $flag_sanitize_address
# $build_args += $flag_sanitize_memory
# foreach ($arg in $build_args) {
# write-host `t $arg -ForegroundColor Cyan
# }
if ( Test-Path $executable) {
$executable_pre_build_hash = get-filehash -path $executable -Algorithm MD5

View File

@ -32,6 +32,7 @@ $flag_format_odin = '--format=sokol_odin'
$flag_module = '--module'
push-location $path_shaders
write-host 'Compiling shaders'
& $sokol_shdc --input $shadersrc_ve_blit_atlas --output $shaderout_ve_blit_atlas --slang 'hlsl4' $flag_format_odin $flag_module='vefc_blit_atlas'
& $sokol_shdc --input $shadersrc_ve_render_glyph --output $shaderout_ve_render_glyph --slang 'hlsl4' $flag_format_odin $flag_module='vefc_render_glyph'
& $sokol_shdc --input $shadersrc_ve_draw_text --output $shaderout_ve_draw_text --slang 'hlsl4' $flag_format_odin $flag_module='vefc_draw_text'

View File

@ -0,0 +1,28 @@
if ($env:VCINSTALLDIR) {
return
}
$ErrorActionPreference = "Stop"
# Use vswhere to find the latest Visual Studio installation
$vswhere_out = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath
if ($null -eq $vswhere_out) {
Write-Host "ERROR: Visual Studio installation not found"
exit 1
}
# Find Launch-VsDevShell.ps1 in the Visual Studio installation
$vs_path = $vswhere_out
$vs_devshell = Join-Path $vs_path "\Common7\Tools\Launch-VsDevShell.ps1"
if ( -not (Test-Path $vs_devshell) ) {
Write-Host "ERROR: Launch-VsDevShell.ps1 not found in Visual Studio installation"
Write-Host Tested path: $vs_devshell
exit 1
}
# Launch the Visual Studio Developer Shell
Push-Location
write-host @args
& $vs_devshell @args
Pop-Location

View File

@ -7,7 +7,6 @@ $path_thirdparty = join-path $path_root 'thirdparty'
$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/harfbuzz-odin.git'
$url_ini_parser = 'https://github.com/laytan/odin-ini-parser.git'
$url_odin_repo = 'https://github.com/Ed94/Odin.git'
@ -15,7 +14,6 @@ $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_harfbuzz = join-path $path_thirdparty 'harfbuzz'
$path_ini_parser = join-path $path_thirdparty 'ini'
$path_odin = join-path $path_toolchain 'Odin'
@ -35,7 +33,6 @@ $result = verify-path $path_toolchain
$binaries_dirty = $false
clone-gitrepo $path_backtrace $url_backtrace_repo
clone-gitrepo $path_freetype $url_freetype
clone-gitrepo $path_ini_parser $url_ini_parser
clone-gitrepo $path_ini_parser $url_ini_parser
clone-gitrepo $path_sokol_tools $url_sokol_tools
@ -46,7 +43,6 @@ Update-GitRepo -path $path_harfbuzz -url $url_harfbuzz -build_command '.\script
$path_vendor = join-path $path_odin 'vendor'
$path_vendor_raylib = join-path $path_vendor 'raylib'
$path_freetype_dlls = join-path $path_freetype 'binaries/release'
$path_harfbuzz_dlls = join-path $path_harfbuzz 'lib/win64'
$path_raylib_dlls = join-path $path_vendor_raylib 'windows'
$path_sokol_dlls = join-path $path_thirdparty 'sokol'
@ -63,8 +59,27 @@ if ( $binaries_dirty -or $true )
$third_party_dlls = Get-ChildItem -Path $path_sokol_dlls -Filter '*.dll'
foreach ($dll in $third_party_dlls) {
$destination = join-path $path_build $dll.Name
Copy-Item $dll.FullName -Destination $destination -Force
$destination = join-path $path_build $dll.Name
Copy-Item $dll.FullName -Destination $destination -Force
}
}
pop-location
$path_helpers = join-path $PSScriptRoot 'helpers'
$path_devshell = join-path $path_helpers 'devshell.ps1'
. $path_devshell -arch amd64
$path_stb = join-path $path_thirdparty 'stb'
$path_stb_src = join-path $path_stb 'src'
$pkg_stb_truetype_dirty = check-ModuleForChanges $path_stb
if ( $pkg_stb_truetype_dirty)
{
push-location $path_stb_src
& '.\build.bat'
pop-location
}

9
sectr.proj Normal file
View File

@ -0,0 +1,9 @@
// raddbg 0.9.18 project file
recent_file: path: "code/host/host.odin"
target:
{
executable: "build/sectr_host.exe"
working_directory: build
enabled: 1
}

26
thirdparty/stb/LICENSE vendored Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2016-2024 Ginger Bill. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

8
thirdparty/stb/README.md vendored Normal file
View File

@ -0,0 +1,8 @@
# stb_truetype-odin
A modification of the stb_truetype vendor library.
Adds support for:
* Allocator assignement via gb/zpl allocators (essentially equivalent to odin's allocator procedure/data struct)
* Pass #by_ptr on font_info's when they are expected to be immutable (library has the proc signature as `const font_info*`)

0
thirdparty/stb/lib/.gitkeep vendored Normal file
View File

BIN
thirdparty/stb/lib/darwin/stb_truetype.a vendored Normal file

Binary file not shown.

BIN
thirdparty/stb/lib/stb_truetype.a vendored Normal file

Binary file not shown.

BIN
thirdparty/stb/lib/stb_truetype.lib vendored Normal file

Binary file not shown.

BIN
thirdparty/stb/lib/stb_truetype_wasm.o vendored Normal file

Binary file not shown.

27
thirdparty/stb/src/Makefile vendored Normal file
View File

@ -0,0 +1,27 @@
OS=$(shell uname)
ifeq ($(OS), Darwin)
all: darwin
else
all: unix
endif
$(info Current OS is: $(OS))
wasm:
mkdir -p ../lib
$(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_truetype.c -o ../lib/stb_truetype_wasm.o
unix:
mkdir -p ../lib
$(CC) -c -O2 -Os -fPIC stb_truetype.c
$(AR) rcs ../lib/stb_truetype.a stb_truetype.o
#$(CC) -fPIC -shared -Wl,-soname=stb_truetype.so -o ../lib/stb_truetype.so stb_image_truetype.o
rm *.o
darwin:
mkdir -p ../lib
$(CC) -arch x86_64 -c -O2 -Os -fPIC stb_truetype.c -o stb_truetype-x86_64.o -mmacosx-version-min=10.12
$(CC) -arch arm64 -c -O2 -Os -fPIC stb_truetype.c -o stb_truetype-arm64.o -mmacosx-version-min=10.12
lipo -create stb_truetype-x86_64.o stb_truetype-arm64.o -output ../lib/darwin/stb_truetype.a
rm *.o

8
thirdparty/stb/src/build.bat vendored Normal file
View File

@ -0,0 +1,8 @@
@echo off
if not exist "..\lib" mkdir ..\lib
cl -nologo -MT -TC -O2 -c stb_image.c stb_truetype.c
lib -nologo stb_truetype.obj -out:..\lib\stb_truetype.lib
del *.obj

2
thirdparty/stb/src/stb_truetype.c vendored Normal file
View File

@ -0,0 +1,2 @@
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"

5114
thirdparty/stb/src/stb_truetype.h vendored Normal file

File diff suppressed because it is too large Load Diff

19055
thirdparty/stb/src/zpl/zpl.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,660 @@
package stb_truetype
import c "core:c"
import stbrp "vendor:stb/rect_pack"
@(private)
LIB :: (
"../lib/stb_truetype.lib" when ODIN_OS == .Windows
else "../lib/stb_truetype.a" when ODIN_OS == .Linux
else "../lib/darwin/stb_truetype.a" when ODIN_OS == .Darwin
else "../lib/stb_truetype_wasm.o" when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
else ""
)
when LIB != "" {
when !#exists(LIB) {
#panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`")
}
}
when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
foreign import stbtt "../lib/stb_truetype_wasm.o"
} else when LIB != "" {
foreign import stbtt { LIB }
} else {
foreign import stbtt "system:stb_truetype"
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
////
//// INTERFACE
////
////
#assert(size_of(c.int) == size_of(rune))
#assert(size_of(c.int) == size_of(b32))
//-----------------------------------------------------------------------------
// CUSTOM: ODIN COMPATIBLE ALLOCATOR
//-----------------------------------------------------------------------------
zpl_allocator_type :: enum(i32) {
Alloc,
Free,
FreeAll,
Resize,
}
zpl_allocator_proc :: #type proc(allocator_data: rawptr, type: zpl_allocator_type,
size: c.ssize_t, alignment: c.ssize_t,
old_memory: rawptr, old_size: c.ssize_t,
flags : c.ulonglong
) -> rawptr
zpl_allocator :: struct {
procedure: zpl_allocator_proc,
data: rawptr,
}
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
SetAllocator :: proc(allocator : zpl_allocator) ---
}
//-----------------------------------------------------------------------------
// END CUSTOM: ODIN COMPATIBLE ALLOCATOR
//-----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
//
// TEXTURE BAKING API
//
// If you use this API, you only have to call two functions ever.
//
bakedchar :: struct {
x0, y0, x1, y1: u16, // coordinates of bbox in bitmap
xoff, yoff, xadvance: f32,
}
aligned_quad :: struct {
x0, y0, s0, t0: f32, // top-left
x1, y1, s1, t1: f32, // bottom-right
}
// bindings
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// if return is positive, the first unused row of the bitmap
// if return is negative, returns the negative of the number of characters that fit
// if return is 0, no characters fit and no rows were used
// This uses a very crappy packing.
BakeFontBitmap :: proc(data: [^]byte, offset: c.int, // font location (use offset=0 for plain .ttf)
pixel_height: f32, // height of font in pixels
pixels: [^]byte, pw, ph: c.int, // bitmap to be filled in
first_char, num_chars: c.int, // characters to bake
chardata: [^]bakedchar, // you allocate this, it's num_chars long
) -> c.int ---
// Call GetBakedQuad with char_index = 'character - first_char', and it
// creates the quad you need to draw and advances the current position.
//
// The coordinate system used assumes y increases downwards.
//
// Characters will extend both above and below the current position;
// see discussion of "BASELINE" above.
//
// It's inefficient; you might want to c&p it and optimize it.
GetBakedQuad :: proc(chardata: ^bakedchar, pw, ph: c.int, // same data as above
char_index: c.int, // character to display
xpos, ypos: ^f32, // pointers to current position in screen pixel space
q: ^aligned_quad, // output: quad to draw
opengl_fillrule: b32, // true if opengl fill rule; false if DX9 or earlier
) ---
// Query the font vertical metrics without having to create a font first.
GetScaledFontVMetrics :: proc(fontdata: [^]byte, index: c.int, size: f32, ascent, descent, lineGap: ^f32) ---
}
//////////////////////////////////////////////////////////////////////////////
//
// NEW TEXTURE BAKING API
//
// This provides options for packing multiple fonts into one atlas, not
// perfectly but better than nothing.
packedchar :: struct {
x0, y0, x1, y1: u16,
xoff, yoff, xadvance: f32,
xoff2, yoff2: f32,
}
pack_range :: struct {
font_size: f32,
first_unicode_codepoint_in_range: c.int,
array_of_unicode_codepoints: [^]rune,
num_chars: c.int,
chardata_for_range: ^packedchar,
_, _: u8, // used internally to store oversample info
}
pack_context :: struct {
user_allocator_context, pack_info: rawptr,
width, height, stride_in_bytes, padding: c.int,
skip_missing: b32,
h_oversample, v_oversample: u32,
pixels: [^]byte,
nodes: rawptr,
}
POINT_SIZE :: #force_inline proc(x: $T) -> T { return -x } // @NOTE: this was a macro
// bindings
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// Initializes a packing context stored in the passed-in stbtt_pack_context.
// Future calls using this context will pack characters into the bitmap passed
// in here: a 1-channel bitmap that is width * height. stride_in_bytes is
// the distance from one row to the next (or 0 to mean they are packed tightly
// together). "padding" is the amount of padding to leave between each
// character (normally you want '1' for bitmaps you'll use as textures with
// bilinear filtering).
//
// Returns 0 on failure, 1 on success.
PackBegin :: proc(spc: ^pack_context, pixels: [^]byte, width, height, stride_in_bytes, padding: c.int, alloc_context: rawptr) -> c.int ---
// Cleans up the packing context and frees all memory.
PackEnd :: proc(spc: ^pack_context) ---
// Creates character bitmaps from the font_index'th font found in fontdata (use
// font_index=0 if you don't know what that is). It creates num_chars_in_range
// bitmaps for characters with unicode values starting at first_unicode_char_in_range
// and increasing. Data for how to render them is stored in chardata_for_range;
// pass these to stbtt_GetPackedQuad to get back renderable quads.
//
// font_size is the full height of the character from ascender to descender,
// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed
// by stbtt_ScaleForMappingEmToPixels, wrap the point size in POINT_SIZE()
// and pass that result as 'font_size':
// ..., 20 , ... // font max minus min y is 20 pixels tall
// ..., POINT_SIZE(20), ... // 'M' is 20 pixels tall
PackFontRange :: proc(spc: ^pack_context, fontdata: [^]byte, font_index: c.int, font_size: f32, first_unicode_char_in_range, num_chars_in_range: c.int, chardata_for_range: ^packedchar) -> c.int ---
// Creates character bitmaps from multiple ranges of characters stored in
// ranges. This will usually create a better-packed bitmap than multiple
// calls to stbtt_PackFontRange. Note that you can call this multiple
// times within a single PackBegin/PackEnd.
PackFontRanges :: proc(spc: ^pack_context, fontdata: [^]byte, font_index: c.int, ranges: [^]pack_range, num_ranges: c.int) -> c.int ---
// Oversampling a font increases the quality by allowing higher-quality subpixel
// positioning, and is especially valuable at smaller text sizes.
//
// This function sets the amount of oversampling for all following calls to
// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given
// pack context. The default (no oversampling) is achieved by h_oversample=1
// and v_oversample=1. The total number of pixels required is
// h_oversample*v_oversample larger than the default; for example, 2x2
// oversampling requires 4x the storage of 1x1. For best results, render
// oversampled textures with bilinear filtering. Look at the readme in
// stb/tests/oversample for information about oversampled fonts
//
// To use with PackFontRangesGather etc., you must set it before calls
// call to PackFontRangesGatherRects.
PackSetOversampling :: proc(spc: ^pack_context, h_oversample, v_oversample: c.uint) ---
// If skip != false, this tells stb_truetype to skip any codepoints for which
// there is no corresponding glyph. If skip=false, which is the default, then
// codepoints without a glyph recived the font's "missing character" glyph,
// typically an empty box by convention.
PackSetSkipMissingCodepoints :: proc(spc: ^pack_context, skip: b32) ---
GetPackedQuad :: proc(chardata: ^packedchar, pw, ph: c.int, // same data as above
char_index: c.int, // character to display
xpos, ypos: ^f32, // pointers to current position in screen pixel space
q: ^aligned_quad, // output: quad to draw
align_to_integer: b32,
) ---
// Calling these functions in sequence is roughly equivalent to calling
// stbtt_PackFontRanges(). If you more control over the packing of multiple
// fonts, or if you want to pack custom data into a font texture, take a look
// at the source to of stbtt_PackFontRanges() and create a custom version
// using these functions, e.g. call GatherRects multiple times,
// building up a single array of rects, then call PackRects once,
// then call RenderIntoRects repeatedly. This may result in a
// better packing than calling PackFontRanges multiple times
// (or it may not).
PackFontRangesGatherRects :: proc(spc: ^pack_context, #by_ptr info: fontinfo, ranges: ^pack_range, num_ranges: c.int, rects: [^]stbrp.Rect) -> c.int ---
PackFontRangesPackRects :: proc(spc: ^pack_context, rects: [^]stbrp.Rect, num_rects: c.int) ---
PackFontRangesRenderIntoRects :: proc(spc: ^pack_context, #by_ptr info: fontinfo, ranges: ^pack_range, num_ranges: c.int, rects: [^]stbrp.Rect) -> c.int ---
}
//////////////////////////////////////////////////////////////////////////////
//
// FONT LOADING
//
//
fontinfo :: struct {
userdata: rawptr,
data: [^]byte,
fontstart: c.int,
numGlyphs: c.int,
loca, head, glyf, hhea, hmtx, kern, gpos, svg: c.int,
index_map: c.int,
indexToLocFormat: c.int,
cff: _buf,
charstrings: _buf,
gsubrs: _buf,
subrs: _buf,
fontdicts: _buf,
fdselect: _buf,
}
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// Given an offset into the file that defines a font, this function builds
// the necessary cached info for the rest of the system. You must allocate
// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't
// need to do anything special to free it, because the contents are pure
// value data with no additional data structures. Returns 0 on failure.
InitFont :: proc(info: ^fontinfo, data: [^]byte, offset: c.int) -> b32 ---
// This function will determine the number of fonts in a font file. TrueType
// collection (.ttc) files may contain multiple fonts, while TrueType font
// (.ttf) files only contain one font. The number of fonts can be used for
// indexing with the previous function where the index is between zero and one
// less than the total fonts. If an error occurs, -1 is returned.
GetNumberOfFonts :: proc(data: [^]byte) -> c.int ---
// Each .ttf/.ttc file may have more than one font. Each font has a sequential
// index number starting from 0. Call this function to get the font offset for
// a given index; it returns -1 if the index is out of range. A regular .ttf
// file will only define one font and it always be at offset 0, so it will
// return '0' for index 0, and -1 for all other indices.
GetFontOffsetForIndex :: proc(data: [^]byte, index: c.int) -> c.int ---
}
//////////////////////////////////////////////////////////////////////////////
//
// CHARACTER TO GLYPH-INDEX CONVERSION
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// If you're going to perform multiple operations on the same character
// and you want a speed-up, call this function with the character you're
// going to process, then use glyph-based functions instead of the
// codepoint-based functions.
// Returns 0 if the character codepoint is not defined in the font.
FindGlyphIndex :: proc(#by_ptr info: fontinfo, unicode_codepoint: rune) -> c.int ---
}
//////////////////////////////////////////////////////////////////////////////
//
// CHARACTER PROPERTIES
//
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// computes a scale factor to produce a font whose "height" is 'pixels' tall.
// Height is measured as the distance from the highest ascender to the lowest
// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics
// and computing:
// scale = pixels / (ascent - descent)
// so if you prefer to measure height by the ascent only, use a similar calculation.
ScaleForPixelHeight :: proc(#by_ptr info: fontinfo, pixels: f32) -> f32 ---
// computes a scale factor to produce a font whose EM size is mapped to
// 'pixels' tall. This is probably what traditional APIs compute, but
// I'm not positive.
ScaleForMappingEmToPixels :: proc(#by_ptr info: fontinfo, pixels: f32) -> f32 ---
// ascent is the coordinate above the baseline the font extends; descent
// is the coordinate below the baseline the font extends (i.e. it is typically negative)
// lineGap is the spacing between one row's descent and the next row's ascent...
// so you should advance the vertical position by "*ascent - *descent + *lineGap"
// these are expressed in unscaled coordinates, so you must multiply by
// the scale factor for a given size
GetFontVMetrics :: proc(#by_ptr info: fontinfo, ascent, descent, lineGap: ^c.int) ---
// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2
// table (specific to MS/Windows TTF files).
//
// Returns 1 on success (table present), 0 on failure.
GetFontVMetricsOS2 :: proc(#by_ptr info: fontinfo, typoAscent, typoDescent, typoLineGap: ^c.int) -> b32 ---
// the bounding box around all possible characters
GetFontBoundingBox :: proc(#by_ptr info: fontinfo, x0, y0, x1, y1: ^c.int) ---
// leftSideBearing is the offset from the current horizontal position to the left edge of the character
// advanceWidth is the offset from the current horizontal position to the next horizontal position
// these are expressed in unscaled coordinates
GetCodepointHMetrics :: proc(#by_ptr info: fontinfo, codepoint: rune, advanceWidth, leftSideBearing: ^c.int) ---
// an additional amount to add to the 'advance' value between ch1 and ch2
GetCodepointKernAdvance :: proc(#by_ptr info: fontinfo, ch1, ch2: rune) -> (advance: c.int) ---
// Gets the bounding box of the visible part of the glyph, in unscaled coordinates
GetCodepointBox :: proc(#by_ptr info: fontinfo, codepoint: rune, x0, y0, x1, y1: ^c.int) -> c.int ---
// as above, but takes one or more glyph indices for greater efficiency
GetGlyphHMetrics :: proc(#by_ptr info: fontinfo, glyph_index: c.int, advanceWidth, leftSideBearing: ^c.int) ---
GetGlyphKernAdvance :: proc(#by_ptr info: fontinfo, glyph1, glyph2: c.int) -> c.int ---
GetGlyphBox :: proc(#by_ptr info : fontinfo, glyph_index: c.int, x0, y0, x1, y1: ^c.int) -> c.int ---
}
kerningentry :: struct {
glyph1: rune, // use FindGlyphIndex
glyph2: rune,
advance: c.int,
}
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// Retrieves a complete list of all of the kerning pairs provided by the font
// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write.
// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1)
GetKerningTableLength :: proc(#by_ptr info: fontinfo) -> c.int ---
GetKerningTable :: proc(#by_ptr info: fontinfo, table: [^]kerningentry, table_length: c.int) -> c.int ---
}
//////////////////////////////////////////////////////////////////////////////
//
// GLYPH SHAPES (you probably don't need these, but they have to go before
// the bitmaps for C declaration-order reasons)
//
vmove :: enum c.int {
none,
vmove=1,
vline,
vcurve,
vcubic,
}
vertex_type :: distinct c.short // can't use stbtt_int16 because that's not visible in the header file
vertex :: struct {
x, y, cx, cy, cx1, cy1: vertex_type,
type, padding: byte,
}
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// returns true if nothing is drawn for this glyph
IsGlyphEmpty :: proc(#by_ptr info: fontinfo, glyph_index: c.int) -> b32 ---
// returns # of vertices and fills *vertices with the pointer to them
// these are expressed in "unscaled" coordinates
//
// The shape is a series of contours. Each one starts with
// a STBTT_moveto, then consists of a series of mixed
// STBTT_lineto and STBTT_curveto segments. A lineto
// draws a line from previous endpoint to its x,y; a curveto
// draws a quadratic bezier from previous endpoint to
// its x,y, using cx,cy as the bezier control point.
GetCodepointShape :: proc(#by_ptr info: fontinfo, unicode_codepoint: rune, vertices: ^[^]vertex) -> c.int ---
GetGlyphShape :: proc(#by_ptr info: fontinfo, glyph_index: c.int, vertices: ^[^]vertex) -> c.int ---
// frees the data allocated above
FreeShape :: proc(#by_ptr info: fontinfo, vertices: [^]vertex) ---
// fills svg with the character's SVG data.
// returns data size or 0 if SVG not found.
FindSVGDoc :: proc(#by_ptr info: fontinfo, gl: b32) -> [^]byte ---
GetCodepointSVG :: proc(#by_ptr info: fontinfo, unicode_codepoint: rune, svg: ^cstring) -> c.int ---
GetGlyphSVG :: proc(#by_ptr info: fontinfo, gl: b32, svg: ^cstring) -> c.int ---
}
//////////////////////////////////////////////////////////////////////////////
//
// BITMAP RENDERING
//
_bitmap :: struct {
w, h, stride: c.int,
pixels: [^]byte,
}
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// frees the bitmap allocated below
FreeBitmap :: proc(bitmap: [^]byte, userdata: rawptr) ---
// allocates a large-enough single-channel 8bpp bitmap and renders the
// specified character/glyph at the specified scale into it, with
// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque).
// *width & *height are filled out with the width & height of the bitmap,
// which is stored left-to-right, top-to-bottom.
//
// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
GetCodepointBitmap :: proc(#by_ptr info: fontinfo, scale_x, scale_y: f32, codepoint: rune, width, height, xoff, yoff: ^c.int) -> [^]byte ---
// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel
// shift for the character
GetCodepointBitmapSubpixel :: proc(#by_ptr info: fontinfo, scale_x, scale_y, shift_x, shift_y: f32, codepoint: rune, width, height, xoff, yoff: ^c.int) -> [^]byte ---
// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap
// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the
// width and height and positioning info for it first.
MakeCodepointBitmap :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y: f32, codepoint: rune) ---
// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel
// shift for the character
MakeCodepointBitmapSubpixel :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, codepoint: rune) ---
// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering
// is performed (see stbtt_PackSetOversampling)
MakeCodepointBitmapSubpixelPrefilter :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, oversample_x, oversample_y: b32, sub_x, sub_y: ^f32, codepoint: rune) ---
// get the bbox of the bitmap centered around the glyph origin; so the
// bitmap width is ix1-ix0, height is iy1-iy0, and location to place
// the bitmap top left is (leftSideBearing*scale,iy0).
// (Note that the bitmap uses y-increases-down, but the shape uses
// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.)
GetCodepointBitmapBox :: proc(#by_ptr font: fontinfo, codepoint: rune, scale_x, scale_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel
// shift for the character
GetCodepointBitmapBoxSubpixel :: proc(#by_ptr font: fontinfo, codepoint: rune, scale_x, scale_y, shift_x, shift_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
// the following functions are equivalent to the above functions, but operate
// on glyph indices instead of Unicode codepoints (for efficiency)
GetGlyphBitmap :: proc(#by_ptr info: fontinfo, scale_x, scale_y: f32, glyph: c.int, width, height, xoff, yoff: ^c.int) -> [^]byte ---
GetGlyphBitmapSubpixel :: proc(#by_ptr info: fontinfo, scale_x, scale_y, shift_x, shift_y: f32, glyph: c.int, width, height, xoff, yoff: ^c.int) -> [^]byte ---
MakeGlyphBitmap :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y: f32, glyph: c.int) ---
MakeGlyphBitmapSubpixel :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, glyph: c.int) ---
MakeGlyphBitmapSubpixelPrefilter :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, oversample_x, oversample_y: c.int, sub_x, sub_y: ^f32, glyph: c.int) ---
GetGlyphBitmapBox :: proc(#by_ptr font: fontinfo, glyph: c.int, scale_x, scale_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
GetGlyphBitmapBoxSubpixel :: proc(#by_ptr font: fontinfo, glyph: c.int, scale_x, scale_y, shift_x, shift_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
// rasterize a shape with quadratic beziers into a bitmap
Rasterize :: proc(result: ^_bitmap, // 1-channel bitmap to draw into
flatness_in_pixels: f32, // allowable error of curve in pixels
vertices: [^]vertex, // array of vertices defining shape
num_verts: c.int, // number of vertices in above array
scale_x, scale_y: f32, // scale applied to input vertices
shift_x, shift_y: f32, // translation applied to input vertices
x_off, y_off: c.int, // another translation applied to input
invert: b32, // if non-zero, vertically flip shape
userdata: rawptr, // context for to STBTT_MALLOC
) ---
}
//////////////////////////////////////////////////////////////////////////////
//
// Signed Distance Function (or Field) rendering
//
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// frees the SDF bitmap allocated below
FreeSDF :: proc(bitmap: [^]byte, userdata: rawptr) ---
// These functions compute a discretized SDF field for a single character, suitable for storing
// in a single-channel texture, sampling with bilinear filtering, and testing against
// larger than some threshold to produce scalable fonts.
// info -- the font
// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap
// glyph/codepoint -- the character to generate the SDF for
// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0),
// which allows effects like bit outlines
// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character)
// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale)
// if positive, > onedge_value is inside; if negative, < onedge_value is inside
// width,height -- output height & width of the SDF bitmap (including padding)
// xoff,yoff -- output origin of the character
// return value -- a 2D array of bytes 0..255, width*height in size
//
// pixel_dist_scale & onedge_value are a scale & bias that allows you to make
// optimal use of the limited 0..255 for your application, trading off precision
// and special effects. SDF values outside the range 0..255 are clamped to 0..255.
//
// Example:
// scale = stbtt_ScaleForPixelHeight(22)
// padding = 5
// onedge_value = 180
// pixel_dist_scale = 180/5.0 = 36.0
//
// This will create an SDF bitmap in which the character is about 22 pixels
// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled
// shape, sample the SDF at each pixel and fill the pixel if the SDF value
// is greater than or equal to 180/255. (You'll actually want to antialias,
// which is beyond the scope of this example.) Additionally, you can compute
// offset outlines (e.g. to stroke the character border inside & outside,
// or only outside). For example, to fill outside the character up to 3 SDF
// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above
// choice of variables maps a range from 5 pixels outside the shape to
// 2 pixels inside the shape to 0..255; this is intended primarily for apply
// outside effects only (the interior range is needed to allow proper
// antialiasing of the font at *smaller* sizes)
//
// The function computes the SDF analytically at each SDF pixel, not by e.g.
// building a higher-res bitmap and approximating it. In theory the quality
// should be as high as possible for an SDF of this size & representation, but
// unclear if this is true in practice (perhaps building a higher-res bitmap
// and computing from that can allow drop-out prevention).
//
// The algorithm has not been optimized at all, so expect it to be slow
// if computing lots of characters or very large sizes.
GetGlyphSDF :: proc(#by_ptr info: fontinfo, scale: f32, glyph, padding: c.int, onedge_value: u8, pixel_dist_scale: f32, width, height, xoff, yoff: ^c.int) -> [^]byte ---
GetCodepointSDF :: proc(#by_ptr info: fontinfo, scale: f32, codepoint, padding: c.int, onedge_value: u8, pixel_dist_scale: f32, width, height, xoff, yoff: ^c.int) -> [^]byte ---
}
//////////////////////////////////////////////////////////////////////////////
//
// Finding the right font...
//
// You should really just solve this offline, keep your own tables
// of what font is what, and don't try to get it out of the .ttf file.
// That's because getting it out of the .ttf file is really hard, because
// the names in the file can appear in many possible encodings, in many
// possible languages, and e.g. if you need a case-insensitive comparison,
// the details of that depend on the encoding & language in a complex way
// (actually underspecified in truetype, but also gigantic).
//
// But you can use the provided functions in two possible ways:
// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on
// unicode-encoded names to try to find the font you want;
// you can run this before calling stbtt_InitFont()
//
// stbtt_GetFontNameString() lets you get any of the various strings
// from the file yourself and do your own comparisons on them.
// You have to have called stbtt_InitFont() first.
MACSTYLE_DONTCARE :: 0
MACSTYLE_BOLD :: 1
MACSTYLE_ITALIC :: 2
MACSTYLE_UNDERSCORE :: 4
MACSTYLE_NONE :: 8 // <= not same as 0, this makes us check the bitfield is 0
@(default_calling_convention="c", link_prefix="stbtt_")
foreign stbtt {
// returns the offset (not index) of the font that matches, or -1 if none
// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold".
// if you use any other flag, use a font name like "Arial"; this checks
// the 'macStyle' header field; i don't know if fonts set this consistently
FindMatchingFont :: proc(fontdata: [^]byte, name: cstring, flags: c.int) -> c.int ---
// returns 1/0 whether the first string interpreted as utf8 is identical to
// the second string interpreted as big-endian utf16... useful for strings from next func
CompareUTF8toUTF16_bigendian :: proc(s1: cstring, len1: c.int, s2: cstring, len2: c.int) -> c.int ---
// returns the string (which may be big-endian double byte, e.g. for unicode)
// and puts the length in bytes in *length.
//
// some of the values for the IDs are below; for more see the truetype spec:
// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html
// http://www.microsoft.com/typography/otspec/name.htm
GetFontNameString :: proc(#by_ptr font: fontinfo, length: ^c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring ---
}
PLATFORM_ID :: enum c.int { // platformID
PLATFORM_ID_UNICODE = 0,
PLATFORM_ID_MAC = 1,
PLATFORM_ID_ISO = 2,
PLATFORM_ID_MICROSOFT = 3,
}
// encodingID for PLATFORM_ID_UNICODE
UNICODE_EID_UNICODE_1_0 :: 0
UNICODE_EID_UNICODE_1_1 :: 1
UNICODE_EID_ISO_10646 :: 2
UNICODE_EID_UNICODE_2_0_BMP :: 3
UNICODE_EID_UNICODE_2_0_FULL :: 4
// encodingID for PLATFORM_ID_MICROSOFT
MS_EID_SYMBOL :: 0
MS_EID_UNICODE_BMP :: 1
MS_EID_SHIFTJIS :: 2
MS_EID_UNICODE_FULL :: 10
// encodingID for PLATFORM_ID_MAC; same as Script Manager codes
MAC_EID_ROMAN, MAC_EID_ARABIC :: 0, 4
MAC_EID_JAPANESE, MAC_EID_HEBREW :: 1, 5
MAC_EID_CHINESE_TRAD, MAC_EID_GREEK :: 2, 6
MAC_EID_KOREAN, MAC_EID_RUSSIAN :: 3, 7
// languageID for PLATFORM_ID_MICROSOFT; same as LCID...
// problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs
MS_LANG_ENGLISH, MS_LANG_ITALIAN :: 0x0409, 0x0410
MS_LANG_CHINESE, MS_LANG_JAPANESE :: 0x0804, 0x0411
MS_LANG_DUTCH, MS_LANG_KOREAN :: 0x0413, 0x0412
MS_LANG_FRENCH, MS_LANG_RUSSIAN :: 0x040c, 0x0419
MS_LANG_GERMAN, MS_LANG_SPANISH :: 0x0407, 0x0409
MS_LANG_HEBREW, MS_LANG_SWEDISH :: 0x040d, 0x041D
// languageID for PLATFORM_ID_MAC
MAC_LANG_ENGLISH, MAC_LANG_JAPANESE :: 0, 11
MAC_LANG_ARABIC, MAC_LANG_KOREAN :: 12, 23
MAC_LANG_DUTCH, MAC_LANG_RUSSIAN :: 4, 32
MAC_LANG_FRENCH, MAC_LANG_SPANISH :: 1, 6
MAC_LANG_GERMAN, MAC_LANG_SWEDISH :: 2, 5
MAC_LANG_HEBREW, MAC_LANG_CHINESE_SIMPLIFIED :: 10, 33
MAC_LANG_ITALIAN, MAC_LANG_CHINESE_TRAD :: 3, 19
// private structure
_buf :: struct {
data: [^]byte,
cursor: c.int,
size: c.int,
}

View File

@ -0,0 +1,4 @@
#+build wasm32, wasm64p32
package stb_truetype
@(require) import _ "vendor:libc"

Submodule toolchain/Odin deleted from aa8bc79d34