From 36cc5579753bd5ed268cdf400f882ca7d3461849 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 09:07:26 -0500 Subject: [PATCH 01/62] WIP: Updating public repo with latest version --- Readme.md | 52 +- docs/Readme.md | 2 + scripts/build_sokol_demo.ps1 | 8 +- vefontcache/LRU.odin | 208 +-- vefontcache/atlas.odin | 194 +-- vefontcache/draw.odin | 1275 +++++++-------- vefontcache/freetype_wip.odin | 163 ++ vefontcache/misc.odin | 155 +- vefontcache/parser.odin | 405 ++--- .../{mappings.odin => pkg_mapping.odin} | 67 +- vefontcache/shaped_text.odin | 131 -- vefontcache/shaper.odin | 395 ++++- vefontcache/vefontcache.odin | 1378 ++++++++++++----- 13 files changed, 2724 insertions(+), 1709 deletions(-) create mode 100644 vefontcache/freetype_wip.odin rename vefontcache/{mappings.odin => pkg_mapping.odin} (59%) delete mode 100644 vefontcache/shaped_text.odin diff --git a/Readme.md b/Readme.md index 197985d..9951a30 100644 --- a/Readme.md +++ b/Readme.md @@ -1,28 +1,52 @@ -# VE Font Cache : Odin Port +# VE Font Cache https://github.com/user-attachments/assets/b74f1ec1-f980-45df-b604-d6b7d87d40ff -This is a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library. - +This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. +Since then the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements. + +Features: + +* Simple and well documented. +* Load and unload fonts at anytime +* Almost entirely configurabe and tunable at runtime! +* Full support for hot-reload + * Clear the caches at any-time! +* Robust quality of life features: + * Tracks text layers! + * Push and pop stack for font, font_size, colour, view, position, scale and zoom! + * Enforce even only font-sizing (useful for linear-zoom) [TODO] + * Snap-positining to view for better hinting +* Basic or advanced text shaping via Harfbuzz +* All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu. + * Can hand thousands of draw text calls with very large or small shapes. +* 4-Level Regioned Texture Atlas for caching rendered glyphs +* Text shape caching +* Glyph texture buffer for rendering the text with super-sampling to downsample to the atlas or direct to target screen. +* Super-sample by a font size scalar for sharper glyphs +* All caching backed by an optimized 32-bit LRU indexing cache +* Provides a draw list that is backend agnostic (see [backend](./backend) for usage example). + +Upcoming: + +* Support for ear-clipping triangulation + * Support for which triangulation method used on a by font basis? +* Multi-threading supported job queue. + * Lift heavy-lifting portion of the library's context into a thread context. + * Synchronize threads by merging their generated layered drawlist into a file draw-list for processing on the user's render thread. + * User defines how context's are distributed for drawing (a basic quandrant basic selector procedure will be provided.) + See: [docs/Readme.md](docs/Readme.md) for the library's interface. ## Building 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 & mac. +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. +The library depends on harfbuzz, & stb_truetype to build. +Note: harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet). ![image](https://github.com/user-attachments/assets/2f6c0b36-179c-42fe-8903-7640ae3c209e) diff --git a/docs/Readme.md b/docs/Readme.md index 7195858..5ddbbeb 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,5 +1,7 @@ # Interface +TODO: OUTDATED + Notes --- diff --git a/scripts/build_sokol_demo.ps1 b/scripts/build_sokol_demo.ps1 index e9d1ef3..5dad272 100644 --- a/scripts/build_sokol_demo.ps1 +++ b/scripts/build_sokol_demo.ps1 @@ -94,11 +94,11 @@ function build-SokolBackendDemo # $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_none # $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed - $build_args += $falg_optimize_aggressive - # $build_args += $flag_debug + # $build_args += $falg_optimize_aggressive + $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows' # $build_args += ($flag_extra_linker_flags + $linker_args ) @@ -111,6 +111,8 @@ function build-SokolBackendDemo # $build_args += $flag_sanitize_address # $build_args += $flag_sanitize_memory + Write-Host $build_args + Invoke-WithColorCodedOutput { & $odin_compiler $build_args } } build-SokolBackendDemo diff --git a/vefontcache/LRU.odin b/vefontcache/LRU.odin index 3682ccf..20e18a3 100644 --- a/vefontcache/LRU.odin +++ b/vefontcache/LRU.odin @@ -1,22 +1,36 @@ 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" -Pool_ListIter :: i32 -Pool_ListValue :: u64 +// 16-bit hashing was attempted, however it seems to get collisions with djb8_hash_16 -Pool_List_Item :: struct { +LRU_Fail_Mask_16 :: 0xFFFF +LRU_Fail_Mask_32 :: 0xFFFFFFFF +LRU_Fail_Mask_64 :: 0xFFFFFFFFFFFFFFFF + +Pool_ListIter :: i32 + +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,10 +39,10 @@ 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) ) + pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) ) assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array") resize( & pool.items, capacity ) @@ -39,130 +53,130 @@ pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = " 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 ) { - using pool - clear(& items) - clear(& free_list) +pool_list_clear :: proc( pool: ^Pool_List($V_Type) ) +{ + 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 ] + id := pool.free_list[ len(pool.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 + 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 :: #force_inline proc( 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 { +@(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 ) @@ -171,69 +185,69 @@ 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[u64]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[u64]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 : u64, 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 : u64 ) -> i32 { +@(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 + pool_list_move_to_front(&cache.key_queue, link.ptr) + return link.value } return -1 } -lru_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 { +@(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 ) + evict := pool_list_peek_back( cache.key_queue ) return evict } - return 0xFFFFFFFFFFFFFFFF + return ~Key_Type(0) } -lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, 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 @@ -241,8 +255,10 @@ lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := fal return iter.value } -lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 +@(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 { pool_list_move_to_front( & cache.key_queue, link.ptr ) link.value = value @@ -265,8 +281,8 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u return evict } -lru_refresh :: proc( cache : ^LRU_Cache, key : u64 ) { - link, success := lru_find( cache, key ) +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 ) link.ptr = cache.key_queue.front diff --git a/vefontcache/atlas.odin b/vefontcache/atlas.odin index 33a2ceb..ddb95ac 100644 --- a/vefontcache/atlas.odin +++ b/vefontcache/atlas.odin @@ -1,127 +1,127 @@ 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 = 0x41, - B = 0x42, - C = 0x43, - D = 0x44, - E = 0x45, + A = 0x01, + B = 0x02, + C = 0x03, + D = 0x04, + E = 0x05, Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call } -Atlas_Region :: struct { - state : LRU_Cache, +// Note(Ed): Using 16 bit hash had collision failures and no observable performance improvement (tried several 16-bit hashers) +Atlas_Key :: u32 - width : i32, - height : i32, +// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Atlas. +/* Essentially a sub-atlas of the atlas. There is a state cache per region that tracks the glyph inventory (what slot they occupy). + Unlike the shape cache this one's fixed capacity (natrually) and the next avail slot is tracked. +*/ +Atlas_Region :: struct { + state : LRU_Cache(Atlas_Key), size : Vec2i, capacity : Vec2i, offset : Vec2i, + 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 { - width : i32, - height : i32, - - glyph_padding : i32, // Padding to add to bounds__scaled for choosing which atlas region. - glyph_over_scalar : f32, // Scalar to apply to bounds__scaled for choosing which atlas region. - region_a : Atlas_Region, region_b : Atlas_Region, region_c : Atlas_Region, region_d : Atlas_Region, + + regions : [5] ^Atlas_Region, + + glyph_padding : f32, // Padding to add to bounds__scaled for choosing which atlas region. + size_multiplier : f32, // Grows all text by this multiple. + + size : Vec2i, } -atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) -{ - switch region - { - case .A: - size.x = f32(atlas.region_a.width) - size.y = f32(atlas.region_a.height) - - position.x = cast(f32) (( local_idx % atlas.region_a.capacity.x ) * atlas.region_a.width) - position.y = cast(f32) (( local_idx / atlas.region_a.capacity.x ) * atlas.region_a.height) - - position.x += f32(atlas.region_a.offset.x) - position.y += f32(atlas.region_a.offset.y) - - case .B: - size.x = f32(atlas.region_b.width) - size.y = f32(atlas.region_b.height) - - position.x = cast(f32) (( local_idx % atlas.region_b.capacity.x ) * atlas.region_b.width) - position.y = cast(f32) (( local_idx / atlas.region_b.capacity.x ) * atlas.region_b.height) - - position.x += f32(atlas.region_b.offset.x) - position.y += f32(atlas.region_b.offset.y) - - case .C: - size.x = f32(atlas.region_c.width) - size.y = f32(atlas.region_c.height) - - position.x = cast(f32) (( local_idx % atlas.region_c.capacity.x ) * atlas.region_c.width) - position.y = cast(f32) (( local_idx / atlas.region_c.capacity.x ) * atlas.region_c.height) - - position.x += f32(atlas.region_c.offset.x) - position.y += f32(atlas.region_c.offset.y) - - case .D: - size.x = f32(atlas.region_d.width) - size.y = f32(atlas.region_d.height) - - position.x = cast(f32) (( local_idx % atlas.region_d.capacity.x ) * atlas.region_d.width) - position.y = cast(f32) (( local_idx / atlas.region_d.capacity.x ) * atlas.region_d.height) - - position.x += f32(atlas.region_d.offset.x) - position.y += f32(atlas.region_d.offset.y) - - case .Ignore, .None, .E: - } +// 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 } -decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2) +@(optimization_mode="favor_size") +atlas_region_bbox :: #force_inline proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2) { - if parser_is_glyph_empty(&entry.parser_info, glyph_index) { - return .None, nil, {} - } + size = vec2(region.slot_size) - bounds_0, bounds_1 := parser_get_glyph_box(&entry.parser_info, glyph_index) - bounds_width := f32(bounds_1.x - bounds_0.x) - bounds_height := f32(bounds_1.y - bounds_0.y) + 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) - atlas := & ctx.atlas - glyph_buffer := & ctx.glyph_buffer - glyph_padding := f32( atlas.glyph_padding ) * 2 - - bounds_width_scaled := i32(bounds_width * entry.size_scale * atlas.glyph_over_scalar + glyph_padding) - bounds_height_scaled := i32(bounds_height * entry.size_scale * atlas.glyph_over_scalar + glyph_padding) - - // Use a lookup table for faster region selection - region_lookup := [4]struct { kind: Atlas_Region_Kind, region: ^Atlas_Region } { - { .A, & atlas.region_a }, - { .B, & atlas.region_b }, - { .C, & atlas.region_c }, - { .D, & atlas.region_d }, - } - - for region in region_lookup do if bounds_width_scaled <= region.region.width && bounds_height_scaled <= region.region.height { - return region.kind, region.region, glyph_buffer.over_sample - } - - if bounds_width_scaled <= glyph_buffer.width \ - && bounds_height_scaled <= glyph_buffer.height { - over_sample = \ - bounds_width_scaled <= glyph_buffer.width / 2 && - bounds_height_scaled <= glyph_buffer.height / 2 ? \ - {2.0, 2.0} \ - : {1.0, 1.0} - return .E, nil, over_sample - } - return .None, nil, {} + 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) + 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].slot_size.x) && + padded_bounds.y <= f32(atlas.regions[kind].slot_size.y) + { + return cast(Atlas_Region_Kind) kind + } + + if padded_bounds.x <= glyph_buffer_size.x && padded_bounds.y <= glyph_buffer_size.y{ + return .E + } + return .None +} + +// Grab an atlas LRU cache slot. +@(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 + { + evicted := lru_put( & region.state, lru_code, region.next_idx ) + atlas_index = region.next_idx + region.next_idx += 1 + assert( evicted == lru_code ) + } + else + { + next_evict_codepoint := lru_get_next_evicted( region.state ) + assert( next_evict_codepoint != LRU_Fail_Mask_16) + + atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true ) + assert( atlas_index != -1 ) + + evicted := lru_put( & region.state, lru_code, atlas_index ) + assert( evicted == next_evict_codepoint ) + } + + assert( lru_get( & region.state, lru_code ) != - 1 ) + return } diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 0a75744..69a1888 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -1,20 +1,63 @@ package vefontcache -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 "base:runtime" +import "base:intrinsics" import "core:slice" +import "thirdparty:freetype" + +Glyph_Trianglation_Method :: enum(i32) { + Ear_Clipping, + Triangle_Fanning, +} Vertex :: struct { pos : Vec2, u, v : f32, } +Glyph_Bounds_Mat :: matrix[2, 2] f32 + +Glyph_Draw_Quad :: struct { + dst_pos : Vec2, + dst_scale : Vec2, + src_pos : Vec2, + src_scale : Vec2, +} + +// 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 #packed { + position : Vec2, + + atlas_index : i32, + in_atlas : b8, + should_cache : b8, + region_pos : Vec2, + region_size : Vec2, + + 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, +} + Draw_Call :: struct { pass : Frame_Buffer_Pass, start_index : u32, end_index : u32, clear_before_draw : b32, region : Atlas_Region_Kind, - colour : Colour, + colour : RGBAN, } Draw_Call_Default :: Draw_Call { @@ -32,32 +75,50 @@ Draw_List :: struct { calls : [dynamic]Draw_Call, } -// TODO(Ed): This was a rough translation of the raw values the orignal was using, need to give better names... Frame_Buffer_Pass :: enum u32 { None = 0, - Glyph = 1, - Atlas = 2, - Target = 3, - Target_Uncached = 4, + Glyph = 1, // Operations on glyph buffer render target + Atlas = 2, // Operations on atlas render target + Target = 3, // Operations on user's end-destination render target using atlas + Target_Uncached = 4, // Operations on user's end-destination render target using glyph buffer } -Glyph_Draw_Buffer :: struct { - over_sample : Vec2, - batch : i32, - width : i32, - height : i32, - draw_padding : i32, +Glyph_Batch_Cache :: struct { + table : map[Atlas_Key]b8, + num : i32, + cap : i32, +} - batch_x : i32, +// The general tracker for a generator pipeline +Glyph_Draw_Buffer :: struct{ + over_sample : Vec2, + size : Vec2i, + draw_padding : f32, + snap_glyph_height : f32, + + allocated_x : i32, // Space used (horizontally) within the glyph buffer clear_draw_list : Draw_List, draw_list : Draw_List, + + batch_cache : Glyph_Batch_Cache, + shape_gen_scratch : [dynamic]Vertex, // Used during triangulating a glyph into a mesh. + + glyph_pack : #soa[dynamic]Glyph_Pack_Entry, + oversized : [dynamic]i32, + to_cache : [dynamic]i32, + cached : [dynamic]i32, } -blit_quad :: 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 one render target (src uv0 & 1) to the destination rendertarget (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) - // logf("Blitting: xy0: %0.2f, %0.2f xy1: %0.2f, %0.2f uv0: %0.2f, %0.2f uv1: %0.2f, %0.2f", - // p0.x, p0.y, p1.x, p1.y, uv0.x, uv0.y, uv1.x, uv1.y); v_offset := cast(u32) len(draw_list.vertices) quadv : [4]Vertex = { @@ -88,501 +149,19 @@ blit_quad :: proc( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1 return } -// 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, 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.end_index = cast(u32) len(ctx.draw_list.indices) - if draw.end_index > draw.start_index { - append( & ctx.draw_list.calls, draw) - } - - return true -} - -// TODO(Ed): Is it better to cache the glyph vertices for when it must be re-drawn (directly or two atlas)? -cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32 +// 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) - if glyph_index == Glyph(0) { - return false - } - - // Glyph shape handling are not abstractable between freetype and stb_truetype - if entry.parser_info.kind == .Freetype { - result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) - return result - } - - shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index) - assert(error == .None) - if len(shape) == 0 { - return false - } - - outside := Vec2{bounds_0.x - 21, bounds_0.y - 33} - - draw := Draw_Call_Default - draw.pass = Frame_Buffer_Pass.Glyph - draw.start_index = u32(len(ctx.draw_list.indices)) - - path := &ctx.temp_path - clear(path) - - step := 1.0 / entry.curve_quality - for edge in shape do #partial switch edge.type - { - case .Move: - if len(path) > 0 { - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) - clear(path) - } - fallthrough - - case .Line: - append( path, Vertex { pos = Vec2 { f32(edge.x), f32(edge.y)} } ) - - case .Curve: - assert(len(path) > 0) - p0 := path[ len(path) - 1].pos - p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } - p2 := Vec2{ f32(edge.x), f32(edge.y) } - - for index : f32 = 1; index <= entry.curve_quality; index += 1 { - alpha := index * step - append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } ) - } - - case .Cubic: - assert( len(path) > 0) - p0 := path[ len(path) - 1].pos - p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } - p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } - p3 := Vec2{ f32(edge.x), f32(edge.y) } - - for index : f32 = 1; index <= entry.curve_quality; index += 1 { - alpha := index * step - append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } ) - } - } - - if len(path) > 0 { - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) - } - - draw.end_index = u32(len(ctx.draw_list.indices)) - if draw.end_index > draw.start_index { - append( & ctx.draw_list.calls, draw) - } - - parser_free_shape(&entry.parser_info, shape) - return true -} - -/* - Called by: - * can_batch_glyph : If it determines that the glyph was not detected and we haven't reached capacity in the atlas - * draw_text_shape : Glyph -*/ -cache_glyph_to_atlas :: proc( ctx : ^Context, - font : Font_ID, - glyph_index : Glyph, - lru_code : u64, - atlas_index : i32, - entry : ^Entry, - region_kind : Atlas_Region_Kind, - region : ^Atlas_Region, - over_sample : Vec2 ) -{ - // profile(#procedure) - - // Get hb_font text metrics. These are unscaled! - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - vbounds_0 := vec2(bounds_0) - vbounds_1 := vec2(bounds_1) - bounds_size := vbounds_1 - vbounds_0 - - // E region is special case and not cached to atlas. - if region_kind == .None || region_kind == .E do return - - // Grab an atlas LRU cache slot. - atlas_index := atlas_index - if atlas_index == -1 - { - if region.next_idx < region.state.capacity - { - evicted := lru_put( & region.state, lru_code, i32(region.next_idx) ) - atlas_index = i32(region.next_idx) - region.next_idx += 1 - assert( evicted == lru_code ) - } - else - { - next_evict_codepoint := lru_get_next_evicted( & region.state ) - assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF ) - - atlas_index = lru_peek( & region.state, next_evict_codepoint, must_find = true ) - assert( atlas_index != -1 ) - - evicted := lru_put( & region.state, lru_code, atlas_index ) - assert( evicted == next_evict_codepoint ) - } - - assert( lru_get( & region.state, lru_code ) != - 1 ) - } - - atlas := & ctx.atlas - glyph_buffer := & ctx.glyph_buffer - atlas_size := Vec2 { f32(atlas.width), f32(atlas.height) } - glyph_buffer_size := Vec2 { f32(glyph_buffer.width), f32(glyph_buffer.height) } - glyph_padding := cast(f32) atlas.glyph_padding - - if ctx.debug_print - { - @static debug_total_cached : i32 = 0 - logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", - i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached) - debug_total_cached += 1 - } - - // Draw oversized glyph to glyph render target (FBO) - glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := -1 * vbounds_0 * glyph_draw_scale + vec2( glyph_padding ) - - // Allocate a glyph glyph render target region (FBO) - gwidth_scaled_px := bounds_size.x * glyph_draw_scale.x + over_sample.x * glyph_padding + 1.0 - if i32(f32(glyph_buffer.batch_x) + gwidth_scaled_px) >= i32(glyph_buffer.width) { - flush_glyph_buffer_to_atlas( ctx ) - } - - // Calculate the src and destination regions - slot_position, slot_size := atlas_bbox( atlas, region_kind, atlas_index ) - - dst_glyph_position := slot_position - dst_glyph_size := (bounds_size * entry.size_scale + glyph_padding) - dst_size := (slot_size) - screenspace_x_form( & dst_glyph_position, & dst_glyph_size, atlas_size ) - screenspace_x_form( & slot_position, & dst_size, atlas_size ) - - src_position := Vec2 { f32(glyph_buffer.batch_x), 0 } - src_size := (bounds_size * glyph_draw_scale + over_sample * glyph_padding) - textspace_x_form( & src_position, & src_size, glyph_buffer_size ) - - // Advance glyph_update_batch_x and calculate final glyph drawing transform - glyph_draw_translate.x = (glyph_draw_translate.x + f32(glyph_buffer.batch_x)) - glyph_buffer.batch_x += i32(gwidth_scaled_px) - screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_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, - slot_position, slot_position + dst_size, - { 1.0, 1.0 }, { 1.0, 1.0 } ) - - end_index = cast(u32) len(glyph_buffer.clear_draw_list.indices) - } - - 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_position, slot_position + 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) - cache_glyph( ctx, font, glyph_index, entry, vbounds_0, vbounds_1, glyph_draw_scale, glyph_draw_translate ) -} - -// If the glyuph is found in the atlas, nothing occurs, otherwise, the glyph call is setup to catch it to the atlas -check_glyph_in_atlas :: #force_inline proc( ctx : ^Context, font : Font_ID, entry : ^Entry, glyph_index : Glyph, - lru_code : u64, - atlas_index : i32, - region_kind : Atlas_Region_Kind, - region : ^Atlas_Region, - over_sample : Vec2 -) -> b32 -{ - // profile(#procedure) - assert( glyph_index != -1 ) - - // E region can't batch - if region_kind == .E || region_kind == .None do return false - if ctx.temp_codepoint_seen_num > 1024 do return false - // TODO(Ed): Why 1024? - - if atlas_index == - 1 - { - 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_codepoint := lru_get_next_evicted( & region.state ) - seen, success := ctx.temp_codepoint_seen[next_evict_codepoint] - assert(success != false) - - if (seen) { - return false - } - } - - cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) - } - - assert( lru_get( & region.state, lru_code ) != -1 ) - mark_batch_codepoint_seen( ctx, lru_code) - return true -} - -// ve_fontcache_clear_Draw_List -clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) { - clear( & draw_list.calls ) - clear( & draw_list.indices ) - clear( & draw_list.vertices ) -} - -directly_draw_massive_glyph :: proc( ctx : ^Context, - entry : ^Entry, - glyph : Glyph, - bounds_0, bounds_1 : Vec2, - bounds_size : Vec2, - over_sample, position, scale : Vec2 ) -{ - // profile(#procedure) - flush_glyph_buffer_to_atlas( ctx ) - - glyph_padding := f32(ctx.atlas.glyph_padding) - glyph_buffer_size := Vec2 { f32(ctx.glyph_buffer.width), f32(ctx.glyph_buffer.height) } - - // Draw un-antialiased glyph to update FBO. - glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := -1 * bounds_0 * glyph_draw_scale + vec2_from_scalar(glyph_padding) - screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_size ) - - cache_glyph( ctx, entry.id, glyph, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate ) - - glyph_padding_dbl := glyph_padding * 2 - bounds_scaled := bounds_size * entry.size_scale - - // Figure out the source rect. - glyph_position := Vec2 {} - glyph_size := vec2(glyph_padding) - glyph_dst_size := glyph_size + bounds_scaled - glyph_size += bounds_scaled * over_sample - - // Figure out the destination rect. - bounds_0_scaled := (bounds_0 * entry.size_scale) - dst := position + scale * bounds_0_scaled - glyph_padding * scale - dst_size := glyph_dst_size * scale - textspace_x_form( & glyph_position, & glyph_size, glyph_buffer_size ) - - // Add the glyph Draw_Call. - calls : [2]Draw_Call - - draw_to_target := & calls[0] - { - using draw_to_target - pass = .Target_Uncached - colour = ctx.colour - start_index = u32(len(ctx.draw_list.indices)) - - blit_quad( & ctx.draw_list, - dst, dst + dst_size, - glyph_position, glyph_position + glyph_size ) - - end_index = u32(len(ctx.draw_list.indices)) - } - - clear_glyph_update := & calls[1] - { - // Clear glyph render target (FBO) - clear_glyph_update.pass = .Glyph - clear_glyph_update.start_index = 0 - clear_glyph_update.end_index = 0 - clear_glyph_update.clear_before_draw = true - } - append( & ctx.draw_list.calls, ..calls[:] ) -} - -// Constructs a triangle fan to fill a shape using the provided path -// outside_point represents the center point of the fan. -// -// Note(Original Author): -// WARNING: doesn't actually append Draw_Call; caller is responsible for actually appending the Draw_Call. -// ve_fontcache_draw_filled_path -draw_filled_path :: 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 { - point := point + point := point point.pos = point.pos * scale + translate append( & draw_list.vertices, point ) } @@ -608,134 +187,612 @@ draw_filled_path :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : [ } } -draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, - batch_start_idx, batch_end_idx : i32, - position, scale : Vec2, - snap_width, snap_height : f32 ) +// 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 { - flush_glyph_buffer_to_atlas(ctx) + profile(#procedure) + outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33} - atlas := & ctx.atlas - atlas_size := Vec2{ f32(atlas.width), f32(atlas.height) } - glyph_padding := f32(atlas.glyph_padding) + draw := Draw_Call_Default + draw.pass = Frame_Buffer_Pass.Glyph + draw.start_index = u32(len(draw_list.indices)) - for index := batch_start_idx; index < batch_end_idx; index += 1 + clear(path) + + step := 1.0 / curve_quality + for edge, index in glyph_shape do #partial switch edge.type { - glyph_index := shaped.glyphs[index] + case .Move: + if len(path) > 0 { + fill_path_via_fan_triangulation( draw_list, outside, path[:], scale, translate) + clear(path) + } + fallthrough - if glyph_index == 0 || parser_is_glyph_empty( & entry.parser_info, glyph_index) do continue + case .Line: + append( path, Vertex { pos = Vec2 { f32(edge.x), f32(edge.y)} } ) - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - lru_code := font_glyph_lru_code( entry.id, glyph_index ) - atlas_index := region_kind != .E ? lru_get( & region.state, lru_code ) : -1 - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - vbounds_0 := vec2(bounds_0) - vbounds_1 := vec2(bounds_1) - bounds_size := vbounds_1 - vbounds_0 + case .Curve: + assert(len(path) > 0) + p0 := path[ len(path) - 1].pos + p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } + p2 := Vec2{ f32(edge.x), f32(edge.y) } - shaped_position := shaped.positions[index] - glyph_translate := position + (shaped_position) * scale + for index : f32 = 1; index <= curve_quality; index += 1 { + alpha := index * step + append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } ) + } - if region_kind == .E - { - directly_draw_massive_glyph(ctx, entry, glyph_index, - vbounds_0, vbounds_1, - bounds_size, - over_sample, glyph_translate, scale ) - } - else if atlas_index != -1 - { - // Draw cached glyph - slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) - glyph_scale := bounds_size * entry.size_scale + glyph_padding - bounds_0_scaled := ceil(vbounds_0 * entry.size_scale - 0.5 ) - dst := glyph_translate + bounds_0_scaled * scale - dst_scale := glyph_scale * scale - textspace_x_form( & slot_position, & glyph_scale, atlas_size ) + case .Cubic: + assert( len(path) > 0) + p0 := path[ len(path) - 1].pos + p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } + p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } + p3 := Vec2{ f32(edge.x), f32(edge.y) } - call := Draw_Call_Default - call.pass = .Target - call.colour = ctx.colour - call.start_index = u32(len(ctx.draw_list.indices)) + for index : f32 = 1; index <= curve_quality; index += 1 { + alpha := index * step + append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } ) + } + } - blit_quad(&ctx.draw_list, - dst, dst + dst_scale, - slot_position, slot_position + glyph_scale ) + if len(path) > 0 { + fill_path_via_fan_triangulation(draw_list, outside, path[:], scale, translate) + } - call.end_index = u32(len(ctx.draw_list.indices)) - append(&ctx.draw_list.calls, call) - } + draw.end_index = u32(len(draw_list.indices)) + if draw.end_index > draw.start_index { + append( & draw_list.calls, draw) } } -// Helper for draw_text, all raw text content should be confirmed to be either formatting or visible shapes before getting cached. -draw_text_shape :: proc( ctx : ^Context, - font : Font_ID, - entry : ^Entry, - shaped : ^Shaped_Text, - position, scale : Vec2, - snap_width, snap_height : f32 -) -> (cursor_pos : Vec2) +// 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 +) { - // profile(#procedure) - batch_start_idx : i32 = 0 - for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1 - { - glyph_index := shaped.glyphs[ index ] - if is_empty( ctx, entry, glyph_index ) do continue + 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, + ) + } +} - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - lru_code := font_glyph_lru_code(entry.id, glyph_index) - atlas_index := cast(i32) -1 +/* Generator pipeline for shapes - if region_kind != .E do atlas_index = lru_get( & region.state, lru_code ) - if check_glyph_in_atlas( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue + If you'd like to make a custom draw procedure, this can either be used directly or + modified to create an augmented derivative for a specific code path. - // We can no longer directly append the shape as it has missing glyphs in the atlas + This procedure has no awareness of layers. That should be handled by a higher-order codepath. + For this level of codepaths what matters is maximizing memory locality for: + * Dealing with shaping (essentially minimizing having to ever deal with it in a hot path if possible) + * Dealing with atlas regioning (the expensive region resolution & parser calls are done on the shape pass) - // First batch the other cached glyphs - // flush_glyph_buffer_to_atlas(ctx) - draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale, snap_width, snap_height ) - reset_batch_codepoint_state( ctx ) + 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. + * You have to to be drawing a px font size > ~140 px for it to trigger. + * The atlas can be scaled with the size_multiplier parameter of startup so that it becomes more irrelevant if processing a larger atlas is a non-issue. + * The segregation will not allow slices to exceed the batch_cache capacity of the glyph_buffer (configurable within startup params) + * When The capacity is reached batch_generate_glyphs_draw_list will be called which will do futher compute and then finally draw_list generation. + * This may perform better with smaller shapes vs larger shapes, but having more shapes has a cache lookup penatly so keep that in mind. +*/ +generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, + atlas : ^Atlas, + glyph_buffer : ^Glyph_Draw_Buffer, + px_scalar : f32, - cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) - mark_batch_codepoint_seen( ctx, lru_code) - batch_start_idx = index + colour : RGBAN, + entry : Entry, + px_size : f32, + font_scale : f32, + + target_position : Vec2, + target_scale : Vec2, +) -> (cursor_pos : Vec2) #no_bounds_check +{ + profile(#procedure) + + mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : Atlas_Key ) { + cache.table[lru_code] = true + cache.num += 1 + } + reset_batch :: #force_inline proc( cache : ^Glyph_Batch_Cache ) { + clear_map( & cache.table ) + cache.num = 0 } - draw_text_batch( ctx, entry, shaped, batch_start_idx, cast(i32) len(shaped.glyphs), position, scale, snap_width , snap_height ) - reset_batch_codepoint_state( ctx ) + atlas_glyph_pad := atlas.glyph_padding + atlas_size := vec2(atlas.size) + glyph_buffer_size := vec2(glyph_buffer.size) - cursor_pos = position + shaped.end_cursor_pos * scale + // 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 + resize_soa_non_zero(glyph_pack, len(shape.glyph)) + + 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("translate") + for & glyph, index in glyph_pack { + glyph.position = target_position + (shape.position[index]) * target_scale + } + profile_end() + + profile_begin("batching & segregating glyphs") + clear(oversized) + clear(to_cache) + clear(cached) + reset_batch( & glyph_buffer.batch_cache) + + for & glyph, index in glyph_pack + { + atlas_key := shape.atlas_lru_code[index] + region_kind := shape.region_kind[index] + bounds := shape.bounds[index] + bounds_size_scaled := size(bounds) * font_scale + + if region_kind == .None { + assert(false, "FAILED TO ASSGIN REGION") + continue + } + 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[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: + { + 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 + { + // 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] + 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_begin("glyph needs caching") + glyph.atlas_index = atlas_reserve_slot(region, atlas_key) + pack = to_cache + // profile_end() + } + // profile("append cached") + glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index) + 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, shape, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized), + atlas, + glyph_buffer, + atlas_size, + glyph_buffer_size, + entry, + colour, + font_scale, + target_scale + ) + + reset_batch( & glyph_buffer.batch_cache) + clear(oversized) + clear(to_cache) + clear(cached) + } + profile_end() + + if len(oversized) > 0 || glyph_buffer.batch_cache.num > 0 + { + // Last batch pass + 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, + glyph_buffer_size, + entry, + colour, + font_scale, + target_scale, + ) + } + + cursor_pos = target_position + shape.end_cursor_pos * target_scale return } -flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) -{ - // profile(#procedure) - // Flush Draw_Calls to draw list - merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.clear_draw_list ) - merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.draw_list) - clear_draw_list( & ctx.glyph_buffer.draw_list ) - clear_draw_list( & ctx.glyph_buffer.clear_draw_list ) +/* + 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...) - // Clear glyph render target (FBO) - if ctx.glyph_buffer.batch_x != 0 + Order: Oversized first, then to_cache, then cached. + + Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target. + The compute section will have operations reguarding how many glyphs they 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 to 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, + oversized : []i32, + + atlas : ^Atlas, + glyph_buffer : ^Glyph_Draw_Buffer, + atlas_size : Vec2, + glyph_buffer_size : Vec2, + + entry : Entry, + colour : RGBAN, + font_scale : Vec2, + target_scale : Vec2, +) #no_bounds_check +{ + profile(#procedure) + colour := colour + + profile_begin("glyph transform & draw quads compute") + for id, index in cached { - call := Draw_Call_Default - call.pass = .Glyph - call.start_index = 0 - call.end_index = 0 - call.clear_before_draw = true - append( & ctx.draw_list.calls, call ) - ctx.glyph_buffer.batch_x = 0 + // Quad to for drawing atlas slot to target + 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 + (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] + 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_scale * glyph_buffer.over_sample + // Allocate a glyph glyph render target region (FBO) + 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 = 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 * (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 ) + + // Allocate the space + glyph_buffer.allocated_x += i32(to_allocate_x) + + // Quad to for drawing atlas slot to target (used in generate_cached_draw_list) + draw_quad := & glyph.draw_quad + + // Destination (draw_list's target image) + 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_pos = (glyph.region_pos) + to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, atlas_size ) + } + when ENABLE_OVERSIZED_GLYPHS do for id, index in oversized + { + 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 := (bounds_size_scaled + glyph_padding) * glyph.over_sample + + // Allocate a glyph glyph render target region (FBO) + to_allocate_x := buffer_size.x + 2.0 + glyph_buffer.allocated_x += i32(to_allocate_x) + + // If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered. + glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x) + glyph.buffer_x = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) ) + + // Quad to for drawing atlas slot to target + draw_quad := & glyph.draw_quad + + // Target position (draw_list's target image) + 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 * 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 = 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("gen oversized glyphs draw_list") + when ENABLE_OVERSIZED_GLYPHS do if len(oversized) > 0 + { + when ENABLE_DRAW_TYPE_VISUALIZATION { + colour.r = 1.0 + colour.g = 1.0 + colour.b = 0.0 + } + for pack_id, index in oversized { + error : Allocator_Error + glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) + assert(error == .None) + } + for id, index in oversized + { + 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, + bounds, + glyph_pack[id].draw_transform.pos, + glyph_pack[id].draw_transform.scale + ) + + target_quad := & glyph_pack[id].draw_quad + + draw_to_target : Draw_Call + { + draw_to_target.pass = .Target_Uncached + draw_to_target.colour = colour + draw_to_target.start_index = u32(len(draw_list.indices)) + + blit_quad( draw_list, + target_quad.dst_pos, target_quad.dst_pos + target_quad.dst_scale, + target_quad.src_pos, target_quad.src_pos + target_quad.src_scale ) + + draw_to_target.end_index = u32(len(draw_list.indices)) + } + append( & draw_list.calls, draw_to_target ) + } + + flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x) + for id, index in oversized do parser_free_shape(entry.parser_info, glyph_pack[id].shape) + } + profile_end() + + @(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 + for id, index in sub_pack + { + // profile("glyph") + call.start_index = u32(len(draw_list.indices)) + + quad := glyph_pack[id].draw_quad + blit_quad(draw_list, + quad.dst_pos, quad.dst_pos + quad.dst_scale, + quad.src_pos, quad.src_pos + quad.src_scale + ) + call.end_index = u32(len(draw_list.indices)) + append(& draw_list.calls, call) + } + } + + profile_begin("to_cache: caching to atlas") + if len(to_cache) > 0 + { + for pack_id, index in to_cache { + error : Allocator_Error + glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) + assert(error == .None) + } + + 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 = ceil(dst_glyph_size.x) + 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 = ceil(src_size.x) + 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 id, index in to_cache do parser_free_shape(entry.parser_info, glyph_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() + } + profile_end() + + 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_blit_from_atlas_draw_list( draw_list, glyph_pack[:], cached, colour ) + profile_end() } -// ve_fontcache_merge_Draw_List -merge_draw_list :: proc( dst, src : ^Draw_List ) +// Flush the content of the glyph_buffers draw lists to the main draw list +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) + profile(#procedure) + 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 ) + clear_draw_list( glyph_buffer_clear_draw_list ) + + call := Draw_Call_Default + call.pass = .Glyph + call.start_index = 0 + call.end_index = 0 + call.clear_before_draw = true + append( & draw_list.calls, call ) + (allocated_x ^) = 0 +} + +@(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 ) +} + +// 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 v_offset := cast(u32) len( dst.vertices ) @@ -744,13 +801,13 @@ merge_draw_list :: proc( dst, src : ^Draw_List ) assert( error == .None ) i_offset := cast(u32) len(dst.indices) - for index : int = 0; index < len(src.indices); index += 1 { + for index : i32 = 0; index < cast(i32) len(src.indices); index += 1 { ignored : int ignored, error = append( & dst.indices, src.indices[index] + v_offset ) assert( error == .None ) } - for index : int = 0; index < len(src.calls); index += 1 { + for index : i32 = 0; index < cast(i32) len(src.calls); index += 1 { src_call := src.calls[ index ] src_call.start_index += i_offset src_call.end_index += i_offset @@ -759,9 +816,11 @@ merge_draw_list :: proc( dst, src : ^Draw_List ) } } -optimize_draw_list :: 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) + profile(#procedure) assert(draw_list != nil) can_merge_draw_calls :: #force_inline proc "contextless" ( a, b : ^Draw_Call ) -> bool { diff --git a/vefontcache/freetype_wip.odin b/vefontcache/freetype_wip.odin new file mode 100644 index 0000000..8458182 --- /dev/null +++ b/vefontcache/freetype_wip.odin @@ -0,0 +1,163 @@ +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 +} + +} diff --git a/vefontcache/misc.odin b/vefontcache/misc.odin index e34fbf0..7826400 100644 --- a/vefontcache/misc.odin +++ b/vefontcache/misc.odin @@ -1,16 +1,58 @@ package vefontcache +/* + Didn't want to splinter this into more files.. + Just a bunch of utilities. +*/ + import "base:runtime" import "core:simd" import "core:math" import core_log "core:log" -Colour :: [4]f32 +peek_array :: #force_inline proc "contextless" ( self : [dynamic]$Type ) -> Type { + return self[ len(self) - 1 ] +} + +reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) { + raw := transmute( ^runtime.Raw_Dynamic_Array) self + raw.allocator = allocator +} + +reload_array_soa :: #force_inline proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) { + raw := runtime.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.allocator = allocator +} + +to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte { return slice_ptr( transmute(^byte) typed_data, size_of(Type) ) } + +@(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) } + +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) }} @@ -39,91 +81,29 @@ logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc := core_log.logf( level, fmt, ..args, location = loc ) } -reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) { - raw := transmute( ^runtime.Raw_Dynamic_Array) self - raw.allocator = allocator -} - -reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) { - raw := transmute( ^runtime.Raw_Map) self - raw.allocator = allocator -} - -font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u64) { - lru_code = u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) - return -} - -is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 +@(optimization_mode="favor_size") +to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) { - if glyph_index == 0 do return true - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - return false + pos := position^ + scale_32 := scale^ + + quotient : Vec2 = 1.0 / size + pos = pos * quotient * 2.0 - 1.0 + scale_32 = scale_32 * quotient * 2.0 + + (position^) = pos + (scale^) = scale_32 } -mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 ) { - ctx.temp_codepoint_seen[lru_code] = true - ctx.temp_codepoint_seen_num += 1 -} - -reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { - clear_map( & ctx.temp_codepoint_seen ) - ctx.temp_codepoint_seen_num = 0 -} - -USE_F64_PRECISION_ON_X_FORM_OPS :: false - -screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 ) +@(optimization_mode="favor_size") +to_target_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) { - when USE_F64_PRECISION_ON_X_FORM_OPS - { - pos_64 := vec2_64_from_vec2(position^) - scale_64 := vec2_64_from_vec2(scale^) - - quotient : Vec2_64 = 1.0 / vec2_64(size) - pos_64 = pos_64 * quotient * 2.0 - 1.0 - scale_64 = scale_64 * quotient * 2.0 - - (position^) = { f32(pos_64.x), f32(pos_64.y) } - (scale^) = { f32(scale_64.x), f32(scale_64.y) } - } - else - { - pos := position^ - scale_32 := scale^ - - quotient : Vec2 = 1.0 / size - pos = pos * quotient * 2.0 - 1.0 - scale_32 = scale_32 * quotient * 2.0 - - (position^) = pos - (scale^) = scale_32 - } + quotient : Vec2 = 1.0 / size + (position^) *= quotient + (scale^) *= quotient } -textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 ) -{ - when USE_F64_PRECISION_ON_X_FORM_OPS - { - pos_64 := vec2_64_from_vec2(position^) - scale_64 := vec2_64_from_vec2(scale^) - - quotient : Vec2_64 = 1.0 / vec2_64(size) - pos_64 *= quotient - scale_64 *= quotient - - (position^) = { f32(pos_64.x), f32(pos_64.y) } - (scale^) = { f32(scale_64.x), f32(scale_64.y) } - } - else - { - quotient : Vec2 = 1.0 / size - (position^) *= quotient - (scale^) *= quotient - } -} - -USE_MANUAL_SIMD_FOR_BEZIER_OPS :: false +USE_MANUAL_SIMD_FOR_BEZIER_OPS :: true when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS { @@ -132,11 +112,6 @@ when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS // ve_fontcache_eval_bezier (quadratic) eval_point_on_bezier3 :: #force_inline proc "contextless" ( p0, p1, p2 : Vec2, alpha : f32 ) -> Vec2 { - // p0 := vec2_64(p0) - // p1 := vec2_64(p1) - // p2 := vec2_64(p2) - // alpha := f64(alpha) - weight_start := (1 - alpha) * (1 - alpha) weight_control := 2.0 * (1 - alpha) * alpha weight_end := alpha * alpha @@ -154,12 +129,6 @@ when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS // ve_fontcache_eval_bezier (cubic) eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2 { - // p0 := vec2_64(p0) - // p1 := vec2_64(p1) - // p2 := vec2_64(p2) - // p3 := vec2_64(p3) - // alpha := f64(alpha) - weight_start := (1 - alpha) * (1 - alpha) * (1 - alpha) weight_c_a := 3 * (1 - alpha) * (1 - alpha) * alpha weight_c_b := 3 * (1 - alpha) * alpha * alpha @@ -178,14 +147,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) @@ -209,6 +181,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) diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 2bb0b6f..6be355a 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -2,11 +2,17 @@ package vefontcache /* Notes: +This is a minimal wrapper I originally did incase something than stb_truetype is introduced in the future. +Otherwise, its essentially 1:1 with it. -Freetype will do memory allocations and has an interface the user can implement. -That interface is not exposed from this parser but could be added to parser_init. +Freetype isn't really supported and its not a high priority (pretty sure its too slow). +~~Freetype will do memory allocations and has an interface the user can implement.~~ +~~That interface is not exposed from this parser but could be added to parser_init.~~ -STB_Truetype has macros for its allocation unfortuantely +STB_Truetype: +* Has macros for its allocation unfortuantely. +TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator. +Already wanted to do so anyway to evaluate the glyph point shape implementation on its side. */ import "base:runtime" @@ -14,7 +20,7 @@ import "core:c" import "core:math" import "core:slice" import stbtt "vendor:stb/truetype" -import freetype "thirdparty:freetype" +// import freetype "thirdparty:freetype" Parser_Kind :: enum u32 { STB_TrueType, @@ -26,7 +32,7 @@ Parser_Font_Info :: struct { kind : Parser_Kind, using _ : struct #raw_union { stbtt_info : stbtt.fontinfo, - freetype_info : freetype.Face + // freetype_info : freetype.Face }, data : []byte, } @@ -52,20 +58,20 @@ Parser_Glyph_Shape :: [dynamic]Parser_Glyph_Vertex Parser_Context :: struct { kind : Parser_Kind, - ft_library : freetype.Library, + // ft_library : freetype.Library, } parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind ) { - switch kind - { - case .Freetype: - result := freetype.init_free_type( & ctx.ft_library ) - assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" ) + // switch kind + // { + // case .Freetype: + // result := freetype.init_free_type( & ctx.ft_library ) + // assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" ) - case .STB_TrueType: + // case .STB_TrueType: // Do nothing intentional - } + // } ctx.kind = kind } @@ -74,24 +80,23 @@ parser_shutdown :: proc( ctx : ^Parser_Context ) { // TODO(Ed): Implement } -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 - } + // switch ctx.kind + // { + // case .Freetype: + // when ODIN_OS == .Windows { + // error_status := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info ) + // if error != .Ok do error = true + // } + // 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_status != .Ok do error = true + // } - case .STB_TrueType: - success := stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 ) - if ! success do return - } + // case .STB_TrueType: + error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 ) + // } font.label = label font.data = data @@ -101,158 +106,163 @@ 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" ) + // 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: + // case .STB_TrueType: // Do Nothing - } + // } } -parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph) +parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph) { - 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 ) - } + // 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 - - case .STB_TrueType: - glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( & font.stbtt_info, codepoint ) - return - } - return Glyph(-1) + // } + // return Glyph(-1) } -parser_free_shape :: proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape ) +parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape ) { - switch font.kind - { - case .Freetype: - delete(shape) + // switch font.kind + // { + // case .Freetype: + // delete(shape) - case .STB_TrueType: - stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) - } + // case .STB_TrueType: + stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) + // } } -parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 ) +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 ) - } + // 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 - } + // 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 ) - } + // case .STB_TrueType: + 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 +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 ) - } + // 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 ) - } + // 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 ) + // case .STB_TrueType: + kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint ) return kern - } - return -1 + // } + // return -1 } -parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info ) -> (ascent, descent, line_gap : i32 ) +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) + // 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 ) - } + // case .STB_TrueType: + stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap ) + // } return } -parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) +parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : Range2) { - switch font.kind - { - case .Freetype: - freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) + // profile(#procedure) + bounds_0, bounds_1 : Vec2i - metrics := font.freetype_info.glyph.metrics + // switch font.kind + // { + // case .Freetype: + // freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) - 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)} + // metrics := font.freetype_info.glyph.metrics - case .STB_TrueType: + // 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 ) + success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) + // assert( success ) - bounds_0 = { i32(x0), i32(y0) } - bounds_1 = { i32(x1), i32(y1) } - } + bounds_0 = { x0, y0 } + bounds_1 = { x1, y1 } + // } + bounds = { vec2(bounds_0), vec2(bounds_1) } return } -parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) +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. + // 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: + // case .STB_TrueType: stb_shape : [^]stbtt.vertex - nverts := stbtt.GetGlyphShape( & font.stbtt_info, cast(i32) glyph_index, & stb_shape ) + 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 @@ -260,83 +270,82 @@ parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph ) shape_raw.cap = int(nverts) shape_raw.allocator = runtime.nil_allocator() error = Allocator_Error.None - return - } + // return + // } return } -parser_is_glyph_empty :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> b32 +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 + // 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 + // case .STB_TrueType: + return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index ) + // } + // return false } -parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32 +parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32 { - 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_pixel_height( font, -size ) : parser_scale_for_mapping_em_to_pixels( font, size ) + size_scale := size > 0.0 ? parser_scale_for_pixel_height( font, size ) : parser_scale_for_mapping_em_to_pixels( font, -size ) return size_scale } -parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32 +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 + // 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 + // case.STB_TrueType: + return stbtt.ScaleForPixelHeight( font.stbtt_info, size ) + // } + // return 0 } -parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32 +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 + // 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 + // // 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 + // 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 + // 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 + // case .STB_TrueType: + return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size ) + // } + // return 0 } diff --git a/vefontcache/mappings.odin b/vefontcache/pkg_mapping.odin similarity index 59% rename from vefontcache/mappings.odin rename to vefontcache/pkg_mapping.odin index f6afc8d..9e39d0f 100644 --- a/vefontcache/mappings.odin +++ b/vefontcache/pkg_mapping.odin @@ -1,7 +1,10 @@ package vefontcache +import "base:builtin" + resize_soa_non_zero :: non_zero_resize_soa +import "base:runtime" import "core:hash" - fnv64a :: hash.fnv64a + ginger16 :: hash.ginger16 import "core:math" ceil_f16 :: math.ceil_f16 ceil_f16le :: math.ceil_f16le @@ -34,6 +37,7 @@ import "core:mem" arena_allocator :: mem.arena_allocator arena_init :: mem.arena_init import "core:slice" +import "core:unicode" //#region("Proc overload mappings") @@ -43,6 +47,10 @@ append :: proc { append_elem_string, } +append_soa :: proc { + append_soa_elem +} + ceil :: proc { math.ceil_f16, math.ceil_f16le, @@ -58,8 +66,8 @@ ceil :: proc { } clear :: proc { - clear_dynamic_array, - clear_map, + builtin.clear_dynamic_array, + builtin.clear_map, } floor :: proc { @@ -80,16 +88,39 @@ 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 { + 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, +} + +size :: proc { + size_range2, } vec2 :: proc { @@ -105,4 +136,22 @@ 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") + diff --git a/vefontcache/shaped_text.odin b/vefontcache/shaped_text.odin deleted file mode 100644 index d23436e..0000000 --- a/vefontcache/shaped_text.odin +++ /dev/null @@ -1,131 +0,0 @@ -package vefontcache - -Shaped_Text :: struct { - glyphs : [dynamic]Glyph, - positions : [dynamic]Vec2, - end_cursor_pos : Vec2, - size : Vec2, -} - -Shaped_Text_Cache :: struct { - storage : [dynamic]Shaped_Text, - state : LRU_Cache, - next_cache_id : i32, -} - -shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { - for value in bytes { - (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) - } -} - -shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry ) -> ^Shaped_Text -{ - // profile(#procedure) - font := font - font_bytes := slice_ptr( transmute(^byte) & font, size_of(Font_ID) ) - text_bytes := transmute( []byte) text_utf8 - - lru_code : u64 - shape_lru_hash( & lru_code, font_bytes ) - shape_lru_hash( & lru_code, text_bytes ) - - shape_cache := & ctx.shape_cache - state := & ctx.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) { - shape_cache_idx = shape_cache.next_cache_id - shape_cache.next_cache_id += 1 - evicted := lru_put( state, lru_code, shape_cache_idx ) - } - else - { - next_evict_idx := lru_get_next_evicted( state ) - assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF ) - - shape_cache_idx = lru_peek( state, next_evict_idx, must_find = true ) - assert( shape_cache_idx != - 1 ) - - lru_put( state, lru_code, shape_cache_idx ) - } - - shape_entry := & shape_cache.storage[ shape_cache_idx ] - shape_text_uncached( ctx, font, text_utf8, entry, shape_entry ) - } - - return & shape_cache.storage[ shape_cache_idx ] -} - -shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) -{ - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - clear( & output.glyphs ) - clear( & output.positions ) - - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) - ascent := f32(ascent_i32) - descent := f32(descent_i32) - line_gap := f32(line_gap_i32) - line_height := (ascent - descent + line_gap) * entry.size_scale - - if ctx.use_advanced_shaper - { - shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) - return - } - else - { - // Note(Original Author): - // We use our own fallback dumbass text shaping. - // WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. - - line_count : int = 1 - max_line_width : f32 = 0 - position : Vec2 - - prev_codepoint : rune - for codepoint in text_utf8 - { - if prev_codepoint > 0 { - kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint ) - position.x += f32(kern) * entry.size_scale - } - if codepoint == '\n' - { - line_count += 1 - max_line_width = max(max_line_width, position.x) - position.x = 0.0 - position.y -= line_height - position.y = position.y - prev_codepoint = rune(0) - continue - } - if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { - position.x = ceil(position.x) - } - - append( & output.glyphs, parser_find_glyph_index( & entry.parser_info, codepoint )) - advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) - - append( & output.positions, Vec2 { - ceil(position.x), - ceil(position.y) - }) - - position.x += f32(advance) * entry.size_scale - prev_codepoint = codepoint - } - - output.end_cursor_pos = position - max_line_width = max(max_line_width, position.x) - - output.size.x = max_line_width - output.size.y = f32(line_count) * line_height - } -} diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index 309c4ec..de2ab71 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -6,11 +6,61 @@ Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists import "core:c" import "thirdparty:harfbuzz" +Shape_Key :: u32 + +/* A text whose codepoints have had their relevant glyphs and + associated data resolved for processing in a draw list generation stage. + Traditionally a shape only refers to resolving which glyph and + its position should be used for rendering. + + For this library's case it also involes keeping any content + that does not have to be resolved once again in the later stage of processing: + * Resolve atlas lru codes + * Resolve glyph bounds and scale + * Resolve 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 to streamline + a hot path to only focusing on draw list generation that must be computed every frame. +*/ +Shaped_Text :: struct #packed { + glyph : [dynamic]Glyph, + position : [dynamic]Vec2, + atlas_lru_code : [dynamic]Atlas_Key, + region_kind : [dynamic]Atlas_Region_Kind, + bounds : [dynamic]Range2, + end_cursor_pos : Vec2, + size : Vec2, + font_id : Font_ID, + // TODO(Ed): We need to track the font here for usage in user interface when directly drawing the shape. +} + +// Ease of use cache, can handle thousands of lookups per frame with ease. +// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Shaped_Text. +Shaped_Text_Cache :: struct { + storage : [dynamic]Shaped_Text, + state : LRU_Cache(Shape_Key), + next_cache_id : i32, +} + +// Used by shaper_shape_text_cached, allows user to specify their own proc at compile-time without having to rewrite the caching implementation. +Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, + atlas : Atlas, + glyph_buffer_size : Vec2, + 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, @@ -18,6 +68,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, @@ -37,46 +88,59 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context ) } } -shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr ) -> (info : Shaper_Info) +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 :: 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.blob != 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_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, - ascent, descent, line_gap : i32, size, size_scale : f32 ) +// Recommended shaper. Very performant. +// TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal... +@(optimization_mode="favor_size") +shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text ) { - // profile(#procedure) + profile(#procedure) current_script := harfbuzz.Script.UNKNOWN hb_ucfunc := harfbuzz.unicode_funcs_get_default() harfbuzz.buffer_clear_contents( ctx.hb_buffer ) - assert( info.font != nil ) - - ascent := f32(ascent) - descent := f32(descent) - line_gap := f32(line_gap) + ascent := entry.ascent + descent := entry.descent + line_gap := entry.line_gap + max_line_width := f32(0) line_count := 1 - line_height := ((ascent - descent + line_gap) * size_scale) + line_height := ((ascent - descent + line_gap) * font_scale) - position, vertical_position : f32 - shape_run :: proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, - position, vertical_position, max_line_width: ^f32, line_count: ^int, - ascent, descent, line_gap, size, size_scale: f32, - snap_shape_pos : b32, adv_snap_small_font_threshold : f32 ) + position : Vec2 + + @(optimization_mode="favor_size") + shape_run :: proc( output : ^Shaped_Text, + entry : Entry, + buffer : harfbuzz.Buffer, + script : harfbuzz.Script, + + position : ^Vec2, + max_line_width : ^f32, + line_count : ^int, + + font_px_size : f32, + font_scale : f32, + + snap_shape_pos : b32, + adv_snap_small_font_threshold : f32 + ) { + profile(#procedure) // Set script and direction. We use the system's default langauge. // script = HB_SCRIPT_LATIN harfbuzz.buffer_set_script( buffer, script ) @@ -85,57 +149,58 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp // Perform the actual shaping of this run using HarfBuzz. harfbuzz.buffer_set_content_type( buffer, harfbuzz.Buffer_Content_Type.UNICODE ) - harfbuzz.shape( font, buffer, nil, 0 ) + harfbuzz.shape( entry.shaper_info.font, buffer, nil, 0 ) // Loop over glyphs and append to output buffer. glyph_count : u32 glyph_infos := harfbuzz.buffer_get_glyph_infos( buffer, & glyph_count ) glyph_positions := harfbuzz.buffer_get_glyph_positions( buffer, & glyph_count ) - line_height := (ascent - descent + line_gap) * size_scale + line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale for index : i32; index < i32(glyph_count); index += 1 { 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^ ) - (position^) = 0.0 - (vertical_position^) -= line_height - (vertical_position^) = floor(vertical_position^ + 0.5) + (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 continue } - if abs( size ) <= adv_snap_small_font_threshold + if abs( font_px_size ) <= adv_snap_small_font_threshold { - (position^) = ceil( position^ ) + (position^) = ceil( position^ ) } - append( & output.glyphs, glyph_id ) - - pos := position^ - v_pos := vertical_position^ - offset_x := f32(hb_gposition.x_offset) * size_scale - offset_y := f32(hb_gposition.y_offset) * size_scale - pos += offset_x - v_pos += offset_y + glyph_pos := position^ + offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * font_scale + glyph_pos += offset if snap_shape_pos { - pos = ceil(pos) - v_pos = ceil(v_pos) + glyph_pos = ceil(glyph_pos) } - append( & output.positions, Vec2 {pos, v_pos}) - (position^) += f32(hb_gposition.x_advance) * size_scale - (vertical_position^) += f32(hb_gposition.y_advance) * size_scale - (max_line_width^) = max(max_line_width^, position^) + advance := Vec2 { + 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) + + is_empty := parser_is_glyph_empty(entry.parser_info, glyph) + if ! is_empty { + append( & output.glyph, glyph ) + append( & output.position, glyph_pos) + } } - output.end_cursor_pos.x = position^ - output.end_cursor_pos.y = vertical_position^ + output.end_cursor_pos = position^ harfbuzz.buffer_clear_contents( buffer ) } @@ -160,26 +225,232 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp } // End current run since we've encountered a script change. - shape_run( - ctx.hb_buffer, current_script, info.font, output, - & position, & vertical_position, & max_line_width, & line_count, - ascent, descent, line_gap, size, size_scale, - ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold - ) + shape_run( output, + entry, + ctx.hb_buffer, + current_script, + & position, + & max_line_width, + & line_count, + font_px_Size, + font_scale, + ctx.snap_glyph_position, + ctx.adv_snap_small_font_threshold + ) harfbuzz.buffer_add( ctx.hb_buffer, hb_codepoint, codepoint == '\n' ? 1 : 0 ) current_script = script } // End the last run if needed - shape_run( - ctx.hb_buffer, current_script, info.font, output, - & position, & vertical_position, & max_line_width, & line_count, - ascent, descent, line_gap, size, size_scale, - ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold - ) + shape_run( output, + entry, + ctx.hb_buffer, + current_script, + & position, + & max_line_width, + & line_count, + font_px_Size, + font_scale, + ctx.snap_glyph_position, + ctx.adv_snap_small_font_threshold + ) // Set the final size output.size.x = max_line_width output.size.y = f32(line_count) * line_height return } + +shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context, + atlas : Atlas, + glyph_buffer_size : Vec2, + 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 ) + + shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output ) + + // 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 id, index in output.glyph + { + output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id) + } + profile_end() + + profile_begin("bounds & region") + for id, index in output.glyph + { + bounds := & output.bounds[index] + (bounds ^) = parser_get_bounds( entry.parser_info, id ) + bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale + output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled ) + } + profile_end() +} + +// 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, + 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 ) + + line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale + + line_count : int = 1 + max_line_width : f32 = 0 + position : Vec2 + + prev_codepoint : rune + for codepoint, index in text_utf8 + { + if prev_codepoint > 0 { + kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint ) + position.x += f32(kern) * font_scale + } + if codepoint == '\n' + { + line_count += 1 + max_line_width = max(max_line_width, position.x) + position.x = 0.0 + position.y -= line_height + position.y = position.y + prev_codepoint = rune(0) + continue + } + 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.glyph, glyph_index) + append( & output.position, Vec2 { + ceil(position.x), + ceil(position.y) + }) + } + + advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) + position.x += f32(advance) * font_scale + prev_codepoint = codepoint + } + + output.end_cursor_pos = position + max_line_width = max(max_line_width, position.x) + + 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 id, index in output.glyph + { + output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id) + } + profile_end() + + profile_begin("bounds & region") + for id, index in output.glyph + { + bounds := & output.bounds[index] + (bounds ^) = parser_get_bounds( entry.parser_info, id ) + bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale + output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled ) + } + profile_end() +} + +// Shapes are tracked by the library's context using the shape cache +// and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped. +// Thus this procedures cost will be proporitonal to how muh text it has to sift through. +// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-120 charactes long +// (and its very fast). +@(optimization_mode="favor_size") +shaper_shape_text_cached :: proc( text_utf8 : string, + ctx : ^Shaper_Context, + shape_cache : ^Shaped_Text_Cache, + atlas : Atlas, + glyph_buffer_size : Vec2, + font : Font_ID, + entry : Entry, + font_px_size : f32, + font_scale : f32, + shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc +) -> (shaped_text : Shaped_Text) +{ + profile(#procedure) + 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 : 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) { + shape_cache_idx = shape_cache.next_cache_id + shape_cache.next_cache_id += 1 + evicted := lru_put( state, lru_code, shape_cache_idx ) + } + else + { + next_evict_idx := lru_get_next_evicted( state ^ ) + 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 ) + + lru_put( state, lru_code, shape_cache_idx ) + } + + storage_entry := & shape_cache.storage[ shape_cache_idx ] + shape_text_uncached( ctx, atlas, glyph_buffer_size, entry, font_px_size, font_scale, text_utf8, storage_entry ) + + shaped_text = storage_entry ^ + return + } + + shaped_text = shape_cache.storage[ shape_cache_idx ] + return +} diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 8ca238c..8770c09 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -1,285 +1,349 @@ /* -A port of (https://github.com/hypernewbie/VEFontCache) to Odin. - See: https://github.com/Ed94/VEFontCache-Odin */ package vefontcache import "base:runtime" -Font_ID :: distinct i64 +// See: mappings.odin for profiling hookup +DISABLE_PROFILING :: true +ENABLE_OVERSIZED_GLYPHS :: true +// White: Cached Hit, Red: Cache Miss, Yellow: Oversized (Will override user's colors enabled) +ENABLE_DRAW_TYPE_VISUALIZATION :: false + +Font_ID :: distinct i16 Glyph :: distinct i32 +Load_Font_Error :: enum(i32) { + None, + Parser_Failed, +} + Entry :: struct { parser_info : Parser_Font_Info, shaper_info : Shaper_Info, id : Font_ID, used : b32, curve_quality : f32, - size : f32, - size_scale : f32, + + ascent : f32, + descent : f32, + line_gap : f32, } Entry_Default :: Entry { - id = 0, - used = false, - size = 24.0, - size_scale = 1.0, + id = 0, + used = false, + curve_quality = 3, +} + +// Ease of use encapsulation of common fields for a canvas space +VPZ_Transform :: struct { + view : Vec2, + position : Vec2, + zoom : f32, +} + +Scope_Stack :: struct { + font : [dynamic]Font_ID, + font_size : [dynamic]f32, + colour : [dynamic]RGBAN, + view : [dynamic]Vec2, + position : [dynamic]Vec2, + scale : [dynamic]Vec2, + zoom : [dynamic]f32, } Context :: struct { backing : Allocator, - parser_ctx : Parser_Context, - shaper_ctx : Shaper_Context, + parser_ctx : Parser_Context, // Glyph parser state + shaper_ctx : Shaper_Context, // Text shaper state + // The managed font instances entries : [dynamic]Entry, - temp_path : [dynamic]Vertex, - temp_codepoint_seen : map[u64]bool, - temp_codepoint_seen_num : int, + // TODO(Ed): Review these when preparing to handle lifting of working context to a thread context. + glyph_buffer : Glyph_Draw_Buffer, + atlas : Atlas, + shape_cache : Shaped_Text_Cache, + draw_list : Draw_List, - snap_width : f32, - snap_height : f32, - - colour : Colour, - cursor_pos : Vec2, + batch_shapes_buffer : [dynamic]Shaped_Text, // Used for the procs that batch a layer of text. + // Tracks the offsets for the current layer in a draw_list draw_layer : struct { vertices_offset : int, indices_offset : int, calls_offset : int, }, - draw_list : Draw_List, - atlas : Atlas, - glyph_buffer : Glyph_Draw_Buffer, - shape_cache : Shaped_Text_Cache, + // Note(Ed): Not really used anymore. + // debug_print : b32, + // debug_print_verbose : b32, + + snap_width : f32, + snap_height : f32, + + // Will enforce even px_size when drawing. + even_size_only : f32, + + // Whether or not to snap positioning to the pixel of the view + // Helps with hinting + snap_to_view_extent : b32, + + stack : Scope_Stack, + + cursor_pos : Vec2, // TODO(Ed): Review this (most likely not being used properly right now) + // Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges. + // Has a boldening side-effect. If overblown will look smeared. + alpha_sharpen : f32, + // Used by draw interface to super-scale the text by + // upscaling px_size with px_scalar and then down-scaling + // the draw_list result by the same amount. + px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. default_curve_quality : i32, - use_advanced_shaper : b32, - - debug_print : b32, - debug_print_verbose : b32, -} - - -Init_Atlas_Region_Params :: struct { - width : u32, - height : u32, } Init_Atlas_Params :: struct { - width : u32, - height : u32, - glyph_padding : u32, // Padding to add to bounds__scaled for choosing which atlas region. - glyph_over_scalar : f32, // Scalar to apply to bounds__scaled for choosing which atlas region. - - region_a : Init_Atlas_Region_Params, - region_b : Init_Atlas_Region_Params, - region_c : Init_Atlas_Region_Params, - region_d : Init_Atlas_Region_Params, + size_multiplier : u32, // How much to scale the the atlas size to. (Affects everything, the base is 4096 x 2048 and everything follows from there) + glyph_padding : u32, // Padding to add to bounds__scaled for choosing which atlas region. } Init_Atlas_Params_Default :: Init_Atlas_Params { - width = 4096, - height = 2048, - glyph_padding = 1, - glyph_over_scalar = 1, - - region_a = { - width = 32, - height = 32, - }, - region_b = { - width = 32, - height = 64, - }, - region_c = { - width = 64, - height = 64, - }, - region_d = { - width = 128, - height = 128, - } + size_multiplier = 1, + glyph_padding = 1, } Init_Glyph_Draw_Params :: struct { - over_sample : Vec2, - buffer_batch : u32, - draw_padding : u32, + // During the draw list generation stage when blitting to atlas, the quad wil be ceil()'d to the closest pixel. + snap_glyph_height : b32, + // Intended to be x16 (4x4) super-sampling from the glyph buffer to the atlas. + // Oversized glyphs don't use this and instead do 2x or 1x depending on how massive they are. + over_sample : u32, + // Best to just keep this the same as glyph_padding for the atlas.. + draw_padding : u32, + shape_gen_scratch_reserve : u32, + // How many region.D glyphs can be drawn to the glyph render target buffer at once (worst case scenario) + buffer_glyph_limit : u32, + // How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list + batch_glyph_limit : u32, } Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { - over_sample = Vec2 { 4, 4 }, - buffer_batch = 4, - draw_padding = Init_Atlas_Params_Default.glyph_padding, + snap_glyph_height = false, + over_sample = 4, + draw_padding = Init_Atlas_Params_Default.glyph_padding, + shape_gen_scratch_reserve = 512, + buffer_glyph_limit = 16, + batch_glyph_limit = 256, } Init_Shaper_Params :: struct { - use_advanced_text_shaper : b32, + // Forces a glyph position to align to a pixel (make sure to use snap_to_view_extent with this or else it won't be preserveds) snap_glyph_position : b32, + // Will use more signficant advance during shaping for fonts + // Note(Ed): Thinking of removing, doesn't look good often and its an extra condition in the hot-loop. adv_snap_small_font_threshold : u32, } Init_Shaper_Params_Default :: Init_Shaper_Params { - use_advanced_text_shaper = true, - snap_glyph_position = true, + snap_glyph_position = false, adv_snap_small_font_threshold = 0, } Init_Shape_Cache_Params :: struct { - capacity : u32, - reserve_length : u32, + // Note(Ed): This should mostly just be given the worst-case capacity and reserve at the same time. + // If memory is a concern it can easily be 256 - 2k if not much text is going to be rendered often. + // Shapes should really not exceed 1024 glyphs.. + capacity : u32, + reserve : u32, } Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params { - capacity = 8 * 1024, - reserve_length = 256, + capacity = 10 * 1024, + reserve = 128, } //#region("lifetime") // ve_fontcache_init -startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, +startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // Note(Ed): Only sbt_truetype supported for now. allocator := context.allocator, atlas_params := Init_Atlas_Params_Default, glyph_draw_params := Init_Glyph_Draw_Params_Default, shape_cache_params := Init_Shape_Cache_Params_Default, shaper_params := Init_Shaper_Params_Default, + alpha_sharpen : f32 = 0.1, + px_scalar : f32 = 1.89, + + // Curve quality to use for a font when unspecified, + // Affects step size for bezier curve passes in generate_glyph_pass_draw_list default_curve_quality : u32 = 3, - entires_reserve : u32 = 512, - temp_path_reserve : u32 = 1024, - temp_codepoint_seen_reserve : u32 = 2048, + entires_reserve : u32 = 256, + scope_stack_reserve : u32 = 128, ) { assert( ctx != nil, "Must provide a valid context" ) - using ctx ctx.backing = allocator context.allocator = ctx.backing - use_advanced_shaper = shaper_params.use_advanced_text_shaper + ctx.colour = { 1, 1, 1, 1 } + ctx.alpha_sharpen = alpha_sharpen + ctx.px_scalar = px_scalar + + shaper_ctx := & ctx.shaper_ctx shaper_ctx.adv_snap_small_font_threshold = f32(shaper_params.adv_snap_small_font_threshold) shaper_ctx.snap_glyph_position = shaper_params.snap_glyph_position - if default_curve_quality == 0 { - default_curve_quality = 3 - } - ctx.default_curve_quality = default_curve_quality + ctx.default_curve_quality = default_curve_quality == 0 ? 3 : i32(default_curve_quality) error : Allocator_Error - entries, error = make( [dynamic]Entry, len = 0, cap = entires_reserve ) + ctx.entries, error = make( [dynamic]Entry, len = 0, cap = entires_reserve ) assert(error == .None, "VEFontCache.init : Failed to allocate entries") - temp_path, error = make( [dynamic]Vertex, len = 0, cap = temp_path_reserve ) - assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - - temp_codepoint_seen, error = make( map[u64]bool, uint(temp_codepoint_seen_reserve) ) - assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - - draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 4 * Kilobyte ) + ctx.draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices") - draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 8 * Kilobyte ) + ctx.draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 16 * Kilobyte ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.indices") - draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = 512 ) + ctx.draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.calls") - - init_atlas_region :: proc( region : ^Atlas_Region, params : Init_Atlas_Params, region_params : Init_Atlas_Region_Params, factor : Vec2i, expected_cap : i32 ) + + atlas := & ctx.atlas + Atlas_Setup: { - using region + atlas.size_multiplier = f32(atlas_params.size_multiplier) - next_idx = 0; - width = i32(region_params.width) - height = i32(region_params.height) - size = { - i32(params.width) / factor.x, - i32(params.height) / factor.y, + atlas_size := Vec2i { 4096, 2048 } * i32(atlas.size_multiplier) + slot_region_a := Vec2i { 32, 32 } * i32(atlas.size_multiplier) + slot_region_b := Vec2i { 32, 64 } * i32(atlas.size_multiplier) + slot_region_c := Vec2i { 64, 64 } * i32(atlas.size_multiplier) + slot_region_d := Vec2i { 128, 128 } * i32(atlas.size_multiplier) + + init_atlas_region :: proc( region : ^Atlas_Region, atlas_size, slot_size : Vec2i, factor : Vec2i ) + { + region.next_idx = 0; + region.slot_size = slot_size + region.size = atlas_size / factor + region.capacity = region.size / region.slot_size + + error : Allocator_Error + lru_init( & region.state, region.capacity.x * region.capacity.y ) } - capacity = { - size.x / i32(width), - size.y / i32(height), + init_atlas_region( & atlas.region_a, atlas_size, slot_region_a, { 4, 2}) + init_atlas_region( & atlas.region_b, atlas_size, slot_region_b, { 4, 2}) + init_atlas_region( & atlas.region_c, atlas_size, slot_region_c, { 4, 1}) + init_atlas_region( & atlas.region_d, atlas_size, slot_region_d, { 2, 1}) + + atlas.size = atlas_size + atlas.glyph_padding = f32(atlas_params.glyph_padding) + + atlas.region_a.offset = {0, 0} + atlas.region_b.offset.x = 0 + atlas.region_b.offset.y = atlas.region_a.size.y + atlas.region_c.offset.x = atlas.region_a.size.x + atlas.region_c.offset.y = 0 + atlas.region_d.offset.x = atlas.size.x / 2 + atlas.region_d.offset.y = 0 + + atlas.regions = { + nil, + & atlas.region_a, + & atlas.region_b, + & atlas.region_c, + & atlas.region_d, } - assert( capacity.x * capacity.y == expected_cap ) - - error : Allocator_Error - lru_init( & state, capacity.x * capacity.y ) - } - init_atlas_region( & atlas.region_a, atlas_params, atlas_params.region_a, { 4, 2}, 1024 ) - init_atlas_region( & atlas.region_b, atlas_params, atlas_params.region_b, { 4, 2}, 512 ) - init_atlas_region( & atlas.region_c, atlas_params, atlas_params.region_c, { 4, 1}, 512 ) - init_atlas_region( & atlas.region_d, atlas_params, atlas_params.region_d, { 2, 1}, 256 ) - - atlas.width = i32(atlas_params.width) - atlas.height = i32(atlas_params.height) - atlas.glyph_padding = i32(atlas_params.glyph_padding) - atlas.glyph_over_scalar = atlas_params.glyph_over_scalar - - atlas.region_a.offset = {0, 0} - atlas.region_b.offset.x = 0 - atlas.region_b.offset.y = atlas.region_a.size.y - atlas.region_c.offset.x = atlas.region_a.size.x - atlas.region_c.offset.y = 0 - atlas.region_d.offset.x = atlas.width / 2 - atlas.region_d.offset.y = 0 - - lru_init( & shape_cache.state, i32(shape_cache_params.capacity) ) - - shape_cache.storage, error = make( [dynamic]Shaped_Text, shape_cache_params.capacity ) - assert(error == .None, "VEFontCache.init : Failed to allocate shape_cache.storage") - - for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1 { - stroage_entry := & shape_cache.storage[idx] - using stroage_entry - glyphs, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve_length ) - assert( error == .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" ) - - positions, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve_length ) - assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" ) - - draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = glyph_draw_params.buffer_batch * 2 ) - assert( error == .None, "VEFontCache.init : Failed to allocate calls for draw_list" ) - - draw_list.indices, error = make( [dynamic]u32, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 6 ) - assert( error == .None, "VEFontCache.init : Failed to allocate indices array for draw_list" ) - - draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 4 ) - assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for draw_list" ) } - // Note(From original author): We can actually go over VE_FONTCACHE_GLYPHDRAW_BUFFER_BATCH batches due to smart packing! + Shape_Cache_Setup: { - using glyph_buffer - over_sample = glyph_draw_params.over_sample - batch = cast(i32) glyph_draw_params.buffer_batch - width = atlas.region_d.width * i32(over_sample.x) * batch - height = atlas.region_d.height * i32(over_sample.y) - draw_padding = cast(i32) glyph_draw_params.draw_padding + shape_cache := & ctx.shape_cache + lru_init( & shape_cache.state, i32(shape_cache_params.capacity) ) - draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = glyph_draw_params.buffer_batch * 2 ) - assert( error == .None, "VEFontCache.init : Failed to allocate calls for draw_list" ) + shape_cache.storage, error = make( [dynamic]Shaped_Text, shape_cache_params.capacity ) + assert(error == .None, "VEFontCache.init : Failed to allocate shape_cache.storage") - draw_list.indices, error = make( [dynamic]u32, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 6 ) - assert( error == .None, "VEFontCache.init : Failed to allocate indices array for draw_list" ) + for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1 + { + stroage_entry := & shape_cache.storage[idx] - draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 4 ) - assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for draw_list" ) + stroage_entry.glyph, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve ) + assert( error == .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" ) - clear_draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = glyph_draw_params.buffer_batch * 2 ) - assert( error == .None, "VEFontCache.init : Failed to allocate calls for calls for clear_draw_list" ) + stroage_entry.position, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve ) + assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" ) - clear_draw_list.indices, error = make( [dynamic]u32, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 4 ) + stroage_entry.atlas_lru_code, error = make( [dynamic]Atlas_Key, len = 0, cap = shape_cache_params.reserve ) + assert( error == .None, "VEFontCache.init : Failed to allocate atlas_lru_code array for shape cache storage" ) + + stroage_entry.region_kind, error = make( [dynamic]Atlas_Region_Kind, len = 0, cap = shape_cache_params.reserve ) + assert( error == .None, "VEFontCache.init : Failed to allocate region_kind array for shape cache storage" ) + + stroage_entry.bounds, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve ) + assert( error == .None, "VEFontCache.init : Failed to allocate bounds array for shape cache storage" ) + + // stroage_entry.bounds_scaled, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve ) + // assert( error == .None, "VEFontCache.init : Failed to allocate bounds_scaled array for shape cache storage" ) + } + } + + Glyph_Buffer_Setup: + { + glyph_buffer := & ctx.glyph_buffer + glyph_buffer.snap_glyph_height = cast(f32) i32(glyph_draw_params.snap_glyph_height) + glyph_buffer.over_sample = { f32(glyph_draw_params.over_sample), f32(glyph_draw_params.over_sample) } + glyph_buffer.size.x = atlas.region_d.slot_size.x * i32(glyph_buffer.over_sample.x) * i32(glyph_draw_params.buffer_glyph_limit) + glyph_buffer.size.y = atlas.region_d.slot_size.y * i32(glyph_buffer.over_sample.y) + glyph_buffer.draw_padding = cast(f32) glyph_draw_params.draw_padding + + glyph_buffer.draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte ) + assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for glyph_buffer.draw_list" ) + + glyph_buffer.draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 16 * Kilobyte ) + assert( error == .None, "VEFontCache.init : Failed to allocate indices for glyph_buffer.draw_list" ) + + glyph_buffer.draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte ) + assert( error == .None, "VEFontCache.init : Failed to allocate calls for glyph_buffer.draw_list" ) + + glyph_buffer.clear_draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 2 * Kilobyte ) + assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" ) + + glyph_buffer.clear_draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 4 * Kilobyte ) assert( error == .None, "VEFontCache.init : Failed to allocate calls for indices array for clear_draw_list" ) - clear_draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.buffer_batch * 2 * 4 ) - assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" ) + glyph_buffer.clear_draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte ) + assert( error == .None, "VEFontCache.init : Failed to allocate calls for calls for clear_draw_list" ) + + glyph_buffer.shape_gen_scratch, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.shape_gen_scratch_reserve ) + assert(error == .None, "VEFontCache.init : Failed to allocate shape_gen_scratch") + + batch_cache := & glyph_buffer.batch_cache + batch_cache.cap = i32(glyph_draw_params.batch_glyph_limit) + batch_cache.num = 0 + batch_cache.table, error = make( map[Atlas_Key]b8, uint(glyph_draw_params.batch_glyph_limit) ) + assert(error == .None, "VEFontCache.init : Failed to allocate batch_cache") + + glyph_buffer.glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = uint(shape_cache_params.reserve) ) + glyph_buffer.oversized, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) ) + glyph_buffer.to_cache, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) ) + glyph_buffer.cached, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) ) } - parser_init( & parser_ctx, parser_kind ) - shaper_init( & shaper_ctx ) + parser_init( & ctx.parser_ctx, parser_kind ) + shaper_init( & ctx.shaper_ctx ) + + // Set the default stack values + // Will be popped on shutdown + // push_colour(ctx, {1, 1, 1, 1}) + // push_font_size(ctx, 36) + // push_view(ctx, { 0, 0 }) + // push_position(ctx, {0, 0}) + // push_scale(ctx, 1.0) + // push_zoom(ctx, 1.0) } hot_reload :: proc( ctx : ^Context, allocator : Allocator ) @@ -287,15 +351,29 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) assert( ctx != nil ) ctx.backing = allocator context.allocator = ctx.backing - using ctx - reload_array( & entries, allocator ) - reload_array( & temp_path, allocator ) - reload_map( & ctx.temp_codepoint_seen, allocator ) + atlas := & ctx.atlas + glyph_buffer := & ctx.glyph_buffer + shape_cache := & ctx.shape_cache + draw_list := & ctx.draw_list - reload_array( & draw_list.vertices, allocator) - reload_array( & draw_list.indices, allocator ) - reload_array( & draw_list.calls, allocator ) + reload_array( & ctx.entries, allocator ) + + reload_array( & glyph_buffer.draw_list.calls, allocator ) + reload_array( & glyph_buffer.draw_list.indices, allocator ) + reload_array( & glyph_buffer.draw_list.vertices, allocator ) + + reload_array( & glyph_buffer.clear_draw_list.calls, allocator ) + reload_array( & glyph_buffer.clear_draw_list.indices, allocator ) + reload_array( & glyph_buffer.clear_draw_list.vertices, allocator ) + + reload_map( & glyph_buffer.batch_cache.table, allocator ) + reload_array( & glyph_buffer.shape_gen_scratch, allocator ) + + reload_array_soa( & glyph_buffer.glyph_pack, allocator ) + reload_array( & glyph_buffer.oversized, allocator ) + reload_array( & glyph_buffer.to_cache, allocator ) + reload_array( & glyph_buffer.cached, allocator ) lru_reload( & atlas.region_a.state, allocator) lru_reload( & atlas.region_b.state, allocator) @@ -304,57 +382,43 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) lru_reload( & shape_cache.state, allocator ) for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 { - stroage_entry := & shape_cache.storage[idx] - using stroage_entry - - reload_array( & glyphs, allocator ) - reload_array( & positions, allocator ) + storage_entry := & shape_cache.storage[idx] + reload_array( & storage_entry.glyph, allocator) + reload_array( & storage_entry.position, allocator) + reload_array( & storage_entry.atlas_lru_code, allocator) + reload_array( & storage_entry.region_kind, allocator) + reload_array( & storage_entry.bounds, allocator) + // reload_array( & storage_entry.bounds_scaled, allocator) } - - reload_array( & glyph_buffer.draw_list.calls, allocator ) - reload_array( & glyph_buffer.draw_list.indices, allocator ) - reload_array( & glyph_buffer.draw_list.vertices, allocator ) - - reload_array( & glyph_buffer.clear_draw_list.calls, allocator ) - reload_array( & glyph_buffer.clear_draw_list.indices, allocator ) - reload_array( & glyph_buffer.clear_draw_list.vertices, allocator ) - reload_array( & shape_cache.storage, allocator ) + + reload_array( & draw_list.vertices, allocator) + reload_array( & draw_list.indices, allocator ) + reload_array( & draw_list.calls, allocator ) } -// ve_foncache_shutdown shutdown :: proc( ctx : ^Context ) { assert( ctx != nil ) context.allocator = ctx.backing - using ctx - for & entry in entries { + atlas := & ctx.atlas + glyph_buffer := & ctx.glyph_buffer + shape_cache := & ctx.shape_cache + draw_list := & ctx.draw_list + + // pop_colour(ctx) + // pop_font_size(ctx) + // pop_view(ctx) + // pop_position(ctx) + // pop_scale(ctx) + // pop_zoom(ctx) + + for & entry in ctx.entries { unload_font( ctx, entry.id ) } - - delete( entries ) - delete( temp_path ) - delete( temp_codepoint_seen ) - - delete( draw_list.vertices ) - delete( draw_list.indices ) - delete( draw_list.calls ) - - lru_free( & atlas.region_a.state ) - lru_free( & atlas.region_b.state ) - lru_free( & atlas.region_c.state ) - lru_free( & atlas.region_d.state ) - - for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 { - stroage_entry := & shape_cache.storage[idx] - using stroage_entry - - delete( glyphs ) - delete( positions ) - } - lru_free( & shape_cache.state ) - + delete( ctx.entries ) + delete( glyph_buffer.draw_list.vertices ) delete( glyph_buffer.draw_list.indices ) delete( glyph_buffer.draw_list.calls ) @@ -363,180 +427,38 @@ shutdown :: proc( ctx : ^Context ) delete( glyph_buffer.clear_draw_list.indices ) delete( glyph_buffer.clear_draw_list.calls ) - shaper_shutdown( & shaper_ctx ) - parser_shutdown( & parser_ctx ) -} + delete( glyph_buffer.batch_cache.table ) + delete( glyph_buffer.shape_gen_scratch ) -// ve_fontcache_load -load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID) -{ - assert( ctx != nil ) - assert( len(data) > 0 ) - using ctx - context.allocator = backing + delete_soa( glyph_buffer.glyph_pack) + delete( glyph_buffer.oversized) + delete( glyph_buffer.to_cache) + delete( glyph_buffer.cached) - id : i32 = -1 + lru_free( & atlas.region_a.state ) + lru_free( & atlas.region_b.state ) + lru_free( & atlas.region_c.state ) + lru_free( & atlas.region_d.state ) - for index : i32 = 0; index < i32(len(entries)); index += 1 { - if entries[index].used do continue - id = index - break + for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 { + storage_entry := & shape_cache.storage[idx] + delete( storage_entry.glyph ) + delete( storage_entry.position ) + delete( storage_entry.atlas_lru_code) + delete( storage_entry.region_kind) + delete( storage_entry.bounds) + // delete( storage_entry.bounds_scaled) } - if id == -1 { - append_elem( & entries, Entry {}) - id = cast(i32) len(entries) - 1 - } - assert( id >= 0 && id < i32(len(entries)) ) + lru_free( & shape_cache.state ) + + delete( draw_list.vertices ) + delete( draw_list.indices ) + delete( draw_list.calls ) - entry := & entries[ id ] - { - using entry - used = true - - parser_info = parser_load_font( & parser_ctx, label, data ) - shaper_info = shaper_load_font( & shaper_ctx, label, data, transmute(rawptr) id ) - - size = size_px - size_scale = parser_scale( & parser_info, size ) - - if glyph_curve_quality == 0 { - curve_quality = f32(ctx.default_curve_quality) - } - else { - curve_quality = f32(glyph_curve_quality) - } - } - entry.id = Font_ID(id) - ctx.entries[ id ].id = Font_ID(id) - - font_id = Font_ID(id) - return + shaper_shutdown( & ctx.shaper_ctx ) + parser_shutdown( & ctx.parser_ctx ) } -// ve_fontcache_unload -unload_font :: proc( ctx : ^Context, font : Font_ID ) -{ - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - context.allocator = ctx.backing - - using ctx - entry := & ctx.entries[ font ] - entry.used = false - - parser_unload_font( & entry.parser_info ) - shaper_unload_font( & entry.shaper_info ) -} - -//#endregion("lifetime") - -//#region("drawing") - -// ve_fontcache_configure_snap -configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { - assert( ctx != nil ) - ctx.snap_width = f32(snap_width) - ctx.snap_height = f32(snap_height) -} - -get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos } -set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour } - -draw_text :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 -{ - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - ctx.cursor_pos = {} - - position := position - if ctx.snap_width > 0 do position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width - if ctx.snap_height > 0 do position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height - - entry := & ctx.entries[ font ] - - ChunkType :: enum u32 { Visible, Formatting } - chunk_kind : ChunkType - chunk_start : int = 0 - chunk_end : int = 0 - - text_utf8_bytes := transmute([]u8) text_utf8 - text_chunk : string - - text_chunk = transmute(string) text_utf8_bytes[ : ] - if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk, entry ) - ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) - } - return true -} - -// ve_fontcache_Draw_List -get_draw_list :: proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { - assert( ctx != nil ) - if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 ) - return & ctx.draw_list -} - -get_draw_list_layer :: proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) { - assert( ctx != nil ) - if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset ) - vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ] - indices = ctx.draw_list.indices [ ctx.draw_layer.indices_offset : ] - calls = ctx.draw_list.calls [ ctx.draw_layer.calls_offset : ] - return -} - -// ve_fontcache_flush_Draw_List -flush_draw_list :: proc( ctx : ^Context ) { - assert( ctx != nil ) - using ctx - clear_draw_list( & draw_list ) - draw_layer.vertices_offset = 0 - draw_layer.indices_offset = 0 - draw_layer.calls_offset = 0 -} - -flush_draw_list_layer :: proc( ctx : ^Context ) { - assert( ctx != nil ) - using ctx - draw_layer.vertices_offset = len(draw_list.vertices) - draw_layer.indices_offset = len(draw_list.indices) - draw_layer.calls_offset = len(draw_list.calls) -} - -//#endregion("drawing") - -//#region("metrics") - -measure_text_size :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2) -{ - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - entry := &ctx.entries[font] - shaped := shape_text_cached(ctx, font, text_utf8, entry) - return shaped.size -} - -get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID ) -> ( ascent, descent, line_gap : f32 ) -{ - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - entry := & ctx.entries[ font ] - ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) - - ascent = (f32(ascent_i32) * entry.size_scale) - descent = (f32(descent_i32) * entry.size_scale) - line_gap = (f32(line_gap_i32) * entry.size_scale) - return -} - -//#endregion("metrics") - // Can be used with hot-reload clear_atlas_region_caches :: proc(ctx : ^Context) { @@ -554,19 +476,675 @@ clear_atlas_region_caches :: proc(ctx : ^Context) // Can be used with hot-reload clear_shape_cache :: proc (ctx : ^Context) { - using ctx - lru_clear(& shape_cache.state) - for idx : i32 = 0; idx < cast(i32) cap(shape_cache.storage); idx += 1 - { - stroage_entry := & shape_cache.storage[idx] - using stroage_entry - end_cursor_pos = {} - size = {} - clear(& glyphs) - clear(& positions) - clear(& draw_list.calls) - clear(& draw_list.indices) - clear(& draw_list.vertices) + lru_clear(& ctx.shape_cache.state) + for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 { + stroage_entry := & ctx.shape_cache.storage[idx] + stroage_entry.end_cursor_pos = {} + stroage_entry.size = {} + clear(& stroage_entry.glyph) + clear(& stroage_entry.position) } ctx.shape_cache.next_cache_id = 0 } + +load_font :: proc( ctx : ^Context, label : string, data : []byte, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID, error : Load_Font_Error) +{ + profile(#procedure) + assert( ctx != nil ) + assert( len(data) > 0 ) + context.allocator = ctx.backing + + entries := & ctx.entries + + id : Font_ID = -1 + + for index : i32 = 0; index < i32(len(entries)); index += 1 { + if entries[index].used do continue + id = Font_ID(index) + break + } + if id == -1 { + append_elem( entries, Entry {}) + id = cast(Font_ID) len(entries) - 1 + } + assert( id >= 0 && id < Font_ID(len(entries)) ) + + entry := & entries[ id ] + { + entry.used = true + + profile_begin("calling loaders") + parser_error : b32 + entry.parser_info, parser_error = parser_load_font( & ctx.parser_ctx, label, data ) + if parser_error { + error = .Parser_Failed + return + } + entry.shaper_info = shaper_load_font( & ctx.shaper_ctx, label, data ) + profile_end() + + ascent, descent, line_gap := parser_get_font_vertical_metrics(entry.parser_info) + entry.ascent = f32(ascent) + entry.descent = f32(descent) + entry.line_gap = f32(line_gap) + + if glyph_curve_quality == 0 { + entry.curve_quality = f32(ctx.default_curve_quality) + } + else { + entry.curve_quality = f32(glyph_curve_quality) + } + } + entry.id = Font_ID(id) + ctx.entries[ id ].id = Font_ID(id) + + font_id = Font_ID(id) + return +} + +unload_font :: proc( ctx : ^Context, font : Font_ID ) +{ + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + context.allocator = ctx.backing + + entry := & ctx.entries[ font ] + entry.used = false + + parser_unload_font( & entry.parser_info ) + shaper_unload_font( & entry.shaper_info ) +} + +//#endregion("lifetime") + +//#region("scoping") + +/* Scope stacking ease of use interface. + +View: Extents in 2D for the relative space the the text is being drawn within. +Used with snap_to_view_extent to enforce position snapping. + +Position: Used with a draw procedure that uses relative positioning will offset the incoming position by the given amount. +Scale : Used with a draw procedure that uses relative scaling, will scale the procedures incoming scale by the given amount. +Zoom : Used with a draw procedure that uses scaling via zoom, will scale the procedure's incoming font size & scale based on an 'canvas' camera's notion of it. +*/ + +@(deferred_in = auto_pop_font) +scope_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } +push_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } +pop_font :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font) } +auto_pop_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) } + +@(deferred_in = auto_pop_font_size) +scope_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } +push_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } +pop_font_size :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font_size) } +auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); pop(& ctx.stack.font_size) } + +@(deferred_in = auto_pop_colour ) +scope_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } +push_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } +pop_colour :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.colour) } +auto_pop_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); pop(& ctx.stack.colour) } + +@(deferred_in = auto_pop_view) +scope_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } +push_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } +pop_view :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.view) } +auto_pop_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.view) } + +@(deferred_in = auto_pop_position) +scope_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } +push_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } +pop_position :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop( & ctx.stack.position) } +auto_pop_position :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop( & ctx.stack.position) } + +@(deferred_in = auto_pop_scale) +scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } +push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } +pop_scale :: #force_inline proc( ctx : ^Context, ) { assert(ctx != nil); pop(& ctx.stack.scale) } +auto_pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.scale) } + +@(deferred_in = auto_pop_zoom ) +scope_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom ) } +push_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom) } +pop_zoom :: #force_inline proc( ctx : ^Context ) { pop(& ctx.stack.zoom) } +auto_pop_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { pop(& ctx.stack.zoom) } + +@(deferred_in = auto_pop_vpz) +scope_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { + assert(ctx != nil) + append(& ctx.stack.view, camera.view ) + append(& ctx.stack.position, camera.position ) + append(& ctx.stack.zoom, camera.zoom ) +} +push_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { + assert(ctx != nil) + append(& ctx.stack.view, camera.view ) + append(& ctx.stack.position, camera.position ) + append(& ctx.stack.zoom, camera.zoom ) +} +pop_vpz :: #force_inline proc( ctx : ^Context ) { + assert(ctx != nil) + pop(& ctx.stack.view ) + pop(& ctx.stack.position) + pop(& ctx.stack.zoom ) +} +auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { + assert(ctx != nil) + pop(& ctx.stack.view ) + pop(& ctx.stack.position) + pop(& ctx.stack.zoom ) +} + +//#endregion("scoping") + +//#region("draw_list generation") + +get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } + +// Does nothing when view is 1 or 0. +get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view : Vec2 ) -> (snapped_position : Vec2) { + snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } + should_snap := view * snap_quotient + + snapped_position = position + snapped_position.x = ceil(position.x * view.x) * snap_quotient.x + snapped_position.y = ceil(position.y * view.y) * snap_quotient.y + + snapped_position *= should_snap + snapped_position.x = max(snapped_position.x, position.x) + snapped_position.y = max(snapped_position.y, position.y) + return snapped_position +} + +set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } +set_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); ctx.colour = colour } +set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } + +set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { + assert(ctx != nil) + ctx.shaper_ctx.snap_glyph_position = should_snap +} + +set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { + assert(ctx != nil) + ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap) +} + +// Non-scoping context. The most fundamental interface-level draw shape procedure. +// (everything else is quality of life warppers) +@(optimization_mode="favor_size") +draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, + font : Font_ID, + px_size : f32, + colour : RGBAN, + view : Vec2, + position : Vec2, + scale : Vec2, + zoom : f32, // TODO(Ed): Implement zoom support + shape : Shaped_Text +) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + adjusted_position := get_snapped_position( position, view ) + + entry := ctx.entries[ font ] + + adjusted_colour := colour + adjusted_colour.a = 1.0 + ctx.alpha_sharpen + + 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 ) + + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, + ctx.px_scalar, + adjusted_colour, + entry, + target_px_size, + target_font_scale, + position, + target_scale, + ) +} + +// Non-scoping context. The most fundamental interface-level draw text procedure. +// (everything else is quality of life warppers) +@(optimization_mode = "favor_size") +draw_text_normalized_space :: proc( ctx : ^Context, + font : Font_ID, + px_size : f32, + colour : RGBAN, + view : Vec2, + position : Vec2, + scale : Vec2, + zoom : f32, // TODO(Ed): Implement Zoom support + text_utf8 : string +) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + assert( len(text_utf8) > 0 ) + + ctx.cursor_pos = {} + entry := ctx.entries[ font ] + + adjusted_position := get_snapped_position( position, view ) + + adjusted_colour := colour + adjusted_colour.a = 1.0 + ctx.alpha_sharpen + + // Does nothing when px_scalar is 1.0 + target_px_size := px_size * ctx.px_scalar + target_scale := scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_shape_text_uncached_advanced + ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, + ctx.px_scalar, + adjusted_colour, + entry, + target_px_size, + target_font_scale, + adjusted_position, + target_scale, + ) +} + +// Equivalent to draw_text_shape_normalized_space, however position's units is scaled to view and must be normalized. +@(optimization_mode="favor_size") +draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, + font : Font_ID, + px_size : f32, + colour : RGBAN, + view : Vec2, + position : Vec2, + scale : Vec2, + zoom : f32, // TODO(Ed): Implement zoom support + shape : Shaped_Text +) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + norm_position := position * (1 / view) + + view := view; view.x = max(view.x, 1); view.y = max(view.y, 1) + adjusted_position := get_snapped_position( norm_position, view ) + + entry := ctx.entries[ font ] + + adjusted_colour := colour + adjusted_colour.a = 1.0 + ctx.alpha_sharpen + + 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 ) + + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, + ctx.px_scalar, + adjusted_colour, + entry, + target_px_size, + target_font_scale, + position, + target_scale, + ) +} + +// Equivalent to draw_text_normalized_space, however position's units is scaled to view and must be normalized. +@(optimization_mode = "favor_size") +draw_text_view_space :: proc(ctx : ^Context, + font : Font_ID, + px_size : f32, + colour : RGBAN, + view : Vec2, + view_position : Vec2, + scale : Vec2, + zoom : f32, // TODO(Ed): Implement Zoom support + text_utf8 : string +) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + assert( len(text_utf8) > 0 ) + + ctx.cursor_pos = {} + entry := ctx.entries[ font ] + + norm_position := view_position * (1 / view) + + adjusted_position := get_snapped_position( norm_position, view ) + + adjusted_colour := colour + adjusted_colour.a = 1.0 + ctx.alpha_sharpen + + // Does nothing when px_scalar is 1.0 + target_px_size := px_size * ctx.px_scalar + target_scale := scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_shape_text_uncached_advanced + ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, + ctx.px_scalar, + adjusted_colour, + entry, + target_px_size, + target_font_scale, + adjusted_position, + target_scale, + ) +} + +@(optimization_mode = "favor_size") +draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) +{ + profile(#procedure) + assert( ctx != nil ) + + stack := & ctx.stack + assert(len(stack.font) > 0) + assert(len(stack.view) > 0) + assert(len(stack.colour) > 0) + assert(len(stack.position) > 0) + assert(len(stack.scale) > 0) + assert(len(stack.font_size) > 0) + assert(len(stack.zoom) > 0) + + // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) + font := peek(stack.font) + assert( font >= 0 &&int(font) < len(ctx.entries) ) + + view := peek(stack.view); + + ctx.cursor_pos = {} + entry := ctx.entries[ font ] + + adjusted_colour := peek(stack.colour) + adjusted_colour.a = 1.0 + ctx.alpha_sharpen + + // TODO(Ed): Implement zoom for draw_text + zoom := peek(stack.zoom) + + absolute_position := peek(stack.position) + position + absolute_scale := peek(stack.scale) * scale + + adjusted_position := get_snapped_position( absolute_position, view ) + + px_size := peek(stack.font_size) + + // Does nothing when px_scalar is 1.0 + target_px_size := px_size * ctx.px_scalar + target_scale := absolute_scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, + ctx.px_scalar, + adjusted_colour, + entry, + target_px_size, + target_font_scale, + adjusted_position, + target_scale, + ) +} + +@(optimization_mode = "favor_size") +draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) +{ + profile(#procedure) + assert( ctx != nil ) + assert( len(text_utf8) > 0 ) + + stack := & ctx.stack + assert(len(stack.font) > 0) + assert(len(stack.view) > 0) + assert(len(stack.colour) > 0) + assert(len(stack.position) > 0) + assert(len(stack.scale) > 0) + assert(len(stack.font_size) > 0) + assert(len(stack.zoom) > 0) + + font := peek(stack.font) + assert( font >= 0 &&int(font) < len(ctx.entries) ) + + view := peek(stack.view); + + ctx.cursor_pos = {} + entry := ctx.entries[ font ] + + adjusted_colour := peek(stack.colour) + adjusted_colour.a = 1.0 + ctx.alpha_sharpen + + // TODO(Ed): Implement zoom for draw_text + zoom := peek(stack.zoom) + + absolute_position := peek(stack.position) + position + absolute_scale := peek(stack.scale) * scale + + adjusted_position := get_snapped_position( absolute_position, view ) + + px_size := peek(stack.font_size) + + // Does nothing when px_scalar is 1.0 + target_px_size := px_size * ctx.px_scalar + target_scale := absolute_scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_shape_text_uncached_advanced + ) + ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, + ctx.px_scalar, + adjusted_colour, + entry, + target_px_size, + target_font_scale, + adjusted_position, + target_scale, + ) +} + +get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { + assert( ctx != nil ) + if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 ) + return & ctx.draw_list +} + +get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) { + assert( ctx != nil ) + if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset ) + vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ] + indices = ctx.draw_list.indices [ ctx.draw_layer.indices_offset : ] + calls = ctx.draw_list.calls [ ctx.draw_layer.calls_offset : ] + return +} + +flush_draw_list :: #force_inline proc( ctx : ^Context ) { + assert( ctx != nil ) + clear_draw_list( & ctx.draw_list ) + ctx.draw_layer.vertices_offset = 0 + ctx.draw_layer.indices_offset = 0 + ctx.draw_layer.calls_offset = 0 +} + +flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) { + assert( ctx != nil ) + ctx.draw_layer.vertices_offset = len(ctx.draw_list.vertices) + ctx.draw_layer.indices_offset = len(ctx.draw_list.indices) + ctx.draw_layer.calls_offset = len(ctx.draw_list.calls) +} + +//#endregion("draw_list generation") + +//#region("metrics") + +// The metrics follow the convention for providing their values unscaled from ctx.px_scalar +// Where its assumed when utilizing the draw_list generators or shaping procedures that the shape will be affected by it so it must be handled. +// If px_scalar is 1.0 no effect is done and its just redundant ops. + +measure_shape_size :: #force_inline proc( ctx : ^Context, shape : Shaped_Text ) -> (measured : Vec2) { + measured = shape.size * (1 / ctx.px_scalar) + return +} + +// Don't use this if you already have the shape instead use measure_shape_size +measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> (measured : Vec2) +{ + // profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + entry := ctx.entries[font] + + downscale := 1 / ctx.px_scalar + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shaped := shaper_shape_text_cached( text_utf8, + & ctx.shaper_ctx, + & ctx.shape_cache, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_shape_text_uncached_advanced + ) + return shaped.size * downscale +} + +get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 ) +{ + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + entry := ctx.entries[ font ] + + font_scale := parser_scale( entry.parser_info, px_size ) + + ascent = font_scale * entry.ascent + descent = font_scale * entry.descent + line_gap = font_scale * entry.line_gap + return +} + +//#endregion("metrics") + +//#region("shaping") + +shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text +{ + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] + + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + return shaper_shape_text_cached( text_utf8, + & ctx.shaper_ctx, + & ctx.shape_cache, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_shape_text_latin + ) +} + +shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text +{ + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] + + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + return shaper_shape_text_cached( text_utf8, + & ctx.shaper_ctx, + & ctx.shape_cache, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_shape_text_uncached_advanced + ) +} + +// User handled shaped text. Will not be cached +// @(disabled = true) +shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text ) +{ + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] + + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shaper_shape_text_latin(& ctx.shaper_ctx, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + entry, + target_px_size, + target_font_scale, + text_utf8, + shape + ) + return +} + +// User handled shaped text. Will not be cached +// @(disabled = true) +shape_text_advanced_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text ) +{ + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] + + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shaper_shape_text_uncached_advanced(& ctx.shaper_ctx, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + entry, + target_px_size, + target_font_scale, + text_utf8, + shape + ) + return +} + +//#endregion("shaping") From 77cf07ce16ded505efcbc648e5acc91b021f2a84 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 09:52:28 -0500 Subject: [PATCH 02/62] Updating shaders --- backend/sokol/blit_atlas.shdc.glsl | 51 ++++++++++++++++-------------- backend/sokol/draw_text.shdc.glsl | 49 ++++++++++++++-------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/backend/sokol/blit_atlas.shdc.glsl b/backend/sokol/blit_atlas.shdc.glsl index de64c33..71992e2 100644 --- a/backend/sokol/blit_atlas.shdc.glsl +++ b/backend/sokol/blit_atlas.shdc.glsl @@ -1,48 +1,51 @@ -@module blit_atlas +@module ve_blit_atlas -@header package ve_sokol +@header package sectr @header import sg "thirdparty:sokol/gfx" -@vs blit_atlas_vs -@include ./source_shared.shdc.glsl +@vs ve_blit_atlas_vs +@include ./ve_source_shared.shdc.glsl @end -@fs blit_atlas_fs +@fs ve_blit_atlas_fs in vec2 uv; out vec4 frag_color; -layout(binding = 0) uniform texture2D blit_atlas_src_texture; -layout(binding = 0) uniform sampler blit_atlas_src_sampler; +layout(binding = 0) uniform texture2D ve_blit_atlas_src_texture; +layout(binding = 0) uniform sampler ve_blit_atlas_src_sampler; -layout(binding = 0) uniform blit_atlas_fs_params { - int region; +layout(binding = 0) uniform ve_blit_atlas_fs_params { + 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( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 0.0f, 0.0f ) * texture_size ).x * down_sample_scale - + texture(sampler2D( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 0.0f, 1.0f ) * texture_size ).x * down_sample_scale - + texture(sampler2D( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 1.0f, 0.0f ) * texture_size ).x * down_sample_scale - + texture(sampler2D( blit_atlas_src_texture, 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 / vec2( 2048.0f, 512.0f ); + 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 @@ -52,4 +55,4 @@ void main() } @end -@program blit_atlas blit_atlas_vs blit_atlas_fs +@program ve_blit_atlas ve_blit_atlas_vs ve_blit_atlas_fs diff --git a/backend/sokol/draw_text.shdc.glsl b/backend/sokol/draw_text.shdc.glsl index fa73f4f..1a6dfd6 100644 --- a/backend/sokol/draw_text.shdc.glsl +++ b/backend/sokol/draw_text.shdc.glsl @@ -1,51 +1,48 @@ -@module draw_text +@module ve_draw_text -@header package ve_sokol +@header package sectr @header import sg "thirdparty:sokol/gfx" -@vs draw_text_vs +@vs ve_draw_text_vs in vec2 v_position; in vec2 v_texture; out vec2 uv; void main() { -#if SOKOL_GLSL - uv = vec2( v_texture.x, v_texture.y ); -#else - uv = vec2( v_texture.x, 1.0 - v_texture.y ); -#endif + uv = vec2( v_texture.x, 1 - v_texture.y ); gl_Position = vec4( v_position * 2.0f - 1.0f, 0.0f, 1.0f ); } @end -@fs draw_text_fs +@fs ve_draw_text_fs in vec2 uv; out vec4 frag_color; -layout(binding = 0) uniform texture2D draw_text_src_texture; -layout(binding = 0) uniform sampler draw_text_src_sampler; +layout(binding = 0) uniform texture2D ve_draw_text_src_texture; +layout(binding = 0) uniform sampler ve_draw_text_src_sampler; -layout(binding = 0) uniform draw_text_fs_params { - int down_sample; - vec4 colour; +layout(binding = 0) uniform ve_draw_text_fs_params { + vec2 glyph_buffer_size; + float over_sample; + vec4 colour; }; void main() { - float alpha = texture(sampler2D( draw_text_src_texture, 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 - alpha = - (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( -0.5f, -0.5f) * texture_size ).x * 0.25f) - + (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( -0.5f, 0.5f) * texture_size ).x * 0.25f) - + (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( 0.5f, -0.5f) * texture_size ).x * 0.25f) - + (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( 0.5f, 0.5f) * texture_size ).x * 0.25f); - } + float alpha = texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler ), uv ).x; + + // const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); + 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 -@program draw_text draw_text_vs draw_text_fs +@program ve_draw_text ve_draw_text_vs ve_draw_text_fs From ddfd529993fe771920fed53b9eea28abf2f16185 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 09:52:44 -0500 Subject: [PATCH 03/62] Progress on cleanup --- Readme.md | 4 +- vefontcache/atlas.odin | 1 - vefontcache/freetype_wip.odin | 2 +- vefontcache/parser.odin | 239 ++++------------------------------ vefontcache/pkg_mapping.odin | 18 --- vefontcache/profiling.odin | 17 +++ vefontcache/shaper.odin | 11 +- vefontcache/vefontcache.odin | 35 +++-- 8 files changed, 68 insertions(+), 259 deletions(-) create mode 100644 vefontcache/profiling.odin diff --git a/Readme.md b/Readme.md index 9951a30..c73acf9 100644 --- a/Readme.md +++ b/Readme.md @@ -35,8 +35,8 @@ Upcoming: * Support for which triangulation method used on a by font basis? * Multi-threading supported job queue. * Lift heavy-lifting portion of the library's context into a thread context. - * Synchronize threads by merging their generated layered drawlist into a file draw-list for processing on the user's render thread. - * User defines how context's are distributed for drawing (a basic quandrant basic selector procedure will be provided.) + * Synchronize threads by merging their generated layered draw list into a finished draw-list for processing on the user's render thread. + * User defines how thread context's are distributed for drawing (a basic quandrant based selector procedure will be provided.) See: [docs/Readme.md](docs/Readme.md) for the library's interface. diff --git a/vefontcache/atlas.odin b/vefontcache/atlas.odin index ddb95ac..6a0213e 100644 --- a/vefontcache/atlas.odin +++ b/vefontcache/atlas.odin @@ -12,7 +12,6 @@ Atlas_Region_Kind :: enum u8 { Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call } -// Note(Ed): Using 16 bit hash had collision failures and no observable performance improvement (tried several 16-bit hashers) Atlas_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. diff --git a/vefontcache/freetype_wip.odin b/vefontcache/freetype_wip.odin index 8458182..d6306a4 100644 --- a/vefontcache/freetype_wip.odin +++ b/vefontcache/freetype_wip.odin @@ -3,7 +3,7 @@ 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... +// TODO(Ed): glyph triangulation 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, diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 6be355a..cef2502 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -12,7 +12,7 @@ Freetype isn't really supported and its not a high priority (pretty sure its too STB_Truetype: * Has macros for its allocation unfortuantely. TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator. -Already wanted to do so anyway to evaluate the glyph point shape implementation on its side. +Already wanted to do so anyway to evaluate the shape generation implementation. */ import "base:runtime" @@ -24,7 +24,7 @@ import stbtt "vendor:stb/truetype" Parser_Kind :: enum u32 { STB_TrueType, - Freetype, + Freetype, // Currently not implemented. } Parser_Font_Info :: struct { @@ -63,40 +63,16 @@ Parser_Context :: struct { parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind ) { - // switch kind - // { - // case .Freetype: - // result := freetype.init_free_type( & ctx.ft_library ) - // assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" ) - - // case .STB_TrueType: - // Do nothing intentional - // } - ctx.kind = kind } 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, error : b32) { - // switch ctx.kind - // { - // case .Freetype: - // when ODIN_OS == .Windows { - // error_status := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info ) - // if error != .Ok do error = true - // } - // 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_status != .Ok do error = true - // } - - // case .STB_TrueType: error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 ) - // } font.label = label font.data = data @@ -106,122 +82,36 @@ 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) ) - // } + stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) ) } 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 } @@ -230,122 +120,47 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, // 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( ^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 } 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 := size > 0.0 ? parser_scale_for_pixel_height( font, size ) : parser_scale_for_mapping_em_to_pixels( 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 ) } diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index 9e39d0f..87bb178 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -136,22 +136,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") - diff --git a/vefontcache/profiling.odin b/vefontcache/profiling.odin new file mode 100644 index 0000000..5d832f5 --- /dev/null +++ b/vefontcache/profiling.odin @@ -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" () { +} diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index de2ab71..85eca80 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -13,15 +13,14 @@ Shape_Key :: u32 Traditionally a shape only refers to resolving which glyph and its position should be used for rendering. - For this library's case it also involes keeping any content - that does not have to be resolved once again in the later stage of processing: + 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. * Resolve atlas lru codes * Resolve glyph bounds and scale * Resolve 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 to streamline - a hot path to only focusing on draw list generation that must be computed every frame. + They have the best ability to avoid costly lookups. */ Shaped_Text :: struct #packed { glyph : [dynamic]Glyph, @@ -190,8 +189,8 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry 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) if ! is_empty { diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 8770c09..7fb2753 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -94,7 +94,8 @@ Context :: struct { stack : Scope_Stack, - cursor_pos : Vec2, // TODO(Ed): Review this (most likely not being used properly right now) + colour : RGBAN, // Color used in draw interface TODO(Ed): use the stack + cursor_pos : Vec2, // TODO(Ed): Review this, no longer used much at all... (still useful I guess) // Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges. // Has a boldening side-effect. If overblown will look smeared. alpha_sharpen : f32, @@ -647,11 +648,9 @@ get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { r get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view : Vec2 ) -> (snapped_position : Vec2) { snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } should_snap := view * snap_quotient - snapped_position = position snapped_position.x = ceil(position.x * view.x) * snap_quotient.x snapped_position.y = ceil(position.y * view.y) * snap_quotient.y - snapped_position *= should_snap snapped_position.x = max(snapped_position.x, position.x) snapped_position.y = max(snapped_position.y, position.y) @@ -763,7 +762,7 @@ draw_text_normalized_space :: proc( ctx : ^Context, } // Equivalent to draw_text_shape_normalized_space, however position's units is scaled to view and must be normalized. -@(optimization_mode="favor_size") +// @(optimization_mode="favor_size") draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, @@ -805,16 +804,16 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, } // Equivalent to draw_text_normalized_space, however position's units is scaled to view and must be normalized. -@(optimization_mode = "favor_size") +// @(optimization_mode = "favor_size") draw_text_view_space :: proc(ctx : ^Context, - font : Font_ID, - px_size : f32, - colour : RGBAN, - view : Vec2, - view_position : Vec2, - scale : Vec2, - zoom : f32, // TODO(Ed): Implement Zoom support - text_utf8 : string + font : Font_ID, + px_size : f32, + colour : RGBAN, + view : Vec2, + position : Vec2, + scale : Vec2, + zoom : f32, // TODO(Ed): Implement Zoom support + text_utf8 : string ) { profile(#procedure) @@ -855,7 +854,7 @@ draw_text_view_space :: proc(ctx : ^Context, ) } -@(optimization_mode = "favor_size") +// @(optimization_mode = "favor_size") draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) { profile(#procedure) @@ -908,7 +907,7 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) } -@(optimization_mode = "favor_size") +// @(optimization_mode = "favor_size") draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) { profile(#procedure) @@ -1020,7 +1019,7 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size entry := ctx.entries[font] - downscale := 1 / ctx.px_scalar + target_scale := 1 / ctx.px_scalar target_px_size := px_size * ctx.px_scalar target_font_scale := parser_scale( entry.parser_info, target_px_size ) @@ -1035,7 +1034,7 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size target_font_scale, shaper_shape_text_uncached_advanced ) - return shaped.size * downscale + return shaped.size * target_scale } get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 ) @@ -1102,7 +1101,6 @@ shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si } // User handled shaped text. Will not be cached -// @(disabled = true) shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text ) { profile(#procedure) @@ -1125,7 +1123,6 @@ shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, } // User handled shaped text. Will not be cached -// @(disabled = true) shape_text_advanced_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text ) { profile(#procedure) From 18decf3e468fec0899321f577fe385f046a5fb93 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 12:13:45 -0500 Subject: [PATCH 04/62] Add local version of stb_truetype --- thirdparty/stb/lib/.gitkeep | 0 thirdparty/stb/lib/darwin/stb_truetype.a | Bin 0 -> 138088 bytes thirdparty/stb/lib/stb_truetype.lib | Bin 0 -> 170652 bytes thirdparty/stb/lib/stb_truetype_wasm.o | Bin 0 -> 46482 bytes thirdparty/stb/src/Makefile | 60 + thirdparty/stb/src/build.bat | 8 + thirdparty/stb/src/stb_truetype.c | 2 + thirdparty/stb/src/stb_truetype.h | 5077 +++++++++++++++++ thirdparty/stb/truetype/stb_truetype.odin | 629 ++ .../stb/truetype/stb_truetype_wasm.odin | 4 + 10 files changed, 5780 insertions(+) create mode 100644 thirdparty/stb/lib/.gitkeep create mode 100644 thirdparty/stb/lib/darwin/stb_truetype.a create mode 100644 thirdparty/stb/lib/stb_truetype.lib create mode 100644 thirdparty/stb/lib/stb_truetype_wasm.o create mode 100644 thirdparty/stb/src/Makefile create mode 100644 thirdparty/stb/src/build.bat create mode 100644 thirdparty/stb/src/stb_truetype.c create mode 100644 thirdparty/stb/src/stb_truetype.h create mode 100644 thirdparty/stb/truetype/stb_truetype.odin create mode 100644 thirdparty/stb/truetype/stb_truetype_wasm.odin diff --git a/thirdparty/stb/lib/.gitkeep b/thirdparty/stb/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/thirdparty/stb/lib/darwin/stb_truetype.a b/thirdparty/stb/lib/darwin/stb_truetype.a new file mode 100644 index 0000000000000000000000000000000000000000..b55fbe5d3ea3866a28687d16a21f11f6bb211a38 GIT binary patch literal 138088 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 CtB$Y$ literal 0 HcmV?d00001 diff --git a/thirdparty/stb/lib/stb_truetype_wasm.o b/thirdparty/stb/lib/stb_truetype_wasm.o new file mode 100644 index 0000000000000000000000000000000000000000..e545ef8e7f2ae4527168f2c970ef30248bb5fb44 GIT binary patch literal 46482 zcmc(|37lP3mG8gLx#v!GZ&lr@RFXGf3br^8jcq?|+o2s_cRT*SzqQXDY6#f<>Cfk% zeCiB)SbOcY*IsMwwbwq^Z5-PkIOl?Yxo+=Xx9pmA62b4kh`2 zmgFufG~co(=j)N3w=4&=tAncr)2aT|{8NUzsH7MFSiOdaR!&w-CEZ0;pQBv87O9~} zcTrw9f$nHl6^^A0Y71W#^X*w(9=p_RtCrznNm&DW29&x^JC?(%HSKfgtWZ~vbhmC$ zA~bx$SG*#4#YKTGGbHuZ7yx-0+S)-9tm$w(J<*I6Aa_c>B)LD?=A3 zdH&GQ&dpnPkazK>p^al>!=vLvmu%d!&F4~SY<%OUtwXzZZrL$DJes@4DiyYG+_r7! zCYKY00`SI$$6ctG=#tUlVV7^DZ`yUGPtRYnedp#&+!AHvcI|w*i>>s=O*_Y2-4dHm zg|YF`ZNobN{?)x8|EDetT@)6I1yJHK41!Wv?&zqHUhS-Qc6HY(J-wAVeRJo{nLoc= zp4&ICe?fkrGB`Le=)%fCFpyigsDJU|N+HRGg;ME={-ya`#SK)U%wBg|$6vqcIvV*< zF(_RVZkYN%cFV&k33XeYxz2XSF* z8jO$7dWT?m94dMo1s*-w>(T>u{ntu*j~;54SPiq*gzCAS;nYU!MKCVKxzw<(W-WK7 zzYlhgCN3s5?HWn)G}~x%r3*&m;M~EHv^r9E;f5HRbR^adF3_Z4G&!@Ay2rUv5*bY8 z)T11w(_jy2zyCw9J1Ns@Ifl9;BS|@f&t34(86>r?=Kny+dK4GZYeq8Aovu=$-5}0` zUv6vq%RdxH09f3e6u>hs@|@evSfycuP*IvQLM72Tpf#;4Bmpf?JuoY#g>6ZB1j18$ z>R?;95(fa44aZf8(P%c}vPKx@dZ`*ztYcsq*|^2Ik=Wo;rPjd9r9FmMy;O-y2ESW9 z%Uci9>e-^0bFxZ#d_g%GF2MJV>8( zTa(;Udu~l~2>7rYSsrRwHP`DA)DvF?wVMQ^9>ihlP6|m#-8p_HW?~=^V{_Rv`bZKq z=-&a8k{FEJ5GKWuB!HorVccB3Ox7#2!B3de@HELu$7oWC;o+`rOtzXyrKEbMH3q!A zDB2NMVY;quHtQq}%4)qdDmJZyTAjI6FJ+?+D+RlatJNyZQQ}Ql`JyF6g77i4tiHy$ z1Tx~oC@!DuH1WDdn06hSb{)*PM*F0BgxcDJt*jsAG~BIuZ>>7U$ZZAB+U~))1n3L_ zwWUPLd8!y0HS2vD^+#Bgo*B%7MMeyfBCdL3v?JoX8_j}6JR27A(_xYB8JQgsHOikT z-^pJWf8G4m`0L@Xm%lmu_3<~CzjAH^1PuHj!#}Hnuukt*1!6Wtezo3V^R+7unDpU> zI1nAHP^cWwjeFysxE6QEees;y&Bm3C6C*QLFI%mSG_W4$>%B4U$3eYARMrrxg45Q__yTzO5hMboB`_$0tkru@oUhk_(A|JwEYr~_)ymfhB9*OY!cvUM9B?NEV$Ggs z1|hdz4ImIscGb8Lcbf7F7@+2Oiyym`F;edY1~-XKKR4-K#=_-Jmj;L-c(&0}6wi5P zP=H7Jo*5u-m{z1%ANHte(yfEMh=X!Mpb5SmNQ(Ly%phgYQnk(l$!FEmkWwue8ck6* zU+;rdbC^l>4vmH;SKJX-ka&jaVMZ>mj^xs{BdJS|tzil8bpyNyCxxpSCJ4rhOIQ0G z1orBY+8b0z{6N>1hc81QbZyI|L`jl~xpGpt-0DWH!W2C=K}Vj|8Ku9H#hqCP-|I>D zUC*nx4!}NNjS#&icwJGGsn8T%5^-f z^q`V{yRAp>=}PYriF}Vvcl@i*^SAjXNw7Q&uTBJ^$tMZDUSFc$-xkDyw0%2TWb9o( znGDnijVr{Ded0wY$VYByCL249U zAmFDO4gy~|Y{SuPIsK|LE3#RK4}&bD(JMg|z09J$LQ|_0cQ6xe02jscX$~0uv9Gwet>WT?CO@)@NXWoMcUwgbLe{Rq2w<+H zS0eS!(RAgF)>XSFU87#g$%vY_z z?$VAGnXy%*j7Wh1knDQWjVz@xTFY0i3xgc1rnq57fqMEHESQG0`9w8+t6u%wtfbe} zLzP}0B4FTpJ*`a;e-bsazjVQd4R!C&rfrTq6pCN{AZ2)v4IifE@zHvqN9B35UP;2$ zDqy3_*nN7_6)1m{QCxTQAZAjGa0$*DtRtbq$`3Q{%+Yx|7i##(7R^~Y!ZZc2!JLV> zm1P11rlv^KGD9!)&#Ng@xM1I?j_s~`bm>2vVJ!`ni`|OwW$SW#rQJS>v0?e2otl5fy4*Qr|J056LYUy66qkWe zM4u2U<4In(E0V$sMB?;umWV~}|H8e*efwc{0o?t>CfAy(Cj2z9N?RIt8`Rm~vI%i3Sy8?Ga_v@WGuUd@L`hqZ~McYRn>3X(ZX?3+mDtPlH)K)r3^ zz4u_fTS7>Z=Zd=4$xv`kTY)(T>$O&anrxa7qZyOj|F3;r9i=Ih8?|XidlG(DAEAScS~m@&DiX$;QOks#Y(+cs)kVnn*dP zLCXIUCsc8XbfqwvG&=6GJPsyt#Ce@vaqj5wme5bs{2&WCBMH16qW|)7Pda#Zy_>m~ zMz!z2uSOTb$!Ll{Jw3 zO?gWMhl0#2@OwdLcU6W%wX;KN{1r)brhgi*&u7kJ0HN)lLd3}isg^5 zKx;-5HveM?SbMBCWKN;5&y|TcAgl-C>RE`cfMHoEwCnv)SWB{VIweR@y3)yTQ7}R( z-RU$}p(Ga0F|Wh(D?+y}x2xI-L)5-0Sq1$gxRm}zFg#ir#)L}YNSl(C=fYeM7mjpA zE(mkI;_9Nf8YVs%ig}L??+M{i?stW7XxPr)Bu-kw8^dI-GT!R>K9iPVlPTl5`(nSs z@>#;OEA2d|v!+@PWDWgY*3kRa5b5s=nF!q96|%hK4nm6>2zd%`9H zi~QWc1<{+ejQQM^;`@ZO)`E2n$krlVZM4`swWY)`c%#98YnUuj?YpM+cCMd-eauMO zqvb^t6Z^7eO%36IF!4Y~jk0ubO7nEQ(fqtd^S-IcFj=f7A@ma8-6ZxXBz4{I36rGR z={}mCgGdk>eQ`Zr6fc?JU$SUlyy!;K=fz39IF5bVjTa{Q!I&&OPlFkRJzH0=6J4JgfXs(Bx$BhcNBK&EY^Uf>UpVWYDdyLEfFytK-6a*0w!_!#x? zwUgPDCPR$;h9u}@_9muf%|?BiY589>Sj<>~U8Wi<5QOn<(M_mn=_kv?W^L2lZ5zN0 zwWqLp{~M^yu%jBN$(Xg>Gt-z%nQYyGQ~&8$pE=i$H;9t@Ypgn8RcvC{RFW(sa;(Fo zOmHo=bVtc_$9gg!XCq8S;(oup<7K&(FSp8HLQK^-ii;q~BTh3I>tq32_ zt;_XOa4&jGfUHjK)0&H#Ue$*Ah@Q*axO>ufNi5jRg#O7iUN8qE5Ljlg*y&8m=d!Ow zJyVU^K;vyiZr3`PO4E)kuRfE(hN?`z+R&%MtB6eMIBO7E8$pt&rA`xi@@D|>{XHg{-%-%#l^i{RRwY(Nf(l_#K0K(CSYC*IP;+%B{T%B**Q^8P+5( zY){V6Bzb8w85UWcQNFTJ?TVP$UTsG1TWreipNfCz@wbWVvW;3%A)MBqu+^V1+X2C$ z7an`2cLvfmHE0{U8a)J78hAHhMstNX1m-~;JrgG%4K(nLUFFHZe4K^_g%lBDgS8NT zU+EGJ0{0_`1_3Z_Wo%((Vgfj%?6kSS{HJ7VxL^%!Rspb43gmJlb#RqGFE9gTIyBRa ztk4C}<|~)R_yZ9Cat6=}#zCCl>UZ9xo6HOfSbZ4daQ7@&`O_f0CUwRGV6!ywE=;*& zagsn9_!occs7UV=(-EpZmp!8vw2;bJQ<07gZUw(Ae&@=U0uNZC%m5qDh=RCKX9L^{ z7C9VXNH3y==L|AQkm^h>iz{JZ(q3C<*qoT7$5Ao4oc`qXwese+Xhas z$?QC-IzH7luRUUWz zgA$F^+y-3Bws@ThvJ;T?F7dwce`nkYCuo?t=V)?TM3nC(PEiGyVa;ZRx#x%{GKOb7 zSZzBruvvAE)}g{kQqvZ+DH-$Hy^;`~TGl=8kfP?d402Xe+JuDjIxzz>)<%Z1rJ!(= zneA!>TkUF~XesCW6wn@Qge(4XkSL(wr;s)2k%3cFtIq7<_C5)NkW3~Q-DRFMFkl;e ztI+VHA*%Zl={0A0J_)hQMMo~#G(w&SWI`H-tQa0GOMEd*|p2fJwf0XoyisHI%t`k0!bEtj!?K zZ4)n2)HsrmCYvvKhN3FFaEI$gNjWpSfgv!p8(Ii~zD8Nx*@}LPQXl;`eYk9^T)|&> z3fsBN__xP+&5yKJFS(4vxWi%HarnH(SVtOb63%0JVdIPN^c*JF76YDC;|``)=9*Q2 zLBfU(Db;+IW+D%{kg0!U0+h4vNtW^;Svq}$k)>9oHCmcCIRsG+nLA2=Mkj)T@_n79hFNdE$?} zNdhYS0TO?B`GZq(HppHQ$PeKqL3nOwWNuKG=50e$ZHk97h@og>8;CM7D{CAAK@1}S zfIg9s!2ioj(^SIl@kUeab~BdxmD$S1aIH;R#+BZ(wHP7@m~E>rH0!ga9U9gr6an&- zVpHNXrdomR(At^TpjB>^!VfnXFmEK4(su}_+8w$k&UcFv8FG^R$GJ16+eQ?~(DDV& zf{PJ^V%?_IEQQNx`2y#(%_Bo44Jv={?KY9fCVJ$7FxB&kGh+tjE(1EkX%06W>sTJc z4T0tpkg4T-WLs>`6PI?%a!^5U@UWquy_=48sX028pEH99qvMa6fDAKXQ@GL-ftBp2 zTnQQBWf7azCbvGQv~-%m$DlRyj+{my&~N4~pPr{Rd2x~@-pN{48Ejl8>6Ar_ zOu%-z{m9ls2P#V;vZ31m+}j4&jDq_QR64}0KCI#2H8xhE5fE@JwvsjmOiUX)meL0) z++dx^)MidI8Uql*hEzbZvG#9-#cILiB^xSr|gd;Z3Uo+Jt zK2}VuVV2HtKPH0K9ygwcqZt!+&@ zmXf~L5u3$Y9+jt?g?T|w4SEOt_&}uXuWsTpIL~V^n2Q7LcYrLq)prR_rW!7JfhD(ixZ%XoQb@S7M zl*$U;qizTD-&4Ahz!4A@W|OVbpgg z>|1KC^ON}HA zFi%&%&)4s3)}IdqqUdNivW9smWDjcJ5K8*&5pQ7aKAW`m-a=6kl}`bONt}V|cnZjW zJu779U|-!rKBf(FgOtAao}LVDS3H;Dw1DZkv2g?BW6?y!r?`b3jeQnfU5n?`yWplS zh!?Y_U_V`kR2O81jrw3CMy;0iZ9~~+Y-E;$ShQmhzMQY(1F(_evUB_m8Z)scrV-YV z%=1h=5DzkP3Fy}#5~p6+T~izV8qcGV&0PR7Lv@sqYl z=6Zy%*(c)ysm@XjP|^>aMtX^K-lInCDpx#{&_(|sEE5Nw?OG9**5%mz5(!~n z-_d!DV1J#h7%F<6CSBA4l|BfmYB-JQ8n8%f0_>BfVHG-(<#(lMYzdX%T$sjI>9tsF z!C=+I8~t%ji)t;>HAF<%i;i73ueeoLk0$kH^P3f=6cc}_jtloHM;wv zxLRKbT$Hw0=Mj?^^B9se8ubiVUuXnVbEev9vpizkj;Cr2sSCM9!i8eZMKRH&u%eEa zEmp#kxOAuxYs~B;ejt zC+dkB6*pGXeKdi1HU?!d`s2OC&==pq#q1VJt``S-@2C zVmi|d05P--*Fn`DWI|djbo#Ns&1KWQrWIYy)M)xxH=J9u<5ZMg$Z;8Z+YQYK2P48` zJ>BJS1L0+;9ple#Baki+84J+lphD85t$eVS7&a;H*X-SYAc@w)E51@39Web8M|OJ| z^{UiklN4`7uzzwQj`#cFwi$fEZto`Tjyr`$Cy^h}hN*qgH1ggykp85dyxme9`)p$f z_{M=X9}0Kft6GEd?s@5?UVW5c#wo-DYC1`%u$88cXc#ms#=do8e?0I6BEnFI>POh9 zAlblBh~3lH#6*AkNUI43*=Dp24v(c|siy;gg7^r{WuOO^b`j96T+n6;1qay+ZwBXqsfcdT0j~%~O ze$hyx4IGii8G_RcLq#NHMx{bVX|&izuHwOZPpbezoJQil!TOQHChi)nFQ5YI6Im6L zwqISSP1YF%rF*kxUfiRQpeg--q(A*-tN$7W1iaKBLYMa$PBA!3%YaLoiuTKPt}Gm&Zj5E0@g;)}8T@`zOfI(*mB3jF;`JFN>G% zt1qSCa?Oino^sPT@QeNF4XS~?7fpCx zr4SD>U-J9Ga~VA>FuI)+q`;jQ4nmHjJdKm$WD-mY-%}?=0VDYGwvk&tZRFHF$jZV# z16cr|=3k;=!)Rtf4LK5yQBst5`6P?+vdKh;L1+d3TomI2G$UvX|C@voPF)7NP;P>Fu5STZ3@|7q{8VPUpeA|3C40t(E|(8``U`C z;C}c+43EBnILjNnBCDp2rNlXOW`Sl>I|mg3aTF95^^K3<6mOUz%hbI-4Tpqqyz~I$ zK;X9-Eq#ZW)y>4b5uXWqLDD0NG8K)oEzz=oHIx@EK8<(imYv_x3WTRbnZbzrycuM=-7I#QK-m0X+8ik#=mNj>%T%_}u=#|5!1toDO@ax{jcZQ# zQwCmXn<@(q)~8NPn!x8EZB@dW!W8Tyvdb>#a3)VihcUSv^z09Ln+(%RE_x}=1iJ?G zWct8DHOa)1E)gt-ke8LBo>byG0P1yC;lQ!x6iv#?-d8NCqg`>yJFhTtuoqPg&lyCzMo83+>9axgZodDxMq!@vup0U1xJ;f5IwylFiDZf13F4 zonRAY$%N&!l9?%4GMcEFeAitc@E}UfTIFV~TP)JSL%v5}HhjmwRM7V#S?oeX=W=9l0_9n`547*fv@GL89>4S{HuRHhAqcr_b> zN;U*#KLn+02vp66;LZ7>oI#DhQX`-w-kOA-X@M0eveuck=w#pLV)%-~Ot#31l#;&y zIw%pvQ&6%Jix(s%TaIB}v#3khVU&kZoO?lB8XL6gCFRI@r)5Q`k|jE10cA;5>pqr~ zoSw2LP6x78MfuLGJfouG9ohPjD9Y@qGcIREAmIwF`dBH);fh>|Cxv`?jHRHOGGG;%=9}k46HgpFYBs>ErAVt?z+^xaeqb)GGE=zOrr~W~AvL7Bac72C89L}l z6C_joRzoV_FV&|2N?IaU$d|`e`ZRxmgjHFKBNi?Y6l&WtQBm%2NGaXr>uGPBdbmcY zW|drA8PO6NmoAGJ0)rr(_Pcdtx@{D6KwE>*?EX~hXZ=JrOre0M1)>k`1Ad1l`c^f; zIU5J%I=oXMF4djnBi{qa5;`n?B&{M5ks)Ckk9fwbT$}ge0U2bgMNL8akv=G6BwL?; zWq$g#Hrs@T6&$Kde^Qjq(_Z+i^V9FN3R|H%E?tZQz*SF#_58x(Qwa`%bO5lKW zgxMl-b3kG`=ex?xRQ-ZKXUChYqns5iQ7}KDcsm`aJEYYGD6-vzCBssia)qJJU<#7| zcYgZQ7Lqh=6$-v|#FA!8>F>`^e?7BwWXo?P#dP35n4eyEL(`~k0@s>NxJ-xs!};m! zrxyomqd3qjQ?U8*{B*Ka-ecavMU6;CgeV2n5=$0HUxH3SW(Rir8b+$5&}apuJe}G9 zvosFGHTu^mGXs-f&QI@c!=$ZUHEE4Yg+>~xXunC^hh(H+(6;cYRJJkx45dG4iRqd6 z^}p62Ul#a`3x6{|{drq!8ZjwuH@)0%=cm`crb&L=Im(26>?SL0nYqlO_-z;z8lsrk zzK*44KOhKMkr?Ec4%P!Y#pK4J;!kW#b<%QZ`C^v<741vw4H=sq0LI9+U~g4t97G1d zu=muVpxTafsV?z|-&tTHQ*$Z$klV3|+nRLZ_VlSSD=1QzC*4~U0ygDS_uD`*W!koZ zVnuUmZ2dM+EMcox_E0bq%%fzulY~T;%D~NZwwMb{4RQY&1{p}5jn5$fb77W8y*wk5 zbqo9CBwQNWWI6qW6YhUDEy(Kd{nQ==`&hMmwX{WyHY~V~RuEvmk{F+6lr(Z0jr^v} z`>f{u+S)t9MkS3~1ElVBAh8=G5-Y*#Av-$Kg`zBR&fwalF3uiVy=+@by@e-XDj_?K z#%u+fIb}|6+X9yoXmzRyn7vz(0vG0tuF+sqpf>u|=#6#F_pewxWc{+urWUdC*``xt zv&nb<*o;06IGV=lICN(G;?68`;R6QozMxUs)B$xmq!!@02Kt@8ZAP)|-OY{m%e4<0 z2y!~6=5*Bl&Tv$tAzk~daLrD7)`2;SZ z0c9EeTR!M$j4Ioq`K2_nmuYgEv_H_p#+DcxWm2CQWs%Njmf$_9dqsVxh>Yx8A0hLx z^*=6kMQpe_`*4_iyb8j`qpUClae7AvfW8)YkFIpCUNwNc^9dJ{VkWVL72$5xdI(QY zT-}OJ#{kUl@Ta={qDBU}4@_!nM_)Ud(N0pemM~*wl2p@ZENi9;0=^mC`f+F0Ot@!y zGsFoAcft1E$<5Zx)b8Y-wmZ2Q-O-1ornD12eLDpAJPp}`8U0}$Gqs)I>DpnwK3zL; zG`&AqIz>Ks0Z%d6F_3aFN^IjR!i!1wOHpL{=E?u^q<{*iMcYcFiE3GJaK+NOq&#%iA$Q#4^iRWX$^<>uAp(V1>*)1*wfOlf8s zuI80yqi92EPN`Cy0`=`-h@0q|n5DI1W$f4=Uk9Pa-c#f7&;fa^;+!p?!{dTNlD~g) zJr+P#AfN#2sN(`Q;*`=sXk`2eXfp@u@~OSG;jb zJWaXHZ&yB8S7cVUdqK2=z~>Ih%O+)qDL~cC^Fh^8Th-D*Tl{5Jov-RtmF1HpVa&0h z&aFzQSpw@Qw2~$CVKD%>W>is)$~P5PmiA>Eu{s!--^#~vu9eP~-DKEUAQI6=616@k zDKq&HK76xKPO{7hOnnR{rq@!DS7sTKPi7g&VX<;6`)_X%0VIExOx{$dbzr5F!Z>!P z$y}(dgW?wX#PSBif%=$7zCJyOBaYf=;e&gLa1;F5FgA-KT8zVOnc)CoDvJ2Tw)WWS zG0G6%;ld46C_je@Va}mUN!+m{eNhHIm_#>+!o~&Fw|U_v@9f}bWUH?a3ZVL%lb~BKgOUG{>qxkLJIFfKfVchC6bEG z0`sw(`w((i93m$TqQs)pxex}b&x;n?PCFUTrl%7Q>{o5P&9?xy7|)d9mg7)cG(##EPWR zV?I48>jC9Qar257VklFPLymL;_NSW=aO%Nwz64){*F;x$HmqmXM0syx_!+{vCyZtF zt}a75XLbcqT!aXC<59($9Ks?A4ylSm6xHCIaR__G*+>(q+9eL@Rx~CTaR>nE=jbs^{26DzieU3j`wT20 zI9%O*e|-);Q=D7MDHF2h#B<2%(VSv~!gCrhkl}L<)b-g7o@xh(&8dp}B;P8=eOUG4 zzPa4szIoh;g_;kc2w1bgB);}x9H#w##)%ebLXj1T&RR;ID2T?6sM^jvx%0?Ks^oOX zb9qFuiZs=@-*}d97|bV??^N)SG0(L6yqgci5`QIC;}`9~@ZErFyjUOTfNmUMt=_{N;nyfxZZ{N?ZVs+rxAo?^AiUewy5#v3a0cOZ|M)V+|e^5^NM1 z0)m+=v4y4Kz&l&4_AX0qiM2Piu|>^gr&X?|A;NM-GfSp4W3ZP%Bfl_ZM`!`0gud3^ z!v6w@h1m)=6C%k%eX+~;DOs!!fN9jhoEf|D5L%i{oq1SnBu)lLeDQ_W`(%JuVwGga zQob))3|~&hy{1>iTuQAa3t`F0xF<{P9|8Npktu0Ve{noOTC+CZnRwBl|0E}U?9X@$ z%JanJ)#^a3!*S*Xk{bgg5{i=yWUlclgHQN|v6)!JdNn}Tn)%>70OC#eF+7gAQ49<6 zw_g*XpD#Nsf9nN@M+B!_;_vWPBRaEU)8i>LxT}1v12csk)!yv@DQs; zP5v-um9`fs$@yz+wj%Kual@8i>dI<>;Uu5sAfqM*3Hv7&bB&@B9EAg*jW8Jm44OC; zXijvHB(_~Q-c^d*RzV>=sJ)#itADwTJJLB1iZ_XaI#?Ul7$DI5xd z%5<${oz7)?y9klk^s{v#&$W7yX3Wrm@EWHgEWkEU+18qw+TbXgdHjIsiEf{y%SrBG zDrpbYwlIJPS7}eQsJ>!12~vr=!0g|KCkMQWSuB`Yb9v1?>!N&_0jFlX4fJs-#{sB~ z(uuJmA!4!ih*>@#HNDwN{fOS7T(RK`%%_~>_sR>N`JOy|H0x@gQ9+4!&d+jUnh^i& z3x}*u(qB5dr~^Mi>oH8f+epo&-_xVuEJtxY-Dw62?A>ddXL^8o2x}^qp0=Vtwgh_e zvWrde;ym3K#r>BJ|Dnp<$UK@qb*+#5Iaw&{p<-qV!?f}wVRxhtbNJx|&lrWwmg7mcOkxBtF;zoB%G! z)&MZd-gq5Fg=!H+3H?OI%OKi+xDm7Yc;r(O{xO$6=|3W&?;0|>{I?@?&;ZW$+mILO zQwN**?ki{UIy|oFDK&OmqF|V}zR_3EKzOi`pkNOL*qfkJD0O?d|#;-$UO&zVUF*x?D`g6IO*{uKBr; z8DOU+IDLMDs>j;CvE+xzRDCYw*mdCxJ5qMcPx&2!@K!`7fBj4*Dj>^d7uS zgL6iT=Z+r0xyEHS{JcF&LRQI_`Qw>9t=D<)5Mcg0nLf|TXUdunj6NN^n6%V~Ncn?R zRB`q});6*Aq}5ke2u67a3Jk>+x*3KET5KyCliNAkD;NbTzhwa6S2W6q2Vpj&!09IB^!6e5^9vQT64Ojw(8bADViTtfaNo=;$JKZQ)l;EBdTLj^l_O$e577 zlJuVniU)-vrOq9^;#@Ltte-ly{fY zFX2DRd?<#hwvQAW%Rcb7k=nOSegvdMwSV-_bY}!i8d&OH=2ok!3PL1>8 zd*KF6m=bwCWB2LCJn_yZ!!@tvRi$!g4hmA;%KJl;PbrsG*MC3@TFV;b%Rsr^NwL9P zc;`4)Eppq3?fIO%7yBp#Y)XpR#=cH$P@sS zJoYatPlHzSb1L*b&c~dRhTQKGeKT_ZyqrGLBDcn|)IKDHDEfKa2 zP00@oy$^`uvq8e3oIW1!&pIQ#s6S`QiN6@UXS5C%R$G+gJ*F$mX#| zp0u#mVSVkh;aBqRLgyKfuaCoFaaPcxEvr^QWsL^xiE1o-Dj3Tk@@!YeQzf=zLFkFG zmoF&j#eQc)^3rA`?pW=X6|>_y#TTBkTqZDc!f;`nLuOzsglTW8 zwO%bVIn;e-cS*Dk0^0(06a-BK${-_e9k68$bUx)dU zfcQCy`mp?evJA7t5KYA+0~!cg&fx0jF>DR@1bLWIXmG4j9$Up8)2j}7S~=-hE9P(d z0EnqcRs0nt#V{HC{|g4lKUWtqSUd)mKc~B`7?NVf1q_51Kg@kCjT$=%`dXZJI&(Ud zEJR~^$2blc7xvDuvBODcX7s{xm+zPK_GmpK=$37sUb4_y<#UqK;2}H4FsN7kojZ6)XEH%QOfRvLs0US*l4pf%#Z#j7;}Qpr z0qqX$S50|@RR$En-9!Wq#i1niQtci)K`LN?rSlEa(^{JGZ8z-I#_9x50rQUcsa)14( zcu9QZM18r(TGyEKkMXjJ{qYgmamGnz%aK+E*0*_oRD9HAygWXVDd|r}GPXHNaBBI; z1Idc`sCe0}$#Q;4X<4k7<@+Z|%+65SeebD5toR0LXt@EtFkWHzi+0^C-0z4cj9HrNXCl6$JTqKrKLqOoMqMl5~M_8T7cqwO~v&Z^G67Nsy zlOC%LzQ>bH8o;^9IMHe81OXApi}xpdHoShc$TArpHBmnX60x-;LSQ-B$23;NE1>zL z@5BcBykcTMz^G2mW2s3tHCL)UfBN+}Ri`)V22T1kh>x+RFA%dI-FkFlZ_8iVN<6mp zSmxshoK|>J76GhJ&q?Q_JNI!6we1tMl<+>6!C+3k=GGy z>&^wAmKp$njzyOSlPP%8mm}gjQl)Rd?dUH8P+hGdhbU=z6L#ovfWC`%4V`fW??)KG zrR`lzj*6GYN3{SB0PrZ_!~(d~=XroMvY(W?5Q77mb<5#hk!Tq;03IKq#A7FAo^9AL zSwE7*f&KLrR9LjXesm^yY^XdpN%R~YFNHDTUHFgIc+s4gP}37+(!`1wke;voG^ZLZ zI9Fc^>x2wU6Pi!l4Rw+_?QFrBjIlSo~JLT0%(f(nE05)R)G`>_d_uV0GC48H{j2E#BamoCbtM6Pf^iV-d~Lzkx^fY6+z#;3^n#! zHe2;&s=jPy^<@l@#(Np3+eKgvlTc)ct%mR5tqCJFLG!$R^${rj;XD6IDDv;ghyJSe z8pBaVHyrqIvZ{s?8k`e2YNZa%uVkE;#;eY_%H8SZEmelV{ZZo)wdH3=xqSC+PJ^7+xaG1ozR>X2&2(m|>QIMAN9lcD_-Adn?8sO^1y$ z{=5D6GsMcKY#4hO42X@UmwG55V@=z9J8}|o`KHwZy`t%(ebbgf(*h&zv;G=SscDR! zOo3(%H?^EIrj+2w`n=2wWU#k}Vc7}t&}P126WfR+%?<0bWzcYDvXXYm#?p%XdjasFw;A9VHI7tVp}(^nRX z(LC?m`jNqomOG-NPX@X;;4k~CWvmVPMo?|J_L!Kx(m|nY5B^PZUoe*n^jZ!@3|F}q z)9cJze20qbOR2b^rEiOPTl`o4c*^0$wVahMGXI2a)L`7&SN}@()j!F>LT&@Vv3B+4 zN84raz60}JkH*90-c(Tm!3c?M!4WL2Wc47BnBnyD7lEP}97>-6Hn3br)Xd%4V`0gDiVO5At(K6lU zQfe&uT^zvFAO|G4kwa*BzBog4j=s!7-{ooLf&-r4*Ru}<@cYL2H^U8b#xjra_;%`p zANIph-$&Rs%%JNQr?0jRKck}JOu(O$Od~PgU?xu|(??#Wc08*-a*S0r@jo?1VwIS` zahHDzscKJg#V4>yDx?o-B@6%ATP=H|uSWSS#!DYX{<84bmoMs)P|&&+ z^}|T!w$>Tba(Y)YSS6_37Z9w%c&Ou9z>1lufqazz)gYBn`-Nd^zc6g>7m}lo36tl8 zRP@jmr1G=@C*U1a{zhjUg(utnkJ^mR`JEz{u2ev3$k~=U-s;-xl*v{TiVOy`%@76$ z=4u4w0K^>P@U}A2w)hfrkra2EmzJD%`QhU=AguNY`IuPk%W6l?;Vg~y@r*RK;Cvbj zAaerG1nQaqYH3df0D;;B8qP>-0@Vi&wGmfGtg(01po;d8*g(it{x0IUj;44B*>H#^ z1xY@0!}27_mzX!I*e>fRNv6p{?tGh^iZ=HZdF4zBvTSG4w?r2Q{=Kj>nHC`I^ksMk zjTxye=~iTRy%SV|mi!F(nM;XDi>%cT+dI55gsSnSC!Yj6p9N2MoQ05v4RGo(+w?(P z@h4I<;G5zC0G2Iz8Tha5j@G_ve9~onLc_>Teb1V2Teqs)FRnF&E?>_UzO6W6Ln5x8 zZMEcXZtslG^oaTr{?`Ip-Ss6ZLX=CkBTrEy!oY0cl1`e!8>vkL+DB@Q-4dWQf zz}FIl_Jx}+f)nsM%9%;87Pfk|C?iUzCqc~j`vR>DfX9!1BOW~0ur(lsp^XRIUn2xj zzu6X4=7=lRZY~25oK^Wk&Y~e>*0iaeeZVcqypwayzKx9XWxjE&-T00De&;sBD?(yRBY1J#{y3 zd!?EB^{~J#gH=j@%cgH7{i^mL(QVrLz?0jA*n!kyUTBcG=RkEN-TTSC!)!pNy?fZY zlx%NeiMMu7qd_(U8_0JEu?&xFLtA*vhFd7Q$veqOQ1u#9PM8{ImGql@=U;m@Y*R9U zUZ7pnG}@Cs+1_XQj9bk$vCtP*nxwG3SZzwFvoZzRnl-nCjDuSw|B?Lmi+%Kxk4m6U z_zq9DYov_==4Xe&@SXa``jCbF+?9A^dcY1^JPML6hZN;S7J6(Dxa2pM$V^~#w}DOPZryU z7L@?+g|@#BHUefpibV)i`lE?gG=T7?s0sl=S0WD&Uz=aHo%|IXBdJpu-6|{$kImWk z4E1TS`2$^Gp=bht8k0j1x@i0A4Rn|V`gk`g7l1w*hu|_r8!_ zwos@C%IQ6tquxI#^kuZYq%oU=%vQ5ul3at^4<9gU!|AtiF{R-s{s`0)5CZk0%49$^ zI`j$@{___i_j}t=d3`y3P_G^p3ejAqvJ`9rr-DTt$)jHf@+=rf` zUG|(En}@G(E}dKHCTD1T^zz~HD|ZdAx{Nd$U(M~vO8&g_k6XjjF_i&d{e>GhZ9RW@ z{JcHGqhlMl@7lIy$E9RH>MFgL4ee|s4#&?rC0|FWmO_yA< zYHZ6@s_?E>1yYCT^Y9fzms*8Kf=b_(_}R1(A9-ZFC4N@CY3JoT1nNscWzkOH0z&!6 zE5Gqhht^W$=d|V#AG-MRONPdVhqsb?U0CU_FW(tIJHB}3v#t6oUJ>sE^6C}jzBz0n zFI>RF<3rYaLnA)JV&FW24~CVV9Zdvwh?Z*lu~u2%C87N7urfcx#}j7PW#?bEao6yR zUPR`v4wtDB-IuGZ*?IZ+uFJ=VHjQ7gYRBcp^Kr};|9700W+D~?g2UsHv5NUTYIUBTXz%iHSmHO`#&#Kx*74Imv7lTewopi z#A}O{UOyI7)AtuEa~i}M+BG`NSi@>mct^3)y=%)A!`sGQy5$1#tOnz6Tc!6*DgDV} zWl5v-(PQJITQ(0f4Tmnia(sBKS^N9N21lQ^aqI9|J9mtqwq<<##$A-8O@@pXUR{EC z$6WeyMq`(F>g&eBACfXD3{ub_#$IkD}ENZqXn-mvr`T$%2kz(pN> zoOD^BBX=rHGY$cqV8-#^b&m(g*aNR*z^3SV|4_)YL&P5IRf_OQ7 z=jP#EJ0XRM7#CHuahW1{LMnxw89LLXbq$5sl?i@WeCHVALek3BXstU_P(!1a?-<&2 z*~U=~(vC}mus)*?8Q6xbTQkqi55jT-^R@Hcnm?k}i{`rdu9kDP$VIgtPnr(fkEdAu2cByl3N% zO~WqipE_K-E<1hO#xWXOkjd9}tlKL_1DRFOUZ92RU<(;drI$22v9Q01Onb(8=R@2E z-PYt>-i3>@fsw#Okf^IJT0FJ-X{hhb5W?uWBKD^0*}5>0^*$EEtg(4?xJ{! zZ?}>8{EfSIQSr>}FWPD4=yF}gkj$jf;o*$UlT71lZlU?k*tw~V*-q>S&FF|}{6jGp zEp2tbHFK1)EJFjT*`RwF1N!G*ezA!t7af^t^%+H6bkq?|;x)m}0%3WJQDzo%(Tc2} zf6`FgE<8G8x*;Uu*zmTErmcDp!sRok z?(CMr0=Kx0s3#3LeDBQ(y>8I;1>wS(uv$jKLf6|?Vf`Xk3&NvjR+!aJSnL+H)qTbi z*X_cU8DkI4qK{jJ*Q}1+(ze&*>#o;@s~SepuAO6BWb;f@fNd4lB(BuBv^;S#W8U4A(U@$YpH`K3v~OZCCrzGwLm< zhrz)`C%5Fuv@$MwW+qQ&>4b}(m9+_7WiM?0Uzelsqf@e}rKQqo!dR9tj=#;{Y1=`Aa8M#kw=3%KaamNZl^{pu0* z;1|KsSzVr1&)KnMT=}gYtzEm`@g3R5BwTyqGp4+rbn=wfb#tUJ3U;~ID!zq;o7U3hjwkxyM{hUc`Ww-&?Ua~kQJw`>~E zR;J@)Ee%HMkHQ9xrcOK|m1cf!#?nJu#x8+kE_z;O4Nf7wi=N-2ys6#I zqHoI-ctOjCRK>;|I&Zo{#XV4i9y66dL#E+CD7H_66HBh>Y6Vy0T5{0~XH&)ijs92G z4^#L`_>-3E6{;`5195(4i8dzKNo6Vh^E2X2T`J6`^rn_*E+$TDS9&L`U*|ep^rDvK z_kzo}Upzc|-X+3YEgN+7(T3CR35OvPEuZSMfdd^**3b9;TK zbY^3qp3&tNx$weFAh%pL;ZUO$%_f*U2p7yg)5zuK;VTZ?6HPLkls)RnvYCj?VuT#&23tt2Iq9e=hQqq$?I^EVzruC26@JFi z?S)TReaw{CUN|~IPrMbn}6aa zc_)urHM(>2#_^4-W`Lc#oOMB0tK=${hc2j1O;~mO%%n9gIJQ-4+GJaJ~yNiH}%38pSBXI5VKl$EDUl$n*+&rEuT3zEb3tTlszo>n0Z z#j4|1KP@6nK6gRAReRcqG##NcTVNcXqFy%tT(IKsu$v>3`AGlxvT)Ii9>WAP(iq;= zhi`xO#m_8YV2K%^8R^6GT)V?`W>v=dxWi*L>#ArL1YnKnNUdSiXQVL+XQa(Szcq~K zjB>NkbPY6Jd3fw+5stIKorR!lAn1(tq124DS^9m#Ed4%VmVTcwOTSN`->VMapV=ho zOr%bjg-|CzsAY!-+-9`T0uKT`Mcp>@eU`cq^6*T57~^LaFtgBp?JV6~OE;&F(b`$Y zckL|Wi#0#9?kwYb;wUKd3V1$b&7 z4MG=vKFB%uL=Z*)SRm>D7D)O@fuvs+BKPs4bDu93ir*|2i~mwAx`|TBy}n#_2Rl35 zKh-+i|EpEpEj<FNe|8e z^2g>x?#_7y_lN$nn^;hDZ{hEe1wHNy{QWC`|H=Dx1GVCN2a3^W2Jka{VSuV%ADByc z`9M_sz~W-@Ba4gfz>@9J&Giu%P1ec2qt5Qv-Sx=*y8gPr-JNjxtS;X@;`M=h)v_A{ z_fuVNJ&M=+mvgy!1+SAwb9oP!=x0X*>EAiM674+(NH-i4x%aKw$C;GlDDgR6?qAL2 z6S~}WJeLP`Ik<+)54l7)uBHC1YpMU{wbZ|BZR8$VdsEITU#AM|Y4vN*czqDv za5820olMySCsXz7As&RuumisCI77K?AWu;@O0VQ=x(8;kDX#$((E**N$d_iu9lkB!F^CoV1)-*$1) z{r=*zd*7zAdwh7cyYG_Y-J_S5-CvEYaUb7Sb|2baai7^y&vx!TFOQ-}l=KNDJ^u1jo%=FL|8XTrzrT{C*Ic#1xtp$vqPwpm>E5eII&@X) z+}~XlxhJkV#r^0nPIh}=ak87_?;-xa$lv$*``FbdyKnIKZ~Wcx%9Guj`FqFS0G`+7 z)4F_7mv89uJubyx>3mNaYiHwJQ z#-o<;`I~~b3Y0Q_;xm3}8Na(JxPy%AZzki8n+??6Hw)CeZVui?#vx^V&S!klGQN3p za3>l6q>Qh;!D@W#4XW|IHw1T)@uN48@#rnVU(%1y+@g#pZVCR1jIZB9#*cl*FD&D? zw*+^Saow#!8+jr+6wpq1W$DQsdPJDE#dMl>7bx%KiKR83%3)&UEj(jfOsS8)vw`qo?oR7Cgt@ zdOI2S-5#9lK6ZQX+~S>Y1k`<8+&{fBs6=nSBlt5H{q-H{!TawB{x{X{2%_Sv-bSfg zba@LG_r)(W^c#K(GI}0PX*{0KMts0Q$(sgKg0VKB2q^KB2rv zKcT$Oe1g1ReuBKWeKKGx^`1`#N%1?Mh7>n{Hn^RsF!@;(zT>mOEl~gN&rrTpJeMzg!#cb`#f!L+-uT zhp!IYC$A58M^9cK!sOq(K1`xtUmw!o-dAz{=k`~HHw5ml?fQ_e(POVt=9iTDBTL@< zY89EZ>%mt8^h2)+UmLjp^EzFBZ`XgiDa3&J-p!#bpf`j_vfJ%?w_RT|q4a(Gxkhig zEj$4BcitxK58f7Hq`l|1Fp9olwEljE|kkR5tJ2Fc_fHuY&i>F?Ywy7wy|SMV_PoWv2yr|@&6CXFAFXJ literal 0 HcmV?d00001 diff --git a/thirdparty/stb/src/Makefile b/thirdparty/stb/src/Makefile new file mode 100644 index 0000000..194ea5e --- /dev/null +++ b/thirdparty/stb/src/Makefile @@ -0,0 +1,60 @@ +OS=$(shell uname) + +ifeq ($(OS), Darwin) +all: darwin +else +all: unix +endif + +wasm: + mkdir -p ../lib + $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image.c -o ../lib/stb_image_wasm.o -DSTBI_NO_STDIO + $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_write.c -o ../lib/stb_image_write_wasm.o -DSTBI_WRITE_NO_STDIO + $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_resize.c -o ../lib/stb_image_resize_wasm.o + $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_truetype.c -o ../lib/stb_truetype_wasm.o + # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_vorbis.c -o ../lib/stb_vorbis_wasm.o -DSTB_VORBIS_NO_STDIO + $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_rect_pack.c -o ../lib/stb_rect_pack_wasm.o + $(CC) -c -Os --target=wasm32 stb_sprintf.c -o ../lib/stb_sprintf_wasm.o + +unix: + mkdir -p ../lib + $(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c stb_sprintf.c + $(AR) rcs ../lib/stb_image.a stb_image.o + $(AR) rcs ../lib/stb_image_write.a stb_image_write.o + $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o + $(AR) rcs ../lib/stb_truetype.a stb_truetype.o + $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o + $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o + $(AR) rcs ../lib/stb_sprintf.a stb_sprintf.o + #$(CC) -fPIC -shared -Wl,-soname=stb_image.so -o ../lib/stb_image.so stb_image.o + #$(CC) -fPIC -shared -Wl,-soname=stb_image_write.so -o ../lib/stb_image_write.so stb_image_write.o + #$(CC) -fPIC -shared -Wl,-soname=stb_image_resize.so -o ../lib/stb_image_resize.so stb_image_resize.o + #$(CC) -fPIC -shared -Wl,-soname=stb_truetype.so -o ../lib/stb_truetype.so stb_image_truetype.o + #$(CC) -fPIC -shared -Wl,-soname=stb_rect_pack.so -o ../lib/stb_rect_pack.so stb_rect_packl.o + #$(CC) -fPIC -shared -Wl,-soname=stb_vorbis.so -o ../lib/stb_vorbis.so stb_vorbisl.o + rm *.o + +darwin: + mkdir -p ../lib + $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image.c -o stb_image-x86_64.o -mmacosx-version-min=10.12 + $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image.c -o stb_image-arm64.o -mmacosx-version-min=10.12 + lipo -create stb_image-x86_64.o stb_image-arm64.o -output ../lib/darwin/stb_image.a + $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-x86_64.o -mmacosx-version-min=10.12 + $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-arm64.o -mmacosx-version-min=10.12 + lipo -create stb_image_write-x86_64.o stb_image_write-arm64.o -output ../lib/darwin/stb_image_write.a + $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-x86_64.o -mmacosx-version-min=10.12 + $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-arm64.o -mmacosx-version-min=10.12 + lipo -create stb_image_resize-x86_64.o stb_image_resize-arm64.o -output ../lib/darwin/stb_image_resize.a + $(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 + $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-x86_64.o -mmacosx-version-min=10.12 + $(CC) -arch arm64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-arm64.o -mmacosx-version-min=10.12 + lipo -create stb_rect_pack-x86_64.o stb_rect_pack-arm64.o -output ../lib/darwin/stb_rect_pack.a + $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-x86_64.o -mmacosx-version-min=10.12 + $(CC) -arch arm64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-arm64.o -mmacosx-version-min=10.12 + lipo -create stb_vorbis-x86_64.o stb_vorbis-arm64.o -output ../lib/darwin/stb_vorbis.a + $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-x86_64.o -mmacosx-version-min=10.12 + $(CC) -arch arm64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-arm64.o -mmacosx-version-min=10.12 + lipo -create stb_sprintf-x86_64.o stb_sprintf-arm64.o -output ../lib/darwin/stb_sprintf.a + rm *.o diff --git a/thirdparty/stb/src/build.bat b/thirdparty/stb/src/build.bat new file mode 100644 index 0000000..e83d765 --- /dev/null +++ b/thirdparty/stb/src/build.bat @@ -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 diff --git a/thirdparty/stb/src/stb_truetype.c b/thirdparty/stb/src/stb_truetype.c new file mode 100644 index 0000000..05c23f5 --- /dev/null +++ b/thirdparty/stb/src/stb_truetype.c @@ -0,0 +1,2 @@ +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" \ No newline at end of file diff --git a/thirdparty/stb/src/stb_truetype.h b/thirdparty/stb/src/stb_truetype.h new file mode 100644 index 0000000..5e2a2e4 --- /dev/null +++ b/thirdparty/stb/src/stb_truetype.h @@ -0,0 +1,5077 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// 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. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// 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. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// 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. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// 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 STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// 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. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// 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. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// 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). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// 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. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// 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. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// 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. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// 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. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// 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. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// 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. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// 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 + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// 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. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// 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 + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// 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) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// 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. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// 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 + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// 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. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// 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.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// 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. + + + +////////////////////////////////////////////////////////////////////////////// +// +// 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. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// 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 +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// 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 + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// 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 + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/thirdparty/stb/truetype/stb_truetype.odin b/thirdparty/stb/truetype/stb_truetype.odin new file mode 100644 index 0000000..bd521c6 --- /dev/null +++ b/thirdparty/stb/truetype/stb_truetype.odin @@ -0,0 +1,629 @@ +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)) + +////////////////////////////////////////////////////////////////////////////// +// +// 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, +} diff --git a/thirdparty/stb/truetype/stb_truetype_wasm.odin b/thirdparty/stb/truetype/stb_truetype_wasm.odin new file mode 100644 index 0000000..f362390 --- /dev/null +++ b/thirdparty/stb/truetype/stb_truetype_wasm.odin @@ -0,0 +1,4 @@ +#+build wasm32, wasm64p32 +package stb_truetype + +@(require) import _ "vendor:libc" From 2eb94e077f01c69a9e262c6b8bbeb226fee8c0c0 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 12:44:53 -0500 Subject: [PATCH 05/62] More cleanup, doc updates --- .gitignore | 2 +- LICENSE.md | 2 +- vefontcache/LRU.odin | 16 ++++++++-------- vefontcache/draw.odin | 25 ++++++++----------------- vefontcache/misc.odin | 2 +- vefontcache/parser.odin | 6 +++--- vefontcache/shaper.odin | 18 ++++++++++-------- vefontcache/vefontcache.odin | 21 +++++++++++++++++---- 8 files changed, 49 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index dafaa3c..fb8d7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ build -thirdparty +# thirdparty .vscode # backend/sokol/render_glyph.odin # backend/sokol/blit_atlas.odin diff --git a/LICENSE.md b/LICENSE.md index a602421..5652bec 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -VEFontCache Odin Port +VEFontCache Odin Copyright 2024 Edward R. Gonzalez This project is based on Vertex Engine GPU Font Cache diff --git a/vefontcache/LRU.odin b/vefontcache/LRU.odin index 20e18a3..5381890 100644 --- a/vefontcache/LRU.odin +++ b/vefontcache/LRU.odin @@ -43,11 +43,11 @@ pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : s { error : Allocator_Error pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) ) - assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array") + 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 @@ -106,16 +106,16 @@ pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_b assert( length == int(pool.capacity - pool.size) ) id := pool.free_list[ len(pool.free_list) - 1 ] - if pool.dbg_name != "" { - logf("pool_list: back %v", id) - } + // 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 pool.dbg_name != "" { + // logf("pool_list: pushed %v into id %v", value, id) + // } if pool.front != -1 do pool.items[ pool.front ].prev = id if pool.back == -1 do pool.back = id diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 69a1888..9d91ba5 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -109,7 +109,7 @@ Glyph_Draw_Buffer :: struct{ cached : [dynamic]i32, } -// Contructs a quad mesh for bliting a texture from one render target (src uv0 & 1) to the destination rendertarget (p0, p1) +// 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}, @@ -279,24 +279,15 @@ generate_shapes_draw_list :: #force_inline proc ( ctx : ^Context, } /* Generator pipeline for shapes - - If you'd like to make a custom draw procedure, this can either be used directly or - modified to create an augmented derivative for a specific code path. - This procedure has no awareness of layers. That should be handled by a higher-order codepath. - For this level of codepaths what matters is maximizing memory locality for: - * Dealing with shaping (essentially minimizing having to ever deal with it in a hot path if possible) - * Dealing with atlas regioning (the expensive region resolution & parser calls are done on the shape pass) 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. - * You have to to be drawing a px font size > ~140 px for it to trigger. - * The atlas can be scaled with the size_multiplier parameter of startup so that it becomes more irrelevant if processing a larger atlas is a non-issue. + * 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 so keep that in mind. + * 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, @@ -463,16 +454,16 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, /* 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...) + The transform and draw quads are computed first (getting the math done in one spot as possible) + Some of the math from to_cache pass for glyph generation was not moved over (it could be but I'm not sure its worth it) Order: Oversized first, then to_cache, then cached. Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target. - The compute section will have operations reguarding how many glyphs they may alloate before a flush must occur. + 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 to the buffer to the atlas. + * 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, diff --git a/vefontcache/misc.odin b/vefontcache/misc.odin index 7826400..af16f86 100644 --- a/vefontcache/misc.odin +++ b/vefontcache/misc.odin @@ -61,7 +61,7 @@ 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 diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index cef2502..70a3959 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -2,10 +2,10 @@ package vefontcache /* Notes: -This is a minimal wrapper I originally did incase something than stb_truetype is introduced in the future. +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 isn't really supported and its not a high priority (pretty sure its too slow). +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.~~ @@ -19,7 +19,7 @@ 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 { diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index 85eca80..5412349 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -1,6 +1,8 @@ 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" @@ -165,11 +167,11 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry 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 continue } if abs( font_px_size ) <= adv_snap_small_font_threshold @@ -394,8 +396,8 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, // Shapes are tracked by the library's context using the shape cache // and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped. -// Thus this procedures cost will be proporitonal to how muh text it has to sift through. -// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-120 charactes long +// 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, diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 7fb2753..59e2964 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -105,6 +105,8 @@ Context :: struct { px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. default_curve_quality : i32, + + } Init_Atlas_Params :: struct { @@ -657,6 +659,15 @@ get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view return snapped_position } +resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size, interval, min, max : f32 ) -> (resolved_size : f32) +{ + interval_quotient := 1.0 / f32(interval) + size := px_size == 0.0 ? default_size : px_size + even_size := math.round(size * interval_quotient) * interval + resolved_size = clamp( even_size, min, max ) + return +} + set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } set_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); ctx.colour = colour } set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } @@ -761,7 +772,7 @@ draw_text_normalized_space :: proc( ctx : ^Context, ) } -// Equivalent to draw_text_shape_normalized_space, however position's units is scaled to view and must be normalized. +// Equivalent to draw_text_shape_normalized_space, however position's unit convention is expected to be relative to the view // @(optimization_mode="favor_size") draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, font : Font_ID, @@ -803,7 +814,7 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, ) } -// Equivalent to draw_text_normalized_space, however position's units is scaled to view and must be normalized. +// Equivalent to draw_text_shape_normalized_space, however position's unit convention is expected to be relative to the view // @(optimization_mode = "favor_size") draw_text_view_space :: proc(ctx : ^Context, font : Font_ID, @@ -854,6 +865,7 @@ draw_text_view_space :: proc(ctx : ^Context, ) } +// Uses the ctx.stack, position and scale are relative to the position and scale on the stack. // @(optimization_mode = "favor_size") draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) { @@ -907,6 +919,7 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) } +// Uses the ctx.stack, position and scale are relative to the position and scale on the stack. // @(optimization_mode = "favor_size") draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) { @@ -916,11 +929,11 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) stack := & ctx.stack assert(len(stack.font) > 0) - assert(len(stack.view) > 0) + assert(len(stack.font_size) > 0) assert(len(stack.colour) > 0) + assert(len(stack.view) > 0) assert(len(stack.position) > 0) assert(len(stack.scale) > 0) - assert(len(stack.font_size) > 0) assert(len(stack.zoom) > 0) font := peek(stack.font) From f6ea7807470f34f5b2f2b064d4cb112d91ea7847 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 12:45:15 -0500 Subject: [PATCH 06/62] progress on updating sokol backend & demo --- backend/sokol/backend_sokol.odin | 48 +- backend/sokol/blit_atlas.shdc.glsl | 2 +- backend/sokol/draw_text.odin | 966 ++++++++++++++-------------- backend/sokol/draw_text.shdc.glsl | 2 +- examples/sokol_demo/sokol_demo.odin | 90 ++- 5 files changed, 545 insertions(+), 563 deletions(-) diff --git a/backend/sokol/backend_sokol.odin b/backend/sokol/backend_sokol.odin index ecbdc3c..c89c2c9 100644 --- a/backend/sokol/backend_sokol.odin +++ b/backend/sokol/backend_sokol.odin @@ -12,12 +12,12 @@ Context :: struct { atlas_shader : gfx.Shader, screen_shader : gfx.Shader, - // 2k x 512, R8 + // ve.glyph_buffer.(width, height), R8 glyph_rt_color : gfx.Image, glyph_rt_depth : gfx.Image, glyph_rt_sampler : gfx.Sampler, - // 4k x 2k, R8 + // ve.atlas.(width, height), R8 atlas_rt_color : gfx.Image, atlas_rt_depth : gfx.Image, atlas_rt_sampler : gfx.Sampler, @@ -33,7 +33,6 @@ Context :: struct { setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index_cap : u64 ) { - using ctx Attachment_Desc :: gfx.Attachment_Desc Blend_Factor :: gfx.Blend_Factor Blend_Op :: gfx.Blend_Op @@ -60,18 +59,18 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index backend := gfx.query_backend() app_env := glue.environment() - glyph_shader = gfx.make_shader(render_glyph_shader_desc(backend) ) - atlas_shader = gfx.make_shader(blit_atlas_shader_desc(backend) ) - screen_shader = gfx.make_shader(draw_text_shader_desc(backend) ) + ctx.glyph_shader = gfx.make_shader(render_glyph_shader_desc(backend) ) + ctx.atlas_shader = gfx.make_shader(blit_atlas_shader_desc(backend) ) + ctx.screen_shader = gfx.make_shader(ve_draw_text_shader_desc(backend) ) - draw_list_vbuf = gfx.make_buffer( Buffer_Desciption { + ctx.draw_list_vbuf = gfx.make_buffer( Buffer_Desciption { size = cast(uint)(size_of([4]f32) * vert_cap), usage = Buffer_Usage.STREAM, type = Buffer_Type.VERTEXBUFFER, }) - assert( gfx.query_buffer_state( draw_list_vbuf) < Resource_State.FAILED, "Failed to make draw_list_vbuf" ) + assert( gfx.query_buffer_state( ctx.draw_list_vbuf) < Resource_State.FAILED, "Failed to make draw_list_vbuf" ) - draw_list_ibuf = gfx.make_buffer( Buffer_Desciption { + ctx.draw_list_ibuf = gfx.make_buffer( Buffer_Desciption { size = cast(uint)(size_of(u32) * index_cap), usage = Buffer_Usage.STREAM, type = Buffer_Type.INDEXBUFFER, @@ -305,8 +304,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index 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 = Border_Color.OPAQUE_BLACK, compare = .NEVER, max_anisotropy = 1, @@ -534,8 +533,12 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : gfx.apply_pipeline( atlas_pipeline ) - fs_uniform := Blit_Atlas_Fs_Params { region = cast(i32) draw_call.region } - gfx.apply_uniforms( UB_blit_atlas_fs_params, Range { & fs_uniform, size_of(Blit_Atlas_Fs_Params) }) + 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 = { @@ -546,8 +549,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : }, index_buffer = draw_list_ibuf, index_buffer_offset = 0, - images = { IMG_blit_atlas_src_texture = glyph_rt_color, }, - samplers = { SMP_blit_atlas_src_sampler = glyph_rt_sampler, }, + 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. @@ -570,17 +573,18 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : src_rt := atlas_rt_color src_sampler := atlas_rt_sampler - fs_target_uniform := Draw_Text_Fs_Params { - down_sample = 0, - colour = draw_call.colour, + 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.down_sample = 1 + // fs_target_uniform.over_sample = 1.0 src_rt = glyph_rt_color src_sampler = glyph_rt_sampler } - gfx.apply_uniforms( UB_draw_text_fs_params, Range { & fs_target_uniform, size_of(Draw_Text_Fs_Params) }) + 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 = { @@ -591,8 +595,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : }, index_buffer = draw_list_ibuf, index_buffer_offset = 0, - images = { IMG_draw_text_src_texture = src_rt, }, - samplers = { SMP_draw_text_src_sampler = src_sampler, }, + images = { IMG_ve_draw_text_src_texture = src_rt, }, + samplers = { SMP_ve_draw_text_src_sampler = src_sampler, }, }) } diff --git a/backend/sokol/blit_atlas.shdc.glsl b/backend/sokol/blit_atlas.shdc.glsl index 71992e2..5151f9a 100644 --- a/backend/sokol/blit_atlas.shdc.glsl +++ b/backend/sokol/blit_atlas.shdc.glsl @@ -1,6 +1,6 @@ @module ve_blit_atlas -@header package sectr +@header package ve_sokol @header import sg "thirdparty:sokol/gfx" @vs ve_blit_atlas_vs diff --git a/backend/sokol/draw_text.odin b/backend/sokol/draw_text.odin index 285e2fc..badda9e 100644 --- a/backend/sokol/draw_text.odin +++ b/backend/sokol/draw_text.odin @@ -10,35 +10,36 @@ import sg "thirdparty:sokol/gfx" Overview: ========= - Shader program: 'draw_text': - Get shader desc: draw_text_shader_desc(sg.query_backend()) - Vertex Shader: draw_text_vs - Fragment Shader: draw_text_fs + Shader program: 've_draw_text': + Get shader desc: ve_draw_text_shader_desc(sg.query_backend()) + Vertex Shader: ve_draw_text_vs + Fragment Shader: ve_draw_text_fs Attributes: - ATTR_draw_text_v_position => 0 - ATTR_draw_text_v_texture => 1 + ATTR_ve_draw_text_v_position => 0 + ATTR_ve_draw_text_v_texture => 1 Bindings: - Uniform block 'draw_text_fs_params': - Odin struct: Draw_Text_Fs_Params - Bind slot: UB_draw_text_fs_params => 0 - Image 'draw_text_src_texture': + Uniform block 've_draw_text_fs_params': + Odin struct: Ve_Draw_Text_Fs_Params + Bind slot: UB_ve_draw_text_fs_params => 0 + Image 've_draw_text_src_texture': Image type: ._2D Sample type: .FLOAT Multisampled: false - Bind slot: IMG_draw_text_src_texture => 0 - Sampler 'draw_text_src_sampler': + Bind slot: IMG_ve_draw_text_src_texture => 0 + Sampler 've_draw_text_src_sampler': Type: .FILTERING - Bind slot: SMP_draw_text_src_sampler => 0 + Bind slot: SMP_ve_draw_text_src_sampler => 0 */ -ATTR_draw_text_v_position :: 0 -ATTR_draw_text_v_texture :: 1 -UB_draw_text_fs_params :: 0 -IMG_draw_text_src_texture :: 0 -SMP_draw_text_src_sampler :: 0 -Draw_Text_Fs_Params :: struct #align(16) { +ATTR_ve_draw_text_v_position :: 0 +ATTR_ve_draw_text_v_texture :: 1 +UB_ve_draw_text_fs_params :: 0 +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, }, } @@ -51,13 +52,13 @@ Draw_Text_Fs_Params :: struct #align(16) { void main() { - uv = vec2(v_texture.x, v_texture.y); + uv = vec2(v_texture.x, 1.0 - v_texture.y); gl_Position = vec4((v_position * 2.0) - vec2(1.0), 0.0, 1.0); } */ @(private="file") -draw_text_vs_source_glsl410 := [255]u8 { +ve_draw_text_vs_source_glsl410 := [261]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, @@ -68,98 +69,82 @@ draw_text_vs_source_glsl410 := [255]u8 { 0x76,0x65,0x63,0x32,0x20,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b, 0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a, 0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x76,0x5f, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x76,0x5f,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f, - 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28, - 0x28,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2a,0x20,0x32,0x2e, - 0x30,0x29,0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x29,0x2c,0x20, - 0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x31,0x2e,0x30,0x20,0x2d, + 0x20,0x76,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x2a,0x20,0x32,0x2e,0x30,0x29,0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28, + 0x31,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b, + 0x0a,0x7d,0x0a,0x0a,0x00, } /* #version 410 - struct draw_text_fs_params - { - int down_sample; - vec4 colour; - }; - - uniform draw_text_fs_params _31; - - uniform sampler2D draw_text_src_texture_draw_text_src_sampler; + uniform vec4 ve_draw_text_fs_params[2]; + uniform sampler2D ve_draw_text_src_texture_ve_draw_text_src_sampler; layout(location = 0) in vec2 uv; layout(location = 0) out vec4 frag_color; void main() { - float alpha = texture(draw_text_src_texture_draw_text_src_sampler, uv).x; - if (_31.down_sample == 1) - { - alpha = 0.25 * (((texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(-0.000244140625, -0.0009765625)).x + texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(-0.000244140625, 0.0009765625)).x) + texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(0.000244140625, -0.0009765625)).x) + texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(0.000244140625, 0.0009765625)).x); - } - frag_color = vec4(_31.colour.xyz, _31.colour.w * alpha); + frag_color = vec4(ve_draw_text_fs_params[1].xyz, ve_draw_text_fs_params[1].w * ((1.0 / ve_draw_text_fs_params[0].z) * (((texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, fma(vec2(-0.5), ve_draw_text_fs_params[0].xy, uv)).x + texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, fma(vec2(-0.5, 0.5), ve_draw_text_fs_params[0].xy, uv)).x) + texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, fma(vec2(0.5, -0.5), ve_draw_text_fs_params[0].xy, uv)).x) + texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, fma(vec2(0.5), ve_draw_text_fs_params[0].xy, uv)).x))); } */ @(private="file") -draw_text_fs_source_glsl410 := [882]u8 { - 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x73,0x74, - 0x72,0x75,0x63,0x74,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66, - 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x69, - 0x6e,0x74,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a, - 0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x3b, - 0x0a,0x7d,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x64,0x72,0x61, - 0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, - 0x20,0x5f,0x33,0x31,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73, - 0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65, - 0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x64, - 0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, - 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76, - 0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, - 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74, - 0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, - 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f, +ve_draw_text_fs_source_glsl410 := [812]u8 { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x65,0x5f,0x64,0x72, + 0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x32,0x5d,0x3b,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,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, + 0x5f,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72, + 0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30, + 0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,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,0x76,0x65,0x63,0x34,0x28,0x76,0x65,0x5f,0x64, + 0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x76,0x65,0x5f,0x64,0x72, + 0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x31,0x5d,0x2e,0x77,0x20,0x2a,0x20,0x28,0x28,0x31,0x2e,0x30,0x20,0x2f, + 0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x7a,0x29,0x20,0x2a,0x20, + 0x28,0x28,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x66,0x6d, + 0x61,0x28,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x35,0x29,0x2c,0x20,0x76,0x65, + 0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x2c,0x20,0x75,0x76,0x29,0x29, + 0x2e,0x78,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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, + 0x66,0x6d,0x61,0x28,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x35,0x2c,0x20,0x30, + 0x2e,0x35,0x29,0x2c,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78, + 0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78, + 0x79,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,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,0x5f,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,0x2e,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,0x20,0x2a,0x20,0x28,0x28,0x28,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73, - 0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x30, - 0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e, - 0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x2e,0x78,0x20,0x2b, - 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65, - 0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28, - 0x2d,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x2c, - 0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x2e, - 0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61, - 0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75, - 0x72,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,0x76, - 0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36, - 0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32, - 0x35,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, - 0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74, - 0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34, - 0x31,0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36, - 0x35,0x36,0x32,0x35,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,0x76,0x65,0x63,0x34,0x28,0x5f,0x33,0x31,0x2e,0x63,0x6f,0x6c,0x6f,0x75, - 0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x5f,0x33,0x31,0x2e,0x63,0x6f,0x6c,0x6f,0x75, - 0x72,0x2e,0x77,0x20,0x2a,0x20,0x61,0x6c,0x70,0x68,0x61,0x29,0x3b,0x0a,0x7d,0x0a, - 0x0a,0x00, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x66,0x6d,0x61,0x28,0x76,0x65,0x63,0x32, + 0x28,0x30,0x2e,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x35,0x29,0x2c,0x20,0x76,0x65,0x5f, + 0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72, + 0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e, + 0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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, + 0x66,0x6d,0x61,0x28,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29,0x2c,0x20,0x76, + 0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x2c,0x20,0x75,0x76,0x29, + 0x29,0x2e,0x78,0x29,0x29,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } /* #version 300 es @@ -170,13 +155,13 @@ draw_text_fs_source_glsl410 := [882]u8 { void main() { - uv = vec2(v_texture.x, v_texture.y); + uv = vec2(v_texture.x, 1.0 - v_texture.y); gl_Position = vec4((v_position * 2.0) - vec2(1.0), 0.0, 1.0); } */ @(private="file") -draw_text_vs_source_glsl300es := [237]u8 { +ve_draw_text_vs_source_glsl300es := [243]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61, 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, @@ -186,103 +171,87 @@ draw_text_vs_source_glsl300es := [237]u8 { 0x63,0x32,0x20,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x0a, 0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20, 0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x76,0x5f,0x74,0x65, - 0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x76,0x5f,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f, - 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x76, - 0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2a,0x20,0x32,0x2e,0x30,0x29, - 0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e, - 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + 0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x31,0x2e,0x30,0x20,0x2d,0x20,0x76, + 0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76, + 0x65,0x63,0x34,0x28,0x28,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20, + 0x2a,0x20,0x32,0x2e,0x30,0x29,0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e, + 0x30,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d, + 0x0a,0x0a,0x00, } /* #version 300 es precision mediump float; precision highp int; - struct draw_text_fs_params - { - int down_sample; - highp vec4 colour; - }; - - uniform draw_text_fs_params _31; - - uniform highp sampler2D draw_text_src_texture_draw_text_src_sampler; + uniform highp vec4 ve_draw_text_fs_params[2]; + uniform highp sampler2D ve_draw_text_src_texture_ve_draw_text_src_sampler; in highp vec2 uv; layout(location = 0) out highp vec4 frag_color; void main() { - highp float alpha = texture(draw_text_src_texture_draw_text_src_sampler, uv).x; - if (_31.down_sample == 1) - { - alpha = 0.25 * (((texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(-0.000244140625, -0.0009765625)).x + texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(-0.000244140625, 0.0009765625)).x) + texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(0.000244140625, -0.0009765625)).x) + texture(draw_text_src_texture_draw_text_src_sampler, uv + vec2(0.000244140625, 0.0009765625)).x); - } - frag_color = vec4(_31.colour.xyz, _31.colour.w * alpha); + frag_color = vec4(ve_draw_text_fs_params[1].xyz, ve_draw_text_fs_params[1].w * ((1.0 / ve_draw_text_fs_params[0].z) * (((texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, vec2(-0.5) * ve_draw_text_fs_params[0].xy + uv).x + texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, vec2(-0.5, 0.5) * ve_draw_text_fs_params[0].xy + uv).x) + texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, vec2(0.5, -0.5) * ve_draw_text_fs_params[0].xy + uv).x) + texture(ve_draw_text_src_texture_ve_draw_text_src_sampler, vec2(0.5) * ve_draw_text_fs_params[0].xy + uv).x))); } */ @(private="file") -draw_text_fs_source_glsl300es := [940]u8 { +ve_draw_text_fs_source_glsl300es := [852]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, - 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x73, - 0x74,0x72,0x75,0x63,0x74,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f, - 0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, - 0x69,0x6e,0x74,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b, - 0x0a,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20, - 0x63,0x6f,0x6c,0x6f,0x75,0x72,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66, - 0x6f,0x72,0x6d,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73, - 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x5f,0x33,0x31,0x3b,0x0a,0x0a,0x75,0x6e, - 0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x72,0x32,0x44,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f, - 0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x64,0x72,0x61,0x77, - 0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, - 0x72,0x3b,0x0a,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63, - 0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63, - 0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68, - 0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, - 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e, - 0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x66, - 0x6c,0x6f,0x61,0x74,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73, - 0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x2e,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,0x20,0x2a,0x20,0x28,0x28,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x64, - 0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78, - 0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31, - 0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36, - 0x35,0x36,0x32,0x35,0x29,0x29,0x2e,0x78,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63, - 0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x30,0x30,0x30, - 0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30, - 0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74, - 0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74, - 0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x30,0x2e, - 0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x2d,0x30, - 0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x2e,0x78,0x29, - 0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x64,0x72,0x61,0x77,0x5f, - 0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63, + 0x34,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x3b,0x0a,0x75,0x6e,0x69, + 0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x72,0x32,0x44,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,0x5f,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,0x76,0x65,0x63, - 0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35, - 0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,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,0x76,0x65,0x63,0x34, - 0x28,0x5f,0x33,0x31,0x2e,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,0x78,0x79,0x7a,0x2c, - 0x20,0x5f,0x33,0x31,0x2e,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,0x77,0x20,0x2a,0x20, - 0x61,0x6c,0x70,0x68,0x61,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68, + 0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75, + 0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20, + 0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x66, + 0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64, + 0x20,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,0x76,0x65,0x63,0x34,0x28, + 0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x76, + 0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2e,0x77,0x20,0x2a,0x20,0x28,0x28,0x31, + 0x2e,0x30,0x20,0x2f,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78, + 0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x7a, + 0x29,0x20,0x2a,0x20,0x28,0x28,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x35,0x29,0x20,0x2a,0x20,0x76, + 0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x20,0x2b,0x20,0x75,0x76, + 0x29,0x2e,0x78,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x35,0x2c,0x20,0x30,0x2e,0x35,0x29, + 0x20,0x2a,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f, + 0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x20, + 0x2b,0x20,0x75,0x76,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,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,0x5f,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,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x2c,0x20, + 0x2d,0x30,0x2e,0x35,0x29,0x20,0x2a,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f, + 0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30, + 0x5d,0x2e,0x78,0x79,0x20,0x2b,0x20,0x75,0x76,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,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, + 0x5f,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,0x76,0x65,0x63,0x32,0x28, + 0x30,0x2e,0x35,0x29,0x20,0x2a,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74, + 0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d, + 0x2e,0x78,0x79,0x20,0x2b,0x20,0x75,0x76,0x29,0x2e,0x78,0x29,0x29,0x29,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, } /* static float4 gl_Position; @@ -320,7 +289,7 @@ draw_text_fs_source_glsl300es := [940]u8 { } */ @(private="file") -draw_text_vs_source_hlsl4 := [724]u8 { +ve_draw_text_vs_source_hlsl4 := [724]u8 { 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,0x0a,0x73,0x74,0x61, @@ -369,14 +338,15 @@ draw_text_vs_source_hlsl4 := [724]u8 { 0x0a,0x7d,0x0a,0x00, } /* - cbuffer draw_text_fs_params : register(b0) + 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 draw_text_src_texture : register(t0); - SamplerState draw_text_src_sampler : register(s0); + Texture2D ve_draw_text_src_texture : register(t0); + SamplerState ve_draw_text_src_sampler : register(s0); static float2 uv; static float4 frag_color; @@ -393,12 +363,7 @@ draw_text_vs_source_hlsl4 := [724]u8 { void frag_main() { - float alpha = draw_text_src_texture.Sample(draw_text_src_sampler, uv).x; - if (_31_down_sample == 1) - { - alpha = 0.25f * (((draw_text_src_texture.Sample(draw_text_src_sampler, uv + float2(-0.000244140625f, -0.0009765625f)).x + draw_text_src_texture.Sample(draw_text_src_sampler, uv + float2(-0.000244140625f, 0.0009765625f)).x) + draw_text_src_texture.Sample(draw_text_src_sampler, uv + float2(0.000244140625f, -0.0009765625f)).x) + draw_text_src_texture.Sample(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) @@ -411,20 +376,24 @@ draw_text_vs_source_hlsl4 := [724]u8 { } */ @(private="file") -draw_text_fs_source_hlsl4 := [1257]u8 { - 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,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,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,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78, +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,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, @@ -439,58 +408,52 @@ draw_text_fs_source_hlsl4 := [1257]u8 { 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,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,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,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,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, + 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,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,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,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,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,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, + 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,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, } /* #include @@ -520,7 +483,7 @@ draw_text_fs_source_hlsl4 := [1257]u8 { */ @(private="file") -draw_text_vs_source_metal_macos := [495]u8 { +ve_draw_text_vs_source_metal_macos := [495]u8 { 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, @@ -559,9 +522,10 @@ draw_text_vs_source_metal_macos := [495]u8 { using namespace metal; - struct draw_text_fs_params + struct ve_draw_text_fs_params { - int down_sample; + float2 glyph_buffer_size; + float over_sample; float4 colour; }; @@ -575,93 +539,85 @@ draw_text_vs_source_metal_macos := [495]u8 { float2 uv [[user(locn0)]]; }; - fragment main0_out main0(main0_in in [[stage_in]], constant draw_text_fs_params& _31 [[buffer(0)]], texture2d draw_text_src_texture [[texture(0)]], sampler draw_text_src_sampler [[sampler(0)]]) + fragment main0_out main0(main0_in in [[stage_in]], constant ve_draw_text_fs_params& _32 [[buffer(0)]], texture2d ve_draw_text_src_texture [[texture(0)]], sampler ve_draw_text_src_sampler [[sampler(0)]]) { main0_out out = {}; - float alpha = draw_text_src_texture.sample(draw_text_src_sampler, in.uv).x; - if (_31.down_sample == 1) - { - alpha = 0.25 * (((draw_text_src_texture.sample(draw_text_src_sampler, (in.uv + float2(-0.000244140625, -0.0009765625))).x + draw_text_src_texture.sample(draw_text_src_sampler, (in.uv + float2(-0.000244140625, 0.0009765625))).x) + draw_text_src_texture.sample(draw_text_src_sampler, (in.uv + float2(0.000244140625, -0.0009765625))).x) + draw_text_src_texture.sample(draw_text_src_sampler, (in.uv + float2(0.000244140625, 0.0009765625))).x); - } - out.frag_color = float4(_31.colour.xyz, _31.colour.w * alpha); + out.frag_color = float4(_32.colour.xyz, _32.colour.w * ((1.0 / _32.over_sample) * (((ve_draw_text_src_texture.sample(ve_draw_text_src_sampler, fma(float2(-0.5), _32.glyph_buffer_size, in.uv)).x + ve_draw_text_src_texture.sample(ve_draw_text_src_sampler, fma(float2(-0.5, 0.5), _32.glyph_buffer_size, in.uv)).x) + ve_draw_text_src_texture.sample(ve_draw_text_src_sampler, fma(float2(0.5, -0.5), _32.glyph_buffer_size, in.uv)).x) + ve_draw_text_src_texture.sample(ve_draw_text_src_sampler, fma(float2(0.5), _32.glyph_buffer_size, in.uv)).x))); return out; } */ @(private="file") -draw_text_fs_source_metal_macos := [1141]u8 { +ve_draw_text_fs_source_metal_macos := [1094]u8 { 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, - 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x64, - 0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61, - 0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x64,0x6f,0x77, - 0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, - 0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x3b,0x0a,0x7d,0x3b,0x0a, - 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,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,0x5b,0x5b,0x63,0x6f,0x6c,0x6f, - 0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75, - 0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20, - 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x5b,0x5b,0x75,0x73, - 0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a, - 0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, - 0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f, - 0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e, - 0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x64,0x72,0x61, - 0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, - 0x26,0x20,0x5f,0x33,0x31,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30, - 0x29,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66, - 0x6c,0x6f,0x61,0x74,0x3e,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f, - 0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x5b,0x5b,0x74,0x65, - 0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x72,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72, - 0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, - 0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20, - 0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x61,0x6c, - 0x70,0x68,0x61,0x20,0x3d,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f, - 0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63, - 0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x29, - 0x2e,0x78,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x5f,0x33,0x31,0x2e, - 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,0x20,0x2a,0x20,0x28, - 0x28,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x64, - 0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x72,0x2c,0x20,0x28,0x69,0x6e,0x2e,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,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36, - 0x35,0x36,0x32,0x35,0x29,0x29,0x29,0x2e,0x78,0x20,0x2b,0x20,0x64,0x72,0x61,0x77, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x32,0x20,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f, + 0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20, + 0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30, + 0x5f,0x6f,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,0x5b,0x5b,0x63, + 0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x5b, + 0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20, + 0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x33,0x32,0x20,0x5b,0x5b,0x62,0x75, + 0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,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,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30, + 0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28, + 0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x33,0x32,0x2e, + 0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x5f,0x33,0x32,0x2e, + 0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,0x77,0x20,0x2a,0x20,0x28,0x28,0x31,0x2e,0x30, + 0x20,0x2f,0x20,0x5f,0x33,0x32,0x2e,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,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65, - 0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20, - 0x28,0x69,0x6e,0x2e,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,0x2c, - 0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x29, - 0x2e,0x78,0x29,0x20,0x2b,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f, - 0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63, - 0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x28,0x69,0x6e,0x2e,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,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x30, - 0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20, + 0x65,0x2e,0x73,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,0x66,0x6d,0x61,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x30, + 0x2e,0x35,0x29,0x2c,0x20,0x5f,0x33,0x32,0x2e,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62, + 0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x69,0x6e,0x2e,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,0x73,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,0x66,0x6d,0x61,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x30,0x2e, + 0x35,0x2c,0x20,0x30,0x2e,0x35,0x29,0x2c,0x20,0x5f,0x33,0x32,0x2e,0x67,0x6c,0x79, + 0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20, + 0x69,0x6e,0x2e,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,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x64,0x72,0x61, - 0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c, - 0x65,0x72,0x2c,0x20,0x28,0x69,0x6e,0x2e,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,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35, - 0x29,0x29,0x29,0x2e,0x78,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20, - 0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, - 0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x33,0x31,0x2e,0x63,0x6f, - 0x6c,0x6f,0x75,0x72,0x2e,0x78,0x79,0x7a,0x2c,0x20,0x5f,0x33,0x31,0x2e,0x63,0x6f, - 0x6c,0x6f,0x75,0x72,0x2e,0x77,0x20,0x2a,0x20,0x61,0x6c,0x70,0x68,0x61,0x29,0x3b, - 0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b, - 0x0a,0x7d,0x0a,0x0a,0x00, + 0x78,0x74,0x75,0x72,0x65,0x2e,0x73,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,0x66,0x6d,0x61,0x28,0x66,0x6c,0x6f,0x61,0x74, + 0x32,0x28,0x30,0x2e,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x35,0x29,0x2c,0x20,0x5f,0x33, + 0x32,0x2e,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73, + 0x69,0x7a,0x65,0x2c,0x20,0x69,0x6e,0x2e,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,0x73,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,0x66,0x6d,0x61,0x28, + 0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x35,0x29,0x2c,0x20,0x5f,0x33,0x32, + 0x2e,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69, + 0x7a,0x65,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x29,0x29,0x2e,0x78,0x29,0x29,0x29, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74, + 0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } /* diagnostic(off, derivative_uniformity); @@ -701,7 +657,7 @@ draw_text_fs_source_metal_macos := [1141]u8 { */ @(private="file") -draw_text_vs_source_wgsl := [756]u8 { +ve_draw_text_vs_source_wgsl := [756]u8 { 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, @@ -754,45 +710,58 @@ draw_text_vs_source_wgsl := [756]u8 { /* diagnostic(off, derivative_uniformity); - struct draw_text_fs_params { + struct ve_draw_text_fs_params { /_ @offset(0) _/ - down_sample : i32, + glyph_buffer_size : vec2f, + /_ @offset(8) _/ + over_sample : f32, /_ @offset(16) _/ colour : vec4f, } - @group(1) @binding(64) var draw_text_src_texture : texture_2d; + @group(1) @binding(64) var ve_draw_text_src_texture : texture_2d; - @group(1) @binding(80) var draw_text_src_sampler : sampler; + @group(1) @binding(80) var ve_draw_text_src_sampler : sampler; var uv : vec2f; - @group(0) @binding(8) var x_31 : draw_text_fs_params; + @group(0) @binding(8) var x_32 : ve_draw_text_fs_params; var frag_color : vec4f; fn main_1() { var alpha : f32; + var texture_size : vec2f; + var down_sample : f32; let x_22 : vec2f = uv; - let x_24 : vec4f = textureSample(draw_text_src_texture, draw_text_src_sampler, x_22); + let x_24 : vec4f = textureSample(ve_draw_text_src_texture, ve_draw_text_src_sampler, x_22); alpha = x_24.x; - let x_35 : i32 = x_31.down_sample; - if ((x_35 == 1i)) { - let x_44 : vec2f = uv; - let x_49 : vec4f = textureSample(draw_text_src_texture, draw_text_src_sampler, (x_44 + vec2f(-0.000244140625f, -0.0009765625f))); - let x_56 : vec2f = uv; - let x_60 : vec4f = textureSample(draw_text_src_texture, draw_text_src_sampler, (x_56 + vec2f(-0.000244140625f, 0.0009765625f))); - let x_67 : vec2f = uv; - let x_71 : vec4f = textureSample(draw_text_src_texture, draw_text_src_sampler, (x_67 + vec2f(0.000244140625f, -0.0009765625f))); - let x_78 : vec2f = uv; - let x_81 : vec4f = textureSample(draw_text_src_texture, draw_text_src_sampler, (x_78 + vec2f(0.000244140625f, 0.0009765625f))); - alpha = ((((x_49.x * 0.25f) + (x_60.x * 0.25f)) + (x_71.x * 0.25f)) + (x_81.x * 0.25f)); - } - let x_90 : vec4f = x_31.colour; - let x_91 : vec3f = vec3f(x_90.x, x_90.y, x_90.z); - let x_95 : f32 = x_31.colour.w; - let x_96 : f32 = alpha; - frag_color = vec4f(x_91.x, x_91.y, x_91.z, (x_95 * x_96)); + let x_37 : vec2f = x_32.glyph_buffer_size; + texture_size = x_37; + let x_43 : f32 = x_32.over_sample; + down_sample = (1.0f / x_43); + let x_48 : vec2f = uv; + let x_51 : vec2f = texture_size; + let x_54 : vec4f = textureSample(ve_draw_text_src_texture, ve_draw_text_src_sampler, (x_48 + (vec2f(-0.5f, -0.5f) * x_51))); + let x_56 : f32 = down_sample; + let x_61 : vec2f = uv; + let x_64 : vec2f = texture_size; + let x_67 : vec4f = textureSample(ve_draw_text_src_texture, ve_draw_text_src_sampler, (x_61 + (vec2f(-0.5f, 0.5f) * x_64))); + let x_69 : f32 = down_sample; + let x_75 : vec2f = uv; + let x_77 : vec2f = texture_size; + let x_80 : vec4f = textureSample(ve_draw_text_src_texture, ve_draw_text_src_sampler, (x_75 + (vec2f(0.5f, -0.5f) * x_77))); + let x_82 : f32 = down_sample; + let x_88 : vec2f = uv; + let x_90 : vec2f = texture_size; + let x_93 : vec4f = textureSample(ve_draw_text_src_texture, ve_draw_text_src_sampler, (x_88 + (vec2f(0.5f, 0.5f) * x_90))); + let x_95 : f32 = down_sample; + alpha = ((((x_54.x * x_56) + (x_67.x * x_69)) + (x_80.x * x_82)) + (x_93.x * x_95)); + let x_104 : vec4f = x_32.colour; + let x_105 : vec3f = vec3f(x_104.x, x_104.y, x_104.z); + let x_108 : f32 = x_32.colour.w; + let x_109 : f32 = alpha; + frag_color = vec4f(x_105.x, x_105.y, x_105.z, (x_108 * x_109)); return; } @@ -810,139 +779,163 @@ draw_text_vs_source_wgsl := [756]u8 { */ @(private="file") -draw_text_fs_source_wgsl := [1772]u8 { +ve_draw_text_fs_source_wgsl := [2202]u8 { 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, - 0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72, - 0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73, - 0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x64,0x6f,0x77,0x6e,0x5f, - 0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3a,0x20,0x69,0x33,0x32,0x2c,0x0a,0x20,0x20, - 0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x31,0x36,0x29,0x20,0x2a, - 0x2f,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x20,0x3a,0x20,0x76,0x65,0x63, - 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29, - 0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34,0x29,0x20,0x76,0x61, - 0x72,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x3a,0x20,0x74,0x65,0x78,0x74,0x75,0x72, - 0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f, - 0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x38, - 0x30,0x29,0x20,0x76,0x61,0x72,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74, - 0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x3a,0x20,0x73, - 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, - 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, - 0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29,0x20,0x40,0x62,0x69, - 0x6e,0x64,0x69,0x6e,0x67,0x28,0x38,0x29,0x20,0x76,0x61,0x72,0x3c,0x75,0x6e,0x69, - 0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x33,0x31,0x20,0x3a,0x20,0x64,0x72,0x61, - 0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, - 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, - 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63, - 0x34,0x66,0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29, - 0x20,0x7b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3a, - 0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x32, - 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20, - 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x66,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f, + 0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x67,0x6c, + 0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f, + 0x66,0x66,0x73,0x65,0x74,0x28,0x38,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x6f,0x76, + 0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x2c, + 0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x31,0x36, + 0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70, + 0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34,0x29, + 0x20,0x76,0x61,0x72,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, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b, + 0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e, + 0x64,0x69,0x6e,0x67,0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,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,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b, + 0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75, + 0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f, + 0x75,0x70,0x28,0x30,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x38, + 0x29,0x20,0x76,0x61,0x72,0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78, + 0x5f,0x33,0x32,0x20,0x3a,0x20,0x76,0x65,0x5f,0x64,0x72,0x61,0x77,0x5f,0x74,0x65, + 0x78,0x74,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x76, + 0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a, + 0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20, + 0x20,0x76,0x61,0x72,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3a,0x20,0x66,0x33,0x32, + 0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f, + 0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20, + 0x76,0x61,0x72,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20, + 0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61,0x6d,0x70, + 0x6c,0x65,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,0x2c,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,0x2c,0x20,0x78,0x5f,0x32,0x32,0x29,0x3b,0x0a,0x20,0x20, + 0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20,0x78,0x5f,0x32,0x34,0x2e,0x78,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x37,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x20,0x3d,0x20,0x78,0x5f,0x33,0x32,0x2e,0x67,0x6c,0x79,0x70,0x68,0x5f, + 0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x20,0x3d,0x20,0x78,0x5f, + 0x33,0x37,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x33,0x20,0x3a, + 0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x33,0x32,0x2e,0x6f,0x76,0x65,0x72, + 0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x64,0x6f,0x77,0x6e,0x5f, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3d,0x20,0x28,0x31,0x2e,0x30,0x66,0x20,0x2f, + 0x20,0x78,0x5f,0x34,0x33,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, + 0x34,0x38,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x31,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69, + 0x7a,0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x34,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x53,0x61,0x6d,0x70,0x6c,0x65,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,0x2c, + 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,0x2c,0x20,0x28,0x78,0x5f,0x34,0x38, + 0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x2d,0x30,0x2e,0x35,0x66,0x2c, + 0x20,0x2d,0x30,0x2e,0x35,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x35,0x31,0x29,0x29, + 0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x36,0x20,0x3a,0x20, + 0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x31,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x36,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, 0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c, - 0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x64,0x72,0x61,0x77,0x5f,0x74,0x65, + 0x65,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,0x2c,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,0x2c,0x20,0x28,0x78,0x5f,0x36,0x31,0x20,0x2b,0x20,0x28,0x76, + 0x65,0x63,0x32,0x66,0x28,0x2d,0x30,0x2e,0x35,0x66,0x2c,0x20,0x30,0x2e,0x35,0x66, + 0x29,0x20,0x2a,0x20,0x78,0x5f,0x36,0x34,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x36,0x39,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20, + 0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x37,0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20, + 0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x37, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78, + 0x5f,0x38,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x74,0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,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,0x2c,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,0x2c,0x20, - 0x78,0x5f,0x32,0x32,0x29,0x3b,0x0a,0x20,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3d, - 0x20,0x78,0x5f,0x32,0x34,0x2e,0x78,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78, - 0x5f,0x33,0x35,0x20,0x3a,0x20,0x69,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x33,0x31, - 0x2e,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20, - 0x69,0x66,0x20,0x28,0x28,0x78,0x5f,0x33,0x35,0x20,0x3d,0x3d,0x20,0x31,0x69,0x29, - 0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x34, - 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x39,0x20,0x3a,0x20,0x76,0x65, - 0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61,0x6d, - 0x70,0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72, - 0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x64,0x72,0x61,0x77,0x5f, - 0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72, - 0x2c,0x20,0x28,0x78,0x5f,0x34,0x34,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,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,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35, - 0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a, - 0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x30,0x20,0x3a,0x20,0x76, - 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61, - 0x6d,0x70,0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73, - 0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x64,0x72,0x61,0x77, - 0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, - 0x72,0x2c,0x20,0x28,0x78,0x5f,0x35,0x36,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66, - 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,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36, - 0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a, - 0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x31,0x20,0x3a,0x20,0x76, - 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61, - 0x6d,0x70,0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73, - 0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x64,0x72,0x61,0x77, - 0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, - 0x72,0x2c,0x20,0x28,0x78,0x5f,0x36,0x37,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66, - 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,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37, + 0x28,0x78,0x5f,0x37,0x35,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x30, + 0x2e,0x35,0x66,0x2c,0x20,0x2d,0x30,0x2e,0x35,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f, + 0x37,0x37,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38, + 0x32,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38, 0x38,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a, - 0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x31,0x20,0x3a,0x20,0x76, - 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61, - 0x6d,0x70,0x6c,0x65,0x28,0x64,0x72,0x61,0x77,0x5f,0x74,0x65,0x78,0x74,0x5f,0x73, - 0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x64,0x72,0x61,0x77, - 0x5f,0x74,0x65,0x78,0x74,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, - 0x72,0x2c,0x20,0x28,0x78,0x5f,0x37,0x38,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66, - 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,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20, - 0x28,0x28,0x28,0x28,0x78,0x5f,0x34,0x39,0x2e,0x78,0x20,0x2a,0x20,0x30,0x2e,0x32, - 0x35,0x66,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x36,0x30,0x2e,0x78,0x20,0x2a,0x20, - 0x30,0x2e,0x32,0x35,0x66,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x37,0x31,0x2e, - 0x78,0x20,0x2a,0x20,0x30,0x2e,0x32,0x35,0x66,0x29,0x29,0x20,0x2b,0x20,0x28,0x78, - 0x5f,0x38,0x31,0x2e,0x78,0x20,0x2a,0x20,0x30,0x2e,0x32,0x35,0x66,0x29,0x29,0x3b, - 0x0a,0x20,0x20,0x7d,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x30,0x20, - 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x33,0x31,0x2e,0x63, - 0x6f,0x6c,0x6f,0x75,0x72,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39, - 0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x33,0x66,0x20,0x3d,0x20,0x76,0x65,0x63,0x33, - 0x66,0x28,0x78,0x5f,0x39,0x30,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x39,0x30,0x2e,0x79, - 0x2c,0x20,0x78,0x5f,0x39,0x30,0x2e,0x7a,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, - 0x20,0x78,0x5f,0x39,0x35,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f, - 0x33,0x31,0x2e,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x2e,0x77,0x3b,0x0a,0x20,0x20,0x6c, - 0x65,0x74,0x20,0x78,0x5f,0x39,0x36,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20, - 0x61,0x6c,0x70,0x68,0x61,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, - 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x39,0x31, - 0x2e,0x78,0x2c,0x20,0x78,0x5f,0x39,0x31,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x39,0x31, - 0x2e,0x7a,0x2c,0x20,0x28,0x78,0x5f,0x39,0x35,0x20,0x2a,0x20,0x78,0x5f,0x39,0x36, - 0x29,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a, - 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74, - 0x20,0x7b,0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30, - 0x29,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x31, - 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x66,0x72, - 0x61,0x67,0x6d,0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40, - 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76,0x5f,0x70, - 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x29,0x20,0x2d,0x3e, - 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x75,0x76, - 0x20,0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d, - 0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72, - 0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x5f, - 0x63,0x6f,0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x30,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a, + 0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x33,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53, + 0x61,0x6d,0x70,0x6c,0x65,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,0x2c,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,0x2c,0x20,0x28,0x78,0x5f,0x38,0x38,0x20, + 0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x35,0x66,0x2c,0x20,0x30, + 0x2e,0x35,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x39,0x30,0x29,0x29,0x29,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x35,0x20,0x3a,0x20,0x66,0x33,0x32, + 0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a, + 0x20,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20,0x28,0x28,0x28,0x28,0x78,0x5f, + 0x35,0x34,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f,0x35,0x36,0x29,0x20,0x2b,0x20,0x28, + 0x78,0x5f,0x36,0x37,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f,0x36,0x39,0x29,0x29,0x20, + 0x2b,0x20,0x28,0x78,0x5f,0x38,0x30,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f,0x38,0x32, + 0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x39,0x33,0x2e,0x78,0x20,0x2a,0x20,0x78, + 0x5f,0x39,0x35,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31, + 0x30,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x33, + 0x32,0x2e,0x63,0x6f,0x6c,0x6f,0x75,0x72,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x31,0x30,0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x33,0x66,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x33,0x66,0x28,0x78,0x5f,0x31,0x30,0x34,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x31,0x30,0x34,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x31,0x30,0x34,0x2e,0x7a,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x30,0x38,0x20,0x3a,0x20, + 0x66,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x33,0x32,0x2e,0x63,0x6f,0x6c,0x6f,0x75, + 0x72,0x2e,0x77,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x30,0x39, + 0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x61,0x6c,0x70,0x68,0x61,0x3b,0x0a, + 0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76, + 0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x31,0x30,0x35,0x2e,0x78,0x2c,0x20,0x78,0x5f, + 0x31,0x30,0x35,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x31,0x30,0x35,0x2e,0x7a,0x2c,0x20, + 0x28,0x78,0x5f,0x31,0x30,0x38,0x20,0x2a,0x20,0x78,0x5f,0x31,0x30,0x39,0x29,0x29, + 0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b, + 0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a, + 0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x66,0x72,0x61,0x67, + 0x6d,0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f, + 0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76,0x5f,0x70,0x61,0x72, + 0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x29,0x20,0x2d,0x3e,0x20,0x6d, + 0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x75,0x76,0x20,0x3d, + 0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, + 0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } -draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { +ve_draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc: sg.Shader_Desc - desc.label = "draw_text_shader" + desc.label = "ve_draw_text_shader" #partial switch backend { case .GLCORE: - desc.vertex_func.source = transmute(cstring)&draw_text_vs_source_glsl410 + desc.vertex_func.source = transmute(cstring)&ve_draw_text_vs_source_glsl410 desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&draw_text_fs_source_glsl410 + desc.fragment_func.source = transmute(cstring)&ve_draw_text_fs_source_glsl410 desc.fragment_func.entry = "main" desc.attrs[0].glsl_name = "v_position" desc.attrs[1].glsl_name = "v_texture" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 desc.uniform_blocks[0].size = 32 - desc.uniform_blocks[0].glsl_uniforms[0].type = .INT - desc.uniform_blocks[0].glsl_uniforms[0].array_count = 0 - desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "_31.down_sample" - desc.uniform_blocks[0].glsl_uniforms[1].type = .FLOAT4 - desc.uniform_blocks[0].glsl_uniforms[1].array_count = 0 - desc.uniform_blocks[0].glsl_uniforms[1].glsl_name = "_31.colour" + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4 + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 2 + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "ve_draw_text_fs_params" desc.images[0].stage = .FRAGMENT desc.images[0].multisampled = false desc.images[0].image_type = ._2D @@ -952,23 +945,20 @@ draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].stage = .FRAGMENT desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 - desc.image_sampler_pairs[0].glsl_name = "draw_text_src_texture_draw_text_src_sampler" + desc.image_sampler_pairs[0].glsl_name = "ve_draw_text_src_texture_ve_draw_text_src_sampler" case .GLES3: - desc.vertex_func.source = transmute(cstring)&draw_text_vs_source_glsl300es + desc.vertex_func.source = transmute(cstring)&ve_draw_text_vs_source_glsl300es desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&draw_text_fs_source_glsl300es + desc.fragment_func.source = transmute(cstring)&ve_draw_text_fs_source_glsl300es desc.fragment_func.entry = "main" desc.attrs[0].glsl_name = "v_position" desc.attrs[1].glsl_name = "v_texture" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 desc.uniform_blocks[0].size = 32 - desc.uniform_blocks[0].glsl_uniforms[0].type = .INT - desc.uniform_blocks[0].glsl_uniforms[0].array_count = 0 - desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "_31.down_sample" - desc.uniform_blocks[0].glsl_uniforms[1].type = .FLOAT4 - desc.uniform_blocks[0].glsl_uniforms[1].array_count = 0 - desc.uniform_blocks[0].glsl_uniforms[1].glsl_name = "_31.colour" + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4 + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 2 + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "ve_draw_text_fs_params" desc.images[0].stage = .FRAGMENT desc.images[0].multisampled = false desc.images[0].image_type = ._2D @@ -978,12 +968,12 @@ draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].stage = .FRAGMENT desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 - desc.image_sampler_pairs[0].glsl_name = "draw_text_src_texture_draw_text_src_sampler" + desc.image_sampler_pairs[0].glsl_name = "ve_draw_text_src_texture_ve_draw_text_src_sampler" case .D3D11: - desc.vertex_func.source = transmute(cstring)&draw_text_vs_source_hlsl4 + desc.vertex_func.source = transmute(cstring)&ve_draw_text_vs_source_hlsl4 desc.vertex_func.d3d11_target = "vs_4_0" desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&draw_text_fs_source_hlsl4 + desc.fragment_func.source = transmute(cstring)&ve_draw_text_fs_source_hlsl4 desc.fragment_func.d3d11_target = "ps_4_0" desc.fragment_func.entry = "main" desc.attrs[0].hlsl_sem_name = "TEXCOORD" @@ -1006,9 +996,9 @@ draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 case .METAL_MACOS: - desc.vertex_func.source = transmute(cstring)&draw_text_vs_source_metal_macos + desc.vertex_func.source = transmute(cstring)&ve_draw_text_vs_source_metal_macos desc.vertex_func.entry = "main0" - desc.fragment_func.source = transmute(cstring)&draw_text_fs_source_metal_macos + desc.fragment_func.source = transmute(cstring)&ve_draw_text_fs_source_metal_macos desc.fragment_func.entry = "main0" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 @@ -1026,9 +1016,9 @@ draw_text_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 case .WGPU: - desc.vertex_func.source = transmute(cstring)&draw_text_vs_source_wgsl + desc.vertex_func.source = transmute(cstring)&ve_draw_text_vs_source_wgsl desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&draw_text_fs_source_wgsl + desc.fragment_func.source = transmute(cstring)&ve_draw_text_fs_source_wgsl desc.fragment_func.entry = "main" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 diff --git a/backend/sokol/draw_text.shdc.glsl b/backend/sokol/draw_text.shdc.glsl index 1a6dfd6..f71ed76 100644 --- a/backend/sokol/draw_text.shdc.glsl +++ b/backend/sokol/draw_text.shdc.glsl @@ -1,6 +1,6 @@ @module ve_draw_text -@header package sectr +@header package ve_sokol @header import sg "thirdparty:sokol/gfx" @vs ve_draw_text_vs diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 3017e07..a63ffbf 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -42,7 +42,7 @@ FONT_LARGEST_PIXEL_SIZE :: 400 FONT_SIZE_INTERVAL :: 2 FONT_DEFAULT :: Font_ID { "" } -FONT_DEFAULT_SIZEZ :: 12.0 +FONT_DEFAULT_SIZE :: 12.0 FONT_LOAD_USE_DEFAULT_SIZE :: -1 FONT_LOAD_GEN_ID :: "" @@ -56,16 +56,16 @@ Font_ID :: struct { label : string, } -FontDef :: struct { +Font_Entry :: struct { path_file : string, default_size : i32, - size_table : [FONT_LARGEST_PIXEL_SIZE / FONT_SIZE_INTERVAL] ve.Font_ID, + ve_id : ve.Font_ID, } Demo_Context :: struct { ve_ctx : ve.Context, render_ctx : ve_sokol.Context, - font_ids : map[string]FontDef, + font_ids : map[string]Font_Entry, // Values between 1, & -1 on Y axis mouse_scroll : Vec2, @@ -106,7 +106,7 @@ font_load :: proc(path_file : string, font_data, read_succeded : = os.read_entire_file( path_file ) assert( bool(read_succeded), fmt.tprintf("Failed to read font file for: %v", path_file) ) font_data_size := cast(i32) len(font_data) -font_firacode : Font_ID + font_firacode : Font_ID desired_id := desired_id @@ -115,24 +115,19 @@ font_firacode : Font_ID desired_id = file_name_from_path(path_file) } - demo_ctx.font_ids[desired_id] = FontDef {} + demo_ctx.font_ids[desired_id] = Font_Entry {} def := & demo_ctx.font_ids[desired_id] default_size := default_size if default_size < 0 { - default_size = FONT_DEFAULT_SIZEZ + default_size = FONT_DEFAULT_SIZE } + error : ve.Load_Font_Error 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_PIXEL_SIZE; font_size += FONT_SIZE_INTERVAL - { - id := (font_size / FONT_SIZE_INTERVAL) + (font_size % FONT_SIZE_INTERVAL) - ve_id := & def.size_table[id - 1] - ve_ret_id := ve.load_font( & demo_ctx.ve_ctx, desired_id, font_data, f32(font_size), curve_quality ) - (ve_id^) = ve_ret_id - } + def.ve_id, error = ve.load_font( & demo_ctx.ve_ctx, desired_id, font_data, curve_quality ) + assert(error == .None) fid := Font_ID { desired_id } return fid @@ -140,43 +135,36 @@ font_firacode : Font_ID Font_Use_Default_Size :: f32(0.0) -font_resolve_draw_id :: proc( id : Font_ID, size := Font_Use_Default_Size ) -> ( ve_id : ve.Font_ID, resolved_size : i32 ) -{ - def := demo_ctx.font_ids[ id.label ] - 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_PIXEL_SIZE ) - - id := (resolved_size / FONT_SIZE_INTERVAL) + (resolved_size % FONT_SIZE_INTERVAL) - ve_id = def.size_table[ id - 1 ] - return -} - measure_text_size :: proc( text : string, font : Font_ID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 { - ve_id, size := font_resolve_draw_id( font, font_size ) - measured := ve.measure_text_size( & demo_ctx.ve_ctx, ve_id, text ) + def := demo_ctx.font_ids[ font.label ] + measured := ve.measure_text_size( & demo_ctx.ve_ctx, def.ve_id, font_size, text ) return measured } get_font_vertical_metrics :: #force_inline proc ( font : Font_ID, font_size := Font_Use_Default_Size ) -> ( ascent, descent, line_gap : f32 ) { - ve_id, size := font_resolve_draw_id( font, font_size ) - ascent, descent, line_gap = ve.get_font_vertical_metrics( & demo_ctx.ve_ctx, ve_id ) + def := demo_ctx.font_ids[ font.label ] + ascent, descent, line_gap = ve.get_font_vertical_metrics( & demo_ctx.ve_ctx, def.ve_id, font_size ) return } // Draw text using a string and normalized render coordinates -draw_text_string_pos_norm :: proc( content : string, id : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE, scale : f32 = 1.0 ) +draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE, scale : f32 = 1.0 ) { - width := demo_ctx.screen_size.x - height := demo_ctx.screen_size.y + color_norm := normalize_rgba8(color) + def := demo_ctx.font_ids[ font.label ] - ve_id, resolved_size := font_resolve_draw_id( id, size ) - color_norm := normalize_rgba8(color) - - ve.set_colour( & demo_ctx.ve_ctx, color_norm ) - ve.draw_text( & demo_ctx.ve_ctx, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale ) + ve.draw_text_normalized_space( & demo_ctx.ve_ctx, + def.ve_id, + size, + color_norm, + demo_ctx.screen_size, + pos, + scale, + 1.0, + content + ) return } @@ -188,17 +176,13 @@ draw_text_string_pos_extent :: proc( content : string, id : Font_ID, size : f32, } // Adapt the draw_text_string_pos_extent_zoomed procedure -draw_text_zoomed_norm :: proc(content : string, id : Font_ID, size : f32, pos : Vec2, zoom : f32, color := COLOR_WHITE) +draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos : Vec2, zoom : f32, color := COLOR_WHITE) { screen_size := demo_ctx.screen_size - screen_scale := Vec2{1.0 / screen_size.x, 1.0 / screen_size.y} + screen_scale := 1 / screen_size zoom_adjust_size := size * zoom - // Over-sample font-size - - zoom_adjust_size *= OVER_SAMPLE_ZOOM - - ve_id, resolved_size := font_resolve_draw_id(id, zoom_adjust_size) + resolved_size := size text_scale := screen_scale { @@ -209,12 +193,16 @@ draw_text_zoomed_norm :: proc(content : string, id : Font_ID, size : f32, pos : text_scale.y = clamp(text_scale.y, 0, 1) } - // Down-sample back - text_scale /= OVER_SAMPLE_ZOOM - color_norm := normalize_rgba8(color) - ve.set_colour(&demo_ctx.ve_ctx, color_norm) - ve.draw_text(&demo_ctx.ve_ctx, ve_id, content, pos, text_scale) + def := demo_ctx.font_ids[ font.label ] + + ve.draw_text_normalized_space(& demo_ctx.ve_ctx, + def.ve_id, + screen_size, + pos, + text_scale, + content + ) } sokol_app_alloc :: proc "c" ( size : uint, user_data : rawptr ) -> rawptr { From 572abf5d861eccf6b95058f37b20651bd73ee2bc Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 13:09:52 -0500 Subject: [PATCH 07/62] compiling.. borken tho... --- backend/sokol/backend_sokol.odin | 98 +- backend/sokol/blit_atlas.odin | 1946 +++++++++++++++------------ backend/sokol/blit_atlas.shdc.glsl | 2 +- examples/sokol_demo/sokol_demo.odin | 19 +- vefontcache/pkg_mapping.odin | 4 + vefontcache/vefontcache.odin | 7 +- 6 files changed, 1146 insertions(+), 930 deletions(-) diff --git a/backend/sokol/backend_sokol.odin b/backend/sokol/backend_sokol.odin index c89c2c9..ad2a276 100644 --- a/backend/sokol/backend_sokol.odin +++ b/backend/sokol/backend_sokol.odin @@ -60,7 +60,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index app_env := glue.environment() ctx.glyph_shader = gfx.make_shader(render_glyph_shader_desc(backend) ) - ctx.atlas_shader = gfx.make_shader(blit_atlas_shader_desc(backend) ) + ctx.atlas_shader = gfx.make_shader(ve_blit_atlas_shader_desc(backend) ) ctx.screen_shader = gfx.make_shader(ve_draw_text_shader_desc(backend) ) ctx.draw_list_vbuf = gfx.make_buffer( Buffer_Desciption { @@ -75,7 +75,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index usage = Buffer_Usage.STREAM, type = Buffer_Type.INDEXBUFFER, }) - assert( gfx.query_buffer_state( draw_list_ibuf) < Resource_State.FAILED, "Failed to make draw_list_iubuf" ) + assert( gfx.query_buffer_state( ctx.draw_list_ibuf) < Resource_State.FAILED, "Failed to make draw_list_iubuf" ) Image_Filter := Filter.LINEAR @@ -114,8 +114,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index }, } - glyph_pipeline = gfx.make_pipeline({ - shader = glyph_shader, + ctx.glyph_pipeline = gfx.make_pipeline({ + shader = ctx.glyph_shader, layout = vs_layout, index_type = Vertex_Index_Type.UINT32, colors = { @@ -130,29 +130,29 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index cull_mode = .NONE, sample_count = 1, }) - assert( gfx.query_pipeline_state(glyph_pipeline) < Resource_State.FAILED, "Failed to make glyph_pipeline" ) + assert( gfx.query_pipeline_state(ctx.glyph_pipeline) < Resource_State.FAILED, "Failed to make glyph_pipeline" ) } // glyph_pass { - glyph_rt_color = gfx.make_image( Image_Desc { + ctx.glyph_rt_color = gfx.make_image( Image_Desc { type = ._2D, render_target = true, - width = i32(ve_ctx.glyph_buffer.width), - height = i32(ve_ctx.glyph_buffer.height), + 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, }) - assert( gfx.query_image_state(glyph_rt_color) < Resource_State.FAILED, "Failed to make glyph_pipeline" ) + assert( gfx.query_image_state(ctx.glyph_rt_color) < Resource_State.FAILED, "Failed to make glyph_pipeline" ) - glyph_rt_depth = gfx.make_image( Image_Desc { + ctx.glyph_rt_depth = gfx.make_image( Image_Desc { type = ._2D, render_target = true, - width = i32(ve_ctx.glyph_buffer.width), - height = i32(ve_ctx.glyph_buffer.height), + width = i32(ve_ctx.glyph_buffer.size.x), + height = i32(ve_ctx.glyph_buffer.size.y), num_slices = 1, num_mipmaps = 1, usage = .IMMUTABLE, @@ -160,22 +160,22 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index sample_count = 1, }) - glyph_rt_sampler = gfx.make_sampler( Sampler_Description { + ctx.glyph_rt_sampler = gfx.make_sampler( Sampler_Description { min_filter = Image_Filter, mag_filter = Image_Filter, 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 = Border_Color.OPAQUE_BLACK, compare = .NEVER, max_anisotropy = 1, }) - assert( gfx.query_sampler_state( glyph_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" ) + assert( gfx.query_sampler_state( ctx.glyph_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" ) color_attach := Attachment_Desc { - image = glyph_rt_color, + image = ctx.glyph_rt_color, } glyph_attachments := gfx.make_attachments({ @@ -183,7 +183,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index 0 = color_attach, }, depth_stencil = { - image = glyph_rt_depth, + image = ctx.glyph_rt_depth, }, }) assert( gfx.query_attachments_state(glyph_attachments) < Resource_State.FAILED, "Failed to make glyph_attachments" ) @@ -208,7 +208,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index } } - glyph_pass = gfx.Pass { + ctx.glyph_pass = gfx.Pass { action = glyph_action, attachments = glyph_attachments, // label = @@ -220,12 +220,12 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index vs_layout : Vertex_Layout_State { using vs_layout - attrs[ATTR_blit_atlas_v_position] = Vertex_Attribute_State { + attrs[ATTR_ve_blit_atlas_v_position] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = 0, buffer_index = 0, } - attrs[ATTR_blit_atlas_v_texture] = Vertex_Attribute_State { + attrs[ATTR_ve_blit_atlas_v_texture] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = size_of(ve.Vec2), buffer_index = 0, @@ -250,8 +250,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index }, } - atlas_pipeline = gfx.make_pipeline({ - shader = atlas_shader, + ctx.atlas_pipeline = gfx.make_pipeline({ + shader = ctx.atlas_shader, layout = vs_layout, index_type = Vertex_Index_Type.UINT32, colors = { @@ -270,11 +270,11 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index // atlas_pass { - atlas_rt_color = gfx.make_image( Image_Desc { + ctx.atlas_rt_color = gfx.make_image( Image_Desc { type = ._2D, render_target = true, - width = i32(ve_ctx.atlas.width), - height = i32(ve_ctx.atlas.height), + width = i32(ve_ctx.atlas.size.x), + height = i32(ve_ctx.atlas.size.y), num_slices = 1, num_mipmaps = 1, usage = .IMMUTABLE, @@ -283,22 +283,22 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index // TODO(Ed): Setup labels for debug tracing/logging // label = }) - assert( gfx.query_image_state(atlas_rt_color) < Resource_State.FAILED, "Failed to make atlas_rt_color") + assert( gfx.query_image_state(ctx.atlas_rt_color) < Resource_State.FAILED, "Failed to make atlas_rt_color") - atlas_rt_depth = gfx.make_image( Image_Desc { + ctx.atlas_rt_depth = gfx.make_image( Image_Desc { type = ._2D, render_target = true, - width = i32(ve_ctx.atlas.width), - height = i32(ve_ctx.atlas.height), + 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, }) - assert( gfx.query_image_state(atlas_rt_depth) < Resource_State.FAILED, "Failed to make atlas_rt_depth") + assert( gfx.query_image_state(ctx.atlas_rt_depth) < Resource_State.FAILED, "Failed to make atlas_rt_depth") - atlas_rt_sampler = gfx.make_sampler( Sampler_Description { + ctx.atlas_rt_sampler = gfx.make_sampler( Sampler_Description { min_filter = Image_Filter, mag_filter = Image_Filter, mipmap_filter = Filter.NEAREST, @@ -310,10 +310,10 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index compare = .NEVER, max_anisotropy = 1, }) - assert( gfx.query_sampler_state( atlas_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" ) + assert( gfx.query_sampler_state( ctx.atlas_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" ) color_attach := Attachment_Desc { - image = atlas_rt_color, + image = ctx.atlas_rt_color, } atlas_attachments := gfx.make_attachments({ @@ -321,7 +321,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index 0 = color_attach, }, depth_stencil = { - image = atlas_rt_depth, + image = ctx.atlas_rt_depth, }, }) assert( gfx.query_attachments_state(atlas_attachments) < Resource_State.FAILED, "Failed to make atlas_attachments") @@ -346,7 +346,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index } } - atlas_pass = gfx.Pass { + ctx.atlas_pass = gfx.Pass { action = atlas_action, attachments = atlas_attachments, } @@ -357,12 +357,12 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index vs_layout : Vertex_Layout_State { using vs_layout - attrs[ATTR_draw_text_v_position] = Vertex_Attribute_State { + attrs[ATTR_ve_draw_text_v_position] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = 0, buffer_index = 0, } - attrs[ATTR_draw_text_v_texture] = Vertex_Attribute_State { + attrs[ATTR_ve_draw_text_v_texture] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = size_of(ve.Vec2), buffer_index = 0, @@ -387,8 +387,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index }, } - screen_pipeline = gfx.make_pipeline({ - shader = screen_shader, + ctx.screen_pipeline = gfx.make_pipeline({ + shader = ctx.screen_shader, layout = vs_layout, index_type = Vertex_Index_Type.UINT32, colors = { @@ -403,7 +403,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index }, cull_mode = .NONE, }) - assert( gfx.query_pipeline_state(screen_pipeline) < Resource_State.FAILED, "Failed to make screen_pipeline" ) + assert( gfx.query_pipeline_state(ctx.screen_pipeline) < Resource_State.FAILED, "Failed to make screen_pipeline" ) } // screen_pass @@ -438,7 +438,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index } } - screen_pass = gfx.Pass { + ctx.screen_pass = gfx.Pass { action = screen_action, } } @@ -483,8 +483,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : continue } - width := ve_ctx.glyph_buffer.width - height := ve_ctx.glyph_buffer.height + width := ve_ctx.glyph_buffer.size.x + height := ve_ctx.glyph_buffer.size.y pass := glyph_pass if draw_call.clear_before_draw { @@ -518,8 +518,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : continue } - width := ve_ctx.atlas.width - height := ve_ctx.atlas.height + width := ve_ctx.atlas.size.x + height := ve_ctx.atlas.size.y pass := atlas_pass if draw_call.clear_before_draw { @@ -534,8 +534,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : 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, + glyph_buffer_size = ve.vec2(ve_ctx.glyph_buffer.size), + over_sample = ve_ctx.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) }) @@ -575,7 +575,7 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : fs_target_uniform := Ve_Draw_Text_Fs_Params { // glyph_buffer_size = glyph_buf_size, - over_sample = glyph_buffer.over_sample.x, + over_sample = ve_ctx.glyph_buffer.over_sample.x, colour = draw_call.colour, } diff --git a/backend/sokol/blit_atlas.odin b/backend/sokol/blit_atlas.odin index 42a8cc5..9aa257e 100644 --- a/backend/sokol/blit_atlas.odin +++ b/backend/sokol/blit_atlas.odin @@ -10,35 +10,36 @@ import sg "thirdparty:sokol/gfx" Overview: ========= - Shader program: 'blit_atlas': - Get shader desc: blit_atlas_shader_desc(sg.query_backend()) - Vertex Shader: blit_atlas_vs - Fragment Shader: blit_atlas_fs + Shader program: 've_blit_atlas': + Get shader desc: ve_blit_atlas_shader_desc(sg.query_backend()) + Vertex Shader: ve_blit_atlas_vs + Fragment Shader: ve_blit_atlas_fs Attributes: - ATTR_blit_atlas_v_position => 0 - ATTR_blit_atlas_v_texture => 1 + ATTR_ve_blit_atlas_v_position => 0 + ATTR_ve_blit_atlas_v_texture => 1 Bindings: - Uniform block 'blit_atlas_fs_params': - Odin struct: Blit_Atlas_Fs_Params - Bind slot: UB_blit_atlas_fs_params => 0 - Image 'blit_atlas_src_texture': + Uniform block 've_blit_atlas_fs_params': + Odin struct: Ve_Blit_Atlas_Fs_Params + Bind slot: UB_ve_blit_atlas_fs_params => 0 + Image 've_blit_atlas_src_texture': Image type: ._2D Sample type: .FLOAT Multisampled: false - Bind slot: IMG_blit_atlas_src_texture => 0 - Sampler 'blit_atlas_src_sampler': + Bind slot: IMG_ve_blit_atlas_src_texture => 0 + Sampler 've_blit_atlas_src_sampler': Type: .FILTERING - Bind slot: SMP_blit_atlas_src_sampler => 0 + Bind slot: SMP_ve_blit_atlas_src_sampler => 0 */ -ATTR_blit_atlas_v_position :: 0 -ATTR_blit_atlas_v_texture :: 1 -UB_blit_atlas_fs_params :: 0 -IMG_blit_atlas_src_texture :: 0 -SMP_blit_atlas_src_sampler :: 0 -Blit_Atlas_Fs_Params :: struct #align(16) { +ATTR_ve_blit_atlas_v_position :: 0 +ATTR_ve_blit_atlas_v_texture :: 1 +UB_ve_blit_atlas_fs_params :: 0 +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, }, } /* @@ -56,7 +57,7 @@ Blit_Atlas_Fs_Params :: struct #align(16) { */ @(private="file") -blit_atlas_vs_source_glsl410 := [235]u8 { +ve_blit_atlas_vs_source_glsl410 := [235]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, @@ -76,49 +77,67 @@ blit_atlas_vs_source_glsl410 := [235]u8 { /* #version 410 - uniform ivec4 blit_atlas_fs_params[1]; - uniform sampler2D blit_atlas_src_texture_blit_atlas_src_sampler; + struct ve_blit_atlas_fs_params + { + vec2 glyph_buffer_size; + float over_sample; + int region; + }; + + uniform ve_blit_atlas_fs_params _20; + + uniform sampler2D ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler; layout(location = 0) in vec2 uv; layout(location = 0) out vec4 frag_color; - float down_sample(vec2 uv_1, vec2 texture_size) + float down_sample_to_texture(vec2 uv_1, vec2 texture_size) { - return 0.25 * (((texture(blit_atlas_src_texture_blit_atlas_src_sampler, uv_1).x + texture(blit_atlas_src_texture_blit_atlas_src_sampler, fma(vec2(0.0, 1.0), texture_size, uv_1)).x) + texture(blit_atlas_src_texture_blit_atlas_src_sampler, fma(vec2(1.0, 0.0), texture_size, uv_1)).x) + texture(blit_atlas_src_texture_blit_atlas_src_sampler, uv_1 + texture_size).x); + return (1.0 / _20.over_sample) * (((texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, uv_1).x + texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, fma(vec2(0.0, 1.0), texture_size, uv_1)).x) + texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, fma(vec2(1.0, 0.0), texture_size, uv_1)).x) + texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, uv_1 + texture_size).x); } void main() { - bool _93 = blit_atlas_fs_params[0].x == 0; - bool _101; - if (!_93) + vec2 _98 = vec2(1.0) / _20.glyph_buffer_size; + bool _104 = _20.region == 0; + bool _111; + if (!_104) { - _101 = blit_atlas_fs_params[0].x == 1; + _111 = _20.region == 1; } else { - _101 = _93; + _111 = _104; } - bool _109; - if (!_101) + bool _118; + if (!_111) { - _109 = blit_atlas_fs_params[0].x == 2; + _118 = _20.region == 2; } else { - _109 = _101; + _118 = _111; } - if (_109) + bool _126; + if (!_118) { - vec2 param = uv + vec2(-0.00048828125, -0.0029296875); - vec2 param_1 = vec2(0.00048828125, 0.001953125); - vec2 param_2 = uv + vec2(0.000244140625, -0.0029296875); - vec2 param_3 = vec2(0.00048828125, 0.001953125); - vec2 param_4 = uv + vec2(-0.000732421875, 0.0009765625); - vec2 param_5 = vec2(0.00048828125, 0.001953125); - vec2 param_6 = uv + vec2(0.000244140625, 0.0009765625); - vec2 param_7 = vec2(0.00048828125, 0.001953125); - frag_color = vec4(1.0, 1.0, 1.0, 0.25 * (((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) + { + vec2 param = uv + (vec2(-1.0, -1.5) / _20.glyph_buffer_size); + vec2 param_1 = _98; + vec2 param_2 = uv + (vec2(0.5, -1.5) / _20.glyph_buffer_size); + vec2 param_3 = _98; + vec2 param_4 = uv + (vec2(-1.5, 0.5) / _20.glyph_buffer_size); + vec2 param_5 = _98; + vec2 param_6 = uv + (vec2(0.5) / _20.glyph_buffer_size); + vec2 param_7 = _98; + frag_color = vec4(1.0, 1.0, 1.0, (1.0 / _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 { @@ -128,114 +147,130 @@ blit_atlas_vs_source_glsl410 := [235]u8 { */ @(private="file") -blit_atlas_fs_source_glsl410 := [1700]u8 { - 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, - 0x69,0x66,0x6f,0x72,0x6d,0x20,0x69,0x76,0x65,0x63,0x34,0x20,0x62,0x6c,0x69,0x74, - 0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, - 0x5b,0x31,0x5d,0x3b,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61, - 0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x62,0x6c, - 0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, - 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76, - 0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, - 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74, - 0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, - 0x3b,0x0a,0x0a,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61, - 0x6d,0x70,0x6c,0x65,0x28,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x5f,0x31,0x2c,0x20, - 0x76,0x65,0x63,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,0x20,0x2a,0x20,0x28,0x28,0x28,0x74,0x65,0x78,0x74,0x75,0x72, - 0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63, - 0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72, - 0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61, +ve_blit_atlas_fs_source_glsl410 := [1954]u8 { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c, + 0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x67,0x6c,0x79,0x70,0x68,0x5f,0x62,0x75, + 0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x72,0x65,0x67,0x69,0x6f,0x6e, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,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,0x5f,0x32,0x30,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66, + 0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,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,0x5f,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,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,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,0x76,0x65,0x63, + 0x32,0x20,0x75,0x76,0x5f,0x31,0x2c,0x20,0x76,0x65,0x63,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,0x20,0x2f,0x20,0x5f, + 0x32,0x30,0x2e,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x29,0x20, + 0x2a,0x20,0x28,0x28,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x66,0x6d,0x61,0x28,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x2c,0x20, - 0x31,0x2e,0x30,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, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c, - 0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x66,0x6d,0x61,0x28,0x76,0x65,0x63,0x32,0x28, - 0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x62,0x6c,0x69, + 0x2c,0x20,0x75,0x76,0x5f,0x31,0x29,0x2e,0x78,0x20,0x2b,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,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,0x5f,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,0x66,0x6d,0x61,0x28,0x76,0x65,0x63, + 0x32,0x28,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x66,0x6d,0x61,0x28,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30, + 0x2c,0x20,0x30,0x2e,0x30,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,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,0x6d,0x61,0x69, - 0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f, - 0x39,0x33,0x20,0x3d,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f, - 0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,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, + 0x75,0x72,0x65,0x5f,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, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x32,0x20,0x5f,0x39,0x38,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30, + 0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x2e,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,0x2e,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,0x2e,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,0x30,0x31,0x20,0x3d,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61, - 0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78, - 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,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74, - 0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d, - 0x2e,0x78,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,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20, - 0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x30, - 0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x30, - 0x30,0x32,0x39,0x32,0x39,0x36,0x38,0x37,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31, - 0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38, - 0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31, - 0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63, - 0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b, - 0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34, - 0x30,0x36,0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x32,0x39,0x32,0x39,0x36, - 0x38,0x37,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65, - 0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x33,0x20,0x3d,0x20,0x76,0x65,0x63, - 0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c, - 0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x29,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61, - 0x6d,0x5f,0x34,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28, - 0x2d,0x30,0x2e,0x30,0x30,0x30,0x37,0x33,0x32,0x34,0x32,0x31,0x38,0x37,0x35,0x2c, - 0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x3b,0x0a, - 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72, - 0x61,0x6d,0x5f,0x35,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30, - 0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31, - 0x39,0x35,0x33,0x31,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20, - 0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3d,0x20, - 0x75,0x76,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32, - 0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39, - 0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20, - 0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3d,0x20, - 0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31, - 0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x29, - 0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, - 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30,0x2c, - 0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x32,0x35,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, - 0x76,0x65,0x63,0x34,0x28,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30, - 0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a, - 0x7d,0x0a,0x0a,0x00, + 0x5f,0x31,0x32,0x36,0x20,0x3d,0x20,0x5f,0x32,0x30,0x2e,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,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3d, + 0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x28,0x2d,0x31,0x2e,0x30, + 0x2c,0x20,0x2d,0x31,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x76,0x65,0x63,0x32,0x20,0x70, + 0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x5f,0x39,0x38,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d, + 0x5f,0x32,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x28, + 0x30,0x2e,0x35,0x2c,0x20,0x2d,0x31,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30, + 0x2e,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,0x76,0x65,0x63, + 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,0x76,0x65,0x63,0x32,0x20,0x70,0x61, + 0x72,0x61,0x6d,0x5f,0x34,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65, + 0x63,0x32,0x28,0x2d,0x31,0x2e,0x35,0x2c,0x20,0x30,0x2e,0x35,0x29,0x20,0x2f,0x20, + 0x5f,0x32,0x30,0x2e,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, + 0x76,0x65,0x63,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,0x76,0x65,0x63,0x32, + 0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20, + 0x28,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30, + 0x2e,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,0x76,0x65,0x63, + 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,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20, + 0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x28,0x31,0x2e,0x30,0x20,0x2f, + 0x20,0x5f,0x32,0x30,0x2e,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,0x76,0x65, + 0x63,0x34,0x28,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x7d,0x0a, + 0x0a,0x00, } /* #version 300 es @@ -252,7 +287,7 @@ blit_atlas_fs_source_glsl410 := [1700]u8 { */ @(private="file") -blit_atlas_vs_source_glsl300es := [217]u8 { +ve_blit_atlas_vs_source_glsl300es := [217]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61, 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, @@ -273,49 +308,67 @@ blit_atlas_vs_source_glsl300es := [217]u8 { precision mediump float; precision highp int; - uniform ivec4 blit_atlas_fs_params[1]; - uniform highp sampler2D blit_atlas_src_texture_blit_atlas_src_sampler; + struct ve_blit_atlas_fs_params + { + highp vec2 glyph_buffer_size; + highp float over_sample; + int region; + }; + + uniform ve_blit_atlas_fs_params _20; + + uniform highp sampler2D ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler; in highp vec2 uv; layout(location = 0) out highp vec4 frag_color; - highp float down_sample(highp vec2 uv_1, highp vec2 texture_size) + highp float down_sample_to_texture(highp vec2 uv_1, highp vec2 texture_size) { - return 0.25 * (((texture(blit_atlas_src_texture_blit_atlas_src_sampler, uv_1).x + texture(blit_atlas_src_texture_blit_atlas_src_sampler, vec2(0.0, 1.0) * texture_size + uv_1).x) + texture(blit_atlas_src_texture_blit_atlas_src_sampler, vec2(1.0, 0.0) * texture_size + uv_1).x) + texture(blit_atlas_src_texture_blit_atlas_src_sampler, uv_1 + texture_size).x); + return (1.0 / _20.over_sample) * (((texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, uv_1).x + texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, vec2(0.0, 1.0) * texture_size + uv_1).x) + texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, vec2(1.0, 0.0) * texture_size + uv_1).x) + texture(ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler, uv_1 + texture_size).x); } void main() { - bool _93 = blit_atlas_fs_params[0].x == 0; - bool _101; - if (!_93) + highp vec2 _98 = vec2(1.0) / _20.glyph_buffer_size; + bool _104 = _20.region == 0; + bool _111; + if (!_104) { - _101 = blit_atlas_fs_params[0].x == 1; + _111 = _20.region == 1; } else { - _101 = _93; + _111 = _104; } - bool _109; - if (!_101) + bool _118; + if (!_111) { - _109 = blit_atlas_fs_params[0].x == 2; + _118 = _20.region == 2; } else { - _109 = _101; + _118 = _111; } - if (_109) + bool _126; + if (!_118) { - highp vec2 param = uv + vec2(-0.00048828125, -0.0029296875); - highp vec2 param_1 = vec2(0.00048828125, 0.001953125); - highp vec2 param_2 = uv + vec2(0.000244140625, -0.0029296875); - highp vec2 param_3 = vec2(0.00048828125, 0.001953125); - highp vec2 param_4 = uv + vec2(-0.000732421875, 0.0009765625); - highp vec2 param_5 = vec2(0.00048828125, 0.001953125); - highp vec2 param_6 = uv + vec2(0.000244140625, 0.0009765625); - highp vec2 param_7 = vec2(0.00048828125, 0.001953125); - frag_color = vec4(1.0, 1.0, 1.0, 0.25 * (((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) + { + highp vec2 param = uv + (vec2(-1.0, -1.5) / _20.glyph_buffer_size); + highp vec2 param_1 = _98; + highp vec2 param_2 = uv + (vec2(0.5, -1.5) / _20.glyph_buffer_size); + highp vec2 param_3 = _98; + highp vec2 param_4 = uv + (vec2(-1.5, 0.5) / _20.glyph_buffer_size); + highp vec2 param_5 = _98; + highp vec2 param_6 = uv + (vec2(0.5) / _20.glyph_buffer_size); + highp vec2 param_7 = _98; + frag_color = vec4(1.0, 1.0, 1.0, (1.0 / _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 { @@ -325,114 +378,131 @@ blit_atlas_vs_source_glsl300es := [217]u8 { */ @(private="file") -blit_atlas_fs_source_glsl300es := [1806]u8 { +ve_blit_atlas_fs_source_glsl300es := [2078]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, - 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, - 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x69,0x76,0x65,0x63,0x34,0x20,0x62,0x6c,0x69, - 0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, - 0x73,0x5b,0x31,0x5d,0x3b,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69, - 0x67,0x68,0x70,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x62,0x6c, - 0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f, - 0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x69,0x6e, - 0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, - 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, - 0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76, - 0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, - 0x0a,0x68,0x69,0x67,0x68,0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x6f,0x77, - 0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x68,0x69,0x67,0x68,0x70,0x20,0x76, - 0x65,0x63,0x32,0x20,0x75,0x76,0x5f,0x31,0x2c,0x20,0x68,0x69,0x67,0x68,0x70,0x20, - 0x76,0x65,0x63,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,0x20,0x2a,0x20,0x28,0x28,0x28,0x74,0x65,0x78,0x74,0x75,0x72, - 0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63, - 0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72, - 0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29, - 0x20,0x2a,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x20, - 0x2b,0x20,0x75,0x76,0x5f,0x31,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f, - 0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,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,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e, - 0x30,0x29,0x20,0x2a,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a, - 0x65,0x20,0x2b,0x20,0x75,0x76,0x5f,0x31,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74, - 0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61, - 0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x62,0x6c, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74, + 0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x67, + 0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x20,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x72,0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,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,0x5f,0x32,0x30,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d, + 0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44, + 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,0x5f,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,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a, - 0x20,0x20,0x20,0x20,0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x39,0x33,0x20,0x3d,0x20,0x62, - 0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72, - 0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,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,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70, - 0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,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,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73, - 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,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,0x68, - 0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x20, - 0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x30, - 0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x30, - 0x30,0x32,0x39,0x32,0x39,0x36,0x38,0x37,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70, - 0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e, - 0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30, - 0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20, - 0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61, - 0x72,0x61,0x6d,0x5f,0x32,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x76,0x65,0x63, - 0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35, - 0x2c,0x20,0x2d,0x30,0x2e,0x30,0x30,0x32,0x39,0x32,0x39,0x36,0x38,0x37,0x35,0x29, - 0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20, - 0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x33,0x20,0x3d,0x20,0x76, - 0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32, - 0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x29,0x3b, - 0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76, - 0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x20,0x3d,0x20,0x75,0x76, - 0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x30,0x30,0x30,0x37,0x33, - 0x32,0x34,0x32,0x31,0x38,0x37,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37, - 0x36,0x35,0x36,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75, + 0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61, + 0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x68,0x69,0x67,0x68,0x70,0x20, + 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,0x68,0x69,0x67, + 0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x5f,0x31,0x2c,0x20,0x68,0x69, + 0x67,0x68,0x70,0x20,0x76,0x65,0x63,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,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,0x6f, + 0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x29,0x20,0x2a,0x20,0x28,0x28, + 0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x20,0x2a,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69, + 0x7a,0x65,0x20,0x2b,0x20,0x75,0x76,0x5f,0x31,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x76,0x65,0x63, + 0x32,0x28,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x20,0x2a,0x20,0x74,0x65, + 0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x20,0x2b,0x20,0x75,0x76,0x5f, + 0x31,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,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,0x5f,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,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x5f,0x39, + 0x38,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x29,0x20,0x2f,0x20, + 0x5f,0x32,0x30,0x2e,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,0x2e,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,0x2e,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,0x2e,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,0x2e,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, 0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d, - 0x5f,0x35,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30,0x30,0x30,0x34, - 0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35, - 0x33,0x31,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68, - 0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f, - 0x36,0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e, - 0x30,0x30,0x30,0x32,0x34,0x34,0x31,0x34,0x30,0x36,0x32,0x35,0x2c,0x20,0x30,0x2e, - 0x30,0x30,0x30,0x39,0x37,0x36,0x35,0x36,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20, - 0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x30, - 0x2e,0x30,0x30,0x30,0x34,0x38,0x38,0x32,0x38,0x31,0x32,0x35,0x2c,0x20,0x30,0x2e, - 0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, - 0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20, - 0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x32,0x35,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, + 0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x28,0x2d,0x31, + 0x2e,0x30,0x2c,0x20,0x2d,0x31,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e, + 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,0x68,0x69,0x67,0x68, + 0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d, + 0x20,0x5f,0x39,0x38,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68,0x69, + 0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32, + 0x20,0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x28,0x30,0x2e, + 0x35,0x2c,0x20,0x2d,0x31,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x68,0x69,0x67,0x68,0x70, + 0x20,0x76,0x65,0x63,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,0x68,0x69,0x67, + 0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x20, + 0x3d,0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x28,0x2d,0x31,0x2e, + 0x35,0x2c,0x20,0x30,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,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,0x68,0x69,0x67,0x68, + 0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3d, + 0x20,0x75,0x76,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29, + 0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,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,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c, + 0x20,0x31,0x2e,0x30,0x2c,0x20,0x28,0x31,0x2e,0x30,0x20,0x2f,0x20,0x5f,0x32,0x30, + 0x2e,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, @@ -476,7 +546,7 @@ blit_atlas_fs_source_glsl300es := [1806]u8 { } */ @(private="file") -blit_atlas_vs_source_hlsl4 := [705]u8 { +ve_blit_atlas_vs_source_hlsl4 := [705]u8 { 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,0x0a,0x73,0x74,0x61, @@ -524,13 +594,15 @@ blit_atlas_vs_source_hlsl4 := [705]u8 { 0x00, } /* - cbuffer blit_atlas_fs_params : register(b0) + 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 blit_atlas_src_texture : register(t0); - SamplerState blit_atlas_src_sampler : register(s0); + Texture2D ve_blit_atlas_src_texture : register(t0); + SamplerState ve_blit_atlas_src_sampler : register(s0); static float2 uv; static float4 frag_color; @@ -545,43 +617,53 @@ 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 * (((blit_atlas_src_texture.Sample(blit_atlas_src_sampler, uv_1).x + blit_atlas_src_texture.Sample(blit_atlas_src_sampler, mad(float2(0.0f, 1.0f), texture_size, uv_1)).x) + blit_atlas_src_texture.Sample(blit_atlas_src_sampler, mad(float2(1.0f, 0.0f), texture_size, uv_1)).x) + blit_atlas_src_texture.Sample(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 { @@ -599,139 +681,156 @@ blit_atlas_vs_source_hlsl4 := [705]u8 { } */ @(private="file") -blit_atlas_fs_source_hlsl4 := [2107]u8 { - 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,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,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,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,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,0x62,0x6c,0x69,0x74,0x5f,0x61, +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,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,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,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,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,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,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,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,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, + 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,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,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,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,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, } /* #include @@ -761,7 +860,7 @@ blit_atlas_fs_source_hlsl4 := [2107]u8 { */ @(private="file") -blit_atlas_vs_source_metal_macos := [473]u8 { +ve_blit_atlas_vs_source_metal_macos := [473]u8 { 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, @@ -801,8 +900,10 @@ blit_atlas_vs_source_metal_macos := [473]u8 { using namespace metal; - struct blit_atlas_fs_params + struct ve_blit_atlas_fs_params { + float2 glyph_buffer_size; + float over_sample; int region; }; @@ -817,44 +918,54 @@ blit_atlas_vs_source_metal_macos := [473]u8 { }; static inline __attribute__((always_inline)) - float down_sample(thread const float2& uv, thread const float2& texture_size, texture2d blit_atlas_src_texture, sampler blit_atlas_src_sampler) + float down_sample_to_texture(thread const float2& uv, thread const float2& texture_size, constant ve_blit_atlas_fs_params& _20, texture2d ve_blit_atlas_src_texture, sampler ve_blit_atlas_src_sampler) { - return 0.25 * (((blit_atlas_src_texture.sample(blit_atlas_src_sampler, uv).x + blit_atlas_src_texture.sample(blit_atlas_src_sampler, fma(float2(0.0, 1.0), texture_size, uv)).x) + blit_atlas_src_texture.sample(blit_atlas_src_sampler, fma(float2(1.0, 0.0), texture_size, uv)).x) + blit_atlas_src_texture.sample(blit_atlas_src_sampler, (uv + texture_size)).x); + return (1.0 / _20.over_sample) * (((ve_blit_atlas_src_texture.sample(ve_blit_atlas_src_sampler, uv).x + ve_blit_atlas_src_texture.sample(ve_blit_atlas_src_sampler, fma(float2(0.0, 1.0), texture_size, uv)).x) + ve_blit_atlas_src_texture.sample(ve_blit_atlas_src_sampler, fma(float2(1.0, 0.0), texture_size, uv)).x) + ve_blit_atlas_src_texture.sample(ve_blit_atlas_src_sampler, (uv + texture_size)).x); } - fragment main0_out main0(main0_in in [[stage_in]], constant blit_atlas_fs_params& _88 [[buffer(0)]], texture2d blit_atlas_src_texture [[texture(0)]], sampler blit_atlas_src_sampler [[sampler(0)]]) + fragment main0_out main0(main0_in in [[stage_in]], constant ve_blit_atlas_fs_params& _20 [[buffer(0)]], texture2d ve_blit_atlas_src_texture [[texture(0)]], sampler ve_blit_atlas_src_sampler [[sampler(0)]]) { main0_out out = {}; - bool _93 = _88.region == 0; - bool _101; - if (!_93) + float2 _98 = float2(1.0) / _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 = in.uv + float2(-0.00048828125, -0.0029296875); - float2 param_1 = float2(0.00048828125, 0.001953125); - float2 param_2 = in.uv + float2(0.000244140625, -0.0029296875); - float2 param_3 = float2(0.00048828125, 0.001953125); - float2 param_4 = in.uv + float2(-0.000732421875, 0.0009765625); - float2 param_5 = float2(0.00048828125, 0.001953125); - float2 param_6 = in.uv + float2(0.000244140625, 0.0009765625); - float2 param_7 = float2(0.00048828125, 0.001953125); - out.frag_color = float4(1.0, 1.0, 1.0, 0.25 * (((down_sample(param, param_1, blit_atlas_src_texture, blit_atlas_src_sampler) + down_sample(param_2, param_3, blit_atlas_src_texture, blit_atlas_src_sampler)) + down_sample(param_4, param_5, blit_atlas_src_texture, blit_atlas_src_sampler)) + down_sample(param_6, param_7, blit_atlas_src_texture, blit_atlas_src_sampler))); + _126 = _20.region == 4; + } + else + { + _126 = _118; + } + if (_126) + { + float2 param = in.uv + (float2(-1.0, -1.5) / _20.glyph_buffer_size); + float2 param_1 = _98; + float2 param_2 = in.uv + (float2(0.5, -1.5) / _20.glyph_buffer_size); + float2 param_3 = _98; + float2 param_4 = in.uv + (float2(-1.5, 0.5) / _20.glyph_buffer_size); + float2 param_5 = _98; + float2 param_6 = in.uv + (float2(0.5) / _20.glyph_buffer_size); + float2 param_7 = _98; + out.frag_color = float4(1.0, 1.0, 1.0, (1.0 / _20.over_sample) * (((down_sample_to_texture(param, param_1, _20, ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler) + down_sample_to_texture(param_2, param_3, _20, ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler)) + down_sample_to_texture(param_4, param_5, _20, ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler)) + down_sample_to_texture(param_6, param_7, _20, ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler))); } else { @@ -865,7 +976,7 @@ blit_atlas_vs_source_metal_macos := [473]u8 { */ @(private="file") -blit_atlas_fs_source_metal_macos := [2373]u8 { +ve_blit_atlas_fs_source_metal_macos := [2713]u8 { 0x23,0x70,0x72,0x61,0x67,0x6d,0x61,0x20,0x63,0x6c,0x61,0x6e,0x67,0x20,0x64,0x69, 0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x20,0x69,0x67,0x6e,0x6f,0x72,0x65,0x64, 0x20,0x22,0x2d,0x57,0x6d,0x69,0x73,0x73,0x69,0x6e,0x67,0x2d,0x70,0x72,0x6f,0x74, @@ -874,147 +985,168 @@ blit_atlas_fs_source_metal_macos := [2373]u8 { 0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f, 0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a,0x75,0x73,0x69,0x6e,0x67,0x20,0x6e, 0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20,0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a, - 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c, - 0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20, - 0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x72,0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x7d, - 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, - 0x6f,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,0x5b,0x5b,0x63,0x6f, - 0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74, - 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a, - 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x5b,0x5b, - 0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, - 0x3b,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x69,0x6e,0x6c,0x69,0x6e,0x65, - 0x20,0x5f,0x5f,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x5f,0x5f,0x28,0x28, - 0x61,0x6c,0x77,0x61,0x79,0x73,0x5f,0x69,0x6e,0x6c,0x69,0x6e,0x65,0x29,0x29,0x0a, - 0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c, - 0x65,0x28,0x74,0x68,0x72,0x65,0x61,0x64,0x20,0x63,0x6f,0x6e,0x73,0x74,0x20,0x66, - 0x6c,0x6f,0x61,0x74,0x32,0x26,0x20,0x75,0x76,0x2c,0x20,0x74,0x68,0x72,0x65,0x61, - 0x64,0x20,0x63,0x6f,0x6e,0x73,0x74,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x26,0x20, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x74,0x65, - 0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x62, - 0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65, - 0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x62, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f, + 0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a, + 0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x67,0x6c,0x79, + 0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x6f,0x76,0x65,0x72,0x5f,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x72, + 0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,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,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x32,0x20,0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63, + 0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x69,0x6e,0x6c,0x69,0x6e,0x65,0x20,0x5f,0x5f,0x61,0x74,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x5f,0x5f,0x28,0x28,0x61,0x6c,0x77,0x61,0x79,0x73,0x5f,0x69, + 0x6e,0x6c,0x69,0x6e,0x65,0x29,0x29,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,0x74,0x68,0x72,0x65,0x61,0x64,0x20,0x63,0x6f,0x6e,0x73, + 0x74,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x26,0x20,0x75,0x76,0x2c,0x20,0x74,0x68, + 0x72,0x65,0x61,0x64,0x20,0x63,0x6f,0x6e,0x73,0x74,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x32,0x26,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c, + 0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69, + 0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x26,0x20,0x5f,0x32,0x30,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32, + 0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,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,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74, - 0x75,0x72,0x6e,0x20,0x30,0x2e,0x32,0x35,0x20,0x2a,0x20,0x28,0x28,0x28,0x62,0x6c, - 0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,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,0x29,0x2e,0x78,0x20,0x2b,0x20,0x62,0x6c,0x69,0x74, - 0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61, - 0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72, - 0x2c,0x20,0x66,0x6d,0x61,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30, - 0x2c,0x20,0x31,0x2e,0x30,0x29,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f, - 0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,0x78,0x29,0x20,0x2b,0x20, - 0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74, - 0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x62,0x6c, - 0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x72,0x2c,0x20,0x66,0x6d,0x61,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32, - 0x28,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x2c,0x20,0x74,0x65,0x78,0x74, - 0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,0x29,0x29,0x2e,0x78, - 0x29,0x20,0x2b,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73, + 0x75,0x72,0x6e,0x20,0x28,0x31,0x2e,0x30,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x73,0x61,0x6d,0x70,0x6c, - 0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63, - 0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x28,0x75,0x76,0x20,0x2b,0x20, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x29,0x29,0x2e,0x78, - 0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d, - 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d, - 0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61, - 0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e, - 0x74,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f, - 0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x38,0x38,0x20,0x5b,0x5b,0x62,0x75, - 0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x62,0x6c,0x69,0x74, + 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,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,0x73, + 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,0x66,0x6d,0x61,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x30,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73, + 0x69,0x7a,0x65,0x2c,0x20,0x75,0x76,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,0x73,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,0x66,0x6d,0x61,0x28,0x66, + 0x6c,0x6f,0x61,0x74,0x32,0x28,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x2c, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x2c,0x20,0x75, + 0x76,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,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d, - 0x5d,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x62,0x6c,0x69,0x74,0x5f, - 0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, - 0x72,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d, - 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75, - 0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20, - 0x62,0x6f,0x6f,0x6c,0x20,0x5f,0x39,0x33,0x20,0x3d,0x20,0x5f,0x38,0x38,0x2e,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,0x2e,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,0x2e,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,0x69,0x6e,0x2e, - 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,0x2c,0x20,0x2d,0x30,0x2e,0x30, - 0x30,0x32,0x39,0x32,0x39,0x36,0x38,0x37,0x35,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,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31, - 0x39,0x35,0x33,0x31,0x32,0x35,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,0x69,0x6e,0x2e,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,0x2c, - 0x20,0x2d,0x30,0x2e,0x30,0x30,0x32,0x39,0x32,0x39,0x36,0x38,0x37,0x35,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,0x2c,0x20, - 0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35,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,0x69,0x6e,0x2e,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,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30,0x39,0x37,0x36,0x35, - 0x36,0x32,0x35,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,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35,0x33,0x31,0x32,0x35, - 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,0x69,0x6e,0x2e,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,0x2c,0x20,0x30,0x2e,0x30,0x30,0x30, - 0x39,0x37,0x36,0x35,0x36,0x32,0x35,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,0x2c,0x20,0x30,0x2e,0x30,0x30,0x31,0x39,0x35, - 0x33,0x31,0x32,0x35,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x6f, - 0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20, - 0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c, - 0x20,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x32,0x35,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,0x2c,0x20,0x62,0x6c,0x69,0x74, - 0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x2c,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73, - 0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x2c,0x20,0x62,0x6c,0x69,0x74, - 0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75, - 0x72,0x65,0x2c,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73, - 0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x2c,0x20,0x62,0x6c,0x69, + 0x72,0x65,0x2e,0x73,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,0x28,0x75,0x76,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x29,0x29,0x2e,0x78,0x29,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e, + 0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x65,0x5f, + 0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x32,0x30,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66, + 0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,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,0x2c,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f, - 0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x2c,0x20,0x62,0x6c, - 0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78, - 0x74,0x75,0x72,0x65,0x2c,0x20,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73, - 0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x6f,0x75, - 0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66, - 0x6c,0x6f,0x61,0x74,0x34,0x28,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20, - 0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d, - 0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b, - 0x0a,0x7d,0x0a,0x0a,0x00, + 0x75,0x72,0x65,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29, + 0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,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,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28, + 0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x39,0x38,0x20,0x3d, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x31,0x2e,0x30,0x29,0x20,0x2f,0x20,0x5f, + 0x32,0x30,0x2e,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,0x2e,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,0x2e,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,0x2e,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,0x2e,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,0x69,0x6e, + 0x2e,0x75,0x76,0x20,0x2b,0x20,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x31, + 0x2e,0x30,0x2c,0x20,0x2d,0x31,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e, + 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,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,0x69,0x6e,0x2e,0x75,0x76, + 0x20,0x2b,0x20,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x30,0x2e,0x35,0x2c,0x20, + 0x2d,0x31,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x69,0x6e,0x2e,0x75,0x76,0x20,0x2b,0x20,0x28, + 0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x31,0x2e,0x35,0x2c,0x20,0x30,0x2e,0x35, + 0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x69,0x6e,0x2e,0x75,0x76,0x20,0x2b,0x20,0x28,0x66,0x6c,0x6f,0x61, + 0x74,0x32,0x28,0x30,0x2e,0x35,0x29,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28, + 0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x28, + 0x31,0x2e,0x30,0x20,0x2f,0x20,0x5f,0x32,0x30,0x2e,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,0x2c,0x20,0x5f,0x32,0x30,0x2c,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,0x2c,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,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,0x2c,0x20,0x5f,0x32,0x30,0x2c,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,0x2c,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,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,0x2c,0x20, + 0x5f,0x32,0x30,0x2c,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,0x2c,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,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,0x2c,0x20,0x5f,0x32,0x30,0x2c,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,0x2c,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, + 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,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x30,0x2e,0x30,0x2c,0x20,0x30, + 0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, + 0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } /* diagnostic(off, derivative_uniformity); @@ -1053,7 +1185,7 @@ blit_atlas_fs_source_metal_macos := [2373]u8 { */ @(private="file") -blit_atlas_vs_source_wgsl := [698]u8 { +ve_blit_atlas_vs_source_wgsl := [698]u8 { 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, @@ -1102,50 +1234,54 @@ blit_atlas_vs_source_wgsl := [698]u8 { /* diagnostic(off, derivative_uniformity); - struct blit_atlas_fs_params { + struct ve_blit_atlas_fs_params { /_ @offset(0) _/ + glyph_buffer_size : vec2f, + /_ @offset(8) _/ + over_sample : f32, + /_ @offset(12) _/ region : i32, } - @group(1) @binding(64) var blit_atlas_src_texture : texture_2d; + @group(0) @binding(8) var x_20 : ve_blit_atlas_fs_params; - @group(1) @binding(80) var blit_atlas_src_sampler : sampler; + @group(1) @binding(64) var ve_blit_atlas_src_texture : texture_2d; - @group(0) @binding(8) var x_88 : blit_atlas_fs_params; + @group(1) @binding(80) var ve_blit_atlas_src_sampler : sampler; var uv_1 : vec2f; var frag_color : vec4f; - fn down_sample_vf2_vf2_(uv : ptr, texture_size : ptr) -> f32 { - var down_sample_scale : f32; + fn down_sample_to_texture_vf2_vf2_(uv : ptr, texture_size : ptr) -> f32 { + var down_sample : f32; var value : f32; - down_sample_scale = 0.25f; - let x_28 : vec2f = *(uv); - let x_31 : vec2f = *(texture_size); - let x_35 : vec4f = textureSample(blit_atlas_src_texture, blit_atlas_src_sampler, (x_28 + (vec2f(0.0f, 0.0f) * x_31))); - let x_39 : f32 = down_sample_scale; - let x_44 : vec2f = *(uv); - let x_47 : vec2f = *(texture_size); - let x_50 : vec4f = textureSample(blit_atlas_src_texture, blit_atlas_src_sampler, (x_44 + (vec2f(0.0f, 1.0f) * x_47))); - let x_52 : f32 = down_sample_scale; - let x_58 : vec2f = *(uv); - let x_60 : vec2f = *(texture_size); - let x_63 : vec4f = textureSample(blit_atlas_src_texture, blit_atlas_src_sampler, (x_58 + (vec2f(1.0f, 0.0f) * x_60))); - let x_65 : f32 = down_sample_scale; - let x_71 : vec2f = *(uv); - let x_73 : vec2f = *(texture_size); - let x_76 : vec4f = textureSample(blit_atlas_src_texture, blit_atlas_src_sampler, (x_71 + (vec2f(1.0f, 1.0f) * x_73))); - let x_78 : f32 = down_sample_scale; - value = ((((x_35.x * x_39) + (x_50.x * x_52)) + (x_63.x * x_65)) + (x_76.x * x_78)); - let x_81 : f32 = value; - return x_81; + let x_24 : f32 = x_20.over_sample; + down_sample = (1.0f / x_24); + let x_37 : vec2f = *(uv); + let x_40 : vec2f = *(texture_size); + let x_44 : vec4f = textureSample(ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler, (x_37 + (vec2f(0.0f, 0.0f) * x_40))); + let x_48 : f32 = down_sample; + let x_53 : vec2f = *(uv); + let x_55 : vec2f = *(texture_size); + let x_58 : vec4f = textureSample(ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler, (x_53 + (vec2f(0.0f, 1.0f) * x_55))); + let x_60 : f32 = down_sample; + let x_66 : vec2f = *(uv); + let x_68 : vec2f = *(texture_size); + let x_71 : vec4f = textureSample(ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler, (x_66 + (vec2f(1.0f, 0.0f) * x_68))); + let x_73 : f32 = down_sample; + let x_79 : vec2f = *(uv); + let x_81 : vec2f = *(texture_size); + let x_84 : vec4f = textureSample(ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler, (x_79 + (vec2f(1.0f, 1.0f) * x_81))); + let x_86 : f32 = down_sample; + value = ((((x_44.x * x_48) + (x_58.x * x_60)) + (x_71.x * x_73)) + (x_84.x * x_86)); + let x_89 : f32 = value; + return x_89; } - const x_123 = vec2f(0.00048828125f, 0.001953125f); - fn main_1() { - var down_sample_scale_1 : f32; + var texture_size_1 : vec2f; + var down_sample_1 : f32; var alpha : f32; var param : vec2f; var param_1 : vec2f; @@ -1155,49 +1291,68 @@ blit_atlas_vs_source_wgsl := [698]u8 { var param_5 : vec2f; var param_6 : vec2f; var param_7 : vec2f; - var x_100 : bool; - var x_101 : bool; - var x_108 : bool; - var x_109 : bool; - let x_92 : i32 = x_88.region; - let x_93 : bool = (x_92 == 0i); - x_101 = x_93; - if (!(x_93)) { - let x_98 : i32 = x_88.region; - x_100 = (x_98 == 1i); - x_101 = x_100; + var x_110 : bool; + var x_111 : bool; + var x_117 : bool; + var x_118 : bool; + var x_125 : bool; + var x_126 : bool; + let x_96 : vec2f = x_20.glyph_buffer_size; + texture_size_1 = (vec2f(1.0f, 1.0f) / x_96); + let x_103 : i32 = x_20.region; + let x_104 : bool = (x_103 == 0i); + x_111 = x_104; + if (!(x_104)) { + let x_109 : i32 = x_20.region; + x_110 = (x_109 == 1i); + x_111 = x_110; } - x_109 = x_101; - if (!(x_101)) { - let x_106 : i32 = x_88.region; - x_108 = (x_106 == 2i); - x_109 = x_108; + x_118 = x_111; + if (!(x_111)) { + let x_116 : i32 = x_20.region; + x_117 = (x_116 == 2i); + x_118 = x_117; } - if (x_109) { - down_sample_scale_1 = 0.25f; - let x_116 : vec2f = uv_1; - param = (x_116 + vec2f(-0.00048828125f, -0.0029296875f)); - param_1 = x_123; - let x_126 : f32 = down_sample_vf2_vf2_(&(param), &(param_1)); - let x_127 : f32 = down_sample_scale_1; - let x_129 : vec2f = uv_1; - param_2 = (x_129 + vec2f(0.000244140625f, -0.0029296875f)); - param_3 = x_123; - let x_135 : f32 = down_sample_vf2_vf2_(&(param_2), &(param_3)); - let x_136 : f32 = down_sample_scale_1; - let x_139 : vec2f = uv_1; - param_4 = (x_139 + vec2f(-0.000732421875f, 0.0009765625f)); - param_5 = x_123; - let x_146 : f32 = down_sample_vf2_vf2_(&(param_4), &(param_5)); - let x_147 : f32 = down_sample_scale_1; - let x_150 : vec2f = uv_1; - param_6 = (x_150 + vec2f(0.000244140625f, 0.0009765625f)); - param_7 = x_123; - let x_155 : f32 = down_sample_vf2_vf2_(&(param_6), &(param_7)); - let x_156 : f32 = down_sample_scale_1; - alpha = ((((x_126 * x_127) + (x_135 * x_136)) + (x_146 * x_147)) + (x_155 * x_156)); - let x_161 : f32 = alpha; - frag_color = vec4f(1.0f, 1.0f, 1.0f, x_161); + x_126 = x_118; + if (!(x_118)) { + let x_123 : i32 = x_20.region; + x_125 = (x_123 == 4i); + x_126 = x_125; + } + if (x_126) { + let x_131 : f32 = x_20.over_sample; + down_sample_1 = (1.0f / x_131); + let x_136 : vec2f = uv_1; + let x_140 : vec2f = texture_size_1; + param = (x_136 + (vec2f(-1.0f, -1.5f) * x_140)); + let x_145 : vec2f = texture_size_1; + param_1 = x_145; + let x_146 : f32 = down_sample_to_texture_vf2_vf2_(&(param), &(param_1)); + let x_147 : f32 = down_sample_1; + let x_149 : vec2f = uv_1; + let x_152 : vec2f = texture_size_1; + param_2 = (x_149 + (vec2f(0.5f, -1.5f) * x_152)); + let x_157 : vec2f = texture_size_1; + param_3 = x_157; + let x_158 : f32 = down_sample_to_texture_vf2_vf2_(&(param_2), &(param_3)); + let x_159 : f32 = down_sample_1; + let x_162 : vec2f = uv_1; + let x_164 : vec2f = texture_size_1; + param_4 = (x_162 + (vec2f(-1.5f, 0.5f) * x_164)); + let x_169 : vec2f = texture_size_1; + param_5 = x_169; + let x_170 : f32 = down_sample_to_texture_vf2_vf2_(&(param_4), &(param_5)); + let x_171 : f32 = down_sample_1; + let x_174 : vec2f = uv_1; + let x_176 : vec2f = texture_size_1; + param_6 = (x_174 + (vec2f(0.5f, 0.5f) * x_176)); + let x_181 : vec2f = texture_size_1; + param_7 = x_181; + let x_182 : f32 = down_sample_to_texture_vf2_vf2_(&(param_6), &(param_7)); + let x_183 : f32 = down_sample_1; + alpha = ((((x_146 * x_147) + (x_158 * x_159)) + (x_170 * x_171)) + (x_182 * x_183)); + let x_188 : f32 = alpha; + frag_color = vec4f(1.0f, 1.0f, 1.0f, x_188); } else { frag_color = vec4f(0.0f, 0.0f, 0.0f, 1.0f); } @@ -1218,218 +1373,263 @@ blit_atlas_vs_source_wgsl := [698]u8 { */ @(private="file") -blit_atlas_fs_source_wgsl := [3640]u8 { +ve_blit_atlas_fs_source_wgsl := [4360]u8 { 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, - 0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61, - 0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66, - 0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x72,0x65,0x67,0x69, - 0x6f,0x6e,0x20,0x3a,0x20,0x69,0x33,0x32,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72, + 0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40, + 0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x67, + 0x6c,0x79,0x70,0x68,0x5f,0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40, + 0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x38,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x6f, + 0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x3a,0x20,0x66,0x33,0x32, + 0x2c,0x0a,0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x31, + 0x32,0x29,0x20,0x2a,0x2f,0x0a,0x20,0x20,0x72,0x65,0x67,0x69,0x6f,0x6e,0x20,0x3a, + 0x20,0x69,0x33,0x32,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28, + 0x30,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x38,0x29,0x20,0x76, + 0x61,0x72,0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x32,0x30, + 0x20,0x3a,0x20,0x76,0x65,0x5f,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73, + 0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x40,0x67,0x72, 0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28, - 0x36,0x34,0x29,0x20,0x76,0x61,0x72,0x20,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e, - 0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69, - 0x6e,0x64,0x69,0x6e,0x67,0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,0x20,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,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a, - 0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29,0x20,0x40,0x62,0x69,0x6e,0x64, - 0x69,0x6e,0x67,0x28,0x38,0x29,0x20,0x76,0x61,0x72,0x3c,0x75,0x6e,0x69,0x66,0x6f, - 0x72,0x6d,0x3e,0x20,0x78,0x5f,0x38,0x38,0x20,0x3a,0x20,0x62,0x6c,0x69,0x74,0x5f, - 0x61,0x74,0x6c,0x61,0x73,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b, - 0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75, - 0x76,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61, - 0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f, - 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, - 0x66,0x6e,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x76, + 0x36,0x34,0x29,0x20,0x76,0x61,0x72,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,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66, + 0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20, + 0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72, + 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,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x75,0x76,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x5f,0x74,0x6f,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x76, 0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28,0x75,0x76,0x20,0x3a,0x20,0x70,0x74,0x72, 0x3c,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x76,0x65,0x63,0x32,0x66, 0x3e,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x20, 0x3a,0x20,0x70,0x74,0x72,0x3c,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x2c,0x20, 0x76,0x65,0x63,0x32,0x66,0x3e,0x29,0x20,0x2d,0x3e,0x20,0x66,0x33,0x32,0x20,0x7b, 0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a, - 0x20,0x20,0x76,0x61,0x72,0x20,0x76,0x61,0x6c,0x75,0x65,0x20,0x3a,0x20,0x66,0x33, - 0x32,0x3b,0x0a,0x20,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, - 0x5f,0x73,0x63,0x61,0x6c,0x65,0x20,0x3d,0x20,0x30,0x2e,0x32,0x35,0x66,0x3b,0x0a, - 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x38,0x20,0x3a,0x20,0x76,0x65,0x63, - 0x32,0x66,0x20,0x3d,0x20,0x2a,0x28,0x75,0x76,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, - 0x74,0x20,0x78,0x5f,0x33,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d, - 0x20,0x2a,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x29, - 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x35,0x20,0x3a,0x20,0x76, - 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53,0x61, - 0x6d,0x70,0x6c,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f, - 0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x62,0x6c,0x69, - 0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d,0x70, - 0x6c,0x65,0x72,0x2c,0x20,0x28,0x78,0x5f,0x32,0x38,0x20,0x2b,0x20,0x28,0x76,0x65, - 0x63,0x32,0x66,0x28,0x30,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x29,0x20, - 0x2a,0x20,0x78,0x5f,0x33,0x31,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, - 0x20,0x78,0x5f,0x33,0x39,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f, - 0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65,0x3b, - 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x34,0x20,0x3a,0x20,0x76,0x65, + 0x6c,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20, + 0x76,0x61,0x6c,0x75,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20, + 0x78,0x5f,0x32,0x30,0x2e,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, + 0x3b,0x0a,0x20,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20, + 0x3d,0x20,0x28,0x31,0x2e,0x30,0x66,0x20,0x2f,0x20,0x78,0x5f,0x32,0x34,0x29,0x3b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x37,0x20,0x3a,0x20,0x76,0x65, 0x63,0x32,0x66,0x20,0x3d,0x20,0x2a,0x28,0x75,0x76,0x29,0x3b,0x0a,0x20,0x20,0x6c, - 0x65,0x74,0x20,0x78,0x5f,0x34,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20, + 0x65,0x74,0x20,0x78,0x5f,0x34,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20, 0x3d,0x20,0x2a,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65, - 0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x30,0x20,0x3a,0x20, + 0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x34,0x20,0x3a,0x20, 0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x53, - 0x61,0x6d,0x70,0x6c,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73, - 0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x62,0x6c, - 0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x72,0x2c,0x20,0x28,0x78,0x5f,0x34,0x34,0x20,0x2b,0x20,0x28,0x76, - 0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29, - 0x20,0x2a,0x20,0x78,0x5f,0x34,0x37,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, - 0x74,0x20,0x78,0x5f,0x35,0x32,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64, - 0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65, - 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x38,0x20,0x3a,0x20,0x76, + 0x61,0x6d,0x70,0x6c,0x65,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,0x2c, + 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,0x2c,0x20,0x28,0x78,0x5f,0x33, + 0x37,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x30,0x66,0x2c, + 0x20,0x30,0x2e,0x30,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x34,0x30,0x29,0x29,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x38,0x20,0x3a,0x20,0x66, + 0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x33,0x20,0x3a,0x20,0x76, 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x2a,0x28,0x75,0x76,0x29,0x3b,0x0a,0x20,0x20, - 0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, 0x20,0x3d,0x20,0x2a,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a, - 0x65,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x33,0x20,0x3a, + 0x65,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x38,0x20,0x3a, 0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, - 0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61, - 0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20,0x62, - 0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73,0x61, - 0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x28,0x78,0x5f,0x35,0x38,0x20,0x2b,0x20,0x28, - 0x76,0x65,0x63,0x32,0x66,0x28,0x31,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66, - 0x29,0x20,0x2a,0x20,0x78,0x5f,0x36,0x30,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c, - 0x65,0x74,0x20,0x78,0x5f,0x36,0x35,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20, - 0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c, - 0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x31,0x20,0x3a,0x20, + 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,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x2c,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,0x2c,0x20,0x28,0x78,0x5f, + 0x35,0x33,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x30,0x66, + 0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x35,0x35,0x29,0x29, + 0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x30,0x20,0x3a,0x20, + 0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x36,0x20,0x3a,0x20, 0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x2a,0x28,0x75,0x76,0x29,0x3b,0x0a,0x20, - 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x36,0x38,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, 0x66,0x20,0x3d,0x20,0x2a,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69, - 0x7a,0x65,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x36,0x20, + 0x7a,0x65,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x31,0x20, 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72, - 0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c, - 0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2c,0x20, - 0x62,0x6c,0x69,0x74,0x5f,0x61,0x74,0x6c,0x61,0x73,0x5f,0x73,0x72,0x63,0x5f,0x73, - 0x61,0x6d,0x70,0x6c,0x65,0x72,0x2c,0x20,0x28,0x78,0x5f,0x37,0x31,0x20,0x2b,0x20, - 0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, - 0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x37,0x33,0x29,0x29,0x29,0x3b,0x0a,0x20,0x20, - 0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x38,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d, - 0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61, - 0x6c,0x65,0x3b,0x0a,0x20,0x20,0x76,0x61,0x6c,0x75,0x65,0x20,0x3d,0x20,0x28,0x28, - 0x28,0x28,0x78,0x5f,0x33,0x35,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f,0x33,0x39,0x29, - 0x20,0x2b,0x20,0x28,0x78,0x5f,0x35,0x30,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f,0x35, - 0x32,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x36,0x33,0x2e,0x78,0x20,0x2a,0x20, - 0x78,0x5f,0x36,0x35,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x37,0x36,0x2e,0x78, - 0x20,0x2a,0x20,0x78,0x5f,0x37,0x38,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, - 0x20,0x78,0x5f,0x38,0x31,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x76,0x61, - 0x6c,0x75,0x65,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x78,0x5f, - 0x38,0x31,0x3b,0x0a,0x7d,0x0a,0x0a,0x63,0x6f,0x6e,0x73,0x74,0x20,0x78,0x5f,0x31, - 0x32,0x33,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x66,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,0x0a,0x66,0x6e,0x20,0x6d,0x61, - 0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x64, - 0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65, - 0x5f,0x31,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20, - 0x61,0x6c,0x70,0x68,0x61,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20,0x20,0x76, - 0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, - 0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20, - 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70, - 0x61,0x72,0x61,0x6d,0x5f,0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a, - 0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x33,0x20,0x3a,0x20, + 0x65,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,0x74,0x65,0x78,0x74,0x75,0x72, + 0x65,0x2c,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,0x2c,0x20,0x28,0x78, + 0x5f,0x36,0x36,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x31,0x2e,0x30, + 0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x36,0x38,0x29, + 0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x33,0x20,0x3a, + 0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70, + 0x6c,0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x37,0x39,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x2a,0x28,0x75,0x76,0x29,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x31,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x20,0x3d,0x20,0x2a,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73, + 0x69,0x7a,0x65,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x34, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,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,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x2c,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,0x2c,0x20,0x28, + 0x78,0x5f,0x37,0x39,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x31,0x2e, + 0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x38,0x31, + 0x29,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x38,0x36,0x20, + 0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x3b,0x0a,0x20,0x20,0x76,0x61,0x6c,0x75,0x65,0x20,0x3d,0x20,0x28, + 0x28,0x28,0x28,0x78,0x5f,0x34,0x34,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f,0x34,0x38, + 0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x35,0x38,0x2e,0x78,0x20,0x2a,0x20,0x78,0x5f, + 0x36,0x30,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x37,0x31,0x2e,0x78,0x20,0x2a, + 0x20,0x78,0x5f,0x37,0x33,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x38,0x34,0x2e, + 0x78,0x20,0x2a,0x20,0x78,0x5f,0x38,0x36,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x38,0x39,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x76, + 0x61,0x6c,0x75,0x65,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x78, + 0x5f,0x38,0x39,0x3b,0x0a,0x7d,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f, + 0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x5f,0x31,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x20, + 0x20,0x76,0x61,0x72,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3a,0x20,0x66,0x33,0x32, + 0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20, 0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72, - 0x61,0x6d,0x5f,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20, - 0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x35,0x20,0x3a,0x20,0x76,0x65, + 0x61,0x6d,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20, + 0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32,0x20,0x3a,0x20,0x76,0x65, 0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d, - 0x5f,0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61, - 0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, - 0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x78,0x5f,0x31,0x30,0x30,0x20,0x3a, - 0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x78,0x5f,0x31, - 0x30,0x31,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72, - 0x20,0x78,0x5f,0x31,0x30,0x38,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a,0x20, - 0x20,0x76,0x61,0x72,0x20,0x78,0x5f,0x31,0x30,0x39,0x20,0x3a,0x20,0x62,0x6f,0x6f, - 0x6c,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x32,0x20,0x3a,0x20, - 0x69,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x38,0x38,0x2e,0x72,0x65,0x67,0x69,0x6f, - 0x6e,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x33,0x20,0x3a,0x20, - 0x62,0x6f,0x6f,0x6c,0x20,0x3d,0x20,0x28,0x78,0x5f,0x39,0x32,0x20,0x3d,0x3d,0x20, - 0x30,0x69,0x29,0x3b,0x0a,0x20,0x20,0x78,0x5f,0x31,0x30,0x31,0x20,0x3d,0x20,0x78, - 0x5f,0x39,0x33,0x3b,0x0a,0x20,0x20,0x69,0x66,0x20,0x28,0x21,0x28,0x78,0x5f,0x39, - 0x33,0x29,0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, - 0x39,0x38,0x20,0x3a,0x20,0x69,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x38,0x38,0x2e, - 0x72,0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f,0x31,0x30, - 0x30,0x20,0x3d,0x20,0x28,0x78,0x5f,0x39,0x38,0x20,0x3d,0x3d,0x20,0x31,0x69,0x29, - 0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f,0x31,0x30,0x31,0x20,0x3d,0x20,0x78,0x5f, - 0x31,0x30,0x30,0x3b,0x0a,0x20,0x20,0x7d,0x0a,0x20,0x20,0x78,0x5f,0x31,0x30,0x39, - 0x20,0x3d,0x20,0x78,0x5f,0x31,0x30,0x31,0x3b,0x0a,0x20,0x20,0x69,0x66,0x20,0x28, - 0x21,0x28,0x78,0x5f,0x31,0x30,0x31,0x29,0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20, - 0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x30,0x36,0x20,0x3a,0x20,0x69,0x33,0x32,0x20, - 0x3d,0x20,0x78,0x5f,0x38,0x38,0x2e,0x72,0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x78,0x5f,0x31,0x30,0x38,0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x30, + 0x5f,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61, + 0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, + 0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x35, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20, + 0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b, + 0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x78,0x5f, + 0x31,0x31,0x30,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x76,0x61, + 0x72,0x20,0x78,0x5f,0x31,0x31,0x31,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a, + 0x20,0x20,0x76,0x61,0x72,0x20,0x78,0x5f,0x31,0x31,0x37,0x20,0x3a,0x20,0x62,0x6f, + 0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x78,0x5f,0x31,0x31,0x38,0x20, + 0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20,0x78,0x5f, + 0x31,0x32,0x35,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x76,0x61, + 0x72,0x20,0x78,0x5f,0x31,0x32,0x36,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x39,0x36,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x20,0x3d,0x20,0x78,0x5f,0x32,0x30,0x2e,0x67,0x6c,0x79,0x70,0x68,0x5f, + 0x62,0x75,0x66,0x66,0x65,0x72,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x20,0x3d,0x20, + 0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x20,0x2f,0x20,0x78,0x5f,0x39,0x36,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x31,0x30,0x33,0x20,0x3a,0x20,0x69,0x33,0x32,0x20,0x3d,0x20, + 0x78,0x5f,0x32,0x30,0x2e,0x72,0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x31,0x30,0x34,0x20,0x3a,0x20,0x62,0x6f,0x6f,0x6c,0x20, + 0x3d,0x20,0x28,0x78,0x5f,0x31,0x30,0x33,0x20,0x3d,0x3d,0x20,0x30,0x69,0x29,0x3b, + 0x0a,0x20,0x20,0x78,0x5f,0x31,0x31,0x31,0x20,0x3d,0x20,0x78,0x5f,0x31,0x30,0x34, + 0x3b,0x0a,0x20,0x20,0x69,0x66,0x20,0x28,0x21,0x28,0x78,0x5f,0x31,0x30,0x34,0x29, + 0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x30, + 0x39,0x20,0x3a,0x20,0x69,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x32,0x30,0x2e,0x72, + 0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f,0x31,0x31,0x30, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x30,0x39,0x20,0x3d,0x3d,0x20,0x31,0x69,0x29, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f,0x31,0x31,0x31,0x20,0x3d,0x20,0x78,0x5f, + 0x31,0x31,0x30,0x3b,0x0a,0x20,0x20,0x7d,0x0a,0x20,0x20,0x78,0x5f,0x31,0x31,0x38, + 0x20,0x3d,0x20,0x78,0x5f,0x31,0x31,0x31,0x3b,0x0a,0x20,0x20,0x69,0x66,0x20,0x28, + 0x21,0x28,0x78,0x5f,0x31,0x31,0x31,0x29,0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x31,0x36,0x20,0x3a,0x20,0x69,0x33,0x32,0x20, + 0x3d,0x20,0x78,0x5f,0x32,0x30,0x2e,0x72,0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x78,0x5f,0x31,0x31,0x37,0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x31, 0x36,0x20,0x3d,0x3d,0x20,0x32,0x69,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f, - 0x31,0x30,0x39,0x20,0x3d,0x20,0x78,0x5f,0x31,0x30,0x38,0x3b,0x0a,0x20,0x20,0x7d, - 0x0a,0x20,0x20,0x69,0x66,0x20,0x28,0x78,0x5f,0x31,0x30,0x39,0x29,0x20,0x7b,0x0a, + 0x31,0x31,0x38,0x20,0x3d,0x20,0x78,0x5f,0x31,0x31,0x37,0x3b,0x0a,0x20,0x20,0x7d, + 0x0a,0x20,0x20,0x78,0x5f,0x31,0x32,0x36,0x20,0x3d,0x20,0x78,0x5f,0x31,0x31,0x38, + 0x3b,0x0a,0x20,0x20,0x69,0x66,0x20,0x28,0x21,0x28,0x78,0x5f,0x31,0x31,0x38,0x29, + 0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x32, + 0x33,0x20,0x3a,0x20,0x69,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x32,0x30,0x2e,0x72, + 0x65,0x67,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f,0x31,0x32,0x35, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x32,0x33,0x20,0x3d,0x3d,0x20,0x34,0x69,0x29, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x78,0x5f,0x31,0x32,0x36,0x20,0x3d,0x20,0x78,0x5f, + 0x31,0x32,0x35,0x3b,0x0a,0x20,0x20,0x7d,0x0a,0x20,0x20,0x69,0x66,0x20,0x28,0x78, + 0x5f,0x31,0x32,0x36,0x29,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x31,0x33,0x31,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f, + 0x32,0x30,0x2e,0x6f,0x76,0x65,0x72,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3b,0x0a, 0x20,0x20,0x20,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f, - 0x73,0x63,0x61,0x6c,0x65,0x5f,0x31,0x20,0x3d,0x20,0x30,0x2e,0x32,0x35,0x66,0x3b, - 0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x31,0x36,0x20,0x3a, - 0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x5f,0x31,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x31, - 0x36,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,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,0x29,0x3b,0x0a,0x20,0x20,0x20, - 0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31,0x20,0x3d,0x20,0x78,0x5f,0x31,0x32,0x33, - 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x32,0x36,0x20, - 0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28,0x26,0x28,0x70, - 0x61,0x72,0x61,0x6d,0x29,0x2c,0x20,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x31, - 0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x32, - 0x37,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73, - 0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65,0x5f,0x31,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x32,0x39,0x20,0x3a,0x20,0x76, - 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20, - 0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32,0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x32, - 0x39,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,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,0x29,0x3b,0x0a,0x20,0x20,0x20, - 0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x33,0x20,0x3d,0x20,0x78,0x5f,0x31,0x32,0x33, - 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x33,0x35,0x20, - 0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d, - 0x70,0x6c,0x65,0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28,0x26,0x28,0x70, + 0x31,0x20,0x3d,0x20,0x28,0x31,0x2e,0x30,0x66,0x20,0x2f,0x20,0x78,0x5f,0x31,0x33, + 0x31,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x33, + 0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x5f,0x31, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x34,0x30,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72, + 0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x70,0x61, + 0x72,0x61,0x6d,0x20,0x3d,0x20,0x28,0x78,0x5f,0x31,0x33,0x36,0x20,0x2b,0x20,0x28, + 0x76,0x65,0x63,0x32,0x66,0x28,0x2d,0x31,0x2e,0x30,0x66,0x2c,0x20,0x2d,0x31,0x2e, + 0x35,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x31,0x34,0x30,0x29,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x34,0x35,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73, + 0x69,0x7a,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d, + 0x5f,0x31,0x20,0x3d,0x20,0x78,0x5f,0x31,0x34,0x35,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x34,0x36,0x20,0x3a,0x20,0x66,0x33,0x32,0x20, + 0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f, + 0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32, + 0x5f,0x28,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x29,0x2c,0x20,0x26,0x28,0x70,0x61, + 0x72,0x61,0x6d,0x5f,0x31,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x31,0x34,0x37,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64, + 0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x34,0x39,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x35,0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, + 0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65, + 0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x32,0x20, + 0x3d,0x20,0x28,0x78,0x5f,0x31,0x34,0x39,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32, + 0x66,0x28,0x30,0x2e,0x35,0x66,0x2c,0x20,0x2d,0x31,0x2e,0x35,0x66,0x29,0x20,0x2a, + 0x20,0x78,0x5f,0x31,0x35,0x32,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x31,0x35,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20, + 0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x33,0x20,0x3d,0x20, + 0x78,0x5f,0x31,0x35,0x37,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78, + 0x5f,0x31,0x35,0x38,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77, + 0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f,0x5f,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28,0x26,0x28,0x70, 0x61,0x72,0x61,0x6d,0x5f,0x32,0x29,0x2c,0x20,0x26,0x28,0x70,0x61,0x72,0x61,0x6d, 0x5f,0x33,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, - 0x31,0x33,0x36,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e, - 0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65,0x5f,0x31,0x3b, - 0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x33,0x39,0x20,0x3a, - 0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x5f,0x31,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x20,0x3d,0x20,0x28,0x78,0x5f, - 0x31,0x33,0x39,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,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,0x29,0x3b,0x0a,0x20, + 0x31,0x35,0x39,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e, + 0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x31,0x36,0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x31,0x36,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x20,0x3d,0x20,0x28, + 0x78,0x5f,0x31,0x36,0x32,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x2d, + 0x31,0x2e,0x35,0x66,0x2c,0x20,0x30,0x2e,0x35,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f, + 0x31,0x36,0x34,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78, + 0x5f,0x31,0x36,0x39,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x3b,0x0a,0x20, 0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x35,0x20,0x3d,0x20,0x78,0x5f,0x31, - 0x32,0x33,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x34, - 0x36,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73, - 0x61,0x6d,0x70,0x6c,0x65,0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28,0x26, - 0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x34,0x29,0x2c,0x20,0x26,0x28,0x70,0x61,0x72, - 0x61,0x6d,0x5f,0x35,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20, - 0x78,0x5f,0x31,0x34,0x37,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f, - 0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65,0x5f, - 0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x35,0x30, - 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x75,0x76,0x5f,0x31,0x3b, - 0x0a,0x20,0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3d,0x20,0x28, - 0x78,0x5f,0x31,0x35,0x30,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,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,0x3b,0x0a, - 0x20,0x20,0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3d,0x20,0x78,0x5f, - 0x31,0x32,0x33,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31, - 0x35,0x35,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f, - 0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28, - 0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x29,0x2c,0x20,0x26,0x28,0x70,0x61, - 0x72,0x61,0x6d,0x5f,0x37,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74, - 0x20,0x78,0x5f,0x31,0x35,0x36,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64, - 0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65,0x5f,0x73,0x63,0x61,0x6c,0x65, + 0x36,0x39,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x37, + 0x30,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x5f,0x74,0x6f,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x5f,0x76,0x66,0x32,0x5f,0x76,0x66,0x32,0x5f,0x28,0x26,0x28,0x70,0x61,0x72,0x61, + 0x6d,0x5f,0x34,0x29,0x2c,0x20,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x35,0x29, + 0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x37,0x31, + 0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20, + 0x78,0x5f,0x31,0x37,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20, + 0x75,0x76,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, + 0x31,0x37,0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x74,0x75,0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x20,0x3d,0x20,0x28,0x78,0x5f,0x31, + 0x37,0x34,0x20,0x2b,0x20,0x28,0x76,0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x35,0x66, + 0x2c,0x20,0x30,0x2e,0x35,0x66,0x29,0x20,0x2a,0x20,0x78,0x5f,0x31,0x37,0x36,0x29, + 0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x38,0x31, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x5f,0x73,0x69,0x7a,0x65,0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x70, + 0x61,0x72,0x61,0x6d,0x5f,0x37,0x20,0x3d,0x20,0x78,0x5f,0x31,0x38,0x31,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x38,0x32,0x20,0x3a,0x20, + 0x66,0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x5f,0x74,0x6f,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x5f,0x76,0x66,0x32, + 0x5f,0x76,0x66,0x32,0x5f,0x28,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x36,0x29, + 0x2c,0x20,0x26,0x28,0x70,0x61,0x72,0x61,0x6d,0x5f,0x37,0x29,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x38,0x33,0x20,0x3a,0x20,0x66, + 0x33,0x32,0x20,0x3d,0x20,0x64,0x6f,0x77,0x6e,0x5f,0x73,0x61,0x6d,0x70,0x6c,0x65, 0x5f,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x61,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20, - 0x28,0x28,0x28,0x28,0x78,0x5f,0x31,0x32,0x36,0x20,0x2a,0x20,0x78,0x5f,0x31,0x32, - 0x37,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x31,0x33,0x35,0x20,0x2a,0x20,0x78,0x5f, - 0x31,0x33,0x36,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x31,0x34,0x36,0x20,0x2a, - 0x20,0x78,0x5f,0x31,0x34,0x37,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x31,0x35, - 0x35,0x20,0x2a,0x20,0x78,0x5f,0x31,0x35,0x36,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20, - 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x36,0x31,0x20,0x3a,0x20,0x66,0x33,0x32, + 0x28,0x28,0x28,0x28,0x78,0x5f,0x31,0x34,0x36,0x20,0x2a,0x20,0x78,0x5f,0x31,0x34, + 0x37,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x31,0x35,0x38,0x20,0x2a,0x20,0x78,0x5f, + 0x31,0x35,0x39,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x31,0x37,0x30,0x20,0x2a, + 0x20,0x78,0x5f,0x31,0x37,0x31,0x29,0x29,0x20,0x2b,0x20,0x28,0x78,0x5f,0x31,0x38, + 0x32,0x20,0x2a,0x20,0x78,0x5f,0x31,0x38,0x33,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x38,0x38,0x20,0x3a,0x20,0x66,0x33,0x32, 0x20,0x3d,0x20,0x61,0x6c,0x70,0x68,0x61,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72, 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66, 0x28,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, - 0x66,0x2c,0x20,0x78,0x5f,0x31,0x36,0x31,0x29,0x3b,0x0a,0x20,0x20,0x7d,0x20,0x65, + 0x66,0x2c,0x20,0x78,0x5f,0x31,0x38,0x38,0x29,0x3b,0x0a,0x20,0x20,0x7d,0x20,0x65, 0x6c,0x73,0x65,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x30,0x2e,0x30, 0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31, @@ -1448,23 +1648,29 @@ blit_atlas_fs_source_wgsl := [3640]u8 { 0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f, 0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } -blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { +ve_blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc: sg.Shader_Desc - desc.label = "blit_atlas_shader" + desc.label = "ve_blit_atlas_shader" #partial switch backend { case .GLCORE: - desc.vertex_func.source = transmute(cstring)&blit_atlas_vs_source_glsl410 + desc.vertex_func.source = transmute(cstring)&ve_blit_atlas_vs_source_glsl410 desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&blit_atlas_fs_source_glsl410 + desc.fragment_func.source = transmute(cstring)&ve_blit_atlas_fs_source_glsl410 desc.fragment_func.entry = "main" desc.attrs[0].glsl_name = "v_position" desc.attrs[1].glsl_name = "v_texture" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 desc.uniform_blocks[0].size = 16 - desc.uniform_blocks[0].glsl_uniforms[0].type = .INT4 - desc.uniform_blocks[0].glsl_uniforms[0].array_count = 1 - desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "blit_atlas_fs_params" + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT2 + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 0 + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "_20.glyph_buffer_size" + desc.uniform_blocks[0].glsl_uniforms[1].type = .FLOAT + desc.uniform_blocks[0].glsl_uniforms[1].array_count = 0 + desc.uniform_blocks[0].glsl_uniforms[1].glsl_name = "_20.over_sample" + desc.uniform_blocks[0].glsl_uniforms[2].type = .INT + desc.uniform_blocks[0].glsl_uniforms[2].array_count = 0 + desc.uniform_blocks[0].glsl_uniforms[2].glsl_name = "_20.region" desc.images[0].stage = .FRAGMENT desc.images[0].multisampled = false desc.images[0].image_type = ._2D @@ -1474,20 +1680,26 @@ blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].stage = .FRAGMENT desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 - desc.image_sampler_pairs[0].glsl_name = "blit_atlas_src_texture_blit_atlas_src_sampler" + desc.image_sampler_pairs[0].glsl_name = "ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler" case .GLES3: - desc.vertex_func.source = transmute(cstring)&blit_atlas_vs_source_glsl300es + desc.vertex_func.source = transmute(cstring)&ve_blit_atlas_vs_source_glsl300es desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&blit_atlas_fs_source_glsl300es + desc.fragment_func.source = transmute(cstring)&ve_blit_atlas_fs_source_glsl300es desc.fragment_func.entry = "main" desc.attrs[0].glsl_name = "v_position" desc.attrs[1].glsl_name = "v_texture" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 desc.uniform_blocks[0].size = 16 - desc.uniform_blocks[0].glsl_uniforms[0].type = .INT4 - desc.uniform_blocks[0].glsl_uniforms[0].array_count = 1 - desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "blit_atlas_fs_params" + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT2 + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 0 + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "_20.glyph_buffer_size" + desc.uniform_blocks[0].glsl_uniforms[1].type = .FLOAT + desc.uniform_blocks[0].glsl_uniforms[1].array_count = 0 + desc.uniform_blocks[0].glsl_uniforms[1].glsl_name = "_20.over_sample" + desc.uniform_blocks[0].glsl_uniforms[2].type = .INT + desc.uniform_blocks[0].glsl_uniforms[2].array_count = 0 + desc.uniform_blocks[0].glsl_uniforms[2].glsl_name = "_20.region" desc.images[0].stage = .FRAGMENT desc.images[0].multisampled = false desc.images[0].image_type = ._2D @@ -1497,12 +1709,12 @@ blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].stage = .FRAGMENT desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 - desc.image_sampler_pairs[0].glsl_name = "blit_atlas_src_texture_blit_atlas_src_sampler" + desc.image_sampler_pairs[0].glsl_name = "ve_blit_atlas_src_texture_ve_blit_atlas_src_sampler" case .D3D11: - desc.vertex_func.source = transmute(cstring)&blit_atlas_vs_source_hlsl4 + desc.vertex_func.source = transmute(cstring)&ve_blit_atlas_vs_source_hlsl4 desc.vertex_func.d3d11_target = "vs_4_0" desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&blit_atlas_fs_source_hlsl4 + desc.fragment_func.source = transmute(cstring)&ve_blit_atlas_fs_source_hlsl4 desc.fragment_func.d3d11_target = "ps_4_0" desc.fragment_func.entry = "main" desc.attrs[0].hlsl_sem_name = "TEXCOORD" @@ -1525,9 +1737,9 @@ blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 case .METAL_MACOS: - desc.vertex_func.source = transmute(cstring)&blit_atlas_vs_source_metal_macos + desc.vertex_func.source = transmute(cstring)&ve_blit_atlas_vs_source_metal_macos desc.vertex_func.entry = "main0" - desc.fragment_func.source = transmute(cstring)&blit_atlas_fs_source_metal_macos + desc.fragment_func.source = transmute(cstring)&ve_blit_atlas_fs_source_metal_macos desc.fragment_func.entry = "main0" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 @@ -1545,9 +1757,9 @@ blit_atlas_shader_desc :: proc (backend: sg.Backend) -> sg.Shader_Desc { desc.image_sampler_pairs[0].image_slot = 0 desc.image_sampler_pairs[0].sampler_slot = 0 case .WGPU: - desc.vertex_func.source = transmute(cstring)&blit_atlas_vs_source_wgsl + desc.vertex_func.source = transmute(cstring)&ve_blit_atlas_vs_source_wgsl desc.vertex_func.entry = "main" - desc.fragment_func.source = transmute(cstring)&blit_atlas_fs_source_wgsl + desc.fragment_func.source = transmute(cstring)&ve_blit_atlas_fs_source_wgsl desc.fragment_func.entry = "main" desc.uniform_blocks[0].stage = .FRAGMENT desc.uniform_blocks[0].layout = .STD140 diff --git a/backend/sokol/blit_atlas.shdc.glsl b/backend/sokol/blit_atlas.shdc.glsl index 5151f9a..568aaad 100644 --- a/backend/sokol/blit_atlas.shdc.glsl +++ b/backend/sokol/blit_atlas.shdc.glsl @@ -4,7 +4,7 @@ @header import sg "thirdparty:sokol/gfx" @vs ve_blit_atlas_vs -@include ./ve_source_shared.shdc.glsl +@include ./source_shared.shdc.glsl @end @fs ve_blit_atlas_fs diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index a63ffbf..431b773 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -186,8 +186,7 @@ draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos text_scale := screen_scale { - f32_resolved_size := f32(resolved_size) - diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_resolved_size + diff_scalar := 1 + (zoom_adjust_size - resolved_size) / resolved_size text_scale = diff_scalar * screen_scale text_scale.x = clamp(text_scale.x, 0, 1) text_scale.y = clamp(text_scale.y, 0, 1) @@ -198,9 +197,12 @@ draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos ve.draw_text_normalized_space(& demo_ctx.ve_ctx, def.ve_id, + resolved_size, + color_norm, screen_size, pos, text_scale, + 1.0, content ) } @@ -263,7 +265,7 @@ init :: proc "c" () ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 1024 * 1024, index_cap = 1024 * 1024 ) error : mem.Allocator_Error - demo_ctx.font_ids, error = make( map[string]FontDef, 256 ) + demo_ctx.font_ids, error = make( map[string]Font_Entry, 256 ) assert( error == .None, "Failed to allocate demo_ctx.font_ids" ) path_sawarabi_mincho := strings.concatenate({ PATH_FONTS, "SawarabiMincho-Regular.ttf" }) @@ -323,8 +325,8 @@ frame :: proc "c" () gfx.begin_pass({ action = pass_action, swapchain = glue.swapchain() }) gfx.end_pass() { - ve.configure_snap( & demo_ctx.ve_ctx, u32(demo_ctx.screen_size.x), u32(demo_ctx.screen_size.y) ) - ve.set_colour( & demo_ctx.ve_ctx, ve.Colour { 1.0, 1.0, 1.0, 1.0 }) + // ve.configure_snap( & demo_ctx.ve_ctx, u32(demo_ctx.screen_size.x), u32(demo_ctx.screen_size.y) ) + // ve.set_colour( & demo_ctx.ve_ctx, ve.Colour { 1.0, 1.0, 1.0, 1.0 }) using demo_ctx @@ -513,11 +515,12 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` zoomed_text_base_size : f32 = 12.0 zoom_adjust_size := zoomed_text_base_size * current_zoom - ve_id, resolved_size := font_resolve_draw_id( font_firacode, zoom_adjust_size * OVER_SAMPLE_ZOOM ) + // ve_id, resolved_size := font_resolve_draw_id( font_firacode, zoom_adjust_size * OVER_SAMPLE_ZOOM ) + resolved_size := zoom_adjust_size current_zoom_text := fmt.tprintf("Current Zoom : %.2f x\nCurrent Resolved Size: %v px", current_zoom, resolved_size ) draw_text_string_pos_norm(current_zoom_text, font_firacode, 19, {0.2, zoom_info_y}, COLOR_WHITE) - ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) ) + // ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) ) size := measure_text_size( zoom_text, font_firacode, zoomed_text_base_size, 0 ) * current_zoom x_offset := (size.x / demo_ctx.screen_size.x) * 0.5 @@ -594,7 +597,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` draw_text_string_pos_norm(codes[grid[y * GRID_W + x]], font_demo_raincode, 20, {pos_x, pos_y}, code_colour) } - ve.set_colour(&ve_ctx, {1.0, 1.0, 1.0, 1.0}) + // ve.set_colour(&ve_ctx, {1.0, 1.0, 1.0, 1.0}) } // Cache pressure test diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index 87bb178..62cb6e1 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -119,6 +119,10 @@ resize :: proc { builtin.resize_dynamic_array, } +round :: proc { + math.round_f32, +} + size :: proc { size_range2, } diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 59e2964..93166ca 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -94,7 +94,6 @@ Context :: struct { stack : Scope_Stack, - colour : RGBAN, // Color used in draw interface TODO(Ed): use the stack cursor_pos : Vec2, // TODO(Ed): Review this, no longer used much at all... (still useful I guess) // Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges. // Has a boldening side-effect. If overblown will look smeared. @@ -193,7 +192,6 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N ctx.backing = allocator context.allocator = ctx.backing - ctx.colour = { 1, 1, 1, 1 } ctx.alpha_sharpen = alpha_sharpen ctx.px_scalar = px_scalar @@ -663,13 +661,12 @@ resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size { interval_quotient := 1.0 / f32(interval) size := px_size == 0.0 ? default_size : px_size - even_size := math.round(size * interval_quotient) * interval + even_size := round(size * interval_quotient) * interval resolved_size = clamp( even_size, min, max ) return } set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } -set_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); ctx.colour = colour } set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { @@ -835,7 +832,7 @@ draw_text_view_space :: proc(ctx : ^Context, ctx.cursor_pos = {} entry := ctx.entries[ font ] - norm_position := view_position * (1 / view) + norm_position := position * (1 / view) adjusted_position := get_snapped_position( norm_position, view ) From a14e4faf2992bb02ab3e3459538de1b80881723b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 13:24:58 -0500 Subject: [PATCH 08/62] remove using usage from sokol_demo.odin --- examples/sokol_demo/sokol_demo.odin | 146 +++++++++++++--------------- 1 file changed, 68 insertions(+), 78 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 431b773..c8d4cee 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -168,13 +168,6 @@ draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, return } -// Draw text using a string and extent-based screen coordinates -draw_text_string_pos_extent :: proc( content : string, id : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE ) { - render_pos := pos + demo_ctx.screen_size * 0.5 - normalized_pos := render_pos * (1.0 / demo_ctx.screen_size) - draw_text_string_pos_norm( content, id, size, normalized_pos, color ) -} - // Adapt the draw_text_string_pos_extent_zoomed procedure draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos : Vec2, zoom : f32, color := COLOR_WHITE) { @@ -284,26 +277,25 @@ init :: proc "c" () path_noto_sans_jp_reg := strings.concatenate({ PATH_FONTS, "NotoSansJP-Regular.otf" }) path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" }) - using demo_ctx - font_logo = font_load(path_sawarabi_mincho, 330.0, "SawarabiMincho", 6 ) - font_title = font_load(path_open_sans, 92.0, "OpenSans", 12 ) - font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP") - font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono") - font_small = font_load(path_roboto, 10.0, "Roboto") - font_demo_sans = font_load(path_open_sans, 18.0, "OpenSans") - font_demo_serif = font_load(path_bitter, 18.0, "Bitter") - font_demo_script = font_load(path_dancing_script, 22.0, "DancingScript") - font_demo_mono = font_load(path_nova_mono, 18.0, "NovaMono") - font_demo_chinese = font_load(path_noto_serif_sc, 24.0, "NotoSerifSC") - font_demo_japanese = font_load(path_sawarabi_mincho, 24.0, "SawarabiMincho") - font_demo_korean = font_load(path_nanum_pen_script, 36.0, "NanumPenScript") - font_demo_thai = font_load(path_krub, 24.0, "Krub") - font_demo_arabic = font_load(path_tajawal, 24.0, "Tajawal") - font_demo_hebrew = font_load(path_david_libre, 22.0, "DavidLibre") - font_demo_raincode = font_load(path_noto_sans_jp_reg, 20.0, "NotoSansJPRegular") - font_demo_grid2 = font_load(path_noto_serif_sc, 54.0, "NotoSerifSC") - font_demo_grid3 = font_load(path_bitter, 44.0, "Bitter") - font_firacode = font_load(path_firacode, 16.0, "FiraCode", 12 ) + demo_ctx.font_logo = font_load(path_sawarabi_mincho, 330.0, "SawarabiMincho", 6 ) + demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 12 ) + demo_ctx.font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP") + demo_ctx.font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono") + demo_ctx.font_small = font_load(path_roboto, 10.0, "Roboto") + demo_ctx.font_demo_sans = font_load(path_open_sans, 18.0, "OpenSans") + demo_ctx.font_demo_serif = font_load(path_bitter, 18.0, "Bitter") + demo_ctx.font_demo_script = font_load(path_dancing_script, 22.0, "DancingScript") + demo_ctx.font_demo_mono = font_load(path_nova_mono, 18.0, "NovaMono") + demo_ctx.font_demo_chinese = font_load(path_noto_serif_sc, 24.0, "NotoSerifSC") + demo_ctx.font_demo_japanese = font_load(path_sawarabi_mincho, 24.0, "SawarabiMincho") + demo_ctx.font_demo_korean = font_load(path_nanum_pen_script, 36.0, "NanumPenScript") + demo_ctx.font_demo_thai = font_load(path_krub, 24.0, "Krub") + demo_ctx.font_demo_arabic = font_load(path_tajawal, 24.0, "Tajawal") + demo_ctx.font_demo_hebrew = font_load(path_david_libre, 22.0, "DavidLibre") + demo_ctx.font_demo_raincode = font_load(path_noto_sans_jp_reg, 20.0, "NotoSansJPRegular") + demo_ctx.font_demo_grid2 = font_load(path_noto_serif_sc, 54.0, "NotoSerifSC") + demo_ctx.font_demo_grid3 = font_load(path_bitter, 44.0, "Bitter") + demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 12 ) } event :: proc "c" (sokol_event : ^app.Event) @@ -328,8 +320,6 @@ frame :: proc "c" () // ve.configure_snap( & demo_ctx.ve_ctx, u32(demo_ctx.screen_size.x), u32(demo_ctx.screen_size.y) ) // ve.set_colour( & demo_ctx.ve_ctx, ve.Colour { 1.0, 1.0, 1.0, 1.0 }) - using demo_ctx - // Smooth scrolling implementation @static demo_autoscroll := false @static current_scroll : f32 = 0.0 @@ -340,7 +330,7 @@ frame :: proc "c" () frame_duration := cast(f32) app.frame_duration() - scroll_velocity += mouse_scroll.y * 0.05 + scroll_velocity += demo_ctx.mouse_scroll.y * 0.05 mouse_down_pos = -1.0 substep_dt := frame_duration / 4.0 for _ in 0 ..< 4 { @@ -350,14 +340,14 @@ frame :: proc "c" () if demo_autoscroll { current_scroll += 0.05 * frame_duration } - mouse_scroll = {} // Reset mouse scroll + demo_ctx.mouse_scroll = {} // Reset mouse scroll // Clamp scroll value if needed current_scroll = clamp(current_scroll, 0, 6.1) // Adjust max value as needed // Frametime display frametime_text := fmt.tprintf("Frametime %v", frame_duration) - draw_text_string_pos_norm(frametime_text, font_title, 0, {0.0, 0.0}, COLOR_WHITE) + draw_text_string_pos_norm(frametime_text, demo_ctx.font_title, 0, {0.0, 0.0}, COLOR_WHITE) if current_scroll < 1.5 { intro := `Ça va! Everything here is rendered using VE Font Cache, a single header-only library designed for game engines. @@ -372,9 +362,9 @@ It aims to: • Support cached text shaping with HarfBuzz with simple Latin-style fallback. • Load and unload fonts at any time.` - draw_text_string_pos_norm("ゑ", font_logo, 330, {0.4, current_scroll}, COLOR_WHITE) - draw_text_string_pos_norm("VEFontCache Demo", font_title, 92, {0.2, current_scroll - 0.1}, COLOR_WHITE) - draw_text_string_pos_norm(intro, font_print, 19, {0.2, current_scroll - 0.14}, COLOR_WHITE) + draw_text_string_pos_norm("ゑ", demo_ctx.font_logo, 330, { 0.4, current_scroll }, COLOR_WHITE) + draw_text_string_pos_norm("VEFontCache Demo", demo_ctx.font_title, 92, { 0.2, current_scroll - 0.1 }, COLOR_WHITE) + draw_text_string_pos_norm(intro, demo_ctx.font_print, 19, { 0.2, current_scroll - 0.14 }, COLOR_WHITE) } section_start : f32 = 0.42 @@ -417,10 +407,10 @@ Glyphs are first rendered to an intermediate 2k x 512px R8 texture. This allows 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.` - draw_text_string_pos_norm("How it works", font_title, 92, {0.2, current_scroll - (section_start + 0.06)}, COLOR_WHITE) - draw_text_string_pos_norm(how_it_works, font_print, 19, {0.2, current_scroll - (section_start + 0.1)}, COLOR_WHITE) - draw_text_string_pos_norm(caching_strategy, demo_ctx.font_mono, 21, {0.28, current_scroll - (section_start + 0.32)}, COLOR_WHITE) - draw_text_string_pos_norm(how_it_works2, font_print, 19, {0.2, current_scroll - (section_start + 0.82)}, COLOR_WHITE) + draw_text_string_pos_norm("How it works", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.06) }, COLOR_WHITE) + draw_text_string_pos_norm(how_it_works, demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.1) }, COLOR_WHITE) + draw_text_string_pos_norm(caching_strategy, demo_ctx.font_mono, 21, { 0.28, current_scroll - (section_start + 0.32) }, COLOR_WHITE) + draw_text_string_pos_norm(how_it_works2, demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.82) }, COLOR_WHITE) } // Showcase section @@ -431,49 +421,49 @@ intermediate texture to the final atlas location.` incididunt ut labore et dolore magna aliqua. Est ullamcorper eget nulla facilisi etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` - draw_text_string_pos_norm("Showcase", font_title, 92, {0.2, current_scroll - (section_start + 0.2)}, COLOR_WHITE) - draw_text_string_pos_norm("This is a showcase demonstrating different hb_font categories and languages.", font_print, 19, {0.2, current_scroll - (section_start + 0.24)}, COLOR_WHITE) + draw_text_string_pos_norm("Showcase", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE) + draw_text_string_pos_norm("This is a showcase demonstrating different hb_font categories and languages.", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.24) }, COLOR_WHITE) - draw_text_string_pos_norm("Sans serif", font_print, 19, {0.2, current_scroll - (section_start + 0.28)}, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, font_demo_sans, 18, {0.3, current_scroll - (section_start + 0.28)}, COLOR_WHITE) + draw_text_string_pos_norm("Sans serif", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.28) }, COLOR_WHITE) + draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.28) }, COLOR_WHITE) - draw_text_string_pos_norm("Serif", font_print, 19, {0.2, current_scroll - (section_start + 0.36)}, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, font_demo_serif, 18, {0.3, current_scroll - (section_start + 0.36)}, COLOR_WHITE) + draw_text_string_pos_norm("Serif", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.36) }, COLOR_WHITE) + draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_serif, 18, { 0.3, current_scroll - (section_start + 0.36) }, COLOR_WHITE) - draw_text_string_pos_norm("Script", font_print, 19, {0.2, current_scroll - (section_start + 0.44)}, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, font_demo_script, 22, {0.3, current_scroll - (section_start + 0.44)}, COLOR_WHITE) + draw_text_string_pos_norm("Script", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.44) }, COLOR_WHITE) + draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_script, 22, { 0.3, current_scroll - (section_start + 0.44) }, COLOR_WHITE) - draw_text_string_pos_norm("Monospace", font_print, 19, {0.2, current_scroll - (section_start + 0.52)}, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, font_demo_mono, 18, {0.3, current_scroll - (section_start + 0.52)}, COLOR_WHITE) + draw_text_string_pos_norm("Monospace", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.52) }, COLOR_WHITE) + draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_mono, 18, { 0.3, current_scroll - (section_start + 0.52) }, COLOR_WHITE) - draw_text_string_pos_norm("Small", font_print, 19, {0.2, current_scroll - (section_start + 0.60)}, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, font_small, 10, {0.3, current_scroll - (section_start + 0.60)}, COLOR_WHITE) + draw_text_string_pos_norm("Small", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.60) }, COLOR_WHITE) + draw_text_string_pos_norm(font_family_test, demo_ctx.font_small, 10, { 0.3, current_scroll - (section_start + 0.60) }, COLOR_WHITE) - draw_text_string_pos_norm("Greek", font_print, 19, {0.2, current_scroll - (section_start + 0.72)}, COLOR_WHITE) - draw_text_string_pos_norm("Ήταν απλώς θέμα χρόνου.", font_demo_sans, 18, {0.3, current_scroll - (section_start + 0.72)}, COLOR_WHITE) + draw_text_string_pos_norm("Greek", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.72) }, COLOR_WHITE) + draw_text_string_pos_norm("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.72) }, COLOR_WHITE) - draw_text_string_pos_norm("Vietnamese", font_print, 19, {0.2, current_scroll - (section_start + 0.76)}, COLOR_WHITE) - draw_text_string_pos_norm("Bầu trời trong xanh thăm thẳm, không một gợn mây.", font_demo_sans, 18, {0.3, current_scroll - (section_start + 0.76)}, COLOR_WHITE) + draw_text_string_pos_norm("Vietnamese", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.76) }, COLOR_WHITE) + draw_text_string_pos_norm("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.76) }, COLOR_WHITE) - draw_text_string_pos_norm("Thai", font_print, 19, {0.2, current_scroll - (section_start + 0.80)}, COLOR_WHITE) - draw_text_string_pos_norm("การเดินทางขากลับคงจะเหงา", font_demo_thai, 24, {0.3, current_scroll - (section_start + 0.80)}, COLOR_WHITE) + draw_text_string_pos_norm("Thai", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.80)}, COLOR_WHITE) + draw_text_string_pos_norm("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, 24, {0.3, current_scroll - (section_start + 0.80)}, COLOR_WHITE) - draw_text_string_pos_norm("Chinese", font_print, 19, {0.2, current_scroll - (section_start + 0.84)}, COLOR_WHITE) - draw_text_string_pos_norm("床前明月光 疑是地上霜 举头望明月 低头思故乡", font_demo_chinese, 24, {0.3, current_scroll - (section_start + 0.84)}, COLOR_WHITE) + draw_text_string_pos_norm("Chinese", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.84)}, COLOR_WHITE) + draw_text_string_pos_norm("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, 24, {0.3, current_scroll - (section_start + 0.84)}, COLOR_WHITE) - draw_text_string_pos_norm("Japanese", font_print, 19, {0.2, current_scroll - (section_start + 0.88)}, COLOR_WHITE) - draw_text_string_pos_norm("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", font_demo_japanese, 24, {0.3, current_scroll - (section_start + 0.88)}, COLOR_WHITE) + draw_text_string_pos_norm("Japanese", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.88)}, COLOR_WHITE) + draw_text_string_pos_norm("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, 24, {0.3, current_scroll - (section_start + 0.88)}, COLOR_WHITE) - draw_text_string_pos_norm("Korean", font_print, 19, {0.2, current_scroll - (section_start + 0.92)}, COLOR_WHITE) - draw_text_string_pos_norm("그들의 장비와 기구는 모두 살아 있다.", font_demo_korean, 36, {0.3, current_scroll - (section_start + 0.92)}, COLOR_WHITE) + draw_text_string_pos_norm("Korean", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.92)}, COLOR_WHITE) + draw_text_string_pos_norm("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, 36, {0.3, current_scroll - (section_start + 0.92)}, COLOR_WHITE) - draw_text_string_pos_norm("Needs harfbuzz to work:", font_print, 14, {0.2, current_scroll - (section_start + 0.96)}, COLOR_WHITE) + draw_text_string_pos_norm("Needs harfbuzz to work:", demo_ctx.font_print, 14, {0.2, current_scroll - (section_start + 0.96)}, COLOR_WHITE) - draw_text_string_pos_norm("Arabic", font_print, 19, {0.2, current_scroll - (section_start + 1.00)}, COLOR_WHITE) - draw_text_string_pos_norm("حب السماء لا تمطر غير الأحلام.", font_demo_arabic, 24, {0.3, current_scroll - (section_start + 1.00)}, COLOR_WHITE) + draw_text_string_pos_norm("Arabic", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 1.00)}, COLOR_WHITE) + draw_text_string_pos_norm("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, 24, {0.3, current_scroll - (section_start + 1.00)}, COLOR_WHITE) - draw_text_string_pos_norm("Hebrew", font_print, 19, {0.2, current_scroll - (section_start + 1.04)}, COLOR_WHITE) - draw_text_string_pos_norm("אז הגיע הלילה של כוכב השביט הראשון.", font_demo_hebrew, 22, {0.3, current_scroll - (section_start + 1.04)}, COLOR_WHITE) + draw_text_string_pos_norm("Hebrew", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 1.04)}, COLOR_WHITE) + draw_text_string_pos_norm("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, 22, {0.3, current_scroll - (section_start + 1.04)}, COLOR_WHITE) } // Zoom Test @@ -511,21 +501,21 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` zoom_info_y := current_scroll - (section_start + 0.10) zoomed_text_y := current_scroll - (section_start + 0.30) + math.sin(zoom_time) * 0.02 - draw_text_string_pos_norm("Zoom Test", font_title, 92, {0.2, title_y}, COLOR_WHITE) + draw_text_string_pos_norm("Zoom Test", demo_ctx.font_title, 92, {0.2, title_y}, COLOR_WHITE) zoomed_text_base_size : f32 = 12.0 zoom_adjust_size := zoomed_text_base_size * current_zoom // ve_id, resolved_size := font_resolve_draw_id( font_firacode, zoom_adjust_size * OVER_SAMPLE_ZOOM ) resolved_size := zoom_adjust_size current_zoom_text := fmt.tprintf("Current Zoom : %.2f x\nCurrent Resolved Size: %v px", current_zoom, resolved_size ) - draw_text_string_pos_norm(current_zoom_text, font_firacode, 19, {0.2, zoom_info_y}, COLOR_WHITE) + draw_text_string_pos_norm(current_zoom_text, demo_ctx.font_firacode, 19, {0.2, zoom_info_y}, COLOR_WHITE) // ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) ) - size := measure_text_size( zoom_text, font_firacode, zoomed_text_base_size, 0 ) * current_zoom + size := measure_text_size( zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, 0 ) * current_zoom x_offset := (size.x / demo_ctx.screen_size.x) * 0.5 zoomed_text_pos := Vec2 { 0.5 - x_offset, zoomed_text_y } - draw_text_zoomed_norm(zoom_text, font_firacode, zoomed_text_base_size, zoomed_text_pos, current_zoom, COLOR_WHITE) + draw_text_zoomed_norm(zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, zoomed_text_pos, current_zoom, COLOR_WHITE) } // Raincode Demo @@ -577,7 +567,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } // Draw grid - draw_text_string_pos_norm("Raincode demo", font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE) + draw_text_string_pos_norm("Raincode demo", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE) for y in 0 ..< GRID_H do for x in 0 ..< GRID_W { pos_x := 0.2 + f32(x) * 0.007 @@ -594,7 +584,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` if code_colour.a == 0 do continue } - draw_text_string_pos_norm(codes[grid[y * GRID_W + x]], font_demo_raincode, 20, {pos_x, pos_y}, code_colour) + draw_text_string_pos_norm(codes[grid[y * GRID_W + x]], demo_ctx.font_demo_raincode, 20, {pos_x, pos_y}, code_colour) } // ve.set_colour(&ve_ctx, {1.0, 1.0, 1.0, 1.0}) @@ -664,28 +654,28 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } // Draw grid - draw_text_string_pos_norm("Cache pressure test", font_title, 92, {0.2, current_scroll - (section_start + 0.2)}, COLOR_WHITE) + draw_text_string_pos_norm("Cache pressure test", demo_ctx.font_title, 92, {0.2, current_scroll - (section_start + 0.2)}, COLOR_WHITE) for y in 0..< GRID_H do for x in 0 ..< GRID_W { posx := 0.2 + f32(x) * 0.02 posy := current_scroll - (section_start + 0.24 + f32(y) * 0.025) c := [5]u8{} codepoint_to_utf8(c[:], grid[ y * GRID_W + x ]) - draw_text_string_pos_norm(string( c[:] ), font_demo_chinese, 24, {posx, posy}, COLOR_WHITE) + draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_chinese, 24, {posx, posy}, COLOR_WHITE) } for y in 0 ..< GRID2_H do for x in 0 ..< GRID2_W { posx := 0.2 + f32(x) * 0.03 posy := current_scroll - (section_start + 0.66 + f32(y) * 0.052) c := [5]u8{} codepoint_to_utf8(c[:], grid2[ y * GRID2_W + x ]) - draw_text_string_pos_norm(string( c[:] ), font_demo_grid2, 54, {posx, posy}, COLOR_WHITE) + draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_grid2, 54, {posx, posy}, COLOR_WHITE) } for y in 0 ..< GRID3_H do for x in 0 ..< GRID3_W { posx := 0.45 + f32(x) * 0.02 posy := current_scroll - (section_start + 0.64 + f32(y) * 0.034) c := [5]u8{} codepoint_to_utf8( c[:], grid3[ y * GRID3_W + x ]) - draw_text_string_pos_norm(string( c[:] ), font_demo_grid3, 44, {posx, posy}, COLOR_WHITE) + draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_grid3, 44, {posx, posy}, COLOR_WHITE) } } From 99a1f77699cad2a50e017a216bffeba8ee0848a6 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 13:29:10 -0500 Subject: [PATCH 09/62] remove proc using stmt usage in backend_sokol.odin --- backend/sokol/backend_sokol.odin | 63 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/backend/sokol/backend_sokol.odin b/backend/sokol/backend_sokol.odin index ad2a276..de8f331 100644 --- a/backend/sokol/backend_sokol.odin +++ b/backend/sokol/backend_sokol.odin @@ -83,18 +83,17 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index { vs_layout : Vertex_Layout_State { - using vs_layout - attrs[ATTR_render_glyph_v_position] = Vertex_Attribute_State { + vs_layout.attrs[ATTR_render_glyph_v_position] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = 0, buffer_index = 0, } - attrs[ATTR_render_glyph_v_texture] = Vertex_Attribute_State { + vs_layout.attrs[ATTR_render_glyph_v_texture] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = size_of(ve.Vec2), buffer_index = 0, } - buffers[0] = Vertex_Buffer_Layout_State { + vs_layout.buffers[0] = Vertex_Buffer_Layout_State { stride = size_of([4]f32), step_func = Vertex_Step.PER_VERTEX } @@ -219,18 +218,17 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index { vs_layout : Vertex_Layout_State { - using vs_layout - attrs[ATTR_ve_blit_atlas_v_position] = Vertex_Attribute_State { + vs_layout.attrs[ATTR_ve_blit_atlas_v_position] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = 0, buffer_index = 0, } - attrs[ATTR_ve_blit_atlas_v_texture] = Vertex_Attribute_State { + vs_layout.attrs[ATTR_ve_blit_atlas_v_texture] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = size_of(ve.Vec2), buffer_index = 0, } - buffers[0] = Vertex_Buffer_Layout_State { + vs_layout.buffers[0] = Vertex_Buffer_Layout_State { stride = size_of([4]f32), step_func = Vertex_Step.PER_VERTEX } @@ -356,18 +354,17 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index { vs_layout : Vertex_Layout_State { - using vs_layout - attrs[ATTR_ve_draw_text_v_position] = Vertex_Attribute_State { + vs_layout.attrs[ATTR_ve_draw_text_v_position] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = 0, buffer_index = 0, } - attrs[ATTR_ve_draw_text_v_texture] = Vertex_Attribute_State { + vs_layout.attrs[ATTR_ve_draw_text_v_texture] = Vertex_Attribute_State { format = Vertex_Format.FLOAT2, offset = size_of(ve.Vec2), buffer_index = 0, } - buffers[0] = Vertex_Buffer_Layout_State { + vs_layout.buffers[0] = Vertex_Buffer_Layout_State { stride = size_of([4]f32), step_func = Vertex_Step.PER_VERTEX } @@ -447,8 +444,6 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : Context ) { // profile("VEFontCache: render text layer") - using ctx - Bindings :: gfx.Bindings Range :: gfx.Range Shader_Stage :: gfx.Shader_Stage @@ -458,8 +453,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, 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 ) + gfx.append_buffer( ctx.draw_list_vbuf, vbuf_ve_range ) + gfx.append_buffer( ctx.draw_list_ibuf, ibuf_ve_range ) ve.flush_draw_list_layer( ve_ctx ) @@ -486,7 +481,7 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : width := ve_ctx.glyph_buffer.size.x height := ve_ctx.glyph_buffer.size.y - pass := glyph_pass + pass := ctx.glyph_pass if draw_call.clear_before_draw { pass.action.colors[0].load_action = .CLEAR pass.action.colors[0].clear_value.a = 1.0 @@ -496,16 +491,16 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : 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 ) + gfx.apply_pipeline( ctx.glyph_pipeline ) bindings := Bindings { vertex_buffers = { - 0 = draw_list_vbuf, + 0 = ctx.draw_list_vbuf, }, vertex_buffer_offsets = { 0 = 0, }, - index_buffer = draw_list_ibuf, + index_buffer = ctx.draw_list_ibuf, index_buffer_offset = 0, } gfx.apply_bindings( bindings ) @@ -521,7 +516,7 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : width := ve_ctx.atlas.size.x height := ve_ctx.atlas.size.y - pass := atlas_pass + pass := ctx.atlas_pass if draw_call.clear_before_draw { pass.action.colors[0].load_action = .CLEAR pass.action.colors[0].clear_value.a = 1.0 @@ -531,7 +526,7 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : 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 ) + gfx.apply_pipeline( ctx.atlas_pipeline ) fs_uniform := Ve_Blit_Atlas_Fs_Params { glyph_buffer_size = ve.vec2(ve_ctx.glyph_buffer.size), @@ -542,15 +537,15 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : gfx.apply_bindings(Bindings { vertex_buffers = { - 0 = draw_list_vbuf, + 0 = ctx.draw_list_vbuf, }, vertex_buffer_offsets = { 0 = 0, }, - index_buffer = draw_list_ibuf, + index_buffer = ctx.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, }, + images = { IMG_ve_blit_atlas_src_texture = ctx.glyph_rt_color, }, + samplers = { SMP_ve_blit_atlas_src_sampler = ctx.glyph_rt_sampler, }, }) // 3. Use the atlas to then render the text. @@ -561,17 +556,17 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : // profile("VEFontCache: draw call: target") - pass := screen_pass + pass := ctx.screen_pass pass.swapchain = 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 ) + gfx.apply_pipeline( ctx.screen_pipeline ) - src_rt := atlas_rt_color - src_sampler := atlas_rt_sampler + src_rt := ctx.atlas_rt_color + src_sampler := ctx.atlas_rt_sampler fs_target_uniform := Ve_Draw_Text_Fs_Params { // glyph_buffer_size = glyph_buf_size, @@ -581,19 +576,19 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : if draw_call.pass == .Target_Uncached { // fs_target_uniform.over_sample = 1.0 - src_rt = glyph_rt_color - src_sampler = glyph_rt_sampler + src_rt = ctx.glyph_rt_color + src_sampler = ctx.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, + 0 = ctx.draw_list_vbuf, }, vertex_buffer_offsets = { 0 = 0, }, - index_buffer = draw_list_ibuf, + index_buffer = ctx.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, }, From 91e8af88391bd3a1122ce83eaaef2c3aa8e4e6d4 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 15:27:47 -0500 Subject: [PATCH 10/62] Got the demo to show text again (but with lots of errors) --- examples/sokol_demo/sokol_demo.odin | 9 +++-- vefontcache/vefontcache.odin | 60 ++++++++++++++--------------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index c8d4cee..6448a3c 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -155,13 +155,15 @@ draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, color_norm := normalize_rgba8(color) def := demo_ctx.font_ids[ font.label ] + size := size > 2.0 ? size : f32(def.default_size) + ve.draw_text_normalized_space( & demo_ctx.ve_ctx, def.ve_id, size, color_norm, demo_ctx.screen_size, pos, - scale, + scale * 1 / demo_ctx.screen_size, 1.0, content ) @@ -224,6 +226,7 @@ sokol_gfx_free :: proc "c" ( data : rawptr, user_data : rawptr ) { free(data, allocator = context.allocator ) } + init :: proc "c" () { context = runtime.default_context() @@ -278,7 +281,7 @@ init :: proc "c" () path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" }) demo_ctx.font_logo = font_load(path_sawarabi_mincho, 330.0, "SawarabiMincho", 6 ) - demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 12 ) + demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 6 ) demo_ctx.font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP") demo_ctx.font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono") demo_ctx.font_small = font_load(path_roboto, 10.0, "Roboto") @@ -295,7 +298,7 @@ init :: proc "c" () demo_ctx.font_demo_raincode = font_load(path_noto_sans_jp_reg, 20.0, "NotoSansJPRegular") demo_ctx.font_demo_grid2 = font_load(path_noto_serif_sc, 54.0, "NotoSerifSC") demo_ctx.font_demo_grid3 = font_load(path_bitter, 44.0, "Bitter") - demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 12 ) + demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 3 ) } event :: proc "c" (sokol_event : ^app.Event) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 93166ca..100b63a 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -177,8 +177,8 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N glyph_draw_params := Init_Glyph_Draw_Params_Default, shape_cache_params := Init_Shape_Cache_Params_Default, shaper_params := Init_Shaper_Params_Default, - alpha_sharpen : f32 = 0.1, - px_scalar : f32 = 1.89, + alpha_sharpen : f32 = 0.0, + px_scalar : f32 = 1, // Curve quality to use for a font when unspecified, // Affects step size for bezier curve passes in generate_glyph_pass_draw_list @@ -571,46 +571,46 @@ Zoom : Used with a draw procedure that uses scaling via zoom, will scale the */ @(deferred_in = auto_pop_font) -scope_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } -push_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } -pop_font :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font) } -auto_pop_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) } +scope_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } +push_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } +pop_font :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font) } +auto_pop_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) } @(deferred_in = auto_pop_font_size) -scope_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } -push_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } -pop_font_size :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font_size) } -auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); pop(& ctx.stack.font_size) } +scope_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } +push_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } +pop_font_size :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font_size) } +auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); pop(& ctx.stack.font_size) } @(deferred_in = auto_pop_colour ) -scope_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } -push_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } -pop_colour :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.colour) } -auto_pop_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); pop(& ctx.stack.colour) } +scope_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } +push_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } +pop_colour :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.colour) } +auto_pop_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); pop(& ctx.stack.colour) } @(deferred_in = auto_pop_view) -scope_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } -push_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } -pop_view :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.view) } -auto_pop_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.view) } +scope_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } +push_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } +pop_view :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.view) } +auto_pop_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.view) } @(deferred_in = auto_pop_position) -scope_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } -push_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } -pop_position :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop( & ctx.stack.position) } -auto_pop_position :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop( & ctx.stack.position) } +scope_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } +push_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } +pop_position :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop( & ctx.stack.position) } +auto_pop_position :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop( & ctx.stack.position) } @(deferred_in = auto_pop_scale) -scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } -push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } -pop_scale :: #force_inline proc( ctx : ^Context, ) { assert(ctx != nil); pop(& ctx.stack.scale) } -auto_pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.scale) } +scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } +push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } +pop_scale :: #force_inline proc( ctx : ^Context, ) { assert(ctx != nil); pop(& ctx.stack.scale) } +auto_pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.scale) } @(deferred_in = auto_pop_zoom ) -scope_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom ) } -push_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom) } -pop_zoom :: #force_inline proc( ctx : ^Context ) { pop(& ctx.stack.zoom) } -auto_pop_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { pop(& ctx.stack.zoom) } +scope_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom ) } +push_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom) } +pop_zoom :: #force_inline proc( ctx : ^Context ) { pop(& ctx.stack.zoom) } +auto_pop_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { pop(& ctx.stack.zoom) } @(deferred_in = auto_pop_vpz) scope_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { From 52584f888cdd22edcc011e677ecdec53b8bfb554 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 16:14:59 -0500 Subject: [PATCH 11/62] fixing some normalized space calculation issues I need to review the convention I'm using for the "view" or at least how I interpret these coordinate spaces so its inutitive for the interface. At the end of the day, the draw_list should be in normalized space, however how it gets digested to that state needs to be better documented or made more explicit in its transformation from the usual user calls. --- examples/sokol_demo/sokol_demo.odin | 18 ++++++++++++++---- vefontcache/vefontcache.odin | 18 ++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 6448a3c..7303e69 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -154,8 +154,7 @@ draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, { color_norm := normalize_rgba8(color) def := demo_ctx.font_ids[ font.label ] - - size := size > 2.0 ? size : f32(def.default_size) + size := size > 2.0 ? size : f32(def.default_size) ve.draw_text_normalized_space( & demo_ctx.ve_ctx, def.ve_id, @@ -163,7 +162,7 @@ draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, color_norm, demo_ctx.screen_size, pos, - scale * 1 / demo_ctx.screen_size, + scale, 1.0, content ) @@ -257,7 +256,18 @@ init :: proc "c" () case .DUMMY: fmt.println(">> using dummy backend") } - ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator ) + glyph_draw_opts := ve.Init_Glyph_Draw_Params_Default + glyph_draw_opts.snap_glyph_height = false + + shaper_opts := ve.Init_Shaper_Params_Default + shaper_opts.snap_glyph_position = false + + ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, + glyph_draw_params = glyph_draw_opts, + shaper_params = shaper_opts, + px_scalar = 1.5, + alpha_sharpen = 0.1, + ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 1024 * 1024, index_cap = 1024 * 1024 ) error : mem.Allocator_Error diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 100b63a..3f22030 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -686,7 +686,7 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, colour : RGBAN, - view : Vec2, + view : Vec2, // Screen position : Vec2, scale : Vec2, zoom : f32, // TODO(Ed): Implement zoom support @@ -702,10 +702,13 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, entry := ctx.entries[ font ] adjusted_colour := colour - adjusted_colour.a = 1.0 + ctx.alpha_sharpen + adjusted_colour.a += ctx.alpha_sharpen + + view_norm := 1 / view + scale_norm := scale * view_norm target_px_size := px_size * ctx.px_scalar - target_scale := scale * (1 / ctx.px_scalar) + target_scale := scale_norm * (1 / ctx.px_scalar) target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, @@ -743,12 +746,15 @@ draw_text_normalized_space :: proc( ctx : ^Context, adjusted_position := get_snapped_position( position, view ) - adjusted_colour := colour - adjusted_colour.a = 1.0 + ctx.alpha_sharpen + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen + + view_norm := 1 / view + scale_norm := scale * view_norm // Does nothing when px_scalar is 1.0 target_px_size := px_size * ctx.px_scalar - target_scale := scale * (1 / ctx.px_scalar) + target_scale := scale_norm * (1 / ctx.px_scalar) target_font_scale := parser_scale( entry.parser_info, target_px_size ) shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), From 4afa50f1df028aba0a0b640b8c761df23b487fdf Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 17:45:15 -0500 Subject: [PATCH 12/62] made get_normalized_position_scale (got rid of get_snapped_position), more docs --- examples/sokol_demo/sokol_demo.odin | 19 +- vefontcache/vefontcache.odin | 275 ++++++++++++++++++++-------- 2 files changed, 214 insertions(+), 80 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 7303e69..1238f33 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -156,16 +156,28 @@ draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, def := demo_ctx.font_ids[ font.label ] size := size > 2.0 ? size : f32(def.default_size) + norm_pos, norm_scale := ve.get_normalized_position_scale( pos, scale, demo_ctx.screen_size ) + ve.draw_text_normalized_space( & demo_ctx.ve_ctx, def.ve_id, size, color_norm, - demo_ctx.screen_size, - pos, - scale, + norm_pos, + norm_scale, 1.0, content ) + + // ve.draw_text_view_space( & demo_ctx.ve_ctx, + // def.ve_id, + // size, + // color_norm, + // demo_ctx.screen_size, + // pos, + // scale, + // 1.0, + // content + // ) return } @@ -193,7 +205,6 @@ draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos def.ve_id, resolved_size, color_norm, - screen_size, pos, text_scale, 1.0, diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 3f22030..c2606f3 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -39,19 +39,19 @@ Entry_Default :: Entry { // Ease of use encapsulation of common fields for a canvas space VPZ_Transform :: struct { - view : Vec2, - position : Vec2, - zoom : f32, + view : Vec2, + position : Vec2, + zoom : f32, } Scope_Stack :: struct { - font : [dynamic]Font_ID, - font_size : [dynamic]f32, - colour : [dynamic]RGBAN, - view : [dynamic]Vec2, - position : [dynamic]Vec2, - scale : [dynamic]Vec2, - zoom : [dynamic]f32, + font : [dynamic]Font_ID, + font_size : [dynamic]f32, + colour : [dynamic]RGBAN, + view : [dynamic]Vec2, + position : [dynamic]Vec2, + scale : [dynamic]Vec2, + zoom : [dynamic]f32, } Context :: struct { @@ -104,8 +104,6 @@ Context :: struct { px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. default_curve_quality : i32, - - } Init_Atlas_Params :: struct { @@ -644,21 +642,29 @@ auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } -// Does nothing when view is 1 or 0. -get_snapped_position :: #force_inline proc "contextless" ( position : Vec2, view : Vec2 ) -> (snapped_position : Vec2) { +// 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) +get_normalized_position_scale :: #force_inline proc "contextless" ( position, scale, view : Vec2 ) -> (position_norm, scale_norm : Vec2) +{ snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } should_snap := view * snap_quotient - snapped_position = position + + snapped_position := position snapped_position.x = ceil(position.x * view.x) * snap_quotient.x snapped_position.y = ceil(position.y * view.y) * snap_quotient.y + snapped_position *= should_snap snapped_position.x = max(snapped_position.x, position.x) snapped_position.y = max(snapped_position.y, position.y) - return snapped_position + + position_norm = snapped_position + scale_norm = scale * snap_quotient + return } -resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size, interval, min, max : f32 ) -> (resolved_size : f32) -{ +// Used to constrain the px_size used in draw calls. +resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size, interval, min, max : f32 ) -> (resolved_size : f32) { interval_quotient := 1.0 / f32(interval) size := px_size == 0.0 ? default_size : px_size even_size := round(size * interval_quotient) * interval @@ -666,27 +672,49 @@ resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size return } -set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } -set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } +set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } +set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } +// During a shaping pass on text, will snap each glyph's position via ceil. set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { assert(ctx != nil) ctx.shaper_ctx.snap_glyph_position = should_snap } +// During to_cache pass within batch_generate_glyphs_draw_list, will snap the quad's size using ceil. set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { assert(ctx != nil) ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap) } -// Non-scoping context. The most fundamental interface-level draw shape procedure. -// (everything else is quality of life warppers) +/* The most fundamental interface-level draw shape procedure. + Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied. + view, position, and scale are expected to be in unsigned normalized space: + +| +----------------------------------+ (1.0, 1.0) +| | | +| | | +| | Glyph Quad | +| | +---------+ < scale.y | +| | | ** | * | | +| | | * * | **** | | +| | | **** | * * | | +| | | * * | **** | | +| | +--------+--------+.... | +| | position ^ ^ scale.x | +| | | +| | | +| | | +| (0.0, 0.0) +----------------------------------+ + + • position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned) + <-> scale : Scale the glyph beyond its default scaling from its px_size. +*/ @(optimization_mode="favor_size") draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, colour : RGBAN, - view : Vec2, // Screen position : Vec2, scale : Vec2, zoom : f32, // TODO(Ed): Implement zoom support @@ -697,18 +725,13 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - adjusted_position := get_snapped_position( position, view ) - entry := ctx.entries[ font ] adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen - view_norm := 1 / view - scale_norm := scale * view_norm - target_px_size := px_size * ctx.px_scalar - target_scale := scale_norm * (1 / ctx.px_scalar) + target_scale := scale * (1 / ctx.px_scalar) target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, @@ -722,14 +745,35 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, ) } -// Non-scoping context. The most fundamental interface-level draw text procedure. -// (everything else is quality of life warppers) +/* Non-scoping context. The most fundamental interface-level draw shape procedure (everything else is quality of life warppers). + + Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied. + view, position, and scale are expected to be in unsigned normalized space: + +| +----------------------------------+ (1.0, 1.0) +| | | +| | | +| | Glyph Quad | +| | +---------+ < scale.y | +| | | ** | * | | +| | | * * | **** | | +| | | **** | * * | | +| | | * * | **** | | +| | +--------+--------+.... | +| | position ^ ^ scale.x | +| | | +| | | +| | | +| (0.0, 0.0) +----------------------------------+ + + • position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned) + <-> scale : Scale the glyph beyond its default scaling from its px_size. +*/ @(optimization_mode = "favor_size") draw_text_normalized_space :: proc( ctx : ^Context, font : Font_ID, px_size : f32, colour : RGBAN, - view : Vec2, position : Vec2, scale : Vec2, zoom : f32, // TODO(Ed): Implement Zoom support @@ -744,17 +788,12 @@ draw_text_normalized_space :: proc( ctx : ^Context, ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_position := get_snapped_position( position, view ) - adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen - view_norm := 1 / view - scale_norm := scale * view_norm - // Does nothing when px_scalar is 1.0 target_px_size := px_size * ctx.px_scalar - target_scale := scale_norm * (1 / ctx.px_scalar) + target_scale := scale * (1 / ctx.px_scalar) target_font_scale := parser_scale( entry.parser_info, target_px_size ) shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), @@ -770,12 +809,33 @@ draw_text_normalized_space :: proc( ctx : ^Context, entry, target_px_size, target_font_scale, - adjusted_position, + position, target_scale, ) } -// Equivalent to draw_text_shape_normalized_space, however position's unit convention is expected to be relative to the view +/* Equivalent to draw_text_shape_normalized_space, however the coordinate space is expected to be relative to the view. + view, position, and scale are expected to be in unsigned view space: + +| +----------------------------------+ (view.x, view.y) +| | | +| | | +| | Glyph Quad | +| | +---------+ < scale.y | +| | | ** | * | | +| | | * * | **** | | +| | | **** | * * | | +| | | * * | **** | | +| | +--------+--------+.... | +| | position ^ ^ scale.x | +| | | +| | | +| | | +| (0.0, 0.0) +----------------------------------+ + + • position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned) + <-> scale : Scale the glyph beyond its default scaling from its px_size. +*/ // @(optimization_mode="favor_size") draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, font : Font_ID, @@ -791,19 +851,18 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - - norm_position := position * (1 / view) - - view := view; view.x = max(view.x, 1); view.y = max(view.y, 1) - adjusted_position := get_snapped_position( norm_position, view ) + assert( ctx.px_scalar > 0.0 ) entry := ctx.entries[ font ] adjusted_colour := colour adjusted_colour.a = 1.0 + ctx.alpha_sharpen - target_px_size := px_size * ctx.px_scalar - target_scale := scale * (1 / ctx.px_scalar) + norm_position, norm_scale := get_normalized_position_scale( position, scale, view ) + + // Does nothing if px_scalar is 1.0 + target_px_size := px_size * ctx.px_scalar + target_scale := norm_scale * (1 / ctx.px_scalar) target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, @@ -812,12 +871,33 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, entry, target_px_size, target_font_scale, - position, + norm_position, target_scale, ) } -// Equivalent to draw_text_shape_normalized_space, however position's unit convention is expected to be relative to the view +/* Equivalent to draw_text_shape_normalized_space, however the coordinate space is expected to be relative to the view. + view, position, and scale are expected to be in unsigned view space: + +| +----------------------------------+ (view.x, view.y) +| | | +| | | +| | Glyph Quad | +| | +---------+ < scale.y | +| | | ** | * | | +| | | * * | **** | | +| | | **** | * * | | +| | | * * | **** | | +| | +--------+--------+.... | +| | position ^ ^ scale.x | +| | | +| | | +| | | +| (0.0, 0.0) +----------------------------------+ + + • position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned) + <-> scale : Scale the glyph beyond its default scaling from its px_size. +*/ // @(optimization_mode = "favor_size") draw_text_view_space :: proc(ctx : ^Context, font : Font_ID, @@ -834,20 +914,19 @@ draw_text_view_space :: proc(ctx : ^Context, assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) assert( len(text_utf8) > 0 ) + assert( ctx.px_scalar > 0.0 ) ctx.cursor_pos = {} entry := ctx.entries[ font ] - norm_position := position * (1 / view) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen - adjusted_position := get_snapped_position( norm_position, view ) + norm_position, norm_scale := get_normalized_position_scale( position, scale, view ) - adjusted_colour := colour - adjusted_colour.a = 1.0 + ctx.alpha_sharpen - - // Does nothing when px_scalar is 1.0 - target_px_size := px_size * ctx.px_scalar - target_scale := scale * (1 / ctx.px_scalar) + // Does nothing if px_scalar is 1.0 + target_px_size := px_size * ctx.px_scalar + target_scale := norm_scale * (1 / ctx.px_scalar) target_font_scale := parser_scale( entry.parser_info, target_px_size ) shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), @@ -863,26 +942,48 @@ draw_text_view_space :: proc(ctx : ^Context, entry, target_px_size, target_font_scale, - adjusted_position, + norm_position, target_scale, ) } -// Uses the ctx.stack, position and scale are relative to the position and scale on the stack. +/* Uses the ctx.stack, position and scale are relative to the position and scale on the stack. + +absolute_position := peek(stack.position) + position +absolute_scale := peek(stack.scale ) * scale + +| +-----------------------------------+ (view.x, view.y) +| | | +| | | +| | Glyph Quad absolute | +| | +---------+ < scale.y | +| | | ** | * | | +| | | * * | **** | | +| | | **** | * * | | +| | | * * | **** | | +| | +---------+--------+.... | +| | absolute ^ ^ absolute | +| | position scale.x | +| | | +| | | +| | | +| (0.0, 0.0) +-----------------------------------+ +*/ // @(optimization_mode = "favor_size") draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) { profile(#procedure) assert( ctx != nil ) + assert( ctx.px_scalar > 0.0 ) stack := & ctx.stack - assert(len(stack.font) > 0) - assert(len(stack.view) > 0) - assert(len(stack.colour) > 0) - assert(len(stack.position) > 0) - assert(len(stack.scale) > 0) + assert(len(stack.font) > 0) + assert(len(stack.view) > 0) + assert(len(stack.colour) > 0) + assert(len(stack.position) > 0) + assert(len(stack.scale) > 0) assert(len(stack.font_size) > 0) - assert(len(stack.zoom) > 0) + assert(len(stack.zoom) > 0) // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) font := peek(stack.font) @@ -893,8 +994,8 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_colour := peek(stack.colour) - adjusted_colour.a = 1.0 + ctx.alpha_sharpen + adjusted_colour := peek(stack.colour) + adjusted_colour.a += ctx.alpha_sharpen // TODO(Ed): Implement zoom for draw_text zoom := peek(stack.zoom) @@ -922,22 +1023,44 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ) } -// Uses the ctx.stack, position and scale are relative to the position and scale on the stack. +/* Uses the ctx.stack, position and scale are relative to the position and scale on the stack. + +absolute_position := peek(stack.position) + position +absolute_scale := peek(stack.scale ) * scale + +| +-----------------------------------+ (view.x, view.y) +| | | +| | | +| | Glyph Quad absolute | +| | +---------+ < scale.y | +| | | ** | * | | +| | | * * | **** | | +| | | **** | * * | | +| | | * * | **** | | +| | +---------+--------+.... | +| | absolute ^ ^ absolute | +| | position scale.x | +| | | +| | | +| | | +| (0.0, 0.0) +-----------------------------------+ +*/ // @(optimization_mode = "favor_size") draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) { profile(#procedure) assert( ctx != nil ) assert( len(text_utf8) > 0 ) + assert( ctx.px_scalar > 0.0 ) stack := & ctx.stack - assert(len(stack.font) > 0) + assert(len(stack.font) > 0) assert(len(stack.font_size) > 0) - assert(len(stack.colour) > 0) - assert(len(stack.view) > 0) - assert(len(stack.position) > 0) - assert(len(stack.scale) > 0) - assert(len(stack.zoom) > 0) + assert(len(stack.colour) > 0) + assert(len(stack.view) > 0) + assert(len(stack.position) > 0) + assert(len(stack.scale) > 0) + assert(len(stack.zoom) > 0) font := peek(stack.font) assert( font >= 0 &&int(font) < len(ctx.entries) ) @@ -947,8 +1070,8 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_colour := peek(stack.colour) - adjusted_colour.a = 1.0 + ctx.alpha_sharpen + adjusted_colour := peek(stack.colour) + adjusted_colour.a += ctx.alpha_sharpen // TODO(Ed): Implement zoom for draw_text zoom := peek(stack.zoom) From 7af3c49dfc7111e89fdf4af0f050923dfea2f20d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 17:53:11 -0500 Subject: [PATCH 13/62] remove ols.json --- .gitignore | 2 ++ ols.json | 31 ------------------------------- 2 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 ols.json diff --git a/.gitignore b/.gitignore index fb8d7a6..1e9b221 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ build # thirdparty .vscode +ols.json + # backend/sokol/render_glyph.odin # backend/sokol/blit_atlas.odin # backend/sokol/draw_text.odin diff --git a/ols.json b/ols.json deleted file mode 100644 index 4f0edda..0000000 --- a/ols.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", - "odin_command": "odin.exe", - "collections": [ - { - "name": "backend", - "path": "./backend" - }, - { - "name": "examples", - "path": "./sectr" - }, - { - "name": "thirdparty", - "path": "./thirdparty" - } - ], - "enable_document_symbols": true, - "enable_fake_methods": true, - "enable_format": false, - "enable_hover": true, - "enable_semantic_tokens": false, - "enable_snippets": false, - "enable_references": true, - "thread_pool_count": 10, - "enable_inlay_hints": true, - "enable_procedure_context": true, - "enable_procedure_snippet": false, - "verbose": true, - "disable_parser_errors": true -} From 3b59ac75bff4b652b0c1afd8aa325e4352ab6b62 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 17:54:18 -0500 Subject: [PATCH 14/62] update ignore for thirdparty --- .gitignore | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 1e9b221..7a505e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ -build -# thirdparty -.vscode +.vscode ols.json - -# backend/sokol/render_glyph.odin -# backend/sokol/blit_atlas.odin -# backend/sokol/draw_text.odin +build +thirdparty/freetype +thirdparty/harfbuzz +thirdparty/sokol +thirdparty/sokol-tools From c995af36e7c414856e1088b18a4c72bf084fbdd6 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 20:16:51 -0500 Subject: [PATCH 15/62] Got zoom implemented on library's side. Still figuring out font size issue --- examples/sokol_demo/sokol_demo.odin | 181 +++++++++++----------------- vefontcache/shaper.odin | 42 +++---- vefontcache/vefontcache.odin | 180 ++++++++++++++++----------- 3 files changed, 198 insertions(+), 205 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 1238f33..9ddb10a 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -150,66 +150,23 @@ get_font_vertical_metrics :: #force_inline proc ( font : Font_ID, font_size := F } // Draw text using a string and normalized render coordinates -draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE, scale : f32 = 1.0 ) +draw_text :: proc( content : string, font : Font_ID, pos : Vec2, size : f32 = 0.0, color := COLOR_WHITE, scale : f32 = 1.0, zoom : f32 = 1.0 ) { color_norm := normalize_rgba8(color) def := demo_ctx.font_ids[ font.label ] - size := size > 2.0 ? size : f32(def.default_size) + size := size >= 2.0 ? size : f32(def.default_size) - norm_pos, norm_scale := ve.get_normalized_position_scale( pos, scale, demo_ctx.screen_size ) - - ve.draw_text_normalized_space( & demo_ctx.ve_ctx, + ve.draw_text_view_space( & demo_ctx.ve_ctx, def.ve_id, size, color_norm, - norm_pos, - norm_scale, - 1.0, - content - ) - - // ve.draw_text_view_space( & demo_ctx.ve_ctx, - // def.ve_id, - // size, - // color_norm, - // demo_ctx.screen_size, - // pos, - // scale, - // 1.0, - // content - // ) - return -} - -// Adapt the draw_text_string_pos_extent_zoomed procedure -draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos : Vec2, zoom : f32, color := COLOR_WHITE) -{ - screen_size := demo_ctx.screen_size - screen_scale := 1 / screen_size - zoom_adjust_size := size * zoom - - resolved_size := size - - text_scale := screen_scale - { - diff_scalar := 1 + (zoom_adjust_size - resolved_size) / resolved_size - text_scale = diff_scalar * screen_scale - text_scale.x = clamp(text_scale.x, 0, 1) - text_scale.y = clamp(text_scale.y, 0, 1) - } - - color_norm := normalize_rgba8(color) - def := demo_ctx.font_ids[ font.label ] - - ve.draw_text_normalized_space(& demo_ctx.ve_ctx, - def.ve_id, - resolved_size, - color_norm, + demo_ctx.screen_size, pos, - text_scale, - 1.0, + scale, + zoom, content ) + return } sokol_app_alloc :: proc "c" ( size : uint, user_data : rawptr ) -> rawptr { @@ -236,7 +193,6 @@ sokol_gfx_free :: proc "c" ( data : rawptr, user_data : rawptr ) { free(data, allocator = context.allocator ) } - init :: proc "c" () { context = runtime.default_context() @@ -268,18 +224,18 @@ init :: proc "c" () } glyph_draw_opts := ve.Init_Glyph_Draw_Params_Default - glyph_draw_opts.snap_glyph_height = false + glyph_draw_opts.snap_glyph_height = true shaper_opts := ve.Init_Shaper_Params_Default - shaper_opts.snap_glyph_position = false + shaper_opts.snap_glyph_position = true ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, glyph_draw_params = glyph_draw_opts, - shaper_params = shaper_opts, - px_scalar = 1.5, - alpha_sharpen = 0.1, + shaper_params = shaper_opts, + px_scalar = 1, + alpha_sharpen = 0.1, ) - ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 1024 * 1024, index_cap = 1024 * 1024 ) + ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) error : mem.Allocator_Error demo_ctx.font_ids, error = make( map[string]Font_Entry, 256 ) @@ -301,12 +257,12 @@ init :: proc "c" () path_noto_sans_jp_reg := strings.concatenate({ PATH_FONTS, "NotoSansJP-Regular.otf" }) path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" }) - demo_ctx.font_logo = font_load(path_sawarabi_mincho, 330.0, "SawarabiMincho", 6 ) - demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 6 ) + demo_ctx.font_logo = font_load(path_sawarabi_mincho, 300.0, "SawarabiMincho", 12 ) + // demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 6 ) demo_ctx.font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP") demo_ctx.font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono") demo_ctx.font_small = font_load(path_roboto, 10.0, "Roboto") - demo_ctx.font_demo_sans = font_load(path_open_sans, 18.0, "OpenSans") + demo_ctx.font_demo_sans = font_load(path_open_sans, 18.0, "OpenSans", 6) demo_ctx.font_demo_serif = font_load(path_bitter, 18.0, "Bitter") demo_ctx.font_demo_script = font_load(path_dancing_script, 22.0, "DancingScript") demo_ctx.font_demo_mono = font_load(path_nova_mono, 18.0, "NovaMono") @@ -317,9 +273,10 @@ init :: proc "c" () demo_ctx.font_demo_arabic = font_load(path_tajawal, 24.0, "Tajawal") demo_ctx.font_demo_hebrew = font_load(path_david_libre, 22.0, "DavidLibre") demo_ctx.font_demo_raincode = font_load(path_noto_sans_jp_reg, 20.0, "NotoSansJPRegular") - demo_ctx.font_demo_grid2 = font_load(path_noto_serif_sc, 54.0, "NotoSerifSC") - demo_ctx.font_demo_grid3 = font_load(path_bitter, 44.0, "Bitter") - demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 3 ) + demo_ctx.font_title = demo_ctx.font_demo_sans + demo_ctx.font_demo_grid2 = demo_ctx.font_demo_chinese + demo_ctx.font_demo_grid3 = demo_ctx.font_demo_serif + demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 12 ) } event :: proc "c" (sokol_event : ^app.Event) @@ -371,7 +328,7 @@ frame :: proc "c" () // Frametime display frametime_text := fmt.tprintf("Frametime %v", frame_duration) - draw_text_string_pos_norm(frametime_text, demo_ctx.font_title, 0, {0.0, 0.0}, COLOR_WHITE) + draw_text(frametime_text, demo_ctx.font_title, {0.0, 0.0}, size = 30) if current_scroll < 1.5 { intro := `Ça va! Everything here is rendered using VE Font Cache, a single header-only library designed for game engines. @@ -386,9 +343,9 @@ It aims to: • Support cached text shaping with HarfBuzz with simple Latin-style fallback. • Load and unload fonts at any time.` - draw_text_string_pos_norm("ゑ", demo_ctx.font_logo, 330, { 0.4, current_scroll }, COLOR_WHITE) - draw_text_string_pos_norm("VEFontCache Demo", demo_ctx.font_title, 92, { 0.2, current_scroll - 0.1 }, COLOR_WHITE) - draw_text_string_pos_norm(intro, demo_ctx.font_print, 19, { 0.2, current_scroll - 0.14 }, COLOR_WHITE) + draw_text("ゑ", demo_ctx.font_logo, { 0.4, current_scroll }, size = 200, scale = 2.0) + draw_text("VEFontCache Demo", demo_ctx.font_title, { 0.2, current_scroll - 0.1 }, size = 92) + draw_text(intro, demo_ctx.font_print, { 0.2, current_scroll - 0.14 }, size = 19) } section_start : f32 = 0.42 @@ -431,10 +388,10 @@ Glyphs are first rendered to an intermediate 2k x 512px R8 texture. This allows 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.` - draw_text_string_pos_norm("How it works", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.06) }, COLOR_WHITE) - draw_text_string_pos_norm(how_it_works, demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.1) }, COLOR_WHITE) - draw_text_string_pos_norm(caching_strategy, demo_ctx.font_mono, 21, { 0.28, current_scroll - (section_start + 0.32) }, COLOR_WHITE) - draw_text_string_pos_norm(how_it_works2, demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.82) }, COLOR_WHITE) + draw_text("How it works", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.06) }) + draw_text(how_it_works, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.1) }) + draw_text(caching_strategy, demo_ctx.font_mono, { 0.28, current_scroll - (section_start + 0.32) }) + draw_text(how_it_works2, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.82) }) } // Showcase section @@ -445,49 +402,49 @@ intermediate texture to the final atlas location.` incididunt ut labore et dolore magna aliqua. Est ullamcorper eget nulla facilisi etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` - draw_text_string_pos_norm("Showcase", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE) - draw_text_string_pos_norm("This is a showcase demonstrating different hb_font categories and languages.", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.24) }, COLOR_WHITE) + draw_text("Showcase", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 92) + draw_text("This is a showcase demonstrating different hb_font categories and languages.", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.24) }, size = 19) - draw_text_string_pos_norm("Sans serif", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.28) }, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.28) }, COLOR_WHITE) + draw_text("Sans serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.28) }, size = 19) + draw_text(font_family_test, demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.28) }, size = 18) - draw_text_string_pos_norm("Serif", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.36) }, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_serif, 18, { 0.3, current_scroll - (section_start + 0.36) }, COLOR_WHITE) + draw_text("Serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.36) }) + draw_text(font_family_test, demo_ctx.font_demo_serif, { 0.3, current_scroll - (section_start + 0.36) }) - draw_text_string_pos_norm("Script", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.44) }, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_script, 22, { 0.3, current_scroll - (section_start + 0.44) }, COLOR_WHITE) + draw_text("Script", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.44) }) + draw_text(font_family_test, demo_ctx.font_demo_script, { 0.3, current_scroll - (section_start + 0.44) }) - draw_text_string_pos_norm("Monospace", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.52) }, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_mono, 18, { 0.3, current_scroll - (section_start + 0.52) }, COLOR_WHITE) + draw_text("Monospace", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.52) }) + draw_text(font_family_test, demo_ctx.font_demo_mono, { 0.3, current_scroll - (section_start + 0.52) }) - draw_text_string_pos_norm("Small", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.60) }, COLOR_WHITE) - draw_text_string_pos_norm(font_family_test, demo_ctx.font_small, 10, { 0.3, current_scroll - (section_start + 0.60) }, COLOR_WHITE) + draw_text("Small", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.60) }) + draw_text(font_family_test, demo_ctx.font_small, { 0.3, current_scroll - (section_start + 0.60) }) - draw_text_string_pos_norm("Greek", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.72) }, COLOR_WHITE) - draw_text_string_pos_norm("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.72) }, COLOR_WHITE) + draw_text("Greek", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.72) }) + draw_text("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.72) }) - draw_text_string_pos_norm("Vietnamese", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.76) }, COLOR_WHITE) - draw_text_string_pos_norm("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.76) }, COLOR_WHITE) + draw_text("Vietnamese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.76) }) + draw_text("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.76) }) - draw_text_string_pos_norm("Thai", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.80)}, COLOR_WHITE) - draw_text_string_pos_norm("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, 24, {0.3, current_scroll - (section_start + 0.80)}, COLOR_WHITE) + draw_text("Thai", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.80) }) + draw_text("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, { 0.3, current_scroll - (section_start + 0.80) }) - draw_text_string_pos_norm("Chinese", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.84)}, COLOR_WHITE) - draw_text_string_pos_norm("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, 24, {0.3, current_scroll - (section_start + 0.84)}, COLOR_WHITE) + draw_text("Chinese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.84) }) + draw_text("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, {0.3, current_scroll - (section_start + 0.84) }) - draw_text_string_pos_norm("Japanese", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.88)}, COLOR_WHITE) - draw_text_string_pos_norm("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, 24, {0.3, current_scroll - (section_start + 0.88)}, COLOR_WHITE) + draw_text("Japanese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.88) }) + draw_text("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, { 0.3, current_scroll - (section_start + 0.88) }) - draw_text_string_pos_norm("Korean", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.92)}, COLOR_WHITE) - draw_text_string_pos_norm("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, 36, {0.3, current_scroll - (section_start + 0.92)}, COLOR_WHITE) + draw_text("Korean", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.92) }) + draw_text("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, { 0.3, current_scroll - (section_start + 0.92) }) - draw_text_string_pos_norm("Needs harfbuzz to work:", demo_ctx.font_print, 14, {0.2, current_scroll - (section_start + 0.96)}, COLOR_WHITE) + draw_text("Needs harfbuzz to work:", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.96)}) - draw_text_string_pos_norm("Arabic", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 1.00)}, COLOR_WHITE) - draw_text_string_pos_norm("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, 24, {0.3, current_scroll - (section_start + 1.00)}, COLOR_WHITE) + draw_text("Arabic", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.00) }) + draw_text("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, { 0.3, current_scroll - (section_start + 1.00) }) - draw_text_string_pos_norm("Hebrew", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 1.04)}, COLOR_WHITE) - draw_text_string_pos_norm("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, 22, {0.3, current_scroll - (section_start + 1.04)}, COLOR_WHITE) + draw_text("Hebrew", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.04) }) + draw_text("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, { 0.3, current_scroll - (section_start + 1.04) }) } // Zoom Test @@ -525,21 +482,21 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` zoom_info_y := current_scroll - (section_start + 0.10) zoomed_text_y := current_scroll - (section_start + 0.30) + math.sin(zoom_time) * 0.02 - draw_text_string_pos_norm("Zoom Test", demo_ctx.font_title, 92, {0.2, title_y}, COLOR_WHITE) + draw_text("Zoom Test", demo_ctx.font_title, { 0.2, title_y }, size = 92) zoomed_text_base_size : f32 = 12.0 zoom_adjust_size := zoomed_text_base_size * current_zoom // ve_id, resolved_size := font_resolve_draw_id( font_firacode, zoom_adjust_size * OVER_SAMPLE_ZOOM ) resolved_size := zoom_adjust_size current_zoom_text := fmt.tprintf("Current Zoom : %.2f x\nCurrent Resolved Size: %v px", current_zoom, resolved_size ) - draw_text_string_pos_norm(current_zoom_text, demo_ctx.font_firacode, 19, {0.2, zoom_info_y}, COLOR_WHITE) + draw_text(current_zoom_text, demo_ctx.font_firacode, { 0.2, zoom_info_y }) // ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) ) size := measure_text_size( zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, 0 ) * current_zoom x_offset := (size.x / demo_ctx.screen_size.x) * 0.5 zoomed_text_pos := Vec2 { 0.5 - x_offset, zoomed_text_y } - draw_text_zoomed_norm(zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, zoomed_text_pos, current_zoom, COLOR_WHITE) + draw_text(zoom_text, demo_ctx.font_firacode, zoomed_text_pos, size = zoomed_text_base_size, zoom = current_zoom) } // Raincode Demo @@ -562,7 +519,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "Z", "T", "H", "E", "|", "¦", "日", "ハ", "ミ", "ヒ", "ー", "ウ", "シ", "ナ", "モ", "ニ", "サ", "ワ", "ツ", "オ", "リ", "ア", "ホ", "テ", "マ", "ケ", "メ", "エ", "カ", "キ", "ム", "ユ", "ラ", "セ", "ネ", "ス", "ツ", "タ", "ヌ", "ヘ", ":", "・", ".", - "\"", "=", "*", "+", "-", "<", ">", "ç", "リ", "ク", "コ", "チ", "ヤ", "ル", "ン", "C", "O", "D" + "\"", "=", "*", "+", "-", "<", ">", "ç", "リ", "ク", "コ", "チ", "ヤ", "ル", "ン", "C", "O", "D", } if !init_grid { @@ -571,7 +528,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } @static fixed_timestep_passed : f32 = 0.0 - fixed_timestep : f32 = (1.0 / 30.0) + fixed_timestep : f32 = (1.0 / 20.0) fixed_timestep_passed += frame_duration for fixed_timestep_passed > fixed_timestep { @@ -591,7 +548,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } // Draw grid - draw_text_string_pos_norm("Raincode demo", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE) + draw_text("Raincode demo", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 92) for y in 0 ..< GRID_H do for x in 0 ..< GRID_W { pos_x := 0.2 + f32(x) * 0.007 @@ -608,7 +565,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` if code_colour.a == 0 do continue } - draw_text_string_pos_norm(codes[grid[y * GRID_W + x]], demo_ctx.font_demo_raincode, 20, {pos_x, pos_y}, code_colour) + draw_text(codes[grid[y * GRID_W + x]], demo_ctx.font_demo_raincode, { pos_x, pos_y }, size = 20, color = code_colour) } // ve.set_colour(&ve_ctx, {1.0, 1.0, 1.0, 1.0}) @@ -634,7 +591,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` @static fixed_timestep_passed : f32 = 0.0 fixed_timestep_passed += frame_duration - fixed_timestep := f32(1.0 / 30.0) + fixed_timestep := f32(1.0 / 20.0) for fixed_timestep_passed > fixed_timestep { rotate_current = (rotate_current + 1) % 4 @@ -678,28 +635,28 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } // Draw grid - draw_text_string_pos_norm("Cache pressure test", demo_ctx.font_title, 92, {0.2, current_scroll - (section_start + 0.2)}, COLOR_WHITE) + draw_text("Cache pressure test (throttled to 120 hz)", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 92) for y in 0..< GRID_H do for x in 0 ..< GRID_W { posx := 0.2 + f32(x) * 0.02 posy := current_scroll - (section_start + 0.24 + f32(y) * 0.025) c := [5]u8{} codepoint_to_utf8(c[:], grid[ y * GRID_W + x ]) - draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_chinese, 24, {posx, posy}, COLOR_WHITE) + draw_text(string( c[:] ), demo_ctx.font_demo_chinese, { posx, posy }, size = 24) } for y in 0 ..< GRID2_H do for x in 0 ..< GRID2_W { posx := 0.2 + f32(x) * 0.03 posy := current_scroll - (section_start + 0.66 + f32(y) * 0.052) c := [5]u8{} codepoint_to_utf8(c[:], grid2[ y * GRID2_W + x ]) - draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_grid2, 54, {posx, posy}, COLOR_WHITE) + draw_text(string( c[:] ), demo_ctx.font_demo_chinese, { posx, posy }, size = 54) } for y in 0 ..< GRID3_H do for x in 0 ..< GRID3_W { posx := 0.45 + f32(x) * 0.02 posy := current_scroll - (section_start + 0.64 + f32(y) * 0.034) c := [5]u8{} codepoint_to_utf8( c[:], grid3[ y * GRID3_W + x ]) - draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_grid3, 44, {posx, posy}, COLOR_WHITE) + draw_text(string( c[:] ), demo_ctx.font_demo_serif, { posx, posy }, size = 44) } } diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index 5412349..e342f8e 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -16,10 +16,10 @@ Shape_Key :: u32 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. - * Resolve atlas lru codes - * Resolve glyph bounds and scale - * Resolve atlas region the glyph is associated with. + 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. @@ -82,7 +82,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 ) @@ -144,9 +144,9 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry 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 ) @@ -171,12 +171,11 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry position.x = 0.0 position.y -= line_height position.y = floor(position.y) - (line_count^) += 1 + (line_count^) += 1 continue } - if abs( font_px_size ) <= adv_snap_small_font_threshold - { - (position^) = ceil( position^ ) + if abs( font_px_size ) <= adv_snap_small_font_threshold { + (position^) = ceil( position^ ) } glyph_pos := position^ @@ -219,7 +218,10 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry ScriptKind :: harfbuzz.Script special_script : b32 = script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON - if special_script || script == current_script || byte_offset == 0 { + if special_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 continue @@ -287,15 +289,13 @@ shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context resize( & output.bounds, len(output.glyph) ) profile_begin("atlas_lru_code") - for id, index in output.glyph - { + for id, index in output.glyph { output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id) } profile_end() profile_begin("bounds & region") - for id, index in output.glyph - { + for id, index in output.glyph { bounds := & output.bounds[index] (bounds ^) = parser_get_bounds( entry.parser_info, id ) bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale @@ -377,15 +377,13 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, resize( & output.bounds, len(output.glyph) ) profile_begin("atlas_lru_code") - for id, index in output.glyph - { + for id, index in output.glyph { output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id) } profile_end() profile_begin("bounds & region") - for id, index in output.glyph - { + for id, index in output.glyph { bounds := & output.bounds[index] (bounds ^) = parser_get_bounds( entry.parser_info, id ) bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale @@ -429,12 +427,12 @@ shaper_shape_text_cached :: proc( text_utf8 : string, 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 != LRU_Fail_Mask_32 ) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index c2606f3..2197217 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -101,7 +101,8 @@ Context :: struct { // Used by draw interface to super-scale the text by // upscaling px_size with px_scalar and then down-scaling // the draw_list result by the same amount. - px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. + px_scalar : f32, // Improves hinting, positioning, etc. Can make zoomed out text too jagged. + zoom_px_interval : f32, // When using zoom, the size can be locked to to this interval (fixes text width jitter) default_curve_quality : i32, } @@ -177,6 +178,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N shaper_params := Init_Shaper_Params_Default, alpha_sharpen : f32 = 0.0, px_scalar : f32 = 1, + zoom_px_interval : i32 = 2, // Curve quality to use for a font when unspecified, // Affects step size for bezier curve passes in generate_glyph_pass_draw_list @@ -190,8 +192,9 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N ctx.backing = allocator context.allocator = ctx.backing - ctx.alpha_sharpen = alpha_sharpen - ctx.px_scalar = px_scalar + ctx.alpha_sharpen = alpha_sharpen + ctx.px_scalar = px_scalar + ctx.zoom_px_interval = f32(zoom_px_interval) shaper_ctx := & ctx.shaper_ctx shaper_ctx.adv_snap_small_font_threshold = f32(shaper_params.adv_snap_small_font_threshold) @@ -664,16 +667,15 @@ get_normalized_position_scale :: #force_inline proc "contextless" ( position, sc } // Used to constrain the px_size used in draw calls. -resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, default_size, interval, min, max : f32 ) -> (resolved_size : f32) { +resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, interval, min, max : f32 ) -> (resolved_size : f32) { interval_quotient := 1.0 / f32(interval) - size := px_size == 0.0 ? default_size : px_size - even_size := round(size * interval_quotient) * interval - resolved_size = clamp( even_size, min, max ) + interval_size := round(px_size * interval_quotient) * interval + resolved_size = clamp( interval_size, min, max ) return } set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } -set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } +set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } // During a shaping pass on text, will snap each glyph's position via ceil. set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { @@ -695,7 +697,7 @@ set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap | | | | | | | | Glyph Quad | -| | +---------+ < scale.y | +| | +--------+ < scale.y | | | | ** | * | | | | | * * | **** | | | | | **** | * * | | @@ -717,12 +719,12 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, colour : RGBAN, position : Vec2, scale : Vec2, - zoom : f32, // TODO(Ed): Implement zoom support shape : Shaped_Text ) { profile(#procedure) assert( ctx != nil ) + // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) assert( font >= 0 && int(font) < len(ctx.entries) ) entry := ctx.entries[ font ] @@ -730,9 +732,9 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen - 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 ) + 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 ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, @@ -771,13 +773,13 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, */ @(optimization_mode = "favor_size") draw_text_normalized_space :: proc( ctx : ^Context, - font : Font_ID, - px_size : f32, - colour : RGBAN, - position : Vec2, - scale : Vec2, - zoom : f32, // TODO(Ed): Implement Zoom support - text_utf8 : string + font : Font_ID, + px_size : f32, + colour : RGBAN, + position : Vec2, + scale : Vec2, + text_utf8 : string, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_text_uncached_advanced ) { profile(#procedure) @@ -792,16 +794,16 @@ draw_text_normalized_space :: proc( ctx : ^Context, adjusted_colour.a += ctx.alpha_sharpen // Does nothing when px_scalar is 1.0 - target_px_size := px_size * ctx.px_scalar - target_scale := scale * (1 / ctx.px_scalar) - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + 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 ) shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), font, entry, target_px_size, target_font_scale, - shaper_shape_text_uncached_advanced + shaper_proc ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, @@ -833,8 +835,10 @@ draw_text_normalized_space :: proc( ctx : ^Context, | | | | (0.0, 0.0) +----------------------------------+ + □ view : The coordinate space is scaled to the view. Positions will be snapped to it. • position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned) <-> scale : Scale the glyph beyond its default scaling from its px_size. + zoom : Will affect the scale similar to how the zoom on a canvas would behave. */ // @(optimization_mode="favor_size") draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, @@ -844,12 +848,13 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, view : Vec2, position : Vec2, scale : Vec2, - zoom : f32, // TODO(Ed): Implement zoom support + zoom : f32, shape : Shaped_Text ) { profile(#procedure) assert( ctx != nil ) + // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) assert( font >= 0 && int(font) < len(ctx.entries) ) assert( ctx.px_scalar > 0.0 ) @@ -858,12 +863,20 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, adjusted_colour := colour adjusted_colour.a = 1.0 + ctx.alpha_sharpen - norm_position, norm_scale := get_normalized_position_scale( position, scale, view ) + // Does nothing when zoom is 1.0 + zoom_px_size := px_size * zoom + resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval, 2, 999.0 ) + zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) + zoom_scale := zoom_diff_scalar * scale + zoom_scale.x = clamp(zoom_scale.x, 0, view.x) + zoom_scale.y = clamp(zoom_scale.y, 0, view.y) + + norm_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) // Does nothing if px_scalar is 1.0 - target_px_size := px_size * ctx.px_scalar - target_scale := norm_scale * (1 / ctx.px_scalar) - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + target_px_size := resolved_size * ctx.px_scalar + target_scale := norm_scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, @@ -895,19 +908,22 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, | | | | (0.0, 0.0) +----------------------------------+ + □ view : The coordinate space is scaled to the view. Positions will be snapped to it. • position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned) <-> scale : Scale the glyph beyond its default scaling from its px_size. + zoom : Will affect the scale similar to how the zoom on a canvas would behave. */ // @(optimization_mode = "favor_size") draw_text_view_space :: proc(ctx : ^Context, - font : Font_ID, - px_size : f32, - colour : RGBAN, - view : Vec2, - position : Vec2, - scale : Vec2, - zoom : f32, // TODO(Ed): Implement Zoom support - text_utf8 : string + font : Font_ID, + px_size : f32, + colour : RGBAN, + view : Vec2, + position : Vec2, + scale : Vec2, + zoom : f32, + text_utf8 : string, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_text_uncached_advanced ) { profile(#procedure) @@ -922,19 +938,27 @@ draw_text_view_space :: proc(ctx : ^Context, adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen - norm_position, norm_scale := get_normalized_position_scale( position, scale, view ) + // Does nothing when zoom is 1.0 + zoom_px_size := px_size * zoom + resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval, 2, 999.0 ) + zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) + zoom_scale := zoom_diff_scalar * scale + zoom_scale.x = clamp(zoom_scale.x, 0, view.x) + zoom_scale.y = clamp(zoom_scale.y, 0, view.y) + + target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) // Does nothing if px_scalar is 1.0 - target_px_size := px_size * ctx.px_scalar - target_scale := norm_scale * (1 / ctx.px_scalar) - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + target_px_size := resolved_size * ctx.px_scalar + target_scale := norm_scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), font, entry, target_px_size, target_font_scale, - shaper_shape_text_uncached_advanced + shaper_proc ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, @@ -942,7 +966,7 @@ draw_text_view_space :: proc(ctx : ^Context, entry, target_px_size, target_font_scale, - norm_position, + target_position, target_scale, ) } @@ -962,8 +986,8 @@ absolute_scale := peek(stack.scale ) * scale | | | **** | * * | | | | | * * | **** | | | | +---------+--------+.... | -| | absolute ^ ^ absolute | -| | position scale.x | +| | absolute ^ ^ absolute | +| | position scale.x | | | | | | | | | | @@ -997,28 +1021,34 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text adjusted_colour := peek(stack.colour) adjusted_colour.a += ctx.alpha_sharpen - // TODO(Ed): Implement zoom for draw_text - zoom := peek(stack.zoom) - - absolute_position := peek(stack.position) + position - absolute_scale := peek(stack.scale) * scale - - adjusted_position := get_snapped_position( absolute_position, view ) - px_size := peek(stack.font_size) + // Does nothing when zoom is 1.0 + zoom := peek(stack.zoom) + zoom_px_size := zoom * px_size + resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval, 2, 999.0 ) + zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) + zoom_scale := zoom_diff_scalar * scale + zoom_scale.x = clamp(zoom_scale.x, 0, view.x) + zoom_scale.y = clamp(zoom_scale.y, 0, view.y) + + absolute_position := peek(stack.position) + position + absolute_scale := peek(stack.scale) * zoom_scale + + target_position, norm_scale := get_normalized_position_scale( absolute_position, absolute_scale, view ) + // Does nothing when px_scalar is 1.0 - target_px_size := px_size * ctx.px_scalar - target_scale := absolute_scale * (1 / ctx.px_scalar) - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + target_px_size := resolved_size * ctx.px_scalar + target_scale := norm_scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, adjusted_colour, entry, target_px_size, - target_font_scale, - adjusted_position, + target_font_scale, + target_position, target_scale, ) } @@ -1038,15 +1068,17 @@ absolute_scale := peek(stack.scale ) * scale | | | **** | * * | | | | | * * | **** | | | | +---------+--------+.... | -| | absolute ^ ^ absolute | -| | position scale.x | +| | absolute ^ ^ absolute | +| | position scale.x | | | | | | | | | | | (0.0, 0.0) +-----------------------------------+ */ // @(optimization_mode = "favor_size") -draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) +draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_text_uncached_advanced +) { profile(#procedure) assert( ctx != nil ) @@ -1073,27 +1105,33 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) adjusted_colour := peek(stack.colour) adjusted_colour.a += ctx.alpha_sharpen - // TODO(Ed): Implement zoom for draw_text - zoom := peek(stack.zoom) + px_size := peek(stack.font_size) + + // Does nothing when zoom is 1.0 + zoom := peek(stack.zoom) + zoom_px_size := zoom * px_size + resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval , 2, 999.0 ) + zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) + zoom_scale := zoom_diff_scalar * scale + zoom_scale.x = clamp(zoom_scale.x, 0, view.x) + zoom_scale.y = clamp(zoom_scale.y, 0, view.y) absolute_position := peek(stack.position) + position absolute_scale := peek(stack.scale) * scale - adjusted_position := get_snapped_position( absolute_position, view ) - - px_size := peek(stack.font_size) + target_position, norm_scale := get_normalized_position_scale( absolute_position, absolute_scale, view ) // Does nothing when px_scalar is 1.0 - target_px_size := px_size * ctx.px_scalar - target_scale := absolute_scale * (1 / ctx.px_scalar) - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + target_px_size := resolved_size * ctx.px_scalar + target_scale := norm_scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, target_px_size ) shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size), font, entry, target_px_size, target_font_scale, - shaper_shape_text_uncached_advanced + shaper_proc ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, @@ -1101,7 +1139,7 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string ) entry, target_px_size, target_font_scale, - adjusted_position, + target_position, target_scale, ) } From a5844975635e7fdb6128c6fd28739cf6ab86b6d5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 20:36:30 -0500 Subject: [PATCH 16/62] left zoom compute to its own proc --- vefontcache/vefontcache.odin | 126 +++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 50 deletions(-) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 2197217..0833dfe 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -338,14 +338,44 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N parser_init( & ctx.parser_ctx, parser_kind ) shaper_init( & ctx.shaper_ctx ) + // Scoping Stack + { + stack := & ctx.stack + + error : Allocator_Error + stack.font, error = make([dynamic]Font_ID, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.font") + + stack.font_size, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.font_size") + + stack.font_size, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.font_size") + + stack.colour, error = make([dynamic]RGBAN, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.colour") + + stack.view, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.view") + + stack.position, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.position") + + stack.scale, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.scale") + + stack.zoom, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve) + assert(error == .None, "VEFontCache.init : Failed to allocate stack.zoom") + } + // Set the default stack values // Will be popped on shutdown - // push_colour(ctx, {1, 1, 1, 1}) - // push_font_size(ctx, 36) - // push_view(ctx, { 0, 0 }) - // push_position(ctx, {0, 0}) - // push_scale(ctx, 1.0) - // push_zoom(ctx, 1.0) + push_colour(ctx, {1, 1, 1, 1}) + push_font_size(ctx, 36) + push_view(ctx, { 0, 0 }) + push_position(ctx, {0, 0}) + push_scale(ctx, 1.0) + push_zoom(ctx, 1.0) } hot_reload :: proc( ctx : ^Context, allocator : Allocator ) @@ -395,8 +425,13 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) reload_array( & shape_cache.storage, allocator ) reload_array( & draw_list.vertices, allocator) - reload_array( & draw_list.indices, allocator ) - reload_array( & draw_list.calls, allocator ) + reload_array( & draw_list.indices, allocator) + reload_array( & draw_list.calls, allocator) + + // Scope Stack + { + + } } shutdown :: proc( ctx : ^Context ) @@ -409,12 +444,12 @@ shutdown :: proc( ctx : ^Context ) shape_cache := & ctx.shape_cache draw_list := & ctx.draw_list - // pop_colour(ctx) - // pop_font_size(ctx) - // pop_view(ctx) - // pop_position(ctx) - // pop_scale(ctx) - // pop_zoom(ctx) + pop_colour(ctx) + pop_font_size(ctx) + pop_view(ctx) + pop_position(ctx) + pop_scale(ctx) + pop_zoom(ctx) for & entry in ctx.entries { unload_font( ctx, entry.id ) @@ -459,6 +494,11 @@ shutdown :: proc( ctx : ^Context ) shaper_shutdown( & ctx.shaper_ctx ) parser_shutdown( & ctx.parser_ctx ) + + // Scope Stack + { + + } } // Can be used with hot-reload @@ -674,8 +714,22 @@ resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, interval, mi return } -set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } -set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } +// 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 +resolve_zoom_size_scale :: #force_inline proc "contextless" ( zoom, px_size : f32, scale : Vec2, interval, min, max : f32, clamp_scale : Vec2 ) -> (resolved_size : f32, zoom_scale : Vec2) +{ + zoom_px_size := px_size * zoom + resolved_size = resolve_draw_px_size( zoom_px_size, interval, min, max ) + zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) + zoom_scale = zoom_diff_scalar * scale + zoom_scale.x = clamp(zoom_scale.x, 0, clamp_scale.x) + zoom_scale.y = clamp(zoom_scale.y, 0, clamp_scale.y) + return +} + +set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } +set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } +set_zoom_px_interval :: #force_inline proc( ctx : ^Context, interval : i32 ) { assert(ctx != nil); ctx.zoom_px_interval = f32(interval) } // During a shaping pass on text, will snap each glyph's position via ceil. set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { @@ -863,15 +917,8 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, adjusted_colour := colour adjusted_colour.a = 1.0 + ctx.alpha_sharpen - // Does nothing when zoom is 1.0 - zoom_px_size := px_size * zoom - resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval, 2, 999.0 ) - zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) - zoom_scale := zoom_diff_scalar * scale - zoom_scale.x = clamp(zoom_scale.x, 0, view.x) - zoom_scale.y = clamp(zoom_scale.y, 0, view.y) - - norm_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) + resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) + target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) // Does nothing if px_scalar is 1.0 target_px_size := resolved_size * ctx.px_scalar @@ -884,7 +931,7 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, entry, target_px_size, target_font_scale, - norm_position, + target_position, target_scale, ) } @@ -938,14 +985,7 @@ draw_text_view_space :: proc(ctx : ^Context, adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen - // Does nothing when zoom is 1.0 - zoom_px_size := px_size * zoom - resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval, 2, 999.0 ) - zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) - zoom_scale := zoom_diff_scalar * scale - zoom_scale.x = clamp(zoom_scale.x, 0, view.x) - zoom_scale.y = clamp(zoom_scale.y, 0, view.y) - + resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) // Does nothing if px_scalar is 1.0 @@ -1023,14 +1063,7 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text px_size := peek(stack.font_size) - // Does nothing when zoom is 1.0 - zoom := peek(stack.zoom) - zoom_px_size := zoom * px_size - resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval, 2, 999.0 ) - zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) - zoom_scale := zoom_diff_scalar * scale - zoom_scale.x = clamp(zoom_scale.x, 0, view.x) - zoom_scale.y = clamp(zoom_scale.y, 0, view.y) + resolved_size, zoom_scale = resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) absolute_position := peek(stack.position) + position absolute_scale := peek(stack.scale) * zoom_scale @@ -1107,14 +1140,7 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string, px_size := peek(stack.font_size) - // Does nothing when zoom is 1.0 - zoom := peek(stack.zoom) - zoom_px_size := zoom * px_size - resolved_size := resolve_draw_px_size( zoom_px_size, ctx.zoom_px_interval , 2, 999.0 ) - zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) - zoom_scale := zoom_diff_scalar * scale - zoom_scale.x = clamp(zoom_scale.x, 0, view.x) - zoom_scale.y = clamp(zoom_scale.y, 0, view.y) + resolved_size, zoom_scale = resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) absolute_position := peek(stack.position) + position absolute_scale := peek(stack.scale) * scale From 79498efbf3b9567b4d810178cce738651d0e20f5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 20:41:09 -0500 Subject: [PATCH 17/62] oops : dumb mistakes --- vefontcache/vefontcache.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 0833dfe..76826de 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -1062,8 +1062,9 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text adjusted_colour.a += ctx.alpha_sharpen px_size := peek(stack.font_size) + zoom := peek(stack.zoom) - resolved_size, zoom_scale = resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) + resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) absolute_position := peek(stack.position) + position absolute_scale := peek(stack.scale) * zoom_scale @@ -1139,8 +1140,9 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string, adjusted_colour.a += ctx.alpha_sharpen px_size := peek(stack.font_size) + zoom := peek(stack.zoom) - resolved_size, zoom_scale = resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) + resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) absolute_position := peek(stack.position) + position absolute_scale := peek(stack.scale) * scale From 87ab22c2071788690afc8f5ecf2d9a715a606b16 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 20:50:33 -0500 Subject: [PATCH 18/62] Fixed size issue (parsesr_scale convention needs to be em_to_pixels by default). --- examples/sokol_demo/sokol_demo.odin | 10 +++++----- vefontcache/parser.odin | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 9ddb10a..222b578 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -224,15 +224,15 @@ init :: proc "c" () } glyph_draw_opts := ve.Init_Glyph_Draw_Params_Default - glyph_draw_opts.snap_glyph_height = true + glyph_draw_opts.snap_glyph_height = false shaper_opts := ve.Init_Shaper_Params_Default - shaper_opts.snap_glyph_position = true + shaper_opts.snap_glyph_position = false ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, - px_scalar = 1, + px_scalar = 1.89, alpha_sharpen = 0.1, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) @@ -257,7 +257,7 @@ init :: proc "c" () path_noto_sans_jp_reg := strings.concatenate({ PATH_FONTS, "NotoSansJP-Regular.otf" }) path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" }) - demo_ctx.font_logo = font_load(path_sawarabi_mincho, 300.0, "SawarabiMincho", 12 ) + demo_ctx.font_logo = font_load(path_sawarabi_mincho, 150.0, "SawarabiMincho", 18 ) // demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 6 ) demo_ctx.font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP") demo_ctx.font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono") @@ -635,7 +635,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } // Draw grid - draw_text("Cache pressure test (throttled to 120 hz)", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 92) + draw_text("Cache pressure test (throttled to 120 hz)", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 72) for y in 0..< GRID_H do for x in 0 ..< GRID_W { posx := 0.2 + f32(x) * 0.02 diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 70a3959..ee05912 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -151,7 +151,7 @@ parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_I 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 := size > 0.0 ? parser_scale_for_mapping_em_to_pixels( font, size ) : parser_scale_for_pixel_height( font, -size ) return size_scale } From c64f8132dc1f86413589ecf92ef0e42b2d2b727c Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 Jan 2025 22:44:39 -0500 Subject: [PATCH 19/62] Add assignable allocator support to stb_truetype --- Readme.md | 5 +- examples/sokol_demo/sokol_demo.odin | 73 +- scripts/build_sokol_demo.ps1 | 9 + thirdparty/stb/lib/stb_truetype.lib | Bin 170652 -> 380552 bytes thirdparty/stb/src/gb/gb.h | 10824 ++++++++++++++++++++ thirdparty/stb/src/stb_image.c | 2 + thirdparty/stb/src/stb_image.h | 7897 ++++++++++++++ thirdparty/stb/src/stb_truetype.h | 37 + thirdparty/stb/truetype/stb_truetype.odin | 31 + vefontcache/draw.odin | 12 +- vefontcache/parser.odin | 38 +- 11 files changed, 18882 insertions(+), 46 deletions(-) create mode 100644 thirdparty/stb/src/gb/gb.h create mode 100644 thirdparty/stb/src/stb_image.c create mode 100644 thirdparty/stb/src/stb_image.h diff --git a/Readme.md b/Readme.md index c73acf9..056702d 100644 --- a/Readme.md +++ b/Readme.md @@ -17,7 +17,7 @@ Features: * Robust quality of life features: * Tracks text layers! * Push and pop stack for font, font_size, colour, view, position, scale and zoom! - * Enforce even only font-sizing (useful for linear-zoom) [TODO] + * Enforce even only font-sizing (useful for linear-zoom) * Snap-positining to view for better hinting * Basic or advanced text shaping via Harfbuzz * All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu. @@ -31,8 +31,9 @@ Features: Upcoming: -* Support for ear-clipping triangulation +* Support for ear-clipping triangulation, or just better triangulation.. * Support for which triangulation method used on a by font basis? + * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf * Multi-threading supported job queue. * Lift heavy-lifting portion of the library's context into a thread context. * Synchronize threads by merging their generated layered draw list into a finished draw-list for processing on the user's render thread. diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 222b578..4c8b372 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -224,16 +224,16 @@ init :: proc "c" () } glyph_draw_opts := ve.Init_Glyph_Draw_Params_Default - glyph_draw_opts.snap_glyph_height = false + glyph_draw_opts.snap_glyph_height = true shaper_opts := ve.Init_Shaper_Params_Default - shaper_opts.snap_glyph_position = false + shaper_opts.snap_glyph_position = true ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, - px_scalar = 1.89, - alpha_sharpen = 0.1, + px_scalar = 1.25, + alpha_sharpen = 0.0, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) @@ -388,10 +388,10 @@ Glyphs are first rendered to an intermediate 2k x 512px R8 texture. This allows 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.` - draw_text("How it works", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.06) }) - draw_text(how_it_works, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.1) }) - draw_text(caching_strategy, demo_ctx.font_mono, { 0.28, current_scroll - (section_start + 0.32) }) - draw_text(how_it_works2, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.82) }) + draw_text("How it works", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.06) }, size = 92) + draw_text(how_it_works, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.1) }, size = 19) + draw_text(caching_strategy, demo_ctx.font_mono, { 0.28, current_scroll - (section_start + 0.32) }, size = 21) + draw_text(how_it_works2, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.82) }, size = 19) } // Showcase section @@ -408,43 +408,43 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` draw_text("Sans serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.28) }, size = 19) draw_text(font_family_test, demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.28) }, size = 18) - draw_text("Serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.36) }) - draw_text(font_family_test, demo_ctx.font_demo_serif, { 0.3, current_scroll - (section_start + 0.36) }) + draw_text("Serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.36) }, size = 19) + draw_text(font_family_test, demo_ctx.font_demo_serif, { 0.3, current_scroll - (section_start + 0.36) }, size = 18) - draw_text("Script", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.44) }) - draw_text(font_family_test, demo_ctx.font_demo_script, { 0.3, current_scroll - (section_start + 0.44) }) + draw_text("Script", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.44) }, size = 19) + draw_text(font_family_test, demo_ctx.font_demo_script, { 0.3, current_scroll - (section_start + 0.44) }, size = 22) - draw_text("Monospace", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.52) }) - draw_text(font_family_test, demo_ctx.font_demo_mono, { 0.3, current_scroll - (section_start + 0.52) }) + draw_text("Monospace", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.52) }, size = 19) + draw_text(font_family_test, demo_ctx.font_demo_mono, { 0.3, current_scroll - (section_start + 0.52) }, size = 22) - draw_text("Small", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.60) }) - draw_text(font_family_test, demo_ctx.font_small, { 0.3, current_scroll - (section_start + 0.60) }) + draw_text("Small", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.60) }, size = 19) + draw_text(font_family_test, demo_ctx.font_small, { 0.3, current_scroll - (section_start + 0.60) }, size = 10) - draw_text("Greek", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.72) }) - draw_text("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.72) }) + draw_text("Greek", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.72) }, size = 19) + draw_text("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.72) }, size = 18) - draw_text("Vietnamese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.76) }) - draw_text("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.76) }) + draw_text("Vietnamese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.76) }, size = 19) + draw_text("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.76) }, size = 18) - draw_text("Thai", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.80) }) - draw_text("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, { 0.3, current_scroll - (section_start + 0.80) }) + draw_text("Thai", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.80) }, size = 19) + draw_text("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, { 0.3, current_scroll - (section_start + 0.80) }, size = 24) - draw_text("Chinese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.84) }) - draw_text("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, {0.3, current_scroll - (section_start + 0.84) }) + draw_text("Chinese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.84) }, size = 19) + draw_text("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, {0.3, current_scroll - (section_start + 0.84) }, size = 24) - draw_text("Japanese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.88) }) - draw_text("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, { 0.3, current_scroll - (section_start + 0.88) }) + draw_text("Japanese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.88) }, size = 19) + draw_text("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, { 0.3, current_scroll - (section_start + 0.88) }, size = 24) - draw_text("Korean", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.92) }) - draw_text("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, { 0.3, current_scroll - (section_start + 0.92) }) + draw_text("Korean", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.92) }, size = 19) + draw_text("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, { 0.3, current_scroll - (section_start + 0.92) }, size = 36) - draw_text("Needs harfbuzz to work:", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.96)}) + draw_text("Needs harfbuzz to work:", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.96)}, size = 14) - draw_text("Arabic", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.00) }) - draw_text("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, { 0.3, current_scroll - (section_start + 1.00) }) + draw_text("Arabic", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.00) }, size = 19) + draw_text("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, { 0.3, current_scroll - (section_start + 1.00) }, size = 24) - draw_text("Hebrew", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.04) }) - draw_text("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, { 0.3, current_scroll - (section_start + 1.04) }) + draw_text("Hebrew", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.04) }, size = 19) + draw_text("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, { 0.3, current_scroll - (section_start + 1.04) }, size = 22) } // Zoom Test @@ -486,13 +486,10 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` zoomed_text_base_size : f32 = 12.0 zoom_adjust_size := zoomed_text_base_size * current_zoom - // ve_id, resolved_size := font_resolve_draw_id( font_firacode, zoom_adjust_size * OVER_SAMPLE_ZOOM ) - resolved_size := zoom_adjust_size + resolved_size, _ := ve.resolve_zoom_size_scale(current_zoom, zoomed_text_base_size, 1.0, 2, 2, 999.0, demo_ctx.screen_size) current_zoom_text := fmt.tprintf("Current Zoom : %.2f x\nCurrent Resolved Size: %v px", current_zoom, resolved_size ) draw_text(current_zoom_text, demo_ctx.font_firacode, { 0.2, zoom_info_y }) - // ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) ) - size := measure_text_size( zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, 0 ) * current_zoom x_offset := (size.x / demo_ctx.screen_size.x) * 0.5 zoomed_text_pos := Vec2 { 0.5 - x_offset, zoomed_text_y } @@ -567,8 +564,6 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` draw_text(codes[grid[y * GRID_W + x]], demo_ctx.font_demo_raincode, { pos_x, pos_y }, size = 20, color = code_colour) } - - // ve.set_colour(&ve_ctx, {1.0, 1.0, 1.0, 1.0}) } // Cache pressure test diff --git a/scripts/build_sokol_demo.ps1 b/scripts/build_sokol_demo.ps1 index 5dad272..c600cf0 100644 --- a/scripts/build_sokol_demo.ps1 +++ b/scripts/build_sokol_demo.ps1 @@ -67,6 +67,15 @@ push-location $path_thirdparty } pop-location +$path_stb_truetype = join-path $path_thirdparty 'stb\src' + +push-location $path_stb_truetype + $devshell = Join-Path $PSScriptRoot 'helpers/devshell.ps1' + . $devshell -arch amd64 + + & .\build.bat +pop-location + $odin_compiler_defs = join-path $PSScriptRoot 'helpers/odin_compiler_defs.ps1' . $odin_compiler_defs diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index 16ecf944df9807f2d62cb212e611dd5df9270cd3..4f1ea6cd216c5424493d593354ddfd07a88d4958 100644 GIT binary patch literal 380552 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 mSC7h?U>9b$N!sJ;r{`C6$};t literal 170652 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 CtB$Y$ diff --git a/thirdparty/stb/src/gb/gb.h b/thirdparty/stb/src/gb/gb.h new file mode 100644 index 0000000..adeb554 --- /dev/null +++ b/thirdparty/stb/src/gb/gb.h @@ -0,0 +1,10824 @@ +/* gb.h - v0.33 - Ginger Bill's C Helper Library - public domain + - no warranty implied; use at your own risk + + This is a single header file with a bunch of useful stuff + to replace the C/C++ standard library + +=========================================================================== + YOU MUST + + #define GB_IMPLEMENTATION + + in EXACTLY _one_ C or C++ file that includes this header, BEFORE the + include like this: + + #define GB_IMPLEMENTATION + #include "gb.h" + + All other files should just #include "gb.h" without #define + + + If you want the platform layer, YOU MUST + + #define GB_PLATFORM + + BEFORE the include like this: + + #define GB_PLATFORM + #include "gb.h" + +=========================================================================== + +LICENSE + This software is dual-licensed to the public domain and under the following + license: you are granted a perpetual, irrevocable license to copy, modify, + publish, and distribute this file as you see fit. + +WARNING + - This library is _slightly_ experimental and features may not work as expected. + - This also means that many functions are not documented. + +CREDITS + Written by Ginger Bill + +TODOS + - Remove CRT dependency for people who want that + - But do I really? + - Or make it only depend on the really needed stuff? + - Older compiler support? + - How old do you wanna go? + - Only support C90+extension and C99 not pure C89. + - File handling + - All files to be UTF-8 (even on windows) + - Better Virtual Memory handling + - Generic Heap Allocator (tcmalloc/dlmalloc/?) + - Fixed Heap Allocator + - Better UTF support and conversion + - Free List, best fit rather than first fit + - More date & time functions + +VERSION HISTORY + 0.33 - Minor fixes + 0.32 - Minor fixes + 0.31 - Add gb_file_remove + 0.30 - Changes to gbThread (and gbMutex on Windows) + 0.29 - Add extras for gbString + 0.28 - Handle UCS2 correctly in Win32 part + 0.27 - OSX fixes and Linux gbAffinity + 0.26d - Minor changes to how gbFile works + 0.26c - gb_str_to_f* fix + 0.26b - Minor fixes + 0.26a - gbString Fix + 0.26 - Default allocator flags and generic hash table + 0.25a - Fix UTF-8 stuff + 0.25 - OS X gbPlatform Support (missing some things) + 0.24b - Compile on OSX (excluding platform part) + 0.24a - Minor additions + 0.24 - Enum convention change + 0.23 - Optional Windows.h removal (because I'm crazy) + 0.22a - Remove gbVideoMode from gb_platform_init_* + 0.22 - gbAffinity - (Missing Linux version) + 0.21 - Platform Layer Restructuring + 0.20 - Improve file io + 0.19 - Clipboard Text + 0.18a - Controller vibration + 0.18 - Raw keyboard and mouse input for WIN32 + 0.17d - Fixed printf bug for strings + 0.17c - Compile as 32 bit + 0.17b - Change formating style because why not? + 0.17a - Dropped C90 Support (For numerous reasons) + 0.17 - Instantiated Hash Table + 0.16a - Minor code layout changes + 0.16 - New file API and improved platform layer + 0.15d - Linux Experimental Support (DON'T USE IT PLEASE) + 0.15c - Linux Experimental Support (DON'T USE IT) + 0.15b - C90 Support + 0.15a - gb_atomic(32|64)_spin_(lock|unlock) + 0.15 - Recursive "Mutex"; Key States; gbRandom + 0.14 - Better File Handling and better printf (WIN32 Only) + 0.13 - Highly experimental platform layer (WIN32 Only) + 0.12b - Fix minor file bugs + 0.12a - Compile as C++ + 0.12 - New File Handing System! No stdio or stdlib! (WIN32 Only) + 0.11a - Add string precision and width (experimental) + 0.11 - Started making stdio & stdlib optional (Not tested much) + 0.10c - Fix gb_endian_swap32() + 0.10b - Probable timing bug for gb_time_now() + 0.10a - Work on multiple compilers + 0.10 - Scratch Memory Allocator + 0.09a - Faster Mutex and the Free List is slightly improved + 0.09 - Basic Virtual Memory System and Dreadful Free List allocator + 0.08a - Fix *_appendv bug + 0.08 - Huge Overhaul! + 0.07a - Fix alignment in gb_heap_allocator_proc + 0.07 - Hash Table and Hashing Functions + 0.06c - Better Documentation + 0.06b - OS X Support + 0.06a - Linux Support + 0.06 - Windows GCC Support and MSVC x86 Support + 0.05b - Formatting + 0.05a - Minor function name changes + 0.05 - Radix Sort for unsigned integers (TODO: Other primitives) + 0.04 - Better UTF support and search/sort procs + 0.03 - Completely change procedure naming convention + 0.02a - Bug fixes + 0.02 - Change naming convention and gbArray(Type) + 0.01 - Initial Version +*/ + + +#ifndef GB_INCLUDE_GB_H +#define GB_INCLUDE_GB_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__cplusplus) + #define GB_EXTERN extern "C" +#else + #define GB_EXTERN extern +#endif + +#if defined(_WIN32) + #define GB_DLL_EXPORT GB_EXTERN __declspec(dllexport) + #define GB_DLL_IMPORT GB_EXTERN __declspec(dllimport) +#else + #define GB_DLL_EXPORT GB_EXTERN __attribute__((visibility("default"))) + #define GB_DLL_IMPORT GB_EXTERN +#endif + +// NOTE(bill): Redefine for DLL, etc. +#ifndef GB_DEF + #ifdef GB_STATIC + #define GB_DEF static + #else + #define GB_DEF extern + #endif +#endif + +#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) + #ifndef GB_ARCH_64_BIT + #define GB_ARCH_64_BIT 1 + #endif +#else + // NOTE(bill): I'm only supporting 32 bit and 64 bit systems + #ifndef GB_ARCH_32_BIT + #define GB_ARCH_32_BIT 1 + #endif +#endif + + +#ifndef GB_ENDIAN_ORDER +#define GB_ENDIAN_ORDER + // TODO(bill): Is the a good way or is it better to test for certain compilers and macros? + #define GB_IS_BIG_ENDIAN (!*(u8*)&(u16){1}) + #define GB_IS_LITTLE_ENDIAN (!GB_IS_BIG_ENDIAN) +#endif + +#if defined(_WIN32) || defined(_WIN64) + #ifndef GB_SYSTEM_WINDOWS + #define GB_SYSTEM_WINDOWS 1 + #endif +#elif defined(__APPLE__) && defined(__MACH__) + #ifndef GB_SYSTEM_OSX + #define GB_SYSTEM_OSX 1 + #endif +#elif defined(__unix__) + #ifndef GB_SYSTEM_UNIX + #define GB_SYSTEM_UNIX 1 + #endif + + #if defined(__linux__) + #ifndef GB_SYSTEM_LINUX + #define GB_SYSTEM_LINUX 1 + #endif + #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + #ifndef GB_SYSTEM_FREEBSD + #define GB_SYSTEM_FREEBSD 1 + #endif + #else + #error This UNIX operating system is not supported + #endif +#else + #error This operating system is not supported +#endif + +#if defined(_MSC_VER) + #define GB_COMPILER_MSVC 1 +#elif defined(__GNUC__) + #define GB_COMPILER_GCC 1 +#elif defined(__clang__) + #define GB_COMPILER_CLANG 1 +#else + #error Unknown compiler +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + #ifndef GB_CPU_X86 + #define GB_CPU_X86 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 64 + #endif + +#elif defined(_M_PPC) || defined(__powerpc__) || defined(__powerpc64__) + #ifndef GB_CPU_PPC + #define GB_CPU_PPC 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 128 + #endif + +#elif defined(__arm__) + #ifndef GB_CPU_ARM + #define GB_CPU_ARM 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 64 + #endif + +#elif defined(__MIPSEL__) || defined(__mips_isa_rev) + #ifndef GB_CPU_MIPS + #define GB_CPU_MIPS 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 64 + #endif + +#else + #error Unknown CPU Type +#endif + + + +#ifndef GB_STATIC_ASSERT + #define GB_STATIC_ASSERT3(cond, msg) typedef char static_assertion_##msg[(!!(cond))*2-1] + // NOTE(bill): Token pasting madness!! + #define GB_STATIC_ASSERT2(cond, line) GB_STATIC_ASSERT3(cond, static_assertion_at_line_##line) + #define GB_STATIC_ASSERT1(cond, line) GB_STATIC_ASSERT2(cond, line) + #define GB_STATIC_ASSERT(cond) GB_STATIC_ASSERT1(cond, __LINE__) +#endif + + +//////////////////////////////////////////////////////////////// +// +// Headers +// +// + +#if defined(_WIN32) && !defined(__MINGW32__) + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +#if defined(GB_SYSTEM_UNIX) + #define _GNU_SOURCE + #define _LARGEFILE64_SOURCE +#endif + + +// TODO(bill): How many of these headers do I really need? +// #include +#if !defined(GB_SYSTEM_WINDOWS) + #include + #include +#endif + + + +#if defined(GB_SYSTEM_WINDOWS) + #if !defined(GB_NO_WINDOWS_H) + #define NOMINMAX 1 + #define WIN32_LEAN_AND_MEAN 1 + #define WIN32_MEAN_AND_LEAN 1 + #define VC_EXTRALEAN 1 + #include + #undef NOMINMAX + #undef WIN32_LEAN_AND_MEAN + #undef WIN32_MEAN_AND_LEAN + #undef VC_EXTRALEAN + #endif + + #include // NOTE(bill): _aligned_*() + #include +#else + #include + #include + #include + #include + #ifndef _IOSC11_SOURCE + #define _IOSC11_SOURCE + #endif + #include // NOTE(bill): malloc on linux + #include + #if !defined(GB_SYSTEM_OSX) + #include + #endif + #include + #include + #include + #include + #include + + #if defined(GB_CPU_X86) + #include + #endif +#endif + +#if defined(GB_SYSTEM_OSX) + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#if defined(GB_SYSTEM_UNIX) + #include +#endif + + +//////////////////////////////////////////////////////////////// +// +// Base Types +// +// + +#if defined(GB_COMPILER_MSVC) + #if _MSC_VER < 1300 + typedef unsigned char u8; + typedef signed char i8; + typedef unsigned short u16; + typedef signed short i16; + typedef unsigned int u32; + typedef signed int i32; + #else + typedef unsigned __int8 u8; + typedef signed __int8 i8; + typedef unsigned __int16 u16; + typedef signed __int16 i16; + typedef unsigned __int32 u32; + typedef signed __int32 i32; + #endif + typedef unsigned __int64 u64; + typedef signed __int64 i64; +#else + #include + typedef uint8_t u8; + typedef int8_t i8; + typedef uint16_t u16; + typedef int16_t i16; + typedef uint32_t u32; + typedef int32_t i32; + typedef uint64_t u64; + typedef int64_t i64; +#endif + +GB_STATIC_ASSERT(sizeof(u8) == sizeof(i8)); +GB_STATIC_ASSERT(sizeof(u16) == sizeof(i16)); +GB_STATIC_ASSERT(sizeof(u32) == sizeof(i32)); +GB_STATIC_ASSERT(sizeof(u64) == sizeof(i64)); + +GB_STATIC_ASSERT(sizeof(u8) == 1); +GB_STATIC_ASSERT(sizeof(u16) == 2); +GB_STATIC_ASSERT(sizeof(u32) == 4); +GB_STATIC_ASSERT(sizeof(u64) == 8); + +typedef size_t usize; +typedef ptrdiff_t isize; + +GB_STATIC_ASSERT(sizeof(usize) == sizeof(isize)); + +// NOTE(bill): (u)intptr is only here for semantic reasons really as this library will only support 32/64 bit OSes. +// NOTE(bill): Are there any modern OSes (not 16 bit) where intptr != isize ? +#if defined(_WIN64) + typedef signed __int64 intptr; + typedef unsigned __int64 uintptr; +#elif defined(_WIN32) + // NOTE(bill); To mark types changing their size, e.g. intptr + #ifndef _W64 + #if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 + #define _W64 __w64 + #else + #define _W64 + #endif + #endif + + typedef _W64 signed int intptr; + typedef _W64 unsigned int uintptr; +#else + typedef uintptr_t uintptr; + typedef intptr_t intptr; +#endif + +GB_STATIC_ASSERT(sizeof(uintptr) == sizeof(intptr)); + +typedef float f32; +typedef double f64; + +GB_STATIC_ASSERT(sizeof(f32) == 4); +GB_STATIC_ASSERT(sizeof(f64) == 8); + +typedef i32 Rune; // NOTE(bill): Unicode codepoint +#define GB_RUNE_INVALID cast(Rune)(0xfffd) +#define GB_RUNE_MAX cast(Rune)(0x0010ffff) +#define GB_RUNE_BOM cast(Rune)(0xfeff) +#define GB_RUNE_EOF cast(Rune)(-1) + + +typedef i8 b8; +typedef i16 b16; +typedef i32 b32; // NOTE(bill): Prefer this!!! + +// NOTE(bill): Get true and false +#if !defined(__cplusplus) + #if (defined(_MSC_VER) && _MSC_VER < 1800) || (!defined(_MSC_VER) && !defined(__STDC_VERSION__)) + #ifndef true + #define true (0 == 0) + #endif + #ifndef false + #define false (0 != 0) + #endif + typedef b8 bool; + #else + #include + #endif +#endif + +// NOTE(bill): These do are not prefixed with gb because the types are not. +#ifndef U8_MIN +#define U8_MIN 0u +#define U8_MAX 0xffu +#define I8_MIN (-0x7f - 1) +#define I8_MAX 0x7f + +#define U16_MIN 0u +#define U16_MAX 0xffffu +#define I16_MIN (-0x7fff - 1) +#define I16_MAX 0x7fff + +#define U32_MIN 0u +#define U32_MAX 0xffffffffu +#define I32_MIN (-0x7fffffff - 1) +#define I32_MAX 0x7fffffff + +#define U64_MIN 0ull +#define U64_MAX 0xffffffffffffffffull +#define I64_MIN (-0x7fffffffffffffffll - 1) +#define I64_MAX 0x7fffffffffffffffll + +#if defined(GB_ARCH_32_BIT) + #define USIZE_MIX U32_MIN + #define USIZE_MAX U32_MAX + + #define ISIZE_MIX S32_MIN + #define ISIZE_MAX S32_MAX +#elif defined(GB_ARCH_64_BIT) + #define USIZE_MIX U64_MIN + #define USIZE_MAX U64_MAX + + #define ISIZE_MIX I64_MIN + #define ISIZE_MAX I64_MAX +#else + #error Unknown architecture size. This library only supports 32 bit and 64 bit architectures. +#endif + +#define F32_MIN 1.17549435e-38f +#define F32_MAX 3.40282347e+38f + +#define F64_MIN 2.2250738585072014e-308 +#define F64_MAX 1.7976931348623157e+308 + +#endif + +#ifndef NULL + #if defined(__cplusplus) + #if __cplusplus >= 201103L + #define NULL nullptr + #else + #define NULL 0 + #endif + #else + #define NULL ((void *)0) + #endif +#endif + +// TODO(bill): Is this enough to get inline working? +#if !defined(__cplusplus) + #if defined(_MSC_VER) && _MSC_VER <= 1800 + #define inline __inline + #elif !defined(__STDC_VERSION__) + #define inline __inline__ + #else + #define inline + #endif +#endif + +#if !defined(gb_restrict) + #if defined(_MSC_VER) + #define gb_restrict __restrict + #elif defined(__STDC_VERSION__) + #define gb_restrict restrict + #else + #define gb_restrict + #endif +#endif + +// TODO(bill): Should force inline be a separate keyword and gb_inline be inline? +#if !defined(gb_inline) + #if defined(_MSC_VER) + #if _MSC_VER < 1300 + #define gb_inline + #else + #define gb_inline __forceinline + #endif + #else + #define gb_inline __attribute__ ((__always_inline__)) + #endif +#endif + +#if !defined(gb_no_inline) + #if defined(_MSC_VER) + #define gb_no_inline __declspec(noinline) + #else + #define gb_no_inline __attribute__ ((noinline)) + #endif +#endif + + +#if !defined(gb_thread_local) + #if defined(_MSC_VER) && _MSC_VER >= 1300 + #define gb_thread_local __declspec(thread) + #elif defined(__GNUC__) + #define gb_thread_local __thread + #else + #define gb_thread_local thread_local + #endif +#endif + + +// NOTE(bill): Easy to grep +// NOTE(bill): Not needed in macros +#ifndef cast +#define cast(Type) (Type) +#endif + +// NOTE(bill): Because a signed sizeof is more useful +#ifndef gb_size_of +#define gb_size_of(x) (isize)(sizeof(x)) +#endif + +#ifndef gb_count_of +#define gb_count_of(x) ((gb_size_of(x)/gb_size_of(0[x])) / ((isize)(!(gb_size_of(x) % gb_size_of(0[x]))))) +#endif + +#ifndef gb_offset_of +#define gb_offset_of(Type, element) ((isize)&(((Type *)0)->element)) +#endif + +#if defined(__cplusplus) +#ifndef gb_align_of + #if __cplusplus >= 201103L + #define gb_align_of(Type) (isize)alignof(Type) + #else +extern "C++" { + // NOTE(bill): Fucking Templates! + template struct gbAlignment_Trick { char c; T member; }; + #define gb_align_of(Type) gb_offset_of(gbAlignment_Trick, member) +} + #endif +#endif +#else + #ifndef gb_align_of + #define gb_align_of(Type) gb_offset_of(struct { char c; Type member; }, member) + #endif +#endif + +// NOTE(bill): I do wish I had a type_of that was portable +#ifndef gb_swap +#define gb_swap(Type, a, b) do { Type tmp = (a); (a) = (b); (b) = tmp; } while (0) +#endif + +// NOTE(bill): Because static means 3/4 different things in C/C++. Great design (!) +#ifndef gb_global +#define gb_global static // Global variables +#define gb_internal static // Internal linkage +#define gb_local_persist static // Local Persisting variables +#endif + + +#ifndef gb_unused + #if defined(_MSC_VER) + #define gb_unused(x) (__pragma(warning(suppress:4100))(x)) + #elif defined (__GCC__) + #define gb_unused(x) __attribute__((__unused__))(x) + #else + #define gb_unused(x) ((void)(gb_size_of(x))) + #endif +#endif + + + + +//////////////////////////////////////////////////////////////// +// +// Defer statement +// Akin to D's SCOPE_EXIT or +// similar to Go's defer but scope-based +// +// NOTE: C++11 (and above) only! +// +#if !defined(GB_NO_DEFER) && defined(__cplusplus) && ((defined(_MSC_VER) && _MSC_VER >= 1400) || (__cplusplus >= 201103L)) +extern "C++" { + // NOTE(bill): Stupid fucking templates + template struct gbRemoveReference { typedef T Type; }; + template struct gbRemoveReference { typedef T Type; }; + template struct gbRemoveReference { typedef T Type; }; + + /// NOTE(bill): "Move" semantics - invented because the C++ committee are idiots (as a collective not as indiviuals (well a least some aren't)) + template inline T &&gb_forward(typename gbRemoveReference::Type &t) { return static_cast(t); } + template inline T &&gb_forward(typename gbRemoveReference::Type &&t) { return static_cast(t); } + template inline T &&gb_move (T &&t) { return static_cast::Type &&>(t); } + template + struct gbprivDefer { + F f; + gbprivDefer(F &&f) : f(gb_forward(f)) {} + ~gbprivDefer() { f(); } + }; + template gbprivDefer gb__defer_func(F &&f) { return gbprivDefer(gb_forward(f)); } + + #define GB_DEFER_1(x, y) x##y + #define GB_DEFER_2(x, y) GB_DEFER_1(x, y) + #define GB_DEFER_3(x) GB_DEFER_2(x, __COUNTER__) + #define defer(code) auto GB_DEFER_3(_defer_) = gb__defer_func([&]()->void{code;}) +} + +// Example +#if 0 + gbMutex m; + gb_mutex_init(&m); + { + gb_mutex_lock(&m); + defer (gb_mutex_unlock(&m)); + + ... + } +#endif + +#endif + + +//////////////////////////////////////////////////////////////// +// +// Macro Fun! +// +// + +#ifndef GB_JOIN_MACROS +#define GB_JOIN_MACROS + #define GB_JOIN2_IND(a, b) a##b + + #define GB_JOIN2(a, b) GB_JOIN2_IND(a, b) + #define GB_JOIN3(a, b, c) GB_JOIN2(GB_JOIN2(a, b), c) + #define GB_JOIN4(a, b, c, d) GB_JOIN2(GB_JOIN2(GB_JOIN2(a, b), c), d) +#endif + + +#ifndef GB_BIT +#define GB_BIT(x) (1<<(x)) +#endif + +#ifndef gb_min +#define gb_min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef gb_max +#define gb_max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef gb_min3 +#define gb_min3(a, b, c) gb_min(gb_min(a, b), c) +#endif + +#ifndef gb_max3 +#define gb_max3(a, b, c) gb_max(gb_max(a, b), c) +#endif + +#ifndef gb_clamp +#define gb_clamp(x, lower, upper) gb_min(gb_max((x), (lower)), (upper)) +#endif + +#ifndef gb_clamp01 +#define gb_clamp01(x) gb_clamp((x), 0, 1) +#endif + +#ifndef gb_is_between +#define gb_is_between(x, lower, upper) (((lower) <= (x)) && ((x) <= (upper))) +#endif + +#ifndef gb_abs +#define gb_abs(x) ((x) < 0 ? -(x) : (x)) +#endif + +/* NOTE(bill): Very useful bit setting */ +#ifndef GB_MASK_SET +#define GB_MASK_SET(var, set, mask) do { \ + if (set) (var) |= (mask); \ + else (var) &= ~(mask); \ +} while (0) +#endif + + +// NOTE(bill): Some compilers support applying printf-style warnings to user functions. +#if defined(__clang__) || defined(__GNUC__) +#define GB_PRINTF_ARGS(FMT) __attribute__((format(printf, FMT, (FMT+1)))) +#else +#define GB_PRINTF_ARGS(FMT) +#endif + +//////////////////////////////////////////////////////////////// +// +// Debug +// +// + + +#ifndef GB_DEBUG_TRAP + #if defined(_MSC_VER) + #if _MSC_VER < 1300 + #define GB_DEBUG_TRAP() __asm int 3 /* Trap to debugger! */ + #else + #define GB_DEBUG_TRAP() __debugbreak() + #endif + #else + #define GB_DEBUG_TRAP() __builtin_trap() + #endif +#endif + +#ifndef GB_ASSERT_MSG +#define GB_ASSERT_MSG(cond, msg, ...) do { \ + if (!(cond)) { \ + gb_assert_handler("Assertion Failure", #cond, __FILE__, cast(i64)__LINE__, msg, ##__VA_ARGS__); \ + GB_DEBUG_TRAP(); \ + } \ +} while (0) +#endif + +#ifndef GB_ASSERT +#define GB_ASSERT(cond) GB_ASSERT_MSG(cond, NULL) +#endif + +#ifndef GB_ASSERT_NOT_NULL +#define GB_ASSERT_NOT_NULL(ptr) GB_ASSERT_MSG((ptr) != NULL, #ptr " must not be NULL") +#endif + +// NOTE(bill): Things that shouldn't happen with a message! +#ifndef GB_PANIC +#define GB_PANIC(msg, ...) do { \ + gb_assert_handler("Panic", NULL, __FILE__, cast(i64)__LINE__, msg, ##__VA_ARGS__); \ + GB_DEBUG_TRAP(); \ +} while (0) +#endif + +GB_DEF void gb_assert_handler(char const *prefix, char const *condition, char const *file, i32 line, char const *msg, ...); + + + +//////////////////////////////////////////////////////////////// +// +// Memory +// +// + + +GB_DEF b32 gb_is_power_of_two(isize x); + +GB_DEF void * gb_align_forward(void *ptr, isize alignment); + +GB_DEF void * gb_pointer_add (void *ptr, isize bytes); +GB_DEF void * gb_pointer_sub (void *ptr, isize bytes); +GB_DEF void const *gb_pointer_add_const(void const *ptr, isize bytes); +GB_DEF void const *gb_pointer_sub_const(void const *ptr, isize bytes); +GB_DEF isize gb_pointer_diff (void const *begin, void const *end); + + +GB_DEF void gb_zero_size(void *ptr, isize size); +#ifndef gb_zero_item +#define gb_zero_item(t) gb_zero_size((t), gb_size_of(*(t))) // NOTE(bill): Pass pointer of struct +#define gb_zero_array(a, count) gb_zero_size((a), gb_size_of(*(a))*count) +#endif + +GB_DEF void * gb_memcopy (void *dest, void const *source, isize size); +GB_DEF void * gb_memmove (void *dest, void const *source, isize size); +GB_DEF void * gb_memset (void *data, u8 byte_value, isize size); +GB_DEF i32 gb_memcompare(void const *s1, void const *s2, isize size); +GB_DEF void gb_memswap (void *i, void *j, isize size); +GB_DEF void const *gb_memchr (void const *data, u8 byte_value, isize size); +GB_DEF void const *gb_memrchr (void const *data, u8 byte_value, isize size); + + +#ifndef gb_memcopy_array +#define gb_memcopy_array(dst, src, count) gb_memcopy((dst), (src), gb_size_of(*(dst))*(count)) +#endif + +#ifndef gb_memmove_array +#define gb_memmove_array(dst, src, count) gb_memmove((dst), (src), gb_size_of(*(dst))*(count)) +#endif + +// NOTE(bill): Very similar to doing `*cast(T *)(&u)` +#ifndef GB_BIT_CAST +#define GB_BIT_CAST(dest, source) do { \ + GB_STATIC_ASSERT(gb_size_of(*(dest)) <= gb_size_of(source)); \ + gb_memcopy((dest), &(source), gb_size_of(*dest)); \ +} while (0) +#endif + + + + +#ifndef gb_kilobytes +#define gb_kilobytes(x) ( (x) * (i64)(1024)) +#define gb_megabytes(x) (gb_kilobytes(x) * (i64)(1024)) +#define gb_gigabytes(x) (gb_megabytes(x) * (i64)(1024)) +#define gb_terabytes(x) (gb_gigabytes(x) * (i64)(1024)) +#endif + + + + +// Atomics + +// TODO(bill): Be specific with memory order? +// e.g. relaxed, acquire, release, acquire_release + +#if defined(GB_COMPILER_MSVC) +typedef struct gbAtomic32 { i32 volatile value; } gbAtomic32; +typedef struct gbAtomic64 { i64 volatile value; } gbAtomic64; +typedef struct gbAtomicPtr { void *volatile value; } gbAtomicPtr; +#else + #if defined(GB_ARCH_32_BIT) + #define GB_ATOMIC_PTR_ALIGNMENT 4 + #elif defined(GB_ARCH_64_BIT) + #define GB_ATOMIC_PTR_ALIGNMENT 8 + #else + #error Unknown architecture + #endif + +typedef struct gbAtomic32 { i32 volatile value; } __attribute__ ((aligned(4))) gbAtomic32; +typedef struct gbAtomic64 { i64 volatile value; } __attribute__ ((aligned(8))) gbAtomic64; +typedef struct gbAtomicPtr { void *volatile value; } __attribute__ ((aligned(GB_ATOMIC_PTR_ALIGNMENT))) gbAtomicPtr; +#endif + +GB_DEF i32 gb_atomic32_load (gbAtomic32 const volatile *a); +GB_DEF void gb_atomic32_store (gbAtomic32 volatile *a, i32 value); +GB_DEF i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired); +GB_DEF i32 gb_atomic32_exchanged (gbAtomic32 volatile *a, i32 desired); +GB_DEF i32 gb_atomic32_fetch_add (gbAtomic32 volatile *a, i32 operand); +GB_DEF i32 gb_atomic32_fetch_and (gbAtomic32 volatile *a, i32 operand); +GB_DEF i32 gb_atomic32_fetch_or (gbAtomic32 volatile *a, i32 operand); +GB_DEF b32 gb_atomic32_spin_lock (gbAtomic32 volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default +GB_DEF void gb_atomic32_spin_unlock (gbAtomic32 volatile *a); +GB_DEF b32 gb_atomic32_try_acquire_lock(gbAtomic32 volatile *a); + + +GB_DEF i64 gb_atomic64_load (gbAtomic64 const volatile *a); +GB_DEF void gb_atomic64_store (gbAtomic64 volatile *a, i64 value); +GB_DEF i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired); +GB_DEF i64 gb_atomic64_exchanged (gbAtomic64 volatile *a, i64 desired); +GB_DEF i64 gb_atomic64_fetch_add (gbAtomic64 volatile *a, i64 operand); +GB_DEF i64 gb_atomic64_fetch_and (gbAtomic64 volatile *a, i64 operand); +GB_DEF i64 gb_atomic64_fetch_or (gbAtomic64 volatile *a, i64 operand); +GB_DEF b32 gb_atomic64_spin_lock (gbAtomic64 volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default +GB_DEF void gb_atomic64_spin_unlock (gbAtomic64 volatile *a); +GB_DEF b32 gb_atomic64_try_acquire_lock(gbAtomic64 volatile *a); + + +GB_DEF void *gb_atomic_ptr_load (gbAtomicPtr const volatile *a); +GB_DEF void gb_atomic_ptr_store (gbAtomicPtr volatile *a, void *value); +GB_DEF void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired); +GB_DEF void *gb_atomic_ptr_exchanged (gbAtomicPtr volatile *a, void *desired); +GB_DEF void *gb_atomic_ptr_fetch_add (gbAtomicPtr volatile *a, void *operand); +GB_DEF void *gb_atomic_ptr_fetch_and (gbAtomicPtr volatile *a, void *operand); +GB_DEF void *gb_atomic_ptr_fetch_or (gbAtomicPtr volatile *a, void *operand); +GB_DEF b32 gb_atomic_ptr_spin_lock (gbAtomicPtr volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default +GB_DEF void gb_atomic_ptr_spin_unlock (gbAtomicPtr volatile *a); +GB_DEF b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a); + + +// Fences +GB_DEF void gb_yield_thread(void); +GB_DEF void gb_mfence (void); +GB_DEF void gb_sfence (void); +GB_DEF void gb_lfence (void); + + +#if defined(GB_SYSTEM_WINDOWS) +typedef struct gbSemaphore { void *win32_handle; } gbSemaphore; +#elif defined(GB_SYSTEM_OSX) +typedef struct gbSemaphore { semaphore_t osx_handle; } gbSemaphore; +#elif defined(GB_SYSTEM_UNIX) +typedef struct gbSemaphore { sem_t unix_handle; } gbSemaphore; +#else +#error +#endif + +GB_DEF void gb_semaphore_init (gbSemaphore *s); +GB_DEF void gb_semaphore_destroy(gbSemaphore *s); +GB_DEF void gb_semaphore_post (gbSemaphore *s, i32 count); +GB_DEF void gb_semaphore_release(gbSemaphore *s); // NOTE(bill): gb_semaphore_post(s, 1) +GB_DEF void gb_semaphore_wait (gbSemaphore *s); + + +// Mutex +typedef struct gbMutex { +#if defined(GB_SYSTEM_WINDOWS) + CRITICAL_SECTION win32_critical_section; +#else + pthread_mutex_t pthread_mutex; + pthread_mutexattr_t pthread_mutexattr; +#endif +} gbMutex; + +GB_DEF void gb_mutex_init (gbMutex *m); +GB_DEF void gb_mutex_destroy (gbMutex *m); +GB_DEF void gb_mutex_lock (gbMutex *m); +GB_DEF b32 gb_mutex_try_lock(gbMutex *m); +GB_DEF void gb_mutex_unlock (gbMutex *m); + +// NOTE(bill): If you wanted a Scoped Mutex in C++, why not use the defer() construct? +// No need for a silly wrapper class and it's clear! +#if 0 +gbMutex m = {0}; +gb_mutex_init(&m); +{ + gb_mutex_lock(&m); + defer (gb_mutex_unlock(&m)); + + // Do whatever as the mutex is now scoped based! +} +#endif + + + +#define GB_THREAD_PROC(name) isize name(struct gbThread *thread) +typedef GB_THREAD_PROC(gbThreadProc); + +typedef struct gbThread { +#if defined(GB_SYSTEM_WINDOWS) + void * win32_handle; +#else + pthread_t posix_handle; +#endif + + gbThreadProc *proc; + void * user_data; + isize user_index; + isize return_value; + + gbSemaphore semaphore; + isize stack_size; + b32 volatile is_running; +} gbThread; + +GB_DEF void gb_thread_init (gbThread *t); +GB_DEF void gb_thread_destroy (gbThread *t); +GB_DEF void gb_thread_start (gbThread *t, gbThreadProc *proc, void *data); +GB_DEF void gb_thread_start_with_stack(gbThread *t, gbThreadProc *proc, void *data, isize stack_size); +GB_DEF void gb_thread_join (gbThread *t); +GB_DEF b32 gb_thread_is_running (gbThread const *t); +GB_DEF u32 gb_thread_current_id (void); +GB_DEF void gb_thread_set_name (gbThread *t, char const *name); + + +// NOTE(bill): Thread Merge Operation +// Based on Sean Barrett's stb_sync +typedef struct gbSync { + i32 target; // Target Number of threads + i32 current; // Threads to hit + i32 waiting; // Threads waiting + + gbMutex start; + gbMutex mutex; + gbSemaphore release; +} gbSync; + +GB_DEF void gb_sync_init (gbSync *s); +GB_DEF void gb_sync_destroy (gbSync *s); +GB_DEF void gb_sync_set_target (gbSync *s, i32 count); +GB_DEF void gb_sync_release (gbSync *s); +GB_DEF i32 gb_sync_reach (gbSync *s); +GB_DEF void gb_sync_reach_and_wait(gbSync *s); + + + +#if defined(GB_SYSTEM_WINDOWS) + +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + #define GB_WIN32_MAX_THREADS (8 * gb_size_of(usize)) + usize core_masks[GB_WIN32_MAX_THREADS]; + +} gbAffinity; + +#elif defined(GB_SYSTEM_OSX) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; + +#elif defined(GB_SYSTEM_LINUX) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; +#else +#error TODO(bill): Unknown system +#endif + +GB_DEF void gb_affinity_init (gbAffinity *a); +GB_DEF void gb_affinity_destroy(gbAffinity *a); +GB_DEF b32 gb_affinity_set (gbAffinity *a, isize core, isize thread); +GB_DEF isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core); + + + + +//////////////////////////////////////////////////////////////// +// +// Virtual Memory +// +// + +typedef struct gbVirtualMemory { + void *data; + isize size; +} gbVirtualMemory; + +GB_DEF gbVirtualMemory gb_virtual_memory(void *data, isize size); +GB_DEF gbVirtualMemory gb_vm_alloc (void *addr, isize size); +GB_DEF b32 gb_vm_free (gbVirtualMemory vm); +GB_DEF gbVirtualMemory gb_vm_trim (gbVirtualMemory vm, isize lead_size, isize size); +GB_DEF b32 gb_vm_purge (gbVirtualMemory vm); +GB_DEF isize gb_virtual_memory_page_size(isize *alignment_out); + + + + +//////////////////////////////////////////////////////////////// +// +// Custom Allocation +// +// + +typedef enum gbAllocationType { + gbAllocation_Alloc, + gbAllocation_Free, + gbAllocation_FreeAll, + gbAllocation_Resize, +} gbAllocationType; + +// NOTE(bill): This is useful so you can define an allocator of the same type and parameters +#define GB_ALLOCATOR_PROC(name) \ +void *name(void *allocator_data, gbAllocationType type, \ + isize size, isize alignment, \ + void *old_memory, isize old_size, \ + u64 flags) +typedef GB_ALLOCATOR_PROC(gbAllocatorProc); + +typedef struct gbAllocator { + gbAllocatorProc *proc; + void * data; +} gbAllocator; + +typedef enum gbAllocatorFlag { + gbAllocatorFlag_ClearToZero = GB_BIT(0), +} gbAllocatorFlag; + +// TODO(bill): Is this a decent default alignment? +#ifndef GB_DEFAULT_MEMORY_ALIGNMENT +#define GB_DEFAULT_MEMORY_ALIGNMENT (2 * gb_size_of(void *)) +#endif + +#ifndef GB_DEFAULT_ALLOCATOR_FLAGS +#define GB_DEFAULT_ALLOCATOR_FLAGS (gbAllocatorFlag_ClearToZero) +#endif + +GB_DEF void *gb_alloc_align (gbAllocator a, isize size, isize alignment); +GB_DEF void *gb_alloc (gbAllocator a, isize size); +GB_DEF void gb_free (gbAllocator a, void *ptr); +GB_DEF void gb_free_all (gbAllocator a); +GB_DEF void *gb_resize (gbAllocator a, void *ptr, isize old_size, isize new_size); +GB_DEF void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment); +// TODO(bill): For gb_resize, should the use need to pass the old_size or only the new_size? + +GB_DEF void *gb_alloc_copy (gbAllocator a, void const *src, isize size); +GB_DEF void *gb_alloc_copy_align(gbAllocator a, void const *src, isize size, isize alignment); +GB_DEF char *gb_alloc_str (gbAllocator a, char const *str); +GB_DEF char *gb_alloc_str_len (gbAllocator a, char const *str, isize len); + + +// NOTE(bill): These are very useful and the type cast has saved me from numerous bugs +#ifndef gb_alloc_item +#define gb_alloc_item(allocator_, Type) (Type *)gb_alloc(allocator_, gb_size_of(Type)) +#define gb_alloc_array(allocator_, Type, count) (Type *)gb_alloc(allocator_, gb_size_of(Type) * (count)) +#endif + +// NOTE(bill): Use this if you don't need a "fancy" resize allocation +GB_DEF void *gb_default_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment); + + + +// TODO(bill): Probably use a custom heap allocator system that doesn't depend on malloc/free +// Base it off TCMalloc or something else? Or something entirely custom? +GB_DEF gbAllocator gb_heap_allocator(void); +GB_DEF GB_ALLOCATOR_PROC(gb_heap_allocator_proc); + +// NOTE(bill): Yep, I use my own allocator system! +#ifndef gb_malloc +#define gb_malloc(sz) gb_alloc(gb_heap_allocator(), sz) +#define gb_mfree(ptr) gb_free(gb_heap_allocator(), ptr) +#endif + + + +// +// Arena Allocator +// +typedef struct gbArena { + gbAllocator backing; + void * physical_start; + isize total_size; + isize total_allocated; + isize temp_count; +} gbArena; + +GB_DEF void gb_arena_init_from_memory (gbArena *arena, void *start, isize size); +GB_DEF void gb_arena_init_from_allocator(gbArena *arena, gbAllocator backing, isize size); +GB_DEF void gb_arena_init_sub (gbArena *arena, gbArena *parent_arena, isize size); +GB_DEF void gb_arena_free (gbArena *arena); + +GB_DEF isize gb_arena_alignment_of (gbArena *arena, isize alignment); +GB_DEF isize gb_arena_size_remaining(gbArena *arena, isize alignment); +GB_DEF void gb_arena_check (gbArena *arena); + + +// Allocation Types: alloc, free_all, resize +GB_DEF gbAllocator gb_arena_allocator(gbArena *arena); +GB_DEF GB_ALLOCATOR_PROC(gb_arena_allocator_proc); + + + +typedef struct gbTempArenaMemory { + gbArena *arena; + isize original_count; +} gbTempArenaMemory; + +GB_DEF gbTempArenaMemory gb_temp_arena_memory_begin(gbArena *arena); +GB_DEF void gb_temp_arena_memory_end (gbTempArenaMemory tmp_mem); + + + + + + + +// +// Pool Allocator +// + + +typedef struct gbPool { + gbAllocator backing; + void * physical_start; + void * free_list; + isize block_size; + isize block_align; + isize total_size; +} gbPool; + +GB_DEF void gb_pool_init (gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size); +GB_DEF void gb_pool_init_align(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size, isize block_align); +GB_DEF void gb_pool_free (gbPool *pool); + +// Allocation Types: alloc, free +GB_DEF gbAllocator gb_pool_allocator(gbPool *pool); +GB_DEF GB_ALLOCATOR_PROC(gb_pool_allocator_proc); + + + +// NOTE(bill): Used for allocators to keep track of sizes +typedef struct gbAllocationHeader { + isize size; +} gbAllocationHeader; + +GB_DEF gbAllocationHeader *gb_allocation_header (void *data); +GB_DEF void gb_allocation_header_fill(gbAllocationHeader *header, void *data, isize size); + +// TODO(bill): Find better way of doing this without #if #elif etc. +#if defined(GB_ARCH_32_BIT) +#define GB_ISIZE_HIGH_BIT 0x80000000 +#elif defined(GB_ARCH_64_BIT) +#define GB_ISIZE_HIGH_BIT 0x8000000000000000ll +#else +#error +#endif + +// +// Free List Allocator +// + +// IMPORTANT TODO(bill): Thoroughly test the free list allocator! +// NOTE(bill): This is a very shitty free list as it just picks the first free block not the best size +// as I am just being lazy. Also, I will probably remove it later; it's only here because why not?! +// +// NOTE(bill): I may also complete remove this if I completely implement a fixed heap allocator + +typedef struct gbFreeListBlock gbFreeListBlock; +struct gbFreeListBlock { + gbFreeListBlock *next; + isize size; +}; + +typedef struct gbFreeList { + void * physical_start; + isize total_size; + + gbFreeListBlock *curr_block; + + isize total_allocated; + isize allocation_count; +} gbFreeList; + +GB_DEF void gb_free_list_init (gbFreeList *fl, void *start, isize size); +GB_DEF void gb_free_list_init_from_allocator(gbFreeList *fl, gbAllocator backing, isize size); + +// Allocation Types: alloc, free, free_all, resize +GB_DEF gbAllocator gb_free_list_allocator(gbFreeList *fl); +GB_DEF GB_ALLOCATOR_PROC(gb_free_list_allocator_proc); + + + +// +// Scratch Memory Allocator - Ring Buffer Based Arena +// + +typedef struct gbScratchMemory { + void *physical_start; + isize total_size; + void *alloc_point; + void *free_point; +} gbScratchMemory; + +GB_DEF void gb_scratch_memory_init (gbScratchMemory *s, void *start, isize size); +GB_DEF b32 gb_scratch_memory_is_in_use(gbScratchMemory *s, void *ptr); + + +// Allocation Types: alloc, free, free_all, resize +GB_DEF gbAllocator gb_scratch_allocator(gbScratchMemory *s); +GB_DEF GB_ALLOCATOR_PROC(gb_scratch_allocator_proc); + +// TODO(bill): Stack allocator +// TODO(bill): Fixed heap allocator +// TODO(bill): General heap allocator. Maybe a TCMalloc like clone? + + +//////////////////////////////////////////////////////////////// +// +// Sort & Search +// +// + +#define GB_COMPARE_PROC(name) int name(void const *a, void const *b) +typedef GB_COMPARE_PROC(gbCompareProc); + +#define GB_COMPARE_PROC_PTR(def) GB_COMPARE_PROC((*def)) + +// Producure pointers +// NOTE(bill): The offset parameter specifies the offset in the structure +// e.g. gb_i32_cmp(gb_offset_of(Thing, value)) +// Use 0 if it's just the type instead. + +GB_DEF GB_COMPARE_PROC_PTR(gb_i16_cmp (isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_i32_cmp (isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_i64_cmp (isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_isize_cmp(isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_str_cmp (isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_f32_cmp (isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_f64_cmp (isize offset)); +GB_DEF GB_COMPARE_PROC_PTR(gb_char_cmp (isize offset)); + +// TODO(bill): Better sorting algorithms +// NOTE(bill): Uses quick sort for large arrays but insertion sort for small +#define gb_sort_array(array, count, compare_proc) gb_sort(array, count, gb_size_of(*(array)), compare_proc) +GB_DEF void gb_sort(void *base, isize count, isize size, gbCompareProc compare_proc); + +// NOTE(bill): the count of temp == count of items +#define gb_radix_sort(Type) gb_radix_sort_##Type +#define GB_RADIX_SORT_PROC(Type) void gb_radix_sort(Type)(Type *items, Type *temp, isize count) + +GB_DEF GB_RADIX_SORT_PROC(u8); +GB_DEF GB_RADIX_SORT_PROC(u16); +GB_DEF GB_RADIX_SORT_PROC(u32); +GB_DEF GB_RADIX_SORT_PROC(u64); + + +// NOTE(bill): Returns index or -1 if not found +#define gb_binary_search_array(array, count, key, compare_proc) gb_binary_search(array, count, gb_size_of(*(array)), key, compare_proc) +GB_DEF isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc); + +#define gb_shuffle_array(array, count) gb_shuffle(array, count, gb_size_of(*(array))) +GB_DEF void gb_shuffle(void *base, isize count, isize size); + +#define gb_reverse_array(array, count) gb_reverse(array, count, gb_size_of(*(array))) +GB_DEF void gb_reverse(void *base, isize count, isize size); + +//////////////////////////////////////////////////////////////// +// +// Char Functions +// +// + +GB_DEF char gb_char_to_lower (char c); +GB_DEF char gb_char_to_upper (char c); +GB_DEF b32 gb_char_is_space (char c); +GB_DEF b32 gb_char_is_digit (char c); +GB_DEF b32 gb_char_is_hex_digit (char c); +GB_DEF b32 gb_char_is_alpha (char c); +GB_DEF b32 gb_char_is_alphanumeric(char c); +GB_DEF i32 gb_digit_to_int (char c); +GB_DEF i32 gb_hex_digit_to_int (char c); + +// NOTE(bill): ASCII only +GB_DEF void gb_str_to_lower(char *str); +GB_DEF void gb_str_to_upper(char *str); + +GB_DEF isize gb_strlen (char const *str); +GB_DEF isize gb_strnlen(char const *str, isize max_len); +GB_DEF i32 gb_strcmp (char const *s1, char const *s2); +GB_DEF i32 gb_strncmp(char const *s1, char const *s2, isize len); +GB_DEF char *gb_strcpy (char *dest, char const *source); +GB_DEF char *gb_strncpy(char *dest, char const *source, isize len); +GB_DEF isize gb_strlcpy(char *dest, char const *source, isize len); +GB_DEF char *gb_strrev (char *str); // NOTE(bill): ASCII only + +// NOTE(bill): A less fucking crazy strtok! +GB_DEF char const *gb_strtok(char *output, char const *src, char const *delimit); + +GB_DEF b32 gb_str_has_prefix(char const *str, char const *prefix); +GB_DEF b32 gb_str_has_suffix(char const *str, char const *suffix); + +GB_DEF char const *gb_char_first_occurence(char const *str, char c); +GB_DEF char const *gb_char_last_occurence (char const *str, char c); + +GB_DEF void gb_str_concat(char *dest, isize dest_len, + char const *src_a, isize src_a_len, + char const *src_b, isize src_b_len); + +GB_DEF u64 gb_str_to_u64(char const *str, char **end_ptr, i32 base); // TODO(bill): Support more than just decimal and hexadecimal +GB_DEF i64 gb_str_to_i64(char const *str, char **end_ptr, i32 base); // TODO(bill): Support more than just decimal and hexadecimal +GB_DEF f32 gb_str_to_f32(char const *str, char **end_ptr); +GB_DEF f64 gb_str_to_f64(char const *str, char **end_ptr); +GB_DEF void gb_i64_to_str(i64 value, char *string, i32 base); +GB_DEF void gb_u64_to_str(u64 value, char *string, i32 base); + + +//////////////////////////////////////////////////////////////// +// +// UTF-8 Handling +// +// + +// NOTE(bill): Does not check if utf-8 string is valid +GB_DEF isize gb_utf8_strlen (u8 const *str); +GB_DEF isize gb_utf8_strnlen(u8 const *str, isize max_len); + +// NOTE(bill): Windows doesn't handle 8 bit filenames well ('cause Micro$hit) +GB_DEF u16 *gb_utf8_to_ucs2 (u16 *buffer, isize len, u8 const *str); +GB_DEF u8 * gb_ucs2_to_utf8 (u8 *buffer, isize len, u16 const *str); +GB_DEF u16 *gb_utf8_to_ucs2_buf(u8 const *str); // NOTE(bill): Uses locally persisting buffer +GB_DEF u8 * gb_ucs2_to_utf8_buf(u16 const *str); // NOTE(bill): Uses locally persisting buffer + +// NOTE(bill): Returns size of codepoint in bytes +GB_DEF isize gb_utf8_decode (u8 const *str, isize str_len, Rune *codepoint); +GB_DEF isize gb_utf8_codepoint_size(u8 const *str, isize str_len); +GB_DEF isize gb_utf8_encode_rune (u8 buf[4], Rune r); + +//////////////////////////////////////////////////////////////// +// +// gbString - C Read-Only-Compatible +// +// +/* +Reasoning: + + By default, strings in C are null terminated which means you have to count + the number of character up to the null character to calculate the length. + Many "better" C string libraries will create a struct for a string. + i.e. + + struct String { + Allocator allocator; + size_t length; + size_t capacity; + char * cstring; + }; + + This library tries to augment normal C strings in a better way that is still + compatible with C-style strings. + + +--------+-----------------------+-----------------+ + | Header | Binary C-style String | Null Terminator | + +--------+-----------------------+-----------------+ + | + +-> Pointer returned by functions + + Due to the meta-data being stored before the string pointer and every gb string + having an implicit null terminator, gb strings are full compatible with c-style + strings and read-only functions. + +Advantages: + + * gb strings can be passed to C-style string functions without accessing a struct + member of calling a function, i.e. + + gb_printf("%s\n", gb_str); + + Many other libraries do either of these: + + gb_printf("%s\n", string->cstr); + gb_printf("%s\n", get_cstring(string)); + + * You can access each character just like a C-style string: + + gb_printf("%c %c\n", str[0], str[13]); + + * gb strings are singularly allocated. The meta-data is next to the character + array which is better for the cache. + +Disadvantages: + + * In the C version of these functions, many return the new string. i.e. + str = gb_string_appendc(str, "another string"); + This could be changed to gb_string_appendc(&str, "another string"); but I'm still not sure. + + * This is incompatible with "gb_string.h" strings +*/ + +#if 0 +#define GB_IMPLEMENTATION +#include "gb.h" +int main(int argc, char **argv) { + gbString str = gb_string_make("Hello"); + gbString other_str = gb_string_make_length(", ", 2); + str = gb_string_append(str, other_str); + str = gb_string_appendc(str, "world!"); + + gb_printf("%s\n", str); // Hello, world! + + gb_printf("str length = %d\n", gb_string_length(str)); + + str = gb_string_set(str, "Potato soup"); + gb_printf("%s\n", str); // Potato soup + + str = gb_string_set(str, "Hello"); + other_str = gb_string_set(other_str, "Pizza"); + if (gb_strings_are_equal(str, other_str)) + gb_printf("Not called\n"); + else + gb_printf("Called\n"); + + str = gb_string_set(str, "Ab.;!...AHello World ??"); + str = gb_string_trim(str, "Ab.;!. ?"); + gb_printf("%s\n", str); // "Hello World" + + gb_string_free(str); + gb_string_free(other_str); + + return 0; +} +#endif + +// TODO(bill): Should this be a wrapper to gbArray(char) or this extra type safety better? +typedef char *gbString; + +// NOTE(bill): If you only need a small string, just use a standard c string or change the size from isize to u16, etc. +typedef struct gbStringHeader { + gbAllocator allocator; + isize length; + isize capacity; +} gbStringHeader; + +#define GB_STRING_HEADER(str) (cast(gbStringHeader *)(str) - 1) + +GB_DEF gbString gb_string_make_reserve (gbAllocator a, isize capacity); +GB_DEF gbString gb_string_make (gbAllocator a, char const *str); +GB_DEF gbString gb_string_make_length (gbAllocator a, void const *str, isize num_bytes); +GB_DEF void gb_string_free (gbString str); +GB_DEF gbString gb_string_duplicate (gbAllocator a, gbString const str); +GB_DEF isize gb_string_length (gbString const str); +GB_DEF isize gb_string_capacity (gbString const str); +GB_DEF isize gb_string_available_space(gbString const str); +GB_DEF void gb_string_clear (gbString str); +GB_DEF gbString gb_string_append (gbString str, gbString const other); +GB_DEF gbString gb_string_append_length (gbString str, void const *other, isize num_bytes); +GB_DEF gbString gb_string_appendc (gbString str, char const *other); +GB_DEF gbString gb_string_append_rune (gbString str, Rune r); +GB_DEF gbString gb_string_append_fmt (gbString str, char const *fmt, ...); +GB_DEF gbString gb_string_set (gbString str, char const *cstr); +GB_DEF gbString gb_string_make_space_for (gbString str, isize add_len); +GB_DEF isize gb_string_allocation_size(gbString const str); +GB_DEF b32 gb_string_are_equal (gbString const lhs, gbString const rhs); +GB_DEF gbString gb_string_trim (gbString str, char const *cut_set); +GB_DEF gbString gb_string_trim_space (gbString str); // Whitespace ` \t\r\n\v\f` + + + +//////////////////////////////////////////////////////////////// +// +// Fixed Capacity Buffer (POD Types) +// +// +// gbBuffer(Type) works like gbString or gbArray where the actual type is just a pointer to the first +// element. +// + +typedef struct gbBufferHeader { + isize count; + isize capacity; +} gbBufferHeader; + +#define gbBuffer(Type) Type * + +#define GB_BUFFER_HEADER(x) (cast(gbBufferHeader *)(x) - 1) +#define gb_buffer_count(x) (GB_BUFFER_HEADER(x)->count) +#define gb_buffer_capacity(x) (GB_BUFFER_HEADER(x)->capacity) + +#define gb_buffer_init(x, allocator, cap) do { \ + void **nx = cast(void **)&(x); \ + gbBufferHeader *gb__bh = cast(gbBufferHeader *)gb_alloc((allocator), (cap)*gb_size_of(*(x))); \ + gb__bh->count = 0; \ + gb__bh->capacity = cap; \ + *nx = cast(void *)(gb__bh+1); \ +} while (0) + + +#define gb_buffer_free(x, allocator) (gb_free(allocator, GB_BUFFER_HEADER(x))) + +#define gb_buffer_append(x, item) do { (x)[gb_buffer_count(x)++] = (item); } while (0) + +#define gb_buffer_appendv(x, items, item_count) do { \ + GB_ASSERT(gb_size_of(*(items)) == gb_size_of(*(x))); \ + GB_ASSERT(gb_buffer_count(x)+item_count <= gb_buffer_capacity(x)); \ + gb_memcopy(&(x)[gb_buffer_count(x)], (items), gb_size_of(*(x))*(item_count)); \ + gb_buffer_count(x) += (item_count); \ +} while (0) + +#define gb_buffer_pop(x) do { GB_ASSERT(gb_buffer_count(x) > 0); gb_buffer_count(x)--; } while (0) +#define gb_buffer_clear(x) do { gb_buffer_count(x) = 0; } while (0) + + + +//////////////////////////////////////////////////////////////// +// +// Dynamic Array (POD Types) +// +// NOTE(bill): I know this is a macro hell but C is an old (and shit) language with no proper arrays +// Also why the fuck not?! It fucking works! And it has custom allocation, which is already better than C++! +// +// gbArray(Type) works like gbString or gbBuffer where the actual type is just a pointer to the first +// element. +// + + + +// Available Procedures for gbArray(Type) +// gb_array_init +// gb_array_free +// gb_array_set_capacity +// gb_array_grow +// gb_array_append +// gb_array_appendv +// gb_array_pop +// gb_array_clear +// gb_array_resize +// gb_array_reserve +// + +#if 0 // Example +void foo(void) { + isize i; + int test_values[] = {4, 2, 1, 7}; + gbAllocator a = gb_heap_allocator(); + gbArray(int) items; + + gb_array_init(items, a); + + gb_array_append(items, 1); + gb_array_append(items, 4); + gb_array_append(items, 9); + gb_array_append(items, 16); + + items[1] = 3; // Manually set value + // NOTE: No array bounds checking + + for (i = 0; i < items.count; i++) + gb_printf("%d\n", items[i]); + // 1 + // 3 + // 9 + // 16 + + gb_array_clear(items); + + gb_array_appendv(items, test_values, gb_count_of(test_values)); + for (i = 0; i < items.count; i++) + gb_printf("%d\n", items[i]); + // 4 + // 2 + // 1 + // 7 + + gb_array_free(items); +} +#endif + +typedef struct gbArrayHeader { + gbAllocator allocator; + isize count; + isize capacity; +} gbArrayHeader; + +// NOTE(bill): This thing is magic! +#define gbArray(Type) Type * + +#ifndef GB_ARRAY_GROW_FORMULA +#define GB_ARRAY_GROW_FORMULA(x) (2*(x) + 8) +#endif + +GB_STATIC_ASSERT(GB_ARRAY_GROW_FORMULA(0) > 0); + +#define GB_ARRAY_HEADER(x) (cast(gbArrayHeader *)(x) - 1) +#define gb_array_allocator(x) (GB_ARRAY_HEADER(x)->allocator) +#define gb_array_count(x) (GB_ARRAY_HEADER(x)->count) +#define gb_array_capacity(x) (GB_ARRAY_HEADER(x)->capacity) + +// TODO(bill): Have proper alignment! +#define gb_array_init_reserve(x, allocator_, cap) do { \ + void **gb__array_ = cast(void **)&(x); \ + gbArrayHeader *gb__ah = cast(gbArrayHeader *)gb_alloc(allocator_, gb_size_of(gbArrayHeader)+gb_size_of(*(x))*(cap)); \ + gb__ah->allocator = allocator_; \ + gb__ah->count = 0; \ + gb__ah->capacity = cap; \ + *gb__array_ = cast(void *)(gb__ah+1); \ +} while (0) + +// NOTE(bill): Give it an initial default capacity +#define gb_array_init(x, allocator) gb_array_init_reserve(x, allocator, GB_ARRAY_GROW_FORMULA(0)) + +#define gb_array_free(x) do { \ + gbArrayHeader *gb__ah = GB_ARRAY_HEADER(x); \ + gb_free(gb__ah->allocator, gb__ah); \ +} while (0) + +#define gb_array_set_capacity(x, capacity) do { \ + if (x) { \ + void **gb__array_ = cast(void **)&(x); \ + *gb__array_ = gb__array_set_capacity((x), (capacity), gb_size_of(*(x))); \ + } \ +} while (0) + +// NOTE(bill): Do not use the thing below directly, use the macro +GB_DEF void *gb__array_set_capacity(void *array, isize capacity, isize element_size); + + +// TODO(bill): Decide on a decent growing formula for gbArray +#define gb_array_grow(x, min_capacity) do { \ + isize new_capacity = GB_ARRAY_GROW_FORMULA(gb_array_capacity(x)); \ + if (new_capacity < (min_capacity)) \ + new_capacity = (min_capacity); \ + gb_array_set_capacity(x, new_capacity); \ +} while (0) + + +#define gb_array_append(x, item) do { \ + if (gb_array_capacity(x) < gb_array_count(x)+1) \ + gb_array_grow(x, 0); \ + (x)[gb_array_count(x)++] = (item); \ +} while (0) + +#define gb_array_appendv(x, items, item_count) do { \ + gbArrayHeader *gb__ah = GB_ARRAY_HEADER(x); \ + GB_ASSERT(gb_size_of((items)[0]) == gb_size_of((x)[0])); \ + if (gb__ah->capacity < gb__ah->count+(item_count)) \ + gb_array_grow(x, gb__ah->count+(item_count)); \ + gb_memcopy(&(x)[gb__ah->count], (items), gb_size_of((x)[0])*(item_count));\ + gb__ah->count += (item_count); \ +} while (0) + + + +#define gb_array_pop(x) do { GB_ASSERT(GB_ARRAY_HEADER(x)->count > 0); GB_ARRAY_HEADER(x)->count--; } while (0) +#define gb_array_clear(x) do { GB_ARRAY_HEADER(x)->count = 0; } while (0) + +#define gb_array_resize(x, new_count) do { \ + if (GB_ARRAY_HEADER(x)->capacity < (new_count)) \ + gb_array_grow(x, (new_count)); \ + GB_ARRAY_HEADER(x)->count = (new_count); \ +} while (0) + + +#define gb_array_reserve(x, new_capacity) do { \ + if (GB_ARRAY_HEADER(x)->capacity < (new_capacity)) \ + gb_array_set_capacity(x, new_capacity); \ +} while (0) + + + + + +//////////////////////////////////////////////////////////////// +// +// Hashing and Checksum Functions +// +// + +GB_EXTERN u32 gb_adler32(void const *data, isize len); + +GB_EXTERN u32 gb_crc32(void const *data, isize len); +GB_EXTERN u64 gb_crc64(void const *data, isize len); + +GB_EXTERN u32 gb_fnv32 (void const *data, isize len); +GB_EXTERN u64 gb_fnv64 (void const *data, isize len); +GB_EXTERN u32 gb_fnv32a(void const *data, isize len); +GB_EXTERN u64 gb_fnv64a(void const *data, isize len); + +// NOTE(bill): Default seed of 0x9747b28c +// NOTE(bill): I prefer using murmur64 for most hashes +GB_EXTERN u32 gb_murmur32(void const *data, isize len); +GB_EXTERN u64 gb_murmur64(void const *data, isize len); + +GB_EXTERN u32 gb_murmur32_seed(void const *data, isize len, u32 seed); +GB_EXTERN u64 gb_murmur64_seed(void const *data, isize len, u64 seed); + + +//////////////////////////////////////////////////////////////// +// +// Instantiated Hash Table +// +// This is an attempt to implement a templated hash table +// NOTE(bill): The key is aways a u64 for simplicity and you will _probably_ _never_ need anything bigger. +// +// Hash table type and function declaration, call: GB_TABLE_DECLARE(PREFIX, NAME, N, VALUE) +// Hash table function definitions, call: GB_TABLE_DEFINE(NAME, N, VALUE) +// +// PREFIX - a prefix for function prototypes e.g. extern, static, etc. +// NAME - Name of the Hash Table +// FUNC - the name will prefix function names +// VALUE - the type of the value to be stored +// +// NOTE(bill): I really wish C had decent metaprogramming capabilities (and no I don't mean C++'s templates either) +// + +typedef struct gbHashTableFindResult { + isize hash_index; + isize entry_prev; + isize entry_index; +} gbHashTableFindResult; + +#define GB_TABLE(PREFIX, NAME, FUNC, VALUE) \ + GB_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE); \ + GB_TABLE_DEFINE(NAME, FUNC, VALUE); + +#define GB_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE) \ +typedef struct GB_JOIN2(NAME,Entry) { \ + u64 key; \ + isize next; \ + VALUE value; \ +} GB_JOIN2(NAME,Entry); \ +\ +typedef struct NAME { \ + gbArray(isize) hashes; \ + gbArray(GB_JOIN2(NAME,Entry)) entries; \ +} NAME; \ +\ +PREFIX void GB_JOIN2(FUNC,init) (NAME *h, gbAllocator a); \ +PREFIX void GB_JOIN2(FUNC,destroy) (NAME *h); \ +PREFIX VALUE * GB_JOIN2(FUNC,get) (NAME *h, u64 key); \ +PREFIX void GB_JOIN2(FUNC,set) (NAME *h, u64 key, VALUE value); \ +PREFIX void GB_JOIN2(FUNC,grow) (NAME *h); \ +PREFIX void GB_JOIN2(FUNC,rehash) (NAME *h, isize new_count); \ + + + + + +#define GB_TABLE_DEFINE(NAME, FUNC, VALUE) \ +void GB_JOIN2(FUNC,init)(NAME *h, gbAllocator a) { \ + gb_array_init(h->hashes, a); \ + gb_array_init(h->entries, a); \ +} \ +\ +void GB_JOIN2(FUNC,destroy)(NAME *h) { \ + if (h->entries) gb_array_free(h->entries); \ + if (h->hashes) gb_array_free(h->hashes); \ +} \ +\ +gb_internal isize GB_JOIN2(FUNC,_add_entry)(NAME *h, u64 key) { \ + isize index; \ + GB_JOIN2(NAME,Entry) e = {0}; \ + e.key = key; \ + e.next = -1; \ + index = gb_array_count(h->entries); \ + gb_array_append(h->entries, e); \ + return index; \ +} \ +\ +gb_internal gbHashTableFindResult GB_JOIN2(FUNC,_find)(NAME *h, u64 key) { \ + gbHashTableFindResult r = {-1, -1, -1}; \ + if (gb_array_count(h->hashes) > 0) { \ + r.hash_index = key % gb_array_count(h->hashes); \ + r.entry_index = h->hashes[r.hash_index]; \ + while (r.entry_index >= 0) { \ + if (h->entries[r.entry_index].key == key) \ + return r; \ + r.entry_prev = r.entry_index; \ + r.entry_index = h->entries[r.entry_index].next; \ + } \ + } \ + return r; \ +} \ +\ +gb_internal b32 GB_JOIN2(FUNC,_full)(NAME *h) { \ + return 0.75f * gb_array_count(h->hashes) < gb_array_count(h->entries); \ +} \ +\ +void GB_JOIN2(FUNC,grow)(NAME *h) { \ + isize new_count = GB_ARRAY_GROW_FORMULA(gb_array_count(h->entries)); \ + GB_JOIN2(FUNC,rehash)(h, new_count); \ +} \ +\ +void GB_JOIN2(FUNC,rehash)(NAME *h, isize new_count) { \ + isize i, j; \ + NAME nh = {0}; \ + GB_JOIN2(FUNC,init)(&nh, gb_array_allocator(h->hashes)); \ + gb_array_resize(nh.hashes, new_count); \ + gb_array_reserve(nh.entries, gb_array_count(h->entries)); \ + for (i = 0; i < new_count; i++) \ + nh.hashes[i] = -1; \ + for (i = 0; i < gb_array_count(h->entries); i++) { \ + GB_JOIN2(NAME,Entry) *e; \ + gbHashTableFindResult fr; \ + if (gb_array_count(nh.hashes) == 0) \ + GB_JOIN2(FUNC,grow)(&nh); \ + e = &h->entries[i]; \ + fr = GB_JOIN2(FUNC,_find)(&nh, e->key); \ + j = GB_JOIN2(FUNC,_add_entry)(&nh, e->key); \ + if (fr.entry_prev < 0) \ + nh.hashes[fr.hash_index] = j; \ + else \ + nh.entries[fr.entry_prev].next = j; \ + nh.entries[j].next = fr.entry_index; \ + nh.entries[j].value = e->value; \ + if (GB_JOIN2(FUNC,_full)(&nh)) \ + GB_JOIN2(FUNC,grow)(&nh); \ + } \ + GB_JOIN2(FUNC,destroy)(h); \ + h->hashes = nh.hashes; \ + h->entries = nh.entries; \ +} \ +\ +VALUE *GB_JOIN2(FUNC,get)(NAME *h, u64 key) { \ + isize index = GB_JOIN2(FUNC,_find)(h, key).entry_index; \ + if (index >= 0) \ + return &h->entries[index].value; \ + return NULL; \ +} \ +\ +void GB_JOIN2(FUNC,set)(NAME *h, u64 key, VALUE value) { \ + isize index; \ + gbHashTableFindResult fr; \ + if (gb_array_count(h->hashes) == 0) \ + GB_JOIN2(FUNC,grow)(h); \ + fr = GB_JOIN2(FUNC,_find)(h, key); \ + if (fr.entry_index >= 0) { \ + index = fr.entry_index; \ + } else { \ + index = GB_JOIN2(FUNC,_add_entry)(h, key); \ + if (fr.entry_prev >= 0) { \ + h->entries[fr.entry_prev].next = index; \ + } else { \ + h->hashes[fr.hash_index] = index; \ + } \ + } \ + h->entries[index].value = value; \ + if (GB_JOIN2(FUNC,_full)(h)) \ + GB_JOIN2(FUNC,grow)(h); \ +} \ + + + + +//////////////////////////////////////////////////////////////// +// +// File Handling +// + + +typedef u32 gbFileMode; +typedef enum gbFileModeFlag { + gbFileMode_Read = GB_BIT(0), + gbFileMode_Write = GB_BIT(1), + gbFileMode_Append = GB_BIT(2), + gbFileMode_Rw = GB_BIT(3), + + gbFileMode_Modes = gbFileMode_Read | gbFileMode_Write | gbFileMode_Append | gbFileMode_Rw, +} gbFileModeFlag; + +// NOTE(bill): Only used internally and for the file operations +typedef enum gbSeekWhenceType { + gbSeekWhence_Begin = 0, + gbSeekWhence_Current = 1, + gbSeekWhence_End = 2, +} gbSeekWhenceType; + +typedef enum gbFileError { + gbFileError_None, + gbFileError_Invalid, + gbFileError_InvalidFilename, + gbFileError_Exists, + gbFileError_NotExists, + gbFileError_Permission, + gbFileError_TruncationFailure, +} gbFileError; + +typedef union gbFileDescriptor { + void * p; + intptr i; + uintptr u; +} gbFileDescriptor; + +typedef struct gbFileOperations gbFileOperations; + +#define GB_FILE_OPEN_PROC(name) gbFileError name(gbFileDescriptor *fd, gbFileOperations *ops, gbFileMode mode, char const *filename) +#define GB_FILE_READ_AT_PROC(name) b32 name(gbFileDescriptor fd, void *buffer, isize size, i64 offset, isize *bytes_read) +#define GB_FILE_WRITE_AT_PROC(name) b32 name(gbFileDescriptor fd, void const *buffer, isize size, i64 offset, isize *bytes_written) +#define GB_FILE_SEEK_PROC(name) b32 name(gbFileDescriptor fd, i64 offset, gbSeekWhenceType whence, i64 *new_offset) +#define GB_FILE_CLOSE_PROC(name) void name(gbFileDescriptor fd) +typedef GB_FILE_OPEN_PROC(gbFileOpenProc); +typedef GB_FILE_READ_AT_PROC(gbFileReadProc); +typedef GB_FILE_WRITE_AT_PROC(gbFileWriteProc); +typedef GB_FILE_SEEK_PROC(gbFileSeekProc); +typedef GB_FILE_CLOSE_PROC(gbFileCloseProc); + +struct gbFileOperations { + gbFileReadProc *read_at; + gbFileWriteProc *write_at; + gbFileSeekProc *seek; + gbFileCloseProc *close; +}; + +extern gbFileOperations const gbDefaultFileOperations; + + +// typedef struct gbDirInfo { +// u8 *buf; +// isize buf_count; +// isize buf_pos; +// } gbDirInfo; + +typedef u64 gbFileTime; + +typedef struct gbFile { + gbFileOperations ops; + gbFileDescriptor fd; + char const * filename; + gbFileTime last_write_time; + // gbDirInfo * dir_info; // TODO(bill): Get directory info +} gbFile; + +// TODO(bill): gbAsyncFile + +typedef enum gbFileStandardType { + gbFileStandard_Input, + gbFileStandard_Output, + gbFileStandard_Error, + + gbFileStandard_Count, +} gbFileStandardType; + +GB_DEF gbFile *const gb_file_get_standard(gbFileStandardType std); + +GB_DEF gbFileError gb_file_create (gbFile *file, char const *filename); +GB_DEF gbFileError gb_file_open (gbFile *file, char const *filename); +GB_DEF gbFileError gb_file_open_mode (gbFile *file, gbFileMode mode, char const *filename); +GB_DEF gbFileError gb_file_new (gbFile *file, gbFileDescriptor fd, gbFileOperations ops, char const *filename); +GB_DEF b32 gb_file_read_at_check (gbFile *file, void *buffer, isize size, i64 offset, isize *bytes_read); +GB_DEF b32 gb_file_write_at_check(gbFile *file, void const *buffer, isize size, i64 offset, isize *bytes_written); +GB_DEF b32 gb_file_read_at (gbFile *file, void *buffer, isize size, i64 offset); +GB_DEF b32 gb_file_write_at (gbFile *file, void const *buffer, isize size, i64 offset); +GB_DEF i64 gb_file_seek (gbFile *file, i64 offset); +GB_DEF i64 gb_file_seek_to_end (gbFile *file); +GB_DEF i64 gb_file_skip (gbFile *file, i64 bytes); // NOTE(bill): Skips a certain amount of bytes +GB_DEF i64 gb_file_tell (gbFile *file); +GB_DEF gbFileError gb_file_close (gbFile *file); +GB_DEF b32 gb_file_read (gbFile *file, void *buffer, isize size); +GB_DEF b32 gb_file_write (gbFile *file, void const *buffer, isize size); +GB_DEF i64 gb_file_size (gbFile *file); +GB_DEF char const *gb_file_name (gbFile *file); +GB_DEF gbFileError gb_file_truncate (gbFile *file, i64 size); +GB_DEF b32 gb_file_has_changed (gbFile *file); // NOTE(bill): Changed since lasted checked +// TODO(bill): +// gbFileError gb_file_temp(gbFile *file); +// + +typedef struct gbFileContents { + gbAllocator allocator; + void * data; + isize size; +} gbFileContents; + + +GB_DEF gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char const *filepath); +GB_DEF void gb_file_free_contents(gbFileContents *fc); + + +// TODO(bill): Should these have different na,es as they do not take in a gbFile * ??? +GB_DEF b32 gb_file_exists (char const *filepath); +GB_DEF gbFileTime gb_file_last_write_time(char const *filepath); +GB_DEF b32 gb_file_copy (char const *existing_filename, char const *new_filename, b32 fail_if_exists); +GB_DEF b32 gb_file_move (char const *existing_filename, char const *new_filename); +GB_DEF b32 gb_file_remove (char const *filename); + + +#ifndef GB_PATH_SEPARATOR + #if defined(GB_SYSTEM_WINDOWS) + #define GB_PATH_SEPARATOR '\\' + #else + #define GB_PATH_SEPARATOR '/' + #endif +#endif + +GB_DEF b32 gb_path_is_absolute (char const *path); +GB_DEF b32 gb_path_is_relative (char const *path); +GB_DEF b32 gb_path_is_root (char const *path); +GB_DEF char const *gb_path_base_name (char const *path); +GB_DEF char const *gb_path_extension (char const *path); +GB_DEF char * gb_path_get_full_name(gbAllocator a, char const *path); + + +//////////////////////////////////////////////////////////////// +// +// Printing +// +// + +GB_DEF isize gb_printf (char const *fmt, ...) GB_PRINTF_ARGS(1); +GB_DEF isize gb_printf_va (char const *fmt, va_list va); +GB_DEF isize gb_printf_err (char const *fmt, ...) GB_PRINTF_ARGS(1); +GB_DEF isize gb_printf_err_va (char const *fmt, va_list va); +GB_DEF isize gb_fprintf (gbFile *f, char const *fmt, ...) GB_PRINTF_ARGS(2); +GB_DEF isize gb_fprintf_va (gbFile *f, char const *fmt, va_list va); + +GB_DEF char *gb_bprintf (char const *fmt, ...) GB_PRINTF_ARGS(1); // NOTE(bill): A locally persisting buffer is used internally +GB_DEF char *gb_bprintf_va (char const *fmt, va_list va); // NOTE(bill): A locally persisting buffer is used internally +GB_DEF isize gb_snprintf (char *str, isize n, char const *fmt, ...) GB_PRINTF_ARGS(3); +GB_DEF isize gb_snprintf_va(char *str, isize n, char const *fmt, va_list va); + +//////////////////////////////////////////////////////////////// +// +// DLL Handling +// +// + +typedef void *gbDllHandle; +typedef void (*gbDllProc)(void); + +GB_DEF gbDllHandle gb_dll_load (char const *filepath); +GB_DEF void gb_dll_unload (gbDllHandle dll); +GB_DEF gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name); + + +//////////////////////////////////////////////////////////////// +// +// Time +// +// + +GB_DEF u64 gb_rdtsc (void); +GB_DEF f64 gb_time_now (void); // NOTE(bill): This is only for relative time e.g. game loops +GB_DEF u64 gb_utc_time_now(void); // NOTE(bill): Number of microseconds since 1601-01-01 UTC +GB_DEF void gb_sleep_ms (u32 ms); + + +//////////////////////////////////////////////////////////////// +// +// Miscellany +// +// + +typedef struct gbRandom { + u32 offsets[8]; + u32 value; +} gbRandom; + +// NOTE(bill): Generates from numerous sources to produce a decent pseudo-random seed +GB_DEF void gb_random_init (gbRandom *r); +GB_DEF u32 gb_random_gen_u32 (gbRandom *r); +GB_DEF u32 gb_random_gen_u32_unique(gbRandom *r); +GB_DEF u64 gb_random_gen_u64 (gbRandom *r); // NOTE(bill): (gb_random_gen_u32() << 32) | gb_random_gen_u32() +GB_DEF isize gb_random_gen_isize (gbRandom *r); +GB_DEF i64 gb_random_range_i64 (gbRandom *r, i64 lower_inc, i64 higher_inc); +GB_DEF isize gb_random_range_isize (gbRandom *r, isize lower_inc, isize higher_inc); +GB_DEF f64 gb_random_range_f64 (gbRandom *r, f64 lower_inc, f64 higher_inc); + + + + +GB_DEF void gb_exit (u32 code); +GB_DEF void gb_yield (void); +GB_DEF void gb_set_env (char const *name, char const *value); +GB_DEF void gb_unset_env(char const *name); + +GB_DEF u16 gb_endian_swap16(u16 i); +GB_DEF u32 gb_endian_swap32(u32 i); +GB_DEF u64 gb_endian_swap64(u64 i); + +GB_DEF isize gb_count_set_bits(u64 mask); + +//////////////////////////////////////////////////////////////// +// +// Platform Stuff +// +// + +#if defined(GB_PLATFORM) + +// NOTE(bill): +// Coordiate system - +ve x - left to right +// - +ve y - bottom to top +// - Relative to window + +// TODO(bill): Proper documentation for this with code examples + +// Window Support - Complete +// OS X Support - Missing: +// * Sofware framebuffer +// * (show|hide) window +// * show_cursor +// * toggle (fullscreen|borderless) +// * set window position +// * Clipboard +// * GameControllers +// Linux Support - None +// Other OS Support - None + +#ifndef GB_MAX_GAME_CONTROLLER_COUNT +#define GB_MAX_GAME_CONTROLLER_COUNT 4 +#endif + +typedef enum gbKeyType { + gbKey_Unknown = 0, // Unhandled key + + // NOTE(bill): Allow the basic printable keys to be aliased with their chars + gbKey_0 = '0', + gbKey_1, + gbKey_2, + gbKey_3, + gbKey_4, + gbKey_5, + gbKey_6, + gbKey_7, + gbKey_8, + gbKey_9, + + gbKey_A = 'A', + gbKey_B, + gbKey_C, + gbKey_D, + gbKey_E, + gbKey_F, + gbKey_G, + gbKey_H, + gbKey_I, + gbKey_J, + gbKey_K, + gbKey_L, + gbKey_M, + gbKey_N, + gbKey_O, + gbKey_P, + gbKey_Q, + gbKey_R, + gbKey_S, + gbKey_T, + gbKey_U, + gbKey_V, + gbKey_W, + gbKey_X, + gbKey_Y, + gbKey_Z, + + gbKey_Lbracket = '[', + gbKey_Rbracket = ']', + gbKey_Semicolon = ';', + gbKey_Comma = ',', + gbKey_Period = '.', + gbKey_Quote = '\'', + gbKey_Slash = '/', + gbKey_Backslash = '\\', + gbKey_Grave = '`', + gbKey_Equals = '=', + gbKey_Minus = '-', + gbKey_Space = ' ', + + gbKey__Pad = 128, // NOTE(bill): make sure ASCII is reserved + + gbKey_Escape, // Escape + gbKey_Lcontrol, // Left Control + gbKey_Lshift, // Left Shift + gbKey_Lalt, // Left Alt + gbKey_Lsystem, // Left OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... + gbKey_Rcontrol, // Right Control + gbKey_Rshift, // Right Shift + gbKey_Ralt, // Right Alt + gbKey_Rsystem, // Right OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... + gbKey_Menu, // Menu + gbKey_Return, // Return + gbKey_Backspace, // Backspace + gbKey_Tab, // Tabulation + gbKey_Pageup, // Page up + gbKey_Pagedown, // Page down + gbKey_End, // End + gbKey_Home, // Home + gbKey_Insert, // Insert + gbKey_Delete, // Delete + gbKey_Plus, // + + gbKey_Subtract, // - + gbKey_Multiply, // * + gbKey_Divide, // / + gbKey_Left, // Left arrow + gbKey_Right, // Right arrow + gbKey_Up, // Up arrow + gbKey_Down, // Down arrow + gbKey_Numpad0, // Numpad 0 + gbKey_Numpad1, // Numpad 1 + gbKey_Numpad2, // Numpad 2 + gbKey_Numpad3, // Numpad 3 + gbKey_Numpad4, // Numpad 4 + gbKey_Numpad5, // Numpad 5 + gbKey_Numpad6, // Numpad 6 + gbKey_Numpad7, // Numpad 7 + gbKey_Numpad8, // Numpad 8 + gbKey_Numpad9, // Numpad 9 + gbKey_NumpadDot, // Numpad . + gbKey_NumpadEnter, // Numpad Enter + gbKey_F1, // F1 + gbKey_F2, // F2 + gbKey_F3, // F3 + gbKey_F4, // F4 + gbKey_F5, // F5 + gbKey_F6, // F6 + gbKey_F7, // F7 + gbKey_F8, // F8 + gbKey_F9, // F8 + gbKey_F10, // F10 + gbKey_F11, // F11 + gbKey_F12, // F12 + gbKey_F13, // F13 + gbKey_F14, // F14 + gbKey_F15, // F15 + gbKey_Pause, // Pause + + gbKey_Count, +} gbKeyType; + +/* TODO(bill): Change name? */ +typedef u8 gbKeyState; +typedef enum gbKeyStateFlag { + gbKeyState_Down = GB_BIT(0), + gbKeyState_Pressed = GB_BIT(1), + gbKeyState_Released = GB_BIT(2) +} gbKeyStateFlag; + +GB_DEF void gb_key_state_update(gbKeyState *s, b32 is_down); + +typedef enum gbMouseButtonType { + gbMouseButton_Left, + gbMouseButton_Middle, + gbMouseButton_Right, + gbMouseButton_X1, + gbMouseButton_X2, + + gbMouseButton_Count +} gbMouseButtonType; + +typedef enum gbControllerAxisType { + gbControllerAxis_LeftX, + gbControllerAxis_LeftY, + gbControllerAxis_RightX, + gbControllerAxis_RightY, + gbControllerAxis_LeftTrigger, + gbControllerAxis_RightTrigger, + + gbControllerAxis_Count +} gbControllerAxisType; + +typedef enum gbControllerButtonType { + gbControllerButton_Up, + gbControllerButton_Down, + gbControllerButton_Left, + gbControllerButton_Right, + gbControllerButton_A, + gbControllerButton_B, + gbControllerButton_X, + gbControllerButton_Y, + gbControllerButton_LeftShoulder, + gbControllerButton_RightShoulder, + gbControllerButton_Back, + gbControllerButton_Start, + gbControllerButton_LeftThumb, + gbControllerButton_RightThumb, + + gbControllerButton_Count +} gbControllerButtonType; + +typedef struct gbGameController { + b16 is_connected, is_analog; + + f32 axes[gbControllerAxis_Count]; + gbKeyState buttons[gbControllerButton_Count]; +} gbGameController; + +#if defined(GB_SYSTEM_WINDOWS) + typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD; + typedef struct _XINPUT_STATE XINPUT_STATE; + typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION; + + #define GB_XINPUT_GET_STATE(name) unsigned long __stdcall name(unsigned long dwUserIndex, XINPUT_STATE *pState) + typedef GB_XINPUT_GET_STATE(gbXInputGetStateProc); + + #define GB_XINPUT_SET_STATE(name) unsigned long __stdcall name(unsigned long dwUserIndex, XINPUT_VIBRATION *pVibration) + typedef GB_XINPUT_SET_STATE(gbXInputSetStateProc); +#endif + + +typedef enum gbWindowFlag { + gbWindow_Fullscreen = GB_BIT(0), + gbWindow_Hidden = GB_BIT(1), + gbWindow_Borderless = GB_BIT(2), + gbWindow_Resizable = GB_BIT(3), + gbWindow_Minimized = GB_BIT(4), + gbWindow_Maximized = GB_BIT(5), + gbWindow_FullscreenDesktop = gbWindow_Fullscreen | gbWindow_Borderless, +} gbWindowFlag; + +typedef enum gbRendererType { + gbRenderer_Opengl, + gbRenderer_Software, + + gbRenderer_Count, +} gbRendererType; + + + +#if defined(GB_SYSTEM_WINDOWS) && !defined(_WINDOWS_) +typedef struct tagBITMAPINFOHEADER { + unsigned long biSize; + long biWidth; + long biHeight; + u16 biPlanes; + u16 biBitCount; + unsigned long biCompression; + unsigned long biSizeImage; + long biXPelsPerMeter; + long biYPelsPerMeter; + unsigned long biClrUsed; + unsigned long biClrImportant; +} BITMAPINFOHEADER, *PBITMAPINFOHEADER; +typedef struct tagRGBQUAD { + u8 rgbBlue; + u8 rgbGreen; + u8 rgbRed; + u8 rgbReserved; +} RGBQUAD; +typedef struct tagBITMAPINFO { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} BITMAPINFO, *PBITMAPINFO; +#endif + +typedef struct gbPlatform { + b32 is_initialized; + + void *window_handle; + i32 window_x, window_y; + i32 window_width, window_height; + u32 window_flags; + b16 window_is_closed, window_has_focus; + +#if defined(GB_SYSTEM_WINDOWS) + void *win32_dc; +#elif defined(GB_SYSTEM_OSX) + void *osx_autorelease_pool; // TODO(bill): Is this really needed? +#endif + + gbRendererType renderer_type; + union { + struct { + void * context; + i32 major; + i32 minor; + b16 core, compatible; + gbDllHandle dll_handle; + } opengl; + + // NOTE(bill): Software rendering + struct { +#if defined(GB_SYSTEM_WINDOWS) + BITMAPINFO win32_bmi; +#endif + void * memory; + isize memory_size; + i32 pitch; + i32 bits_per_pixel; + } sw_framebuffer; + }; + + gbKeyState keys[gbKey_Count]; + struct { + gbKeyState control; + gbKeyState alt; + gbKeyState shift; + } key_modifiers; + + Rune char_buffer[256]; + isize char_buffer_count; + + b32 mouse_clip; + i32 mouse_x, mouse_y; + i32 mouse_dx, mouse_dy; // NOTE(bill): Not raw mouse movement + i32 mouse_raw_dx, mouse_raw_dy; // NOTE(bill): Raw mouse movement + f32 mouse_wheel_delta; + gbKeyState mouse_buttons[gbMouseButton_Count]; + + gbGameController game_controllers[GB_MAX_GAME_CONTROLLER_COUNT]; + + f64 curr_time; + f64 dt_for_frame; + b32 quit_requested; + +#if defined(GB_SYSTEM_WINDOWS) + struct { + gbXInputGetStateProc *get_state; + gbXInputSetStateProc *set_state; + } xinput; +#endif +} gbPlatform; + + +typedef struct gbVideoMode { + i32 width, height; + i32 bits_per_pixel; +} gbVideoMode; + +GB_DEF gbVideoMode gb_video_mode (i32 width, i32 height, i32 bits_per_pixel); +GB_DEF b32 gb_video_mode_is_valid (gbVideoMode mode); +GB_DEF gbVideoMode gb_video_mode_get_desktop (void); +GB_DEF isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count); // NOTE(bill): returns mode count +GB_DEF GB_COMPARE_PROC(gb_video_mode_cmp); // NOTE(bill): Sort smallest to largest (Ascending) +GB_DEF GB_COMPARE_PROC(gb_video_mode_dsc_cmp); // NOTE(bill): Sort largest to smallest (Descending) + + +// NOTE(bill): Software rendering +GB_DEF b32 gb_platform_init_with_software (gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags); +// NOTE(bill): OpenGL Rendering +GB_DEF b32 gb_platform_init_with_opengl (gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags, i32 major, i32 minor, b32 core, b32 compatible); +GB_DEF void gb_platform_update (gbPlatform *p); +GB_DEF void gb_platform_display (gbPlatform *p); +GB_DEF void gb_platform_destroy (gbPlatform *p); +GB_DEF void gb_platform_show_cursor (gbPlatform *p, b32 show); +GB_DEF void gb_platform_set_mouse_position (gbPlatform *p, i32 x, i32 y); +GB_DEF void gb_platform_set_controller_vibration (gbPlatform *p, isize index, f32 left_motor, f32 right_motor); +GB_DEF b32 gb_platform_has_clipboard_text (gbPlatform *p); +GB_DEF void gb_platform_set_clipboard_text (gbPlatform *p, char const *str); +GB_DEF char *gb_platform_get_clipboard_text (gbPlatform *p, gbAllocator a); +GB_DEF void gb_platform_set_window_position (gbPlatform *p, i32 x, i32 y); +GB_DEF void gb_platform_set_window_title (gbPlatform *p, char const *title, ...) GB_PRINTF_ARGS(2); +GB_DEF void gb_platform_toggle_fullscreen (gbPlatform *p, b32 fullscreen_desktop); +GB_DEF void gb_platform_toggle_borderless (gbPlatform *p); +GB_DEF void gb_platform_make_opengl_context_current(gbPlatform *p); +GB_DEF void gb_platform_show_window (gbPlatform *p); +GB_DEF void gb_platform_hide_window (gbPlatform *p); + + +#endif // GB_PLATFORM + +#if defined(__cplusplus) +} +#endif + +#endif // GB_INCLUDE_GB_H + + + + + + +//////////////////////////////////////////////////////////////// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// Implementation +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// It's turtles all the way down! +//////////////////////////////////////////////////////////////// +#if defined(GB_IMPLEMENTATION) && !defined(GB_IMPLEMENTATION_DONE) +#define GB_IMPLEMENTATION_DONE + +#if defined(__cplusplus) +extern "C" { +#endif + + +#if defined(GB_COMPILER_MSVC) && !defined(_WINDOWS_) + //////////////////////////////////////////////////////////////// + // + // Bill's Mini Windows.h + // + // + + #define WINAPI __stdcall + #define WINAPIV __cdecl + #define CALLBACK __stdcall + #define MAX_PATH 260 + #define CCHDEVICENAME 32 + #define CCHFORMNAME 32 + + typedef unsigned long DWORD; + typedef int WINBOOL; + #ifndef XFree86Server + #ifndef __OBJC__ + typedef WINBOOL BOOL; + #else + #define BOOL WINBOOL + #endif + typedef unsigned char BYTE; + #endif + typedef unsigned short WORD; + typedef float FLOAT; + typedef int INT; + typedef unsigned int UINT; + typedef short SHORT; + typedef long LONG; + typedef long long LONGLONG; + typedef unsigned short USHORT; + typedef unsigned long ULONG; + typedef unsigned long long ULONGLONG; + + typedef UINT WPARAM; + typedef LONG LPARAM; + typedef LONG LRESULT; + #ifndef _HRESULT_DEFINED + typedef LONG HRESULT; + #define _HRESULT_DEFINED + #endif + #ifndef XFree86Server + typedef WORD ATOM; + #endif /* XFree86Server */ + typedef void *HANDLE; + typedef HANDLE HGLOBAL; + typedef HANDLE HLOCAL; + typedef HANDLE GLOBALHANDLE; + typedef HANDLE LOCALHANDLE; + typedef void *HGDIOBJ; + + #define DECLARE_HANDLE(name) typedef HANDLE name + DECLARE_HANDLE(HACCEL); + DECLARE_HANDLE(HBITMAP); + DECLARE_HANDLE(HBRUSH); + DECLARE_HANDLE(HCOLORSPACE); + DECLARE_HANDLE(HDC); + DECLARE_HANDLE(HGLRC); + DECLARE_HANDLE(HDESK); + DECLARE_HANDLE(HENHMETAFILE); + DECLARE_HANDLE(HFONT); + DECLARE_HANDLE(HICON); + DECLARE_HANDLE(HKEY); + typedef HKEY *PHKEY; + DECLARE_HANDLE(HMENU); + DECLARE_HANDLE(HMETAFILE); + DECLARE_HANDLE(HINSTANCE); + typedef HINSTANCE HMODULE; + DECLARE_HANDLE(HPALETTE); + DECLARE_HANDLE(HPEN); + DECLARE_HANDLE(HRGN); + DECLARE_HANDLE(HRSRC); + DECLARE_HANDLE(HSTR); + DECLARE_HANDLE(HTASK); + DECLARE_HANDLE(HWND); + DECLARE_HANDLE(HWINSTA); + DECLARE_HANDLE(HKL); + DECLARE_HANDLE(HRAWINPUT); + DECLARE_HANDLE(HMONITOR); + #undef DECLARE_HANDLE + + typedef int HFILE; + typedef HICON HCURSOR; + typedef DWORD COLORREF; + typedef int (WINAPI *FARPROC)(); + typedef int (WINAPI *NEARPROC)(); + typedef int (WINAPI *PROC)(); + typedef LRESULT (CALLBACK *WNDPROC)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + #if defined(_WIN64) + typedef unsigned __int64 ULONG_PTR; + typedef signed __int64 LONG_PTR; + #else + typedef unsigned long ULONG_PTR; + typedef signed long LONG_PTR; + #endif + typedef ULONG_PTR DWORD_PTR; + + typedef struct tagRECT { + LONG left; + LONG top; + LONG right; + LONG bottom; + } RECT; + typedef struct tagRECTL { + LONG left; + LONG top; + LONG right; + LONG bottom; + } RECTL; + typedef struct tagPOINT { + LONG x; + LONG y; + } POINT; + typedef struct tagSIZE { + LONG cx; + LONG cy; + } SIZE; + typedef struct tagPOINTS { + SHORT x; + SHORT y; + } POINTS; + typedef struct _SECURITY_ATTRIBUTES { + DWORD nLength; + HANDLE lpSecurityDescriptor; + BOOL bInheritHandle; + } SECURITY_ATTRIBUTES; + typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP { + RelationProcessorCore, + RelationNumaNode, + RelationCache, + RelationProcessorPackage, + RelationGroup, + RelationAll = 0xffff + } LOGICAL_PROCESSOR_RELATIONSHIP; + typedef enum _PROCESSOR_CACHE_TYPE { + CacheUnified, + CacheInstruction, + CacheData, + CacheTrace + } PROCESSOR_CACHE_TYPE; + typedef struct _CACHE_DESCRIPTOR { + BYTE Level; + BYTE Associativity; + WORD LineSize; + DWORD Size; + PROCESSOR_CACHE_TYPE Type; + } CACHE_DESCRIPTOR; + typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION { + ULONG_PTR ProcessorMask; + LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + union { + struct { + BYTE Flags; + } ProcessorCore; + struct { + DWORD NodeNumber; + } NumaNode; + CACHE_DESCRIPTOR Cache; + ULONGLONG Reserved[2]; + }; + } SYSTEM_LOGICAL_PROCESSOR_INFORMATION; + typedef struct _MEMORY_BASIC_INFORMATION { + void *BaseAddress; + void *AllocationBase; + DWORD AllocationProtect; + usize RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; + } MEMORY_BASIC_INFORMATION; + typedef struct _SYSTEM_INFO { + union { + DWORD dwOemId; + struct { + WORD wProcessorArchitecture; + WORD wReserved; + }; + }; + DWORD dwPageSize; + void * lpMinimumApplicationAddress; + void * lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; + } SYSTEM_INFO; + typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + }; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; + } LARGE_INTEGER; + typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + }; + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; + } ULARGE_INTEGER; + + typedef struct _OVERLAPPED { + ULONG_PTR Internal; + ULONG_PTR InternalHigh; + union { + struct { + DWORD Offset; + DWORD OffsetHigh; + }; + void *Pointer; + }; + HANDLE hEvent; + } OVERLAPPED; + typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; + } FILETIME; + typedef struct _WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + wchar_t cFileName[MAX_PATH]; + wchar_t cAlternateFileName[14]; + } WIN32_FIND_DATAW; + typedef struct _WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + } WIN32_FILE_ATTRIBUTE_DATA; + typedef enum _GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel + } GET_FILEEX_INFO_LEVELS; + typedef struct tagRAWINPUTHEADER { + DWORD dwType; + DWORD dwSize; + HANDLE hDevice; + WPARAM wParam; + } RAWINPUTHEADER; + typedef struct tagRAWINPUTDEVICE { + USHORT usUsagePage; + USHORT usUsage; + DWORD dwFlags; + HWND hwndTarget; + } RAWINPUTDEVICE; + typedef struct tagRAWMOUSE { + WORD usFlags; + union { + ULONG ulButtons; + struct { + WORD usButtonFlags; + WORD usButtonData; + }; + }; + ULONG ulRawButtons; + LONG lLastX; + LONG lLastY; + ULONG ulExtraInformation; + } RAWMOUSE; + typedef struct tagRAWKEYBOARD { + WORD MakeCode; + WORD Flags; + WORD Reserved; + WORD VKey; + UINT Message; + ULONG ExtraInformation; + } RAWKEYBOARD; + typedef struct tagRAWHID { + DWORD dwSizeHid; + DWORD dwCount; + BYTE bRawData[1]; + } RAWHID; + typedef struct tagRAWINPUT { + RAWINPUTHEADER header; + union { + RAWMOUSE mouse; + RAWKEYBOARD keyboard; + RAWHID hid; + } data; + } RAWINPUT; + typedef struct tagWNDCLASSEXW { + UINT cbSize; + UINT style; + WNDPROC lpfnWndProc; + INT cbClsExtra; + INT cbWndExtra; + HINSTANCE hInstance; + HICON hIcon; + HCURSOR hCursor; + HANDLE hbrBackground; + wchar_t const *lpszMenuName; + wchar_t const *lpszClassName; + HICON hIconSm; + } WNDCLASSEXW; + typedef struct _POINTL { + LONG x; + LONG y; + } POINTL; + typedef struct _devicemodew { + wchar_t dmDeviceName[CCHDEVICENAME]; + WORD dmSpecVersion; + WORD dmDriverVersion; + WORD dmSize; + WORD dmDriverExtra; + DWORD dmFields; + union { + struct { + short dmOrientation; + short dmPaperSize; + short dmPaperLength; + short dmPaperWidth; + short dmScale; + short dmCopies; + short dmDefaultSource; + short dmPrintQuality; + }; + struct { + POINTL dmPosition; + DWORD dmDisplayOrientation; + DWORD dmDisplayFixedOutput; + }; + }; + short dmColor; + short dmDuplex; + short dmYResolution; + short dmTTOption; + short dmCollate; + wchar_t dmFormName[CCHFORMNAME]; + WORD dmLogPixels; + DWORD dmBitsPerPel; + DWORD dmPelsWidth; + DWORD dmPelsHeight; + union { + DWORD dmDisplayFlags; + DWORD dmNup; + }; + DWORD dmDisplayFrequency; + #if (WINVER >= 0x0400) + DWORD dmICMMethod; + DWORD dmICMIntent; + DWORD dmMediaType; + DWORD dmDitherType; + DWORD dmReserved1; + DWORD dmReserved2; + #if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400) + DWORD dmPanningWidth; + DWORD dmPanningHeight; + #endif + #endif + } DEVMODEW; + typedef struct tagPIXELFORMATDESCRIPTOR { + WORD nSize; + WORD nVersion; + DWORD dwFlags; + BYTE iPixelType; + BYTE cColorBits; + BYTE cRedBits; + BYTE cRedShift; + BYTE cGreenBits; + BYTE cGreenShift; + BYTE cBlueBits; + BYTE cBlueShift; + BYTE cAlphaBits; + BYTE cAlphaShift; + BYTE cAccumBits; + BYTE cAccumRedBits; + BYTE cAccumGreenBits; + BYTE cAccumBlueBits; + BYTE cAccumAlphaBits; + BYTE cDepthBits; + BYTE cStencilBits; + BYTE cAuxBuffers; + BYTE iLayerType; + BYTE bReserved; + DWORD dwLayerMask; + DWORD dwVisibleMask; + DWORD dwDamageMask; + } PIXELFORMATDESCRIPTOR; + typedef struct tagMSG { // msg + HWND hwnd; + UINT message; + WPARAM wParam; + LPARAM lParam; + DWORD time; + POINT pt; + } MSG; + typedef struct tagWINDOWPLACEMENT { + UINT length; + UINT flags; + UINT showCmd; + POINT ptMinPosition; + POINT ptMaxPosition; + RECT rcNormalPosition; + } WINDOWPLACEMENT; + typedef struct tagMONITORINFO { + DWORD cbSize; + RECT rcMonitor; + RECT rcWork; + DWORD dwFlags; + } MONITORINFO; + + #define INFINITE 0xffffffffl + #define INVALID_HANDLE_VALUE ((void *)(intptr)(-1)) + + + typedef DWORD WINAPI THREAD_START_ROUTINE(void *parameter); + + GB_DLL_IMPORT DWORD WINAPI GetLastError (void); + GB_DLL_IMPORT BOOL WINAPI CloseHandle (HANDLE object); + GB_DLL_IMPORT HANDLE WINAPI CreateSemaphoreA (SECURITY_ATTRIBUTES *semaphore_attributes, LONG initial_count, + LONG maximum_count, char const *name); + GB_DLL_IMPORT BOOL WINAPI ReleaseSemaphore (HANDLE semaphore, LONG release_count, LONG *previous_count); + GB_DLL_IMPORT DWORD WINAPI WaitForSingleObject(HANDLE handle, DWORD milliseconds); + GB_DLL_IMPORT HANDLE WINAPI CreateThread (SECURITY_ATTRIBUTES *semaphore_attributes, usize stack_size, + THREAD_START_ROUTINE *start_address, void *parameter, + DWORD creation_flags, DWORD *thread_id); + GB_DLL_IMPORT DWORD WINAPI GetThreadId (HANDLE handle); + GB_DLL_IMPORT void WINAPI RaiseException (DWORD, DWORD, DWORD, ULONG_PTR const *); + + + GB_DLL_IMPORT BOOL WINAPI GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer, DWORD *return_length); + GB_DLL_IMPORT DWORD_PTR WINAPI SetThreadAffinityMask(HANDLE thread, DWORD_PTR check_mask); + GB_DLL_IMPORT HANDLE WINAPI GetCurrentThread(void); + + #define PAGE_NOACCESS 0x01 + #define PAGE_READONLY 0x02 + #define PAGE_READWRITE 0x04 + #define PAGE_WRITECOPY 0x08 + #define PAGE_EXECUTE 0x10 + #define PAGE_EXECUTE_READ 0x20 + #define PAGE_EXECUTE_READWRITE 0x40 + #define PAGE_EXECUTE_WRITECOPY 0x80 + #define PAGE_GUARD 0x100 + #define PAGE_NOCACHE 0x200 + #define PAGE_WRITECOMBINE 0x400 + + #define MEM_COMMIT 0x1000 + #define MEM_RESERVE 0x2000 + #define MEM_DECOMMIT 0x4000 + #define MEM_RELEASE 0x8000 + #define MEM_FREE 0x10000 + #define MEM_PRIVATE 0x20000 + #define MEM_MAPPED 0x40000 + #define MEM_RESET 0x80000 + #define MEM_TOP_DOWN 0x100000 + #define MEM_LARGE_PAGES 0x20000000 + #define MEM_4MB_PAGES 0x80000000 + + + + + GB_DLL_IMPORT void * WINAPI VirtualAlloc (void *addr, usize size, DWORD allocation_type, DWORD protect); + GB_DLL_IMPORT usize WINAPI VirtualQuery (void const *address, MEMORY_BASIC_INFORMATION *buffer, usize length); + GB_DLL_IMPORT BOOL WINAPI VirtualFree (void *address, usize size, DWORD free_type); + GB_DLL_IMPORT void WINAPI GetSystemInfo(SYSTEM_INFO *system_info); + + + #ifndef VK_UNKNOWN + #define VK_UNKNOWN 0 + #define VK_LBUTTON 0x01 + #define VK_RBUTTON 0x02 + #define VK_CANCEL 0x03 + #define VK_MBUTTON 0x04 + #define VK_XBUTTON1 0x05 + #define VK_XBUTTON2 0x06 + #define VK_BACK 0x08 + #define VK_TAB 0x09 + #define VK_CLEAR 0x0C + #define VK_RETURN 0x0D + #define VK_SHIFT 0x10 + #define VK_CONTROL 0x11 // CTRL key + #define VK_MENU 0x12 // ALT key + #define VK_PAUSE 0x13 // PAUSE key + #define VK_CAPITAL 0x14 // CAPS LOCK key + #define VK_KANA 0x15 // Input Method Editor (IME) Kana mode + #define VK_HANGUL 0x15 // IME Hangul mode + #define VK_JUNJA 0x17 // IME Junja mode + #define VK_FINAL 0x18 // IME final mode + #define VK_HANJA 0x19 // IME Hanja mode + #define VK_KANJI 0x19 // IME Kanji mode + #define VK_ESCAPE 0x1B // ESC key + #define VK_CONVERT 0x1C // IME convert + #define VK_NONCONVERT 0x1D // IME nonconvert + #define VK_ACCEPT 0x1E // IME accept + #define VK_MODECHANGE 0x1F // IME mode change request + #define VK_SPACE 0x20 // SPACE key + #define VK_PRIOR 0x21 // PAGE UP key + #define VK_NEXT 0x22 // PAGE DOWN key + #define VK_END 0x23 // END key + #define VK_HOME 0x24 // HOME key + #define VK_LEFT 0x25 // LEFT ARROW key + #define VK_UP 0x26 // UP ARROW key + #define VK_RIGHT 0x27 // RIGHT ARROW key + #define VK_DOWN 0x28 // DOWN ARROW key + #define VK_SELECT 0x29 // SELECT key + #define VK_PRINT 0x2A // PRINT key + #define VK_EXECUTE 0x2B // EXECUTE key + #define VK_SNAPSHOT 0x2C // PRINT SCREEN key + #define VK_INSERT 0x2D // INS key + #define VK_DELETE 0x2E // DEL key + #define VK_HELP 0x2F // HELP key + #define VK_0 0x30 + #define VK_1 0x31 + #define VK_2 0x32 + #define VK_3 0x33 + #define VK_4 0x34 + #define VK_5 0x35 + #define VK_6 0x36 + #define VK_7 0x37 + #define VK_8 0x38 + #define VK_9 0x39 + #define VK_A 0x41 + #define VK_B 0x42 + #define VK_C 0x43 + #define VK_D 0x44 + #define VK_E 0x45 + #define VK_F 0x46 + #define VK_G 0x47 + #define VK_H 0x48 + #define VK_I 0x49 + #define VK_J 0x4A + #define VK_K 0x4B + #define VK_L 0x4C + #define VK_M 0x4D + #define VK_N 0x4E + #define VK_O 0x4F + #define VK_P 0x50 + #define VK_Q 0x51 + #define VK_R 0x52 + #define VK_S 0x53 + #define VK_T 0x54 + #define VK_U 0x55 + #define VK_V 0x56 + #define VK_W 0x57 + #define VK_X 0x58 + #define VK_Y 0x59 + #define VK_Z 0x5A + #define VK_LWIN 0x5B // Left Windows key (Microsoft Natural keyboard) + #define VK_RWIN 0x5C // Right Windows key (Natural keyboard) + #define VK_APPS 0x5D // Applications key (Natural keyboard) + #define VK_SLEEP 0x5F // Computer Sleep key + // Num pad keys + #define VK_NUMPAD0 0x60 + #define VK_NUMPAD1 0x61 + #define VK_NUMPAD2 0x62 + #define VK_NUMPAD3 0x63 + #define VK_NUMPAD4 0x64 + #define VK_NUMPAD5 0x65 + #define VK_NUMPAD6 0x66 + #define VK_NUMPAD7 0x67 + #define VK_NUMPAD8 0x68 + #define VK_NUMPAD9 0x69 + #define VK_MULTIPLY 0x6A + #define VK_ADD 0x6B + #define VK_SEPARATOR 0x6C + #define VK_SUBTRACT 0x6D + #define VK_DECIMAL 0x6E + #define VK_DIVIDE 0x6F + #define VK_F1 0x70 + #define VK_F2 0x71 + #define VK_F3 0x72 + #define VK_F4 0x73 + #define VK_F5 0x74 + #define VK_F6 0x75 + #define VK_F7 0x76 + #define VK_F8 0x77 + #define VK_F9 0x78 + #define VK_F10 0x79 + #define VK_F11 0x7A + #define VK_F12 0x7B + #define VK_F13 0x7C + #define VK_F14 0x7D + #define VK_F15 0x7E + #define VK_F16 0x7F + #define VK_F17 0x80 + #define VK_F18 0x81 + #define VK_F19 0x82 + #define VK_F20 0x83 + #define VK_F21 0x84 + #define VK_F22 0x85 + #define VK_F23 0x86 + #define VK_F24 0x87 + #define VK_NUMLOCK 0x90 + #define VK_SCROLL 0x91 + #define VK_LSHIFT 0xA0 + #define VK_RSHIFT 0xA1 + #define VK_LCONTROL 0xA2 + #define VK_RCONTROL 0xA3 + #define VK_LMENU 0xA4 + #define VK_RMENU 0xA5 + #define VK_BROWSER_BACK 0xA6 // Windows 2000/XP: Browser Back key + #define VK_BROWSER_FORWARD 0xA7 // Windows 2000/XP: Browser Forward key + #define VK_BROWSER_REFRESH 0xA8 // Windows 2000/XP: Browser Refresh key + #define VK_BROWSER_STOP 0xA9 // Windows 2000/XP: Browser Stop key + #define VK_BROWSER_SEARCH 0xAA // Windows 2000/XP: Browser Search key + #define VK_BROWSER_FAVORITES 0xAB // Windows 2000/XP: Browser Favorites key + #define VK_BROWSER_HOME 0xAC // Windows 2000/XP: Browser Start and Home key + #define VK_VOLUME_MUTE 0xAD // Windows 2000/XP: Volume Mute key + #define VK_VOLUME_DOWN 0xAE // Windows 2000/XP: Volume Down key + #define VK_VOLUME_UP 0xAF // Windows 2000/XP: Volume Up key + #define VK_MEDIA_NEXT_TRACK 0xB0 // Windows 2000/XP: Next Track key + #define VK_MEDIA_PREV_TRACK 0xB1 // Windows 2000/XP: Previous Track key + #define VK_MEDIA_STOP 0xB2 // Windows 2000/XP: Stop Media key + #define VK_MEDIA_PLAY_PAUSE 0xB3 // Windows 2000/XP: Play/Pause Media key + #define VK_MEDIA_LAUNCH_MAIL 0xB4 // Windows 2000/XP: Start Mail key + #define VK_MEDIA_LAUNCH_MEDIA_SELECT 0xB5 // Windows 2000/XP: Select Media key + #define VK_MEDIA_LAUNCH_APP1 0xB6 // VK_LAUNCH_APP1 (B6) Windows 2000/XP: Start Application 1 key + #define VK_MEDIA_LAUNCH_APP2 0xB7 // VK_LAUNCH_APP2 (B7) Windows 2000/XP: Start Application 2 key + #define VK_OEM_1 0xBA + #define VK_OEM_PLUS 0xBB + #define VK_OEM_COMMA 0xBC + #define VK_OEM_MINUS 0xBD + #define VK_OEM_PERIOD 0xBE + #define VK_OEM_2 0xBF + #define VK_OEM_3 0xC0 + #define VK_OEM_4 0xDB + #define VK_OEM_5 0xDC + #define VK_OEM_6 0xDD + #define VK_OEM_7 0xDE + #define VK_OEM_8 0xDF + #define VK_OEM_102 0xE2 + #define VK_PROCESSKEY 0xE5 + #define VK_PACKET 0xE7 + #define VK_ATTN 0xF6 // Attn key + #define VK_CRSEL 0xF7 // CrSel key + #define VK_EXSEL 0xF8 // ExSel key + #define VK_EREOF 0xF9 // Erase EOF key + #define VK_PLAY 0xFA // Play key + #define VK_ZOOM 0xFB // Zoom key + #define VK_NONAME 0xFC // Reserved for future use + #define VK_PA1 0xFD // VK_PA1 (FD) PA1 key + #define VK_OEM_CLEAR 0xFE // Clear key + #endif // VK_UNKNOWN + + + + #define GENERIC_READ 0x80000000 + #define GENERIC_WRITE 0x40000000 + #define GENERIC_EXECUTE 0x20000000 + #define GENERIC_ALL 0x10000000 + #define FILE_SHARE_READ 0x00000001 + #define FILE_SHARE_WRITE 0x00000002 + #define FILE_SHARE_DELETE 0x00000004 + #define CREATE_NEW 1 + #define CREATE_ALWAYS 2 + #define OPEN_EXISTING 3 + #define OPEN_ALWAYS 4 + #define TRUNCATE_EXISTING 5 + #define FILE_ATTRIBUTE_READONLY 0x00000001 + #define FILE_ATTRIBUTE_NORMAL 0x00000080 + #define FILE_ATTRIBUTE_TEMPORARY 0x00000100 + #define ERROR_FILE_NOT_FOUND 2l + #define ERROR_ACCESS_DENIED 5L + #define ERROR_NO_MORE_FILES 18l + #define ERROR_FILE_EXISTS 80l + #define ERROR_ALREADY_EXISTS 183l + #define STD_INPUT_HANDLE ((DWORD)-10) + #define STD_OUTPUT_HANDLE ((DWORD)-11) + #define STD_ERROR_HANDLE ((DWORD)-12) + + GB_DLL_IMPORT int MultiByteToWideChar(UINT code_page, DWORD flags, char const * multi_byte_str, int multi_byte_len, wchar_t const *wide_char_str, int wide_char_len); + GB_DLL_IMPORT int WideCharToMultiByte(UINT code_page, DWORD flags, wchar_t const *wide_char_str, int wide_char_len, char const * multi_byte_str, int multi_byte_len); + GB_DLL_IMPORT BOOL WINAPI SetFilePointerEx(HANDLE file, LARGE_INTEGER distance_to_move, + LARGE_INTEGER *new_file_pointer, DWORD move_method); + GB_DLL_IMPORT BOOL WINAPI ReadFile (HANDLE file, void *buffer, DWORD bytes_to_read, DWORD *bytes_read, OVERLAPPED *overlapped); + GB_DLL_IMPORT BOOL WINAPI WriteFile (HANDLE file, void const *buffer, DWORD bytes_to_write, DWORD *bytes_written, OVERLAPPED *overlapped); + GB_DLL_IMPORT HANDLE WINAPI CreateFileW (wchar_t const *path, DWORD desired_access, DWORD share_mode, + SECURITY_ATTRIBUTES *, DWORD creation_disposition, + DWORD flags_and_attributes, HANDLE template_file); + GB_DLL_IMPORT HANDLE WINAPI GetStdHandle (DWORD std_handle); + GB_DLL_IMPORT BOOL WINAPI GetFileSizeEx (HANDLE file, LARGE_INTEGER *size); + GB_DLL_IMPORT BOOL WINAPI SetEndOfFile (HANDLE file); + GB_DLL_IMPORT HANDLE WINAPI FindFirstFileW (wchar_t const *path, WIN32_FIND_DATAW *data); + GB_DLL_IMPORT BOOL WINAPI FindClose (HANDLE find_file); + GB_DLL_IMPORT BOOL WINAPI GetFileAttributesExW(wchar_t const *path, GET_FILEEX_INFO_LEVELS info_level_id, WIN32_FILE_ATTRIBUTE_DATA *data); + GB_DLL_IMPORT BOOL WINAPI CopyFileW(wchar_t const *old_f, wchar_t const *new_f, BOOL fail_if_exists); + GB_DLL_IMPORT BOOL WINAPI MoveFileW(wchar_t const *old_f, wchar_t const *new_f); + + GB_DLL_IMPORT HMODULE WINAPI LoadLibraryA (char const *filename); + GB_DLL_IMPORT BOOL WINAPI FreeLibrary (HMODULE module); + GB_DLL_IMPORT FARPROC WINAPI GetProcAddress(HMODULE module, char const *name); + + GB_DLL_IMPORT BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *frequency); + GB_DLL_IMPORT BOOL WINAPI QueryPerformanceCounter (LARGE_INTEGER *counter); + GB_DLL_IMPORT void WINAPI GetSystemTimeAsFileTime (FILETIME *system_time_as_file_time); + GB_DLL_IMPORT void WINAPI Sleep(DWORD milliseconds); + GB_DLL_IMPORT void WINAPI ExitProcess(UINT exit_code); + + GB_DLL_IMPORT BOOL WINAPI SetEnvironmentVariableA(char const *name, char const *value); + + + #define WM_NULL 0x0000 + #define WM_CREATE 0x0001 + #define WM_DESTROY 0x0002 + #define WM_MOVE 0x0003 + #define WM_SIZE 0x0005 + #define WM_ACTIVATE 0x0006 + #define WM_SETFOCUS 0x0007 + #define WM_KILLFOCUS 0x0008 + #define WM_ENABLE 0x000A + #define WM_SETREDRAW 0x000B + #define WM_SETTEXT 0x000C + #define WM_GETTEXT 0x000D + #define WM_GETTEXTLENGTH 0x000E + #define WM_PAINT 0x000F + #define WM_CLOSE 0x0010 + #define WM_QUERYENDSESSION 0x0011 + #define WM_QUERYOPEN 0x0013 + #define WM_ENDSESSION 0x0016 + #define WM_QUIT 0x0012 + #define WM_ERASEBKGND 0x0014 + #define WM_SYSCOLORCHANGE 0x0015 + #define WM_SHOWWINDOW 0x0018 + #define WM_WININICHANGE 0x001A + #define WM_SETTINGCHANGE WM_WININICHANGE + #define WM_DEVMODECHANGE 0x001B + #define WM_ACTIVATEAPP 0x001C + #define WM_FONTCHANGE 0x001D + #define WM_TIMECHANGE 0x001E + #define WM_CANCELMODE 0x001F + #define WM_SETCURSOR 0x0020 + #define WM_MOUSEACTIVATE 0x0021 + #define WM_CHILDACTIVATE 0x0022 + #define WM_QUEUESYNC 0x0023 + #define WM_GETMINMAXINFO 0x0024 + #define WM_PAINTICON 0x0026 + #define WM_ICONERASEBKGND 0x0027 + #define WM_NEXTDLGCTL 0x0028 + #define WM_SPOOLERSTATUS 0x002A + #define WM_DRAWITEM 0x002B + #define WM_MEASUREITEM 0x002C + #define WM_DELETEITEM 0x002D + #define WM_VKEYTOITEM 0x002E + #define WM_CHARTOITEM 0x002F + #define WM_SETFONT 0x0030 + #define WM_GETFONT 0x0031 + #define WM_SETHOTKEY 0x0032 + #define WM_GETHOTKEY 0x0033 + #define WM_QUERYDRAGICON 0x0037 + #define WM_COMPAREITEM 0x0039 + #define WM_GETOBJECT 0x003D + #define WM_COMPACTING 0x0041 + #define WM_COMMNOTIFY 0x0044 /* no longer suported */ + #define WM_WINDOWPOSCHANGING 0x0046 + #define WM_WINDOWPOSCHANGED 0x0047 + #define WM_POWER 0x0048 + #define WM_COPYDATA 0x004A + #define WM_CANCELJOURNAL 0x004B + #define WM_NOTIFY 0x004E + #define WM_INPUTLANGCHANGEREQUEST 0x0050 + #define WM_INPUTLANGCHANGE 0x0051 + #define WM_TCARD 0x0052 + #define WM_HELP 0x0053 + #define WM_USERCHANGED 0x0054 + #define WM_NOTIFYFORMAT 0x0055 + #define WM_CONTEXTMENU 0x007B + #define WM_STYLECHANGING 0x007C + #define WM_STYLECHANGED 0x007D + #define WM_DISPLAYCHANGE 0x007E + #define WM_GETICON 0x007F + #define WM_SETICON 0x0080 + #define WM_INPUT 0x00FF + #define WM_KEYFIRST 0x0100 + #define WM_KEYDOWN 0x0100 + #define WM_KEYUP 0x0101 + #define WM_CHAR 0x0102 + #define WM_DEADCHAR 0x0103 + #define WM_SYSKEYDOWN 0x0104 + #define WM_SYSKEYUP 0x0105 + #define WM_SYSCHAR 0x0106 + #define WM_SYSDEADCHAR 0x0107 + #define WM_UNICHAR 0x0109 + #define WM_KEYLAST 0x0109 + #define WM_APP 0x8000 + + + #define RID_INPUT 0x10000003 + + #define RIM_TYPEMOUSE 0x00000000 + #define RIM_TYPEKEYBOARD 0x00000001 + #define RIM_TYPEHID 0x00000002 + + #define RI_KEY_MAKE 0x0000 + #define RI_KEY_BREAK 0x0001 + #define RI_KEY_E0 0x0002 + #define RI_KEY_E1 0x0004 + #define RI_MOUSE_WHEEL 0x0400 + + #define RIDEV_NOLEGACY 0x00000030 + + #define MAPVK_VK_TO_VSC 0 + #define MAPVK_VSC_TO_VK 1 + #define MAPVK_VK_TO_CHAR 2 + #define MAPVK_VSC_TO_VK_EX 3 + + GB_DLL_IMPORT BOOL WINAPI RegisterRawInputDevices(RAWINPUTDEVICE const *raw_input_devices, UINT num_devices, UINT size); + GB_DLL_IMPORT UINT WINAPI GetRawInputData(HRAWINPUT raw_input, UINT ui_command, void *data, UINT *size, UINT size_header); + GB_DLL_IMPORT UINT WINAPI MapVirtualKeyW(UINT code, UINT map_type); + + + #define CS_DBLCLKS 0x0008 + #define CS_VREDRAW 0x0001 + #define CS_HREDRAW 0x0002 + + #define MB_OK 0x0000l + #define MB_ICONSTOP 0x0010l + #define MB_YESNO 0x0004l + #define MB_HELP 0x4000l + #define MB_ICONEXCLAMATION 0x0030l + + GB_DLL_IMPORT LRESULT WINAPI DefWindowProcW(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam); + GB_DLL_IMPORT HGDIOBJ WINAPI GetStockObject(int object); + GB_DLL_IMPORT HMODULE WINAPI GetModuleHandleW(wchar_t const *); + GB_DLL_IMPORT ATOM WINAPI RegisterClassExW(WNDCLASSEXW const *wcx); // u16 == ATOM + GB_DLL_IMPORT int WINAPI MessageBoxW(void *wnd, wchar_t const *text, wchar_t const *caption, unsigned int type); + + + #define DM_BITSPERPEL 0x00040000l + #define DM_PELSWIDTH 0x00080000l + #define DM_PELSHEIGHT 0x00100000l + + #define CDS_FULLSCREEN 0x4 + #define DISP_CHANGE_SUCCESSFUL 0 + #define IDYES 6 + + #define WS_VISIBLE 0x10000000 + #define WS_THICKFRAME 0x00040000 + #define WS_MAXIMIZE 0x01000000 + #define WS_MAXIMIZEBOX 0x00010000 + #define WS_MINIMIZE 0x20000000 + #define WS_MINIMIZEBOX 0x00020000 + #define WS_POPUP 0x80000000 + #define WS_OVERLAPPED 0 + #define WS_OVERLAPPEDWINDOW 0xcf0000 + #define CW_USEDEFAULT 0x80000000 + #define WS_BORDER 0x800000 + #define WS_CAPTION 0xc00000 + #define WS_SYSMENU 0x80000 + + #define HWND_NOTOPMOST (HWND)(-2) + #define HWND_TOPMOST (HWND)(-1) + #define HWND_TOP (HWND)(+0) + #define HWND_BOTTOM (HWND)(+1) + #define SWP_NOSIZE 0x0001 + #define SWP_NOMOVE 0x0002 + #define SWP_NOZORDER 0x0004 + #define SWP_NOREDRAW 0x0008 + #define SWP_NOACTIVATE 0x0010 + #define SWP_FRAMECHANGED 0x0020 + #define SWP_SHOWWINDOW 0x0040 + #define SWP_HIDEWINDOW 0x0080 + #define SWP_NOCOPYBITS 0x0100 + #define SWP_NOOWNERZORDER 0x0200 + #define SWP_NOSENDCHANGING 0x0400 + + #define SW_HIDE 0 + #define SW_SHOWNORMAL 1 + #define SW_NORMAL 1 + #define SW_SHOWMINIMIZED 2 + #define SW_SHOWMAXIMIZED 3 + #define SW_MAXIMIZE 3 + #define SW_SHOWNOACTIVATE 4 + #define SW_SHOW 5 + #define SW_MINIMIZE 6 + #define SW_SHOWMINNOACTIVE 7 + #define SW_SHOWNA 8 + #define SW_RESTORE 9 + #define SW_SHOWDEFAULT 10 + #define SW_FORCEMINIMIZE 11 + #define SW_MAX 11 + + #define ENUM_CURRENT_SETTINGS cast(DWORD)-1 + #define ENUM_REGISTRY_SETTINGS cast(DWORD)-2 + + GB_DLL_IMPORT LONG WINAPI ChangeDisplaySettingsW(DEVMODEW *dev_mode, DWORD flags); + GB_DLL_IMPORT BOOL WINAPI AdjustWindowRect(RECT *rect, DWORD style, BOOL enu); + GB_DLL_IMPORT HWND WINAPI CreateWindowExW(DWORD ex_style, wchar_t const *class_name, wchar_t const *window_name, + DWORD style, int x, int y, int width, int height, HWND wnd_parent, + HMENU menu, HINSTANCE instance, void *param); + GB_DLL_IMPORT HMODULE WINAPI GetModuleHandleW(wchar_t const *); + GB_DLL_IMPORT HDC GetDC(HANDLE); + GB_DLL_IMPORT BOOL WINAPI GetWindowPlacement(HWND hWnd, WINDOWPLACEMENT *lpwndpl); + GB_DLL_IMPORT BOOL GetMonitorInfoW(HMONITOR hMonitor, MONITORINFO *lpmi); + GB_DLL_IMPORT HMONITOR MonitorFromWindow(HWND hwnd, DWORD dwFlags); + GB_DLL_IMPORT LONG WINAPI SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong); + GB_DLL_IMPORT BOOL WINAPI SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); + GB_DLL_IMPORT BOOL WINAPI SetWindowPlacement(HWND hWnd, WINDOWPLACEMENT const *lpwndpl); + GB_DLL_IMPORT BOOL WINAPI ShowWindow(HWND hWnd, int nCmdShow); + GB_DLL_IMPORT LONG_PTR WINAPI GetWindowLongPtrW(HWND wnd, int index); + + GB_DLL_IMPORT BOOL EnumDisplaySettingsW(wchar_t const *lpszDeviceName, DWORD iModeNum, DEVMODEW *lpDevMode); + GB_DLL_IMPORT void * WINAPI GlobalLock(HGLOBAL hMem); + GB_DLL_IMPORT BOOL WINAPI GlobalUnlock(HGLOBAL hMem); + GB_DLL_IMPORT HGLOBAL WINAPI GlobalAlloc(UINT uFlags, usize dwBytes); + GB_DLL_IMPORT HANDLE WINAPI GetClipboardData(UINT uFormat); + GB_DLL_IMPORT BOOL WINAPI IsClipboardFormatAvailable(UINT format); + GB_DLL_IMPORT BOOL WINAPI OpenClipboard(HWND hWndNewOwner); + GB_DLL_IMPORT BOOL WINAPI EmptyClipboard(void); + GB_DLL_IMPORT BOOL WINAPI CloseClipboard(void); + GB_DLL_IMPORT HANDLE WINAPI SetClipboardData(UINT uFormat, HANDLE hMem); + + #define PFD_TYPE_RGBA 0 + #define PFD_TYPE_COLORINDEX 1 + #define PFD_MAIN_PLANE 0 + #define PFD_OVERLAY_PLANE 1 + #define PFD_UNDERLAY_PLANE (-1) + #define PFD_DOUBLEBUFFER 1 + #define PFD_STEREO 2 + #define PFD_DRAW_TO_WINDOW 4 + #define PFD_DRAW_TO_BITMAP 8 + #define PFD_SUPPORT_GDI 16 + #define PFD_SUPPORT_OPENGL 32 + #define PFD_GENERIC_FORMAT 64 + #define PFD_NEED_PALETTE 128 + #define PFD_NEED_SYSTEM_PALETTE 0x00000100 + #define PFD_SWAP_EXCHANGE 0x00000200 + #define PFD_SWAP_COPY 0x00000400 + #define PFD_SWAP_LAYER_BUFFERS 0x00000800 + #define PFD_GENERIC_ACCELERATED 0x00001000 + #define PFD_DEPTH_DONTCARE 0x20000000 + #define PFD_DOUBLEBUFFER_DONTCARE 0x40000000 + #define PFD_STEREO_DONTCARE 0x80000000 + + #define GWLP_USERDATA -21 + + #define GWL_ID -12 + #define GWL_STYLE -16 + + GB_DLL_IMPORT BOOL WINAPI SetPixelFormat (HDC hdc, int pixel_format, PIXELFORMATDESCRIPTOR const *pfd); + GB_DLL_IMPORT int WINAPI ChoosePixelFormat(HDC hdc, PIXELFORMATDESCRIPTOR const *pfd); + GB_DLL_IMPORT HGLRC WINAPI wglCreateContext (HDC hdc); + GB_DLL_IMPORT BOOL WINAPI wglMakeCurrent (HDC hdc, HGLRC hglrc); + GB_DLL_IMPORT PROC WINAPI wglGetProcAddress(char const *str); + GB_DLL_IMPORT BOOL WINAPI wglDeleteContext (HGLRC hglrc); + + GB_DLL_IMPORT BOOL WINAPI SetForegroundWindow(HWND hWnd); + GB_DLL_IMPORT HWND WINAPI SetFocus(HWND hWnd); + GB_DLL_IMPORT LONG_PTR WINAPI SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong); + GB_DLL_IMPORT BOOL WINAPI GetClientRect(HWND hWnd, RECT *lpRect); + GB_DLL_IMPORT BOOL WINAPI IsIconic(HWND hWnd); + GB_DLL_IMPORT HWND WINAPI GetFocus(void); + GB_DLL_IMPORT int WINAPI ShowCursor(BOOL bShow); + GB_DLL_IMPORT SHORT WINAPI GetAsyncKeyState(int key); + GB_DLL_IMPORT BOOL WINAPI GetCursorPos(POINT *lpPoint); + GB_DLL_IMPORT BOOL WINAPI SetCursorPos(int x, int y); + GB_DLL_IMPORT BOOL ScreenToClient(HWND hWnd, POINT *lpPoint); + GB_DLL_IMPORT BOOL ClientToScreen(HWND hWnd, POINT *lpPoint); + GB_DLL_IMPORT BOOL WINAPI MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint); + GB_DLL_IMPORT BOOL WINAPI SetWindowTextW(HWND hWnd, wchar_t const *lpString); + GB_DLL_IMPORT DWORD WINAPI GetWindowLongW(HWND hWnd, int nIndex); + + + + + #define PM_NOREMOVE 0 + #define PM_REMOVE 1 + + GB_DLL_IMPORT BOOL WINAPI PeekMessageW(MSG *lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); + GB_DLL_IMPORT BOOL WINAPI TranslateMessage(MSG const *lpMsg); + GB_DLL_IMPORT LRESULT WINAPI DispatchMessageW(MSG const *lpMsg); + + typedef enum + { + DIB_RGB_COLORS = 0x00, + DIB_PAL_COLORS = 0x01, + DIB_PAL_INDICES = 0x02 + } DIBColors; + + #define SRCCOPY (u32)0x00CC0020 + #define SRCPAINT (u32)0x00EE0086 + #define SRCAND (u32)0x008800C6 + #define SRCINVERT (u32)0x00660046 + #define SRCERASE (u32)0x00440328 + #define NOTSRCCOPY (u32)0x00330008 + #define NOTSRCERASE (u32)0x001100A6 + #define MERGECOPY (u32)0x00C000CA + #define MERGEPAINT (u32)0x00BB0226 + #define PATCOPY (u32)0x00F00021 + #define PATPAINT (u32)0x00FB0A09 + #define PATINVERT (u32)0x005A0049 + #define DSTINVERT (u32)0x00550009 + #define BLACKNESS (u32)0x00000042 + #define WHITENESS (u32)0x00FF0062 + + GB_DLL_IMPORT BOOL WINAPI SwapBuffers(HDC hdc); + GB_DLL_IMPORT BOOL WINAPI DestroyWindow(HWND hWnd); + GB_DLL_IMPORT int StretchDIBits(HDC hdc, int XDest, int YDest, int nDestWidth, int nDestHeight, + int XSrc, int YSrc, int nSrcWidth, int nSrcHeight, + void const *lpBits, /*BITMAPINFO*/void const *lpBitsInfo, UINT iUsage, DWORD dwRop); + // IMPORTANT TODO(bill): FIX THIS!!!! +#endif // Bill's Mini Windows.h + + + +#if defined(__GCC__) || defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" +#pragma GCC diagnostic ignored "-Wmissing-braces" +#endif + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4201) +#pragma warning(disable:4127) // Conditional expression is constant +#endif + +void gb_assert_handler(char const *prefix, char const *condition, char const *file, i32 line, char const *msg, ...) { + gb_printf_err("%s(%d): %s: ", file, line, prefix); + if (condition) + gb_printf_err( "`%s` ", condition); + if (msg) { + va_list va; + va_start(va, msg); + gb_printf_err_va(msg, va); + va_end(va); + } + gb_printf_err("\n"); +} + +b32 gb_is_power_of_two(isize x) { + if (x <= 0) + return false; + return !(x & (x-1)); +} + +gb_inline void *gb_align_forward(void *ptr, isize alignment) { + uintptr p; + + GB_ASSERT(gb_is_power_of_two(alignment)); + + p = cast(uintptr)ptr; + return cast(void *)((p + (alignment-1)) &~ (alignment-1)); +} + + + +gb_inline void * gb_pointer_add (void *ptr, isize bytes) { return cast(void *)(cast(u8 *)ptr + bytes); } +gb_inline void * gb_pointer_sub (void *ptr, isize bytes) { return cast(void *)(cast(u8 *)ptr - bytes); } +gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr + bytes); } +gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr - bytes); } +gb_inline isize gb_pointer_diff (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); } + +gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); } + + +#if defined(_MSC_VER) +#pragma intrinsic(__movsb) +#endif + +gb_inline void *gb_memcopy(void *dest, void const *source, isize n) { +#if defined(_MSC_VER) + if (dest == NULL) { + return NULL; + } + // TODO(bill): Is this good enough? + __movsb(cast(u8 *)dest, cast(u8 *)source, n); +// #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) + // NOTE(zangent): I assume there's a reason this isn't being used elsewhere, + // but casting pointers as arguments to an __asm__ call is considered an + // error on MacOS and (I think) Linux + // TODO(zangent): Figure out how to refactor the asm code so it works on MacOS, + // since this is probably not the way the author intended this to work. + // memcpy(dest, source, n); +#elif defined(GB_CPU_X86) + if (dest == NULL) { + return NULL; + } + + void *dest_copy = dest; + __asm__ __volatile__("rep movsb" : "+D"(dest_copy), "+S"(source), "+c"(n) : : "memory"); +#else + u8 *d = cast(u8 *)dest; + u8 const *s = cast(u8 const *)source; + u32 w, x; + + if (dest == NULL) { + return NULL; + } + + for (; cast(uintptr)s % 4 && n; n--) { + *d++ = *s++; + } + + if (cast(uintptr)d % 4 == 0) { + for (; n >= 16; + s += 16, d += 16, n -= 16) { + *cast(u32 *)(d+ 0) = *cast(u32 *)(s+ 0); + *cast(u32 *)(d+ 4) = *cast(u32 *)(s+ 4); + *cast(u32 *)(d+ 8) = *cast(u32 *)(s+ 8); + *cast(u32 *)(d+12) = *cast(u32 *)(s+12); + } + if (n & 8) { + *cast(u32 *)(d+0) = *cast(u32 *)(s+0); + *cast(u32 *)(d+4) = *cast(u32 *)(s+4); + d += 8; + s += 8; + } + if (n&4) { + *cast(u32 *)(d+0) = *cast(u32 *)(s+0); + d += 4; + s += 4; + } + if (n&2) { + *d++ = *s++; *d++ = *s++; + } + if (n&1) { + *d = *s; + } + return dest; + } + + if (n >= 32) { + #if __BYTE_ORDER == __BIG_ENDIAN + #define LS << + #define RS >> + #else + #define LS >> + #define RS << + #endif + switch (cast(uintptr)d % 4) { + case 1: { + w = *cast(u32 *)s; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + n -= 3; + while (n > 16) { + x = *cast(u32 *)(s+1); + *cast(u32 *)(d+0) = (w LS 24) | (x RS 8); + w = *cast(u32 *)(s+5); + *cast(u32 *)(d+4) = (x LS 24) | (w RS 8); + x = *cast(u32 *)(s+9); + *cast(u32 *)(d+8) = (w LS 24) | (x RS 8); + w = *cast(u32 *)(s+13); + *cast(u32 *)(d+12) = (x LS 24) | (w RS 8); + + s += 16; + d += 16; + n -= 16; + } + } break; + case 2: { + w = *cast(u32 *)s; + *d++ = *s++; + *d++ = *s++; + n -= 2; + while (n > 17) { + x = *cast(u32 *)(s+2); + *cast(u32 *)(d+0) = (w LS 16) | (x RS 16); + w = *cast(u32 *)(s+6); + *cast(u32 *)(d+4) = (x LS 16) | (w RS 16); + x = *cast(u32 *)(s+10); + *cast(u32 *)(d+8) = (w LS 16) | (x RS 16); + w = *cast(u32 *)(s+14); + *cast(u32 *)(d+12) = (x LS 16) | (w RS 16); + + s += 16; + d += 16; + n -= 16; + } + } break; + case 3: { + w = *cast(u32 *)s; + *d++ = *s++; + n -= 1; + while (n > 18) { + x = *cast(u32 *)(s+3); + *cast(u32 *)(d+0) = (w LS 8) | (x RS 24); + w = *cast(u32 *)(s+7); + *cast(u32 *)(d+4) = (x LS 8) | (w RS 24); + x = *cast(u32 *)(s+11); + *cast(u32 *)(d+8) = (w LS 8) | (x RS 24); + w = *cast(u32 *)(s+15); + *cast(u32 *)(d+12) = (x LS 8) | (w RS 24); + + s += 16; + d += 16; + n -= 16; + } + } break; + default: break; // NOTE(bill): Do nowt! + } + #undef LS + #undef RS + if (n & 16) { + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + } + if (n & 8) { + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + } + if (n & 4) { + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + } + if (n & 2) { + *d++ = *s++; *d++ = *s++; + } + if (n & 1) { + *d = *s; + } + } + +#endif + return dest; +} + +gb_inline void *gb_memmove(void *dest, void const *source, isize n) { + u8 *d = cast(u8 *)dest; + u8 const *s = cast(u8 const *)source; + + if (dest == NULL) { + return NULL; + } + + if (d == s) { + return d; + } + if (s+n <= d || d+n <= s) { // NOTE(bill): Non-overlapping + return gb_memcopy(d, s, n); + } + + if (d < s) { + if (cast(uintptr)s % gb_size_of(isize) == cast(uintptr)d % gb_size_of(isize)) { + while (cast(uintptr)d % gb_size_of(isize)) { + if (!n--) return dest; + *d++ = *s++; + } + while (n>=gb_size_of(isize)) { + *cast(isize *)d = *cast(isize *)s; + n -= gb_size_of(isize); + d += gb_size_of(isize); + s += gb_size_of(isize); + } + } + for (; n; n--) *d++ = *s++; + } else { + if ((cast(uintptr)s % gb_size_of(isize)) == (cast(uintptr)d % gb_size_of(isize))) { + while (cast(uintptr)(d+n) % gb_size_of(isize)) { + if (!n--) + return dest; + d[n] = s[n]; + } + while (n >= gb_size_of(isize)) { + n -= gb_size_of(isize); + *cast(isize *)(d+n) = *cast(isize *)(s+n); + } + } + while (n) n--, d[n] = s[n]; + } + + return dest; +} + +gb_inline void *gb_memset(void *dest, u8 c, isize n) { + u8 *s = cast(u8 *)dest; + isize k; + u32 c32 = ((u32)-1)/255 * c; + + if (dest == NULL) { + return NULL; + } + + if (n == 0) + return dest; + s[0] = s[n-1] = c; + if (n < 3) + return dest; + s[1] = s[n-2] = c; + s[2] = s[n-3] = c; + if (n < 7) + return dest; + s[3] = s[n-4] = c; + if (n < 9) + return dest; + + k = -cast(intptr)s & 3; + s += k; + n -= k; + n &= -4; + + *cast(u32 *)(s+0) = c32; + *cast(u32 *)(s+n-4) = c32; + if (n < 9) { + return dest; + } + *cast(u32 *)(s + 4) = c32; + *cast(u32 *)(s + 8) = c32; + *cast(u32 *)(s+n-12) = c32; + *cast(u32 *)(s+n- 8) = c32; + if (n < 25) { + return dest; + } + *cast(u32 *)(s + 12) = c32; + *cast(u32 *)(s + 16) = c32; + *cast(u32 *)(s + 20) = c32; + *cast(u32 *)(s + 24) = c32; + *cast(u32 *)(s+n-28) = c32; + *cast(u32 *)(s+n-24) = c32; + *cast(u32 *)(s+n-20) = c32; + *cast(u32 *)(s+n-16) = c32; + + k = 24 + (cast(uintptr)s & 4); + s += k; + n -= k; + + + { + u64 c64 = (cast(u64)c32 << 32) | c32; + while (n > 31) { + *cast(u64 *)(s+0) = c64; + *cast(u64 *)(s+8) = c64; + *cast(u64 *)(s+16) = c64; + *cast(u64 *)(s+24) = c64; + + n -= 32; + s += 32; + } + } + + return dest; +} + +gb_inline i32 gb_memcompare(void const *s1, void const *s2, isize size) { + // TODO(bill): Heavily optimize + u8 const *s1p8 = cast(u8 const *)s1; + u8 const *s2p8 = cast(u8 const *)s2; + + if (s1 == NULL || s2 == NULL) { + return 0; + } + + while (size--) { + if (*s1p8 != *s2p8) { + return (*s1p8 - *s2p8); + } + s1p8++, s2p8++; + } + return 0; +} + +void gb_memswap(void *i, void *j, isize size) { + if (i == j) return; + + if (size == 4) { + gb_swap(u32, *cast(u32 *)i, *cast(u32 *)j); + } else if (size == 8) { + gb_swap(u64, *cast(u64 *)i, *cast(u64 *)j); + } else if (size < 8) { + u8 *a = cast(u8 *)i; + u8 *b = cast(u8 *)j; + if (a != b) { + while (size--) { + gb_swap(u8, *a, *b); + a++, b++; + } + } + } else { + char buffer[256]; + + // TODO(bill): Is the recursion ever a problem? + while (size > gb_size_of(buffer)) { + gb_memswap(i, j, gb_size_of(buffer)); + i = gb_pointer_add(i, gb_size_of(buffer)); + j = gb_pointer_add(j, gb_size_of(buffer)); + size -= gb_size_of(buffer); + } + + gb_memcopy(buffer, i, size); + gb_memcopy(i, j, size); + gb_memcopy(j, buffer, size); + } +} + +#define GB__ONES (cast(usize)-1/U8_MAX) +#define GB__HIGHS (GB__ONES * (U8_MAX/2+1)) +#define GB__HAS_ZERO(x) ((x)-GB__ONES & ~(x) & GB__HIGHS) + + +void const *gb_memchr(void const *data, u8 c, isize n) { + u8 const *s = cast(u8 const *)data; + while ((cast(uintptr)s & (sizeof(usize)-1)) && + n && *s != c) { + s++; + n--; + } + if (n && *s != c) { + isize const *w; + isize k = GB__ONES * c; + w = cast(isize const *)s; + while (n >= gb_size_of(isize) && !GB__HAS_ZERO(*w ^ k)) { + w++; + n -= gb_size_of(isize); + } + s = cast(u8 const *)w; + while (n && *s != c) { + s++; + n--; + } + } + + return n ? cast(void const *)s : NULL; +} + + +void const *gb_memrchr(void const *data, u8 c, isize n) { + u8 const *s = cast(u8 const *)data; + while (n--) { + if (s[n] == c) + return cast(void const *)(s + n); + } + return NULL; +} + + + +gb_inline void *gb_alloc_align (gbAllocator a, isize size, isize alignment) { return a.proc(a.data, gbAllocation_Alloc, size, alignment, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } +gb_inline void *gb_alloc (gbAllocator a, isize size) { return gb_alloc_align(a, size, GB_DEFAULT_MEMORY_ALIGNMENT); } +gb_inline void gb_free (gbAllocator a, void *ptr) { if (ptr != NULL) a.proc(a.data, gbAllocation_Free, 0, 0, ptr, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } +gb_inline void gb_free_all (gbAllocator a) { a.proc(a.data, gbAllocation_FreeAll, 0, 0, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } +gb_inline void *gb_resize (gbAllocator a, void *ptr, isize old_size, isize new_size) { return gb_resize_align(a, ptr, old_size, new_size, GB_DEFAULT_MEMORY_ALIGNMENT); } +gb_inline void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment) { return a.proc(a.data, gbAllocation_Resize, new_size, alignment, ptr, old_size, GB_DEFAULT_ALLOCATOR_FLAGS); } + +gb_inline void *gb_alloc_copy (gbAllocator a, void const *src, isize size) { + return gb_memcopy(gb_alloc(a, size), src, size); +} +gb_inline void *gb_alloc_copy_align(gbAllocator a, void const *src, isize size, isize alignment) { + return gb_memcopy(gb_alloc_align(a, size, alignment), src, size); +} + +gb_inline char *gb_alloc_str(gbAllocator a, char const *str) { + return gb_alloc_str_len(a, str, gb_strlen(str)); +} + +gb_inline char *gb_alloc_str_len(gbAllocator a, char const *str, isize len) { + char *result; + result = cast(char *)gb_alloc_copy(a, str, len+1); + result[len] = '\0'; + return result; +} + + +gb_inline void *gb_default_resize_align(gbAllocator a, void *old_memory, isize old_size, isize new_size, isize alignment) { + if (!old_memory) return gb_alloc_align(a, new_size, alignment); + + if (new_size == 0) { + gb_free(a, old_memory); + return NULL; + } + + if (new_size < old_size) + new_size = old_size; + + if (old_size == new_size) { + return old_memory; + } else { + void *new_memory = gb_alloc_align(a, new_size, alignment); + if (!new_memory) return NULL; + gb_memmove(new_memory, old_memory, gb_min(new_size, old_size)); + gb_free(a, old_memory); + return new_memory; + } +} + + + + +//////////////////////////////////////////////////////////////// +// +// Concurrency +// +// +// IMPORTANT TODO(bill): Use compiler intrinsics for the atomics + +#if defined(GB_COMPILER_MSVC) && !defined(GB_COMPILER_CLANG) +gb_inline i32 gb_atomic32_load (gbAtomic32 const volatile *a) { return a->value; } +gb_inline void gb_atomic32_store(gbAtomic32 volatile *a, i32 value) { a->value = value; } + +gb_inline i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired) { + return _InterlockedCompareExchange(cast(long volatile *)a, desired, expected); +} +gb_inline i32 gb_atomic32_exchanged(gbAtomic32 volatile *a, i32 desired) { + return _InterlockedExchange(cast(long volatile *)a, desired); +} +gb_inline i32 gb_atomic32_fetch_add(gbAtomic32 volatile *a, i32 operand) { + return _InterlockedExchangeAdd(cast(long volatile *)a, operand); +} +gb_inline i32 gb_atomic32_fetch_and(gbAtomic32 volatile *a, i32 operand) { + return _InterlockedAnd(cast(long volatile *)a, operand); +} +gb_inline i32 gb_atomic32_fetch_or(gbAtomic32 volatile *a, i32 operand) { + return _InterlockedOr(cast(long volatile *)a, operand); +} + +gb_inline i64 gb_atomic64_load(gbAtomic64 const volatile *a) { +#if defined(GB_ARCH_64_BIT) + return a->value; +#elif GB_CPU_X86 + // NOTE(bill): The most compatible way to get an atomic 64-bit load on x86 is with cmpxchg8b + i64 result; + __asm { + mov esi, a; + mov ebx, eax; + mov ecx, edx; + lock cmpxchg8b [esi]; + mov dword ptr result, eax; + mov dword ptr result[4], edx; + } + return result; +#else +#error TODO(bill): atomics for this CPU +#endif +} + +gb_inline void gb_atomic64_store(gbAtomic64 volatile *a, i64 value) { +#if defined(GB_ARCH_64_BIT) + a->value = value; +#elif GB_CPU_X86 + // NOTE(bill): The most compatible way to get an atomic 64-bit store on x86 is with cmpxchg8b + __asm { + mov esi, a; + mov ebx, dword ptr value; + mov ecx, dword ptr value[4]; + retry: + cmpxchg8b [esi]; + jne retry; + } +#else +#error TODO(bill): atomics for this CPU +#endif +} + +gb_inline i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired) { + return _InterlockedCompareExchange64(cast(i64 volatile *)a, desired, expected); +} + +gb_inline i64 gb_atomic64_exchanged(gbAtomic64 volatile *a, i64 desired) { +#if defined(GB_ARCH_64_BIT) + return _InterlockedExchange64(cast(i64 volatile *)a, desired); +#elif GB_CPU_X86 + i64 expected = a->value; + for (;;) { + i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, desired, expected); + if (original == expected) + return original; + expected = original; + } +#else +#error TODO(bill): atomics for this CPU +#endif +} + +gb_inline i64 gb_atomic64_fetch_add(gbAtomic64 volatile *a, i64 operand) { +#if defined(GB_ARCH_64_BIT) + return _InterlockedExchangeAdd64(cast(i64 volatile *)a, operand); +#elif GB_CPU_X86 + i64 expected = a->value; + for (;;) { + i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected + operand, expected); + if (original == expected) + return original; + expected = original; + } +#else +#error TODO(bill): atomics for this CPU +#endif +} + +gb_inline i64 gb_atomic64_fetch_and(gbAtomic64 volatile *a, i64 operand) { +#if defined(GB_ARCH_64_BIT) + return _InterlockedAnd64(cast(i64 volatile *)a, operand); +#elif GB_CPU_X86 + i64 expected = a->value; + for (;;) { + i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected & operand, expected); + if (original == expected) + return original; + expected = original; + } +#else +#error TODO(bill): atomics for this CPU +#endif +} + +gb_inline i64 gb_atomic64_fetch_or(gbAtomic64 volatile *a, i64 operand) { +#if defined(GB_ARCH_64_BIT) + return _InterlockedOr64(cast(i64 volatile *)a, operand); +#elif GB_CPU_X86 + i64 expected = a->value; + for (;;) { + i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected | operand, expected); + if (original == expected) + return original; + expected = original; + } +#else +#error TODO(bill): atomics for this CPU +#endif +} + + + +#elif defined(GB_CPU_X86) + +gb_inline i32 gb_atomic32_load (gbAtomic32 const volatile *a) { return a->value; } +gb_inline void gb_atomic32_store(gbAtomic32 volatile *a, i32 value) { a->value = value; } + +gb_inline i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired) { + i32 original; + __asm__ volatile( + "lock; cmpxchgl %2, %1" + : "=a"(original), "+m"(a->value) + : "q"(desired), "0"(expected) + ); + return original; +} + +gb_inline i32 gb_atomic32_exchanged(gbAtomic32 volatile *a, i32 desired) { + // NOTE(bill): No lock prefix is necessary for xchgl + i32 original; + __asm__ volatile( + "xchgl %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(desired) + ); + return original; +} + +gb_inline i32 gb_atomic32_fetch_add(gbAtomic32 volatile *a, i32 operand) { + i32 original; + __asm__ volatile( + "lock; xaddl %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(operand) + ); + return original; +} + +gb_inline i32 gb_atomic32_fetch_and(gbAtomic32 volatile *a, i32 operand) { + i32 original; + i32 tmp; + __asm__ volatile( + "1: movl %1, %0\n" + " movl %0, %2\n" + " andl %3, %2\n" + " lock; cmpxchgl %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(tmp) + : "r"(operand) + ); + return original; +} + +gb_inline i32 gb_atomic32_fetch_or(gbAtomic32 volatile *a, i32 operand) { + i32 original; + i32 temp; + __asm__ volatile( + "1: movl %1, %0\n" + " movl %0, %2\n" + " orl %3, %2\n" + " lock; cmpxchgl %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(temp) + : "r"(operand) + ); + return original; +} + + +gb_inline i64 gb_atomic64_load(gbAtomic64 const volatile *a) { +#if defined(GB_ARCH_64_BIT) + return a->value; +#else + i64 original; + __asm__ volatile( + "movl %%ebx, %%eax\n" + "movl %%ecx, %%edx\n" + "lock; cmpxchg8b %1" + : "=&A"(original) + : "m"(a->value) + ); + return original; +#endif +} + +gb_inline void gb_atomic64_store(gbAtomic64 volatile *a, i64 value) { +#if defined(GB_ARCH_64_BIT) + a->value = value; +#else + i64 expected = a->value; + __asm__ volatile( + "1: cmpxchg8b %0\n" + " jne 1b" + : "=m"(a->value) + : "b"((i32)value), "c"((i32)(value >> 32)), "A"(expected) + ); +#endif +} + +gb_inline i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired) { +#if defined(GB_ARCH_64_BIT) + i64 original; + __asm__ volatile( + "lock; cmpxchgq %2, %1" + : "=a"(original), "+m"(a->value) + : "q"(desired), "0"(expected) + ); + return original; +#else + i64 original; + __asm__ volatile( + "lock; cmpxchg8b %1" + : "=A"(original), "+m"(a->value) + : "b"((i32)desired), "c"((i32)(desired >> 32)), "0"(expected) + ); + return original; +#endif +} + +gb_inline i64 gb_atomic64_exchanged(gbAtomic64 volatile *a, i64 desired) { +#if defined(GB_ARCH_64_BIT) + i64 original; + __asm__ volatile( + "xchgq %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(desired) + ); + return original; +#else + i64 original = a->value; + for (;;) { + i64 previous = gb_atomic64_compare_exchange(a, original, desired); + if (original == previous) + return original; + original = previous; + } +#endif +} + +gb_inline i64 gb_atomic64_fetch_add(gbAtomic64 volatile *a, i64 operand) { +#if defined(GB_ARCH_64_BIT) + i64 original; + __asm__ volatile( + "lock; xaddq %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(operand) + ); + return original; +#else + for (;;) { + i64 original = a->value; + if (gb_atomic64_compare_exchange(a, original, original + operand) == original) + return original; + } +#endif +} + +gb_inline i64 gb_atomic64_fetch_and(gbAtomic64 volatile *a, i64 operand) { +#if defined(GB_ARCH_64_BIT) + i64 original; + i64 tmp; + __asm__ volatile( + "1: movq %1, %0\n" + " movq %0, %2\n" + " andq %3, %2\n" + " lock; cmpxchgq %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(tmp) + : "r"(operand) + ); + return original; +#else + for (;;) { + i64 original = a->value; + if (gb_atomic64_compare_exchange(a, original, original & operand) == original) + return original; + } +#endif +} + +gb_inline i64 gb_atomic64_fetch_or(gbAtomic64 volatile *a, i64 operand) { +#if defined(GB_ARCH_64_BIT) + i64 original; + i64 temp; + __asm__ volatile( + "1: movq %1, %0\n" + " movq %0, %2\n" + " orq %3, %2\n" + " lock; cmpxchgq %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(temp) + : "r"(operand) + ); + return original; +#else + for (;;) { + i64 original = a->value; + if (gb_atomic64_compare_exchange(a, original, original | operand) == original) + return original; + } +#endif +} + +#else +#error TODO(bill): Implement Atomics for this CPU +#endif + +gb_inline b32 gb_atomic32_spin_lock(gbAtomic32 volatile *a, isize time_out) { + i32 old_value = gb_atomic32_compare_exchange(a, 1, 0); + i32 counter = 0; + while (old_value != 0 && (time_out < 0 || counter++ < time_out)) { + gb_yield_thread(); + old_value = gb_atomic32_compare_exchange(a, 1, 0); + gb_mfence(); + } + return old_value == 0; +} +gb_inline void gb_atomic32_spin_unlock(gbAtomic32 volatile *a) { + gb_atomic32_store(a, 0); + gb_mfence(); +} + +gb_inline b32 gb_atomic64_spin_lock(gbAtomic64 volatile *a, isize time_out) { + i64 old_value = gb_atomic64_compare_exchange(a, 1, 0); + i64 counter = 0; + while (old_value != 0 && (time_out < 0 || counter++ < time_out)) { + gb_yield_thread(); + old_value = gb_atomic64_compare_exchange(a, 1, 0); + gb_mfence(); + } + return old_value == 0; +} + +gb_inline void gb_atomic64_spin_unlock(gbAtomic64 volatile *a) { + gb_atomic64_store(a, 0); + gb_mfence(); +} + +gb_inline b32 gb_atomic32_try_acquire_lock(gbAtomic32 volatile *a) { + i32 old_value; + gb_yield_thread(); + old_value = gb_atomic32_compare_exchange(a, 1, 0); + gb_mfence(); + return old_value == 0; +} + +gb_inline b32 gb_atomic64_try_acquire_lock(gbAtomic64 volatile *a) { + i64 old_value; + gb_yield_thread(); + old_value = gb_atomic64_compare_exchange(a, 1, 0); + gb_mfence(); + return old_value == 0; +} + + + +#if defined(GB_ARCH_32_BIT) + +gb_inline void *gb_atomic_ptr_load(gbAtomicPtr const volatile *a) { + return cast(void *)cast(intptr)gb_atomic32_load(cast(gbAtomic32 const volatile *)a); +} +gb_inline void gb_atomic_ptr_store(gbAtomicPtr volatile *a, void *value) { + gb_atomic32_store(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)value); +} +gb_inline void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired) { + return cast(void *)cast(intptr)gb_atomic32_compare_exchange(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)expected, cast(i32)cast(intptr)desired); +} +gb_inline void *gb_atomic_ptr_exchanged(gbAtomicPtr volatile *a, void *desired) { + return cast(void *)cast(intptr)gb_atomic32_exchanged(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)desired); +} +gb_inline void *gb_atomic_ptr_fetch_add(gbAtomicPtr volatile *a, void *operand) { + return cast(void *)cast(intptr)gb_atomic32_fetch_add(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand); +} +gb_inline void *gb_atomic_ptr_fetch_and(gbAtomicPtr volatile *a, void *operand) { + return cast(void *)cast(intptr)gb_atomic32_fetch_and(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand); +} +gb_inline void *gb_atomic_ptr_fetch_or(gbAtomicPtr volatile *a, void *operand) { + return cast(void *)cast(intptr)gb_atomic32_fetch_or(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand); +} +gb_inline b32 gb_atomic_ptr_spin_lock(gbAtomicPtr volatile *a, isize time_out) { + return gb_atomic32_spin_lock(cast(gbAtomic32 volatile *)a, time_out); +} +gb_inline void gb_atomic_ptr_spin_unlock(gbAtomicPtr volatile *a) { + gb_atomic32_spin_unlock(cast(gbAtomic32 volatile *)a); +} +gb_inline b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a) { + return gb_atomic32_try_acquire_lock(cast(gbAtomic32 volatile *)a); +} + +#elif defined(GB_ARCH_64_BIT) + +gb_inline void *gb_atomic_ptr_load(gbAtomicPtr const volatile *a) { + return cast(void *)cast(intptr)gb_atomic64_load(cast(gbAtomic64 const volatile *)a); +} +gb_inline void gb_atomic_ptr_store(gbAtomicPtr volatile *a, void *value) { + gb_atomic64_store(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)value); +} +gb_inline void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired) { + return cast(void *)cast(intptr)gb_atomic64_compare_exchange(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)expected, cast(i64)cast(intptr)desired); +} +gb_inline void *gb_atomic_ptr_exchanged(gbAtomicPtr volatile *a, void *desired) { + return cast(void *)cast(intptr)gb_atomic64_exchanged(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)desired); +} +gb_inline void *gb_atomic_ptr_fetch_add(gbAtomicPtr volatile *a, void *operand) { + return cast(void *)cast(intptr)gb_atomic64_fetch_add(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand); +} +gb_inline void *gb_atomic_ptr_fetch_and(gbAtomicPtr volatile *a, void *operand) { + return cast(void *)cast(intptr)gb_atomic64_fetch_and(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand); +} +gb_inline void *gb_atomic_ptr_fetch_or(gbAtomicPtr volatile *a, void *operand) { + return cast(void *)cast(intptr)gb_atomic64_fetch_or(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand); +} +gb_inline b32 gb_atomic_ptr_spin_lock(gbAtomicPtr volatile *a, isize time_out) { + return gb_atomic64_spin_lock(cast(gbAtomic64 volatile *)a, time_out); +} +gb_inline void gb_atomic_ptr_spin_unlock(gbAtomicPtr volatile *a) { + gb_atomic64_spin_unlock(cast(gbAtomic64 volatile *)a); +} +gb_inline b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a) { + return gb_atomic64_try_acquire_lock(cast(gbAtomic64 volatile *)a); +} +#endif + + +gb_inline void gb_yield_thread(void) { +#if defined(GB_SYSTEM_WINDOWS) + _mm_pause(); +#elif defined(GB_SYSTEM_OSX) + __asm__ volatile ("" : : : "memory"); +#elif defined(GB_CPU_X86) + _mm_pause(); +#else +#error Unknown architecture +#endif +} + +gb_inline void gb_mfence(void) { +#if defined(GB_SYSTEM_WINDOWS) + _ReadWriteBarrier(); +#elif defined(GB_SYSTEM_OSX) + __sync_synchronize(); +#elif defined(GB_CPU_X86) + _mm_mfence(); +#else +#error Unknown architecture +#endif +} + +gb_inline void gb_sfence(void) { +#if defined(GB_SYSTEM_WINDOWS) + _WriteBarrier(); +#elif defined(GB_SYSTEM_OSX) + __asm__ volatile ("" : : : "memory"); +#elif defined(GB_CPU_X86) + _mm_sfence(); +#else +#error Unknown architecture +#endif +} + +gb_inline void gb_lfence(void) { +#if defined(GB_SYSTEM_WINDOWS) + _ReadBarrier(); +#elif defined(GB_SYSTEM_OSX) + __asm__ volatile ("" : : : "memory"); +#elif defined(GB_CPU_X86) + _mm_lfence(); +#else +#error Unknown architecture +#endif +} + + +gb_inline void gb_semaphore_release(gbSemaphore *s) { gb_semaphore_post(s, 1); } + +#if defined(GB_SYSTEM_WINDOWS) + gb_inline void gb_semaphore_init (gbSemaphore *s) { s->win32_handle = CreateSemaphoreA(NULL, 0, I32_MAX, NULL); } + gb_inline void gb_semaphore_destroy(gbSemaphore *s) { CloseHandle(s->win32_handle); } + gb_inline void gb_semaphore_post (gbSemaphore *s, i32 count) { ReleaseSemaphore(s->win32_handle, count, NULL); } + gb_inline void gb_semaphore_wait (gbSemaphore *s) { WaitForSingleObject(s->win32_handle, INFINITE); } + +#elif defined(GB_SYSTEM_OSX) + gb_inline void gb_semaphore_init (gbSemaphore *s) { semaphore_create(mach_task_self(), &s->osx_handle, SYNC_POLICY_FIFO, 0); } + gb_inline void gb_semaphore_destroy(gbSemaphore *s) { semaphore_destroy(mach_task_self(), s->osx_handle); } + gb_inline void gb_semaphore_post (gbSemaphore *s, i32 count) { while (count --> 0) semaphore_signal(s->osx_handle); } + gb_inline void gb_semaphore_wait (gbSemaphore *s) { semaphore_wait(s->osx_handle); } + +#elif defined(GB_SYSTEM_UNIX) + gb_inline void gb_semaphore_init (gbSemaphore *s) { sem_init(&s->unix_handle, 0, 0); } + gb_inline void gb_semaphore_destroy(gbSemaphore *s) { sem_destroy(&s->unix_handle); } + gb_inline void gb_semaphore_post (gbSemaphore *s, i32 count) { while (count --> 0) sem_post(&s->unix_handle); } + gb_inline void gb_semaphore_wait (gbSemaphore *s) { int i; do { i = sem_wait(&s->unix_handle); } while (i == -1 && errno == EINTR); } + +#else +#error +#endif + +gb_inline void gb_mutex_init(gbMutex *m) { +#if defined(GB_SYSTEM_WINDOWS) + InitializeCriticalSection(&m->win32_critical_section); +#else + pthread_mutexattr_init(&m->pthread_mutexattr); + pthread_mutexattr_settype(&m->pthread_mutexattr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m->pthread_mutex, &m->pthread_mutexattr); +#endif +} + +gb_inline void gb_mutex_destroy(gbMutex *m) { +#if defined(GB_SYSTEM_WINDOWS) + DeleteCriticalSection(&m->win32_critical_section); +#else + pthread_mutex_destroy(&m->pthread_mutex); +#endif +} + +gb_inline void gb_mutex_lock(gbMutex *m) { +#if defined(GB_SYSTEM_WINDOWS) + EnterCriticalSection(&m->win32_critical_section); +#else + pthread_mutex_lock(&m->pthread_mutex); +#endif +} + +gb_inline b32 gb_mutex_try_lock(gbMutex *m) { +#if defined(GB_SYSTEM_WINDOWS) + return TryEnterCriticalSection(&m->win32_critical_section) != 0; +#else + return pthread_mutex_trylock(&m->pthread_mutex) == 0; +#endif +} + +gb_inline void gb_mutex_unlock(gbMutex *m) { +#if defined(GB_SYSTEM_WINDOWS) + LeaveCriticalSection(&m->win32_critical_section); +#else + pthread_mutex_unlock(&m->pthread_mutex); +#endif +} + + + + + + + +void gb_thread_init(gbThread *t) { + gb_zero_item(t); +#if defined(GB_SYSTEM_WINDOWS) + t->win32_handle = INVALID_HANDLE_VALUE; +#else + t->posix_handle = 0; +#endif + gb_semaphore_init(&t->semaphore); +} + +void gb_thread_destroy(gbThread *t) { + if (t->is_running) gb_thread_join(t); + gb_semaphore_destroy(&t->semaphore); +} + + +gb_inline void gb__thread_run(gbThread *t) { + gb_semaphore_release(&t->semaphore); + t->return_value = t->proc(t); +} + +#if defined(GB_SYSTEM_WINDOWS) + gb_inline DWORD __stdcall gb__thread_proc(void *arg) { + gbThread *t = cast(gbThread *)arg; + gb__thread_run(t); + t->is_running = false; + return 0; + } +#else + gb_inline void * gb__thread_proc(void *arg) { + gbThread *t = cast(gbThread *)arg; + gb__thread_run(t); + t->is_running = false; + return NULL; + } +#endif + +gb_inline void gb_thread_start(gbThread *t, gbThreadProc *proc, void *user_data) { gb_thread_start_with_stack(t, proc, user_data, 0); } + +gb_inline void gb_thread_start_with_stack(gbThread *t, gbThreadProc *proc, void *user_data, isize stack_size) { + GB_ASSERT(!t->is_running); + GB_ASSERT(proc != NULL); + t->proc = proc; + t->user_data = user_data; + t->stack_size = stack_size; + t->is_running = true; + +#if defined(GB_SYSTEM_WINDOWS) + t->win32_handle = CreateThread(NULL, stack_size, gb__thread_proc, t, 0, NULL); + GB_ASSERT_MSG(t->win32_handle != NULL, "CreateThread: GetLastError"); +#else + { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + if (stack_size != 0) { + pthread_attr_setstacksize(&attr, stack_size); + } + pthread_create(&t->posix_handle, &attr, gb__thread_proc, t); + pthread_attr_destroy(&attr); + } +#endif + + gb_semaphore_wait(&t->semaphore); +} + +gb_inline void gb_thread_join(gbThread *t) { + if (!t->is_running) return; + +#if defined(GB_SYSTEM_WINDOWS) + WaitForSingleObject(t->win32_handle, INFINITE); + CloseHandle(t->win32_handle); + t->win32_handle = INVALID_HANDLE_VALUE; +#else + pthread_join(t->posix_handle, NULL); + t->posix_handle = 0; +#endif + t->is_running = false; +} + +gb_inline b32 gb_thread_is_running(gbThread const *t) { return t->is_running != 0; } + +gb_inline u32 gb_thread_current_id(void) { + u32 thread_id; +#if defined(GB_SYSTEM_WINDOWS) + #if defined(GB_ARCH_32_BIT) && defined(GB_CPU_X86) + thread_id = (cast(u32 *)__readfsdword(24))[9]; + #elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86) + thread_id = (cast(u32 *)__readgsqword(48))[18]; + #else + thread_id = GetCurrentThreadId(); + #endif + +#elif defined(GB_SYSTEM_OSX) && defined(GB_ARCH_64_BIT) + thread_id = pthread_mach_thread_np(pthread_self()); +#elif defined(GB_ARCH_32_BIT) && defined(GB_CPU_X86) + __asm__("mov %%gs:0x08,%0" : "=r"(thread_id)); +#elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86) + __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); +#else + #error Unsupported architecture for gb_thread_current_id() +#endif + + return thread_id; +} + + + +void gb_thread_set_name(gbThread *t, char const *name) { +#if defined(GB_COMPILER_MSVC) + #pragma pack(push, 8) + typedef struct { + DWORD type; + char const *name; + DWORD id; + DWORD flags; + } gbprivThreadName; + #pragma pack(pop) + gbprivThreadName tn; + tn.type = 0x1000; + tn.name = name; + tn.id = GetThreadId(cast(HANDLE)t->win32_handle); + tn.flags = 0; + + __try { + RaiseException(0x406d1388, 0, gb_size_of(tn)/4, cast(ULONG_PTR *)&tn); + } __except(1 /*EXCEPTION_EXECUTE_HANDLER*/) { + } + +#elif defined(GB_SYSTEM_WINDOWS) && !defined(GB_COMPILER_MSVC) + // IMPORTANT TODO(bill): Set thread name for GCC/Clang on windows + return; +#elif defined(GB_SYSTEM_OSX) + // TODO(bill): Test if this works + pthread_setname_np(name); +#else + // TODO(bill): Test if this works + pthread_setname_np(t->posix_handle, name); +#endif +} + + + + +void gb_sync_init(gbSync *s) { + gb_zero_item(s); + gb_mutex_init(&s->mutex); + gb_mutex_init(&s->start); + gb_semaphore_init(&s->release); +} + +void gb_sync_destroy(gbSync *s) { + if (s->waiting) + GB_PANIC("Cannot destroy while threads are waiting!"); + + gb_mutex_destroy(&s->mutex); + gb_mutex_destroy(&s->start); + gb_semaphore_destroy(&s->release); +} + +void gb_sync_set_target(gbSync *s, i32 count) { + gb_mutex_lock(&s->start); + + gb_mutex_lock(&s->mutex); + GB_ASSERT(s->target == 0); + s->target = count; + s->current = 0; + s->waiting = 0; + gb_mutex_unlock(&s->mutex); +} + +void gb_sync_release(gbSync *s) { + if (s->waiting) { + gb_semaphore_release(&s->release); + } else { + s->target = 0; + gb_mutex_unlock(&s->start); + } +} + +i32 gb_sync_reach(gbSync *s) { + i32 n; + gb_mutex_lock(&s->mutex); + GB_ASSERT(s->current < s->target); + n = ++s->current; // NOTE(bill): Record this value to avoid possible race if `return s->current` was done + if (s->current == s->target) + gb_sync_release(s); + gb_mutex_unlock(&s->mutex); + return n; +} + +void gb_sync_reach_and_wait(gbSync *s) { + gb_mutex_lock(&s->mutex); + GB_ASSERT(s->current < s->target); + s->current++; + if (s->current == s->target) { + gb_sync_release(s); + gb_mutex_unlock(&s->mutex); + } else { + s->waiting++; // NOTE(bill): Waiting, so one more waiter + gb_mutex_unlock(&s->mutex); // NOTE(bill): Release the mutex to other threads + + gb_semaphore_wait(&s->release); // NOTE(bill): Wait for merge completion + + gb_mutex_lock(&s->mutex); // NOTE(bill): On merge completion, lock mutex + s->waiting--; // NOTE(bill): Done waiting + gb_sync_release(s); // NOTE(bill): Restart the next waiter + gb_mutex_unlock(&s->mutex); + } +} + + + + + + + + +gb_inline gbAllocator gb_heap_allocator(void) { + gbAllocator a; + a.proc = gb_heap_allocator_proc; + a.data = NULL; + return a; +} + +GB_ALLOCATOR_PROC(gb_heap_allocator_proc) { + void *ptr = NULL; + gb_unused(allocator_data); + gb_unused(old_size); +// TODO(bill): Throughly test! + switch (type) { +#if defined(GB_COMPILER_MSVC) + case gbAllocation_Alloc: + ptr = _aligned_malloc(size, alignment); + if (flags & gbAllocatorFlag_ClearToZero) + gb_zero_size(ptr, size); + break; + case gbAllocation_Free: + _aligned_free(old_memory); + break; + case gbAllocation_Resize: + ptr = _aligned_realloc(old_memory, size, alignment); + break; + +#elif defined(GB_SYSTEM_LINUX) + // TODO(bill): *nix version that's decent + case gbAllocation_Alloc: { + ptr = aligned_alloc(alignment, size); + // ptr = malloc(size+alignment); + + if (flags & gbAllocatorFlag_ClearToZero) { + gb_zero_size(ptr, size); + } + } break; + + case gbAllocation_Free: { + free(old_memory); + } break; + + case gbAllocation_Resize: { + // ptr = realloc(old_memory, size); + ptr = gb_default_resize_align(gb_heap_allocator(), old_memory, old_size, size, alignment); + } break; +#else + // TODO(bill): *nix version that's decent + case gbAllocation_Alloc: { + posix_memalign(&ptr, alignment, size); + + if (flags & gbAllocatorFlag_ClearToZero) { + gb_zero_size(ptr, size); + } + } break; + + case gbAllocation_Free: { + free(old_memory); + } break; + + case gbAllocation_Resize: { + ptr = gb_default_resize_align(gb_heap_allocator(), old_memory, old_size, size, alignment); + } break; +#endif + + case gbAllocation_FreeAll: + break; + } + + return ptr; +} + + +#if defined(GB_SYSTEM_WINDOWS) +void gb_affinity_init(gbAffinity *a) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *start_processor_info = NULL; + DWORD length = 0; + b32 result = GetLogicalProcessorInformation(NULL, &length); + + gb_zero_item(a); + + if (!result && GetLastError() == 122l /*ERROR_INSUFFICIENT_BUFFER*/ && length > 0) { + start_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)gb_alloc(gb_heap_allocator(), length); + result = GetLogicalProcessorInformation(start_processor_info, &length); + if (result) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *end_processor_info, *processor_info; + + a->is_accurate = true; + a->core_count = 0; + a->thread_count = 0; + end_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)gb_pointer_add(start_processor_info, length); + + for (processor_info = start_processor_info; + processor_info < end_processor_info; + processor_info++) { + if (processor_info->Relationship == RelationProcessorCore) { + isize thread = gb_count_set_bits(processor_info->ProcessorMask); + if (thread == 0) { + a->is_accurate = false; + } else if (a->thread_count + thread > GB_WIN32_MAX_THREADS) { + a->is_accurate = false; + } else { + GB_ASSERT(a->core_count <= a->thread_count && + a->thread_count < GB_WIN32_MAX_THREADS); + a->core_masks[a->core_count++] = processor_info->ProcessorMask; + a->thread_count += thread; + } + } + } + } + + gb_free(gb_heap_allocator(), start_processor_info); + } + + GB_ASSERT(a->core_count <= a->thread_count); + if (a->thread_count == 0) { + a->is_accurate = false; + a->core_count = 1; + a->thread_count = 1; + a->core_masks[0] = 1; + } + +} +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread) { + usize available_mask, check_mask = 1; + GB_ASSERT(thread < gb_affinity_thread_count_for_core(a, core)); + + available_mask = a->core_masks[core]; + for (;;) { + if ((available_mask & check_mask) != 0) { + if (thread-- == 0) { + usize result = SetThreadAffinityMask(GetCurrentThread(), check_mask); + return result != 0; + } + } + check_mask <<= 1; // NOTE(bill): Onto the next bit + } +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(core >= 0 && core < a->core_count); + return gb_count_set_bits(a->core_masks[core]); +} + +#elif defined(GB_SYSTEM_OSX) +void gb_affinity_init(gbAffinity *a) { + usize count = 0; + usize count_size = sizeof(count); + + a->is_accurate = false; + a->thread_count = 1; + a->core_count = 1; + a->threads_per_core = 1; + + if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) { + if (count > 0) { + a->thread_count = count; + // Get # of physical cores + if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) { + if (count > 0) { + a->core_count = count; + a->threads_per_core = a->thread_count / count; + if (a->threads_per_core < 1) + a->threads_per_core = 1; + else + a->is_accurate = true; + } + } + } + } + +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + isize index; + thread_t thread; + thread_affinity_policy_data_t info; + kern_return_t result; + + GB_ASSERT(core < a->core_count); + GB_ASSERT(thread_index < a->threads_per_core); + + index = core * a->threads_per_core + thread_index; + thread = mach_thread_self(); + info.affinity_tag = cast(integer_t)index; + result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT); + return result == KERN_SUCCESS; +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(core >= 0 && core < a->core_count); + return a->threads_per_core; +} + +#elif defined(GB_SYSTEM_LINUX) +// IMPORTANT TODO(bill): This gbAffinity stuff for linux needs be improved a lot! +// NOTE(zangent): I have to read /proc/cpuinfo to get the number of threads per core. +#include + +void gb_affinity_init(gbAffinity *a) { + b32 accurate = true; + isize threads = 0; + + a->thread_count = 1; + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + + + if(a->core_count <= 0) { + a->core_count = 1; + accurate = false; + } + + // Parsing /proc/cpuinfo to get the number of threads per core. + // NOTE(zangent): This calls the CPU's threads "cores", although the wording + // is kind of weird. This should be right, though. + + FILE* cpu_info = fopen("/proc/cpuinfo", "r"); + + if (cpu_info != NULL) { + for (;;) { + // The 'temporary char'. Everything goes into this char, + // so that we can check against EOF at the end of this loop. + char c; + +#define AF__CHECK(letter) ((c = getc(cpu_info)) == letter) + if (AF__CHECK('c') && AF__CHECK('p') && AF__CHECK('u') && AF__CHECK(' ') && + AF__CHECK('c') && AF__CHECK('o') && AF__CHECK('r') && AF__CHECK('e') && AF__CHECK('s')) { + // We're on a CPU info line. + while (!AF__CHECK(EOF)) { + if (c == '\n') { + break; + } else if (c < '0' || '9' > c) { + continue; + } + threads = threads * 10 + (c - '0'); + } + break; + } else { + while (!AF__CHECK('\n')) { + if (c==EOF) { + break; + } + } + } + if (c == EOF) { + break; + } +#undef AF__CHECK + } + + fclose(cpu_info); + } + + if (threads == 0) { + threads = 1; + accurate = false; + } + + a->threads_per_core = threads; + a->thread_count = a->threads_per_core * a->core_count; + a->is_accurate = accurate; + +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + return true; +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; +} +#else +#error TODO(bill): Unknown system +#endif + + + + + + + + + +//////////////////////////////////////////////////////////////// +// +// Virtual Memory +// +// + +gbVirtualMemory gb_virtual_memory(void *data, isize size) { + gbVirtualMemory vm; + vm.data = data; + vm.size = size; + return vm; +} + + +#if defined(GB_SYSTEM_WINDOWS) +gb_inline gbVirtualMemory gb_vm_alloc(void *addr, isize size) { + gbVirtualMemory vm; + GB_ASSERT(size > 0); + vm.data = VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + vm.size = size; + return vm; +} + +gb_inline b32 gb_vm_free(gbVirtualMemory vm) { + MEMORY_BASIC_INFORMATION info; + while (vm.size > 0) { + if (VirtualQuery(vm.data, &info, gb_size_of(info)) == 0) + return false; + if (info.BaseAddress != vm.data || + info.AllocationBase != vm.data || + info.State != MEM_COMMIT || info.RegionSize > cast(usize)vm.size) { + return false; + } + if (VirtualFree(vm.data, 0, MEM_RELEASE) == 0) + return false; + vm.data = gb_pointer_add(vm.data, info.RegionSize); + vm.size -= info.RegionSize; + } + return true; +} + +gb_inline gbVirtualMemory gb_vm_trim(gbVirtualMemory vm, isize lead_size, isize size) { + gbVirtualMemory new_vm = {0}; + void *ptr; + GB_ASSERT(vm.size >= lead_size + size); + + ptr = gb_pointer_add(vm.data, lead_size); + + gb_vm_free(vm); + new_vm = gb_vm_alloc(ptr, size); + if (new_vm.data == ptr) + return new_vm; + if (new_vm.data) + gb_vm_free(new_vm); + return new_vm; +} + +gb_inline b32 gb_vm_purge(gbVirtualMemory vm) { + VirtualAlloc(vm.data, vm.size, MEM_RESET, PAGE_READWRITE); + // NOTE(bill): Can this really fail? + return true; +} + +isize gb_virtual_memory_page_size(isize *alignment_out) { + SYSTEM_INFO info; + GetSystemInfo(&info); + if (alignment_out) *alignment_out = info.dwAllocationGranularity; + return info.dwPageSize; +} + +#else + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +gb_inline gbVirtualMemory gb_vm_alloc(void *addr, isize size) { + gbVirtualMemory vm; + GB_ASSERT(size > 0); + vm.data = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + vm.size = size; + return vm; +} + +gb_inline b32 gb_vm_free(gbVirtualMemory vm) { + munmap(vm.data, vm.size); + return true; +} + +gb_inline gbVirtualMemory gb_vm_trim(gbVirtualMemory vm, isize lead_size, isize size) { + void *ptr; + isize trail_size; + GB_ASSERT(vm.size >= lead_size + size); + + ptr = gb_pointer_add(vm.data, lead_size); + trail_size = vm.size - lead_size - size; + + if (lead_size != 0) + gb_vm_free(gb_virtual_memory(vm.data, lead_size)); + if (trail_size != 0) + gb_vm_free(gb_virtual_memory(ptr, trail_size)); + return gb_virtual_memory(ptr, size); + +} + +gb_inline b32 gb_vm_purge(gbVirtualMemory vm) { + int err = madvise(vm.data, vm.size, MADV_DONTNEED); + return err != 0; +} + +isize gb_virtual_memory_page_size(isize *alignment_out) { + // TODO(bill): Is this always true? + isize result = cast(isize)sysconf(_SC_PAGE_SIZE); + if (alignment_out) *alignment_out = result; + return result; +} + +#endif + + + + +//////////////////////////////////////////////////////////////// +// +// Custom Allocation +// +// + + +// +// Arena Allocator +// + +gb_inline void gb_arena_init_from_memory(gbArena *arena, void *start, isize size) { + arena->backing.proc = NULL; + arena->backing.data = NULL; + arena->physical_start = start; + arena->total_size = size; + arena->total_allocated = 0; + arena->temp_count = 0; +} + +gb_inline void gb_arena_init_from_allocator(gbArena *arena, gbAllocator backing, isize size) { + arena->backing = backing; + arena->physical_start = gb_alloc(backing, size); // NOTE(bill): Uses default alignment + arena->total_size = size; + arena->total_allocated = 0; + arena->temp_count = 0; +} + +gb_inline void gb_arena_init_sub(gbArena *arena, gbArena *parent_arena, isize size) { gb_arena_init_from_allocator(arena, gb_arena_allocator(parent_arena), size); } + + +gb_inline void gb_arena_free(gbArena *arena) { + if (arena->backing.proc) { + gb_free(arena->backing, arena->physical_start); + arena->physical_start = NULL; + } +} + + +gb_inline isize gb_arena_alignment_of(gbArena *arena, isize alignment) { + isize alignment_offset, result_pointer, mask; + GB_ASSERT(gb_is_power_of_two(alignment)); + + alignment_offset = 0; + result_pointer = cast(isize)arena->physical_start + arena->total_allocated; + mask = alignment - 1; + if (result_pointer & mask) + alignment_offset = alignment - (result_pointer & mask); + + return alignment_offset; +} + +gb_inline isize gb_arena_size_remaining(gbArena *arena, isize alignment) { + isize result = arena->total_size - (arena->total_allocated + gb_arena_alignment_of(arena, alignment)); + return result; +} + +gb_inline void gb_arena_check(gbArena *arena) { GB_ASSERT(arena->temp_count == 0); } + + + + + + +gb_inline gbAllocator gb_arena_allocator(gbArena *arena) { + gbAllocator allocator; + allocator.proc = gb_arena_allocator_proc; + allocator.data = arena; + return allocator; +} + +GB_ALLOCATOR_PROC(gb_arena_allocator_proc) { + gbArena *arena = cast(gbArena *)allocator_data; + void *ptr = NULL; + + gb_unused(old_size); + + switch (type) { + case gbAllocation_Alloc: { + void *end = gb_pointer_add(arena->physical_start, arena->total_allocated); + isize total_size = size + alignment; + + // NOTE(bill): Out of memory + if (arena->total_allocated + total_size > cast(isize)arena->total_size) { + gb_printf_err("Arena out of memory\n"); + return NULL; + } + + ptr = gb_align_forward(end, alignment); + arena->total_allocated += total_size; + if (flags & gbAllocatorFlag_ClearToZero) + gb_zero_size(ptr, size); + } break; + + case gbAllocation_Free: + // NOTE(bill): Free all at once + // Use Temp_Arena_Memory if you want to free a block + break; + + case gbAllocation_FreeAll: + arena->total_allocated = 0; + break; + + case gbAllocation_Resize: { + // TODO(bill): Check if ptr is on top of stack and just extend + gbAllocator a = gb_arena_allocator(arena); + ptr = gb_default_resize_align(a, old_memory, old_size, size, alignment); + } break; + } + return ptr; +} + + +gb_inline gbTempArenaMemory gb_temp_arena_memory_begin(gbArena *arena) { + gbTempArenaMemory tmp; + tmp.arena = arena; + tmp.original_count = arena->total_allocated; + arena->temp_count++; + return tmp; +} + +gb_inline void gb_temp_arena_memory_end(gbTempArenaMemory tmp) { + GB_ASSERT_MSG(tmp.arena->total_allocated >= tmp.original_count, + "%td >= %td", tmp.arena->total_allocated, tmp.original_count); + GB_ASSERT(tmp.arena->temp_count > 0); + tmp.arena->total_allocated = tmp.original_count; + tmp.arena->temp_count--; +} + + + + +// +// Pool Allocator +// + + +gb_inline void gb_pool_init(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size) { + gb_pool_init_align(pool, backing, num_blocks, block_size, GB_DEFAULT_MEMORY_ALIGNMENT); +} + +void gb_pool_init_align(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size, isize block_align) { + isize actual_block_size, pool_size, block_index; + void *data, *curr; + uintptr *end; + + gb_zero_item(pool); + + pool->backing = backing; + pool->block_size = block_size; + pool->block_align = block_align; + + actual_block_size = block_size + block_align; + pool_size = num_blocks * actual_block_size; + + data = gb_alloc_align(backing, pool_size, block_align); + + // NOTE(bill): Init intrusive freelist + curr = data; + for (block_index = 0; block_index < num_blocks-1; block_index++) { + uintptr *next = cast(uintptr *)curr; + *next = cast(uintptr)curr + actual_block_size; + curr = gb_pointer_add(curr, actual_block_size); + } + + end = cast(uintptr *)curr; + *end = cast(uintptr)NULL; + + pool->physical_start = data; + pool->free_list = data; +} + +gb_inline void gb_pool_free(gbPool *pool) { + if (pool->backing.proc) { + gb_free(pool->backing, pool->physical_start); + } +} + + +gb_inline gbAllocator gb_pool_allocator(gbPool *pool) { + gbAllocator allocator; + allocator.proc = gb_pool_allocator_proc; + allocator.data = pool; + return allocator; +} +GB_ALLOCATOR_PROC(gb_pool_allocator_proc) { + gbPool *pool = cast(gbPool *)allocator_data; + void *ptr = NULL; + + gb_unused(old_size); + + switch (type) { + case gbAllocation_Alloc: { + uintptr next_free; + GB_ASSERT(size == pool->block_size); + GB_ASSERT(alignment == pool->block_align); + GB_ASSERT(pool->free_list != NULL); + + next_free = *cast(uintptr *)pool->free_list; + ptr = pool->free_list; + pool->free_list = cast(void *)next_free; + pool->total_size += pool->block_size; + if (flags & gbAllocatorFlag_ClearToZero) + gb_zero_size(ptr, size); + } break; + + case gbAllocation_Free: { + uintptr *next; + if (old_memory == NULL) return NULL; + + next = cast(uintptr *)old_memory; + *next = cast(uintptr)pool->free_list; + pool->free_list = old_memory; + pool->total_size -= pool->block_size; + } break; + + case gbAllocation_FreeAll: + // TODO(bill): + break; + + case gbAllocation_Resize: + // NOTE(bill): Cannot resize + GB_PANIC("You cannot resize something allocated by with a pool."); + break; + } + + return ptr; +} + + + + + +gb_inline gbAllocationHeader *gb_allocation_header(void *data) { + isize *p = cast(isize *)data; + while (p[-1] == cast(isize)(-1)) { + p--; + } + return cast(gbAllocationHeader *)p - 1; +} + +gb_inline void gb_allocation_header_fill(gbAllocationHeader *header, void *data, isize size) { + isize *ptr; + header->size = size; + ptr = cast(isize *)(header + 1); + while (cast(void *)ptr < data) { + *ptr++ = cast(isize)(-1); + } +} + + + +// +// Free List Allocator +// + +gb_inline void gb_free_list_init(gbFreeList *fl, void *start, isize size) { + GB_ASSERT(size > gb_size_of(gbFreeListBlock)); + + fl->physical_start = start; + fl->total_size = size; + fl->curr_block = cast(gbFreeListBlock *)start; + fl->curr_block->size = size; + fl->curr_block->next = NULL; +} + + +gb_inline void gb_free_list_init_from_allocator(gbFreeList *fl, gbAllocator backing, isize size) { + void *start = gb_alloc(backing, size); + gb_free_list_init(fl, start, size); +} + + + +gb_inline gbAllocator gb_free_list_allocator(gbFreeList *fl) { + gbAllocator a; + a.proc = gb_free_list_allocator_proc; + a.data = fl; + return a; +} + +GB_ALLOCATOR_PROC(gb_free_list_allocator_proc) { + gbFreeList *fl = cast(gbFreeList *)allocator_data; + void *ptr = NULL; + + GB_ASSERT_NOT_NULL(fl); + + switch (type) { + case gbAllocation_Alloc: { + gbFreeListBlock *prev_block = NULL; + gbFreeListBlock *curr_block = fl->curr_block; + + while (curr_block) { + isize total_size; + gbAllocationHeader *header; + + total_size = size + alignment + gb_size_of(gbAllocationHeader); + + if (curr_block->size < total_size) { + prev_block = curr_block; + curr_block = curr_block->next; + continue; + } + + if (curr_block->size - total_size <= gb_size_of(gbAllocationHeader)) { + total_size = curr_block->size; + + if (prev_block) + prev_block->next = curr_block->next; + else + fl->curr_block = curr_block->next; + } else { + // NOTE(bill): Create a new block for the remaining memory + gbFreeListBlock *next_block; + next_block = cast(gbFreeListBlock *)gb_pointer_add(curr_block, total_size); + + GB_ASSERT(cast(void *)next_block < gb_pointer_add(fl->physical_start, fl->total_size)); + + next_block->size = curr_block->size - total_size; + next_block->next = curr_block->next; + + if (prev_block) + prev_block->next = next_block; + else + fl->curr_block = next_block; + } + + + // TODO(bill): Set Header Info + header = cast(gbAllocationHeader *)curr_block; + ptr = gb_align_forward(header+1, alignment); + gb_allocation_header_fill(header, ptr, size); + + fl->total_allocated += total_size; + fl->allocation_count++; + + + if (flags & gbAllocatorFlag_ClearToZero) + gb_zero_size(ptr, size); + return ptr; + } + // NOTE(bill): if ptr == NULL, ran out of free list memory! FUCK! + return NULL; + } break; + + case gbAllocation_Free: { + gbAllocationHeader *header = gb_allocation_header(old_memory); + isize block_size = header->size; + uintptr block_start, block_end; + gbFreeListBlock *prev_block = NULL; + gbFreeListBlock *curr_block = fl->curr_block; + + block_start = cast(uintptr)header; + block_end = cast(uintptr)block_start + block_size; + + while (curr_block) { + if (cast(uintptr)curr_block >= block_end) + break; + prev_block = curr_block; + curr_block = curr_block->next; + } + + if (prev_block == NULL) { + prev_block = cast(gbFreeListBlock *)block_start; + prev_block->size = block_size; + prev_block->next = fl->curr_block; + + fl->curr_block = prev_block; + } else if ((cast(uintptr)prev_block + prev_block->size) == block_start) { + prev_block->size += block_size; + } else { + gbFreeListBlock *tmp = cast(gbFreeListBlock *)block_start; + tmp->size = block_size; + tmp->next = prev_block->next; + prev_block->next = tmp; + + prev_block = tmp; + } + + if (curr_block && (cast(uintptr)curr_block == block_end)) { + prev_block->size += curr_block->size; + prev_block->next = curr_block->next; + } + + fl->allocation_count--; + fl->total_allocated -= block_size; + } break; + + case gbAllocation_FreeAll: + gb_free_list_init(fl, fl->physical_start, fl->total_size); + break; + + case gbAllocation_Resize: + ptr = gb_default_resize_align(gb_free_list_allocator(fl), old_memory, old_size, size, alignment); + break; + } + + return ptr; +} + + + +void gb_scratch_memory_init(gbScratchMemory *s, void *start, isize size) { + s->physical_start = start; + s->total_size = size; + s->alloc_point = start; + s->free_point = start; +} + + +b32 gb_scratch_memory_is_in_use(gbScratchMemory *s, void *ptr) { + if (s->free_point == s->alloc_point) return false; + if (s->alloc_point > s->free_point) + return ptr >= s->free_point && ptr < s->alloc_point; + return ptr >= s->free_point || ptr < s->alloc_point; +} + + +gbAllocator gb_scratch_allocator(gbScratchMemory *s) { + gbAllocator a; + a.proc = gb_scratch_allocator_proc; + a.data = s; + return a; +} + +GB_ALLOCATOR_PROC(gb_scratch_allocator_proc) { + gbScratchMemory *s = cast(gbScratchMemory *)allocator_data; + void *ptr = NULL; + GB_ASSERT_NOT_NULL(s); + + switch (type) { + case gbAllocation_Alloc: { + void *pt = s->alloc_point; + gbAllocationHeader *header = cast(gbAllocationHeader *)pt; + void *data = gb_align_forward(header+1, alignment); + void *end = gb_pointer_add(s->physical_start, s->total_size); + + GB_ASSERT(alignment % 4 == 0); + size = ((size + 3)/4)*4; + pt = gb_pointer_add(pt, size); + + // NOTE(bill): Wrap around + if (pt > end) { + header->size = gb_pointer_diff(header, end) | GB_ISIZE_HIGH_BIT; + pt = s->physical_start; + header = cast(gbAllocationHeader *)pt; + data = gb_align_forward(header+1, alignment); + pt = gb_pointer_add(pt, size); + } + + if (!gb_scratch_memory_is_in_use(s, pt)) { + gb_allocation_header_fill(header, pt, gb_pointer_diff(header, pt)); + s->alloc_point = cast(u8 *)pt; + ptr = data; + } + + if (flags & gbAllocatorFlag_ClearToZero) + gb_zero_size(ptr, size); + } break; + + case gbAllocation_Free: { + if (old_memory) { + void *end = gb_pointer_add(s->physical_start, s->total_size); + if (old_memory < s->physical_start || old_memory >= end) { + GB_ASSERT(false); + } else { + // NOTE(bill): Mark as free + gbAllocationHeader *h = gb_allocation_header(old_memory); + GB_ASSERT((h->size & GB_ISIZE_HIGH_BIT) == 0); + h->size = h->size | GB_ISIZE_HIGH_BIT; + + while (s->free_point != s->alloc_point) { + gbAllocationHeader *header = cast(gbAllocationHeader *)s->free_point; + if ((header->size & GB_ISIZE_HIGH_BIT) == 0) + break; + + s->free_point = gb_pointer_add(s->free_point, h->size & (~GB_ISIZE_HIGH_BIT)); + if (s->free_point == end) + s->free_point = s->physical_start; + } + } + } + } break; + + case gbAllocation_FreeAll: + s->alloc_point = s->physical_start; + s->free_point = s->physical_start; + break; + + case gbAllocation_Resize: + ptr = gb_default_resize_align(gb_scratch_allocator(s), old_memory, old_size, size, alignment); + break; + } + + return ptr; +} + + + + + + +//////////////////////////////////////////////////////////////// +// +// Sorting +// +// + +// TODO(bill): Should I make all the macros local? + +#define GB__COMPARE_PROC(Type) \ +gb_global isize gb__##Type##_cmp_offset; GB_COMPARE_PROC(gb__##Type##_cmp) { \ + Type const p = *cast(Type const *)gb_pointer_add_const(a, gb__##Type##_cmp_offset); \ + Type const q = *cast(Type const *)gb_pointer_add_const(b, gb__##Type##_cmp_offset); \ + return p < q ? -1 : p > q; \ +} \ +GB_COMPARE_PROC_PTR(gb_##Type##_cmp(isize offset)) { \ + gb__##Type##_cmp_offset = offset; \ + return &gb__##Type##_cmp; \ +} + + +GB__COMPARE_PROC(i16); +GB__COMPARE_PROC(i32); +GB__COMPARE_PROC(i64); +GB__COMPARE_PROC(isize); +GB__COMPARE_PROC(f32); +GB__COMPARE_PROC(f64); +GB__COMPARE_PROC(char); + +// NOTE(bill): str_cmp is special as it requires a funny type and funny comparison +gb_global isize gb__str_cmp_offset; GB_COMPARE_PROC(gb__str_cmp) { + char const *p = *cast(char const **)gb_pointer_add_const(a, gb__str_cmp_offset); + char const *q = *cast(char const **)gb_pointer_add_const(b, gb__str_cmp_offset); + return gb_strcmp(p, q); +} +GB_COMPARE_PROC_PTR(gb_str_cmp(isize offset)) { + gb__str_cmp_offset = offset; + return &gb__str_cmp; +} + +#undef GB__COMPARE_PROC + + + + +// TODO(bill): Make user definable? +#define GB__SORT_STACK_SIZE 64 +#define GB__SORT_INSERT_SORT_THRESHOLD 8 + +#define GB__SORT_PUSH(_base, _limit) do { \ + stack_ptr[0] = (_base); \ + stack_ptr[1] = (_limit); \ + stack_ptr += 2; \ +} while (0) + + +#define GB__SORT_POP(_base, _limit) do { \ + stack_ptr -= 2; \ + (_base) = stack_ptr[0]; \ + (_limit) = stack_ptr[1]; \ +} while (0) + + + +void gb_sort(void *base_, isize count, isize size, gbCompareProc cmp) { + u8 *i, *j; + u8 *base = cast(u8 *)base_; + u8 *limit = base + count*size; + isize threshold = GB__SORT_INSERT_SORT_THRESHOLD * size; + + // NOTE(bill): Prepare the stack + u8 *stack[GB__SORT_STACK_SIZE] = {0}; + u8 **stack_ptr = stack; + + for (;;) { + if ((limit-base) > threshold) { + // NOTE(bill): Quick sort + i = base + size; + j = limit - size; + + gb_memswap(((limit-base)/size/2) * size + base, base, size); + if (cmp(i, j) > 0) gb_memswap(i, j, size); + if (cmp(base, j) > 0) gb_memswap(base, j, size); + if (cmp(i, base) > 0) gb_memswap(i, base, size); + + for (;;) { + do i += size; while (cmp(i, base) < 0); + do j -= size; while (cmp(j, base) > 0); + if (i > j) break; + gb_memswap(i, j, size); + } + + gb_memswap(base, j, size); + + if (j - base > limit - i) { + GB__SORT_PUSH(base, j); + base = i; + } else { + GB__SORT_PUSH(i, limit); + limit = j; + } + } else { + // NOTE(bill): Insertion sort + for (j = base, i = j+size; + i < limit; + j = i, i += size) { + for (; cmp(j, j+size) > 0; j -= size) { + gb_memswap(j, j+size, size); + if (j == base) break; + } + } + + if (stack_ptr == stack) break; // NOTE(bill): Sorting is done! + GB__SORT_POP(base, limit); + } + } +} + +#undef GB__SORT_PUSH +#undef GB__SORT_POP + + +#define GB_RADIX_SORT_PROC_GEN(Type) GB_RADIX_SORT_PROC(Type) { \ + Type *source = items; \ + Type *dest = temp; \ + isize byte_index, i, byte_max = 8*gb_size_of(Type); \ + for (byte_index = 0; byte_index < byte_max; byte_index += 8) { \ + isize offsets[256] = {0}; \ + isize total = 0; \ + /* NOTE(bill): First pass - count how many of each key */ \ + for (i = 0; i < count; i++) { \ + Type radix_value = source[i]; \ + Type radix_piece = (radix_value >> byte_index) & 0xff; \ + offsets[radix_piece]++; \ + } \ + /* NOTE(bill): Change counts to offsets */ \ + for (i = 0; i < gb_count_of(offsets); i++) { \ + isize skcount = offsets[i]; \ + offsets[i] = total; \ + total += skcount; \ + } \ + /* NOTE(bill): Second pass - place elements into the right location */ \ + for (i = 0; i < count; i++) { \ + Type radix_value = source[i]; \ + Type radix_piece = (radix_value >> byte_index) & 0xff; \ + dest[offsets[radix_piece]++] = source[i]; \ + } \ + gb_swap(Type *, source, dest); \ + } \ +} + +GB_RADIX_SORT_PROC_GEN(u8); +GB_RADIX_SORT_PROC_GEN(u16); +GB_RADIX_SORT_PROC_GEN(u32); +GB_RADIX_SORT_PROC_GEN(u64); + +gb_inline isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc) { + isize start = 0; + isize end = count; + + while (start < end) { + isize mid = start + (end-start)/2; + isize result = compare_proc(key, cast(u8 *)base + mid*size); + if (result < 0) + end = mid; + else if (result > 0) + start = mid+1; + else + return mid; + } + + return -1; +} + +void gb_shuffle(void *base, isize count, isize size) { + u8 *a; + isize i, j; + gbRandom random; gb_random_init(&random); + + a = cast(u8 *)base + (count-1) * size; + for (i = count; i > 1; i--) { + j = gb_random_gen_isize(&random) % i; + gb_memswap(a, cast(u8 *)base + j*size, size); + a -= size; + } +} + +void gb_reverse(void *base, isize count, isize size) { + isize i, j = count-1; + for (i = 0; i < j; i++, j++) { + gb_memswap(cast(u8 *)base + i*size, cast(u8 *)base + j*size, size); + } +} + + + +//////////////////////////////////////////////////////////////// +// +// Char things +// +// + + + + +gb_inline char gb_char_to_lower(char c) { + if (c >= 'A' && c <= 'Z') + return 'a' + (c - 'A'); + return c; +} + +gb_inline char gb_char_to_upper(char c) { + if (c >= 'a' && c <= 'z') + return 'A' + (c - 'a'); + return c; +} + +gb_inline b32 gb_char_is_space(char c) { + if (c == ' ' || + c == '\t' || + c == '\n' || + c == '\r' || + c == '\f' || + c == '\v') + return true; + return false; +} + +gb_inline b32 gb_char_is_digit(char c) { + if (c >= '0' && c <= '9') + return true; + return false; +} + +gb_inline b32 gb_char_is_hex_digit(char c) { + if (gb_char_is_digit(c) || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) + return true; + return false; +} + +gb_inline b32 gb_char_is_alpha(char c) { + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z')) + return true; + return false; +} + +gb_inline b32 gb_char_is_alphanumeric(char c) { + return gb_char_is_alpha(c) || gb_char_is_digit(c); +} + +gb_inline i32 gb_digit_to_int(char c) { + return gb_char_is_digit(c) ? c - '0' : c - 'W'; +} + +gb_inline i32 gb_hex_digit_to_int(char c) { + if (gb_char_is_digit(c)) + return gb_digit_to_int(c); + else if (gb_is_between(c, 'a', 'f')) + return c - 'a' + 10; + else if (gb_is_between(c, 'A', 'F')) + return c - 'A' + 10; + return -1; +} + + + + +gb_inline void gb_str_to_lower(char *str) { + if (!str) return; + while (*str) { + *str = gb_char_to_lower(*str); + str++; + } +} + +gb_inline void gb_str_to_upper(char *str) { + if (!str) return; + while (*str) { + *str = gb_char_to_upper(*str); + str++; + } +} + + +gb_inline isize gb_strlen(char const *str) { + char const *begin = str; + isize const *w; + if (str == NULL) { + return 0; + } + while (cast(uintptr)str % sizeof(usize)) { + if (!*str) + return str - begin; + str++; + } + w = cast(isize const *)str; + while (!GB__HAS_ZERO(*w)) { + w++; + } + str = cast(char const *)w; + while (*str) { + str++; + } + return str - begin; +} + +gb_inline isize gb_strnlen(char const *str, isize max_len) { + char const *end = cast(char const *)gb_memchr(str, 0, max_len); + if (end) { + return end - str; + } + return max_len; +} + +gb_inline isize gb_utf8_strlen(u8 const *str) { + isize count = 0; + for (; *str; count++) { + u8 c = *str; + isize inc = 0; + if (c < 0x80) inc = 1; + else if ((c & 0xe0) == 0xc0) inc = 2; + else if ((c & 0xf0) == 0xe0) inc = 3; + else if ((c & 0xf8) == 0xf0) inc = 4; + else return -1; + + str += inc; + } + return count; +} + +gb_inline isize gb_utf8_strnlen(u8 const *str, isize max_len) { + isize count = 0; + for (; *str && max_len > 0; count++) { + u8 c = *str; + isize inc = 0; + if (c < 0x80) inc = 1; + else if ((c & 0xe0) == 0xc0) inc = 2; + else if ((c & 0xf0) == 0xe0) inc = 3; + else if ((c & 0xf8) == 0xf0) inc = 4; + else return -1; + + str += inc; + max_len -= inc; + } + return count; +} + + +gb_inline i32 gb_strcmp(char const *s1, char const *s2) { + while (*s1 && (*s1 == *s2)) { + s1++, s2++; + } + return *(u8 *)s1 - *(u8 *)s2; +} + +gb_inline char *gb_strcpy(char *dest, char const *source) { + GB_ASSERT_NOT_NULL(dest); + if (source) { + char *str = dest; + while (*source) *str++ = *source++; + } + return dest; +} + + +gb_inline char *gb_strncpy(char *dest, char const *source, isize len) { + GB_ASSERT_NOT_NULL(dest); + if (source) { + char *str = dest; + while (len > 0 && *source) { + *str++ = *source++; + len--; + } + while (len > 0) { + *str++ = '\0'; + len--; + } + } + return dest; +} + +gb_inline isize gb_strlcpy(char *dest, char const *source, isize len) { + isize result = 0; + GB_ASSERT_NOT_NULL(dest); + if (source) { + char const *source_start = source; + char *str = dest; + while (len > 0 && *source) { + *str++ = *source++; + len--; + } + while (len > 0) { + *str++ = '\0'; + len--; + } + + result = source - source_start; + } + return result; +} + +gb_inline char *gb_strrev(char *str) { + isize len = gb_strlen(str); + char *a = str + 0; + char *b = str + len-1; + len /= 2; + while (len--) { + gb_swap(char, *a, *b); + a++, b--; + } + return str; +} + + + + +gb_inline i32 gb_strncmp(char const *s1, char const *s2, isize len) { + for (; len > 0; + s1++, s2++, len--) { + if (*s1 != *s2) { + return ((s1 < s2) ? -1 : +1); + } else if (*s1 == '\0') { + return 0; + } + } + return 0; +} + + +gb_inline char const *gb_strtok(char *output, char const *src, char const *delimit) { + while (*src && gb_char_first_occurence(delimit, *src) != NULL) { + *output++ = *src++; + } + + *output = 0; + return *src ? src+1 : src; +} + +gb_inline b32 gb_str_has_prefix(char const *str, char const *prefix) { + while (*prefix) { + if (*str++ != *prefix++) { + return false; + } + } + return true; +} + +gb_inline b32 gb_str_has_suffix(char const *str, char const *suffix) { + isize i = gb_strlen(str); + isize j = gb_strlen(suffix); + if (j <= i) { + return gb_strcmp(str+i-j, suffix) == 0; + } + return false; +} + + + + +gb_inline char const *gb_char_first_occurence(char const *s, char c) { + char ch = c; + for (; *s != ch; s++) { + if (*s == '\0') { + return NULL; + } + } + return s; +} + + +gb_inline char const *gb_char_last_occurence(char const *s, char c) { + char const *result = NULL; + do { + if (*s == c) { + result = s; + } + } while (*s++); + + return result; +} + + + +gb_inline void gb_str_concat(char *dest, isize dest_len, + char const *src_a, isize src_a_len, + char const *src_b, isize src_b_len) { + GB_ASSERT(dest_len >= src_a_len+src_b_len+1); + if (dest) { + gb_memcopy(dest, src_a, src_a_len); + gb_memcopy(dest+src_a_len, src_b, src_b_len); + dest[src_a_len+src_b_len] = '\0'; + } +} + + +gb_internal isize gb__scan_i64(char const *text, i32 base, i64 *value) { + char const *text_begin = text; + i64 result = 0; + b32 negative = false; + + if (*text == '-') { + negative = true; + text++; + } + + if (base == 16 && gb_strncmp(text, "0x", 2) == 0) { + text += 2; + } + + for (;;) { + i64 v; + if (gb_char_is_digit(*text)) { + v = *text - '0'; + } else if (base == 16 && gb_char_is_hex_digit(*text)) { + v = gb_hex_digit_to_int(*text); + } else { + break; + } + + result *= base; + result += v; + text++; + } + + if (value) { + if (negative) result = -result; + *value = result; + } + + return (text - text_begin); +} + +gb_internal isize gb__scan_u64(char const *text, i32 base, u64 *value) { + char const *text_begin = text; + u64 result = 0; + + if (base == 16 && gb_strncmp(text, "0x", 2) == 0) { + text += 2; + } + + for (;;) { + u64 v; + if (gb_char_is_digit(*text)) { + v = *text - '0'; + } else if (base == 16 && gb_char_is_hex_digit(*text)) { + v = gb_hex_digit_to_int(*text); + } else { + break; + } + + result *= base; + result += v; + text++; + } + + if (value) *value = result; + return (text - text_begin); +} + + +// TODO(bill): Make better +u64 gb_str_to_u64(char const *str, char **end_ptr, i32 base) { + isize len; + u64 value = 0; + + if (!base) { + if ((gb_strlen(str) > 2) && (gb_strncmp(str, "0x", 2) == 0)) { + base = 16; + } else { + base = 10; + } + } + + len = gb__scan_u64(str, base, &value); + if (end_ptr) *end_ptr = (char *)str + len; + return value; +} + +i64 gb_str_to_i64(char const *str, char **end_ptr, i32 base) { + isize len; + i64 value; + + if (!base) { + if ((gb_strlen(str) > 2) && (gb_strncmp(str, "0x", 2) == 0)) { + base = 16; + } else { + base = 10; + } + } + + len = gb__scan_i64(str, base, &value); + if (end_ptr) *end_ptr = (char *)str + len; + return value; +} + +// TODO(bill): Are these good enough for characters? +gb_global char const gb__num_to_char_table[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "@$"; + +gb_inline void gb_i64_to_str(i64 value, char *string, i32 base) { + char *buf = string; + b32 negative = false; + u64 v; + if (value < 0) { + negative = true; + value = -value; + } + v = cast(u64)value; + if (v != 0) { + while (v > 0) { + *buf++ = gb__num_to_char_table[v % base]; + v /= base; + } + } else { + *buf++ = '0'; + } + if (negative) { + *buf++ = '-'; + } + *buf = '\0'; + gb_strrev(string); +} + + + +gb_inline void gb_u64_to_str(u64 value, char *string, i32 base) { + char *buf = string; + + if (value) { + while (value > 0) { + *buf++ = gb__num_to_char_table[value % base]; + value /= base; + } + } else { + *buf++ = '0'; + } + *buf = '\0'; + + gb_strrev(string); +} + +gb_inline f32 gb_str_to_f32(char const *str, char **end_ptr) { + f64 f = gb_str_to_f64(str, end_ptr); + f32 r = cast(f32)f; + return r; +} + +gb_inline f64 gb_str_to_f64(char const *str, char **end_ptr) { + f64 result, value, sign, scale; + i32 frac; + + while (gb_char_is_space(*str)) { + str++; + } + + sign = 1.0; + if (*str == '-') { + sign = -1.0; + str++; + } else if (*str == '+') { + str++; + } + + for (value = 0.0; gb_char_is_digit(*str); str++) { + value = value * 10.0 + (*str-'0'); + } + + if (*str == '.') { + f64 pow10 = 10.0; + str++; + while (gb_char_is_digit(*str)) { + value += (*str-'0') / pow10; + pow10 *= 10.0; + str++; + } + } + + frac = 0; + scale = 1.0; + if ((*str == 'e') || (*str == 'E')) { + u32 exp; + + str++; + if (*str == '-') { + frac = 1; + str++; + } else if (*str == '+') { + str++; + } + + for (exp = 0; gb_char_is_digit(*str); str++) { + exp = exp * 10 + (*str-'0'); + } + if (exp > 308) exp = 308; + + while (exp >= 50) { scale *= 1e50; exp -= 50; } + while (exp >= 8) { scale *= 1e8; exp -= 8; } + while (exp > 0) { scale *= 10.0; exp -= 1; } + } + + result = sign * (frac ? (value / scale) : (value * scale)); + + if (end_ptr) *end_ptr = cast(char *)str; + + return result; +} + + + + + + + +gb_inline void gb__set_string_length (gbString str, isize len) { GB_STRING_HEADER(str)->length = len; } +gb_inline void gb__set_string_capacity(gbString str, isize cap) { GB_STRING_HEADER(str)->capacity = cap; } + + +gbString gb_string_make_reserve(gbAllocator a, isize capacity) { + isize header_size = gb_size_of(gbStringHeader); + void *ptr = gb_alloc(a, header_size + capacity + 1); + + gbString str; + gbStringHeader *header; + + if (ptr == NULL) return NULL; + gb_zero_size(ptr, header_size + capacity + 1); + + str = cast(char *)ptr + header_size; + header = GB_STRING_HEADER(str); + header->allocator = a; + header->length = 0; + header->capacity = capacity; + str[capacity] = '\0'; + + return str; +} + + +gb_inline gbString gb_string_make(gbAllocator a, char const *str) { + isize len = str ? gb_strlen(str) : 0; + return gb_string_make_length(a, str, len); +} + +gbString gb_string_make_length(gbAllocator a, void const *init_str, isize num_bytes) { + isize header_size = gb_size_of(gbStringHeader); + void *ptr = gb_alloc(a, header_size + num_bytes + 1); + + gbString str; + gbStringHeader *header; + + if (ptr == NULL) return NULL; + if (!init_str) gb_zero_size(ptr, header_size + num_bytes + 1); + + str = cast(char *)ptr + header_size; + header = GB_STRING_HEADER(str); + header->allocator = a; + header->length = num_bytes; + header->capacity = num_bytes; + if (num_bytes && init_str) { + gb_memcopy(str, init_str, num_bytes); + } + str[num_bytes] = '\0'; + + return str; +} + +gb_inline void gb_string_free(gbString str) { + if (str) { + gbStringHeader *header = GB_STRING_HEADER(str); + gb_free(header->allocator, header); + } + +} + +gb_inline gbString gb_string_duplicate(gbAllocator a, gbString const str) { return gb_string_make_length(a, str, gb_string_length(str)); } + +gb_inline isize gb_string_length (gbString const str) { return GB_STRING_HEADER(str)->length; } +gb_inline isize gb_string_capacity(gbString const str) { return GB_STRING_HEADER(str)->capacity; } + +gb_inline isize gb_string_available_space(gbString const str) { + gbStringHeader *h = GB_STRING_HEADER(str); + if (h->capacity > h->length) { + return h->capacity - h->length; + } + return 0; +} + + +gb_inline void gb_string_clear(gbString str) { gb__set_string_length(str, 0); str[0] = '\0'; } + +gb_inline gbString gb_string_append(gbString str, gbString const other) { return gb_string_append_length(str, other, gb_string_length(other)); } + +gbString gb_string_append_length(gbString str, void const *other, isize other_len) { + if (other_len > 0) { + isize curr_len = gb_string_length(str); + + str = gb_string_make_space_for(str, other_len); + if (str == NULL) { + return NULL; + } + + gb_memcopy(str + curr_len, other, other_len); + str[curr_len + other_len] = '\0'; + gb__set_string_length(str, curr_len + other_len); + } + return str; +} + +gb_inline gbString gb_string_appendc(gbString str, char const *other) { + return gb_string_append_length(str, other, gb_strlen(other)); +} + +gbString gb_string_append_rune(gbString str, Rune r) { + if (r >= 0) { + u8 buf[8] = {0}; + isize len = gb_utf8_encode_rune(buf, r); + return gb_string_append_length(str, buf, len); + } + return str; +} + +gbString gb_string_append_fmt(gbString str, char const *fmt, ...) { + isize res; + char buf[4096] = {0}; + va_list va; + va_start(va, fmt); + res = gb_snprintf_va(buf, gb_count_of(buf)-1, fmt, va)-1; + va_end(va); + return gb_string_append_length(str, buf, res); +} + + + +gbString gb_string_set(gbString str, char const *cstr) { + isize len = gb_strlen(cstr); + if (gb_string_capacity(str) < len) { + str = gb_string_make_space_for(str, len - gb_string_length(str)); + if (str == NULL) { + return NULL; + } + } + + gb_memcopy(str, cstr, len); + str[len] = '\0'; + gb__set_string_length(str, len); + + return str; +} + + + +gbString gb_string_make_space_for(gbString str, isize add_len) { + isize available = gb_string_available_space(str); + + // NOTE(bill): Return if there is enough space left + if (available >= add_len) { + return str; + } else { + isize new_len, old_size, new_size; + void *ptr, *new_ptr; + gbAllocator a = GB_STRING_HEADER(str)->allocator; + gbStringHeader *header; + + new_len = gb_string_length(str) + add_len; + ptr = GB_STRING_HEADER(str); + old_size = gb_size_of(gbStringHeader) + gb_string_length(str) + 1; + new_size = gb_size_of(gbStringHeader) + new_len + 1; + + new_ptr = gb_resize(a, ptr, old_size, new_size); + if (new_ptr == NULL) return NULL; + + header = cast(gbStringHeader *)new_ptr; + header->allocator = a; + + str = cast(gbString)(header+1); + gb__set_string_capacity(str, new_len); + + return str; + } +} + +gb_inline isize gb_string_allocation_size(gbString const str) { + isize cap = gb_string_capacity(str); + return gb_size_of(gbStringHeader) + cap; +} + + +gb_inline b32 gb_string_are_equal(gbString const lhs, gbString const rhs) { + isize lhs_len, rhs_len, i; + lhs_len = gb_string_length(lhs); + rhs_len = gb_string_length(rhs); + if (lhs_len != rhs_len) { + return false; + } + + for (i = 0; i < lhs_len; i++) { + if (lhs[i] != rhs[i]) { + return false; + } + } + + return true; +} + + +gbString gb_string_trim(gbString str, char const *cut_set) { + char *start, *end, *start_pos, *end_pos; + isize len; + + start_pos = start = str; + end_pos = end = str + gb_string_length(str) - 1; + + while (start_pos <= end && gb_char_first_occurence(cut_set, *start_pos)) { + start_pos++; + } + while (end_pos > start_pos && gb_char_first_occurence(cut_set, *end_pos)) { + end_pos--; + } + + len = cast(isize)((start_pos > end_pos) ? 0 : ((end_pos - start_pos)+1)); + + if (str != start_pos) + gb_memmove(str, start_pos, len); + str[len] = '\0'; + + gb__set_string_length(str, len); + + return str; +} + +gb_inline gbString gb_string_trim_space(gbString str) { return gb_string_trim(str, " \t\r\n\v\f"); } + + + + +//////////////////////////////////////////////////////////////// +// +// Windows UTF-8 Handling +// +// + + +u16 *gb_utf8_to_ucs2(u16 *buffer, isize len, u8 const *str) { + Rune c; + isize i = 0; + len--; + while (*str) { + if (i >= len) + return NULL; + if (!(*str & 0x80)) { + buffer[i++] = *str++; + } else if ((*str & 0xe0) == 0xc0) { + if (*str < 0xc2) + return NULL; + c = (*str++ & 0x1f) << 6; + if ((*str & 0xc0) != 0x80) + return NULL; + buffer[i++] = cast(u16)(c + (*str++ & 0x3f)); + } else if ((*str & 0xf0) == 0xe0) { + if (*str == 0xe0 && + (str[1] < 0xa0 || str[1] > 0xbf)) + return NULL; + if (*str == 0xed && str[1] > 0x9f) // str[1] < 0x80 is checked below + return NULL; + c = (*str++ & 0x0f) << 12; + if ((*str & 0xc0) != 0x80) + return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) + return NULL; + buffer[i++] = cast(u16)(c + (*str++ & 0x3f)); + } else if ((*str & 0xf8) == 0xf0) { + if (*str > 0xf4) + return NULL; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) + return NULL; + if (*str == 0xf4 && str[1] > 0x8f) // str[1] < 0x80 is checked below + return NULL; + c = (*str++ & 0x07) << 18; + if ((*str & 0xc0) != 0x80) + return NULL; + c += (*str++ & 0x3f) << 12; + if ((*str & 0xc0) != 0x80) + return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) + return NULL; + c += (*str++ & 0x3f); + // UTF-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xfffff800) == 0xd800) + return NULL; + if (c >= 0x10000) { + c -= 0x10000; + if (i+2 > len) + return NULL; + buffer[i++] = 0xd800 | (0x3ff & (c>>10)); + buffer[i++] = 0xdc00 | (0x3ff & (c )); + } + } else { + return NULL; + } + } + buffer[i] = 0; + return buffer; +} + +u8 *gb_ucs2_to_utf8(u8 *buffer, isize len, u16 const *str) { + isize i = 0; + len--; + while (*str) { + if (*str < 0x80) { + if (i+1 > len) + return NULL; + buffer[i++] = (char) *str++; + } else if (*str < 0x800) { + if (i+2 > len) + return NULL; + buffer[i++] = cast(char)(0xc0 + (*str >> 6)); + buffer[i++] = cast(char)(0x80 + (*str & 0x3f)); + str += 1; + } else if (*str >= 0xd800 && *str < 0xdc00) { + Rune c; + if (i+4 > len) + return NULL; + c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000; + buffer[i++] = cast(char)(0xf0 + (c >> 18)); + buffer[i++] = cast(char)(0x80 + ((c >> 12) & 0x3f)); + buffer[i++] = cast(char)(0x80 + ((c >> 6) & 0x3f)); + buffer[i++] = cast(char)(0x80 + ((c ) & 0x3f)); + str += 2; + } else if (*str >= 0xdc00 && *str < 0xe000) { + return NULL; + } else { + if (i+3 > len) + return NULL; + buffer[i++] = 0xe0 + (*str >> 12); + buffer[i++] = 0x80 + ((*str >> 6) & 0x3f); + buffer[i++] = 0x80 + ((*str ) & 0x3f); + str += 1; + } + } + buffer[i] = 0; + return buffer; +} + +u16 *gb_utf8_to_ucs2_buf(u8 const *str) { // NOTE(bill): Uses locally persisting buffer + gb_local_persist u16 buf[4096]; + return gb_utf8_to_ucs2(buf, gb_count_of(buf), str); +} + +u8 *gb_ucs2_to_utf8_buf(u16 const *str) { // NOTE(bill): Uses locally persisting buffer + gb_local_persist u8 buf[4096]; + return gb_ucs2_to_utf8(buf, gb_count_of(buf), str); +} + + + +gb_global u8 const gb__utf8_first[256] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7F + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8F + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9F + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xA0-0xAF + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xB0-0xBF + 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xC0-0xCF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xD0-0xDF + 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xE0-0xEF + 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xF0-0xFF +}; + + +typedef struct gbUtf8AcceptRange { + u8 lo, hi; +} gbUtf8AcceptRange; + +gb_global gbUtf8AcceptRange const gb__utf8_accept_ranges[] = { + {0x80, 0xbf}, + {0xa0, 0xbf}, + {0x80, 0x9f}, + {0x90, 0xbf}, + {0x80, 0x8f}, +}; + + +isize gb_utf8_decode(u8 const *str, isize str_len, Rune *codepoint_out) { + isize width = 0; + Rune codepoint = GB_RUNE_INVALID; + + if (str_len > 0) { + u8 s0 = str[0]; + u8 x = gb__utf8_first[s0], sz; + u8 b1, b2, b3; + gbUtf8AcceptRange accept; + if (x >= 0xf0) { + Rune mask = (cast(Rune)x << 31) >> 31; + codepoint = (cast(Rune)s0 & (~mask)) | (GB_RUNE_INVALID & mask); + width = 1; + goto end; + } + if (s0 < 0x80) { + codepoint = s0; + width = 1; + goto end; + } + + sz = x&7; + accept = gb__utf8_accept_ranges[x>>4]; + if (str_len < gb_size_of(sz)) + goto invalid_codepoint; + + b1 = str[1]; + if (b1 < accept.lo || accept.hi < b1) + goto invalid_codepoint; + + if (sz == 2) { + codepoint = (cast(Rune)s0&0x1f)<<6 | (cast(Rune)b1&0x3f); + width = 2; + goto end; + } + + b2 = str[2]; + if (!gb_is_between(b2, 0x80, 0xbf)) + goto invalid_codepoint; + + if (sz == 3) { + codepoint = (cast(Rune)s0&0x1f)<<12 | (cast(Rune)b1&0x3f)<<6 | (cast(Rune)b2&0x3f); + width = 3; + goto end; + } + + b3 = str[3]; + if (!gb_is_between(b3, 0x80, 0xbf)) + goto invalid_codepoint; + + codepoint = (cast(Rune)s0&0x07)<<18 | (cast(Rune)b1&0x3f)<<12 | (cast(Rune)b2&0x3f)<<6 | (cast(Rune)b3&0x3f); + width = 4; + goto end; + + invalid_codepoint: + codepoint = GB_RUNE_INVALID; + width = 1; + } + +end: + if (codepoint_out) *codepoint_out = codepoint; + return width; +} + +isize gb_utf8_codepoint_size(u8 const *str, isize str_len) { + isize i = 0; + for (; i < str_len && str[i]; i++) { + if ((str[i] & 0xc0) != 0x80) + break; + } + return i+1; +} + +isize gb_utf8_encode_rune(u8 buf[4], Rune r) { + u32 i = cast(u32)r; + u8 mask = 0x3f; + if (i <= (1<<7)-1) { + buf[0] = cast(u8)r; + return 1; + } + if (i <= (1<<11)-1) { + buf[0] = 0xc0 | cast(u8)(r>>6); + buf[1] = 0x80 | (cast(u8)(r)&mask); + return 2; + } + + // Invalid or Surrogate range + if (i > GB_RUNE_MAX || + gb_is_between(i, 0xd800, 0xdfff)) { + r = GB_RUNE_INVALID; + + buf[0] = 0xe0 | cast(u8)(r>>12); + buf[1] = 0x80 | (cast(u8)(r>>6)&mask); + buf[2] = 0x80 | (cast(u8)(r)&mask); + return 3; + } + + if (i <= (1<<16)-1) { + buf[0] = 0xe0 | cast(u8)(r>>12); + buf[1] = 0x80 | (cast(u8)(r>>6)&mask); + buf[2] = 0x80 | (cast(u8)(r)&mask); + return 3; + } + + buf[0] = 0xf0 | cast(u8)(r>>18); + buf[1] = 0x80 | (cast(u8)(r>>12)&mask); + buf[2] = 0x80 | (cast(u8)(r>>6)&mask); + buf[3] = 0x80 | (cast(u8)(r)&mask); + return 4; +} + + + + +//////////////////////////////////////////////////////////////// +// +// gbArray +// +// + + +gb_no_inline void *gb__array_set_capacity(void *array, isize capacity, isize element_size) { + gbArrayHeader *h = GB_ARRAY_HEADER(array); + + GB_ASSERT(element_size > 0); + + if (capacity == h->capacity) + return array; + + if (capacity < h->count) { + if (h->capacity < capacity) { + isize new_capacity = GB_ARRAY_GROW_FORMULA(h->capacity); + if (new_capacity < capacity) + new_capacity = capacity; + gb__array_set_capacity(array, new_capacity, element_size); + } + h->count = capacity; + } + + { + isize size = gb_size_of(gbArrayHeader) + element_size*capacity; + gbArrayHeader *nh = cast(gbArrayHeader *)gb_alloc(h->allocator, size); + gb_memmove(nh, h, gb_size_of(gbArrayHeader) + element_size*h->count); + nh->allocator = h->allocator; + nh->count = h->count; + nh->capacity = capacity; + gb_free(h->allocator, h); + return nh+1; + } +} + + +//////////////////////////////////////////////////////////////// +// +// Hashing functions +// +// + +u32 gb_adler32(void const *data, isize len) { + u32 const MOD_ALDER = 65521; + u32 a = 1, b = 0; + isize i, block_len; + u8 const *bytes = cast(u8 const *)data; + + block_len = len % 5552; + + while (len) { + for (i = 0; i+7 < block_len; i += 8) { + a += bytes[0], b += a; + a += bytes[1], b += a; + a += bytes[2], b += a; + a += bytes[3], b += a; + a += bytes[4], b += a; + a += bytes[5], b += a; + a += bytes[6], b += a; + a += bytes[7], b += a; + + bytes += 8; + } + for (; i < block_len; i++) { + a += *bytes++, b += a; + } + + a %= MOD_ALDER, b %= MOD_ALDER; + len -= block_len; + block_len = 5552; + } + + return (b << 16) | a; +} + + +gb_global u32 const GB__CRC32_TABLE[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + +gb_global u64 const GB__CRC64_TABLE[256] = { + 0x0000000000000000ull, 0x42f0e1eba9ea3693ull, 0x85e1c3d753d46d26ull, 0xc711223cfa3e5bb5ull, + 0x493366450e42ecdfull, 0x0bc387aea7a8da4cull, 0xccd2a5925d9681f9ull, 0x8e224479f47cb76aull, + 0x9266cc8a1c85d9beull, 0xd0962d61b56fef2dull, 0x17870f5d4f51b498ull, 0x5577eeb6e6bb820bull, + 0xdb55aacf12c73561ull, 0x99a54b24bb2d03f2ull, 0x5eb4691841135847ull, 0x1c4488f3e8f96ed4ull, + 0x663d78ff90e185efull, 0x24cd9914390bb37cull, 0xe3dcbb28c335e8c9ull, 0xa12c5ac36adfde5aull, + 0x2f0e1eba9ea36930ull, 0x6dfeff5137495fa3ull, 0xaaefdd6dcd770416ull, 0xe81f3c86649d3285ull, + 0xf45bb4758c645c51ull, 0xb6ab559e258e6ac2ull, 0x71ba77a2dfb03177ull, 0x334a9649765a07e4ull, + 0xbd68d2308226b08eull, 0xff9833db2bcc861dull, 0x388911e7d1f2dda8ull, 0x7a79f00c7818eb3bull, + 0xcc7af1ff21c30bdeull, 0x8e8a101488293d4dull, 0x499b3228721766f8ull, 0x0b6bd3c3dbfd506bull, + 0x854997ba2f81e701ull, 0xc7b97651866bd192ull, 0x00a8546d7c558a27ull, 0x4258b586d5bfbcb4ull, + 0x5e1c3d753d46d260ull, 0x1cecdc9e94ace4f3ull, 0xdbfdfea26e92bf46ull, 0x990d1f49c77889d5ull, + 0x172f5b3033043ebfull, 0x55dfbadb9aee082cull, 0x92ce98e760d05399ull, 0xd03e790cc93a650aull, + 0xaa478900b1228e31ull, 0xe8b768eb18c8b8a2ull, 0x2fa64ad7e2f6e317ull, 0x6d56ab3c4b1cd584ull, + 0xe374ef45bf6062eeull, 0xa1840eae168a547dull, 0x66952c92ecb40fc8ull, 0x2465cd79455e395bull, + 0x3821458aada7578full, 0x7ad1a461044d611cull, 0xbdc0865dfe733aa9ull, 0xff3067b657990c3aull, + 0x711223cfa3e5bb50ull, 0x33e2c2240a0f8dc3ull, 0xf4f3e018f031d676ull, 0xb60301f359dbe0e5ull, + 0xda050215ea6c212full, 0x98f5e3fe438617bcull, 0x5fe4c1c2b9b84c09ull, 0x1d14202910527a9aull, + 0x93366450e42ecdf0ull, 0xd1c685bb4dc4fb63ull, 0x16d7a787b7faa0d6ull, 0x5427466c1e109645ull, + 0x4863ce9ff6e9f891ull, 0x0a932f745f03ce02ull, 0xcd820d48a53d95b7ull, 0x8f72eca30cd7a324ull, + 0x0150a8daf8ab144eull, 0x43a04931514122ddull, 0x84b16b0dab7f7968ull, 0xc6418ae602954ffbull, + 0xbc387aea7a8da4c0ull, 0xfec89b01d3679253ull, 0x39d9b93d2959c9e6ull, 0x7b2958d680b3ff75ull, + 0xf50b1caf74cf481full, 0xb7fbfd44dd257e8cull, 0x70eadf78271b2539ull, 0x321a3e938ef113aaull, + 0x2e5eb66066087d7eull, 0x6cae578bcfe24bedull, 0xabbf75b735dc1058ull, 0xe94f945c9c3626cbull, + 0x676dd025684a91a1ull, 0x259d31cec1a0a732ull, 0xe28c13f23b9efc87ull, 0xa07cf2199274ca14ull, + 0x167ff3eacbaf2af1ull, 0x548f120162451c62ull, 0x939e303d987b47d7ull, 0xd16ed1d631917144ull, + 0x5f4c95afc5edc62eull, 0x1dbc74446c07f0bdull, 0xdaad56789639ab08ull, 0x985db7933fd39d9bull, + 0x84193f60d72af34full, 0xc6e9de8b7ec0c5dcull, 0x01f8fcb784fe9e69ull, 0x43081d5c2d14a8faull, + 0xcd2a5925d9681f90ull, 0x8fdab8ce70822903ull, 0x48cb9af28abc72b6ull, 0x0a3b7b1923564425ull, + 0x70428b155b4eaf1eull, 0x32b26afef2a4998dull, 0xf5a348c2089ac238ull, 0xb753a929a170f4abull, + 0x3971ed50550c43c1ull, 0x7b810cbbfce67552ull, 0xbc902e8706d82ee7ull, 0xfe60cf6caf321874ull, + 0xe224479f47cb76a0ull, 0xa0d4a674ee214033ull, 0x67c58448141f1b86ull, 0x253565a3bdf52d15ull, + 0xab1721da49899a7full, 0xe9e7c031e063acecull, 0x2ef6e20d1a5df759ull, 0x6c0603e6b3b7c1caull, + 0xf6fae5c07d3274cdull, 0xb40a042bd4d8425eull, 0x731b26172ee619ebull, 0x31ebc7fc870c2f78ull, + 0xbfc9838573709812ull, 0xfd39626eda9aae81ull, 0x3a28405220a4f534ull, 0x78d8a1b9894ec3a7ull, + 0x649c294a61b7ad73ull, 0x266cc8a1c85d9be0ull, 0xe17dea9d3263c055ull, 0xa38d0b769b89f6c6ull, + 0x2daf4f0f6ff541acull, 0x6f5faee4c61f773full, 0xa84e8cd83c212c8aull, 0xeabe6d3395cb1a19ull, + 0x90c79d3fedd3f122ull, 0xd2377cd44439c7b1ull, 0x15265ee8be079c04ull, 0x57d6bf0317edaa97ull, + 0xd9f4fb7ae3911dfdull, 0x9b041a914a7b2b6eull, 0x5c1538adb04570dbull, 0x1ee5d94619af4648ull, + 0x02a151b5f156289cull, 0x4051b05e58bc1e0full, 0x87409262a28245baull, 0xc5b073890b687329ull, + 0x4b9237f0ff14c443ull, 0x0962d61b56fef2d0ull, 0xce73f427acc0a965ull, 0x8c8315cc052a9ff6ull, + 0x3a80143f5cf17f13ull, 0x7870f5d4f51b4980ull, 0xbf61d7e80f251235ull, 0xfd913603a6cf24a6ull, + 0x73b3727a52b393ccull, 0x31439391fb59a55full, 0xf652b1ad0167feeaull, 0xb4a25046a88dc879ull, + 0xa8e6d8b54074a6adull, 0xea16395ee99e903eull, 0x2d071b6213a0cb8bull, 0x6ff7fa89ba4afd18ull, + 0xe1d5bef04e364a72ull, 0xa3255f1be7dc7ce1ull, 0x64347d271de22754ull, 0x26c49cccb40811c7ull, + 0x5cbd6cc0cc10fafcull, 0x1e4d8d2b65facc6full, 0xd95caf179fc497daull, 0x9bac4efc362ea149ull, + 0x158e0a85c2521623ull, 0x577eeb6e6bb820b0ull, 0x906fc95291867b05ull, 0xd29f28b9386c4d96ull, + 0xcedba04ad0952342ull, 0x8c2b41a1797f15d1ull, 0x4b3a639d83414e64ull, 0x09ca82762aab78f7ull, + 0x87e8c60fded7cf9dull, 0xc51827e4773df90eull, 0x020905d88d03a2bbull, 0x40f9e43324e99428ull, + 0x2cffe7d5975e55e2ull, 0x6e0f063e3eb46371ull, 0xa91e2402c48a38c4ull, 0xebeec5e96d600e57ull, + 0x65cc8190991cb93dull, 0x273c607b30f68faeull, 0xe02d4247cac8d41bull, 0xa2dda3ac6322e288ull, + 0xbe992b5f8bdb8c5cull, 0xfc69cab42231bacfull, 0x3b78e888d80fe17aull, 0x7988096371e5d7e9ull, + 0xf7aa4d1a85996083ull, 0xb55aacf12c735610ull, 0x724b8ecdd64d0da5ull, 0x30bb6f267fa73b36ull, + 0x4ac29f2a07bfd00dull, 0x08327ec1ae55e69eull, 0xcf235cfd546bbd2bull, 0x8dd3bd16fd818bb8ull, + 0x03f1f96f09fd3cd2ull, 0x41011884a0170a41ull, 0x86103ab85a2951f4ull, 0xc4e0db53f3c36767ull, + 0xd8a453a01b3a09b3ull, 0x9a54b24bb2d03f20ull, 0x5d45907748ee6495ull, 0x1fb5719ce1045206ull, + 0x919735e51578e56cull, 0xd367d40ebc92d3ffull, 0x1476f63246ac884aull, 0x568617d9ef46bed9ull, + 0xe085162ab69d5e3cull, 0xa275f7c11f7768afull, 0x6564d5fde549331aull, 0x279434164ca30589ull, + 0xa9b6706fb8dfb2e3ull, 0xeb46918411358470ull, 0x2c57b3b8eb0bdfc5ull, 0x6ea7525342e1e956ull, + 0x72e3daa0aa188782ull, 0x30133b4b03f2b111ull, 0xf7021977f9cceaa4ull, 0xb5f2f89c5026dc37ull, + 0x3bd0bce5a45a6b5dull, 0x79205d0e0db05dceull, 0xbe317f32f78e067bull, 0xfcc19ed95e6430e8ull, + 0x86b86ed5267cdbd3ull, 0xc4488f3e8f96ed40ull, 0x0359ad0275a8b6f5ull, 0x41a94ce9dc428066ull, + 0xcf8b0890283e370cull, 0x8d7be97b81d4019full, 0x4a6acb477bea5a2aull, 0x089a2aacd2006cb9ull, + 0x14dea25f3af9026dull, 0x562e43b4931334feull, 0x913f6188692d6f4bull, 0xd3cf8063c0c759d8ull, + 0x5dedc41a34bbeeb2ull, 0x1f1d25f19d51d821ull, 0xd80c07cd676f8394ull, 0x9afce626ce85b507ull, +}; + +u32 gb_crc32(void const *data, isize len) { + isize remaining; + u32 result = ~(cast(u32)0); + u8 const *c = cast(u8 const *)data; + for (remaining = len; remaining--; c++) { + result = (result >> 8) ^ (GB__CRC32_TABLE[(result ^ *c) & 0xff]); + } + return ~result; +} + +u64 gb_crc64(void const *data, isize len) { + isize remaining; + u64 result = ~(cast(u64)0); + u8 const *c = cast(u8 const *)data; + for (remaining = len; remaining--; c++) { + result = (result >> 8) ^ (GB__CRC64_TABLE[(result ^ *c) & 0xff]); + } + return ~result; +} + +u32 gb_fnv32(void const *data, isize len) { + isize i; + u32 h = 0x811c9dc5; + u8 const *c = cast(u8 const *)data; + + for (i = 0; i < len; i++) { + h = (h * 0x01000193) ^ c[i]; + } + + return h; +} + +u64 gb_fnv64(void const *data, isize len) { + isize i; + u64 h = 0xcbf29ce484222325ull; + u8 const *c = cast(u8 const *)data; + + for (i = 0; i < len; i++) { + h = (h * 0x100000001b3ll) ^ c[i]; + } + + return h; +} + +u32 gb_fnv32a(void const *data, isize len) { + isize i; + u32 h = 0x811c9dc5; + u8 const *c = cast(u8 const *)data; + + for (i = 0; i < len; i++) { + h = (h ^ c[i]) * 0x01000193; + } + + return h; +} + +u64 gb_fnv64a(void const *data, isize len) { + isize i; + u64 h = 0xcbf29ce484222325ull; + u8 const *c = cast(u8 const *)data; + + for (i = 0; i < len; i++) { + h = (h ^ c[i]) * 0x100000001b3ll; + } + + return h; +} + +gb_inline u32 gb_murmur32(void const *data, isize len) { return gb_murmur32_seed(data, len, 0x9747b28c); } +gb_inline u64 gb_murmur64(void const *data, isize len) { return gb_murmur64_seed(data, len, 0x9747b28c); } + +u32 gb_murmur32_seed(void const *data, isize len, u32 seed) { + u32 const c1 = 0xcc9e2d51; + u32 const c2 = 0x1b873593; + u32 const r1 = 15; + u32 const r2 = 13; + u32 const m = 5; + u32 const n = 0xe6546b64; + + isize i, nblocks = len / 4; + u32 hash = seed, k1 = 0; + u32 const *blocks = cast(u32 const*)data; + u8 const *tail = cast(u8 const *)(data) + nblocks*4; + + for (i = 0; i < nblocks; i++) { + u32 k = blocks[i]; + k *= c1; + k = (k << r1) | (k >> (32 - r1)); + k *= c2; + + hash ^= k; + hash = ((hash << r2) | (hash >> (32 - r2))) * m + n; + } + + switch (len & 3) { + case 3: + k1 ^= tail[2] << 16; + case 2: + k1 ^= tail[1] << 8; + case 1: + k1 ^= tail[0]; + + k1 *= c1; + k1 = (k1 << r1) | (k1 >> (32 - r1)); + k1 *= c2; + hash ^= k1; + } + + hash ^= len; + hash ^= (hash >> 16); + hash *= 0x85ebca6b; + hash ^= (hash >> 13); + hash *= 0xc2b2ae35; + hash ^= (hash >> 16); + + return hash; +} + +u64 gb_murmur64_seed(void const *data_, isize len, u64 seed) { +#if defined(GB_ARCH_64_BIT) + u64 const m = 0xc6a4a7935bd1e995ULL; + i32 const r = 47; + + u64 h = seed ^ (len * m); + + u64 const *data = cast(u64 const *)data_; + u8 const *data2 = cast(u8 const *)data_; + u64 const* end = data + (len / 8); + + while (data != end) { + u64 k = *data++; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + switch (len & 7) { + case 7: h ^= cast(u64)(data2[6]) << 48; + case 6: h ^= cast(u64)(data2[5]) << 40; + case 5: h ^= cast(u64)(data2[4]) << 32; + case 4: h ^= cast(u64)(data2[3]) << 24; + case 3: h ^= cast(u64)(data2[2]) << 16; + case 2: h ^= cast(u64)(data2[1]) << 8; + case 1: h ^= cast(u64)(data2[0]); + h *= m; + }; + + h ^= h >> r; + h *= m; + h ^= h >> r; + + return h; +#else + u64 h; + u32 const m = 0x5bd1e995; + i32 const r = 24; + + u32 h1 = cast(u32)(seed) ^ cast(u32)(len); + u32 h2 = cast(u32)(seed >> 32); + + u32 const *data = cast(u32 const *)data_; + + while (len >= 8) { + u32 k1, k2; + k1 = *data++; + k1 *= m; + k1 ^= k1 >> r; + k1 *= m; + h1 *= m; + h1 ^= k1; + len -= 4; + + k2 = *data++; + k2 *= m; + k2 ^= k2 >> r; + k2 *= m; + h2 *= m; + h2 ^= k2; + len -= 4; + } + + if (len >= 4) { + u32 k1 = *data++; + k1 *= m; + k1 ^= k1 >> r; + k1 *= m; + h1 *= m; + h1 ^= k1; + len -= 4; + } + + switch (len) { + case 3: h2 ^= (cast(u8 const *)data)[2] << 16; + case 2: h2 ^= (cast(u8 const *)data)[1] << 8; + case 1: h2 ^= (cast(u8 const *)data)[0] << 0; + h2 *= m; + }; + + h1 ^= h2 >> 18; + h1 *= m; + h2 ^= h1 >> 22; + h2 *= m; + h1 ^= h2 >> 17; + h1 *= m; + h2 ^= h1 >> 19; + h2 *= m; + + h = h1; + h = (h << 32) | h2; + + return h; +#endif +} + + + + + + + +//////////////////////////////////////////////////////////////// +// +// File Handling +// +// + +#if defined(GB_SYSTEM_WINDOWS) + + gb_internal wchar_t *gb__alloc_utf8_to_ucs2(gbAllocator a, char const *text, isize *w_len_) { + wchar_t *w_text = NULL; + isize len = 0, w_len = 0, w_len1 = 0; + if (text == NULL) { + if (w_len_) *w_len_ = w_len; + return NULL; + } + len = gb_strlen(text); + if (len == 0) { + if (w_len_) *w_len_ = w_len; + return NULL; + } + w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, cast(int)len, NULL, 0); + if (w_len == 0) { + if (w_len_) *w_len_ = w_len; + return NULL; + } + w_text = gb_alloc_array(a, wchar_t, w_len+1); + w_len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, cast(int)len, w_text, cast(int)w_len); + if (w_len1 == 0) { + gb_free(a, w_text); + if (w_len_) *w_len_ = 0; + return NULL; + } + w_text[w_len] = 0; + if (w_len_) *w_len_ = w_len; + return w_text; + } + + gb_internal GB_FILE_SEEK_PROC(gb__win32_file_seek) { + LARGE_INTEGER li_offset; + li_offset.QuadPart = offset; + if (!SetFilePointerEx(fd.p, li_offset, &li_offset, whence)) { + return false; + } + + if (new_offset) *new_offset = li_offset.QuadPart; + return true; + } + + gb_internal GB_FILE_READ_AT_PROC(gb__win32_file_read) { + b32 result = false; + DWORD size_ = cast(DWORD)(size > I32_MAX ? I32_MAX : size); + DWORD bytes_read_; + gb__win32_file_seek(fd, offset, gbSeekWhence_Begin, NULL); + if (ReadFile(fd.p, buffer, size_, &bytes_read_, NULL)) { + if (bytes_read) *bytes_read = bytes_read_; + result = true; + } + + return result; + } + + gb_internal GB_FILE_WRITE_AT_PROC(gb__win32_file_write) { + DWORD size_ = cast(DWORD)(size > I32_MAX ? I32_MAX : size); + DWORD bytes_written_; + gb__win32_file_seek(fd, offset, gbSeekWhence_Begin, NULL); + if (WriteFile(fd.p, buffer, size_, &bytes_written_, NULL)) { + if (bytes_written) *bytes_written = bytes_written_; + return true; + } + return false; + } + + gb_internal GB_FILE_CLOSE_PROC(gb__win32_file_close) { + CloseHandle(fd.p); + } + + gbFileOperations const gbDefaultFileOperations = { + gb__win32_file_read, + gb__win32_file_write, + gb__win32_file_seek, + gb__win32_file_close + }; + + gb_no_inline GB_FILE_OPEN_PROC(gb__win32_file_open) { + DWORD desired_access; + DWORD creation_disposition; + void *handle; + wchar_t *w_text; + + switch (mode & gbFileMode_Modes) { + case gbFileMode_Read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + break; + case gbFileMode_Write: + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case gbFileMode_Append: + desired_access = GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + case gbFileMode_Read | gbFileMode_Rw: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + break; + case gbFileMode_Write | gbFileMode_Rw: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case gbFileMode_Append | gbFileMode_Rw: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + default: + GB_PANIC("Invalid file mode"); + return gbFileError_Invalid; + } + + w_text = gb__alloc_utf8_to_ucs2(gb_heap_allocator(), filename, NULL); + if (w_text == NULL) { + return gbFileError_InvalidFilename; + } + handle = CreateFileW(w_text, + desired_access, + FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + + gb_free(gb_heap_allocator(), w_text); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + switch (err) { + case ERROR_FILE_NOT_FOUND: return gbFileError_NotExists; + case ERROR_FILE_EXISTS: return gbFileError_Exists; + case ERROR_ALREADY_EXISTS: return gbFileError_Exists; + case ERROR_ACCESS_DENIED: return gbFileError_Permission; + } + return gbFileError_Invalid; + } + + if (mode & gbFileMode_Append) { + LARGE_INTEGER offset = {0}; + if (!SetFilePointerEx(handle, offset, NULL, gbSeekWhence_End)) { + CloseHandle(handle); + return gbFileError_Invalid; + } + } + + fd->p = handle; + *ops = gbDefaultFileOperations; + return gbFileError_None; + } + +#else // POSIX + gb_internal GB_FILE_SEEK_PROC(gb__posix_file_seek) { + #if defined(GB_SYSTEM_OSX) + i64 res = lseek(fd.i, offset, whence); + #else + i64 res = lseek64(fd.i, offset, whence); + #endif + if (res < 0) return false; + if (new_offset) *new_offset = res; + return true; + } + + gb_internal GB_FILE_READ_AT_PROC(gb__posix_file_read) { + isize res = pread(fd.i, buffer, size, offset); + if (res < 0) return false; + if (bytes_read) *bytes_read = res; + return true; + } + + gb_internal GB_FILE_WRITE_AT_PROC(gb__posix_file_write) { + isize res; + i64 curr_offset = 0; + gb__posix_file_seek(fd, 0, gbSeekWhence_Current, &curr_offset); + if (curr_offset == offset) { + // NOTE(bill): Writing to stdout et al. doesn't like pwrite for numerous reasons + res = write(cast(int)fd.i, buffer, size); + } else { + res = pwrite(cast(int)fd.i, buffer, size, offset); + } + if (res < 0) return false; + if (bytes_written) *bytes_written = res; + return true; + } + + + gb_internal GB_FILE_CLOSE_PROC(gb__posix_file_close) { + close(fd.i); + } + + gbFileOperations const gbDefaultFileOperations = { + gb__posix_file_read, + gb__posix_file_write, + gb__posix_file_seek, + gb__posix_file_close + }; + + gb_no_inline GB_FILE_OPEN_PROC(gb__posix_file_open) { + i32 os_mode; + switch (mode & gbFileMode_Modes) { + case gbFileMode_Read: + os_mode = O_RDONLY; + break; + case gbFileMode_Write: + os_mode = O_WRONLY | O_CREAT | O_TRUNC; + break; + case gbFileMode_Append: + os_mode = O_WRONLY | O_APPEND | O_CREAT; + break; + case gbFileMode_Read | gbFileMode_Rw: + os_mode = O_RDWR; + break; + case gbFileMode_Write | gbFileMode_Rw: + os_mode = O_RDWR | O_CREAT | O_TRUNC; + break; + case gbFileMode_Append | gbFileMode_Rw: + os_mode = O_RDWR | O_APPEND | O_CREAT; + break; + default: + GB_PANIC("Invalid file mode"); + return gbFileError_Invalid; + } + + fd->i = open(filename, os_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd->i < 0) { + // TODO(bill): More file errors + return gbFileError_Invalid; + } + + *ops = gbDefaultFileOperations; + return gbFileError_None; + } + +#endif + + + +gbFileError gb_file_new(gbFile *f, gbFileDescriptor fd, gbFileOperations ops, char const *filename) { + gbFileError err = gbFileError_None; + isize len = gb_strlen(filename); + + // gb_printf_err("gb_file_new: %s\n", filename); + + f->ops = ops; + f->fd = fd; + f->filename = gb_alloc_array(gb_heap_allocator(), char, len+1); + gb_memcopy(cast(char *)f->filename, cast(char *)filename, len+1); + f->last_write_time = gb_file_last_write_time(f->filename); + + return err; +} + + + +gbFileError gb_file_open_mode(gbFile *f, gbFileMode mode, char const *filename) { + gbFileError err; +#if defined(GB_SYSTEM_WINDOWS) + err = gb__win32_file_open(&f->fd, &f->ops, mode, filename); +#else + err = gb__posix_file_open(&f->fd, &f->ops, mode, filename); +#endif + if (err == gbFileError_None) { + return gb_file_new(f, f->fd, f->ops, filename); + } + return err; +} + +gbFileError gb_file_close(gbFile *f) { + if (f == NULL) { + return gbFileError_Invalid; + } + +#if defined(GB_COMPILER_MSVC) + if (f->filename != NULL) { + gb_free(gb_heap_allocator(), cast(char *)f->filename); + } +#else + // TODO HACK(bill): Memory Leak!!! +#endif + +#if defined(GB_SYSTEM_WINDOWS) + if (f->fd.p == INVALID_HANDLE_VALUE) { + return gbFileError_Invalid; + } +#else + if (f->fd.i < 0) { + return gbFileError_Invalid; + } +#endif + + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + f->ops.close(f->fd); + + return gbFileError_None; +} + +gb_inline b32 gb_file_read_at_check(gbFile *f, void *buffer, isize size, i64 offset, isize *bytes_read) { + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + return f->ops.read_at(f->fd, buffer, size, offset, bytes_read); +} + +gb_inline b32 gb_file_write_at_check(gbFile *f, void const *buffer, isize size, i64 offset, isize *bytes_written) { + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + return f->ops.write_at(f->fd, buffer, size, offset, bytes_written); +} + + +gb_inline b32 gb_file_read_at(gbFile *f, void *buffer, isize size, i64 offset) { + return gb_file_read_at_check(f, buffer, size, offset, NULL); +} + +gb_inline b32 gb_file_write_at(gbFile *f, void const *buffer, isize size, i64 offset) { + return gb_file_write_at_check(f, buffer, size, offset, NULL); +} + +gb_inline i64 gb_file_seek(gbFile *f, i64 offset) { + i64 new_offset = 0; + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + f->ops.seek(f->fd, offset, gbSeekWhence_Begin, &new_offset); + return new_offset; +} + +gb_inline i64 gb_file_seek_to_end(gbFile *f) { + i64 new_offset = 0; + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + f->ops.seek(f->fd, 0, gbSeekWhence_End, &new_offset); + return new_offset; +} + +// NOTE(bill): Skips a certain amount of bytes +gb_inline i64 gb_file_skip(gbFile *f, i64 bytes) { + i64 new_offset = 0; + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + f->ops.seek(f->fd, bytes, gbSeekWhence_Current, &new_offset); + return new_offset; +} + +gb_inline i64 gb_file_tell(gbFile *f) { + i64 new_offset = 0; + if (!f->ops.read_at) f->ops = gbDefaultFileOperations; + f->ops.seek(f->fd, 0, gbSeekWhence_Current, &new_offset); + return new_offset; +} +gb_inline b32 gb_file_read (gbFile *f, void *buffer, isize size) { return gb_file_read_at(f, buffer, size, gb_file_tell(f)); } +gb_inline b32 gb_file_write(gbFile *f, void const *buffer, isize size) { return gb_file_write_at(f, buffer, size, gb_file_tell(f)); } + + +gbFileError gb_file_create(gbFile *f, char const *filename) { + return gb_file_open_mode(f, gbFileMode_Write|gbFileMode_Rw, filename); +} + + +gbFileError gb_file_open(gbFile *f, char const *filename) { + return gb_file_open_mode(f, gbFileMode_Read, filename); +} + + +char const *gb_file_name(gbFile *f) { return f->filename ? f->filename : ""; } + +gb_inline b32 gb_file_has_changed(gbFile *f) { + b32 result = false; + gbFileTime last_write_time = gb_file_last_write_time(f->filename); + if (f->last_write_time != last_write_time) { + result = true; + f->last_write_time = last_write_time; + } + return result; +} + +// TODO(bill): Is this a bad idea? +gb_global b32 gb__std_file_set = false; +gb_global gbFile gb__std_files[gbFileStandard_Count] = {{0}}; + + +#if defined(GB_SYSTEM_WINDOWS) + +gb_inline gbFile *const gb_file_get_standard(gbFileStandardType std) { + if (!gb__std_file_set) { + #define GB__SET_STD_FILE(type, v) gb__std_files[type].fd.p = v; gb__std_files[type].ops = gbDefaultFileOperations + GB__SET_STD_FILE(gbFileStandard_Input, GetStdHandle(STD_INPUT_HANDLE)); + GB__SET_STD_FILE(gbFileStandard_Output, GetStdHandle(STD_OUTPUT_HANDLE)); + GB__SET_STD_FILE(gbFileStandard_Error, GetStdHandle(STD_ERROR_HANDLE)); + #undef GB__SET_STD_FILE + gb__std_file_set = true; + } + return &gb__std_files[std]; +} + +gb_inline i64 gb_file_size(gbFile *f) { + LARGE_INTEGER size; + GetFileSizeEx(f->fd.p, &size); + return size.QuadPart; +} + +gbFileError gb_file_truncate(gbFile *f, i64 size) { + gbFileError err = gbFileError_None; + i64 prev_offset = gb_file_tell(f); + gb_file_seek(f, size); + if (!SetEndOfFile(f)) { + err = gbFileError_TruncationFailure; + } + gb_file_seek(f, prev_offset); + return err; +} + + +b32 gb_file_exists(char const *name) { + WIN32_FIND_DATAW data; + wchar_t *w_text; + void *handle; + b32 found = false; + gbAllocator a = gb_heap_allocator(); + + w_text = gb__alloc_utf8_to_ucs2(a, name, NULL); + if (w_text == NULL) { + return false; + } + handle = FindFirstFileW(w_text, &data); + gb_free(a, w_text); + found = handle != INVALID_HANDLE_VALUE; + if (found) FindClose(handle); + return found; +} + +#else // POSIX + +gb_inline gbFile *const gb_file_get_standard(gbFileStandardType std) { + if (!gb__std_file_set) { + #define GB__SET_STD_FILE(type, v) gb__std_files[type].fd.i = v; gb__std_files[type].ops = gbDefaultFileOperations + GB__SET_STD_FILE(gbFileStandard_Input, 0); + GB__SET_STD_FILE(gbFileStandard_Output, 1); + GB__SET_STD_FILE(gbFileStandard_Error, 2); + #undef GB__SET_STD_FILE + gb__std_file_set = true; + } + return &gb__std_files[std]; +} + +gb_inline i64 gb_file_size(gbFile *f) { + i64 size = 0; + i64 prev_offset = gb_file_tell(f); + gb_file_seek_to_end(f); + size = gb_file_tell(f); + gb_file_seek(f, prev_offset); + return size; +} + +gb_inline gbFileError gb_file_truncate(gbFile *f, i64 size) { + gbFileError err = gbFileError_None; + int i = ftruncate(f->fd.i, size); + if (i != 0) err = gbFileError_TruncationFailure; + return err; +} + +gb_inline b32 gb_file_exists(char const *name) { + return access(name, F_OK) != -1; +} +#endif + + + +#if defined(GB_SYSTEM_WINDOWS) +gbFileTime gb_file_last_write_time(char const *filepath) { + ULARGE_INTEGER li = {0}; + FILETIME last_write_time = {0}; + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + gbAllocator a = gb_heap_allocator(); + + wchar_t *w_text = gb__alloc_utf8_to_ucs2(a, filepath, NULL); + if (w_text == NULL) { + return 0; + } + + if (GetFileAttributesExW(w_text, GetFileExInfoStandard, &data)) { + last_write_time = data.ftLastWriteTime; + } + gb_free(a, w_text); + + li.LowPart = last_write_time.dwLowDateTime; + li.HighPart = last_write_time.dwHighDateTime; + return cast(gbFileTime)li.QuadPart; +} + + +gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) { + wchar_t *w_old = NULL; + wchar_t *w_new = NULL; + gbAllocator a = gb_heap_allocator(); + b32 result = false; + + w_old = gb__alloc_utf8_to_ucs2(a, existing_filename, NULL); + if (w_old == NULL) { + return false; + } + w_new = gb__alloc_utf8_to_ucs2(a, new_filename, NULL); + if (w_new != NULL) { + result = CopyFileW(w_old, w_new, fail_if_exists); + } + gb_free(a, w_new); + gb_free(a, w_old); + return result; +} + +gb_inline b32 gb_file_move(char const *existing_filename, char const *new_filename) { + wchar_t *w_old = NULL; + wchar_t *w_new = NULL; + gbAllocator a = gb_heap_allocator(); + b32 result = false; + + w_old = gb__alloc_utf8_to_ucs2(a, existing_filename, NULL); + if (w_old == NULL) { + return false; + } + w_new = gb__alloc_utf8_to_ucs2(a, new_filename, NULL); + if (w_new != NULL) { + result = MoveFileW(w_old, w_new); + } + gb_free(a, w_new); + gb_free(a, w_old); + return result; +} + +b32 gb_file_remove(char const *filename) { + wchar_t *w_filename = NULL; + gbAllocator a = gb_heap_allocator(); + b32 result = false; + w_filename = gb__alloc_utf8_to_ucs2(a, filename, NULL); + if (w_filename == NULL) { + return false; + } + result = DeleteFileW(w_filename); + gb_free(a, w_filename); + return result; +} + + + +#else + +gbFileTime gb_file_last_write_time(char const *filepath) { + time_t result = 0; + struct stat file_stat; + + if (stat(filepath, &file_stat) == 0) { + result = file_stat.st_mtime; + } + + return cast(gbFileTime)result; +} + + +gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) { +#if defined(GB_SYSTEM_OSX) + return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0; +#else + isize size; + int existing_fd = open(existing_filename, O_RDONLY, 0); + int new_fd = open(new_filename, O_WRONLY|O_CREAT, 0666); + + struct stat stat_existing; + fstat(existing_fd, &stat_existing); + + size = sendfile(new_fd, existing_fd, 0, stat_existing.st_size); + + close(new_fd); + close(existing_fd); + + return size == stat_existing.st_size; +#endif +} + +gb_inline b32 gb_file_move(char const *existing_filename, char const *new_filename) { + if (link(existing_filename, new_filename) == 0) { + return unlink(existing_filename) != -1; + } + return false; +} + +b32 gb_file_remove(char const *filename) { +#if defined(GB_SYSTEM_OSX) + return unlink(filename) != -1; +#else + return remove(filename) == 0; +#endif +} + + +#endif + + + + + +gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char const *filepath) { + gbFileContents result = {0}; + gbFile file = {0}; + + result.allocator = a; + + if (gb_file_open(&file, filepath) == gbFileError_None) { + isize file_size = cast(isize)gb_file_size(&file); + if (file_size > 0) { + result.data = gb_alloc(a, zero_terminate ? file_size+1 : file_size); + result.size = file_size; + gb_file_read_at(&file, result.data, result.size, 0); + if (zero_terminate) { + u8 *str = cast(u8 *)result.data; + str[file_size] = '\0'; + } + } + gb_file_close(&file); + } + + return result; +} + +void gb_file_free_contents(gbFileContents *fc) { + GB_ASSERT_NOT_NULL(fc->data); + gb_free(fc->allocator, fc->data); + fc->data = NULL; + fc->size = 0; +} + + + + + +gb_inline b32 gb_path_is_absolute(char const *path) { + b32 result = false; + GB_ASSERT_NOT_NULL(path); +#if defined(GB_SYSTEM_WINDOWS) + result == (gb_strlen(path) > 2) && + gb_char_is_alpha(path[0]) && + (path[1] == ':' && path[2] == GB_PATH_SEPARATOR); +#else + result = (gb_strlen(path) > 0 && path[0] == GB_PATH_SEPARATOR); +#endif + return result; +} + +gb_inline b32 gb_path_is_relative(char const *path) { return !gb_path_is_absolute(path); } + +gb_inline b32 gb_path_is_root(char const *path) { + b32 result = false; + GB_ASSERT_NOT_NULL(path); +#if defined(GB_SYSTEM_WINDOWS) + result = gb_path_is_absolute(path) && (gb_strlen(path) == 3); +#else + result = gb_path_is_absolute(path) && (gb_strlen(path) == 1); +#endif + return result; +} + +gb_inline char const *gb_path_base_name(char const *path) { + char const *ls; + GB_ASSERT_NOT_NULL(path); + ls = gb_char_last_occurence(path, '/'); + return (ls == NULL) ? path : ls+1; +} + +gb_inline char const *gb_path_extension(char const *path) { + char const *ld; + GB_ASSERT_NOT_NULL(path); + ld = gb_char_last_occurence(path, '.'); + return (ld == NULL) ? NULL : ld+1; +} + + +#if !defined(_WINDOWS_) && defined(GB_SYSTEM_WINDOWS) +GB_DLL_IMPORT DWORD WINAPI GetFullPathNameA(char const *lpFileName, DWORD nBufferLength, char *lpBuffer, char **lpFilePart); +GB_DLL_IMPORT DWORD WINAPI GetFullPathNameW(wchar_t const *lpFileName, DWORD nBufferLength, wchar_t *lpBuffer, wchar_t **lpFilePart); +#endif + +char *gb_path_get_full_name(gbAllocator a, char const *path) { +#if defined(GB_SYSTEM_WINDOWS) +// TODO(bill): Make UTF-8 + wchar_t *w_path = NULL; + wchar_t *w_fullpath = NULL; + isize w_len = 0; + isize new_len = 0; + isize new_len1 = 0; + char *new_path = 0; + w_path = gb__alloc_utf8_to_ucs2(gb_heap_allocator(), path, NULL); + if (w_path == NULL) { + return NULL; + } + w_len = GetFullPathNameW(w_path, 0, NULL, NULL); + if (w_len == 0) { + return NULL; + } + w_fullpath = gb_alloc_array(gb_heap_allocator(), wchar_t, w_len+1); + GetFullPathNameW(w_path, cast(int)w_len, w_fullpath, NULL); + w_fullpath[w_len] = 0; + gb_free(gb_heap_allocator(), w_path); + + new_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w_fullpath, cast(int)w_len, NULL, 0, NULL, NULL); + if (new_len == 0) { + gb_free(gb_heap_allocator(), w_fullpath); + return NULL; + } + new_path = gb_alloc_array(a, char, new_len+1); + new_len1 = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w_fullpath, cast(int)w_len, new_path, cast(int)new_len, NULL, NULL); + if (new_len1 == 0) { + gb_free(gb_heap_allocator(), w_fullpath); + gb_free(a, new_path); + return NULL; + } + new_path[new_len] = 0; + return new_path; +#else + char *p, *result, *fullpath = NULL; + isize len; + p = realpath(path, NULL); + fullpath = p; + if (p == NULL) { + // NOTE(bill): File does not exist + fullpath = cast(char *)path; + } + + len = gb_strlen(fullpath); + + result = gb_alloc_array(a, char, len + 1); + gb_memmove(result, fullpath, len); + result[len] = 0; + free(p); + + return result; +#endif +} + + + + + +//////////////////////////////////////////////////////////////// +// +// Printing +// +// + + +isize gb_printf(char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = gb_printf_va(fmt, va); + va_end(va); + return res; +} + + +isize gb_printf_err(char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = gb_printf_err_va(fmt, va); + va_end(va); + return res; +} + +isize gb_fprintf(struct gbFile *f, char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = gb_fprintf_va(f, fmt, va); + va_end(va); + return res; +} + +char *gb_bprintf(char const *fmt, ...) { + va_list va; + char *str; + va_start(va, fmt); + str = gb_bprintf_va(fmt, va); + va_end(va); + return str; +} + +isize gb_snprintf(char *str, isize n, char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = gb_snprintf_va(str, n, fmt, va); + va_end(va); + return res; +} + + + +gb_inline isize gb_printf_va(char const *fmt, va_list va) { + return gb_fprintf_va(gb_file_get_standard(gbFileStandard_Output), fmt, va); +} + +gb_inline isize gb_printf_err_va(char const *fmt, va_list va) { + return gb_fprintf_va(gb_file_get_standard(gbFileStandard_Error), fmt, va); +} + +gb_inline isize gb_fprintf_va(struct gbFile *f, char const *fmt, va_list va) { + gb_local_persist char buf[4096]; + isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); + gb_file_write(f, buf, len-1); // NOTE(bill): prevent extra whitespace + return len; +} + + +gb_inline char *gb_bprintf_va(char const *fmt, va_list va) { + gb_local_persist char buffer[4096]; + gb_snprintf_va(buffer, gb_size_of(buffer), fmt, va); + return buffer; +} + + +enum { + gbFmt_Minus = GB_BIT(0), + gbFmt_Plus = GB_BIT(1), + gbFmt_Alt = GB_BIT(2), + gbFmt_Space = GB_BIT(3), + gbFmt_Zero = GB_BIT(4), + + gbFmt_Char = GB_BIT(5), + gbFmt_Short = GB_BIT(6), + gbFmt_Int = GB_BIT(7), + gbFmt_Long = GB_BIT(8), + gbFmt_Llong = GB_BIT(9), + gbFmt_Size = GB_BIT(10), + gbFmt_Intptr = GB_BIT(11), + + gbFmt_Unsigned = GB_BIT(12), + gbFmt_Lower = GB_BIT(13), + gbFmt_Upper = GB_BIT(14), + + + gbFmt_Done = GB_BIT(30), + + gbFmt_Ints = gbFmt_Char|gbFmt_Short|gbFmt_Int|gbFmt_Long|gbFmt_Llong|gbFmt_Size|gbFmt_Intptr +}; + +typedef struct { + i32 base; + i32 flags; + i32 width; + i32 precision; +} gbprivFmtInfo; + + +gb_internal isize gb__print_string(char *text, isize max_len, gbprivFmtInfo *info, char const *str) { + // TODO(bill): Get precision and width to work correctly. How does it actually work?! + // TODO(bill): This looks very buggy indeed. + isize res = 0, len; + isize remaining = max_len; + + if (info && info->precision >= 0) { + len = gb_strnlen(str, info->precision); + } else { + len = gb_strlen(str); + } + + if (info && (info->width == 0 || info->flags & gbFmt_Minus)) { + if (info->precision > 0) { + len = info->precision < len ? info->precision : len; + } + + res += gb_strlcpy(text, str, len); + + if (info->width > res) { + isize padding = info->width - len; + char pad = (info->flags & gbFmt_Zero) ? '0' : ' '; + while (padding --> 0 && remaining --> 0) { + *text++ = pad, res++; + } + } + } else { + if (info && (info->width > res)) { + isize padding = info->width - len; + char pad = (info->flags & gbFmt_Zero) ? '0' : ' '; + while (padding --> 0 && remaining --> 0) { + *text++ = pad, res++; + } + } + + res += gb_strlcpy(text, str, len); + } + + + if (info) { + if (info->flags & gbFmt_Upper) { + gb_str_to_upper(text); + } else if (info->flags & gbFmt_Lower) { + gb_str_to_lower(text); + } + } + + return res; +} + +gb_internal isize gb__print_char(char *text, isize max_len, gbprivFmtInfo *info, char arg) { + char str[2] = ""; + str[0] = arg; + return gb__print_string(text, max_len, info, str); +} + + +gb_internal isize gb__print_i64(char *text, isize max_len, gbprivFmtInfo *info, i64 value) { + char num[130]; + gb_i64_to_str(value, num, info ? info->base : 10); + return gb__print_string(text, max_len, info, num); +} + +gb_internal isize gb__print_u64(char *text, isize max_len, gbprivFmtInfo *info, u64 value) { + char num[130]; + gb_u64_to_str(value, num, info ? info->base : 10); + return gb__print_string(text, max_len, info, num); +} + + +gb_internal isize gb__print_f64(char *text, isize max_len, gbprivFmtInfo *info, f64 arg) { + // TODO(bill): Handle exponent notation + isize width, len, remaining = max_len; + char *text_begin = text; + + if (arg) { + u64 value; + if (arg < 0) { + if (remaining > 1) { + *text = '-', remaining--; + } + text++; + arg = -arg; + } else if (info->flags & gbFmt_Minus) { + if (remaining > 1) { + *text = '+', remaining--; + } + text++; + } + + value = cast(u64)arg; + len = gb__print_u64(text, remaining, NULL, value); + text += len; + + if (len >= remaining) { + remaining = gb_min(remaining, 1); + } else { + remaining -= len; + } + arg -= value; + + if (info->precision < 0) { + info->precision = 6; + } + + if ((info->flags & gbFmt_Alt) || info->precision > 0) { + i64 mult = 10; + if (remaining > 1) { + *text = '.', remaining--; + } + text++; + while (info->precision-- > 0) { + value = cast(u64)(arg * mult); + len = gb__print_u64(text, remaining, NULL, value); + text += len; + if (len >= remaining) { + remaining = gb_min(remaining, 1); + } else { + remaining -= len; + } + arg -= cast(f64)value / mult; + mult *= 10; + } + } + } else { + if (remaining > 1) { + *text = '0', remaining--; + } + text++; + if (info->flags & gbFmt_Alt) { + if (remaining > 1) { + *text = '.', remaining--; + } + text++; + } + } + + width = info->width - (text - text_begin); + if (width > 0) { + char fill = (info->flags & gbFmt_Zero) ? '0' : ' '; + char *end = text+remaining-1; + len = (text - text_begin); + + for (len = (text - text_begin); len--; ) { + if ((text_begin+len+width) < end) { + *(text_begin+len+width) = *(text_begin+len); + } + } + + len = width; + text += len; + if (len >= remaining) { + remaining = gb_min(remaining, 1); + } else { + remaining -= len; + } + + while (len--) { + if (text_begin+len < end) { + text_begin[len] = fill; + } + } + } + + return (text - text_begin); +} + + + +gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va_list va) { + char const *text_begin = text; + isize remaining = max_len, res; + + while (*fmt) { + gbprivFmtInfo info = {0}; + isize len = 0; + info.precision = -1; + + while (*fmt && *fmt != '%' && remaining) { + *text++ = *fmt++; + } + + if (*fmt == '%') { + do { + switch (*++fmt) { + case '-': info.flags |= gbFmt_Minus; break; + case '+': info.flags |= gbFmt_Plus; break; + case '#': info.flags |= gbFmt_Alt; break; + case ' ': info.flags |= gbFmt_Space; break; + case '0': info.flags |= gbFmt_Zero; break; + default: info.flags |= gbFmt_Done; break; + } + } while (!(info.flags & gbFmt_Done)); + } + + // NOTE(bill): Optional Width + if (*fmt == '*') { + int width = va_arg(va, int); + if (width < 0) { + info.flags |= gbFmt_Minus; + info.width = -width; + } else { + info.width = width; + } + fmt++; + } else { + info.width = cast(i32)gb_str_to_i64(fmt, cast(char **)&fmt, 10); + } + + // NOTE(bill): Optional Precision + if (*fmt == '.') { + fmt++; + if (*fmt == '*') { + info.precision = va_arg(va, int); + fmt++; + } else { + info.precision = cast(i32)gb_str_to_i64(fmt, cast(char **)&fmt, 10); + } + info.flags &= ~gbFmt_Zero; + } + + + switch (*fmt++) { + case 'h': + if (*fmt == 'h') { // hh => char + info.flags |= gbFmt_Char; + fmt++; + } else { // h => short + info.flags |= gbFmt_Short; + } + break; + + case 'l': + if (*fmt == 'l') { // ll => long long + info.flags |= gbFmt_Llong; + fmt++; + } else { // l => long + info.flags |= gbFmt_Long; + } + break; + + break; + + case 'z': // NOTE(bill): usize + info.flags |= gbFmt_Unsigned; + // fallthrough + case 't': // NOTE(bill): isize + info.flags |= gbFmt_Size; + break; + + default: fmt--; break; + } + + + switch (*fmt) { + case 'u': + info.flags |= gbFmt_Unsigned; + // fallthrough + case 'd': + case 'i': + info.base = 10; + break; + + case 'o': + info.base = 8; + break; + + case 'x': + info.base = 16; + info.flags |= (gbFmt_Unsigned | gbFmt_Lower); + break; + + case 'X': + info.base = 16; + info.flags |= (gbFmt_Unsigned | gbFmt_Upper); + break; + + case 'f': + case 'F': + case 'g': + case 'G': + len = gb__print_f64(text, remaining, &info, va_arg(va, f64)); + break; + + case 'a': + case 'A': + // TODO(bill): + break; + + case 'c': + len = gb__print_char(text, remaining, &info, cast(char)va_arg(va, int)); + break; + + case 's': + len = gb__print_string(text, remaining, &info, va_arg(va, char *)); + break; + + case 'p': + info.base = 16; + info.flags |= (gbFmt_Lower|gbFmt_Unsigned|gbFmt_Alt|gbFmt_Intptr); + break; + + case '%': + len = gb__print_char(text, remaining, &info, '%'); + break; + + default: fmt--; break; + } + + fmt++; + + if (info.base != 0) { + if (info.flags & gbFmt_Unsigned) { + u64 value = 0; + switch (info.flags & gbFmt_Ints) { + case gbFmt_Char: value = cast(u64)cast(u8) va_arg(va, int); break; + case gbFmt_Short: value = cast(u64)cast(u16)va_arg(va, int); break; + case gbFmt_Long: value = cast(u64)va_arg(va, unsigned long); break; + case gbFmt_Llong: value = cast(u64)va_arg(va, unsigned long long); break; + case gbFmt_Size: value = cast(u64)va_arg(va, usize); break; + case gbFmt_Intptr: value = cast(u64)va_arg(va, uintptr); break; + default: value = cast(u64)va_arg(va, unsigned int); break; + } + + len = gb__print_u64(text, remaining, &info, value); + + } else { + i64 value = 0; + switch (info.flags & gbFmt_Ints) { + case gbFmt_Char: value = cast(i64)cast(i8) va_arg(va, int); break; + case gbFmt_Short: value = cast(i64)cast(i16)va_arg(va, int); break; + case gbFmt_Long: value = cast(i64)va_arg(va, long); break; + case gbFmt_Llong: value = cast(i64)va_arg(va, long long); break; + case gbFmt_Size: value = cast(i64)va_arg(va, usize); break; + case gbFmt_Intptr: value = cast(i64)va_arg(va, uintptr); break; + default: value = cast(i64)va_arg(va, int); break; + } + + len = gb__print_i64(text, remaining, &info, value); + } + } + + + text += len; + if (len >= remaining) { + remaining = gb_min(remaining, 1); + } else { + remaining -= len; + } + } + + *text++ = '\0'; + res = (text - text_begin); + return (res >= max_len || res < 0) ? -1 : res; +} + + +//////////////////////////////////////////////////////////////// +// +// DLL Handling +// +// + +#if defined(GB_SYSTEM_WINDOWS) + +gbDllHandle gb_dll_load(char const *filepath) { + return cast(gbDllHandle)LoadLibraryA(filepath); +} +gb_inline void gb_dll_unload (gbDllHandle dll) { FreeLibrary(cast(HMODULE)dll); } +gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) { return cast(gbDllProc)GetProcAddress(cast(HMODULE)dll, proc_name); } + +#else // POSIX + +gbDllHandle gb_dll_load(char const *filepath) { + // TODO(bill): Should this be RTLD_LOCAL? + return cast(gbDllHandle)dlopen(filepath, RTLD_LAZY|RTLD_GLOBAL); +} + +gb_inline void gb_dll_unload (gbDllHandle dll) { dlclose(dll); } +gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) { return cast(gbDllProc)dlsym(dll, proc_name); } + +#endif + + +//////////////////////////////////////////////////////////////// +// +// Time +// +// + +#if defined(GB_COMPILER_MSVC) && !defined(__clang__) + gb_inline u64 gb_rdtsc(void) { return __rdtsc(); } +#elif defined(__i386__) + gb_inline u64 gb_rdtsc(void) { + u64 x; + __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); + return x; + } +#elif defined(__x86_64__) + gb_inline u64 gb_rdtsc(void) { + u32 hi, lo; + __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); + return (cast(u64)lo) | ((cast(u64)hi)<<32); + } +#elif defined(__powerpc__) + gb_inline u64 gb_rdtsc(void) { + u64 result = 0; + u32 upper, lower,tmp; + __asm__ volatile( + "0: \n" + "\tmftbu %0 \n" + "\tmftb %1 \n" + "\tmftbu %2 \n" + "\tcmpw %2,%0 \n" + "\tbne 0b \n" + : "=r"(upper),"=r"(lower),"=r"(tmp) + ); + result = upper; + result = result<<32; + result = result|lower; + + return result; + } +#endif + +#if defined(GB_SYSTEM_WINDOWS) + + gb_inline f64 gb_time_now(void) { + gb_local_persist LARGE_INTEGER win32_perf_count_freq = {0}; + f64 result; + LARGE_INTEGER counter; + if (!win32_perf_count_freq.QuadPart) { + QueryPerformanceFrequency(&win32_perf_count_freq); + GB_ASSERT(win32_perf_count_freq.QuadPart != 0); + } + + QueryPerformanceCounter(&counter); + + result = counter.QuadPart / cast(f64)(win32_perf_count_freq.QuadPart); + return result; + } + + gb_inline u64 gb_utc_time_now(void) { + FILETIME ft; + ULARGE_INTEGER li; + + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + return li.QuadPart/10; + } + + gb_inline void gb_sleep_ms(u32 ms) { Sleep(ms); } + +#else + + gb_global f64 gb__timebase = 0.0; + gb_global u64 gb__timestart = 0; + + gb_inline f64 gb_time_now(void) { +#if defined(GB_SYSTEM_OSX) + f64 result; + + if (!gb__timestart) { + mach_timebase_info_data_t tb = {0}; + mach_timebase_info(&tb); + gb__timebase = tb.numer; + gb__timebase /= tb.denom; + gb__timestart = mach_absolute_time(); + } + + // NOTE(bill): mach_absolute_time() returns things in nanoseconds + result = 1.0e-9 * (mach_absolute_time() - gb__timestart) * gb__timebase; + return result; +#else + struct timespec t; + f64 result; + + // IMPORTANT TODO(bill): THIS IS A HACK + clock_gettime(1 /*CLOCK_MONOTONIC*/, &t); + result = t.tv_sec + 1.0e-9 * t.tv_nsec; + return result; +#endif + } + + gb_inline u64 gb_utc_time_now(void) { + struct timespec t; +#if defined(GB_SYSTEM_OSX) + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + t.tv_sec = mts.tv_sec; + t.tv_nsec = mts.tv_nsec; +#else + // IMPORTANT TODO(bill): THIS IS A HACK + clock_gettime(0 /*CLOCK_REALTIME*/, &t); +#endif + return cast(u64)t.tv_sec * 1000000ull + t.tv_nsec/1000 + 11644473600000000ull; + } + + gb_inline void gb_sleep_ms(u32 ms) { + struct timespec req = {cast(time_t)ms/1000, cast(long)((ms%1000)*1000000)}; + struct timespec rem = {0, 0}; + nanosleep(&req, &rem); + } + +#endif + + + +//////////////////////////////////////////////////////////////// +// +// Miscellany +// +// + +gb_global gbAtomic32 gb__random_shared_counter = {0}; + +gb_internal u32 gb__get_noise_from_time(void) { + u32 accum = 0; + f64 start, remaining, end, curr = 0; + u64 interval = 100000ll; + + start = gb_time_now(); + remaining = (interval - cast(u64)(interval*start)%interval) / cast(f64)interval; + end = start + remaining; + + do { + curr = gb_time_now(); + accum += cast(u32)curr; + } while (curr >= end); + return accum; +} + +// NOTE(bill): Partly from http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/ +// But the generation is even more random-er-est + +gb_internal gb_inline u32 gb__permute_qpr(u32 x) { + gb_local_persist u32 const prime = 4294967291; // 2^32 - 5 + if (x >= prime) { + return x; + } else { + u32 residue = cast(u32)(cast(u64) x * x) % prime; + if (x <= prime / 2) { + return residue; + } else { + return prime - residue; + } + } +} + +gb_internal gb_inline u32 gb__permute_with_offset(u32 x, u32 offset) { + return (gb__permute_qpr(x) + offset) ^ 0x5bf03635; +} + + +void gb_random_init(gbRandom *r) { + u64 time, tick; + isize i, j; + u32 x = 0; + r->value = 0; + + r->offsets[0] = gb__get_noise_from_time(); + r->offsets[1] = gb_atomic32_fetch_add(&gb__random_shared_counter, 1); + r->offsets[2] = gb_thread_current_id(); + r->offsets[3] = gb_thread_current_id() * 3 + 1; + time = gb_utc_time_now(); + r->offsets[4] = cast(u32)(time >> 32); + r->offsets[5] = cast(u32)time; + r->offsets[6] = gb__get_noise_from_time(); + tick = gb_rdtsc(); + r->offsets[7] = cast(u32)(tick ^ (tick >> 32)); + + for (j = 0; j < 4; j++) { + for (i = 0; i < gb_count_of(r->offsets); i++) { + r->offsets[i] = x = gb__permute_with_offset(x, r->offsets[i]); + } + } +} + +u32 gb_random_gen_u32(gbRandom *r) { + u32 x = r->value; + u32 carry = 1; + isize i; + for (i = 0; i < gb_count_of(r->offsets); i++) { + x = gb__permute_with_offset(x, r->offsets[i]); + if (carry > 0) { + carry = ++r->offsets[i] ? 0 : 1; + } + } + + r->value = x; + return x; +} + +u32 gb_random_gen_u32_unique(gbRandom *r) { + u32 x = r->value; + isize i; + r->value++; + for (i = 0; i < gb_count_of(r->offsets); i++) { + x = gb__permute_with_offset(x, r->offsets[i]); + } + + return x; +} + +u64 gb_random_gen_u64(gbRandom *r) { + return ((cast(u64)gb_random_gen_u32(r)) << 32) | gb_random_gen_u32(r); +} + + +isize gb_random_gen_isize(gbRandom *r) { + u64 u = gb_random_gen_u64(r); + return *cast(isize *)&u; +} + + + + +i64 gb_random_range_i64(gbRandom *r, i64 lower_inc, i64 higher_inc) { + u64 u = gb_random_gen_u64(r); + i64 i = *cast(i64 *)&u; + i64 diff = higher_inc-lower_inc+1; + i %= diff; + i += lower_inc; + return i; +} + +isize gb_random_range_isize(gbRandom *r, isize lower_inc, isize higher_inc) { + u64 u = gb_random_gen_u64(r); + isize i = *cast(isize *)&u; + isize diff = higher_inc-lower_inc+1; + i %= diff; + i += lower_inc; + return i; +} + +// NOTE(bill): Semi-cc'ed from gb_math to remove need for fmod and math.h +f64 gb__copy_sign64(f64 x, f64 y) { + i64 ix, iy; + ix = *(i64 *)&x; + iy = *(i64 *)&y; + + ix &= 0x7fffffffffffffff; + ix |= iy & 0x8000000000000000; + return *cast(f64 *)&ix; +} + +f64 gb__floor64 (f64 x) { return cast(f64)((x >= 0.0) ? cast(i64)x : cast(i64)(x-0.9999999999999999)); } +f64 gb__ceil64 (f64 x) { return cast(f64)((x < 0) ? cast(i64)x : (cast(i64)x)+1); } +f64 gb__round64 (f64 x) { return cast(f64)((x >= 0.0) ? gb__floor64(x + 0.5) : gb__ceil64(x - 0.5)); } +f64 gb__remainder64(f64 x, f64 y) { return x - (gb__round64(x/y)*y); } +f64 gb__abs64 (f64 x) { return x < 0 ? -x : x; } +f64 gb__sign64 (f64 x) { return x < 0 ? -1.0 : +1.0; } + +f64 gb__mod64(f64 x, f64 y) { + f64 result; + y = gb__abs64(y); + result = gb__remainder64(gb__abs64(x), y); + if (gb__sign64(result)) result += y; + return gb__copy_sign64(result, x); +} + + +f64 gb_random_range_f64(gbRandom *r, f64 lower_inc, f64 higher_inc) { + u64 u = gb_random_gen_u64(r); + f64 f = *cast(f64 *)&u; + f64 diff = higher_inc-lower_inc+1.0; + f = gb__mod64(f, diff); + f += lower_inc; + return f; +} + + + +#if defined(GB_SYSTEM_WINDOWS) +gb_inline void gb_exit(u32 code) { ExitProcess(code); } +#else +gb_inline void gb_exit(u32 code) { exit(code); } +#endif + +gb_inline void gb_yield(void) { +#if defined(GB_SYSTEM_WINDOWS) + Sleep(0); +#else + sched_yield(); +#endif +} + +gb_inline void gb_set_env(char const *name, char const *value) { +#if defined(GB_SYSTEM_WINDOWS) + // TODO(bill): Should this be a Wide version? + SetEnvironmentVariableA(name, value); +#else + setenv(name, value, 1); +#endif +} + +gb_inline void gb_unset_env(char const *name) { +#if defined(GB_SYSTEM_WINDOWS) + // TODO(bill): Should this be a Wide version? + SetEnvironmentVariableA(name, NULL); +#else + unsetenv(name); +#endif +} + + +gb_inline u16 gb_endian_swap16(u16 i) { + return (i>>8) | (i<<8); +} + +gb_inline u32 gb_endian_swap32(u32 i) { + return (i>>24) |(i<<24) | + ((i&0x00ff0000u)>>8) | ((i&0x0000ff00u)<<8); +} + +gb_inline u64 gb_endian_swap64(u64 i) { + return (i>>56) | (i<<56) | + ((i&0x00ff000000000000ull)>>40) | ((i&0x000000000000ff00ull)<<40) | + ((i&0x0000ff0000000000ull)>>24) | ((i&0x0000000000ff0000ull)<<24) | + ((i&0x000000ff00000000ull)>>8) | ((i&0x00000000ff000000ull)<<8); +} + + +gb_inline isize gb_count_set_bits(u64 mask) { + isize count = 0; + while (mask) { + count += (mask & 1); + mask >>= 1; + } + return count; +} + + + + + + +//////////////////////////////////////////////////////////////// +// +// Platform +// +// + +#if defined(GB_PLATFORM) + +gb_inline void gb_key_state_update(gbKeyState *s, b32 is_down) { + b32 was_down = (*s & gbKeyState_Down) != 0; + is_down = is_down != 0; // NOTE(bill): Make sure it's a boolean + GB_MASK_SET(*s, is_down, gbKeyState_Down); + GB_MASK_SET(*s, !was_down && is_down, gbKeyState_Pressed); + GB_MASK_SET(*s, was_down && !is_down, gbKeyState_Released); +} + +#if defined(GB_SYSTEM_WINDOWS) + +#ifndef ERROR_DEVICE_NOT_CONNECTED +#define ERROR_DEVICE_NOT_CONNECTED 1167 +#endif + +GB_XINPUT_GET_STATE(gbXInputGetState_Stub) { + gb_unused(dwUserIndex); gb_unused(pState); + return ERROR_DEVICE_NOT_CONNECTED; +} +GB_XINPUT_SET_STATE(gbXInputSetState_Stub) { + gb_unused(dwUserIndex); gb_unused(pVibration); + return ERROR_DEVICE_NOT_CONNECTED; +} + + +gb_internal gb_inline f32 gb__process_xinput_stick_value(i16 value, i16 dead_zone_threshold) { + f32 result = 0; + + if (value < -dead_zone_threshold) { + result = cast(f32) (value + dead_zone_threshold) / (32768.0f - dead_zone_threshold); + } else if (value > dead_zone_threshold) { + result = cast(f32) (value - dead_zone_threshold) / (32767.0f - dead_zone_threshold); + } + + return result; +} + +gb_internal void gb__platform_resize_dib_section(gbPlatform *p, i32 width, i32 height) { + if ((p->renderer_type == gbRenderer_Software) && + !(p->window_width == width && p->window_height == height)) { + BITMAPINFO bmi = {0}; + + if (width == 0 || height == 0) { + return; + } + + p->window_width = width; + p->window_height = height; + + // TODO(bill): Is this slow to get the desktop mode everytime? + p->sw_framebuffer.bits_per_pixel = gb_video_mode_get_desktop().bits_per_pixel; + p->sw_framebuffer.pitch = (p->sw_framebuffer.bits_per_pixel * width / 8); + + bmi.bmiHeader.biSize = gb_size_of(bmi.bmiHeader); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = height; // NOTE(bill): -ve is top-down, +ve is bottom-up + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = cast(u16)p->sw_framebuffer.bits_per_pixel; + bmi.bmiHeader.biCompression = 0 /*BI_RGB*/; + + p->sw_framebuffer.win32_bmi = bmi; + + + if (p->sw_framebuffer.memory) { + gb_vm_free(gb_virtual_memory(p->sw_framebuffer.memory, p->sw_framebuffer.memory_size)); + } + + { + isize memory_size = p->sw_framebuffer.pitch * height; + gbVirtualMemory vm = gb_vm_alloc(0, memory_size); + p->sw_framebuffer.memory = vm.data; + p->sw_framebuffer.memory_size = vm.size; + } + } +} + + +gb_internal gbKeyType gb__win32_from_vk(unsigned int key) { + // NOTE(bill): Letters and numbers are defined the same for VK_* and GB_* + if (key >= 'A' && key < 'Z') return cast(gbKeyType)key; + if (key >= '0' && key < '9') return cast(gbKeyType)key; + switch (key) { + case VK_ESCAPE: return gbKey_Escape; + + case VK_LCONTROL: return gbKey_Lcontrol; + case VK_LSHIFT: return gbKey_Lshift; + case VK_LMENU: return gbKey_Lalt; + case VK_LWIN: return gbKey_Lsystem; + case VK_RCONTROL: return gbKey_Rcontrol; + case VK_RSHIFT: return gbKey_Rshift; + case VK_RMENU: return gbKey_Ralt; + case VK_RWIN: return gbKey_Rsystem; + case VK_MENU: return gbKey_Menu; + + case VK_OEM_4: return gbKey_Lbracket; + case VK_OEM_6: return gbKey_Rbracket; + case VK_OEM_1: return gbKey_Semicolon; + case VK_OEM_COMMA: return gbKey_Comma; + case VK_OEM_PERIOD: return gbKey_Period; + case VK_OEM_7: return gbKey_Quote; + case VK_OEM_2: return gbKey_Slash; + case VK_OEM_5: return gbKey_Backslash; + case VK_OEM_3: return gbKey_Grave; + case VK_OEM_PLUS: return gbKey_Equals; + case VK_OEM_MINUS: return gbKey_Minus; + + case VK_SPACE: return gbKey_Space; + case VK_RETURN: return gbKey_Return; + case VK_BACK: return gbKey_Backspace; + case VK_TAB: return gbKey_Tab; + + case VK_PRIOR: return gbKey_Pageup; + case VK_NEXT: return gbKey_Pagedown; + case VK_END: return gbKey_End; + case VK_HOME: return gbKey_Home; + case VK_INSERT: return gbKey_Insert; + case VK_DELETE: return gbKey_Delete; + + case VK_ADD: return gbKey_Plus; + case VK_SUBTRACT: return gbKey_Subtract; + case VK_MULTIPLY: return gbKey_Multiply; + case VK_DIVIDE: return gbKey_Divide; + + case VK_LEFT: return gbKey_Left; + case VK_RIGHT: return gbKey_Right; + case VK_UP: return gbKey_Up; + case VK_DOWN: return gbKey_Down; + + case VK_NUMPAD0: return gbKey_Numpad0; + case VK_NUMPAD1: return gbKey_Numpad1; + case VK_NUMPAD2: return gbKey_Numpad2; + case VK_NUMPAD3: return gbKey_Numpad3; + case VK_NUMPAD4: return gbKey_Numpad4; + case VK_NUMPAD5: return gbKey_Numpad5; + case VK_NUMPAD6: return gbKey_Numpad6; + case VK_NUMPAD7: return gbKey_Numpad7; + case VK_NUMPAD8: return gbKey_Numpad8; + case VK_NUMPAD9: return gbKey_Numpad9; + case VK_SEPARATOR: return gbKey_NumpadEnter; + case VK_DECIMAL: return gbKey_NumpadDot; + + case VK_F1: return gbKey_F1; + case VK_F2: return gbKey_F2; + case VK_F3: return gbKey_F3; + case VK_F4: return gbKey_F4; + case VK_F5: return gbKey_F5; + case VK_F6: return gbKey_F6; + case VK_F7: return gbKey_F7; + case VK_F8: return gbKey_F8; + case VK_F9: return gbKey_F9; + case VK_F10: return gbKey_F10; + case VK_F11: return gbKey_F11; + case VK_F12: return gbKey_F12; + case VK_F13: return gbKey_F13; + case VK_F14: return gbKey_F14; + case VK_F15: return gbKey_F15; + + case VK_PAUSE: return gbKey_Pause; + } + return gbKey_Unknown; +} +LRESULT CALLBACK gb__win32_window_callback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + // NOTE(bill): Silly callbacks + gbPlatform *platform = cast(gbPlatform *)GetWindowLongPtrW(hWnd, GWLP_USERDATA); + b32 window_has_focus = (platform != NULL) && platform->window_has_focus; + + if (msg == WM_CREATE) { // NOTE(bill): Doesn't need the platform + // NOTE(bill): https://msdn.microsoft.com/en-us/library/windows/desktop/ms645536(v=vs.85).aspx + RAWINPUTDEVICE rid[2] = {0}; + + // NOTE(bill): Keyboard + rid[0].usUsagePage = 0x01; + rid[0].usUsage = 0x06; + rid[0].dwFlags = 0x00000030/*RIDEV_NOLEGACY*/; // NOTE(bill): Do not generate legacy messages such as WM_KEYDOWN + rid[0].hwndTarget = hWnd; + + // NOTE(bill): Mouse + rid[1].usUsagePage = 0x01; + rid[1].usUsage = 0x02; + rid[1].dwFlags = 0; // NOTE(bill): adds HID mouse and also allows legacy mouse messages to allow for window movement etc. + rid[1].hwndTarget = hWnd; + + if (RegisterRawInputDevices(rid, gb_count_of(rid), gb_size_of(rid[0])) == false) { + DWORD err = GetLastError(); + GB_PANIC("Failed to initialize raw input device for win32." + "Err: %u", err); + } + } + + if (!platform) { + return DefWindowProcW(hWnd, msg, wParam, lParam); + } + + switch (msg) { + case WM_CLOSE: + case WM_DESTROY: + platform->window_is_closed = true; + return 0; + + case WM_QUIT: { + platform->quit_requested = true; + } break; + + case WM_UNICHAR: { + if (window_has_focus) { + if (wParam == '\r') { + wParam = '\n'; + } + // TODO(bill): Does this need to be thread-safe? + platform->char_buffer[platform->char_buffer_count++] = cast(Rune)wParam; + } + } break; + + + case WM_INPUT: { + RAWINPUT raw = {0}; + unsigned int size = gb_size_of(RAWINPUT); + + if (!GetRawInputData(cast(HRAWINPUT)lParam, RID_INPUT, &raw, &size, gb_size_of(RAWINPUTHEADER))) { + return 0; + } + switch (raw.header.dwType) { + case RIM_TYPEKEYBOARD: { + // NOTE(bill): Many thanks to https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ + // for the + RAWKEYBOARD *raw_kb = &raw.data.keyboard; + unsigned int vk = raw_kb->VKey; + unsigned int scan_code = raw_kb->MakeCode; + unsigned int flags = raw_kb->Flags; + // NOTE(bill): e0 and e1 are escape sequences used for certain special keys, such as PRINT and PAUSE/BREAK. + // NOTE(bill): http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + b32 is_e0 = (flags & RI_KEY_E0) != 0; + b32 is_e1 = (flags & RI_KEY_E1) != 0; + b32 is_up = (flags & RI_KEY_BREAK) != 0; + b32 is_down = !is_up; + + // TODO(bill): Should I handle scan codes? + + if (vk == 255) { + // NOTE(bill): Discard "fake keys" + return 0; + } else if (vk == VK_SHIFT) { + // NOTE(bill): Correct left/right shift + vk = MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX); + } else if (vk == VK_NUMLOCK) { + // NOTE(bill): Correct PAUSE/BREAK and NUM LOCK and set the extended bit + scan_code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC) | 0x100; + } + + if (is_e1) { + // NOTE(bill): Escaped sequences, turn vk into the correct scan code + // except for VK_PAUSE (it's a bug) + if (vk == VK_PAUSE) { + scan_code = 0x45; + } else { + scan_code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); + } + } + + switch (vk) { + case VK_CONTROL: vk = (is_e0) ? VK_RCONTROL : VK_LCONTROL; break; + case VK_MENU: vk = (is_e0) ? VK_RMENU : VK_LMENU; break; + + case VK_RETURN: if (is_e0) vk = VK_SEPARATOR; break; // NOTE(bill): Numpad return + case VK_DELETE: if (!is_e0) vk = VK_DECIMAL; break; // NOTE(bill): Numpad dot + case VK_INSERT: if (!is_e0) vk = VK_NUMPAD0; break; + case VK_HOME: if (!is_e0) vk = VK_NUMPAD7; break; + case VK_END: if (!is_e0) vk = VK_NUMPAD1; break; + case VK_PRIOR: if (!is_e0) vk = VK_NUMPAD9; break; + case VK_NEXT: if (!is_e0) vk = VK_NUMPAD3; break; + + // NOTE(bill): The standard arrow keys will always have their e0 bit set, but the + // corresponding keys on the NUMPAD will not. + case VK_LEFT: if (!is_e0) vk = VK_NUMPAD4; break; + case VK_RIGHT: if (!is_e0) vk = VK_NUMPAD6; break; + case VK_UP: if (!is_e0) vk = VK_NUMPAD8; break; + case VK_DOWN: if (!is_e0) vk = VK_NUMPAD2; break; + + // NUMPAD 5 doesn't have its e0 bit set + case VK_CLEAR: if (!is_e0) vk = VK_NUMPAD5; break; + } + + // NOTE(bill): Set appropriate key state flags + gb_key_state_update(&platform->keys[gb__win32_from_vk(vk)], is_down); + + } break; + case RIM_TYPEMOUSE: { + RAWMOUSE *raw_mouse = &raw.data.mouse; + u16 flags = raw_mouse->usButtonFlags; + long dx = +raw_mouse->lLastX; + long dy = -raw_mouse->lLastY; + + if (flags & RI_MOUSE_WHEEL) { + platform->mouse_wheel_delta = cast(i16)raw_mouse->usButtonData; + } + + platform->mouse_raw_dx = dx; + platform->mouse_raw_dy = dy; + } break; + } + } break; + + default: break; + } + + return DefWindowProcW(hWnd, msg, wParam, lParam); +} + + +typedef void *wglCreateContextAttribsARB_Proc(void *hDC, void *hshareContext, int const *attribList); + + +b32 gb__platform_init(gbPlatform *p, char const *window_title, gbVideoMode mode, gbRendererType type, u32 window_flags) { + WNDCLASSEXW wc = {gb_size_of(WNDCLASSEXW)}; + DWORD ex_style = 0, style = 0; + RECT wr; + u16 title_buffer[256] = {0}; // TODO(bill): gb_local_persist this? + + wc.style = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC + wc.lpfnWndProc = gb__win32_window_callback; + wc.hbrBackground = cast(HBRUSH)GetStockObject(0/*WHITE_BRUSH*/); + wc.lpszMenuName = NULL; + wc.lpszClassName = L"gb-win32-wndclass"; // TODO(bill): Is this enough? + wc.hInstance = GetModuleHandleW(NULL); + + if (RegisterClassExW(&wc) == 0) { + MessageBoxW(NULL, L"Failed to register the window class", L"ERROR", MB_OK | MB_ICONEXCLAMATION); + return false; + } + + if ((window_flags & gbWindow_Fullscreen) && !(window_flags & gbWindow_Borderless)) { + DEVMODEW screen_settings = {gb_size_of(DEVMODEW)}; + screen_settings.dmPelsWidth = mode.width; + screen_settings.dmPelsHeight = mode.height; + screen_settings.dmBitsPerPel = mode.bits_per_pixel; + screen_settings.dmFields = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; + + if (ChangeDisplaySettingsW(&screen_settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { + if (MessageBoxW(NULL, L"The requested fullscreen mode is not supported by\n" + L"your video card. Use windowed mode instead?", + L"", + MB_YESNO|MB_ICONEXCLAMATION) == IDYES) { + window_flags &= ~gbWindow_Fullscreen; + } else { + mode = gb_video_mode_get_desktop(); + screen_settings.dmPelsWidth = mode.width; + screen_settings.dmPelsHeight = mode.height; + screen_settings.dmBitsPerPel = mode.bits_per_pixel; + ChangeDisplaySettingsW(&screen_settings, CDS_FULLSCREEN); + } + } + } + + + // ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + // style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; + + style |= WS_VISIBLE; + + if (window_flags & gbWindow_Hidden) style &= ~WS_VISIBLE; + if (window_flags & gbWindow_Resizable) style |= WS_THICKFRAME | WS_MAXIMIZEBOX; + if (window_flags & gbWindow_Maximized) style |= WS_MAXIMIZE; + if (window_flags & gbWindow_Minimized) style |= WS_MINIMIZE; + + // NOTE(bill): Completely ignore the given mode and just change it + if (window_flags & gbWindow_FullscreenDesktop) { + mode = gb_video_mode_get_desktop(); + } + + if ((window_flags & gbWindow_Fullscreen) || (window_flags & gbWindow_Borderless)) { + style |= WS_POPUP; + } else { + style |= WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + } + + + wr.left = 0; + wr.top = 0; + wr.right = mode.width; + wr.bottom = mode.height; + AdjustWindowRect(&wr, style, false); + + p->window_flags = window_flags; + p->window_handle = CreateWindowExW(ex_style, + wc.lpszClassName, + cast(wchar_t const *)gb_utf8_to_ucs2(title_buffer, gb_size_of(title_buffer), window_title), + style, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, wr.bottom - wr.top, + 0, 0, + GetModuleHandleW(NULL), + NULL); + + if (!p->window_handle) { + MessageBoxW(NULL, L"Window creation failed", L"Error", MB_OK|MB_ICONEXCLAMATION); + return false; + } + + p->win32_dc = GetDC(cast(HWND)p->window_handle); + + p->renderer_type = type; + switch (p->renderer_type) { + case gbRenderer_Opengl: { + wglCreateContextAttribsARB_Proc *wglCreateContextAttribsARB; + i32 attribs[8] = {0}; + isize c = 0; + + PIXELFORMATDESCRIPTOR pfd = {gb_size_of(PIXELFORMATDESCRIPTOR)}; + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + pfd.iLayerType = PFD_MAIN_PLANE; + + SetPixelFormat(cast(HDC)p->win32_dc, ChoosePixelFormat(cast(HDC)p->win32_dc, &pfd), NULL); + p->opengl.context = cast(void *)wglCreateContext(cast(HDC)p->win32_dc); + wglMakeCurrent(cast(HDC)p->win32_dc, cast(HGLRC)p->opengl.context); + + if (p->opengl.major > 0) { + attribs[c++] = 0x2091; // WGL_CONTEXT_MAJOR_VERSION_ARB + attribs[c++] = gb_max(p->opengl.major, 1); + } + if (p->opengl.major > 0 && p->opengl.minor >= 0) { + attribs[c++] = 0x2092; // WGL_CONTEXT_MINOR_VERSION_ARB + attribs[c++] = gb_max(p->opengl.minor, 0); + } + + if (p->opengl.core) { + attribs[c++] = 0x9126; // WGL_CONTEXT_PROFILE_MASK_ARB + attribs[c++] = 0x0001; // WGL_CONTEXT_CORE_PROFILE_BIT_ARB + } else if (p->opengl.compatible) { + attribs[c++] = 0x9126; // WGL_CONTEXT_PROFILE_MASK_ARB + attribs[c++] = 0x0002; // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB + } + attribs[c++] = 0; // NOTE(bill): tells the proc that this is the end of attribs + + wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_Proc *)wglGetProcAddress("wglCreateContextAttribsARB"); + if (wglCreateContextAttribsARB) { + HGLRC rc = cast(HGLRC)wglCreateContextAttribsARB(p->win32_dc, 0, attribs); + if (rc && wglMakeCurrent(cast(HDC)p->win32_dc, rc)) { + p->opengl.context = rc; + } else { + // TODO(bill): Handle errors from GetLastError + // ERROR_INVALID_VERSION_ARB 0x2095 + // ERROR_INVALID_PROFILE_ARB 0x2096 + } + } + + } break; + + case gbRenderer_Software: + gb__platform_resize_dib_section(p, mode.width, mode.height); + break; + + default: + GB_PANIC("Unknown window type"); + break; + } + + SetForegroundWindow(cast(HWND)p->window_handle); + SetFocus(cast(HWND)p->window_handle); + SetWindowLongPtrW(cast(HWND)p->window_handle, GWLP_USERDATA, cast(LONG_PTR)p); + + p->window_width = mode.width; + p->window_height = mode.height; + + if (p->renderer_type == gbRenderer_Opengl) { + p->opengl.dll_handle = gb_dll_load("opengl32.dll"); + } + + { // Load XInput + // TODO(bill): What other dlls should I look for? + gbDllHandle xinput_library = gb_dll_load("xinput1_4.dll"); + p->xinput.get_state = gbXInputGetState_Stub; + p->xinput.set_state = gbXInputSetState_Stub; + + if (!xinput_library) xinput_library = gb_dll_load("xinput9_1_0.dll"); + if (!xinput_library) xinput_library = gb_dll_load("xinput1_3.dll"); + if (!xinput_library) { + // TODO(bill): Proper Diagnostic + gb_printf_err("XInput could not be loaded. Controllers will not work!\n"); + } else { + p->xinput.get_state = cast(gbXInputGetStateProc *)gb_dll_proc_address(xinput_library, "XInputGetState"); + p->xinput.set_state = cast(gbXInputSetStateProc *)gb_dll_proc_address(xinput_library, "XInputSetState"); + } + } + + // Init keys + gb_zero_array(p->keys, gb_count_of(p->keys)); + + p->is_initialized = true; + return true; +} + +gb_inline b32 gb_platform_init_with_software(gbPlatform *p, char const *window_title, + i32 width, i32 height, u32 window_flags) { + gbVideoMode mode; + mode.width = width; + mode.height = height; + mode.bits_per_pixel = 32; + return gb__platform_init(p, window_title, mode, gbRenderer_Software, window_flags); +} + +gb_inline b32 gb_platform_init_with_opengl(gbPlatform *p, char const *window_title, + i32 width, i32 height, u32 window_flags, i32 major, i32 minor, b32 core, b32 compatible) { + gbVideoMode mode; + mode.width = width; + mode.height = height; + mode.bits_per_pixel = 32; + p->opengl.major = major; + p->opengl.minor = minor; + p->opengl.core = cast(b16)core; + p->opengl.compatible = cast(b16)compatible; + return gb__platform_init(p, window_title, mode, gbRenderer_Opengl, window_flags); +} + +#ifndef _XINPUT_H_ +typedef struct _XINPUT_GAMEPAD { + u16 wButtons; + u8 bLeftTrigger; + u8 bRightTrigger; + u16 sThumbLX; + u16 sThumbLY; + u16 sThumbRX; + u16 sThumbRY; +} XINPUT_GAMEPAD; + +typedef struct _XINPUT_STATE { + DWORD dwPacketNumber; + XINPUT_GAMEPAD Gamepad; +} XINPUT_STATE; + +typedef struct _XINPUT_VIBRATION { + u16 wLeftMotorSpeed; + u16 wRightMotorSpeed; +} XINPUT_VIBRATION; + +#define XINPUT_GAMEPAD_DPAD_UP 0x00000001 +#define XINPUT_GAMEPAD_DPAD_DOWN 0x00000002 +#define XINPUT_GAMEPAD_DPAD_LEFT 0x00000004 +#define XINPUT_GAMEPAD_DPAD_RIGHT 0x00000008 +#define XINPUT_GAMEPAD_START 0x00000010 +#define XINPUT_GAMEPAD_BACK 0x00000020 +#define XINPUT_GAMEPAD_LEFT_THUMB 0x00000040 +#define XINPUT_GAMEPAD_RIGHT_THUMB 0x00000080 +#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 +#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 +#define XINPUT_GAMEPAD_A 0x1000 +#define XINPUT_GAMEPAD_B 0x2000 +#define XINPUT_GAMEPAD_X 0x4000 +#define XINPUT_GAMEPAD_Y 0x8000 +#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 +#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 +#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30 +#endif + +#ifndef XUSER_MAX_COUNT +#define XUSER_MAX_COUNT 4 +#endif + +void gb_platform_update(gbPlatform *p) { + isize i; + + { // NOTE(bill): Set window state + // TODO(bill): Should this be moved to gb__win32_window_callback ? + RECT window_rect; + i32 x, y, w, h; + + GetClientRect(cast(HWND)p->window_handle, &window_rect); + x = window_rect.left; + y = window_rect.top; + w = window_rect.right - window_rect.left; + h = window_rect.bottom - window_rect.top; + + if ((p->window_width != w) || (p->window_height != h)) { + if (p->renderer_type == gbRenderer_Software) { + gb__platform_resize_dib_section(p, w, h); + } + } + + + p->window_x = x; + p->window_y = y; + p->window_width = w; + p->window_height = h; + GB_MASK_SET(p->window_flags, IsIconic(cast(HWND)p->window_handle) != 0, gbWindow_Minimized); + + p->window_has_focus = GetFocus() == cast(HWND)p->window_handle; + } + + { // NOTE(bill): Set mouse position + POINT mouse_pos; + DWORD win_button_id[gbMouseButton_Count] = { + VK_LBUTTON, + VK_MBUTTON, + VK_RBUTTON, + VK_XBUTTON1, + VK_XBUTTON2, + }; + + // NOTE(bill): This needs to be GetAsyncKeyState as RAWMOUSE doesn't aways work for some odd reason + // TODO(bill): Try and get RAWMOUSE to work for key presses + for (i = 0; i < gbMouseButton_Count; i++) { + gb_key_state_update(p->mouse_buttons+i, GetAsyncKeyState(win_button_id[i]) < 0); + } + + GetCursorPos(&mouse_pos); + ScreenToClient(cast(HWND)p->window_handle, &mouse_pos); + { + i32 x = mouse_pos.x; + i32 y = p->window_height-1 - mouse_pos.y; + p->mouse_dx = x - p->mouse_x; + p->mouse_dy = y - p->mouse_y; + p->mouse_x = x; + p->mouse_y = y; + } + + if (p->mouse_clip) { + b32 update = false; + i32 x = p->mouse_x; + i32 y = p->mouse_y; + if (p->mouse_x < 0) { + x = 0; + update = true; + } else if (p->mouse_y > p->window_height-1) { + y = p->window_height-1; + update = true; + } + + if (p->mouse_y < 0) { + y = 0; + update = true; + } else if (p->mouse_x > p->window_width-1) { + x = p->window_width-1; + update = true; + } + + if (update) { + gb_platform_set_mouse_position(p, x, y); + } + } + + + } + + + // NOTE(bill): Set Key/Button states + if (p->window_has_focus) { + p->char_buffer_count = 0; // TODO(bill): Reset buffer count here or else where? + + // NOTE(bill): Need to update as the keys only get updates on events + for (i = 0; i < gbKey_Count; i++) { + b32 is_down = (p->keys[i] & gbKeyState_Down) != 0; + gb_key_state_update(&p->keys[i], is_down); + } + + p->key_modifiers.control = p->keys[gbKey_Lcontrol] | p->keys[gbKey_Rcontrol]; + p->key_modifiers.alt = p->keys[gbKey_Lalt] | p->keys[gbKey_Ralt]; + p->key_modifiers.shift = p->keys[gbKey_Lshift] | p->keys[gbKey_Rshift]; + + } + + { // NOTE(bill): Set Controller states + isize max_controller_count = XUSER_MAX_COUNT; + if (max_controller_count > gb_count_of(p->game_controllers)) { + max_controller_count = gb_count_of(p->game_controllers); + } + + for (i = 0; i < max_controller_count; i++) { + gbGameController *controller = &p->game_controllers[i]; + XINPUT_STATE controller_state = {0}; + if (p->xinput.get_state(cast(DWORD)i, &controller_state) != 0) { + // NOTE(bill): The controller is not available + controller->is_connected = false; + } else { + // NOTE(bill): This controller is plugged in + // TODO(bill): See if ControllerState.dwPacketNumber increments too rapidly + XINPUT_GAMEPAD *pad = &controller_state.Gamepad; + + controller->is_connected = true; + + // TODO(bill): This is a square deadzone, check XInput to verify that the deadzone is "round" and do round deadzone processing. + controller->axes[gbControllerAxis_LeftX] = gb__process_xinput_stick_value(pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); + controller->axes[gbControllerAxis_LeftY] = gb__process_xinput_stick_value(pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); + controller->axes[gbControllerAxis_RightX] = gb__process_xinput_stick_value(pad->sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); + controller->axes[gbControllerAxis_RightY] = gb__process_xinput_stick_value(pad->sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); + + controller->axes[gbControllerAxis_LeftTrigger] = cast(f32)pad->bLeftTrigger / 255.0f; + controller->axes[gbControllerAxis_RightTrigger] = cast(f32)pad->bRightTrigger / 255.0f; + + + if ((controller->axes[gbControllerAxis_LeftX] != 0.0f) || + (controller->axes[gbControllerAxis_LeftY] != 0.0f)) { + controller->is_analog = true; + } + + #define GB__PROCESS_DIGITAL_BUTTON(button_type, xinput_button) \ + gb_key_state_update(&controller->buttons[button_type], (pad->wButtons & xinput_button) == xinput_button) + + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_A, XINPUT_GAMEPAD_A); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_B, XINPUT_GAMEPAD_B); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_X, XINPUT_GAMEPAD_X); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Y, XINPUT_GAMEPAD_Y); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_LeftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_RightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Start, XINPUT_GAMEPAD_START); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Back, XINPUT_GAMEPAD_BACK); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Left, XINPUT_GAMEPAD_DPAD_LEFT); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Right, XINPUT_GAMEPAD_DPAD_RIGHT); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Down, XINPUT_GAMEPAD_DPAD_DOWN); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Up, XINPUT_GAMEPAD_DPAD_UP); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_LeftThumb, XINPUT_GAMEPAD_LEFT_THUMB); + GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_RightThumb, XINPUT_GAMEPAD_RIGHT_THUMB); + #undef GB__PROCESS_DIGITAL_BUTTON + } + } + } + + { // NOTE(bill): Process pending messages + MSG message; + for (;;) { + BOOL is_okay = PeekMessageW(&message, 0, 0, 0, PM_REMOVE); + if (!is_okay) break; + + switch (message.message) { + case WM_QUIT: + p->quit_requested = true; + break; + + default: + TranslateMessage(&message); + DispatchMessageW(&message); + break; + } + } + } +} + +void gb_platform_display(gbPlatform *p) { + if (p->renderer_type == gbRenderer_Opengl) { + SwapBuffers(cast(HDC)p->win32_dc); + } else if (p->renderer_type == gbRenderer_Software) { + StretchDIBits(cast(HDC)p->win32_dc, + 0, 0, p->window_width, p->window_height, + 0, 0, p->window_width, p->window_height, + p->sw_framebuffer.memory, + &p->sw_framebuffer.win32_bmi, + DIB_RGB_COLORS, SRCCOPY); + } else { + GB_PANIC("Invalid window rendering type"); + } + + { + f64 prev_time = p->curr_time; + f64 curr_time = gb_time_now(); + p->dt_for_frame = curr_time - prev_time; + p->curr_time = curr_time; + } +} + + +void gb_platform_destroy(gbPlatform *p) { + if (p->renderer_type == gbRenderer_Opengl) { + wglDeleteContext(cast(HGLRC)p->opengl.context); + } else if (p->renderer_type == gbRenderer_Software) { + gb_vm_free(gb_virtual_memory(p->sw_framebuffer.memory, p->sw_framebuffer.memory_size)); + } + + DestroyWindow(cast(HWND)p->window_handle); +} + +void gb_platform_show_cursor(gbPlatform *p, b32 show) { + gb_unused(p); + ShowCursor(show); +} + +void gb_platform_set_mouse_position(gbPlatform *p, i32 x, i32 y) { + POINT point; + point.x = cast(LONG)x; + point.y = cast(LONG)(p->window_height-1 - y); + ClientToScreen(cast(HWND)p->window_handle, &point); + SetCursorPos(point.x, point.y); + + p->mouse_x = point.x; + p->mouse_y = p->window_height-1 - point.y; +} + + + +void gb_platform_set_controller_vibration(gbPlatform *p, isize index, f32 left_motor, f32 right_motor) { + if (gb_is_between(index, 0, GB_MAX_GAME_CONTROLLER_COUNT-1)) { + XINPUT_VIBRATION vibration = {0}; + left_motor = gb_clamp01(left_motor); + right_motor = gb_clamp01(right_motor); + vibration.wLeftMotorSpeed = cast(WORD)(65535 * left_motor); + vibration.wRightMotorSpeed = cast(WORD)(65535 * right_motor); + + p->xinput.set_state(cast(DWORD)index, &vibration); + } +} + + +void gb_platform_set_window_position(gbPlatform *p, i32 x, i32 y) { + RECT rect; + i32 width, height; + + GetClientRect(cast(HWND)p->window_handle, &rect); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + MoveWindow(cast(HWND)p->window_handle, x, y, width, height, false); +} + +void gb_platform_set_window_title(gbPlatform *p, char const *title, ...) { + u16 buffer[256] = {0}; + char str[512] = {0}; + va_list va; + va_start(va, title); + gb_snprintf_va(str, gb_size_of(str), title, va); + va_end(va); + + if (str[0] != '\0') { + SetWindowTextW(cast(HWND)p->window_handle, cast(wchar_t const *)gb_utf8_to_ucs2(buffer, gb_size_of(buffer), str)); + } +} + +void gb_platform_toggle_fullscreen(gbPlatform *p, b32 fullscreen_desktop) { + // NOTE(bill): From the man himself, Raymond Chen! (Modified for my need.) + HWND handle = cast(HWND)p->window_handle; + DWORD style = cast(DWORD)GetWindowLongW(handle, GWL_STYLE); + WINDOWPLACEMENT placement; + + if (style & WS_OVERLAPPEDWINDOW) { + MONITORINFO monitor_info = {gb_size_of(monitor_info)}; + if (GetWindowPlacement(handle, &placement) && + GetMonitorInfoW(MonitorFromWindow(handle, 1), &monitor_info)) { + style &= ~WS_OVERLAPPEDWINDOW; + if (fullscreen_desktop) { + style &= ~WS_CAPTION; + style |= WS_POPUP; + } + SetWindowLongW(handle, GWL_STYLE, style); + SetWindowPos(handle, HWND_TOP, + monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, + monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + + if (fullscreen_desktop) { + p->window_flags |= gbWindow_FullscreenDesktop; + } else { + p->window_flags |= gbWindow_Fullscreen; + } + } + } else { + style &= ~WS_POPUP; + style |= WS_OVERLAPPEDWINDOW | WS_CAPTION; + SetWindowLongW(handle, GWL_STYLE, style); + SetWindowPlacement(handle, &placement); + SetWindowPos(handle, 0, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + + p->window_flags &= ~gbWindow_Fullscreen; + } +} + +void gb_platform_toggle_borderless(gbPlatform *p) { + HWND handle = cast(HWND)p->window_handle; + DWORD style = GetWindowLongW(handle, GWL_STYLE); + b32 is_borderless = (style & WS_POPUP) != 0; + + GB_MASK_SET(style, is_borderless, WS_OVERLAPPEDWINDOW | WS_CAPTION); + GB_MASK_SET(style, !is_borderless, WS_POPUP); + + SetWindowLongW(handle, GWL_STYLE, style); + + GB_MASK_SET(p->window_flags, !is_borderless, gbWindow_Borderless); +} + + + +gb_inline void gb_platform_make_opengl_context_current(gbPlatform *p) { + if (p->renderer_type == gbRenderer_Opengl) { + wglMakeCurrent(cast(HDC)p->win32_dc, cast(HGLRC)p->opengl.context); + } +} + +gb_inline void gb_platform_show_window(gbPlatform *p) { + ShowWindow(cast(HWND)p->window_handle, SW_SHOW); + p->window_flags &= ~gbWindow_Hidden; +} + +gb_inline void gb_platform_hide_window(gbPlatform *p) { + ShowWindow(cast(HWND)p->window_handle, SW_HIDE); + p->window_flags |= gbWindow_Hidden; +} + +gb_inline gbVideoMode gb_video_mode_get_desktop(void) { + DEVMODEW win32_mode = {gb_size_of(win32_mode)}; + EnumDisplaySettingsW(NULL, ENUM_CURRENT_SETTINGS, &win32_mode); + return gb_video_mode(win32_mode.dmPelsWidth, win32_mode.dmPelsHeight, win32_mode.dmBitsPerPel); +} + +isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count) { + DEVMODEW win32_mode = {gb_size_of(win32_mode)}; + i32 count; + for (count = 0; + count < max_mode_count && EnumDisplaySettingsW(NULL, count, &win32_mode); + count++) { + modes[count] = gb_video_mode(win32_mode.dmPelsWidth, win32_mode.dmPelsHeight, win32_mode.dmBitsPerPel); + } + + gb_sort_array(modes, count, gb_video_mode_dsc_cmp); + return count; +} + + + +b32 gb_platform_has_clipboard_text(gbPlatform *p) { + b32 result = false; + + if (IsClipboardFormatAvailable(1/*CF_TEXT*/) && + OpenClipboard(cast(HWND)p->window_handle)) { + HANDLE mem = GetClipboardData(1/*CF_TEXT*/); + if (mem) { + char *str = cast(char *)GlobalLock(mem); + if (str && str[0] != '\0') { + result = true; + } + GlobalUnlock(mem); + } else { + return false; + } + + CloseClipboard(); + } + + return result; +} + +// TODO(bill): Handle UTF-8 +void gb_platform_set_clipboard_text(gbPlatform *p, char const *str) { + if (OpenClipboard(cast(HWND)p->window_handle)) { + isize i, len = gb_strlen(str)+1; + + HANDLE mem = cast(HANDLE)GlobalAlloc(0x0002/*GMEM_MOVEABLE*/, len); + if (mem) { + char *dst = cast(char *)GlobalLock(mem); + if (dst) { + for (i = 0; str[i]; i++) { + // TODO(bill): Does this cause a buffer overflow? + // NOTE(bill): Change \n to \r\n 'cause windows + if (str[i] == '\n' && (i == 0 || str[i-1] != '\r')) { + *dst++ = '\r'; + } + *dst++ = str[i]; + } + *dst = 0; + } + GlobalUnlock(mem); + } + + EmptyClipboard(); + if (!SetClipboardData(1/*CF_TEXT*/, mem)) { + return; + } + CloseClipboard(); + } +} + +// TODO(bill): Handle UTF-8 +char *gb_platform_get_clipboard_text(gbPlatform *p, gbAllocator a) { + char *text = NULL; + + if (IsClipboardFormatAvailable(1/*CF_TEXT*/) && + OpenClipboard(cast(HWND)p->window_handle)) { + HANDLE mem = GetClipboardData(1/*CF_TEXT*/); + if (mem) { + char *str = cast(char *)GlobalLock(mem); + text = gb_alloc_str(a, str); + GlobalUnlock(mem); + } else { + return NULL; + } + + CloseClipboard(); + } + + return text; +} + +#elif defined(GB_SYSTEM_OSX) + +#include +#include +#include +#include + +#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 + #define NSIntegerEncoding "q" + #define NSUIntegerEncoding "L" +#else + #define NSIntegerEncoding "i" + #define NSUIntegerEncoding "I" +#endif + +#ifdef __OBJC__ + #import +#else + typedef CGPoint NSPoint; + typedef CGSize NSSize; + typedef CGRect NSRect; + + extern id NSApp; + extern id const NSDefaultRunLoopMode; +#endif + +#if defined(__OBJC__) && __has_feature(objc_arc) +#error TODO(bill): Cannot compile as objective-c code just yet! +#endif + +// ABI is a bit different between platforms +#ifdef __arm64__ +#define abi_objc_msgSend_stret objc_msgSend +#else +#define abi_objc_msgSend_stret objc_msgSend_stret +#endif +#ifdef __i386__ +#define abi_objc_msgSend_fpret objc_msgSend_fpret +#else +#define abi_objc_msgSend_fpret objc_msgSend +#endif + +#define objc_msgSend_id ((id (*)(id, SEL))objc_msgSend) +#define objc_msgSend_void ((void (*)(id, SEL))objc_msgSend) +#define objc_msgSend_void_id ((void (*)(id, SEL, id))objc_msgSend) +#define objc_msgSend_void_bool ((void (*)(id, SEL, BOOL))objc_msgSend) +#define objc_msgSend_id_char_const ((id (*)(id, SEL, char const *))objc_msgSend) + +gb_internal NSUInteger gb__osx_application_should_terminate(id self, SEL _sel, id sender) { + // NOTE(bill): Do nothing + return 0; +} + +gb_internal void gb__osx_window_will_close(id self, SEL _sel, id notification) { + NSUInteger value = true; + object_setInstanceVariable(self, "closed", cast(void *)value); +} + +gb_internal void gb__osx_window_did_become_key(id self, SEL _sel, id notification) { + gbPlatform *p = NULL; + object_getInstanceVariable(self, "gbPlatform", cast(void **)&p); + if (p) { + // TODO(bill): + } +} + +b32 gb__platform_init(gbPlatform *p, char const *window_title, gbVideoMode mode, gbRendererType type, u32 window_flags) { + if (p->is_initialized) { + return true; + } + // Init Platform + { // Initial OSX State + Class appDelegateClass; + b32 resultAddProtoc, resultAddMethod; + id dgAlloc, dg, menubarAlloc, menubar; + id appMenuItemAlloc, appMenuItem; + id appMenuAlloc, appMenu; + + #if defined(ARC_AVAILABLE) + #error TODO(bill): This code should be compiled as C for now + #else + id poolAlloc = objc_msgSend_id(cast(id)objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + p->osx_autorelease_pool = objc_msgSend_id(poolAlloc, sel_registerName("init")); + #endif + + objc_msgSend_id(cast(id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + ((void (*)(id, SEL, NSInteger))objc_msgSend)(NSApp, sel_registerName("setActivationPolicy:"), 0); + + appDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "AppDelegate", 0); + resultAddProtoc = class_addProtocol(appDelegateClass, objc_getProtocol("NSApplicationDelegate")); + assert(resultAddProtoc); + resultAddMethod = class_addMethod(appDelegateClass, sel_registerName("applicationShouldTerminate:"), cast(IMP)gb__osx_application_should_terminate, NSUIntegerEncoding "@:@"); + assert(resultAddMethod); + dgAlloc = objc_msgSend_id(cast(id)appDelegateClass, sel_registerName("alloc")); + dg = objc_msgSend_id(dgAlloc, sel_registerName("init")); + #ifndef ARC_AVAILABLE + objc_msgSend_void(dg, sel_registerName("autorelease")); + #endif + + objc_msgSend_void_id(NSApp, sel_registerName("setDelegate:"), dg); + objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); + + menubarAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenu"), sel_registerName("alloc")); + menubar = objc_msgSend_id(menubarAlloc, sel_registerName("init")); + #ifndef ARC_AVAILABLE + objc_msgSend_void(menubar, sel_registerName("autorelease")); + #endif + + appMenuItemAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); + appMenuItem = objc_msgSend_id(appMenuItemAlloc, sel_registerName("init")); + #ifndef ARC_AVAILABLE + objc_msgSend_void(appMenuItem, sel_registerName("autorelease")); + #endif + + objc_msgSend_void_id(menubar, sel_registerName("addItem:"), appMenuItem); + ((id (*)(id, SEL, id))objc_msgSend)(NSApp, sel_registerName("setMainMenu:"), menubar); + + appMenuAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenu"), sel_registerName("alloc")); + appMenu = objc_msgSend_id(appMenuAlloc, sel_registerName("init")); + #ifndef ARC_AVAILABLE + objc_msgSend_void(appMenu, sel_registerName("autorelease")); + #endif + + { + id processInfo = objc_msgSend_id(cast(id)objc_getClass("NSProcessInfo"), sel_registerName("processInfo")); + id appName = objc_msgSend_id(processInfo, sel_registerName("processName")); + + id quitTitlePrefixString = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "Quit "); + id quitTitle = ((id (*)(id, SEL, id))objc_msgSend)(quitTitlePrefixString, sel_registerName("stringByAppendingString:"), appName); + + id quitMenuItemKey = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "q"); + id quitMenuItemAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); + id quitMenuItem = ((id (*)(id, SEL, id, SEL, id))objc_msgSend)(quitMenuItemAlloc, sel_registerName("initWithTitle:action:keyEquivalent:"), quitTitle, sel_registerName("terminate:"), quitMenuItemKey); + #ifndef ARC_AVAILABLE + objc_msgSend_void(quitMenuItem, sel_registerName("autorelease")); + #endif + + objc_msgSend_void_id(appMenu, sel_registerName("addItem:"), quitMenuItem); + objc_msgSend_void_id(appMenuItem, sel_registerName("setSubmenu:"), appMenu); + } + } + + { // Init Window + NSRect rect = {{0, 0}, {cast(CGFloat)mode.width, cast(CGFloat)mode.height}}; + id windowAlloc, window, wdgAlloc, wdg, contentView, titleString; + Class WindowDelegateClass; + b32 resultAddProtoc, resultAddIvar, resultAddMethod; + + windowAlloc = objc_msgSend_id(cast(id)objc_getClass("NSWindow"), sel_registerName("alloc")); + window = ((id (*)(id, SEL, NSRect, NSUInteger, NSUInteger, BOOL))objc_msgSend)(windowAlloc, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, 15, 2, NO); + #ifndef ARC_AVAILABLE + objc_msgSend_void(window, sel_registerName("autorelease")); + #endif + + // when we are not using ARC, than window will be added to autorelease pool + // so if we close it by hand (pressing red button), we don't want it to be released for us + // so it will be released by autorelease pool later + objc_msgSend_void_bool(window, sel_registerName("setReleasedWhenClosed:"), NO); + + WindowDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "WindowDelegate", 0); + resultAddProtoc = class_addProtocol(WindowDelegateClass, objc_getProtocol("NSWindowDelegate")); + GB_ASSERT(resultAddProtoc); + resultAddIvar = class_addIvar(WindowDelegateClass, "closed", gb_size_of(NSUInteger), rint(log2(gb_size_of(NSUInteger))), NSUIntegerEncoding); + GB_ASSERT(resultAddIvar); + resultAddIvar = class_addIvar(WindowDelegateClass, "gbPlatform", gb_size_of(void *), rint(log2(gb_size_of(void *))), "ˆv"); + GB_ASSERT(resultAddIvar); + resultAddMethod = class_addMethod(WindowDelegateClass, sel_registerName("windowWillClose:"), cast(IMP)gb__osx_window_will_close, "v@:@"); + GB_ASSERT(resultAddMethod); + resultAddMethod = class_addMethod(WindowDelegateClass, sel_registerName("windowDidBecomeKey:"), cast(IMP)gb__osx_window_did_become_key, "v@:@"); + GB_ASSERT(resultAddMethod); + wdgAlloc = objc_msgSend_id(cast(id)WindowDelegateClass, sel_registerName("alloc")); + wdg = objc_msgSend_id(wdgAlloc, sel_registerName("init")); + #ifndef ARC_AVAILABLE + objc_msgSend_void(wdg, sel_registerName("autorelease")); + #endif + + objc_msgSend_void_id(window, sel_registerName("setDelegate:"), wdg); + + contentView = objc_msgSend_id(window, sel_registerName("contentView")); + + { + NSPoint point = {20, 20}; + ((void (*)(id, SEL, NSPoint))objc_msgSend)(window, sel_registerName("cascadeTopLeftFromPoint:"), point); + } + + titleString = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), window_title); + objc_msgSend_void_id(window, sel_registerName("setTitle:"), titleString); + + if (type == gbRenderer_Opengl) { + // TODO(bill): Make sure this works correctly + u32 opengl_hex_version = (p->opengl.major << 12) | (p->opengl.minor << 8); + u32 gl_attribs[] = { + 8, 24, // NSOpenGLPFAColorSize, 24, + 11, 8, // NSOpenGLPFAAlphaSize, 8, + 5, // NSOpenGLPFADoubleBuffer, + 73, // NSOpenGLPFAAccelerated, + //72, // NSOpenGLPFANoRecovery, + //55, 1, // NSOpenGLPFASampleBuffers, 1, + //56, 4, // NSOpenGLPFASamples, 4, + 99, opengl_hex_version, // NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + 0 + }; + + id pixel_format_alloc, pixel_format; + id opengl_context_alloc, opengl_context; + + pixel_format_alloc = objc_msgSend_id(cast(id)objc_getClass("NSOpenGLPixelFormat"), sel_registerName("alloc")); + pixel_format = ((id (*)(id, SEL, const uint32_t*))objc_msgSend)(pixel_format_alloc, sel_registerName("initWithAttributes:"), gl_attribs); + #ifndef ARC_AVAILABLE + objc_msgSend_void(pixel_format, sel_registerName("autorelease")); + #endif + + opengl_context_alloc = objc_msgSend_id(cast(id)objc_getClass("NSOpenGLContext"), sel_registerName("alloc")); + opengl_context = ((id (*)(id, SEL, id, id))objc_msgSend)(opengl_context_alloc, sel_registerName("initWithFormat:shareContext:"), pixel_format, nil); + #ifndef ARC_AVAILABLE + objc_msgSend_void(opengl_context, sel_registerName("autorelease")); + #endif + + objc_msgSend_void_id(opengl_context, sel_registerName("setView:"), contentView); + objc_msgSend_void_id(window, sel_registerName("makeKeyAndOrderFront:"), window); + objc_msgSend_void_bool(window, sel_registerName("setAcceptsMouseMovedEvents:"), YES); + + + p->window_handle = cast(void *)window; + p->opengl.context = cast(void *)opengl_context; + } else { + GB_PANIC("TODO(bill): Software rendering"); + } + + { + id blackColor = objc_msgSend_id(cast(id)objc_getClass("NSColor"), sel_registerName("blackColor")); + objc_msgSend_void_id(window, sel_registerName("setBackgroundColor:"), blackColor); + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), YES); + } + object_setInstanceVariable(wdg, "gbPlatform", cast(void *)p); + + p->is_initialized = true; + } + + return true; +} + +// NOTE(bill): Software rendering +b32 gb_platform_init_with_software(gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags) { + GB_PANIC("TODO(bill): Software rendering in not yet implemented on OS X\n"); + return gb__platform_init(p, window_title, gb_video_mode(width, height, 32), gbRenderer_Software, window_flags); +} +// NOTE(bill): OpenGL Rendering +b32 gb_platform_init_with_opengl(gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags, + i32 major, i32 minor, b32 core, b32 compatible) { + + p->opengl.major = major; + p->opengl.minor = minor; + p->opengl.core = core; + p->opengl.compatible = compatible; + return gb__platform_init(p, window_title, gb_video_mode(width, height, 32), gbRenderer_Opengl, window_flags); +} + +// NOTE(bill): Reverse engineering can be fun!!! +gb_internal gbKeyType gb__osx_from_key_code(u16 key_code) { + switch (key_code) { + default: return gbKey_Unknown; + // NOTE(bill): WHO THE FUCK DESIGNED THIS VIRTUAL KEY CODE SYSTEM?! + // THEY ARE FUCKING IDIOTS! + case 0x1d: return gbKey_0; + case 0x12: return gbKey_1; + case 0x13: return gbKey_2; + case 0x14: return gbKey_3; + case 0x15: return gbKey_4; + case 0x17: return gbKey_5; + case 0x16: return gbKey_6; + case 0x1a: return gbKey_7; + case 0x1c: return gbKey_8; + case 0x19: return gbKey_9; + + case 0x00: return gbKey_A; + case 0x0b: return gbKey_B; + case 0x08: return gbKey_C; + case 0x02: return gbKey_D; + case 0x0e: return gbKey_E; + case 0x03: return gbKey_F; + case 0x05: return gbKey_G; + case 0x04: return gbKey_H; + case 0x22: return gbKey_I; + case 0x26: return gbKey_J; + case 0x28: return gbKey_K; + case 0x25: return gbKey_L; + case 0x2e: return gbKey_M; + case 0x2d: return gbKey_N; + case 0x1f: return gbKey_O; + case 0x23: return gbKey_P; + case 0x0c: return gbKey_Q; + case 0x0f: return gbKey_R; + case 0x01: return gbKey_S; + case 0x11: return gbKey_T; + case 0x20: return gbKey_U; + case 0x09: return gbKey_V; + case 0x0d: return gbKey_W; + case 0x07: return gbKey_X; + case 0x10: return gbKey_Y; + case 0x06: return gbKey_Z; + + case 0x21: return gbKey_Lbracket; + case 0x1e: return gbKey_Rbracket; + case 0x29: return gbKey_Semicolon; + case 0x2b: return gbKey_Comma; + case 0x2f: return gbKey_Period; + case 0x27: return gbKey_Quote; + case 0x2c: return gbKey_Slash; + case 0x2a: return gbKey_Backslash; + case 0x32: return gbKey_Grave; + case 0x18: return gbKey_Equals; + case 0x1b: return gbKey_Minus; + case 0x31: return gbKey_Space; + + case 0x35: return gbKey_Escape; // Escape + case 0x3b: return gbKey_Lcontrol; // Left Control + case 0x38: return gbKey_Lshift; // Left Shift + case 0x3a: return gbKey_Lalt; // Left Alt + case 0x37: return gbKey_Lsystem; // Left OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... + case 0x3e: return gbKey_Rcontrol; // Right Control + case 0x3c: return gbKey_Rshift; // Right Shift + case 0x3d: return gbKey_Ralt; // Right Alt + // case 0x37: return gbKey_Rsystem; // Right OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... + case 0x6e: return gbKey_Menu; // Menu + case 0x24: return gbKey_Return; // Return + case 0x33: return gbKey_Backspace; // Backspace + case 0x30: return gbKey_Tab; // Tabulation + case 0x74: return gbKey_Pageup; // Page up + case 0x79: return gbKey_Pagedown; // Page down + case 0x77: return gbKey_End; // End + case 0x73: return gbKey_Home; // Home + case 0x72: return gbKey_Insert; // Insert + case 0x75: return gbKey_Delete; // Delete + case 0x45: return gbKey_Plus; // + + case 0x4e: return gbKey_Subtract; // - + case 0x43: return gbKey_Multiply; // * + case 0x4b: return gbKey_Divide; // / + case 0x7b: return gbKey_Left; // Left arrow + case 0x7c: return gbKey_Right; // Right arrow + case 0x7e: return gbKey_Up; // Up arrow + case 0x7d: return gbKey_Down; // Down arrow + case 0x52: return gbKey_Numpad0; // Numpad 0 + case 0x53: return gbKey_Numpad1; // Numpad 1 + case 0x54: return gbKey_Numpad2; // Numpad 2 + case 0x55: return gbKey_Numpad3; // Numpad 3 + case 0x56: return gbKey_Numpad4; // Numpad 4 + case 0x57: return gbKey_Numpad5; // Numpad 5 + case 0x58: return gbKey_Numpad6; // Numpad 6 + case 0x59: return gbKey_Numpad7; // Numpad 7 + case 0x5b: return gbKey_Numpad8; // Numpad 8 + case 0x5c: return gbKey_Numpad9; // Numpad 9 + case 0x41: return gbKey_NumpadDot; // Numpad . + case 0x4c: return gbKey_NumpadEnter; // Numpad Enter + case 0x7a: return gbKey_F1; // F1 + case 0x78: return gbKey_F2; // F2 + case 0x63: return gbKey_F3; // F3 + case 0x76: return gbKey_F4; // F4 + case 0x60: return gbKey_F5; // F5 + case 0x61: return gbKey_F6; // F6 + case 0x62: return gbKey_F7; // F7 + case 0x64: return gbKey_F8; // F8 + case 0x65: return gbKey_F9; // F8 + case 0x6d: return gbKey_F10; // F10 + case 0x67: return gbKey_F11; // F11 + case 0x6f: return gbKey_F12; // F12 + case 0x69: return gbKey_F13; // F13 + case 0x6b: return gbKey_F14; // F14 + case 0x71: return gbKey_F15; // F15 + // case : return gbKey_Pause; // Pause // NOTE(bill): Not possible on OS X + } +} + +gb_internal void gb__osx_on_cocoa_event(gbPlatform *p, id event, id window) { + if (!event) { + return; + } else if (objc_msgSend_id(window, sel_registerName("delegate"))) { + NSUInteger event_type = ((NSUInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("type")); + switch (event_type) { + case 1: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Left], true); break; // NSLeftMouseDown + case 2: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Left], false); break; // NSLeftMouseUp + case 3: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Right], true); break; // NSRightMouseDown + case 4: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Right], false); break; // NSRightMouseUp + case 25: { // NSOtherMouseDown + // TODO(bill): Test thoroughly + NSInteger number = ((NSInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); + if (number == 2) gb_key_state_update(&p->mouse_buttons[gbMouseButton_Middle], true); + if (number == 3) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X1], true); + if (number == 4) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X2], true); + } break; + case 26: { // NSOtherMouseUp + NSInteger number = ((NSInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); + if (number == 2) gb_key_state_update(&p->mouse_buttons[gbMouseButton_Middle], false); + if (number == 3) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X1], false); + if (number == 4) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X2], false); + + } break; + + // TODO(bill): Scroll wheel + case 22: { // NSScrollWheel + CGFloat dx = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("scrollingDeltaX")); + CGFloat dy = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("scrollingDeltaY")); + BOOL precision_scrolling = ((BOOL (*)(id, SEL))objc_msgSend)(event, sel_registerName("hasPreciseScrollingDeltas")); + if (precision_scrolling) { + dx *= 0.1f; + dy *= 0.1f; + } + // TODO(bill): Handle sideways + p->mouse_wheel_delta = dy; + // p->mouse_wheel_dy = dy; + // gb_printf("%f %f\n", dx, dy); + } break; + + case 12: { // NSFlagsChanged + #if 0 + // TODO(bill): Reverse engineer this properly + NSUInteger modifiers = ((NSUInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("modifierFlags")); + u32 upper_mask = (modifiers & 0xffff0000ul) >> 16; + b32 shift = (upper_mask & 0x02) != 0; + b32 control = (upper_mask & 0x04) != 0; + b32 alt = (upper_mask & 0x08) != 0; + b32 command = (upper_mask & 0x10) != 0; + #endif + + // gb_printf("%u\n", keys.mask); + // gb_printf("%x\n", cast(u32)modifiers); + } break; + + case 10: { // NSKeyDown + u16 key_code; + + id input_text = objc_msgSend_id(event, sel_registerName("characters")); + char const *input_text_utf8 = ((char const *(*)(id, SEL))objc_msgSend)(input_text, sel_registerName("UTF8String")); + p->char_buffer_count = gb_strnlen(input_text_utf8, gb_size_of(p->char_buffer)); + gb_memcopy(p->char_buffer, input_text_utf8, p->char_buffer_count); + + key_code = ((unsigned short (*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); + gb_key_state_update(&p->keys[gb__osx_from_key_code(key_code)], true); + } break; + + case 11: { // NSKeyUp + u16 key_code = ((unsigned short (*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); + gb_key_state_update(&p->keys[gb__osx_from_key_code(key_code)], false); + } break; + + default: break; + } + + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), event); + } +} + + +void gb_platform_update(gbPlatform *p) { + id window, key_window, content_view; + NSRect original_frame; + + window = cast(id)p->window_handle; + key_window = objc_msgSend_id(NSApp, sel_registerName("keyWindow")); + p->window_has_focus = key_window == window; // TODO(bill): Is this right + + + if (p->window_has_focus) { + isize i; + p->char_buffer_count = 0; // TODO(bill): Reset buffer count here or else where? + + // NOTE(bill): Need to update as the keys only get updates on events + for (i = 0; i < gbKey_Count; i++) { + b32 is_down = (p->keys[i] & gbKeyState_Down) != 0; + gb_key_state_update(&p->keys[i], is_down); + } + + for (i = 0; i < gbMouseButton_Count; i++) { + b32 is_down = (p->mouse_buttons[i] & gbKeyState_Down) != 0; + gb_key_state_update(&p->mouse_buttons[i], is_down); + } + + } + + { // Handle Events + id distant_past = objc_msgSend_id(cast(id)objc_getClass("NSDate"), sel_registerName("distantPast")); + id event = ((id (*)(id, SEL, NSUInteger, id, id, BOOL))objc_msgSend)(NSApp, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), NSUIntegerMax, distant_past, NSDefaultRunLoopMode, YES); + gb__osx_on_cocoa_event(p, event, window); + } + + if (p->window_has_focus) { + p->key_modifiers.control = p->keys[gbKey_Lcontrol] | p->keys[gbKey_Rcontrol]; + p->key_modifiers.alt = p->keys[gbKey_Lalt] | p->keys[gbKey_Ralt]; + p->key_modifiers.shift = p->keys[gbKey_Lshift] | p->keys[gbKey_Rshift]; + } + + { // Check if window is closed + id wdg = objc_msgSend_id(window, sel_registerName("delegate")); + if (!wdg) { + p->window_is_closed = false; + } else { + NSUInteger value = 0; + object_getInstanceVariable(wdg, "closed", cast(void **)&value); + p->window_is_closed = (value != 0); + } + } + + + + content_view = objc_msgSend_id(window, sel_registerName("contentView")); + original_frame = ((NSRect (*)(id, SEL))abi_objc_msgSend_stret)(content_view, sel_registerName("frame")); + + { // Window + NSRect frame = original_frame; + frame = ((NSRect (*)(id, SEL, NSRect))abi_objc_msgSend_stret)(content_view, sel_registerName("convertRectToBacking:"), frame); + p->window_width = frame.size.width; + p->window_height = frame.size.height; + frame = ((NSRect (*)(id, SEL, NSRect))abi_objc_msgSend_stret)(window, sel_registerName("convertRectToScreen:"), frame); + p->window_x = frame.origin.x; + p->window_y = frame.origin.y; + } + + { // Mouse + NSRect frame = original_frame; + NSPoint mouse_pos = ((NSPoint (*)(id, SEL))objc_msgSend)(window, sel_registerName("mouseLocationOutsideOfEventStream")); + mouse_pos.x = gb_clamp(mouse_pos.x, 0, frame.size.width-1); + mouse_pos.y = gb_clamp(mouse_pos.y, 0, frame.size.height-1); + + { + i32 x = mouse_pos.x; + i32 y = mouse_pos.y; + p->mouse_dx = x - p->mouse_x; + p->mouse_dy = y - p->mouse_y; + p->mouse_x = x; + p->mouse_y = y; + } + + if (p->mouse_clip) { + b32 update = false; + i32 x = p->mouse_x; + i32 y = p->mouse_y; + if (p->mouse_x < 0) { + x = 0; + update = true; + } else if (p->mouse_y > p->window_height-1) { + y = p->window_height-1; + update = true; + } + + if (p->mouse_y < 0) { + y = 0; + update = true; + } else if (p->mouse_x > p->window_width-1) { + x = p->window_width-1; + update = true; + } + + if (update) { + gb_platform_set_mouse_position(p, x, y); + } + } + } + + { // TODO(bill): Controllers + + } + + // TODO(bill): Is this in the correct place? + objc_msgSend_void(NSApp, sel_registerName("updateWindows")); + if (p->renderer_type == gbRenderer_Opengl) { + objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("update")); + gb_platform_make_opengl_context_current(p); + } +} + +void gb_platform_display(gbPlatform *p) { + // TODO(bill): Do more + if (p->renderer_type == gbRenderer_Opengl) { + gb_platform_make_opengl_context_current(p); + objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("flushBuffer")); + } else if (p->renderer_type == gbRenderer_Software) { + // TODO(bill): + } else { + GB_PANIC("Invalid window rendering type"); + } + + { + f64 prev_time = p->curr_time; + f64 curr_time = gb_time_now(); + p->dt_for_frame = curr_time - prev_time; + p->curr_time = curr_time; + } +} + +void gb_platform_destroy(gbPlatform *p) { + gb_platform_make_opengl_context_current(p); + + objc_msgSend_void(cast(id)p->window_handle, sel_registerName("close")); + + #if defined(ARC_AVAILABLE) + // TODO(bill): autorelease pool + #else + objc_msgSend_void(cast(id)p->osx_autorelease_pool, sel_registerName("drain")); + #endif +} + +void gb_platform_show_cursor(gbPlatform *p, b32 show) { + if (show ) { + // objc_msgSend_void(class_registerName("NSCursor"), sel_registerName("unhide")); + } else { + // objc_msgSend_void(class_registerName("NSCursor"), sel_registerName("hide")); + } +} + +void gb_platform_set_mouse_position(gbPlatform *p, i32 x, i32 y) { + // TODO(bill): + CGPoint pos = {cast(CGFloat)x, cast(CGFloat)y}; + pos.x += p->window_x; + pos.y += p->window_y; + CGWarpMouseCursorPosition(pos); +} + +void gb_platform_set_controller_vibration(gbPlatform *p, isize index, f32 left_motor, f32 right_motor) { + // TODO(bill): +} + +b32 gb_platform_has_clipboard_text(gbPlatform *p) { + // TODO(bill): + return false; +} + +void gb_platform_set_clipboard_text(gbPlatform *p, char const *str) { + // TODO(bill): +} + +char *gb_platform_get_clipboard_text(gbPlatform *p, gbAllocator a) { + // TODO(bill): + return NULL; +} + +void gb_platform_set_window_position(gbPlatform *p, i32 x, i32 y) { + // TODO(bill): +} + +void gb_platform_set_window_title(gbPlatform *p, char const *title, ...) { + id title_string; + char buf[256] = {0}; + va_list va; + va_start(va, title); + gb_snprintf_va(buf, gb_count_of(buf), title, va); + va_end(va); + + title_string = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), buf); + objc_msgSend_void_id(cast(id)p->window_handle, sel_registerName("setTitle:"), title_string); +} + +void gb_platform_toggle_fullscreen(gbPlatform *p, b32 fullscreen_desktop) { + // TODO(bill): +} + +void gb_platform_toggle_borderless(gbPlatform *p) { + // TODO(bill): +} + +void gb_platform_make_opengl_context_current(gbPlatform *p) { + objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("makeCurrentContext")); +} + +void gb_platform_show_window(gbPlatform *p) { + // TODO(bill): +} + +void gb_platform_hide_window(gbPlatform *p) { + // TODO(bill): +} + +i32 gb__osx_mode_bits_per_pixel(CGDisplayModeRef mode) { + i32 bits_per_pixel = 0; + CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode); + if(CFStringCompare(pixel_encoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + bits_per_pixel = 32; + } else if(CFStringCompare(pixel_encoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + bits_per_pixel = 16; + } else if(CFStringCompare(pixel_encoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + bits_per_pixel = 8; + } + CFRelease(pixel_encoding); + + return bits_per_pixel; +} + +i32 gb__osx_display_bits_per_pixel(CGDirectDisplayID display) { + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display); + i32 bits_per_pixel = gb__osx_mode_bits_per_pixel(mode); + CGDisplayModeRelease(mode); + return bits_per_pixel; +} + +gbVideoMode gb_video_mode_get_desktop(void) { + CGDirectDisplayID display = CGMainDisplayID(); + return gb_video_mode(CGDisplayPixelsWide(display), + CGDisplayPixelsHigh(display), + gb__osx_display_bits_per_pixel(display)); +} + + +isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count) { + CFArrayRef cg_modes = CGDisplayCopyAllDisplayModes(CGMainDisplayID(), NULL); + CFIndex i, count; + if (cg_modes == NULL) { + return 0; + } + + count = gb_min(CFArrayGetCount(cg_modes), max_mode_count); + for (i = 0; i < count; i++) { + CGDisplayModeRef cg_mode = cast(CGDisplayModeRef)CFArrayGetValueAtIndex(cg_modes, i); + modes[i] = gb_video_mode(CGDisplayModeGetWidth(cg_mode), + CGDisplayModeGetHeight(cg_mode), + gb__osx_mode_bits_per_pixel(cg_mode)); + } + + CFRelease(cg_modes); + + gb_sort_array(modes, count, gb_video_mode_dsc_cmp); + return cast(isize)count; +} + +#endif + + +// TODO(bill): OSX Platform Layer +// NOTE(bill): Use this as a guide so there is no need for Obj-C https://github.com/jimon/osx_app_in_plain_c + +gb_inline gbVideoMode gb_video_mode(i32 width, i32 height, i32 bits_per_pixel) { + gbVideoMode m; + m.width = width; + m.height = height; + m.bits_per_pixel = bits_per_pixel; + return m; +} + +gb_inline b32 gb_video_mode_is_valid(gbVideoMode mode) { + gb_local_persist gbVideoMode modes[256] = {0}; + gb_local_persist isize mode_count = 0; + gb_local_persist b32 is_set = false; + isize i; + + if (!is_set) { + mode_count = gb_video_mode_get_fullscreen_modes(modes, gb_count_of(modes)); + is_set = true; + } + + for (i = 0; i < mode_count; i++) { + gb_printf("%d %d\n", modes[i].width, modes[i].height); + } + + return gb_binary_search_array(modes, mode_count, &mode, gb_video_mode_cmp) >= 0; +} + +GB_COMPARE_PROC(gb_video_mode_cmp) { + gbVideoMode const *x = cast(gbVideoMode const *)a; + gbVideoMode const *y = cast(gbVideoMode const *)b; + + if (x->bits_per_pixel == y->bits_per_pixel) { + if (x->width == y->width) { + return x->height < y->height ? -1 : x->height > y->height; + } + return x->width < y->width ? -1 : x->width > y->width; + } + return x->bits_per_pixel < y->bits_per_pixel ? -1 : +1; +} + +GB_COMPARE_PROC(gb_video_mode_dsc_cmp) { + return gb_video_mode_cmp(b, a); +} + +#endif // defined(GB_PLATFORM) + + + + +#if defined(GB_COMPILER_MSVC) +#pragma warning(pop) +#endif + +#if defined(__GCC__) || defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + + +#if defined(__cplusplus) +} +#endif + +#endif // GB_IMPLEMENTATION diff --git a/thirdparty/stb/src/stb_image.c b/thirdparty/stb/src/stb_image.c new file mode 100644 index 0000000..badb3ef --- /dev/null +++ b/thirdparty/stb/src/stb_image.c @@ -0,0 +1,2 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" \ No newline at end of file diff --git a/thirdparty/stb/src/stb_image.h b/thirdparty/stb/src/stb_image.h new file mode 100644 index 0000000..39acae6 --- /dev/null +++ b/thirdparty/stb/src/stb_image.h @@ -0,0 +1,7897 @@ +/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/thirdparty/stb/src/stb_truetype.h b/thirdparty/stb/src/stb_truetype.h index 5e2a2e4..2ca3ff9 100644 --- a/thirdparty/stb/src/stb_truetype.h +++ b/thirdparty/stb/src/stb_truetype.h @@ -412,6 +412,43 @@ int main(int arg, char **argv) } #endif +#pragma region ODIN: CUSTOM ALLOCATOR + +#ifdef STB_TRUETYPE_IMPLEMENTATION +#define GB_IMPLEMENTATION +#endif +#include "gb/gb.h" + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +STBTT_DEF void stbtt_SetAllocator( gbAllocator allocator ); + +#ifdef __cplusplus +} +#endif + +#ifndef STBTT_malloc +#define STBTT_malloc(x,u) ((void)(u), gb_alloc(stbtt__allocator, x)) +#define STBTT_free(x,u) ((void)(u), gb_free(stbtt__allocator, x)) +#endif + +#ifdef STB_TRUETYPE_IMPLEMENTATION +gb_global gbAllocator stbtt__allocator = { gb_heap_allocator_proc, NULL }; + +STBTT_DEF void stbtt_SetAllocator( gbAllocator allocator ) { + stbtt__allocator = allocator; +} +#endif + +#pragma endregion ODIN: CUSTOM ALLOCATOR ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// diff --git a/thirdparty/stb/truetype/stb_truetype.odin b/thirdparty/stb/truetype/stb_truetype.odin index bd521c6..f128ad0 100644 --- a/thirdparty/stb/truetype/stb_truetype.odin +++ b/thirdparty/stb/truetype/stb_truetype.odin @@ -36,6 +36,37 @@ when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { #assert(size_of(c.int) == size_of(rune)) #assert(size_of(c.int) == size_of(b32)) +//----------------------------------------------------------------------------- +// CUSTOM: ODIN COMPATIBLE ALLOCATOR +//----------------------------------------------------------------------------- + +gbAllocationType :: enum(i32) { + Alloc, + Free, + FreeAll, + Resize, +} + +gbAllocatorProc :: #type proc(allocator_data: rawptr, type: gbAllocationType, + size: c.ssize_t, alignment: c.ssize_t, + old_memory: rawptr, old_size: c.ssize_t, + flags : c.ulonglong +) -> rawptr + +gbAllocator :: struct { + procedure: gbAllocatorProc, + data: rawptr, +} + +@(default_calling_convention="c", link_prefix="stbtt_") +foreign stbtt { + SetAllocator :: proc(allocator : gbAllocator) --- +} + +//----------------------------------------------------------------------------- +// END CUSTOM: ODIN COMPATIBLE ALLOCATOR +//----------------------------------------------------------------------------- + ////////////////////////////////////////////////////////////////////////////// // // TEXTURE BAKING API diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 9d91ba5..189e308 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -596,6 +596,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, error : Allocator_Error glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) assert(error == .None) + assert(glyph_pack[pack_id].shape != nil) } for id, index in oversized { @@ -633,7 +634,10 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, } 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) + 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() @@ -666,6 +670,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, error : Allocator_Error glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) assert(error == .None) + assert(glyph_pack[pack_id].shape != nil) } for id, index in to_cache @@ -731,7 +736,10 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, } 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) + 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 { diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index ee05912..666f058 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -18,6 +18,7 @@ Already wanted to do so anyway to evaluate the shape generation implementation. import "base:runtime" import "core:c" import "core:math" +import "core:mem" import "core:slice" import stbtt "thirdparty:stb/truetype" // import freetype "thirdparty:freetype" @@ -57,13 +58,44 @@ 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.gbAllocationType, + size : c.ssize_t, + alignment : c.ssize_t, + old_memory : rawptr, + old_size : c.ssize_t, + flags : c.ulonglong +) -> rawptr { - ctx.kind = kind + allocator := transmute(^Allocator) allocator_data + result, error := allocator.procedure( allocator.data, cast(mem.Allocator_Mode) type, cast(int) size, cast(int) alignment, old_memory, cast(int) old_size ) + assert(error == .None) + + if type == .Alloc || type == .Resize { + return transmute(rawptr) & result[0] + } + else do return nil +} + +parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind, allocator := context.allocator ) +{ + ctx.kind = kind + ctx.lib_backing = allocator + + stbtt_allocator := stbtt.gbAllocator { 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.gbAllocator { parser_stbtt_allocator_proc, & ctx.lib_backing } + stbtt.SetAllocator( stbtt_allocator ) } parser_shutdown :: proc( ctx : ^Parser_Context ) { From 48927fd00883e07c695552b00b59eedaf6f0b34b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 00:51:39 -0500 Subject: [PATCH 20/62] Fixed shaper bug, removed shaper_shape_text_uncached_advanced (intergrated into harfbuzz) --- examples/sokol_demo/sokol_demo.odin | 12 ++-- thirdparty/stb/lib/stb_truetype.lib | Bin 380552 -> 380552 bytes vefontcache/draw.odin | 56 +++++++++++------- vefontcache/shaper.odin | 51 ++++++++-------- vefontcache/vefontcache.odin | 87 +++++++++++----------------- 5 files changed, 99 insertions(+), 107 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 4c8b372..9b6eacf 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -58,6 +58,7 @@ Font_ID :: struct { Font_Entry :: struct { path_file : string, + data : []byte, default_size : i32, ve_id : ve.Font_ID, } @@ -126,6 +127,7 @@ font_load :: proc(path_file : string, error : ve.Load_Font_Error def.path_file = path_file def.default_size = default_size + def.data = font_data def.ve_id, error = ve.load_font( & demo_ctx.ve_ctx, desired_id, font_data, curve_quality ) assert(error == .None) @@ -164,7 +166,8 @@ draw_text :: proc( content : string, font : Font_ID, pos : Vec2, size : f32 = 0. pos, scale, zoom, - content + content, + // ve.shaper_shape_text_latin, ) return } @@ -258,7 +261,6 @@ init :: proc "c" () path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" }) demo_ctx.font_logo = font_load(path_sawarabi_mincho, 150.0, "SawarabiMincho", 18 ) - // demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 6 ) demo_ctx.font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP") demo_ctx.font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono") demo_ctx.font_small = font_load(path_roboto, 10.0, "Roboto") @@ -644,14 +646,14 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` posy := current_scroll - (section_start + 0.66 + f32(y) * 0.052) c := [5]u8{} codepoint_to_utf8(c[:], grid2[ y * GRID2_W + x ]) - draw_text(string( c[:] ), demo_ctx.font_demo_chinese, { posx, posy }, size = 54) + draw_text(string( c[:] ), demo_ctx.font_demo_grid2, { posx, posy }, size = 54) } for y in 0 ..< GRID3_H do for x in 0 ..< GRID3_W { posx := 0.45 + f32(x) * 0.02 - posy := current_scroll - (section_start + 0.64 + f32(y) * 0.034) + posy := current_scroll - (section_start + 0.64 + f32(y) * 0.04) c := [5]u8{} codepoint_to_utf8( c[:], grid3[ y * GRID3_W + x ]) - draw_text(string( c[:] ), demo_ctx.font_demo_serif, { posx, posy }, size = 44) + draw_text(string( c[:] ), demo_ctx.font_demo_grid3, { posx, posy }, size = 44) } } diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index 4f1ea6cd216c5424493d593354ddfd07a88d4958..4217b43a23ccabe1873cfc7c74981b97e6aebf01 100644 GIT binary patch delta 85 zcmeB}E8a0ze1a^ixrv#n*<=Mf@r{;`Y$3eOK%NysVsdbW5tP?eq0YqC)V#T(eRBmP S5HkTWGZ3?E-(10ZH533+j~q+@ delta 85 zcmeB}E8a0ze1a^inVGqf!DIzH@r{;`Y$3eOK%NysVsdbW5tP?eq0aQYv3YYv`{oKp SAZ7w$W*}zSzPW<+YA66?b{!A^ diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 189e308..a85f1b6 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -592,12 +592,12 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, colour.g = 1.0 colour.b = 0.0 } - for pack_id, index in oversized { - error : Allocator_Error - glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) - assert(error == .None) - assert(glyph_pack[pack_id].shape != nil) - } + // for pack_id, index in oversized { + // error : Allocator_Error + // glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) + // assert(error == .None) + // assert(glyph_pack[pack_id].shape != nil) + // } for id, index in oversized { glyph := & glyph_pack[id] @@ -607,6 +607,11 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x ) + + error : Allocator_Error + glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[id]) + assert(error == .None) + assert(glyph.shape != nil) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, glyph_pack[id].shape, @@ -616,6 +621,9 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph_pack[id].draw_transform.scale ) + assert(glyph.shape != nil) + parser_free_shape(entry.parser_info, glyph.shape) + target_quad := & glyph_pack[id].draw_quad draw_to_target : Draw_Call @@ -634,10 +642,10 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, } 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) - } + // 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() @@ -666,12 +674,12 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, profile_begin("to_cache: caching to atlas") if len(to_cache) > 0 { - for pack_id, index in to_cache { - error : Allocator_Error - glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) - assert(error == .None) - assert(glyph_pack[pack_id].shape != nil) - } + // for pack_id, index in to_cache { + // error : Allocator_Error + // glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) + // assert(error == .None) + // assert(glyph_pack[pack_id].shape != nil) + // } for id, index in to_cache { @@ -724,6 +732,11 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, append( & glyph_buffer.clear_draw_list.calls, clear_target_region ) append( & glyph_buffer.draw_list.calls, blit_to_atlas ) + + error : Allocator_Error + glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[id]) + assert(error == .None) + assert(glyph.shape != nil) // Render glyph to glyph render target (FBO) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, @@ -733,13 +746,16 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph.draw_transform.pos, glyph.draw_transform.scale ) + + assert(glyph.shape != nil) + parser_free_shape(entry.parser_info, glyph.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 to_cache { - assert(glyph_pack[pack_id].shape != nil) - parser_free_shape(entry.parser_info, glyph_pack[pack_id].shape) - } + // 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 { diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index e342f8e..dc6d51e 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -107,9 +107,22 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info ) // Recommended shaper. Very performant. // TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal... @(optimization_mode="favor_size") -shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text ) +shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, + atlas : Atlas, + glyph_buffer_size : Vec2, + 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 ) + current_script := harfbuzz.Script.UNKNOWN hb_ucfunc := harfbuzz.unicode_funcs_get_default() harfbuzz.buffer_clear_contents( ctx.hb_buffer ) @@ -142,8 +155,6 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry ) { 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_direction( buffer, harfbuzz.script_get_horizontal_direction( script )) harfbuzz.buffer_set_language ( buffer, harfbuzz.language_get_default() ) @@ -159,11 +170,12 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry 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 := cast(Glyph) hb_glyph.codepoint + glyph := cast(Glyph) hb_glyph.codepoint if hb_glyph.cluster > 0 { @@ -172,6 +184,8 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry 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 { @@ -194,7 +208,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry (max_line_width^) = max(max_line_width^, position.x) is_empty := parser_is_glyph_empty(entry.parser_info, glyph) - if ! is_empty { + if ! is_empty && glyph != 0 { append( & output.glyph, glyph ) append( & output.position, glyph_pos) } @@ -235,7 +249,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry & position, & max_line_width, & line_count, - font_px_Size, + font_px_size, font_scale, ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold @@ -252,7 +266,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry & position, & max_line_width, & line_count, - font_px_Size, + font_px_size, font_scale, ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold @@ -261,27 +275,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry // Set the final size output.size.x = max_line_width output.size.y = f32(line_count) * line_height - return -} -shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context, - atlas : Atlas, - glyph_buffer_size : Vec2, - 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 ) - - shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output ) - // 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) ) @@ -302,6 +296,7 @@ shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled ) } profile_end() + return } // Basic western alphabet based shaping. Not that much faster than harfbuzz if at all. diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 76826de..3e93885 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -428,9 +428,18 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) reload_array( & draw_list.indices, allocator) reload_array( & draw_list.calls, allocator) + parser_reload(& ctx.parser_ctx, allocator) + // Scope Stack { - + stack := & ctx.stack + reload_array(& stack.font, allocator) + reload_array(& stack.font_size, allocator) + reload_array(& stack.colour, allocator) + reload_array(& stack.view, allocator) + reload_array(& stack.position, allocator) + reload_array(& stack.scale, allocator) + reload_array(& stack.zoom, allocator) } } @@ -497,7 +506,14 @@ shutdown :: proc( ctx : ^Context ) // Scope Stack { - + stack := & ctx.stack + delete(stack.font) + delete(stack.font_size) + delete(stack.colour) + delete(stack.view) + delete(stack.position) + delete(stack.scale) + delete(stack.zoom) } } @@ -833,7 +849,7 @@ draw_text_normalized_space :: proc( ctx : ^Context, position : Vec2, scale : Vec2, text_utf8 : string, - shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_text_uncached_advanced + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz ) { profile(#procedure) @@ -970,7 +986,7 @@ draw_text_view_space :: proc(ctx : ^Context, scale : Vec2, zoom : f32, text_utf8 : string, - shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_text_uncached_advanced + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz ) { profile(#procedure) @@ -1111,7 +1127,7 @@ absolute_scale := peek(stack.scale ) * scale */ // @(optimization_mode = "favor_size") draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string, - shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_text_uncached_advanced + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz ) { profile(#procedure) @@ -1216,7 +1232,9 @@ measure_shape_size :: #force_inline proc( ctx : ^Context, shape : Shaped_Text ) } // Don't use this if you already have the shape instead use measure_shape_size -measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> (measured : Vec2) +measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz +) -> (measured : Vec2) { // profile(#procedure) assert( ctx != nil ) @@ -1237,7 +1255,7 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size entry, target_px_size, target_font_scale, - shaper_shape_text_uncached_advanced + shaper_proc ) return shaped.size * target_scale } @@ -1261,7 +1279,9 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID //#region("shaping") -shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text +shape_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz +) -> Shaped_Text { profile(#procedure) assert( len(text_utf8) > 0 ) @@ -1279,34 +1299,15 @@ shape_text_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size entry, target_px_size, target_font_scale, - shaper_shape_text_latin + shaper_proc ) } -shape_text_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string ) -> Shaped_Text -{ - profile(#procedure) - assert( len(text_utf8) > 0 ) - entry := ctx.entries[ font ] - - target_px_size := px_size * ctx.px_scalar - target_font_scale := parser_scale( entry.parser_info, target_px_size ) - - return shaper_shape_text_cached( text_utf8, - & ctx.shaper_ctx, - & ctx.shape_cache, - ctx.atlas, - vec2(ctx.glyph_buffer.size), - font, - entry, - target_px_size, - target_font_scale, - shaper_shape_text_uncached_advanced - ) -} // User handled shaped text. Will not be cached -shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text ) +shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz +) { profile(#procedure) assert( len(text_utf8) > 0 ) @@ -1315,29 +1316,7 @@ shape_text_latin_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, target_px_size := px_size * ctx.px_scalar target_font_scale := parser_scale( entry.parser_info, target_px_size ) - shaper_shape_text_latin(& ctx.shaper_ctx, - ctx.atlas, - vec2(ctx.glyph_buffer.size), - entry, - target_px_size, - target_font_scale, - text_utf8, - shape - ) - return -} - -// User handled shaped text. Will not be cached -shape_text_advanced_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text ) -{ - profile(#procedure) - assert( len(text_utf8) > 0 ) - entry := ctx.entries[ font ] - - target_px_size := px_size * ctx.px_scalar - target_font_scale := parser_scale( entry.parser_info, target_px_size ) - - shaper_shape_text_uncached_advanced(& ctx.shaper_ctx, + shaper_proc(& ctx.shaper_ctx, ctx.atlas, vec2(ctx.glyph_buffer.size), entry, From 4f9de8f539249d070f489ff9f673b632c6f75e51 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 01:08:56 -0500 Subject: [PATCH 21/62] refinement to demo --- examples/sokol_demo/sokol_demo.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 9b6eacf..d9f8bfb 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -235,7 +235,7 @@ init :: proc "c" () ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, - px_scalar = 1.25, + px_scalar = 1.4, alpha_sharpen = 0.0, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) @@ -313,7 +313,7 @@ frame :: proc "c" () frame_duration := cast(f32) app.frame_duration() - scroll_velocity += demo_ctx.mouse_scroll.y * 0.05 + scroll_velocity += demo_ctx.mouse_scroll.y * 0.035 mouse_down_pos = -1.0 substep_dt := frame_duration / 4.0 for _ in 0 ..< 4 { @@ -504,7 +504,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` if current_scroll > section_start && current_scroll < section_end { GRID_W :: 80 - GRID_H :: 50 + GRID_H :: 45 NUM_RAINDROPS :: GRID_W / 3 @static init_grid := false @@ -527,7 +527,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` } @static fixed_timestep_passed : f32 = 0.0 - fixed_timestep : f32 = (1.0 / 20.0) + fixed_timestep : f32 = (1.0 / 30.0) fixed_timestep_passed += frame_duration for fixed_timestep_passed > fixed_timestep { @@ -571,7 +571,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` // Cache pressure test section_start = 5.3 section_end = 6.2 - if current_scroll > section_start && current_scroll < section_end && true + if current_scroll > section_start && current_scroll < section_end { GRID_W :: 30 GRID_H :: 15 @@ -588,7 +588,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.` @static fixed_timestep_passed : f32 = 0.0 fixed_timestep_passed += frame_duration - fixed_timestep := f32(1.0 / 20.0) + fixed_timestep := f32(1.0 / 120.0) for fixed_timestep_passed > fixed_timestep { rotate_current = (rotate_current + 1) % 4 From a1b4bcf77f6625d50707505f07d89395ec2a201d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 01:36:51 -0500 Subject: [PATCH 22/62] Update Readme.md --- Readme.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 056702d..35b8d03 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # VE Font Cache -https://github.com/user-attachments/assets/b74f1ec1-f980-45df-b604-d6b7d87d40ff + This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. @@ -50,4 +50,12 @@ Currently the scripts provided & the library itself were developed & tested on W The library depends on harfbuzz, & stb_truetype to build. Note: harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet). -![image](https://github.com/user-attachments/assets/2f6c0b36-179c-42fe-8903-7640ae3c209e) +# Gallery + +![sokol_demo_2025-01-11_01-32-24](https://github.com/user-attachments/assets/4aea2b23-4362-47e6-b6d1-286e84891702) + +https://github.com/user-attachments/assets/db8c7725-84dd-48df-9a3f-65605d3ab444 + +https://github.com/user-attachments/assets/40030308-37db-492d-a196-f830e8a39f3c + +https://github.com/user-attachments/assets/0985246b-74f8-4d1c-82d8-053414c44aec From d56e1d608c045efce8eca8cae56eb9a8cc37033b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 01:41:42 -0500 Subject: [PATCH 23/62] Finalize picking for demo Won't be satsified, just need to fix the actual rendering so the tuning isn't required. --- examples/sokol_demo/sokol_demo.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index d9f8bfb..680d5e8 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -235,8 +235,8 @@ init :: proc "c" () ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, - px_scalar = 1.4, - alpha_sharpen = 0.0, + px_scalar = 1.8, + alpha_sharpen = 0.05, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) @@ -332,6 +332,8 @@ frame :: proc "c" () frametime_text := fmt.tprintf("Frametime %v", frame_duration) draw_text(frametime_text, demo_ctx.font_title, {0.0, 0.0}, size = 30) + // Below is content based on the original demo from the C++ library. + if current_scroll < 1.5 { intro := `Ça va! Everything here is rendered using VE Font Cache, a single header-only library designed for game engines. It aims to: From dbe97a7176ca7cb5919b222ced70feff174aa6eb Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 08:47:11 -0500 Subject: [PATCH 24/62] Made sure alpha sharpen is only applied when the color alpha is at or above 1.0 --- Readme.md | 2 +- docs/Readme.md | 80 ++++++++++++++++++++--------- examples/sokol_demo/sokol_demo.odin | 6 +-- vefontcache/vefontcache.odin | 38 +++++++++----- 4 files changed, 85 insertions(+), 41 deletions(-) diff --git a/Readme.md b/Readme.md index 35b8d03..de8d04c 100644 --- a/Readme.md +++ b/Readme.md @@ -18,7 +18,7 @@ Features: * Tracks text layers! * Push and pop stack for font, font_size, colour, view, position, scale and zoom! * Enforce even only font-sizing (useful for linear-zoom) - * Snap-positining to view for better hinting + * Snap-positioning to view for better hinting * Basic or advanced text shaping via Harfbuzz * All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu. * Can hand thousands of draw text calls with very large or small shapes. diff --git a/docs/Readme.md b/docs/Readme.md index 5ddbbeb..09bbb5f 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,25 +1,6 @@ # Interface -TODO: OUTDATED - -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 @@ -33,21 +14,72 @@ 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 if doing tuning of the library. +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. +### load_font + +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. + +### unload_font + +Will free an entry, (parser and shaper resources also freed) + +## Scoping Context interface + +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_`: push with a defer pop +* `push_` +* `pop_` + +## 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 draw calls. + +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. + +For the provided procedures that utilize it, they reference the context's zoom_px_interval. It can be set with `set_zoom_px_interval` and the default value is 2. + +## resolve_zoom_size_scale + + + ### configure_snap 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. If snapping is not desired, set the snap_width and height before calling draw_text to 0. -### get_cursor_pos - -Will provide the current cursor_pos for the resulting text drawn. ### set_color diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 680d5e8..c600d32 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -231,12 +231,12 @@ init :: proc "c" () shaper_opts := ve.Init_Shaper_Params_Default shaper_opts.snap_glyph_position = true - + ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator, glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, - px_scalar = 1.8, - alpha_sharpen = 0.05, + px_scalar = 1.6, + alpha_sharpen = 0.4, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 3e93885..6703e71 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -697,7 +697,7 @@ auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { //#endregion("scoping") -//#region("draw_list generation") +//#region("misc") get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } @@ -759,6 +759,10 @@ set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap) } +//#endreigon("misc") + +//#region("draw_list generation") + /* The most fundamental interface-level draw shape procedure. Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied. view, position, and scale are expected to be in unsigned normalized space: @@ -799,8 +803,9 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, entry := ctx.entries[ font ] - adjusted_colour := colour - adjusted_colour.a += ctx.alpha_sharpen + should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen target_px_size := px_size * ctx.px_scalar target_scale := scale * (1 / ctx.px_scalar) @@ -860,8 +865,9 @@ draw_text_normalized_space :: proc( ctx : ^Context, ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_colour := colour - adjusted_colour.a += ctx.alpha_sharpen + should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen // Does nothing when px_scalar is 1.0 target_px_size := px_size * ctx.px_scalar @@ -930,8 +936,9 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, entry := ctx.entries[ font ] - adjusted_colour := colour - adjusted_colour.a = 1.0 + ctx.alpha_sharpen + should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) @@ -998,8 +1005,9 @@ draw_text_view_space :: proc(ctx : ^Context, ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_colour := colour - adjusted_colour.a += ctx.alpha_sharpen + should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) @@ -1074,8 +1082,10 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_colour := peek(stack.colour) - adjusted_colour.a += ctx.alpha_sharpen + colour := peek(stack.colour) + should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen px_size := peek(stack.font_size) zoom := peek(stack.zoom) @@ -1152,8 +1162,10 @@ draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string, ctx.cursor_pos = {} entry := ctx.entries[ font ] - adjusted_colour := peek(stack.colour) - adjusted_colour.a += ctx.alpha_sharpen + colour := peek(stack.colour) + should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) + adjusted_colour := colour + adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen px_size := peek(stack.font_size) zoom := peek(stack.zoom) From b78a544aa8570703f4af65ed11b337654dcc6a6b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 11:29:48 -0500 Subject: [PATCH 25/62] Progress on documentation --- docs/Readme.md | 140 ++++++--- docs/guide_.architecturemd | 28 ++ docs/guide_backend.md | 7 + examples/sokol_demo/sokol_demo.odin | 2 +- thirdparty/stb/lib/stb_truetype.lib | Bin 380552 -> 380552 bytes vefontcache/pkg_mapping.odin | 2 +- vefontcache/shaper.odin | 11 +- vefontcache/vefontcache.odin | 446 ++++++++++++++-------------- 8 files changed, 363 insertions(+), 273 deletions(-) create mode 100644 docs/guide_.architecturemd create mode 100644 docs/guide_backend.md diff --git a/docs/Readme.md b/docs/Readme.md index 09bbb5f..97797c1 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -14,12 +14,20 @@ 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. +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. +### clear_atlas_region_caches + +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. + +### clear_shape_cache + +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). + ### load_font 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. @@ -28,23 +36,55 @@ Will load an instance of a font. The user needs to load the file's bytes themsel Will free an entry, (parser and shaper resources also freed) -## Scoping Context interface +## Shaping -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. +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. -* 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. +### shape_text -Procedure types: +Will shape the text using the `shaper_proc` arugment (user overloadable). Shape will be cached by the library. -* `scope_`: push with a defer pop -* `push_` -* `pop_` +### 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 + +### 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 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. +By default, if `get_draw_list_layer` is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +The draw layer offsets are cleared with `flush_draw_list` + +### flush_draw_list + +Will clear the draw list and draw layer offsets. + +### flush_draw_list_layer + +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. + +### 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 @@ -62,62 +102,68 @@ 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 +### resolve_draw_px_size -Used to constrain the px_size used in draw calls. +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. -For the provided procedures that utilize it, they reference the context's zoom_px_interval. It can be set with `set_zoom_px_interval` and the default value is 2. +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 +### 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. -### configure_snap +### set_alpha_scalar -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. +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. -If snapping is not desired, set the snap_width and height before calling draw_text to 0. +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 -### set_color +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: -Sets the color to utilize on `Draw_Call`s for FrameBuffer.Target or .Target_Uncached 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 ) +``` -### get_draw_list +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). -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. +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. -### get_draw_list_layer +### set_zoom_px_interval -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. -By default, if `get_draw_list_layer` is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. +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. -The draw layer offsets are cleared with `flush_draw_list` +### set_snap_glyph_shape_position -### flush_draw_list +During the shaping pass, the position of each glyph can be rounded up to the integer to (ussually) allow better hinting. -Will clear the draw list and draw layer offsets. +### set_snap_glyph_render_height -### flush_draw_list_layer +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. -Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays. +## Scope Stack -### measure_text_size +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. -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. +* 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. -### get_font_vertical_metrics +Procedure types: -A wrapper for `parser_get_font_vertical_metrics`. Will provide the ascent, descent, and line_gap for a font entry. - -### clear_atlas_region_caches - -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. - -### clear_shape_cache - -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). +* `scope_`: push with a defer pop +* `push_` +* `pop_` diff --git a/docs/guide_.architecturemd b/docs/guide_.architecturemd new file mode 100644 index 0000000..9893ee5 --- /dev/null +++ b/docs/guide_.architecturemd @@ -0,0 +1,28 @@ +# Architecture + +The purpose of this library to really allieviate four issues with one encapsulation: + +* font parsing +* text codepoint shaping +* glyph shape triangulation +* glyph draw-list generation + +Shaping text, getting metrics for the glyphs, triangulating glyphs, and anti-aliasing their render are expensive todo per frame. So anything related to that compute that may be cached, will be. + +There are two cache types used: + +* shape cache (shape_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). So far these are the text shaping itself, and per-glyph infos: atlas_lru_code (atlas key), atlas region resolution, & glyph bounds. +The atlas region cache tracks what slots have glyphs rendered to the texture atlas. This essentially is caching of triangulation and super-sampling compute. + +All caching uses the [LRU.odin](../vefontcache/LRU.odin) + +## Codepaths + +## Library Lifetime + +## Draw List Generation + +The base draw list generation pipepline provided by the library allows the user to batch whatever the want into a single "layer". However diff --git a/docs/guide_backend.md b/docs/guide_backend.md new file mode 100644 index 0000000..8360f74 --- /dev/null +++ b/docs/guide_backend.md @@ -0,0 +1,7 @@ +# Backend Guide + +The end-user needs adapt this library for hookup into their own codebase. As an example they may see the [examples](../examples/) and [backend](../backend/) for working code of what this guide will go over. + +When rendering text, the two products the user has to deal with: The text to draw and their "layering". Similar to UIs text should be drawn in layer batches, where each layer can represent a pass on some arbitrary set of distictions between the other layers. + + diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index c600d32..85988ad 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -236,7 +236,7 @@ init :: proc "c" () glyph_draw_params = glyph_draw_opts, shaper_params = shaper_opts, px_scalar = 1.6, - alpha_sharpen = 0.4, + alpha_sharpen = 0.35, ) ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 ) diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index 4217b43a23ccabe1873cfc7c74981b97e6aebf01..bc37f121124d31e11ea0ebda195bbf87be6966ec 100644 GIT binary patch delta 89 zcmeB}E8a0ze1Z&{nSrU9x$$HLJMoQ{4{f2`%?fs6R!CBlgDVUnT%*Zd6>3Z`>YF!J Xv~Q|l1Y#y2W(H!G?VBoCuZ02twxJ#1 delta 89 zcmeB}E8a0ze1Z&{skw=nso7)&JMoQ{4{f2`%?fs6R!CBlgDVVS+^z~WrUPuvn=0Bj URWJfE6A&{4G0XN%6|C1n0jroDSO5S3 diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index 62cb6e1..a684efc 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -48,7 +48,7 @@ append :: proc { } append_soa :: proc { - append_soa_elem + append_soa_elem, } ceil :: proc { diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index dc6d51e..6ebb3b9 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -104,6 +104,7 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info ) if info.blob != nil do harfbuzz.blob_destroy( info.blob ) } +// 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") @@ -299,6 +300,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, return } +// 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, @@ -347,11 +349,12 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index ) if ! is_glyph_empty { + if ctx.snap_glyph_position { + position.x = ceil(position.x) + position.y = ceil(position.y) + } append( & output.glyph, glyph_index) - append( & output.position, Vec2 { - ceil(position.x), - ceil(position.y) - }) + append( & output.position, position) } advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint ) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 6703e71..c0b009c 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -64,27 +64,25 @@ Context :: struct { entries : [dynamic]Entry, // TODO(Ed): Review these when preparing to handle lifting of working context to a thread context. - glyph_buffer : Glyph_Draw_Buffer, - atlas : Atlas, - shape_cache : Shaped_Text_Cache, - draw_list : Draw_List, + glyph_buffer : Glyph_Draw_Buffer, // -> draw.odin + atlas : Atlas, // -> atlas.odin + shape_cache : Shaped_Text_Cache, // -> shaper.doin + draw_list : Draw_List, // -> draw.odin batch_shapes_buffer : [dynamic]Shaped_Text, // Used for the procs that batch a layer of text. // Tracks the offsets for the current layer in a draw_list - draw_layer : struct { + draw_layer : struct { vertices_offset : int, indices_offset : int, calls_offset : int, }, + // See: get_draw_list_layer & flush_draw_list_layer // Note(Ed): Not really used anymore. // debug_print : b32, // debug_print_verbose : b32, - snap_width : f32, - snap_height : f32, - // Will enforce even px_size when drawing. even_size_only : f32, @@ -94,7 +92,7 @@ Context :: struct { stack : Scope_Stack, - cursor_pos : Vec2, // TODO(Ed): Review this, no longer used much at all... (still useful I guess) + cursor_pos : Vec2, // Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges. // Has a boldening side-effect. If overblown will look smeared. alpha_sharpen : f32, @@ -107,6 +105,8 @@ Context :: struct { default_curve_quality : i32, } +//#region("Init Params") + Init_Atlas_Params :: struct { size_multiplier : u32, // How much to scale the the atlas size to. (Affects everything, the base is 4096 x 2048 and everything follows from there) glyph_padding : u32, // Padding to add to bounds__scaled for choosing which atlas region. @@ -133,7 +133,7 @@ Init_Glyph_Draw_Params :: struct { } Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { - snap_glyph_height = false, + snap_glyph_height = true, over_sample = 4, draw_padding = Init_Atlas_Params_Default.glyph_padding, shape_gen_scratch_reserve = 512, @@ -150,7 +150,7 @@ Init_Shaper_Params :: struct { } Init_Shaper_Params_Default :: Init_Shaper_Params { - snap_glyph_position = false, + snap_glyph_position = true, adv_snap_small_font_threshold = 0, } @@ -167,6 +167,8 @@ Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params { reserve = 128, } +//#endregion("Init Params") + //#region("lifetime") // ve_fontcache_init @@ -176,15 +178,15 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N glyph_draw_params := Init_Glyph_Draw_Params_Default, shape_cache_params := Init_Shape_Cache_Params_Default, shaper_params := Init_Shaper_Params_Default, - alpha_sharpen : f32 = 0.0, - px_scalar : f32 = 1, + alpha_sharpen : f32 = 0.35, + px_scalar : f32 = 1.6, zoom_px_interval : i32 = 2, // Curve quality to use for a font when unspecified, // Affects step size for bezier curve passes in generate_glyph_pass_draw_list default_curve_quality : u32 = 3, entires_reserve : u32 = 256, - scope_stack_reserve : u32 = 128, + scope_stack_reserve : u32 = 32, ) { assert( ctx != nil, "Must provide a valid context" ) @@ -517,34 +519,6 @@ shutdown :: proc( ctx : ^Context ) } } -// Can be used with hot-reload -clear_atlas_region_caches :: proc(ctx : ^Context) -{ - lru_clear(& ctx.atlas.region_a.state) - lru_clear(& ctx.atlas.region_b.state) - lru_clear(& ctx.atlas.region_c.state) - lru_clear(& ctx.atlas.region_d.state) - - ctx.atlas.region_a.next_idx = 0 - ctx.atlas.region_b.next_idx = 0 - ctx.atlas.region_c.next_idx = 0 - ctx.atlas.region_d.next_idx = 0 -} - -// Can be used with hot-reload -clear_shape_cache :: proc (ctx : ^Context) -{ - lru_clear(& ctx.shape_cache.state) - for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 { - stroage_entry := & ctx.shape_cache.storage[idx] - stroage_entry.end_cursor_pos = {} - stroage_entry.size = {} - clear(& stroage_entry.glyph) - clear(& stroage_entry.position) - } - ctx.shape_cache.next_cache_id = 0 -} - load_font :: proc( ctx : ^Context, label : string, data : []byte, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID, error : Load_Font_Error) { profile(#procedure) @@ -613,153 +587,90 @@ unload_font :: proc( ctx : ^Context, font : Font_ID ) shaper_unload_font( & entry.shaper_info ) } +// Can be used with hot-reload +clear_atlas_region_caches :: proc(ctx : ^Context) +{ + lru_clear(& ctx.atlas.region_a.state) + lru_clear(& ctx.atlas.region_b.state) + lru_clear(& ctx.atlas.region_c.state) + lru_clear(& ctx.atlas.region_d.state) + + ctx.atlas.region_a.next_idx = 0 + ctx.atlas.region_b.next_idx = 0 + ctx.atlas.region_c.next_idx = 0 + ctx.atlas.region_d.next_idx = 0 +} + +// Can be used with hot-reload +clear_shape_cache :: proc (ctx : ^Context) +{ + lru_clear(& ctx.shape_cache.state) + for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 { + stroage_entry := & ctx.shape_cache.storage[idx] + stroage_entry.end_cursor_pos = {} + stroage_entry.size = {} + clear(& stroage_entry.glyph) + clear(& stroage_entry.position) + } + ctx.shape_cache.next_cache_id = 0 +} + //#endregion("lifetime") -//#region("scoping") +//#region("shaping") -/* Scope stacking ease of use interface. +// For high performance, the user should track the shapes and use the draw list interface on shapes. +// Doing so avoids cache lookups. -View: Extents in 2D for the relative space the the text is being drawn within. -Used with snap_to_view_extent to enforce position snapping. - -Position: Used with a draw procedure that uses relative positioning will offset the incoming position by the given amount. -Scale : Used with a draw procedure that uses relative scaling, will scale the procedures incoming scale by the given amount. -Zoom : Used with a draw procedure that uses scaling via zoom, will scale the procedure's incoming font size & scale based on an 'canvas' camera's notion of it. -*/ - -@(deferred_in = auto_pop_font) -scope_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } -push_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } -pop_font :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font) } -auto_pop_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) } - -@(deferred_in = auto_pop_font_size) -scope_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } -push_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } -pop_font_size :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font_size) } -auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); pop(& ctx.stack.font_size) } - -@(deferred_in = auto_pop_colour ) -scope_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } -push_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } -pop_colour :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.colour) } -auto_pop_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); pop(& ctx.stack.colour) } - -@(deferred_in = auto_pop_view) -scope_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } -push_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } -pop_view :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.view) } -auto_pop_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.view) } - -@(deferred_in = auto_pop_position) -scope_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } -push_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } -pop_position :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop( & ctx.stack.position) } -auto_pop_position :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop( & ctx.stack.position) } - -@(deferred_in = auto_pop_scale) -scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } -push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } -pop_scale :: #force_inline proc( ctx : ^Context, ) { assert(ctx != nil); pop(& ctx.stack.scale) } -auto_pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.scale) } - -@(deferred_in = auto_pop_zoom ) -scope_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom ) } -push_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom) } -pop_zoom :: #force_inline proc( ctx : ^Context ) { pop(& ctx.stack.zoom) } -auto_pop_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { pop(& ctx.stack.zoom) } - -@(deferred_in = auto_pop_vpz) -scope_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { - assert(ctx != nil) - append(& ctx.stack.view, camera.view ) - append(& ctx.stack.position, camera.position ) - append(& ctx.stack.zoom, camera.zoom ) -} -push_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { - assert(ctx != nil) - append(& ctx.stack.view, camera.view ) - append(& ctx.stack.position, camera.position ) - append(& ctx.stack.zoom, camera.zoom ) -} -pop_vpz :: #force_inline proc( ctx : ^Context ) { - assert(ctx != nil) - pop(& ctx.stack.view ) - pop(& ctx.stack.position) - pop(& ctx.stack.zoom ) -} -auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { - assert(ctx != nil) - pop(& ctx.stack.view ) - pop(& ctx.stack.position) - pop(& ctx.stack.zoom ) -} - -//#endregion("scoping") - -//#region("misc") - -get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } - -// 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) -get_normalized_position_scale :: #force_inline proc "contextless" ( position, scale, view : Vec2 ) -> (position_norm, scale_norm : Vec2) +shape_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz +) -> Shaped_Text { - snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } - should_snap := view * snap_quotient + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] - snapped_position := position - snapped_position.x = ceil(position.x * view.x) * snap_quotient.x - snapped_position.y = ceil(position.y * view.y) * snap_quotient.y + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) - snapped_position *= should_snap - snapped_position.x = max(snapped_position.x, position.x) - snapped_position.y = max(snapped_position.y, position.y) - - position_norm = snapped_position - scale_norm = scale * snap_quotient - return + return shaper_shape_text_cached( text_utf8, + & ctx.shaper_ctx, + & ctx.shape_cache, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + font, + entry, + target_px_size, + target_font_scale, + shaper_proc + ) } -// Used to constrain the px_size used in draw calls. -resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, interval, min, max : f32 ) -> (resolved_size : f32) { - interval_quotient := 1.0 / f32(interval) - interval_size := round(px_size * interval_quotient) * interval - resolved_size = clamp( interval_size, min, max ) - return -} - -// 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 -resolve_zoom_size_scale :: #force_inline proc "contextless" ( zoom, px_size : f32, scale : Vec2, interval, min, max : f32, clamp_scale : Vec2 ) -> (resolved_size : f32, zoom_scale : Vec2) +// User handled shaped text. Will not be cached +shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text, + shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz +) { - zoom_px_size := px_size * zoom - resolved_size = resolve_draw_px_size( zoom_px_size, interval, min, max ) - zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) - zoom_scale = zoom_diff_scalar * scale - zoom_scale.x = clamp(zoom_scale.x, 0, clamp_scale.x) - zoom_scale.y = clamp(zoom_scale.y, 0, clamp_scale.y) + profile(#procedure) + assert( len(text_utf8) > 0 ) + entry := ctx.entries[ font ] + + target_px_size := px_size * ctx.px_scalar + target_font_scale := parser_scale( entry.parser_info, target_px_size ) + + shaper_proc(& ctx.shaper_ctx, + ctx.atlas, + vec2(ctx.glyph_buffer.size), + entry, + target_px_size, + target_font_scale, + text_utf8, + shape + ) return } -set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } -set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } -set_zoom_px_interval :: #force_inline proc( ctx : ^Context, interval : i32 ) { assert(ctx != nil); ctx.zoom_px_interval = f32(interval) } - -// During a shaping pass on text, will snap each glyph's position via ceil. -set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { - assert(ctx != nil) - ctx.shaper_ctx.snap_glyph_position = should_snap -} - -// During to_cache pass within batch_generate_glyphs_draw_list, will snap the quad's size using ceil. -set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { - assert(ctx != nil) - ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap) -} - -//#endreigon("misc") +//#endregion("shaping") //#region("draw_list generation") @@ -1289,55 +1200,150 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID //#endregion("metrics") -//#region("shaping") +//#region("miscellaneous") -shape_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string, - shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz -) -> Shaped_Text +get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } + +// 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) +get_normalized_position_scale :: #force_inline proc "contextless" ( position, scale, view : Vec2 ) -> (position_norm, scale_norm : Vec2) { - profile(#procedure) - assert( len(text_utf8) > 0 ) - entry := ctx.entries[ font ] + snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } + should_snap := view * snap_quotient - target_px_size := px_size * ctx.px_scalar - target_font_scale := parser_scale( entry.parser_info, target_px_size ) + snapped_position := position + snapped_position.x = ceil(position.x * view.x) * snap_quotient.x + snapped_position.y = ceil(position.y * view.y) * snap_quotient.y - return shaper_shape_text_cached( text_utf8, - & ctx.shaper_ctx, - & ctx.shape_cache, - ctx.atlas, - vec2(ctx.glyph_buffer.size), - font, - entry, - target_px_size, - target_font_scale, - shaper_proc - ) -} + snapped_position *= should_snap + snapped_position.x = max(snapped_position.x, position.x) + snapped_position.y = max(snapped_position.y, position.y) - -// User handled shaped text. Will not be cached -shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text, - shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz -) -{ - profile(#procedure) - assert( len(text_utf8) > 0 ) - entry := ctx.entries[ font ] - - target_px_size := px_size * ctx.px_scalar - target_font_scale := parser_scale( entry.parser_info, target_px_size ) - - shaper_proc(& ctx.shaper_ctx, - ctx.atlas, - vec2(ctx.glyph_buffer.size), - entry, - target_px_size, - target_font_scale, - text_utf8, - shape - ) + position_norm = snapped_position + scale_norm = scale * snap_quotient return } -//#endregion("shaping") +// Used to constrain the px_size used in draw calls. +resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, interval, min, max : f32 ) -> (resolved_size : f32) { + interval_quotient := 1.0 / f32(interval) + interval_size := round(px_size * interval_quotient) * interval + resolved_size = clamp( interval_size, min, max ) + return +} + +// 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 +resolve_zoom_size_scale :: #force_inline proc "contextless" ( + zoom, px_size : f32, scale : Vec2, interval, min, max : f32, clamp_scale : Vec2 +) -> (resolved_size : f32, zoom_scale : Vec2) +{ + zoom_px_size := px_size * zoom + resolved_size = resolve_draw_px_size( zoom_px_size, interval, min, max ) + zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size) + zoom_scale = zoom_diff_scalar * scale + zoom_scale.x = clamp(zoom_scale.x, 0, clamp_scale.x) + zoom_scale.y = clamp(zoom_scale.y, 0, clamp_scale.y) + return +} + +set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } +set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } +set_zoom_px_interval :: #force_inline proc( ctx : ^Context, interval : i32 ) { assert(ctx != nil); ctx.zoom_px_interval = f32(interval) } + +// During a shaping pass on text, will snap each glyph's position via ceil. +set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { + assert(ctx != nil) + ctx.shaper_ctx.snap_glyph_position = should_snap +} + +// During to_cache pass within batch_generate_glyphs_draw_list, will snap the quad's size using ceil. +set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { + assert(ctx != nil) + ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap) +} + +//#endregion("misc") + +//#region("scope stack") + +/* Scope stacking ease of use interface. + +View: Extents in 2D for the relative space the the text is being drawn within. +Used with snap_to_view_extent to enforce position snapping. + +Position: Used with a draw procedure that uses relative positioning will offset the incoming position by the given amount. +Scale : Used with a draw procedure that uses relative scaling, will scale the procedures incoming scale by the given amount. +Zoom : Used with a draw procedure that uses scaling via zoom, will scale the procedure's incoming font size & scale based on an 'canvas' camera's notion of it. +*/ + +@(deferred_in = auto_pop_font) +scope_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } +push_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) } +pop_font :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font) } +auto_pop_font :: #force_inline proc( ctx : ^Context, font : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) } + +@(deferred_in = auto_pop_font_size) +scope_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } +push_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) } +pop_font_size :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.font_size) } +auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size : f32 ) { assert(ctx != nil); pop(& ctx.stack.font_size) } + +@(deferred_in = auto_pop_colour ) +scope_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } +push_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); append(& ctx.stack.colour, colour) } +pop_colour :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.colour) } +auto_pop_colour :: #force_inline proc( ctx : ^Context, colour : RGBAN ) { assert(ctx != nil); pop(& ctx.stack.colour) } + +@(deferred_in = auto_pop_view) +scope_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } +push_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); append(& ctx.stack.view, view) } +pop_view :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop(& ctx.stack.view) } +auto_pop_view :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.view) } + +@(deferred_in = auto_pop_position) +scope_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } +push_position :: #force_inline proc( ctx : ^Context, position : Vec2 ) { assert(ctx != nil); append(& ctx.stack.position, position ) } +pop_position :: #force_inline proc( ctx : ^Context ) { assert(ctx != nil); pop( & ctx.stack.position) } +auto_pop_position :: #force_inline proc( ctx : ^Context, view : Vec2 ) { assert(ctx != nil); pop( & ctx.stack.position) } + +@(deferred_in = auto_pop_scale) +scope_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } +push_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) } +pop_scale :: #force_inline proc( ctx : ^Context, ) { assert(ctx != nil); pop(& ctx.stack.scale) } +auto_pop_scale :: #force_inline proc( ctx : ^Context, scale : Vec2 ) { assert(ctx != nil); pop(& ctx.stack.scale) } + +@(deferred_in = auto_pop_zoom ) +scope_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom ) } +push_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { append(& ctx.stack.zoom, zoom) } +pop_zoom :: #force_inline proc( ctx : ^Context ) { pop(& ctx.stack.zoom) } +auto_pop_zoom :: #force_inline proc( ctx : ^Context, zoom : f32 ) { pop(& ctx.stack.zoom) } + +@(deferred_in = auto_pop_vpz) +scope_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { + assert(ctx != nil) + append(& ctx.stack.view, camera.view ) + append(& ctx.stack.position, camera.position ) + append(& ctx.stack.zoom, camera.zoom ) +} +push_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { + assert(ctx != nil) + append(& ctx.stack.view, camera.view ) + append(& ctx.stack.position, camera.position ) + append(& ctx.stack.zoom, camera.zoom ) +} +pop_vpz :: #force_inline proc( ctx : ^Context ) { + assert(ctx != nil) + pop(& ctx.stack.view ) + pop(& ctx.stack.position) + pop(& ctx.stack.zoom ) +} +auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { + assert(ctx != nil) + pop(& ctx.stack.view ) + pop(& ctx.stack.position) + pop(& ctx.stack.zoom ) +} + +//#endregion("scope stack") From e8a7b21eba415489d67d2e0323bfd1b3d6392833 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 17:31:21 -0500 Subject: [PATCH 26/62] fix parser handling of glyph shape freeing --- vefontcache/draw.odin | 8 ++++---- vefontcache/parser.odin | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index a85f1b6..8191304 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -611,7 +611,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, error : Allocator_Error glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[id]) assert(error == .None) - assert(glyph.shape != nil) + assert(len(glyph.shape) > 0) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, glyph_pack[id].shape, @@ -621,7 +621,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph_pack[id].draw_transform.scale ) - assert(glyph.shape != nil) + assert(len(glyph.shape) > 0) parser_free_shape(entry.parser_info, glyph.shape) target_quad := & glyph_pack[id].draw_quad @@ -736,7 +736,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, error : Allocator_Error glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[id]) assert(error == .None) - assert(glyph.shape != nil) + assert(len(glyph.shape) > 0) // Render glyph to glyph render target (FBO) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, @@ -747,7 +747,7 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph.draw_transform.scale ) - assert(glyph.shape != nil) + assert(len(glyph.shape) > 0) parser_free_shape(entry.parser_info, glyph.shape) } diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 666f058..0d1eca8 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -126,7 +126,9 @@ parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape ) { - 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 ) @@ -166,11 +168,11 @@ parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_in 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 := transmute( ^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() + shape_raw.allocator = nil_allocator() error = Allocator_Error.None return } From f7e427830049fa790e0ff1df638b4f608591147d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 17:31:32 -0500 Subject: [PATCH 27/62] formatting, cleanup, more progress on docs --- docs/guide_.architecturemd | 28 -------- docs/guide_architecture.md | 128 +++++++++++++++++++++++++++++++++++ vefontcache/LRU.odin | 2 - vefontcache/draw.odin | 27 ++++---- vefontcache/misc.odin | 8 +-- vefontcache/parser.odin | 8 +-- vefontcache/pkg_mapping.odin | 5 ++ vefontcache/shaper.odin | 29 +++++--- vefontcache/vefontcache.odin | 44 +++++------- 9 files changed, 186 insertions(+), 93 deletions(-) delete mode 100644 docs/guide_.architecturemd create mode 100644 docs/guide_architecture.md diff --git a/docs/guide_.architecturemd b/docs/guide_.architecturemd deleted file mode 100644 index 9893ee5..0000000 --- a/docs/guide_.architecturemd +++ /dev/null @@ -1,28 +0,0 @@ -# Architecture - -The purpose of this library to really allieviate four issues with one encapsulation: - -* font parsing -* text codepoint shaping -* glyph shape triangulation -* glyph draw-list generation - -Shaping text, getting metrics for the glyphs, triangulating glyphs, and anti-aliasing their render are expensive todo per frame. So anything related to that compute that may be cached, will be. - -There are two cache types used: - -* shape cache (shape_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). So far these are the text shaping itself, and per-glyph infos: atlas_lru_code (atlas key), atlas region resolution, & glyph bounds. -The atlas region cache tracks what slots have glyphs rendered to the texture atlas. This essentially is caching of triangulation and super-sampling compute. - -All caching uses the [LRU.odin](../vefontcache/LRU.odin) - -## Codepaths - -## Library Lifetime - -## Draw List Generation - -The base draw list generation pipepline provided by the library allows the user to batch whatever the want into a single "layer". However diff --git a/docs/guide_architecture.md b/docs/guide_architecture.md new file mode 100644 index 0000000..501ef9c --- /dev/null +++ b/docs/guide_architecture.md @@ -0,0 +1,128 @@ +# Guide: Architecture + +Overview on the state of package design and codepath layout. + +--- + +The purpose of this library to really allieviate four issues with one encapsulation: + +* font parsing +* text codepoint shaping +* glyph shape triangulation +* glyph draw-list generation + +Shaping text, getting metrics for the glyphs, triangulating glyphs, and anti-aliasing their render are expensive todo per frame. So anything related to that compute that may 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). So far these are the text shaping itself, and per-glyph infos: atlas_lru_code (atlas key), atlas region resolution, & glyph bounds. +The atlas region cache tracks what slots have glyphs rendered to the texture atlas. This essentially is caching of triangulation and super-sampling compute. + +All caching uses the [LRU.odin](../vefontcache/LRU.odin) + +## Codepaths + +### Lifetime + +The library lifetime is pretty straightfoward, you have a startup to do that should just be called sometime in your usual app start.s. 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 those 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. So just call the `clear_atlas_region_caches` & `clear_shape_cache` right after. + +Any scratch memory used for draw list generation is kept persistently in the library's `Context`. I wanted to avoid any dynamic allocation slowness as its an extremely hot path. + +Ideally there should be zero dynamic allocation on a per-frame basis so long as the reserves for the dynamic containers are never exceeded. Its alright if they do as their memory locality is so large their distance in the pages to load into cpu cache won't matter, just needs to be a low incidence. + +### Shaping pass + +If the user is using the library's cache, then at some point `shaper_shape_text_cached` which handles the hasing and lookup. So long as a shape is found it will not enter uncached codepath. 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 fullfill 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`. Which has the following structure: + +```odin +Shaped_Text :: struct #packed { + glyph : [dynamic]Glyph, + position : [dynamic]Vec2, + 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, +} +``` + +What is actually the result of the shaping process is the arrays of glyphs and their positions for the the shape or most historically known as: *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. + +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 + + + +### Draw List Generation + + + +### On Layering + +The base draw list generation pippline provided by the library allows the user to batch whatever the 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 leng 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 a section of their pipline. + +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) diff --git a/vefontcache/LRU.odin b/vefontcache/LRU.odin index 5381890..dab054e 100644 --- a/vefontcache/LRU.odin +++ b/vefontcache/LRU.odin @@ -12,8 +12,6 @@ package vefontcache 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 diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 8191304..9a2b403 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -4,10 +4,7 @@ package vefontcache Note(Ed): This may be seperated in the future into another file dedending on how much is involved with supportin ear-clipping triangulation. */ -import "base:runtime" -import "base:intrinsics" -import "core:slice" -import "thirdparty:freetype" +// import "thirdparty:freetype" Glyph_Trianglation_Method :: enum(i32) { Ear_Clipping, @@ -41,8 +38,8 @@ Glyph_Pack_Entry :: struct #packed { over_sample : Vec2, // Only used for oversized glyphs - shape : Parser_Glyph_Shape, - draw_transform : Transform, + shape : Parser_Glyph_Shape, + draw_transform : Transform, draw_quad : Glyph_Draw_Quad, draw_atlas_quad : Glyph_Draw_Quad, @@ -294,10 +291,10 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, glyph_buffer : ^Glyph_Draw_Buffer, px_scalar : f32, - colour : RGBAN, - entry : Entry, - px_size : f32, - font_scale : f32, + colour : RGBAN, + entry : Entry, + px_size : f32, + font_scale : f32, target_position : Vec2, target_scale : Vec2, @@ -327,7 +324,7 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 ) { - raw := cast(^runtime.Raw_Dynamic_Array) pack + raw := cast(^Raw_Dynamic_Array) pack raw.len += 1 pack[len(pack) - 1] = entry } @@ -478,10 +475,10 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, atlas_size : Vec2, glyph_buffer_size : Vec2, - entry : Entry, - colour : RGBAN, - font_scale : Vec2, - target_scale : Vec2, + entry : Entry, + colour : RGBAN, + font_scale : Vec2, + target_scale : Vec2, ) #no_bounds_check { profile(#procedure) diff --git a/vefontcache/misc.odin b/vefontcache/misc.odin index af16f86..39f847b 100644 --- a/vefontcache/misc.odin +++ b/vefontcache/misc.odin @@ -5,9 +5,7 @@ package vefontcache Just a bunch of utilities. */ -import "base:runtime" import "core:simd" -import "core:math" import core_log "core:log" @@ -16,17 +14,17 @@ peek_array :: #force_inline proc "contextless" ( self : [dynamic]$Type ) -> Type } 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 } diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 0d1eca8..433acb3 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -15,11 +15,7 @@ TODO(Ed): Just keep a local version of stb_truetype and modify it to support a s Already wanted to do so anyway to evaluate the shape generation implementation. */ -import "base:runtime" import "core:c" -import "core:math" -import "core:mem" -import "core:slice" import stbtt "thirdparty:stb/truetype" // import freetype "thirdparty:freetype" @@ -32,7 +28,7 @@ Parser_Font_Info :: struct { label : string, kind : Parser_Kind, using _ : struct #raw_union { - stbtt_info : stbtt.fontinfo, + stbtt_info : stbtt.fontinfo, // freetype_info : freetype.Face }, data : []byte, @@ -74,7 +70,7 @@ parser_stbtt_allocator_proc :: proc( ) -> rawptr { allocator := transmute(^Allocator) allocator_data - result, error := allocator.procedure( allocator.data, cast(mem.Allocator_Mode) type, cast(int) size, cast(int) alignment, old_memory, cast(int) old_size ) + 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) if type == .Alloc || type == .Resize { diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index a684efc..fd884dc 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -3,6 +3,10 @@ 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_soa_footer :: runtime.raw_soa_footer + nil_allocator :: runtime.nil_allocator import "core:hash" ginger16 :: hash.ginger16 import "core:math" @@ -32,6 +36,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 diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index 6ebb3b9..a407ecf 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -25,15 +25,15 @@ Shape_Key :: u32 They have the best ability to avoid costly lookups. */ Shaped_Text :: struct #packed { - glyph : [dynamic]Glyph, - position : [dynamic]Vec2, - atlas_lru_code : [dynamic]Atlas_Key, - region_kind : [dynamic]Atlas_Region_Kind, - bounds : [dynamic]Range2, - end_cursor_pos : Vec2, - size : Vec2, - font_id : Font_ID, - // TODO(Ed): We need to track the font here for usage in user interface when directly drawing the shape. + glyph : [dynamic]Glyph, + position : [dynamic]Vec2, + 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. @@ -48,6 +48,7 @@ Shaped_Text_Cache :: struct { 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, @@ -111,6 +112,7 @@ shaper_unload_font :: #force_inline proc( info : ^Shaper_Info ) 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, @@ -297,6 +299,9 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled ) } profile_end() + + output.font = font + output.px_size = font_px_size return } @@ -305,6 +310,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, 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, @@ -388,6 +394,9 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled ) } profile_end() + + output.font = font + output.px_size = font_px_size } // Shapes are tracked by the library's context using the shape cache @@ -442,7 +451,7 @@ shaper_shape_text_cached :: proc( text_utf8 : string, } storage_entry := & shape_cache.storage[ shape_cache_idx ] - shape_text_uncached( ctx, atlas, glyph_buffer_size, 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 diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index c0b009c..c8f3168 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -3,8 +3,6 @@ See: https://github.com/Ed94/VEFontCache-Odin */ package vefontcache -import "base:runtime" - // See: mappings.odin for profiling hookup DISABLE_PROFILING :: true ENABLE_OVERSIZED_GLYPHS :: true @@ -661,6 +659,7 @@ shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si shaper_proc(& ctx.shaper_ctx, ctx.atlas, vec2(ctx.glyph_buffer.size), + font, entry, target_px_size, target_font_scale, @@ -674,7 +673,7 @@ shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si //#region("draw_list generation") -/* The most fundamental interface-level draw shape procedure. +/* The most basic interface-level draw shape procedure. Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied. view, position, and scale are expected to be in unsigned normalized space: @@ -698,29 +697,20 @@ shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si <-> scale : Scale the glyph beyond its default scaling from its px_size. */ @(optimization_mode="favor_size") -draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, - font : Font_ID, - px_size : f32, - colour : RGBAN, - position : Vec2, - scale : Vec2, - shape : Shaped_Text -) +draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, colour : RGBAN, position : Vec2, scale : Vec2, shape : Shaped_Text ) { profile(#procedure) assert( ctx != nil ) - // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) - assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := ctx.entries[ font ] + entry := ctx.entries[ shape.font ] should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen - 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 ) + target_px_size := shape.px_size + target_scale := scale * (1 / ctx.px_scalar) + target_font_scale := parser_scale( entry.parser_info, shape.px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar, @@ -733,7 +723,7 @@ draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, ) } -/* Non-scoping context. The most fundamental interface-level draw shape procedure (everything else is quality of life warppers). +/* Non-scoping context. The most basic interface-level draw shape procedure (everything else is quality of life warppers). Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied. view, position, and scale are expected to be in unsigned normalized space: @@ -829,8 +819,6 @@ draw_text_normalized_space :: proc( ctx : ^Context, */ // @(optimization_mode="favor_size") draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, - font : Font_ID, - px_size : f32, colour : RGBAN, view : Vec2, position : Vec2, @@ -842,21 +830,23 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, profile(#procedure) assert( ctx != nil ) // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) - assert( font >= 0 && int(font) < len(ctx.entries) ) assert( ctx.px_scalar > 0.0 ) - entry := ctx.entries[ font ] + entry := ctx.entries[ shape.font ] should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0) adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen + px_scalar_quotient := (1 / ctx.px_scalar) + px_size := shape.px_size * px_scalar_quotient + resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view ) // Does nothing if px_scalar is 1.0 target_px_size := resolved_size * ctx.px_scalar - target_scale := norm_scale * (1 / ctx.px_scalar) + target_scale := norm_scale * px_scalar_quotient target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, @@ -976,12 +966,10 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text assert( ctx.px_scalar > 0.0 ) stack := & ctx.stack - assert(len(stack.font) > 0) assert(len(stack.view) > 0) assert(len(stack.colour) > 0) assert(len(stack.position) > 0) assert(len(stack.scale) > 0) - assert(len(stack.font_size) > 0) assert(len(stack.zoom) > 0) // TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape) @@ -998,7 +986,9 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text adjusted_colour := colour adjusted_colour.a += ctx.alpha_sharpen * should_alpha_sharpen - px_size := peek(stack.font_size) + px_scalar_quotient := 1 / ctx.px_scalar + + px_size := shape.px_size * px_scalar_quotient zoom := peek(stack.zoom) resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view ) @@ -1010,7 +1000,7 @@ draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text // Does nothing when px_scalar is 1.0 target_px_size := resolved_size * ctx.px_scalar - target_scale := norm_scale * (1 / ctx.px_scalar) + target_scale := norm_scale * px_scalar_quotient target_font_scale := parser_scale( entry.parser_info, target_px_size ) ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, From 046c69c477df2e731cf427ce527f47ee850f0a1d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 20:38:43 -0500 Subject: [PATCH 28/62] Realized while writing the docs that I need to preseve non-visible glyphs in the shape. (Fixed some crashing as well) So the shaper has been adjusted along with downstream codepaths in drawlist gen pass. --- vefontcache/draw.odin | 92 +++++++++++++++++++++--------------- vefontcache/parser.odin | 4 +- vefontcache/pkg_mapping.odin | 1 + vefontcache/shaper.odin | 80 ++++++++++++++++++++----------- vefontcache/vefontcache.odin | 2 +- 5 files changed, 110 insertions(+), 69 deletions(-) diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 9a2b403..62b5997 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -28,6 +28,7 @@ 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 #packed { + vis_index : i16, position : Vec2, atlas_index : i32, @@ -320,7 +321,7 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, oversized := & glyph_buffer.oversized to_cache := & glyph_buffer.to_cache cached := & glyph_buffer.cached - resize_soa_non_zero(glyph_pack, len(shape.glyph)) + resize_soa_non_zero(glyph_pack, len(shape.visible)) append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 ) { @@ -332,11 +333,18 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, profile_begin("translate") for & glyph, index in glyph_pack { - glyph.position = target_position + (shape.position[index]) * target_scale + // 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() 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) @@ -344,15 +352,13 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, for & glyph, index in glyph_pack { + // 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 - if region_kind == .None { - assert(false, "FAILED TO ASSGIN REGION") - continue - } + assert(region_kind != .None, "FAILED TO ASSGIN REGION") when ENABLE_OVERSIZED_GLYPHS { if region_kind == .E @@ -454,7 +460,11 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, 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. + 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. @@ -589,12 +599,13 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, colour.g = 1.0 colour.b = 0.0 } - // for pack_id, index in oversized { - // error : Allocator_Error - // glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) - // assert(error == .None) - // assert(glyph_pack[pack_id].shape != nil) - // } + for pack_id, index in oversized { + 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 oversized { glyph := & glyph_pack[id] @@ -605,10 +616,11 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, & glyph_buffer.allocated_x ) - error : Allocator_Error - glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[id]) - assert(error == .None) - assert(len(glyph.shape) > 0) + // vis_id := shape.visible[id] + // error : Allocator_Error + // glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id]) + // assert(error == .None) + // assert(len(glyph.shape) > 0) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, glyph_pack[id].shape, @@ -618,8 +630,8 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph_pack[id].draw_transform.scale ) - assert(len(glyph.shape) > 0) - parser_free_shape(entry.parser_info, glyph.shape) + // assert(len(glyph.shape) > 0) + // parser_free_shape(entry.parser_info, glyph.shape) target_quad := & glyph_pack[id].draw_quad @@ -639,10 +651,10 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, } 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) - // } + 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() @@ -671,12 +683,13 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, profile_begin("to_cache: caching to atlas") if len(to_cache) > 0 { - // for pack_id, index in to_cache { - // error : Allocator_Error - // glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[pack_id]) - // assert(error == .None) - // assert(glyph_pack[pack_id].shape != nil) - // } + 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 { @@ -730,10 +743,11 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, append( & glyph_buffer.clear_draw_list.calls, clear_target_region ) append( & glyph_buffer.draw_list.calls, blit_to_atlas ) - error : Allocator_Error - glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[id]) - assert(error == .None) - assert(len(glyph.shape) > 0) + // vis_id := shape.visible[id] + // error : Allocator_Error + // glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id]) + // assert(error == .None) + // assert(len(glyph.shape) > 0) // Render glyph to glyph render target (FBO) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, @@ -744,15 +758,15 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph.draw_transform.scale ) - assert(len(glyph.shape) > 0) - parser_free_shape(entry.parser_info, glyph.shape) + // assert(len(glyph.shape) > 0) + // parser_free_shape(entry.parser_info, glyph.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 to_cache { - // assert(glyph_pack[pack_id].shape != nil) - // parser_free_shape(entry.parser_info, glyph_pack[pack_id].shape) - // } + 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 { diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 433acb3..9223baf 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -74,7 +74,9 @@ parser_stbtt_allocator_proc :: proc( assert(error == .None) if type == .Alloc || type == .Resize { - return transmute(rawptr) & result[0] + raw := transmute(Raw_Slice) result + // assert(raw.len > 0, "Allocation is 0 bytes?") + return transmute(rawptr) raw.data } else do return nil } diff --git a/vefontcache/pkg_mapping.odin b/vefontcache/pkg_mapping.odin index fd884dc..acf9be7 100644 --- a/vefontcache/pkg_mapping.odin +++ b/vefontcache/pkg_mapping.odin @@ -5,6 +5,7 @@ import "base:builtin" 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" diff --git a/vefontcache/shaper.odin b/vefontcache/shaper.odin index a407ecf..f609b4d 100644 --- a/vefontcache/shaper.odin +++ b/vefontcache/shaper.odin @@ -27,6 +27,7 @@ Shape_Key :: u32 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, @@ -100,7 +101,7 @@ shaper_load_font :: #force_inline proc( ctx : ^Shaper_Context, label : string, d shaper_unload_font :: #force_inline proc( info : ^Shaper_Info ) { - if info.blob != nil do harfbuzz.font_destroy( info.font ) + 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 ) } @@ -125,6 +126,7 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, clear( & output.glyph ) clear( & output.position ) + clear( & output.visible ) current_script := harfbuzz.Script.UNKNOWN hb_ucfunc := harfbuzz.unicode_funcs_get_default() @@ -210,10 +212,15 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, (position^) += advance (max_line_width^) = max(max_line_width^, position.x) - is_empty := parser_is_glyph_empty(entry.parser_info, glyph) - if ! is_empty && glyph != 0 { - append( & output.glyph, glyph ) - append( & output.position, 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 ) } } @@ -234,17 +241,23 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, // Can we continue the current run? ScriptKind :: harfbuzz.Script - special_script : b32 = script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON - if special_script \ + // 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, @@ -281,22 +294,26 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, // 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) ) + 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 id, index in output.glyph { - output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id) + 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 id, index in output.glyph { + for vis_id, index in output.visible { + glyph_id := output.glyph[vis_id] bounds := & output.bounds[index] - (bounds ^) = parser_get_bounds( entry.parser_info, id ) + (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() @@ -323,6 +340,7 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, clear( & output.glyph ) clear( & output.position ) + clear( & output.visible ) line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale @@ -353,14 +371,16 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, 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 - { - 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 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 ) @@ -381,17 +401,21 @@ shaper_shape_text_latin :: proc( ctx : ^Shaper_Context, resize( & output.bounds, len(output.glyph) ) profile_begin("atlas_lru_code") - for id, index in output.glyph { - output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id) + 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 id, index in output.glyph { + for vis_id, index in output.visible { + glyph_id := output.glyph[vis_id] bounds := & output.bounds[index] - (bounds ^) = parser_get_bounds( entry.parser_info, id ) + (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() diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index c8f3168..0f65d82 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -581,8 +581,8 @@ unload_font :: proc( ctx : ^Context, font : Font_ID ) entry := & ctx.entries[ font ] entry.used = false - parser_unload_font( & entry.parser_info ) shaper_unload_font( & entry.shaper_info ) + parser_unload_font( & entry.parser_info ) } // Can be used with hot-reload From b2208129503dbddf82e576530c19982fd505831c Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 20:39:03 -0500 Subject: [PATCH 29/62] Initial draft of shaping pass guide --- docs/guide_architecture.md | 58 ++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/docs/guide_architecture.md b/docs/guide_architecture.md index 501ef9c..e99dcc0 100644 --- a/docs/guide_architecture.md +++ b/docs/guide_architecture.md @@ -18,7 +18,7 @@ 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). So far these are the text shaping itself, and per-glyph infos: atlas_lru_code (atlas key), atlas region resolution, & glyph bounds. +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 is caching of triangulation and super-sampling compute. All caching uses the [LRU.odin](../vefontcache/LRU.odin) @@ -31,13 +31,11 @@ The library lifetime is pretty straightfoward, you have a startup to do that sho 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 those 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. So just call the `clear_atlas_region_caches` & `clear_shape_cache` right after. - -Any scratch memory used for draw list generation is kept persistently in the library's `Context`. I wanted to avoid any dynamic allocation slowness as its an extremely hot path. +Usually when hot-reloading the library for tuning or major changes, you'd also want to clear the caches. So just call the clear_atlas_region_caches` & `clear_shape_cache` right after. Ideally there should be zero dynamic allocation on a per-frame basis so long as the reserves for the dynamic containers are never exceeded. Its alright if they do as their memory locality is so large their distance in the pages to load into cpu cache won't matter, just needs to be a low incidence. -### Shaping pass +### Shaping Pass If the user is using the library's cache, then at some point `shaper_shape_text_cached` which handles the hasing and lookup. So long as a shape is found it will not enter uncached codepath. By default this library uses `shaper_shape_harfbuzz` as the `shape_text_uncached` procedure. @@ -68,6 +66,7 @@ Which will resolve the output `Shaped_Text`. Which has the following structure: 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, @@ -80,16 +79,59 @@ Shaped_Text :: struct #packed { What is actually the result of the shaping process is the arrays of glyphs and their positions for the the shape or most historically known as: *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`) +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 - +They're arrays are the same length as `visible`, 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), or 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 may 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 From e8218f3bf8113354bbf6c787020d5d5c9e794911 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 22:59:35 -0500 Subject: [PATCH 30/62] Finished inital draft for draw list generation docs in guide_architecture.md --- Readme.md | 8 ++- backend/sokol/blit_atlas.shdc.glsl | 1 - backend/sokol/draw_text.shdc.glsl | 1 - docs/guide_architecture.md | 81 +++++++++++++++++++++++++++--- vefontcache/draw.odin | 47 ++++------------- vefontcache/vefontcache.odin | 7 ++- 6 files changed, 95 insertions(+), 50 deletions(-) diff --git a/Readme.md b/Readme.md index de8d04c..eea01b5 100644 --- a/Readme.md +++ b/Readme.md @@ -33,13 +33,17 @@ Upcoming: * Support for ear-clipping triangulation, or just better triangulation.. * Support for which triangulation method used on a by font basis? - * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + * [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf) * Multi-threading supported job queue. * Lift heavy-lifting portion of the library's context into a thread context. * Synchronize threads by merging their generated layered draw list into a finished draw-list for processing on the user's render thread. * User defines how thread context's are distributed for drawing (a basic quandrant based selector procedure will be provided.) -See: [docs/Readme.md](docs/Readme.md) for the library's interface. +## Documentation + +* [docs/Readme.md](docs/Readme.md) for the library's interface. +* [docs/guide_backend.md](docs/guide_backend.md) for information on whats needed rolling your own backend. +* [docs/guide_architecture.md](docs/guide_architecture.md) for an in-depth breakdown of the significant design decisions, and codepaths. ## Building diff --git a/backend/sokol/blit_atlas.shdc.glsl b/backend/sokol/blit_atlas.shdc.glsl index 568aaad..0cfdd8d 100644 --- a/backend/sokol/blit_atlas.shdc.glsl +++ b/backend/sokol/blit_atlas.shdc.glsl @@ -35,7 +35,6 @@ float down_sample_to_texture( vec2 uv, vec2 texture_size ) void main() { - // const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); const vec2 texture_size = 1.0f / glyph_buffer_size; if ( region == 0 || region == 1 || region == 2 || region == 4 ) { diff --git a/backend/sokol/draw_text.shdc.glsl b/backend/sokol/draw_text.shdc.glsl index f71ed76..10233a0 100644 --- a/backend/sokol/draw_text.shdc.glsl +++ b/backend/sokol/draw_text.shdc.glsl @@ -32,7 +32,6 @@ void main() { float alpha = texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler ), uv ).x; - // const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); const vec2 texture_size = glyph_buffer_size; const float down_sample = 1.0f / over_sample; diff --git a/docs/guide_architecture.md b/docs/guide_architecture.md index e99dcc0..9ac8995 100644 --- a/docs/guide_architecture.md +++ b/docs/guide_architecture.md @@ -4,7 +4,7 @@ Overview on the state of package design and codepath layout. --- -The purpose of this library to really allieviate four issues with one encapsulation: +The purpose of this library to really allieviate four issues with one encapsulating package: * font parsing * text codepoint shaping @@ -45,7 +45,7 @@ Shapes are cached using the following parameters to hash a key: * font_size: f32 * the text itself: string -All shapers fullfill the following interface: +All shapers fulfill the following interface: ```odin Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, @@ -60,7 +60,7 @@ Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context, ) ``` -Which will resolve the output `Shaped_Text`. Which has the following structure: +Which will resolve the output `Shaped_Text`. It has the following definition: ```odin Shaped_Text :: struct #packed { @@ -91,8 +91,7 @@ As stated under the main heading of this guide, the the following are within sha They're arrays are the same length as `visible`, 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_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 @@ -100,7 +99,7 @@ They're arrays are the same length as `visible`, so indexing those will not need 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), or 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 may 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. + 3. Otherwise we may 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. @@ -135,14 +134,80 @@ There are other shapers out there: ### 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 immeidately 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 futher actual generaiton 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 though 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 reseved 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 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 needsto 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 alternatve 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 the 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 leng 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`. +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 leng 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: @@ -154,7 +219,7 @@ Importantly, this leads to the following pattern when enuquing a layer to render 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 a section of their pipline. +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 pipline. This would involve just tracking *slices* of thier draw-list that represents layers: diff --git a/vefontcache/draw.odin b/vefontcache/draw.odin index 62b5997..80bd3f5 100644 --- a/vefontcache/draw.odin +++ b/vefontcache/draw.odin @@ -28,23 +28,14 @@ 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 #packed { - vis_index : i16, position : Vec2, - atlas_index : i32, - in_atlas : b8, - should_cache : b8, region_pos : Vec2, region_size : Vec2, - 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, } @@ -322,6 +313,16 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, to_cache := & glyph_buffer.to_cache cached := & glyph_buffer.cached 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 ) { @@ -340,16 +341,6 @@ generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text, } profile_end() - 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) - for & glyph, index in glyph_pack { // atlas_lru_code, region_kind, and bounds are all 1:1 with shape.visible @@ -616,12 +607,6 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, & glyph_buffer.allocated_x ) - // vis_id := shape.visible[id] - // error : Allocator_Error - // glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id]) - // assert(error == .None) - // assert(len(glyph.shape) > 0) - generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, glyph_pack[id].shape, entry.curve_quality, @@ -630,9 +615,6 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph_pack[id].draw_transform.scale ) - // assert(len(glyph.shape) > 0) - // parser_free_shape(entry.parser_info, glyph.shape) - target_quad := & glyph_pack[id].draw_quad draw_to_target : Draw_Call @@ -743,12 +725,6 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, append( & glyph_buffer.clear_draw_list.calls, clear_target_region ) append( & glyph_buffer.draw_list.calls, blit_to_atlas ) - // vis_id := shape.visible[id] - // error : Allocator_Error - // glyph.shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id]) - // assert(error == .None) - // assert(len(glyph.shape) > 0) - // Render glyph to glyph render target (FBO) generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, glyph.shape, @@ -757,9 +733,6 @@ batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List, glyph.draw_transform.pos, glyph.draw_transform.scale ) - - // assert(len(glyph.shape) > 0) - // parser_free_shape(entry.parser_info, glyph.shape) } flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 0f65d82..65d4d99 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -697,7 +697,12 @@ shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si <-> scale : Scale the glyph beyond its default scaling from its px_size. */ @(optimization_mode="favor_size") -draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, colour : RGBAN, position : Vec2, scale : Vec2, shape : Shaped_Text ) +draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, + colour : RGBAN, + position : Vec2, + scale : Vec2, + shape : Shaped_Text +) { profile(#procedure) assert( ctx != nil ) From a4dafe01c8975e53f2600f6eac35bd8b9b5e4d2c Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 11 Jan 2025 23:10:00 -0500 Subject: [PATCH 31/62] Finish ### draw_text procedures draft in docs/readme.md --- docs/Readme.md | 19 +++++++++++++++++++ vefontcache/vefontcache.odin | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/Readme.md b/docs/Readme.md index 97797c1..c9954c4 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -51,6 +51,25 @@ Shape will NOT be cached by the library. Use this if you want to roll your own s ## 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. diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 65d4d99..8c0257d 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -697,7 +697,7 @@ shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_si <-> scale : Scale the glyph beyond its default scaling from its px_size. */ @(optimization_mode="favor_size") -draw_text_shape_normalized_space :: #force_inline proc( ctx : ^Context, +draw_shape_normalized_space :: #force_inline proc( ctx : ^Context, colour : RGBAN, position : Vec2, scale : Vec2, @@ -823,7 +823,7 @@ draw_text_normalized_space :: proc( ctx : ^Context, zoom : Will affect the scale similar to how the zoom on a canvas would behave. */ // @(optimization_mode="favor_size") -draw_text_shape_view_space :: #force_inline proc( ctx : ^Context, +draw_shape_view_space :: #force_inline proc( ctx : ^Context, colour : RGBAN, view : Vec2, position : Vec2, From a47937f6151a45c30a01c8a9f1360aa3fd08d5e8 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 09:38:24 -0500 Subject: [PATCH 32/62] Completed initial draft for guide_backend.odin --- Readme.md | 2 +- backend/sokol/backend_sokol.odin | 2 +- docs/guide_backend.md | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index eea01b5..5987387 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # VE Font Cache - +Vertex Engine GPU Font Cache: A text rendering libary. This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. diff --git a/backend/sokol/backend_sokol.odin b/backend/sokol/backend_sokol.odin index de8f331..69aca80 100644 --- a/backend/sokol/backend_sokol.odin +++ b/backend/sokol/backend_sokol.odin @@ -548,7 +548,7 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : samplers = { SMP_ve_blit_atlas_src_sampler = ctx.glyph_rt_sampler, }, }) - // 3. Use the atlas to then render the text. + // 3. Use the atlas (.Target) or the glyph buffer (.Target_Unchached) to then render the text. case .None, .Target, .Target_Uncached: if num_indices == 0 && ! draw_call.clear_before_draw { continue diff --git a/docs/guide_backend.md b/docs/guide_backend.md index 8360f74..fa306e0 100644 --- a/docs/guide_backend.md +++ b/docs/guide_backend.md @@ -4,4 +4,35 @@ The end-user needs adapt this library for hookup into their own codebase. As an When rendering text, the two products the user has to deal with: The text to draw and their "layering". Similar to UIs text should be drawn in layer batches, where each layer can represent a pass on some arbitrary set of distictions between the other layers. +The following are generally needed: +* Vertex and Index Buffers for glyph meshes +* Glyph shader for rendering the glyph to the glyph buffer +* Atlas shader for blitting the 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 some "target" image buffers + +Currently the library doesn't support sub-pixel AA so we're just rendering to R8 images. + +## 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: Blit from the atlas image to the target image +* Target_Uncached: Blit 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 out using the atlas image with the glyph buffer image. This is how the backend_soko.odin's `render_text_layer` has those passes setup. + +## The vertex buffer will have the following alyout for all passes + +`[2]f32` for positions +`[2]f32` for texture coords (Offset is naturally `[2]f32`) +With a total stride of `[4]f32` + +--- + +The index buffer is just a u32 stream. + +For how a quad mesh is laid out see `blit_quad` in [draw.odin](../vefontcache/draw.odin) + +For how glyph shape triangulation meshes, the library currently only uses a triangle fanning technique so `fill_path_via_fan_triangulation` within [draw.odin](../vefontcache/draw.odin) is where that is being done. Eventually the libary will also support other modes on a per-font basis. From 470800af2ac4186404734ac586d1808268156a06 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 09:52:10 -0500 Subject: [PATCH 33/62] Added note in backend guide about UV convention. --- backend/sokol/source_shared.shdc.glsl | 1 - docs/guide_backend.md | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/sokol/source_shared.shdc.glsl b/backend/sokol/source_shared.shdc.glsl index 2d30f5d..cd9ed9a 100644 --- a/backend/sokol/source_shared.shdc.glsl +++ b/backend/sokol/source_shared.shdc.glsl @@ -1,6 +1,5 @@ in vec2 v_position; in vec2 v_texture; -// in vec4 v_elem; out vec2 uv; void main() diff --git a/docs/guide_backend.md b/docs/guide_backend.md index fa306e0..b0bd5f6 100644 --- a/docs/guide_backend.md +++ b/docs/guide_backend.md @@ -36,3 +36,17 @@ The index buffer is just a u32 stream. For how a quad mesh is laid out see `blit_quad` in [draw.odin](../vefontcache/draw.odin) For how glyph shape triangulation meshes, the library currently only uses a triangle fanning technique so `fill_path_via_fan_triangulation` within [draw.odin](../vefontcache/draw.odin) is where that is being done. Eventually the libary will also support other modes on a per-font basis. + +## Keep in mind GLSL vs HLSL UV (texture) coordinate convention + +The UV coordinates used DirectX, Metal, and Vulkan all consider the top-left corner (0, 0), Where the Y axis increases downwards (traditional screenspace). This library follows the convention of (0, 0) being at the bottom-left (Y goes up) which is what OpenGL uses. + +In the shader the UV just has to be adjusted accordingly: + +```c +#if ! OpenGL +uv = vec2( v_texture.x, 1.0 - v_texture.y ); +#else +uv = vec2( v_texture.x, v_texture.y ); +#endif +``` From 207fa9480368a2a276651ed651ec273786cd687d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 10:29:11 -0500 Subject: [PATCH 34/62] Proofing --- Readme.md | 64 ++++++++++++++++++++------------------ docs/Readme.md | 2 +- docs/guide_architecture.md | 61 ++++++++++++++++++------------------ docs/guide_backend.md | 64 ++++++++++++++++++++++++-------------- 4 files changed, 105 insertions(+), 86 deletions(-) diff --git a/Readme.md b/Readme.md index 5987387..76931a7 100644 --- a/Readme.md +++ b/Readme.md @@ -1,58 +1,62 @@ # VE Font Cache -Vertex Engine GPU Font Cache: A text rendering libary. +Vertex Engine GPU Font Cache: A text rendering library. -This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. -Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. +This project started as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. +While originally intended for game engines, its rendering quality and performance make it suitable for many other applications. -Since then the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements. +Since then, the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements. Features: -* Simple and well documented. -* Load and unload fonts at anytime -* Almost entirely configurabe and tunable at runtime! +* Simple and well documented +* Load and unload fonts at any time +* Almost entirely configurable and tunable at runtime * Full support for hot-reload - * Clear the caches at any-time! + * Clear the caches at any time * Robust quality of life features: - * Tracks text layers! - * Push and pop stack for font, font_size, colour, view, position, scale and zoom! - * Enforce even only font-sizing (useful for linear-zoom) - * Snap-positioning to view for better hinting + * Snap positioning to view for better hinting + * Tracks text layers + * Enforce even-only font sizing (useful for linear zoom) + * Push and pop stack for font, font_size, color, view, position, scale, and zoom * Basic or advanced text shaping via Harfbuzz -* All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu. - * Can hand thousands of draw text calls with very large or small shapes. +* All rendering is real-time, with triangulation on the CPU, vertex rendering and texture blitting on the GPU + * Can handle thousands of draw text calls with very large or small shapes * 4-Level Regioned Texture Atlas for caching rendered glyphs * Text shape caching -* Glyph texture buffer for rendering the text with super-sampling to downsample to the atlas or direct to target screen. +* Glyph texture buffer for rendering text with super-sampling to downsample to the atlas or direct to target screen * Super-sample by a font size scalar for sharper glyphs * All caching backed by an optimized 32-bit LRU indexing cache -* Provides a draw list that is backend agnostic (see [backend](./backend) for usage example). +* Provides a backend-agnostic draw list (see [backend](./backend) for usage example) Upcoming: -* Support for ear-clipping triangulation, or just better triangulation.. - * Support for which triangulation method used on a by font basis? - * [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf) -* Multi-threading supported job queue. - * Lift heavy-lifting portion of the library's context into a thread context. - * Synchronize threads by merging their generated layered draw list into a finished draw-list for processing on the user's render thread. - * User defines how thread context's are distributed for drawing (a basic quandrant based selector procedure will be provided.) +* Support choosing between top-left or bottom-left coordinate convention (currently bottom-left) +* Support for better triangulation + * Support for triangulation method selection on a per-font basis + * [Reference paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf) +* Better support for tuning glyph render sampling + * Support for sub-pixel AA + * Ability to decide AA method & degree on a per-font basis +* Multi-threading supported job queue + * Lift heavy-lifting portion of the library's context into a thread context + * Synchronize threads by merging their generated layered draw list into a finished draw list for processing on the user's render thread + * User defines how thread contexts are distributed for drawing (a basic quadrant-based selector procedure will be provided) ## Documentation -* [docs/Readme.md](docs/Readme.md) for the library's interface. -* [docs/guide_backend.md](docs/guide_backend.md) for information on whats needed rolling your own backend. -* [docs/guide_architecture.md](docs/guide_architecture.md) for an in-depth breakdown of the significant design decisions, and codepaths. +* [docs/Readme.md](docs/Readme.md) for the library's interface +* [docs/guide_backend.md](docs/guide_backend.md) for information on implementing your own backend +* [docs/guide_architecture.md](docs/guide_architecture.md) for an in-depth breakdown of significant design decisions and code-paths ## 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). +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 harfbuzz, & stb_truetype to build. -Note: harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet). +The library depends on harfbuzz & stb_truetype to build. +Note: harfbuzz could technically be removed if the user removes their definitions, however this hasn't been made into a conditional compilation option yet. # Gallery @@ -62,4 +66,4 @@ https://github.com/user-attachments/assets/db8c7725-84dd-48df-9a3f-65605d3ab444 https://github.com/user-attachments/assets/40030308-37db-492d-a196-f830e8a39f3c -https://github.com/user-attachments/assets/0985246b-74f8-4d1c-82d8-053414c44aec +https://github.com/user-attachments/assets/0985246b-74f8-4d1c-82d8-053414c44aec \ No newline at end of file diff --git a/docs/Readme.md b/docs/Readme.md index c9954c4..48594d6 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -58,7 +58,7 @@ 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_normalized_space` * `draw_text_view_space` * `draw_text` diff --git a/docs/guide_architecture.md b/docs/guide_architecture.md index 9ac8995..607aebc 100644 --- a/docs/guide_architecture.md +++ b/docs/guide_architecture.md @@ -1,43 +1,42 @@ # Guide: Architecture -Overview on the state of package design and codepath layout. +Overview of the package design and code-path layout. --- -The purpose of this library to really allieviate four issues with one encapsulating package: +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 +* Font parsing +* Text codepoint shaping +* Glyph shape triangulation +* Glyph draw-list generation -Shaping text, getting metrics for the glyphs, triangulating glyphs, and anti-aliasing their render are expensive todo per frame. So anything related to that compute that may be cached, will be. +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`) +* 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 is caching of triangulation and super-sampling compute. +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 the [LRU.odin](../vefontcache/LRU.odin) +All caching uses [LRU.odin](../vefontcache/LRU.odin) -## Codepaths +## Code Paths ### Lifetime -The library lifetime is pretty straightfoward, you have a startup to do that should just be called sometime in your usual app start.s. From there you may either choose to manually shut it down or let the OS clean it up. +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 those should be the only aspects that have been scrambled. +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. -Usually when hot-reloading the library for tuning or major changes, you'd also want to clear the caches. So just call the clear_atlas_region_caches` & `clear_shape_cache` right after. - -Ideally there should be zero dynamic allocation on a per-frame basis so long as the reserves for the dynamic containers are never exceeded. Its alright if they do as their memory locality is so large their distance in the pages to load into cpu cache won't matter, just needs to be a low incidence. +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 the user is using the library's cache, then at some point `shaper_shape_text_cached` which handles the hasing and lookup. So long as a shape is found it will not enter uncached codepath. By default this library uses `shaper_shape_harfbuzz` as the `shape_text_uncached` procedure. +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: @@ -77,7 +76,7 @@ Shaped_Text :: struct #packed { } ``` -What is actually the result of the shaping process is the arrays of glyphs and their positions for the the shape or most historically known as: *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. +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. @@ -89,7 +88,7 @@ As stated under the main heading of this guide, the the following are within sha * region_kind * bounds -They're arrays are the same length as `visible`, so indexing those will not need to use visibile's relative index. +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: @@ -98,11 +97,11 @@ They're arrays are the same length as `visible`, so indexing those will not need 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), or 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 may 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. + 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. +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. @@ -134,15 +133,15 @@ There are other shapers out there: ### 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 immeidately before calling it. +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. +* 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 futher actual generaiton 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. +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. @@ -151,12 +150,12 @@ 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 though the shape.visible and resolve glyph_pack's positions. +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 reseved for within the atlas's region and the glyph is appended to `to_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. @@ -171,7 +170,7 @@ The batch is organized into three major stages: 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 there *except* for `to_cache`, which does its blitting compute in its glyph_buffer draw-list gen pass. +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. @@ -196,9 +195,9 @@ For `to_cached`: 4. free glyph shapes 5. Do blits from atlas to draw list. -`cached` only needsto blit from the atlas to the render target. +`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 alternatve modes that can be specified on a per-font basis. +`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. diff --git a/docs/guide_backend.md b/docs/guide_backend.md index b0bd5f6..66ae60e 100644 --- a/docs/guide_backend.md +++ b/docs/guide_backend.md @@ -1,52 +1,68 @@ # Backend Guide -The end-user needs adapt this library for hookup into their own codebase. As an example they may see the [examples](../examples/) and [backend](../backend/) for working code of what this guide will go over. +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, the two products the user has to deal with: The text to draw and their "layering". Similar to UIs text should be drawn in layer batches, where each layer can represent a pass on some arbitrary set of distictions between the other layers. +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 are generally needed: +The following components are required: * Vertex and Index Buffers for glyph meshes -* Glyph shader for rendering the glyph to the glyph buffer -* Atlas shader for blitting the upscaled glyph quads from the glyph buffer to an atlas region slot downsampled. +* 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 some "target" image buffers +* The glyph, atlas, and target image buffers -Currently the library doesn't support sub-pixel AA so we're just rendering to R8 images. +Currently, the library doesn't support sub-pixel AA, so we're only rendering to R8 images. -## There are four passes that need to be handled when rendering a draw list +## 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: Blit from the atlas image to the target image -* Target_Uncached: Blit from the glyph buffer image to the target image +* 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 out using the atlas image with the glyph buffer image. This is how the backend_soko.odin's `render_text_layer` has those passes setup. +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. -## The vertex buffer will have the following alyout for all passes +## Vertex Buffer Layout -`[2]f32` for positions -`[2]f32` for texture coords (Offset is naturally `[2]f32`) -With a total stride of `[4]f32` +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 just a u32 stream. +The index buffer is a simple u32 stream. -For how a quad mesh is laid out see `blit_quad` in [draw.odin](../vefontcache/draw.odin) +For quad mesh layout details, see `blit_quad` in [draw.odin](../vefontcache/draw.odin). -For how glyph shape triangulation meshes, the library currently only uses a triangle fanning technique so `fill_path_via_fan_triangulation` within [draw.odin](../vefontcache/draw.odin) is where that is being done. Eventually the libary will also support other modes on a per-font basis. +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. -## Keep in mind GLSL vs HLSL UV (texture) coordinate convention +## UV Coordinate Conventions (GLSL vs HLSL) -The UV coordinates used DirectX, Metal, and Vulkan all consider the top-left corner (0, 0), Where the Y axis increases downwards (traditional screenspace). This library follows the convention of (0, 0) being at the bottom-left (Y goes up) which is what OpenGL uses. +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). -In the shader the UV just has to be adjusted accordingly: +Adjust the UV coordinates in your shader accordingly: ```c -#if ! OpenGL -uv = vec2( v_texture.x, 1.0 - v_texture.y ); +#if !OpenGL +uv = vec2(v_texture.x, 1.0 - v_texture.y); #else -uv = vec2( v_texture.x, v_texture.y ); +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. + +--- From 989be15390529e4880b034fd93620010539ef0cd Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 10:34:44 -0500 Subject: [PATCH 35/62] More proofing --- docs/guide_architecture.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guide_architecture.md b/docs/guide_architecture.md index 607aebc..cac801a 100644 --- a/docs/guide_architecture.md +++ b/docs/guide_architecture.md @@ -203,22 +203,22 @@ For `to_cached`: ### On Layering -The base draw list generation pippline provided by the library allows the user to batch whatever the want into a single "layer". +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 leng 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`. +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 + 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 pipline. +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: From 8f491fd84365a0fae45710a29d67c5084be8542d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 10:42:22 -0500 Subject: [PATCH 36/62] Update scripts doc --- docs/guide_backend.md | 2 +- scripts/Readme.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/guide_backend.md b/docs/guide_backend.md index 66ae60e..a388ccf 100644 --- a/docs/guide_backend.md +++ b/docs/guide_backend.md @@ -1,4 +1,4 @@ -# Backend Guide +# 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. diff --git a/scripts/Readme.md b/scripts/Readme.md index 2599011..ba1549b 100644 --- a/scripts/Readme.md +++ b/scripts/Readme.md @@ -12,7 +12,12 @@ Its assumed the user has Odin installed and exposed to the OS enviornment's PATH #### Note on dependency packages -All dependencies are cloned directly into a created thirdparty directory. +A custom version of the vendor:stb/truetype is maintained by this library: + +* 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*`. + +All other dependencies are cloned directly into a created thirdparty directory. [harfbuzz](https://github.com/Ed94/odin_harfbuzz) is configured to pull & build the C++ library, it will use the MSVC toolchain (you can change it to use meson instead of preferred). [freetype](https://github.com/Ed94/odin-freetype) package has pre-built .lib files for windows (debug/release). From cbbb48d631498d3178a222e8d53f26db420dcb71 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 10:43:54 -0500 Subject: [PATCH 37/62] see freetype_wip.odin to ignored for now --- vefontcache/{freetype_wip.odin => .freetype_wip.odin} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vefontcache/{freetype_wip.odin => .freetype_wip.odin} (100%) diff --git a/vefontcache/freetype_wip.odin b/vefontcache/.freetype_wip.odin similarity index 100% rename from vefontcache/freetype_wip.odin rename to vefontcache/.freetype_wip.odin From 7bec503af7099ff48a1bffc6345e960c6ac7f8eb Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 11:16:14 -0500 Subject: [PATCH 38/62] measure_shape_size & get_font_vertical_metrics: Make context immutable pass --- Readme.md | 2 +- examples/sokol_demo/sokol_demo.odin | 2 +- vefontcache/vefontcache.odin | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index 76931a7..0f68c04 100644 --- a/Readme.md +++ b/Readme.md @@ -66,4 +66,4 @@ https://github.com/user-attachments/assets/db8c7725-84dd-48df-9a3f-65605d3ab444 https://github.com/user-attachments/assets/40030308-37db-492d-a196-f830e8a39f3c -https://github.com/user-attachments/assets/0985246b-74f8-4d1c-82d8-053414c44aec \ No newline at end of file +https://github.com/user-attachments/assets/0985246b-74f8-4d1c-82d8-053414c44aec diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index 85988ad..b3c3aab 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -147,7 +147,7 @@ measure_text_size :: proc( text : string, font : Font_ID, font_size := Font_Use_ get_font_vertical_metrics :: #force_inline proc ( font : Font_ID, font_size := Font_Use_Default_Size ) -> ( ascent, descent, line_gap : f32 ) { def := demo_ctx.font_ids[ font.label ] - ascent, descent, line_gap = ve.get_font_vertical_metrics( & demo_ctx.ve_ctx, def.ve_id, font_size ) + ascent, descent, line_gap = ve.get_font_vertical_metrics( demo_ctx.ve_ctx, def.ve_id, font_size ) return } diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 8c0257d..86c5af6 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -1144,7 +1144,7 @@ flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) { // Where its assumed when utilizing the draw_list generators or shaping procedures that the shape will be affected by it so it must be handled. // If px_scalar is 1.0 no effect is done and its just redundant ops. -measure_shape_size :: #force_inline proc( ctx : ^Context, shape : Shaped_Text ) -> (measured : Vec2) { +measure_shape_size :: #force_inline proc( ctx : Context, shape : Shaped_Text ) -> (measured : Vec2) { measured = shape.size * (1 / ctx.px_scalar) return } @@ -1178,9 +1178,8 @@ measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size return shaped.size * target_scale } -get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 ) +get_font_vertical_metrics :: #force_inline proc ( ctx : Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 ) { - assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) entry := ctx.entries[ font ] From 2e3b55b972f9b49a76baf8c551a72e0d50f8ccca Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 11:31:42 -0500 Subject: [PATCH 39/62] Reademe: Add link to notes --- Readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 0f68c04..5a841ee 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # VE Font Cache -Vertex Engine GPU Font Cache: A text rendering library. +Vertex Engine GPU Font Cache: A text shaping & rendering library. This project started as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language. While originally intended for game engines, its rendering quality and performance make it suitable for many other applications. @@ -19,7 +19,7 @@ Features: * Tracks text layers * Enforce even-only font sizing (useful for linear zoom) * Push and pop stack for font, font_size, color, view, position, scale, and zoom -* Basic or advanced text shaping via Harfbuzz +* Basic (latin) or advanced (harfbuzz) text shaping * All rendering is real-time, with triangulation on the CPU, vertex rendering and texture blitting on the GPU * Can handle thousands of draw text calls with very large or small shapes * 4-Level Regioned Texture Atlas for caching rendered glyphs @@ -49,6 +49,8 @@ Upcoming: * [docs/guide_backend.md](docs/guide_backend.md) for information on implementing your own backend * [docs/guide_architecture.md](docs/guide_architecture.md) for an in-depth breakdown of significant design decisions and code-paths +For learning about text shaping & rendering see: [notes](https://github.com/Ed94/TextRendering_Notes) + ## Building See [scripts/Readme.md](scripts/Readme.md) for building examples or utilizing the provided backends. From 3b2bf6e3225d6cacaf0747b7af314321892dbe7d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 22:02:46 -0500 Subject: [PATCH 40/62] Opps: Forgot to do lifetime of storage_entry.visible --- vefontcache/vefontcache.odin | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index 86c5af6..e51999e 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -279,6 +279,9 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N stroage_entry.position, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" ) + stroage_entry.visible, error = make( [dynamic]i32, len = 0, cap = shape_cache_params.reserve ) + assert( error == .None, "VEFontCache.init : Failed to allocate visible array for shape cache storage" ) + stroage_entry.atlas_lru_code, error = make( [dynamic]Atlas_Key, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate atlas_lru_code array for shape cache storage" ) @@ -287,9 +290,6 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N stroage_entry.bounds, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate bounds array for shape cache storage" ) - - // stroage_entry.bounds_scaled, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve ) - // assert( error == .None, "VEFontCache.init : Failed to allocate bounds_scaled array for shape cache storage" ) } } @@ -417,10 +417,10 @@ hot_reload :: proc( ctx : ^Context, allocator : Allocator ) storage_entry := & shape_cache.storage[idx] reload_array( & storage_entry.glyph, allocator) reload_array( & storage_entry.position, allocator) + reload_array( & storage_entry.visible, allocator) reload_array( & storage_entry.atlas_lru_code, allocator) reload_array( & storage_entry.region_kind, allocator) reload_array( & storage_entry.bounds, allocator) - // reload_array( & storage_entry.bounds_scaled, allocator) } reload_array( & shape_cache.storage, allocator ) @@ -490,10 +490,10 @@ shutdown :: proc( ctx : ^Context ) storage_entry := & shape_cache.storage[idx] delete( storage_entry.glyph ) delete( storage_entry.position ) + delete( storage_entry.visible ) delete( storage_entry.atlas_lru_code) delete( storage_entry.region_kind) delete( storage_entry.bounds) - // delete( storage_entry.bounds_scaled) } lru_free( & shape_cache.state ) From 375825d34e6bbd537480d653b1bcdc0633c41401 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 22:10:05 -0500 Subject: [PATCH 41/62] Correct misspelling of storage_entry --- vefontcache/vefontcache.odin | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index e51999e..c14f6b1 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -271,24 +271,24 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // N for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1 { - stroage_entry := & shape_cache.storage[idx] + storage_entry := & shape_cache.storage[idx] - stroage_entry.glyph, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve ) + storage_entry.glyph, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" ) - stroage_entry.position, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve ) + storage_entry.position, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" ) - stroage_entry.visible, error = make( [dynamic]i32, len = 0, cap = shape_cache_params.reserve ) + storage_entry.visible, error = make( [dynamic]i32, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate visible array for shape cache storage" ) - stroage_entry.atlas_lru_code, error = make( [dynamic]Atlas_Key, len = 0, cap = shape_cache_params.reserve ) + storage_entry.atlas_lru_code, error = make( [dynamic]Atlas_Key, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate atlas_lru_code array for shape cache storage" ) - stroage_entry.region_kind, error = make( [dynamic]Atlas_Region_Kind, len = 0, cap = shape_cache_params.reserve ) + storage_entry.region_kind, error = make( [dynamic]Atlas_Region_Kind, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate region_kind array for shape cache storage" ) - stroage_entry.bounds, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve ) + storage_entry.bounds, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve ) assert( error == .None, "VEFontCache.init : Failed to allocate bounds array for shape cache storage" ) } } @@ -604,11 +604,11 @@ clear_shape_cache :: proc (ctx : ^Context) { lru_clear(& ctx.shape_cache.state) for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 { - stroage_entry := & ctx.shape_cache.storage[idx] - stroage_entry.end_cursor_pos = {} - stroage_entry.size = {} - clear(& stroage_entry.glyph) - clear(& stroage_entry.position) + storage_entry := & ctx.shape_cache.storage[idx] + storage_entry.end_cursor_pos = {} + storage_entry.size = {} + clear(& storage_entry.glyph) + clear(& storage_entry.position) } ctx.shape_cache.next_cache_id = 0 } From 4ff3edfed4b9c9a3097a170dcc8432839da7a466 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 12 Jan 2025 23:28:48 -0500 Subject: [PATCH 42/62] fix get_normalized_position_scale --- vefontcache/vefontcache.odin | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index c14f6b1..d9a66c3 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -1203,19 +1203,14 @@ get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { r // (Does nothing if view is 1 or 0) get_normalized_position_scale :: #force_inline proc "contextless" ( position, scale, view : Vec2 ) -> (position_norm, scale_norm : Vec2) { - snap_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } - should_snap := view * snap_quotient + should_snap := cast(f32) i32(view.x > 0 && view.y > 0) + view_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } - snapped_position := position - snapped_position.x = ceil(position.x * view.x) * snap_quotient.x - snapped_position.y = ceil(position.y * view.y) * snap_quotient.y + position_snapped := ceil(position) * view_quotient * should_snap + position_norm = position * view_quotient - snapped_position *= should_snap - snapped_position.x = max(snapped_position.x, position.x) - snapped_position.y = max(snapped_position.y, position.y) - - position_norm = snapped_position - scale_norm = scale * snap_quotient + position_norm = max(position_snapped, position_norm) + scale_norm = scale * view_quotient return } From 33e0b227a0491437bb8da3a955f9f1df87ced1b4 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 00:39:14 -0500 Subject: [PATCH 43/62] Added get_font_entry, resolve_px_scalar_size, snap_normalized_position_to_view --- vefontcache/vefontcache.odin | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/vefontcache/vefontcache.odin b/vefontcache/vefontcache.odin index d9a66c3..2cfd37d 100644 --- a/vefontcache/vefontcache.odin +++ b/vefontcache/vefontcache.odin @@ -1196,6 +1196,10 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : Context, font : Font_ID, //#region("miscellaneous") +get_font_entry :: #force_inline proc "contextless" ( ctx : ^Context, font : Font_ID ) -> Entry { + return ctx.entries[font] +} + get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos } // Will normalize the value of the position and scale based on the provided view. @@ -1237,6 +1241,25 @@ resolve_zoom_size_scale :: #force_inline proc "contextless" ( return } +resolve_px_scalar_size :: #force_inline proc "contextless" ( parser_info : Parser_Font_Info, px_size, px_scalar : f32, norm_scale : Vec2 +) -> (target_px_size, target_font_scale : f32, target_scale : Vec2 ) +{ + target_px_size = px_size * px_scalar + target_scale = norm_scale * (1 / px_scalar) + target_font_scale = parser_scale( parser_info, target_px_size ) + return +} + +snap_normalized_position_to_view :: #force_inline proc "contextless" ( position, view : Vec2 ) -> (position_snapped : Vec2) +{ + should_snap := cast(f32) i32(view.x > 0 && view.y > 0) + view_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) } + + position_snapped = ceil(position * view) * view_quotient * should_snap + position_snapped = max(position, position_snapped) + return +} + set_alpha_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.alpha_sharpen = scalar } set_px_scalar :: #force_inline proc( ctx : ^Context, scalar : f32 ) { assert(ctx != nil); ctx.px_scalar = scalar } set_zoom_px_interval :: #force_inline proc( ctx : ^Context, interval : i32 ) { assert(ctx != nil); ctx.zoom_px_interval = f32(interval) } From d7560b4a89d6e09e71be7cf3aa14962ce9e30102 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 00:39:21 -0500 Subject: [PATCH 44/62] Adjusted sokol demo --- examples/sokol_demo/sokol_demo.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/sokol_demo/sokol_demo.odin b/examples/sokol_demo/sokol_demo.odin index b3c3aab..12026db 100644 --- a/examples/sokol_demo/sokol_demo.odin +++ b/examples/sokol_demo/sokol_demo.odin @@ -158,16 +158,16 @@ draw_text :: proc( content : string, font : Font_ID, pos : Vec2, size : f32 = 0. def := demo_ctx.font_ids[ font.label ] size := size >= 2.0 ? size : f32(def.default_size) - ve.draw_text_view_space( & demo_ctx.ve_ctx, - def.ve_id, - size, + resolved_size, zoom_scale := ve.resolve_zoom_size_scale( zoom, size, scale, 2, 2, 999.0, demo_ctx.screen_size ) + snapped_pos := ve.snap_normalized_position_to_view( pos, demo_ctx.screen_size ) + norm_scale := zoom_scale * (1 / demo_ctx.screen_size) + ve.draw_text_normalized_space( & demo_ctx.ve_ctx, + def.ve_id, + resolved_size, color_norm, - demo_ctx.screen_size, - pos, - scale, - zoom, - content, - // ve.shaper_shape_text_latin, + snapped_pos, + norm_scale, + content ) return } From 7dd1bad72d0dc400b59fa6cc2a035423def6b099 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 12:00:00 -0500 Subject: [PATCH 45/62] Working toward mac and linux builds, no need for stb_image it looks like... --- scripts/build_sokol_demo.sh | 6 + scripts/clean.sh | 2 +- thirdparty/stb/lib/stb_truetype.lib | Bin 380552 -> 380552 bytes thirdparty/stb/src/Makefile | 63 +- thirdparty/stb/src/stb_image.c | 2 - thirdparty/stb/src/stb_image.h | 7897 --------------------------- vefontcache/parser.odin | 6 +- 7 files changed, 43 insertions(+), 7933 deletions(-) delete mode 100644 thirdparty/stb/src/stb_image.c delete mode 100644 thirdparty/stb/src/stb_image.h diff --git a/scripts/build_sokol_demo.sh b/scripts/build_sokol_demo.sh index 0cbfbdd..411e986 100644 --- a/scripts/build_sokol_demo.sh +++ b/scripts/build_sokol_demo.sh @@ -81,6 +81,12 @@ pushd "$path_thirdparty" > /dev/null fi popd > /dev/null +path_stb_truetype="$path_thirdparty/stb/src" + +pushd "$path_stb_truetype" > /dev/null + eval make +popd > /dev/null + source "$(dirname "$0")/helpers/odin_compiler_defs.sh" pkg_collection_backend="backend=$path_backend" diff --git a/scripts/clean.sh b/scripts/clean.sh index 31613f8..0d73019 100644 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -6,4 +6,4 @@ path_scripts="$path_root/scripts" path_thirdparty="$path_root/thirdparty" if [ -d "$path_build" ]; then rm -rf "$path_build"; fi -# if [ -d "$path_thirdparty" ]; then rm -rf "$path_thirdparty"; fi \ No newline at end of file +# if [ -d "$path_thirdparty" ]; then rm -rf "$path_thirdparty"; fi diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index bc37f121124d31e11ea0ebda195bbf87be6966ec..c21773696c3e9e759cbe17d06a4886e8a98cb021 100644 GIT binary patch delta 90 zcmeB}E8a0ze1Z&{xrMo@vB6{oJMoQ{4{f2`%?fs6R!CBlgDVVS+^z~WW-qta=FJuD Vn=2TBm7{c>YJK3 YSF~@gU<6_&AZ7+)mhGD>Sg(cx0M?xzXaE2J diff --git a/thirdparty/stb/src/Makefile b/thirdparty/stb/src/Makefile index 194ea5e..939b181 100644 --- a/thirdparty/stb/src/Makefile +++ b/thirdparty/stb/src/Makefile @@ -8,24 +8,26 @@ endif wasm: mkdir -p ../lib - $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image.c -o ../lib/stb_image_wasm.o -DSTBI_NO_STDIO - $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_write.c -o ../lib/stb_image_write_wasm.o -DSTBI_WRITE_NO_STDIO - $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_resize.c -o ../lib/stb_image_resize_wasm.o $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_truetype.c -o ../lib/stb_truetype_wasm.o + + # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image.c -o ../lib/stb_image_wasm.o -DSTBI_NO_STDIO + # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_write.c -o ../lib/stb_image_write_wasm.o -DSTBI_WRITE_NO_STDIO + # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_resize.c -o ../lib/stb_image_resize_wasm.o # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_vorbis.c -o ../lib/stb_vorbis_wasm.o -DSTB_VORBIS_NO_STDIO - $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_rect_pack.c -o ../lib/stb_rect_pack_wasm.o - $(CC) -c -Os --target=wasm32 stb_sprintf.c -o ../lib/stb_sprintf_wasm.o + # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_rect_pack.c -o ../lib/stb_rect_pack_wasm.o + # $(CC) -c -Os --target=wasm32 stb_sprintf.c -o ../lib/stb_sprintf_wasm.o unix: mkdir -p ../lib $(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c stb_sprintf.c - $(AR) rcs ../lib/stb_image.a stb_image.o - $(AR) rcs ../lib/stb_image_write.a stb_image_write.o - $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o $(AR) rcs ../lib/stb_truetype.a stb_truetype.o - $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o - $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o - $(AR) rcs ../lib/stb_sprintf.a stb_sprintf.o + + # $(AR) rcs ../lib/stb_image.a stb_image.o + # $(AR) rcs ../lib/stb_image_write.a stb_image_write.o + # $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o + # $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o + # $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o + # $(AR) rcs ../lib/stb_sprintf.a stb_sprintf.o #$(CC) -fPIC -shared -Wl,-soname=stb_image.so -o ../lib/stb_image.so stb_image.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_write.so -o ../lib/stb_image_write.so stb_image_write.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_resize.so -o ../lib/stb_image_resize.so stb_image_resize.o @@ -36,25 +38,26 @@ unix: darwin: mkdir -p ../lib - $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image.c -o stb_image-x86_64.o -mmacosx-version-min=10.12 - $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image.c -o stb_image-arm64.o -mmacosx-version-min=10.12 - lipo -create stb_image-x86_64.o stb_image-arm64.o -output ../lib/darwin/stb_image.a - $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-x86_64.o -mmacosx-version-min=10.12 - $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-arm64.o -mmacosx-version-min=10.12 - lipo -create stb_image_write-x86_64.o stb_image_write-arm64.o -output ../lib/darwin/stb_image_write.a - $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-x86_64.o -mmacosx-version-min=10.12 - $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-arm64.o -mmacosx-version-min=10.12 - lipo -create stb_image_resize-x86_64.o stb_image_resize-arm64.o -output ../lib/darwin/stb_image_resize.a + lipo -create stb_truetype-x86_64.o stb_truetype-arm64.o -output ../lib/darwin/stb_truetype.a $(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 - $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-x86_64.o -mmacosx-version-min=10.12 - $(CC) -arch arm64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-arm64.o -mmacosx-version-min=10.12 - lipo -create stb_rect_pack-x86_64.o stb_rect_pack-arm64.o -output ../lib/darwin/stb_rect_pack.a - $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-x86_64.o -mmacosx-version-min=10.12 - $(CC) -arch arm64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-arm64.o -mmacosx-version-min=10.12 - lipo -create stb_vorbis-x86_64.o stb_vorbis-arm64.o -output ../lib/darwin/stb_vorbis.a - $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-x86_64.o -mmacosx-version-min=10.12 - $(CC) -arch arm64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-arm64.o -mmacosx-version-min=10.12 - lipo -create stb_sprintf-x86_64.o stb_sprintf-arm64.o -output ../lib/darwin/stb_sprintf.a + + # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image.c -o stb_image-x86_64.o -mmacosx-version-min=10.12 + # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image.c -o stb_image-arm64.o -mmacosx-version-min=10.12 + # lipo -create stb_image-x86_64.o stb_image-arm64.o -output ../lib/darwin/stb_image.a + # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-x86_64.o -mmacosx-version-min=10.12 + # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-arm64.o -mmacosx-version-min=10.12 + # lipo -create stb_image_write-x86_64.o stb_image_write-arm64.o -output ../lib/darwin/stb_image_write.a + # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-x86_64.o -mmacosx-version-min=10.12 + # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-arm64.o -mmacosx-version-min=10.12 + # lipo -create stb_image_resize-x86_64.o stb_image_resize-arm64.o -output ../lib/darwin/stb_image_resize.a + # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-x86_64.o -mmacosx-version-min=10.12 + # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-arm64.o -mmacosx-version-min=10.12 + # lipo -create stb_rect_pack-x86_64.o stb_rect_pack-arm64.o -output ../lib/darwin/stb_rect_pack.a + # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-x86_64.o -mmacosx-version-min=10.12 + # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-arm64.o -mmacosx-version-min=10.12 + # lipo -create stb_vorbis-x86_64.o stb_vorbis-arm64.o -output ../lib/darwin/stb_vorbis.a + # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-x86_64.o -mmacosx-version-min=10.12 + # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-arm64.o -mmacosx-version-min=10.12 + # lipo -create stb_sprintf-x86_64.o stb_sprintf-arm64.o -output ../lib/darwin/stb_sprintf.a rm *.o diff --git a/thirdparty/stb/src/stb_image.c b/thirdparty/stb/src/stb_image.c deleted file mode 100644 index badb3ef..0000000 --- a/thirdparty/stb/src/stb_image.c +++ /dev/null @@ -1,2 +0,0 @@ -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" \ No newline at end of file diff --git a/thirdparty/stb/src/stb_image.h b/thirdparty/stb/src/stb_image.h deleted file mode 100644 index 39acae6..0000000 --- a/thirdparty/stb/src/stb_image.h +++ /dev/null @@ -1,7897 +0,0 @@ -/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes - 2.26 (2020-07-13) many minor fixes - 2.25 (2020-02-02) fix warnings - 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically - 2.23 (2019-08-11) fix clang static analysis warning - 2.22 (2019-03-04) gif fixes, fix warnings - 2.21 (2019-02-25) fix typo in comment - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings - 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes Mikhail Morozov (1-bit BMP) - Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) - Arseny Kapoulkine Simon Breuss (16-bit PNM) - John-Mark Allen - Carmelo J Fdez-Aguera - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski - Phil Jordan Dave Moore Roy Eltham - Hayaki Saito Nathan Reed Won Chun - Luke Graham Johan Duparc Nick Verigakis the Horde3D community - Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Eugene Golushkov Laurent Gomila Cort Stratton github:snagar - Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex - Cass Everitt Ryamond Barbiero github:grim210 - Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw - Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Matthew Gregan github:poppolopoppo - Julian Raschke Gregory Mullen Christian Floisand github:darealshinji - Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 - Brad Weinberger Matvey Cherevko github:mosra - Luca Sas Alexander Veselov Zack Middleton [reserved] - Ryan C. Gordon [reserved] [reserved] - DO NOT ADD YOUR NAME HERE - - Jacko Dirks - - To add your name to the credits, pick a random blank space in the middle and fill it. - 80% of merge conflicts on stb PRs are due to people adding their name at the end - of the credits. -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'desired_channels' if desired_channels is non-zero, or -// *channels_in_file otherwise. If desired_channels is non-zero, -// *channels_in_file has the number of components that _would_ have been -// output otherwise. E.g. if you set desired_channels to 4, you will always -// get RGBA output, but you can check *channels_in_file to see if it's trivially -// opaque because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *channels_in_file will be unchanged. The function -// stbi_failure_reason() can be queried for an extremely brief, end-user -// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS -// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// To query the width, height and component count of an image without having to -// decode the full file, you can use the stbi_info family of functions: -// -// int x,y,n,ok; -// ok = stbi_info(filename, &x, &y, &n); -// // returns ok=1 and sets x, y, n if image is a supported format, -// // 0 otherwise. -// -// Note that stb_image pervasively uses ints in its public API for sizes, -// including sizes of memory buffers. This is now part of the API and thus -// hard to change without causing breakage. As a result, the various image -// loaders all have certain limits on image size; these differ somewhat -// by format but generally boil down to either just under 2GB or just under -// 1GB. When the decoded image would be larger than this, stb_image decoding -// will fail. -// -// Additionally, stb_image will reject image files that have any of their -// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, -// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, -// the only way to have an image with such dimensions load correctly -// is for it to have a rather extreme aspect ratio. Either way, the -// assumption here is that such larger images are likely to be malformed -// or malicious. If you do need to load an image with individual dimensions -// larger than that, and it still fits in the overall size limit, you can -// #define STBI_MAX_DIMENSIONS on your own to be something larger. -// -// =========================================================================== -// -// UNICODE: -// -// If compiling for Windows and you wish to use Unicode filenames, compile -// with -// #define STBI_WINDOWS_UTF8 -// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert -// Windows wchar_t filenames to utf8. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy-to-use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// provide more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image supports loading HDR images in general, and currently the Radiance -// .HDR file format specifically. You can still load any file through the existing -// interface; if you attempt to load an HDR file, it will be automatically remapped -// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// We optionally support converting iPhone-formatted PNGs (which store -// premultiplied BGRA) back to RGB, even though they're internally encoded -// differently. To enable this conversion, call -// stbi_convert_iphone_png_to_rgb(1). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// -// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater -// than that size (in either width or height) without further processing. -// This is to let programs in the wild set an upper bound to prevent -// denial-of-service attacks on untrusted data, as one could generate a -// valid image of gigantic dimensions and force stb_image to allocate a -// huge block of memory and spend disproportionate time decoding it. By -// default this is set to (1 << 24), which is 16777216, but that's still -// very big. - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for desired_channels - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -#include -typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef STBIDEF -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - -#ifdef STBI_WINDOWS_UTF8 -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif - -//////////////////////////////////// -// -// float-per-channel interface -// -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// on most compilers (and ALL modern mainstream compilers) this is threadsafe -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit (char const *filename); -STBIDEF int stbi_is_16_bit_from_file(FILE *f); -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// as above, but only applies to images loaded on the thread that calls the function -// this function is only available if your compiler supports thread-local variables; -// calling it will fail to link if your compiler doesn't -STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); -STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp, pow -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - -#ifdef __cplusplus -#define STBI_EXTERN extern "C" -#else -#define STBI_EXTERN extern -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - -#ifndef STBI_NO_THREAD_LOCALS - #if defined(__cplusplus) && __cplusplus >= 201103L - #define STBI_THREAD_LOCAL thread_local - #elif defined(__GNUC__) && __GNUC__ < 5 - #define STBI_THREAD_LOCAL __thread - #elif defined(_MSC_VER) - #define STBI_THREAD_LOCAL __declspec(thread) - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) - #define STBI_THREAD_LOCAL _Thread_local - #endif - - #ifndef STBI_THREAD_LOCAL - #if defined(__GNUC__) - #define STBI_THREAD_LOCAL __thread - #endif - #endif -#endif - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// which in turn means it gets to use SSE2 everywhere. This is unfortunate, -// but previous attempts to provide the SSE2 functions with runtime -// detection caused numerous issues. The way architecture extensions are -// exposed in GCC/Clang is, sadly, not really suited for one-file libs. -// New behavior: if compiled with -msse2, we use SSE2 without any -// detection; if not, we don't use it at all. -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) -// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET -// -// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the -// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. -// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not -// simultaneously enabling "-mstackrealign". -// -// See https://github.com/nothings/stb/issues/81 for more information. -// -// So default to no SSE2 on 32-bit MinGW. If you've read this far and added -// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. -#define STBI_NO_SIMD -#endif - -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#endif - -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) -{ - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} -#endif - -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -#ifdef _MSC_VER -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name -#else -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -#ifndef STBI_MAX_DIMENSIONS -#define STBI_MAX_DIMENSIONS (1 << 24) -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - int callback_already_read; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->callback_already_read = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->callback_already_read = 0; - s->img_buffer = s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - int ch; - fseek((FILE*) user, n, SEEK_CUR); - ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ - if (ch != EOF) { - ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ - } -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user) || ferror((FILE *) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__pnm_is16(stbi__context *s); -#endif - -static -#ifdef STBI_THREAD_LOCAL -STBI_THREAD_LOCAL -#endif -const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -#ifndef STBI_NO_FAILURE_STRINGS -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} -#endif - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} -#endif - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} -#endif - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} -#endif - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} -#endif - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load_global = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load_global = flag_true_if_should_flip; -} - -#ifndef STBI_THREAD_LOCAL -#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global -#else -static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; - -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load_local = flag_true_if_should_flip; - stbi__vertically_flip_on_load_set = 1; -} - -#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ - ? stbi__vertically_flip_on_load_local \ - : stbi__vertically_flip_on_load_global) -#endif // STBI_THREAD_LOCAL - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - - // test the formats with a very explicit header first (at least a FOURCC - // or distinctive magic number first) - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #else - STBI_NOTUSED(bpc); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); - #endif - - // then the formats that can end up attempting to load with just 1 or 2 - // bytes matching expectations; these are prone to false positives, so - // try them later - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} - -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) -{ - int row; - size_t bytes_per_row = (size_t)w * bytes_per_pixel; - stbi_uc temp[2048]; - stbi_uc *bytes = (stbi_uc *)image; - - for (row = 0; row < (h>>1); row++) { - stbi_uc *row0 = bytes + row*bytes_per_row; - stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; - // swap row0 with row1 - size_t bytes_left = bytes_per_row; - while (bytes_left) { - size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); - memcpy(temp, row0, bytes_copy); - memcpy(row0, row1, bytes_copy); - memcpy(row1, temp, bytes_copy); - row0 += bytes_copy; - row1 += bytes_copy; - bytes_left -= bytes_copy; - } - } -} - -#ifndef STBI_NO_GIF -static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) -{ - int slice; - int slice_size = w * h * bytes_per_pixel; - - stbi_uc *bytes = (stbi_uc *)image; - for (slice = 0; slice < z; ++slice) { - stbi__vertical_flip(bytes, w, h, bytes_per_pixel); - bytes += slice_size; - } -} -#endif - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - - // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. - STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); - - if (ri.bits_per_channel != 8) { - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. - STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); - - if (ri.bits_per_channel != 16) { - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); - } - - return (stbi__uint16 *) result; -} - -#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); - } -} -#endif - -#ifndef STBI_NO_STDIO - -#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) -STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); -#endif - -#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) - return 0; - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - -#endif //!STBI_NO_STDIO - -STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_mem(&s,buffer,len); - - result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); - } - - return result; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - long pos = ftell(f); - int res; - stbi__context s; - stbi__start_file(&s,f); - res = stbi__hdr_test(&s); - fseek(f, pos, SEEK_SET); - return res; - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} -#endif - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) -// nothing -#else -static void stbi__skip(stbi__context *s, int n) -{ - if (n == 0) return; // already there! - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) -// nothing -#else -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} -#endif - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} -#endif - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - z += (stbi__uint32)stbi__get16le(s) << 16; - return z; -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - } - if (n < comp) { - for (i=0; i < x*y; ++i) { - output[i*comp + n] = data[i*comp + n]/255.0f; - } - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0; - unsigned int code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - unsigned int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & (sgn - 1)); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static const stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * (1 << j->succ_low)); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * (1 << shift)); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) * 4096) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0]*4; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); - int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); - L -= (sixteen ? 129 : 65); - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); - return 1; - } - - return stbi__err("unknown marker","Corrupt JPEG"); -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - z->rgb = 0; - for (i=0; i < s->img_n; ++i) { - static const unsigned char rgb[3] = { 'R', 'G', 'B' }; - z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios - // and I've never seen a non-corrupted JPEG file actually use them - for (i=0; i < s->img_n; ++i) { - if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); - if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - stbi__free_jpeg_components(j, j->s->img_n, 0); -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n, is_rgb; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); - - if (z->s->img_n == 3 && n < 3 && !is_rgb) - decode_n = 1; - else - decode_n = z->s->img_n; - - // nothing to do if no components requested; check this now to avoid - // accessing uninitialized coutput[0] later - if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], m); - out[1] = stbi__blinn_8x8(coutput[1][i], m); - out[2] = stbi__blinn_8x8(coutput[2][i], m); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], m); - out[1] = stbi__blinn_8x8(255 - out[1], m); - out[2] = stbi__blinn_8x8(255 - out[2], m); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } - } - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output - return output; - } -} - -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - if (!j) return stbi__errpuc("outofmem", "Out of memory"); - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - if (!j) return stbi__err("outofmem", "Out of memory"); - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); - stbi__rewind(s); - STBI_FREE(j); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - if (!j) return stbi__err("outofmem", "Out of memory"); - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) -#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[STBI__ZNSYMS]; - stbi__uint16 value[STBI__ZNSYMS]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static int stbi__zeof(stbi__zbuf *z) -{ - return (z->zbuffer >= z->zbuffer_end); -} - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - return stbi__zeof(z) ? 0 : *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - if (z->code_buffer >= (1U << z->num_bits)) { - z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ - return; - } - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s >= 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! - if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) { - if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ - } - stbi__fill_bits(a); - } - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - unsigned int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (unsigned int) (z->zout - z->zout_start); - limit = old_limit = (unsigned) (z->zout_end - z->zout_start); - if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); - while (cur + n > limit) { - if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); - limit *= 2; - } - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static const int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static const int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static const int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) { - c = stbi__zreceive(a,3)+3; - } else if (c == 18) { - c = stbi__zreceive(a,7)+11; - } else { - return stbi__err("bad codelengths", "Corrupt PNG"); - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); - n += c; - } - } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncompressed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} -*/ - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; - int depth; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - int bytes = (depth == 16? 2 : 1); - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - - // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, - // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), - // so just check for raw_len < img_len always. - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; - int filter = *raw++; - - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - if (!final) return stbi__err("outofmem", "Out of memory"); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load_global = 0; -static int stbi__de_iphone_flag_global = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag_global = flag_true_if_should_convert; -} - -#ifndef STBI_THREAD_LOCAL -#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global -#define stbi__de_iphone_flag stbi__de_iphone_flag_global -#else -static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; -static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; - -STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; - stbi__unpremultiply_on_load_set = 1; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag_local = flag_true_if_should_convert; - stbi__de_iphone_flag_set = 1; -} - -#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ - ? stbi__unpremultiply_on_load_local \ - : stbi__unpremultiply_on_load_global) -#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ - ? stbi__de_iphone_flag_local \ - : stbi__de_iphone_flag_global) -#endif // STBI_THREAD_LOCAL - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - stbi_uc half = a / 2; - p[0] = (p[2] * 255 + half) / a; - p[1] = (p[1] * 255 + half) / a; - p[2] = ( t * 255 + half) / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]={0}; - stbi__uint16 tc16[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); - s->img_y = stbi__get32be(s); - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } else if (has_trans) { - // non-paletted image with tRNS -> source image has (constant) alpha - ++s->img_n; - } - STBI_FREE(z->expanded); z->expanded = NULL; - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) -{ - void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth <= 8) - ri->bits_per_channel = 8; - else if (p->depth == 16) - ri->bits_per_channel = 16; - else - return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} - -static int stbi__png_is16(stbi__context *s) -{ - stbi__png p; - p.s = s; - if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) - return 0; - if (p.depth != 16) { - stbi__rewind(p.s); - return 0; - } - return 1; -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) { n += 16; z >>= 16; } - if (z >= 0x00100) { n += 8; z >>= 8; } - if (z >= 0x00010) { n += 4; z >>= 4; } - if (z >= 0x00004) { n += 2; z >>= 2; } - if (z >= 0x00002) { n += 1;/* >>= 1;*/ } - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -// extract an arbitrarily-aligned N-bit value (N=bits) -// from v, and then make it 8-bits long and fractionally -// extend it to full full range. -static int stbi__shiftsigned(unsigned int v, int shift, int bits) -{ - static unsigned int mul_table[9] = { - 0, - 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, - 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, - }; - static unsigned int shift_table[9] = { - 0, 0,0,1,0,2,4,6,0, - }; - if (shift < 0) - v <<= -shift; - else - v >>= shift; - STBI_ASSERT(v < 256); - v >>= (8-bits); - STBI_ASSERT(bits >= 0 && bits <= 8); - return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; - int extra_read; -} stbi__bmp_data; - -static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) -{ - // BI_BITFIELDS specifies masks explicitly, don't override - if (compress == 3) - return 1; - - if (compress == 0) { - if (info->bpp == 16) { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } else if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - // otherwise, use defaults, which is all-0 - info->mr = info->mg = info->mb = info->ma = 0; - } - return 1; - } - return 0; // error -} - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - info->extra_read = 14; - - if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes - if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - if (compress == 0) { - stbi__bmp_set_mask_defaults(info, compress); - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->extra_read += 12; - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - // V4/V5 header - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs - stbi__bmp_set_mask_defaults(info, compress); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - STBI_NOTUSED(ri); - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - info.extra_read - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - info.extra_read - info.hsz) >> 2; - } - if (psize == 0) { - if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { - return stbi__errpuc("bad offset", "Corrupt BMP"); - } - } - - if (info.bpp == 24 && ma == 0xff000000) - s->img_n = 3; - else - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 1) width = (s->img_x + 7) >> 3; - else if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - if (info.bpp == 1) { - for (j=0; j < (int) s->img_y; ++j) { - int bit_offset = 7, v = stbi__get8(s); - for (i=0; i < (int) s->img_x; ++i) { - int color = (v>>bit_offset)&0x1; - out[z++] = pal[color][0]; - out[z++] = pal[color][1]; - out[z++] = pal[color][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - if((--bit_offset) < 0) { - bit_offset = 7; - v = stbi__get8(s); - } - } - stbi__skip(s, pad); - } - } else { - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - info.extra_read - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - unsigned int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i]; p1[i] = p2[i]; p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if (is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fallthrough - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4] = {0}; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - STBI_NOTUSED(ri); - STBI_NOTUSED(tga_x_origin); // @TODO - STBI_NOTUSED(tga_y_origin); // @TODO - - if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - if (tga_palette_len == 0) { /* you have to have at least one entry! */ - STBI_FREE(tga_data); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - STBI_NOTUSED(tga_palette_start); - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - int pixelCount; - int channelCount, compression; - int channel, i; - int bitdepth; - int w,h; - stbi_uc *out; - STBI_NOTUSED(ri); - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - if (channel >= channelCount) { - // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } - } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); - } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); - } - } - } - } - - // convert to desired output format - if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) -{ - stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - - if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); - if (!result) return stbi__errpuc("outofmem", "Out of memory"); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out; // output buffer (always 4 components) - stbi_uc *background; // The current "background" as far as a gif is concerned - stbi_uc *history; - int flags, bgindex, ratio, transparent, eflags; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[8192]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; - int delay; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!g) return stbi__err("outofmem", "Out of memory"); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); - stbi__rewind( s ); - return 0; - } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - int idx; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - idx = g->cur_x + g->cur_y; - p = &g->out[idx]; - g->history[idx / 4] = 1; - - c = &g->color_table[g->codes[code].suffix * 4]; - if (c[3] > 128) { // don't render transparent pixels; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) { - return stbi__errpuc("no clear code", "Corrupt GIF"); - } - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 8192) { - return stbi__errpuc("too many codes", "Corrupt GIF"); - } - - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -// two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) -{ - int dispose; - int first_frame; - int pi; - int pcount; - STBI_NOTUSED(req_comp); - - // on first frame, any non-written pixels get the background colour (non-transparent) - first_frame = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) - return stbi__errpuc("too large", "GIF image is too large"); - pcount = g->w * g->h; - g->out = (stbi_uc *) stbi__malloc(4 * pcount); - g->background = (stbi_uc *) stbi__malloc(4 * pcount); - g->history = (stbi_uc *) stbi__malloc(pcount); - if (!g->out || !g->background || !g->history) - return stbi__errpuc("outofmem", "Out of memory"); - - // image is treated as "transparent" at the start - ie, nothing overwrites the current background; - // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to the color that was there the previous frame. - memset(g->out, 0x00, 4 * pcount); - memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) - memset(g->history, 0x00, pcount); // pixels that were affected previous frame - first_frame = 1; - } else { - // second frame - how do we dispose of the previous one? - dispose = (g->eflags & 0x1C) >> 2; - pcount = g->w * g->h; - - if ((dispose == 3) && (two_back == 0)) { - dispose = 2; // if I don't have an image to revert back to, default to the old background - } - - if (dispose == 3) { // use previous graphic - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); - } - } - } else if (dispose == 2) { - // restore what was changed last frame to background before that frame; - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); - } - } - } else { - // This is a non-disposal case eithe way, so just - // leave the pixels as is, and they will become the new background - // 1: do not dispose - // 0: not specified. - } - - // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); - } - - // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - - for (;;) { - int tag = stbi__get8(s); - switch (tag) { - case 0x2C: /* Image Descriptor */ - { - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - // if the width of the specified rectangle is 0, that means - // we may not see *any* pixels or the image is malformed; - // to make sure this is caught, move the current y down to - // max_y (which is what out_gif_code checks). - if (w == 0) - g->cur_y = g->max_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (!o) return NULL; - - // if this was the first frame, - pcount = g->w * g->h; - if (first_frame && (g->bgindex > 0)) { - // if first frame, any pixel not drawn to gets the background color - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi] == 0) { - g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; - memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); - } - } - } - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - int ext = stbi__get8(s); - if (ext == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. - - // unset old transparent - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 255; - } - if (g->eflags & 0x01) { - g->transparent = stbi__get8(s); - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 0; - } - } else { - // don't need transparent - stbi__skip(s, 1); - g->transparent = -1; - } - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) { - stbi__skip(s, len); - } - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } -} - -static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) -{ - STBI_FREE(g->out); - STBI_FREE(g->history); - STBI_FREE(g->background); - - if (out) STBI_FREE(out); - if (delays && *delays) STBI_FREE(*delays); - return stbi__errpuc("outofmem", "Out of memory"); -} - -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - if (stbi__gif_test(s)) { - int layers = 0; - stbi_uc *u = 0; - stbi_uc *out = 0; - stbi_uc *two_back = 0; - stbi__gif g; - int stride; - int out_size = 0; - int delays_size = 0; - - STBI_NOTUSED(out_size); - STBI_NOTUSED(delays_size); - - memset(&g, 0, sizeof(g)); - if (delays) { - *delays = 0; - } - - do { - u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - - if (u) { - *x = g.w; - *y = g.h; - ++layers; - stride = g.w * g.h * 4; - - if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); - if (!tmp) - return stbi__load_gif_main_outofmem(&g, out, delays); - else { - out = (stbi_uc*) tmp; - out_size = layers * stride; - } - - if (delays) { - int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); - if (!new_delays) - return stbi__load_gif_main_outofmem(&g, out, delays); - *delays = new_delays; - delays_size = layers * sizeof(int); - } - } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); - if (!out) - return stbi__load_gif_main_outofmem(&g, out, delays); - out_size = layers * stride; - if (delays) { - *delays = (int*) stbi__malloc( layers * sizeof(int) ); - if (!*delays) - return stbi__load_gif_main_outofmem(&g, out, delays); - delays_size = layers * sizeof(int); - } - } - memcpy( out + ((layers - 1) * stride), u, stride ); - if (layers >= 2) { - two_back = out - 2 * stride; - } - - if (delays) { - (*delays)[layers - 1U] = g.delay; - } - } - } while (u != 0); - - // free temp buffer; - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - - // do the final conversion after loading everything; - if (req_comp && req_comp != 4) - out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); - - *z = layers; - return out; - } else { - return stbi__errpuc("not GIF", "Image was not as a gif type."); - } -} - -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - STBI_NOTUSED(ri); - - u = stbi__gif_load_next(s, &g, comp, req_comp, 0); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - - // moved conversion to after successful load so that the same - // can be done for multiple frames. - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } else if (g.out) { - // if there was an error and we allocated an image buffer, free it! - STBI_FREE(g.out); - } - - // free buffers needed for multiple frame loading; - STBI_FREE(g.history); - STBI_FREE(g.background); - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) -{ - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); - stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); - - // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); - if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } - - for (k = 0; k < 4; ++k) { - int nleft; - i = 0; - while ((nleft = width - i) > 0) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - if (scanline) - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - if (p == NULL) { - stbi__rewind( s ); - return 0; - } - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) { - if (info.bpp == 24 && info.ma == 0xff000000) - *comp = 3; - else - *comp = info.ma ? 4 : 3; - } - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount, dummy, depth; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 8 && depth != 16) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} - -static int stbi__psd_is16(stbi__context *s) -{ - int channelCount, depth; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - STBI_NOTUSED(stbi__get32be(s)); - STBI_NOTUSED(stbi__get32be(s)); - depth = stbi__get16be(s); - if (depth != 16) { - stbi__rewind( s ); - return 0; - } - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained,dummy; - stbi__pic_packet packets[10]; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - STBI_NOTUSED(ri); - - ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); - if (ri->bits_per_channel == 0) - return 0; - - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - - if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv, dummy; - char c, p, t; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - if (maxv > 65535) - return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); - else if (maxv > 255) - return 16; - else - return 8; -} - -static int stbi__pnm_is16(stbi__context *s) -{ - if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) - return 1; - return 0; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -static int stbi__is_16_main(stbi__context *s) -{ - #ifndef STBI_NO_PNG - if (stbi__png_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_is16(s)) return 1; - #endif - return 0; -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} - -STBIDEF int stbi_is_16_bit(char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_is_16_bit_from_file(f); - fclose(f); - return result; -} - -STBIDEF int stbi_is_16_bit_from_file(FILE *f) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__is_16_main(&s); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__is_16_main(&s); -} - -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__is_16_main(&s); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug - 1-bit BMP - *_is_16_bit api - avoid warnings - 2.16 (2017-07-23) all functions have 16-bit variants; - STBI_NO_STDIO works again; - compilation fixes; - fix rounding in unpremultiply; - optimize vertical flip; - disable raw_len validation; - documentation fixes - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ \ No newline at end of file diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 9223baf..9815870 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -10,9 +10,9 @@ Freetype isn't really supported and its not a high priority. ~~That interface is not exposed from this parser but could be added to parser_init.~~ STB_Truetype: -* Has macros for its allocation unfortuantely. -TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator. -Already wanted to do so anyway to evaluate the shape generation implementation. +* 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 "core:c" From 6bc7514ef05c964587c852a08ba0cbd1722fb79b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 12:04:36 -0500 Subject: [PATCH 46/62] force script to be LF (not sure why it wasnt...) --- scripts/build_sokol_demo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_sokol_demo.sh b/scripts/build_sokol_demo.sh index 411e986..736f57e 100644 --- a/scripts/build_sokol_demo.sh +++ b/scripts/build_sokol_demo.sh @@ -84,7 +84,7 @@ popd > /dev/null path_stb_truetype="$path_thirdparty/stb/src" pushd "$path_stb_truetype" > /dev/null - eval make + eval make -C "$path_stb_truetype" popd > /dev/null source "$(dirname "$0")/helpers/odin_compiler_defs.sh" From c8cd665d5fe90af39da1dadd1b2d0ca978a44abe Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 12:32:48 -0500 Subject: [PATCH 47/62] preogress on getting wsl to compile... --- .github/workflows/macos_build.yaml | 2 +- thirdparty/stb/src/Makefile | 42 +++--------------------------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/.github/workflows/macos_build.yaml b/.github/workflows/macos_build.yaml index e7f5ee5..b520d46 100644 --- a/.github/workflows/macos_build.yaml +++ b/.github/workflows/macos_build.yaml @@ -35,7 +35,7 @@ jobs: brew install harfbuzz brew install odin - make -C "/opt/homebrew/Cellar/odin/2024-10/libexec/vendor/stb/src" + make -C "/opt/homebrew/Cellar/odin/2024-12/libexec/vendor/stb/src" - name: Run build script run: | diff --git a/thirdparty/stb/src/Makefile b/thirdparty/stb/src/Makefile index 939b181..27361c1 100644 --- a/thirdparty/stb/src/Makefile +++ b/thirdparty/stb/src/Makefile @@ -10,30 +10,12 @@ wasm: mkdir -p ../lib $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_truetype.c -o ../lib/stb_truetype_wasm.o - # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image.c -o ../lib/stb_image_wasm.o -DSTBI_NO_STDIO - # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_write.c -o ../lib/stb_image_write_wasm.o -DSTBI_WRITE_NO_STDIO - # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_image_resize.c -o ../lib/stb_image_resize_wasm.o - # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_vorbis.c -o ../lib/stb_vorbis_wasm.o -DSTB_VORBIS_NO_STDIO - # $(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_rect_pack.c -o ../lib/stb_rect_pack_wasm.o - # $(CC) -c -Os --target=wasm32 stb_sprintf.c -o ../lib/stb_sprintf_wasm.o - unix: mkdir -p ../lib - $(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c stb_sprintf.c - $(AR) rcs ../lib/stb_truetype.a stb_truetype.o + $(CC) -c -O2 -Os -fPIC stb_truetype.c + $(AR) rcs ../lib/stb_truetype.a stb_truetype.o - # $(AR) rcs ../lib/stb_image.a stb_image.o - # $(AR) rcs ../lib/stb_image_write.a stb_image_write.o - # $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o - # $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o - # $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o - # $(AR) rcs ../lib/stb_sprintf.a stb_sprintf.o - #$(CC) -fPIC -shared -Wl,-soname=stb_image.so -o ../lib/stb_image.so stb_image.o - #$(CC) -fPIC -shared -Wl,-soname=stb_image_write.so -o ../lib/stb_image_write.so stb_image_write.o - #$(CC) -fPIC -shared -Wl,-soname=stb_image_resize.so -o ../lib/stb_image_resize.so stb_image_resize.o - #$(CC) -fPIC -shared -Wl,-soname=stb_truetype.so -o ../lib/stb_truetype.so stb_image_truetype.o - #$(CC) -fPIC -shared -Wl,-soname=stb_rect_pack.so -o ../lib/stb_rect_pack.so stb_rect_packl.o - #$(CC) -fPIC -shared -Wl,-soname=stb_vorbis.so -o ../lib/stb_vorbis.so stb_vorbisl.o + #$(CC) -fPIC -shared -Wl,-soname=stb_truetype.so -o ../lib/stb_truetype.so stb_image_truetype.o rm *.o darwin: @@ -42,22 +24,4 @@ darwin: $(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 - # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image.c -o stb_image-x86_64.o -mmacosx-version-min=10.12 - # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image.c -o stb_image-arm64.o -mmacosx-version-min=10.12 - # lipo -create stb_image-x86_64.o stb_image-arm64.o -output ../lib/darwin/stb_image.a - # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-x86_64.o -mmacosx-version-min=10.12 - # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_write.c -o stb_image_write-arm64.o -mmacosx-version-min=10.12 - # lipo -create stb_image_write-x86_64.o stb_image_write-arm64.o -output ../lib/darwin/stb_image_write.a - # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-x86_64.o -mmacosx-version-min=10.12 - # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_image_resize.c -o stb_image_resize-arm64.o -mmacosx-version-min=10.12 - # lipo -create stb_image_resize-x86_64.o stb_image_resize-arm64.o -output ../lib/darwin/stb_image_resize.a - # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-x86_64.o -mmacosx-version-min=10.12 - # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_rect_pack.c -o stb_rect_pack-arm64.o -mmacosx-version-min=10.12 - # lipo -create stb_rect_pack-x86_64.o stb_rect_pack-arm64.o -output ../lib/darwin/stb_rect_pack.a - # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-x86_64.o -mmacosx-version-min=10.12 - # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_vorbis.c -o stb_vorbis-arm64.o -mmacosx-version-min=10.12 - # lipo -create stb_vorbis-x86_64.o stb_vorbis-arm64.o -output ../lib/darwin/stb_vorbis.a - # $(CC) -arch x86_64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-x86_64.o -mmacosx-version-min=10.12 - # $(CC) -arch arm64 -c -O2 -Os -fPIC stb_sprintf.c -o stb_sprintf-arm64.o -mmacosx-version-min=10.12 - # lipo -create stb_sprintf-x86_64.o stb_sprintf-arm64.o -output ../lib/darwin/stb_sprintf.a rm *.o From 2a805d2de0147e24471e25a0cab0f478de71aa98 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 13:00:34 -0500 Subject: [PATCH 48/62] Add notice to readme reguarding user managing thirdparty dependencies for their codebase. --- Readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 5a841ee..5781690 100644 --- a/Readme.md +++ b/Readme.md @@ -60,7 +60,9 @@ Currently, the scripts provided & the library itself were developed & tested on The library depends on harfbuzz & stb_truetype to build. Note: harfbuzz could technically be removed if the user removes their definitions, however this hasn't been made into a conditional compilation option yet. -# Gallery +**NOTICE: All library dependencies are in the "thirdparty" collection of this repository. For their codebase, the user soley has to modify that collection specification for where they would like to put these external "vendor" dependencies not provided by odin.** + +## Gallery ![sokol_demo_2025-01-11_01-32-24](https://github.com/user-attachments/assets/4aea2b23-4362-47e6-b6d1-286e84891702) From 8f9cff4119d1130267778060c8cc8273bd77df89 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 13:02:53 -0500 Subject: [PATCH 49/62] clarification --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 5781690..d9a5735 100644 --- a/Readme.md +++ b/Readme.md @@ -60,7 +60,7 @@ Currently, the scripts provided & the library itself were developed & tested on The library depends on harfbuzz & stb_truetype to build. Note: harfbuzz could technically be removed if the user removes their definitions, however this hasn't been made into a conditional compilation option yet. -**NOTICE: All library dependencies are in the "thirdparty" collection of this repository. For their codebase, the user soley has to modify that collection specification for where they would like to put these external "vendor" dependencies not provided by odin.** +**NOTICE: All library dependency packages are in the "thirdparty" collection of this repository. For their codebase, the user soley has to modify that collection specification for where they would like to put these external vendor package not provided by the offical Odin vendor collections.** ## Gallery From 4abb591e1c7f2ec8520e9a115866dcb961a69d67 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 20:13:01 -0500 Subject: [PATCH 50/62] progress on compiling within wsl linux --- scripts/build_sokol_demo.sh | 2 +- thirdparty/stb/lib/stb_truetype.a | Bin 0 -> 151506 bytes thirdparty/stb/src/Makefile | 10 +++++----- thirdparty/stb/src/gb/gb.h | 9 ++++++--- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 thirdparty/stb/lib/stb_truetype.a diff --git a/scripts/build_sokol_demo.sh b/scripts/build_sokol_demo.sh index 736f57e..e76888d 100644 --- a/scripts/build_sokol_demo.sh +++ b/scripts/build_sokol_demo.sh @@ -84,7 +84,7 @@ popd > /dev/null path_stb_truetype="$path_thirdparty/stb/src" pushd "$path_stb_truetype" > /dev/null - eval make -C "$path_stb_truetype" + make popd > /dev/null source "$(dirname "$0")/helpers/odin_compiler_defs.sh" diff --git a/thirdparty/stb/lib/stb_truetype.a b/thirdparty/stb/lib/stb_truetype.a new file mode 100644 index 0000000000000000000000000000000000000000..4c940365aaf2be8c27c93e284c02dff4d8e58315 GIT binary patch literal 151506 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 eK(oJ3HD)H=e`fd2RDR@Hu1B`oQo%pE{Qm Date: Mon, 13 Jan 2025 20:42:08 -0500 Subject: [PATCH 51/62] WSL LINUX WORKS --- backend/sokol/draw_text.odin | 34 ++++++++++++-------------- backend/sokol/draw_text.shdc.glsl | 6 ++++- scripts/helpers/odin_compiler_defs.sh | 3 ++- thirdparty/harfbuzz | 1 + thirdparty/stb/lib/stb_truetype.lib | Bin 380552 -> 380552 bytes 5 files changed, 24 insertions(+), 20 deletions(-) create mode 160000 thirdparty/harfbuzz diff --git a/backend/sokol/draw_text.odin b/backend/sokol/draw_text.odin index badda9e..6bd09e4 100644 --- a/backend/sokol/draw_text.odin +++ b/backend/sokol/draw_text.odin @@ -52,13 +52,13 @@ Ve_Draw_Text_Fs_Params :: struct #align(16) { void main() { - uv = vec2(v_texture.x, 1.0 - v_texture.y); + uv = vec2(v_texture.x, v_texture.y); gl_Position = vec4((v_position * 2.0) - vec2(1.0), 0.0, 1.0); } */ @(private="file") -ve_draw_text_vs_source_glsl410 := [261]u8 { +ve_draw_text_vs_source_glsl410 := [255]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, @@ -69,13 +69,12 @@ ve_draw_text_vs_source_glsl410 := [261]u8 { 0x76,0x65,0x63,0x32,0x20,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b, 0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a, 0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x76,0x5f, - 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x31,0x2e,0x30,0x20,0x2d, - 0x20,0x76,0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20, - 0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d, - 0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, - 0x6e,0x20,0x2a,0x20,0x32,0x2e,0x30,0x29,0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28, - 0x31,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b, - 0x0a,0x7d,0x0a,0x0a,0x00, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x76,0x5f,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28, + 0x28,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2a,0x20,0x32,0x2e, + 0x30,0x29,0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x29,0x2c,0x20, + 0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } /* #version 410 @@ -155,13 +154,13 @@ ve_draw_text_fs_source_glsl410 := [812]u8 { void main() { - uv = vec2(v_texture.x, 1.0 - v_texture.y); + uv = vec2(v_texture.x, v_texture.y); gl_Position = vec4((v_position * 2.0) - vec2(1.0), 0.0, 1.0); } */ @(private="file") -ve_draw_text_vs_source_glsl300es := [243]u8 { +ve_draw_text_vs_source_glsl300es := [237]u8 { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61, 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, @@ -171,13 +170,12 @@ ve_draw_text_vs_source_glsl300es := [243]u8 { 0x63,0x32,0x20,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x0a, 0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20, 0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x76,0x5f,0x74,0x65, - 0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x31,0x2e,0x30,0x20,0x2d,0x20,0x76, - 0x5f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20, - 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76, - 0x65,0x63,0x34,0x28,0x28,0x76,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20, - 0x2a,0x20,0x32,0x2e,0x30,0x29,0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e, - 0x30,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d, - 0x0a,0x0a,0x00, + 0x78,0x74,0x75,0x72,0x65,0x2e,0x78,0x2c,0x20,0x76,0x5f,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x2e,0x79,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x76, + 0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2a,0x20,0x32,0x2e,0x30,0x29, + 0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, } /* #version 300 es diff --git a/backend/sokol/draw_text.shdc.glsl b/backend/sokol/draw_text.shdc.glsl index 10233a0..a3278c7 100644 --- a/backend/sokol/draw_text.shdc.glsl +++ b/backend/sokol/draw_text.shdc.glsl @@ -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 diff --git a/scripts/helpers/odin_compiler_defs.sh b/scripts/helpers/odin_compiler_defs.sh index 721aba0..9f1045f 100644 --- a/scripts/helpers/odin_compiler_defs.sh +++ b/scripts/helpers/odin_compiler_defs.sh @@ -62,4 +62,5 @@ flag_vet_using_stmt='-vet-using-stmt' # flag_msvc_link_debug='/DEBUG' # Assuming to be in default path, change if otherwise -odin_compiler='odin' +# odin_compiler='odin' +odin_compiler='/mnt/c/projects/SectrPrototype/toolchain/Odin/odin' diff --git a/thirdparty/harfbuzz b/thirdparty/harfbuzz new file mode 160000 index 0000000..834fd86 --- /dev/null +++ b/thirdparty/harfbuzz @@ -0,0 +1 @@ +Subproject commit 834fd866785786477b2c0bd5ce5aa1e2848efa12 diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index c21773696c3e9e759cbe17d06a4886e8a98cb021..6d2c66604be0b605d0dd99b7643c83fe802dc463 100644 GIT binary patch delta 1673 zcmYjPYfzL`7~c1s11yNRa$O2yxwH_PE{t5nl@?=iK|-{~EL7A%1xE=|E|UhUqOKIP zTqa*Hp+x4SgNEU9VllmkBYFbPY&i9g!vVAVgEm+Ii5JrwbRA~?Zpa$N);EX@W5 zE;4`)He-(ghRN41SknQ1_*V(MLCXFiai!2LW;U820OQM`fT*2t*$fiI|15)I3O#(& zPI!mfsh$3-&6_{i4cmY`bQOhxe9vAu3DARrP|5Qv;3sF;f|{$~&sWrd(VhI_hA~&c zk4LvcF(F2`u_}1q&#)ZG<1sb~^E*HVYW{r(WDzi;?*+IlDrJ$xpL<>ey#pK?v&1uh zz^72d1^X!s4AxlLb(r%nt|$U2+M8fL?88MScnu7Eg9)TS%G^*_1m0M61HvH(Lv9NF zzPtf;kSk(;`-JH^@@!>HeI6HRxq>Wpw^nTia*u!>I~mykg_vMNQb3K>He?>8BZ_!pcO>Z{Db8XG{?e_Z;s};n znJ9Kd*C-PH{})d{&X>+0OYF&h^{jp>#kd@Y70;}F31^?!^3wa(lo6gOU zJ_)Nar1X93`e)M*}F@CDsj>a>Ku(^(E6uo${ zN%lU*pU%CyEW^)u#9T7#+k>wCb?tQp>lU}3-;4@Q719-xdlSQU4*7B|r;kDE+!e6? z=G>CmsHvxe^8THwo{^gJrt~+%FSHyeYD`SV`i2wT8cb@SovP1=^~W=7wZ|K0dIf)0 zMTR{Nq%S}I#r1sss!N7w?}_!914olT_S=--aHMtfv67$#nA9jV{K2&R%wd`tf%-<; zEOou~;Vnn^_Hs;WqIuGDYtEKU9JqHIJDaFhDxLk-UeDxp3>7D7rQ(Ar@qb-AvvQy% zsPf8!&)S;5Q&taom|Rz9Y`8K~74u>>-Z)9+8sDi;wSAuZTc7qDu3z42c=*=z`*(D0 zTQU>MeG`2bw(d91?+Y&7Z9G(KjVIF#@VQ# z)Lz#illucMJ58IE?7`x7r?B%59b_p!?Q(wZF4Y5jzB@dGFErC?U~dKwH>0$drm@8B z(iU9bOBF0)=w&_b649Y=PF7=+h}s(`oA`}hssR>fI3JGz78=V|#3c6M8jFyg{KjrC zEV0lU)|&OHn#Vq*T~g*U-!GIap3#krWpDe&h?PU4<&S-~*dij?U_>%E579Qt4(2>L zjK@cW;GCmd9k6ReFpv6uZ!#)Ih1iH@jVH#6C~7q1Fs6+P-KmnptGxU6 zO>7&-6l_V!gj;A4W0Rs?lsL^%K5>|iW`F5?0ajncobLwyK7>~yQ delta 1673 zcmYjPdsK~C6u&`^e&@9Q#a{yFFO+xvI+w|{$onS1Or_t@us zB=ryQA2+t#xp$%wyrz`<{g7!yPe`WB@meD1cNFXoZU=5KsQH0CFfa^R(|E zl$r)w{nwil?|>}sY7T<;L?9(*dE3$##4=Ghs$ZU^#^Ksq)tvJuiyBP9zU5RXVn6fDO+DOoQL ztY1o8_zn}Y*^=Zsh*NRrA+|(g29{jdRkM|GPaeE*Bq;>27Z><3*Nh^sfn2s3=!-A* zB^f|EYzAKA;4!3*{B0z>cMO>cbg%ikpGol{YzdMG95i8yuLehaOG5!*`H1>K6I_fKHEsdU<;x;=8Z|P{Htjd1&&3iSgBGI>s&IyP;R!}y|%Q8W3A~~C8 zK{Qnjot@8*r^Ic1WFFT{5;ZTgH}^#AJ1$$i$eJ;yW%ZcKA?gC(XJMsSS4CCIyOmCU z*Eb$?IJ=>F(Ha-s(QW(sQ@kgyD?U`+J2t=nn(mBm8+NIdc?;J)}(MMNi zCnqko-@kHkWp+!-E@$fnf301gyJSR2gKNF(m6Dh?UD<)lDvUfW7`A%k=Qu8Lm@Q_W zrggF(%YJk8@H!KVku@|ymie^mfls)lB{tPiwXExn_5BOmQ^%t63@uT9I=Z6c*X>^{ zulFpuJ}X&YcgX$78+(K8s-&E!)>iVik=S~MI;gDOPZE7{*zgzKxh>bybDwAq=oPE9 zQzG;gjXvf3E~e_DpLyr+v|H)q_M)OyebD6Ej;M*!8$;s*oxi=BHGN7C2Gmkt2Tfno z_vK{|Z+1V7ZH!ub^M_bNRHMh%sw~vj(yx>jo3w}ZhL|fW+@|f!k_}gH>7MZD#F}4% z!ayhUHc_AB^0TzYoxSLAoQF+qw1@q9ZITDS^nhjp8*{g|kI!kRM}UnDEZv8)$8-g2 z?k&BFagV8zsdpc)#vLO1sjJKpYeZBZ)Y8sdA5#^usGocD(5;h(u-oHwlW}#YkdAm; zREK$;^f;Teq4o|Bc|vc=7`$G%k}F@)B*qR(Lo=}Cji4<5a;^gFMf9Nh&^q4!hUzKv zN>8lE%040Z`$%~M-s%&I`(HX%fXaR$Hnnqn5r&A!eEIenxT0ThQ(f6^ty{{pl+E^< znatZM`-HGa+5Q^tX~LEQQ{C9jd7dfjBFx8Rh%Ih5XDe7TPc&ecIm>3V_A8E|b`T3; zrKj5FqJ9ulGRv744`Zho^Xxbfi+&aY>I$Ad0+TFQ51Uq=WQz%wg06G$rYzhoqOYY8 g&g(3h17Y5Vt3&Y*YvChsyZd~GTF$syE>+e33oYV=wg3PC From 19c3525096198f61590cb3309db059fab36edea8 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 20:50:10 -0500 Subject: [PATCH 52/62] inital draft on linux workflow --- .github/workflows/linux_build.yaml | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/linux_build.yaml diff --git a/.github/workflows/linux_build.yaml b/.github/workflows/linux_build.yaml new file mode 100644 index 0000000..13d14d4 --- /dev/null +++ b/.github/workflows/linux_build.yaml @@ -0,0 +1,68 @@ +name: Linux Build + +on: + push: + branches: + - '**' + - '**' + pull_request: + branches: + - '**' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Odin + uses: laytan/setup-odin@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up environment + run: | + if ! command -v git &> /dev/null; then + echo "Git not found. Installing Git..." + sudo apt-get update + sudo apt-get install -y git + else + echo "Git is already installed." + fi + git --version + + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + bash \ + libfreetype6-dev \ + libharfbuzz-dev + + make -C "/usr/local/share/odin/vendor/stb/src" + + - name: Run build script + run: | + echo "Setting execute permissions on specific .sh files" + chmod +x ./scripts/build_sokol_demo.sh + chmod +x ./scripts/build_sokol_library.sh + chmod +x ./scripts/compile_sokol_shaders.sh + chmod +x ./scripts/clean.sh + chmod +x ./scripts/helpers/misc.sh + chmod +x ./scripts/helpers/odin_compiler_defs.sh + + echo "Running build_sokol_demo.sh" + ./scripts/build_sokol_demo.sh + shell: bash + + - name: List build artifacts + run: ls -R ./build || echo "build directory not found" + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: linux-build + path: ./build/ + if-no-files-found: warn From d4748ab30652329f51ec3ce71a33bdb4ef17c86d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 20:51:23 -0500 Subject: [PATCH 53/62] might not need to deal with vendor stb errors on gh actions... --- .github/workflows/linux_build.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/linux_build.yaml b/.github/workflows/linux_build.yaml index 13d14d4..9535b39 100644 --- a/.github/workflows/linux_build.yaml +++ b/.github/workflows/linux_build.yaml @@ -41,8 +41,6 @@ jobs: libfreetype6-dev \ libharfbuzz-dev - make -C "/usr/local/share/odin/vendor/stb/src" - - name: Run build script run: | echo "Setting execute permissions on specific .sh files" From 19fef224519e470c243b20ff4665114d9f7be2fd Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 20:56:47 -0500 Subject: [PATCH 54/62] added gl --- .github/workflows/linux_build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux_build.yaml b/.github/workflows/linux_build.yaml index 9535b39..7dc6b7c 100644 --- a/.github/workflows/linux_build.yaml +++ b/.github/workflows/linux_build.yaml @@ -39,7 +39,9 @@ jobs: build-essential \ bash \ libfreetype6-dev \ - libharfbuzz-dev + libharfbuzz-dev \ + libgl1-mesa-dev \ + libglew-dev - name: Run build script run: | From 14b9eda80960f8d1331a16e21781c1888bb09b8f Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:04:52 -0500 Subject: [PATCH 55/62] more DEPS for gh actions --- .github/workflows/linux_build.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux_build.yaml b/.github/workflows/linux_build.yaml index 7dc6b7c..d6370ca 100644 --- a/.github/workflows/linux_build.yaml +++ b/.github/workflows/linux_build.yaml @@ -41,7 +41,13 @@ jobs: libfreetype6-dev \ libharfbuzz-dev \ libgl1-mesa-dev \ - libglew-dev + libx11-dev \ + libxcursor-dev \ + libxrandr-dev \ + libxinerama-dev \ + libxi-dev \ + libglew-dev \ + libxtst-dev - name: Run build script run: | From ca1cd05f2b993a506418e64f19a5ec42d373e1cd Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:08:39 -0500 Subject: [PATCH 56/62] should bit it for sokol building on linux... --- .github/workflows/linux_build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux_build.yaml b/.github/workflows/linux_build.yaml index d6370ca..8816292 100644 --- a/.github/workflows/linux_build.yaml +++ b/.github/workflows/linux_build.yaml @@ -47,7 +47,8 @@ jobs: libxinerama-dev \ libxi-dev \ libglew-dev \ - libxtst-dev + libxtst-dev \ + libasound-dev - name: Run build script run: | From 867cbee924fe1db54c0451e4f1ba2052eb979310 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:24:00 -0500 Subject: [PATCH 57/62] delete harfbuzz subgit (oops) --- thirdparty/harfbuzz | 1 - 1 file changed, 1 deletion(-) delete mode 160000 thirdparty/harfbuzz diff --git a/thirdparty/harfbuzz b/thirdparty/harfbuzz deleted file mode 160000 index 834fd86..0000000 --- a/thirdparty/harfbuzz +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 834fd866785786477b2c0bd5ce5aa1e2848efa12 From d7ae6d1d4161dcde61dfb78380c5fa5fc1a7c1b6 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:26:50 -0500 Subject: [PATCH 58/62] oops (bad odin path) --- scripts/helpers/odin_compiler_defs.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/helpers/odin_compiler_defs.sh b/scripts/helpers/odin_compiler_defs.sh index 9f1045f..721aba0 100644 --- a/scripts/helpers/odin_compiler_defs.sh +++ b/scripts/helpers/odin_compiler_defs.sh @@ -62,5 +62,4 @@ flag_vet_using_stmt='-vet-using-stmt' # flag_msvc_link_debug='/DEBUG' # Assuming to be in default path, change if otherwise -# odin_compiler='odin' -odin_compiler='/mnt/c/projects/SectrPrototype/toolchain/Odin/odin' +odin_compiler='odin' From 3e861760db30bd1b53b5c2b97a256d27c91a3089 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:29:56 -0500 Subject: [PATCH 59/62] We need to do vendor stb after all (linux gh action) --- .github/workflows/linux_build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/linux_build.yaml b/.github/workflows/linux_build.yaml index 8816292..7727bc1 100644 --- a/.github/workflows/linux_build.yaml +++ b/.github/workflows/linux_build.yaml @@ -50,6 +50,8 @@ jobs: libxtst-dev \ libasound-dev + make -C "/home/runner/odin/vendor/stb/src" + - name: Run build script run: | echo "Setting execute permissions on specific .sh files" From cdbef8d65693b2b6f6d76e992c0d7cbaa6a70543 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:35:23 -0500 Subject: [PATCH 60/62] change default build args for buld_sokol_demo.ps1 --- scripts/build_sokol_demo.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/build_sokol_demo.ps1 b/scripts/build_sokol_demo.ps1 index c600cf0..2b3d504 100644 --- a/scripts/build_sokol_demo.ps1 +++ b/scripts/build_sokol_demo.ps1 @@ -103,11 +103,11 @@ function build-SokolBackendDemo # $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_none # $build_args += $flag_optimize_minimal - # $build_args += $flag_optimize_speed + $build_args += $flag_optimize_speed # $build_args += $falg_optimize_aggressive - $build_args += $flag_debug + # $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows' # $build_args += ($flag_extra_linker_flags + $linker_args ) From 21cc8740edb509e538ba88c6613812b376a3c07b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 21:43:53 -0500 Subject: [PATCH 61/62] fix for mac build (hopefully) --- thirdparty/stb/src/gb/gb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/stb/src/gb/gb.h b/thirdparty/stb/src/gb/gb.h index 20118b0..9be470c 100644 --- a/thirdparty/stb/src/gb/gb.h +++ b/thirdparty/stb/src/gb/gb.h @@ -230,7 +230,7 @@ extern "C" { #define GB_CACHE_LINE_SIZE 128 #endif -#elif defined(__arm__) +#elif defined(__arm__) || defined(__aarch64__) #ifndef GB_CPU_ARM #define GB_CPU_ARM 1 #endif From a6a9f622f4d961aec830d180cb3a711584df4760 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 13 Jan 2025 22:03:12 -0500 Subject: [PATCH 62/62] using zpl for stb_truetype allocation extension instead of gb.h (trying to gamble a build with mac) --- thirdparty/stb/lib/stb_truetype.lib | Bin 380552 -> 944930 bytes thirdparty/stb/src/gb/gb.h | 10827 ----------- thirdparty/stb/src/stb_truetype.h | 14 +- thirdparty/stb/src/zpl/zpl.h | 19055 ++++++++++++++++++++ thirdparty/stb/truetype/stb_truetype.odin | 10 +- vefontcache/parser.odin | 6 +- 6 files changed, 19070 insertions(+), 10842 deletions(-) delete mode 100644 thirdparty/stb/src/gb/gb.h create mode 100644 thirdparty/stb/src/zpl/zpl.h diff --git a/thirdparty/stb/lib/stb_truetype.lib b/thirdparty/stb/lib/stb_truetype.lib index 6d2c66604be0b605d0dd99b7643c83fe802dc463..fb5a44962aa8d0bf3cbaf55e9516acc852044e9d 100644 GIT binary patch literal 944930 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 z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C z0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C 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�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%0AS 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¬N$MT+Dl7@S$1(-F7ZaU#XUhe*nJ;H;JX{<2oZxrqv z5Pagn#OHH;u2y~EeapL(apcjo5a0Bxb{qcLYgXl5&sW|Hryj3Tc-CzAw4WOThWbjh za)0}|rUXVjXu0O2>N59vM>OZJ8q~V+vTrLf$);lZCx51;$eb>M0v)gysj+vk4 z=sq~-x3>Dcp1Y1*n`2rnC%~dZmeqhfp96~~44V09VaU#gYjb~pdbP3Saq)$Mrdux_ zolqK3rf1Vz=iZSaQyfohYjSVyhH1^3{vK?2PJ7vx;cHel?0+$5(c6Y!zeNmcG2Yh8 z_{_n(fk!&O^_(`?twE1(6B|TG2DMHsX{WN7-T!L0p18nxkzoq^J?VIw&#tz2Pp&>t zW);8Z^AnRQJB)io-WqUeYUXQOH*y~zH6&nCvC0&^r+|jm5nbJ-rrwT zzl@z{`-e3n_8+dd;WhhR_x{qMp*?%r7jgsUSj5X-d+x55?>R%F&dwj!>N&q&^I9z* z&l#xYE6X|6ZtjkszD6M>+g3LU|7I+GYZEhK+SnH+y|RMz>QB+vUHnL+VbckD^*@=v z8}g`Vt9SB+hNlaIJ8dl1I=3*Q^_I=^u3KIHE%`iU*?c!W<7&aJo*%4rbzR(zjmHL? zkL^9vIkVRBJBLQ@9y2g@!MDNct1fLA_f4{({i6EChH0H2X}3An(4uyHtmmZk9rsU! zc&c33yCv3itLwppMy-YppEKIxqXfYPCVbcv~S<<5s^No z`+gfPFy~k~HCX)PsLv7CA?&x!@fLMPzFAd^{Vu!_HPf-N#{7Z9%The*7ykJ3{F{jM zx3iq0RR4)==Wa8rDBRMx#`MJPsxgC9SI^an>3Zr@+0u#I2b{ifDq-HqVp}`gpkA)O zjt;h1wlYm~ZSK&J-8Ea<9pBph#)W3qN2oBR?~RLw2=-AOa;x5(e$ z<@L44nMHel^ZcvzA6{$4-aoIW<)uTCS2P=A=o8T6H zwkh1XKRkc^&#HA>{d&RlPkX-Z$$fSE-t9-#o-3qB22D7=s%`IkrS?PmG+5j-cAHDl z?mD`DZBm-v=Hk1ZJ)Wq(xmxR)rt5mEW^O&0e8I5I47Q&;{+wm&M;A&1zpD58(IEQj zmJ3efE<9l?*N*s3w`-NKX}n!({F<1$HY3BD_PCgFJ;3$$ptKPM$*nKC zI1N7?xzA##OY(uzg_o*J7KBXvwN&fc(#%=2>uDMubuyFW-5=xBGI~$m_ZUannt3PU zjQSi5_82oL`7FAxEq_#vi#cKcMNQQ?9a3yS5K~@N7|L&r?fJ9$4G&-iWA0 zO@?leRGWA@Lz zhn*(;y7m0p-d43%44mGutX{UWRbQ*V#`WtzSvO!~#=V`N zCPWPJ@{FI~_CZ|pIW2XMHW{gYDCV8dsYxEHBZ5+^zev078XUGlfA#CzoUB2~>QSdl zuGTX(+#E9|x9{?ZDPb4y9b4bW`CGfk`Gdw4o6d24ka)Ic_MZ7pYu7gK=22zW0GA~) z%~TiTs`1^w>}sZ6ZU3RitJa=eK7an4U-_HHzG|Ht`tp~a!_vf#UxTN%X_C4|?bPy1 z3+x`cuG$dxt?jv+b8mdz+Ap%^-~;|%Wyv0I9a2A-HC&tV=~UnG346D0it6)glc_;f z&Fm%{MrT!ZS{$dlF3`}rOFQp1?Z&@&AG-hXourJ9(g33&87^k&gO;8c1x)&FZFo;*4GT7nyd!)>76kuXE|;tKA3ZX*+8zXzcOIw@sZp^Ag^C>@_&}{Uk|8 z{~cpC4B8uByu*0I1)aBJ$9z4k*Dt2SDeKq8T&K7;(q#>eMm*MaJYILskEyIaVD-zV zI*T`%c3h^Ty}W0@TfZ9b*!9Kqy1RWFaI{O61wN`a^S0Fr(VXSgXV$|plZK5M<=fcE zeE$vK-3@C0O#gP(TW|BQanpz0T%5nr+P_oXGfl>i`To5~d*R8gUo3KpTZO$X=@RU8 za#SwUJ3De??1VEdyWd$67{1H+!P_xQcXc@0{EVZ{cKr}TvzJEf_`I9DeQZ8`c*gA6 zUpvyY--OOOoxazIKl1R+p(jX^r*u=)Bk^bOaZg*y?oj4e{ zxRYf4r5wBWt@h?gEw)v4$&7k3czE!IK_;;yjDPLg{%VkM!QC5G)!h58``mo0n`46J zP1^&>tM9&4AMIm)B8x=(#wKdv8D)USIV)mP4Lebc61>Eax`b8A3n_vfP)W|r0TD=}Z$B4~?+Uz^yT zruFYVIi9?&i*C<;TQ_|_&{=Z(g6obnmU?p_)`;pNd^1D!+jv)>fPCe$!Ibb8{p_OBf$%{EEd zlymr3w#-9v?EaA%!S;jgg3o5<=r%}lWiL0h4pMz zRL!Yc?AIF!i>9g_s9Tf}7H^Z)Grjcl)pP&MdJvyIbiSu$(Z0j2U)tndS)4l2X#A81 zuIDo!-3oZ!_|VK3X96cxpK-&;Mt4Y?SDy-ZX&vpm^x0@nw~Kxqn-uIVvfi+BLwNPq zUAr$kHd1Zn`J7kdJkDHdJTX2gC~kuHW{(0Pdxdf?oUhr?FwIJp1Nfj51-bsautYT1ydV^1$?6P{jo#{v5jGcHF|GqRVK ztnVLlW}koFjgsZs!&>=`$(eWdOru4Ne|4AU-MV+48@e(xI4kVq;pDlmW^7ioPPjOC zckEY_<3HW^=5(28lar@rc_duso_Tzzt%v>2n3~;ce=znQGum^3jf%-S}PCucsm=<{d6t>2q59p^tI> ziBjM5?rSV#dl~=QKKWQ=_W6J_mfM>2SzM>jl?QE7GA}ooo_nL}_bF$eN1r)hxaN63 z-@2LJe_nFmrI{Cr>-8n zEi~?{%F44h7cA=d*kDChpX0gxA8fxRZC7%pkIG)dhPSG>+?(%nDTp572Yy@X2z;4E2C5X|cDwPYhq@XWu6H z>)Dr?CpsK%{Lx{q=H^E6@4d5L%)h;1-}@}v@116j8`i+*OXPy!AvN278QI&B-REt4 z_?}r?Ce7c`Fl&TI`0a_)dzpN>vFNH!Ztk}iHVt~p4i8W5etQM<E+tXb2Nc$nvwb*?(u1Cq9#r1m4VfU4z zo>ymoi+=2xT(|A)(HeC^Mttyjx+UYN_p~8hPY-NxBy;@ZeoHc@9XNY#bN!mtjTZ&f zS?6`~^k;`-8jo~8B>ps?X0mCRYTDrNdn2|?)0$OFPyeOUg`w+S9Q>tqZGSzN2SL?p zY_dMd*?w&4GOE`b`}gxd4o+#XFFtyJ7yHdwr=Kl4tn)Jd-1*B|MN4}5HQRH&#TEa9 zjbc;JW&5}OaZYmRYuXC?%U16f+BrAc?_2-*Yj5kTTc(U%@Kj|*l6so!`^A3hC;VTo zn&o?JN0a1RUALPy)VwfKb!E-lQ8vG=mUi&}F*tRcTG{fY3l4qByX-J5=|=0ihuaOj zk+x&u`PLsVzIc{Vxcz(IvFtZX5BqI#c39D_#7^JX?-7%Y#;<%R<=F2_kzTrothJ8) z&MbKnWBY#bNVnO2V?R6fV!s_wQ;gZC#~c%IA6BV@sN23JrABSNKV10Ha+!6twzqDbSi7g;s{LA*CZG4T@aUi! zlKo)A)1%#Iv9o|d_#&t~~IJz?Fzy}lvQ4YC^8l>XXsx5ZWS zZAKThB)yFe>%7ytRF&By{keHapA8Li*>7eK`|VpR{6Xq$rvc2~IgvG%|7bqr-0Z8} z8&=oUoj!C<@3-8mtdUomZXUC<+Q*MSZ{Emlxb(G=j>q^aX0;wa&u`mg(C?7#vQ0_1 zEDE^1mQwM)@6Z6CKcE7`<%~YCywOKmlw#VCnW4u2a8yvP8?YwByuz8)d z`}PkWxt!(SfC@S2}L%2E2{> z(KTYWeRPKfW|zOZsodOE$o$==rLX;zXon3fzdN3dy||~*^8+ETVh&4Z4D<8&cBAO~ z&9Fpf|Jvto)@Bu^YO(8;U4PFLd~chw1nC{-pY_kG7+l&H_~_Xj>tIWV+e<9I`R(Lf)~`3b_e1BGd1%brF^$`; z|7S(DhZ46Cv9qom=JBdg40LzC`_qRb-DT12TUER%lbHP< z19xn5S~b28yZ+A(Z`WY=({!tq#}mi4bG_>CRCkBi+b_s;f4O^{`@`pXtzCSJ*IAyPP$^f_t>u#_Iv015pi~%9~>E5 zxZcz(&vzLskE-JujM{K#^`p_Z{ibF;9kGYTRXLF)?<(U z{4|f`-9O&X*}QbRn%`6Zryko^-%DHd^=!l56T%j?jq}(O|4B{#d0OP%Dps!!dpORo zy+1W6qvNL*E&<<)YCh1_SnK-n?myCxcAXpoxcsZ_8_c|M<+E&5*6xn1JS@5qwZWys zo=H)@2h@GvMH{*KB#sI&8@7n$|LQt#>-=-)XTh<8bDz#`ahuvZp!2H72ev$B>1X~b z(_0_wyyNTr*>4)^&a&Kg^R1V`dG{cj73!ycJ@+!M9Y402!;?>CwQnY#ZPM^m(aX&& zJ{^0@4+*c&O<;ViKKa~aQ=6KJFH*xaJ+kw=myBZPRdT+KEV*30*eLaVe*dV{hmQ}e z&heb^xy!cvhh55Ae$Hb0&q`GmT>SCn(YWq|d;NTWj0-CsH{*HkjmeiC^-}M@zYx=S z#f09g)Q0?g{Cz}_f!VK@(;uA}Gq~QoPtSB!cNz8FXtn!hjj=yfhxMGX;c6q#Sd(C{ zB_mEqhZw%v6JM(y|g7@*?>#8h8Qns>`H9Jv+vWG}SLb64a zEp4)7O+>aVt+ukHqJ5*1l8P2&k0>dWN-0|S&5S#rPwx2k>7U=cGMzK?KF>U}J@d>t zhZB*%n^~9d%bfHC?X}Tk>fdJ?cRCgh2)*V^{`P+4I+N1#m74O>d+IjYv+43*+p*)# z>bLzP7j`XR<~{vYZ7`z!$cw>nGuNRIYA!Fk2glsQ)5;qgrrNow>k2Q8Y`387E<7c2 z9k-{M(f)nTM^xo}ma~6rrYinty~OKv$|WiLoj=w%Nm`7gD|@RfHa{ZWBe^T@M?`)P z`_E;8eDN>2{bvc~3-YNhdUNrRy~~Q`rxMBSFa4g5az9_ck?Ba%4x?9rf=9x4om%EO zTXz3{=Kmtu(3>h-`&<53zPhp2a*h^ZtE05?_LJ!&oY?;Bld6u*(^muIMd=Dv?q!j6%~ zQ0DT7*ZCrfo%fsG?~QPH!BTGJwrax;u~%|Wn=4<>`Q8wM$JdOw{?)=I)(7x89&h-Z z`cpHbhZ6o6pZ`byxh2J!sgK5zk6gX5GU{3z&KJtFoyxH>LU8cX+K)X^zkklGyXY$} zY}GP_>uo_r%jhS(P7PD?hXSorX?WfVCWZ+)59+#k!1+~}gFE=;(VaHIcdPj~%NiTbd35zg7?M7D*l`fVB#!_n~UaJWg~kiYc~Q&r>M zUs|ED;mN!_8m3$AggMNyU#F}2^WDpp>ux9{O-U_CF)|gYKO*YK&Ae`17E|o`@LT=Z z&nb0d3Vk|Ic@mF5lcZIGyOZ;UZ*jC+j5hkk<9UbIMLZr`BF>3Pa?EP*IXe3J^8saB zm9wnMm)*|&uJ1q2s<^JP9Qy^hf3V%%ko$O9m~{1xErIKjri4bN4kfY+e`%hzKgu^w$W@RXNXvhB6+%ySV$?ca`b#r}Fwd&YXUk!MpQ7*#4x^KJDR5o)_^Ed?IEC z=a{$_Tu#cKv7vWIj-ldoz4Y@JjXLsH-5C}Tno^x$^kDPiAE6#AJIf04@&Y^$+Q(Jd z&BT6(N$wRrn~#@Wt&4?EIq-YuU_Y~@L{pll)}q`cqQAMTTI|~5)87@?%*#1+d^r8> z{S!an_6|t$MnL)Y19i+BG?f^9daKTZd5?hu1-Bg3>@nY! zWZ$hGkb4f-1KXv&qTAKxbi3Zfe0IC@Yf*L+5xgIKCoESOc~z@;j)R_W@!LJi@jQ`N z_jwj&svTg!@exixB7qacpiQ|6|(jIvz*!ZddIIF zsY_=$=I6NdnQsXd-QFyDONTda{gu=4HwBlum?f9LIU5>Z9jV56eThG3Nq4JC-Dc11 zosXQ;RJJHJ{rtSZJ+*MiVC0p1;*%wE-hvBDgj?_MWp2J#{lvq2aesp<9w3^P8Hd%~v+oB;)?YJYGLT9&8+WwX;mP z&vE*l*?g>)*Mb*WnGCYZ;c?y-^nQ11uw`9CNZb2lk zF?H`NJTEct>btd@)tOrypX2*0iCI#;)H|OtJ!j7j+Md~W_S-9|?cCj4?ZRv4G4-t# zP+{_nChJNAuAYvxcg#DrqE-F;FJ3mCy=IAQ zAJ5p@Vu_`-Pr{tKu5WvY*JZp8#Plj2KHq&Y2=5Q@c);@okLU9ZCKZEOI?TFV9}{nQ z8;HFWKYz}Y>T0vsQcF+V(0_|o1IL_hDaT?wudzSad3^u21S$Qxy8Jkaww1KbgFLU? z<|>Wq%}8hsFc!b<>Sx5el1I=@rn^mX z)Gs}Iuwi@bh6}3fmLU~*eywWEt+i`+J&o7dC0Zx-`noxM6nj1x#qTqg!TZ&|oxweE zIV@S!D3f68&O`Z{E2p2g;b~g&WKWCrNQ#?RS;EGoL(ChFpD=TM-(PDBm4TV89u{p28S`5l-UqJU zoa^?}>P;B0!udP+9N*u;<>USCan@BDN%9X;B2;HSEz70eW6XctSz0P`Jlz-jL0=-w zMy$G4E%E7g)zjRY(bGEZP$~s{W)(g)LtlH<+^zZ*HV{L zY2r4ef_n>_3si$Sw@KGO^ye3Ap;=fRNENkV!`CO2>|Hx`^XgGtAAElS&u@G`wcz{h z#4wkDREo^1l5EfLrJB!HeC}zt@j1%xS)!})HRV;6eA3(Aom*!}`nLt@%+->dn>@8G zdezj64n5Nh6xsEd{0rKBLe6MOFY&n-`q_LWIaf4$=^4kj=j3^ISV`aMh3FS8^^-lju zo{~U08mRWMILmlmFVo`r3UTt8No^J?wByNshcYt=ruzr9wweUyivwK7ZVa#n50?I#P$`ExJ%R-L`>@A{b2 z=eE~if^Fxwkdb$i;C0vW@yDI8Za?c7_nWGs;mxPtb14h>)_WfM(r)$OO!2GZPI`}y ze{gzp{#m;OVqUKMuD+roeXFI{jhH)AHY-xNQy&}~ z3kz*+V+kDlK+X1@o_>NeT##40FKPD31?Q!fJeNM<`N=*brHiewY*(;aVbSXd2cw-e z?dLu^Sl!;+Ip=`&*1flyv&-&|e6eufv5R>R+m;nvpGK!zc|A7UitYEUJh8Nv4~4F? zw)t6Sw%S~koh35&F<A;HQOL`u;es^z-x6d$GTT=OUPbl+RGVL{tv+ejzib|vZ8 zepS~9p4;oA!hUS*V&I|1erLWl#wF{1T~O8MV88B#&+-2uhqO9kOT7=_|D$>WZ;!0X zuRHeT<=v{cJ7sO%Ux*eo?B0*%@p6VHXPi#?5oUa>?Ve}1FFwcr&;6ZToPEvvDgGa? z3ma}{7~XpS^ML3a<-#5t>m?WXYkUOvC~gvevTyZArW>dJf6K@A0^3jAKl|T`MO_kI z{)zD%^G-}18O2WyEyU;e{wE&K(;H*g+b=Z`Gdo>it>II5ma@y0)6l@^2j=m90M~D! z_6^o|%vB}$z6j>&J?Bfsum53%i$ zy8brO`&hb#k43}nXPNqI`ep`dZxVc(ap=;jE}nCu{Jx>@=i5x#yoxPme(8d}Std`e zCGh2^;dK!E9oSBnkFIO`Tw2(O@3YQb8W!N=_ zN7TWAYDqi~T-DO5zYi|vmbCwR*m7n5(T%dpqjI(=etNug`=J{$`*Nb!RmQNbd;0|6 z&lEW2<-sm>c^AH)`Q}BFZFbn)MekP_m2VMpI;!5VATO~ax&Kk{wdM7RL8n_KeJZ*K zWR&OL*`KXcA!d&6XNFAcUR1|vXpZk^>c8R4yKV4#K(yL?f6o{DpSYj#{uK8|&TWNv zLubCH;rios2%qD13ZJJN$Z3RZ8PLGzv&tWMjcKbtI#ih0TvYIK7v6t9vMOwllASlA zVD_e2-~3KDzF$+PKIi19+w>!>ztUOcR4QKTS2v~(3r(wr&n~t|VUF<>vDg$j z!P|DVV+U8fxRDd3$zj{Sw(>y*9-mg#wLZ$%oY&%cfS+?Qp4;+>TNSTzc8lGxasA-C zE!Mp(MOXGtQLehM@xYkq&59&_&NIFHr`zm_+j_iaztF{Makp58}7SZ z?Mm9ByZdcWA;hHVslydhO{>tGK^d?#}o!_IOeA z&JBxSc^NW!@7*_T*9*DxX+B5zUr|S6ekU|vU0qSPZy>>LiM!A#(bmFM>k9AhwqA_; z&q@2~lb@f8d+>O|_pvp9HyvZm-lU|l_*iYQ?0gGs=Wu)B`r+rAx%UnDPBRs=E#Z;7 zXFLD!b9|1U>n$vix_{`T%TxTE-stoCfW*<2OP*3WLKA{)A2)>+&TRcA^y>T4&itKr zHK(o|i34xX<~HbxynP*PwT!Bn9UveZE$Qs4b1r*Wshr+EJJ z&Ix$vHKbl)A;aF-RoDEzaGPmmL72DOv)3Ch#+dXk`Yvx?@p-?5dDMmDOFk{KJLsG2 zX!rZBhbs2FR>!h7d~oknrs~DD*_&(mt@t{8FP+DP6Z^&I$J*8>HeKFK6};8{`&J!G zYT%cstH)khG7t9nsOVdI;P$}w3AeAI>LeP<^&!h#u)<4)=-e4RXVx%SUAtnpw!H5sh)n3;3rR8t9wa24t$y>yB3@}xll=JfkqSe z_Ep`|=F7DC_CY|h=6)+ust7?yME|FF07qW1P#O_HZ%qvrj*mwEEh%Q=@HW-ZXzq|LH; z%c!~d%V8D^wYrQ8%q^T3^iSRW{b?in?bxp_*k0lG3VfO1%6)H(E4GJKQCb_hmmQvN z74TxZcv>t!ey)J|6G<((QE&W%@i`u^_}pxTv{ps#f&=W^$~ine+LyINf3W*{`ul0T zF80qYqHVU?ml#`OtoPz-=zzcH=-X8aF24tQ#I34-W{Ki;i7D7SQ9(Y`lvY(~-DWn^ z4)3Rbo}@idpW~>+XCbui{2KWSUlnab1%~QttDIY_9^33xzZ4N%I6Zgad7d>v+ZS1K z=f)hak(;%Nop|T&)WyEo^z470Tj2dG_WwV7-5GmtEM+08 zAaq0XB{MUwS5ak+vSLu8_SvB^O$A3o#i>q@2TVc_3A%G@KiYVRP2>^Br-EdmjI+lM zo;lKdynJw3p5IV@l3N&!byoI%UP&r`E`gutE{^fl+mpcQi^pyBp!?zbo7nmrj;fyv zO8MbhnSZk1HQVHT>dcReTU*Pn*Hv2#WH-JHsn}*dutGTh*;K(+W8HVrefEi0rZRi- zYK^tIqPd8Js+WFFy|0-Sw7Kijq`Apj=fS)hp=Pc_~gx{ZIJIsyC7l?gf(V{8! z(t1TqbHJmrT@C|Z#VvmCS<;>T#4&J7ccbye6U$<{1+N-=iS6bNHZ{lN20t&$7`x@J zvh1=ue*fcQx%vJ#snSai7VYTQ;aAvvBINZIug$(fC8sH>7xyZ}cWcj6zia0C13#x( zRZz5WCu?_!i=S`lv@;sv_&E(rO-)l>{DwOVM59-o^!bp8?O4XK4arxz_RjszmUsKg zqo4Ucyn(ZlqFya#65>jePjINB*j-o917bc^1DvbGs>~ zO5}y&dXvppUJd#OPQmYsV7~{?Q*2k=Q+Hf0T7U8T3Z>QhK~>um^lluelM&SIF*d5S z+9bvzjqU7Xf6vX_0v?;OeDOi+Bx>}w|Ga-v@ngN(KFV#`MPg-d<#dkK;`=FhUBmAO zRSma=9sV)$1pC*rfl@zwAE?yYqzn+h0PJ^SzY5=v z!TvgRI=QhpI1Cgh|#>h^@ngvxxGfyzU4>xk5akTZ%xDUJcrixg%xt` z#phUGJm0Zj+1V>Oy*`US5ZkxQUIY7LOD`IfMN+Iv*o)6bd&uT2-x6f=c>by6O)pym zq;H?upq%FG_prj}hk0e$sVAzU)KAo{nVi&!6h1bMcFS^O-d*JYxaIDzD|-rf4+oA(B- zcWqVNE5w8?A8Rf@a?VSt5kEJ>)>_o;J-Y2hCH-@*_{J_sA82e}E$jtDRhPZQ9UH8kD+m$qRJTnSx zIpLvu{y0x^>?4=CiW)L*fe+&9mNi^kq?RqKBPdcIBXFWQOZ;iq^VuQW-+Dh8yJOc& zn-$Jd60X4UlC8Ayl_>WVrB?jB29FQy*W&q*Y<08Ia^{2Icz)sck?{D!{g2PR%el?O zmwCHj`-kl*KF8~b%^neZf8KC*k{LSU+sf@Hw`3p1N^YA4#9C*}S|^p*LVpGnT*RWzF)S z8P`AQt_n$MKdW3nmfo4Olbvam>7oVs?a!Is)%w2rB6?0+_-2yMgJPUYWrK0|7>+f<8$1;r(-BLh7JuFd%Eq>-ghgz4Eyc){ZM>=%c~~oTSNa; zslpp&{ZqG3If?II@$1AL=^O|!*fG2%FlC8Ng-Yt0lQy?Ezr^}7))%)A&hKjeUTT(T zy#?3b(?EE^{gQ&GsXj}FPX(Gz!+sjxC*yrs&QyU%7dMIH~qf9;jnBlR-?KNp1az0J`3+7pPyK= zG~;1aG#+mT-^xdiITbjE>(>Mp^CvFWP%o=V{#kx!FlGz8UmyR5Cswo-B6Id9sq4+n z33A}v*#6b#o*3VOU=8LHwiSsy+okzR4>%P)0S3NWDD&zn>+9*v8MXRHQ>IR>S55nJ zY9nrs8!Yu4J2JH|y6tijkUC?w&3s#h`-4n@8{Jh6KCdFPu1eaK&E38-t!v0r^68X5{QOo&$oAmNZ^7ONZz!M9 zIo!%GoD+MlbK`WL{nd+=U3~@9t6v#DGrN1(v*!A;jOyP?CL3a<)f&!+XKdLikR<(s z%Qe%-K)IIQ^<0f-?7cI1^A{Uxn`|t{{-9R-`GMZo zkpl0Z8^^0$+S2i8=a69ZN_6Ikf%J)E5Dk~}cE_)2z1;L-z<*HTTI=FPm*N%=s9T&ZzUIe2RnvjR zcCG1!w+HZhLYd)W0L!19UgA*mXBIe!mlY=!m_)>AO5L1!~ji zV=n)hG8Wf&+O0jIuypxR>!FuX-QQ-gBf^Y@uf85*eJ!%YuFx!?_1xIcgsdzq>FE=-($HNw)0WZL(H}! zUHjU$EIMhvL~>1OyBK%>i=RU!-^8YSj!LM8`6-1&$?$&Mywd5HRmlT$=Z51tjwW|Z zugQM8#L@cuK)_~es`{rrHUdrrSP z2d)X^scik^vr_Nv9i!&oO~Zu|8WAij2bp)(^8rhh+9|y(ajE`StNb6)t1_o^SUYh^gQi3rNVejb`IJ z_xxkzsC%r2@smy-Cf#4D>`8vTTQu)0niY7yNl}bfF`Hs>X29!A_=Tz^;d>gyqO(GB zM|^e}*hs6Xam>uzABRsb0-C4iPum@55a&^*k)9b_r_uh}OUcLWpmrvz_qrpea z7f?2QG|>_l=kWK7&Jz52_=tgmf7tBYczv7wP1f0YRhqrYy<0RP=4Rg3!-Yo=_?OB`PTg`tPIQmQ*#6DtBBPR* zGWu^2-Q{6h2QUr)GYUZ6_V^;Z}he)XN7KcW4FNzamSpX`vD&LpSBuHcNC?c^O);= zv^#x~+su4Xm&49tX596jjz1lRdAf{_j)Y}pK_Hy}FKU=OJ z^D2VnA~Mffy!hyBe#O%z&U5j(5!nys+FG-jA2^vc>i_taTdXxLIoVU*DDy}E{=^l6 z50CHgVU~G1c;Tb@hZ$R*1gY0I*GfLUxcs&48O@^Du(6*}+eBB3zqy!av!qDf?@HLb z<6mB~xt!+|v2V7>o@2jRl1FPt=unz=tkng@_uyRz9i@Ix{j}3VSJ(LEOL~tzI_n~$ z-!g10xbVZ2o?B-d#Y&wcbryc=PcKY8${pO6$^AxGS+pl(q^6ijqA|;1-fFcNy;HQ0jX|`~pkQKRqlnY&lM-Gc5p74dZ=Zf*U#DAgO=F<& zh3#Xz8V1|NK25#GT{pK)`mMf&LX>8ZL9#}y?_xtohn;s7-{cK@_FES%5xMOs@wh`H zU-u) zUp3suUUWlg&x^1c+iyEXL#lga46K=p-4tfIoBDnqeXZRo?X`Z3yduwz(M?9F$Bg>B zV}9~8)ryCXilvzhIT0~e(@YG=);`-s?$ z*n66q{p~dp=`0mI)Ae37j;?qewOaYKp9HtvaQSTGtZh#G^)8iPB~=7`W%%MP6J`ch zpOd5R~hS8Ik?ck}MqzAMBzeN#{O zol&NewrO1JS~Az^S?aZnD%{Okvc$yPv)VGV{fYc=gkv<{u)UwhlunKf*Y$NGzLdXS zocCqx#|6K3W^&GwqB=+{-*U?TP^y6G4}0B5?gyo~)-455(s_{4;Q6N9?^}9&b z?&o6ELreM7O8on)&2vL!R#BtBu=MH5-!{B)_a|&gu%nwr>hEDByy9?X5KaL z;Jb6}J>UHp#av6~?5wu|yjP4P=DBw7+!ofqL7`krxlYkxj(SZ^i7%h(ZsR>3*6AGG zQj*Tw{c}#7?Yg57c^J-bMa%iT9YQ89^14Wp;*^!1YW-U#yG zmxdrCztJCj(Pt<8Gp^tpKOlu_1P>2fl6VV(_xAX{a^;V3S?7sK}s0-fh=ru3S|R0%bt+38=S#! zaE*^B$Q%JF<#Yz7g0GLllqr8QWiSOO^WUqUf)qZ$j;H+D?;J3dIv)xGA^8q37}5Zq zFgDP8)Hz@#hkXrB!gOw`gdjyl5+$!3T&73cAdSX960Z3RFHv~^~b9mNd|ry zjYf^6GgbUFJNF_e~AF!ZXg(bBnN)`7S5~6YS)bKGUkD=R0uT>pLBz%ccd{%Y#Dswu>a*#0Vm`?TBPN{AeByFC zOEMye!TP+uAmdLm^AY1pm($B-Z%8r=5R(XWhJBjr;AcuQqKJX>82qDz8udhwj2L28 z(B%Z&T$o2PpvgcRnu8eDy@_l0a*~li4E&xQWOC}^Ul)g^{y7IF|ACQ0%mom`z-&YfrgLct*szsk zR1q@=FUr+WG&D|%sM*570f$}l%t0jcc20=^^{=OC1E6^j~G1W>s&&DNX7s$zCdAC za1_1pb_>ZEA_m*BPAW?}$*e$37|;j$WZSQ;I!`i2hzSKT)LD|r2^=!Ep)q32Kn!y> z2)Ka(#mJZ-#tC#N3pjfI_2V$ftV9eA6a<<5--Ur;W0V8TI_T3b5W`R*ls=tgpe8ix zcVG~p@5tYkahYURA!Zwh0Y>3cwE&Zwu`FoeX;fHm7&6)JZzdUN!Dv)Ba4D4YY?JsX z$-v&EQ6)eOgRG~=L6QN}8!(WBVd|-87fEIf$e~f$K@2rz>S69B8DJs-gTM59sPOPx zlCegN3KRlDd4}j4l7ZF$_=kXjbMWV;!K)-=gBVa@6r>%kf=LF>2pUxtG$Nc|?Zq#J zNX8a1MIeSMl^2^HB^f)!d;&2HdG_%SNX8!I(5PTwq49iBA!rgg>k;EMgF=BG)rUD{ zHKZH|#6V9H29p89N&0jH$e~f;ynyYa+p}mpDF>Jdz=(ht2DSN{yGX_f33?Lv$%NQV91 zuK!VuWC9Qa3=`6Mux+j@$?QfIgT zPY>@WlqdO>IED<91vnGnQ)g$;${?F)fHVyrtHIW#J40T@iS{VE|DWSyuz;8Iq= zL>Ha7L^5F@hem~8X@@$`O0&!)nQ+8706B1M>|gZBiew@XV+UjK!)YcfMlz9zkpM9a zEAyv<1)s5AQ6Ps#Jpp1^mb{tJBa+#Rm@A+l$Y`{xE07GFq2L}G81Qq!Q91v#B9hsM z7=IuK_IvL2!Bom96G!R35Os;+_BpEOifIeW^K{eL&9j+&t zIK&i#j^G1FscAoCNG2XJuzg_5rDZSynz0QJ{D%PtlpwunW+an|c9LBPe5c3WsgQlkVEv5RzE{Q<||2 zk0J(ID#+ZbjMN`L`qNK1h8S58!>a>s1_S2{(tk3?5yJ@p$cVp)0YjgW0cHZ|14jr9 zn)wExFBq9DkVB)6ff&lMvR@8H5+jq17()=lux8&DVA3G{r#?C0oJIwWi$d;^wLlId zlZzNIVNmdnn#>=AvxY|11~I&vDbN&jGNYW6h`9rjq2KauUhr#@$wN#yU})edzUiAQ z$>bv@5ojt1jzk`+gEnL=s{k?eG$(%(DY(E%u zi{xgLOfkrzQGsDX;b?C1BsK~xX@KFSGwSZqO{5&)?f^!T&Im1O5+#}Qh=EmswP|0O z=Rh(S5R*ZdV_$T0k}-A>G3p?Oatf)9cgeoEG=Zt*$f_Wj%ZQN#T@BZZZ^=r_Nqw#$ z<}6^~_;9$E{D5SxB1QrjULJ6C*^PN6$y`HBA{abe;HcW@@*|SDju;^j!_nsycXS!4 zGw5c}&H{91gY>6+BvXnQanLx>hNo}qbS29wLrgb_VH^6#TmVB7mip&>zlj)4AVeD+ zIpuZ%eIWfOa|YusU=L0j$sPlcqz&?dJT{&k#lQS|^h-m~WKsk>xwQETRs0j3d_ZT4)G2;L@ zVwCd`F{&VjIX`(n0_TE}0mBZ+389a};yqV(l1w#Xeu5a5rL{FzoMdVc0~`ocLhmX5 za*}zB7+?g)^=YcGB$+3O0aFJB>e`cF?lAVn(+Ldgi;zf?d4?Dz5W^bp%~{4qmIay> z)E&+a7-nhtfr-o5K6N06M%95a_<7>p4U7#VQ~w_Z=D5d?I+DzD#9)2yw8sJk8Raw} zrW7Q@`iITk3Y-*12DToJstl6hI4qly2@EzP1I#gC!hpi_A=79E7ClC$88LWXM5Lwy z6U)f7AVwF&FysY2H6@u=kTbq^L#D-85*SfNIcfu*94}? z@q7@;yh6-J5JOF0&Xe>cnb(NH`iM;#1OtJwUO+`qcg%$68iK`vk$E$LF;i2TWKHRr zz?7Z4045isoVSQE05J?ngEm|w^A6_4#&z^(6BFF;NfzVcsdn7?K$PIpg<)AtqcM z0tOwUKCm}w)B+I0oV6VGzewg2Vp3rYek5uo!0>03^BFM#AO@X4@wu=rlVqSp9lxId znR;`jSd#gIm}D4(pCJS1Ya|0#MUQO+>P8Q+6Io#TUVfnJkojLR7-C~kqM$7^_)C*K{g3j=1`1pG= z;DGY*&Ymn%=UIq>{RJ5_`R}}>9Qa-qjVc22Vc3veH{hbbp5aA)cpUz2q0z_T^~=|j1sb5&`YOLq2+{Q zK3?J1IkH~!5CaA=8e{BMQ#eRQ5HZlY!f`10{CE<{2q8ua6m~NTY%Qfr3(5 z0a%C_8Ck?&ov-u-Ig^YWV&K{ZLzmS936hbYz*O?;29k^dV&GVYVOZiyA;~Br=J+^5 zS$u%$CdnuvhMUglRV9L@7bH!nJKX8~x9%Fr>fcFb5n|Nna`=5#3z7_cs{X&)S#?5L zlehC?#QeRTmmucv?W{I|nY^9V5%c$UUWypp&U`HDllZ+Fi1~XvYa#}Zv5oQ14w7xC zg_ys$^D@LVff(B8{dSR0NIA<9Qv_o0kMg5q`6TmH8!>A@KAg|9MVR|ZIncu#zqbP! z?WKEVNd~^vOQZ6Ed>D9A4tJ4^9%8_tLE%msC6Z+HCol_zYr{x8Z-5wnx}33t0YM~V zh!|ly<676VpJY}bMjbHVfMRX41Gx5#Yqt?%pl=9+$a>P}I*Zc|kHJ zh!LYRCQKVaUogs9i5R>VxP-%ZWf>XtRu~noJ205AhYOI58IptNj@z#zjwA!0r2O~Y zX3wMCiX>x>7~Ia0(lT0Pf2~H0+_*XvGvRY9NX7y&fA=LU5rg}y**0Z0sm~h3=zy}o z0j0L(axGbw6=I-22ASdGCr*)!HDdm*&sxM}PaEIM%+wjuA?4U0<~@kP;!m-0%9vzL zS%;WX(2a0zeCPKB!vrKvu)Vg3fz|`&*fhVtLe||5F`%o|MGjp=vW%zTy4BS&fIYSvP z@+9Mq7)iRE&(}YIVj1OZ`Ug24hyjZYvh(jAzbqr=cp_#aU|_%7`!;Bh%x1)N(dDe% z8nKjQwjc(2zi@Bvkx<`AGG2&*Z3qr1PjwIXkc>BCQbEiD7?$03llUz@hyg|!`RcxI zmWHGpU&P!744hv>E#{N>qUiUIsCpnDz!cRd2>^#jfA-f_BnPg`FlWADb0x|6BPJ8% z!#+*x(0xQQ+Yp26KI=sdXmTj=&$6~7<`qbVYvsx3qhM`fWOg8C805qD(GXkVL(b)$ zh=D!;)Tc6MJ#cT~(Vu1QLX0+@d9aBsf@A^^vjbcT_3miix{lOmH)6&>4Evq+?WvO_ z6NngndRaqHo50%5s80}LjOok`JyCCx2}TTXGEm)ruAC2!K+=SIg&<}ZNCoDXqMmeV zl78QVm@6QLV}4g{!4XnUC}N;}hH|3%7qyT~7-F!_%lNJ@BXtf(47}?C?MthEg)}KA z0x|U_^OfAO+Sd zRpF&6DJKpwn&U-LEX1RwNG2XJV8WpAJM17BV<2gQ&Ib_l_j)D#1CxlD`Sh}G+h$Et zR?JGs+**&gNWHjXT*PGO>(aiTHXJ?vpspA zru_qxj+n*tvi2G9hLSpG{DYiBh=KDI*8L8vTmvcRFk;@(%d*Z1ts|Kuh;gPfk9OMw z{|6*ZXv3q3`Fp<~LkzS(@M=@>>lLJ&;mbD3{Ay?~zOnVpQo&HS@Y8lF6OGtQX%8w#6W6Li?OR%mR=KbMABJ zTGG`D2?K528AKsd&K{96%11$rzy~*BDllYLuh%u+@qj!F@IVqq%M z*SPsASJ#rvImCbogLJ0#3r$kidBivX2I?~)+W3o=Z-&Lza~fP8QO?u&i`hoF)Z#?NKMfXyVTm$2JpzxxI)28|j8m>J-w?m_z`{eBfO zCqX{!3%221BBajOCX{uM!*4OkTt|!wkO4z`TK6ca^9{s+Nrmba`nuyBDW?=Ma9sum zlm{0_fLieA&v{XXn7{8GZX#v@UCz>>q-;{ot$&bn8!@7EIUB!mZ6W29|AU-6h`EU6 zv{&ySq+j9 zRUrm63Sze9NCS}|X+nQJK+I#13hm(&YDX2xJVeY45JNi_7a6smWF8@ADVPdSXU&xn z3rVILF~Be(MrhY`;PZo|3HsC^26O;ovcqbRlFVbo%m)m-diPAtOpPF0bx}3><{x^t$D>&@;zV6qb$h!9+=I`U@En+|ipzz@R``e_P zcZk_XXD0XgdlB>ZHta(T)EsiTr*~W<%j!pr7`?3M!5@tz19#K^Yk%o}(_Nn(= zW<<&vKnz%nQJ8#x@ewhFfPgwvr7uom!#^P=j?R3!E!{_!^%*gzfD;AvX^*%G+;)&O zVLT5arX2vVtT}hmi%8}RV&LydL8jjR12|%o^A$08zr&a4ahha?5CarN;pDs}&q?MR zVsZfi=c#@2IcJjj{twJBV!qJjOgUo=ZjFPa33VSq%zHZXYo5+cl7YKd8Wn6{&^-|I zj6!9S`H7f3I@1#A<4!Wr!qceEfPrmjV5J#MGQSaXkj|`KKH2@lQN)DMnV&KvDWse+ z#E1a~98g3|m)s&5I3&P(o**BFblVMJszLhCwTX%tMMO|2hnLMcL^AOAkjDS|5XAQP zDb|t<4Ke;89}Go`{KExiB*TmtXD9@O$@jTa5Chu}hRN50sfZ~XM=0<8L?&5NrXdEd z|FFhEYRrd8eOM4<0szQdlN+!cpGAMR4=ZB8q(b5SXCL4{L;6pKZM-a)48mq#JsDCC zJ7N?81KVfeFgI|!80Aby46gg+YxfMqL;)Gl{zeE3m6LKf5Cax$#2l8np+_>DhWNB!B(@V9{f z*K?UU|G@Ad2F^jKv#F{8a8DTff)_FHoi6B)-H6@-mS{$X4>8&>8H5)%FM_QPBg2mv zXro}x=93>L@sR`&qd=DvBJr!7lrtAG&<}^0BbO#G$;?BH6ac^h#l+3qlVk)D!vXSP z=)WV=OEN-;xd`%Mzi)8dXF)Q;h=F#M9sH;#wNElHL=eM8*Ev&KJeia;A2ENg*8;@A z_JMU5{MIH*$`M5j_M^13UpkSD7-AlR80sv-bTEcw#1R8F;u=yG1Qj+T;)6k@=DKwLp87<_1LI>qfU5odWbQhGm$!pKsClT)JF_leW2ag z`fT+Fk}*IG^rqmtb7U)vJINR#hMQj2&>3Mc1Q^R&ftVt|z?|cG0c<2=gqTjcoDlFw zOGyU)^?-4o7Rp)nEfbi0#pV0N&K^pVUO#P~rqLHOlryp3e65CcaMv{A`DhTBQT8ZltP zAf4X^I&_fCTEzUlUN(q0Cj~MI&Y!%U;WDYtY2Cm)Ej%DuS3M1ug7%z*?yczyH zk7OJXBSdE$zK=|@UN|A96O;qX+C%a6C*{DWHZ&?|I8^uKslT*I#sx7UAcnz2O?i?r z?}`|Bw*-cjde&gcgeQNlxf}nXEH}hdfEbSXOSaOpNIC9^2?hDEUK~mfCz(5&5MvAS z!2#u2?`CkwsE@}#FrJ73#tQ}7fXNn8&Su2i2L#k->x=3cB(nuE@J=JF*L}}TE+pfH z82J1GhA7{PQzYX(fl)EcO(hv0#5@@nLm6e=Dnc^8h>}lCLkuvuDDYl)0;)0g*LK91fd+)_ zd`^7MD9OOLacI=Dz-Y_|%uTj;lkBZ_A|?TdhijbD5(!gM&aUyYKt9w$?0Cdyk_kWz zv`;Vy>B~+sHg+TCJIIG}*1Z~| zMht8LsFQI#clJ1Mt-ubq+%e z96#WI5>hQuK{DZp!DBUJA^Q=Mi9ihWSzr)K)vG0$NW=icghJ!)U7%|j^@&0Z7{n+X zZ&U-@EJgBv50|pnBkghU6hhYmbD)-h9Dp6lhZvk$(}I|G3In;<(YMp)IADC}N;pfjN`^&e}1={9Whch{1mNy@h4MWLcSrfpZ6rXD&zf zy(E)`7(u!|vKgMh#beYt8!;-N;;?Ry9qn64CI>MK>2hjY6~T05l#`1XJjN2!w@k9e zoj^<_xD;|ew1!E>;Yq~6F%L`FdiV4swl@zk3UsEV*I|odorG0t0%E_=MaPEd(ix`dt_PX5d+^aGFE_(TnWG*8HGzto32|tHO z<_co=&>2RdKI9QbvNa;_uB69CY5%vkl| zBiTMT5VHm_P>#w^f3Ob1qd)zhQpBi$R2X*TG?|lSl_5rnF2_VdT85N!bAp@$l3uE$ zoLh)dA1{icy@?Iz43Z}3a~m&I7~%2OWi zO#lWgr#GS-dxV zl6ldH7_ji8&|-e6kZi*y#Nhe9)O8wYLy$C~tY*ZdfmB$QxkS7%$+RE_bR^PLO-y!@ zK5az|7@#P;yAD3OMwHct7%M=)b!B#tNq-A68v%v;1H0cBuc*rr$}lgvBBhy#NFHcS+;#0oKz=|#*8kPkz`Jxx#~ ztl^(yz7H{A@S(ES&$QAbnSR9F0t~#`A<|?v$-GC*DUc6?+_w!;B=Z3=crB1{JmgC< z1OJb)vw)J~TBCIjg9UeY_rT!p?iY6kcNts~+=7!30)*i19zg;Df)gYmgrGr_5S-w= zI=lDz&-^LYd#~2I_j144U+p@lPFGj=^vodRO&sR=`pucO{F(d6nECVm5m@;7@Pq!$ z17t3GUpM2=7GuOBf94@FcbOTJq182i<`FX9Ny0tS@^%@$UH*3tA0so&%f#{iGood$ z75>ZfbPKmM+NktyqC%#mK~ zILo`&`tLD6M<${7b?+a6?H3YyFKhmrdGY^|d5Mg7A%J}~*_LJg{k%fPTcF4!{Wbj% zf95qZ@p(M&|Jlhm$P6)u>1|U=cX-d^;w>`X0>!xcRG%{1-}Md|`?#oGCgWy*#+;_$ zeT%94Y4^Az{*3um7yqx{VG;$Iy53G=di$AwXI2CMenOEc6*c%;@5a=$?|BUy6&dph z@YLRZ%J!JF-oKw{$RuOer_YvM_Gh9ali%Bi*`~%^Qo)~z5j-AmKi)qArMk3O=g-7M z#(VO^R{eR>Fa4PhkSXHLw%Ia=KKCvm-cHr^Cl)gQ_wyAS8ShC8Tjmsd(YS=Bk(Q{t?Jhci~8XCIK>k zd0#h=<@kgDJnhdUL?*NMb+gs0*TuUu{&#*7A!E+D*&g@U|H_|9jEotV*&b{x|DLsx z1R3v16x*80-@WG^ASp66yo5Q5zQdj*^>-yhCU0;zf#|yvmGEbhBV%5BnyuxJ>$dwd zDUit=j0Dz{|6rs)lM)&4i5A<+RndC-GpUf7=q1cD>v(j3A%7+{GK;*gn{hQO{qI?S z#{8yJ_j{YeTc+}M`QLMq7MXG$HeHo3o$_vl{+mgMOeOE@W*faa?*o4(Ju-GZ|B*2D zdmg75kg@AI?6cxI{rfTZR-qwwo_iih`JT0!2^n);#db2@xBmTPM#iqObs@#PUH*4m zS&*@>Q!4LHGRvRIij21)uytIQ?LEhn4Vfm)bbb5qJ@U6k;%-= zw!hxD<_oAyM3x8T{9R$leCqATtogeY@@MpC3L@h@0bpFq8&2_V2mU+uLXz=ffen9r z(Ab|Tj7&#QHC-{cA3pET6hX$W!^yW&dbcqD?JA0lU59J(yeZ<(6hp>7c5?k1_}iZ; zj*PjdGuy$ZbARw>N+4sNzsxpye<|-}&wr1nBr^4bk-((*8NFxv|7J=dQ=6HhHRgBr zXG$XzkC~-A?p*O_${^!S3byxuCZ{Yi-Xg}fDN`=*b>)ALryMdrGE=o>hk5=?d1S)9 zjIr}RkNU`;sep_fS9s}Z@3{x4h>RUq>hbsX`}b1`89T1`f4-$MG6%e4G3%%Qyb14l zoK`_*2aow(^mOmJhF3+#p05^d2fycjz8W%iTw7m+E%P69b!5_dU1od#=Pqg>6E_$M zBpF@&Jdwlx1eWM}lK$ zKoVC-N0N9#hLPlqVy(#}d4$X-$tA?R<8QXyLd^TKW@{K~_ha6Uv7E6eE zhr(>JJrewPw=1S3Tw8BLd@gAYzaf`5jH1DE$^7W^45H)_rKmJL(T1%w=EQspJaiMawK00X+|<1#GK`U z-s911wL|UL2QX7d$Y_$fLZ*?_7c!5eo{*I!wS*iXsU2cR@e4^EA$Le>2zg0TO-LN` zmZG;+53&2nL{eFZ`3|nxs)X1~ZIb3fT9bSuq#sEwA(Kd|3Ryt%op*ME=VAlNZXtU~ z_6RvnvR%jpk{v?+B-ttC9m!T9Nn!-B{URhi$ps;~Nxm0Sgyg)C3MBi4)F#<0q$$a- zLOPKAETj*~Z$gHVd@W=;$u=QNNGi#!en(PM$XSwIo)raSzE1M3kcTAS2*KMuUV5Lm zpP&^#Cgi|N$)qFsKvt&tyuR7qNTwV~EXgz?c`KQ2Brzm2lq9;4StK!qtRy)l``Jr! zQphhPXN24(IV%L;g!Yd4fRL2t>Dt>43CTlpOh_q`(?V*J{3N6!$$lXtNq!VEo8+93 z>~L=$QhCo9tp*F+%Q;4E9Kr zfZ5)W^b(ScF9F93$wx9oNClFQg)}A^A*4IWM?yxBtQ9hqWSx)&Be#khdh+gd~p}z?NM|Hj*qtN|R&~Qir6aw6`H?A*2sUD&945Ib)74ikiBO%L49t+t{@=VBnl7EGqA$cm~ z8p#tO_ej1J@{**ZkXZ2p*xCt6OVVCQQIZZqYLJ8rX+zRkNPm(pLMD@R60(TofAXxi zhGdh>^A3{DLJpB^5%LSk9NF`Al7Q^_0m(JVgeJhy1J{M5BKe=U=TLkxFUc^s(zmR`P3J8gwI5_r#LXwk&3CT?ILP!|Nb0OtPlFF>sBdIH-HOV+w=F!x% zcL&tRmHqT!rk+^CNxl*?k))!KPe~@reio997qXG$Z+Yx|Lo!b?2T0<9;PKu!_;}wW znUl;+5%McZF(G$IzQqw@KV5qT%r;;4+`e}|koqC!v1~H^!-JVWWIvC%pXA7x_#2XB zLSiN{+6kBJ1@MJ8c}v=`E&cV`0vzeFa;oCh&qOV2%b5t4_b zD~Rdp(|eGK20oNglwjr)A(coLh}FJh*B-{I0Al`ChncNH+LC-JB!VPUs2%$#l8izo zlVle1IZ1IL%Snm}*+^1C$hRalgd8EMF61mpO(B;_k_-8ZB%P2KB(a4=Pikhsn{OeB zNm2;OK$1{MUXl-mlpx6>q$)`fAq`3L3kfI5DWn%kNg=~XstTD%Qc=iklF~w!ki-$P znIwad?@3Y$IYp94i1`iVX3Hw%9!XIluSp6BiJdHH$!#GINNx%VNg2R)T}U#LyFxOO z{2?R{$qXSSNInx%jbxgT#w4E#=|M76$Z(QbLS~ZuBV;Mb=R&?F`Af(llBn|hb%ErG zWNwi>74n?qp^y(!1+Ybv{UjmzTQccM<_O74GG9nZlG#FPkjxWe{${b+z6i08f<7d3 zg^VNlQ^;JBC~|}wN#03jKS@9`=SV_?+$ISX@{;7W>?c<00Jb+mQj)wCl9S|_5c4}& z%=SFQj;kigOCfDZ?hE;t<_*jmWgD9Y$ItVpNOh9YLK>2c71D;}j=YNO zM)H@C0VKDDj3fC|$ZV32WY5b<#t7L?^0AP^BqM}eB^f2;DalA7i8BOe<9BJ#Msin3 zagr-SYLQ$O(w5|!kO-2?LMD*h5HgSCrjQLJw}gC8@`sS0NiGPvL-L!D*CZE(#LF1K z_N$Q0B$tE~A-OK3D#^b>T97;y5<&7@$XJqhLS~UX6S9!xzK~5M{|MPb@>0lIk{3d5 zlEe(L*Ouob0U>cRVd#OVLeh|g2+2bdO-Lz{C_-wHgbHa%@>q_iH^~Dbqe-3!nN9Lg z$SRUYLcSw;CFCcP*Fyd#i7v+zC365<3?b=B-iTF*O0rVO6q3b4z9LyB#QYTrvn>~Lnq-}jYb2|MJRw;xBu3WYe%1&{ zPqJ1>0g_chs*_|5v14yRl0!%@k_9EhAt@jvBzpi`K_Tf$ ziVG=7Qc6fQl0rhdkQ5d&lB9@`xg zkt9ciEF?KBWE06DA$v)F6mo{-xR4tp$Amm0IVdEMCxGpgkc1?`&k}cP-=lZGj=@Zj zwCU8Lcb|^Eh|Eaabn4opqkK%+kj@?Zx3>t52le$Q-7Y_JFq^v*q)VT0YXv zM~3^zG#~lON4EONaUZ$uBX4~qZeBO~;3zWqNKqfD>Lbm3q`!|$@{uonWWA3Z^O3)N zBzC_4jv||nl=qP~J~GTlX8FiEANkrxj{3+oA9?8`aX?TT4?j5>D z1c!nBv>6o9Qxo&WUudmB#*n)tr)AHtNX`g}A7+x?-&7Eik>rq&0whO-)F3%5q#4OE zAzes*6f%fpwU8Mk`-Che*)QZ^W{2O+OXItclo zU;tYuA<0O7@{T9im6>F*kWwUzgft>qBBTdNgg1)d${b13SIAV7ocqBz=UGB^fBB0ZBI@y+{TK8BVfCW@9qR_d*tu z>=m+&Xo1teb!*-ElQ$YGN2gq$bYE#w}_R*wYdCwh_KNVf?|NAj(Z!X)1a zsX?+!NIQ~c9tp1b!6ZwCOe6VO$P$uYgls1OxwP)DRLuQb))XlDa}Rl9Uqi14&IGe~{D? z5>h;P%(aDNB`G1K6iF^2O-afK=}eMT$Z(SX3Hh9)zK|Uxd4wD%$t&b0Ne&@zNiqvb zSt2-!EJBKsWD-)Fq@a)vB!z^0OcEw!21#)t>qv?T*-uhV$j>C%h1?@4DkP+2@R-vG zNl21HNG6hOLW+?T5mJLBm5>%B>4fwr$tYwtNqHd~Nh%0AO;S9BYCn+GL7fB@{V@WCtSwd1p$Ptncgy>LPn8v0_oqsQG;rA|JTs;&mj>Im~At)>ei`>L;RdX zK;SbF^Tp4(=Z}RfBN-rM2T5)r7fDJAc||fvNSv}L4-6KPnxw3df+U{`sYudTNK=w# zLVA&O6EcdVrI0U3W(rwH(nrX5Bt3+jBk3*VPm*3j0_B3^>M0}z$v`1FNV*ECNHS4K zGm;iU`j9jiGM%K6kd-7&gzO?|D&!)`Fd_etd?X}LK7ehwkfbD|gk&cfDx?C*Bq6Oy z#tQj}udDfkliZN;K8PMB$Pxjt?B}(En#riDU?y@Ei1#^k zw;Pa7J$jozT@tCill`FpFtcaxlj;AldUovDzR!@zmPh(^>>ruf&!GpI*^fv5vmZ5d z98W)f-rreC57$r)VIX$CQ#23TOO!oNr5o# zXqW9O5XQOx){;VY&-?7Sidpi3-H&A6SS!pPbPSu3vwX^C3fRm^OA6X?o$-;gmW0`1 z9I&K_o%v&y$az0)Ndaq_-$8=&B{O!;k^*+h4q9SC;0H_W0S0>8*^p!IZi$S&zYwcM z2(fcG+!8s$(U!=gppPX*E$V4WLA#$(mPmUyON!eyKiCo(X)j9(+dYrBqzK=@3kw1CHAxj)>%@>&d+*FidnM8M>beeh;PyaHd<1^u7s@id=<6l zJ)0#m_Ux7vv-6Y1l7e>ZnJg(_2iDSd$=F+1QpBE%R+fb2=U8vq{m7f&w=F4Hki)oR zi9Hp88 z?7&(Hv0bfwq>Ux^A`zHtNfA38lPoD{PtQH?n1UmntT>07-;#oMqy;RoAW+Z}8CRGkcESQl?b@)*Cosikgq-(f zl6lt`oE^#JvqbLDlKMzqA$ERJ`AC=$JB-Sf*auu7wT~3EL}sIkC2~Aze58;N+m+Tw z3R@!U=Zc*Txwc%gL}uf%CHB@hFvSviM*GAPxfi`-iR|aLkKD9G&fs-R!t7P+Q%efk zd7f#BShFmVQ9Q9k+Mik?V}EFgjN%JRWY2Rgk>mN(63M)?M9xmAU3HRq?aRFJk++uE z%VFS|k36?T#`V%i?pq@J`PWBYSR(s*m(LLsqgxW}}8B za_?8x63NuGM4qk6St7?=(GqzEscVV6_N;1&?5C6^a^6c?BG=~zmdFv_labnKt!y(z z?E$UyWmXHZ>v@$WMJ!oiiL9R__HimCk0mm$a+b)9#rKiyK9ayka#$i)n`(Ak#q6%@ zSW>{;b(^iBB{mYM=Oc|Ru@i>-QCw-|wN(?Fu~QiM)7!hg8jrCV`@xXF$3krV2utL3 z_b5wbZH%-;u2Gk4mt0$}3$bVCUm^DT^Hhj^mGs<_0tGnMcb3R~)H6%uj_AG+`^@;# z61fL>VTs*wAf{a{#qD)IV2Pa!@2566heASpB$|&z@sUtVi*ll0SyIH#>T65nQ5oGHPf>d=VpvkZKA7K#Wf$~YOXRV9+FG_eaK@5Cw*9P- zLOiF9Epe;Y61h8VYKgoSj$$8QVfFzZYDpn`dZJolU-$;1Sz;rB6?XO7VFi|2B9EQL zmWZ|7N6c$EJia8e#uB-PueC&uXO$&#-t*g$%6)hVOJvXHm89-U#7iYVGH~&89S}!wJt^>ucWS8BFA&d61!Le zmo1UC5liOBKA*<6L}osYB?as{++tU+y+{N$SR!k6vk>ne*iP8}Nc%}k3fuOhmdMrb zh!ER;*b=$xK4giE{YM`;Zi!gOERkbAD8xQJoU){_xw4t}XwQ59i`Q|u)R}Ka7!o`e z=AET_ikM?=sfZbAdqvE3qqibvxeSX;aN5niymS?_GbSX=ygfHPY9O$}GjTj&_QVS* zU@qdndhbw0cq;*;z^ZHH8HNN$VfOr`BIbB*Dq;@ET=|@3W-LWyg7uTx+pA>osH~uf z*|T}SK{8nL<{c47ih7xT-q?lU`SgS$=7OEt8@tPxBOI!TIl{+^m{a&RGQrh&p!Y78 z><9NYzk9hp-Z5du@Cy2~$OO+8<|EK1gE_?cGGh~B1`#kHDG`DtRxUEZimDQs;Ox{= z#H^^gikS0d&YqindpYzxfoz)zJPhblOr<09M1MklW~v1^@^DJIT)E>Wt#Vc z#ll%$pokf}d3V8O%t%W_Ciwov)W`&{O7=u1_eQ|A4@?UsbuZ#;Z0#NM~;u%wu6|IU&^ z#k?J5@vbDhJ%YytUgwVW=FpJf@t7|2&bJWUT~zjFLr4MhzHu7woJJy^RvHppy!* zRvsa^0D1o}uMj&@^W%2Gb7~&TX3OmT2SncG%VLSVy2)gT+((&p6x@&PGE)~MxM+I+ z&vXWfomMj|AoAU}a+cUgAiE{hImK|syjU{#p11T(#{bUp3Elh0Yxb%KfSz-@4 zkj@g>Pew~5Q{EEERIo%|r&P2=tn`+MmBB~Mb5HOH?W~%!1R_=?OXOawvL*6b#yt0E z%lt?Th*)N2fykX_JxgSsQ(F>dUzV7;)O*fqiG3&sl35~SPi%>Nr`Y^3B=!?#7Fnq{ z-d}w1Nbq^tjQun3`eO((xFUsm*B?jBt`jI?)>gg91a}waJ04=;s%~DXJ7T7FgCb_n z>AkDEWN--&SH!IB9*P*Nkas@_uK5YC^mEouTF9ub7O?Cbo-Tec zema@Mv$T*=T`g7DA45LP<*dJHA)~rl`MScTD{5tLxZZ}0>T0dJ8mH;j%2`QiA)~t5 zz%p}aww0sm-is?;S!p4oy4tF)Qme-%b5>be$fz#!7z+(CS90&cA4oNHrN)s=Zw?J3Usj21Ggt0OGC z4%_yNzSCK&X(6M!I;pN>Jyu_H)*f2OsIJbi>^e-it<8w&GKXhrA)~swsIDtNB`@Wy zTeOf-U0q?>IXu+px6;mfP74{;)lGFpzmWX3v*MU1E^k9db#;ej=dfUja^swpffh2V z%Uq+~bz|77baS0mh!!%ct0ycwhch>J`znE~`D(O~QC+=M*ULVQGdQa)Eo4+zZ(mn@ z=^8`}8P#Q8b+~bvYp=5=(LzRb_4Rcvm#%rVkWpO`s;hCe(es?OmKHLq%e>Ba&nadO zC&)VdjutYitH0{XU3cZL&N@j88Pzqw*ELbPuF^tAbq!QqFPkr^=&TpCkWpQOVA<>8 ztUS3NB$PQ!82pHR02$RaSalU2z3q~-veQCFbq(=#C6caEw2)CZKH*Z>N20yackamIqN(v zWK`D(U)K)ldO-^r)iqLeWtiOeyt9&33tpp;QC*{8nd_n1vKK#?shMROxsRW)bLriF~^8VAc>dq11C=3i&+ zq=k&?8n3#V6=}cNSr=#_qq-)*iisKY-e1jHacs0eH2%L=w2)C<6IEBGjU8V*E46tk z?`_Dau1PX3^L6uBPA%uF1TAD#*C(p$MB1puoz;RCGOBAbEW0*Vhp&B*Pp(nJXd$Dz zrl_uFNB*4dtU0uhQC(A2*My4gpX8OUEwqqPUDH(8%u)Lme;s>Y5G9jLU2Vci;MEv&`WkTF9ub z&sA5kug@HF)@@qIsIED%>^fYMciJCc$>S@Ad9%vfkWpP&ZuEntIy^VRTS0~h7drQ$mMs+PwT~D@Gn&7PZw2)C?Y{M~vkucjMs=-LT|YcsoVS*A{Z0!R)wM=-ef9B< zF3t)uZ$*0>GOBB>>N?VHX0IJ`jmk<38P&B;b(#BlXO*FajOtns%f7B`oPJt)XEmmU zjOyB;x;Cd+_KUOn&_YIaZG>g7^FJe?!n?b;_@H)$cGy0(dBy3AwQS zWxMuDS1j{Q5^qCBb?p$#bX^(y?v=CB(n3ac?SvKi8uhDmeMk!#)%CUNn*D9t&d#bx z3mMh*jp~~IL5lM~OIKrB$f&MwRad-&iB>wR8!cp1*LSe&nxEWebw_7?L<ohH7 zRM+>aYv#f)5D_w19A)~qusjivt zE`Q{#fwYiOT|dII&tGw)yt%wjUK35Bg^cPtthy?l9@5lVOKBmax{kmy-^(*wihDnf z{Xm|-zM+MT>N={r&ekk_&RJ(@A)~sE!Ls)Nb!+brc_VZ9CoN=D*KySqe^i&G&I+v; zJZ@xE*9oyq!mN!LGOpCLkWpPH#q$0U7+*VSe`gh>g^cPt15TX zIz$T@)pbsF&CfkAhO@5GLPmA{thyc_Zn^QSbiJa5jOzMDb)|_Jvcy@b>Ic^bGOFu5 zEV~Y8RbATsfOHk3g^cR@RdqFJzoovjn$bc=bzM+hRo3o$b4??kfuAgZk zqq;7uuCOZY&N%B|TF9ubE3oYI@{pK`20JUo|ANCsMs;0PT?gI`%;v0;w2)C<*HqWf zP4|^@R%=?wsIKd(>rv@N3!OEJ7BZ^q53$S`%)RJ2nZsqYkWpPX#Pa?Ts8McAF=y?f zg^cRD3Co@@^LqK9be*MzjOx0jx^fNJchXspXd$DzZo@L?%X|+pYLuTE{~%on8hFUt zkWpQCR9Ea7Wez(lA1!25*Inr{do|a?6Vg?a7BZ^qPt_HzQJ&t;YEKIp)%6!FJBQ}` zDMzJiC@o}E*Waqkyl3vLS+tN*UH4Si(p49p^p<;o)wGaNUH_=A*av=z@2nqbA)~tP zi)H4}ti$fob%Pc%s_TJR-ai6$K0H$1Sy3CBJp}^DsIG^w?DJQb)PJSuFI|~wA)~q; zsjf7cp6+v28CuAwuE((K_0T*oM@UywTF9ubC#vg*6|qV!n!UKLY0Um$P2eLPmAHf@L3HTOZ|b-&49$G&22x05YoU zwdx8h_VGYx6{m%a>Usk!iMK~{gza7|Iq0lrw2)Chx37 z_0C#A3mMfFi0U1ieBS0*wndGdwU-t$sw)JRSsP|tU%joo%~{uIA)~sYsIKupte@|! z(8k`5ybT%E6{@-}pUHX3Sy^czqq?H1u2E+WR(4iZTF9ubXt3>_LmKHLq>jTwQV8q0tgQRO8 zEo4+zEY)>y|AL&(x=ITf)fL;kutuR96yTm$QDSg^cP-3d=sHY#r2~%K+(mNDCR& zl}vTLi$5)ovtl$2t_@^VS8`w180ktw3mMgwLUrX!HF>AA3erMGb)|%5-`{xDaQ@oC z(p8-nGO8<;>N4-8II9aSWK@^=nVHZK`<#*@_L5f48cz!u)s;qd?Ru7Ksk2tlLPm9^ zg=Mevt4ij(=Bz`skWpRfRM*1}Y5#H7En3K^uJmG=Gn)Ts;Z40{4x={k=R(4v*sIHu_GVpwDTVHFz5IJAfXd$Dza;dKW z#Vs|`SsiF0qq=g#GUv-|=C$6((lvq>GO8<&>iX>5KRcW?pB6HzD=#d&4(s>dL3O=9P>Y;jFW?kWpP9ie<)SUVnWgUH54rqq_2oWgcIXzL>qjS+UIzta%$U zs;dC3EZ*15Yx5}8pG{pWUD;?Mqq@RWSF(xg(>tpaEo4+zL0I;l%{(u!ldk%-kWpRc z6a4PJ*W3d*t1B&JR99hG891&FS`?bIUb;roLPm8JQC*ey{yWK83uz&vx{AWGYvbQ_ zv-hl#uHCedQC-DUSDek4Cp+sjEo4+zan*G>-sLPCr0WJPWK>rP)zxKRrq0fKP74{; zRZ?}CHNR21Vz>0B*4vO#U8Pi4mXoJWI4dJ9WK>saSdrI8R(UU_2rXn(R~gl1zBlTu z+O&{SU1edJ>xNnLO;6RjkxjbV(?Ujdl~Y}HhGiV@tg*C^QC;PIse)%Cw4Z`(QRXIjXpt~#pg&lFvcI_o|yWK>sO)wQ^B%C^q>pmlI< zAfvkKsjg>#&l}~e%(ReEUG-tvdzwtFh|Za--iA*R_`xGODYI zSmxZ6Y|!d%D_QdwXd$Dznu=xaH?C}m{e`n0(LzRbHG^f>{EDd8cR4Foo8a0&Ms+n; zT{Q<+yymPdw2)Cb+v+(#T$TmPKiht zzuhgl9!AhYMs>ATT|+1TQNdXgXd$Dz+Q72c!xhD6=D96h^JyWYy4tEPb8q9UZM2Y4 zUE#1Ya9r(rezxR}bRDOKjOuEqx-wR~P}y03&_YIawTES|z2^S#hIGB4g^cRzpt{V@ z_&6(dTkpWV4H?zd5tezbH0SI5u?^R6N>{129`ZJ1R97d}_1!1ww>qnBTSx(9R99zM z=6sn)N|l!><~wUlTMu~~GODYK>gshS@l$85Y6~fVjOywN%dYt*ZL7s{)(Kk3sIG3R zYhjD!i=Fj|7BZ@2%zkf#|J<{X zoUd?N$f&MfVwsumx4-C3XW5^$MMib?7Rz***Iz}YYdpJDo*S8PydbmK~R~4%0$Lb@da=beVA#k*>?MkWpRz#j@ja)^l3OsICF9 z>|=T7lZduUSb$f&MCu*{k_oB53NGU+Nt3mMfl zSatRL{7NQg)un}u>KX#ej_cxt9bL*vS4UdNsIH-^E9;Zh7o9bP7BZ@9nCdd$<0vm( zQ)nThx`wN+)8+5Abkm)5?RM!Yt z_W8@au1qUk*J&Z6x<;xl^SKmfy`+VV>KX;hp08KqV;)#2Yd&H7;EF*;b&XbCFSG2q z?W}CHkWpP@VADz0SHt3mMflS#^~jRcE}j-qAuvbxl!SZD;K3$80<@4(UDH(8Z>P_G=Bz5TkWpRJeO-^Gt2r%XRM!mEW!`ge zRs=0%RM$*j*E8vwL<iSG|nd_mmj?zL# zba6FqkWpQ8U`1XVPoyhe$KZ-WMs`ag)QY*OLqpXHeRM@hag2p9pRU->};4%JGYY~?7%K$)DbR#7026YsP~^PvyU!v z)(Be2sIITXGDl&+ zx}4R97BZ@9h3e|mcv$gH()AH7WK`Em)m3Utp8?LAM++I%wF;J9hvw6Lo8&HK2Q6e& z*J{;eeuIItj?zL#b*+JAUX7ZwT)pg!=FYlA3mMh5R&|X#{Oj+|dPEBu)wK?meTGf8 z>FG;n#prBShWEEdOh$FBS6w6G#_Q;;G_;UWT^nH8IW*6Dn`I6Q&_YIaZB$*!Z)HB| ztm?FoQC*u+2e>S*uHqsyOQzEo4;J_pt0cW_QYG`k<|JMepj> zdmA#UYoF>0&-&9CXJw#;jOyC2#`P>ljYh4ct2ixWRM!ux%Y1jqSq*6+qq+|Gx>`$D zFIvc`u7kSE`?RyOCecDhbsh3`wUMr+w2)CN=vj%%{_x^*1eKRM$~h=DK0l{Oy=uZ*$f=TF9ubW2!6l>PLs1m86@u zBX2`SbsdLguZQKiPwiU5DXA&;8}Br?ik!UFTKT`RUJEI4f3nbJX5<2uwzG{R+#@;rNWp8#^mK zEo4;J1=UqAPRl=>RfrZcs_Qqg%(2XDb~;U2xwolK3mMgQQ7kiu<~JESt354bRM+pY z?7KZvhL4Tntl_kfQC*i**YAx}-F4O+TF9ub%dqVET2*z&FlTL{g^cRDqPo(|IK0hS zCukv~x~{^q=PUoN+Vz}uhZZub>ze9X)-y$JXN3k|<_3^aUDs7t{7(;j>a29MkWpQK zsIL9%qHb_jaazczt{bq-9GWdj-R>jG$l7Q~3mMgQQ+1i&IN_{bw2)CrTLJJwybqAI`UlE^OJMFC9w2)CVD{fD-r$7K1)%7>5n87ax%=;S&q$?XOWK`EZ z)zz`k_}0!UM++I%^$#rjZcn4h6M7Aiu4c55QC;^{mwBh%S$$|Bqq-ibahac77%E-k zX(6M!9;z<$UW&6;(?UjdJyPSUI{k+qhDp~kTF9ub$Exdk*N`^Ox=ITf)%66Hx$iYw zvr;FX4VSJbw2)CsIHf=>~+3e>+1hFYZxtLRM#uj zRWIVz8)wa;g^cQYt-5}Sv!$c6R?|X8b-httHMi|A;jHgzA)~t9s;z(SVmbm+1XZ=G98P#QOnnFYD^>AhD*<+j)t#@$6Afvj>Q-S;RY>skCwmB;eEo4-e zSqQFc*B9$5IjbNoWK@^=g$k}~;cxqkII9LNWK>sF)wT59^GItWoE9>wE1K$xndih= z*EN_HGO8;&EW74&w@!U5nXLKgw2)CWZhv6{q&u$F6G_Eo4+zeASg?SpN#n`jQqh zsw;tv%Z$9{)(5ka%lX<(3mMgwP<5I6C};gb3mMgw2v!EK$IM}eywkpR)<3k6QC*2u zSB9-ejyNl3UvEd=hK%Y;BI7b$=03_{pa8@Z=$f&Ml(q)dtd=ewQ z%wc0%$f&O5sw-dq5$Bv0K?@nxl|prWS|Q?HI_a893mMgwQgxY6m^f=KEo4+zDp>aU zYeeh1+nsfo7BZ?Uwd#tw{P0iCx=jli)n)!(i@WyzI6Z0dLIT%($GjlNK_nD~sw{m+(@8 zl+tyI7BZ?UtLj>v_(or6-Jyky>dFSo-p@x*8^39O=?e45qJ@m=%B#A}PcJ%a0WD-yS3cEczWy(Q zkWpO)byu---#RNHEo9WE2nxaSZke$i|88BrFVPkGQv}GUBP^_r@ZqCGRg;-pPiz_4 zg^W7FBI*by4C>d;Sw(3fqq>Uvy7Ea^En3K^u41~Y)r1gdwWEcM>ME|f%&Y9A(lv+{ zGODYD>dJb#)z{9NLJJwyRnpg$SGpF_LPm9!QeEYL+Wo*;+i4-Ax=M>>PSMAQYNbjg zT}Nmkqq@q7Wv&3dzq&lqSyyNwqq@q9Wx57*9@`{Lx}MNNMs<}F%XFDn0nUmw!0agy zKt^?y7t4IzT-hp1S9)5=sICfPnXVJn|9jJ7BZ@sGu}qhl!z$9Xj21Gg ztD0D*E5nt!37qvEEo4+zby)V^%_+*yCpLPmAf6w8cjb;@^3 z3rSa$frfdnLrg|>)e_6jp|eucLPmAfhLr*P^xl;$)w)+&=_*JI8P!!sb(!CF@2pz1 zkWpQAVcEONE~RfbC@ftaXd$Dz>Zz{o%ZHb7)#}q`riF~^YOK0u9Q?Ywv*Hd4o_u6fR}T0UG%p5wa5-ns@S2I|V z=kOEh3a5pP>T0gK%p5vv2rXn(R|~Ps$j!U@ccg0uEo4+zOR>!3>xU_Cn>lMOEo4+z zD_C|Nn)7vAx(?DpMs>ATT@SzNU&~onXd$Dz+Q2e%XtoAX5_Y{QUC(JDqq^Fvu2^q3 zrF2%p!NGkYqq@RXSM#S03SO74oV1WpUF}rY%F*c(I;#pTWK>ssvCPq!^Yy88wWEcM z>gphtJzvfmNeda()ltS}9?Lz(jov>~x)#tvMs;;kT}48FKkTgSw2)CwtBY7>T&<=wob9Z;w2)C~IW>6%3g8P(NCb(uMI)*4#KsII=S>>TD9pZC^W z>DosN8Pye`y3EhrIO{SkWK>r_SUEU{m(vu^{ik%jq=k&?>aV&sXQY5cZQ7W z8UV{)56$b8chXgW7BZ@9pz2!Jqtr*vsznPK)inr~nM1SYQ#~rYEi{UUuyvw^jOrS! zy3Fqga@HtX$f&L%s;k@MEG=KlxaQMBMs*EUU0D~EPvfkuw2)Ca-%8iNw2)Cd(rFbm(n$n7BZ@9tm-QMOUqQwT22cY)iuu7bzi#n(LzRbjn`eTR}FF2ZCc2v zt_iBkoUebSE6(uX)FPw0CaNxTzMPeV7BZ@95-fYZ;*P6d>VsIDoh>(1Tv*PS(w7BZ@9s;}#TbbUh$8PzpSb(z1T>a6Ru zkWpRJVcF-(fkR9G{YbiEeH1)j$f&Lvs_RC?gjmkXObZ#+HB)sJxpaKwW9ceM3mMfl zOLbMN(O{Fan$SW1*ZBxq$f&N*RM)LMdn-F@8ZBg0*KAmYc-@#= zuhpeW(zTivGOFuy)io|>&AiU~krpzlYYwao?8-N4{E-^cb%Pc%s_P5YwQWH6D9(C8 z3mMfl7nZ%>cw9T*+OpCW_v7FpAfvkGsjgPP?)smzGSNatbRO=2l{9U-73HL>JuPHZ*H@~`{9Z?A4W)&Q>RPDAWnSx5l&%@H zkWpQWRF`=V)>+GGA)~q$i)GG@dGD*PbbUh$8P&B!Ec4nS_CJ|=IO`NGWK`Euu}s(b zXn8kOm987KkWpRB#4=r#UQI3OtT(ifQC-VnnR{%rmHGG5rc%t|>P!n6)wR{vwMx1s(n3acZBt#B zzaLlKS*vLwqq?@kDi#QC9U>mwULjpaXd$DzcBro9&(4%~)*V{NsIHy9aU}`$5Vq)} zg8z$*>iSxBO}aHHinG$tLPm9cBbM3kZ}SrO$s=6_Xd$Dzz7@+{qs;e-oK=+;GOFu4 zSa!`vnfzz6a?;h37BZ@9m+Hz_r+pu1MbJV9$&g9&_YIa?NMFkr>mT` zkQOqkYp?36mh5$n?9#P^7BZ^qd(~xr-?Fn#(n3ac?NeRjiY@#lfpp!Zg^cRjue!|N zL2%YvTF9ubAH*_iBSo=^opVT6veAZl@8_C~>N+5nSsUhWjXCQ>TF9ubgRtzq=!}Uo zqE(aYVO3hlsIEh*%bYJ~wW5WL>iSW2J-vGCd>!eEpoNU;I;^@b?rl}sS(9iXqq>g3 z%7eKI1iqNMW(sV7~>X(6M!j;pTl zzQrmz>knGUsIC*fuEx^!f)+BW>!h#CSqa7jCkYwVbqbcfZe-gMUagUIWut|R>iS7_ zneX{Js}e0_RM%-(Ie5Ox7mK;OiFAe2LPm9+QC)Fv7OCZ|5wws|U1woMey;p8;2~^Z z(n3acol{*;uB|KQtZlTAQC&a#y2i-3j?qF!b^YS&a@HTTkWpRdVcGMQF1%5SkEQD+ zEo4;Jud2&@|I1lP#|CEw8P#<`brow@e!~dq`j8efs_Qq^Wxf~Sth%(2QC$~d*=tnF zwHdw~C0)H}A)~r}S6$|BAvtR@Eo4;JC0JR!VTXACNs)P5xslSfk`^+m>$2(^-}zc{ zXPuyhjOw}~mYL<%&FlShN!H;bTF9ubt73(E*5K&Z{&ZH{ab{0}05YoU8Z5i!&HLEb zr7JrvWK`F6)perigXYdEM++I%^#?4w4$V)0{3~5eX(6M!Zm6ywkCdzEtp2o+QC&A- z*>TP3pX2^h>6%Up8P#=5b(z1B;j9g`kWpQ?VVUc^nfaEZSH^JGaazczt~;vh{D`}2 zob@*?WK`E()iq~r&nC`_Gv1uzKmZxl^{48pxL{)+XXT-VjOzLemN{Q$TqS4kN$jjz zw2)Cs7XqNnF-*RyEJL!5y3mMh*NOdh=QsSkvl1?y3 z?fp(PlTlrdVcF|pqn)SsKa;Lpw2)Cgs*8NL6PoriF~^dg1GOAziy^ zA)~rps;==V-)wc(d0NP*u2-<^_0atN%9tS@!uEg`GOFvf>S_{kV~Vq4PYl|~sIE7v z%dGi;bY-Q5jOu!;x^ng&Q_xxEX(6M!-odihUh|w1Rk~WzLPm83qI>IE{{ENw$pvTi zr-h8_3V{{5D@3{`(?UjdMNwU0JwD&;tfjP&QC*>8nOQdPRYsGpZ)qW;x}u6@uA(0e zDSpdYXJ{d#x}u3?x|U?lQ8kKm-KB+$>WVIw=?Z+DW|^}>CK(h6Afvish-JQR#uX}E zDQF?1x?+lD$K|a2w2)C?jSo=DebTF9ubc&f|%O%rDwr-h8_im$qQKPvFe zL+QFj3mMgwKy_vPWcvtbg?wVpd?0{~>Po1(%(xy&S87_wsIEk+%Z$reMQ9sDSoWT+_n{PXoi<GO8e_fT@hoSp zq=k&?N)F53N5vb{_?EK{(n3acrBGehOE;?JtUqWWqqrw z)s<$_r&XMlo)$8yE4AvX@#V+|1gq;=LDTF9ub^swH4zP8gsMs;OSU2$%Xt>n7S(n3acWfaSt8*}Y_Eo=TE zEo4+zCb7)5*ZdtiXC<8yJj=+auFSAv2EQCI-zSPL*ZE?!kWpP(RM)*-qoz8mF)d_N zS60Eo4+zHq~|LakfUz`hpfRsw+Dz`&eF-XX($*+C>W))s;haEpB>b zhO@5ILPmAvR9$g~_nGOem{WsOi;U{ZrMhzE=rGY)`Dh`dx^ly^>o8Z!zJEKbAuVK7 zS02?B^acAwOg^cR@5SD%I zFltM(Ja47z0xe`zSANyC@^zH$&U!`*8P!!ly37&oYC8O#vl2}+V+{n5QC(rGtL>n+ z5zfj_3mMf_5SBe(HIp@po#|Kii~`1*tv zGODYH>WUa&yNR+DQu;)m2P&ZH~1yp0iHULPm8Jhh^8s z_}bG(ot3T|w2)C4Mw4_6H#n;mEo4+z8P)aR-`B^SHJlbQs;ew4d%mvM%$KpT%;9`m z$f&Mzsw>KcKYKgtJ6g!7uJST2Gsg=LuV~jqx_+aDjOwbOx{9wVx5rs8X(6M!D#EhY z`DUfUi#jXi4AUP7Afvh}>8|f)UUpUqTF9ub%CPL(*u1xSdS|tug^cQ|qPlW+nzqte z!)YO-x~i&i{Z;Df31=;!g^cQ|rn+t}3@_=dJ+zQfUDaVF@Mg-a`GZp~q;u94TF9ub z8meo5ixpd)6_{z}JrF=fb=6c|Ki5Ch*;yHBA)~r#sjg>lKfmj&3bc?>UA1*rh2$}u z)t(kIs;iFby0msjTW5`>g^cQ|3(H=kVy`XOys51D<+PAdUG-GgSGihmbk=cN$f&OR zuS|MZ+iPc~niV`>$f&LcGA^?=Cf}=g*;y56A)~q)s;(A= z+HZGOH(JQ3u12uzI^25xkIK%PMGG0#)mU{sOrNT&v%aN;jOuElx+1>+AepnS(?Ujd zHC0_H+x4#GtXQ80=MWjy)l9lf!o0r`CDeO63|km2WK>sk)%En^(Z~`q>oCp5^KYCro)$8ytF`KS+M#oCXDy(G zjOuEmx?){TcFssHLh1hwv~2Pg3p3$1R2%UL3J%?+CHwcveQCFb#+wZO1`M`bZ3>Jg^cRz zq`Go7s{h(qjc6gGx;jgj8N=|a-F8Hkb=ZRzGODYK>N4MVaMoyA$f&Nau*|)UxklYR zvwO3%=Fmb$b#+r+DKpP(@2m~9kWpRTWn8AK*vL=vIqM)TWK>rV)z!UJmkiGOofa~x ztEY_1s|qyVb|-%{nZw7lkWpQ|R9A|EOVciWpnwMM#D(?UjdeXP37 zxSVx_7BZ@91T3>Q%;T$d+81xvO4l7)$f&N7s>}S21!qN>6FgtYsIF1Iu2s^Nnievu zYqaVzbLgy+w2)Cu!$*Ti{i z8#(JsTF9ub@v1BCl((0iwVM_)>NoIAfMw2=-Lm3-KJstiK}J~=nS)cg2GWslJlr`CBW%60bC~FF=sKEmal#Ow*d}g18jIyTs ztSmkY8D&j_6`NfXvJc&m&1WH_tm$f8nGf~em)&O}qpTUQ?7jGlWdr`q>9deg)=XHD z*T#oF3mIk2Qb*V#?UDsyJ_{LTeG1F2jb^bX&ClhtkWtoWzOLLp3mIk2_E~v+7Bb5E z9F|=h&-WA@me*$?qpUeTE1%CoMqL5EfMuWc%ug=lhh-MaWj>aXQCEPuu;O?bGmjY- zkK5*~H?)vZM>r2we6wqB`>EI5UxHTTD*!Uenh(of0qT4{v`7KpxR6oSm$2+Qypz7e zx`I9n8D%Yi6?q*N_gTm&>nnAHW*wIDS;#1BA*{&ju#nF}Mp=t|U4?xXGRj))vx@jE zWR$f8R^)Y9)Mp{1tffAyn9o8+t;1!oBCo>|u*^zH{e}7OKmZxF4wu76wA4omwiWYlb|hGl2Nd=GJ!+;6nxxR6n^ zu?Ci%4fA;$XAPo-jOtpey3BoVBI%k*3mMh5PIa9w`|!E5R?$L6b*=YxC6ul`w2)C< z8+={P`jr+ks%xX_GIRKsbUmbnjOyB?x@I33{)4kVm>XP&$f&N(zOFx|D+4WLRM!^O zb?CyGBF-vF3mMh56_&a0HJf?w>w$DNqJ@m=+NQc{bQ{#dS-of>qq?@kvac(Tq)j>G zp>&O>g^cRjp}Wc~{L)!#Xd$DzcEXCh=Kqnd6SR;~U0QVU(tjO2- zyXcC11wcj};kU3dc(ZP*Y~jO>NTHq zx+-1MX(6M!_NlHX+bd0Q)>>M~sIL9Ku1nH&h!!%c>j%~KaNEcaopp&8GOFukQC&x2rSOi<9O1W@ z`WAN9d|Jq;u4Ag}`MksRowb7&GOFvi8rS7-Q%9PO)3lILT_;pmhH0Pt<+}c+g^cPt z3Cq5Vm88$YJI;#nr8iV>Lq>I-Qe8idLyf zWFKcWrG<>@I-|OtJPsUkRzF(EsIIfBEAuC_w>WDWEo4;JIap?Gn9Y3ZKDMm+)wGcL zKZKnLyj9ct#%~fyL=qW8#)t+a(p*A=N@YCmaBlb3z4u(_oZBS7keST$EMz8Qrb4C+ znF*0dgb2wz{NMLo?^^GA_Bq%8d_HpbyPofQ)>?b*wbx#IpMAjVJ@DdhN*c95Dbaw} z`+;IFep;+$@cKw8(SXXufl&SoduQq0KRg(o(%D-n z(SX;7;I(qzfSrvxOexWT*GEG6a|*9I-WR+ol@bkjEdj5C7GL$CQKu;-8u0oU>NWe? z$rnBlyslSDG~o3Kcs;z~q}fKzS4uSCwG`^bE1ZkM{#vS(Xu#`J@LE{*@xDg=tdwZL zYnf2nxZPQ@aDxkr2E0B4uk%m1@m!dMWaqoN;KfLLMVTB=r(ia{zf$@B^vPh z61?7?JNFNx&R0q_;PsVI{_ODg_a9}9xMf;2176>N*ZnsS*w(0(N{I%%z7@)!9R?kHitCVQKYZZ7c{Pl)&jB0zM>xZHN zukXO?;NzC{GiooTL<3&mgV%`n4{c>sf2BkNUaP@t!t(CZjXFjt(SX+v;C0+r|12>o ztCVQqNcmAHe{J5b_0|oJijNf0KnZ^m%8$K#b@OsKQqEB?(Lf2;KneNx9gLczlxV=~ zXQASG@512qzEYwAueIRy<%nB8GitR`q5-d80>%A+&tESFuk~+oeM2$~V*Gs{xn^K|yuipd3Ui`GPQNxrH4S4+#D1yZ6wcu5)lxV=~&p@%) z&o|Eb!l)Tai3Ys>3KT)&^=k0CPASoV*WZC+uZ>rK`MgmNDkU25`X^8XmF)lPn~Pou zUN0#n8u0ozQ0&FOxnR^MN{I%%O4g?tNE1I|zjF1E8;tr%Dbave6QTUd(mOZ4*v+U7 zZYF#Bhz7jY5z3Ej{2RMQwO2|s;MEkoR-S&sE=KLIlxScCSXU^2MgP_pO%HNZJOYRY zO4uxvkZbVW+E%GILdy?TFVR2=*MkzCMZdXV)HtO?177P3)q#9?e;xkXlRFtTT`AFk zS99=s>iNtlqpns;G~l&Cs26)pnEU2qMm?mIXuzulc-{5h#HckEDJ2?c`3;4l&M2Sq zy}xSiyyBJ@4V18D@M4VL()?POAAY4?qJa`_6ezCO{+kV*XH=6}Qh*ZCfY-)C`LXw* z0ehabD0pq9lxV=K6?k1cJ^PGN-INjycx@t-pC2wwFP(8s@EW3&XuxYz@S3pXglmkd zRZ2AA)mkV&)01zS1h4az5)F85242JZ47zB&K;5a7XuzwDP`(X$)p7kmy`Yq6z-x2x zy8MX~#u@dwQlbH`wgq0zgV#Swi3Ys3DDX0Bn_FDp5Dj>3DU_cB^t!*p_{G8N0Hs6& zUhTkZ{&`EbHfpp|q5-e1f*1GRr{C=~$f&GRq5-e1!RyP5UjE9ciPnuPG%O@Y+r&KhC$9dc*PN^^H=Z0k7@B>#V_JjxwtGt!{sb z2E5usy&6Ve*VCw7loAbi?Eqc_)@|R;sQyZc2E2BJdW|SOf0j{YN{I%%b^@;@e{Hv| zQKu*+8u026D6aYKtD4<;Tj+;3C?y*3+Br~7iE6h~=W3(oDmW-!y84QlbH`y}_$P!}eW`T5q;%dC`E^K0?LklsiH{+*K*jfLCYm z;u(=qhbSc)@aiIzAA8SlHSntz!K+Fs(STQ1@ZvXx8g-^pq5-dN1zsBluUnK74S4Mf zUi=FbMm?*PXuxYfq5K?R@3BY!+cJ19RZ2AAwLf@G>)CIVQGY8X8u02al;2;^t-X1# zje^(Kx4XS88t^&*y!fphM)goiG~m@kD6Ur%`fvKo7dMz4yv8ae8t^(0yyk4R!%0Tv zloAbi9VC<=H&(WoyV$6iN{I%%dV<%-Pt1DEs5_Ms4R{?4UUx5>Q)bi)N{I%%dV$xH zKGma*`b;U&fLCv!{1~;*@U=~h`dcZ{fL9;zD%*d<0Y zL(uEEy%rgDv{Ir0uc1P58}hjEO8<+-E(~5%l@bkj4Fj*2cbR>bQ5P#E8t@uk;Pq)N;B~A}ew^<;VA?ZAJ*$*x!0R~h+Ut(Xwm0fSr9=Z>$Aed= z@{4aa>PMwS170ccy7{jiCmXfF-L6N82E0xHue1N`IK!wNloAbim4esG)DdqRb%0W$ z0k1Ofdg7O!Ul?_SQlbH`a`2jT`jN*PHC`#vfLBHE;+o%nz~0;68II){N{I%%(%{A4 z^f&5Or9=Z>l|uRX;XcprdT2I0EAqTjq5-dQ;I&Dc-^UxZOexWTS5=_6UT^*}`1uoq z*Plv>2E3{R#UlW};mW8@?%@#q{ugV&YrIf?+_?1g`iSbFlxV!z1e2w)k7)K zfLD!B{`gvT*IXCwM_ZhO+=3Bl_N zr9=Z>Iq*86^sdfE{iT#>z$-75Z^PwRuA8b0UYp(PN+25WY5=b%m;RbJYEPv^16~t_ z^5>KrKfQ8Oqxvf)8t|F~USFU0dPk#Pdw&U zqb^ZOG~hKgcyW7ue0SMxnb3y!C?y*3Itje^xfG*bQ%W@8HBBhrhO;l&`+lRoRZ2AA zbuxHWExhn|qgvkQ_OfWe>wlqM+Z#LKTabA0fct(0iMYX*4n zuap|~vQnY}uhWI{*D3EkmU_7%czvssXu#_X@cQGE`f{V1-0#XQ8t^((D1ZLSE-2e$ zemGaQRZ2AAbryK>I)G7oDJ2^4I$J0|$DZ;+)tYC5S3jjh177EV*IS!6yVj_bQlbH` zbA|GK{-J$(&o*kRQlbH`^T4a=yiS`Lb){0G0k89g@@ufK*1S5{sE3sj4S3B2uOq&H zt+i2aDJ2^4x0NcG}kK8nxjZ*Ed81UKfE^&woekXw+^> zi3Yqb2Ctrrk9otW0ZNGmye<*SpHpU(Z~WBLp>LEaB^vO$6ukHzy;0MZ5)F7=23|Y| zcrJKdtCVQK>vHh=`PH#I88uHS(SX+#LT%!jSl*oceDGSTlxV=~O7Ob2NnM3eO&)NW zi3Yr`0ppSy_6CScwHlu-(TE@?*^} zje1)t(ZJpIn}k|V`^JTbj$PML@!fXOKnZUa%CEqzJE#3oSBDaQtzM#m63&7W@)Q0> zt^1%Wf@r|&7NPtonws{*RHL?2N;KegD|j8y(DoUldMG6t@VZSXK2mt*@a7f=Rv9%~ zDbaw}Z16fGGwgb!vPy{tylxlDpJDk4_kq`#YmfZPs1uYD4S3xzl;2+$r+$9!kyQ}?Ve>IS7m16~gZejyP!iW5Mf5r9=Z>4}#aXGuJ$6)Q3um2D~1E zdUe03XOBmN*Dp$m2D}~yul(}GvyIy9VcE$gq5-c*0>uhmX@4?!?X8q(!0XXK@m}WN z<1lKVQlbH`xkC9_CBOf0Uhq0rDbaw}W8l?1_uvCYO;t)X;PrT*xL%!~`eL11gV&Wx zi3Yr$2o%?gM{J`WQA#x6^`ua3h+yix9UghssJE384R}2TUVFYe^9iGVR7y19H4nV{ zT<}+|QJXwM_Vf`Ac+Cf|=XX41gi(7dB^vO0S|~sEKC{C|QELoUN;Kg040zqM)!oa? zt5zw|fY-C&_4yG;MXhnZQlbH`1>p5zW|u?F>u#k)176PwMJdsM z*Yn`@-Gb|PH)^F)q5-cLg!1R*7rXAX$$P=8*`scMi3Yr01TS7$GHM5y1z^RvfYC6@z*PudS344S2l?UTr@f@PSbWDvz4ZGQMxCLQXu#`};KeoPZwC(zZFq}Pq5-d^;FY`L(;ba^ zK`GII*QY{l>6*CY)h<)NIw*L3sg!8IYZ-Wroq17Dqt2)EK2i172T%*9Jo$xyz`$QlbH`6~T*Z ze&6ho?;RG}@M5Jz172T(*B?jE>SxsBN{I%%z7nb%`SAJcz!|sPXVfyKL<3$c!E4N= zlTJ2jgD1GuB_*N(udhSB*sJZFwSO4ZMJdsM*Eis`_T!elj51pCT~oE~P{RUf&7j`{Dh|w|vm3HF!Gi3YrW73y%xl>7WATW{3KsN9xuZO$t-Os2eloAbi{Q+LL_v*dSsFg~I2E6_h${%0+R8^1AhHdA$HWUqb{RLk9 zThB)AtCVQK>+b@uJ%ZOrr9=Z>|A5zN6X(8S)Fh=u1780M<xk`x!yqXB*$BloluejH!ca#zhc&!6oFKuvT52JomN;KfrR49Ke zzqQwK|1+x9e7C@H$f|(STQT@G3p#wOx(6T`AFk*9Hak+AVm!q?BmDs|9%Rs=iSx zl@bkjZ3y)m_Rl3lb_!n2pLY98G~m?|ywcsyJl?2|N{I%%HWG^aA%A{odBaZ~g4YnG zL<3$MgIDio_CCX?oKm6zuU6po#?RZ_vt#hON-5ER*Cyb#;JTVUje1lm(SX;cLixTi zY{-HpU4z#TY*=vOTT!_sCAw#k&kG=Yipr=pXYwqCG4-A zl@bkjZ3A9wFWm8Bqk1VN8t~dSP+S;(`lx&G8mp9Oz-zle@m?-%)nS=Y6O|GTcx@jj z_BwvWM)P_HuS=B@4S2N=6npV+n;LbWQlbH`9RkH(ypFwV@LH&pXuxa7K(SZ5Ngv*4 z)Jmm91714`<@eXAmtXPR{=sYg1?*o^A{y}O0ABnaGoyA=N;KfLbMWF4PS|YziAEi) zlxVKLU&175od<@@}E>pLtlYO+$I0k7S_>+54TztgD8l@bkj?JgAk zBOiWK%{HM8A5=;-;I#*M@i*d)dPOPGfLBMM{QlzKz1=c+eWsLXz-v$NI+WgVXVf1` zi3Ys(3KUn2`{6FZtMzlPwxR*APJ#0M(5QWs5)F9mEtEf}@O)#x;59@k(SX-J;MMz^ zC0`mfPASoVS7)KU4`O)$e(kkY&m+n;Prq~ zq5-dN;PufN2aYuAZKXs5Ui%8=`^JmQbIVL`3FUo)S3jjh176+1Yvq*>3^XdOlxV=~fC8_+!Rt(=L<3$uz$>@%xigKLqm*dC z>p-Elan&w4rLw+Vzu@(uQlbH`gTU+TS!?DP)$#?GnP|YPr%-X<*gkk2tdwZL>tOKe zf5yc(8dav0Xuzvifmi$Bb%s)+0k7WR#eLqW2b2;Gc=ZvA{*e#glj;<_K2}OJ;MEtr z_>C?`{jQW~z^k87?bWO0=#y!wL|Kj&an52Zu{UIT>k^TUP(2Rv=m z@k)sXyas~TS@#@&h*762B^vM=Boz0_Ubk%c(R8EUR7y19 zHCU){t{k|*k4F8XlxV=~Q1BY_=!hqb+U6zK@}dE+!@z4si_5P!s;^R_0k6Zst5f-2 z7aLWjlxV4}IeTr9=Z>L&0n8n)9zV>K>&;175=d#b!7C(yH%(;PtXn zq5-erf#SW)?BwNj!1uOozNPXtrzKmGd2MzvW;A^M00yhec6ws+hy&Zq;G5)F8b z6lyohk-ZLi=(q!oDpN`{;5AC9mP#G-*2Jksouia!!0Sk%+ACGJa__B;nxm9xz-u&k zeXv3KT%$fvN;Kd#MkwFsd%icjk5MHrQxWJR8t@tmUgO(7Hq5BqloAbi9VL{X+YGw! zx#tFkzA;=W(SX;{;Kl0zMom{rG~jiNQ1p*{xXG~jhCcyXUMioP@{AJKr< zaYD6s$@J9Kbu;$~UaOQ64R{?7USFo?a=%rqbigV4S1Cb<@-j9RomQX)J&yB172m|wf#9iUSQOtN{I%%%7tq0s$KHO z?@zupJhb7bN{I%%D!^;PZPN}m>Q|*i172yN=pXs;ym#y1wehPiDjM*r1TUTg7}Y^3 z(SX-Dp?0H8o6vvXkND!&eS_BlN{I%%s=#a1K|>!kYNS%40k3MI{JC<$fVuA&Ril(> zz-v5sZSnY@3ynHUDbaw}1fe=mowy&KoWAP>qv&h6@(~Sq)qvNq7wVcDHBTwgfLE#KW8^7l2B^vOm2d_tZ3~y=F z?n;RUyiN>WobTNqjqb2>=o|f&5)F7|!K=@k^L{X@OexWTS57ED_VRUQ+u$`_Dbave z9=!OCNk-kElxV=KK`8o1KD<`BS@3#FDbaw}MDVKU(59nNUnwOT@R}r)uNSXl?;gB1 zUF1q28t|G7UVAh=`?^t`l@bkjO$ij|%j*DT!E3luq5-d|f#M^DufL3{Qc5)7byA?b zS9$Q7u9RrNYg(Y#>!Q_%H#h1^r9=Z>Ckw^L7k~KqjpKvYeM*T2y#5DX{A`a=FDNA% z@S0xWbz1ORqLgUB>lEIt{$|`6#1y zR!TJBHA5)+M?QSNa$NB0qm*dC>vZtq^OsR$l@bkjodI55zixI~Rq(1)N;KegCU}*M zTKSSu7bqnf@H$H0po6NZPX@jxcwm-@H$T@e@U&Dbaw}r9%1jD1OeoGI(wIrYnJH z!0R&b`r+2rM;o=TQlbH`%LB#v^8J*C;B|yjq5-cf0>xwR?SHNFh*1-i5)F7=DHQ!9 zA09V42CvhV5)F7=1z!B@fl&`AB^vO$y1?te;Pr`8q5-dKz>A-$Fsj*GuH2#luWNboyB zYNAr20k1oS;x^<@)#=@*9~HbVQA#x6br*Q?oAHdAqm*dC>u#awANg$haBBMa;I&98 z(SX-I;B~_u$KP+%N~J^tUiS*+=KyEjf9ZWjt@Dm6foQ<%KJfZxmm~HzYCEMw177zF z<@+K39=%aLloAbi%>l0ur|Ci*HNop+r9=Z>kAfGU zmyP;cDbaw}T=3$0WrEkX@4DKG2D}~vuZik06h zHRGeljhd*GXu#`9q5Pcf{@2UiuMJ+8DkU25dJ4SwZ3#v_q?BmDYo1VTv~TdQ$xIAh zZ!0Al@R|=^{8nwFzEMgv;Po`rYjv|PuCESWP2O`w5Dj=e175#%s>>MFK`GII*Rw($ zPMPxB*Y*?kU2}Hu8mN?Lz-s|`Ro?#40;4LF5)F7g2lYBViFux=AU~fY%Fw;`ZYC;nBfszEYwAuNMQw=afU+O!(KR6-tQ)yj}{F_c}6oZTLRN zN=ifnUJC=oUR_83aJx|*l@bkjy(|>{BcF}d`FrV@;B}}{q5-d0z-#h)pM7K0IHg1b zUatz}kFU4>clotrgVzkDL<3&0fme$uWd|5_mr|kuuSJ34ik<(&*{_@wyxvqwG~o4m zptueB**T-WRZ2AA^@dP06W42NEDJ2^4dLO)ceD~RVM!lw#XuxZ+P=3B~%fG*UZ`3zR zi3Yqr0I$Uhj+$ds^AB7bi3Yqr6v~hDpATPHerjmLU6c|Hczpz3w|qCM%BbE-i3Yru z2<7M4`#0^Lo*KN4Qc5)7^)Yz$e{0HEqoye(8u0ogc=7(){r-y&H0lPWL<3$+!Rya0 z-iu1OKq=9H*QY}H{dLEIojxkIJW=QoVHPASoV*9xJ0 z-&l3dSAXV%*8-(P172T(7vFa<>MNy0172SV)s5Pa`^HnRpRm}d4L@>CD;n@x310U; z{lDXk>a3J#!0T(FeBZdY$2WtG8l#kG!0Q|E>bCRDyNxrouE0o_~ecCL~ z8MTK}q5-d8!0XcV@gEp9LMhRJ*RN17{*?ivrYa>G@cIqB-h99O8>40^B^vPhT`2lT zKD_2IGxYgaloAbi{Q+JbJ3l$isPB{#4S4-2lyAc>4^KSVs8*l25{L%8{sOOFhqd0% zsIE$h2E6_j%J+?zmaKZgs4+^32E6_Oui6LpyxFLeloAbi{TnE*L(7BqXm>$q!yAgdH>E@aUK@f}_TjfOMz#Kw+qI-bG~m?|ynZ?1({+v7 zQz_Aa*G5A5zA@;${Dga4H)_LWuHd2pug!(>`|Ig*R;)Ctqf(*)ueRXz=pT0+Y}8>& zi3Ys35URT?N=fgF4*c}O(1sI~5)F8530^J!nbXdwbCnVec(oJCx8c8cZ*Z(pcPJ$q z@Y)Kz2ILOC$Ebx$i3Ys37RtBbmuT=rI zdmA-PDbaw}4np}hJYm=G>y5fvDbaw}j^K6to`a4w>QSXc1715py@oV@^Btq!QA#x6 z)d9SoIeOcaQ9mdp8t~d#DDH>+Iqb9<&$SNwYopIyR5akV3wV9EvieY?x+*0a@Y+?V z-6&H&>%DXPc6S*yRw>bd*KXi7>ggwD88uxg(SX@;t(Uu&7Y%srB@|8EutEL@n)Dopc176+0i{JHO z)LNxP177=r*V^TmJY-a>FWg=f4S4MbUi_{PqjpzHG~l(rP&_|uLjPU*b!F=*p>On4 zN;Kfr9lZDr!$uvilxV=~0HNBGPgDA@q4#M=7&Tcb(STPE@H+p7xr2&tF0L|z9dB^vPRBb0B$p*_)EeF3pTOQ7O@YS3jW+a@8(5>eu)8Ju9@~Or=BvUj4zV{`~7dHR=JSL<3#} zg!1!^1HWBw$%Nqbu2P}_uYut8-1+|;Wz?@qi3Yp|3Dtq>#eIIAO}=PlRJ$)-bwvYS zhk#e-9`m+0s;5$-0k6SA`Tezb<(hlAJnSASY<)U!&72E2v{)!sE$$+$afMx7VDK2S zBfyKFGdF6sQlbH`kwW=(fJ2ub(D#(!^`TOt0k2WuwR+)@n~nNODbaw}kwW=4-0Iy6 z7f%mfEmpcV6b*Qd2CosjE?jQZ4oZmzyv9Ji_h}Gv8|^wzy{!~$kUCbVwo0Ac|F#82ZTPhdvj(Z-gleVKJ2O}BX4C;nu?DH* zg`$7tbM>9y&;aL4I8G_nAe9noU8VjSd;dO0ouw3OkU9aV?$yhN7stU;<&sIAoN z)ww5KV${=0u?DF!q1IDs>9Cf!8TGGHtU;<=C}00`SFZlUs2#p>nXv|`3ZeMSz@LNf zyNtSt&tpHOSc6m=yw>c;YfzpVsuXL8D$C_u-X$fKLM?oN`)=}6rj%%)AC41hkOIG6 z^PS}DHYX{?8Z2RzP+gUJqs7jLS_$t{iZw`83$=?L8=z0J$9-YW7Hz0Sc6oZ zQ2S{K@7iIHs2^^#%7s~jR7R-HlzOerxzV^WN-5SLRWDRarMmt7@k}e>bfs8>)QRAA z+35R;_xc8h2^{`T`L28mvegvp%JM%fC zeo~4xu3gvvzNCZ-M!l!nt#f*Km1gOfM|YRLR+&n<|&BKf~uzQgWx}%o;4uNkaK^Q`4*8=xfws zrC5X1G@-a;{ONw->vY2LwOD_(i?Rl(lZD#UC6}!If}bMt)c#7b2C4rE<BDpmuw0LdK|Vlwu81GlcT}`Ngt#4=}2$QmjGhbfGradaYhjQf<_sO0fp1 zGlbe$sUfYF)ALTgUXzq!jf>6ZNatcb^GwOd&q;=!)$3``l$5-n`LG7v&l1X?LFNzs z-|TtVhvK~3blz+TeKYh zuzBT`VhvK~3FZ6q!i&c)G-|d|tU>C0p?rTHdndi3n+Ly3WDb^r$kx)CT*Nz_?KH8`o zlwu817YpV4^V{90y<*e~rC8(qw^+m3H!a~F=-Q&2MAnHSqa@NqBk}dcCF14Bx*K-9 znDY1a$_iCd!Wt~$r9y40^*Z8N26$S8D2|E6y|OJ*8OV{C}p%Z~OQo zj-d`emz12+*+=49TrN3x(43e2QhT7!h2DWe6n|Lbaz2Y9e5SSY&Mb;t6mn+u^Ac%N znf#KgVgmxZKz&j|9Ow*sJ>oLD8(A2 zZV<|ky+fv*7btK|w<*OMr0x)^rFzY~{Nxcvy{Qyykh)VSKi^n3m47GEm+)_;ScBAELbX<} z1mADK^fo_bj+)*v-UD6gB<;q=Rm`ba6(AoYMyey)C9+2cKo zTCEgoka|!ke|(Mo-`Jas+LHP|e^`UmLqcusl1o-Rc_dxH`SOlciZw_*EYy}tWh(x; z(5Q2iVhvJ{2(^b&ZRh{6g;CEc#Tuj@70S29pC_zYY}9W`u?DHRLXm-dhF#e{8rgRF z(?wZ>)MG*&qtwyU&K+Q0$0@}cq#hS)2c_12{^3VMy*ha zHAp=v)Q(C`{^+2MjN0xm7iJAoPYKmcsSl2RIcoU{O0fp1c|x^SYW=;ZO*gNHm0}H2 z^M&&JYqQly)2Yn&sLz#R4N^~oSL@AZ9br`Kzg-bngVZzN)wa|0sBiRAiZw_*D^w4y zSI-`!qc+Sa#Tuj*2(`IVUw+Z+eJkNZO0fp1=Y;YzqRiC2hZ?m^Db^tMyik5*n|krO zlZ|Tik1GOeka|I=t+j-8i~2;X5PK=b8l+wnY8$1RzCVi!>-SemDb^tMl2E=ix@`Gw z7o#p!iZw_r6pBt-^4apCi=y%H6{T2%)XPHo5^nm|p=tAK@vqB^HAuZ8lrLf5IbG;a zU$29eVhvKS3bmbNTPKXtVR_n?$4N{YtS0sYO7&R5j;y^ID}8 zYmjvWt}uKF+spl>k`mS+^@dP>zVYr)tL7OsLMhfD^`=mM-rKco?R7?- zr4(zBdP^vOhCS~=ep9qB;jK!s2C27&@@px}JAOLKsK=FJ4N~ub*K1ui7;V(MO0fp1 zcZKq0T7C2$?Tz|cDb^tMo>2Z8_PnMmFE;8QrC5X1`$G9+dAIz`?nZ6ZggOd+ScBAJ zp?qsxvZ}+eMs-z+HAsB`Uc0W}5FIIlm0}H29|}eP$YvzvG>T0D}gVe`D`F{A#LtO?KHAgAdAoU4&)n3SJHNJ!| zDa9J3mI~#Ml$ttl8l;v9<P7U4^M5})U_dNkopp+%_}ZkY1Gb2u?DHHfa>u4 z)|(o2kW#EcYNb%VKmc_2(WVT9v>NX znNqAl>RX|F32RDsjJ)P3#Tukm0kzc!2aPnZSCwK7Qr`*X=Z9@>-l)>3FO^~qQr`>Z z=ZC+|T{YdPHtV{*%o?Ot3+3xI=YWlNFsi>&tU>Arpw=#$8jYgID8(A2eiW*=9w~=+ zKlNAh$}7bhq<#|0_xamL??R_&-+LcXiZw{B5z5yq^VH~iquy4EHAwvoUVR=~`H4~A zD8(A2)&iAZIK9fK7R_ACvj(YOg!29H&qvp}&8S_JVhvKi0@e3|_oB02Z>3m+)Nep- z^}xwAne}aWtWvB&>UW{MuKmCVZ#QbPQmjGh51?+J@ORp%3zcFGQhx$<B8q5NLn<=S^{HENzxtU>B;@M^cy=5)~bTE43kYmoW}yyj0?H)@Sz z@?-Ps-!!jv*K>V?HAt1v39o4rKLSj7B^|AOZL1V(kZK~79|1-j^!6F%b(m7DL24cF z>VJ9rsO3*miZw_z1+ND-f1{Uq-Juj~kXlzLx_pw)b$=c5xltb|#TulV3FT)F9d3R0 z8>4z~2z*BN!IQmjF$g;2gG)qmGdH0njAScBAtLithjomYqNZqz4A zu?DG@Kuwx|R&>_;T`ATewUJPCB9YIQXI*i;d2QC*wLEK(+E^%GuY>kjI@YL>O0fp1 zRzP)_vGh5k#w*1dm+z?*xrqvJ6d#rFRMjSu&;PW{AJ=>x%?d8jd{~3-n+mm$Qaj#~ z+tE9fl&n>XHAuAD%2)g!r7nw9*xEyDa9J3b`#34*B!fH{c7{t zVq;fA)*!XJP;J!foZH&JWYlR&u?DFw;iIh%ymk!2B}U$9j(++P0yi1`Eh;|Udp5o zYmnMoDBtp{9y>O=N|FUoqF95}K0^7k!$#E;mzmd>>ctwQI)m3O&#gGfs3DuW60!!V zE<#b~lg}S>AB%d_!%DFRsjfo#5;nhJ>M!QizP0mW4N~2N@_l1SYNJj@U9J>sklGih z1Lv>vkWud`#Tumc6Uv{7uGzJ0wo#jJ=1RyKr1l4D&9$?l>w+0du?DH`LT#mO*zkUv zRp#}zQmjGh0Pyb<=qD;#Tumg1C@LApLs?dsT6CF z8X#12E#cM8=iX*iS}E2bH4wb^Uq~+;4JABTDb^r02&j8zKC+WhGnHZuQilL_T>oCD z8g;!=tU+opP|MEv{12ns@t~=SNeU)MjQX_=&_4;h`(*ukepcHG68VO$OjhXO| zQO7C88l*;n*IP@sTxnFTQmjGhNT4oz^O?(yIzuVeAT=7OQ(pUMmQmL$#Tul>0CmNC z{Ch)w+;~JO)*v+&s0C9p_FUDW5}XarcH6l;(=8oc(qsoPoRwN@$C zAax9QUB2;cXBxFpJJ%0cgVeD=O?j>EUZb{EiZw_b2h@@eQ$vi}ODWbMbv#f@uh=CT zdwVLy8l+M{bs6>D73MWWDb^r$f>6HCzf#2B~tOZap$}nNgQ3#Tuk4g!1(oa`NM}V}0+vO)1tOl?JaLntu_^Gv_JA z8l)=0Yn{w4@0r&krC5X1IPlu^;2wt37aiZw{pfY+*Sr8H6X`>Ur?tU;<4 zyn0-oyWglIm0}H2bwIVB@mRDblU9l~NM(T9;;?4dnpcBTtU;MB3O#$kHvKyn3@SxHe=BQYQoTP219IjM`Nx)~r*Ks;tR3m#<85 zQttzLA0Tj5y0ku3T2qrLFU@DNsrqcD++{kcx~^*%a;ZtDa_RJhf?zgXT2T<3l&#Kd zAtS!LCX>@>ZMrs-os!Hpj#|EPG|D&*Th0~f%F>3KyvsRLpU#%%t21@39b+O&>P78Q zn+~m%BB-_@pH7`vpSAW$m1oPT9irB!z<%8{aP;uQQzHhXQbPue>XYhsNS_fSOMGa| zA%jN^7&*L8zX2twR5o3o$?DE2uS%Css4T6nX~?EasOhW6)uk&^mDzN(RV#Z$nzuBU zOA|`nqM|09jYH+x(p;4Us&lFO%%pTSm8neSCuN+7Gfq`zvXe@)ZbylgkEE)pTXN^9 z&rqizw6wyh{C2LPESa7vrxta6&Z{b_D=S4dHJ#0*a@AAaex??$%}fk`=hBX%aBfm* zy#Vxgd6laV{h5vaF3-^0kGN>k2(E=}TmH(XgO&R;sp39w3~!}s(sdG%9-qsX=5wj) zx@uRjD8`3$ib_P?G%0uGDbF<26;QHMMejReLAmfqbA`*M>q=cU`RFSavm>%Tlc|vs zM|G80k)u~bbOfZxw!-%tcbHq0kCqy7%~5B{mse@UtznXJZnG3M5kR2;#iLdQYW4C7 zR$og{I~u3{lPWK*FD~M=x-V^Ykj=FAv?}hcw%)n-%wiPTdBTu91V`X&%04Nld9n( zQCJ^opkhT4I$Cp77nh}IC{j^9PWz&dRHY{uiI&#XSCv}sL`sCt6zOcW8@eScK5x*0 zEy|En9Q1_?iI6EB>G^C%`vx66l$+Iyk0uIOwItwMj5l%5!bu5MOpDur20QOT1a*yQs) z=T)BT;?n642~dIyL<&B?7pN@A%8GP(rot6p<`TRmBf9lwiBlyhC6#Tcb7CqVPb6wf zCr~gtOyfB1r*V0ATGFu2JETk)3Z>Gp9>0ZIxCGa|RXd)}TacxmV>DUHD2ExJ@nT^# zHil!@`)AXs^oewY$NDf7c##`ql8vHeudEF{&o@<^Sr}t9v%;#vIOi=V(sY66f6>4} zv$DLO*+sd;VaQ5ma^r-dsf3>sNR$q}x^%gpV@PpboaSOWos(^vJGmwgTd7*#k0ex= zcEho3gvNDArM0dP$yT0PkB-qO!iO6TNi=zN(-4tWR8xPe%urvWcGJ0z$d=6l zWs_%+DH`hSoGf9PLKcau+^X$`76}tz$%wc3=%Z7ON%CnulZGN#noV9qB2T6A>AJj| zph}p#Mv<^9O`%+hZ9|qTS)QV`DclV%hWn2C6$|}HQJG7nCs*fuX>uvF@BGo^yc2-UxRQdyLw zfBU4;c--|HJ~3Apfqug$W-IbJH`embrhVeZL%)%#b-h-$8}gBoHXt!SHL)qR&();U z^*({FS1U5LHsRFmacP=ncI_g14AG9LK9Rne!V|SPMbD&Bs5*>58l?Zxgw3CVF<4yd z&(Bk;(>3m3=kAxTb5jTXI~+!Sn~pA5(Ma1+$K$mpb5p42)za}(TTAtKVhDw)fLv|J zpk7w3$7glt{Zf9~)6hNqSxuLzKFsrp@Hba2Y*E?5-<1(TH4X7>X$2p!e8t+}<`;3K z^M2a)CJH;sLVns))lgZf>lQTQN@w-bp_DIX6_YD$$x|+^<*&-RiJD4(X zpJk=FG?m>=RfN?@G|8!LpeuW_O}Z|i4oX91rB32m zSz4c`IUo`KT97ts`3lOzvccCE5~!f@swSfgCcM#52b;X?!dKvQmUG8erarzrm8~#% z$(FxdmyP;Nu9}xS=+Xq@afO_a#}>H;l43+t}?aV>#6rqR(j^At%@``W!$v^ZPGPCZFOCg9;SJ8!sT9As%tv5S&<$` zr%YF7I&`?pm*lB(*shvVpK&Rr+47uDVCm2Dj8-FCo-cKUrYQaC zM!-tGyhu-Wrz*dpgCiGHic-n-kiV)*YiJ!PLkpA{y$+JUIK)Y!nV2r`Qkmja8{4$M z-1tVCuBho6;I5TH(+xGk^cTI(wcTGh#ry@i(r>&^wv~_^ENS$a4i{_(HbIz}UR{o*_ zb}LfzZiyh(oTg3wDoD1_OhVqtZ?KKqHxfy-LpQ5(x46pW9aWjOqAJstPRpcrw28&= zQD2g1J5iWYx(=kpIy#&B9VJUql3k5IFT0A;2{}W{=qSvUmnKk&xLxNs?kJd5GM_{y z$sA~6T9xUd6FJ@k66hKQXuVaAVt*m2?Oe%MglWFfQ<$omrYhWNN(jvtVi62+TBs<< z9RzfGrnPT3Hc9RpNY%JWBh3}^u9@U7w|Du^RAQe| z_|u$h_`V<;{?zDGtRQ>_jJE6)=yTL_k0;|zI~7@Kl$RvO+j>HBVm)`rMmRyp7A=O1 zm)A@0-S6H65<^yq57KA zI-ZEqtq#9JT$ydi<>^jtH1YMJTzyrVt{kfC!i31DxaHU)31ykdv7Rm@(pfh#%++Qx zwAd2XG~71zk`UoD58Ve-Jr8!U)eW|2PC!@cu@<*!YJmY7z@vb)Vc=!UiYl9yyGx~i z-68B!6Puh>iWXV38Fzr1D2~$lQND}oQv9dRS4&IF$~4M<#!+5z%DdCCB+0frNl8jd zSlP9bp=`(LyfQ~u7p})S#D}==xU5}VZnWmeFBXR&-55*-^@5`sa-x4%W^x&VvZVyOg8MlG2sbAudf zc7us zL@jznx~968?@~%u91l71hFcCV6uG!76ZO0}kA`fut#tB+m6W%vXkjZZ83#iNa+&f8 z)G5Lew{A!07#|4_-)I1@E;Pu?*m`qEBkp-3sk%n^@TP+@tWgz)sW_ExJwelQ{vL^L z(>R#+bFxT6dp$N%l`Oo)@mxAw+$l_Rn0z%I9U)GSQPfuR13O`PEkttkgoT@{h7hlh zmD2e)J=xuQ60?w6JX)FN=Om~dP^@7;VEAIVuz$3}M|1V`6a_tURyr`L|Duw=|s+3x!suV3&RSQE^Ll~+`(PCBg;ex7C!hcjX zRZrtXqOuZ8R#$@mQDI3hQe}x3t8|Ab;~r`rt@a7)^Kd5A)1ZrifkN6O{$jpa=7m&Elkg0 z>cXtIp&ObFb#?v}qfxp~Ne_Y02<&!=&zHu?aAPtzrLNoxEn9KfB*15ODjbciu9+Eu zc6WhNoy`bC{eXucdeSLWy|hxUKHVc!Ze;S2*fPk-7FE7hd_#kun4keeM@#oyDxYDk zE>SF;k>VKLpOx&R7~Qe=4{ynXu9BgfOC+T_!;ojfl4-u7qMYTt#Lth3Rx%IWFKoGe zEhl4>g|dq*T}RWuhO|@CbR$C#vqpJpve8x}hjRtP9UlqFeQJLXC`2rWii|#3BWZjS zi0}X8YwO+Je0p%Sy0$(wD4idfuQ-JEH?6FM(11F68ZA>-OMP&3X||f54jH4VBh&dJ zseLrHpWJ^LNH@B?zCRu3`E(o^NHgDlGI4NzygiT~IF3bBF5c{ur|DN29rZb%UqQlv z$#D{Oj^XrFz)-q>@53W#WaGTz3RDcM#1L-{cTcMhn5@b%{J?P}lR`sNQDeC6!8A9? z(seprXDK|Qv^tj_Fqxm5wjdS$aJtC%afP@}R_{sVNo zxOk=`+%P}VkMLT|F*ME(%w$K>fnSpzRyLl>s3{})2EP71U~+YSxOQAu{ZalF9)_u1 zS=)~;kubsa$5HO==CUAdKR3KW*eR9vU5B(zG`K>GM3IqlGoAN%GB%1IUkNTw$FV*+ z?nRh-cxD{$9W~5k3wzLL_dsYLey~<^)e!A;@1qgvG%DiM!>j2km992JVc4gS&aYkY zaJprzGJo_^cXrk7qvQLK(%b~i!?jP?j~?kq>(g90-(o}PQZ?STMi`YDHFYH2{iv?2 zEU5l|DgN32RT)exSA_nneYkIIap707<{bpjNC_azT2Wpb7D@R$sc{G?OLw%J54-sPD_y1)$2 zQPNN!zT^-dkfOyqIycc%XMA>`=L%?Q;>uh}ZOGSS{Nz+6JtIyF4Mh_8VS!3FCy-}< zIJ+nlenLndOv^R6H5Wen>$PNEIBmL%-RQcO9z~@KZuusN^K{$vy#l_OUQv(`n$_w2 zQ>dDaf2>8Ct}a8Ppj>OE^8VSb_`4A5r(cecDO0Ah(oZ1h0hI9E2tSx&8*~w78#J@G z4S66XZ1Phmwh;@tu|rA<9`vZNCslodd)}i+!bBQtqZXn`F5I=wzpC%Ms=Dd?%~AWG9( zLn>46*9NJO8b~~QqK~>HvvbQW^hA7vTi|mR@<0nMYn9T?%gSn9%HSkhc_2@E)+ChI zZPM)|JdTd@>ZFNWj*jpWoiUEn5KSKHF7 z9-e?Z)4ZFb*V9<(0&ebQM=38ajlsEE9 zJYNIpw8wZ`itF;w6)CqtMV_cE|H-#pbW_)UoJ^LR>`z)%@*9#nb#xz))id1~GqTS} zzCNS_$H^(skFI>{LV!xg3txOzPzj$K5+M?C@kNx&m*w**I#m(r|Z?e>2Y+C>a@H)fZE=+ z7#cx0zS6~FaW+)KVf^?(X)S#Nf)+q63rZPDU*Z^2O?|X(9M9V1j+BR~d~6G4=0I@H z4WcQ2Iy*w{c38HS&g#ZfN6^AynkHQNOspZ-p|Z$Kmj+bDs4%=aOwLL)WgSfHWUUKN zwmDHZ2T~^;R5PW%D%MC#6F0nia=PY_wA?juMfNE}=#rG0VLi5*16WJ@0% zHnNMlYeHXsW{a}xOSiI|4<&>qJ_Pr6oOD<^Ti2&zqTE?51R*=AWMNhkE}D&`Ko%^c zQjX$F%fsEbI)Z7O=36=^SqzZt{2&a~8d=3BzYrH~9OEf5Y}1|^IeJik8s`IKnT;M~ zQI}PSa7M|)D}et`!pMfQdhP_8`4BoPi=#zS{(sghhV$2oYWRY&ux74+upbg7pfsP6 z@ow)W29+q7Ah`HZYE<1Q`rnLCEmD~%52y&neFkbif-bu0D{S-yQr9TbANmhWRB>b+ z2@PXWKE8+Wq>VamblCTE3;joq8rVIbp?_(jT2@WZnDg_b=D@&E8p21q`$?`GJp1Aa zMR(Q7c0GLs1OoC6kqD44z(w1Xf#>Pl8JvgT@g}0b+JlU8VvG) zUzr)@2V$ML=Ato)m)WF)${;jql;`<&^8~t+9~+hD`1)X?m$*$g7ltfdLRM}A(`3Xq zw9xc*6?#ylAj;n@q&vwLm$snB2{c{HaBLi(#rP3aYc`rB%cRjw2|a-I3N7HnJd$(U z;CY2xqaUpkWa%regX50qv3~S*p0K}saM18!Ba7r!WnX>vg>oUDMPZ~|=Uv)Ve5}yZ zntaPpBksC^_Fpx94X+}VuCN1#FDvOGf-Ie9re>&>=+0iL$Nb~yjU;=Nh68?%JSJ(R zrl;zq4C0oPuUBbl=o|QM(2{RUXn8`j$Tujsg#4{(`qD2wPUg!KCDCoY@(IP$ygJzU z08KsJ7x#UDR$pl*%rk!JKgGgj^z~p@?13btyS=o;Hjy44b1|Hl!l6db-SB$4zfD1U ziq!K_L342%R;uXo0MBbFx|5y^ai5auCM~5@RnsFYp(Xj=1vMr=ACoFePvwhMP2*d6 z{9*=qD47QKTq+#$<@5-4mL7ZZ(p;v7jvk7J%BKEn?04n!x&5ayF{rtvf%;PVhM1O8 zF7iWrCQn4xqN!9rx`=a&Y~i&Isnnp6hsXj}wjaMAf-Wy)5*nE4!Y|nf(t1&H zgwrWIew9iAUpzg^Ao+Tg$h#mZi6zTr&0p*VE%6ZU->=em4vi_|=5I_B?|{ZME}yQl z8ynNOeEwS#wFw9L@dPZ{n`$--3hL$=Y^8=3bD4I*rA) zjlDc41{>8x8SzLi7r*>YnFz+X>(}l~iYcx)7fW%BJF6CBiltz|DX7#j#0RmEG%dFG z@L*vvDS`1VUAk$UAbhk?%r{31#S)Fgw9QW=Q&nSe9F4?qYdOjr<1rAw!Y2kG9s|*f zdt!YYj^tEqx>9ma!_(YA-l!E}MUxxB_3bi^&_1;g_V1r41Vc)rkgSk33cA!nSjSjj zBs&djbrb1@8OWo5g|;qSD5q0M+&9!kqJ?69UclYGlQ$9-x+DdOLo@77d#jP=5f0R1 z5|>LjLW@aYnh^Dy$f&S7?nF?~#0BREYRazBt}k>O5bCNU%8ZolgUY3ZlTM&my%?Zl7#rR zKuNj8lEUPLoRruFlhW`ar)0*KkVN7)JtZkfOM?En99mMM5v{7AE(PiF0VH)OP{dP# z;tH3CJMkA+xIF$#5g&s8m4`b>iz{3n?(|t);mYw}ie!V*D`@HqTG0iQQunA;a(8Jo zDW-MT=mlI!D3RI-Tu?5>VE!Ww;U2HRxl$q_=^=M(lI3#2Vp5(4@E2K%5g{!J$_v4g zgbOC6T(yGCT`(ziUlB@XtI?!bnvB0ON6(+-iW<lLE&_l}`#>4$%Re6u2DzL*R0V4&UNA z{D&af2-3jr4ROg16DK7xI_3G7$R)8jsS%9NKLuS)IhWQ5$hSlr@sp$&3$Lq-0Sls( z&(iWPI}}?O<;hpwAytryu)GQa2^T^c-w=e#=G^P^;%hs5jUMKw@79Z@_Wry$(>NO0 zTbOU(=*#vZi{k0Y4BSikl9)!5Vs2`CWnU6Xq$XiHXUmKIl30jGfIq+U?W(vbFkS?3 z^E(?QJmBK-@PH}txPU2+akBzUaZIt4cn~SZxX}Ys;$Z_*oR1qXFvT(5+{$hAu(NNn zVkwT{2WxDi}X~&G#~W-T4e-u_8i# zp1YXX#S?(OG%~6%=k22jv0p^DS4t+hB7#P;h~GWgNG2e04ey$YOX!_I6nET4MJ^}T zQyUezoc(d$GDmB~E)`$gkcuvnC<4<=dw!jas+nl#SW zVnu|Be7#n}agTz8c&LN~N4T*K5*$$^!4dnp+bQ~z)u>EG5*)FAV?>dJsM`FE4gS7p z+&&g7BINh3M!VB%C7hJ=yIqr7i4_qR%U53q8&6fl&%G8lnX^WddFQ7Vlz~H$j30pW zuaJ#0g>a$VzhSnJ2q}#~xO%KM0&z-l$i0iUxR<6Cf|RwsueK13(i#Pm>o{6eMGF>m zAep}+)R?QJMXo%9<2Tzz`Vexx+YR+07?1xU=#*|K55f4z48cO{AP4XLjZ2kGX%vdhc>aQ5Tt;_>FP82ay7Bb5RT}4A z;}lDe3(wENMSVq{eZ%9%-Xq*SR>rR(ri3WZIECJX7v-KzX#}G07&ZcNN+VG67_nj} zMuMEa205|_A=k3HhL5^L&?$8vp)wJS%d2l)j;#HQm^-)(-B~OI<5OD*I;A^%g`iU= zf|0d<$#Y~KLUFA_&}Haq9fEPKLon{HAsAW1mj&ZJn@ET=Pb9?_i6lq4&YnneRM8|y zxzV5~RW!*_?vku1RWzxvDfD&N(Z0~!e1bD_jh{%0_e&zlQLg(Xl8Q8-_lk;#@8*t* zmq;i;==Q9xJHeczKGz19K1?ei!c@9!ua@9QWW@8>A&)b2=#!cJWf zc4~J76sQZrPVIVrfw~~<)NYt7P#1)g%}38#$NNw+kXTaapE1|But&R6m}GK6`y>Sg zEtM1$v{_Q%a)??lDJW>iq`>76wP;e1Y=k05nbbbS3kn@l3Q8MN3W^z03d$E!k_B_$ zUFFBK3if84R2-9UJ;Q~Q+okfQe4pq_868Y6U!UxX+Gw()-9TC-b4NFtoGdm?aOqBe zK}~%)DW?^GxP+7fxL{Js&%yJn|C8N2#FK#kX@DdU;t{}K94A|Pr*Nc>f++QGHbQNP zqL{u~pua4D@+gi#@>vinM!|C+?&~7*vjb7CaWu-){R}{)(?BGt$&7<}sN0SN@&;FV z<9d`&6htY#)S6$;j#3{)se5xfDm8{e!HZ=Hvt%ifytOx(r3)tWEF#s{!pC!O%2i(v zkGc9F=IY-akJ=DLv468XibE8|{=M@k4p9`_Tj^0A#S!RA3p(yK_6S6A1njN$2t;uN zBqw=|DgseF0%#UOzcxp!1MbE45t2+vLi~#9q+DV}gzjazMbw%QA@cU}E{&9_NMaE* zQ8Et|DO~*ecq|>HL>|;CbddPB$tM|0NFx44)=477lR*5w{3HZv5hyiEQh?}1@6lchwTdEmoEpJ$@gP4P5iwB^rTQBdDD^><>JMC?)CW;YZzOZS zPk~|!qnut@R!nYTl-qA-B(wKFp%EWk+&RN zsHccEniSJ3b@AC&m$Qo|#krzljTVUG9|TDjT}tDB7bI>pSw z&NoU(AoQwadf{rKnKhV@(kvUM^PI7ZC4}x<_xKe77F#?4gdc98>L{{ z#D$1{5+>mkrzCvhAB{;+kd(mqJJ0+hFbO1ti^%y0Vv5KmTtuFDvO+3IrV=h9=O30S zlDmY9$Z0ewCYNwRu3rl)QrkGCI8>z03KgriCKai9oKoDWNc|Nm)&!bVq!r?ngp+$Q ztp3zaqOCD4Ie1}ea_GX;R21DFx9c{1|WA-Om%Ey zsg8BeJ~YNQmiiw%hz}Hchg-6f$0ZArL^ee7mywF^=tSawp&@_Q0K8l$M{%5e(70x> z^BMRAmCNb>Q@OnC-1J|0{eLQ#m+kof%Ip7AB^t}!)hpi2m{t^|k|gfTN%xUEi9Rja zsxDifnrux>b*#-0Fx9b*rA7s|w;RWOF^(q1@_nTOu||uErGMaEN-=QXeDc+eD^w&o zu2GR>N4xG)B-zo8CP&5QHzogHYgf7)$Bm@@Fn$1bvni6ly6(HWYAwyg&iv_Ul1+PB2R=fuz+nM_IdCLi;IM$e95@~?a9BVHb|ssr@yMHF z5T+rUSS${Ep%!P3+i5)J$5wor-)TJN=l1EGe5di4pWDN8{7&N;oguCzcR%;iWey_w zuNk7~QgiWm;dB&=b!`-z;l(I6HDnZ<&R`Uq?Kg@Uo11GCGdA0zQOww0F=O*r0v@(k z>?ni`)K!QqWX5bPg0Y3`6*D%Qf`{!Dhr|es28xk_8HDl)K)3U)VjtaKNtFZ>fE&Vg z;DAh9a6qQA91v5n!W@t9yfr(6L3SlPwb=kY{cF9$R7jL z3gQ!Cl}NnjNQWQ;ls4EWmzVeZ0#5*+1REOq#Ydme26M0Q_$c*gBXHj=AOx%-#KAHb z8^LG-KlCV$r>gfKjTVHU9KtI>#xyPWk@1?JQI}cjA*=IO ztXwgFV0;BINaS-Ku0&R`kFGfBDkO!1_(Zs=#s2Oh;0CP$HyF@UMXsPmVF;Cj3%^@w)TO8_xXZw89Z_*1 zTL6!IMzQG&MloZvu^Yvv4;aOa&E{$pGqzXE*leaAwpYy9Y@QyrSIpR)1$)?Dv8%rD zgP?9N%wWwhGo6Jbnw}LkcPSY_52<*yajC~$Jw6&F@V+5oW#GLz_p1{J;MOCq@$j0! zg)S;RZt62NxJX#Ms_IIj$~}ux5H?uDs1(GrD+OU1 z!?YB{5KKRI=v+nV0D>gWRhOEC0451n`Htq z-dkIAK5V8&Yqm+^8dXZ*45@nsky$6hO#AbptRgl;A8J~o4`o_5N*~I!97cU8)6PMe zmZRq!?HrV8IhM}R&OuEjPj|A2Aw<56X}FcR0YyweF&oriOI;xZOJgAfOKBklOK%|r z=Du2;7seEzN?1>=UWPrB?p77*4 zcG>ZwXYxSyo+lX^6a zJ2?zvI_da9x-g9Cq|*oK!Z4lu8%obgxEQN0ES0A}BcxtseYvS~>{xWPcYu4}$8Y|wh30Ga8C6aKV2 z!J$Bxa)-F3+P?NQ31*#(+z>9OTH<=`BqoB=YW4Etfq8K9>g<0)@X zKPJp16rW)~K8dH|af^n0xXg4&MDh^ADHhhuJjeqW4ihnpn~}f*nPhS`Q9LAWHrXr= zpo`gi2%pPM0$#JBcgRitX0eGb4*iIc#5Yn0V%ewzvrN>1F`Y~v9GDp+=x~=INk~Y3 zINfa4#85&^B06Qmp+i2cZW0EdEUEOR0Niq?0G#Qi(*)p5KM$V~9!EV7)k6g#6?Jx008>#e4N!>LGK6J|mz7G)eWL5^U*;$K%Z|^?{bn z`d}7SMphqeS*{OeYH8R$*s>-c%+%7@e6VFpKA5ScDf!^A%J8xo-ZGz2*#tC6#jyvz zDM@5r7NHBWbmamqO}RizPcG2Xk_&W28rA=@wF+D2P2!QBAOlc~{ASET3w z<4!yKISl1qn}bfKvP8R@;p_?g`S>>jiAVxZZ5H;cyH&Vx<$xSd2}ZkvyBJBh~JEle)h`MO;z61$P;wPj^T9z`W{@;;R^EY((<{7_&^oJNMHY_g3GTd@Q6{R-x7? zQ3&XglHJTU!+{2gEy>sjlqQMZLa=QJE%Z@rLkglWmnoT+3Hv1jpJ4F${pR`MhrZO`HyLK6 z@hpZto;>ubG(`#`oMNFXJHbjLew5{i`|chdaS@~4w*fIH-UfwLiQgHa5aB2%7%Y68 zscu`yV2v8pm5!`#G6PeQ;A6oS2sQV41{#OGd@a)nyBdwfoK9geuPK3B=5-2-d42kj zT&J*@*X4(Jox-vdFHF$t7E|LMaEa7aHT$%~LL+gDrg3!^jm0^M#@wc9;0bOg(U=={ zQBSmig*%NW`AuWZ3Gr4Z@jlWGMgUpp(y2cTY>T<=0ijT17*88`AgBZO$Z6h?;?Zh1LQaj8urOgc4xdjq!o#weeZ>ifXTE9AF#rqVga8Z1 zov%WG1#?2M1!FoDhAmh~$Xnc0`2F2Y{g_O_xIz_I9HJokLoF6)C>cO*1&Gq-kC# zPFl`$I)%l&rrF{$uTxld17D^|{9ZiWC~e7&Fmp&OmU8AfG{@)X&>V%$p)oh5u{kv6 zwwjhM_j7pC%TMA-e$(>hDSjvMB)@63;S|49I#hwrasD~=Zg zF<2;NAVD0*I)XT|adIV#s-KZY1@j=E*+%{6{o(dEK{DSe1BJy=vCXK;K;m|tKw?hQ zZmHQYkJAaHxu(rF$W3>YgE$mMxzn8n#OXu>;&hz>k(M}hssUjdH3k_FrU^lqMycDQ z2|;X~z2VdHqj*JSPsy>P1`p~e!-G32@!*bPJh-DC4?Zva&4W7UF2_xin4k;78`R2i zXXur)dGH0{iN7U$0j0kkzo%kK;tV|wh{c{f!2xoeGeD-CqI{5Y2FR3CJP%UN0IfzQ z*nSdIe%>&gnfT8l+2YTlu?nV!%%a&%nnh!7Q{!gQnAx|qf?5D-j3Vr0S7&|>$k7bM3{_GJ@jceRBa@_ zOL}lF4F^;wnlT_hM1I(Tg_@GIH=ob4R!tE5f(Jnd_No^IFM)y@6)mp8@j zd>E3vsv$d?D#(AXdJSx{qRX2d>N*pE?I#6F+Ne*!qe>sXGMSn4LS&}4m3hFXu4&omgW0mK96Bl(G8P%?{Hf`&AmvN$0}W%8EKkqiru9UZu{(on;T`t5?N8%c5r2 z*DdUV$o85Jrg#6e-QeZfhg;DNLWiOo{;i19GD_kmOUr(oA%1_wssdg3VG`}eF{#Tm znIYo)jKtQOB1)Pl&Ev95i!KuDK&Cv;Pfw5AYsfa-h`TiR0i5Lp?OV^yM`>gZoj z@7s&p!*_B9T>46_dVTtKd=q>2+Y4wxi;+dyuG*z((MRaZB`@luW-R->&4nS=ucCTW zO3UMtEJ}-_F5@`2m6I0Ri;byc7@xE=(dTWK)=kq>4PROHQ>Ucm_Tucv)tUXD(G;PA z8a>kYRg@mSOT$#{e?!u@y%KGti)|o&Vz^G0BB0dqP&et{(%)6qlvz3KYHd6N^N&@5ru5mEHEjvaxla;lH?skNyBloD7JztL;{Jt9fUa zI5H4L*U`0ua{G!GaGw}eqj1blQgr;7AyV~G{T*L>3fsbJA{^iwPSvXhoTa;Qm&8>X z$tdyqcE{g$<6HNiC~iz8uTxJZWwgt8Njp?cBv~#-=dvBLvPd{S6TzZw)lpH8aR>Gh zuMscXZIVP82AQGlDz+oERFp+y-PBo9vx``iOD!6s zp=hh7OULMrEAv9*`K9WSRa>X^I2IV6*1B+)fgf&+X`a9mK^|YH9-pOYvfZ7RP)}R6JHf2L|JxTW zRukoQK6G_IM28ogF#26sJMQ>Xa2e(S*g5c3(yL(6l=Y6VWq1)jJXNk~nIs zB=72u1g@E6v4S>g>b%>~UY=15=z%8}~k+_4wjhuK)HBGcjRjzk^u|r}u zkWQbpK6g=uerwV`j^4l1tXJGha6*nU%DWb!VaG}}373RO(`cysH1EbL5}%=q+(mTp zD-xlLr~7+^i>c^N6g?il|N8cID$+6+QI*#syDX!d1MZRl9@uo^uj@699R2oU{Yiva z)8OvuOX7RnmY^dc?psmu^2UX~eS22G!>bFG#!^S^(2hx!ClM+GzZ1GkqI?$*LyPeb zLf4XJNo}8oMhx2{2S0IT76?V6%a5_JaVxeMDk<2DBVbS8_BdYgf&`3CDPh99{ezpT z$M3(}Ba_)5J_5_MYO{14%PNl$7igfn+V5|_;rJPu5EVV(*q~Q-zV^#-k-CQ`23W83 zK0u9&w>&)YwY|%6FU%CUm_y^35h+QQ;0t|udP=4^h`VXt*W-Y3f<=p zBy_LFEX9D-rpA!nMEwJ;18pxP!Tl3r&x!i_s7T^6?qZB_Kd|HdcZjn(>Z?5KF`kma zp*giM3cAKb%-!&LF^Nr+4*?bT3kh9j19rX5MkjGsH2Kg+_sa&}N4v79+CJr>^&U(Y zW)zJWh_kwD;z$f)*i~8Pj@)3^l}HXM_MnMqsgiX!q)6tWfEW{OblcRGD+Zv%D&8P} z-o^DGRK^t3-->cw_Fa)AyXcP_UDjk{8NKL&Rob;_-(aR}Slk#TX;U-l#ZW zIGiNDQdI0{g7dbN(RS|4TyG5O9#e`tAZhqc|Yb8Hul|K!~&8)NA6(pP)fw~e^>PwiLr)t z;978v4VELtIb2Cfixd(lZ-uhf60q)&?20z6>gxZ@BP*gdPm%#su88ZHBqmS%ww@ zm|~$&F~U?Zt=qQ5x|Ouer5t!fzFL(@7IQX4$?B57f;%#WqKkLYcT8HB1A;K4BHN`g zrO7oBEHTHbW|mh~TkVGE?ayaY=w#MnEi}%)?7#eL%=bgmM)L}qH&OfGUPHURALANA&fyIY-9FR3&uAr-8-o`w!KSXW2zNuTR6G2tALANAoGDLlc8RmdhC8~r+ypQOn zf^PD$z$zhC=ut0Liy=H4#x6rXM@Rn2``}y)i5eD|;b&#nWF1ePkgT5QK4d6$()D@H zb3K}H392R0E_PTO&-!wVjyYLfx4l+9gB7c`Z}PGPP!*onZJ8r2!RRTh)WR>y4)be7 z&a?kk_^W1!`>qM;_tJyipyjMZ3O@@&> zExhjI5@bD+7NJ`kTQMJ^A+3uZ#yF{H346W6O0O_@!O)mEj7Wj836Wwifp2uh1W-(R zP8eAoWypfZ4y%D+SlgsamWK2yjT5ZBkRW2kxwutL+N51S?AjW^%3QD9?2;UyF2Ws!W^flyqR`IsCK)Rl zuV_yu-Cil!8lKRLog#1-v!Y6KUl#}|;{Yj9jNK1ou7oiyOLpCW6;ATvW#x{Mm|)~E zK%t{r$cDe?)97^yLrzB2hlS*o7PaVmEWBZmMlsAy0rEJiuol$j(DRc_zUBk89t#zN zO^UURV#sOnu(%a{s0XQaC05+Ao+*Sk`*z5)qU+#LC=bl6HU*O1K5h`nuZW|e_4BIh zng*+kv^{6mc0QElfb|^FA7XT|V13aeF&%}8V%N@st&*n6u`@v`*3-MyuG@8W*@`|a zjj6&=l}*_+!qW_UTDN%#0IkhEdP|*V1;jh;ZVq|iD1cWz_ca3ts9J+yDlm6v~0CRQ?eS!prnH;&a0su>%Pjw zjvFREQ}A2C=b=GfRxhKUK5&0kX`M@BK;i{MhV_O@4uEn3v z;t!+~ag5FUIEHfL7y&Ad5eVY=P5gndK4Je;{4*DS6q6{cEJhV^K8(4@bAd@yOZpp| zqLQx&&!R}|s^M??bwmF|7ij=hg`_%;{}O+YAQP$H!(WM&?7k@bwoj-^tUpgoRTUIK zrc#8=>7Nh}(=1V?{F`L4IUu|U1kpW$Of?ni&PR-|*leMCm&An+s5%h|;utwm9K&ql zn0~r{s7f%ashh0E(8G8iT(bZBBMHS!9Hnx(I>1kh%E zQLjP}eTeTNQ*l%I09`*vLCD6gEgP z;s|#X>E1irIh``@PVrfkmi_9<|+X$bw z>BI~9>R5GkJ4WJbA&ra)-Y&)bEBgBF?T0R|kP3Gh=7qoE6Ej^rriGZ#W(Z-!XI)%1 zWjbO@PHfnl&B2~*U3M5vz8!w3=BzLX_8FFn@^M=85>sNSdNOk|BeE)yE|8&<-H^v? zLlYrRI=|tOlUOV0tBsM>_t2+6B!=(eUlcT+#lQ5=z4)m^3i$$8Kq3+1H-V$W@Mgd+ zWR^Bc3pu0Pp{3te-}SUTxu5*cj4p`1B<6b z(TKkdZ-}vP)a?qimn!{}z=SI)O@Ak-%H39&p*ENR#2E%@LBe6p(mi=Kkxi0FewKFy z2&qWTf>u~*%&Hi1k+!R3(6K^5h%a}C7}_D7A$N>r%eZf_%T96=n$3(1$5ywp^A)yc zi+pVGKQWYkSQypQVnx5iytJUPKS&>{pL)e>3{vJ@-6i8lDgEDLHW^1?;0WkMoi$q( z*lEUY@wlVJK-Ou6xKIq`-Z)jK%|_P=W2><%)nIp4D1w}kjGa7^{O}I}(6&z~UrB3| zEeF)9eF39jRb&XHsYtBl$Us#PCtV{uv_SbAHR|5Sx=xl1F~%pVX4d#mNltD>4cDUS zc!{i~9k8N+8Sq>+A9GG!4GoosLR6EWxe~{P`V28lNNx3W$P}zj4h@Gk2V*!?%ND{5 zaZ;ceaE6mArFdtDn#JxG2P|R?;cb&>db{35dgK7u?@ES(jw{G4=AaCRlskoJDyD3d zdYgU%M!|R~FzT2kYDu$Lnb)=WQ@a2b@;WSy zHW>3sCp-ob(v*Obnu(Wgj~)OCKqjKbe@d46rovH{Ii4KsNuUK!C5=*5)BtJbI;`|2|(`u2)z3_r=+#_2%wweZM+f-{0!;OCi=q|90xV zzoqtQ_}zv0?~`n$QGR!W7V6)}upaJ@_vU{Ze@1Wj_h;*?`wN!h?99NwUa#&C_qSI! zXJ?m6uNz0M&DnY?vf8mZSS?Q&8l)1kh jw8^a|^y#-mn|fxF#r^G|OYlBpJ6-4eukSk7xlZbRyVG6u>$~cC zYVDqGe7J|TBs;ff-A*CiA-qdubWC_m*N8Chc;{(&=a{grENN(6ozCwsovysNW~=c* zO&h3b12t`+rVZ4zf&cs4Kx}MgygoB5EI!?m9%nGd>-9Q0Dm*bUEya*zO4Y|kyCak1 zk`3u*OM*Vk6&I0~fM75ouG|$Fm0*ZZPfATr)aMsvTiwx#ruY;~e4;_0U0^Ntj7~~P zicdD^d-qF@?d*`VqF0LN{$pNshOe5>xf@NlBh6 zsTm2TWOH(|zQ9`Uk*V2iGA3G5^~3VV*xe#&iD`zEf$oE@Vkof{T4OsGmBE@K zJMy&^6qSte=zej@`eXwY4Q;vD>d4h7S~6#!9RXJ!|b$(Q?9qe-8h zkY-4VGaK{{DZ9~mg^>}Nxz@rQYEN*IV%4PRF?uAMQz2jg0M_Y;%~crH;gslA;oK>C*IRCS!cE(PYq%Du|8p=zgsI=i_2mpNu!7GSHLV zS7)FDrzWSwXBhRvvNH2ZGmDEx+e$Kv?3s?yMewA_-MhTno<*M)my&8UBq!&cKkSd6k%4J1-oIVayM!eBv)LZ2i zSX5&3Xhms~jGX9TE@jDXjd*>0g2@;cZ!xnbpP6sNxP!|wq)SUnvNNqLGBn;yCs}mj zdAT{k9G{qip*S0PdtMsG+>s0?V1{xfqF-=jb&DnHQ#0Zd&1OrSUTWLLwi3H!P9~f@ zGB)bTGHZ^-S^`hIuS(X(rJCbSDJf}e(8`rO;cO6;d02T&n0Ls}cVBPP#~b2JCR1FJ zyMv%{Qsw0_6&m?)LdO_na5O9{2~}>+D|L&c>eG@9$tf7e5!mW(#R+k+*km>*CHVH1B8N3U)0$7K0f&u^hR!;1k0l9-`sDNkbB5WNq?hzr z1;EE>)mFPbuQ1OsCevN-uJR^eT(lUI%?YU{{ZmC{s2{o1B{s=%r9}lcM{Zu>FqGHB zSqT`0Qd2BR7Iahe%?J$O!)@7)(%6W8iAhC;j(BT!t}V7}MowN~Y=k4%m0zMsZ$c|E z(IiA3^zIdfwsJ=%YvCTnFD}X}blBX!f{)UZEU6Y_V$%P1Yd52+<1l-q#Zf7e9A0jf zVq8T|)2CRH(-KpqsSxXkYUaOmz0qJyHklG=;emNGUxPNAEEYqGG|SQwg$)%+IqWKo zc=(H09JmT&(Z|K7B&B3Xm&C~v0LHD7vcf{tk_VV-@V_ce< z(QMAEp0-%hEh%Xk3C<}`n%r2m@l#fKWPC=7A>EWnPB9o<%ct--16Cx7DH&#rzLWyH z4TdqaBOUD7=)?X((;4e^ePX&fJt+xGHrJ(CBsfW2kAcs5#)QM24x-GH42HB6%xmo8 zaw5asxN_#M6)C$u(UOAylZOokh3lovX#jT>!t44}Lt?TaH6_k%JQEQ#K3<=ikd~5^ zY)od?XHh2J#$)K_ofkn-F>WU$>f?=;R0D1~+(w|i&@$LCjCP}$hO;_6+*!<0J(UuS zhSan;%pa5@=nkeX=jTy3iFzYRC1RK%LTVG9#mZ$DsujyM-h{^`>eG{x6Aej8l9v>U zrQjv08hneDsyC+@j48OWV#ONE3V~G{^D@(6O*oHYN@!=JT{i6;v||ZHbmW>j!{air zWKJ=p8PbxNDDx`=St*zTh7p4S!vp1$#Oj6tIPqv&Y@TL_XM^+TEg5=Ks?nH|DtUqx zh?Rxqm`UoavY9lqgCX9YR&bVA4k^i&G(!Sb+a$-zPP4GvKg8dzJ)61 z1#TpA+ZV65BxBsqz%vNakXp3tF3D6^vvS6oPEa**j3Id^B8g1;Kn)yb0$0GcW%qDBErL!v;?@81f?$RR8?pQOvzPr z1&?|wc$CYvgbase#-s#1rHPj&G&UwP@36+@Zeayfr2o|}=V_ZcJ~hRhnw-IGW0kCFZ- z!K8F)B4+)Cm6y4X6`z(cRBjgS9$DEb(pUp?)}1`(dkrY=sU;q{#o@*@#VpNuOr8w_ zUR{co*l4@VIqo5X6^%7?*1cHv%#~8#caMa~#gdeU*&TPKB*d<974Uzj^?56Jv_98E zuW-sVEyb9qK0ApGiOeNz{9*<&Q(3jUhhIFhNjI6}EEbE@&{^M5a-df&c=aHqK}$i# z^)N0x!Jv<~7!orqxQAesBag+-w!zvy=VMlXtQmQa&dfySQfVM|S9WZu(Id-Ly(uNZ zlwmQ(u`F2@?m@je6}bU<+3)2bxBadTmym&;V2LwJQyS~2tc=VptnO470@7H^3dBYk z)<@XIY$kWlG;kj7G?Od|Nm9*8gFsiwp;Dol3TyN(Z@LBtx1-#>f*<8v_9Ug^)+xKVvJ%}ke3#tbyWw0H}iy|N7AnM)$sK7#fv3+8{8_i&11UUuihJRHmDew3U@yChc@ z8TzF3_zX)1S|TNQb$z8tnZ}*0GP^!DJT?~h`}X1z+%elTM_KjyuKKPl&5j#rIXR0@ zR(!q!(UMeVCLYG+>m$2J&rx*F^BC?tJ0dctOPJM_ZsXEzS&2~R< zjQhL`T^{8==dQQsbfyq3Kg@A+IXPL#*?n&Fdfej?Z}$xDc)1`xy7sV$7<=bz-}tT( z;dZM%J0>eEtc%OuD2|H?i*QD<6lZ=>VN$gF+}qBmFndh2RTje^7Z)DxtrOlA4vTT+ z;pTFfpzA!c3p3DtuI!(vu3e%cBJJ+{tewNdv%+lIox9kgqq@56%;M@fi>t>hySF`A zc4hzae$5VdmAky4Kp!c~xE&!KIpbjtVO``ZRr||f&IaJbo#!$pmE4^rf6igfWd5&i zSXP3~ZY|4qB;n~_1|DQu=_j31#Ajw&vr40*q@$7&>zK?^nfNNZOhA- ziR@f!NoICIu@qla3>v@t$cc75|97U@qodr%`9(z~GLsh`?TXLy;PX6q_MFp2;g>1- z+7erVHLozo=F}>|?-R~6Dm0#O;^C8b**)U%q{J?}==ZYBqGB71v*O2&lE?^3c0J&fAGLbL(W6hV2$aj~-2GG(zuwu4P$oRTW(57P zMlqwUB{@Tf~@Jtrz?$d14iT4>#z+g8(*26EZ2-Ykz={HiFoAQjzg!%alryq+X zZT5>}k=-+vJtr=bBxF#I%5-{{{T7pn2ejEE+*i_%7w$9i8XmP56*!+fdn&pUrQg}O z%Sy|#+%fbN8IuMzAG8n_&wkdFf3A|s=_o45%cdq-R8WkP?jv=M`qcWs+~47>g|{^o@5oTZ@x)l#I!=W{)h( zgCX8Hbu?8Ak*HuHVihcO$9h`miuJS*k=_;}Lcv03EN>yjds~PYbqh0#sZXog$dVPT zWbq1idd7QN>Jjg0D~tBFmPIMp>yGCwmQuWJW^qbZXXO=IQQf6BdQX%Zx4gLWIK(oH z&OC>2c5KfpDRpEPW#hMETVXb9>MS15@~!#Bx$cXc5rt(1wvs$H{y4#$ykXMd=8VX- zm1p`ymSRG2>*wPqRE2CEMQA7Z>C;tKM3fa{x?&OZb2OWsvho~k>dG!be`QG}+0q=5 zBh7N?>#WPW21iz$OsHW^N*Cnh=et@Y9n+vh{TJ6UWs0LZk$R=AFelGim{~g7S{xot zmwQA=1@MTF3SujleBv=DdQ6FI4$00hDrFv#&g6kxI$=LLQ%>v@Vx^MB?3De&n~mSM z@asM+EsMpk`sgM2`CXnTSY)oXlqwyg7#m(#9P|C?l01hklh)O)TndUtxlicr8COc7 zZM5syIrA|oI?1G0M_kGDGr8AErjDidCJ zBU4KViC1<_PaI&mv2H@+x!TD5=ivbijHDj-*;=2U3)D zf5U`bw>s=hp5a&};>S#99Q(=Md!Azm{gUsFcFmsdH0RR7J4RvJ#H?QIR-$~pV_e#< z{_D|UJl1U<%QlZy8y%ub59suxXJ(eQ6gRMPpJFlik=a&QibXi(z+$Ka+siQUcyToFW|bD@BUcxR zxDs1FmSs|hWmi*NQ4#BF@^977^@ztQX2rr%FD56?&K7P?NM1I1oO)hUOU3B==N!vQavY^>s+FcQ267WyiEWgvM4CoQvvF5P%TVbp4XK8FB3+f_ zOhNC#TWYQv;-gEEl36C_fE9FcF1^&_(K||AkxrxCF~vnvYjek--P+I>$P{!0AGP^xrx+33ZE6yxnb6#noyWu#G<%UA7f@!#K?buyj5sm!L1ezI( zOKkSMa?hAj=kh%=9Z3Tmj_Af! zt~iVd>|HTAUzUQarMb?DvWQH3fzuv0MfY>^eXci*nNRYfoEwv1&CAKrP0z?OE1Q#? zaij3kr!@<=ysibDoZ|V2z!`cvuwli z*rei{B)1{wvj*w?(9Arxh>{~cI<_3+EV1*{^_CCIUV4ddINpx(IHe*wCo%altW2d& z>3NELrH95W z5f@@2%dxSE&DFg~4^>17E(!}1oYJdI?l{!HT+!?bY7U;~R5Fy%B_W?XOExO6#DQK# zOB#%{M0v+x`p4|x9P~%YPdgY_KyMndav-|646p9W(attJCeN17>W>}BFV_(>)>eYm z7%hrR9a#=XW*j;ay%QIg=fEH?N5)I*t-gJdVjM--Bi^m0V&>3$l`?{; zcxSbFDfuSE6*-HinAW>2t2nRRmai&Y9iS*}OtYaHvP%^dQ}JPgA!n48?x`hLc$UfS z0k7D8$x5uZe^?dLb5D5Q%V0`K#{#oAiiAg8u1qOD!){04NGdAvtV3i?Y5jbR@Qlsq z9pRQ@&48~2k4Ufbc*pzt)+1dl(bMS+_jNdnR&+K?@p7~jDic%DuC#`L-NS2g_NQuq38v=V>V`T4F&l9>Gf) zrJ-Xe_XLF$&ZE^37Fq)3+fepaJ>E{O`8JfCfVb_j)qES;S$afQ^KB^Jd#(94wC3B;nr}nn z*c;&N9e%I(;+0;UcYl5oSMzNs_vr#=8Trlpnr}nl3CTMi4W{PX&e`nE#1wf=G#!T3HC7+`$9_W6V#e-L%Dy{!Ts4F z8?V^B=JkP?=RbK-^KGd7-7xzS&$T3whI;0@nr}l5HQ$ETd>iWg+Oy`{P<(7?bAAcP z{^C17ydH*cURAzbr1+X|LuNODvF;CR+_d+1 zk|347l9X`v5oOJ{q3n%m_d1JS7-nB@$}hRIz z=G##AeH(5=@u`IJXP-3t%IO98Dn%KhfB&QA+fd&RKx@7YMeC{gHnis3Q0Y5h=^HnE zw#a?Z=J{2o#8d}(ex~U99L)DqMOTvI=ZdaWuTK`;v7VnTx?{aRU8Jw#;%dGPWuH5v z3fU)#iq2*!UXGSxJe@4XcstnhD^U4sL*(NbTk~xw*Hcu!9Ig2_l)lM#e*5`9{x+2R zPXlZImw~&m4@0pVS{Gm1;&Ex2%`v9f*0CsSxUOy|=YK$0hSb#XufIk|M~8$&MsF~eL+j>D{_3vtm6L|$4-e2i*9f1TB6P&Rs+R4*^CLNQ!e8^x&NKOM-C%IGtg>OD zy`3p@aPSPr>2<+z9ZTrH0OzE1x@iq`x}bA9-5Y;Oae?@w*ID!(@vk;HLJJC=WKSGy z0&_>^=&RU}V9QXRdkyh-U(@LpV58uVO^sUtCK>-_WeRFsZKOR7CQIhj@*RwXd@wt& zGdW!V{wU@9Fyeoa8HrTp>L9Vs-#T3|HVSI_Za_jaFo$nQoSGdokZ=jin13biF8om{ z--C$%L}nyXnF~eY4se%bR;_%GgS#%E*Baa-w{$uqHVSHbk0PxXm@yjML*OQWS){=& zKt@Z!q}--lb-MeoDfy)h;#=I&>Ef_aP%B?6q?y4KYH*K$D+lwY0QWwa9U9!5(Ax*5 zfR#+AQ>)+Z;NFoL9I~KfZ!A)d3UDpKJ>ci3>w=AfnqRh|{Taa24wN|R=SulbKyoKA zEAFbEGogbW1@p+=5{F?#Q7;?G?}MpRuX??~xIPTb44G5wzZu{@0drG>y9%ypeLr2@ z22#GXSF-mI;v0jxsKHTvUXvLdvY@0#+&=K2$QojEnrAmN%0o=|O+Ik`2QXbUisQnEGw?Jm;3Z?Qri})pATDO!m z)#`(mKWShxWll}+S2V0VFnwA{dTQ;U8}!zKxhivN`3?ow>=8)|hb$=Bk&l$I0$ek2 z=LEPBlvAjmZUi<8YU9Bqq&*KNu(iag*|7@A9l)G!BXQL4mF%E(&dY5j28S#taWt;I zF2K=x?JWV0=B@VxxlIBbjV~t!xHxcU1UT|b=wsS`p?o_Ca81B<65#FzmnguIpVI|6 z((5b0QGE;%aEag+2ykS_PXZj}dr5$63hs&kNBP!ir`-;yKJF3VNUx~? zNA>ZrAQvmZkzWQ2a8w^w0gmkWK!BrsKNH}nKE4p(7J~aufO`Vmi1vQECDkf6%rFo;rFgM^J>wns^NYU@e5FKOS| zPZxuYf|?ztkPru^RTqh)aZ}09a}hrdOrscyBfl$g8xWrW#=on?sXZ^D`;P-)0=h{Y z?UnSdBB248JKZIY+JzE#29C|>;in6al{huO+>7g91G7Zt)bgdLoq3dLuvo z29sp*lK*T8g)ll0W;_ciF5!JL*knxB;HjYj<4@qW5kY!uYW*BWWP!IW!oFF|HH zn0pf?y;`_VDc|>zoB`%qQuSN{t`AH0(>btFP%EDaX;Z-L)!=B}`WcMBQPQKmQu&e* zUmr|wnX4|}VPKYPaQmUR70l}v zeyXIW#yv{qkr^Dapj1A(ze*P1I)f__)j{a~I+tDfr$Zj;R5 zkOd`sBam`afTR2P4(Wcnp4ceRUP&(u9l{8vypP1uxiQu!j0vR8n6 z7E*x&we@H{`NGrMTmv8r1vmq^PX}TBGFZ}7n~%tU^@nKZOY5d!aLr|wu2Awz72;b9 z>itDrrj)mu9zC+H3#R2ziBns@(70U==6RV@(<5#wm{|hcd@%27aMYeZ0kcJeqkO*y zb3lWm=Xxi=q*x_;)z)FejRSLDgKLY5ZyR}OBh|?8e z{a0K)N7a9Bq@T`Qs?BYHXy~ZwxlTx)JK9h8O}WIWjcfEXz)!@Ct)6R%?rCfk)Hu4YE0P%oVyEO6 z`W<_W02d8zr2w}R#kvUYiz!n1)Y=vG^Sxkxk~yN4%6Am&$=|?ie_7I_=c!5@_5VL- zNem8IP~tiuCEyipj{MgNTvwS@D(2MVtkmFW z9Nz%u8x4-y?;e@KAqz_WTZokX;7VspK1B5>)(7SL8JN{Fr{+JpU#$f5qXu^#1^flf zp4X*(>A9;?`Tjxj&Ut>i&GRKrZN4P`wRqD{*B%=MwSKe^X|Z5V%A8t1?*y*)Tap$I zSx_pU1xU02N7KgSxBYZ~E|4^7-d571>96%Wm}eGN&+SHXC779uB#!nhdM-S#XnOR?ROnz|8{l z;0IE^YVH0u^GJ3)Y>=XDkVj@HjV3vg7vR-b6w@e`0o1vo72b#*`0){6p? zzf7BZ9pzj2nKsuONSEc>-0wi9eXh+>eT@0SPd5o01-16F3u$kFv8|Liwf6ixxOZiS z^e8B`D;hUKR%w^-eQX&a3@&!?97Iy;8pP+{_^}#8FV<1|q)o8tw8u z4eohxM`f1gF(tjD6u%bluVJI0w$6DQX-&WM)2+frL2doF32Aq&!}Bm~6x8l7+aWFL zE4)XBje=VJK7+IpFu%#1njQ3zuA%YwMXw58P~- zRrBBX;Fg1Vc7vozd!_P`zh06V9I~Lq(Rz0_xbJ0F&3}pDPJ?N)QOZ}1(;>MVm~NF4 zr*c+Ir;Y>EHS37Gk5I=4WcpD`kd(*eUsi#)FOE zF32pYDsj}V{sfc1OUjq#PbKa%#4iAo_I>r`dlZ}%%oLeZ%l85jW`lWVkEBQAgHpcK zuKoj4t4iY3^r+s0Wd?^VDCyDoe6IjU{q;8i?lidn1UT~RiM_b5#YRERj%~P+_zlb# zha^sI+-L!f-aq*1im+9uN8A`NuW4}e;lOvmbUh~Nsr92t(CZK8xCS=?+)Xgg92Q|1A$nFA|H_uA> zs;ytfBi|pu^#8f~^1T3V1ei-29ND2e=cjuD8wEB0Wh3oLFe^2h~(&|ay(B_rWgFh?&+oSI$+uD<~$`B#Zkv!esJr@_1=z`Y4(y9T!cddFl2 zhb$=B(GJC$ami1&=C|t0w-*ZEfqCn)#L-?!kGK!OjHU%Vf*MEb#1~|Sf!HbOeTVp# zS1{gVqoC$L3)21rllDjT`92A*KbR#N+~de-1(=h6N_uMTej6&j`8B+k@wdd$a~35# zo<;KL8+bp09@rqL+0g*Wb#6)w1F=)my9da9|6;v%OJZrS#NBC#_btG5sz*8E9X}Or z2n7DE@2`8Rp~R_`uQ|8^FjImgPHnvU2l*V98OoP}Qu+EJes8d(#UK@K3gREV$6xmu zHVSI?689w-L!;`sHz1G-=Ip%^r?x&YgKN^*Uzc^C#Hsajx_&&ENiwHazJ&l^2Ggmz zq(|#PCBJ-#_@D>l& zH8Q7WZxLiRfeCv=(o?&SqVaSZn7yGAr{?GJ;C=^F)JEde?(^tAeifJ~`r!;g&5ksr z4FdC#%&FNi6b1SM%)RX;J+*cpfQ+)i*gI9vokqT^WroT}L8*TK#=!G>n7?jj7fDa; z{-_k!Uj<`}kvO&Siu~}d%ut>bl=8K~5$}Vm+pT)NHn=`XW|)|Y9<`Tz0gmniz6Q5L zX4UHB85C=ufL;^?(w>lPsIGhugL|Q;ziv^i#HzJFx(}_Cnd{R$>& zfK)~`ZagX~2h7>0Bu=egPKKUwpucW6HVSHb_z6dM3CzSn5{GG9u|B9@1`qbvl?{AnYZ+A48s;~KTU@GObJAqz_FU=s{{INM)$#xAjH;}H3! zN=bHrX zOE7n2PHi2r9tMVw@Yns8FX^e(hYfm>1^&8u*eIx#ZxRHSf(b8^xOQOG>^L||Vwj)` zcNOu^jrPZXFFZ8=re=z%Hj%cOweTRf!z^r&y(o<_M?;!ay zm>J_Gj(#^*(mRNF!wdepCND}H?Ugw4b2~6EYjE`Y!h14(5{iqGN6Eee$#ZJlI_YfaA z&0qH+HVSI_u0}?`fq8Fc_1rvkoC{!Hm?Lq`p{bN_3G`lm&0pvDhQz70m$j(4C&2vm zzQmbS^yqiGJxly`IUiMDK6;M#F_<+n2e&JhZy97Zfhk%l>Cx|xN_LR{GC%d#`O^gm zYI?-gml+0Pr=+(J0-u3@1SE>fN8f%;%L34q(_fQ=7AZ%M&i`QwPCn^h0Ih} zzP*rnV6DII;yOuBt={N;);3>BOm%w1B?@p&z~u;Vq&HT8qu-C-P~ytH|9u)77ngFJ zJ~&L@vDllV@nt2rN|{CT)-jILqjlH`Fjsj_K`(f{zpe!~3Tk@6Nb3eBh36FXa=|!w zjw>JO&6XL;mx7wTi@+@t(Ay=bcO2Y#4Lz!lT3>s&E4A`91lLSv)%-{G5d+4|a|-?& z4rUb3asDH{IbfFXoPypqF#C8;LGKEfzztkGP|#}yrX$aBdNhui!Q{vsjaO>^D+DuE zKyMzH7JrN6EzHVUrxJgO+)k(JNlXh-!KEi$631-YIA9C^(kz>)vr1-S%4E>Vz666BHvIinzF65yylQUtj)0gmd! zBEV67^b+KH3vzt~xxRv2KS8d)AU8mOqxyJCkQ*exQGH|za8w^wK`u*)ZTB==~$8=eNmU*8m#@m%ZFLPVwz!hJm!B z@t*Sw&08HgPR-A8;L*MZw6 zkndSekLAnN2lbD0g4{1k99M54&^r&V&St3{(DNKMJHo*9kU3X-;q=Ha17!w>EU4*a zfh!cyn=Ys~2i!spJz5WK5Y*cZ?x=v?4MDv+Tl{s6u~AU-AKm9g$P5FqQ}bU>a475Qm zr$_a%L1tJ1u~Tb*hrpfD&?Eopwn?^X=>>ypA+s*OaOEStZi0F#;Q9&ZIRy14f}5qG zNAH_06V&?_+#wA;8c(kY>fHiYXFJ!fIRBAe8=1i&3u^V-5nML`y#a!HL&4<>=uH#U zn+RkutzeBq|LS#m(KH7qd642`=1@(4;JFKBc z<-0DZcN<*Y@2hXmq}NtvaL9sMdkzEFLqm`1W1yg3A-J&`dQ`r7f_e+VE!EJY@%dXp zy))o0Yv|EDU4o4QJ)cl(e?7sZ$ef!0sC>C#CJN}y1T$YlF9dpP!5kLQI|JsjfL{G7e_aP` z6sof~8cdwbso5I>jiF%13+PP)Ggm{8{I^nOaL9sMeN=%vuAxW$?IxH;^us(tb@sLZ z(^lrx?4|J~3CuvAQ<%@o!A#^iZvLSDy9mq*o>S1P0&|?_I6W%gO)w4jY1`WrOsLGM z+1nO+abSk?oI?4gfSJQ{T=~e}6<{{=oPypdFqe3a)1&82b@xlQ;*bTk_R<7gE16ZZ zm-O_4dS-9~H1x>cQG$At!Oa%XTP~j1$!5RSS1v1yjy*3VL(F zEaEu@y-Jy31;kFR{Z)ZGuAxWuds9%)|1jnmY!qC6;mSwzc?X$cAa-iyGlJ`*p-1z1 ziJ;yzaC0^E$ljHLdh5aM(9omycTQ06Cb(Khs`nq+8!9t6WI@e;J-`_S^z4Fq&w-n! zp-28(BB-|<+mh6u)a)faJs6A3RkwZ~2*$3VNA^wx zGfzNo5twBfdQ`q$GJ``F)av&*xbqr%RKK;3i?x@A;F`&-n*Ye&7%*m@Q>c&OU`FvA zSHGk;2h0+lQ_$N6W*^USdbHlXA~QH-LCt@+z}5M&`u0NQYa=tVoN9mT1};fJF9%FH z&nehD7tA7_LP}wM`m9GuB2n{{*pG8n_ zD7btLJ(@44fmy_J3iVM5W*5(K_EPySg1O0a3VKaX<9P%&3a)m==~4OOWQKv*srhdp zIJ<@()yG6Zz3JfQ3FxgB)Y|~=TLHcEf_hiL-4f7ic1H5Cmj7CT>nO8o^+EnK3+nX& zH&j6HIYGV2;ARWxEf>^V3vR219<}E)f_hiM-4@Vm@sn76bO0A4v(?o{KS8}A;D&4H zk-bv{_2z+Ftf5EkWwW5(x8M$G=u!Eu3F-x&_16VsqoC$L8ecksN#Z$$@hS&QG0$=1 z70s73!7SoA1-(izyLgV%W8*29n>?qW*YszMr`RZ{*-P_foXju~JGJpN9o#?-y--|V z4(2VHtL}c`128Kz^g^Il1?Gx?-c2yI&Q)(OJ&y>L862{pX0INcSwL^Npx$I~vo-Xn z{Vf;N+X3!?h932|tActr!PWYudjFALsLU|uVW;N54&Y)0^!f?v4FNY?Ly!8~6fke` zoI?Lt2WA`3DfG9qV6O3;f?lKZcrOwg1-1T0{_7z#48%^&e+l3$0(ymldZWO-sG&#w zW1*nldT={5^k{rJC#ZK7+-(8978fKRYx%D&xG0%bs}Cw)FG0N_;D!t6O%c?a18$*! z-UdOvZ^0cB(7Pt6r@M&n_pwn>^B=Y6b~3|2?9|$G1h}38dV>V@a=;Y}=*<+=n+tA{ zh90$-Ns6( zo>S1f1m*_MDd;u1g zs{r@9h91rPs|59Sf;%Xn_otvJ?XWT862{pRv$6o5;XLv zezOGi3c-!l(4+g+d0^J_oWgu`6wEoEM9H&SAD+V*3 z=M?l7fcco`I6az=cE}8skAhl#oC0@ALyy{D-K*aHQcbTBxRx^O@(Wi!>K{G843N3% zo*!g^DHPC~4rYmf-U=`qH1w!Gj>`-VSx~e061W>0dQ=}x{={=uY!qC6QK(-tm;pSe z;J;B|X7ika-f}SOd5+VgdFv>c%RHx`SN|IR{S9nhb|~mYgGu2z1-)D_4xZ!ms6J+c zS;TV+dX->y@tlI*MKCvcPC>8fUwFQTtvY+Dd~slU@tlHQF_>{Yr%=B6U_RzK1-%_$ z4)C0U-c^}lhG3`Gp8c=meIINTTORlhrpcSIj+4>|Ipp=*9Bvv;PQ)tUPmz9cuqlYfXpyK?9}|13(ldTN8|HsLA{T` zt=7;Z{~Z8xp63+ot@RI{3t*$*@{5AKVPNz;r=T|k%y6Dl(3>JNEMx4{{5KEWVhz10 zT)!F2b)HiwU*ns2&k`Gj>g??aCY|RL^zy-!^BiX{^|!e)!!pKB&E5szKGx8q`rQHM zIL|4R?B1=M?l#fw{tS3VIE1 z;e9D=6soJ=7@1)pc53xu1lLDH&y4F!z--_-h4LK-bDrlE?5*`5-haVH!DWYnUKAKT z&vAOx{)Wg56U0uTLyAC7^d%P|qJ;YsfsO?mx1(gUsNN1y%on zvuNm1|1A{M8wKt~4LvH~LNIH2PN6*?0&|Avxb{MNI==v2Q*0Dm?Mgu}8cZC|aeA!3 zfhp!W1-+SI=JOn;%&-Dtr&b@kz#Z1mqvu1{1@-*>(O$4oaQTHRAL(_F83tmf zR=yZ;2?Baqf_jDE#%k!1|KY-28hYfv^@4g;;ErqPQG2;5s8=g6K-UNx1vURs{|%EF z24bgXuMu1y4L$N-iJ;zCaFaFksD2j<>a7Gbaqt{B z{!#tT2D6Cg6!a>=?BY2Ey^COO@|=QR(>ejVP;3;`?4|mRlNknLr`F$6!1dG6qw&-s zs5c(mbPc@_=zR?4Tb@&}_dJ+uJjeO3E%X}Q9iZ!mje^TB3VH*;z~K`#PKPoCrSSp9;@=Q#zvX<+8^oPyp;Fk5*}LGKKh%RHx` zSHGcTD@X*iYBtg9);D&4HQTv-Bs5cATTN-*)zIB3n`@o&h(4+b2 zmY`mpAhauN6x94jdJ!_iK_wf0Btr5l(Oo>S1v1>@j3u6$I#vt@?zrJzmQ_d ziI~QmUljCOf@#NdoF4TLBbWg^r=T|q%!@q7=}~)LC^I-@L9IU4gWI8@NA3BXpx#w* zw>9+0-WK;szLQF)-hVrQi;-Eng4w~@OYNnfpxzL0!!`7%ey4zWi{}*TV;z`nJja!f z>^%$S8qX={HEI%|Yl)45n!O1~>mf4?#7?b#$Ag=$p-2AvSWs^TxD5h&#|8Dyg1aK1 z*YJL^_R<7gE16aEAK9xH)H8zXqoGIhWr?8PG;nh@^r-!<6x7=R?tq3KmG7#cUfrev zx+d5tsQJ%~v?ws+Wv;sW!D(RTYUok}walNlVcpl0u8aJx11XuWYsP_Ir4+)rbp z;A#h4`A9DUOghgg*qaZgoaZ<_^50xAAMl)l-c~SGJjdx#{a%(C9I~M1zgiFC`5HC~ zYW9*|n9MK`J2gEcxIP+sMB^U()jR%qx^|EL0Umgf}8=l>A;IW`Jv_J$#?1DHWPr=T|$ z%w(S9?Ck)(#bBy5>1MhTu6NHVQ5~6!en7^x`>AkNj5*W+Kli=q&=X zjORE#D&H3M$dB!dPBer=Q*x?q&Ef3Jf2g~TMcG2&vANT z&^skFIAlSsJqNeO^HXdT)a)hybpw;ma|-3l2UE^-T=~e}xnMrvIR(9~V5)eI(~E)L zWtqVt3u^YZX@lqE*eFzIuLVpF&ncAeMKCjYjw>J8yA;fNo>R~}3g#ToaeB0V34Bzt zf$X55W^V|%4l=7|Fa1840;Z7X6v{Up%sigs%18FD2D6Rl6!gx5xx#at9+j_QThtpi z3NAYo^ekWo@f@c|?Rl)sFhT6p{5KQad=0&}xPC2|Q#_|ozT05xKE~O>m5=<_7EBD! zDd_bBlf`qK9+huAm?b=?ptlXoKAz+BXnk-+W^l-Yn*VNq3v5@ty;L8qz(n($LizfD z8On29`KW(92WCFcDd?>Qvz6yKJ*tm0V6O65QmXD|701DM@Br=WKU z%nhF7^k}?l(n-wTmf+gSteU+c&@&3^<$`l)=uv&l7Svk?Zk>i6*?U+}?-aO88hSLo z)D08!Un_7OWme69WUpCJFBhCcLywJrf_fi=Tg~g0mI&>7ivl-NfNKTreF5%n zaN7kq>X%gl9Qoyv0Cyj_fJkk7>w#-3z=eZ*M1X4su8#os0JuT{?jdk53349@aM95F zMu4Mr`@aGl^~*=1wEaxuS{DJ%2rj{g^KH-ff}8Hc`Sv4v-t>+DM|z(Ka5SE7@Zo&> zc^hz*J{-!IRa%N9SGW=8I&>Y3*I6q0@xBRC&nf9~^8$@8w*)xqzx6t6*BcR!`*6PY z)&>_Qz}*9`ix1~pZ!~`x1vpxN5A@-Jea1iPmrwg}zWGu+C==kQKBfq8R38hJIIbPg zx`_|tgY`b`fY!U8D(QJIBXqQm-3#ut%(D9wov+?=;C}Vd^X+fcO!K0(+Y7a;Hv~Ac z<0}EK8@Q7KTp+jyy71-mZKu?(9`xaS+X0P#odmeM!1edxeDfu4kRWFjcK`vQdzVl2VxLJbS96@fLAorFaw@{Eb>P3{KAdmABtNh9;e6|Z^w#@uzIw!M736jaat8#t;{qJnd)9|jw)eab=W8$N zUGd?3^@zJBz>)uM_;AYk>U!|~*Ee6%3-sZX_3Ha@zItRwumDH-Hum9^^KIe7`Q}S{ zAwHb4URxi|SC8!IAjpLYa8$l1A5OV^J$yLj^63RQnpYEiIE&AHBXNBMxg0^xA;?Ws z;<9r`ltxFPR8lznINmSjI9*-Tc}Pg7gv2C6UvnR`DXv>9?Bdh_)^?~T{i?FoAS>$MBk`nRn!<*v*XN9u12y-@G-m&O%!y2qS5;FAZfo1S>wzIvA7 z_^4VVkNK?+**C9U@WJ1wPWpW0L#tNTdjIUkcOnn0J-=vY?eeI9bU`zh+&#DHyoRU0 zYB2oy$Db&u^Ll>HD}IBr&s}?R#E2|wev89lwn^!_tgFAhRxAIR_x+l0x>o1K{#%3o z+ID2Q&2nK)ql;@dRE(SQ<*kzs{+MNY{OEmuj@v)oWquc#(v)GJR;?X+wDj@7gr6S_h|DzC{dniN z20!)~d6(s5ga4z!!Syyw4ynJh^{U+dUrZ_rx%+_q%XiLNcQ^d!#E^MAk380DUe)rI zOMcjX{}W$4^+JP{9lHncMgto?2E7aQl-KeIYu{=Uy0U6T)9{A~2Tx9@9n zEO0`Td(NDm!wVWL8901wy*&-jzJ2HJS07!btNZza+RrAu8gymfdv#vQ zz3SKU_U2l7gZpRw{oOFz_>s~15BySVMB%9B2X3Egzklk$u}6bE)Yq z-fVL<`_fzQ4KJ(vYJvYpweEf^HoD=jlZMqUxwpUWt`}1_zSel`+B3`AfBsBN^VRj= zoVjmygAE6NUi16$aSxw7R`0;R{gdO)9QfnL7eD3}PB{9|_f0mu@N}Cmub&>hyl8at znx^Ll)|pZ~Jm}81-D}wg)$_aO&H~%ZdCRhH?7uU@nz$#wN%@(&3oq|$@I#*of$5vS z2AaBHtf|edVlcV%Fw%_tJb|*>xb_j>wlup#$iVu={VN@#o83>R{!R? zgXgp_YS(2$y_N6ItiPrC$-4&5_}w1^n-pG+zWC9vr?14dDLBykhb^Zkt$MoW%^t1p z*?#g;T*E|rq{)1A$%o5c+EVYI7v>J0{rD7-iu$ITYY(SUu);>jrM%p_t@2d$G-|qegBQ*0rwi3=B*rZpzxpb ze@;ng@%yCHv+k5XVITKJy{?Vl_&#*!`OaI~es%n(fhSH5-!ibnyJ30nEm+sGVW;;p zO##Q>t7i&_C&oc58wG? z?5?yY9-GoQc-qVx_WMiPM!abnVfpXami;5@`Tg=jr**HJCe7YEV)B!tzTE!kw7%mC z`Yh3{T)*+)AWZyS>R}KAT-tO6TZQXX(AJelcd}gc9byJf+UbJ>c(_eGfoWK94 z>t}vQ`JmIpgDp~feA2Jra@Np|iN8C3dbZEBCd(R5e0N9C%1g65yk>v8>yX4T-`hf8 z?*HMZ(-I$!X<j4w+BzI{JLBHH~Oz07Iw$-)z9zkd8EZd3T%Fez|$m+06N4dj&rh z()htE-*-G~$%%e7``@i;UrpM!_t3(dYhL>BhbD=0gIne&J=*7$8yBv>vpd`9zdIn) z(dpH?YhHWeePe?czYl%yhx+ebDtS4*@h3NqF0uIi*vjx^xM^X0?wIF4X)xmRiT`H2 z=6`OQVO!eYYRP@{=d*H>!iCpgwWSm%?j zbIbdEdi2e=_s71vX7Id>KimB

gs$G@q3uHwXnZFviBPh4~BhTqL4jUONMV4e6` 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 *b@IgCVq~F5^pNY?3>ca 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 diff --git a/thirdparty/stb/src/gb/gb.h b/thirdparty/stb/src/gb/gb.h deleted file mode 100644 index 9be470c..0000000 --- a/thirdparty/stb/src/gb/gb.h +++ /dev/null @@ -1,10827 +0,0 @@ -/* gb.h - v0.33 - Ginger Bill's C Helper Library - public domain - - no warranty implied; use at your own risk - - This is a single header file with a bunch of useful stuff - to replace the C/C++ standard library - -=========================================================================== - YOU MUST - - #define GB_IMPLEMENTATION - - in EXACTLY _one_ C or C++ file that includes this header, BEFORE the - include like this: - - #define GB_IMPLEMENTATION - #include "gb.h" - - All other files should just #include "gb.h" without #define - - - If you want the platform layer, YOU MUST - - #define GB_PLATFORM - - BEFORE the include like this: - - #define GB_PLATFORM - #include "gb.h" - -=========================================================================== - -LICENSE - This software is dual-licensed to the public domain and under the following - license: you are granted a perpetual, irrevocable license to copy, modify, - publish, and distribute this file as you see fit. - -WARNING - - This library is _slightly_ experimental and features may not work as expected. - - This also means that many functions are not documented. - -CREDITS - Written by Ginger Bill - -TODOS - - Remove CRT dependency for people who want that - - But do I really? - - Or make it only depend on the really needed stuff? - - Older compiler support? - - How old do you wanna go? - - Only support C90+extension and C99 not pure C89. - - File handling - - All files to be UTF-8 (even on windows) - - Better Virtual Memory handling - - Generic Heap Allocator (tcmalloc/dlmalloc/?) - - Fixed Heap Allocator - - Better UTF support and conversion - - Free List, best fit rather than first fit - - More date & time functions - -VERSION HISTORY - 0.33 - Minor fixes - 0.32 - Minor fixes - 0.31 - Add gb_file_remove - 0.30 - Changes to gbThread (and gbMutex on Windows) - 0.29 - Add extras for gbString - 0.28 - Handle UCS2 correctly in Win32 part - 0.27 - OSX fixes and Linux gbAffinity - 0.26d - Minor changes to how gbFile works - 0.26c - gb_str_to_f* fix - 0.26b - Minor fixes - 0.26a - gbString Fix - 0.26 - Default allocator flags and generic hash table - 0.25a - Fix UTF-8 stuff - 0.25 - OS X gbPlatform Support (missing some things) - 0.24b - Compile on OSX (excluding platform part) - 0.24a - Minor additions - 0.24 - Enum convention change - 0.23 - Optional Windows.h removal (because I'm crazy) - 0.22a - Remove gbVideoMode from gb_platform_init_* - 0.22 - gbAffinity - (Missing Linux version) - 0.21 - Platform Layer Restructuring - 0.20 - Improve file io - 0.19 - Clipboard Text - 0.18a - Controller vibration - 0.18 - Raw keyboard and mouse input for WIN32 - 0.17d - Fixed printf bug for strings - 0.17c - Compile as 32 bit - 0.17b - Change formating style because why not? - 0.17a - Dropped C90 Support (For numerous reasons) - 0.17 - Instantiated Hash Table - 0.16a - Minor code layout changes - 0.16 - New file API and improved platform layer - 0.15d - Linux Experimental Support (DON'T USE IT PLEASE) - 0.15c - Linux Experimental Support (DON'T USE IT) - 0.15b - C90 Support - 0.15a - gb_atomic(32|64)_spin_(lock|unlock) - 0.15 - Recursive "Mutex"; Key States; gbRandom - 0.14 - Better File Handling and better printf (WIN32 Only) - 0.13 - Highly experimental platform layer (WIN32 Only) - 0.12b - Fix minor file bugs - 0.12a - Compile as C++ - 0.12 - New File Handing System! No stdio or stdlib! (WIN32 Only) - 0.11a - Add string precision and width (experimental) - 0.11 - Started making stdio & stdlib optional (Not tested much) - 0.10c - Fix gb_endian_swap32() - 0.10b - Probable timing bug for gb_time_now() - 0.10a - Work on multiple compilers - 0.10 - Scratch Memory Allocator - 0.09a - Faster Mutex and the Free List is slightly improved - 0.09 - Basic Virtual Memory System and Dreadful Free List allocator - 0.08a - Fix *_appendv bug - 0.08 - Huge Overhaul! - 0.07a - Fix alignment in gb_heap_allocator_proc - 0.07 - Hash Table and Hashing Functions - 0.06c - Better Documentation - 0.06b - OS X Support - 0.06a - Linux Support - 0.06 - Windows GCC Support and MSVC x86 Support - 0.05b - Formatting - 0.05a - Minor function name changes - 0.05 - Radix Sort for unsigned integers (TODO: Other primitives) - 0.04 - Better UTF support and search/sort procs - 0.03 - Completely change procedure naming convention - 0.02a - Bug fixes - 0.02 - Change naming convention and gbArray(Type) - 0.01 - Initial Version -*/ - - -#ifndef GB_INCLUDE_GB_H -#define GB_INCLUDE_GB_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#if defined(__cplusplus) - #define GB_EXTERN extern "C" -#else - #define GB_EXTERN extern -#endif - -#if defined(_WIN32) - #define GB_DLL_EXPORT GB_EXTERN __declspec(dllexport) - #define GB_DLL_IMPORT GB_EXTERN __declspec(dllimport) -#else - #define GB_DLL_EXPORT GB_EXTERN __attribute__((visibility("default"))) - #define GB_DLL_IMPORT GB_EXTERN -#endif - -// NOTE(bill): Redefine for DLL, etc. -#ifndef GB_DEF - #ifdef GB_STATIC - #define GB_DEF static - #else - #define GB_DEF extern - #endif -#endif - -#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) - #ifndef GB_ARCH_64_BIT - #define GB_ARCH_64_BIT 1 - #endif -#else - // NOTE(bill): I'm only supporting 32 bit and 64 bit systems - #ifndef GB_ARCH_32_BIT - #define GB_ARCH_32_BIT 1 - #endif -#endif - - -#ifndef GB_ENDIAN_ORDER -#define GB_ENDIAN_ORDER - // TODO(bill): Is the a good way or is it better to test for certain compilers and macros? - #define GB_IS_BIG_ENDIAN (!*(u8*)&(u16){1}) - #define GB_IS_LITTLE_ENDIAN (!GB_IS_BIG_ENDIAN) -#endif - -#if defined(_WIN32) || defined(_WIN64) - #ifndef GB_SYSTEM_WINDOWS - #define GB_SYSTEM_WINDOWS 1 - #endif -#elif defined(__APPLE__) && defined(__MACH__) - #ifndef GB_SYSTEM_OSX - #define GB_SYSTEM_OSX 1 - #endif -#elif defined(__unix__) - #ifndef GB_SYSTEM_UNIX - #define GB_SYSTEM_UNIX 1 - #endif - - #if defined(__linux__) - #ifndef GB_SYSTEM_LINUX - #define GB_SYSTEM_LINUX 1 - #endif - #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) - #ifndef GB_SYSTEM_FREEBSD - #define GB_SYSTEM_FREEBSD 1 - #endif - #else - #error This UNIX operating system is not supported - #endif -#else - #error This operating system is not supported -#endif - -#if defined(_MSC_VER) - #define GB_COMPILER_MSVC 1 -#elif defined(__GNUC__) - #define GB_COMPILER_GCC 1 -#elif defined(__clang__) - #define GB_COMPILER_CLANG 1 -#else - #error Unknown compiler -#endif - -#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) - #ifndef GB_CPU_X86 - #define GB_CPU_X86 1 - #endif - #ifndef GB_CACHE_LINE_SIZE - #define GB_CACHE_LINE_SIZE 64 - #endif - -#elif defined(_M_PPC) || defined(__powerpc__) || defined(__powerpc64__) - #ifndef GB_CPU_PPC - #define GB_CPU_PPC 1 - #endif - #ifndef GB_CACHE_LINE_SIZE - #define GB_CACHE_LINE_SIZE 128 - #endif - -#elif defined(__arm__) || defined(__aarch64__) - #ifndef GB_CPU_ARM - #define GB_CPU_ARM 1 - #endif - #ifndef GB_CACHE_LINE_SIZE - #define GB_CACHE_LINE_SIZE 64 - #endif - -#elif defined(__MIPSEL__) || defined(__mips_isa_rev) - #ifndef GB_CPU_MIPS - #define GB_CPU_MIPS 1 - #endif - #ifndef GB_CACHE_LINE_SIZE - #define GB_CACHE_LINE_SIZE 64 - #endif - -#else - #error Unknown CPU Type -#endif - - - -#ifndef GB_STATIC_ASSERT - #define GB_STATIC_ASSERT3(cond, msg) typedef char static_assertion_##msg[(!!(cond))*2-1] - // NOTE(bill): Token pasting madness!! - #define GB_STATIC_ASSERT2(cond, line) GB_STATIC_ASSERT3(cond, static_assertion_at_line_##line) - #define GB_STATIC_ASSERT1(cond, line) GB_STATIC_ASSERT2(cond, line) - #define GB_STATIC_ASSERT(cond) GB_STATIC_ASSERT1(cond, __LINE__) -#endif - - -//////////////////////////////////////////////////////////////// -// -// Headers -// -// - -#if defined(_WIN32) && !defined(__MINGW32__) - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif -#endif - -#if defined(GB_SYSTEM_UNIX) - #define _GNU_SOURCE - #define _LARGEFILE64_SOURCE -#endif - - -// TODO(bill): How many of these headers do I really need? -// #include -#if !defined(GB_SYSTEM_WINDOWS) - #include - #include -#endif - - - -#if defined(GB_SYSTEM_WINDOWS) - #if !defined(GB_NO_WINDOWS_H) - #define NOMINMAX 1 - #define WIN32_LEAN_AND_MEAN 1 - #define WIN32_MEAN_AND_LEAN 1 - #define VC_EXTRALEAN 1 - #include - #undef NOMINMAX - #undef WIN32_LEAN_AND_MEAN - #undef WIN32_MEAN_AND_LEAN - #undef VC_EXTRALEAN - #endif - - #include // NOTE(bill): _aligned_*() - #include -#else - #include - #include - #include - #include - #ifndef _IOSC11_SOURCE - #define _IOSC11_SOURCE - #endif - #include // NOTE(bill): malloc on linux - #include - #if !defined(GB_SYSTEM_OSX) - #include - #endif - #include - #include - #include - #include - #include - - #if defined(GB_CPU_X86) - #include - #endif -#endif - -#if defined(GB_SYSTEM_OSX) - #include - #include - #include - #include - #include - #include - #include - #include -#endif - -#if defined(GB_SYSTEM_UNIX) - #include -#endif - - -//////////////////////////////////////////////////////////////// -// -// Base Types -// -// - -#if defined(GB_COMPILER_MSVC) - #if _MSC_VER < 1300 - typedef unsigned char u8; - typedef signed char i8; - typedef unsigned short u16; - typedef signed short i16; - typedef unsigned int u32; - typedef signed int i32; - #else - typedef unsigned __int8 u8; - typedef signed __int8 i8; - typedef unsigned __int16 u16; - typedef signed __int16 i16; - typedef unsigned __int32 u32; - typedef signed __int32 i32; - #endif - typedef unsigned __int64 u64; - typedef signed __int64 i64; -#else - #include - typedef uint8_t u8; - typedef int8_t i8; - typedef uint16_t u16; - typedef int16_t i16; - typedef uint32_t u32; - typedef int32_t i32; - typedef uint64_t u64; - typedef int64_t i64; -#endif - -GB_STATIC_ASSERT(sizeof(u8) == sizeof(i8)); -GB_STATIC_ASSERT(sizeof(u16) == sizeof(i16)); -GB_STATIC_ASSERT(sizeof(u32) == sizeof(i32)); -GB_STATIC_ASSERT(sizeof(u64) == sizeof(i64)); - -GB_STATIC_ASSERT(sizeof(u8) == 1); -GB_STATIC_ASSERT(sizeof(u16) == 2); -GB_STATIC_ASSERT(sizeof(u32) == 4); -GB_STATIC_ASSERT(sizeof(u64) == 8); - -typedef size_t usize; -typedef ptrdiff_t isize; - -GB_STATIC_ASSERT(sizeof(usize) == sizeof(isize)); - -// NOTE(bill): (u)intptr is only here for semantic reasons really as this library will only support 32/64 bit OSes. -// NOTE(bill): Are there any modern OSes (not 16 bit) where intptr != isize ? -#if defined(_WIN64) - typedef signed __int64 intptr; - typedef unsigned __int64 uintptr; -#elif defined(_WIN32) - // NOTE(bill); To mark types changing their size, e.g. intptr - #ifndef _W64 - #if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 - #define _W64 __w64 - #else - #define _W64 - #endif - #endif - - typedef _W64 signed int intptr; - typedef _W64 unsigned int uintptr; -#else - typedef uintptr_t uintptr; - typedef intptr_t intptr; -#endif - -GB_STATIC_ASSERT(sizeof(uintptr) == sizeof(intptr)); - -typedef float f32; -typedef double f64; - -GB_STATIC_ASSERT(sizeof(f32) == 4); -GB_STATIC_ASSERT(sizeof(f64) == 8); - -typedef i32 Rune; // NOTE(bill): Unicode codepoint -#define GB_RUNE_INVALID cast(Rune)(0xfffd) -#define GB_RUNE_MAX cast(Rune)(0x0010ffff) -#define GB_RUNE_BOM cast(Rune)(0xfeff) -#define GB_RUNE_EOF cast(Rune)(-1) - - -typedef i8 b8; -typedef i16 b16; -typedef i32 b32; // NOTE(bill): Prefer this!!! - -// NOTE(bill): Get true and false -#if !defined(__cplusplus) - #if (defined(_MSC_VER) && _MSC_VER < 1800) || (!defined(_MSC_VER) && !defined(__STDC_VERSION__)) - #ifndef true - #define true (0 == 0) - #endif - #ifndef false - #define false (0 != 0) - #endif - typedef b8 bool; - #else - #include - #endif -#endif - -// NOTE(bill): These do are not prefixed with gb because the types are not. -#ifndef U8_MIN -#define U8_MIN 0u -#define U8_MAX 0xffu -#define I8_MIN (-0x7f - 1) -#define I8_MAX 0x7f - -#define U16_MIN 0u -#define U16_MAX 0xffffu -#define I16_MIN (-0x7fff - 1) -#define I16_MAX 0x7fff - -#define U32_MIN 0u -#define U32_MAX 0xffffffffu -#define I32_MIN (-0x7fffffff - 1) -#define I32_MAX 0x7fffffff - -#define U64_MIN 0ull -#define U64_MAX 0xffffffffffffffffull -#define I64_MIN (-0x7fffffffffffffffll - 1) -#define I64_MAX 0x7fffffffffffffffll - -#if defined(GB_ARCH_32_BIT) - #define USIZE_MIX U32_MIN - #define USIZE_MAX U32_MAX - - #define ISIZE_MIX S32_MIN - #define ISIZE_MAX S32_MAX -#elif defined(GB_ARCH_64_BIT) - #define USIZE_MIX U64_MIN - #define USIZE_MAX U64_MAX - - #define ISIZE_MIX I64_MIN - #define ISIZE_MAX I64_MAX -#else - #error Unknown architecture size. This library only supports 32 bit and 64 bit architectures. -#endif - -#define F32_MIN 1.17549435e-38f -#define F32_MAX 3.40282347e+38f - -#define F64_MIN 2.2250738585072014e-308 -#define F64_MAX 1.7976931348623157e+308 - -#endif - -#ifndef NULL - #if defined(__cplusplus) - #if __cplusplus >= 201103L - #define NULL nullptr - #else - #define NULL 0 - #endif - #else - #define NULL ((void *)0) - #endif -#endif - -// TODO(bill): Is this enough to get inline working? -#if !defined(__cplusplus) - #if defined(_MSC_VER) && _MSC_VER <= 1800 - #define inline __inline - #elif !defined(__STDC_VERSION__) - #define inline __inline__ - #else - #define inline - #endif -#endif - -#if !defined(gb_restrict) - #if defined(_MSC_VER) - #define gb_restrict __restrict - #elif defined(__STDC_VERSION__) - #define gb_restrict restrict - #else - #define gb_restrict - #endif -#endif - -// TODO(bill): Should force inline be a separate keyword and gb_inline be inline? -#if !defined(gb_inline) - #if defined(_MSC_VER) - #if _MSC_VER < 1300 - #define gb_inline - #else - #define gb_inline __forceinline - #endif - #elif (__GNUC__) - #define gb_inline inline - #else - #define gb_inline __attribute__ ((__always_inline__)) - #endif -#endif - -#if !defined(gb_no_inline) - #if defined(_MSC_VER) - #define gb_no_inline __declspec(noinline) - #else - #define gb_no_inline __attribute__ ((noinline)) - #endif -#endif - - -#if !defined(gb_thread_local) - #if defined(_MSC_VER) && _MSC_VER >= 1300 - #define gb_thread_local __declspec(thread) - #elif defined(__GNUC__) - #define gb_thread_local __thread - #else - #define gb_thread_local thread_local - #endif -#endif - - -// NOTE(bill): Easy to grep -// NOTE(bill): Not needed in macros -#ifndef cast -#define cast(Type) (Type) -#endif - -// NOTE(bill): Because a signed sizeof is more useful -#ifndef gb_size_of -#define gb_size_of(x) (isize)(sizeof(x)) -#endif - -#ifndef gb_count_of -#define gb_count_of(x) ((gb_size_of(x)/gb_size_of(0[x])) / ((isize)(!(gb_size_of(x) % gb_size_of(0[x]))))) -#endif - -#ifndef gb_offset_of -#define gb_offset_of(Type, element) ((isize)&(((Type *)0)->element)) -#endif - -#if defined(__cplusplus) -#ifndef gb_align_of - #if __cplusplus >= 201103L - #define gb_align_of(Type) (isize)alignof(Type) - #else -extern "C++" { - // NOTE(bill): Fucking Templates! - template struct gbAlignment_Trick { char c; T member; }; - #define gb_align_of(Type) gb_offset_of(gbAlignment_Trick, member) -} - #endif -#endif -#else - #ifndef gb_align_of - #define gb_align_of(Type) gb_offset_of(struct { char c; Type member; }, member) - #endif -#endif - -// NOTE(bill): I do wish I had a type_of that was portable -#ifndef gb_swap -#define gb_swap(Type, a, b) do { Type tmp = (a); (a) = (b); (b) = tmp; } while (0) -#endif - -// NOTE(bill): Because static means 3/4 different things in C/C++. Great design (!) -#ifndef gb_global -#define gb_global static // Global variables -#define gb_internal static // Internal linkage -#define gb_local_persist static // Local Persisting variables -#endif - - -#ifndef gb_unused - #if defined(_MSC_VER) - #define gb_unused(x) (__pragma(warning(suppress:4100))(x)) - #elif defined (__GCC__) - #define gb_unused(x) __attribute__((__unused__))(x) - #else - #define gb_unused(x) ((void)(gb_size_of(x))) - #endif -#endif - - - - -//////////////////////////////////////////////////////////////// -// -// Defer statement -// Akin to D's SCOPE_EXIT or -// similar to Go's defer but scope-based -// -// NOTE: C++11 (and above) only! -// -#if !defined(GB_NO_DEFER) && defined(__cplusplus) && ((defined(_MSC_VER) && _MSC_VER >= 1400) || (__cplusplus >= 201103L)) -extern "C++" { - // NOTE(bill): Stupid fucking templates - template struct gbRemoveReference { typedef T Type; }; - template struct gbRemoveReference { typedef T Type; }; - template struct gbRemoveReference { typedef T Type; }; - - /// NOTE(bill): "Move" semantics - invented because the C++ committee are idiots (as a collective not as indiviuals (well a least some aren't)) - template inline T &&gb_forward(typename gbRemoveReference::Type &t) { return static_cast(t); } - template inline T &&gb_forward(typename gbRemoveReference::Type &&t) { return static_cast(t); } - template inline T &&gb_move (T &&t) { return static_cast::Type &&>(t); } - template - struct gbprivDefer { - F f; - gbprivDefer(F &&f) : f(gb_forward(f)) {} - ~gbprivDefer() { f(); } - }; - template gbprivDefer gb__defer_func(F &&f) { return gbprivDefer(gb_forward(f)); } - - #define GB_DEFER_1(x, y) x##y - #define GB_DEFER_2(x, y) GB_DEFER_1(x, y) - #define GB_DEFER_3(x) GB_DEFER_2(x, __COUNTER__) - #define defer(code) auto GB_DEFER_3(_defer_) = gb__defer_func([&]()->void{code;}) -} - -// Example -#if 0 - gbMutex m; - gb_mutex_init(&m); - { - gb_mutex_lock(&m); - defer (gb_mutex_unlock(&m)); - - ... - } -#endif - -#endif - - -//////////////////////////////////////////////////////////////// -// -// Macro Fun! -// -// - -#ifndef GB_JOIN_MACROS -#define GB_JOIN_MACROS - #define GB_JOIN2_IND(a, b) a##b - - #define GB_JOIN2(a, b) GB_JOIN2_IND(a, b) - #define GB_JOIN3(a, b, c) GB_JOIN2(GB_JOIN2(a, b), c) - #define GB_JOIN4(a, b, c, d) GB_JOIN2(GB_JOIN2(GB_JOIN2(a, b), c), d) -#endif - - -#ifndef GB_BIT -#define GB_BIT(x) (1<<(x)) -#endif - -#ifndef gb_min -#define gb_min(a, b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef gb_max -#define gb_max(a, b) ((a) > (b) ? (a) : (b)) -#endif - -#ifndef gb_min3 -#define gb_min3(a, b, c) gb_min(gb_min(a, b), c) -#endif - -#ifndef gb_max3 -#define gb_max3(a, b, c) gb_max(gb_max(a, b), c) -#endif - -#ifndef gb_clamp -#define gb_clamp(x, lower, upper) gb_min(gb_max((x), (lower)), (upper)) -#endif - -#ifndef gb_clamp01 -#define gb_clamp01(x) gb_clamp((x), 0, 1) -#endif - -#ifndef gb_is_between -#define gb_is_between(x, lower, upper) (((lower) <= (x)) && ((x) <= (upper))) -#endif - -#ifndef gb_abs -#define gb_abs(x) ((x) < 0 ? -(x) : (x)) -#endif - -/* NOTE(bill): Very useful bit setting */ -#ifndef GB_MASK_SET -#define GB_MASK_SET(var, set, mask) do { \ - if (set) (var) |= (mask); \ - else (var) &= ~(mask); \ -} while (0) -#endif - - -// NOTE(bill): Some compilers support applying printf-style warnings to user functions. -#if defined(__clang__) || defined(__GNUC__) -#define GB_PRINTF_ARGS(FMT) __attribute__((format(printf, FMT, (FMT+1)))) -#else -#define GB_PRINTF_ARGS(FMT) -#endif - -//////////////////////////////////////////////////////////////// -// -// Debug -// -// - - -#ifndef GB_DEBUG_TRAP - #if defined(_MSC_VER) - #if _MSC_VER < 1300 - #define GB_DEBUG_TRAP() __asm int 3 /* Trap to debugger! */ - #else - #define GB_DEBUG_TRAP() __debugbreak() - #endif - #else - #define GB_DEBUG_TRAP() __builtin_trap() - #endif -#endif - -#ifndef GB_ASSERT_MSG -#define GB_ASSERT_MSG(cond, msg, ...) do { \ - if (!(cond)) { \ - gb_assert_handler("Assertion Failure", #cond, __FILE__, cast(i64)__LINE__, msg, ##__VA_ARGS__); \ - GB_DEBUG_TRAP(); \ - } \ -} while (0) -#endif - -#ifndef GB_ASSERT -#define GB_ASSERT(cond) GB_ASSERT_MSG(cond, NULL) -#endif - -#ifndef GB_ASSERT_NOT_NULL -#define GB_ASSERT_NOT_NULL(ptr) GB_ASSERT_MSG((ptr) != NULL, #ptr " must not be NULL") -#endif - -// NOTE(bill): Things that shouldn't happen with a message! -#ifndef GB_PANIC -#define GB_PANIC(msg, ...) do { \ - gb_assert_handler("Panic", NULL, __FILE__, cast(i64)__LINE__, msg, ##__VA_ARGS__); \ - GB_DEBUG_TRAP(); \ -} while (0) -#endif - -GB_DEF void gb_assert_handler(char const *prefix, char const *condition, char const *file, i32 line, char const *msg, ...); - - - -//////////////////////////////////////////////////////////////// -// -// Memory -// -// - - -GB_DEF b32 gb_is_power_of_two(isize x); - -GB_DEF void * gb_align_forward(void *ptr, isize alignment); - -GB_DEF void * gb_pointer_add (void *ptr, isize bytes); -GB_DEF void * gb_pointer_sub (void *ptr, isize bytes); -GB_DEF void const *gb_pointer_add_const(void const *ptr, isize bytes); -GB_DEF void const *gb_pointer_sub_const(void const *ptr, isize bytes); -GB_DEF isize gb_pointer_diff (void const *begin, void const *end); - - -GB_DEF void gb_zero_size(void *ptr, isize size); -#ifndef gb_zero_item -#define gb_zero_item(t) gb_zero_size((t), gb_size_of(*(t))) // NOTE(bill): Pass pointer of struct -#define gb_zero_array(a, count) gb_zero_size((a), gb_size_of(*(a))*count) -#endif - -GB_DEF void * gb_memcopy (void *dest, void const *source, isize size); -GB_DEF void * gb_memmove (void *dest, void const *source, isize size); -GB_DEF void * gb_memset (void *data, u8 byte_value, isize size); -GB_DEF i32 gb_memcompare(void const *s1, void const *s2, isize size); -GB_DEF void gb_memswap (void *i, void *j, isize size); -GB_DEF void const *gb_memchr (void const *data, u8 byte_value, isize size); -GB_DEF void const *gb_memrchr (void const *data, u8 byte_value, isize size); - - -#ifndef gb_memcopy_array -#define gb_memcopy_array(dst, src, count) gb_memcopy((dst), (src), gb_size_of(*(dst))*(count)) -#endif - -#ifndef gb_memmove_array -#define gb_memmove_array(dst, src, count) gb_memmove((dst), (src), gb_size_of(*(dst))*(count)) -#endif - -// NOTE(bill): Very similar to doing `*cast(T *)(&u)` -#ifndef GB_BIT_CAST -#define GB_BIT_CAST(dest, source) do { \ - GB_STATIC_ASSERT(gb_size_of(*(dest)) <= gb_size_of(source)); \ - gb_memcopy((dest), &(source), gb_size_of(*dest)); \ -} while (0) -#endif - - - - -#ifndef gb_kilobytes -#define gb_kilobytes(x) ( (x) * (i64)(1024)) -#define gb_megabytes(x) (gb_kilobytes(x) * (i64)(1024)) -#define gb_gigabytes(x) (gb_megabytes(x) * (i64)(1024)) -#define gb_terabytes(x) (gb_gigabytes(x) * (i64)(1024)) -#endif - - - - -// Atomics - -// TODO(bill): Be specific with memory order? -// e.g. relaxed, acquire, release, acquire_release - -#if defined(GB_COMPILER_MSVC) -typedef struct gbAtomic32 { i32 volatile value; } gbAtomic32; -typedef struct gbAtomic64 { i64 volatile value; } gbAtomic64; -typedef struct gbAtomicPtr { void *volatile value; } gbAtomicPtr; -#else - #if defined(GB_ARCH_32_BIT) - #define GB_ATOMIC_PTR_ALIGNMENT 4 - #elif defined(GB_ARCH_64_BIT) - #define GB_ATOMIC_PTR_ALIGNMENT 8 - #else - #error Unknown architecture - #endif - -typedef struct gbAtomic32 { i32 volatile value; } __attribute__ ((aligned(4))) gbAtomic32; -typedef struct gbAtomic64 { i64 volatile value; } __attribute__ ((aligned(8))) gbAtomic64; -typedef struct gbAtomicPtr { void *volatile value; } __attribute__ ((aligned(GB_ATOMIC_PTR_ALIGNMENT))) gbAtomicPtr; -#endif - -GB_DEF i32 gb_atomic32_load (gbAtomic32 const volatile *a); -GB_DEF void gb_atomic32_store (gbAtomic32 volatile *a, i32 value); -GB_DEF i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired); -GB_DEF i32 gb_atomic32_exchanged (gbAtomic32 volatile *a, i32 desired); -GB_DEF i32 gb_atomic32_fetch_add (gbAtomic32 volatile *a, i32 operand); -GB_DEF i32 gb_atomic32_fetch_and (gbAtomic32 volatile *a, i32 operand); -GB_DEF i32 gb_atomic32_fetch_or (gbAtomic32 volatile *a, i32 operand); -GB_DEF b32 gb_atomic32_spin_lock (gbAtomic32 volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default -GB_DEF void gb_atomic32_spin_unlock (gbAtomic32 volatile *a); -GB_DEF b32 gb_atomic32_try_acquire_lock(gbAtomic32 volatile *a); - - -GB_DEF i64 gb_atomic64_load (gbAtomic64 const volatile *a); -GB_DEF void gb_atomic64_store (gbAtomic64 volatile *a, i64 value); -GB_DEF i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired); -GB_DEF i64 gb_atomic64_exchanged (gbAtomic64 volatile *a, i64 desired); -GB_DEF i64 gb_atomic64_fetch_add (gbAtomic64 volatile *a, i64 operand); -GB_DEF i64 gb_atomic64_fetch_and (gbAtomic64 volatile *a, i64 operand); -GB_DEF i64 gb_atomic64_fetch_or (gbAtomic64 volatile *a, i64 operand); -GB_DEF b32 gb_atomic64_spin_lock (gbAtomic64 volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default -GB_DEF void gb_atomic64_spin_unlock (gbAtomic64 volatile *a); -GB_DEF b32 gb_atomic64_try_acquire_lock(gbAtomic64 volatile *a); - - -GB_DEF void *gb_atomic_ptr_load (gbAtomicPtr const volatile *a); -GB_DEF void gb_atomic_ptr_store (gbAtomicPtr volatile *a, void *value); -GB_DEF void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired); -GB_DEF void *gb_atomic_ptr_exchanged (gbAtomicPtr volatile *a, void *desired); -GB_DEF void *gb_atomic_ptr_fetch_add (gbAtomicPtr volatile *a, void *operand); -GB_DEF void *gb_atomic_ptr_fetch_and (gbAtomicPtr volatile *a, void *operand); -GB_DEF void *gb_atomic_ptr_fetch_or (gbAtomicPtr volatile *a, void *operand); -GB_DEF b32 gb_atomic_ptr_spin_lock (gbAtomicPtr volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default -GB_DEF void gb_atomic_ptr_spin_unlock (gbAtomicPtr volatile *a); -GB_DEF b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a); - - -// Fences -GB_DEF void gb_yield_thread(void); -GB_DEF void gb_mfence (void); -GB_DEF void gb_sfence (void); -GB_DEF void gb_lfence (void); - - -#if defined(GB_SYSTEM_WINDOWS) -typedef struct gbSemaphore { void *win32_handle; } gbSemaphore; -#elif defined(GB_SYSTEM_OSX) -typedef struct gbSemaphore { semaphore_t osx_handle; } gbSemaphore; -#elif defined(GB_SYSTEM_UNIX) -typedef struct gbSemaphore { sem_t unix_handle; } gbSemaphore; -#else -#error -#endif - -GB_DEF void gb_semaphore_init (gbSemaphore *s); -GB_DEF void gb_semaphore_destroy(gbSemaphore *s); -GB_DEF void gb_semaphore_post (gbSemaphore *s, i32 count); -GB_DEF void gb_semaphore_release(gbSemaphore *s); // NOTE(bill): gb_semaphore_post(s, 1) -GB_DEF void gb_semaphore_wait (gbSemaphore *s); - - -// Mutex -typedef struct gbMutex { -#if defined(GB_SYSTEM_WINDOWS) - CRITICAL_SECTION win32_critical_section; -#else - pthread_mutex_t pthread_mutex; - pthread_mutexattr_t pthread_mutexattr; -#endif -} gbMutex; - -GB_DEF void gb_mutex_init (gbMutex *m); -GB_DEF void gb_mutex_destroy (gbMutex *m); -GB_DEF void gb_mutex_lock (gbMutex *m); -GB_DEF b32 gb_mutex_try_lock(gbMutex *m); -GB_DEF void gb_mutex_unlock (gbMutex *m); - -// NOTE(bill): If you wanted a Scoped Mutex in C++, why not use the defer() construct? -// No need for a silly wrapper class and it's clear! -#if 0 -gbMutex m = {0}; -gb_mutex_init(&m); -{ - gb_mutex_lock(&m); - defer (gb_mutex_unlock(&m)); - - // Do whatever as the mutex is now scoped based! -} -#endif - - -typedef struct gbThread gbThread; - -#define GB_THREAD_PROC(name) isize name(gbThread *thread) -typedef GB_THREAD_PROC(gbThreadProc); - -struct gbThread { -#if defined(GB_SYSTEM_WINDOWS) - void * win32_handle; -#else - pthread_t posix_handle; -#endif - - gbThreadProc *proc; - void * user_data; - isize user_index; - isize return_value; - - gbSemaphore semaphore; - isize stack_size; - b32 volatile is_running; -}; - -GB_DEF void gb_thread_init (gbThread *t); -GB_DEF void gb_thread_destroy (gbThread *t); -GB_DEF void gb_thread_start (gbThread *t, gbThreadProc *proc, void *data); -GB_DEF void gb_thread_start_with_stack(gbThread *t, gbThreadProc *proc, void *data, isize stack_size); -GB_DEF void gb_thread_join (gbThread *t); -GB_DEF b32 gb_thread_is_running (gbThread const *t); -GB_DEF u32 gb_thread_current_id (void); -GB_DEF void gb_thread_set_name (gbThread *t, char const *name); - - -// NOTE(bill): Thread Merge Operation -// Based on Sean Barrett's stb_sync -typedef struct gbSync { - i32 target; // Target Number of threads - i32 current; // Threads to hit - i32 waiting; // Threads waiting - - gbMutex start; - gbMutex mutex; - gbSemaphore release; -} gbSync; - -GB_DEF void gb_sync_init (gbSync *s); -GB_DEF void gb_sync_destroy (gbSync *s); -GB_DEF void gb_sync_set_target (gbSync *s, i32 count); -GB_DEF void gb_sync_release (gbSync *s); -GB_DEF i32 gb_sync_reach (gbSync *s); -GB_DEF void gb_sync_reach_and_wait(gbSync *s); - - - -#if defined(GB_SYSTEM_WINDOWS) - -typedef struct gbAffinity { - b32 is_accurate; - isize core_count; - isize thread_count; - #define GB_WIN32_MAX_THREADS (8 * gb_size_of(usize)) - usize core_masks[GB_WIN32_MAX_THREADS]; - -} gbAffinity; - -#elif defined(GB_SYSTEM_OSX) -typedef struct gbAffinity { - b32 is_accurate; - isize core_count; - isize thread_count; - isize threads_per_core; -} gbAffinity; - -#elif defined(GB_SYSTEM_LINUX) -typedef struct gbAffinity { - b32 is_accurate; - isize core_count; - isize thread_count; - isize threads_per_core; -} gbAffinity; -#else -#error TODO(bill): Unknown system -#endif - -GB_DEF void gb_affinity_init (gbAffinity *a); -GB_DEF void gb_affinity_destroy(gbAffinity *a); -GB_DEF b32 gb_affinity_set (gbAffinity *a, isize core, isize thread); -GB_DEF isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core); - - - - -//////////////////////////////////////////////////////////////// -// -// Virtual Memory -// -// - -typedef struct gbVirtualMemory { - void *data; - isize size; -} gbVirtualMemory; - -GB_DEF gbVirtualMemory gb_virtual_memory(void *data, isize size); -GB_DEF gbVirtualMemory gb_vm_alloc (void *addr, isize size); -GB_DEF b32 gb_vm_free (gbVirtualMemory vm); -GB_DEF gbVirtualMemory gb_vm_trim (gbVirtualMemory vm, isize lead_size, isize size); -GB_DEF b32 gb_vm_purge (gbVirtualMemory vm); -GB_DEF isize gb_virtual_memory_page_size(isize *alignment_out); - - - - -//////////////////////////////////////////////////////////////// -// -// Custom Allocation -// -// - -typedef enum gbAllocationType { - gbAllocation_Alloc, - gbAllocation_Free, - gbAllocation_FreeAll, - gbAllocation_Resize, -} gbAllocationType; - -// NOTE(bill): This is useful so you can define an allocator of the same type and parameters -#define GB_ALLOCATOR_PROC(name) \ -void *name(void *allocator_data, gbAllocationType type, \ - isize size, isize alignment, \ - void *old_memory, isize old_size, \ - u64 flags) -typedef GB_ALLOCATOR_PROC(gbAllocatorProc); - -typedef struct gbAllocator { - gbAllocatorProc *proc; - void * data; -} gbAllocator; - -typedef enum gbAllocatorFlag { - gbAllocatorFlag_ClearToZero = GB_BIT(0), -} gbAllocatorFlag; - -// TODO(bill): Is this a decent default alignment? -#ifndef GB_DEFAULT_MEMORY_ALIGNMENT -#define GB_DEFAULT_MEMORY_ALIGNMENT (2 * gb_size_of(void *)) -#endif - -#ifndef GB_DEFAULT_ALLOCATOR_FLAGS -#define GB_DEFAULT_ALLOCATOR_FLAGS (gbAllocatorFlag_ClearToZero) -#endif - -GB_DEF void *gb_alloc_align (gbAllocator a, isize size, isize alignment); -GB_DEF void *gb_alloc (gbAllocator a, isize size); -GB_DEF void gb_free (gbAllocator a, void *ptr); -GB_DEF void gb_free_all (gbAllocator a); -GB_DEF void *gb_resize (gbAllocator a, void *ptr, isize old_size, isize new_size); -GB_DEF void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment); -// TODO(bill): For gb_resize, should the use need to pass the old_size or only the new_size? - -GB_DEF void *gb_alloc_copy (gbAllocator a, void const *src, isize size); -GB_DEF void *gb_alloc_copy_align(gbAllocator a, void const *src, isize size, isize alignment); -GB_DEF char *gb_alloc_str (gbAllocator a, char const *str); -GB_DEF char *gb_alloc_str_len (gbAllocator a, char const *str, isize len); - - -// NOTE(bill): These are very useful and the type cast has saved me from numerous bugs -#ifndef gb_alloc_item -#define gb_alloc_item(allocator_, Type) (Type *)gb_alloc(allocator_, gb_size_of(Type)) -#define gb_alloc_array(allocator_, Type, count) (Type *)gb_alloc(allocator_, gb_size_of(Type) * (count)) -#endif - -// NOTE(bill): Use this if you don't need a "fancy" resize allocation -GB_DEF void *gb_default_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment); - - - -// TODO(bill): Probably use a custom heap allocator system that doesn't depend on malloc/free -// Base it off TCMalloc or something else? Or something entirely custom? -GB_DEF gbAllocator gb_heap_allocator(void); -GB_DEF GB_ALLOCATOR_PROC(gb_heap_allocator_proc); - -// NOTE(bill): Yep, I use my own allocator system! -#ifndef gb_malloc -#define gb_malloc(sz) gb_alloc(gb_heap_allocator(), sz) -#define gb_mfree(ptr) gb_free(gb_heap_allocator(), ptr) -#endif - - - -// -// Arena Allocator -// -typedef struct gbArena { - gbAllocator backing; - void * physical_start; - isize total_size; - isize total_allocated; - isize temp_count; -} gbArena; - -GB_DEF void gb_arena_init_from_memory (gbArena *arena, void *start, isize size); -GB_DEF void gb_arena_init_from_allocator(gbArena *arena, gbAllocator backing, isize size); -GB_DEF void gb_arena_init_sub (gbArena *arena, gbArena *parent_arena, isize size); -GB_DEF void gb_arena_free (gbArena *arena); - -GB_DEF isize gb_arena_alignment_of (gbArena *arena, isize alignment); -GB_DEF isize gb_arena_size_remaining(gbArena *arena, isize alignment); -GB_DEF void gb_arena_check (gbArena *arena); - - -// Allocation Types: alloc, free_all, resize -GB_DEF gbAllocator gb_arena_allocator(gbArena *arena); -GB_DEF GB_ALLOCATOR_PROC(gb_arena_allocator_proc); - - - -typedef struct gbTempArenaMemory { - gbArena *arena; - isize original_count; -} gbTempArenaMemory; - -GB_DEF gbTempArenaMemory gb_temp_arena_memory_begin(gbArena *arena); -GB_DEF void gb_temp_arena_memory_end (gbTempArenaMemory tmp_mem); - - - - - - - -// -// Pool Allocator -// - - -typedef struct gbPool { - gbAllocator backing; - void * physical_start; - void * free_list; - isize block_size; - isize block_align; - isize total_size; -} gbPool; - -GB_DEF void gb_pool_init (gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size); -GB_DEF void gb_pool_init_align(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size, isize block_align); -GB_DEF void gb_pool_free (gbPool *pool); - -// Allocation Types: alloc, free -GB_DEF gbAllocator gb_pool_allocator(gbPool *pool); -GB_DEF GB_ALLOCATOR_PROC(gb_pool_allocator_proc); - - - -// NOTE(bill): Used for allocators to keep track of sizes -typedef struct gbAllocationHeader { - isize size; -} gbAllocationHeader; - -GB_DEF gbAllocationHeader *gb_allocation_header (void *data); -GB_DEF void gb_allocation_header_fill(gbAllocationHeader *header, void *data, isize size); - -// TODO(bill): Find better way of doing this without #if #elif etc. -#if defined(GB_ARCH_32_BIT) -#define GB_ISIZE_HIGH_BIT 0x80000000 -#elif defined(GB_ARCH_64_BIT) -#define GB_ISIZE_HIGH_BIT 0x8000000000000000ll -#else -#error -#endif - -// -// Free List Allocator -// - -// IMPORTANT TODO(bill): Thoroughly test the free list allocator! -// NOTE(bill): This is a very shitty free list as it just picks the first free block not the best size -// as I am just being lazy. Also, I will probably remove it later; it's only here because why not?! -// -// NOTE(bill): I may also complete remove this if I completely implement a fixed heap allocator - -typedef struct gbFreeListBlock gbFreeListBlock; -struct gbFreeListBlock { - gbFreeListBlock *next; - isize size; -}; - -typedef struct gbFreeList { - void * physical_start; - isize total_size; - - gbFreeListBlock *curr_block; - - isize total_allocated; - isize allocation_count; -} gbFreeList; - -GB_DEF void gb_free_list_init (gbFreeList *fl, void *start, isize size); -GB_DEF void gb_free_list_init_from_allocator(gbFreeList *fl, gbAllocator backing, isize size); - -// Allocation Types: alloc, free, free_all, resize -GB_DEF gbAllocator gb_free_list_allocator(gbFreeList *fl); -GB_DEF GB_ALLOCATOR_PROC(gb_free_list_allocator_proc); - - - -// -// Scratch Memory Allocator - Ring Buffer Based Arena -// - -typedef struct gbScratchMemory { - void *physical_start; - isize total_size; - void *alloc_point; - void *free_point; -} gbScratchMemory; - -GB_DEF void gb_scratch_memory_init (gbScratchMemory *s, void *start, isize size); -GB_DEF b32 gb_scratch_memory_is_in_use(gbScratchMemory *s, void *ptr); - - -// Allocation Types: alloc, free, free_all, resize -GB_DEF gbAllocator gb_scratch_allocator(gbScratchMemory *s); -GB_DEF GB_ALLOCATOR_PROC(gb_scratch_allocator_proc); - -// TODO(bill): Stack allocator -// TODO(bill): Fixed heap allocator -// TODO(bill): General heap allocator. Maybe a TCMalloc like clone? - - -//////////////////////////////////////////////////////////////// -// -// Sort & Search -// -// - -#define GB_COMPARE_PROC(name) int name(void const *a, void const *b) -typedef GB_COMPARE_PROC(gbCompareProc); - -#define GB_COMPARE_PROC_PTR(def) GB_COMPARE_PROC((*def)) - -// Producure pointers -// NOTE(bill): The offset parameter specifies the offset in the structure -// e.g. gb_i32_cmp(gb_offset_of(Thing, value)) -// Use 0 if it's just the type instead. - -GB_DEF GB_COMPARE_PROC_PTR(gb_i16_cmp (isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_i32_cmp (isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_i64_cmp (isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_isize_cmp(isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_str_cmp (isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_f32_cmp (isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_f64_cmp (isize offset)); -GB_DEF GB_COMPARE_PROC_PTR(gb_char_cmp (isize offset)); - -// TODO(bill): Better sorting algorithms -// NOTE(bill): Uses quick sort for large arrays but insertion sort for small -#define gb_sort_array(array, count, compare_proc) gb_sort(array, count, gb_size_of(*(array)), compare_proc) -GB_DEF void gb_sort(void *base, isize count, isize size, gbCompareProc compare_proc); - -// NOTE(bill): the count of temp == count of items -#define gb_radix_sort(Type) gb_radix_sort_##Type -#define GB_RADIX_SORT_PROC(Type) void gb_radix_sort(Type)(Type *items, Type *temp, isize count) - -GB_DEF GB_RADIX_SORT_PROC(u8); -GB_DEF GB_RADIX_SORT_PROC(u16); -GB_DEF GB_RADIX_SORT_PROC(u32); -GB_DEF GB_RADIX_SORT_PROC(u64); - - -// NOTE(bill): Returns index or -1 if not found -#define gb_binary_search_array(array, count, key, compare_proc) gb_binary_search(array, count, gb_size_of(*(array)), key, compare_proc) -GB_DEF isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc); - -#define gb_shuffle_array(array, count) gb_shuffle(array, count, gb_size_of(*(array))) -GB_DEF void gb_shuffle(void *base, isize count, isize size); - -#define gb_reverse_array(array, count) gb_reverse(array, count, gb_size_of(*(array))) -GB_DEF void gb_reverse(void *base, isize count, isize size); - -//////////////////////////////////////////////////////////////// -// -// Char Functions -// -// - -GB_DEF char gb_char_to_lower (char c); -GB_DEF char gb_char_to_upper (char c); -GB_DEF b32 gb_char_is_space (char c); -GB_DEF b32 gb_char_is_digit (char c); -GB_DEF b32 gb_char_is_hex_digit (char c); -GB_DEF b32 gb_char_is_alpha (char c); -GB_DEF b32 gb_char_is_alphanumeric(char c); -GB_DEF i32 gb_digit_to_int (char c); -GB_DEF i32 gb_hex_digit_to_int (char c); - -// NOTE(bill): ASCII only -GB_DEF void gb_str_to_lower(char *str); -GB_DEF void gb_str_to_upper(char *str); - -GB_DEF isize gb_strlen (char const *str); -GB_DEF isize gb_strnlen(char const *str, isize max_len); -GB_DEF i32 gb_strcmp (char const *s1, char const *s2); -GB_DEF i32 gb_strncmp(char const *s1, char const *s2, isize len); -GB_DEF char *gb_strcpy (char *dest, char const *source); -GB_DEF char *gb_strncpy(char *dest, char const *source, isize len); -GB_DEF isize gb_strlcpy(char *dest, char const *source, isize len); -GB_DEF char *gb_strrev (char *str); // NOTE(bill): ASCII only - -// NOTE(bill): A less fucking crazy strtok! -GB_DEF char const *gb_strtok(char *output, char const *src, char const *delimit); - -GB_DEF b32 gb_str_has_prefix(char const *str, char const *prefix); -GB_DEF b32 gb_str_has_suffix(char const *str, char const *suffix); - -GB_DEF char const *gb_char_first_occurence(char const *str, char c); -GB_DEF char const *gb_char_last_occurence (char const *str, char c); - -GB_DEF void gb_str_concat(char *dest, isize dest_len, - char const *src_a, isize src_a_len, - char const *src_b, isize src_b_len); - -GB_DEF u64 gb_str_to_u64(char const *str, char **end_ptr, i32 base); // TODO(bill): Support more than just decimal and hexadecimal -GB_DEF i64 gb_str_to_i64(char const *str, char **end_ptr, i32 base); // TODO(bill): Support more than just decimal and hexadecimal -GB_DEF f32 gb_str_to_f32(char const *str, char **end_ptr); -GB_DEF f64 gb_str_to_f64(char const *str, char **end_ptr); -GB_DEF void gb_i64_to_str(i64 value, char *string, i32 base); -GB_DEF void gb_u64_to_str(u64 value, char *string, i32 base); - - -//////////////////////////////////////////////////////////////// -// -// UTF-8 Handling -// -// - -// NOTE(bill): Does not check if utf-8 string is valid -GB_DEF isize gb_utf8_strlen (u8 const *str); -GB_DEF isize gb_utf8_strnlen(u8 const *str, isize max_len); - -// NOTE(bill): Windows doesn't handle 8 bit filenames well ('cause Micro$hit) -GB_DEF u16 *gb_utf8_to_ucs2 (u16 *buffer, isize len, u8 const *str); -GB_DEF u8 * gb_ucs2_to_utf8 (u8 *buffer, isize len, u16 const *str); -GB_DEF u16 *gb_utf8_to_ucs2_buf(u8 const *str); // NOTE(bill): Uses locally persisting buffer -GB_DEF u8 * gb_ucs2_to_utf8_buf(u16 const *str); // NOTE(bill): Uses locally persisting buffer - -// NOTE(bill): Returns size of codepoint in bytes -GB_DEF isize gb_utf8_decode (u8 const *str, isize str_len, Rune *codepoint); -GB_DEF isize gb_utf8_codepoint_size(u8 const *str, isize str_len); -GB_DEF isize gb_utf8_encode_rune (u8 buf[4], Rune r); - -//////////////////////////////////////////////////////////////// -// -// gbString - C Read-Only-Compatible -// -// -/* -Reasoning: - - By default, strings in C are null terminated which means you have to count - the number of character up to the null character to calculate the length. - Many "better" C string libraries will create a struct for a string. - i.e. - - struct String { - Allocator allocator; - size_t length; - size_t capacity; - char * cstring; - }; - - This library tries to augment normal C strings in a better way that is still - compatible with C-style strings. - - +--------+-----------------------+-----------------+ - | Header | Binary C-style String | Null Terminator | - +--------+-----------------------+-----------------+ - | - +-> Pointer returned by functions - - Due to the meta-data being stored before the string pointer and every gb string - having an implicit null terminator, gb strings are full compatible with c-style - strings and read-only functions. - -Advantages: - - * gb strings can be passed to C-style string functions without accessing a struct - member of calling a function, i.e. - - gb_printf("%s\n", gb_str); - - Many other libraries do either of these: - - gb_printf("%s\n", string->cstr); - gb_printf("%s\n", get_cstring(string)); - - * You can access each character just like a C-style string: - - gb_printf("%c %c\n", str[0], str[13]); - - * gb strings are singularly allocated. The meta-data is next to the character - array which is better for the cache. - -Disadvantages: - - * In the C version of these functions, many return the new string. i.e. - str = gb_string_appendc(str, "another string"); - This could be changed to gb_string_appendc(&str, "another string"); but I'm still not sure. - - * This is incompatible with "gb_string.h" strings -*/ - -#if 0 -#define GB_IMPLEMENTATION -#include "gb.h" -int main(int argc, char **argv) { - gbString str = gb_string_make("Hello"); - gbString other_str = gb_string_make_length(", ", 2); - str = gb_string_append(str, other_str); - str = gb_string_appendc(str, "world!"); - - gb_printf("%s\n", str); // Hello, world! - - gb_printf("str length = %d\n", gb_string_length(str)); - - str = gb_string_set(str, "Potato soup"); - gb_printf("%s\n", str); // Potato soup - - str = gb_string_set(str, "Hello"); - other_str = gb_string_set(other_str, "Pizza"); - if (gb_strings_are_equal(str, other_str)) - gb_printf("Not called\n"); - else - gb_printf("Called\n"); - - str = gb_string_set(str, "Ab.;!...AHello World ??"); - str = gb_string_trim(str, "Ab.;!. ?"); - gb_printf("%s\n", str); // "Hello World" - - gb_string_free(str); - gb_string_free(other_str); - - return 0; -} -#endif - -// TODO(bill): Should this be a wrapper to gbArray(char) or this extra type safety better? -typedef char *gbString; - -// NOTE(bill): If you only need a small string, just use a standard c string or change the size from isize to u16, etc. -typedef struct gbStringHeader { - gbAllocator allocator; - isize length; - isize capacity; -} gbStringHeader; - -#define GB_STRING_HEADER(str) (cast(gbStringHeader *)(str) - 1) - -GB_DEF gbString gb_string_make_reserve (gbAllocator a, isize capacity); -GB_DEF gbString gb_string_make (gbAllocator a, char const *str); -GB_DEF gbString gb_string_make_length (gbAllocator a, void const *str, isize num_bytes); -GB_DEF void gb_string_free (gbString str); -GB_DEF gbString gb_string_duplicate (gbAllocator a, gbString const str); -GB_DEF isize gb_string_length (gbString const str); -GB_DEF isize gb_string_capacity (gbString const str); -GB_DEF isize gb_string_available_space(gbString const str); -GB_DEF void gb_string_clear (gbString str); -GB_DEF gbString gb_string_append (gbString str, gbString const other); -GB_DEF gbString gb_string_append_length (gbString str, void const *other, isize num_bytes); -GB_DEF gbString gb_string_appendc (gbString str, char const *other); -GB_DEF gbString gb_string_append_rune (gbString str, Rune r); -GB_DEF gbString gb_string_append_fmt (gbString str, char const *fmt, ...); -GB_DEF gbString gb_string_set (gbString str, char const *cstr); -GB_DEF gbString gb_string_make_space_for (gbString str, isize add_len); -GB_DEF isize gb_string_allocation_size(gbString const str); -GB_DEF b32 gb_string_are_equal (gbString const lhs, gbString const rhs); -GB_DEF gbString gb_string_trim (gbString str, char const *cut_set); -GB_DEF gbString gb_string_trim_space (gbString str); // Whitespace ` \t\r\n\v\f` - - - -//////////////////////////////////////////////////////////////// -// -// Fixed Capacity Buffer (POD Types) -// -// -// gbBuffer(Type) works like gbString or gbArray where the actual type is just a pointer to the first -// element. -// - -typedef struct gbBufferHeader { - isize count; - isize capacity; -} gbBufferHeader; - -#define gbBuffer(Type) Type * - -#define GB_BUFFER_HEADER(x) (cast(gbBufferHeader *)(x) - 1) -#define gb_buffer_count(x) (GB_BUFFER_HEADER(x)->count) -#define gb_buffer_capacity(x) (GB_BUFFER_HEADER(x)->capacity) - -#define gb_buffer_init(x, allocator, cap) do { \ - void **nx = cast(void **)&(x); \ - gbBufferHeader *gb__bh = cast(gbBufferHeader *)gb_alloc((allocator), (cap)*gb_size_of(*(x))); \ - gb__bh->count = 0; \ - gb__bh->capacity = cap; \ - *nx = cast(void *)(gb__bh+1); \ -} while (0) - - -#define gb_buffer_free(x, allocator) (gb_free(allocator, GB_BUFFER_HEADER(x))) - -#define gb_buffer_append(x, item) do { (x)[gb_buffer_count(x)++] = (item); } while (0) - -#define gb_buffer_appendv(x, items, item_count) do { \ - GB_ASSERT(gb_size_of(*(items)) == gb_size_of(*(x))); \ - GB_ASSERT(gb_buffer_count(x)+item_count <= gb_buffer_capacity(x)); \ - gb_memcopy(&(x)[gb_buffer_count(x)], (items), gb_size_of(*(x))*(item_count)); \ - gb_buffer_count(x) += (item_count); \ -} while (0) - -#define gb_buffer_pop(x) do { GB_ASSERT(gb_buffer_count(x) > 0); gb_buffer_count(x)--; } while (0) -#define gb_buffer_clear(x) do { gb_buffer_count(x) = 0; } while (0) - - - -//////////////////////////////////////////////////////////////// -// -// Dynamic Array (POD Types) -// -// NOTE(bill): I know this is a macro hell but C is an old (and shit) language with no proper arrays -// Also why the fuck not?! It fucking works! And it has custom allocation, which is already better than C++! -// -// gbArray(Type) works like gbString or gbBuffer where the actual type is just a pointer to the first -// element. -// - - - -// Available Procedures for gbArray(Type) -// gb_array_init -// gb_array_free -// gb_array_set_capacity -// gb_array_grow -// gb_array_append -// gb_array_appendv -// gb_array_pop -// gb_array_clear -// gb_array_resize -// gb_array_reserve -// - -#if 0 // Example -void foo(void) { - isize i; - int test_values[] = {4, 2, 1, 7}; - gbAllocator a = gb_heap_allocator(); - gbArray(int) items; - - gb_array_init(items, a); - - gb_array_append(items, 1); - gb_array_append(items, 4); - gb_array_append(items, 9); - gb_array_append(items, 16); - - items[1] = 3; // Manually set value - // NOTE: No array bounds checking - - for (i = 0; i < items.count; i++) - gb_printf("%d\n", items[i]); - // 1 - // 3 - // 9 - // 16 - - gb_array_clear(items); - - gb_array_appendv(items, test_values, gb_count_of(test_values)); - for (i = 0; i < items.count; i++) - gb_printf("%d\n", items[i]); - // 4 - // 2 - // 1 - // 7 - - gb_array_free(items); -} -#endif - -typedef struct gbArrayHeader { - gbAllocator allocator; - isize count; - isize capacity; -} gbArrayHeader; - -// NOTE(bill): This thing is magic! -#define gbArray(Type) Type * - -#ifndef GB_ARRAY_GROW_FORMULA -#define GB_ARRAY_GROW_FORMULA(x) (2*(x) + 8) -#endif - -GB_STATIC_ASSERT(GB_ARRAY_GROW_FORMULA(0) > 0); - -#define GB_ARRAY_HEADER(x) (cast(gbArrayHeader *)(x) - 1) -#define gb_array_allocator(x) (GB_ARRAY_HEADER(x)->allocator) -#define gb_array_count(x) (GB_ARRAY_HEADER(x)->count) -#define gb_array_capacity(x) (GB_ARRAY_HEADER(x)->capacity) - -// TODO(bill): Have proper alignment! -#define gb_array_init_reserve(x, allocator_, cap) do { \ - void **gb__array_ = cast(void **)&(x); \ - gbArrayHeader *gb__ah = cast(gbArrayHeader *)gb_alloc(allocator_, gb_size_of(gbArrayHeader)+gb_size_of(*(x))*(cap)); \ - gb__ah->allocator = allocator_; \ - gb__ah->count = 0; \ - gb__ah->capacity = cap; \ - *gb__array_ = cast(void *)(gb__ah+1); \ -} while (0) - -// NOTE(bill): Give it an initial default capacity -#define gb_array_init(x, allocator) gb_array_init_reserve(x, allocator, GB_ARRAY_GROW_FORMULA(0)) - -#define gb_array_free(x) do { \ - gbArrayHeader *gb__ah = GB_ARRAY_HEADER(x); \ - gb_free(gb__ah->allocator, gb__ah); \ -} while (0) - -#define gb_array_set_capacity(x, capacity) do { \ - if (x) { \ - void **gb__array_ = cast(void **)&(x); \ - *gb__array_ = gb__array_set_capacity((x), (capacity), gb_size_of(*(x))); \ - } \ -} while (0) - -// NOTE(bill): Do not use the thing below directly, use the macro -GB_DEF void *gb__array_set_capacity(void *array, isize capacity, isize element_size); - - -// TODO(bill): Decide on a decent growing formula for gbArray -#define gb_array_grow(x, min_capacity) do { \ - isize new_capacity = GB_ARRAY_GROW_FORMULA(gb_array_capacity(x)); \ - if (new_capacity < (min_capacity)) \ - new_capacity = (min_capacity); \ - gb_array_set_capacity(x, new_capacity); \ -} while (0) - - -#define gb_array_append(x, item) do { \ - if (gb_array_capacity(x) < gb_array_count(x)+1) \ - gb_array_grow(x, 0); \ - (x)[gb_array_count(x)++] = (item); \ -} while (0) - -#define gb_array_appendv(x, items, item_count) do { \ - gbArrayHeader *gb__ah = GB_ARRAY_HEADER(x); \ - GB_ASSERT(gb_size_of((items)[0]) == gb_size_of((x)[0])); \ - if (gb__ah->capacity < gb__ah->count+(item_count)) \ - gb_array_grow(x, gb__ah->count+(item_count)); \ - gb_memcopy(&(x)[gb__ah->count], (items), gb_size_of((x)[0])*(item_count));\ - gb__ah->count += (item_count); \ -} while (0) - - - -#define gb_array_pop(x) do { GB_ASSERT(GB_ARRAY_HEADER(x)->count > 0); GB_ARRAY_HEADER(x)->count--; } while (0) -#define gb_array_clear(x) do { GB_ARRAY_HEADER(x)->count = 0; } while (0) - -#define gb_array_resize(x, new_count) do { \ - if (GB_ARRAY_HEADER(x)->capacity < (new_count)) \ - gb_array_grow(x, (new_count)); \ - GB_ARRAY_HEADER(x)->count = (new_count); \ -} while (0) - - -#define gb_array_reserve(x, new_capacity) do { \ - if (GB_ARRAY_HEADER(x)->capacity < (new_capacity)) \ - gb_array_set_capacity(x, new_capacity); \ -} while (0) - - - - - -//////////////////////////////////////////////////////////////// -// -// Hashing and Checksum Functions -// -// - -GB_EXTERN u32 gb_adler32(void const *data, isize len); - -GB_EXTERN u32 gb_crc32(void const *data, isize len); -GB_EXTERN u64 gb_crc64(void const *data, isize len); - -GB_EXTERN u32 gb_fnv32 (void const *data, isize len); -GB_EXTERN u64 gb_fnv64 (void const *data, isize len); -GB_EXTERN u32 gb_fnv32a(void const *data, isize len); -GB_EXTERN u64 gb_fnv64a(void const *data, isize len); - -// NOTE(bill): Default seed of 0x9747b28c -// NOTE(bill): I prefer using murmur64 for most hashes -GB_EXTERN u32 gb_murmur32(void const *data, isize len); -GB_EXTERN u64 gb_murmur64(void const *data, isize len); - -GB_EXTERN u32 gb_murmur32_seed(void const *data, isize len, u32 seed); -GB_EXTERN u64 gb_murmur64_seed(void const *data, isize len, u64 seed); - - -//////////////////////////////////////////////////////////////// -// -// Instantiated Hash Table -// -// This is an attempt to implement a templated hash table -// NOTE(bill): The key is aways a u64 for simplicity and you will _probably_ _never_ need anything bigger. -// -// Hash table type and function declaration, call: GB_TABLE_DECLARE(PREFIX, NAME, N, VALUE) -// Hash table function definitions, call: GB_TABLE_DEFINE(NAME, N, VALUE) -// -// PREFIX - a prefix for function prototypes e.g. extern, static, etc. -// NAME - Name of the Hash Table -// FUNC - the name will prefix function names -// VALUE - the type of the value to be stored -// -// NOTE(bill): I really wish C had decent metaprogramming capabilities (and no I don't mean C++'s templates either) -// - -typedef struct gbHashTableFindResult { - isize hash_index; - isize entry_prev; - isize entry_index; -} gbHashTableFindResult; - -#define GB_TABLE(PREFIX, NAME, FUNC, VALUE) \ - GB_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE); \ - GB_TABLE_DEFINE(NAME, FUNC, VALUE); - -#define GB_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE) \ -typedef struct GB_JOIN2(NAME,Entry) { \ - u64 key; \ - isize next; \ - VALUE value; \ -} GB_JOIN2(NAME,Entry); \ -\ -typedef struct NAME { \ - gbArray(isize) hashes; \ - gbArray(GB_JOIN2(NAME,Entry)) entries; \ -} NAME; \ -\ -PREFIX void GB_JOIN2(FUNC,init) (NAME *h, gbAllocator a); \ -PREFIX void GB_JOIN2(FUNC,destroy) (NAME *h); \ -PREFIX VALUE * GB_JOIN2(FUNC,get) (NAME *h, u64 key); \ -PREFIX void GB_JOIN2(FUNC,set) (NAME *h, u64 key, VALUE value); \ -PREFIX void GB_JOIN2(FUNC,grow) (NAME *h); \ -PREFIX void GB_JOIN2(FUNC,rehash) (NAME *h, isize new_count); \ - - - - - -#define GB_TABLE_DEFINE(NAME, FUNC, VALUE) \ -void GB_JOIN2(FUNC,init)(NAME *h, gbAllocator a) { \ - gb_array_init(h->hashes, a); \ - gb_array_init(h->entries, a); \ -} \ -\ -void GB_JOIN2(FUNC,destroy)(NAME *h) { \ - if (h->entries) gb_array_free(h->entries); \ - if (h->hashes) gb_array_free(h->hashes); \ -} \ -\ -gb_internal isize GB_JOIN2(FUNC,_add_entry)(NAME *h, u64 key) { \ - isize index; \ - GB_JOIN2(NAME,Entry) e = {0}; \ - e.key = key; \ - e.next = -1; \ - index = gb_array_count(h->entries); \ - gb_array_append(h->entries, e); \ - return index; \ -} \ -\ -gb_internal gbHashTableFindResult GB_JOIN2(FUNC,_find)(NAME *h, u64 key) { \ - gbHashTableFindResult r = {-1, -1, -1}; \ - if (gb_array_count(h->hashes) > 0) { \ - r.hash_index = key % gb_array_count(h->hashes); \ - r.entry_index = h->hashes[r.hash_index]; \ - while (r.entry_index >= 0) { \ - if (h->entries[r.entry_index].key == key) \ - return r; \ - r.entry_prev = r.entry_index; \ - r.entry_index = h->entries[r.entry_index].next; \ - } \ - } \ - return r; \ -} \ -\ -gb_internal b32 GB_JOIN2(FUNC,_full)(NAME *h) { \ - return 0.75f * gb_array_count(h->hashes) < gb_array_count(h->entries); \ -} \ -\ -void GB_JOIN2(FUNC,grow)(NAME *h) { \ - isize new_count = GB_ARRAY_GROW_FORMULA(gb_array_count(h->entries)); \ - GB_JOIN2(FUNC,rehash)(h, new_count); \ -} \ -\ -void GB_JOIN2(FUNC,rehash)(NAME *h, isize new_count) { \ - isize i, j; \ - NAME nh = {0}; \ - GB_JOIN2(FUNC,init)(&nh, gb_array_allocator(h->hashes)); \ - gb_array_resize(nh.hashes, new_count); \ - gb_array_reserve(nh.entries, gb_array_count(h->entries)); \ - for (i = 0; i < new_count; i++) \ - nh.hashes[i] = -1; \ - for (i = 0; i < gb_array_count(h->entries); i++) { \ - GB_JOIN2(NAME,Entry) *e; \ - gbHashTableFindResult fr; \ - if (gb_array_count(nh.hashes) == 0) \ - GB_JOIN2(FUNC,grow)(&nh); \ - e = &h->entries[i]; \ - fr = GB_JOIN2(FUNC,_find)(&nh, e->key); \ - j = GB_JOIN2(FUNC,_add_entry)(&nh, e->key); \ - if (fr.entry_prev < 0) \ - nh.hashes[fr.hash_index] = j; \ - else \ - nh.entries[fr.entry_prev].next = j; \ - nh.entries[j].next = fr.entry_index; \ - nh.entries[j].value = e->value; \ - if (GB_JOIN2(FUNC,_full)(&nh)) \ - GB_JOIN2(FUNC,grow)(&nh); \ - } \ - GB_JOIN2(FUNC,destroy)(h); \ - h->hashes = nh.hashes; \ - h->entries = nh.entries; \ -} \ -\ -VALUE *GB_JOIN2(FUNC,get)(NAME *h, u64 key) { \ - isize index = GB_JOIN2(FUNC,_find)(h, key).entry_index; \ - if (index >= 0) \ - return &h->entries[index].value; \ - return NULL; \ -} \ -\ -void GB_JOIN2(FUNC,set)(NAME *h, u64 key, VALUE value) { \ - isize index; \ - gbHashTableFindResult fr; \ - if (gb_array_count(h->hashes) == 0) \ - GB_JOIN2(FUNC,grow)(h); \ - fr = GB_JOIN2(FUNC,_find)(h, key); \ - if (fr.entry_index >= 0) { \ - index = fr.entry_index; \ - } else { \ - index = GB_JOIN2(FUNC,_add_entry)(h, key); \ - if (fr.entry_prev >= 0) { \ - h->entries[fr.entry_prev].next = index; \ - } else { \ - h->hashes[fr.hash_index] = index; \ - } \ - } \ - h->entries[index].value = value; \ - if (GB_JOIN2(FUNC,_full)(h)) \ - GB_JOIN2(FUNC,grow)(h); \ -} \ - - - - -//////////////////////////////////////////////////////////////// -// -// File Handling -// - - -typedef u32 gbFileMode; -typedef enum gbFileModeFlag { - gbFileMode_Read = GB_BIT(0), - gbFileMode_Write = GB_BIT(1), - gbFileMode_Append = GB_BIT(2), - gbFileMode_Rw = GB_BIT(3), - - gbFileMode_Modes = gbFileMode_Read | gbFileMode_Write | gbFileMode_Append | gbFileMode_Rw, -} gbFileModeFlag; - -// NOTE(bill): Only used internally and for the file operations -typedef enum gbSeekWhenceType { - gbSeekWhence_Begin = 0, - gbSeekWhence_Current = 1, - gbSeekWhence_End = 2, -} gbSeekWhenceType; - -typedef enum gbFileError { - gbFileError_None, - gbFileError_Invalid, - gbFileError_InvalidFilename, - gbFileError_Exists, - gbFileError_NotExists, - gbFileError_Permission, - gbFileError_TruncationFailure, -} gbFileError; - -typedef union gbFileDescriptor { - void * p; - intptr i; - uintptr u; -} gbFileDescriptor; - -typedef struct gbFileOperations gbFileOperations; - -#define GB_FILE_OPEN_PROC(name) gbFileError name(gbFileDescriptor *fd, gbFileOperations *ops, gbFileMode mode, char const *filename) -#define GB_FILE_READ_AT_PROC(name) b32 name(gbFileDescriptor fd, void *buffer, isize size, i64 offset, isize *bytes_read) -#define GB_FILE_WRITE_AT_PROC(name) b32 name(gbFileDescriptor fd, void const *buffer, isize size, i64 offset, isize *bytes_written) -#define GB_FILE_SEEK_PROC(name) b32 name(gbFileDescriptor fd, i64 offset, gbSeekWhenceType whence, i64 *new_offset) -#define GB_FILE_CLOSE_PROC(name) void name(gbFileDescriptor fd) -typedef GB_FILE_OPEN_PROC(gbFileOpenProc); -typedef GB_FILE_READ_AT_PROC(gbFileReadProc); -typedef GB_FILE_WRITE_AT_PROC(gbFileWriteProc); -typedef GB_FILE_SEEK_PROC(gbFileSeekProc); -typedef GB_FILE_CLOSE_PROC(gbFileCloseProc); - -struct gbFileOperations { - gbFileReadProc *read_at; - gbFileWriteProc *write_at; - gbFileSeekProc *seek; - gbFileCloseProc *close; -}; - -extern gbFileOperations const gbDefaultFileOperations; - - -// typedef struct gbDirInfo { -// u8 *buf; -// isize buf_count; -// isize buf_pos; -// } gbDirInfo; - -typedef u64 gbFileTime; - -typedef struct gbFile { - gbFileOperations ops; - gbFileDescriptor fd; - char const * filename; - gbFileTime last_write_time; - // gbDirInfo * dir_info; // TODO(bill): Get directory info -} gbFile; - -// TODO(bill): gbAsyncFile - -typedef enum gbFileStandardType { - gbFileStandard_Input, - gbFileStandard_Output, - gbFileStandard_Error, - - gbFileStandard_Count, -} gbFileStandardType; - -GB_DEF gbFile *const gb_file_get_standard(gbFileStandardType std); - -GB_DEF gbFileError gb_file_create (gbFile *file, char const *filename); -GB_DEF gbFileError gb_file_open (gbFile *file, char const *filename); -GB_DEF gbFileError gb_file_open_mode (gbFile *file, gbFileMode mode, char const *filename); -GB_DEF gbFileError gb_file_new (gbFile *file, gbFileDescriptor fd, gbFileOperations ops, char const *filename); -GB_DEF b32 gb_file_read_at_check (gbFile *file, void *buffer, isize size, i64 offset, isize *bytes_read); -GB_DEF b32 gb_file_write_at_check(gbFile *file, void const *buffer, isize size, i64 offset, isize *bytes_written); -GB_DEF b32 gb_file_read_at (gbFile *file, void *buffer, isize size, i64 offset); -GB_DEF b32 gb_file_write_at (gbFile *file, void const *buffer, isize size, i64 offset); -GB_DEF i64 gb_file_seek (gbFile *file, i64 offset); -GB_DEF i64 gb_file_seek_to_end (gbFile *file); -GB_DEF i64 gb_file_skip (gbFile *file, i64 bytes); // NOTE(bill): Skips a certain amount of bytes -GB_DEF i64 gb_file_tell (gbFile *file); -GB_DEF gbFileError gb_file_close (gbFile *file); -GB_DEF b32 gb_file_read (gbFile *file, void *buffer, isize size); -GB_DEF b32 gb_file_write (gbFile *file, void const *buffer, isize size); -GB_DEF i64 gb_file_size (gbFile *file); -GB_DEF char const *gb_file_name (gbFile *file); -GB_DEF gbFileError gb_file_truncate (gbFile *file, i64 size); -GB_DEF b32 gb_file_has_changed (gbFile *file); // NOTE(bill): Changed since lasted checked -// TODO(bill): -// gbFileError gb_file_temp(gbFile *file); -// - -typedef struct gbFileContents { - gbAllocator allocator; - void * data; - isize size; -} gbFileContents; - - -GB_DEF gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char const *filepath); -GB_DEF void gb_file_free_contents(gbFileContents *fc); - - -// TODO(bill): Should these have different na,es as they do not take in a gbFile * ??? -GB_DEF b32 gb_file_exists (char const *filepath); -GB_DEF gbFileTime gb_file_last_write_time(char const *filepath); -GB_DEF b32 gb_file_copy (char const *existing_filename, char const *new_filename, b32 fail_if_exists); -GB_DEF b32 gb_file_move (char const *existing_filename, char const *new_filename); -GB_DEF b32 gb_file_remove (char const *filename); - - -#ifndef GB_PATH_SEPARATOR - #if defined(GB_SYSTEM_WINDOWS) - #define GB_PATH_SEPARATOR '\\' - #else - #define GB_PATH_SEPARATOR '/' - #endif -#endif - -GB_DEF b32 gb_path_is_absolute (char const *path); -GB_DEF b32 gb_path_is_relative (char const *path); -GB_DEF b32 gb_path_is_root (char const *path); -GB_DEF char const *gb_path_base_name (char const *path); -GB_DEF char const *gb_path_extension (char const *path); -GB_DEF char * gb_path_get_full_name(gbAllocator a, char const *path); - - -//////////////////////////////////////////////////////////////// -// -// Printing -// -// - -GB_DEF isize gb_printf (char const *fmt, ...) GB_PRINTF_ARGS(1); -GB_DEF isize gb_printf_va (char const *fmt, va_list va); -GB_DEF isize gb_printf_err (char const *fmt, ...) GB_PRINTF_ARGS(1); -GB_DEF isize gb_printf_err_va (char const *fmt, va_list va); -GB_DEF isize gb_fprintf (gbFile *f, char const *fmt, ...) GB_PRINTF_ARGS(2); -GB_DEF isize gb_fprintf_va (gbFile *f, char const *fmt, va_list va); - -GB_DEF char *gb_bprintf (char const *fmt, ...) GB_PRINTF_ARGS(1); // NOTE(bill): A locally persisting buffer is used internally -GB_DEF char *gb_bprintf_va (char const *fmt, va_list va); // NOTE(bill): A locally persisting buffer is used internally -GB_DEF isize gb_snprintf (char *str, isize n, char const *fmt, ...) GB_PRINTF_ARGS(3); -GB_DEF isize gb_snprintf_va(char *str, isize n, char const *fmt, va_list va); - -//////////////////////////////////////////////////////////////// -// -// DLL Handling -// -// - -typedef void *gbDllHandle; -typedef void (*gbDllProc)(void); - -GB_DEF gbDllHandle gb_dll_load (char const *filepath); -GB_DEF void gb_dll_unload (gbDllHandle dll); -GB_DEF gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name); - - -//////////////////////////////////////////////////////////////// -// -// Time -// -// - -GB_DEF u64 gb_rdtsc (void); -GB_DEF f64 gb_time_now (void); // NOTE(bill): This is only for relative time e.g. game loops -GB_DEF u64 gb_utc_time_now(void); // NOTE(bill): Number of microseconds since 1601-01-01 UTC -GB_DEF void gb_sleep_ms (u32 ms); - - -//////////////////////////////////////////////////////////////// -// -// Miscellany -// -// - -typedef struct gbRandom { - u32 offsets[8]; - u32 value; -} gbRandom; - -// NOTE(bill): Generates from numerous sources to produce a decent pseudo-random seed -GB_DEF void gb_random_init (gbRandom *r); -GB_DEF u32 gb_random_gen_u32 (gbRandom *r); -GB_DEF u32 gb_random_gen_u32_unique(gbRandom *r); -GB_DEF u64 gb_random_gen_u64 (gbRandom *r); // NOTE(bill): (gb_random_gen_u32() << 32) | gb_random_gen_u32() -GB_DEF isize gb_random_gen_isize (gbRandom *r); -GB_DEF i64 gb_random_range_i64 (gbRandom *r, i64 lower_inc, i64 higher_inc); -GB_DEF isize gb_random_range_isize (gbRandom *r, isize lower_inc, isize higher_inc); -GB_DEF f64 gb_random_range_f64 (gbRandom *r, f64 lower_inc, f64 higher_inc); - - - - -GB_DEF void gb_exit (u32 code); -GB_DEF void gb_yield (void); -GB_DEF void gb_set_env (char const *name, char const *value); -GB_DEF void gb_unset_env(char const *name); - -GB_DEF u16 gb_endian_swap16(u16 i); -GB_DEF u32 gb_endian_swap32(u32 i); -GB_DEF u64 gb_endian_swap64(u64 i); - -GB_DEF isize gb_count_set_bits(u64 mask); - -//////////////////////////////////////////////////////////////// -// -// Platform Stuff -// -// - -#if defined(GB_PLATFORM) - -// NOTE(bill): -// Coordiate system - +ve x - left to right -// - +ve y - bottom to top -// - Relative to window - -// TODO(bill): Proper documentation for this with code examples - -// Window Support - Complete -// OS X Support - Missing: -// * Sofware framebuffer -// * (show|hide) window -// * show_cursor -// * toggle (fullscreen|borderless) -// * set window position -// * Clipboard -// * GameControllers -// Linux Support - None -// Other OS Support - None - -#ifndef GB_MAX_GAME_CONTROLLER_COUNT -#define GB_MAX_GAME_CONTROLLER_COUNT 4 -#endif - -typedef enum gbKeyType { - gbKey_Unknown = 0, // Unhandled key - - // NOTE(bill): Allow the basic printable keys to be aliased with their chars - gbKey_0 = '0', - gbKey_1, - gbKey_2, - gbKey_3, - gbKey_4, - gbKey_5, - gbKey_6, - gbKey_7, - gbKey_8, - gbKey_9, - - gbKey_A = 'A', - gbKey_B, - gbKey_C, - gbKey_D, - gbKey_E, - gbKey_F, - gbKey_G, - gbKey_H, - gbKey_I, - gbKey_J, - gbKey_K, - gbKey_L, - gbKey_M, - gbKey_N, - gbKey_O, - gbKey_P, - gbKey_Q, - gbKey_R, - gbKey_S, - gbKey_T, - gbKey_U, - gbKey_V, - gbKey_W, - gbKey_X, - gbKey_Y, - gbKey_Z, - - gbKey_Lbracket = '[', - gbKey_Rbracket = ']', - gbKey_Semicolon = ';', - gbKey_Comma = ',', - gbKey_Period = '.', - gbKey_Quote = '\'', - gbKey_Slash = '/', - gbKey_Backslash = '\\', - gbKey_Grave = '`', - gbKey_Equals = '=', - gbKey_Minus = '-', - gbKey_Space = ' ', - - gbKey__Pad = 128, // NOTE(bill): make sure ASCII is reserved - - gbKey_Escape, // Escape - gbKey_Lcontrol, // Left Control - gbKey_Lshift, // Left Shift - gbKey_Lalt, // Left Alt - gbKey_Lsystem, // Left OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... - gbKey_Rcontrol, // Right Control - gbKey_Rshift, // Right Shift - gbKey_Ralt, // Right Alt - gbKey_Rsystem, // Right OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... - gbKey_Menu, // Menu - gbKey_Return, // Return - gbKey_Backspace, // Backspace - gbKey_Tab, // Tabulation - gbKey_Pageup, // Page up - gbKey_Pagedown, // Page down - gbKey_End, // End - gbKey_Home, // Home - gbKey_Insert, // Insert - gbKey_Delete, // Delete - gbKey_Plus, // + - gbKey_Subtract, // - - gbKey_Multiply, // * - gbKey_Divide, // / - gbKey_Left, // Left arrow - gbKey_Right, // Right arrow - gbKey_Up, // Up arrow - gbKey_Down, // Down arrow - gbKey_Numpad0, // Numpad 0 - gbKey_Numpad1, // Numpad 1 - gbKey_Numpad2, // Numpad 2 - gbKey_Numpad3, // Numpad 3 - gbKey_Numpad4, // Numpad 4 - gbKey_Numpad5, // Numpad 5 - gbKey_Numpad6, // Numpad 6 - gbKey_Numpad7, // Numpad 7 - gbKey_Numpad8, // Numpad 8 - gbKey_Numpad9, // Numpad 9 - gbKey_NumpadDot, // Numpad . - gbKey_NumpadEnter, // Numpad Enter - gbKey_F1, // F1 - gbKey_F2, // F2 - gbKey_F3, // F3 - gbKey_F4, // F4 - gbKey_F5, // F5 - gbKey_F6, // F6 - gbKey_F7, // F7 - gbKey_F8, // F8 - gbKey_F9, // F8 - gbKey_F10, // F10 - gbKey_F11, // F11 - gbKey_F12, // F12 - gbKey_F13, // F13 - gbKey_F14, // F14 - gbKey_F15, // F15 - gbKey_Pause, // Pause - - gbKey_Count, -} gbKeyType; - -/* TODO(bill): Change name? */ -typedef u8 gbKeyState; -typedef enum gbKeyStateFlag { - gbKeyState_Down = GB_BIT(0), - gbKeyState_Pressed = GB_BIT(1), - gbKeyState_Released = GB_BIT(2) -} gbKeyStateFlag; - -GB_DEF void gb_key_state_update(gbKeyState *s, b32 is_down); - -typedef enum gbMouseButtonType { - gbMouseButton_Left, - gbMouseButton_Middle, - gbMouseButton_Right, - gbMouseButton_X1, - gbMouseButton_X2, - - gbMouseButton_Count -} gbMouseButtonType; - -typedef enum gbControllerAxisType { - gbControllerAxis_LeftX, - gbControllerAxis_LeftY, - gbControllerAxis_RightX, - gbControllerAxis_RightY, - gbControllerAxis_LeftTrigger, - gbControllerAxis_RightTrigger, - - gbControllerAxis_Count -} gbControllerAxisType; - -typedef enum gbControllerButtonType { - gbControllerButton_Up, - gbControllerButton_Down, - gbControllerButton_Left, - gbControllerButton_Right, - gbControllerButton_A, - gbControllerButton_B, - gbControllerButton_X, - gbControllerButton_Y, - gbControllerButton_LeftShoulder, - gbControllerButton_RightShoulder, - gbControllerButton_Back, - gbControllerButton_Start, - gbControllerButton_LeftThumb, - gbControllerButton_RightThumb, - - gbControllerButton_Count -} gbControllerButtonType; - -typedef struct gbGameController { - b16 is_connected, is_analog; - - f32 axes[gbControllerAxis_Count]; - gbKeyState buttons[gbControllerButton_Count]; -} gbGameController; - -#if defined(GB_SYSTEM_WINDOWS) - typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD; - typedef struct _XINPUT_STATE XINPUT_STATE; - typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION; - - #define GB_XINPUT_GET_STATE(name) unsigned long __stdcall name(unsigned long dwUserIndex, XINPUT_STATE *pState) - typedef GB_XINPUT_GET_STATE(gbXInputGetStateProc); - - #define GB_XINPUT_SET_STATE(name) unsigned long __stdcall name(unsigned long dwUserIndex, XINPUT_VIBRATION *pVibration) - typedef GB_XINPUT_SET_STATE(gbXInputSetStateProc); -#endif - - -typedef enum gbWindowFlag { - gbWindow_Fullscreen = GB_BIT(0), - gbWindow_Hidden = GB_BIT(1), - gbWindow_Borderless = GB_BIT(2), - gbWindow_Resizable = GB_BIT(3), - gbWindow_Minimized = GB_BIT(4), - gbWindow_Maximized = GB_BIT(5), - gbWindow_FullscreenDesktop = gbWindow_Fullscreen | gbWindow_Borderless, -} gbWindowFlag; - -typedef enum gbRendererType { - gbRenderer_Opengl, - gbRenderer_Software, - - gbRenderer_Count, -} gbRendererType; - - - -#if defined(GB_SYSTEM_WINDOWS) && !defined(_WINDOWS_) -typedef struct tagBITMAPINFOHEADER { - unsigned long biSize; - long biWidth; - long biHeight; - u16 biPlanes; - u16 biBitCount; - unsigned long biCompression; - unsigned long biSizeImage; - long biXPelsPerMeter; - long biYPelsPerMeter; - unsigned long biClrUsed; - unsigned long biClrImportant; -} BITMAPINFOHEADER, *PBITMAPINFOHEADER; -typedef struct tagRGBQUAD { - u8 rgbBlue; - u8 rgbGreen; - u8 rgbRed; - u8 rgbReserved; -} RGBQUAD; -typedef struct tagBITMAPINFO { - BITMAPINFOHEADER bmiHeader; - RGBQUAD bmiColors[1]; -} BITMAPINFO, *PBITMAPINFO; -#endif - -typedef struct gbPlatform { - b32 is_initialized; - - void *window_handle; - i32 window_x, window_y; - i32 window_width, window_height; - u32 window_flags; - b16 window_is_closed, window_has_focus; - -#if defined(GB_SYSTEM_WINDOWS) - void *win32_dc; -#elif defined(GB_SYSTEM_OSX) - void *osx_autorelease_pool; // TODO(bill): Is this really needed? -#endif - - gbRendererType renderer_type; - union { - struct { - void * context; - i32 major; - i32 minor; - b16 core, compatible; - gbDllHandle dll_handle; - } opengl; - - // NOTE(bill): Software rendering - struct { -#if defined(GB_SYSTEM_WINDOWS) - BITMAPINFO win32_bmi; -#endif - void * memory; - isize memory_size; - i32 pitch; - i32 bits_per_pixel; - } sw_framebuffer; - }; - - gbKeyState keys[gbKey_Count]; - struct { - gbKeyState control; - gbKeyState alt; - gbKeyState shift; - } key_modifiers; - - Rune char_buffer[256]; - isize char_buffer_count; - - b32 mouse_clip; - i32 mouse_x, mouse_y; - i32 mouse_dx, mouse_dy; // NOTE(bill): Not raw mouse movement - i32 mouse_raw_dx, mouse_raw_dy; // NOTE(bill): Raw mouse movement - f32 mouse_wheel_delta; - gbKeyState mouse_buttons[gbMouseButton_Count]; - - gbGameController game_controllers[GB_MAX_GAME_CONTROLLER_COUNT]; - - f64 curr_time; - f64 dt_for_frame; - b32 quit_requested; - -#if defined(GB_SYSTEM_WINDOWS) - struct { - gbXInputGetStateProc *get_state; - gbXInputSetStateProc *set_state; - } xinput; -#endif -} gbPlatform; - - -typedef struct gbVideoMode { - i32 width, height; - i32 bits_per_pixel; -} gbVideoMode; - -GB_DEF gbVideoMode gb_video_mode (i32 width, i32 height, i32 bits_per_pixel); -GB_DEF b32 gb_video_mode_is_valid (gbVideoMode mode); -GB_DEF gbVideoMode gb_video_mode_get_desktop (void); -GB_DEF isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count); // NOTE(bill): returns mode count -GB_DEF GB_COMPARE_PROC(gb_video_mode_cmp); // NOTE(bill): Sort smallest to largest (Ascending) -GB_DEF GB_COMPARE_PROC(gb_video_mode_dsc_cmp); // NOTE(bill): Sort largest to smallest (Descending) - - -// NOTE(bill): Software rendering -GB_DEF b32 gb_platform_init_with_software (gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags); -// NOTE(bill): OpenGL Rendering -GB_DEF b32 gb_platform_init_with_opengl (gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags, i32 major, i32 minor, b32 core, b32 compatible); -GB_DEF void gb_platform_update (gbPlatform *p); -GB_DEF void gb_platform_display (gbPlatform *p); -GB_DEF void gb_platform_destroy (gbPlatform *p); -GB_DEF void gb_platform_show_cursor (gbPlatform *p, b32 show); -GB_DEF void gb_platform_set_mouse_position (gbPlatform *p, i32 x, i32 y); -GB_DEF void gb_platform_set_controller_vibration (gbPlatform *p, isize index, f32 left_motor, f32 right_motor); -GB_DEF b32 gb_platform_has_clipboard_text (gbPlatform *p); -GB_DEF void gb_platform_set_clipboard_text (gbPlatform *p, char const *str); -GB_DEF char *gb_platform_get_clipboard_text (gbPlatform *p, gbAllocator a); -GB_DEF void gb_platform_set_window_position (gbPlatform *p, i32 x, i32 y); -GB_DEF void gb_platform_set_window_title (gbPlatform *p, char const *title, ...) GB_PRINTF_ARGS(2); -GB_DEF void gb_platform_toggle_fullscreen (gbPlatform *p, b32 fullscreen_desktop); -GB_DEF void gb_platform_toggle_borderless (gbPlatform *p); -GB_DEF void gb_platform_make_opengl_context_current(gbPlatform *p); -GB_DEF void gb_platform_show_window (gbPlatform *p); -GB_DEF void gb_platform_hide_window (gbPlatform *p); - - -#endif // GB_PLATFORM - -#if defined(__cplusplus) -} -#endif - -#endif // GB_INCLUDE_GB_H - - - - - - -//////////////////////////////////////////////////////////////// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// Implementation -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// It's turtles all the way down! -//////////////////////////////////////////////////////////////// -#if defined(GB_IMPLEMENTATION) && !defined(GB_IMPLEMENTATION_DONE) -#define GB_IMPLEMENTATION_DONE - -#if defined(__cplusplus) -extern "C" { -#endif - - -#if defined(GB_COMPILER_MSVC) && !defined(_WINDOWS_) - //////////////////////////////////////////////////////////////// - // - // Bill's Mini Windows.h - // - // - - #define WINAPI __stdcall - #define WINAPIV __cdecl - #define CALLBACK __stdcall - #define MAX_PATH 260 - #define CCHDEVICENAME 32 - #define CCHFORMNAME 32 - - typedef unsigned long DWORD; - typedef int WINBOOL; - #ifndef XFree86Server - #ifndef __OBJC__ - typedef WINBOOL BOOL; - #else - #define BOOL WINBOOL - #endif - typedef unsigned char BYTE; - #endif - typedef unsigned short WORD; - typedef float FLOAT; - typedef int INT; - typedef unsigned int UINT; - typedef short SHORT; - typedef long LONG; - typedef long long LONGLONG; - typedef unsigned short USHORT; - typedef unsigned long ULONG; - typedef unsigned long long ULONGLONG; - - typedef UINT WPARAM; - typedef LONG LPARAM; - typedef LONG LRESULT; - #ifndef _HRESULT_DEFINED - typedef LONG HRESULT; - #define _HRESULT_DEFINED - #endif - #ifndef XFree86Server - typedef WORD ATOM; - #endif /* XFree86Server */ - typedef void *HANDLE; - typedef HANDLE HGLOBAL; - typedef HANDLE HLOCAL; - typedef HANDLE GLOBALHANDLE; - typedef HANDLE LOCALHANDLE; - typedef void *HGDIOBJ; - - #define DECLARE_HANDLE(name) typedef HANDLE name - DECLARE_HANDLE(HACCEL); - DECLARE_HANDLE(HBITMAP); - DECLARE_HANDLE(HBRUSH); - DECLARE_HANDLE(HCOLORSPACE); - DECLARE_HANDLE(HDC); - DECLARE_HANDLE(HGLRC); - DECLARE_HANDLE(HDESK); - DECLARE_HANDLE(HENHMETAFILE); - DECLARE_HANDLE(HFONT); - DECLARE_HANDLE(HICON); - DECLARE_HANDLE(HKEY); - typedef HKEY *PHKEY; - DECLARE_HANDLE(HMENU); - DECLARE_HANDLE(HMETAFILE); - DECLARE_HANDLE(HINSTANCE); - typedef HINSTANCE HMODULE; - DECLARE_HANDLE(HPALETTE); - DECLARE_HANDLE(HPEN); - DECLARE_HANDLE(HRGN); - DECLARE_HANDLE(HRSRC); - DECLARE_HANDLE(HSTR); - DECLARE_HANDLE(HTASK); - DECLARE_HANDLE(HWND); - DECLARE_HANDLE(HWINSTA); - DECLARE_HANDLE(HKL); - DECLARE_HANDLE(HRAWINPUT); - DECLARE_HANDLE(HMONITOR); - #undef DECLARE_HANDLE - - typedef int HFILE; - typedef HICON HCURSOR; - typedef DWORD COLORREF; - typedef int (WINAPI *FARPROC)(); - typedef int (WINAPI *NEARPROC)(); - typedef int (WINAPI *PROC)(); - typedef LRESULT (CALLBACK *WNDPROC)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - - #if defined(_WIN64) - typedef unsigned __int64 ULONG_PTR; - typedef signed __int64 LONG_PTR; - #else - typedef unsigned long ULONG_PTR; - typedef signed long LONG_PTR; - #endif - typedef ULONG_PTR DWORD_PTR; - - typedef struct tagRECT { - LONG left; - LONG top; - LONG right; - LONG bottom; - } RECT; - typedef struct tagRECTL { - LONG left; - LONG top; - LONG right; - LONG bottom; - } RECTL; - typedef struct tagPOINT { - LONG x; - LONG y; - } POINT; - typedef struct tagSIZE { - LONG cx; - LONG cy; - } SIZE; - typedef struct tagPOINTS { - SHORT x; - SHORT y; - } POINTS; - typedef struct _SECURITY_ATTRIBUTES { - DWORD nLength; - HANDLE lpSecurityDescriptor; - BOOL bInheritHandle; - } SECURITY_ATTRIBUTES; - typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP { - RelationProcessorCore, - RelationNumaNode, - RelationCache, - RelationProcessorPackage, - RelationGroup, - RelationAll = 0xffff - } LOGICAL_PROCESSOR_RELATIONSHIP; - typedef enum _PROCESSOR_CACHE_TYPE { - CacheUnified, - CacheInstruction, - CacheData, - CacheTrace - } PROCESSOR_CACHE_TYPE; - typedef struct _CACHE_DESCRIPTOR { - BYTE Level; - BYTE Associativity; - WORD LineSize; - DWORD Size; - PROCESSOR_CACHE_TYPE Type; - } CACHE_DESCRIPTOR; - typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION { - ULONG_PTR ProcessorMask; - LOGICAL_PROCESSOR_RELATIONSHIP Relationship; - union { - struct { - BYTE Flags; - } ProcessorCore; - struct { - DWORD NodeNumber; - } NumaNode; - CACHE_DESCRIPTOR Cache; - ULONGLONG Reserved[2]; - }; - } SYSTEM_LOGICAL_PROCESSOR_INFORMATION; - typedef struct _MEMORY_BASIC_INFORMATION { - void *BaseAddress; - void *AllocationBase; - DWORD AllocationProtect; - usize RegionSize; - DWORD State; - DWORD Protect; - DWORD Type; - } MEMORY_BASIC_INFORMATION; - typedef struct _SYSTEM_INFO { - union { - DWORD dwOemId; - struct { - WORD wProcessorArchitecture; - WORD wReserved; - }; - }; - DWORD dwPageSize; - void * lpMinimumApplicationAddress; - void * lpMaximumApplicationAddress; - DWORD_PTR dwActiveProcessorMask; - DWORD dwNumberOfProcessors; - DWORD dwProcessorType; - DWORD dwAllocationGranularity; - WORD wProcessorLevel; - WORD wProcessorRevision; - } SYSTEM_INFO; - typedef union _LARGE_INTEGER { - struct { - DWORD LowPart; - LONG HighPart; - }; - struct { - DWORD LowPart; - LONG HighPart; - } u; - LONGLONG QuadPart; - } LARGE_INTEGER; - typedef union _ULARGE_INTEGER { - struct { - DWORD LowPart; - DWORD HighPart; - }; - struct { - DWORD LowPart; - DWORD HighPart; - } u; - ULONGLONG QuadPart; - } ULARGE_INTEGER; - - typedef struct _OVERLAPPED { - ULONG_PTR Internal; - ULONG_PTR InternalHigh; - union { - struct { - DWORD Offset; - DWORD OffsetHigh; - }; - void *Pointer; - }; - HANDLE hEvent; - } OVERLAPPED; - typedef struct _FILETIME { - DWORD dwLowDateTime; - DWORD dwHighDateTime; - } FILETIME; - typedef struct _WIN32_FIND_DATAW { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - DWORD dwReserved0; - DWORD dwReserved1; - wchar_t cFileName[MAX_PATH]; - wchar_t cAlternateFileName[14]; - } WIN32_FIND_DATAW; - typedef struct _WIN32_FILE_ATTRIBUTE_DATA { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - } WIN32_FILE_ATTRIBUTE_DATA; - typedef enum _GET_FILEEX_INFO_LEVELS { - GetFileExInfoStandard, - GetFileExMaxInfoLevel - } GET_FILEEX_INFO_LEVELS; - typedef struct tagRAWINPUTHEADER { - DWORD dwType; - DWORD dwSize; - HANDLE hDevice; - WPARAM wParam; - } RAWINPUTHEADER; - typedef struct tagRAWINPUTDEVICE { - USHORT usUsagePage; - USHORT usUsage; - DWORD dwFlags; - HWND hwndTarget; - } RAWINPUTDEVICE; - typedef struct tagRAWMOUSE { - WORD usFlags; - union { - ULONG ulButtons; - struct { - WORD usButtonFlags; - WORD usButtonData; - }; - }; - ULONG ulRawButtons; - LONG lLastX; - LONG lLastY; - ULONG ulExtraInformation; - } RAWMOUSE; - typedef struct tagRAWKEYBOARD { - WORD MakeCode; - WORD Flags; - WORD Reserved; - WORD VKey; - UINT Message; - ULONG ExtraInformation; - } RAWKEYBOARD; - typedef struct tagRAWHID { - DWORD dwSizeHid; - DWORD dwCount; - BYTE bRawData[1]; - } RAWHID; - typedef struct tagRAWINPUT { - RAWINPUTHEADER header; - union { - RAWMOUSE mouse; - RAWKEYBOARD keyboard; - RAWHID hid; - } data; - } RAWINPUT; - typedef struct tagWNDCLASSEXW { - UINT cbSize; - UINT style; - WNDPROC lpfnWndProc; - INT cbClsExtra; - INT cbWndExtra; - HINSTANCE hInstance; - HICON hIcon; - HCURSOR hCursor; - HANDLE hbrBackground; - wchar_t const *lpszMenuName; - wchar_t const *lpszClassName; - HICON hIconSm; - } WNDCLASSEXW; - typedef struct _POINTL { - LONG x; - LONG y; - } POINTL; - typedef struct _devicemodew { - wchar_t dmDeviceName[CCHDEVICENAME]; - WORD dmSpecVersion; - WORD dmDriverVersion; - WORD dmSize; - WORD dmDriverExtra; - DWORD dmFields; - union { - struct { - short dmOrientation; - short dmPaperSize; - short dmPaperLength; - short dmPaperWidth; - short dmScale; - short dmCopies; - short dmDefaultSource; - short dmPrintQuality; - }; - struct { - POINTL dmPosition; - DWORD dmDisplayOrientation; - DWORD dmDisplayFixedOutput; - }; - }; - short dmColor; - short dmDuplex; - short dmYResolution; - short dmTTOption; - short dmCollate; - wchar_t dmFormName[CCHFORMNAME]; - WORD dmLogPixels; - DWORD dmBitsPerPel; - DWORD dmPelsWidth; - DWORD dmPelsHeight; - union { - DWORD dmDisplayFlags; - DWORD dmNup; - }; - DWORD dmDisplayFrequency; - #if (WINVER >= 0x0400) - DWORD dmICMMethod; - DWORD dmICMIntent; - DWORD dmMediaType; - DWORD dmDitherType; - DWORD dmReserved1; - DWORD dmReserved2; - #if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400) - DWORD dmPanningWidth; - DWORD dmPanningHeight; - #endif - #endif - } DEVMODEW; - typedef struct tagPIXELFORMATDESCRIPTOR { - WORD nSize; - WORD nVersion; - DWORD dwFlags; - BYTE iPixelType; - BYTE cColorBits; - BYTE cRedBits; - BYTE cRedShift; - BYTE cGreenBits; - BYTE cGreenShift; - BYTE cBlueBits; - BYTE cBlueShift; - BYTE cAlphaBits; - BYTE cAlphaShift; - BYTE cAccumBits; - BYTE cAccumRedBits; - BYTE cAccumGreenBits; - BYTE cAccumBlueBits; - BYTE cAccumAlphaBits; - BYTE cDepthBits; - BYTE cStencilBits; - BYTE cAuxBuffers; - BYTE iLayerType; - BYTE bReserved; - DWORD dwLayerMask; - DWORD dwVisibleMask; - DWORD dwDamageMask; - } PIXELFORMATDESCRIPTOR; - typedef struct tagMSG { // msg - HWND hwnd; - UINT message; - WPARAM wParam; - LPARAM lParam; - DWORD time; - POINT pt; - } MSG; - typedef struct tagWINDOWPLACEMENT { - UINT length; - UINT flags; - UINT showCmd; - POINT ptMinPosition; - POINT ptMaxPosition; - RECT rcNormalPosition; - } WINDOWPLACEMENT; - typedef struct tagMONITORINFO { - DWORD cbSize; - RECT rcMonitor; - RECT rcWork; - DWORD dwFlags; - } MONITORINFO; - - #define INFINITE 0xffffffffl - #define INVALID_HANDLE_VALUE ((void *)(intptr)(-1)) - - - typedef DWORD WINAPI THREAD_START_ROUTINE(void *parameter); - - GB_DLL_IMPORT DWORD WINAPI GetLastError (void); - GB_DLL_IMPORT BOOL WINAPI CloseHandle (HANDLE object); - GB_DLL_IMPORT HANDLE WINAPI CreateSemaphoreA (SECURITY_ATTRIBUTES *semaphore_attributes, LONG initial_count, - LONG maximum_count, char const *name); - GB_DLL_IMPORT BOOL WINAPI ReleaseSemaphore (HANDLE semaphore, LONG release_count, LONG *previous_count); - GB_DLL_IMPORT DWORD WINAPI WaitForSingleObject(HANDLE handle, DWORD milliseconds); - GB_DLL_IMPORT HANDLE WINAPI CreateThread (SECURITY_ATTRIBUTES *semaphore_attributes, usize stack_size, - THREAD_START_ROUTINE *start_address, void *parameter, - DWORD creation_flags, DWORD *thread_id); - GB_DLL_IMPORT DWORD WINAPI GetThreadId (HANDLE handle); - GB_DLL_IMPORT void WINAPI RaiseException (DWORD, DWORD, DWORD, ULONG_PTR const *); - - - GB_DLL_IMPORT BOOL WINAPI GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer, DWORD *return_length); - GB_DLL_IMPORT DWORD_PTR WINAPI SetThreadAffinityMask(HANDLE thread, DWORD_PTR check_mask); - GB_DLL_IMPORT HANDLE WINAPI GetCurrentThread(void); - - #define PAGE_NOACCESS 0x01 - #define PAGE_READONLY 0x02 - #define PAGE_READWRITE 0x04 - #define PAGE_WRITECOPY 0x08 - #define PAGE_EXECUTE 0x10 - #define PAGE_EXECUTE_READ 0x20 - #define PAGE_EXECUTE_READWRITE 0x40 - #define PAGE_EXECUTE_WRITECOPY 0x80 - #define PAGE_GUARD 0x100 - #define PAGE_NOCACHE 0x200 - #define PAGE_WRITECOMBINE 0x400 - - #define MEM_COMMIT 0x1000 - #define MEM_RESERVE 0x2000 - #define MEM_DECOMMIT 0x4000 - #define MEM_RELEASE 0x8000 - #define MEM_FREE 0x10000 - #define MEM_PRIVATE 0x20000 - #define MEM_MAPPED 0x40000 - #define MEM_RESET 0x80000 - #define MEM_TOP_DOWN 0x100000 - #define MEM_LARGE_PAGES 0x20000000 - #define MEM_4MB_PAGES 0x80000000 - - - - - GB_DLL_IMPORT void * WINAPI VirtualAlloc (void *addr, usize size, DWORD allocation_type, DWORD protect); - GB_DLL_IMPORT usize WINAPI VirtualQuery (void const *address, MEMORY_BASIC_INFORMATION *buffer, usize length); - GB_DLL_IMPORT BOOL WINAPI VirtualFree (void *address, usize size, DWORD free_type); - GB_DLL_IMPORT void WINAPI GetSystemInfo(SYSTEM_INFO *system_info); - - - #ifndef VK_UNKNOWN - #define VK_UNKNOWN 0 - #define VK_LBUTTON 0x01 - #define VK_RBUTTON 0x02 - #define VK_CANCEL 0x03 - #define VK_MBUTTON 0x04 - #define VK_XBUTTON1 0x05 - #define VK_XBUTTON2 0x06 - #define VK_BACK 0x08 - #define VK_TAB 0x09 - #define VK_CLEAR 0x0C - #define VK_RETURN 0x0D - #define VK_SHIFT 0x10 - #define VK_CONTROL 0x11 // CTRL key - #define VK_MENU 0x12 // ALT key - #define VK_PAUSE 0x13 // PAUSE key - #define VK_CAPITAL 0x14 // CAPS LOCK key - #define VK_KANA 0x15 // Input Method Editor (IME) Kana mode - #define VK_HANGUL 0x15 // IME Hangul mode - #define VK_JUNJA 0x17 // IME Junja mode - #define VK_FINAL 0x18 // IME final mode - #define VK_HANJA 0x19 // IME Hanja mode - #define VK_KANJI 0x19 // IME Kanji mode - #define VK_ESCAPE 0x1B // ESC key - #define VK_CONVERT 0x1C // IME convert - #define VK_NONCONVERT 0x1D // IME nonconvert - #define VK_ACCEPT 0x1E // IME accept - #define VK_MODECHANGE 0x1F // IME mode change request - #define VK_SPACE 0x20 // SPACE key - #define VK_PRIOR 0x21 // PAGE UP key - #define VK_NEXT 0x22 // PAGE DOWN key - #define VK_END 0x23 // END key - #define VK_HOME 0x24 // HOME key - #define VK_LEFT 0x25 // LEFT ARROW key - #define VK_UP 0x26 // UP ARROW key - #define VK_RIGHT 0x27 // RIGHT ARROW key - #define VK_DOWN 0x28 // DOWN ARROW key - #define VK_SELECT 0x29 // SELECT key - #define VK_PRINT 0x2A // PRINT key - #define VK_EXECUTE 0x2B // EXECUTE key - #define VK_SNAPSHOT 0x2C // PRINT SCREEN key - #define VK_INSERT 0x2D // INS key - #define VK_DELETE 0x2E // DEL key - #define VK_HELP 0x2F // HELP key - #define VK_0 0x30 - #define VK_1 0x31 - #define VK_2 0x32 - #define VK_3 0x33 - #define VK_4 0x34 - #define VK_5 0x35 - #define VK_6 0x36 - #define VK_7 0x37 - #define VK_8 0x38 - #define VK_9 0x39 - #define VK_A 0x41 - #define VK_B 0x42 - #define VK_C 0x43 - #define VK_D 0x44 - #define VK_E 0x45 - #define VK_F 0x46 - #define VK_G 0x47 - #define VK_H 0x48 - #define VK_I 0x49 - #define VK_J 0x4A - #define VK_K 0x4B - #define VK_L 0x4C - #define VK_M 0x4D - #define VK_N 0x4E - #define VK_O 0x4F - #define VK_P 0x50 - #define VK_Q 0x51 - #define VK_R 0x52 - #define VK_S 0x53 - #define VK_T 0x54 - #define VK_U 0x55 - #define VK_V 0x56 - #define VK_W 0x57 - #define VK_X 0x58 - #define VK_Y 0x59 - #define VK_Z 0x5A - #define VK_LWIN 0x5B // Left Windows key (Microsoft Natural keyboard) - #define VK_RWIN 0x5C // Right Windows key (Natural keyboard) - #define VK_APPS 0x5D // Applications key (Natural keyboard) - #define VK_SLEEP 0x5F // Computer Sleep key - // Num pad keys - #define VK_NUMPAD0 0x60 - #define VK_NUMPAD1 0x61 - #define VK_NUMPAD2 0x62 - #define VK_NUMPAD3 0x63 - #define VK_NUMPAD4 0x64 - #define VK_NUMPAD5 0x65 - #define VK_NUMPAD6 0x66 - #define VK_NUMPAD7 0x67 - #define VK_NUMPAD8 0x68 - #define VK_NUMPAD9 0x69 - #define VK_MULTIPLY 0x6A - #define VK_ADD 0x6B - #define VK_SEPARATOR 0x6C - #define VK_SUBTRACT 0x6D - #define VK_DECIMAL 0x6E - #define VK_DIVIDE 0x6F - #define VK_F1 0x70 - #define VK_F2 0x71 - #define VK_F3 0x72 - #define VK_F4 0x73 - #define VK_F5 0x74 - #define VK_F6 0x75 - #define VK_F7 0x76 - #define VK_F8 0x77 - #define VK_F9 0x78 - #define VK_F10 0x79 - #define VK_F11 0x7A - #define VK_F12 0x7B - #define VK_F13 0x7C - #define VK_F14 0x7D - #define VK_F15 0x7E - #define VK_F16 0x7F - #define VK_F17 0x80 - #define VK_F18 0x81 - #define VK_F19 0x82 - #define VK_F20 0x83 - #define VK_F21 0x84 - #define VK_F22 0x85 - #define VK_F23 0x86 - #define VK_F24 0x87 - #define VK_NUMLOCK 0x90 - #define VK_SCROLL 0x91 - #define VK_LSHIFT 0xA0 - #define VK_RSHIFT 0xA1 - #define VK_LCONTROL 0xA2 - #define VK_RCONTROL 0xA3 - #define VK_LMENU 0xA4 - #define VK_RMENU 0xA5 - #define VK_BROWSER_BACK 0xA6 // Windows 2000/XP: Browser Back key - #define VK_BROWSER_FORWARD 0xA7 // Windows 2000/XP: Browser Forward key - #define VK_BROWSER_REFRESH 0xA8 // Windows 2000/XP: Browser Refresh key - #define VK_BROWSER_STOP 0xA9 // Windows 2000/XP: Browser Stop key - #define VK_BROWSER_SEARCH 0xAA // Windows 2000/XP: Browser Search key - #define VK_BROWSER_FAVORITES 0xAB // Windows 2000/XP: Browser Favorites key - #define VK_BROWSER_HOME 0xAC // Windows 2000/XP: Browser Start and Home key - #define VK_VOLUME_MUTE 0xAD // Windows 2000/XP: Volume Mute key - #define VK_VOLUME_DOWN 0xAE // Windows 2000/XP: Volume Down key - #define VK_VOLUME_UP 0xAF // Windows 2000/XP: Volume Up key - #define VK_MEDIA_NEXT_TRACK 0xB0 // Windows 2000/XP: Next Track key - #define VK_MEDIA_PREV_TRACK 0xB1 // Windows 2000/XP: Previous Track key - #define VK_MEDIA_STOP 0xB2 // Windows 2000/XP: Stop Media key - #define VK_MEDIA_PLAY_PAUSE 0xB3 // Windows 2000/XP: Play/Pause Media key - #define VK_MEDIA_LAUNCH_MAIL 0xB4 // Windows 2000/XP: Start Mail key - #define VK_MEDIA_LAUNCH_MEDIA_SELECT 0xB5 // Windows 2000/XP: Select Media key - #define VK_MEDIA_LAUNCH_APP1 0xB6 // VK_LAUNCH_APP1 (B6) Windows 2000/XP: Start Application 1 key - #define VK_MEDIA_LAUNCH_APP2 0xB7 // VK_LAUNCH_APP2 (B7) Windows 2000/XP: Start Application 2 key - #define VK_OEM_1 0xBA - #define VK_OEM_PLUS 0xBB - #define VK_OEM_COMMA 0xBC - #define VK_OEM_MINUS 0xBD - #define VK_OEM_PERIOD 0xBE - #define VK_OEM_2 0xBF - #define VK_OEM_3 0xC0 - #define VK_OEM_4 0xDB - #define VK_OEM_5 0xDC - #define VK_OEM_6 0xDD - #define VK_OEM_7 0xDE - #define VK_OEM_8 0xDF - #define VK_OEM_102 0xE2 - #define VK_PROCESSKEY 0xE5 - #define VK_PACKET 0xE7 - #define VK_ATTN 0xF6 // Attn key - #define VK_CRSEL 0xF7 // CrSel key - #define VK_EXSEL 0xF8 // ExSel key - #define VK_EREOF 0xF9 // Erase EOF key - #define VK_PLAY 0xFA // Play key - #define VK_ZOOM 0xFB // Zoom key - #define VK_NONAME 0xFC // Reserved for future use - #define VK_PA1 0xFD // VK_PA1 (FD) PA1 key - #define VK_OEM_CLEAR 0xFE // Clear key - #endif // VK_UNKNOWN - - - - #define GENERIC_READ 0x80000000 - #define GENERIC_WRITE 0x40000000 - #define GENERIC_EXECUTE 0x20000000 - #define GENERIC_ALL 0x10000000 - #define FILE_SHARE_READ 0x00000001 - #define FILE_SHARE_WRITE 0x00000002 - #define FILE_SHARE_DELETE 0x00000004 - #define CREATE_NEW 1 - #define CREATE_ALWAYS 2 - #define OPEN_EXISTING 3 - #define OPEN_ALWAYS 4 - #define TRUNCATE_EXISTING 5 - #define FILE_ATTRIBUTE_READONLY 0x00000001 - #define FILE_ATTRIBUTE_NORMAL 0x00000080 - #define FILE_ATTRIBUTE_TEMPORARY 0x00000100 - #define ERROR_FILE_NOT_FOUND 2l - #define ERROR_ACCESS_DENIED 5L - #define ERROR_NO_MORE_FILES 18l - #define ERROR_FILE_EXISTS 80l - #define ERROR_ALREADY_EXISTS 183l - #define STD_INPUT_HANDLE ((DWORD)-10) - #define STD_OUTPUT_HANDLE ((DWORD)-11) - #define STD_ERROR_HANDLE ((DWORD)-12) - - GB_DLL_IMPORT int MultiByteToWideChar(UINT code_page, DWORD flags, char const * multi_byte_str, int multi_byte_len, wchar_t const *wide_char_str, int wide_char_len); - GB_DLL_IMPORT int WideCharToMultiByte(UINT code_page, DWORD flags, wchar_t const *wide_char_str, int wide_char_len, char const * multi_byte_str, int multi_byte_len); - GB_DLL_IMPORT BOOL WINAPI SetFilePointerEx(HANDLE file, LARGE_INTEGER distance_to_move, - LARGE_INTEGER *new_file_pointer, DWORD move_method); - GB_DLL_IMPORT BOOL WINAPI ReadFile (HANDLE file, void *buffer, DWORD bytes_to_read, DWORD *bytes_read, OVERLAPPED *overlapped); - GB_DLL_IMPORT BOOL WINAPI WriteFile (HANDLE file, void const *buffer, DWORD bytes_to_write, DWORD *bytes_written, OVERLAPPED *overlapped); - GB_DLL_IMPORT HANDLE WINAPI CreateFileW (wchar_t const *path, DWORD desired_access, DWORD share_mode, - SECURITY_ATTRIBUTES *, DWORD creation_disposition, - DWORD flags_and_attributes, HANDLE template_file); - GB_DLL_IMPORT HANDLE WINAPI GetStdHandle (DWORD std_handle); - GB_DLL_IMPORT BOOL WINAPI GetFileSizeEx (HANDLE file, LARGE_INTEGER *size); - GB_DLL_IMPORT BOOL WINAPI SetEndOfFile (HANDLE file); - GB_DLL_IMPORT HANDLE WINAPI FindFirstFileW (wchar_t const *path, WIN32_FIND_DATAW *data); - GB_DLL_IMPORT BOOL WINAPI FindClose (HANDLE find_file); - GB_DLL_IMPORT BOOL WINAPI GetFileAttributesExW(wchar_t const *path, GET_FILEEX_INFO_LEVELS info_level_id, WIN32_FILE_ATTRIBUTE_DATA *data); - GB_DLL_IMPORT BOOL WINAPI CopyFileW(wchar_t const *old_f, wchar_t const *new_f, BOOL fail_if_exists); - GB_DLL_IMPORT BOOL WINAPI MoveFileW(wchar_t const *old_f, wchar_t const *new_f); - - GB_DLL_IMPORT HMODULE WINAPI LoadLibraryA (char const *filename); - GB_DLL_IMPORT BOOL WINAPI FreeLibrary (HMODULE module); - GB_DLL_IMPORT FARPROC WINAPI GetProcAddress(HMODULE module, char const *name); - - GB_DLL_IMPORT BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *frequency); - GB_DLL_IMPORT BOOL WINAPI QueryPerformanceCounter (LARGE_INTEGER *counter); - GB_DLL_IMPORT void WINAPI GetSystemTimeAsFileTime (FILETIME *system_time_as_file_time); - GB_DLL_IMPORT void WINAPI Sleep(DWORD milliseconds); - GB_DLL_IMPORT void WINAPI ExitProcess(UINT exit_code); - - GB_DLL_IMPORT BOOL WINAPI SetEnvironmentVariableA(char const *name, char const *value); - - - #define WM_NULL 0x0000 - #define WM_CREATE 0x0001 - #define WM_DESTROY 0x0002 - #define WM_MOVE 0x0003 - #define WM_SIZE 0x0005 - #define WM_ACTIVATE 0x0006 - #define WM_SETFOCUS 0x0007 - #define WM_KILLFOCUS 0x0008 - #define WM_ENABLE 0x000A - #define WM_SETREDRAW 0x000B - #define WM_SETTEXT 0x000C - #define WM_GETTEXT 0x000D - #define WM_GETTEXTLENGTH 0x000E - #define WM_PAINT 0x000F - #define WM_CLOSE 0x0010 - #define WM_QUERYENDSESSION 0x0011 - #define WM_QUERYOPEN 0x0013 - #define WM_ENDSESSION 0x0016 - #define WM_QUIT 0x0012 - #define WM_ERASEBKGND 0x0014 - #define WM_SYSCOLORCHANGE 0x0015 - #define WM_SHOWWINDOW 0x0018 - #define WM_WININICHANGE 0x001A - #define WM_SETTINGCHANGE WM_WININICHANGE - #define WM_DEVMODECHANGE 0x001B - #define WM_ACTIVATEAPP 0x001C - #define WM_FONTCHANGE 0x001D - #define WM_TIMECHANGE 0x001E - #define WM_CANCELMODE 0x001F - #define WM_SETCURSOR 0x0020 - #define WM_MOUSEACTIVATE 0x0021 - #define WM_CHILDACTIVATE 0x0022 - #define WM_QUEUESYNC 0x0023 - #define WM_GETMINMAXINFO 0x0024 - #define WM_PAINTICON 0x0026 - #define WM_ICONERASEBKGND 0x0027 - #define WM_NEXTDLGCTL 0x0028 - #define WM_SPOOLERSTATUS 0x002A - #define WM_DRAWITEM 0x002B - #define WM_MEASUREITEM 0x002C - #define WM_DELETEITEM 0x002D - #define WM_VKEYTOITEM 0x002E - #define WM_CHARTOITEM 0x002F - #define WM_SETFONT 0x0030 - #define WM_GETFONT 0x0031 - #define WM_SETHOTKEY 0x0032 - #define WM_GETHOTKEY 0x0033 - #define WM_QUERYDRAGICON 0x0037 - #define WM_COMPAREITEM 0x0039 - #define WM_GETOBJECT 0x003D - #define WM_COMPACTING 0x0041 - #define WM_COMMNOTIFY 0x0044 /* no longer suported */ - #define WM_WINDOWPOSCHANGING 0x0046 - #define WM_WINDOWPOSCHANGED 0x0047 - #define WM_POWER 0x0048 - #define WM_COPYDATA 0x004A - #define WM_CANCELJOURNAL 0x004B - #define WM_NOTIFY 0x004E - #define WM_INPUTLANGCHANGEREQUEST 0x0050 - #define WM_INPUTLANGCHANGE 0x0051 - #define WM_TCARD 0x0052 - #define WM_HELP 0x0053 - #define WM_USERCHANGED 0x0054 - #define WM_NOTIFYFORMAT 0x0055 - #define WM_CONTEXTMENU 0x007B - #define WM_STYLECHANGING 0x007C - #define WM_STYLECHANGED 0x007D - #define WM_DISPLAYCHANGE 0x007E - #define WM_GETICON 0x007F - #define WM_SETICON 0x0080 - #define WM_INPUT 0x00FF - #define WM_KEYFIRST 0x0100 - #define WM_KEYDOWN 0x0100 - #define WM_KEYUP 0x0101 - #define WM_CHAR 0x0102 - #define WM_DEADCHAR 0x0103 - #define WM_SYSKEYDOWN 0x0104 - #define WM_SYSKEYUP 0x0105 - #define WM_SYSCHAR 0x0106 - #define WM_SYSDEADCHAR 0x0107 - #define WM_UNICHAR 0x0109 - #define WM_KEYLAST 0x0109 - #define WM_APP 0x8000 - - - #define RID_INPUT 0x10000003 - - #define RIM_TYPEMOUSE 0x00000000 - #define RIM_TYPEKEYBOARD 0x00000001 - #define RIM_TYPEHID 0x00000002 - - #define RI_KEY_MAKE 0x0000 - #define RI_KEY_BREAK 0x0001 - #define RI_KEY_E0 0x0002 - #define RI_KEY_E1 0x0004 - #define RI_MOUSE_WHEEL 0x0400 - - #define RIDEV_NOLEGACY 0x00000030 - - #define MAPVK_VK_TO_VSC 0 - #define MAPVK_VSC_TO_VK 1 - #define MAPVK_VK_TO_CHAR 2 - #define MAPVK_VSC_TO_VK_EX 3 - - GB_DLL_IMPORT BOOL WINAPI RegisterRawInputDevices(RAWINPUTDEVICE const *raw_input_devices, UINT num_devices, UINT size); - GB_DLL_IMPORT UINT WINAPI GetRawInputData(HRAWINPUT raw_input, UINT ui_command, void *data, UINT *size, UINT size_header); - GB_DLL_IMPORT UINT WINAPI MapVirtualKeyW(UINT code, UINT map_type); - - - #define CS_DBLCLKS 0x0008 - #define CS_VREDRAW 0x0001 - #define CS_HREDRAW 0x0002 - - #define MB_OK 0x0000l - #define MB_ICONSTOP 0x0010l - #define MB_YESNO 0x0004l - #define MB_HELP 0x4000l - #define MB_ICONEXCLAMATION 0x0030l - - GB_DLL_IMPORT LRESULT WINAPI DefWindowProcW(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam); - GB_DLL_IMPORT HGDIOBJ WINAPI GetStockObject(int object); - GB_DLL_IMPORT HMODULE WINAPI GetModuleHandleW(wchar_t const *); - GB_DLL_IMPORT ATOM WINAPI RegisterClassExW(WNDCLASSEXW const *wcx); // u16 == ATOM - GB_DLL_IMPORT int WINAPI MessageBoxW(void *wnd, wchar_t const *text, wchar_t const *caption, unsigned int type); - - - #define DM_BITSPERPEL 0x00040000l - #define DM_PELSWIDTH 0x00080000l - #define DM_PELSHEIGHT 0x00100000l - - #define CDS_FULLSCREEN 0x4 - #define DISP_CHANGE_SUCCESSFUL 0 - #define IDYES 6 - - #define WS_VISIBLE 0x10000000 - #define WS_THICKFRAME 0x00040000 - #define WS_MAXIMIZE 0x01000000 - #define WS_MAXIMIZEBOX 0x00010000 - #define WS_MINIMIZE 0x20000000 - #define WS_MINIMIZEBOX 0x00020000 - #define WS_POPUP 0x80000000 - #define WS_OVERLAPPED 0 - #define WS_OVERLAPPEDWINDOW 0xcf0000 - #define CW_USEDEFAULT 0x80000000 - #define WS_BORDER 0x800000 - #define WS_CAPTION 0xc00000 - #define WS_SYSMENU 0x80000 - - #define HWND_NOTOPMOST (HWND)(-2) - #define HWND_TOPMOST (HWND)(-1) - #define HWND_TOP (HWND)(+0) - #define HWND_BOTTOM (HWND)(+1) - #define SWP_NOSIZE 0x0001 - #define SWP_NOMOVE 0x0002 - #define SWP_NOZORDER 0x0004 - #define SWP_NOREDRAW 0x0008 - #define SWP_NOACTIVATE 0x0010 - #define SWP_FRAMECHANGED 0x0020 - #define SWP_SHOWWINDOW 0x0040 - #define SWP_HIDEWINDOW 0x0080 - #define SWP_NOCOPYBITS 0x0100 - #define SWP_NOOWNERZORDER 0x0200 - #define SWP_NOSENDCHANGING 0x0400 - - #define SW_HIDE 0 - #define SW_SHOWNORMAL 1 - #define SW_NORMAL 1 - #define SW_SHOWMINIMIZED 2 - #define SW_SHOWMAXIMIZED 3 - #define SW_MAXIMIZE 3 - #define SW_SHOWNOACTIVATE 4 - #define SW_SHOW 5 - #define SW_MINIMIZE 6 - #define SW_SHOWMINNOACTIVE 7 - #define SW_SHOWNA 8 - #define SW_RESTORE 9 - #define SW_SHOWDEFAULT 10 - #define SW_FORCEMINIMIZE 11 - #define SW_MAX 11 - - #define ENUM_CURRENT_SETTINGS cast(DWORD)-1 - #define ENUM_REGISTRY_SETTINGS cast(DWORD)-2 - - GB_DLL_IMPORT LONG WINAPI ChangeDisplaySettingsW(DEVMODEW *dev_mode, DWORD flags); - GB_DLL_IMPORT BOOL WINAPI AdjustWindowRect(RECT *rect, DWORD style, BOOL enu); - GB_DLL_IMPORT HWND WINAPI CreateWindowExW(DWORD ex_style, wchar_t const *class_name, wchar_t const *window_name, - DWORD style, int x, int y, int width, int height, HWND wnd_parent, - HMENU menu, HINSTANCE instance, void *param); - GB_DLL_IMPORT HMODULE WINAPI GetModuleHandleW(wchar_t const *); - GB_DLL_IMPORT HDC GetDC(HANDLE); - GB_DLL_IMPORT BOOL WINAPI GetWindowPlacement(HWND hWnd, WINDOWPLACEMENT *lpwndpl); - GB_DLL_IMPORT BOOL GetMonitorInfoW(HMONITOR hMonitor, MONITORINFO *lpmi); - GB_DLL_IMPORT HMONITOR MonitorFromWindow(HWND hwnd, DWORD dwFlags); - GB_DLL_IMPORT LONG WINAPI SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong); - GB_DLL_IMPORT BOOL WINAPI SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); - GB_DLL_IMPORT BOOL WINAPI SetWindowPlacement(HWND hWnd, WINDOWPLACEMENT const *lpwndpl); - GB_DLL_IMPORT BOOL WINAPI ShowWindow(HWND hWnd, int nCmdShow); - GB_DLL_IMPORT LONG_PTR WINAPI GetWindowLongPtrW(HWND wnd, int index); - - GB_DLL_IMPORT BOOL EnumDisplaySettingsW(wchar_t const *lpszDeviceName, DWORD iModeNum, DEVMODEW *lpDevMode); - GB_DLL_IMPORT void * WINAPI GlobalLock(HGLOBAL hMem); - GB_DLL_IMPORT BOOL WINAPI GlobalUnlock(HGLOBAL hMem); - GB_DLL_IMPORT HGLOBAL WINAPI GlobalAlloc(UINT uFlags, usize dwBytes); - GB_DLL_IMPORT HANDLE WINAPI GetClipboardData(UINT uFormat); - GB_DLL_IMPORT BOOL WINAPI IsClipboardFormatAvailable(UINT format); - GB_DLL_IMPORT BOOL WINAPI OpenClipboard(HWND hWndNewOwner); - GB_DLL_IMPORT BOOL WINAPI EmptyClipboard(void); - GB_DLL_IMPORT BOOL WINAPI CloseClipboard(void); - GB_DLL_IMPORT HANDLE WINAPI SetClipboardData(UINT uFormat, HANDLE hMem); - - #define PFD_TYPE_RGBA 0 - #define PFD_TYPE_COLORINDEX 1 - #define PFD_MAIN_PLANE 0 - #define PFD_OVERLAY_PLANE 1 - #define PFD_UNDERLAY_PLANE (-1) - #define PFD_DOUBLEBUFFER 1 - #define PFD_STEREO 2 - #define PFD_DRAW_TO_WINDOW 4 - #define PFD_DRAW_TO_BITMAP 8 - #define PFD_SUPPORT_GDI 16 - #define PFD_SUPPORT_OPENGL 32 - #define PFD_GENERIC_FORMAT 64 - #define PFD_NEED_PALETTE 128 - #define PFD_NEED_SYSTEM_PALETTE 0x00000100 - #define PFD_SWAP_EXCHANGE 0x00000200 - #define PFD_SWAP_COPY 0x00000400 - #define PFD_SWAP_LAYER_BUFFERS 0x00000800 - #define PFD_GENERIC_ACCELERATED 0x00001000 - #define PFD_DEPTH_DONTCARE 0x20000000 - #define PFD_DOUBLEBUFFER_DONTCARE 0x40000000 - #define PFD_STEREO_DONTCARE 0x80000000 - - #define GWLP_USERDATA -21 - - #define GWL_ID -12 - #define GWL_STYLE -16 - - GB_DLL_IMPORT BOOL WINAPI SetPixelFormat (HDC hdc, int pixel_format, PIXELFORMATDESCRIPTOR const *pfd); - GB_DLL_IMPORT int WINAPI ChoosePixelFormat(HDC hdc, PIXELFORMATDESCRIPTOR const *pfd); - GB_DLL_IMPORT HGLRC WINAPI wglCreateContext (HDC hdc); - GB_DLL_IMPORT BOOL WINAPI wglMakeCurrent (HDC hdc, HGLRC hglrc); - GB_DLL_IMPORT PROC WINAPI wglGetProcAddress(char const *str); - GB_DLL_IMPORT BOOL WINAPI wglDeleteContext (HGLRC hglrc); - - GB_DLL_IMPORT BOOL WINAPI SetForegroundWindow(HWND hWnd); - GB_DLL_IMPORT HWND WINAPI SetFocus(HWND hWnd); - GB_DLL_IMPORT LONG_PTR WINAPI SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong); - GB_DLL_IMPORT BOOL WINAPI GetClientRect(HWND hWnd, RECT *lpRect); - GB_DLL_IMPORT BOOL WINAPI IsIconic(HWND hWnd); - GB_DLL_IMPORT HWND WINAPI GetFocus(void); - GB_DLL_IMPORT int WINAPI ShowCursor(BOOL bShow); - GB_DLL_IMPORT SHORT WINAPI GetAsyncKeyState(int key); - GB_DLL_IMPORT BOOL WINAPI GetCursorPos(POINT *lpPoint); - GB_DLL_IMPORT BOOL WINAPI SetCursorPos(int x, int y); - GB_DLL_IMPORT BOOL ScreenToClient(HWND hWnd, POINT *lpPoint); - GB_DLL_IMPORT BOOL ClientToScreen(HWND hWnd, POINT *lpPoint); - GB_DLL_IMPORT BOOL WINAPI MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint); - GB_DLL_IMPORT BOOL WINAPI SetWindowTextW(HWND hWnd, wchar_t const *lpString); - GB_DLL_IMPORT DWORD WINAPI GetWindowLongW(HWND hWnd, int nIndex); - - - - - #define PM_NOREMOVE 0 - #define PM_REMOVE 1 - - GB_DLL_IMPORT BOOL WINAPI PeekMessageW(MSG *lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); - GB_DLL_IMPORT BOOL WINAPI TranslateMessage(MSG const *lpMsg); - GB_DLL_IMPORT LRESULT WINAPI DispatchMessageW(MSG const *lpMsg); - - typedef enum - { - DIB_RGB_COLORS = 0x00, - DIB_PAL_COLORS = 0x01, - DIB_PAL_INDICES = 0x02 - } DIBColors; - - #define SRCCOPY (u32)0x00CC0020 - #define SRCPAINT (u32)0x00EE0086 - #define SRCAND (u32)0x008800C6 - #define SRCINVERT (u32)0x00660046 - #define SRCERASE (u32)0x00440328 - #define NOTSRCCOPY (u32)0x00330008 - #define NOTSRCERASE (u32)0x001100A6 - #define MERGECOPY (u32)0x00C000CA - #define MERGEPAINT (u32)0x00BB0226 - #define PATCOPY (u32)0x00F00021 - #define PATPAINT (u32)0x00FB0A09 - #define PATINVERT (u32)0x005A0049 - #define DSTINVERT (u32)0x00550009 - #define BLACKNESS (u32)0x00000042 - #define WHITENESS (u32)0x00FF0062 - - GB_DLL_IMPORT BOOL WINAPI SwapBuffers(HDC hdc); - GB_DLL_IMPORT BOOL WINAPI DestroyWindow(HWND hWnd); - GB_DLL_IMPORT int StretchDIBits(HDC hdc, int XDest, int YDest, int nDestWidth, int nDestHeight, - int XSrc, int YSrc, int nSrcWidth, int nSrcHeight, - void const *lpBits, /*BITMAPINFO*/void const *lpBitsInfo, UINT iUsage, DWORD dwRop); - // IMPORTANT TODO(bill): FIX THIS!!!! -#endif // Bill's Mini Windows.h - - - -#if defined(__GCC__) || defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wattributes" -#pragma GCC diagnostic ignored "-Wmissing-braces" -#endif - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4201) -#pragma warning(disable:4127) // Conditional expression is constant -#endif - -void gb_assert_handler(char const *prefix, char const *condition, char const *file, i32 line, char const *msg, ...) { - gb_printf_err("%s(%d): %s: ", file, line, prefix); - if (condition) - gb_printf_err( "`%s` ", condition); - if (msg) { - va_list va; - va_start(va, msg); - gb_printf_err_va(msg, va); - va_end(va); - } - gb_printf_err("\n"); -} - -b32 gb_is_power_of_two(isize x) { - if (x <= 0) - return false; - return !(x & (x-1)); -} - -gb_inline void *gb_align_forward(void *ptr, isize alignment) { - uintptr p; - - GB_ASSERT(gb_is_power_of_two(alignment)); - - p = cast(uintptr)ptr; - return cast(void *)((p + (alignment-1)) &~ (alignment-1)); -} - - - -gb_inline void * gb_pointer_add (void *ptr, isize bytes) { return cast(void *)(cast(u8 *)ptr + bytes); } -gb_inline void * gb_pointer_sub (void *ptr, isize bytes) { return cast(void *)(cast(u8 *)ptr - bytes); } -gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr + bytes); } -gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr - bytes); } -gb_inline isize gb_pointer_diff (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); } - -gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); } - - -#if defined(_MSC_VER) -#pragma intrinsic(__movsb) -#endif - -gb_inline void *gb_memcopy(void *dest, void const *source, isize n) { -#if defined(_MSC_VER) - if (dest == NULL) { - return NULL; - } - // TODO(bill): Is this good enough? - __movsb(cast(u8 *)dest, cast(u8 *)source, n); -// #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) - // NOTE(zangent): I assume there's a reason this isn't being used elsewhere, - // but casting pointers as arguments to an __asm__ call is considered an - // error on MacOS and (I think) Linux - // TODO(zangent): Figure out how to refactor the asm code so it works on MacOS, - // since this is probably not the way the author intended this to work. - // memcpy(dest, source, n); -#elif defined(GB_CPU_X86) - if (dest == NULL) { - return NULL; - } - - void *dest_copy = dest; - __asm__ __volatile__("rep movsb" : "+D"(dest_copy), "+S"(source), "+c"(n) : : "memory"); -#else - u8 *d = cast(u8 *)dest; - u8 const *s = cast(u8 const *)source; - u32 w, x; - - if (dest == NULL) { - return NULL; - } - - for (; cast(uintptr)s % 4 && n; n--) { - *d++ = *s++; - } - - if (cast(uintptr)d % 4 == 0) { - for (; n >= 16; - s += 16, d += 16, n -= 16) { - *cast(u32 *)(d+ 0) = *cast(u32 *)(s+ 0); - *cast(u32 *)(d+ 4) = *cast(u32 *)(s+ 4); - *cast(u32 *)(d+ 8) = *cast(u32 *)(s+ 8); - *cast(u32 *)(d+12) = *cast(u32 *)(s+12); - } - if (n & 8) { - *cast(u32 *)(d+0) = *cast(u32 *)(s+0); - *cast(u32 *)(d+4) = *cast(u32 *)(s+4); - d += 8; - s += 8; - } - if (n&4) { - *cast(u32 *)(d+0) = *cast(u32 *)(s+0); - d += 4; - s += 4; - } - if (n&2) { - *d++ = *s++; *d++ = *s++; - } - if (n&1) { - *d = *s; - } - return dest; - } - - if (n >= 32) { - #if __BYTE_ORDER == __BIG_ENDIAN - #define LS << - #define RS >> - #else - #define LS >> - #define RS << - #endif - switch (cast(uintptr)d % 4) { - case 1: { - w = *cast(u32 *)s; - *d++ = *s++; - *d++ = *s++; - *d++ = *s++; - n -= 3; - while (n > 16) { - x = *cast(u32 *)(s+1); - *cast(u32 *)(d+0) = (w LS 24) | (x RS 8); - w = *cast(u32 *)(s+5); - *cast(u32 *)(d+4) = (x LS 24) | (w RS 8); - x = *cast(u32 *)(s+9); - *cast(u32 *)(d+8) = (w LS 24) | (x RS 8); - w = *cast(u32 *)(s+13); - *cast(u32 *)(d+12) = (x LS 24) | (w RS 8); - - s += 16; - d += 16; - n -= 16; - } - } break; - case 2: { - w = *cast(u32 *)s; - *d++ = *s++; - *d++ = *s++; - n -= 2; - while (n > 17) { - x = *cast(u32 *)(s+2); - *cast(u32 *)(d+0) = (w LS 16) | (x RS 16); - w = *cast(u32 *)(s+6); - *cast(u32 *)(d+4) = (x LS 16) | (w RS 16); - x = *cast(u32 *)(s+10); - *cast(u32 *)(d+8) = (w LS 16) | (x RS 16); - w = *cast(u32 *)(s+14); - *cast(u32 *)(d+12) = (x LS 16) | (w RS 16); - - s += 16; - d += 16; - n -= 16; - } - } break; - case 3: { - w = *cast(u32 *)s; - *d++ = *s++; - n -= 1; - while (n > 18) { - x = *cast(u32 *)(s+3); - *cast(u32 *)(d+0) = (w LS 8) | (x RS 24); - w = *cast(u32 *)(s+7); - *cast(u32 *)(d+4) = (x LS 8) | (w RS 24); - x = *cast(u32 *)(s+11); - *cast(u32 *)(d+8) = (w LS 8) | (x RS 24); - w = *cast(u32 *)(s+15); - *cast(u32 *)(d+12) = (x LS 8) | (w RS 24); - - s += 16; - d += 16; - n -= 16; - } - } break; - default: break; // NOTE(bill): Do nowt! - } - #undef LS - #undef RS - if (n & 16) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n & 8) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n & 4) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n & 2) { - *d++ = *s++; *d++ = *s++; - } - if (n & 1) { - *d = *s; - } - } - -#endif - return dest; -} - -gb_inline void *gb_memmove(void *dest, void const *source, isize n) { - u8 *d = cast(u8 *)dest; - u8 const *s = cast(u8 const *)source; - - if (dest == NULL) { - return NULL; - } - - if (d == s) { - return d; - } - if (s+n <= d || d+n <= s) { // NOTE(bill): Non-overlapping - return gb_memcopy(d, s, n); - } - - if (d < s) { - if (cast(uintptr)s % gb_size_of(isize) == cast(uintptr)d % gb_size_of(isize)) { - while (cast(uintptr)d % gb_size_of(isize)) { - if (!n--) return dest; - *d++ = *s++; - } - while (n>=gb_size_of(isize)) { - *cast(isize *)d = *cast(isize *)s; - n -= gb_size_of(isize); - d += gb_size_of(isize); - s += gb_size_of(isize); - } - } - for (; n; n--) *d++ = *s++; - } else { - if ((cast(uintptr)s % gb_size_of(isize)) == (cast(uintptr)d % gb_size_of(isize))) { - while (cast(uintptr)(d+n) % gb_size_of(isize)) { - if (!n--) - return dest; - d[n] = s[n]; - } - while (n >= gb_size_of(isize)) { - n -= gb_size_of(isize); - *cast(isize *)(d+n) = *cast(isize *)(s+n); - } - } - while (n) n--, d[n] = s[n]; - } - - return dest; -} - -gb_inline void *gb_memset(void *dest, u8 c, isize n) { - u8 *s = cast(u8 *)dest; - isize k; - u32 c32 = ((u32)-1)/255 * c; - - if (dest == NULL) { - return NULL; - } - - if (n == 0) - return dest; - s[0] = s[n-1] = c; - if (n < 3) - return dest; - s[1] = s[n-2] = c; - s[2] = s[n-3] = c; - if (n < 7) - return dest; - s[3] = s[n-4] = c; - if (n < 9) - return dest; - - k = -cast(intptr)s & 3; - s += k; - n -= k; - n &= -4; - - *cast(u32 *)(s+0) = c32; - *cast(u32 *)(s+n-4) = c32; - if (n < 9) { - return dest; - } - *cast(u32 *)(s + 4) = c32; - *cast(u32 *)(s + 8) = c32; - *cast(u32 *)(s+n-12) = c32; - *cast(u32 *)(s+n- 8) = c32; - if (n < 25) { - return dest; - } - *cast(u32 *)(s + 12) = c32; - *cast(u32 *)(s + 16) = c32; - *cast(u32 *)(s + 20) = c32; - *cast(u32 *)(s + 24) = c32; - *cast(u32 *)(s+n-28) = c32; - *cast(u32 *)(s+n-24) = c32; - *cast(u32 *)(s+n-20) = c32; - *cast(u32 *)(s+n-16) = c32; - - k = 24 + (cast(uintptr)s & 4); - s += k; - n -= k; - - - { - u64 c64 = (cast(u64)c32 << 32) | c32; - while (n > 31) { - *cast(u64 *)(s+0) = c64; - *cast(u64 *)(s+8) = c64; - *cast(u64 *)(s+16) = c64; - *cast(u64 *)(s+24) = c64; - - n -= 32; - s += 32; - } - } - - return dest; -} - -gb_inline i32 gb_memcompare(void const *s1, void const *s2, isize size) { - // TODO(bill): Heavily optimize - u8 const *s1p8 = cast(u8 const *)s1; - u8 const *s2p8 = cast(u8 const *)s2; - - if (s1 == NULL || s2 == NULL) { - return 0; - } - - while (size--) { - if (*s1p8 != *s2p8) { - return (*s1p8 - *s2p8); - } - s1p8++, s2p8++; - } - return 0; -} - -void gb_memswap(void *i, void *j, isize size) { - if (i == j) return; - - if (size == 4) { - gb_swap(u32, *cast(u32 *)i, *cast(u32 *)j); - } else if (size == 8) { - gb_swap(u64, *cast(u64 *)i, *cast(u64 *)j); - } else if (size < 8) { - u8 *a = cast(u8 *)i; - u8 *b = cast(u8 *)j; - if (a != b) { - while (size--) { - gb_swap(u8, *a, *b); - a++, b++; - } - } - } else { - char buffer[256]; - - // TODO(bill): Is the recursion ever a problem? - while (size > gb_size_of(buffer)) { - gb_memswap(i, j, gb_size_of(buffer)); - i = gb_pointer_add(i, gb_size_of(buffer)); - j = gb_pointer_add(j, gb_size_of(buffer)); - size -= gb_size_of(buffer); - } - - gb_memcopy(buffer, i, size); - gb_memcopy(i, j, size); - gb_memcopy(j, buffer, size); - } -} - -#define GB__ONES (cast(usize)-1/U8_MAX) -#define GB__HIGHS (GB__ONES * (U8_MAX/2+1)) -#define GB__HAS_ZERO(x) ((x)-GB__ONES & ~(x) & GB__HIGHS) - - -void const *gb_memchr(void const *data, u8 c, isize n) { - u8 const *s = cast(u8 const *)data; - while ((cast(uintptr)s & (sizeof(usize)-1)) && - n && *s != c) { - s++; - n--; - } - if (n && *s != c) { - isize const *w; - isize k = GB__ONES * c; - w = cast(isize const *)s; - while (n >= gb_size_of(isize) && !GB__HAS_ZERO(*w ^ k)) { - w++; - n -= gb_size_of(isize); - } - s = cast(u8 const *)w; - while (n && *s != c) { - s++; - n--; - } - } - - return n ? cast(void const *)s : NULL; -} - - -void const *gb_memrchr(void const *data, u8 c, isize n) { - u8 const *s = cast(u8 const *)data; - while (n--) { - if (s[n] == c) - return cast(void const *)(s + n); - } - return NULL; -} - - - -gb_inline void *gb_alloc_align (gbAllocator a, isize size, isize alignment) { return a.proc(a.data, gbAllocation_Alloc, size, alignment, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } -gb_inline void *gb_alloc (gbAllocator a, isize size) { return gb_alloc_align(a, size, GB_DEFAULT_MEMORY_ALIGNMENT); } -gb_inline void gb_free (gbAllocator a, void *ptr) { if (ptr != NULL) a.proc(a.data, gbAllocation_Free, 0, 0, ptr, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } -gb_inline void gb_free_all (gbAllocator a) { a.proc(a.data, gbAllocation_FreeAll, 0, 0, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); } -gb_inline void *gb_resize (gbAllocator a, void *ptr, isize old_size, isize new_size) { return gb_resize_align(a, ptr, old_size, new_size, GB_DEFAULT_MEMORY_ALIGNMENT); } -gb_inline void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment) { return a.proc(a.data, gbAllocation_Resize, new_size, alignment, ptr, old_size, GB_DEFAULT_ALLOCATOR_FLAGS); } - -gb_inline void *gb_alloc_copy (gbAllocator a, void const *src, isize size) { - return gb_memcopy(gb_alloc(a, size), src, size); -} -gb_inline void *gb_alloc_copy_align(gbAllocator a, void const *src, isize size, isize alignment) { - return gb_memcopy(gb_alloc_align(a, size, alignment), src, size); -} - -gb_inline char *gb_alloc_str(gbAllocator a, char const *str) { - return gb_alloc_str_len(a, str, gb_strlen(str)); -} - -gb_inline char *gb_alloc_str_len(gbAllocator a, char const *str, isize len) { - char *result; - result = cast(char *)gb_alloc_copy(a, str, len+1); - result[len] = '\0'; - return result; -} - - -gb_inline void *gb_default_resize_align(gbAllocator a, void *old_memory, isize old_size, isize new_size, isize alignment) { - if (!old_memory) return gb_alloc_align(a, new_size, alignment); - - if (new_size == 0) { - gb_free(a, old_memory); - return NULL; - } - - if (new_size < old_size) - new_size = old_size; - - if (old_size == new_size) { - return old_memory; - } else { - void *new_memory = gb_alloc_align(a, new_size, alignment); - if (!new_memory) return NULL; - gb_memmove(new_memory, old_memory, gb_min(new_size, old_size)); - gb_free(a, old_memory); - return new_memory; - } -} - - - - -//////////////////////////////////////////////////////////////// -// -// Concurrency -// -// -// IMPORTANT TODO(bill): Use compiler intrinsics for the atomics - -#if defined(GB_COMPILER_MSVC) && !defined(GB_COMPILER_CLANG) -gb_inline i32 gb_atomic32_load (gbAtomic32 const volatile *a) { return a->value; } -gb_inline void gb_atomic32_store(gbAtomic32 volatile *a, i32 value) { a->value = value; } - -gb_inline i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired) { - return _InterlockedCompareExchange(cast(long volatile *)a, desired, expected); -} -gb_inline i32 gb_atomic32_exchanged(gbAtomic32 volatile *a, i32 desired) { - return _InterlockedExchange(cast(long volatile *)a, desired); -} -gb_inline i32 gb_atomic32_fetch_add(gbAtomic32 volatile *a, i32 operand) { - return _InterlockedExchangeAdd(cast(long volatile *)a, operand); -} -gb_inline i32 gb_atomic32_fetch_and(gbAtomic32 volatile *a, i32 operand) { - return _InterlockedAnd(cast(long volatile *)a, operand); -} -gb_inline i32 gb_atomic32_fetch_or(gbAtomic32 volatile *a, i32 operand) { - return _InterlockedOr(cast(long volatile *)a, operand); -} - -gb_inline i64 gb_atomic64_load(gbAtomic64 const volatile *a) { -#if defined(GB_ARCH_64_BIT) - return a->value; -#elif GB_CPU_X86 - // NOTE(bill): The most compatible way to get an atomic 64-bit load on x86 is with cmpxchg8b - i64 result; - __asm { - mov esi, a; - mov ebx, eax; - mov ecx, edx; - lock cmpxchg8b [esi]; - mov dword ptr result, eax; - mov dword ptr result[4], edx; - } - return result; -#else -#error TODO(bill): atomics for this CPU -#endif -} - -gb_inline void gb_atomic64_store(gbAtomic64 volatile *a, i64 value) { -#if defined(GB_ARCH_64_BIT) - a->value = value; -#elif GB_CPU_X86 - // NOTE(bill): The most compatible way to get an atomic 64-bit store on x86 is with cmpxchg8b - __asm { - mov esi, a; - mov ebx, dword ptr value; - mov ecx, dword ptr value[4]; - retry: - cmpxchg8b [esi]; - jne retry; - } -#else -#error TODO(bill): atomics for this CPU -#endif -} - -gb_inline i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired) { - return _InterlockedCompareExchange64(cast(i64 volatile *)a, desired, expected); -} - -gb_inline i64 gb_atomic64_exchanged(gbAtomic64 volatile *a, i64 desired) { -#if defined(GB_ARCH_64_BIT) - return _InterlockedExchange64(cast(i64 volatile *)a, desired); -#elif GB_CPU_X86 - i64 expected = a->value; - for (;;) { - i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, desired, expected); - if (original == expected) - return original; - expected = original; - } -#else -#error TODO(bill): atomics for this CPU -#endif -} - -gb_inline i64 gb_atomic64_fetch_add(gbAtomic64 volatile *a, i64 operand) { -#if defined(GB_ARCH_64_BIT) - return _InterlockedExchangeAdd64(cast(i64 volatile *)a, operand); -#elif GB_CPU_X86 - i64 expected = a->value; - for (;;) { - i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected + operand, expected); - if (original == expected) - return original; - expected = original; - } -#else -#error TODO(bill): atomics for this CPU -#endif -} - -gb_inline i64 gb_atomic64_fetch_and(gbAtomic64 volatile *a, i64 operand) { -#if defined(GB_ARCH_64_BIT) - return _InterlockedAnd64(cast(i64 volatile *)a, operand); -#elif GB_CPU_X86 - i64 expected = a->value; - for (;;) { - i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected & operand, expected); - if (original == expected) - return original; - expected = original; - } -#else -#error TODO(bill): atomics for this CPU -#endif -} - -gb_inline i64 gb_atomic64_fetch_or(gbAtomic64 volatile *a, i64 operand) { -#if defined(GB_ARCH_64_BIT) - return _InterlockedOr64(cast(i64 volatile *)a, operand); -#elif GB_CPU_X86 - i64 expected = a->value; - for (;;) { - i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected | operand, expected); - if (original == expected) - return original; - expected = original; - } -#else -#error TODO(bill): atomics for this CPU -#endif -} - - - -#elif defined(GB_CPU_X86) - -gb_inline i32 gb_atomic32_load (gbAtomic32 const volatile *a) { return a->value; } -gb_inline void gb_atomic32_store(gbAtomic32 volatile *a, i32 value) { a->value = value; } - -gb_inline i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired) { - i32 original; - __asm__ volatile( - "lock; cmpxchgl %2, %1" - : "=a"(original), "+m"(a->value) - : "q"(desired), "0"(expected) - ); - return original; -} - -gb_inline i32 gb_atomic32_exchanged(gbAtomic32 volatile *a, i32 desired) { - // NOTE(bill): No lock prefix is necessary for xchgl - i32 original; - __asm__ volatile( - "xchgl %0, %1" - : "=r"(original), "+m"(a->value) - : "0"(desired) - ); - return original; -} - -gb_inline i32 gb_atomic32_fetch_add(gbAtomic32 volatile *a, i32 operand) { - i32 original; - __asm__ volatile( - "lock; xaddl %0, %1" - : "=r"(original), "+m"(a->value) - : "0"(operand) - ); - return original; -} - -gb_inline i32 gb_atomic32_fetch_and(gbAtomic32 volatile *a, i32 operand) { - i32 original; - i32 tmp; - __asm__ volatile( - "1: movl %1, %0\n" - " movl %0, %2\n" - " andl %3, %2\n" - " lock; cmpxchgl %2, %1\n" - " jne 1b" - : "=&a"(original), "+m"(a->value), "=&r"(tmp) - : "r"(operand) - ); - return original; -} - -gb_inline i32 gb_atomic32_fetch_or(gbAtomic32 volatile *a, i32 operand) { - i32 original; - i32 temp; - __asm__ volatile( - "1: movl %1, %0\n" - " movl %0, %2\n" - " orl %3, %2\n" - " lock; cmpxchgl %2, %1\n" - " jne 1b" - : "=&a"(original), "+m"(a->value), "=&r"(temp) - : "r"(operand) - ); - return original; -} - - -gb_inline i64 gb_atomic64_load(gbAtomic64 const volatile *a) { -#if defined(GB_ARCH_64_BIT) - return a->value; -#else - i64 original; - __asm__ volatile( - "movl %%ebx, %%eax\n" - "movl %%ecx, %%edx\n" - "lock; cmpxchg8b %1" - : "=&A"(original) - : "m"(a->value) - ); - return original; -#endif -} - -gb_inline void gb_atomic64_store(gbAtomic64 volatile *a, i64 value) { -#if defined(GB_ARCH_64_BIT) - a->value = value; -#else - i64 expected = a->value; - __asm__ volatile( - "1: cmpxchg8b %0\n" - " jne 1b" - : "=m"(a->value) - : "b"((i32)value), "c"((i32)(value >> 32)), "A"(expected) - ); -#endif -} - -gb_inline i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired) { -#if defined(GB_ARCH_64_BIT) - i64 original; - __asm__ volatile( - "lock; cmpxchgq %2, %1" - : "=a"(original), "+m"(a->value) - : "q"(desired), "0"(expected) - ); - return original; -#else - i64 original; - __asm__ volatile( - "lock; cmpxchg8b %1" - : "=A"(original), "+m"(a->value) - : "b"((i32)desired), "c"((i32)(desired >> 32)), "0"(expected) - ); - return original; -#endif -} - -gb_inline i64 gb_atomic64_exchanged(gbAtomic64 volatile *a, i64 desired) { -#if defined(GB_ARCH_64_BIT) - i64 original; - __asm__ volatile( - "xchgq %0, %1" - : "=r"(original), "+m"(a->value) - : "0"(desired) - ); - return original; -#else - i64 original = a->value; - for (;;) { - i64 previous = gb_atomic64_compare_exchange(a, original, desired); - if (original == previous) - return original; - original = previous; - } -#endif -} - -gb_inline i64 gb_atomic64_fetch_add(gbAtomic64 volatile *a, i64 operand) { -#if defined(GB_ARCH_64_BIT) - i64 original; - __asm__ volatile( - "lock; xaddq %0, %1" - : "=r"(original), "+m"(a->value) - : "0"(operand) - ); - return original; -#else - for (;;) { - i64 original = a->value; - if (gb_atomic64_compare_exchange(a, original, original + operand) == original) - return original; - } -#endif -} - -gb_inline i64 gb_atomic64_fetch_and(gbAtomic64 volatile *a, i64 operand) { -#if defined(GB_ARCH_64_BIT) - i64 original; - i64 tmp; - __asm__ volatile( - "1: movq %1, %0\n" - " movq %0, %2\n" - " andq %3, %2\n" - " lock; cmpxchgq %2, %1\n" - " jne 1b" - : "=&a"(original), "+m"(a->value), "=&r"(tmp) - : "r"(operand) - ); - return original; -#else - for (;;) { - i64 original = a->value; - if (gb_atomic64_compare_exchange(a, original, original & operand) == original) - return original; - } -#endif -} - -gb_inline i64 gb_atomic64_fetch_or(gbAtomic64 volatile *a, i64 operand) { -#if defined(GB_ARCH_64_BIT) - i64 original; - i64 temp; - __asm__ volatile( - "1: movq %1, %0\n" - " movq %0, %2\n" - " orq %3, %2\n" - " lock; cmpxchgq %2, %1\n" - " jne 1b" - : "=&a"(original), "+m"(a->value), "=&r"(temp) - : "r"(operand) - ); - return original; -#else - for (;;) { - i64 original = a->value; - if (gb_atomic64_compare_exchange(a, original, original | operand) == original) - return original; - } -#endif -} - -#else -#error TODO(bill): Implement Atomics for this CPU -#endif - -gb_inline b32 gb_atomic32_spin_lock(gbAtomic32 volatile *a, isize time_out) { - i32 old_value = gb_atomic32_compare_exchange(a, 1, 0); - i32 counter = 0; - while (old_value != 0 && (time_out < 0 || counter++ < time_out)) { - gb_yield_thread(); - old_value = gb_atomic32_compare_exchange(a, 1, 0); - gb_mfence(); - } - return old_value == 0; -} -gb_inline void gb_atomic32_spin_unlock(gbAtomic32 volatile *a) { - gb_atomic32_store(a, 0); - gb_mfence(); -} - -gb_inline b32 gb_atomic64_spin_lock(gbAtomic64 volatile *a, isize time_out) { - i64 old_value = gb_atomic64_compare_exchange(a, 1, 0); - i64 counter = 0; - while (old_value != 0 && (time_out < 0 || counter++ < time_out)) { - gb_yield_thread(); - old_value = gb_atomic64_compare_exchange(a, 1, 0); - gb_mfence(); - } - return old_value == 0; -} - -gb_inline void gb_atomic64_spin_unlock(gbAtomic64 volatile *a) { - gb_atomic64_store(a, 0); - gb_mfence(); -} - -gb_inline b32 gb_atomic32_try_acquire_lock(gbAtomic32 volatile *a) { - i32 old_value; - gb_yield_thread(); - old_value = gb_atomic32_compare_exchange(a, 1, 0); - gb_mfence(); - return old_value == 0; -} - -gb_inline b32 gb_atomic64_try_acquire_lock(gbAtomic64 volatile *a) { - i64 old_value; - gb_yield_thread(); - old_value = gb_atomic64_compare_exchange(a, 1, 0); - gb_mfence(); - return old_value == 0; -} - - - -#if defined(GB_ARCH_32_BIT) - -gb_inline void *gb_atomic_ptr_load(gbAtomicPtr const volatile *a) { - return cast(void *)cast(intptr)gb_atomic32_load(cast(gbAtomic32 const volatile *)a); -} -gb_inline void gb_atomic_ptr_store(gbAtomicPtr volatile *a, void *value) { - gb_atomic32_store(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)value); -} -gb_inline void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired) { - return cast(void *)cast(intptr)gb_atomic32_compare_exchange(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)expected, cast(i32)cast(intptr)desired); -} -gb_inline void *gb_atomic_ptr_exchanged(gbAtomicPtr volatile *a, void *desired) { - return cast(void *)cast(intptr)gb_atomic32_exchanged(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)desired); -} -gb_inline void *gb_atomic_ptr_fetch_add(gbAtomicPtr volatile *a, void *operand) { - return cast(void *)cast(intptr)gb_atomic32_fetch_add(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand); -} -gb_inline void *gb_atomic_ptr_fetch_and(gbAtomicPtr volatile *a, void *operand) { - return cast(void *)cast(intptr)gb_atomic32_fetch_and(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand); -} -gb_inline void *gb_atomic_ptr_fetch_or(gbAtomicPtr volatile *a, void *operand) { - return cast(void *)cast(intptr)gb_atomic32_fetch_or(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand); -} -gb_inline b32 gb_atomic_ptr_spin_lock(gbAtomicPtr volatile *a, isize time_out) { - return gb_atomic32_spin_lock(cast(gbAtomic32 volatile *)a, time_out); -} -gb_inline void gb_atomic_ptr_spin_unlock(gbAtomicPtr volatile *a) { - gb_atomic32_spin_unlock(cast(gbAtomic32 volatile *)a); -} -gb_inline b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a) { - return gb_atomic32_try_acquire_lock(cast(gbAtomic32 volatile *)a); -} - -#elif defined(GB_ARCH_64_BIT) - -gb_inline void *gb_atomic_ptr_load(gbAtomicPtr const volatile *a) { - return cast(void *)cast(intptr)gb_atomic64_load(cast(gbAtomic64 const volatile *)a); -} -gb_inline void gb_atomic_ptr_store(gbAtomicPtr volatile *a, void *value) { - gb_atomic64_store(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)value); -} -gb_inline void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired) { - return cast(void *)cast(intptr)gb_atomic64_compare_exchange(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)expected, cast(i64)cast(intptr)desired); -} -gb_inline void *gb_atomic_ptr_exchanged(gbAtomicPtr volatile *a, void *desired) { - return cast(void *)cast(intptr)gb_atomic64_exchanged(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)desired); -} -gb_inline void *gb_atomic_ptr_fetch_add(gbAtomicPtr volatile *a, void *operand) { - return cast(void *)cast(intptr)gb_atomic64_fetch_add(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand); -} -gb_inline void *gb_atomic_ptr_fetch_and(gbAtomicPtr volatile *a, void *operand) { - return cast(void *)cast(intptr)gb_atomic64_fetch_and(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand); -} -gb_inline void *gb_atomic_ptr_fetch_or(gbAtomicPtr volatile *a, void *operand) { - return cast(void *)cast(intptr)gb_atomic64_fetch_or(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand); -} -gb_inline b32 gb_atomic_ptr_spin_lock(gbAtomicPtr volatile *a, isize time_out) { - return gb_atomic64_spin_lock(cast(gbAtomic64 volatile *)a, time_out); -} -gb_inline void gb_atomic_ptr_spin_unlock(gbAtomicPtr volatile *a) { - gb_atomic64_spin_unlock(cast(gbAtomic64 volatile *)a); -} -gb_inline b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a) { - return gb_atomic64_try_acquire_lock(cast(gbAtomic64 volatile *)a); -} -#endif - - -gb_inline void gb_yield_thread(void) { -#if defined(GB_SYSTEM_WINDOWS) - _mm_pause(); -#elif defined(GB_SYSTEM_OSX) - __asm__ volatile ("" : : : "memory"); -#elif defined(GB_CPU_X86) - _mm_pause(); -#else -#error Unknown architecture -#endif -} - -gb_inline void gb_mfence(void) { -#if defined(GB_SYSTEM_WINDOWS) - _ReadWriteBarrier(); -#elif defined(GB_SYSTEM_OSX) - __sync_synchronize(); -#elif defined(GB_CPU_X86) - _mm_mfence(); -#else -#error Unknown architecture -#endif -} - -gb_inline void gb_sfence(void) { -#if defined(GB_SYSTEM_WINDOWS) - _WriteBarrier(); -#elif defined(GB_SYSTEM_OSX) - __asm__ volatile ("" : : : "memory"); -#elif defined(GB_CPU_X86) - _mm_sfence(); -#else -#error Unknown architecture -#endif -} - -gb_inline void gb_lfence(void) { -#if defined(GB_SYSTEM_WINDOWS) - _ReadBarrier(); -#elif defined(GB_SYSTEM_OSX) - __asm__ volatile ("" : : : "memory"); -#elif defined(GB_CPU_X86) - _mm_lfence(); -#else -#error Unknown architecture -#endif -} - - -gb_inline void gb_semaphore_release(gbSemaphore *s) { gb_semaphore_post(s, 1); } - -#if defined(GB_SYSTEM_WINDOWS) - gb_inline void gb_semaphore_init (gbSemaphore *s) { s->win32_handle = CreateSemaphoreA(NULL, 0, I32_MAX, NULL); } - gb_inline void gb_semaphore_destroy(gbSemaphore *s) { CloseHandle(s->win32_handle); } - gb_inline void gb_semaphore_post (gbSemaphore *s, i32 count) { ReleaseSemaphore(s->win32_handle, count, NULL); } - gb_inline void gb_semaphore_wait (gbSemaphore *s) { WaitForSingleObject(s->win32_handle, INFINITE); } - -#elif defined(GB_SYSTEM_OSX) - gb_inline void gb_semaphore_init (gbSemaphore *s) { semaphore_create(mach_task_self(), &s->osx_handle, SYNC_POLICY_FIFO, 0); } - gb_inline void gb_semaphore_destroy(gbSemaphore *s) { semaphore_destroy(mach_task_self(), s->osx_handle); } - gb_inline void gb_semaphore_post (gbSemaphore *s, i32 count) { while (count --> 0) semaphore_signal(s->osx_handle); } - gb_inline void gb_semaphore_wait (gbSemaphore *s) { semaphore_wait(s->osx_handle); } - -#elif defined(GB_SYSTEM_UNIX) - gb_inline void gb_semaphore_init (gbSemaphore *s) { sem_init(&s->unix_handle, 0, 0); } - gb_inline void gb_semaphore_destroy(gbSemaphore *s) { sem_destroy(&s->unix_handle); } - gb_inline void gb_semaphore_post (gbSemaphore *s, i32 count) { while (count --> 0) sem_post(&s->unix_handle); } - gb_inline void gb_semaphore_wait (gbSemaphore *s) { int i; do { i = sem_wait(&s->unix_handle); } while (i == -1 && errno == EINTR); } - -#else -#error -#endif - -gb_inline void gb_mutex_init(gbMutex *m) { -#if defined(GB_SYSTEM_WINDOWS) - InitializeCriticalSection(&m->win32_critical_section); -#else - pthread_mutexattr_init(&m->pthread_mutexattr); - pthread_mutexattr_settype(&m->pthread_mutexattr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&m->pthread_mutex, &m->pthread_mutexattr); -#endif -} - -gb_inline void gb_mutex_destroy(gbMutex *m) { -#if defined(GB_SYSTEM_WINDOWS) - DeleteCriticalSection(&m->win32_critical_section); -#else - pthread_mutex_destroy(&m->pthread_mutex); -#endif -} - -gb_inline void gb_mutex_lock(gbMutex *m) { -#if defined(GB_SYSTEM_WINDOWS) - EnterCriticalSection(&m->win32_critical_section); -#else - pthread_mutex_lock(&m->pthread_mutex); -#endif -} - -gb_inline b32 gb_mutex_try_lock(gbMutex *m) { -#if defined(GB_SYSTEM_WINDOWS) - return TryEnterCriticalSection(&m->win32_critical_section) != 0; -#else - return pthread_mutex_trylock(&m->pthread_mutex) == 0; -#endif -} - -gb_inline void gb_mutex_unlock(gbMutex *m) { -#if defined(GB_SYSTEM_WINDOWS) - LeaveCriticalSection(&m->win32_critical_section); -#else - pthread_mutex_unlock(&m->pthread_mutex); -#endif -} - - - - - - - -void gb_thread_init(gbThread *t) { - gb_zero_item(t); -#if defined(GB_SYSTEM_WINDOWS) - t->win32_handle = INVALID_HANDLE_VALUE; -#else - t->posix_handle = 0; -#endif - gb_semaphore_init(&t->semaphore); -} - -void gb_thread_destroy(gbThread *t) { - if (t->is_running) gb_thread_join(t); - gb_semaphore_destroy(&t->semaphore); -} - - -gb_inline void gb__thread_run(gbThread *t) { - gb_semaphore_release(&t->semaphore); - t->return_value = t->proc(t); -} - -#if defined(GB_SYSTEM_WINDOWS) - gb_inline DWORD __stdcall gb__thread_proc(void *arg) { - gbThread *t = cast(gbThread *)arg; - gb__thread_run(t); - t->is_running = false; - return 0; - } -#else - gb_inline void * gb__thread_proc(void *arg) { - gbThread *t = cast(gbThread *)arg; - gb__thread_run(t); - t->is_running = false; - return NULL; - } -#endif - -gb_inline void gb_thread_start(gbThread *t, gbThreadProc *proc, void *user_data) { gb_thread_start_with_stack(t, proc, user_data, 0); } - -gb_inline void gb_thread_start_with_stack(gbThread *t, gbThreadProc *proc, void *user_data, isize stack_size) { - GB_ASSERT(!t->is_running); - GB_ASSERT(proc != NULL); - t->proc = proc; - t->user_data = user_data; - t->stack_size = stack_size; - t->is_running = true; - -#if defined(GB_SYSTEM_WINDOWS) - t->win32_handle = CreateThread(NULL, stack_size, gb__thread_proc, t, 0, NULL); - GB_ASSERT_MSG(t->win32_handle != NULL, "CreateThread: GetLastError"); -#else - { - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - if (stack_size != 0) { - pthread_attr_setstacksize(&attr, stack_size); - } - pthread_create(&t->posix_handle, &attr, gb__thread_proc, t); - pthread_attr_destroy(&attr); - } -#endif - - gb_semaphore_wait(&t->semaphore); -} - -gb_inline void gb_thread_join(gbThread *t) { - if (!t->is_running) return; - -#if defined(GB_SYSTEM_WINDOWS) - WaitForSingleObject(t->win32_handle, INFINITE); - CloseHandle(t->win32_handle); - t->win32_handle = INVALID_HANDLE_VALUE; -#else - pthread_join(t->posix_handle, NULL); - t->posix_handle = 0; -#endif - t->is_running = false; -} - -gb_inline b32 gb_thread_is_running(gbThread const *t) { return t->is_running != 0; } - -gb_inline u32 gb_thread_current_id(void) { - u32 thread_id; -#if defined(GB_SYSTEM_WINDOWS) - #if defined(GB_ARCH_32_BIT) && defined(GB_CPU_X86) - thread_id = (cast(u32 *)__readfsdword(24))[9]; - #elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86) - thread_id = (cast(u32 *)__readgsqword(48))[18]; - #else - thread_id = GetCurrentThreadId(); - #endif - -#elif defined(GB_SYSTEM_OSX) && defined(GB_ARCH_64_BIT) - thread_id = pthread_mach_thread_np(pthread_self()); -#elif defined(GB_ARCH_32_BIT) && defined(GB_CPU_X86) - __asm__("mov %%gs:0x08,%0" : "=r"(thread_id)); -#elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86) - __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); -#else - #error Unsupported architecture for gb_thread_current_id() -#endif - - return thread_id; -} - - - -void gb_thread_set_name(gbThread *t, char const *name) { -#if defined(GB_COMPILER_MSVC) - #pragma pack(push, 8) - typedef struct { - DWORD type; - char const *name; - DWORD id; - DWORD flags; - } gbprivThreadName; - #pragma pack(pop) - gbprivThreadName tn; - tn.type = 0x1000; - tn.name = name; - tn.id = GetThreadId(cast(HANDLE)t->win32_handle); - tn.flags = 0; - - __try { - RaiseException(0x406d1388, 0, gb_size_of(tn)/4, cast(ULONG_PTR *)&tn); - } __except(1 /*EXCEPTION_EXECUTE_HANDLER*/) { - } - -#elif defined(GB_SYSTEM_WINDOWS) && !defined(GB_COMPILER_MSVC) - // IMPORTANT TODO(bill): Set thread name for GCC/Clang on windows - return; -#elif defined(GB_SYSTEM_OSX) - // TODO(bill): Test if this works - pthread_setname_np(name); -#else - // TODO(bill): Test if this works - pthread_setname_np(t->posix_handle, name); -#endif -} - - - - -void gb_sync_init(gbSync *s) { - gb_zero_item(s); - gb_mutex_init(&s->mutex); - gb_mutex_init(&s->start); - gb_semaphore_init(&s->release); -} - -void gb_sync_destroy(gbSync *s) { - if (s->waiting) - GB_PANIC("Cannot destroy while threads are waiting!"); - - gb_mutex_destroy(&s->mutex); - gb_mutex_destroy(&s->start); - gb_semaphore_destroy(&s->release); -} - -void gb_sync_set_target(gbSync *s, i32 count) { - gb_mutex_lock(&s->start); - - gb_mutex_lock(&s->mutex); - GB_ASSERT(s->target == 0); - s->target = count; - s->current = 0; - s->waiting = 0; - gb_mutex_unlock(&s->mutex); -} - -void gb_sync_release(gbSync *s) { - if (s->waiting) { - gb_semaphore_release(&s->release); - } else { - s->target = 0; - gb_mutex_unlock(&s->start); - } -} - -i32 gb_sync_reach(gbSync *s) { - i32 n; - gb_mutex_lock(&s->mutex); - GB_ASSERT(s->current < s->target); - n = ++s->current; // NOTE(bill): Record this value to avoid possible race if `return s->current` was done - if (s->current == s->target) - gb_sync_release(s); - gb_mutex_unlock(&s->mutex); - return n; -} - -void gb_sync_reach_and_wait(gbSync *s) { - gb_mutex_lock(&s->mutex); - GB_ASSERT(s->current < s->target); - s->current++; - if (s->current == s->target) { - gb_sync_release(s); - gb_mutex_unlock(&s->mutex); - } else { - s->waiting++; // NOTE(bill): Waiting, so one more waiter - gb_mutex_unlock(&s->mutex); // NOTE(bill): Release the mutex to other threads - - gb_semaphore_wait(&s->release); // NOTE(bill): Wait for merge completion - - gb_mutex_lock(&s->mutex); // NOTE(bill): On merge completion, lock mutex - s->waiting--; // NOTE(bill): Done waiting - gb_sync_release(s); // NOTE(bill): Restart the next waiter - gb_mutex_unlock(&s->mutex); - } -} - - - - - - - - -gb_inline gbAllocator gb_heap_allocator(void) { - gbAllocator a; - a.proc = gb_heap_allocator_proc; - a.data = NULL; - return a; -} - -GB_ALLOCATOR_PROC(gb_heap_allocator_proc) { - void *ptr = NULL; - gb_unused(allocator_data); - gb_unused(old_size); -// TODO(bill): Throughly test! - switch (type) { -#if defined(GB_COMPILER_MSVC) - case gbAllocation_Alloc: - ptr = _aligned_malloc(size, alignment); - if (flags & gbAllocatorFlag_ClearToZero) - gb_zero_size(ptr, size); - break; - case gbAllocation_Free: - _aligned_free(old_memory); - break; - case gbAllocation_Resize: - ptr = _aligned_realloc(old_memory, size, alignment); - break; - -#elif defined(GB_SYSTEM_LINUX) - // TODO(bill): *nix version that's decent - case gbAllocation_Alloc: { - ptr = aligned_alloc(alignment, size); - // ptr = malloc(size+alignment); - - if (flags & gbAllocatorFlag_ClearToZero) { - gb_zero_size(ptr, size); - } - } break; - - case gbAllocation_Free: { - free(old_memory); - } break; - - case gbAllocation_Resize: { - // ptr = realloc(old_memory, size); - ptr = gb_default_resize_align(gb_heap_allocator(), old_memory, old_size, size, alignment); - } break; -#else - // TODO(bill): *nix version that's decent - case gbAllocation_Alloc: { - posix_memalign(&ptr, alignment, size); - - if (flags & gbAllocatorFlag_ClearToZero) { - gb_zero_size(ptr, size); - } - } break; - - case gbAllocation_Free: { - free(old_memory); - } break; - - case gbAllocation_Resize: { - ptr = gb_default_resize_align(gb_heap_allocator(), old_memory, old_size, size, alignment); - } break; -#endif - - case gbAllocation_FreeAll: - break; - } - - return ptr; -} - - -#if defined(GB_SYSTEM_WINDOWS) -void gb_affinity_init(gbAffinity *a) { - SYSTEM_LOGICAL_PROCESSOR_INFORMATION *start_processor_info = NULL; - DWORD length = 0; - b32 result = GetLogicalProcessorInformation(NULL, &length); - - gb_zero_item(a); - - if (!result && GetLastError() == 122l /*ERROR_INSUFFICIENT_BUFFER*/ && length > 0) { - start_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)gb_alloc(gb_heap_allocator(), length); - result = GetLogicalProcessorInformation(start_processor_info, &length); - if (result) { - SYSTEM_LOGICAL_PROCESSOR_INFORMATION *end_processor_info, *processor_info; - - a->is_accurate = true; - a->core_count = 0; - a->thread_count = 0; - end_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)gb_pointer_add(start_processor_info, length); - - for (processor_info = start_processor_info; - processor_info < end_processor_info; - processor_info++) { - if (processor_info->Relationship == RelationProcessorCore) { - isize thread = gb_count_set_bits(processor_info->ProcessorMask); - if (thread == 0) { - a->is_accurate = false; - } else if (a->thread_count + thread > GB_WIN32_MAX_THREADS) { - a->is_accurate = false; - } else { - GB_ASSERT(a->core_count <= a->thread_count && - a->thread_count < GB_WIN32_MAX_THREADS); - a->core_masks[a->core_count++] = processor_info->ProcessorMask; - a->thread_count += thread; - } - } - } - } - - gb_free(gb_heap_allocator(), start_processor_info); - } - - GB_ASSERT(a->core_count <= a->thread_count); - if (a->thread_count == 0) { - a->is_accurate = false; - a->core_count = 1; - a->thread_count = 1; - a->core_masks[0] = 1; - } - -} -void gb_affinity_destroy(gbAffinity *a) { - gb_unused(a); -} - - -b32 gb_affinity_set(gbAffinity *a, isize core, isize thread) { - usize available_mask, check_mask = 1; - GB_ASSERT(thread < gb_affinity_thread_count_for_core(a, core)); - - available_mask = a->core_masks[core]; - for (;;) { - if ((available_mask & check_mask) != 0) { - if (thread-- == 0) { - usize result = SetThreadAffinityMask(GetCurrentThread(), check_mask); - return result != 0; - } - } - check_mask <<= 1; // NOTE(bill): Onto the next bit - } -} - -isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { - GB_ASSERT(core >= 0 && core < a->core_count); - return gb_count_set_bits(a->core_masks[core]); -} - -#elif defined(GB_SYSTEM_OSX) -void gb_affinity_init(gbAffinity *a) { - usize count = 0; - usize count_size = sizeof(count); - - a->is_accurate = false; - a->thread_count = 1; - a->core_count = 1; - a->threads_per_core = 1; - - if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) { - if (count > 0) { - a->thread_count = count; - // Get # of physical cores - if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) { - if (count > 0) { - a->core_count = count; - a->threads_per_core = a->thread_count / count; - if (a->threads_per_core < 1) - a->threads_per_core = 1; - else - a->is_accurate = true; - } - } - } - } - -} - -void gb_affinity_destroy(gbAffinity *a) { - gb_unused(a); -} - -b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { - isize index; - thread_t thread; - thread_affinity_policy_data_t info; - kern_return_t result; - - GB_ASSERT(core < a->core_count); - GB_ASSERT(thread_index < a->threads_per_core); - - index = core * a->threads_per_core + thread_index; - thread = mach_thread_self(); - info.affinity_tag = cast(integer_t)index; - result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT); - return result == KERN_SUCCESS; -} - -isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { - GB_ASSERT(core >= 0 && core < a->core_count); - return a->threads_per_core; -} - -#elif defined(GB_SYSTEM_LINUX) -// IMPORTANT TODO(bill): This gbAffinity stuff for linux needs be improved a lot! -// NOTE(zangent): I have to read /proc/cpuinfo to get the number of threads per core. -#include - -void gb_affinity_init(gbAffinity *a) { - b32 accurate = true; - isize threads = 0; - - a->thread_count = 1; - a->core_count = sysconf(_SC_NPROCESSORS_ONLN); - a->threads_per_core = 1; - - - if(a->core_count <= 0) { - a->core_count = 1; - accurate = false; - } - - // Parsing /proc/cpuinfo to get the number of threads per core. - // NOTE(zangent): This calls the CPU's threads "cores", although the wording - // is kind of weird. This should be right, though. - - FILE* cpu_info = fopen("/proc/cpuinfo", "r"); - - if (cpu_info != NULL) { - for (;;) { - // The 'temporary char'. Everything goes into this char, - // so that we can check against EOF at the end of this loop. - char c; - -#define AF__CHECK(letter) ((c = getc(cpu_info)) == letter) - if (AF__CHECK('c') && AF__CHECK('p') && AF__CHECK('u') && AF__CHECK(' ') && - AF__CHECK('c') && AF__CHECK('o') && AF__CHECK('r') && AF__CHECK('e') && AF__CHECK('s')) { - // We're on a CPU info line. - while (!AF__CHECK(EOF)) { - if (c == '\n') { - break; - } else if (c < '0' || '9' > c) { - continue; - } - threads = threads * 10 + (c - '0'); - } - break; - } else { - while (!AF__CHECK('\n')) { - if (c==EOF) { - break; - } - } - } - if (c == EOF) { - break; - } -#undef AF__CHECK - } - - fclose(cpu_info); - } - - if (threads == 0) { - threads = 1; - accurate = false; - } - - a->threads_per_core = threads; - a->thread_count = a->threads_per_core * a->core_count; - a->is_accurate = accurate; - -} - -void gb_affinity_destroy(gbAffinity *a) { - gb_unused(a); -} - -b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { - return true; -} - -isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { - GB_ASSERT(0 <= core && core < a->core_count); - return a->threads_per_core; -} -#else -#error TODO(bill): Unknown system -#endif - - - - - - - - - -//////////////////////////////////////////////////////////////// -// -// Virtual Memory -// -// - -gbVirtualMemory gb_virtual_memory(void *data, isize size) { - gbVirtualMemory vm; - vm.data = data; - vm.size = size; - return vm; -} - - -#if defined(GB_SYSTEM_WINDOWS) -gb_inline gbVirtualMemory gb_vm_alloc(void *addr, isize size) { - gbVirtualMemory vm; - GB_ASSERT(size > 0); - vm.data = VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - vm.size = size; - return vm; -} - -gb_inline b32 gb_vm_free(gbVirtualMemory vm) { - MEMORY_BASIC_INFORMATION info; - while (vm.size > 0) { - if (VirtualQuery(vm.data, &info, gb_size_of(info)) == 0) - return false; - if (info.BaseAddress != vm.data || - info.AllocationBase != vm.data || - info.State != MEM_COMMIT || info.RegionSize > cast(usize)vm.size) { - return false; - } - if (VirtualFree(vm.data, 0, MEM_RELEASE) == 0) - return false; - vm.data = gb_pointer_add(vm.data, info.RegionSize); - vm.size -= info.RegionSize; - } - return true; -} - -gb_inline gbVirtualMemory gb_vm_trim(gbVirtualMemory vm, isize lead_size, isize size) { - gbVirtualMemory new_vm = {0}; - void *ptr; - GB_ASSERT(vm.size >= lead_size + size); - - ptr = gb_pointer_add(vm.data, lead_size); - - gb_vm_free(vm); - new_vm = gb_vm_alloc(ptr, size); - if (new_vm.data == ptr) - return new_vm; - if (new_vm.data) - gb_vm_free(new_vm); - return new_vm; -} - -gb_inline b32 gb_vm_purge(gbVirtualMemory vm) { - VirtualAlloc(vm.data, vm.size, MEM_RESET, PAGE_READWRITE); - // NOTE(bill): Can this really fail? - return true; -} - -isize gb_virtual_memory_page_size(isize *alignment_out) { - SYSTEM_INFO info; - GetSystemInfo(&info); - if (alignment_out) *alignment_out = info.dwAllocationGranularity; - return info.dwPageSize; -} - -#else - -#ifndef MAP_ANONYMOUS -#define MAP_ANONYMOUS MAP_ANON -#endif - -gb_inline gbVirtualMemory gb_vm_alloc(void *addr, isize size) { - gbVirtualMemory vm; - GB_ASSERT(size > 0); - vm.data = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - vm.size = size; - return vm; -} - -gb_inline b32 gb_vm_free(gbVirtualMemory vm) { - munmap(vm.data, vm.size); - return true; -} - -gb_inline gbVirtualMemory gb_vm_trim(gbVirtualMemory vm, isize lead_size, isize size) { - void *ptr; - isize trail_size; - GB_ASSERT(vm.size >= lead_size + size); - - ptr = gb_pointer_add(vm.data, lead_size); - trail_size = vm.size - lead_size - size; - - if (lead_size != 0) - gb_vm_free(gb_virtual_memory(vm.data, lead_size)); - if (trail_size != 0) - gb_vm_free(gb_virtual_memory(ptr, trail_size)); - return gb_virtual_memory(ptr, size); - -} - -gb_inline b32 gb_vm_purge(gbVirtualMemory vm) { - int err = madvise(vm.data, vm.size, MADV_DONTNEED); - return err != 0; -} - -isize gb_virtual_memory_page_size(isize *alignment_out) { - // TODO(bill): Is this always true? - isize result = cast(isize)sysconf(_SC_PAGE_SIZE); - if (alignment_out) *alignment_out = result; - return result; -} - -#endif - - - - -//////////////////////////////////////////////////////////////// -// -// Custom Allocation -// -// - - -// -// Arena Allocator -// - -gb_inline void gb_arena_init_from_memory(gbArena *arena, void *start, isize size) { - arena->backing.proc = NULL; - arena->backing.data = NULL; - arena->physical_start = start; - arena->total_size = size; - arena->total_allocated = 0; - arena->temp_count = 0; -} - -gb_inline void gb_arena_init_from_allocator(gbArena *arena, gbAllocator backing, isize size) { - arena->backing = backing; - arena->physical_start = gb_alloc(backing, size); // NOTE(bill): Uses default alignment - arena->total_size = size; - arena->total_allocated = 0; - arena->temp_count = 0; -} - -gb_inline void gb_arena_init_sub(gbArena *arena, gbArena *parent_arena, isize size) { gb_arena_init_from_allocator(arena, gb_arena_allocator(parent_arena), size); } - - -gb_inline void gb_arena_free(gbArena *arena) { - if (arena->backing.proc) { - gb_free(arena->backing, arena->physical_start); - arena->physical_start = NULL; - } -} - - -gb_inline isize gb_arena_alignment_of(gbArena *arena, isize alignment) { - isize alignment_offset, result_pointer, mask; - GB_ASSERT(gb_is_power_of_two(alignment)); - - alignment_offset = 0; - result_pointer = cast(isize)arena->physical_start + arena->total_allocated; - mask = alignment - 1; - if (result_pointer & mask) - alignment_offset = alignment - (result_pointer & mask); - - return alignment_offset; -} - -gb_inline isize gb_arena_size_remaining(gbArena *arena, isize alignment) { - isize result = arena->total_size - (arena->total_allocated + gb_arena_alignment_of(arena, alignment)); - return result; -} - -gb_inline void gb_arena_check(gbArena *arena) { GB_ASSERT(arena->temp_count == 0); } - - - - - - -gb_inline gbAllocator gb_arena_allocator(gbArena *arena) { - gbAllocator allocator; - allocator.proc = gb_arena_allocator_proc; - allocator.data = arena; - return allocator; -} - -GB_ALLOCATOR_PROC(gb_arena_allocator_proc) { - gbArena *arena = cast(gbArena *)allocator_data; - void *ptr = NULL; - - gb_unused(old_size); - - switch (type) { - case gbAllocation_Alloc: { - void *end = gb_pointer_add(arena->physical_start, arena->total_allocated); - isize total_size = size + alignment; - - // NOTE(bill): Out of memory - if (arena->total_allocated + total_size > cast(isize)arena->total_size) { - gb_printf_err("Arena out of memory\n"); - return NULL; - } - - ptr = gb_align_forward(end, alignment); - arena->total_allocated += total_size; - if (flags & gbAllocatorFlag_ClearToZero) - gb_zero_size(ptr, size); - } break; - - case gbAllocation_Free: - // NOTE(bill): Free all at once - // Use Temp_Arena_Memory if you want to free a block - break; - - case gbAllocation_FreeAll: - arena->total_allocated = 0; - break; - - case gbAllocation_Resize: { - // TODO(bill): Check if ptr is on top of stack and just extend - gbAllocator a = gb_arena_allocator(arena); - ptr = gb_default_resize_align(a, old_memory, old_size, size, alignment); - } break; - } - return ptr; -} - - -gb_inline gbTempArenaMemory gb_temp_arena_memory_begin(gbArena *arena) { - gbTempArenaMemory tmp; - tmp.arena = arena; - tmp.original_count = arena->total_allocated; - arena->temp_count++; - return tmp; -} - -gb_inline void gb_temp_arena_memory_end(gbTempArenaMemory tmp) { - GB_ASSERT_MSG(tmp.arena->total_allocated >= tmp.original_count, - "%td >= %td", tmp.arena->total_allocated, tmp.original_count); - GB_ASSERT(tmp.arena->temp_count > 0); - tmp.arena->total_allocated = tmp.original_count; - tmp.arena->temp_count--; -} - - - - -// -// Pool Allocator -// - - -gb_inline void gb_pool_init(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size) { - gb_pool_init_align(pool, backing, num_blocks, block_size, GB_DEFAULT_MEMORY_ALIGNMENT); -} - -void gb_pool_init_align(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size, isize block_align) { - isize actual_block_size, pool_size, block_index; - void *data, *curr; - uintptr *end; - - gb_zero_item(pool); - - pool->backing = backing; - pool->block_size = block_size; - pool->block_align = block_align; - - actual_block_size = block_size + block_align; - pool_size = num_blocks * actual_block_size; - - data = gb_alloc_align(backing, pool_size, block_align); - - // NOTE(bill): Init intrusive freelist - curr = data; - for (block_index = 0; block_index < num_blocks-1; block_index++) { - uintptr *next = cast(uintptr *)curr; - *next = cast(uintptr)curr + actual_block_size; - curr = gb_pointer_add(curr, actual_block_size); - } - - end = cast(uintptr *)curr; - *end = cast(uintptr)NULL; - - pool->physical_start = data; - pool->free_list = data; -} - -gb_inline void gb_pool_free(gbPool *pool) { - if (pool->backing.proc) { - gb_free(pool->backing, pool->physical_start); - } -} - - -gb_inline gbAllocator gb_pool_allocator(gbPool *pool) { - gbAllocator allocator; - allocator.proc = gb_pool_allocator_proc; - allocator.data = pool; - return allocator; -} -GB_ALLOCATOR_PROC(gb_pool_allocator_proc) { - gbPool *pool = cast(gbPool *)allocator_data; - void *ptr = NULL; - - gb_unused(old_size); - - switch (type) { - case gbAllocation_Alloc: { - uintptr next_free; - GB_ASSERT(size == pool->block_size); - GB_ASSERT(alignment == pool->block_align); - GB_ASSERT(pool->free_list != NULL); - - next_free = *cast(uintptr *)pool->free_list; - ptr = pool->free_list; - pool->free_list = cast(void *)next_free; - pool->total_size += pool->block_size; - if (flags & gbAllocatorFlag_ClearToZero) - gb_zero_size(ptr, size); - } break; - - case gbAllocation_Free: { - uintptr *next; - if (old_memory == NULL) return NULL; - - next = cast(uintptr *)old_memory; - *next = cast(uintptr)pool->free_list; - pool->free_list = old_memory; - pool->total_size -= pool->block_size; - } break; - - case gbAllocation_FreeAll: - // TODO(bill): - break; - - case gbAllocation_Resize: - // NOTE(bill): Cannot resize - GB_PANIC("You cannot resize something allocated by with a pool."); - break; - } - - return ptr; -} - - - - - -gb_inline gbAllocationHeader *gb_allocation_header(void *data) { - isize *p = cast(isize *)data; - while (p[-1] == cast(isize)(-1)) { - p--; - } - return cast(gbAllocationHeader *)p - 1; -} - -gb_inline void gb_allocation_header_fill(gbAllocationHeader *header, void *data, isize size) { - isize *ptr; - header->size = size; - ptr = cast(isize *)(header + 1); - while (cast(void *)ptr < data) { - *ptr++ = cast(isize)(-1); - } -} - - - -// -// Free List Allocator -// - -gb_inline void gb_free_list_init(gbFreeList *fl, void *start, isize size) { - GB_ASSERT(size > gb_size_of(gbFreeListBlock)); - - fl->physical_start = start; - fl->total_size = size; - fl->curr_block = cast(gbFreeListBlock *)start; - fl->curr_block->size = size; - fl->curr_block->next = NULL; -} - - -gb_inline void gb_free_list_init_from_allocator(gbFreeList *fl, gbAllocator backing, isize size) { - void *start = gb_alloc(backing, size); - gb_free_list_init(fl, start, size); -} - - - -gb_inline gbAllocator gb_free_list_allocator(gbFreeList *fl) { - gbAllocator a; - a.proc = gb_free_list_allocator_proc; - a.data = fl; - return a; -} - -GB_ALLOCATOR_PROC(gb_free_list_allocator_proc) { - gbFreeList *fl = cast(gbFreeList *)allocator_data; - void *ptr = NULL; - - GB_ASSERT_NOT_NULL(fl); - - switch (type) { - case gbAllocation_Alloc: { - gbFreeListBlock *prev_block = NULL; - gbFreeListBlock *curr_block = fl->curr_block; - - while (curr_block) { - isize total_size; - gbAllocationHeader *header; - - total_size = size + alignment + gb_size_of(gbAllocationHeader); - - if (curr_block->size < total_size) { - prev_block = curr_block; - curr_block = curr_block->next; - continue; - } - - if (curr_block->size - total_size <= gb_size_of(gbAllocationHeader)) { - total_size = curr_block->size; - - if (prev_block) - prev_block->next = curr_block->next; - else - fl->curr_block = curr_block->next; - } else { - // NOTE(bill): Create a new block for the remaining memory - gbFreeListBlock *next_block; - next_block = cast(gbFreeListBlock *)gb_pointer_add(curr_block, total_size); - - GB_ASSERT(cast(void *)next_block < gb_pointer_add(fl->physical_start, fl->total_size)); - - next_block->size = curr_block->size - total_size; - next_block->next = curr_block->next; - - if (prev_block) - prev_block->next = next_block; - else - fl->curr_block = next_block; - } - - - // TODO(bill): Set Header Info - header = cast(gbAllocationHeader *)curr_block; - ptr = gb_align_forward(header+1, alignment); - gb_allocation_header_fill(header, ptr, size); - - fl->total_allocated += total_size; - fl->allocation_count++; - - - if (flags & gbAllocatorFlag_ClearToZero) - gb_zero_size(ptr, size); - return ptr; - } - // NOTE(bill): if ptr == NULL, ran out of free list memory! FUCK! - return NULL; - } break; - - case gbAllocation_Free: { - gbAllocationHeader *header = gb_allocation_header(old_memory); - isize block_size = header->size; - uintptr block_start, block_end; - gbFreeListBlock *prev_block = NULL; - gbFreeListBlock *curr_block = fl->curr_block; - - block_start = cast(uintptr)header; - block_end = cast(uintptr)block_start + block_size; - - while (curr_block) { - if (cast(uintptr)curr_block >= block_end) - break; - prev_block = curr_block; - curr_block = curr_block->next; - } - - if (prev_block == NULL) { - prev_block = cast(gbFreeListBlock *)block_start; - prev_block->size = block_size; - prev_block->next = fl->curr_block; - - fl->curr_block = prev_block; - } else if ((cast(uintptr)prev_block + prev_block->size) == block_start) { - prev_block->size += block_size; - } else { - gbFreeListBlock *tmp = cast(gbFreeListBlock *)block_start; - tmp->size = block_size; - tmp->next = prev_block->next; - prev_block->next = tmp; - - prev_block = tmp; - } - - if (curr_block && (cast(uintptr)curr_block == block_end)) { - prev_block->size += curr_block->size; - prev_block->next = curr_block->next; - } - - fl->allocation_count--; - fl->total_allocated -= block_size; - } break; - - case gbAllocation_FreeAll: - gb_free_list_init(fl, fl->physical_start, fl->total_size); - break; - - case gbAllocation_Resize: - ptr = gb_default_resize_align(gb_free_list_allocator(fl), old_memory, old_size, size, alignment); - break; - } - - return ptr; -} - - - -void gb_scratch_memory_init(gbScratchMemory *s, void *start, isize size) { - s->physical_start = start; - s->total_size = size; - s->alloc_point = start; - s->free_point = start; -} - - -b32 gb_scratch_memory_is_in_use(gbScratchMemory *s, void *ptr) { - if (s->free_point == s->alloc_point) return false; - if (s->alloc_point > s->free_point) - return ptr >= s->free_point && ptr < s->alloc_point; - return ptr >= s->free_point || ptr < s->alloc_point; -} - - -gbAllocator gb_scratch_allocator(gbScratchMemory *s) { - gbAllocator a; - a.proc = gb_scratch_allocator_proc; - a.data = s; - return a; -} - -GB_ALLOCATOR_PROC(gb_scratch_allocator_proc) { - gbScratchMemory *s = cast(gbScratchMemory *)allocator_data; - void *ptr = NULL; - GB_ASSERT_NOT_NULL(s); - - switch (type) { - case gbAllocation_Alloc: { - void *pt = s->alloc_point; - gbAllocationHeader *header = cast(gbAllocationHeader *)pt; - void *data = gb_align_forward(header+1, alignment); - void *end = gb_pointer_add(s->physical_start, s->total_size); - - GB_ASSERT(alignment % 4 == 0); - size = ((size + 3)/4)*4; - pt = gb_pointer_add(pt, size); - - // NOTE(bill): Wrap around - if (pt > end) { - header->size = gb_pointer_diff(header, end) | GB_ISIZE_HIGH_BIT; - pt = s->physical_start; - header = cast(gbAllocationHeader *)pt; - data = gb_align_forward(header+1, alignment); - pt = gb_pointer_add(pt, size); - } - - if (!gb_scratch_memory_is_in_use(s, pt)) { - gb_allocation_header_fill(header, pt, gb_pointer_diff(header, pt)); - s->alloc_point = cast(u8 *)pt; - ptr = data; - } - - if (flags & gbAllocatorFlag_ClearToZero) - gb_zero_size(ptr, size); - } break; - - case gbAllocation_Free: { - if (old_memory) { - void *end = gb_pointer_add(s->physical_start, s->total_size); - if (old_memory < s->physical_start || old_memory >= end) { - GB_ASSERT(false); - } else { - // NOTE(bill): Mark as free - gbAllocationHeader *h = gb_allocation_header(old_memory); - GB_ASSERT((h->size & GB_ISIZE_HIGH_BIT) == 0); - h->size = h->size | GB_ISIZE_HIGH_BIT; - - while (s->free_point != s->alloc_point) { - gbAllocationHeader *header = cast(gbAllocationHeader *)s->free_point; - if ((header->size & GB_ISIZE_HIGH_BIT) == 0) - break; - - s->free_point = gb_pointer_add(s->free_point, h->size & (~GB_ISIZE_HIGH_BIT)); - if (s->free_point == end) - s->free_point = s->physical_start; - } - } - } - } break; - - case gbAllocation_FreeAll: - s->alloc_point = s->physical_start; - s->free_point = s->physical_start; - break; - - case gbAllocation_Resize: - ptr = gb_default_resize_align(gb_scratch_allocator(s), old_memory, old_size, size, alignment); - break; - } - - return ptr; -} - - - - - - -//////////////////////////////////////////////////////////////// -// -// Sorting -// -// - -// TODO(bill): Should I make all the macros local? - -#define GB__COMPARE_PROC(Type) \ -gb_global isize gb__##Type##_cmp_offset; GB_COMPARE_PROC(gb__##Type##_cmp) { \ - Type const p = *cast(Type const *)gb_pointer_add_const(a, gb__##Type##_cmp_offset); \ - Type const q = *cast(Type const *)gb_pointer_add_const(b, gb__##Type##_cmp_offset); \ - return p < q ? -1 : p > q; \ -} \ -GB_COMPARE_PROC_PTR(gb_##Type##_cmp(isize offset)) { \ - gb__##Type##_cmp_offset = offset; \ - return &gb__##Type##_cmp; \ -} - - -GB__COMPARE_PROC(i16); -GB__COMPARE_PROC(i32); -GB__COMPARE_PROC(i64); -GB__COMPARE_PROC(isize); -GB__COMPARE_PROC(f32); -GB__COMPARE_PROC(f64); -GB__COMPARE_PROC(char); - -// NOTE(bill): str_cmp is special as it requires a funny type and funny comparison -gb_global isize gb__str_cmp_offset; GB_COMPARE_PROC(gb__str_cmp) { - char const *p = *cast(char const **)gb_pointer_add_const(a, gb__str_cmp_offset); - char const *q = *cast(char const **)gb_pointer_add_const(b, gb__str_cmp_offset); - return gb_strcmp(p, q); -} -GB_COMPARE_PROC_PTR(gb_str_cmp(isize offset)) { - gb__str_cmp_offset = offset; - return &gb__str_cmp; -} - -#undef GB__COMPARE_PROC - - - - -// TODO(bill): Make user definable? -#define GB__SORT_STACK_SIZE 64 -#define GB__SORT_INSERT_SORT_THRESHOLD 8 - -#define GB__SORT_PUSH(_base, _limit) do { \ - stack_ptr[0] = (_base); \ - stack_ptr[1] = (_limit); \ - stack_ptr += 2; \ -} while (0) - - -#define GB__SORT_POP(_base, _limit) do { \ - stack_ptr -= 2; \ - (_base) = stack_ptr[0]; \ - (_limit) = stack_ptr[1]; \ -} while (0) - - - -void gb_sort(void *base_, isize count, isize size, gbCompareProc cmp) { - u8 *i, *j; - u8 *base = cast(u8 *)base_; - u8 *limit = base + count*size; - isize threshold = GB__SORT_INSERT_SORT_THRESHOLD * size; - - // NOTE(bill): Prepare the stack - u8 *stack[GB__SORT_STACK_SIZE] = {0}; - u8 **stack_ptr = stack; - - for (;;) { - if ((limit-base) > threshold) { - // NOTE(bill): Quick sort - i = base + size; - j = limit - size; - - gb_memswap(((limit-base)/size/2) * size + base, base, size); - if (cmp(i, j) > 0) gb_memswap(i, j, size); - if (cmp(base, j) > 0) gb_memswap(base, j, size); - if (cmp(i, base) > 0) gb_memswap(i, base, size); - - for (;;) { - do i += size; while (cmp(i, base) < 0); - do j -= size; while (cmp(j, base) > 0); - if (i > j) break; - gb_memswap(i, j, size); - } - - gb_memswap(base, j, size); - - if (j - base > limit - i) { - GB__SORT_PUSH(base, j); - base = i; - } else { - GB__SORT_PUSH(i, limit); - limit = j; - } - } else { - // NOTE(bill): Insertion sort - for (j = base, i = j+size; - i < limit; - j = i, i += size) { - for (; cmp(j, j+size) > 0; j -= size) { - gb_memswap(j, j+size, size); - if (j == base) break; - } - } - - if (stack_ptr == stack) break; // NOTE(bill): Sorting is done! - GB__SORT_POP(base, limit); - } - } -} - -#undef GB__SORT_PUSH -#undef GB__SORT_POP - - -#define GB_RADIX_SORT_PROC_GEN(Type) GB_RADIX_SORT_PROC(Type) { \ - Type *source = items; \ - Type *dest = temp; \ - isize byte_index, i, byte_max = 8*gb_size_of(Type); \ - for (byte_index = 0; byte_index < byte_max; byte_index += 8) { \ - isize offsets[256] = {0}; \ - isize total = 0; \ - /* NOTE(bill): First pass - count how many of each key */ \ - for (i = 0; i < count; i++) { \ - Type radix_value = source[i]; \ - Type radix_piece = (radix_value >> byte_index) & 0xff; \ - offsets[radix_piece]++; \ - } \ - /* NOTE(bill): Change counts to offsets */ \ - for (i = 0; i < gb_count_of(offsets); i++) { \ - isize skcount = offsets[i]; \ - offsets[i] = total; \ - total += skcount; \ - } \ - /* NOTE(bill): Second pass - place elements into the right location */ \ - for (i = 0; i < count; i++) { \ - Type radix_value = source[i]; \ - Type radix_piece = (radix_value >> byte_index) & 0xff; \ - dest[offsets[radix_piece]++] = source[i]; \ - } \ - gb_swap(Type *, source, dest); \ - } \ -} - -GB_RADIX_SORT_PROC_GEN(u8); -GB_RADIX_SORT_PROC_GEN(u16); -GB_RADIX_SORT_PROC_GEN(u32); -GB_RADIX_SORT_PROC_GEN(u64); - -gb_inline isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc) { - isize start = 0; - isize end = count; - - while (start < end) { - isize mid = start + (end-start)/2; - isize result = compare_proc(key, cast(u8 *)base + mid*size); - if (result < 0) - end = mid; - else if (result > 0) - start = mid+1; - else - return mid; - } - - return -1; -} - -void gb_shuffle(void *base, isize count, isize size) { - u8 *a; - isize i, j; - gbRandom random; gb_random_init(&random); - - a = cast(u8 *)base + (count-1) * size; - for (i = count; i > 1; i--) { - j = gb_random_gen_isize(&random) % i; - gb_memswap(a, cast(u8 *)base + j*size, size); - a -= size; - } -} - -void gb_reverse(void *base, isize count, isize size) { - isize i, j = count-1; - for (i = 0; i < j; i++, j++) { - gb_memswap(cast(u8 *)base + i*size, cast(u8 *)base + j*size, size); - } -} - - - -//////////////////////////////////////////////////////////////// -// -// Char things -// -// - - - - -gb_inline char gb_char_to_lower(char c) { - if (c >= 'A' && c <= 'Z') - return 'a' + (c - 'A'); - return c; -} - -gb_inline char gb_char_to_upper(char c) { - if (c >= 'a' && c <= 'z') - return 'A' + (c - 'a'); - return c; -} - -gb_inline b32 gb_char_is_space(char c) { - if (c == ' ' || - c == '\t' || - c == '\n' || - c == '\r' || - c == '\f' || - c == '\v') - return true; - return false; -} - -gb_inline b32 gb_char_is_digit(char c) { - if (c >= '0' && c <= '9') - return true; - return false; -} - -gb_inline b32 gb_char_is_hex_digit(char c) { - if (gb_char_is_digit(c) || - (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F')) - return true; - return false; -} - -gb_inline b32 gb_char_is_alpha(char c) { - if ((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z')) - return true; - return false; -} - -gb_inline b32 gb_char_is_alphanumeric(char c) { - return gb_char_is_alpha(c) || gb_char_is_digit(c); -} - -gb_inline i32 gb_digit_to_int(char c) { - return gb_char_is_digit(c) ? c - '0' : c - 'W'; -} - -gb_inline i32 gb_hex_digit_to_int(char c) { - if (gb_char_is_digit(c)) - return gb_digit_to_int(c); - else if (gb_is_between(c, 'a', 'f')) - return c - 'a' + 10; - else if (gb_is_between(c, 'A', 'F')) - return c - 'A' + 10; - return -1; -} - - - - -gb_inline void gb_str_to_lower(char *str) { - if (!str) return; - while (*str) { - *str = gb_char_to_lower(*str); - str++; - } -} - -gb_inline void gb_str_to_upper(char *str) { - if (!str) return; - while (*str) { - *str = gb_char_to_upper(*str); - str++; - } -} - - -gb_inline isize gb_strlen(char const *str) { - char const *begin = str; - isize const *w; - if (str == NULL) { - return 0; - } - while (cast(uintptr)str % sizeof(usize)) { - if (!*str) - return str - begin; - str++; - } - w = cast(isize const *)str; - while (!GB__HAS_ZERO(*w)) { - w++; - } - str = cast(char const *)w; - while (*str) { - str++; - } - return str - begin; -} - -gb_inline isize gb_strnlen(char const *str, isize max_len) { - char const *end = cast(char const *)gb_memchr(str, 0, max_len); - if (end) { - return end - str; - } - return max_len; -} - -gb_inline isize gb_utf8_strlen(u8 const *str) { - isize count = 0; - for (; *str; count++) { - u8 c = *str; - isize inc = 0; - if (c < 0x80) inc = 1; - else if ((c & 0xe0) == 0xc0) inc = 2; - else if ((c & 0xf0) == 0xe0) inc = 3; - else if ((c & 0xf8) == 0xf0) inc = 4; - else return -1; - - str += inc; - } - return count; -} - -gb_inline isize gb_utf8_strnlen(u8 const *str, isize max_len) { - isize count = 0; - for (; *str && max_len > 0; count++) { - u8 c = *str; - isize inc = 0; - if (c < 0x80) inc = 1; - else if ((c & 0xe0) == 0xc0) inc = 2; - else if ((c & 0xf0) == 0xe0) inc = 3; - else if ((c & 0xf8) == 0xf0) inc = 4; - else return -1; - - str += inc; - max_len -= inc; - } - return count; -} - - -gb_inline i32 gb_strcmp(char const *s1, char const *s2) { - while (*s1 && (*s1 == *s2)) { - s1++, s2++; - } - return *(u8 *)s1 - *(u8 *)s2; -} - -gb_inline char *gb_strcpy(char *dest, char const *source) { - GB_ASSERT_NOT_NULL(dest); - if (source) { - char *str = dest; - while (*source) *str++ = *source++; - } - return dest; -} - - -gb_inline char *gb_strncpy(char *dest, char const *source, isize len) { - GB_ASSERT_NOT_NULL(dest); - if (source) { - char *str = dest; - while (len > 0 && *source) { - *str++ = *source++; - len--; - } - while (len > 0) { - *str++ = '\0'; - len--; - } - } - return dest; -} - -gb_inline isize gb_strlcpy(char *dest, char const *source, isize len) { - isize result = 0; - GB_ASSERT_NOT_NULL(dest); - if (source) { - char const *source_start = source; - char *str = dest; - while (len > 0 && *source) { - *str++ = *source++; - len--; - } - while (len > 0) { - *str++ = '\0'; - len--; - } - - result = source - source_start; - } - return result; -} - -gb_inline char *gb_strrev(char *str) { - isize len = gb_strlen(str); - char *a = str + 0; - char *b = str + len-1; - len /= 2; - while (len--) { - gb_swap(char, *a, *b); - a++, b--; - } - return str; -} - - - - -gb_inline i32 gb_strncmp(char const *s1, char const *s2, isize len) { - for (; len > 0; - s1++, s2++, len--) { - if (*s1 != *s2) { - return ((s1 < s2) ? -1 : +1); - } else if (*s1 == '\0') { - return 0; - } - } - return 0; -} - - -gb_inline char const *gb_strtok(char *output, char const *src, char const *delimit) { - while (*src && gb_char_first_occurence(delimit, *src) != NULL) { - *output++ = *src++; - } - - *output = 0; - return *src ? src+1 : src; -} - -gb_inline b32 gb_str_has_prefix(char const *str, char const *prefix) { - while (*prefix) { - if (*str++ != *prefix++) { - return false; - } - } - return true; -} - -gb_inline b32 gb_str_has_suffix(char const *str, char const *suffix) { - isize i = gb_strlen(str); - isize j = gb_strlen(suffix); - if (j <= i) { - return gb_strcmp(str+i-j, suffix) == 0; - } - return false; -} - - - - -gb_inline char const *gb_char_first_occurence(char const *s, char c) { - char ch = c; - for (; *s != ch; s++) { - if (*s == '\0') { - return NULL; - } - } - return s; -} - - -gb_inline char const *gb_char_last_occurence(char const *s, char c) { - char const *result = NULL; - do { - if (*s == c) { - result = s; - } - } while (*s++); - - return result; -} - - - -gb_inline void gb_str_concat(char *dest, isize dest_len, - char const *src_a, isize src_a_len, - char const *src_b, isize src_b_len) { - GB_ASSERT(dest_len >= src_a_len+src_b_len+1); - if (dest) { - gb_memcopy(dest, src_a, src_a_len); - gb_memcopy(dest+src_a_len, src_b, src_b_len); - dest[src_a_len+src_b_len] = '\0'; - } -} - - -gb_internal isize gb__scan_i64(char const *text, i32 base, i64 *value) { - char const *text_begin = text; - i64 result = 0; - b32 negative = false; - - if (*text == '-') { - negative = true; - text++; - } - - if (base == 16 && gb_strncmp(text, "0x", 2) == 0) { - text += 2; - } - - for (;;) { - i64 v; - if (gb_char_is_digit(*text)) { - v = *text - '0'; - } else if (base == 16 && gb_char_is_hex_digit(*text)) { - v = gb_hex_digit_to_int(*text); - } else { - break; - } - - result *= base; - result += v; - text++; - } - - if (value) { - if (negative) result = -result; - *value = result; - } - - return (text - text_begin); -} - -gb_internal isize gb__scan_u64(char const *text, i32 base, u64 *value) { - char const *text_begin = text; - u64 result = 0; - - if (base == 16 && gb_strncmp(text, "0x", 2) == 0) { - text += 2; - } - - for (;;) { - u64 v; - if (gb_char_is_digit(*text)) { - v = *text - '0'; - } else if (base == 16 && gb_char_is_hex_digit(*text)) { - v = gb_hex_digit_to_int(*text); - } else { - break; - } - - result *= base; - result += v; - text++; - } - - if (value) *value = result; - return (text - text_begin); -} - - -// TODO(bill): Make better -u64 gb_str_to_u64(char const *str, char **end_ptr, i32 base) { - isize len; - u64 value = 0; - - if (!base) { - if ((gb_strlen(str) > 2) && (gb_strncmp(str, "0x", 2) == 0)) { - base = 16; - } else { - base = 10; - } - } - - len = gb__scan_u64(str, base, &value); - if (end_ptr) *end_ptr = (char *)str + len; - return value; -} - -i64 gb_str_to_i64(char const *str, char **end_ptr, i32 base) { - isize len; - i64 value; - - if (!base) { - if ((gb_strlen(str) > 2) && (gb_strncmp(str, "0x", 2) == 0)) { - base = 16; - } else { - base = 10; - } - } - - len = gb__scan_i64(str, base, &value); - if (end_ptr) *end_ptr = (char *)str + len; - return value; -} - -// TODO(bill): Are these good enough for characters? -gb_global char const gb__num_to_char_table[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "@$"; - -gb_inline void gb_i64_to_str(i64 value, char *string, i32 base) { - char *buf = string; - b32 negative = false; - u64 v; - if (value < 0) { - negative = true; - value = -value; - } - v = cast(u64)value; - if (v != 0) { - while (v > 0) { - *buf++ = gb__num_to_char_table[v % base]; - v /= base; - } - } else { - *buf++ = '0'; - } - if (negative) { - *buf++ = '-'; - } - *buf = '\0'; - gb_strrev(string); -} - - - -gb_inline void gb_u64_to_str(u64 value, char *string, i32 base) { - char *buf = string; - - if (value) { - while (value > 0) { - *buf++ = gb__num_to_char_table[value % base]; - value /= base; - } - } else { - *buf++ = '0'; - } - *buf = '\0'; - - gb_strrev(string); -} - -gb_inline f32 gb_str_to_f32(char const *str, char **end_ptr) { - f64 f = gb_str_to_f64(str, end_ptr); - f32 r = cast(f32)f; - return r; -} - -gb_inline f64 gb_str_to_f64(char const *str, char **end_ptr) { - f64 result, value, sign, scale; - i32 frac; - - while (gb_char_is_space(*str)) { - str++; - } - - sign = 1.0; - if (*str == '-') { - sign = -1.0; - str++; - } else if (*str == '+') { - str++; - } - - for (value = 0.0; gb_char_is_digit(*str); str++) { - value = value * 10.0 + (*str-'0'); - } - - if (*str == '.') { - f64 pow10 = 10.0; - str++; - while (gb_char_is_digit(*str)) { - value += (*str-'0') / pow10; - pow10 *= 10.0; - str++; - } - } - - frac = 0; - scale = 1.0; - if ((*str == 'e') || (*str == 'E')) { - u32 exp; - - str++; - if (*str == '-') { - frac = 1; - str++; - } else if (*str == '+') { - str++; - } - - for (exp = 0; gb_char_is_digit(*str); str++) { - exp = exp * 10 + (*str-'0'); - } - if (exp > 308) exp = 308; - - while (exp >= 50) { scale *= 1e50; exp -= 50; } - while (exp >= 8) { scale *= 1e8; exp -= 8; } - while (exp > 0) { scale *= 10.0; exp -= 1; } - } - - result = sign * (frac ? (value / scale) : (value * scale)); - - if (end_ptr) *end_ptr = cast(char *)str; - - return result; -} - - - - - - - -gb_inline void gb__set_string_length (gbString str, isize len) { GB_STRING_HEADER(str)->length = len; } -gb_inline void gb__set_string_capacity(gbString str, isize cap) { GB_STRING_HEADER(str)->capacity = cap; } - - -gbString gb_string_make_reserve(gbAllocator a, isize capacity) { - isize header_size = gb_size_of(gbStringHeader); - void *ptr = gb_alloc(a, header_size + capacity + 1); - - gbString str; - gbStringHeader *header; - - if (ptr == NULL) return NULL; - gb_zero_size(ptr, header_size + capacity + 1); - - str = cast(char *)ptr + header_size; - header = GB_STRING_HEADER(str); - header->allocator = a; - header->length = 0; - header->capacity = capacity; - str[capacity] = '\0'; - - return str; -} - - -gb_inline gbString gb_string_make(gbAllocator a, char const *str) { - isize len = str ? gb_strlen(str) : 0; - return gb_string_make_length(a, str, len); -} - -gbString gb_string_make_length(gbAllocator a, void const *init_str, isize num_bytes) { - isize header_size = gb_size_of(gbStringHeader); - void *ptr = gb_alloc(a, header_size + num_bytes + 1); - - gbString str; - gbStringHeader *header; - - if (ptr == NULL) return NULL; - if (!init_str) gb_zero_size(ptr, header_size + num_bytes + 1); - - str = cast(char *)ptr + header_size; - header = GB_STRING_HEADER(str); - header->allocator = a; - header->length = num_bytes; - header->capacity = num_bytes; - if (num_bytes && init_str) { - gb_memcopy(str, init_str, num_bytes); - } - str[num_bytes] = '\0'; - - return str; -} - -gb_inline void gb_string_free(gbString str) { - if (str) { - gbStringHeader *header = GB_STRING_HEADER(str); - gb_free(header->allocator, header); - } - -} - -gb_inline gbString gb_string_duplicate(gbAllocator a, gbString const str) { return gb_string_make_length(a, str, gb_string_length(str)); } - -gb_inline isize gb_string_length (gbString const str) { return GB_STRING_HEADER(str)->length; } -gb_inline isize gb_string_capacity(gbString const str) { return GB_STRING_HEADER(str)->capacity; } - -gb_inline isize gb_string_available_space(gbString const str) { - gbStringHeader *h = GB_STRING_HEADER(str); - if (h->capacity > h->length) { - return h->capacity - h->length; - } - return 0; -} - - -gb_inline void gb_string_clear(gbString str) { gb__set_string_length(str, 0); str[0] = '\0'; } - -gb_inline gbString gb_string_append(gbString str, gbString const other) { return gb_string_append_length(str, other, gb_string_length(other)); } - -gbString gb_string_append_length(gbString str, void const *other, isize other_len) { - if (other_len > 0) { - isize curr_len = gb_string_length(str); - - str = gb_string_make_space_for(str, other_len); - if (str == NULL) { - return NULL; - } - - gb_memcopy(str + curr_len, other, other_len); - str[curr_len + other_len] = '\0'; - gb__set_string_length(str, curr_len + other_len); - } - return str; -} - -gb_inline gbString gb_string_appendc(gbString str, char const *other) { - return gb_string_append_length(str, other, gb_strlen(other)); -} - -gbString gb_string_append_rune(gbString str, Rune r) { - if (r >= 0) { - u8 buf[8] = {0}; - isize len = gb_utf8_encode_rune(buf, r); - return gb_string_append_length(str, buf, len); - } - return str; -} - -gbString gb_string_append_fmt(gbString str, char const *fmt, ...) { - isize res; - char buf[4096] = {0}; - va_list va; - va_start(va, fmt); - res = gb_snprintf_va(buf, gb_count_of(buf)-1, fmt, va)-1; - va_end(va); - return gb_string_append_length(str, buf, res); -} - - - -gbString gb_string_set(gbString str, char const *cstr) { - isize len = gb_strlen(cstr); - if (gb_string_capacity(str) < len) { - str = gb_string_make_space_for(str, len - gb_string_length(str)); - if (str == NULL) { - return NULL; - } - } - - gb_memcopy(str, cstr, len); - str[len] = '\0'; - gb__set_string_length(str, len); - - return str; -} - - - -gbString gb_string_make_space_for(gbString str, isize add_len) { - isize available = gb_string_available_space(str); - - // NOTE(bill): Return if there is enough space left - if (available >= add_len) { - return str; - } else { - isize new_len, old_size, new_size; - void *ptr, *new_ptr; - gbAllocator a = GB_STRING_HEADER(str)->allocator; - gbStringHeader *header; - - new_len = gb_string_length(str) + add_len; - ptr = GB_STRING_HEADER(str); - old_size = gb_size_of(gbStringHeader) + gb_string_length(str) + 1; - new_size = gb_size_of(gbStringHeader) + new_len + 1; - - new_ptr = gb_resize(a, ptr, old_size, new_size); - if (new_ptr == NULL) return NULL; - - header = cast(gbStringHeader *)new_ptr; - header->allocator = a; - - str = cast(gbString)(header+1); - gb__set_string_capacity(str, new_len); - - return str; - } -} - -gb_inline isize gb_string_allocation_size(gbString const str) { - isize cap = gb_string_capacity(str); - return gb_size_of(gbStringHeader) + cap; -} - - -gb_inline b32 gb_string_are_equal(gbString const lhs, gbString const rhs) { - isize lhs_len, rhs_len, i; - lhs_len = gb_string_length(lhs); - rhs_len = gb_string_length(rhs); - if (lhs_len != rhs_len) { - return false; - } - - for (i = 0; i < lhs_len; i++) { - if (lhs[i] != rhs[i]) { - return false; - } - } - - return true; -} - - -gbString gb_string_trim(gbString str, char const *cut_set) { - char *start, *end, *start_pos, *end_pos; - isize len; - - start_pos = start = str; - end_pos = end = str + gb_string_length(str) - 1; - - while (start_pos <= end && gb_char_first_occurence(cut_set, *start_pos)) { - start_pos++; - } - while (end_pos > start_pos && gb_char_first_occurence(cut_set, *end_pos)) { - end_pos--; - } - - len = cast(isize)((start_pos > end_pos) ? 0 : ((end_pos - start_pos)+1)); - - if (str != start_pos) - gb_memmove(str, start_pos, len); - str[len] = '\0'; - - gb__set_string_length(str, len); - - return str; -} - -gb_inline gbString gb_string_trim_space(gbString str) { return gb_string_trim(str, " \t\r\n\v\f"); } - - - - -//////////////////////////////////////////////////////////////// -// -// Windows UTF-8 Handling -// -// - - -u16 *gb_utf8_to_ucs2(u16 *buffer, isize len, u8 const *str) { - Rune c; - isize i = 0; - len--; - while (*str) { - if (i >= len) - return NULL; - if (!(*str & 0x80)) { - buffer[i++] = *str++; - } else if ((*str & 0xe0) == 0xc0) { - if (*str < 0xc2) - return NULL; - c = (*str++ & 0x1f) << 6; - if ((*str & 0xc0) != 0x80) - return NULL; - buffer[i++] = cast(u16)(c + (*str++ & 0x3f)); - } else if ((*str & 0xf0) == 0xe0) { - if (*str == 0xe0 && - (str[1] < 0xa0 || str[1] > 0xbf)) - return NULL; - if (*str == 0xed && str[1] > 0x9f) // str[1] < 0x80 is checked below - return NULL; - c = (*str++ & 0x0f) << 12; - if ((*str & 0xc0) != 0x80) - return NULL; - c += (*str++ & 0x3f) << 6; - if ((*str & 0xc0) != 0x80) - return NULL; - buffer[i++] = cast(u16)(c + (*str++ & 0x3f)); - } else if ((*str & 0xf8) == 0xf0) { - if (*str > 0xf4) - return NULL; - if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) - return NULL; - if (*str == 0xf4 && str[1] > 0x8f) // str[1] < 0x80 is checked below - return NULL; - c = (*str++ & 0x07) << 18; - if ((*str & 0xc0) != 0x80) - return NULL; - c += (*str++ & 0x3f) << 12; - if ((*str & 0xc0) != 0x80) - return NULL; - c += (*str++ & 0x3f) << 6; - if ((*str & 0xc0) != 0x80) - return NULL; - c += (*str++ & 0x3f); - // UTF-8 encodings of values used in surrogate pairs are invalid - if ((c & 0xfffff800) == 0xd800) - return NULL; - if (c >= 0x10000) { - c -= 0x10000; - if (i+2 > len) - return NULL; - buffer[i++] = 0xd800 | (0x3ff & (c>>10)); - buffer[i++] = 0xdc00 | (0x3ff & (c )); - } - } else { - return NULL; - } - } - buffer[i] = 0; - return buffer; -} - -u8 *gb_ucs2_to_utf8(u8 *buffer, isize len, u16 const *str) { - isize i = 0; - len--; - while (*str) { - if (*str < 0x80) { - if (i+1 > len) - return NULL; - buffer[i++] = (char) *str++; - } else if (*str < 0x800) { - if (i+2 > len) - return NULL; - buffer[i++] = cast(char)(0xc0 + (*str >> 6)); - buffer[i++] = cast(char)(0x80 + (*str & 0x3f)); - str += 1; - } else if (*str >= 0xd800 && *str < 0xdc00) { - Rune c; - if (i+4 > len) - return NULL; - c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000; - buffer[i++] = cast(char)(0xf0 + (c >> 18)); - buffer[i++] = cast(char)(0x80 + ((c >> 12) & 0x3f)); - buffer[i++] = cast(char)(0x80 + ((c >> 6) & 0x3f)); - buffer[i++] = cast(char)(0x80 + ((c ) & 0x3f)); - str += 2; - } else if (*str >= 0xdc00 && *str < 0xe000) { - return NULL; - } else { - if (i+3 > len) - return NULL; - buffer[i++] = 0xe0 + (*str >> 12); - buffer[i++] = 0x80 + ((*str >> 6) & 0x3f); - buffer[i++] = 0x80 + ((*str ) & 0x3f); - str += 1; - } - } - buffer[i] = 0; - return buffer; -} - -u16 *gb_utf8_to_ucs2_buf(u8 const *str) { // NOTE(bill): Uses locally persisting buffer - gb_local_persist u16 buf[4096]; - return gb_utf8_to_ucs2(buf, gb_count_of(buf), str); -} - -u8 *gb_ucs2_to_utf8_buf(u16 const *str) { // NOTE(bill): Uses locally persisting buffer - gb_local_persist u8 buf[4096]; - return gb_ucs2_to_utf8(buf, gb_count_of(buf), str); -} - - - -gb_global u8 const gb__utf8_first[256] = { - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6F - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7F - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8F - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9F - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xA0-0xAF - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xB0-0xBF - 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xC0-0xCF - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xD0-0xDF - 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xE0-0xEF - 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xF0-0xFF -}; - - -typedef struct gbUtf8AcceptRange { - u8 lo, hi; -} gbUtf8AcceptRange; - -gb_global gbUtf8AcceptRange const gb__utf8_accept_ranges[] = { - {0x80, 0xbf}, - {0xa0, 0xbf}, - {0x80, 0x9f}, - {0x90, 0xbf}, - {0x80, 0x8f}, -}; - - -isize gb_utf8_decode(u8 const *str, isize str_len, Rune *codepoint_out) { - isize width = 0; - Rune codepoint = GB_RUNE_INVALID; - - if (str_len > 0) { - u8 s0 = str[0]; - u8 x = gb__utf8_first[s0], sz; - u8 b1, b2, b3; - gbUtf8AcceptRange accept; - if (x >= 0xf0) { - Rune mask = (cast(Rune)x << 31) >> 31; - codepoint = (cast(Rune)s0 & (~mask)) | (GB_RUNE_INVALID & mask); - width = 1; - goto end; - } - if (s0 < 0x80) { - codepoint = s0; - width = 1; - goto end; - } - - sz = x&7; - accept = gb__utf8_accept_ranges[x>>4]; - if (str_len < gb_size_of(sz)) - goto invalid_codepoint; - - b1 = str[1]; - if (b1 < accept.lo || accept.hi < b1) - goto invalid_codepoint; - - if (sz == 2) { - codepoint = (cast(Rune)s0&0x1f)<<6 | (cast(Rune)b1&0x3f); - width = 2; - goto end; - } - - b2 = str[2]; - if (!gb_is_between(b2, 0x80, 0xbf)) - goto invalid_codepoint; - - if (sz == 3) { - codepoint = (cast(Rune)s0&0x1f)<<12 | (cast(Rune)b1&0x3f)<<6 | (cast(Rune)b2&0x3f); - width = 3; - goto end; - } - - b3 = str[3]; - if (!gb_is_between(b3, 0x80, 0xbf)) - goto invalid_codepoint; - - codepoint = (cast(Rune)s0&0x07)<<18 | (cast(Rune)b1&0x3f)<<12 | (cast(Rune)b2&0x3f)<<6 | (cast(Rune)b3&0x3f); - width = 4; - goto end; - - invalid_codepoint: - codepoint = GB_RUNE_INVALID; - width = 1; - } - -end: - if (codepoint_out) *codepoint_out = codepoint; - return width; -} - -isize gb_utf8_codepoint_size(u8 const *str, isize str_len) { - isize i = 0; - for (; i < str_len && str[i]; i++) { - if ((str[i] & 0xc0) != 0x80) - break; - } - return i+1; -} - -isize gb_utf8_encode_rune(u8 buf[4], Rune r) { - u32 i = cast(u32)r; - u8 mask = 0x3f; - if (i <= (1<<7)-1) { - buf[0] = cast(u8)r; - return 1; - } - if (i <= (1<<11)-1) { - buf[0] = 0xc0 | cast(u8)(r>>6); - buf[1] = 0x80 | (cast(u8)(r)&mask); - return 2; - } - - // Invalid or Surrogate range - if (i > GB_RUNE_MAX || - gb_is_between(i, 0xd800, 0xdfff)) { - r = GB_RUNE_INVALID; - - buf[0] = 0xe0 | cast(u8)(r>>12); - buf[1] = 0x80 | (cast(u8)(r>>6)&mask); - buf[2] = 0x80 | (cast(u8)(r)&mask); - return 3; - } - - if (i <= (1<<16)-1) { - buf[0] = 0xe0 | cast(u8)(r>>12); - buf[1] = 0x80 | (cast(u8)(r>>6)&mask); - buf[2] = 0x80 | (cast(u8)(r)&mask); - return 3; - } - - buf[0] = 0xf0 | cast(u8)(r>>18); - buf[1] = 0x80 | (cast(u8)(r>>12)&mask); - buf[2] = 0x80 | (cast(u8)(r>>6)&mask); - buf[3] = 0x80 | (cast(u8)(r)&mask); - return 4; -} - - - - -//////////////////////////////////////////////////////////////// -// -// gbArray -// -// - - -gb_no_inline void *gb__array_set_capacity(void *array, isize capacity, isize element_size) { - gbArrayHeader *h = GB_ARRAY_HEADER(array); - - GB_ASSERT(element_size > 0); - - if (capacity == h->capacity) - return array; - - if (capacity < h->count) { - if (h->capacity < capacity) { - isize new_capacity = GB_ARRAY_GROW_FORMULA(h->capacity); - if (new_capacity < capacity) - new_capacity = capacity; - gb__array_set_capacity(array, new_capacity, element_size); - } - h->count = capacity; - } - - { - isize size = gb_size_of(gbArrayHeader) + element_size*capacity; - gbArrayHeader *nh = cast(gbArrayHeader *)gb_alloc(h->allocator, size); - gb_memmove(nh, h, gb_size_of(gbArrayHeader) + element_size*h->count); - nh->allocator = h->allocator; - nh->count = h->count; - nh->capacity = capacity; - gb_free(h->allocator, h); - return nh+1; - } -} - - -//////////////////////////////////////////////////////////////// -// -// Hashing functions -// -// - -u32 gb_adler32(void const *data, isize len) { - u32 const MOD_ALDER = 65521; - u32 a = 1, b = 0; - isize i, block_len; - u8 const *bytes = cast(u8 const *)data; - - block_len = len % 5552; - - while (len) { - for (i = 0; i+7 < block_len; i += 8) { - a += bytes[0], b += a; - a += bytes[1], b += a; - a += bytes[2], b += a; - a += bytes[3], b += a; - a += bytes[4], b += a; - a += bytes[5], b += a; - a += bytes[6], b += a; - a += bytes[7], b += a; - - bytes += 8; - } - for (; i < block_len; i++) { - a += *bytes++, b += a; - } - - a %= MOD_ALDER, b %= MOD_ALDER; - len -= block_len; - block_len = 5552; - } - - return (b << 16) | a; -} - - -gb_global u32 const GB__CRC32_TABLE[256] = { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, - 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, - 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, - 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, - 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, - 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, - 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, - 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, - 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, - 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, - 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, - 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, - 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, - 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, - 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, - 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, - 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, - 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, - 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, - 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, - 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, - 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, -}; - -gb_global u64 const GB__CRC64_TABLE[256] = { - 0x0000000000000000ull, 0x42f0e1eba9ea3693ull, 0x85e1c3d753d46d26ull, 0xc711223cfa3e5bb5ull, - 0x493366450e42ecdfull, 0x0bc387aea7a8da4cull, 0xccd2a5925d9681f9ull, 0x8e224479f47cb76aull, - 0x9266cc8a1c85d9beull, 0xd0962d61b56fef2dull, 0x17870f5d4f51b498ull, 0x5577eeb6e6bb820bull, - 0xdb55aacf12c73561ull, 0x99a54b24bb2d03f2ull, 0x5eb4691841135847ull, 0x1c4488f3e8f96ed4ull, - 0x663d78ff90e185efull, 0x24cd9914390bb37cull, 0xe3dcbb28c335e8c9ull, 0xa12c5ac36adfde5aull, - 0x2f0e1eba9ea36930ull, 0x6dfeff5137495fa3ull, 0xaaefdd6dcd770416ull, 0xe81f3c86649d3285ull, - 0xf45bb4758c645c51ull, 0xb6ab559e258e6ac2ull, 0x71ba77a2dfb03177ull, 0x334a9649765a07e4ull, - 0xbd68d2308226b08eull, 0xff9833db2bcc861dull, 0x388911e7d1f2dda8ull, 0x7a79f00c7818eb3bull, - 0xcc7af1ff21c30bdeull, 0x8e8a101488293d4dull, 0x499b3228721766f8ull, 0x0b6bd3c3dbfd506bull, - 0x854997ba2f81e701ull, 0xc7b97651866bd192ull, 0x00a8546d7c558a27ull, 0x4258b586d5bfbcb4ull, - 0x5e1c3d753d46d260ull, 0x1cecdc9e94ace4f3ull, 0xdbfdfea26e92bf46ull, 0x990d1f49c77889d5ull, - 0x172f5b3033043ebfull, 0x55dfbadb9aee082cull, 0x92ce98e760d05399ull, 0xd03e790cc93a650aull, - 0xaa478900b1228e31ull, 0xe8b768eb18c8b8a2ull, 0x2fa64ad7e2f6e317ull, 0x6d56ab3c4b1cd584ull, - 0xe374ef45bf6062eeull, 0xa1840eae168a547dull, 0x66952c92ecb40fc8ull, 0x2465cd79455e395bull, - 0x3821458aada7578full, 0x7ad1a461044d611cull, 0xbdc0865dfe733aa9ull, 0xff3067b657990c3aull, - 0x711223cfa3e5bb50ull, 0x33e2c2240a0f8dc3ull, 0xf4f3e018f031d676ull, 0xb60301f359dbe0e5ull, - 0xda050215ea6c212full, 0x98f5e3fe438617bcull, 0x5fe4c1c2b9b84c09ull, 0x1d14202910527a9aull, - 0x93366450e42ecdf0ull, 0xd1c685bb4dc4fb63ull, 0x16d7a787b7faa0d6ull, 0x5427466c1e109645ull, - 0x4863ce9ff6e9f891ull, 0x0a932f745f03ce02ull, 0xcd820d48a53d95b7ull, 0x8f72eca30cd7a324ull, - 0x0150a8daf8ab144eull, 0x43a04931514122ddull, 0x84b16b0dab7f7968ull, 0xc6418ae602954ffbull, - 0xbc387aea7a8da4c0ull, 0xfec89b01d3679253ull, 0x39d9b93d2959c9e6ull, 0x7b2958d680b3ff75ull, - 0xf50b1caf74cf481full, 0xb7fbfd44dd257e8cull, 0x70eadf78271b2539ull, 0x321a3e938ef113aaull, - 0x2e5eb66066087d7eull, 0x6cae578bcfe24bedull, 0xabbf75b735dc1058ull, 0xe94f945c9c3626cbull, - 0x676dd025684a91a1ull, 0x259d31cec1a0a732ull, 0xe28c13f23b9efc87ull, 0xa07cf2199274ca14ull, - 0x167ff3eacbaf2af1ull, 0x548f120162451c62ull, 0x939e303d987b47d7ull, 0xd16ed1d631917144ull, - 0x5f4c95afc5edc62eull, 0x1dbc74446c07f0bdull, 0xdaad56789639ab08ull, 0x985db7933fd39d9bull, - 0x84193f60d72af34full, 0xc6e9de8b7ec0c5dcull, 0x01f8fcb784fe9e69ull, 0x43081d5c2d14a8faull, - 0xcd2a5925d9681f90ull, 0x8fdab8ce70822903ull, 0x48cb9af28abc72b6ull, 0x0a3b7b1923564425ull, - 0x70428b155b4eaf1eull, 0x32b26afef2a4998dull, 0xf5a348c2089ac238ull, 0xb753a929a170f4abull, - 0x3971ed50550c43c1ull, 0x7b810cbbfce67552ull, 0xbc902e8706d82ee7ull, 0xfe60cf6caf321874ull, - 0xe224479f47cb76a0ull, 0xa0d4a674ee214033ull, 0x67c58448141f1b86ull, 0x253565a3bdf52d15ull, - 0xab1721da49899a7full, 0xe9e7c031e063acecull, 0x2ef6e20d1a5df759ull, 0x6c0603e6b3b7c1caull, - 0xf6fae5c07d3274cdull, 0xb40a042bd4d8425eull, 0x731b26172ee619ebull, 0x31ebc7fc870c2f78ull, - 0xbfc9838573709812ull, 0xfd39626eda9aae81ull, 0x3a28405220a4f534ull, 0x78d8a1b9894ec3a7ull, - 0x649c294a61b7ad73ull, 0x266cc8a1c85d9be0ull, 0xe17dea9d3263c055ull, 0xa38d0b769b89f6c6ull, - 0x2daf4f0f6ff541acull, 0x6f5faee4c61f773full, 0xa84e8cd83c212c8aull, 0xeabe6d3395cb1a19ull, - 0x90c79d3fedd3f122ull, 0xd2377cd44439c7b1ull, 0x15265ee8be079c04ull, 0x57d6bf0317edaa97ull, - 0xd9f4fb7ae3911dfdull, 0x9b041a914a7b2b6eull, 0x5c1538adb04570dbull, 0x1ee5d94619af4648ull, - 0x02a151b5f156289cull, 0x4051b05e58bc1e0full, 0x87409262a28245baull, 0xc5b073890b687329ull, - 0x4b9237f0ff14c443ull, 0x0962d61b56fef2d0ull, 0xce73f427acc0a965ull, 0x8c8315cc052a9ff6ull, - 0x3a80143f5cf17f13ull, 0x7870f5d4f51b4980ull, 0xbf61d7e80f251235ull, 0xfd913603a6cf24a6ull, - 0x73b3727a52b393ccull, 0x31439391fb59a55full, 0xf652b1ad0167feeaull, 0xb4a25046a88dc879ull, - 0xa8e6d8b54074a6adull, 0xea16395ee99e903eull, 0x2d071b6213a0cb8bull, 0x6ff7fa89ba4afd18ull, - 0xe1d5bef04e364a72ull, 0xa3255f1be7dc7ce1ull, 0x64347d271de22754ull, 0x26c49cccb40811c7ull, - 0x5cbd6cc0cc10fafcull, 0x1e4d8d2b65facc6full, 0xd95caf179fc497daull, 0x9bac4efc362ea149ull, - 0x158e0a85c2521623ull, 0x577eeb6e6bb820b0ull, 0x906fc95291867b05ull, 0xd29f28b9386c4d96ull, - 0xcedba04ad0952342ull, 0x8c2b41a1797f15d1ull, 0x4b3a639d83414e64ull, 0x09ca82762aab78f7ull, - 0x87e8c60fded7cf9dull, 0xc51827e4773df90eull, 0x020905d88d03a2bbull, 0x40f9e43324e99428ull, - 0x2cffe7d5975e55e2ull, 0x6e0f063e3eb46371ull, 0xa91e2402c48a38c4ull, 0xebeec5e96d600e57ull, - 0x65cc8190991cb93dull, 0x273c607b30f68faeull, 0xe02d4247cac8d41bull, 0xa2dda3ac6322e288ull, - 0xbe992b5f8bdb8c5cull, 0xfc69cab42231bacfull, 0x3b78e888d80fe17aull, 0x7988096371e5d7e9ull, - 0xf7aa4d1a85996083ull, 0xb55aacf12c735610ull, 0x724b8ecdd64d0da5ull, 0x30bb6f267fa73b36ull, - 0x4ac29f2a07bfd00dull, 0x08327ec1ae55e69eull, 0xcf235cfd546bbd2bull, 0x8dd3bd16fd818bb8ull, - 0x03f1f96f09fd3cd2ull, 0x41011884a0170a41ull, 0x86103ab85a2951f4ull, 0xc4e0db53f3c36767ull, - 0xd8a453a01b3a09b3ull, 0x9a54b24bb2d03f20ull, 0x5d45907748ee6495ull, 0x1fb5719ce1045206ull, - 0x919735e51578e56cull, 0xd367d40ebc92d3ffull, 0x1476f63246ac884aull, 0x568617d9ef46bed9ull, - 0xe085162ab69d5e3cull, 0xa275f7c11f7768afull, 0x6564d5fde549331aull, 0x279434164ca30589ull, - 0xa9b6706fb8dfb2e3ull, 0xeb46918411358470ull, 0x2c57b3b8eb0bdfc5ull, 0x6ea7525342e1e956ull, - 0x72e3daa0aa188782ull, 0x30133b4b03f2b111ull, 0xf7021977f9cceaa4ull, 0xb5f2f89c5026dc37ull, - 0x3bd0bce5a45a6b5dull, 0x79205d0e0db05dceull, 0xbe317f32f78e067bull, 0xfcc19ed95e6430e8ull, - 0x86b86ed5267cdbd3ull, 0xc4488f3e8f96ed40ull, 0x0359ad0275a8b6f5ull, 0x41a94ce9dc428066ull, - 0xcf8b0890283e370cull, 0x8d7be97b81d4019full, 0x4a6acb477bea5a2aull, 0x089a2aacd2006cb9ull, - 0x14dea25f3af9026dull, 0x562e43b4931334feull, 0x913f6188692d6f4bull, 0xd3cf8063c0c759d8ull, - 0x5dedc41a34bbeeb2ull, 0x1f1d25f19d51d821ull, 0xd80c07cd676f8394ull, 0x9afce626ce85b507ull, -}; - -u32 gb_crc32(void const *data, isize len) { - isize remaining; - u32 result = ~(cast(u32)0); - u8 const *c = cast(u8 const *)data; - for (remaining = len; remaining--; c++) { - result = (result >> 8) ^ (GB__CRC32_TABLE[(result ^ *c) & 0xff]); - } - return ~result; -} - -u64 gb_crc64(void const *data, isize len) { - isize remaining; - u64 result = ~(cast(u64)0); - u8 const *c = cast(u8 const *)data; - for (remaining = len; remaining--; c++) { - result = (result >> 8) ^ (GB__CRC64_TABLE[(result ^ *c) & 0xff]); - } - return ~result; -} - -u32 gb_fnv32(void const *data, isize len) { - isize i; - u32 h = 0x811c9dc5; - u8 const *c = cast(u8 const *)data; - - for (i = 0; i < len; i++) { - h = (h * 0x01000193) ^ c[i]; - } - - return h; -} - -u64 gb_fnv64(void const *data, isize len) { - isize i; - u64 h = 0xcbf29ce484222325ull; - u8 const *c = cast(u8 const *)data; - - for (i = 0; i < len; i++) { - h = (h * 0x100000001b3ll) ^ c[i]; - } - - return h; -} - -u32 gb_fnv32a(void const *data, isize len) { - isize i; - u32 h = 0x811c9dc5; - u8 const *c = cast(u8 const *)data; - - for (i = 0; i < len; i++) { - h = (h ^ c[i]) * 0x01000193; - } - - return h; -} - -u64 gb_fnv64a(void const *data, isize len) { - isize i; - u64 h = 0xcbf29ce484222325ull; - u8 const *c = cast(u8 const *)data; - - for (i = 0; i < len; i++) { - h = (h ^ c[i]) * 0x100000001b3ll; - } - - return h; -} - -gb_inline u32 gb_murmur32(void const *data, isize len) { return gb_murmur32_seed(data, len, 0x9747b28c); } -gb_inline u64 gb_murmur64(void const *data, isize len) { return gb_murmur64_seed(data, len, 0x9747b28c); } - -u32 gb_murmur32_seed(void const *data, isize len, u32 seed) { - u32 const c1 = 0xcc9e2d51; - u32 const c2 = 0x1b873593; - u32 const r1 = 15; - u32 const r2 = 13; - u32 const m = 5; - u32 const n = 0xe6546b64; - - isize i, nblocks = len / 4; - u32 hash = seed, k1 = 0; - u32 const *blocks = cast(u32 const*)data; - u8 const *tail = cast(u8 const *)(data) + nblocks*4; - - for (i = 0; i < nblocks; i++) { - u32 k = blocks[i]; - k *= c1; - k = (k << r1) | (k >> (32 - r1)); - k *= c2; - - hash ^= k; - hash = ((hash << r2) | (hash >> (32 - r2))) * m + n; - } - - switch (len & 3) { - case 3: - k1 ^= tail[2] << 16; - case 2: - k1 ^= tail[1] << 8; - case 1: - k1 ^= tail[0]; - - k1 *= c1; - k1 = (k1 << r1) | (k1 >> (32 - r1)); - k1 *= c2; - hash ^= k1; - } - - hash ^= len; - hash ^= (hash >> 16); - hash *= 0x85ebca6b; - hash ^= (hash >> 13); - hash *= 0xc2b2ae35; - hash ^= (hash >> 16); - - return hash; -} - -u64 gb_murmur64_seed(void const *data_, isize len, u64 seed) { -#if defined(GB_ARCH_64_BIT) - u64 const m = 0xc6a4a7935bd1e995ULL; - i32 const r = 47; - - u64 h = seed ^ (len * m); - - u64 const *data = cast(u64 const *)data_; - u8 const *data2 = cast(u8 const *)data_; - u64 const* end = data + (len / 8); - - while (data != end) { - u64 k = *data++; - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - switch (len & 7) { - case 7: h ^= cast(u64)(data2[6]) << 48; - case 6: h ^= cast(u64)(data2[5]) << 40; - case 5: h ^= cast(u64)(data2[4]) << 32; - case 4: h ^= cast(u64)(data2[3]) << 24; - case 3: h ^= cast(u64)(data2[2]) << 16; - case 2: h ^= cast(u64)(data2[1]) << 8; - case 1: h ^= cast(u64)(data2[0]); - h *= m; - }; - - h ^= h >> r; - h *= m; - h ^= h >> r; - - return h; -#else - u64 h; - u32 const m = 0x5bd1e995; - i32 const r = 24; - - u32 h1 = cast(u32)(seed) ^ cast(u32)(len); - u32 h2 = cast(u32)(seed >> 32); - - u32 const *data = cast(u32 const *)data_; - - while (len >= 8) { - u32 k1, k2; - k1 = *data++; - k1 *= m; - k1 ^= k1 >> r; - k1 *= m; - h1 *= m; - h1 ^= k1; - len -= 4; - - k2 = *data++; - k2 *= m; - k2 ^= k2 >> r; - k2 *= m; - h2 *= m; - h2 ^= k2; - len -= 4; - } - - if (len >= 4) { - u32 k1 = *data++; - k1 *= m; - k1 ^= k1 >> r; - k1 *= m; - h1 *= m; - h1 ^= k1; - len -= 4; - } - - switch (len) { - case 3: h2 ^= (cast(u8 const *)data)[2] << 16; - case 2: h2 ^= (cast(u8 const *)data)[1] << 8; - case 1: h2 ^= (cast(u8 const *)data)[0] << 0; - h2 *= m; - }; - - h1 ^= h2 >> 18; - h1 *= m; - h2 ^= h1 >> 22; - h2 *= m; - h1 ^= h2 >> 17; - h1 *= m; - h2 ^= h1 >> 19; - h2 *= m; - - h = h1; - h = (h << 32) | h2; - - return h; -#endif -} - - - - - - - -//////////////////////////////////////////////////////////////// -// -// File Handling -// -// - -#if defined(GB_SYSTEM_WINDOWS) - - gb_internal wchar_t *gb__alloc_utf8_to_ucs2(gbAllocator a, char const *text, isize *w_len_) { - wchar_t *w_text = NULL; - isize len = 0, w_len = 0, w_len1 = 0; - if (text == NULL) { - if (w_len_) *w_len_ = w_len; - return NULL; - } - len = gb_strlen(text); - if (len == 0) { - if (w_len_) *w_len_ = w_len; - return NULL; - } - w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, cast(int)len, NULL, 0); - if (w_len == 0) { - if (w_len_) *w_len_ = w_len; - return NULL; - } - w_text = gb_alloc_array(a, wchar_t, w_len+1); - w_len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, cast(int)len, w_text, cast(int)w_len); - if (w_len1 == 0) { - gb_free(a, w_text); - if (w_len_) *w_len_ = 0; - return NULL; - } - w_text[w_len] = 0; - if (w_len_) *w_len_ = w_len; - return w_text; - } - - gb_internal GB_FILE_SEEK_PROC(gb__win32_file_seek) { - LARGE_INTEGER li_offset; - li_offset.QuadPart = offset; - if (!SetFilePointerEx(fd.p, li_offset, &li_offset, whence)) { - return false; - } - - if (new_offset) *new_offset = li_offset.QuadPart; - return true; - } - - gb_internal GB_FILE_READ_AT_PROC(gb__win32_file_read) { - b32 result = false; - DWORD size_ = cast(DWORD)(size > I32_MAX ? I32_MAX : size); - DWORD bytes_read_; - gb__win32_file_seek(fd, offset, gbSeekWhence_Begin, NULL); - if (ReadFile(fd.p, buffer, size_, &bytes_read_, NULL)) { - if (bytes_read) *bytes_read = bytes_read_; - result = true; - } - - return result; - } - - gb_internal GB_FILE_WRITE_AT_PROC(gb__win32_file_write) { - DWORD size_ = cast(DWORD)(size > I32_MAX ? I32_MAX : size); - DWORD bytes_written_; - gb__win32_file_seek(fd, offset, gbSeekWhence_Begin, NULL); - if (WriteFile(fd.p, buffer, size_, &bytes_written_, NULL)) { - if (bytes_written) *bytes_written = bytes_written_; - return true; - } - return false; - } - - gb_internal GB_FILE_CLOSE_PROC(gb__win32_file_close) { - CloseHandle(fd.p); - } - - gbFileOperations const gbDefaultFileOperations = { - gb__win32_file_read, - gb__win32_file_write, - gb__win32_file_seek, - gb__win32_file_close - }; - - gb_no_inline GB_FILE_OPEN_PROC(gb__win32_file_open) { - DWORD desired_access; - DWORD creation_disposition; - void *handle; - wchar_t *w_text; - - switch (mode & gbFileMode_Modes) { - case gbFileMode_Read: - desired_access = GENERIC_READ; - creation_disposition = OPEN_EXISTING; - break; - case gbFileMode_Write: - desired_access = GENERIC_WRITE; - creation_disposition = CREATE_ALWAYS; - break; - case gbFileMode_Append: - desired_access = GENERIC_WRITE; - creation_disposition = OPEN_ALWAYS; - break; - case gbFileMode_Read | gbFileMode_Rw: - desired_access = GENERIC_READ | GENERIC_WRITE; - creation_disposition = OPEN_EXISTING; - break; - case gbFileMode_Write | gbFileMode_Rw: - desired_access = GENERIC_READ | GENERIC_WRITE; - creation_disposition = CREATE_ALWAYS; - break; - case gbFileMode_Append | gbFileMode_Rw: - desired_access = GENERIC_READ | GENERIC_WRITE; - creation_disposition = OPEN_ALWAYS; - break; - default: - GB_PANIC("Invalid file mode"); - return gbFileError_Invalid; - } - - w_text = gb__alloc_utf8_to_ucs2(gb_heap_allocator(), filename, NULL); - if (w_text == NULL) { - return gbFileError_InvalidFilename; - } - handle = CreateFileW(w_text, - desired_access, - FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, - creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); - - gb_free(gb_heap_allocator(), w_text); - - if (handle == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); - switch (err) { - case ERROR_FILE_NOT_FOUND: return gbFileError_NotExists; - case ERROR_FILE_EXISTS: return gbFileError_Exists; - case ERROR_ALREADY_EXISTS: return gbFileError_Exists; - case ERROR_ACCESS_DENIED: return gbFileError_Permission; - } - return gbFileError_Invalid; - } - - if (mode & gbFileMode_Append) { - LARGE_INTEGER offset = {0}; - if (!SetFilePointerEx(handle, offset, NULL, gbSeekWhence_End)) { - CloseHandle(handle); - return gbFileError_Invalid; - } - } - - fd->p = handle; - *ops = gbDefaultFileOperations; - return gbFileError_None; - } - -#else // POSIX - gb_internal GB_FILE_SEEK_PROC(gb__posix_file_seek) { - #if defined(GB_SYSTEM_OSX) - i64 res = lseek(fd.i, offset, whence); - #else - i64 res = lseek64(fd.i, offset, whence); - #endif - if (res < 0) return false; - if (new_offset) *new_offset = res; - return true; - } - - gb_internal GB_FILE_READ_AT_PROC(gb__posix_file_read) { - isize res = pread(fd.i, buffer, size, offset); - if (res < 0) return false; - if (bytes_read) *bytes_read = res; - return true; - } - - gb_internal GB_FILE_WRITE_AT_PROC(gb__posix_file_write) { - isize res; - i64 curr_offset = 0; - gb__posix_file_seek(fd, 0, gbSeekWhence_Current, &curr_offset); - if (curr_offset == offset) { - // NOTE(bill): Writing to stdout et al. doesn't like pwrite for numerous reasons - res = write(cast(int)fd.i, buffer, size); - } else { - res = pwrite(cast(int)fd.i, buffer, size, offset); - } - if (res < 0) return false; - if (bytes_written) *bytes_written = res; - return true; - } - - - gb_internal GB_FILE_CLOSE_PROC(gb__posix_file_close) { - close(fd.i); - } - - gbFileOperations const gbDefaultFileOperations = { - gb__posix_file_read, - gb__posix_file_write, - gb__posix_file_seek, - gb__posix_file_close - }; - - gb_no_inline GB_FILE_OPEN_PROC(gb__posix_file_open) { - i32 os_mode; - switch (mode & gbFileMode_Modes) { - case gbFileMode_Read: - os_mode = O_RDONLY; - break; - case gbFileMode_Write: - os_mode = O_WRONLY | O_CREAT | O_TRUNC; - break; - case gbFileMode_Append: - os_mode = O_WRONLY | O_APPEND | O_CREAT; - break; - case gbFileMode_Read | gbFileMode_Rw: - os_mode = O_RDWR; - break; - case gbFileMode_Write | gbFileMode_Rw: - os_mode = O_RDWR | O_CREAT | O_TRUNC; - break; - case gbFileMode_Append | gbFileMode_Rw: - os_mode = O_RDWR | O_APPEND | O_CREAT; - break; - default: - GB_PANIC("Invalid file mode"); - return gbFileError_Invalid; - } - - fd->i = open(filename, os_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (fd->i < 0) { - // TODO(bill): More file errors - return gbFileError_Invalid; - } - - *ops = gbDefaultFileOperations; - return gbFileError_None; - } - -#endif - - - -gbFileError gb_file_new(gbFile *f, gbFileDescriptor fd, gbFileOperations ops, char const *filename) { - gbFileError err = gbFileError_None; - isize len = gb_strlen(filename); - - // gb_printf_err("gb_file_new: %s\n", filename); - - f->ops = ops; - f->fd = fd; - f->filename = gb_alloc_array(gb_heap_allocator(), char, len+1); - gb_memcopy(cast(char *)f->filename, cast(char *)filename, len+1); - f->last_write_time = gb_file_last_write_time(f->filename); - - return err; -} - - - -gbFileError gb_file_open_mode(gbFile *f, gbFileMode mode, char const *filename) { - gbFileError err; -#if defined(GB_SYSTEM_WINDOWS) - err = gb__win32_file_open(&f->fd, &f->ops, mode, filename); -#else - err = gb__posix_file_open(&f->fd, &f->ops, mode, filename); -#endif - if (err == gbFileError_None) { - return gb_file_new(f, f->fd, f->ops, filename); - } - return err; -} - -gbFileError gb_file_close(gbFile *f) { - if (f == NULL) { - return gbFileError_Invalid; - } - -#if defined(GB_COMPILER_MSVC) - if (f->filename != NULL) { - gb_free(gb_heap_allocator(), cast(char *)f->filename); - } -#else - // TODO HACK(bill): Memory Leak!!! -#endif - -#if defined(GB_SYSTEM_WINDOWS) - if (f->fd.p == INVALID_HANDLE_VALUE) { - return gbFileError_Invalid; - } -#else - if (f->fd.i < 0) { - return gbFileError_Invalid; - } -#endif - - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - f->ops.close(f->fd); - - return gbFileError_None; -} - -gb_inline b32 gb_file_read_at_check(gbFile *f, void *buffer, isize size, i64 offset, isize *bytes_read) { - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - return f->ops.read_at(f->fd, buffer, size, offset, bytes_read); -} - -gb_inline b32 gb_file_write_at_check(gbFile *f, void const *buffer, isize size, i64 offset, isize *bytes_written) { - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - return f->ops.write_at(f->fd, buffer, size, offset, bytes_written); -} - - -gb_inline b32 gb_file_read_at(gbFile *f, void *buffer, isize size, i64 offset) { - return gb_file_read_at_check(f, buffer, size, offset, NULL); -} - -gb_inline b32 gb_file_write_at(gbFile *f, void const *buffer, isize size, i64 offset) { - return gb_file_write_at_check(f, buffer, size, offset, NULL); -} - -gb_inline i64 gb_file_seek(gbFile *f, i64 offset) { - i64 new_offset = 0; - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - f->ops.seek(f->fd, offset, gbSeekWhence_Begin, &new_offset); - return new_offset; -} - -gb_inline i64 gb_file_seek_to_end(gbFile *f) { - i64 new_offset = 0; - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - f->ops.seek(f->fd, 0, gbSeekWhence_End, &new_offset); - return new_offset; -} - -// NOTE(bill): Skips a certain amount of bytes -gb_inline i64 gb_file_skip(gbFile *f, i64 bytes) { - i64 new_offset = 0; - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - f->ops.seek(f->fd, bytes, gbSeekWhence_Current, &new_offset); - return new_offset; -} - -gb_inline i64 gb_file_tell(gbFile *f) { - i64 new_offset = 0; - if (!f->ops.read_at) f->ops = gbDefaultFileOperations; - f->ops.seek(f->fd, 0, gbSeekWhence_Current, &new_offset); - return new_offset; -} -gb_inline b32 gb_file_read (gbFile *f, void *buffer, isize size) { return gb_file_read_at(f, buffer, size, gb_file_tell(f)); } -gb_inline b32 gb_file_write(gbFile *f, void const *buffer, isize size) { return gb_file_write_at(f, buffer, size, gb_file_tell(f)); } - - -gbFileError gb_file_create(gbFile *f, char const *filename) { - return gb_file_open_mode(f, gbFileMode_Write|gbFileMode_Rw, filename); -} - - -gbFileError gb_file_open(gbFile *f, char const *filename) { - return gb_file_open_mode(f, gbFileMode_Read, filename); -} - - -char const *gb_file_name(gbFile *f) { return f->filename ? f->filename : ""; } - -gb_inline b32 gb_file_has_changed(gbFile *f) { - b32 result = false; - gbFileTime last_write_time = gb_file_last_write_time(f->filename); - if (f->last_write_time != last_write_time) { - result = true; - f->last_write_time = last_write_time; - } - return result; -} - -// TODO(bill): Is this a bad idea? -gb_global b32 gb__std_file_set = false; -gb_global gbFile gb__std_files[gbFileStandard_Count] = {{0}}; - - -#if defined(GB_SYSTEM_WINDOWS) - -gb_inline gbFile *const gb_file_get_standard(gbFileStandardType std) { - if (!gb__std_file_set) { - #define GB__SET_STD_FILE(type, v) gb__std_files[type].fd.p = v; gb__std_files[type].ops = gbDefaultFileOperations - GB__SET_STD_FILE(gbFileStandard_Input, GetStdHandle(STD_INPUT_HANDLE)); - GB__SET_STD_FILE(gbFileStandard_Output, GetStdHandle(STD_OUTPUT_HANDLE)); - GB__SET_STD_FILE(gbFileStandard_Error, GetStdHandle(STD_ERROR_HANDLE)); - #undef GB__SET_STD_FILE - gb__std_file_set = true; - } - return &gb__std_files[std]; -} - -gb_inline i64 gb_file_size(gbFile *f) { - LARGE_INTEGER size; - GetFileSizeEx(f->fd.p, &size); - return size.QuadPart; -} - -gbFileError gb_file_truncate(gbFile *f, i64 size) { - gbFileError err = gbFileError_None; - i64 prev_offset = gb_file_tell(f); - gb_file_seek(f, size); - if (!SetEndOfFile(f)) { - err = gbFileError_TruncationFailure; - } - gb_file_seek(f, prev_offset); - return err; -} - - -b32 gb_file_exists(char const *name) { - WIN32_FIND_DATAW data; - wchar_t *w_text; - void *handle; - b32 found = false; - gbAllocator a = gb_heap_allocator(); - - w_text = gb__alloc_utf8_to_ucs2(a, name, NULL); - if (w_text == NULL) { - return false; - } - handle = FindFirstFileW(w_text, &data); - gb_free(a, w_text); - found = handle != INVALID_HANDLE_VALUE; - if (found) FindClose(handle); - return found; -} - -#else // POSIX - -gb_inline gbFile *const gb_file_get_standard(gbFileStandardType std) { - if (!gb__std_file_set) { - #define GB__SET_STD_FILE(type, v) gb__std_files[type].fd.i = v; gb__std_files[type].ops = gbDefaultFileOperations - GB__SET_STD_FILE(gbFileStandard_Input, 0); - GB__SET_STD_FILE(gbFileStandard_Output, 1); - GB__SET_STD_FILE(gbFileStandard_Error, 2); - #undef GB__SET_STD_FILE - gb__std_file_set = true; - } - return &gb__std_files[std]; -} - -gb_inline i64 gb_file_size(gbFile *f) { - i64 size = 0; - i64 prev_offset = gb_file_tell(f); - gb_file_seek_to_end(f); - size = gb_file_tell(f); - gb_file_seek(f, prev_offset); - return size; -} - -gb_inline gbFileError gb_file_truncate(gbFile *f, i64 size) { - gbFileError err = gbFileError_None; - int i = ftruncate(f->fd.i, size); - if (i != 0) err = gbFileError_TruncationFailure; - return err; -} - -gb_inline b32 gb_file_exists(char const *name) { - return access(name, F_OK) != -1; -} -#endif - - - -#if defined(GB_SYSTEM_WINDOWS) -gbFileTime gb_file_last_write_time(char const *filepath) { - ULARGE_INTEGER li = {0}; - FILETIME last_write_time = {0}; - WIN32_FILE_ATTRIBUTE_DATA data = {0}; - gbAllocator a = gb_heap_allocator(); - - wchar_t *w_text = gb__alloc_utf8_to_ucs2(a, filepath, NULL); - if (w_text == NULL) { - return 0; - } - - if (GetFileAttributesExW(w_text, GetFileExInfoStandard, &data)) { - last_write_time = data.ftLastWriteTime; - } - gb_free(a, w_text); - - li.LowPart = last_write_time.dwLowDateTime; - li.HighPart = last_write_time.dwHighDateTime; - return cast(gbFileTime)li.QuadPart; -} - - -gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) { - wchar_t *w_old = NULL; - wchar_t *w_new = NULL; - gbAllocator a = gb_heap_allocator(); - b32 result = false; - - w_old = gb__alloc_utf8_to_ucs2(a, existing_filename, NULL); - if (w_old == NULL) { - return false; - } - w_new = gb__alloc_utf8_to_ucs2(a, new_filename, NULL); - if (w_new != NULL) { - result = CopyFileW(w_old, w_new, fail_if_exists); - } - gb_free(a, w_new); - gb_free(a, w_old); - return result; -} - -gb_inline b32 gb_file_move(char const *existing_filename, char const *new_filename) { - wchar_t *w_old = NULL; - wchar_t *w_new = NULL; - gbAllocator a = gb_heap_allocator(); - b32 result = false; - - w_old = gb__alloc_utf8_to_ucs2(a, existing_filename, NULL); - if (w_old == NULL) { - return false; - } - w_new = gb__alloc_utf8_to_ucs2(a, new_filename, NULL); - if (w_new != NULL) { - result = MoveFileW(w_old, w_new); - } - gb_free(a, w_new); - gb_free(a, w_old); - return result; -} - -b32 gb_file_remove(char const *filename) { - wchar_t *w_filename = NULL; - gbAllocator a = gb_heap_allocator(); - b32 result = false; - w_filename = gb__alloc_utf8_to_ucs2(a, filename, NULL); - if (w_filename == NULL) { - return false; - } - result = DeleteFileW(w_filename); - gb_free(a, w_filename); - return result; -} - - - -#else - -gbFileTime gb_file_last_write_time(char const *filepath) { - time_t result = 0; - struct stat file_stat; - - if (stat(filepath, &file_stat) == 0) { - result = file_stat.st_mtime; - } - - return cast(gbFileTime)result; -} - - -gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) { -#if defined(GB_SYSTEM_OSX) - return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0; -#else - isize size; - int existing_fd = open(existing_filename, O_RDONLY, 0); - int new_fd = open(new_filename, O_WRONLY|O_CREAT, 0666); - - struct stat stat_existing; - fstat(existing_fd, &stat_existing); - - size = sendfile(new_fd, existing_fd, 0, stat_existing.st_size); - - close(new_fd); - close(existing_fd); - - return size == stat_existing.st_size; -#endif -} - -gb_inline b32 gb_file_move(char const *existing_filename, char const *new_filename) { - if (link(existing_filename, new_filename) == 0) { - return unlink(existing_filename) != -1; - } - return false; -} - -b32 gb_file_remove(char const *filename) { -#if defined(GB_SYSTEM_OSX) - return unlink(filename) != -1; -#else - return remove(filename) == 0; -#endif -} - - -#endif - - - - - -gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char const *filepath) { - gbFileContents result = {0}; - gbFile file = {0}; - - result.allocator = a; - - if (gb_file_open(&file, filepath) == gbFileError_None) { - isize file_size = cast(isize)gb_file_size(&file); - if (file_size > 0) { - result.data = gb_alloc(a, zero_terminate ? file_size+1 : file_size); - result.size = file_size; - gb_file_read_at(&file, result.data, result.size, 0); - if (zero_terminate) { - u8 *str = cast(u8 *)result.data; - str[file_size] = '\0'; - } - } - gb_file_close(&file); - } - - return result; -} - -void gb_file_free_contents(gbFileContents *fc) { - GB_ASSERT_NOT_NULL(fc->data); - gb_free(fc->allocator, fc->data); - fc->data = NULL; - fc->size = 0; -} - - - - - -gb_inline b32 gb_path_is_absolute(char const *path) { - b32 result = false; - GB_ASSERT_NOT_NULL(path); -#if defined(GB_SYSTEM_WINDOWS) - result == (gb_strlen(path) > 2) && - gb_char_is_alpha(path[0]) && - (path[1] == ':' && path[2] == GB_PATH_SEPARATOR); -#else - result = (gb_strlen(path) > 0 && path[0] == GB_PATH_SEPARATOR); -#endif - return result; -} - -gb_inline b32 gb_path_is_relative(char const *path) { return !gb_path_is_absolute(path); } - -gb_inline b32 gb_path_is_root(char const *path) { - b32 result = false; - GB_ASSERT_NOT_NULL(path); -#if defined(GB_SYSTEM_WINDOWS) - result = gb_path_is_absolute(path) && (gb_strlen(path) == 3); -#else - result = gb_path_is_absolute(path) && (gb_strlen(path) == 1); -#endif - return result; -} - -gb_inline char const *gb_path_base_name(char const *path) { - char const *ls; - GB_ASSERT_NOT_NULL(path); - ls = gb_char_last_occurence(path, '/'); - return (ls == NULL) ? path : ls+1; -} - -gb_inline char const *gb_path_extension(char const *path) { - char const *ld; - GB_ASSERT_NOT_NULL(path); - ld = gb_char_last_occurence(path, '.'); - return (ld == NULL) ? NULL : ld+1; -} - - -#if !defined(_WINDOWS_) && defined(GB_SYSTEM_WINDOWS) -GB_DLL_IMPORT DWORD WINAPI GetFullPathNameA(char const *lpFileName, DWORD nBufferLength, char *lpBuffer, char **lpFilePart); -GB_DLL_IMPORT DWORD WINAPI GetFullPathNameW(wchar_t const *lpFileName, DWORD nBufferLength, wchar_t *lpBuffer, wchar_t **lpFilePart); -#endif - -char *gb_path_get_full_name(gbAllocator a, char const *path) { -#if defined(GB_SYSTEM_WINDOWS) -// TODO(bill): Make UTF-8 - wchar_t *w_path = NULL; - wchar_t *w_fullpath = NULL; - isize w_len = 0; - isize new_len = 0; - isize new_len1 = 0; - char *new_path = 0; - w_path = gb__alloc_utf8_to_ucs2(gb_heap_allocator(), path, NULL); - if (w_path == NULL) { - return NULL; - } - w_len = GetFullPathNameW(w_path, 0, NULL, NULL); - if (w_len == 0) { - return NULL; - } - w_fullpath = gb_alloc_array(gb_heap_allocator(), wchar_t, w_len+1); - GetFullPathNameW(w_path, cast(int)w_len, w_fullpath, NULL); - w_fullpath[w_len] = 0; - gb_free(gb_heap_allocator(), w_path); - - new_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w_fullpath, cast(int)w_len, NULL, 0, NULL, NULL); - if (new_len == 0) { - gb_free(gb_heap_allocator(), w_fullpath); - return NULL; - } - new_path = gb_alloc_array(a, char, new_len+1); - new_len1 = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w_fullpath, cast(int)w_len, new_path, cast(int)new_len, NULL, NULL); - if (new_len1 == 0) { - gb_free(gb_heap_allocator(), w_fullpath); - gb_free(a, new_path); - return NULL; - } - new_path[new_len] = 0; - return new_path; -#else - char *p, *result, *fullpath = NULL; - isize len; - p = realpath(path, NULL); - fullpath = p; - if (p == NULL) { - // NOTE(bill): File does not exist - fullpath = cast(char *)path; - } - - len = gb_strlen(fullpath); - - result = gb_alloc_array(a, char, len + 1); - gb_memmove(result, fullpath, len); - result[len] = 0; - free(p); - - return result; -#endif -} - - - - - -//////////////////////////////////////////////////////////////// -// -// Printing -// -// - - -isize gb_printf(char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = gb_printf_va(fmt, va); - va_end(va); - return res; -} - - -isize gb_printf_err(char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = gb_printf_err_va(fmt, va); - va_end(va); - return res; -} - -isize gb_fprintf(struct gbFile *f, char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = gb_fprintf_va(f, fmt, va); - va_end(va); - return res; -} - -char *gb_bprintf(char const *fmt, ...) { - va_list va; - char *str; - va_start(va, fmt); - str = gb_bprintf_va(fmt, va); - va_end(va); - return str; -} - -isize gb_snprintf(char *str, isize n, char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = gb_snprintf_va(str, n, fmt, va); - va_end(va); - return res; -} - - - -gb_inline isize gb_printf_va(char const *fmt, va_list va) { - return gb_fprintf_va(gb_file_get_standard(gbFileStandard_Output), fmt, va); -} - -gb_inline isize gb_printf_err_va(char const *fmt, va_list va) { - return gb_fprintf_va(gb_file_get_standard(gbFileStandard_Error), fmt, va); -} - -gb_inline isize gb_fprintf_va(struct gbFile *f, char const *fmt, va_list va) { - gb_local_persist char buf[4096]; - isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); - gb_file_write(f, buf, len-1); // NOTE(bill): prevent extra whitespace - return len; -} - - -gb_inline char *gb_bprintf_va(char const *fmt, va_list va) { - gb_local_persist char buffer[4096]; - gb_snprintf_va(buffer, gb_size_of(buffer), fmt, va); - return buffer; -} - - -enum { - gbFmt_Minus = GB_BIT(0), - gbFmt_Plus = GB_BIT(1), - gbFmt_Alt = GB_BIT(2), - gbFmt_Space = GB_BIT(3), - gbFmt_Zero = GB_BIT(4), - - gbFmt_Char = GB_BIT(5), - gbFmt_Short = GB_BIT(6), - gbFmt_Int = GB_BIT(7), - gbFmt_Long = GB_BIT(8), - gbFmt_Llong = GB_BIT(9), - gbFmt_Size = GB_BIT(10), - gbFmt_Intptr = GB_BIT(11), - - gbFmt_Unsigned = GB_BIT(12), - gbFmt_Lower = GB_BIT(13), - gbFmt_Upper = GB_BIT(14), - - - gbFmt_Done = GB_BIT(30), - - gbFmt_Ints = gbFmt_Char|gbFmt_Short|gbFmt_Int|gbFmt_Long|gbFmt_Llong|gbFmt_Size|gbFmt_Intptr -}; - -typedef struct { - i32 base; - i32 flags; - i32 width; - i32 precision; -} gbprivFmtInfo; - - -gb_internal isize gb__print_string(char *text, isize max_len, gbprivFmtInfo *info, char const *str) { - // TODO(bill): Get precision and width to work correctly. How does it actually work?! - // TODO(bill): This looks very buggy indeed. - isize res = 0, len; - isize remaining = max_len; - - if (info && info->precision >= 0) { - len = gb_strnlen(str, info->precision); - } else { - len = gb_strlen(str); - } - - if (info && (info->width == 0 || info->flags & gbFmt_Minus)) { - if (info->precision > 0) { - len = info->precision < len ? info->precision : len; - } - - res += gb_strlcpy(text, str, len); - - if (info->width > res) { - isize padding = info->width - len; - char pad = (info->flags & gbFmt_Zero) ? '0' : ' '; - while (padding --> 0 && remaining --> 0) { - *text++ = pad, res++; - } - } - } else { - if (info && (info->width > res)) { - isize padding = info->width - len; - char pad = (info->flags & gbFmt_Zero) ? '0' : ' '; - while (padding --> 0 && remaining --> 0) { - *text++ = pad, res++; - } - } - - res += gb_strlcpy(text, str, len); - } - - - if (info) { - if (info->flags & gbFmt_Upper) { - gb_str_to_upper(text); - } else if (info->flags & gbFmt_Lower) { - gb_str_to_lower(text); - } - } - - return res; -} - -gb_internal isize gb__print_char(char *text, isize max_len, gbprivFmtInfo *info, char arg) { - char str[2] = ""; - str[0] = arg; - return gb__print_string(text, max_len, info, str); -} - - -gb_internal isize gb__print_i64(char *text, isize max_len, gbprivFmtInfo *info, i64 value) { - char num[130]; - gb_i64_to_str(value, num, info ? info->base : 10); - return gb__print_string(text, max_len, info, num); -} - -gb_internal isize gb__print_u64(char *text, isize max_len, gbprivFmtInfo *info, u64 value) { - char num[130]; - gb_u64_to_str(value, num, info ? info->base : 10); - return gb__print_string(text, max_len, info, num); -} - - -gb_internal isize gb__print_f64(char *text, isize max_len, gbprivFmtInfo *info, f64 arg) { - // TODO(bill): Handle exponent notation - isize width, len, remaining = max_len; - char *text_begin = text; - - if (arg) { - u64 value; - if (arg < 0) { - if (remaining > 1) { - *text = '-', remaining--; - } - text++; - arg = -arg; - } else if (info->flags & gbFmt_Minus) { - if (remaining > 1) { - *text = '+', remaining--; - } - text++; - } - - value = cast(u64)arg; - len = gb__print_u64(text, remaining, NULL, value); - text += len; - - if (len >= remaining) { - remaining = gb_min(remaining, 1); - } else { - remaining -= len; - } - arg -= value; - - if (info->precision < 0) { - info->precision = 6; - } - - if ((info->flags & gbFmt_Alt) || info->precision > 0) { - i64 mult = 10; - if (remaining > 1) { - *text = '.', remaining--; - } - text++; - while (info->precision-- > 0) { - value = cast(u64)(arg * mult); - len = gb__print_u64(text, remaining, NULL, value); - text += len; - if (len >= remaining) { - remaining = gb_min(remaining, 1); - } else { - remaining -= len; - } - arg -= cast(f64)value / mult; - mult *= 10; - } - } - } else { - if (remaining > 1) { - *text = '0', remaining--; - } - text++; - if (info->flags & gbFmt_Alt) { - if (remaining > 1) { - *text = '.', remaining--; - } - text++; - } - } - - width = info->width - (text - text_begin); - if (width > 0) { - char fill = (info->flags & gbFmt_Zero) ? '0' : ' '; - char *end = text+remaining-1; - len = (text - text_begin); - - for (len = (text - text_begin); len--; ) { - if ((text_begin+len+width) < end) { - *(text_begin+len+width) = *(text_begin+len); - } - } - - len = width; - text += len; - if (len >= remaining) { - remaining = gb_min(remaining, 1); - } else { - remaining -= len; - } - - while (len--) { - if (text_begin+len < end) { - text_begin[len] = fill; - } - } - } - - return (text - text_begin); -} - - - -gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va_list va) { - char const *text_begin = text; - isize remaining = max_len, res; - - while (*fmt) { - gbprivFmtInfo info = {0}; - isize len = 0; - info.precision = -1; - - while (*fmt && *fmt != '%' && remaining) { - *text++ = *fmt++; - } - - if (*fmt == '%') { - do { - switch (*++fmt) { - case '-': info.flags |= gbFmt_Minus; break; - case '+': info.flags |= gbFmt_Plus; break; - case '#': info.flags |= gbFmt_Alt; break; - case ' ': info.flags |= gbFmt_Space; break; - case '0': info.flags |= gbFmt_Zero; break; - default: info.flags |= gbFmt_Done; break; - } - } while (!(info.flags & gbFmt_Done)); - } - - // NOTE(bill): Optional Width - if (*fmt == '*') { - int width = va_arg(va, int); - if (width < 0) { - info.flags |= gbFmt_Minus; - info.width = -width; - } else { - info.width = width; - } - fmt++; - } else { - info.width = cast(i32)gb_str_to_i64(fmt, cast(char **)&fmt, 10); - } - - // NOTE(bill): Optional Precision - if (*fmt == '.') { - fmt++; - if (*fmt == '*') { - info.precision = va_arg(va, int); - fmt++; - } else { - info.precision = cast(i32)gb_str_to_i64(fmt, cast(char **)&fmt, 10); - } - info.flags &= ~gbFmt_Zero; - } - - - switch (*fmt++) { - case 'h': - if (*fmt == 'h') { // hh => char - info.flags |= gbFmt_Char; - fmt++; - } else { // h => short - info.flags |= gbFmt_Short; - } - break; - - case 'l': - if (*fmt == 'l') { // ll => long long - info.flags |= gbFmt_Llong; - fmt++; - } else { // l => long - info.flags |= gbFmt_Long; - } - break; - - break; - - case 'z': // NOTE(bill): usize - info.flags |= gbFmt_Unsigned; - // fallthrough - case 't': // NOTE(bill): isize - info.flags |= gbFmt_Size; - break; - - default: fmt--; break; - } - - - switch (*fmt) { - case 'u': - info.flags |= gbFmt_Unsigned; - // fallthrough - case 'd': - case 'i': - info.base = 10; - break; - - case 'o': - info.base = 8; - break; - - case 'x': - info.base = 16; - info.flags |= (gbFmt_Unsigned | gbFmt_Lower); - break; - - case 'X': - info.base = 16; - info.flags |= (gbFmt_Unsigned | gbFmt_Upper); - break; - - case 'f': - case 'F': - case 'g': - case 'G': - len = gb__print_f64(text, remaining, &info, va_arg(va, f64)); - break; - - case 'a': - case 'A': - // TODO(bill): - break; - - case 'c': - len = gb__print_char(text, remaining, &info, cast(char)va_arg(va, int)); - break; - - case 's': - len = gb__print_string(text, remaining, &info, va_arg(va, char *)); - break; - - case 'p': - info.base = 16; - info.flags |= (gbFmt_Lower|gbFmt_Unsigned|gbFmt_Alt|gbFmt_Intptr); - break; - - case '%': - len = gb__print_char(text, remaining, &info, '%'); - break; - - default: fmt--; break; - } - - fmt++; - - if (info.base != 0) { - if (info.flags & gbFmt_Unsigned) { - u64 value = 0; - switch (info.flags & gbFmt_Ints) { - case gbFmt_Char: value = cast(u64)cast(u8) va_arg(va, int); break; - case gbFmt_Short: value = cast(u64)cast(u16)va_arg(va, int); break; - case gbFmt_Long: value = cast(u64)va_arg(va, unsigned long); break; - case gbFmt_Llong: value = cast(u64)va_arg(va, unsigned long long); break; - case gbFmt_Size: value = cast(u64)va_arg(va, usize); break; - case gbFmt_Intptr: value = cast(u64)va_arg(va, uintptr); break; - default: value = cast(u64)va_arg(va, unsigned int); break; - } - - len = gb__print_u64(text, remaining, &info, value); - - } else { - i64 value = 0; - switch (info.flags & gbFmt_Ints) { - case gbFmt_Char: value = cast(i64)cast(i8) va_arg(va, int); break; - case gbFmt_Short: value = cast(i64)cast(i16)va_arg(va, int); break; - case gbFmt_Long: value = cast(i64)va_arg(va, long); break; - case gbFmt_Llong: value = cast(i64)va_arg(va, long long); break; - case gbFmt_Size: value = cast(i64)va_arg(va, usize); break; - case gbFmt_Intptr: value = cast(i64)va_arg(va, uintptr); break; - default: value = cast(i64)va_arg(va, int); break; - } - - len = gb__print_i64(text, remaining, &info, value); - } - } - - - text += len; - if (len >= remaining) { - remaining = gb_min(remaining, 1); - } else { - remaining -= len; - } - } - - *text++ = '\0'; - res = (text - text_begin); - return (res >= max_len || res < 0) ? -1 : res; -} - - -//////////////////////////////////////////////////////////////// -// -// DLL Handling -// -// - -#if defined(GB_SYSTEM_WINDOWS) - -gbDllHandle gb_dll_load(char const *filepath) { - return cast(gbDllHandle)LoadLibraryA(filepath); -} -gb_inline void gb_dll_unload (gbDllHandle dll) { FreeLibrary(cast(HMODULE)dll); } -gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) { return cast(gbDllProc)GetProcAddress(cast(HMODULE)dll, proc_name); } - -#else // POSIX - -gbDllHandle gb_dll_load(char const *filepath) { - // TODO(bill): Should this be RTLD_LOCAL? - return cast(gbDllHandle)dlopen(filepath, RTLD_LAZY|RTLD_GLOBAL); -} - -gb_inline void gb_dll_unload (gbDllHandle dll) { dlclose(dll); } -gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) { return cast(gbDllProc)dlsym(dll, proc_name); } - -#endif - - -//////////////////////////////////////////////////////////////// -// -// Time -// -// - -#if defined(GB_COMPILER_MSVC) && !defined(__clang__) - gb_inline u64 gb_rdtsc(void) { return __rdtsc(); } -#elif defined(__i386__) - gb_inline u64 gb_rdtsc(void) { - u64 x; - __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); - return x; - } -#elif defined(__x86_64__) - gb_inline u64 gb_rdtsc(void) { - u32 hi, lo; - __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); - return (cast(u64)lo) | ((cast(u64)hi)<<32); - } -#elif defined(__powerpc__) - gb_inline u64 gb_rdtsc(void) { - u64 result = 0; - u32 upper, lower,tmp; - __asm__ volatile( - "0: \n" - "\tmftbu %0 \n" - "\tmftb %1 \n" - "\tmftbu %2 \n" - "\tcmpw %2,%0 \n" - "\tbne 0b \n" - : "=r"(upper),"=r"(lower),"=r"(tmp) - ); - result = upper; - result = result<<32; - result = result|lower; - - return result; - } -#endif - -#if defined(GB_SYSTEM_WINDOWS) - - gb_inline f64 gb_time_now(void) { - gb_local_persist LARGE_INTEGER win32_perf_count_freq = {0}; - f64 result; - LARGE_INTEGER counter; - if (!win32_perf_count_freq.QuadPart) { - QueryPerformanceFrequency(&win32_perf_count_freq); - GB_ASSERT(win32_perf_count_freq.QuadPart != 0); - } - - QueryPerformanceCounter(&counter); - - result = counter.QuadPart / cast(f64)(win32_perf_count_freq.QuadPart); - return result; - } - - gb_inline u64 gb_utc_time_now(void) { - FILETIME ft; - ULARGE_INTEGER li; - - GetSystemTimeAsFileTime(&ft); - li.LowPart = ft.dwLowDateTime; - li.HighPart = ft.dwHighDateTime; - - return li.QuadPart/10; - } - - gb_inline void gb_sleep_ms(u32 ms) { Sleep(ms); } - -#else - - gb_global f64 gb__timebase = 0.0; - gb_global u64 gb__timestart = 0; - - gb_inline f64 gb_time_now(void) { -#if defined(GB_SYSTEM_OSX) - f64 result; - - if (!gb__timestart) { - mach_timebase_info_data_t tb = {0}; - mach_timebase_info(&tb); - gb__timebase = tb.numer; - gb__timebase /= tb.denom; - gb__timestart = mach_absolute_time(); - } - - // NOTE(bill): mach_absolute_time() returns things in nanoseconds - result = 1.0e-9 * (mach_absolute_time() - gb__timestart) * gb__timebase; - return result; -#else - struct timespec t; - f64 result; - - // IMPORTANT TODO(bill): THIS IS A HACK - clock_gettime(1 /*CLOCK_MONOTONIC*/, &t); - result = t.tv_sec + 1.0e-9 * t.tv_nsec; - return result; -#endif - } - - gb_inline u64 gb_utc_time_now(void) { - struct timespec t; -#if defined(GB_SYSTEM_OSX) - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - t.tv_sec = mts.tv_sec; - t.tv_nsec = mts.tv_nsec; -#else - // IMPORTANT TODO(bill): THIS IS A HACK - clock_gettime(0 /*CLOCK_REALTIME*/, &t); -#endif - return cast(u64)t.tv_sec * 1000000ull + t.tv_nsec/1000 + 11644473600000000ull; - } - - gb_inline void gb_sleep_ms(u32 ms) { - struct timespec req = {cast(time_t)ms/1000, cast(long)((ms%1000)*1000000)}; - struct timespec rem = {0, 0}; - nanosleep(&req, &rem); - } - -#endif - - - -//////////////////////////////////////////////////////////////// -// -// Miscellany -// -// - -gb_global gbAtomic32 gb__random_shared_counter = {0}; - -gb_internal u32 gb__get_noise_from_time(void) { - u32 accum = 0; - f64 start, remaining, end, curr = 0; - u64 interval = 100000ll; - - start = gb_time_now(); - remaining = (interval - cast(u64)(interval*start)%interval) / cast(f64)interval; - end = start + remaining; - - do { - curr = gb_time_now(); - accum += cast(u32)curr; - } while (curr >= end); - return accum; -} - -// NOTE(bill): Partly from http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/ -// But the generation is even more random-er-est - -gb_internal gb_inline u32 gb__permute_qpr(u32 x) { - gb_local_persist u32 const prime = 4294967291; // 2^32 - 5 - if (x >= prime) { - return x; - } else { - u32 residue = cast(u32)(cast(u64) x * x) % prime; - if (x <= prime / 2) { - return residue; - } else { - return prime - residue; - } - } -} - -gb_internal gb_inline u32 gb__permute_with_offset(u32 x, u32 offset) { - return (gb__permute_qpr(x) + offset) ^ 0x5bf03635; -} - - -void gb_random_init(gbRandom *r) { - u64 time, tick; - isize i, j; - u32 x = 0; - r->value = 0; - - r->offsets[0] = gb__get_noise_from_time(); - r->offsets[1] = gb_atomic32_fetch_add(&gb__random_shared_counter, 1); - r->offsets[2] = gb_thread_current_id(); - r->offsets[3] = gb_thread_current_id() * 3 + 1; - time = gb_utc_time_now(); - r->offsets[4] = cast(u32)(time >> 32); - r->offsets[5] = cast(u32)time; - r->offsets[6] = gb__get_noise_from_time(); - tick = gb_rdtsc(); - r->offsets[7] = cast(u32)(tick ^ (tick >> 32)); - - for (j = 0; j < 4; j++) { - for (i = 0; i < gb_count_of(r->offsets); i++) { - r->offsets[i] = x = gb__permute_with_offset(x, r->offsets[i]); - } - } -} - -u32 gb_random_gen_u32(gbRandom *r) { - u32 x = r->value; - u32 carry = 1; - isize i; - for (i = 0; i < gb_count_of(r->offsets); i++) { - x = gb__permute_with_offset(x, r->offsets[i]); - if (carry > 0) { - carry = ++r->offsets[i] ? 0 : 1; - } - } - - r->value = x; - return x; -} - -u32 gb_random_gen_u32_unique(gbRandom *r) { - u32 x = r->value; - isize i; - r->value++; - for (i = 0; i < gb_count_of(r->offsets); i++) { - x = gb__permute_with_offset(x, r->offsets[i]); - } - - return x; -} - -u64 gb_random_gen_u64(gbRandom *r) { - return ((cast(u64)gb_random_gen_u32(r)) << 32) | gb_random_gen_u32(r); -} - - -isize gb_random_gen_isize(gbRandom *r) { - u64 u = gb_random_gen_u64(r); - return *cast(isize *)&u; -} - - - - -i64 gb_random_range_i64(gbRandom *r, i64 lower_inc, i64 higher_inc) { - u64 u = gb_random_gen_u64(r); - i64 i = *cast(i64 *)&u; - i64 diff = higher_inc-lower_inc+1; - i %= diff; - i += lower_inc; - return i; -} - -isize gb_random_range_isize(gbRandom *r, isize lower_inc, isize higher_inc) { - u64 u = gb_random_gen_u64(r); - isize i = *cast(isize *)&u; - isize diff = higher_inc-lower_inc+1; - i %= diff; - i += lower_inc; - return i; -} - -// NOTE(bill): Semi-cc'ed from gb_math to remove need for fmod and math.h -f64 gb__copy_sign64(f64 x, f64 y) { - i64 ix, iy; - ix = *(i64 *)&x; - iy = *(i64 *)&y; - - ix &= 0x7fffffffffffffff; - ix |= iy & 0x8000000000000000; - return *cast(f64 *)&ix; -} - -f64 gb__floor64 (f64 x) { return cast(f64)((x >= 0.0) ? cast(i64)x : cast(i64)(x-0.9999999999999999)); } -f64 gb__ceil64 (f64 x) { return cast(f64)((x < 0) ? cast(i64)x : (cast(i64)x)+1); } -f64 gb__round64 (f64 x) { return cast(f64)((x >= 0.0) ? gb__floor64(x + 0.5) : gb__ceil64(x - 0.5)); } -f64 gb__remainder64(f64 x, f64 y) { return x - (gb__round64(x/y)*y); } -f64 gb__abs64 (f64 x) { return x < 0 ? -x : x; } -f64 gb__sign64 (f64 x) { return x < 0 ? -1.0 : +1.0; } - -f64 gb__mod64(f64 x, f64 y) { - f64 result; - y = gb__abs64(y); - result = gb__remainder64(gb__abs64(x), y); - if (gb__sign64(result)) result += y; - return gb__copy_sign64(result, x); -} - - -f64 gb_random_range_f64(gbRandom *r, f64 lower_inc, f64 higher_inc) { - u64 u = gb_random_gen_u64(r); - f64 f = *cast(f64 *)&u; - f64 diff = higher_inc-lower_inc+1.0; - f = gb__mod64(f, diff); - f += lower_inc; - return f; -} - - - -#if defined(GB_SYSTEM_WINDOWS) -gb_inline void gb_exit(u32 code) { ExitProcess(code); } -#else -gb_inline void gb_exit(u32 code) { exit(code); } -#endif - -gb_inline void gb_yield(void) { -#if defined(GB_SYSTEM_WINDOWS) - Sleep(0); -#else - sched_yield(); -#endif -} - -gb_inline void gb_set_env(char const *name, char const *value) { -#if defined(GB_SYSTEM_WINDOWS) - // TODO(bill): Should this be a Wide version? - SetEnvironmentVariableA(name, value); -#else - setenv(name, value, 1); -#endif -} - -gb_inline void gb_unset_env(char const *name) { -#if defined(GB_SYSTEM_WINDOWS) - // TODO(bill): Should this be a Wide version? - SetEnvironmentVariableA(name, NULL); -#else - unsetenv(name); -#endif -} - - -gb_inline u16 gb_endian_swap16(u16 i) { - return (i>>8) | (i<<8); -} - -gb_inline u32 gb_endian_swap32(u32 i) { - return (i>>24) |(i<<24) | - ((i&0x00ff0000u)>>8) | ((i&0x0000ff00u)<<8); -} - -gb_inline u64 gb_endian_swap64(u64 i) { - return (i>>56) | (i<<56) | - ((i&0x00ff000000000000ull)>>40) | ((i&0x000000000000ff00ull)<<40) | - ((i&0x0000ff0000000000ull)>>24) | ((i&0x0000000000ff0000ull)<<24) | - ((i&0x000000ff00000000ull)>>8) | ((i&0x00000000ff000000ull)<<8); -} - - -gb_inline isize gb_count_set_bits(u64 mask) { - isize count = 0; - while (mask) { - count += (mask & 1); - mask >>= 1; - } - return count; -} - - - - - - -//////////////////////////////////////////////////////////////// -// -// Platform -// -// - -#if defined(GB_PLATFORM) - -gb_inline void gb_key_state_update(gbKeyState *s, b32 is_down) { - b32 was_down = (*s & gbKeyState_Down) != 0; - is_down = is_down != 0; // NOTE(bill): Make sure it's a boolean - GB_MASK_SET(*s, is_down, gbKeyState_Down); - GB_MASK_SET(*s, !was_down && is_down, gbKeyState_Pressed); - GB_MASK_SET(*s, was_down && !is_down, gbKeyState_Released); -} - -#if defined(GB_SYSTEM_WINDOWS) - -#ifndef ERROR_DEVICE_NOT_CONNECTED -#define ERROR_DEVICE_NOT_CONNECTED 1167 -#endif - -GB_XINPUT_GET_STATE(gbXInputGetState_Stub) { - gb_unused(dwUserIndex); gb_unused(pState); - return ERROR_DEVICE_NOT_CONNECTED; -} -GB_XINPUT_SET_STATE(gbXInputSetState_Stub) { - gb_unused(dwUserIndex); gb_unused(pVibration); - return ERROR_DEVICE_NOT_CONNECTED; -} - - -gb_internal gb_inline f32 gb__process_xinput_stick_value(i16 value, i16 dead_zone_threshold) { - f32 result = 0; - - if (value < -dead_zone_threshold) { - result = cast(f32) (value + dead_zone_threshold) / (32768.0f - dead_zone_threshold); - } else if (value > dead_zone_threshold) { - result = cast(f32) (value - dead_zone_threshold) / (32767.0f - dead_zone_threshold); - } - - return result; -} - -gb_internal void gb__platform_resize_dib_section(gbPlatform *p, i32 width, i32 height) { - if ((p->renderer_type == gbRenderer_Software) && - !(p->window_width == width && p->window_height == height)) { - BITMAPINFO bmi = {0}; - - if (width == 0 || height == 0) { - return; - } - - p->window_width = width; - p->window_height = height; - - // TODO(bill): Is this slow to get the desktop mode everytime? - p->sw_framebuffer.bits_per_pixel = gb_video_mode_get_desktop().bits_per_pixel; - p->sw_framebuffer.pitch = (p->sw_framebuffer.bits_per_pixel * width / 8); - - bmi.bmiHeader.biSize = gb_size_of(bmi.bmiHeader); - bmi.bmiHeader.biWidth = width; - bmi.bmiHeader.biHeight = height; // NOTE(bill): -ve is top-down, +ve is bottom-up - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = cast(u16)p->sw_framebuffer.bits_per_pixel; - bmi.bmiHeader.biCompression = 0 /*BI_RGB*/; - - p->sw_framebuffer.win32_bmi = bmi; - - - if (p->sw_framebuffer.memory) { - gb_vm_free(gb_virtual_memory(p->sw_framebuffer.memory, p->sw_framebuffer.memory_size)); - } - - { - isize memory_size = p->sw_framebuffer.pitch * height; - gbVirtualMemory vm = gb_vm_alloc(0, memory_size); - p->sw_framebuffer.memory = vm.data; - p->sw_framebuffer.memory_size = vm.size; - } - } -} - - -gb_internal gbKeyType gb__win32_from_vk(unsigned int key) { - // NOTE(bill): Letters and numbers are defined the same for VK_* and GB_* - if (key >= 'A' && key < 'Z') return cast(gbKeyType)key; - if (key >= '0' && key < '9') return cast(gbKeyType)key; - switch (key) { - case VK_ESCAPE: return gbKey_Escape; - - case VK_LCONTROL: return gbKey_Lcontrol; - case VK_LSHIFT: return gbKey_Lshift; - case VK_LMENU: return gbKey_Lalt; - case VK_LWIN: return gbKey_Lsystem; - case VK_RCONTROL: return gbKey_Rcontrol; - case VK_RSHIFT: return gbKey_Rshift; - case VK_RMENU: return gbKey_Ralt; - case VK_RWIN: return gbKey_Rsystem; - case VK_MENU: return gbKey_Menu; - - case VK_OEM_4: return gbKey_Lbracket; - case VK_OEM_6: return gbKey_Rbracket; - case VK_OEM_1: return gbKey_Semicolon; - case VK_OEM_COMMA: return gbKey_Comma; - case VK_OEM_PERIOD: return gbKey_Period; - case VK_OEM_7: return gbKey_Quote; - case VK_OEM_2: return gbKey_Slash; - case VK_OEM_5: return gbKey_Backslash; - case VK_OEM_3: return gbKey_Grave; - case VK_OEM_PLUS: return gbKey_Equals; - case VK_OEM_MINUS: return gbKey_Minus; - - case VK_SPACE: return gbKey_Space; - case VK_RETURN: return gbKey_Return; - case VK_BACK: return gbKey_Backspace; - case VK_TAB: return gbKey_Tab; - - case VK_PRIOR: return gbKey_Pageup; - case VK_NEXT: return gbKey_Pagedown; - case VK_END: return gbKey_End; - case VK_HOME: return gbKey_Home; - case VK_INSERT: return gbKey_Insert; - case VK_DELETE: return gbKey_Delete; - - case VK_ADD: return gbKey_Plus; - case VK_SUBTRACT: return gbKey_Subtract; - case VK_MULTIPLY: return gbKey_Multiply; - case VK_DIVIDE: return gbKey_Divide; - - case VK_LEFT: return gbKey_Left; - case VK_RIGHT: return gbKey_Right; - case VK_UP: return gbKey_Up; - case VK_DOWN: return gbKey_Down; - - case VK_NUMPAD0: return gbKey_Numpad0; - case VK_NUMPAD1: return gbKey_Numpad1; - case VK_NUMPAD2: return gbKey_Numpad2; - case VK_NUMPAD3: return gbKey_Numpad3; - case VK_NUMPAD4: return gbKey_Numpad4; - case VK_NUMPAD5: return gbKey_Numpad5; - case VK_NUMPAD6: return gbKey_Numpad6; - case VK_NUMPAD7: return gbKey_Numpad7; - case VK_NUMPAD8: return gbKey_Numpad8; - case VK_NUMPAD9: return gbKey_Numpad9; - case VK_SEPARATOR: return gbKey_NumpadEnter; - case VK_DECIMAL: return gbKey_NumpadDot; - - case VK_F1: return gbKey_F1; - case VK_F2: return gbKey_F2; - case VK_F3: return gbKey_F3; - case VK_F4: return gbKey_F4; - case VK_F5: return gbKey_F5; - case VK_F6: return gbKey_F6; - case VK_F7: return gbKey_F7; - case VK_F8: return gbKey_F8; - case VK_F9: return gbKey_F9; - case VK_F10: return gbKey_F10; - case VK_F11: return gbKey_F11; - case VK_F12: return gbKey_F12; - case VK_F13: return gbKey_F13; - case VK_F14: return gbKey_F14; - case VK_F15: return gbKey_F15; - - case VK_PAUSE: return gbKey_Pause; - } - return gbKey_Unknown; -} -LRESULT CALLBACK gb__win32_window_callback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { - // NOTE(bill): Silly callbacks - gbPlatform *platform = cast(gbPlatform *)GetWindowLongPtrW(hWnd, GWLP_USERDATA); - b32 window_has_focus = (platform != NULL) && platform->window_has_focus; - - if (msg == WM_CREATE) { // NOTE(bill): Doesn't need the platform - // NOTE(bill): https://msdn.microsoft.com/en-us/library/windows/desktop/ms645536(v=vs.85).aspx - RAWINPUTDEVICE rid[2] = {0}; - - // NOTE(bill): Keyboard - rid[0].usUsagePage = 0x01; - rid[0].usUsage = 0x06; - rid[0].dwFlags = 0x00000030/*RIDEV_NOLEGACY*/; // NOTE(bill): Do not generate legacy messages such as WM_KEYDOWN - rid[0].hwndTarget = hWnd; - - // NOTE(bill): Mouse - rid[1].usUsagePage = 0x01; - rid[1].usUsage = 0x02; - rid[1].dwFlags = 0; // NOTE(bill): adds HID mouse and also allows legacy mouse messages to allow for window movement etc. - rid[1].hwndTarget = hWnd; - - if (RegisterRawInputDevices(rid, gb_count_of(rid), gb_size_of(rid[0])) == false) { - DWORD err = GetLastError(); - GB_PANIC("Failed to initialize raw input device for win32." - "Err: %u", err); - } - } - - if (!platform) { - return DefWindowProcW(hWnd, msg, wParam, lParam); - } - - switch (msg) { - case WM_CLOSE: - case WM_DESTROY: - platform->window_is_closed = true; - return 0; - - case WM_QUIT: { - platform->quit_requested = true; - } break; - - case WM_UNICHAR: { - if (window_has_focus) { - if (wParam == '\r') { - wParam = '\n'; - } - // TODO(bill): Does this need to be thread-safe? - platform->char_buffer[platform->char_buffer_count++] = cast(Rune)wParam; - } - } break; - - - case WM_INPUT: { - RAWINPUT raw = {0}; - unsigned int size = gb_size_of(RAWINPUT); - - if (!GetRawInputData(cast(HRAWINPUT)lParam, RID_INPUT, &raw, &size, gb_size_of(RAWINPUTHEADER))) { - return 0; - } - switch (raw.header.dwType) { - case RIM_TYPEKEYBOARD: { - // NOTE(bill): Many thanks to https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ - // for the - RAWKEYBOARD *raw_kb = &raw.data.keyboard; - unsigned int vk = raw_kb->VKey; - unsigned int scan_code = raw_kb->MakeCode; - unsigned int flags = raw_kb->Flags; - // NOTE(bill): e0 and e1 are escape sequences used for certain special keys, such as PRINT and PAUSE/BREAK. - // NOTE(bill): http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html - b32 is_e0 = (flags & RI_KEY_E0) != 0; - b32 is_e1 = (flags & RI_KEY_E1) != 0; - b32 is_up = (flags & RI_KEY_BREAK) != 0; - b32 is_down = !is_up; - - // TODO(bill): Should I handle scan codes? - - if (vk == 255) { - // NOTE(bill): Discard "fake keys" - return 0; - } else if (vk == VK_SHIFT) { - // NOTE(bill): Correct left/right shift - vk = MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX); - } else if (vk == VK_NUMLOCK) { - // NOTE(bill): Correct PAUSE/BREAK and NUM LOCK and set the extended bit - scan_code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC) | 0x100; - } - - if (is_e1) { - // NOTE(bill): Escaped sequences, turn vk into the correct scan code - // except for VK_PAUSE (it's a bug) - if (vk == VK_PAUSE) { - scan_code = 0x45; - } else { - scan_code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); - } - } - - switch (vk) { - case VK_CONTROL: vk = (is_e0) ? VK_RCONTROL : VK_LCONTROL; break; - case VK_MENU: vk = (is_e0) ? VK_RMENU : VK_LMENU; break; - - case VK_RETURN: if (is_e0) vk = VK_SEPARATOR; break; // NOTE(bill): Numpad return - case VK_DELETE: if (!is_e0) vk = VK_DECIMAL; break; // NOTE(bill): Numpad dot - case VK_INSERT: if (!is_e0) vk = VK_NUMPAD0; break; - case VK_HOME: if (!is_e0) vk = VK_NUMPAD7; break; - case VK_END: if (!is_e0) vk = VK_NUMPAD1; break; - case VK_PRIOR: if (!is_e0) vk = VK_NUMPAD9; break; - case VK_NEXT: if (!is_e0) vk = VK_NUMPAD3; break; - - // NOTE(bill): The standard arrow keys will always have their e0 bit set, but the - // corresponding keys on the NUMPAD will not. - case VK_LEFT: if (!is_e0) vk = VK_NUMPAD4; break; - case VK_RIGHT: if (!is_e0) vk = VK_NUMPAD6; break; - case VK_UP: if (!is_e0) vk = VK_NUMPAD8; break; - case VK_DOWN: if (!is_e0) vk = VK_NUMPAD2; break; - - // NUMPAD 5 doesn't have its e0 bit set - case VK_CLEAR: if (!is_e0) vk = VK_NUMPAD5; break; - } - - // NOTE(bill): Set appropriate key state flags - gb_key_state_update(&platform->keys[gb__win32_from_vk(vk)], is_down); - - } break; - case RIM_TYPEMOUSE: { - RAWMOUSE *raw_mouse = &raw.data.mouse; - u16 flags = raw_mouse->usButtonFlags; - long dx = +raw_mouse->lLastX; - long dy = -raw_mouse->lLastY; - - if (flags & RI_MOUSE_WHEEL) { - platform->mouse_wheel_delta = cast(i16)raw_mouse->usButtonData; - } - - platform->mouse_raw_dx = dx; - platform->mouse_raw_dy = dy; - } break; - } - } break; - - default: break; - } - - return DefWindowProcW(hWnd, msg, wParam, lParam); -} - - -typedef void *wglCreateContextAttribsARB_Proc(void *hDC, void *hshareContext, int const *attribList); - - -b32 gb__platform_init(gbPlatform *p, char const *window_title, gbVideoMode mode, gbRendererType type, u32 window_flags) { - WNDCLASSEXW wc = {gb_size_of(WNDCLASSEXW)}; - DWORD ex_style = 0, style = 0; - RECT wr; - u16 title_buffer[256] = {0}; // TODO(bill): gb_local_persist this? - - wc.style = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC - wc.lpfnWndProc = gb__win32_window_callback; - wc.hbrBackground = cast(HBRUSH)GetStockObject(0/*WHITE_BRUSH*/); - wc.lpszMenuName = NULL; - wc.lpszClassName = L"gb-win32-wndclass"; // TODO(bill): Is this enough? - wc.hInstance = GetModuleHandleW(NULL); - - if (RegisterClassExW(&wc) == 0) { - MessageBoxW(NULL, L"Failed to register the window class", L"ERROR", MB_OK | MB_ICONEXCLAMATION); - return false; - } - - if ((window_flags & gbWindow_Fullscreen) && !(window_flags & gbWindow_Borderless)) { - DEVMODEW screen_settings = {gb_size_of(DEVMODEW)}; - screen_settings.dmPelsWidth = mode.width; - screen_settings.dmPelsHeight = mode.height; - screen_settings.dmBitsPerPel = mode.bits_per_pixel; - screen_settings.dmFields = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; - - if (ChangeDisplaySettingsW(&screen_settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { - if (MessageBoxW(NULL, L"The requested fullscreen mode is not supported by\n" - L"your video card. Use windowed mode instead?", - L"", - MB_YESNO|MB_ICONEXCLAMATION) == IDYES) { - window_flags &= ~gbWindow_Fullscreen; - } else { - mode = gb_video_mode_get_desktop(); - screen_settings.dmPelsWidth = mode.width; - screen_settings.dmPelsHeight = mode.height; - screen_settings.dmBitsPerPel = mode.bits_per_pixel; - ChangeDisplaySettingsW(&screen_settings, CDS_FULLSCREEN); - } - } - } - - - // ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - // style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; - - style |= WS_VISIBLE; - - if (window_flags & gbWindow_Hidden) style &= ~WS_VISIBLE; - if (window_flags & gbWindow_Resizable) style |= WS_THICKFRAME | WS_MAXIMIZEBOX; - if (window_flags & gbWindow_Maximized) style |= WS_MAXIMIZE; - if (window_flags & gbWindow_Minimized) style |= WS_MINIMIZE; - - // NOTE(bill): Completely ignore the given mode and just change it - if (window_flags & gbWindow_FullscreenDesktop) { - mode = gb_video_mode_get_desktop(); - } - - if ((window_flags & gbWindow_Fullscreen) || (window_flags & gbWindow_Borderless)) { - style |= WS_POPUP; - } else { - style |= WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; - } - - - wr.left = 0; - wr.top = 0; - wr.right = mode.width; - wr.bottom = mode.height; - AdjustWindowRect(&wr, style, false); - - p->window_flags = window_flags; - p->window_handle = CreateWindowExW(ex_style, - wc.lpszClassName, - cast(wchar_t const *)gb_utf8_to_ucs2(title_buffer, gb_size_of(title_buffer), window_title), - style, - CW_USEDEFAULT, CW_USEDEFAULT, - wr.right - wr.left, wr.bottom - wr.top, - 0, 0, - GetModuleHandleW(NULL), - NULL); - - if (!p->window_handle) { - MessageBoxW(NULL, L"Window creation failed", L"Error", MB_OK|MB_ICONEXCLAMATION); - return false; - } - - p->win32_dc = GetDC(cast(HWND)p->window_handle); - - p->renderer_type = type; - switch (p->renderer_type) { - case gbRenderer_Opengl: { - wglCreateContextAttribsARB_Proc *wglCreateContextAttribsARB; - i32 attribs[8] = {0}; - isize c = 0; - - PIXELFORMATDESCRIPTOR pfd = {gb_size_of(PIXELFORMATDESCRIPTOR)}; - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 32; - pfd.cAlphaBits = 8; - pfd.cDepthBits = 24; - pfd.cStencilBits = 8; - pfd.iLayerType = PFD_MAIN_PLANE; - - SetPixelFormat(cast(HDC)p->win32_dc, ChoosePixelFormat(cast(HDC)p->win32_dc, &pfd), NULL); - p->opengl.context = cast(void *)wglCreateContext(cast(HDC)p->win32_dc); - wglMakeCurrent(cast(HDC)p->win32_dc, cast(HGLRC)p->opengl.context); - - if (p->opengl.major > 0) { - attribs[c++] = 0x2091; // WGL_CONTEXT_MAJOR_VERSION_ARB - attribs[c++] = gb_max(p->opengl.major, 1); - } - if (p->opengl.major > 0 && p->opengl.minor >= 0) { - attribs[c++] = 0x2092; // WGL_CONTEXT_MINOR_VERSION_ARB - attribs[c++] = gb_max(p->opengl.minor, 0); - } - - if (p->opengl.core) { - attribs[c++] = 0x9126; // WGL_CONTEXT_PROFILE_MASK_ARB - attribs[c++] = 0x0001; // WGL_CONTEXT_CORE_PROFILE_BIT_ARB - } else if (p->opengl.compatible) { - attribs[c++] = 0x9126; // WGL_CONTEXT_PROFILE_MASK_ARB - attribs[c++] = 0x0002; // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB - } - attribs[c++] = 0; // NOTE(bill): tells the proc that this is the end of attribs - - wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_Proc *)wglGetProcAddress("wglCreateContextAttribsARB"); - if (wglCreateContextAttribsARB) { - HGLRC rc = cast(HGLRC)wglCreateContextAttribsARB(p->win32_dc, 0, attribs); - if (rc && wglMakeCurrent(cast(HDC)p->win32_dc, rc)) { - p->opengl.context = rc; - } else { - // TODO(bill): Handle errors from GetLastError - // ERROR_INVALID_VERSION_ARB 0x2095 - // ERROR_INVALID_PROFILE_ARB 0x2096 - } - } - - } break; - - case gbRenderer_Software: - gb__platform_resize_dib_section(p, mode.width, mode.height); - break; - - default: - GB_PANIC("Unknown window type"); - break; - } - - SetForegroundWindow(cast(HWND)p->window_handle); - SetFocus(cast(HWND)p->window_handle); - SetWindowLongPtrW(cast(HWND)p->window_handle, GWLP_USERDATA, cast(LONG_PTR)p); - - p->window_width = mode.width; - p->window_height = mode.height; - - if (p->renderer_type == gbRenderer_Opengl) { - p->opengl.dll_handle = gb_dll_load("opengl32.dll"); - } - - { // Load XInput - // TODO(bill): What other dlls should I look for? - gbDllHandle xinput_library = gb_dll_load("xinput1_4.dll"); - p->xinput.get_state = gbXInputGetState_Stub; - p->xinput.set_state = gbXInputSetState_Stub; - - if (!xinput_library) xinput_library = gb_dll_load("xinput9_1_0.dll"); - if (!xinput_library) xinput_library = gb_dll_load("xinput1_3.dll"); - if (!xinput_library) { - // TODO(bill): Proper Diagnostic - gb_printf_err("XInput could not be loaded. Controllers will not work!\n"); - } else { - p->xinput.get_state = cast(gbXInputGetStateProc *)gb_dll_proc_address(xinput_library, "XInputGetState"); - p->xinput.set_state = cast(gbXInputSetStateProc *)gb_dll_proc_address(xinput_library, "XInputSetState"); - } - } - - // Init keys - gb_zero_array(p->keys, gb_count_of(p->keys)); - - p->is_initialized = true; - return true; -} - -gb_inline b32 gb_platform_init_with_software(gbPlatform *p, char const *window_title, - i32 width, i32 height, u32 window_flags) { - gbVideoMode mode; - mode.width = width; - mode.height = height; - mode.bits_per_pixel = 32; - return gb__platform_init(p, window_title, mode, gbRenderer_Software, window_flags); -} - -gb_inline b32 gb_platform_init_with_opengl(gbPlatform *p, char const *window_title, - i32 width, i32 height, u32 window_flags, i32 major, i32 minor, b32 core, b32 compatible) { - gbVideoMode mode; - mode.width = width; - mode.height = height; - mode.bits_per_pixel = 32; - p->opengl.major = major; - p->opengl.minor = minor; - p->opengl.core = cast(b16)core; - p->opengl.compatible = cast(b16)compatible; - return gb__platform_init(p, window_title, mode, gbRenderer_Opengl, window_flags); -} - -#ifndef _XINPUT_H_ -typedef struct _XINPUT_GAMEPAD { - u16 wButtons; - u8 bLeftTrigger; - u8 bRightTrigger; - u16 sThumbLX; - u16 sThumbLY; - u16 sThumbRX; - u16 sThumbRY; -} XINPUT_GAMEPAD; - -typedef struct _XINPUT_STATE { - DWORD dwPacketNumber; - XINPUT_GAMEPAD Gamepad; -} XINPUT_STATE; - -typedef struct _XINPUT_VIBRATION { - u16 wLeftMotorSpeed; - u16 wRightMotorSpeed; -} XINPUT_VIBRATION; - -#define XINPUT_GAMEPAD_DPAD_UP 0x00000001 -#define XINPUT_GAMEPAD_DPAD_DOWN 0x00000002 -#define XINPUT_GAMEPAD_DPAD_LEFT 0x00000004 -#define XINPUT_GAMEPAD_DPAD_RIGHT 0x00000008 -#define XINPUT_GAMEPAD_START 0x00000010 -#define XINPUT_GAMEPAD_BACK 0x00000020 -#define XINPUT_GAMEPAD_LEFT_THUMB 0x00000040 -#define XINPUT_GAMEPAD_RIGHT_THUMB 0x00000080 -#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 -#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 -#define XINPUT_GAMEPAD_A 0x1000 -#define XINPUT_GAMEPAD_B 0x2000 -#define XINPUT_GAMEPAD_X 0x4000 -#define XINPUT_GAMEPAD_Y 0x8000 -#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 -#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 -#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30 -#endif - -#ifndef XUSER_MAX_COUNT -#define XUSER_MAX_COUNT 4 -#endif - -void gb_platform_update(gbPlatform *p) { - isize i; - - { // NOTE(bill): Set window state - // TODO(bill): Should this be moved to gb__win32_window_callback ? - RECT window_rect; - i32 x, y, w, h; - - GetClientRect(cast(HWND)p->window_handle, &window_rect); - x = window_rect.left; - y = window_rect.top; - w = window_rect.right - window_rect.left; - h = window_rect.bottom - window_rect.top; - - if ((p->window_width != w) || (p->window_height != h)) { - if (p->renderer_type == gbRenderer_Software) { - gb__platform_resize_dib_section(p, w, h); - } - } - - - p->window_x = x; - p->window_y = y; - p->window_width = w; - p->window_height = h; - GB_MASK_SET(p->window_flags, IsIconic(cast(HWND)p->window_handle) != 0, gbWindow_Minimized); - - p->window_has_focus = GetFocus() == cast(HWND)p->window_handle; - } - - { // NOTE(bill): Set mouse position - POINT mouse_pos; - DWORD win_button_id[gbMouseButton_Count] = { - VK_LBUTTON, - VK_MBUTTON, - VK_RBUTTON, - VK_XBUTTON1, - VK_XBUTTON2, - }; - - // NOTE(bill): This needs to be GetAsyncKeyState as RAWMOUSE doesn't aways work for some odd reason - // TODO(bill): Try and get RAWMOUSE to work for key presses - for (i = 0; i < gbMouseButton_Count; i++) { - gb_key_state_update(p->mouse_buttons+i, GetAsyncKeyState(win_button_id[i]) < 0); - } - - GetCursorPos(&mouse_pos); - ScreenToClient(cast(HWND)p->window_handle, &mouse_pos); - { - i32 x = mouse_pos.x; - i32 y = p->window_height-1 - mouse_pos.y; - p->mouse_dx = x - p->mouse_x; - p->mouse_dy = y - p->mouse_y; - p->mouse_x = x; - p->mouse_y = y; - } - - if (p->mouse_clip) { - b32 update = false; - i32 x = p->mouse_x; - i32 y = p->mouse_y; - if (p->mouse_x < 0) { - x = 0; - update = true; - } else if (p->mouse_y > p->window_height-1) { - y = p->window_height-1; - update = true; - } - - if (p->mouse_y < 0) { - y = 0; - update = true; - } else if (p->mouse_x > p->window_width-1) { - x = p->window_width-1; - update = true; - } - - if (update) { - gb_platform_set_mouse_position(p, x, y); - } - } - - - } - - - // NOTE(bill): Set Key/Button states - if (p->window_has_focus) { - p->char_buffer_count = 0; // TODO(bill): Reset buffer count here or else where? - - // NOTE(bill): Need to update as the keys only get updates on events - for (i = 0; i < gbKey_Count; i++) { - b32 is_down = (p->keys[i] & gbKeyState_Down) != 0; - gb_key_state_update(&p->keys[i], is_down); - } - - p->key_modifiers.control = p->keys[gbKey_Lcontrol] | p->keys[gbKey_Rcontrol]; - p->key_modifiers.alt = p->keys[gbKey_Lalt] | p->keys[gbKey_Ralt]; - p->key_modifiers.shift = p->keys[gbKey_Lshift] | p->keys[gbKey_Rshift]; - - } - - { // NOTE(bill): Set Controller states - isize max_controller_count = XUSER_MAX_COUNT; - if (max_controller_count > gb_count_of(p->game_controllers)) { - max_controller_count = gb_count_of(p->game_controllers); - } - - for (i = 0; i < max_controller_count; i++) { - gbGameController *controller = &p->game_controllers[i]; - XINPUT_STATE controller_state = {0}; - if (p->xinput.get_state(cast(DWORD)i, &controller_state) != 0) { - // NOTE(bill): The controller is not available - controller->is_connected = false; - } else { - // NOTE(bill): This controller is plugged in - // TODO(bill): See if ControllerState.dwPacketNumber increments too rapidly - XINPUT_GAMEPAD *pad = &controller_state.Gamepad; - - controller->is_connected = true; - - // TODO(bill): This is a square deadzone, check XInput to verify that the deadzone is "round" and do round deadzone processing. - controller->axes[gbControllerAxis_LeftX] = gb__process_xinput_stick_value(pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); - controller->axes[gbControllerAxis_LeftY] = gb__process_xinput_stick_value(pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); - controller->axes[gbControllerAxis_RightX] = gb__process_xinput_stick_value(pad->sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); - controller->axes[gbControllerAxis_RightY] = gb__process_xinput_stick_value(pad->sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); - - controller->axes[gbControllerAxis_LeftTrigger] = cast(f32)pad->bLeftTrigger / 255.0f; - controller->axes[gbControllerAxis_RightTrigger] = cast(f32)pad->bRightTrigger / 255.0f; - - - if ((controller->axes[gbControllerAxis_LeftX] != 0.0f) || - (controller->axes[gbControllerAxis_LeftY] != 0.0f)) { - controller->is_analog = true; - } - - #define GB__PROCESS_DIGITAL_BUTTON(button_type, xinput_button) \ - gb_key_state_update(&controller->buttons[button_type], (pad->wButtons & xinput_button) == xinput_button) - - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_A, XINPUT_GAMEPAD_A); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_B, XINPUT_GAMEPAD_B); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_X, XINPUT_GAMEPAD_X); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Y, XINPUT_GAMEPAD_Y); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_LeftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_RightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Start, XINPUT_GAMEPAD_START); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Back, XINPUT_GAMEPAD_BACK); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Left, XINPUT_GAMEPAD_DPAD_LEFT); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Right, XINPUT_GAMEPAD_DPAD_RIGHT); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Down, XINPUT_GAMEPAD_DPAD_DOWN); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Up, XINPUT_GAMEPAD_DPAD_UP); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_LeftThumb, XINPUT_GAMEPAD_LEFT_THUMB); - GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_RightThumb, XINPUT_GAMEPAD_RIGHT_THUMB); - #undef GB__PROCESS_DIGITAL_BUTTON - } - } - } - - { // NOTE(bill): Process pending messages - MSG message; - for (;;) { - BOOL is_okay = PeekMessageW(&message, 0, 0, 0, PM_REMOVE); - if (!is_okay) break; - - switch (message.message) { - case WM_QUIT: - p->quit_requested = true; - break; - - default: - TranslateMessage(&message); - DispatchMessageW(&message); - break; - } - } - } -} - -void gb_platform_display(gbPlatform *p) { - if (p->renderer_type == gbRenderer_Opengl) { - SwapBuffers(cast(HDC)p->win32_dc); - } else if (p->renderer_type == gbRenderer_Software) { - StretchDIBits(cast(HDC)p->win32_dc, - 0, 0, p->window_width, p->window_height, - 0, 0, p->window_width, p->window_height, - p->sw_framebuffer.memory, - &p->sw_framebuffer.win32_bmi, - DIB_RGB_COLORS, SRCCOPY); - } else { - GB_PANIC("Invalid window rendering type"); - } - - { - f64 prev_time = p->curr_time; - f64 curr_time = gb_time_now(); - p->dt_for_frame = curr_time - prev_time; - p->curr_time = curr_time; - } -} - - -void gb_platform_destroy(gbPlatform *p) { - if (p->renderer_type == gbRenderer_Opengl) { - wglDeleteContext(cast(HGLRC)p->opengl.context); - } else if (p->renderer_type == gbRenderer_Software) { - gb_vm_free(gb_virtual_memory(p->sw_framebuffer.memory, p->sw_framebuffer.memory_size)); - } - - DestroyWindow(cast(HWND)p->window_handle); -} - -void gb_platform_show_cursor(gbPlatform *p, b32 show) { - gb_unused(p); - ShowCursor(show); -} - -void gb_platform_set_mouse_position(gbPlatform *p, i32 x, i32 y) { - POINT point; - point.x = cast(LONG)x; - point.y = cast(LONG)(p->window_height-1 - y); - ClientToScreen(cast(HWND)p->window_handle, &point); - SetCursorPos(point.x, point.y); - - p->mouse_x = point.x; - p->mouse_y = p->window_height-1 - point.y; -} - - - -void gb_platform_set_controller_vibration(gbPlatform *p, isize index, f32 left_motor, f32 right_motor) { - if (gb_is_between(index, 0, GB_MAX_GAME_CONTROLLER_COUNT-1)) { - XINPUT_VIBRATION vibration = {0}; - left_motor = gb_clamp01(left_motor); - right_motor = gb_clamp01(right_motor); - vibration.wLeftMotorSpeed = cast(WORD)(65535 * left_motor); - vibration.wRightMotorSpeed = cast(WORD)(65535 * right_motor); - - p->xinput.set_state(cast(DWORD)index, &vibration); - } -} - - -void gb_platform_set_window_position(gbPlatform *p, i32 x, i32 y) { - RECT rect; - i32 width, height; - - GetClientRect(cast(HWND)p->window_handle, &rect); - width = rect.right - rect.left; - height = rect.bottom - rect.top; - MoveWindow(cast(HWND)p->window_handle, x, y, width, height, false); -} - -void gb_platform_set_window_title(gbPlatform *p, char const *title, ...) { - u16 buffer[256] = {0}; - char str[512] = {0}; - va_list va; - va_start(va, title); - gb_snprintf_va(str, gb_size_of(str), title, va); - va_end(va); - - if (str[0] != '\0') { - SetWindowTextW(cast(HWND)p->window_handle, cast(wchar_t const *)gb_utf8_to_ucs2(buffer, gb_size_of(buffer), str)); - } -} - -void gb_platform_toggle_fullscreen(gbPlatform *p, b32 fullscreen_desktop) { - // NOTE(bill): From the man himself, Raymond Chen! (Modified for my need.) - HWND handle = cast(HWND)p->window_handle; - DWORD style = cast(DWORD)GetWindowLongW(handle, GWL_STYLE); - WINDOWPLACEMENT placement; - - if (style & WS_OVERLAPPEDWINDOW) { - MONITORINFO monitor_info = {gb_size_of(monitor_info)}; - if (GetWindowPlacement(handle, &placement) && - GetMonitorInfoW(MonitorFromWindow(handle, 1), &monitor_info)) { - style &= ~WS_OVERLAPPEDWINDOW; - if (fullscreen_desktop) { - style &= ~WS_CAPTION; - style |= WS_POPUP; - } - SetWindowLongW(handle, GWL_STYLE, style); - SetWindowPos(handle, HWND_TOP, - monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, - monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - - if (fullscreen_desktop) { - p->window_flags |= gbWindow_FullscreenDesktop; - } else { - p->window_flags |= gbWindow_Fullscreen; - } - } - } else { - style &= ~WS_POPUP; - style |= WS_OVERLAPPEDWINDOW | WS_CAPTION; - SetWindowLongW(handle, GWL_STYLE, style); - SetWindowPlacement(handle, &placement); - SetWindowPos(handle, 0, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - - p->window_flags &= ~gbWindow_Fullscreen; - } -} - -void gb_platform_toggle_borderless(gbPlatform *p) { - HWND handle = cast(HWND)p->window_handle; - DWORD style = GetWindowLongW(handle, GWL_STYLE); - b32 is_borderless = (style & WS_POPUP) != 0; - - GB_MASK_SET(style, is_borderless, WS_OVERLAPPEDWINDOW | WS_CAPTION); - GB_MASK_SET(style, !is_borderless, WS_POPUP); - - SetWindowLongW(handle, GWL_STYLE, style); - - GB_MASK_SET(p->window_flags, !is_borderless, gbWindow_Borderless); -} - - - -gb_inline void gb_platform_make_opengl_context_current(gbPlatform *p) { - if (p->renderer_type == gbRenderer_Opengl) { - wglMakeCurrent(cast(HDC)p->win32_dc, cast(HGLRC)p->opengl.context); - } -} - -gb_inline void gb_platform_show_window(gbPlatform *p) { - ShowWindow(cast(HWND)p->window_handle, SW_SHOW); - p->window_flags &= ~gbWindow_Hidden; -} - -gb_inline void gb_platform_hide_window(gbPlatform *p) { - ShowWindow(cast(HWND)p->window_handle, SW_HIDE); - p->window_flags |= gbWindow_Hidden; -} - -gb_inline gbVideoMode gb_video_mode_get_desktop(void) { - DEVMODEW win32_mode = {gb_size_of(win32_mode)}; - EnumDisplaySettingsW(NULL, ENUM_CURRENT_SETTINGS, &win32_mode); - return gb_video_mode(win32_mode.dmPelsWidth, win32_mode.dmPelsHeight, win32_mode.dmBitsPerPel); -} - -isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count) { - DEVMODEW win32_mode = {gb_size_of(win32_mode)}; - i32 count; - for (count = 0; - count < max_mode_count && EnumDisplaySettingsW(NULL, count, &win32_mode); - count++) { - modes[count] = gb_video_mode(win32_mode.dmPelsWidth, win32_mode.dmPelsHeight, win32_mode.dmBitsPerPel); - } - - gb_sort_array(modes, count, gb_video_mode_dsc_cmp); - return count; -} - - - -b32 gb_platform_has_clipboard_text(gbPlatform *p) { - b32 result = false; - - if (IsClipboardFormatAvailable(1/*CF_TEXT*/) && - OpenClipboard(cast(HWND)p->window_handle)) { - HANDLE mem = GetClipboardData(1/*CF_TEXT*/); - if (mem) { - char *str = cast(char *)GlobalLock(mem); - if (str && str[0] != '\0') { - result = true; - } - GlobalUnlock(mem); - } else { - return false; - } - - CloseClipboard(); - } - - return result; -} - -// TODO(bill): Handle UTF-8 -void gb_platform_set_clipboard_text(gbPlatform *p, char const *str) { - if (OpenClipboard(cast(HWND)p->window_handle)) { - isize i, len = gb_strlen(str)+1; - - HANDLE mem = cast(HANDLE)GlobalAlloc(0x0002/*GMEM_MOVEABLE*/, len); - if (mem) { - char *dst = cast(char *)GlobalLock(mem); - if (dst) { - for (i = 0; str[i]; i++) { - // TODO(bill): Does this cause a buffer overflow? - // NOTE(bill): Change \n to \r\n 'cause windows - if (str[i] == '\n' && (i == 0 || str[i-1] != '\r')) { - *dst++ = '\r'; - } - *dst++ = str[i]; - } - *dst = 0; - } - GlobalUnlock(mem); - } - - EmptyClipboard(); - if (!SetClipboardData(1/*CF_TEXT*/, mem)) { - return; - } - CloseClipboard(); - } -} - -// TODO(bill): Handle UTF-8 -char *gb_platform_get_clipboard_text(gbPlatform *p, gbAllocator a) { - char *text = NULL; - - if (IsClipboardFormatAvailable(1/*CF_TEXT*/) && - OpenClipboard(cast(HWND)p->window_handle)) { - HANDLE mem = GetClipboardData(1/*CF_TEXT*/); - if (mem) { - char *str = cast(char *)GlobalLock(mem); - text = gb_alloc_str(a, str); - GlobalUnlock(mem); - } else { - return NULL; - } - - CloseClipboard(); - } - - return text; -} - -#elif defined(GB_SYSTEM_OSX) - -#include -#include -#include -#include - -#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 - #define NSIntegerEncoding "q" - #define NSUIntegerEncoding "L" -#else - #define NSIntegerEncoding "i" - #define NSUIntegerEncoding "I" -#endif - -#ifdef __OBJC__ - #import -#else - typedef CGPoint NSPoint; - typedef CGSize NSSize; - typedef CGRect NSRect; - - extern id NSApp; - extern id const NSDefaultRunLoopMode; -#endif - -#if defined(__OBJC__) && __has_feature(objc_arc) -#error TODO(bill): Cannot compile as objective-c code just yet! -#endif - -// ABI is a bit different between platforms -#ifdef __arm64__ -#define abi_objc_msgSend_stret objc_msgSend -#else -#define abi_objc_msgSend_stret objc_msgSend_stret -#endif -#ifdef __i386__ -#define abi_objc_msgSend_fpret objc_msgSend_fpret -#else -#define abi_objc_msgSend_fpret objc_msgSend -#endif - -#define objc_msgSend_id ((id (*)(id, SEL))objc_msgSend) -#define objc_msgSend_void ((void (*)(id, SEL))objc_msgSend) -#define objc_msgSend_void_id ((void (*)(id, SEL, id))objc_msgSend) -#define objc_msgSend_void_bool ((void (*)(id, SEL, BOOL))objc_msgSend) -#define objc_msgSend_id_char_const ((id (*)(id, SEL, char const *))objc_msgSend) - -gb_internal NSUInteger gb__osx_application_should_terminate(id self, SEL _sel, id sender) { - // NOTE(bill): Do nothing - return 0; -} - -gb_internal void gb__osx_window_will_close(id self, SEL _sel, id notification) { - NSUInteger value = true; - object_setInstanceVariable(self, "closed", cast(void *)value); -} - -gb_internal void gb__osx_window_did_become_key(id self, SEL _sel, id notification) { - gbPlatform *p = NULL; - object_getInstanceVariable(self, "gbPlatform", cast(void **)&p); - if (p) { - // TODO(bill): - } -} - -b32 gb__platform_init(gbPlatform *p, char const *window_title, gbVideoMode mode, gbRendererType type, u32 window_flags) { - if (p->is_initialized) { - return true; - } - // Init Platform - { // Initial OSX State - Class appDelegateClass; - b32 resultAddProtoc, resultAddMethod; - id dgAlloc, dg, menubarAlloc, menubar; - id appMenuItemAlloc, appMenuItem; - id appMenuAlloc, appMenu; - - #if defined(ARC_AVAILABLE) - #error TODO(bill): This code should be compiled as C for now - #else - id poolAlloc = objc_msgSend_id(cast(id)objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); - p->osx_autorelease_pool = objc_msgSend_id(poolAlloc, sel_registerName("init")); - #endif - - objc_msgSend_id(cast(id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - ((void (*)(id, SEL, NSInteger))objc_msgSend)(NSApp, sel_registerName("setActivationPolicy:"), 0); - - appDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "AppDelegate", 0); - resultAddProtoc = class_addProtocol(appDelegateClass, objc_getProtocol("NSApplicationDelegate")); - assert(resultAddProtoc); - resultAddMethod = class_addMethod(appDelegateClass, sel_registerName("applicationShouldTerminate:"), cast(IMP)gb__osx_application_should_terminate, NSUIntegerEncoding "@:@"); - assert(resultAddMethod); - dgAlloc = objc_msgSend_id(cast(id)appDelegateClass, sel_registerName("alloc")); - dg = objc_msgSend_id(dgAlloc, sel_registerName("init")); - #ifndef ARC_AVAILABLE - objc_msgSend_void(dg, sel_registerName("autorelease")); - #endif - - objc_msgSend_void_id(NSApp, sel_registerName("setDelegate:"), dg); - objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); - - menubarAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenu"), sel_registerName("alloc")); - menubar = objc_msgSend_id(menubarAlloc, sel_registerName("init")); - #ifndef ARC_AVAILABLE - objc_msgSend_void(menubar, sel_registerName("autorelease")); - #endif - - appMenuItemAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); - appMenuItem = objc_msgSend_id(appMenuItemAlloc, sel_registerName("init")); - #ifndef ARC_AVAILABLE - objc_msgSend_void(appMenuItem, sel_registerName("autorelease")); - #endif - - objc_msgSend_void_id(menubar, sel_registerName("addItem:"), appMenuItem); - ((id (*)(id, SEL, id))objc_msgSend)(NSApp, sel_registerName("setMainMenu:"), menubar); - - appMenuAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenu"), sel_registerName("alloc")); - appMenu = objc_msgSend_id(appMenuAlloc, sel_registerName("init")); - #ifndef ARC_AVAILABLE - objc_msgSend_void(appMenu, sel_registerName("autorelease")); - #endif - - { - id processInfo = objc_msgSend_id(cast(id)objc_getClass("NSProcessInfo"), sel_registerName("processInfo")); - id appName = objc_msgSend_id(processInfo, sel_registerName("processName")); - - id quitTitlePrefixString = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "Quit "); - id quitTitle = ((id (*)(id, SEL, id))objc_msgSend)(quitTitlePrefixString, sel_registerName("stringByAppendingString:"), appName); - - id quitMenuItemKey = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "q"); - id quitMenuItemAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); - id quitMenuItem = ((id (*)(id, SEL, id, SEL, id))objc_msgSend)(quitMenuItemAlloc, sel_registerName("initWithTitle:action:keyEquivalent:"), quitTitle, sel_registerName("terminate:"), quitMenuItemKey); - #ifndef ARC_AVAILABLE - objc_msgSend_void(quitMenuItem, sel_registerName("autorelease")); - #endif - - objc_msgSend_void_id(appMenu, sel_registerName("addItem:"), quitMenuItem); - objc_msgSend_void_id(appMenuItem, sel_registerName("setSubmenu:"), appMenu); - } - } - - { // Init Window - NSRect rect = {{0, 0}, {cast(CGFloat)mode.width, cast(CGFloat)mode.height}}; - id windowAlloc, window, wdgAlloc, wdg, contentView, titleString; - Class WindowDelegateClass; - b32 resultAddProtoc, resultAddIvar, resultAddMethod; - - windowAlloc = objc_msgSend_id(cast(id)objc_getClass("NSWindow"), sel_registerName("alloc")); - window = ((id (*)(id, SEL, NSRect, NSUInteger, NSUInteger, BOOL))objc_msgSend)(windowAlloc, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, 15, 2, NO); - #ifndef ARC_AVAILABLE - objc_msgSend_void(window, sel_registerName("autorelease")); - #endif - - // when we are not using ARC, than window will be added to autorelease pool - // so if we close it by hand (pressing red button), we don't want it to be released for us - // so it will be released by autorelease pool later - objc_msgSend_void_bool(window, sel_registerName("setReleasedWhenClosed:"), NO); - - WindowDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "WindowDelegate", 0); - resultAddProtoc = class_addProtocol(WindowDelegateClass, objc_getProtocol("NSWindowDelegate")); - GB_ASSERT(resultAddProtoc); - resultAddIvar = class_addIvar(WindowDelegateClass, "closed", gb_size_of(NSUInteger), rint(log2(gb_size_of(NSUInteger))), NSUIntegerEncoding); - GB_ASSERT(resultAddIvar); - resultAddIvar = class_addIvar(WindowDelegateClass, "gbPlatform", gb_size_of(void *), rint(log2(gb_size_of(void *))), "ˆv"); - GB_ASSERT(resultAddIvar); - resultAddMethod = class_addMethod(WindowDelegateClass, sel_registerName("windowWillClose:"), cast(IMP)gb__osx_window_will_close, "v@:@"); - GB_ASSERT(resultAddMethod); - resultAddMethod = class_addMethod(WindowDelegateClass, sel_registerName("windowDidBecomeKey:"), cast(IMP)gb__osx_window_did_become_key, "v@:@"); - GB_ASSERT(resultAddMethod); - wdgAlloc = objc_msgSend_id(cast(id)WindowDelegateClass, sel_registerName("alloc")); - wdg = objc_msgSend_id(wdgAlloc, sel_registerName("init")); - #ifndef ARC_AVAILABLE - objc_msgSend_void(wdg, sel_registerName("autorelease")); - #endif - - objc_msgSend_void_id(window, sel_registerName("setDelegate:"), wdg); - - contentView = objc_msgSend_id(window, sel_registerName("contentView")); - - { - NSPoint point = {20, 20}; - ((void (*)(id, SEL, NSPoint))objc_msgSend)(window, sel_registerName("cascadeTopLeftFromPoint:"), point); - } - - titleString = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), window_title); - objc_msgSend_void_id(window, sel_registerName("setTitle:"), titleString); - - if (type == gbRenderer_Opengl) { - // TODO(bill): Make sure this works correctly - u32 opengl_hex_version = (p->opengl.major << 12) | (p->opengl.minor << 8); - u32 gl_attribs[] = { - 8, 24, // NSOpenGLPFAColorSize, 24, - 11, 8, // NSOpenGLPFAAlphaSize, 8, - 5, // NSOpenGLPFADoubleBuffer, - 73, // NSOpenGLPFAAccelerated, - //72, // NSOpenGLPFANoRecovery, - //55, 1, // NSOpenGLPFASampleBuffers, 1, - //56, 4, // NSOpenGLPFASamples, 4, - 99, opengl_hex_version, // NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, - 0 - }; - - id pixel_format_alloc, pixel_format; - id opengl_context_alloc, opengl_context; - - pixel_format_alloc = objc_msgSend_id(cast(id)objc_getClass("NSOpenGLPixelFormat"), sel_registerName("alloc")); - pixel_format = ((id (*)(id, SEL, const uint32_t*))objc_msgSend)(pixel_format_alloc, sel_registerName("initWithAttributes:"), gl_attribs); - #ifndef ARC_AVAILABLE - objc_msgSend_void(pixel_format, sel_registerName("autorelease")); - #endif - - opengl_context_alloc = objc_msgSend_id(cast(id)objc_getClass("NSOpenGLContext"), sel_registerName("alloc")); - opengl_context = ((id (*)(id, SEL, id, id))objc_msgSend)(opengl_context_alloc, sel_registerName("initWithFormat:shareContext:"), pixel_format, nil); - #ifndef ARC_AVAILABLE - objc_msgSend_void(opengl_context, sel_registerName("autorelease")); - #endif - - objc_msgSend_void_id(opengl_context, sel_registerName("setView:"), contentView); - objc_msgSend_void_id(window, sel_registerName("makeKeyAndOrderFront:"), window); - objc_msgSend_void_bool(window, sel_registerName("setAcceptsMouseMovedEvents:"), YES); - - - p->window_handle = cast(void *)window; - p->opengl.context = cast(void *)opengl_context; - } else { - GB_PANIC("TODO(bill): Software rendering"); - } - - { - id blackColor = objc_msgSend_id(cast(id)objc_getClass("NSColor"), sel_registerName("blackColor")); - objc_msgSend_void_id(window, sel_registerName("setBackgroundColor:"), blackColor); - objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), YES); - } - object_setInstanceVariable(wdg, "gbPlatform", cast(void *)p); - - p->is_initialized = true; - } - - return true; -} - -// NOTE(bill): Software rendering -b32 gb_platform_init_with_software(gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags) { - GB_PANIC("TODO(bill): Software rendering in not yet implemented on OS X\n"); - return gb__platform_init(p, window_title, gb_video_mode(width, height, 32), gbRenderer_Software, window_flags); -} -// NOTE(bill): OpenGL Rendering -b32 gb_platform_init_with_opengl(gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags, - i32 major, i32 minor, b32 core, b32 compatible) { - - p->opengl.major = major; - p->opengl.minor = minor; - p->opengl.core = core; - p->opengl.compatible = compatible; - return gb__platform_init(p, window_title, gb_video_mode(width, height, 32), gbRenderer_Opengl, window_flags); -} - -// NOTE(bill): Reverse engineering can be fun!!! -gb_internal gbKeyType gb__osx_from_key_code(u16 key_code) { - switch (key_code) { - default: return gbKey_Unknown; - // NOTE(bill): WHO THE FUCK DESIGNED THIS VIRTUAL KEY CODE SYSTEM?! - // THEY ARE FUCKING IDIOTS! - case 0x1d: return gbKey_0; - case 0x12: return gbKey_1; - case 0x13: return gbKey_2; - case 0x14: return gbKey_3; - case 0x15: return gbKey_4; - case 0x17: return gbKey_5; - case 0x16: return gbKey_6; - case 0x1a: return gbKey_7; - case 0x1c: return gbKey_8; - case 0x19: return gbKey_9; - - case 0x00: return gbKey_A; - case 0x0b: return gbKey_B; - case 0x08: return gbKey_C; - case 0x02: return gbKey_D; - case 0x0e: return gbKey_E; - case 0x03: return gbKey_F; - case 0x05: return gbKey_G; - case 0x04: return gbKey_H; - case 0x22: return gbKey_I; - case 0x26: return gbKey_J; - case 0x28: return gbKey_K; - case 0x25: return gbKey_L; - case 0x2e: return gbKey_M; - case 0x2d: return gbKey_N; - case 0x1f: return gbKey_O; - case 0x23: return gbKey_P; - case 0x0c: return gbKey_Q; - case 0x0f: return gbKey_R; - case 0x01: return gbKey_S; - case 0x11: return gbKey_T; - case 0x20: return gbKey_U; - case 0x09: return gbKey_V; - case 0x0d: return gbKey_W; - case 0x07: return gbKey_X; - case 0x10: return gbKey_Y; - case 0x06: return gbKey_Z; - - case 0x21: return gbKey_Lbracket; - case 0x1e: return gbKey_Rbracket; - case 0x29: return gbKey_Semicolon; - case 0x2b: return gbKey_Comma; - case 0x2f: return gbKey_Period; - case 0x27: return gbKey_Quote; - case 0x2c: return gbKey_Slash; - case 0x2a: return gbKey_Backslash; - case 0x32: return gbKey_Grave; - case 0x18: return gbKey_Equals; - case 0x1b: return gbKey_Minus; - case 0x31: return gbKey_Space; - - case 0x35: return gbKey_Escape; // Escape - case 0x3b: return gbKey_Lcontrol; // Left Control - case 0x38: return gbKey_Lshift; // Left Shift - case 0x3a: return gbKey_Lalt; // Left Alt - case 0x37: return gbKey_Lsystem; // Left OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... - case 0x3e: return gbKey_Rcontrol; // Right Control - case 0x3c: return gbKey_Rshift; // Right Shift - case 0x3d: return gbKey_Ralt; // Right Alt - // case 0x37: return gbKey_Rsystem; // Right OS specific: window (Windows and Linux), apple/cmd (MacOS X), ... - case 0x6e: return gbKey_Menu; // Menu - case 0x24: return gbKey_Return; // Return - case 0x33: return gbKey_Backspace; // Backspace - case 0x30: return gbKey_Tab; // Tabulation - case 0x74: return gbKey_Pageup; // Page up - case 0x79: return gbKey_Pagedown; // Page down - case 0x77: return gbKey_End; // End - case 0x73: return gbKey_Home; // Home - case 0x72: return gbKey_Insert; // Insert - case 0x75: return gbKey_Delete; // Delete - case 0x45: return gbKey_Plus; // + - case 0x4e: return gbKey_Subtract; // - - case 0x43: return gbKey_Multiply; // * - case 0x4b: return gbKey_Divide; // / - case 0x7b: return gbKey_Left; // Left arrow - case 0x7c: return gbKey_Right; // Right arrow - case 0x7e: return gbKey_Up; // Up arrow - case 0x7d: return gbKey_Down; // Down arrow - case 0x52: return gbKey_Numpad0; // Numpad 0 - case 0x53: return gbKey_Numpad1; // Numpad 1 - case 0x54: return gbKey_Numpad2; // Numpad 2 - case 0x55: return gbKey_Numpad3; // Numpad 3 - case 0x56: return gbKey_Numpad4; // Numpad 4 - case 0x57: return gbKey_Numpad5; // Numpad 5 - case 0x58: return gbKey_Numpad6; // Numpad 6 - case 0x59: return gbKey_Numpad7; // Numpad 7 - case 0x5b: return gbKey_Numpad8; // Numpad 8 - case 0x5c: return gbKey_Numpad9; // Numpad 9 - case 0x41: return gbKey_NumpadDot; // Numpad . - case 0x4c: return gbKey_NumpadEnter; // Numpad Enter - case 0x7a: return gbKey_F1; // F1 - case 0x78: return gbKey_F2; // F2 - case 0x63: return gbKey_F3; // F3 - case 0x76: return gbKey_F4; // F4 - case 0x60: return gbKey_F5; // F5 - case 0x61: return gbKey_F6; // F6 - case 0x62: return gbKey_F7; // F7 - case 0x64: return gbKey_F8; // F8 - case 0x65: return gbKey_F9; // F8 - case 0x6d: return gbKey_F10; // F10 - case 0x67: return gbKey_F11; // F11 - case 0x6f: return gbKey_F12; // F12 - case 0x69: return gbKey_F13; // F13 - case 0x6b: return gbKey_F14; // F14 - case 0x71: return gbKey_F15; // F15 - // case : return gbKey_Pause; // Pause // NOTE(bill): Not possible on OS X - } -} - -gb_internal void gb__osx_on_cocoa_event(gbPlatform *p, id event, id window) { - if (!event) { - return; - } else if (objc_msgSend_id(window, sel_registerName("delegate"))) { - NSUInteger event_type = ((NSUInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("type")); - switch (event_type) { - case 1: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Left], true); break; // NSLeftMouseDown - case 2: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Left], false); break; // NSLeftMouseUp - case 3: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Right], true); break; // NSRightMouseDown - case 4: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Right], false); break; // NSRightMouseUp - case 25: { // NSOtherMouseDown - // TODO(bill): Test thoroughly - NSInteger number = ((NSInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); - if (number == 2) gb_key_state_update(&p->mouse_buttons[gbMouseButton_Middle], true); - if (number == 3) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X1], true); - if (number == 4) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X2], true); - } break; - case 26: { // NSOtherMouseUp - NSInteger number = ((NSInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); - if (number == 2) gb_key_state_update(&p->mouse_buttons[gbMouseButton_Middle], false); - if (number == 3) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X1], false); - if (number == 4) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X2], false); - - } break; - - // TODO(bill): Scroll wheel - case 22: { // NSScrollWheel - CGFloat dx = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("scrollingDeltaX")); - CGFloat dy = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("scrollingDeltaY")); - BOOL precision_scrolling = ((BOOL (*)(id, SEL))objc_msgSend)(event, sel_registerName("hasPreciseScrollingDeltas")); - if (precision_scrolling) { - dx *= 0.1f; - dy *= 0.1f; - } - // TODO(bill): Handle sideways - p->mouse_wheel_delta = dy; - // p->mouse_wheel_dy = dy; - // gb_printf("%f %f\n", dx, dy); - } break; - - case 12: { // NSFlagsChanged - #if 0 - // TODO(bill): Reverse engineer this properly - NSUInteger modifiers = ((NSUInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("modifierFlags")); - u32 upper_mask = (modifiers & 0xffff0000ul) >> 16; - b32 shift = (upper_mask & 0x02) != 0; - b32 control = (upper_mask & 0x04) != 0; - b32 alt = (upper_mask & 0x08) != 0; - b32 command = (upper_mask & 0x10) != 0; - #endif - - // gb_printf("%u\n", keys.mask); - // gb_printf("%x\n", cast(u32)modifiers); - } break; - - case 10: { // NSKeyDown - u16 key_code; - - id input_text = objc_msgSend_id(event, sel_registerName("characters")); - char const *input_text_utf8 = ((char const *(*)(id, SEL))objc_msgSend)(input_text, sel_registerName("UTF8String")); - p->char_buffer_count = gb_strnlen(input_text_utf8, gb_size_of(p->char_buffer)); - gb_memcopy(p->char_buffer, input_text_utf8, p->char_buffer_count); - - key_code = ((unsigned short (*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); - gb_key_state_update(&p->keys[gb__osx_from_key_code(key_code)], true); - } break; - - case 11: { // NSKeyUp - u16 key_code = ((unsigned short (*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); - gb_key_state_update(&p->keys[gb__osx_from_key_code(key_code)], false); - } break; - - default: break; - } - - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), event); - } -} - - -void gb_platform_update(gbPlatform *p) { - id window, key_window, content_view; - NSRect original_frame; - - window = cast(id)p->window_handle; - key_window = objc_msgSend_id(NSApp, sel_registerName("keyWindow")); - p->window_has_focus = key_window == window; // TODO(bill): Is this right - - - if (p->window_has_focus) { - isize i; - p->char_buffer_count = 0; // TODO(bill): Reset buffer count here or else where? - - // NOTE(bill): Need to update as the keys only get updates on events - for (i = 0; i < gbKey_Count; i++) { - b32 is_down = (p->keys[i] & gbKeyState_Down) != 0; - gb_key_state_update(&p->keys[i], is_down); - } - - for (i = 0; i < gbMouseButton_Count; i++) { - b32 is_down = (p->mouse_buttons[i] & gbKeyState_Down) != 0; - gb_key_state_update(&p->mouse_buttons[i], is_down); - } - - } - - { // Handle Events - id distant_past = objc_msgSend_id(cast(id)objc_getClass("NSDate"), sel_registerName("distantPast")); - id event = ((id (*)(id, SEL, NSUInteger, id, id, BOOL))objc_msgSend)(NSApp, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), NSUIntegerMax, distant_past, NSDefaultRunLoopMode, YES); - gb__osx_on_cocoa_event(p, event, window); - } - - if (p->window_has_focus) { - p->key_modifiers.control = p->keys[gbKey_Lcontrol] | p->keys[gbKey_Rcontrol]; - p->key_modifiers.alt = p->keys[gbKey_Lalt] | p->keys[gbKey_Ralt]; - p->key_modifiers.shift = p->keys[gbKey_Lshift] | p->keys[gbKey_Rshift]; - } - - { // Check if window is closed - id wdg = objc_msgSend_id(window, sel_registerName("delegate")); - if (!wdg) { - p->window_is_closed = false; - } else { - NSUInteger value = 0; - object_getInstanceVariable(wdg, "closed", cast(void **)&value); - p->window_is_closed = (value != 0); - } - } - - - - content_view = objc_msgSend_id(window, sel_registerName("contentView")); - original_frame = ((NSRect (*)(id, SEL))abi_objc_msgSend_stret)(content_view, sel_registerName("frame")); - - { // Window - NSRect frame = original_frame; - frame = ((NSRect (*)(id, SEL, NSRect))abi_objc_msgSend_stret)(content_view, sel_registerName("convertRectToBacking:"), frame); - p->window_width = frame.size.width; - p->window_height = frame.size.height; - frame = ((NSRect (*)(id, SEL, NSRect))abi_objc_msgSend_stret)(window, sel_registerName("convertRectToScreen:"), frame); - p->window_x = frame.origin.x; - p->window_y = frame.origin.y; - } - - { // Mouse - NSRect frame = original_frame; - NSPoint mouse_pos = ((NSPoint (*)(id, SEL))objc_msgSend)(window, sel_registerName("mouseLocationOutsideOfEventStream")); - mouse_pos.x = gb_clamp(mouse_pos.x, 0, frame.size.width-1); - mouse_pos.y = gb_clamp(mouse_pos.y, 0, frame.size.height-1); - - { - i32 x = mouse_pos.x; - i32 y = mouse_pos.y; - p->mouse_dx = x - p->mouse_x; - p->mouse_dy = y - p->mouse_y; - p->mouse_x = x; - p->mouse_y = y; - } - - if (p->mouse_clip) { - b32 update = false; - i32 x = p->mouse_x; - i32 y = p->mouse_y; - if (p->mouse_x < 0) { - x = 0; - update = true; - } else if (p->mouse_y > p->window_height-1) { - y = p->window_height-1; - update = true; - } - - if (p->mouse_y < 0) { - y = 0; - update = true; - } else if (p->mouse_x > p->window_width-1) { - x = p->window_width-1; - update = true; - } - - if (update) { - gb_platform_set_mouse_position(p, x, y); - } - } - } - - { // TODO(bill): Controllers - - } - - // TODO(bill): Is this in the correct place? - objc_msgSend_void(NSApp, sel_registerName("updateWindows")); - if (p->renderer_type == gbRenderer_Opengl) { - objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("update")); - gb_platform_make_opengl_context_current(p); - } -} - -void gb_platform_display(gbPlatform *p) { - // TODO(bill): Do more - if (p->renderer_type == gbRenderer_Opengl) { - gb_platform_make_opengl_context_current(p); - objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("flushBuffer")); - } else if (p->renderer_type == gbRenderer_Software) { - // TODO(bill): - } else { - GB_PANIC("Invalid window rendering type"); - } - - { - f64 prev_time = p->curr_time; - f64 curr_time = gb_time_now(); - p->dt_for_frame = curr_time - prev_time; - p->curr_time = curr_time; - } -} - -void gb_platform_destroy(gbPlatform *p) { - gb_platform_make_opengl_context_current(p); - - objc_msgSend_void(cast(id)p->window_handle, sel_registerName("close")); - - #if defined(ARC_AVAILABLE) - // TODO(bill): autorelease pool - #else - objc_msgSend_void(cast(id)p->osx_autorelease_pool, sel_registerName("drain")); - #endif -} - -void gb_platform_show_cursor(gbPlatform *p, b32 show) { - if (show ) { - // objc_msgSend_void(class_registerName("NSCursor"), sel_registerName("unhide")); - } else { - // objc_msgSend_void(class_registerName("NSCursor"), sel_registerName("hide")); - } -} - -void gb_platform_set_mouse_position(gbPlatform *p, i32 x, i32 y) { - // TODO(bill): - CGPoint pos = {cast(CGFloat)x, cast(CGFloat)y}; - pos.x += p->window_x; - pos.y += p->window_y; - CGWarpMouseCursorPosition(pos); -} - -void gb_platform_set_controller_vibration(gbPlatform *p, isize index, f32 left_motor, f32 right_motor) { - // TODO(bill): -} - -b32 gb_platform_has_clipboard_text(gbPlatform *p) { - // TODO(bill): - return false; -} - -void gb_platform_set_clipboard_text(gbPlatform *p, char const *str) { - // TODO(bill): -} - -char *gb_platform_get_clipboard_text(gbPlatform *p, gbAllocator a) { - // TODO(bill): - return NULL; -} - -void gb_platform_set_window_position(gbPlatform *p, i32 x, i32 y) { - // TODO(bill): -} - -void gb_platform_set_window_title(gbPlatform *p, char const *title, ...) { - id title_string; - char buf[256] = {0}; - va_list va; - va_start(va, title); - gb_snprintf_va(buf, gb_count_of(buf), title, va); - va_end(va); - - title_string = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), buf); - objc_msgSend_void_id(cast(id)p->window_handle, sel_registerName("setTitle:"), title_string); -} - -void gb_platform_toggle_fullscreen(gbPlatform *p, b32 fullscreen_desktop) { - // TODO(bill): -} - -void gb_platform_toggle_borderless(gbPlatform *p) { - // TODO(bill): -} - -void gb_platform_make_opengl_context_current(gbPlatform *p) { - objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("makeCurrentContext")); -} - -void gb_platform_show_window(gbPlatform *p) { - // TODO(bill): -} - -void gb_platform_hide_window(gbPlatform *p) { - // TODO(bill): -} - -i32 gb__osx_mode_bits_per_pixel(CGDisplayModeRef mode) { - i32 bits_per_pixel = 0; - CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode); - if(CFStringCompare(pixel_encoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { - bits_per_pixel = 32; - } else if(CFStringCompare(pixel_encoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { - bits_per_pixel = 16; - } else if(CFStringCompare(pixel_encoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) { - bits_per_pixel = 8; - } - CFRelease(pixel_encoding); - - return bits_per_pixel; -} - -i32 gb__osx_display_bits_per_pixel(CGDirectDisplayID display) { - CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display); - i32 bits_per_pixel = gb__osx_mode_bits_per_pixel(mode); - CGDisplayModeRelease(mode); - return bits_per_pixel; -} - -gbVideoMode gb_video_mode_get_desktop(void) { - CGDirectDisplayID display = CGMainDisplayID(); - return gb_video_mode(CGDisplayPixelsWide(display), - CGDisplayPixelsHigh(display), - gb__osx_display_bits_per_pixel(display)); -} - - -isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count) { - CFArrayRef cg_modes = CGDisplayCopyAllDisplayModes(CGMainDisplayID(), NULL); - CFIndex i, count; - if (cg_modes == NULL) { - return 0; - } - - count = gb_min(CFArrayGetCount(cg_modes), max_mode_count); - for (i = 0; i < count; i++) { - CGDisplayModeRef cg_mode = cast(CGDisplayModeRef)CFArrayGetValueAtIndex(cg_modes, i); - modes[i] = gb_video_mode(CGDisplayModeGetWidth(cg_mode), - CGDisplayModeGetHeight(cg_mode), - gb__osx_mode_bits_per_pixel(cg_mode)); - } - - CFRelease(cg_modes); - - gb_sort_array(modes, count, gb_video_mode_dsc_cmp); - return cast(isize)count; -} - -#endif - - -// TODO(bill): OSX Platform Layer -// NOTE(bill): Use this as a guide so there is no need for Obj-C https://github.com/jimon/osx_app_in_plain_c - -gb_inline gbVideoMode gb_video_mode(i32 width, i32 height, i32 bits_per_pixel) { - gbVideoMode m; - m.width = width; - m.height = height; - m.bits_per_pixel = bits_per_pixel; - return m; -} - -gb_inline b32 gb_video_mode_is_valid(gbVideoMode mode) { - gb_local_persist gbVideoMode modes[256] = {0}; - gb_local_persist isize mode_count = 0; - gb_local_persist b32 is_set = false; - isize i; - - if (!is_set) { - mode_count = gb_video_mode_get_fullscreen_modes(modes, gb_count_of(modes)); - is_set = true; - } - - for (i = 0; i < mode_count; i++) { - gb_printf("%d %d\n", modes[i].width, modes[i].height); - } - - return gb_binary_search_array(modes, mode_count, &mode, gb_video_mode_cmp) >= 0; -} - -GB_COMPARE_PROC(gb_video_mode_cmp) { - gbVideoMode const *x = cast(gbVideoMode const *)a; - gbVideoMode const *y = cast(gbVideoMode const *)b; - - if (x->bits_per_pixel == y->bits_per_pixel) { - if (x->width == y->width) { - return x->height < y->height ? -1 : x->height > y->height; - } - return x->width < y->width ? -1 : x->width > y->width; - } - return x->bits_per_pixel < y->bits_per_pixel ? -1 : +1; -} - -GB_COMPARE_PROC(gb_video_mode_dsc_cmp) { - return gb_video_mode_cmp(b, a); -} - -#endif // defined(GB_PLATFORM) - - - - -#if defined(GB_COMPILER_MSVC) -#pragma warning(pop) -#endif - -#if defined(__GCC__) || defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - - -#if defined(__cplusplus) -} -#endif - -#endif // GB_IMPLEMENTATION diff --git a/thirdparty/stb/src/stb_truetype.h b/thirdparty/stb/src/stb_truetype.h index 2ca3ff9..6c82c29 100644 --- a/thirdparty/stb/src/stb_truetype.h +++ b/thirdparty/stb/src/stb_truetype.h @@ -415,9 +415,9 @@ int main(int arg, char **argv) #pragma region ODIN: CUSTOM ALLOCATOR #ifdef STB_TRUETYPE_IMPLEMENTATION -#define GB_IMPLEMENTATION +#define ZPL_IMPLEMENTATION #endif -#include "gb/gb.h" +#include "zpl/zpl.h" #ifdef STBTT_STATIC #define STBTT_DEF static @@ -429,21 +429,21 @@ int main(int arg, char **argv) extern "C" { #endif -STBTT_DEF void stbtt_SetAllocator( gbAllocator allocator ); +STBTT_DEF void stbtt_SetAllocator( zpl_allocator allocator ); #ifdef __cplusplus } #endif #ifndef STBTT_malloc -#define STBTT_malloc(x,u) ((void)(u), gb_alloc(stbtt__allocator, x)) -#define STBTT_free(x,u) ((void)(u), gb_free(stbtt__allocator, x)) +#define STBTT_malloc(x,u) ((void)(u), zpl_alloc(stbtt__allocator, x)) +#define STBTT_free(x,u) ((void)(u), zpl_free(stbtt__allocator, x)) #endif #ifdef STB_TRUETYPE_IMPLEMENTATION -gb_global gbAllocator stbtt__allocator = { gb_heap_allocator_proc, NULL }; +zpl_global zpl_allocator stbtt__allocator = { zpl_heap_allocator_proc, NULL }; -STBTT_DEF void stbtt_SetAllocator( gbAllocator allocator ) { +STBTT_DEF void stbtt_SetAllocator( zpl_allocator allocator ) { stbtt__allocator = allocator; } #endif diff --git a/thirdparty/stb/src/zpl/zpl.h b/thirdparty/stb/src/zpl/zpl.h new file mode 100644 index 0000000..d90720a --- /dev/null +++ b/thirdparty/stb/src/zpl/zpl.h @@ -0,0 +1,19055 @@ +/** + zpl - Pushing the boundaries of simplicity. + +Usage: +# define ZPL_IMPLEMENTATION exactly in ONE source file right BEFORE including the library, like: + +# define ZPL_IMPLEMENTATION +# include "zpl.h" + + You can also use a lightweight version of zpl by using ZPL_NANO, like: + +# define ZPL_IMPLEMENTATION +# define ZPL_NANO +# include "zpl.h" + + There is also a distribution that provides only the essential modules, you can enable it by defining ZPL_PICO. + Currently, the distro offers: preprocessor helpers, debug module, memory API (except vm) and collections. + Some of these modules used to depend on zpl_printf, but they use the standard library if the distro is enabled now. + +# define ZPL_IMPLEMENTATION +# define ZPL_PICO +# include "zpl.h" + +Options: + ZPL_EXPOSE_TYPES - exposes all zpl defined types to the global namespace. This means type such as `zpl_u32` is now available as `u32` globally. + ZPL_DEFINE_NULL_MACRO - to let zpl define what NULL stands for in case it is undefined. + ZPL_NO_MATH_H - disables the use of math.h library and replaces it with custom routines or SIMD. + ZPL_HEAP_ANALYSIS - enables heap allocator analysis tools + ZPL_PARSER_DISABLE_ANALYSIS - disables the extra parsing logic that would collect more information about node's formatting and structure. + this is useful in scenarios where a raw parsing performance is preferred over a more complex analysis. + It is not recommended to serialise data back since we lack the extra information about the original source document. + +GitHub: + https://github.com/zpl-c/zpl + +Version History: + 19.5.0 - math: implemented hypot(x) function (Arin-Grigoras) + 19.4.1 - file: zpl_file_read_contents now ignores folders. + 19.4.0 - json: introduce support for ZPL_JSON_INDENT_STYLE_COMPACT output (rheatley-pervasid) + - fix SJSON value parse not detecting EOF correctly when analysing delimiter used. + example: "foo=123,bar=456" would produce 1 extra garbage field. + 19.3.1 - adt: zpl_adt_parse_number exit early on false hex value assumption + 19.3.0 - socket: new socket api cross-platform layer + 19.2.0 - file: add macros for standard i/o wrappers (ZPL_STDIO_IN, ...) + 19.1.1 - thread: return error values for POSIX calls + 19.1.0 - parser: introduce a new URI parser module + 19.0.4 - fix: zpl_buffer_copy_init missing macros (thanks Ed_ on Discord) + 19.0.3 - fix: zpl_str_skip_literal reading before start of string (mbenniston) + 19.0.2 - fixed ZPL_ISIZE_MIN and MAX for 32-bit architectures (mrossetti) + 19.0.1 - Fixed zpl_array_fill ZPL_ASSERT off-by-one error + 19.0.0 - Check all results of zpl_alloc() when using JSON parser/writer (rheatley-pervasid) + + 18.1.5 - set parent to parsed JSON nodes (fixed) + - fix zpl_json/csv_write_string off-by-one issue + 18.1.4 - fix zpl_random_gen_isize/zpl_random_range_isize 32bit overflow + 18.1.3 - set parent to parsed JSON nodes + 18.1.2 - fix zpl sort procs + 18.1.1 - updated table _clear method + 18.1.0 - added table _clear method + 18.0.4 - fix memory arena alignment & added tests + 18.0.3 - fix emscripten support + 18.0.2 - fix global-buffer-overflow in print module + - raise ZPL_PRINTF_MAXLEN to 64kb + 18.0.1 - fix ADT parser wrongly assuming that an IP address is a real number + 18.0.0 - removed coroutines module + - removed timer module + - rename zpl_adt_get -> zpl_adt_query + + 17.0.0 - ADT API changes + zpl_adt_inset_* -> zpl_adt_append_* + zpl_adt_node now holds a parent field, methods no longer require a pointer to the parent + methods are now documented + - add zpl_thread_init_nowait (gaopeng) + + 16.1.1 - fix scientific notation parsing + 16.1.0 - introduce ZPL_PARSER_DISABLE_ANALYSIS that disables extra parsing capabilities to offer better raw performance + at a cost of lack of node metadata. + 16.0.0 - introduce a new zpl_adt_query method for flexible data retrieval + "a/b/c" navigates through objects "a" and "b" to get to "c" + "arr/[foo=123]/bar" iterates over "arr" to find any object with param "foo" that matches the value "123", then gets its field called "bar" + "arr/3" retrieves the 4th element in "arr" + "arr/[apple]" retrieves the first element of value "apple" in "arr" + - fix memory leak when parsing a json array (gaopeng) + - add zpl_strntok (gaopeng) + - add zpl_semaphore_trywait (gaopeng) + + 15.0.3 - fix zpl_sign call in math failing to compile + on macos devices + 15.0.2 - zpl_sign0 was introduced + 15.0.1 - hashtable performance improvements + - zpl_sign(0) returns 0 + 15.0.0 - Rework zpl ring buffer + - various code improvements + + 14.1.7 - fix zpl_random_range_i64 + - set thread's is_running before we start a thread + 14.1.6 - remove windows.h dependency for header part + 14.1.5 - fix array append_at + 14.1.4 - Fix win32 missing CRITICAL_SECTION definition if + - ZPL_NO_WINDOWS_H is defined + 14.1.0 - add hashtable map_mut method + 14.0.1 - fix zpl_array_remove_at boundary bug + 14.0.0 - heap memory allocator analysis + + 13.4.1 - adt optimizations + 13.4.0 - new adt manipulation methods + 13.3.3 - fix zpl_str_skip_literal bug + 13.3.2 - escape strings in parser output + 13.3.1 - number parsing improvements + 13.3.0 - csv parse numbers + 13.2.0 - hashtable _map function + 13.1.5 - ZPL_DEBUG_TRAP for tcc + 13.1.4 - potential csv ub fix + 13.1.3 - tcc support improvements + 13.1.2 - fix ast -> adt filename + 13.1.1 - fix emscripten support + 13.1.0 - abstract data tree naming update + 13.0.0 - text parsers refactor + + 12.8.0 - zpl_opts improvements + 12.7.0 - math improvements + 12.6.2 - remove register usage (BeastLe9enD) + 12.6.1 - improve tar null padding code + 12.6.0 - introduce zpl_align_forward_u64/i64 + 12.5.1 - small type casting fixes + 12.5.0 - add zpl_asprintf + 12.4.0 - zpl_printf improvements + 12.3.2 - allow zpl_path_dirlist to include symlinks, but don't enter them + 12.3.1 - avoid symbolic link cycle in zpl_path_dirlist + 12.3.0 - add TAR archiving support + 12.2.1 - fix zpl_random_gen_f64 + 12.2.0 - Add zpl_array_fill and zpl_array_appendv_at + 12.1.0 - Add rectangle partitioning + 12.0.1 - Optimize zpl_strlen + 12.0.0 - JSON API revamp + improvements + + 11.3.0 - JSON zpl_json_str_to_flt + cleanup + 11.2.5 - fix small atomics typo + 11.2.4 - JSON rewrite core parser + 11.2.2 - JSON rewrite comment handling + 11.2.1 - JSON zero-initialise node + 11.2.0 - JSON API improvements + 11.1.2 - Improve atomics + 11.1.1 - Fix zpl_json_write_string providing incorrect length + 11.1.0 - Introduce new ZPL_PICO distro + 11.0.11 - remove stdatomic.h include + 11.0.10 - get rid of c11 atomics lib + 11.0.9 - stringlib uses ZPL_PRINTF_MAXLEN now + - zpl_printf family is now thread-safe + 11.0.7 - Add ZPL_PRINTF_MAXLEN + 11.0.6 - Fix zpl_printf left padding bug + 11.0.4 - Disable ZPL_NO_MATH_H on TinyC + 11.0.3 - Added support for TinyC compiler + 11.0.2 - Fixes for Apple M1 chip + 11.0.0 - New jobs system + - Rewrite the timer module + - zpl_ring rework + + 10.13.0 - Initial ARM threading support + 10.12.1 - Fix missing zpL_alloc_str + 10.12.0 - Add zpl_crc64 + 10.11.1 - Fix zpl_time_utc_ms on 32-bit OSes + 10.11.0 - Added zpl_file_stream_buf + 10.10.3 - Math type-punning fixes + 10.10.1 - Fix memory writing issue + new write-only in-situ flag + 10.10.0 - Implement memory streaming API + 10.9.1 - Support ARMv6, ARMv7 and ARMv8-a builds + 10.9.0 - Improve the time API + 10.8.3 - zpl_file_close tempfile Windows fixes + 10.8.2 - zpl_file_temp disallow some operations + 10.8.1 - zpl_file_temp Windows fixes + 10.8.0 - Implemented zpl_json_write_string + 10.7.1 - Fix zpl_file_temp platform bug + 10.7.0 - Add zpl_file_write_contents + 10.6.6 - Fix type mismatch in Jobs system + 10.6.0 - Remove event system + 10.5.8 - Remove zpl__memcpy_4byte + 10.5.7 - zpl_file_new is now OS-agnostic constructor + 10.5.6 - Fix coroutine creation + 10.5.5 - Jobs system uses zpl_f32 for priority setting + 10.5.4 - zpl_buffer_free no longer takes the 2nd argument (allocator) + 10.5.3 - Removed crc64 and annotated some hashing methods + 10.5.2 - Don't expose ZPL types anymore + 10.5.1 - Fixed zpl_rdtsc for Emscripten + 10.5.0 - Changed casts to memcopy in random methods, added embed cmd + 10.4.1 - Jobs system now enqueues jobs with def priority of 1.0 + 10.4.0 - [META] version bump + 10.3.0 - Pool allocator now supports zpl_free_all + 10.2.0 - [META] version bump + 10.1.0 - Additional math methods (thanks to funZX and msmshazan) + 10.0.15 - WIP Emscripten fixes + 10.0.14 - FreeBSD support + 10.0.13 - OpenBSD support + 10.0.12 - Cygwin fixes + 10.0.11 - Tweak module dependencies + 10.0.10 - Fix zero-allocation regression in filesystem module + 10.0.9 - Fix multi-compilation unit builds + 10.0.8 - Fix zpl_printf "%0d" format specifier + 10.0.4 - Flush tester output to fix ordering + 10.0.3 - Fix ZPL_STATIC_ASSERT under MSVC + 10.0.0 - Major overhaul of the library + + 9.8.10 - JSON fix array-based documents with objects + 9.8.9 - JSON document structured as array now properly recognizes the root object as array. + 9.8.8 - Fixed an incorrect parsing of empty array nodes. + 9.8.7 - Improve FreeBSD support + 9.8.6 - WIP: Handle inlined methods properly + 9.8.5 - Fix incorrect usage of EOF and opts dependency on JSON5 module's methods + 9.8.4 - Fix MSVC ZPL_NO_MATH_H code branch using incorrect methods internally + 9.8.3 - Fix MinGW GCC related issue with zpl_printf %lld format + 9.8.2 - Fix VS C4190 issue + 9.8.1 - Fix several C++ type casting quirks + 9.8.0 - Incorporated OpenGL into ZPL core as an optional module + 9.7.0 - Added co-routine module + 9.6.0 - Added process module for creation and manipulation + 9.5.2 - zpl_printf family now prints (null) on NULL string arguments + 9.5.1 - Fixed JSON5 real number export support + indentation fixes + 9.5.0 - Added base64 encode/decode methods + 9.4.10- Small enum style changes + 9.4.9 - Remove #undef for cast and hard_cast (sorry) + 9.4.8 - Fix quote-less JSON node name resolution + 9.4.7 - Additional change to the code + 9.4.6 - Fix issue where zpl_json_find would have false match on substrings + 9.4.5 - Mistakes were made, fixed compilation errors + 9.4.3 - Fix old API shenanigans + 9.4.2 - Fix small API typos + 9.4.1 - Reordered JSON5 constants to integrate better with conditions + 9.4.0 - JSON5 API changes made to zpl_json_find + 9.3.0 - Change how zpl uses basic types internally + 9.2.0 - Directory listing was added. Check dirlist_api.c test for more info + 9.1.1 - Fix WIN32_LEAN_AND_MEAN redefinition properly + 9.1.0 - get_env rework and fixes + 9.0.3 - Small fixes and removals + 9.0.0 - New documentation format, removed deprecated code, changed styles + + 8.14.1 - Fix string library + 8.14.0 - Added zpl_re_match_all + 8.13.0 - Update system command API + 8.12.6 - Fix warning in CLI options parser + 8.12.5 - Support parametric options preceding positionals + 8.12.4 - Fixed opts positionals ordering + 8.12.3 - Fixed incorrect handling of flags preceding positionals + 8.12.2 - JSON parsing remark added + 8.12.1 - Fixed a lot of important stuff + 8.12.0 - Added helper constructors for containers + 8.11.2 - Fix bug in opts module + 8.11.1 - Small code improvements + 8.11.0 - Ported regex processor from https://github.com/gingerBill/gb/ and applied fixes on top of it + 8.10.2 - Fix zpl_strtok + 8.10.1 - Replace zpl_strchr by zpl_char_last_occurence + 8.10.0 - Added zpl_strchr + 8.9.0 - API improvements for JSON5 parser + 8.8.4 - Add support for SJSON formatting http://bitsquid.blogspot.com/2009/10/simplified-json-notation.html + + 6.8.3 - JSON5 exp fix + 6.8.2 - Bugfixes applied from gb + 6.8.1 - Performance improvements for JSON5 parser + 6.8.0 - zpl.h is now generated by build.py + 6.7.0 - Several fixes and added switches + 6.6.0 - Several significant changes made to the repository + 6.5.0 - Ported platform layer from https://github.com/gingerBill/gb/ + 6.4.1 - Use zpl_strlen in zpl_strdup + 6.4.0 - Deprecated zpl_buffer_free and added zpl_array_end, zpl_buffer_end + 6.3.0 - Added zpl_strdup + 6.2.1 - Remove math redundancies + 6.2.0 - Integrated zpl_math.h into zpl.h + 6.1.1 - Added direct.h include for win c++ dir methods + 6.1.0 - Added zpl_path_mkdir, zpl_path_rmdir, and few new zplFileErrors + 6.0.4 - More MSVC(++) satisfaction by fixing warnings + 6.0.3 - Satisfy MSVC by fixing a warning + 6.0.2 - Fixed warnings for json5 i64 printfs + 6.0.1 - Fixed warnings for particual win compiler in dirlist method + 6.0.0 - New build, include/ was renamed to code/ + + 5.8.3 - Naming fixes + 5.8.2 - Job system now supports prioritized tasks + 5.8.1 - Renames zpl_pad to zpl_ring + 5.8.0 - Added instantiated scratch pad (circular buffer) + 5.7.2 - Added Windows support for zpl_path_dirlist + 5.7.1 - Fixed few things in job system + macOS support for zpl_path_dirlist + 5.7.0 - Added a job system (zpl_thread_pool) + 5.6.5 - Fixes extra error cases for zpl_opts when input is: + - missing a value for an option, + - having an extra value for a flag (e.g. --enable-log shouldn't get a value.) + 5.6.4 - Several tweaks to the zpl_opts API + 5.6.3 - Added support for flags without values + 5.6.2 - Improve error handling for zpl_opts + 5.6.1 - Added support for strings with spaces in zpl_opts + 5.6.0 - Added zpl_opts for CLI argument parsing + 5.5.1 - Fixed time method for win + 5.5.0 - Integrate JSON5 writer into the core + 5.4.0 - Improved storage support for numbers in JSON5 parser + 5.3.0 - Integrated zpl_json into ZPL + 5.2.0 - Added zpl_string_sprintf + 5.1.1 - Added zpl_system_command_nores for output-less execution + 5.1.0 - Added event handler + 5.0.4 - Fix alias for zpl_list + 5.0.3 - Finalizing syntax changes + 5.0.2 - Fix segfault when using zpl_stack_memory + 5.0.1 - Small code improvements + 5.0.0 - Project structure changes + + 4.7.2 - Got rid of size arg for zpl_str_split_lines + 4.7.1 - Added an example + 4.7.0 - Added zpl_path_dirlist + 4.6.1 - zpl_memcopy x86 patch from upstream + 4.6.0 - Added few string-related functions + 4.5.9 - Error fixes + 4.5.8 - Warning fixes + 4.5.7 - Fixed timer loops. zpl_time* related functions work with seconds now + 4.5.6 - Fixed zpl_time_now() for Windows and Linux + 4.5.5 - Small cosmetic changes + 4.5.4 - Fixed issue when zpl_list_add would break the links + - when adding a new item between nodes + 4.5.3 - Fixed malformed enum values + 4.5.1 - Fixed some warnings + 4.5.0 - Added zpl_array_append_at + 4.4.0 - Added zpl_array_back, zpl_array_front + 4.3.0 - Added zpl_list + 4.2.0 - Added zpl_system_command_str + 4.1.2 - GG, fixed small compilation error + 4.1.1 - Fixed possible security issue in zpl_system_command + 4.1.0 - Added zpl_string_make_reserve and small fixes + 4.0.2 - Warning fix for _LARGEFILE64_SOURCE + 4.0.1 - include stdlib.h for getenv (temp) + 4.0.0 - ARM support, coding style changes and various improvements + + 3.4.1 - zpl_memcopy now uses memcpy for ARM arch-family + 3.4.0 - Removed obsolete code + 3.3.4 - Added Travis CI config + 3.3.3 - Small macro formatting changes + ZPL_SYSTEM_IOS + 3.3.2 - Fixes for android arm + 3.3.1 - Fixed some type cast warnings + 3.3.0 - Added Android support + 3.1.5 - Renamed userptr to user_data in timer + 3.1.4 - Fix for zpl_buffer not allocating correctly + 3.1.2 - Small fix in zpl_memcompare + 3.1.1 - Added char* conversion for data field in zpl_array_header + 3.1.0 - Added data field to zpl_array_header + 3.0.7 - Added timer userptr as argument to callback + 3.0.6 - Small changes + 3.0.5 - Fixed compilation for emscripten + 3.0.4 - Small fixes for tiny cpp warnings + 3.0.3 - Small fixes for various cpp warnings and errors + 3.0.2 - Fixed linux part, and removed trailing spaces + 3.0.1 - Small bugfix in zpl_file_open + 3.0.0 - Added several fixes and features + + 2.4.0 - Added remove to hash table + 2.3.3 - Removed redundant code + 2.3.2 - Eliminated extra warnings + 2.3.1 - Warning hunt + 2.3.0 - Added the ability to copy array/buffer and fixed bug in hash table. + 2.2.1 - Used tmpfile() for Windows + 2.2.0 - Added zpl_file_temp + 2.1.1 - Very small fix (forgive me) + 2.1.0 - Added the ability to resize bitstream + 2.0.8 - Small adjustments + 2.0.7 - MinGW related fixes + 2.0.0 - New NPM based version + + 1.2.2 - Small fix + 1.2.1 - Macro fixes + 1.2.0 - Added zpl_async macro + 1.1.0 - Added timer feature + 1.0.0 - Initial version + + +License: + This Software is dual licensed under the following licenses: + + Unlicense + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to + + BSD 3-Clause + Copyright (c) 2016-2021 Dominik Madarász. 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. + +*/ + +#ifndef ZPL_H +#define ZPL_H + +#define ZPL_VERSION_MAJOR 19 +#define ZPL_VERSION_MINOR 5 +#define ZPL_VERSION_PATCH 0 +#define ZPL_VERSION_PRE "" + + // file: zpl_hedley.h + + /* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * For details, see . + * SPDX-License-Identifier: CC0-1.0 + */ + + #if !defined(ZPL_HEDLEY_VERSION) || (ZPL_HEDLEY_VERSION < 12) + #if defined(ZPL_HEDLEY_VERSION) + # undef ZPL_HEDLEY_VERSION + #endif + #define ZPL_HEDLEY_VERSION 12 + + #if defined(ZPL_STRINGIFY_EX) + # undef ZPL_STRINGIFY_EX + #endif + #define ZPL_STRINGIFY_EX(x) #x + + #if defined(ZPL_STRINGIFY) + # undef ZPL_STRINGIFY + #endif + #define ZPL_STRINGIFY(x) ZPL_STRINGIFY_EX(x) + + #if defined(ZPL_CONCAT_EX) + # undef ZPL_CONCAT_EX + #endif + #define ZPL_CONCAT_EX(a,b) a##b + + #if defined(ZPL_CONCAT) + # undef ZPL_CONCAT + #endif + #define ZPL_CONCAT(a,b) ZPL_CONCAT_EX(a,b) + + #if defined(ZPL_VERSION_ENCODE) + # undef ZPL_VERSION_ENCODE + #endif + #define ZPL_VERSION_ENCODE(major,minor,patch) (((major) * 1000000) + ((minor) * 1000) + (patch)) + + #if defined(ZPL_VERSION_DECODE_MAJOR) + # undef ZPL_VERSION_DECODE_MAJOR + #endif + #define ZPL_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + + #if defined(ZPL_VERSION_DECODE_MINOR) + # undef ZPL_VERSION_DECODE_MINOR + #endif + #define ZPL_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + + #if defined(ZPL_VERSION_DECODE_PATCH) + # undef ZPL_VERSION_DECODE_PATCH + #endif + #define ZPL_VERSION_DECODE_PATCH(version) ((version) % 1000) + + #if defined(ZPL_VERSION_CHECK) + # undef ZPL_VERSION_CHECK + #endif + #define ZPL_VERSION_CHECK(major,minor,patch) (ZPL_VERSION_ENCODE(major,minor,patch) <= ZPL_VERSION) + + #if defined(ZPL_GNUC_VERSION) + # undef ZPL_GNUC_VERSION + #endif + #if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + # define ZPL_GNUC_VERSION ZPL_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) + #elif defined(__GNUC__) + # define ZPL_GNUC_VERSION ZPL_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) + #endif + + #if defined(ZPL_GNUC_VERSION_CHECK) + # undef ZPL_GNUC_VERSION_CHECK + #endif + #if defined(ZPL_GNUC_VERSION) + # define ZPL_GNUC_VERSION_CHECK(major,minor,patch) (ZPL_GNUC_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_GNUC_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_MSVC_VERSION) + # undef ZPL_MSVC_VERSION + #endif + #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) + # define ZPL_MSVC_VERSION ZPL_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) + #elif defined(_MSC_FULL_VER) + # define ZPL_MSVC_VERSION ZPL_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) + #elif defined(_MSC_VER) + # define ZPL_MSVC_VERSION ZPL_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) + #endif + + #if defined(ZPL_MSVC_VERSION_CHECK) + # undef ZPL_MSVC_VERSION_CHECK + #endif + #if !defined(_MSC_VER) + # define ZPL_MSVC_VERSION_CHECK(major,minor,patch) (0) + #elif defined(_MSC_VER) && (_MSC_VER >= 1400) + # define ZPL_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) + #elif defined(_MSC_VER) && (_MSC_VER >= 1200) + # define ZPL_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) + #else + # define ZPL_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) + #endif + + #if defined(ZPL_INTEL_VERSION) + # undef ZPL_INTEL_VERSION + #endif + #if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) + # define ZPL_INTEL_VERSION ZPL_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) + #elif defined(__INTEL_COMPILER) + # define ZPL_INTEL_VERSION ZPL_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) + #endif + + #if defined(ZPL_INTEL_VERSION_CHECK) + # undef ZPL_INTEL_VERSION_CHECK + #endif + #if defined(ZPL_INTEL_VERSION) + # define ZPL_INTEL_VERSION_CHECK(major,minor,patch) (ZPL_INTEL_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_INTEL_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_PGI_VERSION) + # undef ZPL_PGI_VERSION + #endif + #if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + # define ZPL_PGI_VERSION ZPL_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) + #endif + + #if defined(ZPL_PGI_VERSION_CHECK) + # undef ZPL_PGI_VERSION_CHECK + #endif + #if defined(ZPL_PGI_VERSION) + # define ZPL_PGI_VERSION_CHECK(major,minor,patch) (ZPL_PGI_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_PGI_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_SUNPRO_VERSION) + # undef ZPL_SUNPRO_VERSION + #endif + #if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + # define ZPL_SUNPRO_VERSION ZPL_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) + #elif defined(__SUNPRO_C) + # define ZPL_SUNPRO_VERSION ZPL_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) + #elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + # define ZPL_SUNPRO_VERSION ZPL_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) + #elif defined(__SUNPRO_CC) + # define ZPL_SUNPRO_VERSION ZPL_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) + #endif + + #if defined(ZPL_SUNPRO_VERSION_CHECK) + # undef ZPL_SUNPRO_VERSION_CHECK + #endif + #if defined(ZPL_SUNPRO_VERSION) + # define ZPL_SUNPRO_VERSION_CHECK(major,minor,patch) (ZPL_SUNPRO_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_SUNPRO_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_EMSCRIPTEN_VERSION) + # undef ZPL_EMSCRIPTEN_VERSION + #endif + #if defined(__EMSCRIPTEN__) + # define ZPL_EMSCRIPTEN_VERSION ZPL_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) + #endif + + #if defined(ZPL_EMSCRIPTEN_VERSION_CHECK) + # undef ZPL_EMSCRIPTEN_VERSION_CHECK + #endif + #if defined(ZPL_EMSCRIPTEN_VERSION) + # define ZPL_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (ZPL_EMSCRIPTEN_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_ARM_VERSION) + # undef ZPL_ARM_VERSION + #endif + #if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + # define ZPL_ARM_VERSION ZPL_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) + #elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + # define ZPL_ARM_VERSION ZPL_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) + #endif + + #if defined(ZPL_ARM_VERSION_CHECK) + # undef ZPL_ARM_VERSION_CHECK + #endif + #if defined(ZPL_ARM_VERSION) + # define ZPL_ARM_VERSION_CHECK(major,minor,patch) (ZPL_ARM_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_ARM_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_IBM_VERSION) + # undef ZPL_IBM_VERSION + #endif + #if defined(__ibmxl__) + # define ZPL_IBM_VERSION ZPL_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) + #elif defined(__xlC__) && defined(__xlC_ver__) + # define ZPL_IBM_VERSION ZPL_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) + #elif defined(__xlC__) + # define ZPL_IBM_VERSION ZPL_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) + #endif + + #if defined(ZPL_IBM_VERSION_CHECK) + # undef ZPL_IBM_VERSION_CHECK + #endif + #if defined(ZPL_IBM_VERSION) + # define ZPL_IBM_VERSION_CHECK(major,minor,patch) (ZPL_IBM_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_IBM_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_VERSION) + # undef ZPL_TI_VERSION + #endif + #if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) + # if (__TI_COMPILER_VERSION__ >= 16000000) + # define ZPL_TI_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + # endif + #endif + + #if defined(ZPL_TI_VERSION_CHECK) + # undef ZPL_TI_VERSION_CHECK + #endif + #if defined(ZPL_TI_VERSION) + # define ZPL_TI_VERSION_CHECK(major,minor,patch) (ZPL_TI_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_CL2000_VERSION) + # undef ZPL_TI_CL2000_VERSION + #endif + #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + # define ZPL_TI_CL2000_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + #endif + + #if defined(ZPL_TI_CL2000_VERSION_CHECK) + # undef ZPL_TI_CL2000_VERSION_CHECK + #endif + #if defined(ZPL_TI_CL2000_VERSION) + # define ZPL_TI_CL2000_VERSION_CHECK(major,minor,patch) (ZPL_TI_CL2000_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_CL430_VERSION) + # undef ZPL_TI_CL430_VERSION + #endif + #if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + # define ZPL_TI_CL430_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + #endif + + #if defined(ZPL_TI_CL430_VERSION_CHECK) + # undef ZPL_TI_CL430_VERSION_CHECK + #endif + #if defined(ZPL_TI_CL430_VERSION) + # define ZPL_TI_CL430_VERSION_CHECK(major,minor,patch) (ZPL_TI_CL430_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_CL430_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_ARMCL_VERSION) + # undef ZPL_TI_ARMCL_VERSION + #endif + #if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + # define ZPL_TI_ARMCL_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + #endif + + #if defined(ZPL_TI_ARMCL_VERSION_CHECK) + # undef ZPL_TI_ARMCL_VERSION_CHECK + #endif + #if defined(ZPL_TI_ARMCL_VERSION) + # define ZPL_TI_ARMCL_VERSION_CHECK(major,minor,patch) (ZPL_TI_ARMCL_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_CL6X_VERSION) + # undef ZPL_TI_CL6X_VERSION + #endif + #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + # define ZPL_TI_CL6X_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + #endif + + #if defined(ZPL_TI_CL6X_VERSION_CHECK) + # undef ZPL_TI_CL6X_VERSION_CHECK + #endif + #if defined(ZPL_TI_CL6X_VERSION) + # define ZPL_TI_CL6X_VERSION_CHECK(major,minor,patch) (ZPL_TI_CL6X_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_CL7X_VERSION) + # undef ZPL_TI_CL7X_VERSION + #endif + #if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + # define ZPL_TI_CL7X_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + #endif + + #if defined(ZPL_TI_CL7X_VERSION_CHECK) + # undef ZPL_TI_CL7X_VERSION_CHECK + #endif + #if defined(ZPL_TI_CL7X_VERSION) + # define ZPL_TI_CL7X_VERSION_CHECK(major,minor,patch) (ZPL_TI_CL7X_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TI_CLPRU_VERSION) + # undef ZPL_TI_CLPRU_VERSION + #endif + #if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + # define ZPL_TI_CLPRU_VERSION ZPL_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) + #endif + + #if defined(ZPL_TI_CLPRU_VERSION_CHECK) + # undef ZPL_TI_CLPRU_VERSION_CHECK + #endif + #if defined(ZPL_TI_CLPRU_VERSION) + # define ZPL_TI_CLPRU_VERSION_CHECK(major,minor,patch) (ZPL_TI_CLPRU_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_CRAY_VERSION) + # undef ZPL_CRAY_VERSION + #endif + #if defined(_CRAYC) + # if defined(_RELEASE_PATCHLEVEL) + # define ZPL_CRAY_VERSION ZPL_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + # else + # define ZPL_CRAY_VERSION ZPL_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + # endif + #endif + + #if defined(ZPL_CRAY_VERSION_CHECK) + # undef ZPL_CRAY_VERSION_CHECK + #endif + #if defined(ZPL_CRAY_VERSION) + # define ZPL_CRAY_VERSION_CHECK(major,minor,patch) (ZPL_CRAY_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_CRAY_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_IAR_VERSION) + # undef ZPL_IAR_VERSION + #endif + #if defined(__IAR_SYSTEMS_ICC__) + # if __VER__ > 1000 + # define ZPL_IAR_VERSION ZPL_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + # else + # define ZPL_IAR_VERSION ZPL_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) + # endif + #endif + + #if defined(ZPL_IAR_VERSION_CHECK) + # undef ZPL_IAR_VERSION_CHECK + #endif + #if defined(ZPL_IAR_VERSION) + # define ZPL_IAR_VERSION_CHECK(major,minor,patch) (ZPL_IAR_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_IAR_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_TINYC_VERSION) + # undef ZPL_TINYC_VERSION + #endif + #if defined(__TINYC__) + # define ZPL_TINYC_VERSION ZPL_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) + #endif + + #if defined(ZPL_TINYC_VERSION_CHECK) + # undef ZPL_TINYC_VERSION_CHECK + #endif + #if defined(ZPL_TINYC_VERSION) + # define ZPL_TINYC_VERSION_CHECK(major,minor,patch) (ZPL_TINYC_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_TINYC_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_DMC_VERSION) + # undef ZPL_DMC_VERSION + #endif + #if defined(__DMC__) + # define ZPL_DMC_VERSION ZPL_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) + #endif + + #if defined(ZPL_DMC_VERSION_CHECK) + # undef ZPL_DMC_VERSION_CHECK + #endif + #if defined(ZPL_DMC_VERSION) + # define ZPL_DMC_VERSION_CHECK(major,minor,patch) (ZPL_DMC_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_DMC_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_COMPCERT_VERSION) + # undef ZPL_COMPCERT_VERSION + #endif + #if defined(__COMPCERT_VERSION__) + # define ZPL_COMPCERT_VERSION ZPL_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) + #endif + + #if defined(ZPL_COMPCERT_VERSION_CHECK) + # undef ZPL_COMPCERT_VERSION_CHECK + #endif + #if defined(ZPL_COMPCERT_VERSION) + # define ZPL_COMPCERT_VERSION_CHECK(major,minor,patch) (ZPL_COMPCERT_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_COMPCERT_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_PELLES_VERSION) + # undef ZPL_PELLES_VERSION + #endif + #if defined(__POCC__) + # define ZPL_PELLES_VERSION ZPL_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) + #endif + + #if defined(ZPL_PELLES_VERSION_CHECK) + # undef ZPL_PELLES_VERSION_CHECK + #endif + #if defined(ZPL_PELLES_VERSION) + # define ZPL_PELLES_VERSION_CHECK(major,minor,patch) (ZPL_PELLES_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_PELLES_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_GCC_VERSION) + # undef ZPL_GCC_VERSION + #endif + #if \ + defined(ZPL_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(ZPL_INTEL_VERSION) && \ + !defined(ZPL_PGI_VERSION) && \ + !defined(ZPL_ARM_VERSION) && \ + !defined(ZPL_TI_VERSION) && \ + !defined(ZPL_TI_ARMCL_VERSION) && \ + !defined(ZPL_TI_CL430_VERSION) && \ + !defined(ZPL_TI_CL2000_VERSION) && \ + !defined(ZPL_TI_CL6X_VERSION) && \ + !defined(ZPL_TI_CL7X_VERSION) && \ + !defined(ZPL_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) + # define ZPL_GCC_VERSION ZPL_GNUC_VERSION + #endif + + #if defined(ZPL_GCC_VERSION_CHECK) + # undef ZPL_GCC_VERSION_CHECK + #endif + #if defined(ZPL_GCC_VERSION) + # define ZPL_GCC_VERSION_CHECK(major,minor,patch) (ZPL_GCC_VERSION >= ZPL_VERSION_ENCODE(major, minor, patch)) + #else + # define ZPL_GCC_VERSION_CHECK(major,minor,patch) (0) + #endif + + #if defined(ZPL_HAS_ATTRIBUTE) + # undef ZPL_HAS_ATTRIBUTE + #endif + #if defined(__has_attribute) + # define ZPL_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) + #else + # define ZPL_HAS_ATTRIBUTE(attribute) (0) + #endif + + #if defined(ZPL_GNUC_HAS_ATTRIBUTE) + # undef ZPL_GNUC_HAS_ATTRIBUTE + #endif + #if defined(__has_attribute) + # define ZPL_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) + #else + # define ZPL_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_ATTRIBUTE) + # undef ZPL_GCC_HAS_ATTRIBUTE + #endif + #if defined(__has_attribute) + # define ZPL_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) + #else + # define ZPL_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_HAS_CPP_ATTRIBUTE) + # undef ZPL_HAS_CPP_ATTRIBUTE + #endif + #if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(ZPL_SUNPRO_VERSION) || ZPL_SUNPRO_VERSION_CHECK(5,15,0)) + # define ZPL_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) + #else + # define ZPL_HAS_CPP_ATTRIBUTE(attribute) (0) + #endif + + #if defined(ZPL_HAS_CPP_ATTRIBUTE_NS) + # undef ZPL_HAS_CPP_ATTRIBUTE_NS + #endif + #if !defined(__cplusplus) || !defined(__has_cpp_attribute) + # define ZPL_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) + #elif \ + !defined(ZPL_PGI_VERSION) && \ + !defined(ZPL_IAR_VERSION) && \ + (!defined(ZPL_SUNPRO_VERSION) || ZPL_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(ZPL_MSVC_VERSION) || ZPL_MSVC_VERSION_CHECK(19,20,0)) + # define ZPL_HAS_CPP_ATTRIBUTE_NS(ns,attribute) ZPL_HAS_CPP_ATTRIBUTE(ns::attribute) + #else + # define ZPL_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) + #endif + + #if defined(ZPL_GNUC_HAS_CPP_ATTRIBUTE) + # undef ZPL_GNUC_HAS_CPP_ATTRIBUTE + #endif + #if defined(__has_cpp_attribute) && defined(__cplusplus) + # define ZPL_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) + #else + # define ZPL_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_CPP_ATTRIBUTE) + # undef ZPL_GCC_HAS_CPP_ATTRIBUTE + #endif + #if defined(__has_cpp_attribute) && defined(__cplusplus) + # define ZPL_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) + #else + # define ZPL_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_HAS_BUILTIN) + # undef ZPL_HAS_BUILTIN + #endif + #if defined(__has_builtin) + # define ZPL_HAS_BUILTIN(builtin) __has_builtin(builtin) + #else + # define ZPL_HAS_BUILTIN(builtin) (0) + #endif + + #if defined(ZPL_GNUC_HAS_BUILTIN) + # undef ZPL_GNUC_HAS_BUILTIN + #endif + #if defined(__has_builtin) + # define ZPL_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) + #else + # define ZPL_GNUC_HAS_BUILTIN(builtin,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_BUILTIN) + # undef ZPL_GCC_HAS_BUILTIN + #endif + #if defined(__has_builtin) + # define ZPL_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) + #else + # define ZPL_GCC_HAS_BUILTIN(builtin,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_HAS_FEATURE) + # undef ZPL_HAS_FEATURE + #endif + #if defined(__has_feature) + # define ZPL_HAS_FEATURE(feature) __has_feature(feature) + #else + # define ZPL_HAS_FEATURE(feature) (0) + #endif + + #if defined(ZPL_GNUC_HAS_FEATURE) + # undef ZPL_GNUC_HAS_FEATURE + #endif + #if defined(__has_feature) + # define ZPL_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) + #else + # define ZPL_GNUC_HAS_FEATURE(feature,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_FEATURE) + # undef ZPL_GCC_HAS_FEATURE + #endif + #if defined(__has_feature) + # define ZPL_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) + #else + # define ZPL_GCC_HAS_FEATURE(feature,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_HAS_EXTENSION) + # undef ZPL_HAS_EXTENSION + #endif + #if defined(__has_extension) + # define ZPL_HAS_EXTENSION(extension) __has_extension(extension) + #else + # define ZPL_HAS_EXTENSION(extension) (0) + #endif + + #if defined(ZPL_GNUC_HAS_EXTENSION) + # undef ZPL_GNUC_HAS_EXTENSION + #endif + #if defined(__has_extension) + # define ZPL_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) + #else + # define ZPL_GNUC_HAS_EXTENSION(extension,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_EXTENSION) + # undef ZPL_GCC_HAS_EXTENSION + #endif + #if defined(__has_extension) + # define ZPL_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) + #else + # define ZPL_GCC_HAS_EXTENSION(extension,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_HAS_DECLSPEC_ATTRIBUTE) + # undef ZPL_HAS_DECLSPEC_ATTRIBUTE + #endif + #if defined(__has_declspec_attribute) + # define ZPL_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) + #else + # define ZPL_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) + #endif + + #if defined(ZPL_GNUC_HAS_DECLSPEC_ATTRIBUTE) + # undef ZPL_GNUC_HAS_DECLSPEC_ATTRIBUTE + #endif + #if defined(__has_declspec_attribute) + # define ZPL_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) + #else + # define ZPL_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_DECLSPEC_ATTRIBUTE) + # undef ZPL_GCC_HAS_DECLSPEC_ATTRIBUTE + #endif + #if defined(__has_declspec_attribute) + # define ZPL_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) + #else + # define ZPL_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_HAS_WARNING) + # undef ZPL_HAS_WARNING + #endif + #if defined(__has_warning) + # define ZPL_HAS_WARNING(warning) __has_warning(warning) + #else + # define ZPL_HAS_WARNING(warning) (0) + #endif + + #if defined(ZPL_GNUC_HAS_WARNING) + # undef ZPL_GNUC_HAS_WARNING + #endif + #if defined(__has_warning) + # define ZPL_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) + #else + # define ZPL_GNUC_HAS_WARNING(warning,major,minor,patch) ZPL_GNUC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_GCC_HAS_WARNING) + # undef ZPL_GCC_HAS_WARNING + #endif + #if defined(__has_warning) + # define ZPL_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) + #else + # define ZPL_GCC_HAS_WARNING(warning,major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + /* ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + ZPL INTERNAL USE ONLY. API subject to change without notice. */ + #if defined(ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + # undef ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ + #endif + #if defined(__cplusplus) + # if ZPL_HAS_WARNING("-Wc++98-compat") + # if ZPL_HAS_WARNING("-Wc++17-extensions") + # define ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + ZPL_DIAGNOSTIC_POP + # else + # define ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + ZPL_DIAGNOSTIC_POP + # endif + # endif + #endif + #if !defined(ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + # define ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x + #endif + + #if defined(ZPL_CONST_CAST) + # undef ZPL_CONST_CAST + #endif + #if defined(__cplusplus) + # define ZPL_CONST_CAST(T, expr) (const_cast(expr)) + #elif \ + ZPL_HAS_WARNING("-Wcast-qual") || \ + ZPL_GCC_VERSION_CHECK(4,6,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_CONST_CAST(T, expr) (__extension__ ({ \ + ZPL_DIAGNOSTIC_PUSH \ + ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + ZPL_DIAGNOSTIC_POP \ + })) + #else + # define ZPL_CONST_CAST(T, expr) ((T) (expr)) + #endif + + #if defined(ZPL_REINTERPRET_CAST) + # undef ZPL_REINTERPRET_CAST + #endif + #if defined(__cplusplus) + # define ZPL_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) + #else + # define ZPL_REINTERPRET_CAST(T, expr) ((T) (expr)) + #endif + + #if defined(ZPL_STATIC_CAST) + # undef ZPL_STATIC_CAST + #endif + #if defined(__cplusplus) + # define ZPL_STATIC_CAST(T, expr) (static_cast(expr)) + #else + # define ZPL_STATIC_CAST(T, expr) ((T) (expr)) + #endif + + #if defined(ZPL_CPP_CAST) + # undef ZPL_CPP_CAST + #endif + #if defined(__cplusplus) + # if ZPL_HAS_WARNING("-Wold-style-cast") + # define ZPL_CPP_CAST(T, expr) \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + ZPL_DIAGNOSTIC_POP + # elif ZPL_IAR_VERSION_CHECK(8,3,0) + # define ZPL_CPP_CAST(T, expr) \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + ZPL_DIAGNOSTIC_POP \ + # else + # define ZPL_CPP_CAST(T, expr) ((T) (expr)) + # endif + #else + # define ZPL_CPP_CAST(T, expr) (expr) + #endif + + #if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + ZPL_GCC_VERSION_CHECK(3,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_IAR_VERSION_CHECK(8,0,0) || \ + ZPL_PGI_VERSION_CHECK(18,4,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + ZPL_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + ZPL_TI_CL430_VERSION_CHECK(2,0,1) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,1,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,0,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + ZPL_CRAY_VERSION_CHECK(5,0,0) || \ + ZPL_TINYC_VERSION_CHECK(0,9,17) || \ + ZPL_SUNPRO_VERSION_CHECK(8,0,0) || \ + (ZPL_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + # define ZPL_PRAGMA(value) _Pragma(#value) + #elif ZPL_MSVC_VERSION_CHECK(15,0,0) + # define ZPL_PRAGMA(value) __pragma(value) + #else + # define ZPL_PRAGMA(value) + #endif + + #if defined(ZPL_DIAGNOSTIC_PUSH) + # undef ZPL_DIAGNOSTIC_PUSH + #endif + #if defined(ZPL_DIAGNOSTIC_PUSH_WARNLEVEL) + # undef ZPL_DIAGNOSTIC_PUSH_WARNLEVEL + #endif + #if defined(ZPL_DIAGNOSTIC_POP) + # undef ZPL_DIAGNOSTIC_POP + #endif + #if defined(__clang__) + # define ZPL_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + # define ZPL_DIAGNOSTIC_PUSH_WARNLEVEL(x) + # define ZPL_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") + #elif ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_DIAGNOSTIC_PUSH _Pragma("warning(push)") + # define ZPL_DIAGNOSTIC_PUSH_WARNLEVEL(x) + # define ZPL_DIAGNOSTIC_POP _Pragma("warning(pop)") + #elif ZPL_GCC_VERSION_CHECK(4,6,0) + # define ZPL_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + # define ZPL_DIAGNOSTIC_PUSH_WARNLEVEL(x) + # define ZPL_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + #elif ZPL_MSVC_VERSION_CHECK(15,0,0) + # define ZPL_DIAGNOSTIC_PUSH __pragma(warning(push)) + # define ZPL_DIAGNOSTIC_PUSH_WARNLEVEL(x) __pragma(warning(push, x)) + # define ZPL_DIAGNOSTIC_POP __pragma(warning(pop)) + #elif ZPL_ARM_VERSION_CHECK(5,6,0) + # define ZPL_DIAGNOSTIC_PUSH _Pragma("push") + # define ZPL_DIAGNOSTIC_POP _Pragma("pop") + #elif \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + ZPL_TI_CL430_VERSION_CHECK(4,4,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(8,1,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_DIAGNOSTIC_PUSH _Pragma("diag_push") + # define ZPL_DIAGNOSTIC_POP _Pragma("diag_pop") + #elif ZPL_PELLES_VERSION_CHECK(2,90,0) + # define ZPL_DIAGNOSTIC_PUSH _Pragma("warning(push)") + # define ZPL_DIAGNOSTIC_POP _Pragma("warning(pop)") + #else + # define ZPL_DIAGNOSTIC_PUSH + # define ZPL_DIAGNOSTIC_POP + #endif + + #if defined(ZPL_DIAGNOSTIC_DISABLE_DEPRECATED) + # undef ZPL_DIAGNOSTIC_DISABLE_DEPRECATED + #endif + #if ZPL_HAS_WARNING("-Wdeprecated-declarations") + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") + #elif ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") + #elif ZPL_PGI_VERSION_CHECK(17,10,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") + #elif ZPL_GCC_VERSION_CHECK(4,3,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + #elif ZPL_MSVC_VERSION_CHECK(15,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) + #elif \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") + #elif ZPL_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") + #elif ZPL_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") + #elif ZPL_PELLES_VERSION_CHECK(2,90,0) + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") + #else + # define ZPL_DIAGNOSTIC_DISABLE_DEPRECATED + #endif + + #if defined(ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + # undef ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS + #endif + #if ZPL_HAS_WARNING("-Wunknown-pragmas") + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") + #elif ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") + #elif ZPL_PGI_VERSION_CHECK(17,10,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") + #elif ZPL_GCC_VERSION_CHECK(4,3,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") + #elif ZPL_MSVC_VERSION_CHECK(15,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) + #elif \ + ZPL_TI_VERSION_CHECK(16,9,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(8,0,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,3,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") + #elif ZPL_TI_CL6X_VERSION_CHECK(8,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") + #else + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS + #endif + + #if defined(ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + # undef ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES + #endif + #if ZPL_HAS_WARNING("-Wunknown-attributes") + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") + #elif ZPL_GCC_VERSION_CHECK(4,6,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + #elif ZPL_INTEL_VERSION_CHECK(17,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") + #elif ZPL_MSVC_VERSION_CHECK(19,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) + #elif ZPL_PGI_VERSION_CHECK(17,10,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") + #elif ZPL_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") + #elif \ + ZPL_TI_VERSION_CHECK(18,1,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(8,3,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") + #else + # define ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES + #endif + + #if defined(ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL) + # undef ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL + #endif + #if ZPL_HAS_WARNING("-Wcast-qual") + # define ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") + #elif ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") + #elif ZPL_GCC_VERSION_CHECK(3,0,0) + # define ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") + #else + # define ZPL_DIAGNOSTIC_DISABLE_CAST_QUAL + #endif + + #if defined(ZPL_DEPRECATED) + # undef ZPL_DEPRECATED + #endif + #if defined(ZPL_DEPRECATED_FOR) + # undef ZPL_DEPRECATED_FOR + #endif + #if defined(__cplusplus) && (__cplusplus >= 201402L) + # define ZPL_DEPRECATED(since) ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + # define ZPL_DEPRECATED_FOR(since, replacement) ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) + #elif \ + ZPL_HAS_EXTENSION(attribute_deprecated_with_message) || \ + ZPL_GCC_VERSION_CHECK(4,5,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_ARM_VERSION_CHECK(5,6,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,13,0) || \ + ZPL_PGI_VERSION_CHECK(17,10,0) || \ + ZPL_TI_VERSION_CHECK(18,1,0) || \ + ZPL_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(8,3,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,3,0) + # define ZPL_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + # define ZPL_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) + #elif \ + ZPL_HAS_ATTRIBUTE(deprecated) || \ + ZPL_GCC_VERSION_CHECK(3,1,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_DEPRECATED(since) __attribute__((__deprecated__)) + # define ZPL_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) + #elif ZPL_MSVC_VERSION_CHECK(14,0,0) + # define ZPL_DEPRECATED(since) __declspec(deprecated("Since " # since)) + # define ZPL_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) + #elif \ + ZPL_MSVC_VERSION_CHECK(13,10,0) || \ + ZPL_PELLES_VERSION_CHECK(6,50,0) + # define ZPL_DEPRECATED(since) __declspec(deprecated) + # define ZPL_DEPRECATED_FOR(since, replacement) __declspec(deprecated) + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_DEPRECATED(since) _Pragma("deprecated") + # define ZPL_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") + #else + # define ZPL_DEPRECATED(since) + # define ZPL_DEPRECATED_FOR(since, replacement) + #endif + + #if defined(ZPL_UNAVAILABLE) + # undef ZPL_UNAVAILABLE + #endif + #if \ + ZPL_HAS_ATTRIBUTE(warning) || \ + ZPL_GCC_VERSION_CHECK(4,3,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) + #else + # define ZPL_UNAVAILABLE(available_since) + #endif + + #if defined(ZPL_WARN_UNUSED_RESULT) + # undef ZPL_WARN_UNUSED_RESULT + #endif + #if defined(ZPL_WARN_UNUSED_RESULT_MSG) + # undef ZPL_WARN_UNUSED_RESULT_MSG + #endif + #if (ZPL_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + # define ZPL_WARN_UNUSED_RESULT ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + # define ZPL_WARN_UNUSED_RESULT_MSG(msg) ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) + #elif ZPL_HAS_CPP_ATTRIBUTE(nodiscard) + # define ZPL_WARN_UNUSED_RESULT ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + # define ZPL_WARN_UNUSED_RESULT_MSG(msg) ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #elif \ + ZPL_HAS_ATTRIBUTE(warn_unused_result) || \ + ZPL_GCC_VERSION_CHECK(3,4,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (ZPL_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + ZPL_PGI_VERSION_CHECK(17,10,0) + # define ZPL_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + # define ZPL_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) + #elif defined(_Check_return_) /* SAL */ + # define ZPL_WARN_UNUSED_RESULT _Check_return_ + # define ZPL_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ + #else + # define ZPL_WARN_UNUSED_RESULT + # define ZPL_WARN_UNUSED_RESULT_MSG(msg) + #endif + + #if defined(ZPL_SENTINEL) + # undef ZPL_SENTINEL + #endif + #if \ + ZPL_HAS_ATTRIBUTE(sentinel) || \ + ZPL_GCC_VERSION_CHECK(4,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_ARM_VERSION_CHECK(5,4,0) + # define ZPL_SENTINEL(position) __attribute__((__sentinel__(position))) + #else + # define ZPL_SENTINEL(position) + #endif + + #if defined(ZPL_NO_RETURN) + # undef ZPL_NO_RETURN + #endif + #if ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_NO_RETURN __noreturn + #elif ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_NO_RETURN __attribute__((__noreturn__)) + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + # define ZPL_NO_RETURN _Noreturn + #elif defined(__cplusplus) && (__cplusplus >= 201103L) + # define ZPL_NO_RETURN ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) + #elif \ + ZPL_HAS_ATTRIBUTE(noreturn) || \ + ZPL_GCC_VERSION_CHECK(3,2,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_NO_RETURN __attribute__((__noreturn__)) + #elif ZPL_SUNPRO_VERSION_CHECK(5,10,0) + # define ZPL_NO_RETURN _Pragma("does_not_return") + #elif ZPL_MSVC_VERSION_CHECK(13,10,0) + # define ZPL_NO_RETURN __declspec(noreturn) + #elif ZPL_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + # define ZPL_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") + #elif ZPL_COMPCERT_VERSION_CHECK(3,2,0) + # define ZPL_NO_RETURN __attribute((noreturn)) + #elif ZPL_PELLES_VERSION_CHECK(9,0,0) + # define ZPL_NO_RETURN __declspec(noreturn) + #else + # define ZPL_NO_RETURN + #endif + + #if defined(ZPL_NO_ESCAPE) + # undef ZPL_NO_ESCAPE + #endif + #if ZPL_HAS_ATTRIBUTE(noescape) + # define ZPL_NO_ESCAPE __attribute__((__noescape__)) + #else + # define ZPL_NO_ESCAPE + #endif + + #if defined(ZPL_UNREACHABLE) + # undef ZPL_UNREACHABLE + #endif + #if defined(ZPL_UNREACHABLE_RETURN) + # undef ZPL_UNREACHABLE_RETURN + #endif + #if defined(ZPL_ASSUME) + # undef ZPL_ASSUME + #endif + #if \ + ZPL_MSVC_VERSION_CHECK(13,10,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_ASSUME(expr) __assume(expr) + #elif ZPL_HAS_BUILTIN(__builtin_assume) + # define ZPL_ASSUME(expr) __builtin_assume(expr) + #elif \ + ZPL_TI_CL2000_VERSION_CHECK(6,2,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(4,0,0) + # if defined(__cplusplus) + # define ZPL_ASSUME(expr) std::_nassert(expr) + # else + # define ZPL_ASSUME(expr) _nassert(expr) + # endif + #endif + #if \ + (ZPL_HAS_BUILTIN(__builtin_unreachable) && (!defined(ZPL_ARM_VERSION))) || \ + ZPL_GCC_VERSION_CHECK(4,5,0) || \ + ZPL_PGI_VERSION_CHECK(18,10,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_IBM_VERSION_CHECK(13,1,5) + # define ZPL_UNREACHABLE() __builtin_unreachable() + #elif defined(ZPL_ASSUME) + # define ZPL_UNREACHABLE() ZPL_ASSUME(0) + #endif + #if !defined(ZPL_ASSUME) + # if defined(ZPL_UNREACHABLE) + # define ZPL_ASSUME(expr) ZPL_STATIC_CAST(void, ((expr) ? 1 : (ZPL_UNREACHABLE(), 1))) + # else + # define ZPL_ASSUME(expr) ZPL_STATIC_CAST(void, expr) + # endif + #endif + #if defined(ZPL_UNREACHABLE) + # if \ + ZPL_TI_CL2000_VERSION_CHECK(6,2,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(4,0,0) + # define ZPL_UNREACHABLE_RETURN(value) return (ZPL_STATIC_CAST(void, ZPL_ASSUME(0)), (value)) + # else + # define ZPL_UNREACHABLE_RETURN(value) ZPL_UNREACHABLE() + # endif + #else + # define ZPL_UNREACHABLE_RETURN(value) return (value) + #endif + #if !defined(ZPL_UNREACHABLE) + # define ZPL_UNREACHABLE() ZPL_ASSUME(0) + #endif + + ZPL_DIAGNOSTIC_PUSH + #if ZPL_HAS_WARNING("-Wpedantic") + # pragma clang diagnostic ignored "-Wpedantic" + #endif + #if ZPL_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" + #endif + #if ZPL_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + # if defined(__clang__) + # pragma clang diagnostic ignored "-Wvariadic-macros" + # elif defined(ZPL_GCC_VERSION) + # pragma GCC diagnostic ignored "-Wvariadic-macros" + # endif + #endif + #if defined(ZPL_NON_NULL) + # undef ZPL_NON_NULL + #endif + #if \ + ZPL_HAS_ATTRIBUTE(nonnull) || \ + ZPL_GCC_VERSION_CHECK(3,3,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) + # define ZPL_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) + #else + # define ZPL_NON_NULL(...) + #endif + ZPL_DIAGNOSTIC_POP + + #if defined(ZPL_PRINTF_FORMAT) + # undef ZPL_PRINTF_FORMAT + #endif + #if defined(__MINGW32__) && ZPL_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + # define ZPL_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) + #elif defined(__MINGW32__) && ZPL_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + # define ZPL_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) + #elif \ + ZPL_HAS_ATTRIBUTE(format) || \ + ZPL_GCC_VERSION_CHECK(3,1,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_ARM_VERSION_CHECK(5,6,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) + #elif ZPL_PELLES_VERSION_CHECK(6,0,0) + # define ZPL_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) + #else + # define ZPL_PRINTF_FORMAT(string_idx,first_to_check) + #endif + + #if defined(ZPL_CONSTEXPR) + # undef ZPL_CONSTEXPR + #endif + #if defined(__cplusplus) + # if __cplusplus >= 201103L + # define ZPL_CONSTEXPR ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + # endif + #endif + #if !defined(ZPL_CONSTEXPR) + # define ZPL_CONSTEXPR + #endif + + #if defined(ZPL_PREDICT) + # undef ZPL_PREDICT + #endif + #if defined(ZPL_LIKELY) + # undef ZPL_LIKELY + #endif + #if defined(ZPL_UNLIKELY) + # undef ZPL_UNLIKELY + #endif + #if defined(ZPL_UNPREDICTABLE) + # undef ZPL_UNPREDICTABLE + #endif + #if ZPL_HAS_BUILTIN(__builtin_unpredictable) + # define ZPL_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) + #endif + #if \ + ZPL_HAS_BUILTIN(__builtin_expect_with_probability) || \ + ZPL_GCC_VERSION_CHECK(9,0,0) + # define ZPL_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) + # define ZPL_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) + # define ZPL_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) + # define ZPL_LIKELY(expr) __builtin_expect (!!(expr), 1 ) + # define ZPL_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) + #elif \ + ZPL_HAS_BUILTIN(__builtin_expect) || \ + ZPL_GCC_VERSION_CHECK(3,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + (ZPL_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + ZPL_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + ZPL_TI_CL430_VERSION_CHECK(3,1,0) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,1,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(6,1,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + ZPL_TINYC_VERSION_CHECK(0,9,27) || \ + ZPL_CRAY_VERSION_CHECK(8,1,0) + # define ZPL_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (ZPL_STATIC_CAST(void, expected), (expr))) + # define ZPL_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double zpl_probability_ = (probability); \ + ((zpl_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((zpl_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) + # define ZPL_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double zpl_probability_ = (probability); \ + ((zpl_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((zpl_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) + # define ZPL_LIKELY(expr) __builtin_expect(!!(expr), 1) + # define ZPL_UNLIKELY(expr) __builtin_expect(!!(expr), 0) + #else + # define ZPL_PREDICT(expr, expected, probability) (ZPL_STATIC_CAST(void, expected), (expr)) + # define ZPL_PREDICT_TRUE(expr, probability) (!!(expr)) + # define ZPL_PREDICT_FALSE(expr, probability) (!!(expr)) + # define ZPL_LIKELY(expr) (!!(expr)) + # define ZPL_UNLIKELY(expr) (!!(expr)) + #endif + #if !defined(ZPL_UNPREDICTABLE) + # define ZPL_UNPREDICTABLE(expr) ZPL_PREDICT(expr, 1, 0.5) + #endif + + #if defined(ZPL_MALLOC) + # undef ZPL_MALLOC + #endif + #if \ + ZPL_HAS_ATTRIBUTE(malloc) || \ + ZPL_GCC_VERSION_CHECK(3,1,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(12,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_MALLOC __attribute__((__malloc__)) + #elif ZPL_SUNPRO_VERSION_CHECK(5,10,0) + # define ZPL_MALLOC _Pragma("returns_new_memory") + #elif ZPL_MSVC_VERSION_CHECK(14, 0, 0) + # define ZPL_MALLOC __declspec(restrict) + #else + # define ZPL_MALLOC + #endif + + #if defined(ZPL_PURE) + # undef ZPL_PURE + #endif + #if \ + ZPL_HAS_ATTRIBUTE(pure) || \ + ZPL_GCC_VERSION_CHECK(2,96,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + ZPL_PGI_VERSION_CHECK(17,10,0) + # define ZPL_PURE __attribute__((__pure__)) + #elif ZPL_SUNPRO_VERSION_CHECK(5,10,0) + # define ZPL_PURE _Pragma("does_not_write_global_data") + #elif defined(__cplusplus) && \ + ( \ + ZPL_TI_CL430_VERSION_CHECK(2,0,1) || \ + ZPL_TI_CL6X_VERSION_CHECK(4,0,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) + # define ZPL_PURE _Pragma("FUNC_IS_PURE;") + #else + # define ZPL_PURE + #endif + + #if defined(ZPL_CONST) + # undef ZPL_CONST + #endif + #if \ + ZPL_HAS_ATTRIBUTE(const) || \ + ZPL_GCC_VERSION_CHECK(2,5,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + ZPL_PGI_VERSION_CHECK(17,10,0) + # define ZPL_CONST __attribute__((__const__)) + #elif \ + ZPL_SUNPRO_VERSION_CHECK(5,10,0) + # define ZPL_CONST _Pragma("no_side_effect") + #else + # define ZPL_CONST ZPL_PURE + #endif + + #if defined(ZPL_RESTRICT) + # undef ZPL_RESTRICT + #endif + #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + # define ZPL_RESTRICT restrict + #elif \ + ZPL_GCC_VERSION_CHECK(3,1,0) || \ + ZPL_MSVC_VERSION_CHECK(14,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_PGI_VERSION_CHECK(17,10,0) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,2,4) || \ + ZPL_TI_CL6X_VERSION_CHECK(8,1,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (ZPL_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + ZPL_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) + # define ZPL_RESTRICT __restrict + #elif ZPL_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + # define ZPL_RESTRICT _Restrict + #else + # define ZPL_RESTRICT + #endif + + #if defined(ZPL_INLINE) + # undef ZPL_INLINE + #endif + #if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + # define ZPL_INLINE inline + #elif \ + defined(ZPL_GCC_VERSION) || \ + ZPL_ARM_VERSION_CHECK(6,2,0) + # define ZPL_INLINE __inline__ + #elif \ + ZPL_MSVC_VERSION_CHECK(12,0,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + ZPL_TI_CL430_VERSION_CHECK(3,1,0) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,2,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(8,0,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_INLINE __inline + #else + # define ZPL_INLINE + #endif + + #if defined(ZPL_ALWAYS_INLINE) + # undef ZPL_ALWAYS_INLINE + #endif + #if \ + ZPL_HAS_ATTRIBUTE(always_inline) || \ + ZPL_GCC_VERSION_CHECK(4,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_ALWAYS_INLINE __attribute__((__always_inline__)) ZPL_INLINE + #elif ZPL_MSVC_VERSION_CHECK(12,0,0) + # define ZPL_ALWAYS_INLINE __forceinline + #elif defined(__cplusplus) && \ + ( \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(6,1,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) + # define ZPL_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_ALWAYS_INLINE _Pragma("inline=forced") + #else + # define ZPL_ALWAYS_INLINE ZPL_INLINE + #endif + + #undef ZPL_ALWAYS_INLINE + #define ZPL_ALWAYS_INLINE ZPL_INLINE + + #if defined(ZPL_NEVER_INLINE) + # undef ZPL_NEVER_INLINE + #endif + #if \ + ZPL_HAS_ATTRIBUTE(noinline) || \ + ZPL_GCC_VERSION_CHECK(4,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(10,1,0) || \ + ZPL_TI_VERSION_CHECK(15,12,0) || \ + (ZPL_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (ZPL_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (ZPL_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL430_VERSION_CHECK(4,3,0) || \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) || \ + ZPL_TI_CL7X_VERSION_CHECK(1,2,0) || \ + ZPL_TI_CLPRU_VERSION_CHECK(2,1,0) + # define ZPL_NEVER_INLINE __attribute__((__noinline__)) + #elif ZPL_MSVC_VERSION_CHECK(13,10,0) + # define ZPL_NEVER_INLINE __declspec(noinline) + #elif ZPL_PGI_VERSION_CHECK(10,2,0) + # define ZPL_NEVER_INLINE _Pragma("noinline") + #elif ZPL_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + # define ZPL_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_NEVER_INLINE _Pragma("inline=never") + #elif ZPL_COMPCERT_VERSION_CHECK(3,2,0) + # define ZPL_NEVER_INLINE __attribute((noinline)) + #elif ZPL_PELLES_VERSION_CHECK(9,0,0) + # define ZPL_NEVER_INLINE __declspec(noinline) + #else + # define ZPL_NEVER_INLINE + #endif + + #if defined(ZPL_PRIVATE) + # undef ZPL_PRIVATE + #endif + #if defined(ZPL_PUBLIC) + # undef ZPL_PUBLIC + #endif + #if defined(ZPL_IMPORT) + # undef ZPL_IMPORT + #endif + #if defined(_WIN32) || defined(__CYGWIN__) + # define ZPL_PRIVATE + # define ZPL_PUBLIC __declspec(dllexport) + # define ZPL_IMPORT __declspec(dllimport) + #else + # if \ + ZPL_HAS_ATTRIBUTE(visibility) || \ + ZPL_GCC_VERSION_CHECK(3,3,0) || \ + ZPL_SUNPRO_VERSION_CHECK(5,11,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (ZPL_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + ZPL_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) + # define ZPL_PRIVATE __attribute__((__visibility__("hidden"))) + # define ZPL_PUBLIC __attribute__((__visibility__("default"))) + # else + # define ZPL_PRIVATE + # define ZPL_PUBLIC + # endif + # define ZPL_IMPORT extern + #endif + + #if defined(ZPL_NO_THROW) + # undef ZPL_NO_THROW + #endif + #if \ + ZPL_HAS_ATTRIBUTE(nothrow) || \ + ZPL_GCC_VERSION_CHECK(3,3,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_NO_THROW __attribute__((__nothrow__)) + #elif \ + ZPL_MSVC_VERSION_CHECK(13,1,0) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) + # define ZPL_NO_THROW __declspec(nothrow) + #else + # define ZPL_NO_THROW + #endif + + #if defined(ZPL_FALL_THROUGH) + # undef ZPL_FALL_THROUGH + #endif + #if ZPL_GNUC_HAS_ATTRIBUTE(fallthrough,7,0,0) && !defined(ZPL_PGI_VERSION) + # define ZPL_FALL_THROUGH __attribute__((__fallthrough__)) + #elif ZPL_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + # define ZPL_FALL_THROUGH ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) + #elif ZPL_HAS_CPP_ATTRIBUTE(fallthrough) + # define ZPL_FALL_THROUGH ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) + #elif defined(__fallthrough) /* SAL */ + # define ZPL_FALL_THROUGH __fallthrough + #else + # define ZPL_FALL_THROUGH + #endif + + #if defined(ZPL_RETURNS_NON_NULL) + # undef ZPL_RETURNS_NON_NULL + #endif + #if \ + ZPL_HAS_ATTRIBUTE(returns_nonnull) || \ + ZPL_GCC_VERSION_CHECK(4,9,0) + # define ZPL_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) + #elif defined(_Ret_notnull_) /* SAL */ + # define ZPL_RETURNS_NON_NULL _Ret_notnull_ + #else + # define ZPL_RETURNS_NON_NULL + #endif + + #if defined(ZPL_ARRAY_PARAM) + # undef ZPL_ARRAY_PARAM + #endif + #if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(ZPL_PGI_VERSION) && \ + !defined(ZPL_TINYC_VERSION) + # define ZPL_ARRAY_PARAM(name) (name) + #else + # define ZPL_ARRAY_PARAM(name) + #endif + + #if defined(ZPL_IS_CONSTANT) + # undef ZPL_IS_CONSTANT + #endif + #if defined(ZPL_REQUIRE_CONSTEXPR) + # undef ZPL_REQUIRE_CONSTEXPR + #endif + /* ZPL_IS_CONSTEXPR_ is for + ZPL INTERNAL USE ONLY. API subject to change without notice. */ + #if defined(ZPL_IS_CONSTEXPR_) + # undef ZPL_IS_CONSTEXPR_ + #endif + #if \ + ZPL_HAS_BUILTIN(__builtin_constant_p) || \ + ZPL_GCC_VERSION_CHECK(3,4,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_TINYC_VERSION_CHECK(0,9,19) || \ + ZPL_ARM_VERSION_CHECK(4,1,0) || \ + ZPL_IBM_VERSION_CHECK(13,1,0) || \ + ZPL_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (ZPL_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + ZPL_CRAY_VERSION_CHECK(8,1,0) + # define ZPL_IS_CONSTANT(expr) __builtin_constant_p(expr) + #endif + #if !defined(__cplusplus) + # if \ + ZPL_HAS_BUILTIN(__builtin_types_compatible_p) || \ + ZPL_GCC_VERSION_CHECK(3,4,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + ZPL_IBM_VERSION_CHECK(13,1,0) || \ + ZPL_CRAY_VERSION_CHECK(8,1,0) || \ + ZPL_ARM_VERSION_CHECK(5,4,0) || \ + ZPL_TINYC_VERSION_CHECK(0,9,24) + # if defined(__INTPTR_TYPE__) + # define ZPL_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) + # else + # include + # define ZPL_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) + # endif + # elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(ZPL_SUNPRO_VERSION) && \ + !defined(ZPL_PGI_VERSION) && \ + !defined(ZPL_IAR_VERSION)) || \ + ZPL_HAS_EXTENSION(c_generic_selections) || \ + ZPL_GCC_VERSION_CHECK(4,9,0) || \ + ZPL_INTEL_VERSION_CHECK(17,0,0) || \ + ZPL_IBM_VERSION_CHECK(12,1,0) || \ + ZPL_ARM_VERSION_CHECK(5,3,0) + # if defined(__INTPTR_TYPE__) + # define ZPL_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) + # else + # include + # define ZPL_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) + # endif + # elif \ + defined(ZPL_GCC_VERSION) || \ + defined(ZPL_INTEL_VERSION) || \ + defined(ZPL_TINYC_VERSION) || \ + defined(ZPL_TI_ARMCL_VERSION) || \ + ZPL_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(ZPL_TI_CL2000_VERSION) || \ + defined(ZPL_TI_CL6X_VERSION) || \ + defined(ZPL_TI_CL7X_VERSION) || \ + defined(ZPL_TI_CLPRU_VERSION) || \ + defined(__clang__) + # define ZPL_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ + ((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) + # endif + #endif + #if defined(ZPL_IS_CONSTEXPR_) + # if !defined(ZPL_IS_CONSTANT) + # define ZPL_IS_CONSTANT(expr) ZPL_IS_CONSTEXPR_(expr) + # endif + # define ZPL_REQUIRE_CONSTEXPR(expr) (ZPL_IS_CONSTEXPR_(expr) ? (expr) : (-1)) + #else + # if !defined(ZPL_IS_CONSTANT) + # define ZPL_IS_CONSTANT(expr) (0) + # endif + # define ZPL_REQUIRE_CONSTEXPR(expr) (expr) + #endif + + #if defined(ZPL_BEGIN_C_DECLS) + # undef ZPL_BEGIN_C_DECLS + #endif + #if defined(ZPL_END_C_DECLS) + # undef ZPL_END_C_DECLS + #endif + #if defined(ZPL_C_DECL) + # undef ZPL_C_DECL + #endif + #if defined(__cplusplus) + # define ZPL_BEGIN_C_DECLS extern "C" { + # define ZPL_END_C_DECLS } + # define ZPL_C_DECL extern "C" + #else + # define ZPL_BEGIN_C_DECLS + # define ZPL_END_C_DECLS + # define ZPL_C_DECL + #endif + + #if defined(ZPL_STATIC_ASSERT) + # undef ZPL_STATIC_ASSERT + #endif + #if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + ZPL_HAS_FEATURE(c_static_assert) || \ + ZPL_GCC_VERSION_CHECK(6,0,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) + # define ZPL_STATIC_ASSERT(expr, message) _Static_assert(expr, message) + #elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + ZPL_MSVC_VERSION_CHECK(16,0,0) + # define ZPL_STATIC_ASSERT(expr, message) ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) + #else + # define ZPL_STATIC_ASSERT3(cond, msg) typedef char static_assertion_##msg[(!!(cond)) * 2 - 1] + # define ZPL_STATIC_ASSERT2(cond, line) ZPL_STATIC_ASSERT3(cond, static_assertion_at_line_##line) + # define ZPL_STATIC_ASSERT1(cond, line) ZPL_STATIC_ASSERT2(cond, line) + # define ZPL_STATIC_ASSERT(cond, unused) ZPL_STATIC_ASSERT1(cond, __LINE__) + #endif + + #if defined(ZPL_NULL) + # undef ZPL_NULL + #endif + #if defined(__cplusplus) + # if __cplusplus >= 201103L + # define ZPL_NULL ZPL_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + # elif defined(NULL) + # define ZPL_NULL NULL + # else + # define ZPL_NULL ZPL_STATIC_CAST(void*, 0) + # endif + #elif defined(NULL) + # define ZPL_NULL NULL + #else + # define ZPL_NULL ((void*) 0) + #endif + + #if defined(ZPL_MESSAGE) + # undef ZPL_MESSAGE + #endif + #if ZPL_HAS_WARNING("-Wunknown-pragmas") + # define ZPL_MESSAGE(msg) \ + ZPL_DIAGNOSTIC_PUSH \ + ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + ZPL_PRAGMA(message msg) \ + ZPL_DIAGNOSTIC_POP + #elif \ + ZPL_GCC_VERSION_CHECK(4,4,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_MESSAGE(msg) ZPL_PRAGMA(message msg) + #elif ZPL_CRAY_VERSION_CHECK(5,0,0) + # define ZPL_MESSAGE(msg) ZPL_PRAGMA(_CRI message msg) + #elif ZPL_IAR_VERSION_CHECK(8,0,0) + # define ZPL_MESSAGE(msg) ZPL_PRAGMA(message(msg)) + #elif ZPL_PELLES_VERSION_CHECK(2,0,0) + # define ZPL_MESSAGE(msg) ZPL_PRAGMA(message(msg)) + #else + # define ZPL_MESSAGE(msg) + #endif + + #if defined(ZPL_WARNING) + # undef ZPL_WARNING + #endif + #if ZPL_HAS_WARNING("-Wunknown-pragmas") + # define ZPL_WARNING(msg) \ + ZPL_DIAGNOSTIC_PUSH \ + ZPL_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + ZPL_PRAGMA(clang warning msg) \ + ZPL_DIAGNOSTIC_POP + #elif \ + ZPL_GCC_VERSION_CHECK(4,8,0) || \ + ZPL_PGI_VERSION_CHECK(18,4,0) || \ + ZPL_INTEL_VERSION_CHECK(13,0,0) + # define ZPL_WARNING(msg) ZPL_PRAGMA(GCC warning msg) + #elif ZPL_MSVC_VERSION_CHECK(15,0,0) + # define ZPL_WARNING(msg) ZPL_PRAGMA(message(msg)) + #else + # define ZPL_WARNING(msg) ZPL_MESSAGE(msg) + #endif + + #if defined(ZPL_REQUIRE) + # undef ZPL_REQUIRE + #endif + #if defined(ZPL_REQUIRE_MSG) + # undef ZPL_REQUIRE_MSG + #endif + #if ZPL_HAS_ATTRIBUTE(diagnose_if) + # if ZPL_HAS_WARNING("-Wgcc-compat") + # define ZPL_REQUIRE(expr) \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + ZPL_DIAGNOSTIC_POP + # define ZPL_REQUIRE_MSG(expr,msg) \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + ZPL_DIAGNOSTIC_POP + # else + # define ZPL_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) + # define ZPL_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) + # endif + #else + # define ZPL_REQUIRE(expr) + # define ZPL_REQUIRE_MSG(expr,msg) + #endif + + #if defined(ZPL_FLAGS) + # undef ZPL_FLAGS + #endif + #if ZPL_HAS_ATTRIBUTE(flag_enum) + # define ZPL_FLAGS __attribute__((__flag_enum__)) + #endif + + #if defined(ZPL_FLAGS_CAST) + # undef ZPL_FLAGS_CAST + #endif + #if ZPL_INTEL_VERSION_CHECK(19,0,0) + # define ZPL_FLAGS_CAST(T, expr) (__extension__ ({ \ + ZPL_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + ZPL_DIAGNOSTIC_POP \ + })) + #else + # define ZPL_FLAGS_CAST(T, expr) ZPL_STATIC_CAST(T, expr) + #endif + + #if defined(ZPL_EMPTY_BASES) + # undef ZPL_EMPTY_BASES + #endif + #if ZPL_MSVC_VERSION_CHECK(19,0,23918) && !ZPL_MSVC_VERSION_CHECK(20,0,0) + # define ZPL_EMPTY_BASES __declspec(empty_bases) + #else + # define ZPL_EMPTY_BASES + #endif + + /* Remaining macros are deprecated. */ + + #if defined(ZPL_GCC_NOT_CLANG_VERSION_CHECK) + # undef ZPL_GCC_NOT_CLANG_VERSION_CHECK + #endif + #if defined(__clang__) + # define ZPL_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) + #else + # define ZPL_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) ZPL_GCC_VERSION_CHECK(major,minor,patch) + #endif + + #if defined(ZPL_CLANG_HAS_ATTRIBUTE) + # undef ZPL_CLANG_HAS_ATTRIBUTE + #endif + #define ZPL_CLANG_HAS_ATTRIBUTE(attribute) ZPL_HAS_ATTRIBUTE(attribute) + + #if defined(ZPL_CLANG_HAS_CPP_ATTRIBUTE) + # undef ZPL_CLANG_HAS_CPP_ATTRIBUTE + #endif + #define ZPL_CLANG_HAS_CPP_ATTRIBUTE(attribute) ZPL_HAS_CPP_ATTRIBUTE(attribute) + + #if defined(ZPL_CLANG_HAS_BUILTIN) + # undef ZPL_CLANG_HAS_BUILTIN + #endif + #define ZPL_CLANG_HAS_BUILTIN(builtin) ZPL_HAS_BUILTIN(builtin) + + #if defined(ZPL_CLANG_HAS_FEATURE) + # undef ZPL_CLANG_HAS_FEATURE + #endif + #define ZPL_CLANG_HAS_FEATURE(feature) ZPL_HAS_FEATURE(feature) + + #if defined(ZPL_CLANG_HAS_EXTENSION) + # undef ZPL_CLANG_HAS_EXTENSION + #endif + #define ZPL_CLANG_HAS_EXTENSION(extension) ZPL_HAS_EXTENSION(extension) + + #if defined(ZPL_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + # undef ZPL_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE + #endif + #define ZPL_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) ZPL_HAS_DECLSPEC_ATTRIBUTE(attribute) + + #if defined(ZPL_CLANG_HAS_WARNING) + # undef ZPL_CLANG_HAS_WARNING + #endif + #define ZPL_CLANG_HAS_WARNING(warning) ZPL_HAS_WARNING(warning) + + #endif /* !defined(ZPL_HEDLEY_VERSION) || (ZPL_HEDLEY_VERSION < X) */ + +#define ZPL_VERSION ZPL_VERSION_ENCODE(ZPL_VERSION_MAJOR, ZPL_VERSION_MINOR, ZPL_VERSION_PATCH) + +#ifdef ZPL_IMPL +# ifndef ZPL_IMPLEMENTATION +# define ZPL_IMPLEMENTATION +# endif +#endif + +#if defined(__cplusplus) && !defined(ZPL_EXTERN) +# define ZPL_EXTERN extern "C" +#else +# define ZPL_EXTERN extern +#endif + +#ifndef ZPL_DEF +# if defined(ZPL_SHARED_LIB) +# ifdef ZPL_IMPLEMENTATION +# define ZPL_DEF ZPL_PUBLIC +# else +# define ZPL_DEF ZPL_IMPORT +# endif +# elif defined(ZPL_STATIC_LIB) +# ifdef ZPL_IMPLEMENTATION +# define ZPL_DEF +# else +# define ZPL_DEF ZPL_EXTERN +# endif +# elif defined(ZPL_STATIC) +# define ZPL_DEF static +# else +# define ZPL_DEF ZPL_EXTERN +# endif +#endif + +#ifndef ZPL_DEF_INLINE +# if defined(ZPL_STATIC) +# define ZPL_DEF_INLINE +# define ZPL_IMPL_INLINE +# else +# define ZPL_DEF_INLINE static +# define ZPL_IMPL_INLINE static inline +# endif +#endif + +/* builtin overrides */ +#if defined(__TINYC__) || defined(__EMSCRIPTEN__) +# if defined(ZPL_ENFORCE_THREADING) +# define ZPL_ENABLE_THREADING +# else +# define ZPL_DISABLE_THREADING +# endif +#endif + +/* Distributions */ +#ifndef ZPL_CUSTOM_MODULES + /* default distribution */ +# define ZPL_MODULE_ESSENTIALS +# define ZPL_MODULE_CORE +# define ZPL_MODULE_TIMER +# define ZPL_MODULE_HASHING +# define ZPL_MODULE_REGEX +# define ZPL_MODULE_EVENT +# define ZPL_MODULE_DLL +# define ZPL_MODULE_OPTS +# define ZPL_MODULE_PROCESS +# define ZPL_MODULE_MATH +# define ZPL_MODULE_THREADING +# define ZPL_MODULE_JOBS +# define ZPL_MODULE_PARSER +# define ZPL_MODULE_SOCKET + + /* zpl nano distribution */ +# if defined(ZPL_NANO) || defined(ZPL_PICO) +# undef ZPL_MODULE_TIMER +# undef ZPL_MODULE_HASHING +# undef ZPL_MODULE_REGEX +# undef ZPL_MODULE_EVENT +# undef ZPL_MODULE_DLL +# undef ZPL_MODULE_OPTS +# undef ZPL_MODULE_PROCESS +# undef ZPL_MODULE_MATH +# undef ZPL_MODULE_THREADING +# undef ZPL_MODULE_JOBS +# undef ZPL_MODULE_PARSER +# undef ZPL_MODULE_SOCKET +# endif + +# if defined(ZPL_PICO) +# undef ZPL_MODULE_CORE +# endif + + /* module enabling overrides */ +# if defined(ZPL_ENABLE_CORE) && !defined(ZPL_MODULE_CORE) +# define ZPL_MODULE_CORE +# endif +# if defined(ZPL_ENABLE_HASHING) && !defined(ZPL_MODULE_HASHING) +# define ZPL_MODULE_HASHING +# endif +# if defined(ZPL_ENABLE_REGEX) && !defined(ZPL_MODULE_REGEX) +# define ZPL_MODULE_REGEX +# endif +# if defined(ZPL_ENABLE_DLL) && !defined(ZPL_MODULE_DLL) +# define ZPL_MODULE_DLL +# endif +# if defined(ZPL_ENABLE_OPTS) && !defined(ZPL_MODULE_OPTS) +# define ZPL_MODULE_OPTS +# endif +# if defined(ZPL_ENABLE_PROCESS) && !defined(ZPL_MODULE_PROCESS) +# define ZPL_MODULE_PROCESS +# endif +# if defined(ZPL_ENABLE_MATH) && !defined(ZPL_MODULE_MATH) +# define ZPL_MODULE_MATH +# endif +# if defined(ZPL_ENABLE_THREADING) && !defined(ZPL_MODULE_THREADING) +# define ZPL_MODULE_THREADING +# endif +# if defined(ZPL_ENABLE_JOBS) && !defined(ZPL_MODULE_JOBS) +# ifndef ZPL_MODULE_THREADING +# define ZPL_MODULE_THREADING /* dependency */ +# endif +# define ZPL_MODULE_JOBS +# endif +# if defined(ZPL_ENABLE_PARSER) && !defined(ZPL_MODULE_PARSER) +# define ZPL_MODULE_PARSER +# endif +# if defined(ZPL_ENABLE_SOCKET) && !defined(ZPL_MODULE_SOCKET) +# define ZPL_MODULE_SOCKET +# endif + + /* module disabling overrides */ +# if defined(ZPL_DISABLE_CORE) && defined(ZPL_MODULE_CORE) +# undef ZPL_MODULE_CORE +# endif +# if defined(ZPL_DISABLE_HASHING) && defined(ZPL_MODULE_HASHING) +# undef ZPL_MODULE_HASHING +# endif +# if defined(ZPL_DISABLE_REGEX) && defined(ZPL_MODULE_REGEX) +# undef ZPL_MODULE_REGEX +# endif +# if defined(ZPL_DISABLE_DLL) && defined(ZPL_MODULE_DLL) +# undef ZPL_MODULE_DLL +# endif +# if defined(ZPL_DISABLE_OPTS) && defined(ZPL_MODULE_OPTS) +# undef ZPL_MODULE_OPTS +# endif +# if defined(ZPL_DISABLE_PROCESS) && defined(ZPL_MODULE_PROCESS) +# undef ZPL_MODULE_PROCESS +# endif +# if defined(ZPL_DISABLE_MATH) && defined(ZPL_MODULE_MATH) +# undef ZPL_MODULE_MATH +# endif +# if defined(ZPL_DISABLE_THREADING) && defined(ZPL_MODULE_THREADING) +# ifdef ZPL_MODULE_JOBS +# undef ZPL_MODULE_JOBS /* user */ +# endif +# undef ZPL_MODULE_THREADING +# endif +# if defined(ZPL_DISABLE_JOBS) && defined(ZPL_MODULE_JOBS) +# undef ZPL_MODULE_JOBS +# endif +# if defined(ZPL_DISABLE_PARSER) && defined(ZPL_MODULE_PARSER) +# undef ZPL_MODULE_PARSER +# endif +# if defined(ZPL_DISABLE_SOCKET) && defined(ZPL_MODULE_SOCKET) +# undef ZPL_MODULE_SOCKET +# endif +#endif + +#if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +# pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4201) +# pragma warning(disable : 4127) // Conditional expression is constant +#endif + +/* general purpose includes */ + + // file: header/core/system.h + + + ZPL_BEGIN_C_DECLS + + /* Platform architecture */ + + #if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || \ + defined(__ppc64__) || defined(__aarch64__) + # ifndef ZPL_ARCH_64_BIT + # define ZPL_ARCH_64_BIT 1 + # endif + #else + # ifndef ZPL_ARCH_32_BIT + # define ZPL_ARCH_32_BIT 1 + # endif + #endif + + /* Platform endiannes */ + + #ifndef ZPL_ENDIAN_ORDER + # define ZPL_ENDIAN_ORDER + # define ZPL_IS_BIG_ENDIAN (!*(zpl_u8 *)&(zpl_u16){ 1 }) + # define ZPL_IS_LITTLE_ENDIAN (!ZPL_IS_BIG_ENDIAN) + #endif + + /* Platform OS */ + + #if defined(_WIN32) || defined(_WIN64) + # ifndef ZPL_SYSTEM_WINDOWS + # define ZPL_SYSTEM_WINDOWS 1 + # endif + #elif defined(__APPLE__) && defined(__MACH__) + # ifndef ZPL_SYSTEM_OSX + # define ZPL_SYSTEM_OSX 1 + # endif + # ifndef ZPL_SYSTEM_MACOS + # define ZPL_SYSTEM_MACOS 1 + # endif + # include + # if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1 + # ifndef ZPL_SYSTEM_IOS + # define ZPL_SYSTEM_IOS 1 + # endif + # endif + #elif defined(__unix__) + # ifndef ZPL_SYSTEM_UNIX + # define ZPL_SYSTEM_UNIX 1 + # endif + # if defined(ANDROID) || defined(__ANDROID__) + # ifndef ZPL_SYSTEM_ANDROID + # define ZPL_SYSTEM_ANDROID 1 + # endif + # ifndef ZPL_SYSTEM_LINUX + # define ZPL_SYSTEM_LINUX 1 + # endif + # elif defined(__linux__) + # ifndef ZPL_SYSTEM_LINUX + # define ZPL_SYSTEM_LINUX 1 + # endif + # elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + # ifndef ZPL_SYSTEM_FREEBSD + # define ZPL_SYSTEM_FREEBSD 1 + # endif + # elif defined(__OpenBSD__) + # ifndef ZPL_SYSTEM_OPENBSD + # define ZPL_SYSTEM_OPENBSD 1 + # endif + # elif defined(__EMSCRIPTEN__) + # ifndef ZPL_SYSTEM_EMSCRIPTEN + # define ZPL_SYSTEM_EMSCRIPTEN 1 + # endif + # elif defined(__CYGWIN__) + # ifndef ZPL_SYSTEM_CYGWIN + # define ZPL_SYSTEM_CYGWIN 1 + # endif + # else + # error This UNIX operating system is not supported + # endif + #else + # error This operating system is not supported + #endif + + /* Platform compiler */ + + #if defined(_MSC_VER) + # define ZPL_COMPILER_MSVC 1 + #elif defined(__GNUC__) + # define ZPL_COMPILER_GCC 1 + #elif defined(__clang__) + # define ZPL_COMPILER_CLANG 1 + #elif defined(__MINGW32__) + # define ZPL_COMPILER_MINGW 1 + #elif defined(__TINYC__) + # define ZPL_COMPILER_TINYC 1 + #else + # error Unknown compiler + #endif + + /* Platform CPU */ + + #if defined(__arm__) || defined(__aarch64__) || defined(__ARM_ARCH) + # ifndef ZPL_CPU_ARM + # define ZPL_CPU_ARM 1 + # endif + # ifndef ZPL_CACHE_LINE_SIZE + # define ZPL_CACHE_LINE_SIZE 64 + # endif + #elif defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) || defined(ZPL_SYSTEM_EMSCRIPTEN) + # ifndef ZPL_CPU_X86 + # define ZPL_CPU_X86 1 + # endif + # ifndef ZPL_CACHE_LINE_SIZE + # define ZPL_CACHE_LINE_SIZE 64 + # endif + #elif defined(_M_PPC) || defined(__powerpc__) || defined(__powerpc64__) + # ifndef ZPL_CPU_PPC + # define ZPL_CPU_PPC 1 + # endif + # ifndef ZPL_CACHE_LINE_SIZE + # define ZPL_CACHE_LINE_SIZE 128 + # endif + #elif defined(__MIPSEL__) || defined(__mips_isa_rev) + # ifndef ZPL_CPU_MIPS + # define ZPL_CPU_MIPS 1 + # endif + # ifndef ZPL_CACHE_LINE_SIZE + # define ZPL_CACHE_LINE_SIZE 64 + # endif + #else + # error Unknown CPU Type + #endif + + // TODO(ZaKlaus): Find a better way to get this flag in MinGW. + #if (defined(ZPL_COMPILER_GCC) && !defined(WC_ERR_INVALID_CHARS)) || defined(ZPL_COMPILER_TINYC) + # define WC_ERR_INVALID_CHARS 0x0080 + #endif + + #if defined(ZPL_COMPILER_GCC) && defined(ZPL_SYSTEM_WINDOWS) + # ifndef ZPL_COMPILER_MINGW + # define ZPL_COMPILER_MINGW // assume we use mingw as a compiler + # endif + #endif + + #if defined(ZPL_SYSTEM_UNIX) + # ifndef _GNU_SOURCE + # define _GNU_SOURCE + # endif + + # ifndef _LARGEFILE64_SOURCE + # define _LARGEFILE64_SOURCE + # endif + #endif + + #if ZPL_GNUC_VERSION_CHECK(3, 3, 0) + # define ZPL_INFINITY (__builtin_inff()) + # define ZPL_NAN (__builtin_nanf("")) + #elif defined(ZPL_COMPILER_MSVC) + + # if !defined(ZPL__HACK_INFINITY) + typedef union zpl__msvc_inf_hack { + unsigned __int8 bytes[4]; + float value; + } zpl__msvc_inf_hack; + static union zpl__msvc_inf_hack ZPL__INFINITY_HACK = {{0x00, 0x00, 0x80, 0x7F}}; + # define ZPL__HACK_INFINITY (ZPL__INFINITY_HACK.value) + # endif + + # define ZPL_INFINITY (ZPL__HACK_INFINITY) + # define ZPL_NAN (0) + #else + # define ZPL_INFINITY (1e10000f) + # define ZPL_NAN (0.0f / 0.0f) + #endif + + ZPL_END_C_DECLS + +#include +#include + +#if defined(ZPL_SYSTEM_WINDOWS) +# include +#endif + + // file: header/essentials/types.h + + + ZPL_BEGIN_C_DECLS + + /* Basic types */ + + #if defined(ZPL_COMPILER_MSVC) + # if _MSC_VER < 1300 + typedef unsigned char zpl_u8; + typedef signed char zpl_i8; + typedef unsigned short zpl_u16; + typedef signed short zpl_i16; + typedef unsigned int zpl_u32; + typedef signed int zpl_i32; + # else + typedef unsigned __int8 zpl_u8; + typedef signed __int8 zpl_i8; + typedef unsigned __int16 zpl_u16; + typedef signed __int16 zpl_i16; + typedef unsigned __int32 zpl_u32; + typedef signed __int32 zpl_i32; + # endif + typedef unsigned __int64 zpl_u64; + typedef signed __int64 zpl_i64; + #else + # include + + typedef uint8_t zpl_u8; + typedef int8_t zpl_i8; + typedef uint16_t zpl_u16; + typedef int16_t zpl_i16; + typedef uint32_t zpl_u32; + typedef int32_t zpl_i32; + typedef uint64_t zpl_u64; + typedef int64_t zpl_i64; + #endif + + ZPL_STATIC_ASSERT(sizeof(zpl_u8) == sizeof(zpl_i8), "sizeof(zpl_u8) != sizeof(zpl_i8)"); + ZPL_STATIC_ASSERT(sizeof(zpl_u16) == sizeof(zpl_i16), "sizeof(zpl_u16) != sizeof(zpl_i16)"); + ZPL_STATIC_ASSERT(sizeof(zpl_u32) == sizeof(zpl_i32), "sizeof(zpl_u32) != sizeof(zpl_i32)"); + ZPL_STATIC_ASSERT(sizeof(zpl_u64) == sizeof(zpl_i64), "sizeof(zpl_u64) != sizeof(zpl_i64)"); + + ZPL_STATIC_ASSERT(sizeof(zpl_u8) == 1, "sizeof(zpl_u8) != 1"); + ZPL_STATIC_ASSERT(sizeof(zpl_u16) == 2, "sizeof(zpl_u16) != 2"); + ZPL_STATIC_ASSERT(sizeof(zpl_u32) == 4, "sizeof(zpl_u32) != 4"); + ZPL_STATIC_ASSERT(sizeof(zpl_u64) == 8, "sizeof(zpl_u64) != 8"); + + typedef size_t zpl_usize; + typedef ptrdiff_t zpl_isize; + + ZPL_STATIC_ASSERT(sizeof(zpl_usize) == sizeof(zpl_isize), "sizeof(zpl_usize) != sizeof(zpl_isize)"); + + // NOTE: (u)zpl_intptr is only here for semantic reasons really as this library will only support 32/64 bit OSes. + #if defined(_WIN64) + typedef signed __int64 zpl_intptr; + typedef unsigned __int64 zpl_uintptr; + #elif defined(_WIN32) + // NOTE; To mark types changing their size, e.g. zpl_intptr + # ifndef _W64 + # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 + # define _W64 __w64 + # else + # define _W64 + # endif + # endif + typedef _W64 signed int zpl_intptr; + typedef _W64 unsigned int zpl_uintptr; + #else + typedef uintptr_t zpl_uintptr; + typedef intptr_t zpl_intptr; + #endif + + ZPL_STATIC_ASSERT(sizeof(zpl_uintptr) == sizeof(zpl_intptr), "sizeof(zpl_uintptr) != sizeof(zpl_intptr)"); + + typedef float zpl_f32; + typedef double zpl_f64; + + ZPL_STATIC_ASSERT(sizeof(zpl_f32) == 4, "sizeof(zpl_f32) != 4"); + ZPL_STATIC_ASSERT(sizeof(zpl_f64) == 8, "sizeof(zpl_f64) != 8"); + + typedef zpl_i32 zpl_rune; // NOTE: Unicode codepoint + typedef zpl_i32 zpl_char32; + #define ZPL_RUNE_INVALID cast(zpl_rune)(0xfffd) + #define ZPL_RUNE_MAX cast(zpl_rune)(0x0010ffff) + #define ZPL_RUNE_BOM cast(zpl_rune)(0xfeff) + #define ZPL_RUNE_EOF cast(zpl_rune)(-1) + + typedef zpl_i8 zpl_b8; + typedef zpl_i16 zpl_b16; + typedef zpl_i32 zpl_b32; + + #if !defined(__cplusplus) + # if (defined(_MSC_VER) && _MSC_VER < 1800) || (!defined(_MSC_VER) && !defined(__STDC_VERSION__)) + # ifndef true + # define true(0 == 0) + # endif + # ifndef false + # define false(0 != 0) + # endif + + typedef zpl_b8 bool; + # else + # include + # endif + #endif + + #ifndef ZPL_U8_MIN + # define ZPL_U8_MIN 0u + # define ZPL_U8_MAX 0xffu + # define ZPL_I8_MIN (-0x7f - 1) + # define ZPL_I8_MAX 0x7f + + # define ZPL_U16_MIN 0u + # define ZPL_U16_MAX 0xffffu + # define ZPL_I16_MIN (-0x7fff - 1) + # define ZPL_I16_MAX 0x7fff + + # define ZPL_U32_MIN 0u + # define ZPL_U32_MAX 0xffffffffu + # define ZPL_I32_MIN (-0x7fffffff - 1) + # define ZPL_I32_MAX 0x7fffffff + + # define ZPL_U64_MIN 0ull + # define ZPL_U64_MAX 0xffffffffffffffffull + # define ZPL_I64_MIN (-0x7fffffffffffffffll - 1) + # define ZPL_I64_MAX 0x7fffffffffffffffll + + # if defined(ZPL_ARCH_32_BIT) + # define ZPL_USIZE_MIN ZPL_U32_MIN + # define ZPL_USIZE_MAX ZPL_U32_MAX + # define ZPL_ISIZE_MIN ZPL_I32_MIN + # define ZPL_ISIZE_MAX ZPL_I32_MAX + # elif defined(ZPL_ARCH_64_BIT) + # define ZPL_USIZE_MIN ZPL_U64_MIN + # define ZPL_USIZE_MAX ZPL_U64_MAX + # define ZPL_ISIZE_MIN ZPL_I64_MIN + # define ZPL_ISIZE_MAX ZPL_I64_MAX + # else + # error Unknown architecture size. This library only supports 32 bit and 64 bit architectures. + # endif + + # define ZPL_F32_MIN 1.17549435e-38f + # define ZPL_F32_MAX 3.40282347e+38f + + # define ZPL_F64_MIN 2.2250738585072014e-308 + # define ZPL_F64_MAX 1.7976931348623157e+308 + #endif + + #ifdef ZPL_DEFINE_NULL_MACRO + # ifndef NULL + # define NULL ZPL_NULL + # endif + #endif + + ZPL_END_C_DECLS + // file: header/essentials/helpers.h + + /* Various macro based helpers */ + + ZPL_BEGIN_C_DECLS + + #ifndef cast + # define cast(Type) (Type) + #endif + + #ifndef zpl_size_of + # define zpl_size_of(x) (zpl_isize)(sizeof(x)) + #endif + + #ifndef zpl_count_of + # define zpl_count_of(x) ((zpl_size_of(x) / zpl_size_of(0 [x])) / ((zpl_isize)(!(zpl_size_of(x) % zpl_size_of(0 [x]))))) + #endif + + #ifndef zpl_offset_of + #if defined(_MSC_VER) || defined(ZPL_COMPILER_TINYC) + # define zpl_offset_of(Type, element) ((zpl_isize) & (((Type *)0)->element)) + #else + # define zpl_offset_of(Type, element) __builtin_offsetof(Type, element) + #endif + #endif + + #if defined(__cplusplus) + # ifndef zpl_align_of + # if __cplusplus >= 201103L + # define zpl_align_of(Type) (zpl_isize)alignof(Type) + # else + extern "C++" { + template struct zpl_alignment_trick { + char c; + T member; + }; + } + # define zpl_align_of(Type) zpl_offset_of(zpl_alignment_trick, member) + # endif + # endif + #else + # ifndef zpl_align_of + # define zpl_align_of(Type) \ + zpl_offset_of( \ + struct { \ + char c; \ + Type member; \ + }, \ + member) + # endif + #endif + + #ifndef zpl_swap + # define zpl_swap(Type, a, b) \ + do { \ + Type tmp = (a); \ + (a) = (b); \ + (b) = tmp; \ + } while (0) + #endif + + + + #ifndef zpl_global + # define zpl_global static // Global variables + #endif + + #ifndef zpl_internal + # define zpl_internal static // Internal linkage + #endif + + #ifndef zpl_local_persist + # define zpl_local_persist static // Local Persisting variables + #endif + + #ifndef zpl_unused + # if defined(_MSC_VER) + # define zpl_unused(x) (__pragma(warning(suppress : 4100))(x)) + # elif defined(__GCC__) + # define zpl_unused(x) __attribute__((__unused__))(x) + # else + # define zpl_unused(x) ((void)(zpl_size_of(x))) + # endif + #endif + + + #ifndef ZPL_JOIN_MACROS + # define ZPL_JOIN_MACROS + + # define ZPL_JOIN2 ZPL_CONCAT + # define ZPL_JOIN3(a, b, c) ZPL_JOIN2(ZPL_JOIN2(a, b), c) + # define ZPL_JOIN4(a, b, c, d) ZPL_JOIN2(ZPL_JOIN2(ZPL_JOIN2(a, b), c), d) + #endif + + #ifndef ZPL_BIT + # define ZPL_BIT(x) (1 << (x)) + #endif + + #ifndef zpl_min + # define zpl_min(a, b) ((a) < (b) ? (a) : (b)) + #endif + + #ifndef zpl_max + # define zpl_max(a, b) ((a) > (b) ? (a) : (b)) + #endif + + #ifndef zpl_min3 + # define zpl_min3(a, b, c) zpl_min(zpl_min(a, b), c) + #endif + + #ifndef zpl_max3 + # define zpl_max3(a, b, c) zpl_max(zpl_max(a, b), c) + #endif + + #ifndef zpl_clamp + # define zpl_clamp(x, lower, upper) zpl_min(zpl_max((x), (lower)), (upper)) + #endif + + #ifndef zpl_clamp01 + # define zpl_clamp01(x) zpl_clamp((x), 0, 1) + #endif + + #ifndef zpl_is_between + # define zpl_is_between(x, lower, upper) (((lower) <= (x)) && ((x) <= (upper))) + #endif + + #ifndef zpl_is_between_limit + # define zpl_is_between_limit(x, lower, upper) (((lower) <= (x)) && ((x) < (upper))) + #endif + + #ifndef zpl_step + #define zpl_step(x,y) (((x)/(y))*(y)) + #endif + + #ifndef zpl_abs + # define zpl_abs(x) ((x) < 0 ? -(x) : (x)) + #endif + + #ifndef ZPL_MASK_SET + # define ZPL_MASK_SET(var, set, mask) \ + do { \ + if (set) \ + (var) |= (mask); \ + else \ + (var) &= ~(mask); \ + } while (0) + #endif + + // Multiline string literals in C99! + #ifndef ZPL_MULTILINE + # define ZPL_MULTILINE(...) #__VA_ARGS__ + #endif + + ZPL_END_C_DECLS + +#if defined(ZPL_MODULE_ESSENTIALS) + // file: header/essentials/debug.h + + /* Debugging stuff */ + + ZPL_BEGIN_C_DECLS + + #ifndef ZPL_DEBUG_TRAP + # if defined(_MSC_VER) + # if _MSC_VER < 1300 + # define ZPL_DEBUG_TRAP( ) __asm int 3 /* Trap to debugger! */ + # else + # define ZPL_DEBUG_TRAP( ) __debugbreak( ) + # endif + # elif defined(ZPL_COMPILER_TINYC) + # define ZPL_DEBUG_TRAP( ) zpl_exit(1) + # else + # define ZPL_DEBUG_TRAP( ) __builtin_trap( ) + # endif + #endif + + #ifndef ZPL_ASSERT_MSG + # define ZPL_ASSERT_MSG(cond, msg, ...) \ + do { \ + if (!(cond)) { \ + zpl_assert_handler(#cond, __FILE__, cast(zpl_i64) __LINE__, msg, ##__VA_ARGS__); \ + ZPL_DEBUG_TRAP( ); \ + } \ + } while (0) + #endif + + #ifndef ZPL_ASSERT + # define ZPL_ASSERT(cond) ZPL_ASSERT_MSG(cond, NULL) + #endif + + #ifndef ZPL_ASSERT_NOT_NULL + # define ZPL_ASSERT_NOT_NULL(ptr) ZPL_ASSERT_MSG((ptr) != NULL, #ptr " must not be NULL") + #endif + + // NOTE: Things that shouldn't happen with a message! + #ifndef ZPL_PANIC + # define ZPL_PANIC(msg, ...) ZPL_ASSERT_MSG(0, msg, ##__VA_ARGS__) + #endif + + #ifndef ZPL_NOT_IMPLEMENTED + # define ZPL_NOT_IMPLEMENTED ZPL_PANIC("not implemented") + #endif + + /* Functions */ + + ZPL_DEF void zpl_assert_handler(char const *condition, char const *file, zpl_i32 line, char const *msg, ...); + ZPL_DEF zpl_i32 zpl_assert_crash(char const *condition); + ZPL_DEF void zpl_exit(zpl_u32 code); + + ZPL_END_C_DECLS + // file: header/essentials/memory.h + + /** @file mem.c + @brief Memory manipulation and helpers. + @defgroup memman Memory management + + Consists of pointer arithmetic methods, virtual memory management and custom memory allocators. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + //! Checks if value is power of 2. + ZPL_DEF_INLINE zpl_b32 zpl_is_power_of_two(zpl_isize x); + + //! Aligns address to specified alignment. + ZPL_DEF_INLINE void *zpl_align_forward(void *ptr, zpl_isize alignment); + + //! Aligns value to a specified alignment. + ZPL_DEF_INLINE zpl_i64 zpl_align_forward_i64(zpl_i64 value, zpl_isize alignment); + + //! Aligns value to a specified alignment. + ZPL_DEF_INLINE zpl_u64 zpl_align_forward_u64(zpl_u64 value, zpl_usize alignment); + + //! Moves pointer forward by bytes. + ZPL_DEF_INLINE void *zpl_pointer_add(void *ptr, zpl_isize bytes); + + //! Moves pointer backward by bytes. + ZPL_DEF_INLINE void *zpl_pointer_sub(void *ptr, zpl_isize bytes); + + //! Moves pointer forward by bytes. + ZPL_DEF_INLINE void const *zpl_pointer_add_const(void const *ptr, zpl_isize bytes); + + //! Moves pointer backward by bytes. + ZPL_DEF_INLINE void const *zpl_pointer_sub_const(void const *ptr, zpl_isize bytes); + + //! Calculates difference between two addresses. + ZPL_DEF_INLINE zpl_isize zpl_pointer_diff(void const *begin, void const *end); + + #define zpl_ptr_add zpl_pointer_add + #define zpl_ptr_sub zpl_pointer_sub + #define zpl_ptr_add_const zpl_pointer_add_const + #define zpl_ptr_sub_const zpl_pointer_sub_const + #define zpl_ptr_diff zpl_pointer_diff + + //! Clears up memory at location by specified size. + + //! @param ptr Memory location to clear up. + //! @param size The size to clear up with. + ZPL_DEF_INLINE void zpl_zero_size(void *ptr, zpl_isize size); + + #ifndef zpl_zero_item + //! Clears up an item. + #define zpl_zero_item(t) zpl_zero_size((t), zpl_size_of(*(t))) // NOTE: Pass pointer of struct + + //! Clears up an array. + #define zpl_zero_array(a, count) zpl_zero_size((a), zpl_size_of(*(a)) * count) + #endif + + //! Copy memory from source to destination. + ZPL_DEF_INLINE void *zpl_memmove(void *dest, void const *source, zpl_isize size); + + //! Set constant value at memory location with specified size. + ZPL_DEF_INLINE void *zpl_memset(void *data, zpl_u8 byte_value, zpl_isize size); + + //! Compare two memory locations with specified size. + ZPL_DEF_INLINE zpl_i32 zpl_memcompare(void const *s1, void const *s2, zpl_isize size); + + //! Swap memory contents between 2 locations with size. + ZPL_DEF void zpl_memswap(void *i, void *j, zpl_isize size); + + //! Search for a constant value within the size limit at memory location. + ZPL_DEF void const *zpl_memchr(void const *data, zpl_u8 byte_value, zpl_isize size); + + //! Search for a constant value within the size limit at memory location in backwards. + ZPL_DEF void const *zpl_memrchr(void const *data, zpl_u8 byte_value, zpl_isize size); + + //! Copy non-overlapping memory from source to destination. + ZPL_DEF void *zpl_memcopy(void *dest, void const *source, zpl_isize size); + + #ifndef zpl_memcopy_array + + //! Copy non-overlapping array. + #define zpl_memcopy_array(dst, src, count) zpl_memcopy((dst), (src), zpl_size_of(*(dst)) * (count)) + #endif + + //! Copy an array. + #ifndef zpl_memmove_array + #define zpl_memmove_array(dst, src, count) zpl_memmove((dst), (src), zpl_size_of(*(dst)) * (count)) + #endif + + #ifndef ZPL_BIT_CAST + #define ZPL_BIT_CAST(dest, source) \ + do { \ + ZPL_STATIC_ASSERT(zpl_size_of(*(dest)) <= zpl_size_of(source), "zpl_size_of(*(dest)) !<= zpl_size_of(source)");\ + zpl_memcopy((dest), &(source), zpl_size_of(*dest)); \ + } while (0) + #endif + + #ifndef zpl_kilobytes + #define zpl_kilobytes(x) ((x) * (zpl_i64)(1024)) + #define zpl_megabytes(x) (zpl_kilobytes(x) * (zpl_i64)(1024)) + #define zpl_gigabytes(x) (zpl_megabytes(x) * (zpl_i64)(1024)) + #define zpl_terabytes(x) (zpl_gigabytes(x) * (zpl_i64)(1024)) + #endif + + + /* inlines */ + + #define ZPL__ONES (cast(zpl_usize) - 1 / ZPL_U8_MAX) + #define ZPL__HIGHS (ZPL__ONES * (ZPL_U8_MAX / 2 + 1)) + #define ZPL__HAS_ZERO(x) (((x)-ZPL__ONES) & ~(x)&ZPL__HIGHS) + + ZPL_IMPL_INLINE void *zpl_align_forward(void *ptr, zpl_isize alignment) { + zpl_uintptr p; + + ZPL_ASSERT(zpl_is_power_of_two(alignment)); + + p = cast(zpl_uintptr) ptr; + return cast(void *)((p + (alignment - 1)) & ~(alignment - 1)); + } + + ZPL_IMPL_INLINE zpl_i64 zpl_align_forward_i64(zpl_i64 value, zpl_isize alignment) { + return value + (alignment - value % alignment) % alignment; + } + + ZPL_IMPL_INLINE zpl_u64 zpl_align_forward_u64(zpl_u64 value, zpl_usize alignment) { + return value + (alignment - value % alignment) % alignment; + } + + ZPL_IMPL_INLINE void *zpl_pointer_add(void *ptr, zpl_isize bytes) { return cast(void *)(cast(zpl_u8 *) ptr + bytes); } + ZPL_IMPL_INLINE void *zpl_pointer_sub(void *ptr, zpl_isize bytes) { return cast(void *)(cast(zpl_u8 *) ptr - bytes); } + ZPL_IMPL_INLINE void const *zpl_pointer_add_const(void const *ptr, zpl_isize bytes) { + return cast(void const *)(cast(zpl_u8 const *) ptr + bytes); + } + ZPL_IMPL_INLINE void const *zpl_pointer_sub_const(void const *ptr, zpl_isize bytes) { + return cast(void const *)(cast(zpl_u8 const *) ptr - bytes); + } + ZPL_IMPL_INLINE zpl_isize zpl_pointer_diff(void const *begin, void const *end) { + return cast(zpl_isize)(cast(zpl_u8 const *) end - cast(zpl_u8 const *) begin); + } + + ZPL_IMPL_INLINE void zpl_zero_size(void *ptr, zpl_isize size) { zpl_memset(ptr, 0, size); } + + #if defined(_MSC_VER) && !defined(__clang__) + #pragma intrinsic(__movsb) + #endif + + ZPL_IMPL_INLINE void *zpl_memmove(void *dest, void const *source, zpl_isize n) { + if (dest == NULL) { return NULL; } + + zpl_u8 *d = cast(zpl_u8 *) dest; + zpl_u8 const *s = cast(zpl_u8 const *) source; + + if (d == s) return d; + if (s + n <= d || d + n <= s) // NOTE: Non-overlapping + return zpl_memcopy(d, s, n); + + if (d < s) { + if (cast(zpl_uintptr) s % zpl_size_of(zpl_isize) == cast(zpl_uintptr) d % zpl_size_of(zpl_isize)) { + while (cast(zpl_uintptr) d % zpl_size_of(zpl_isize)) { + if (!n--) return dest; + *d++ = *s++; + } + while (n >= zpl_size_of(zpl_isize)) { + *cast(zpl_isize *) d = *cast(zpl_isize *) s; + n -= zpl_size_of(zpl_isize); + d += zpl_size_of(zpl_isize); + s += zpl_size_of(zpl_isize); + } + } + for (; n; n--) *d++ = *s++; + } else { + if ((cast(zpl_uintptr) s % zpl_size_of(zpl_isize)) == (cast(zpl_uintptr) d % zpl_size_of(zpl_isize))) { + while (cast(zpl_uintptr)(d + n) % zpl_size_of(zpl_isize)) { + if (!n--) return dest; + d[n] = s[n]; + } + while (n >= zpl_size_of(zpl_isize)) { + n -= zpl_size_of(zpl_isize); + *cast(zpl_isize *)(d + n) = *cast(zpl_isize *)(s + n); + } + } + while (n) n--, d[n] = s[n]; + } + + return dest; + } + + ZPL_IMPL_INLINE void *zpl_memset(void *dest, zpl_u8 c, zpl_isize n) { + if (dest == NULL) { return NULL; } + + zpl_u8 *s = cast(zpl_u8 *) dest; + zpl_isize k; + zpl_u32 c32 = ((zpl_u32)-1) / 255 * c; + + if (n == 0) return dest; + s[0] = s[n - 1] = c; + if (n < 3) return dest; + s[1] = s[n - 2] = c; + s[2] = s[n - 3] = c; + if (n < 7) return dest; + s[3] = s[n - 4] = c; + if (n < 9) return dest; + + k = -cast(zpl_intptr) s & 3; + s += k; + n -= k; + n &= -4; + + *cast(zpl_u32 *)(s + 0) = c32; + *cast(zpl_u32 *)(s + n - 4) = c32; + if (n < 9) return dest; + *cast(zpl_u32 *)(s + 4) = c32; + *cast(zpl_u32 *)(s + 8) = c32; + *cast(zpl_u32 *)(s + n - 12) = c32; + *cast(zpl_u32 *)(s + n - 8) = c32; + if (n < 25) return dest; + *cast(zpl_u32 *)(s + 12) = c32; + *cast(zpl_u32 *)(s + 16) = c32; + *cast(zpl_u32 *)(s + 20) = c32; + *cast(zpl_u32 *)(s + 24) = c32; + *cast(zpl_u32 *)(s + n - 28) = c32; + *cast(zpl_u32 *)(s + n - 24) = c32; + *cast(zpl_u32 *)(s + n - 20) = c32; + *cast(zpl_u32 *)(s + n - 16) = c32; + + k = 24 + (cast(zpl_uintptr) s & 4); + s += k; + n -= k; + + { + zpl_u64 c64 = (cast(zpl_u64) c32 << 32) | c32; + while (n > 31) { + *cast(zpl_u64 *)(s + 0) = c64; + *cast(zpl_u64 *)(s + 8) = c64; + *cast(zpl_u64 *)(s + 16) = c64; + *cast(zpl_u64 *)(s + 24) = c64; + + n -= 32; + s += 32; + } + } + + return dest; + } + + ZPL_IMPL_INLINE zpl_i32 zpl_memcompare(void const *s1, void const *s2, zpl_isize size) { + zpl_u8 const *s1p8 = cast(zpl_u8 const *) s1; + zpl_u8 const *s2p8 = cast(zpl_u8 const *) s2; + + if (s1 == NULL || s2 == NULL) { return 0; } + + while (size--) { + zpl_isize d; + if ((d = (*s1p8++ - *s2p8++)) != 0) return cast(zpl_i32) d; + } + return 0; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_is_power_of_two(zpl_isize x) { + if (x <= 0) return false; + return !(x & (x - 1)); + } + + ZPL_END_C_DECLS + // file: header/essentials/memory_custom.h + + //////////////////////////////////////////////////////////////// + // + // Custom Allocation + // + // + + ZPL_BEGIN_C_DECLS + + typedef enum zpl_alloc_type { + ZPL_ALLOCATION_ALLOC, + ZPL_ALLOCATION_FREE, + ZPL_ALLOCATION_FREE_ALL, + ZPL_ALLOCATION_RESIZE, + } zpl_alloc_type; + + // NOTE: This is useful so you can define an allocator of the same type and parameters + #define ZPL_ALLOCATOR_PROC(name) \ + void *name(void *allocator_data, zpl_alloc_type type, zpl_isize size, zpl_isize alignment, void *old_memory, \ + zpl_isize old_size, zpl_u64 flags) + typedef ZPL_ALLOCATOR_PROC(zpl_allocator_proc); + + + typedef struct zpl_allocator { + zpl_allocator_proc *proc; + void *data; + } zpl_allocator; + + typedef enum zpl_alloc_flag { + ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO = ZPL_BIT(0), + } zpl_alloc_flag; + + #ifndef ZPL_DEFAULT_MEMORY_ALIGNMENT + #define ZPL_DEFAULT_MEMORY_ALIGNMENT (2 * zpl_size_of(void *)) + #endif + + #ifndef ZPL_DEFAULT_ALLOCATOR_FLAGS + #define ZPL_DEFAULT_ALLOCATOR_FLAGS (ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) + #endif + + //! Allocate memory with specified alignment. + ZPL_DEF_INLINE void *zpl_alloc_align(zpl_allocator a, zpl_isize size, zpl_isize alignment); + + //! Allocate memory with default alignment. + ZPL_DEF_INLINE void *zpl_alloc(zpl_allocator a, zpl_isize size); + + //! Free allocated memory. + ZPL_DEF_INLINE void zpl_free(zpl_allocator a, void *ptr); + + //! Free all memory allocated by an allocator. + ZPL_DEF_INLINE void zpl_free_all(zpl_allocator a); + + //! Resize an allocated memory. + ZPL_DEF_INLINE void *zpl_resize(zpl_allocator a, void *ptr, zpl_isize old_size, zpl_isize new_size); + + //! Resize an allocated memory with specified alignment. + ZPL_DEF_INLINE void *zpl_resize_align(zpl_allocator a, void *ptr, zpl_isize old_size, zpl_isize new_size, zpl_isize alignment); + + //! Allocate memory and copy data into it. + ZPL_DEF_INLINE void *zpl_alloc_copy(zpl_allocator a, void const *src, zpl_isize size); + + //! Allocate memory with specified alignment and copy data into it. + ZPL_DEF_INLINE void *zpl_alloc_copy_align(zpl_allocator a, void const *src, zpl_isize size, zpl_isize alignment); + + //! Allocate memory for null-terminated C-String. + ZPL_DEF char *zpl_alloc_str(zpl_allocator a, char const *str); + + //! Allocate memory for C-String with specified size. + ZPL_DEF_INLINE char *zpl_alloc_str_len(zpl_allocator a, char const *str, zpl_isize len); + + #ifndef zpl_alloc_item + + //! Allocate memory for an item. + #define zpl_alloc_item(allocator_, Type) (Type *)zpl_alloc(allocator_, zpl_size_of(Type)) + + //! Allocate memory for an array of items. + #define zpl_alloc_array(allocator_, Type, count) (Type *)zpl_alloc(allocator_, zpl_size_of(Type) * (count)) + #endif + + /* heap memory analysis tools */ + /* define ZPL_HEAP_ANALYSIS to enable this feature */ + /* call zpl_heap_stats_init at the beginning of the entry point */ + /* you can call zpl_heap_stats_check near the end of the execution to validate any possible leaks */ + ZPL_DEF void zpl_heap_stats_init(void); + ZPL_DEF zpl_isize zpl_heap_stats_used_memory(void); + ZPL_DEF zpl_isize zpl_heap_stats_alloc_count(void); + ZPL_DEF void zpl_heap_stats_check(void); + + //! Allocate/Resize memory using default options. + + //! Use this if you don't need a "fancy" resize allocation + ZPL_DEF_INLINE void *zpl_default_resize_align(zpl_allocator a, void *ptr, zpl_isize old_size, zpl_isize new_size, zpl_isize alignment); + + //! The heap allocator backed by operating system's memory manager. + ZPL_DEF_INLINE zpl_allocator zpl_heap_allocator(void); + ZPL_DEF ZPL_ALLOCATOR_PROC(zpl_heap_allocator_proc); + + #ifndef zpl_malloc + + //! Helper to allocate memory using heap allocator. + #define zpl_malloc(sz) zpl_alloc(zpl_heap_allocator( ), sz) + + //! Helper to free memory allocated by heap allocator. + #define zpl_mfree(ptr) zpl_free(zpl_heap_allocator( ), ptr) + + //! Alias to heap allocator. + #define zpl_heap zpl_heap_allocator + #endif + + // + // Arena Allocator + // + + typedef struct zpl_arena { + zpl_allocator backing; + void *physical_start; + zpl_isize total_size; + zpl_isize total_allocated; + zpl_isize temp_count; + } zpl_arena; + + //! Initialize memory arena from existing memory region. + ZPL_DEF_INLINE void zpl_arena_init_from_memory(zpl_arena *arena, void *start, zpl_isize size); + + //! Initialize memory arena using existing memory allocator. + ZPL_DEF_INLINE void zpl_arena_init_from_allocator(zpl_arena *arena, zpl_allocator backing, zpl_isize size); + + //! Initialize memory arena within an existing parent memory arena. + ZPL_DEF_INLINE void zpl_arena_init_sub(zpl_arena *arena, zpl_arena *parent_arena, zpl_isize size); + + //! Release the memory used by memory arena. + ZPL_DEF_INLINE void zpl_arena_free(zpl_arena *arena); + + + //! Retrieve memory arena's aligned allocation address. + ZPL_DEF_INLINE zpl_isize zpl_arena_alignment_of(zpl_arena *arena, zpl_isize alignment); + + //! Retrieve memory arena's remaining size. + ZPL_DEF_INLINE zpl_isize zpl_arena_size_remaining(zpl_arena *arena, zpl_isize alignment); + + //! Check whether memory arena has any temporary snapshots. + ZPL_DEF_INLINE void zpl_arena_check(zpl_arena *arena); + + //! Allocation Types: alloc, free_all, resize + ZPL_DEF_INLINE zpl_allocator zpl_arena_allocator(zpl_arena *arena); + ZPL_DEF ZPL_ALLOCATOR_PROC(zpl_arena_allocator_proc); + + + typedef struct zpl_arena_snapshot { + zpl_arena *arena; + zpl_isize original_count; + } zpl_arena_snapshot; + + //! Capture a snapshot of used memory in a memory arena. + ZPL_DEF_INLINE zpl_arena_snapshot zpl_arena_snapshot_begin(zpl_arena *arena); + + //! Reset memory arena's usage by a captured snapshot. + ZPL_DEF_INLINE void zpl_arena_snapshot_end(zpl_arena_snapshot tmp_mem); + + // + // Pool Allocator + // + + + typedef struct zpl_pool { + zpl_allocator backing; + void *physical_start; + void *free_list; + zpl_isize block_size; + zpl_isize block_align; + zpl_isize total_size; + zpl_isize num_blocks; + } zpl_pool; + + + //! Initialize pool allocator. + ZPL_DEF_INLINE void zpl_pool_init(zpl_pool *pool, zpl_allocator backing, zpl_isize num_blocks, zpl_isize block_size); + + //! Initialize pool allocator with specific block alignment. + ZPL_DEF void zpl_pool_init_align(zpl_pool *pool, zpl_allocator backing, zpl_isize num_blocks, zpl_isize block_size, + zpl_isize block_align); + + //! Release the resources used by pool allocator. + ZPL_DEF_INLINE void zpl_pool_free(zpl_pool *pool); + + //! Allocation Types: alloc, free + ZPL_DEF_INLINE zpl_allocator zpl_pool_allocator(zpl_pool *pool); + ZPL_DEF ZPL_ALLOCATOR_PROC(zpl_pool_allocator_proc); + + // + // Scratch Memory Allocator - Ring Buffer Based Arena + // + + typedef struct zpl_allocation_header_ev { + zpl_isize size; + } zpl_allocation_header_ev; + + ZPL_DEF_INLINE zpl_allocation_header_ev *zpl_allocation_header(void *data); + ZPL_DEF_INLINE void zpl_allocation_header_fill(zpl_allocation_header_ev *header, void *data, zpl_isize size); + + #if defined(ZPL_ARCH_32_BIT) + #define ZPL_ISIZE_HIGH_BIT 0x80000000 + #elif defined(ZPL_ARCH_64_BIT) + #define ZPL_ISIZE_HIGH_BIT 0x8000000000000000ll + #else + #error + #endif + + typedef struct zpl_scratch_memory { + void *physical_start; + zpl_isize total_size; + void *alloc_point; + void *free_point; + } zpl_scratch_memory; + + //! Initialize ring buffer arena. + ZPL_DEF void zpl_scratch_memory_init(zpl_scratch_memory *s, void *start, zpl_isize size); + + //! Check whether ring buffer arena is in use. + ZPL_DEF zpl_b32 zpl_scratch_memory_is_in_use(zpl_scratch_memory *s, void *ptr); + + //! Allocation Types: alloc, free, free_all, resize + ZPL_DEF zpl_allocator zpl_scratch_allocator(zpl_scratch_memory *s); + ZPL_DEF ZPL_ALLOCATOR_PROC(zpl_scratch_allocator_proc); + + // + // Stack Memory Allocator + // + + + typedef struct zpl_stack_memory { + zpl_allocator backing; + + void *physical_start; + zpl_usize total_size; + zpl_usize allocated; + } zpl_stack_memory; + + //! Initialize stack allocator from existing memory. + ZPL_DEF_INLINE void zpl_stack_memory_init_from_memory(zpl_stack_memory *s, void *start, zpl_isize size); + + //! Initialize stack allocator using existing memory allocator. + ZPL_DEF_INLINE void zpl_stack_memory_init(zpl_stack_memory *s, zpl_allocator backing, zpl_isize size); + + //! Check whether stack allocator is in use. + ZPL_DEF_INLINE zpl_b32 zpl_stack_memory_is_in_use(zpl_stack_memory *s, void *ptr); + + //! Release the resources used by stack allocator. + ZPL_DEF_INLINE void zpl_stack_memory_free(zpl_stack_memory *s); + + //! Allocation Types: alloc, free, free_all + ZPL_DEF_INLINE zpl_allocator zpl_stack_allocator(zpl_stack_memory *s); + ZPL_DEF ZPL_ALLOCATOR_PROC(zpl_stack_allocator_proc); + + /* inlines */ + + ZPL_IMPL_INLINE void *zpl_alloc_align(zpl_allocator a, zpl_isize size, zpl_isize alignment) { + return a.proc(a.data, ZPL_ALLOCATION_ALLOC, size, alignment, NULL, 0, ZPL_DEFAULT_ALLOCATOR_FLAGS); + } + ZPL_IMPL_INLINE void *zpl_alloc(zpl_allocator a, zpl_isize size) { + return zpl_alloc_align(a, size, ZPL_DEFAULT_MEMORY_ALIGNMENT); + } + ZPL_IMPL_INLINE void zpl_free(zpl_allocator a, void *ptr) { + if (ptr != NULL) a.proc(a.data, ZPL_ALLOCATION_FREE, 0, 0, ptr, 0, ZPL_DEFAULT_ALLOCATOR_FLAGS); + } + ZPL_IMPL_INLINE void zpl_free_all(zpl_allocator a) { + a.proc(a.data, ZPL_ALLOCATION_FREE_ALL, 0, 0, NULL, 0, ZPL_DEFAULT_ALLOCATOR_FLAGS); + } + ZPL_IMPL_INLINE void *zpl_resize(zpl_allocator a, void *ptr, zpl_isize old_size, zpl_isize new_size) { + return zpl_resize_align(a, ptr, old_size, new_size, ZPL_DEFAULT_MEMORY_ALIGNMENT); + } + ZPL_IMPL_INLINE void *zpl_resize_align(zpl_allocator a, void *ptr, zpl_isize old_size, zpl_isize new_size, zpl_isize alignment) { + return a.proc(a.data, ZPL_ALLOCATION_RESIZE, new_size, alignment, ptr, old_size, ZPL_DEFAULT_ALLOCATOR_FLAGS); + } + + ZPL_IMPL_INLINE void *zpl_alloc_copy(zpl_allocator a, void const *src, zpl_isize size) { + return zpl_memcopy(zpl_alloc(a, size), src, size); + } + ZPL_IMPL_INLINE void *zpl_alloc_copy_align(zpl_allocator a, void const *src, zpl_isize size, zpl_isize alignment) { + return zpl_memcopy(zpl_alloc_align(a, size, alignment), src, size); + } + + ZPL_IMPL_INLINE char *zpl_alloc_str_len(zpl_allocator a, char const *str, zpl_isize len) { + char *result; + result = cast(char *) zpl_alloc(a, len + 1); + zpl_memmove(result, str, len); + result[len] = '\0'; + return result; + } + + ZPL_IMPL_INLINE void *zpl_default_resize_align(zpl_allocator a, void *old_memory, zpl_isize old_size, zpl_isize new_size, + zpl_isize alignment) { + if (!old_memory) return zpl_alloc_align(a, new_size, alignment); + + if (new_size == 0) { + zpl_free(a, old_memory); + return NULL; + } + + if (new_size < old_size) new_size = old_size; + + if (old_size == new_size) { + return old_memory; + } else { + void *new_memory = zpl_alloc_align(a, new_size, alignment); + if (!new_memory) return NULL; + zpl_memmove(new_memory, old_memory, zpl_min(new_size, old_size)); + zpl_free(a, old_memory); + return new_memory; + } + } + + // + // Heap Allocator + // + + ZPL_IMPL_INLINE zpl_allocator zpl_heap_allocator(void) { + zpl_allocator a; + a.proc = zpl_heap_allocator_proc; + a.data = NULL; + return a; + } + + // + // Arena Allocator + // + + ZPL_IMPL_INLINE void zpl_arena_init_from_memory(zpl_arena *arena, void *start, zpl_isize size) { + arena->backing.proc = NULL; + arena->backing.data = NULL; + arena->physical_start = start; + arena->total_size = size; + arena->total_allocated = 0; + arena->temp_count = 0; + } + + ZPL_IMPL_INLINE void zpl_arena_init_from_allocator(zpl_arena *arena, zpl_allocator backing, zpl_isize size) { + arena->backing = backing; + arena->physical_start = zpl_alloc(backing, size); // NOTE: Uses default alignment + arena->total_size = size; + arena->total_allocated = 0; + arena->temp_count = 0; + } + + ZPL_IMPL_INLINE void zpl_arena_init_sub(zpl_arena *arena, zpl_arena *parent_arena, zpl_isize size) { + zpl_arena_init_from_allocator(arena, zpl_arena_allocator(parent_arena), size); + } + + ZPL_IMPL_INLINE void zpl_arena_free(zpl_arena *arena) { + if (arena->backing.proc) { + zpl_free(arena->backing, arena->physical_start); + arena->physical_start = NULL; + } + } + + ZPL_IMPL_INLINE zpl_isize zpl_arena_alignment_of(zpl_arena *arena, zpl_isize alignment) { + zpl_isize alignment_offset, result_pointer, mask; + ZPL_ASSERT(zpl_is_power_of_two(alignment)); + + alignment_offset = 0; + result_pointer = cast(zpl_isize) arena->physical_start + arena->total_allocated; + mask = alignment - 1; + if (result_pointer & mask) alignment_offset = alignment - (result_pointer & mask); + + return alignment_offset; + } + + ZPL_IMPL_INLINE zpl_isize zpl_arena_size_remaining(zpl_arena *arena, zpl_isize alignment) { + zpl_isize result = arena->total_size - (arena->total_allocated + zpl_arena_alignment_of(arena, alignment)); + return result; + } + + ZPL_IMPL_INLINE void zpl_arena_check(zpl_arena *arena) { ZPL_ASSERT(arena->temp_count == 0); } + + ZPL_IMPL_INLINE zpl_allocator zpl_arena_allocator(zpl_arena *arena) { + zpl_allocator allocator; + allocator.proc = zpl_arena_allocator_proc; + allocator.data = arena; + return allocator; + } + + ZPL_IMPL_INLINE zpl_arena_snapshot zpl_arena_snapshot_begin(zpl_arena *arena) { + zpl_arena_snapshot tmp; + tmp.arena = arena; + tmp.original_count = arena->total_allocated; + arena->temp_count++; + return tmp; + } + + ZPL_IMPL_INLINE void zpl_arena_snapshot_end(zpl_arena_snapshot tmp) { + ZPL_ASSERT(tmp.arena->total_allocated >= tmp.original_count); + ZPL_ASSERT(tmp.arena->temp_count > 0); + tmp.arena->total_allocated = tmp.original_count; + tmp.arena->temp_count--; + } + + // + // Pool Allocator + // + + ZPL_IMPL_INLINE void zpl_pool_init(zpl_pool *pool, zpl_allocator backing, zpl_isize num_blocks, zpl_isize block_size) { + zpl_pool_init_align(pool, backing, num_blocks, block_size, ZPL_DEFAULT_MEMORY_ALIGNMENT); + } + + ZPL_IMPL_INLINE void zpl_pool_free(zpl_pool *pool) { + if (pool->backing.proc) { zpl_free(pool->backing, pool->physical_start); } + } + + ZPL_IMPL_INLINE zpl_allocator zpl_pool_allocator(zpl_pool *pool) { + zpl_allocator allocator; + allocator.proc = zpl_pool_allocator_proc; + allocator.data = pool; + return allocator; + } + + ZPL_IMPL_INLINE zpl_allocation_header_ev *zpl_allocation_header(void *data) { + zpl_isize *p = cast(zpl_isize *) data; + while (p[-1] == cast(zpl_isize)(-1)) p--; + return cast(zpl_allocation_header_ev *) p - 1; + } + + ZPL_IMPL_INLINE void zpl_allocation_header_fill(zpl_allocation_header_ev *header, void *data, zpl_isize size) { + zpl_isize *ptr; + header->size = size; + ptr = cast(zpl_isize *)(header + 1); + while (cast(void *) ptr < data) *ptr++ = cast(zpl_isize)(-1); + } + + // + // Stack Memory Allocator + // + + #define ZPL_STACK_ALLOC_OFFSET sizeof(zpl_u64) + ZPL_STATIC_ASSERT(ZPL_STACK_ALLOC_OFFSET == 8, "ZPL_STACK_ALLOC_OFFSET != 8"); + + ZPL_IMPL_INLINE void zpl_stack_memory_init_from_memory(zpl_stack_memory *s, void *start, zpl_isize size) { + s->physical_start = start; + s->total_size = size; + s->allocated = 0; + } + + ZPL_IMPL_INLINE void zpl_stack_memory_init(zpl_stack_memory *s, zpl_allocator backing, zpl_isize size) { + s->backing = backing; + s->physical_start = zpl_alloc(backing, size); + s->total_size = size; + s->allocated = 0; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_stack_memory_is_in_use(zpl_stack_memory *s, void *ptr) { + if (s->allocated == 0) return false; + + if (ptr > s->physical_start && ptr < zpl_pointer_add(s->physical_start, s->total_size)) { return true; } + + return false; + } + + ZPL_IMPL_INLINE void zpl_stack_memory_free(zpl_stack_memory *s) { + if (s->backing.proc) { + zpl_free(s->backing, s->physical_start); + s->physical_start = NULL; + } + } + + ZPL_IMPL_INLINE zpl_allocator zpl_stack_allocator(zpl_stack_memory *s) { + zpl_allocator a; + a.proc = zpl_stack_allocator_proc; + a.data = s; + return a; + } + + ZPL_END_C_DECLS + // file: header/essentials/collections/array.h + + //////////////////////////////////////////////////////////////// + // + // Dynamic Array (POD Types) + // + // zpl_array(Type) works like zpl_string or zpl_buffer where the actual type is just a pointer to the first + // element. + // + // Available Procedures for zpl_array(Type) + // zpl_array_init + // zpl_array_free + // zpl_array_set_capacity + // zpl_array_grow + // zpl_array_append + // zpl_array_appendv + // zpl_array_pop + // zpl_array_clear + // zpl_array_back + // zpl_array_front + // zpl_array_resize + // zpl_array_reserve + // + + #if 0 // Example + void foo(void) { + zpl_isize i; + int test_values[] = {4, 2, 1, 7}; + zpl_allocator a = zpl_heap_allocator(); + zpl_array(int) items; + + zpl_array_init(items, a); + + zpl_array_append(items, 1); + zpl_array_append(items, 4); + zpl_array_append(items, 9); + zpl_array_append(items, 16); + + items[1] = 3; // Manually set value + // NOTE: No array bounds checking + + for (i = 0; i < items.count; i++) + zpl_printf("%d\n", items[i]); + // 1 + // 3 + // 9 + // 16 + + zpl_array_clear(items); + + zpl_array_appendv(items, test_values, zpl_count_of(test_values)); + for (i = 0; i < items.count; i++) + zpl_printf("%d\n", items[i]); + // 4 + // 2 + // 1 + // 7 + + zpl_array_free(items); + } + #endif + + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_array_header { + zpl_isize elem_size; + zpl_isize count; + zpl_isize capacity; + zpl_allocator allocator; + } zpl_array_header; + + #define zpl_array(Type) Type * + + #define zpl_array_make(Type, Name, allocator) Type *Name; zpl_array_init(Name, allocator) + + #ifndef ZPL_ARRAY_GROW_FORMULA + #define ZPL_ARRAY_GROW_FORMULA(x) (2 * (x) + 8) + #endif + + ZPL_STATIC_ASSERT(ZPL_ARRAY_GROW_FORMULA(0) > 0, "ZPL_ARRAY_GROW_FORMULA(0) <= 0"); + + #define ZPL_ARRAY_HEADER(x) (cast(zpl_array_header *)(x) - 1) + #define zpl_array_allocator(x) (ZPL_ARRAY_HEADER(x)->allocator) + #define zpl_array_elem_size(x) (ZPL_ARRAY_HEADER(x)->elem_size) + #define zpl_array_count(x) (ZPL_ARRAY_HEADER(x)->count) + #define zpl_array_capacity(x) (ZPL_ARRAY_HEADER(x)->capacity) + #define zpl_array_end(x) (x + (zpl_array_count(x) - 1)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_init_reserve(void **zpl__array_, zpl_allocator allocator_, zpl_isize elem_size, zpl_isize cap) { + zpl_array_header *zpl__ah = + cast(zpl_array_header *) zpl_alloc(allocator_, zpl_size_of(zpl_array_header) + elem_size * cap); + if (!zpl__ah) return false; + zpl__ah->allocator = allocator_; + zpl__ah->elem_size = elem_size; + zpl__ah->count = 0; + zpl__ah->capacity = cap; + *zpl__array_ = cast(void *)(zpl__ah + 1); + return true; + } + + #define zpl_array_init_reserve(x, allocator_, cap) zpl__array_init_reserve(cast(void **) & (x), allocator_, zpl_size_of(*(x)), (cap)) + + // NOTE: Give it an initial default capacity + #define zpl_array_init(x, allocator) zpl_array_init_reserve(x, allocator, ZPL_ARRAY_GROW_FORMULA(0)) + + #define zpl_array_free(x) \ + do { \ + if (x) { \ + zpl_array_header *zpl__ah = ZPL_ARRAY_HEADER(x); \ + zpl_free(zpl__ah->allocator, zpl__ah); \ + } \ + } while (0) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_set_capacity(void **array, zpl_isize capacity) { + zpl_array_header *h = ZPL_ARRAY_HEADER(*array); + if (capacity == h->capacity) return true; + if (capacity < h->count) h->count = capacity; + zpl_isize size = zpl_size_of(zpl_array_header) + h->elem_size * capacity; + zpl_array_header *nh = cast(zpl_array_header *) zpl_alloc(h->allocator, size); + if (!nh) return false; + zpl_memmove(nh, h, zpl_size_of(zpl_array_header) + h->elem_size * h->count); + nh->allocator = h->allocator; + nh->elem_size = h->elem_size; + nh->count = h->count; + nh->capacity = capacity; + zpl_free(h->allocator, h); + *array = nh + 1; + return true; + } + + #define zpl_array_set_capacity(x, capacity) zpl__array_set_capacity(cast(void **) & (x), (capacity)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_grow(void **x, zpl_isize min_capacity) { + zpl_isize new_capacity = ZPL_ARRAY_GROW_FORMULA(zpl_array_capacity(*x)); + if (new_capacity < min_capacity) new_capacity = min_capacity; + return zpl__array_set_capacity(x, new_capacity); + } + + #define zpl_array_grow(x, min_capacity) zpl__array_grow(cast(void **) & (x), (min_capacity)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_append_helper(void **x) { + if (zpl_array_capacity(*x) < zpl_array_count(*x) + 1) { + if (!zpl__array_grow(x, 0)) return false; + } + return true; + } + + #define zpl_array_append(x, item) (zpl__array_append_helper(cast(void **) & (x)) && (((x)[zpl_array_count(x)++] = (item)), true)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_append_at_helper(void **x, zpl_isize ind) { + if (ind >= zpl_array_count(*x)) ind = zpl_array_count(*x) - 1; + if (ind < 0) ind = 0; + if (zpl_array_capacity(*x) < zpl_array_count(*x) + 1) { + if (!zpl__array_grow(x, 0)) return false; + } + zpl_i8 *s = (cast(zpl_i8*)*x) + ind*zpl_array_elem_size(*x); + zpl_memmove(s + zpl_array_elem_size(*x), s, zpl_array_elem_size(*x) * (zpl_array_count(*x) - ind)); + return true; + } + + #define zpl_array_append_at(x, item, ind) (zpl__array_append_at_helper(cast(void **) & (x), (ind)) && (((x)[ind] = (item)), zpl_array_count(x)++, true)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_appendv(void **x, void *items, zpl_isize item_size, zpl_isize item_count) { + ZPL_ASSERT(item_size == zpl_array_elem_size(*x)); + if (zpl_array_capacity(*x) < zpl_array_count(*x) + item_count) { + if (!zpl__array_grow(x, zpl_array_count(*x) + item_count)) return false; + } + zpl_memcopy((cast(zpl_i8*)*x) + zpl_array_count(*x)*zpl_array_elem_size(*x), items, zpl_array_elem_size(*x) * item_count); + zpl_array_count(*x) += item_count; + return true; + } + + #define zpl_array_appendv(x, items, item_count) zpl__array_appendv(cast(void **) & (x), (items), zpl_size_of((items)[0]), (item_count)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_appendv_at(void **x, void *items, zpl_isize item_size, zpl_isize item_count, zpl_isize ind) { + if (ind >= zpl_array_count(*x)) return zpl__array_appendv(x, items, item_size, item_count); + ZPL_ASSERT(item_size == zpl_array_elem_size(*x)); + if (zpl_array_capacity(*x) < zpl_array_count(*x) + item_count) { + if (!zpl__array_grow(x, zpl_array_count(*x) + item_count)) return false; + } + zpl_memmove((cast(zpl_i8*)*x) + (ind + item_count)*zpl_array_elem_size(*x), + (cast(zpl_i8*)*x) + ind*zpl_array_elem_size(*x), zpl_array_elem_size(*x) * (zpl_array_count(*x) - ind)); + zpl_memcopy((cast(zpl_i8*)*x) + ind*zpl_array_elem_size(*x), items, zpl_array_elem_size(*x) * item_count); + zpl_array_count(*x) += item_count; + return true; + } + + #define zpl_array_appendv_at(x, items, item_count, ind) zpl__array_appendv_at(cast(void **) & (x), (items), zpl_size_of((items)[0]), (item_count), (ind)) + + #define zpl_array_fill(x, begin, end, value) \ + do { \ + ZPL_ASSERT((begin) >= 0 && (end) <= zpl_array_count(x)); \ + ZPL_ASSERT(zpl_size_of(value) == zpl_size_of((x)[0])); \ + for (zpl_isize i = (begin); i < (end); i++) { x[i] = value; } \ + } while (0) + + #define zpl_array_remove_at(x, index) \ + do { \ + zpl_array_header *zpl__ah = ZPL_ARRAY_HEADER(x); \ + ZPL_ASSERT(index < zpl__ah->count); \ + zpl_memmove(x + index, x + index + 1, zpl_size_of(x[0]) * (zpl__ah->count - index - 1)); \ + --zpl__ah->count; \ + } while (0) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_copy_init(void **y, void **x) { + if (!zpl__array_init_reserve(y, zpl_array_allocator(*x), zpl_array_elem_size(*x), zpl_array_capacity(*x))) + return false; + zpl_memcopy(*y, *x, zpl_array_capacity(*x) * zpl_array_elem_size(*x)); + zpl_array_count(*y) = zpl_array_count(*x); + return true; + } + + #define zpl_array_copy_init(y, x) zpl__array_copy_init(cast(void **) & (y), cast(void **) & (x)) + + #define zpl_array_pop(x) \ + do { \ + ZPL_ASSERT(ZPL_ARRAY_HEADER(x)->count > 0); \ + ZPL_ARRAY_HEADER(x)->count--; \ + } while (0) + #define zpl_array_back(x) x[ZPL_ARRAY_HEADER(x)->count - 1] + #define zpl_array_front(x) x[0] + #define zpl_array_clear(x) \ + do { ZPL_ARRAY_HEADER(x)->count = 0; } while (0) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_resize(void **x, zpl_isize new_count) { + if (ZPL_ARRAY_HEADER(*x)->capacity < new_count) { + if (!zpl__array_grow(x, new_count)) return false; + } + ZPL_ARRAY_HEADER(*x)->count = new_count; + return true; + } + + #define zpl_array_resize(x, new_count) zpl__array_resize(cast(void **) & (x), (new_count)) + + ZPL_IMPL_INLINE zpl_b8 zpl__array_reserve(void **x, zpl_isize new_capacity) { + if (ZPL_ARRAY_HEADER(*x)->capacity < new_capacity) return zpl__array_set_capacity(x, new_capacity); + return true; + } + + #define zpl_array_reserve(x, new_capacity) zpl__array_reserve(cast(void **) & (x), (new_capacity)) + + ZPL_END_C_DECLS + // file: header/essentials/collections/buffer.h + + //////////////////////////////////////////////////////////////// + // + // Fixed Capacity Buffer (POD Types) + // + // + // zpl_buffer(Type) works like zpl_string or zpl_array where the actual type is just a pointer to the first + // element. + // + // Available Procedures for zpl_buffer(Type) + // zpl_buffer_init + // zpl_buffer_free + // zpl_buffer_append + // zpl_buffer_appendv + // zpl_buffer_pop + // zpl_buffer_clear + + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_buffer_header { + zpl_allocator backing; + zpl_isize count; + zpl_isize capacity; + } zpl_buffer_header; + + #define zpl_buffer(Type) Type * + + #define zpl_buffer_make(Type, Name, allocator, cap) Type *Name; zpl_buffer_init(Name, allocator, cap) + + #define ZPL_BUFFER_HEADER(x) (cast(zpl_buffer_header *)(x) - 1) + #define zpl_buffer_count(x) (ZPL_BUFFER_HEADER(x)->count) + #define zpl_buffer_capacity(x) (ZPL_BUFFER_HEADER(x)->capacity) + #define zpl_buffer_end(x) (x + (zpl_buffer_count(x) - 1)) + #define zpl_buffer_allocator(x) (ZPL_BUFFER_HEADER(x)->backing) + + #define zpl_buffer_init(x, allocator, cap) \ + do { \ + void **nx = cast(void **) & (x); \ + zpl_buffer_header *zpl__bh = \ + cast(zpl_buffer_header *) zpl_alloc((allocator), sizeof(zpl_buffer_header) + (cap)*zpl_size_of(*(x))); \ + zpl__bh->backing = allocator; \ + zpl__bh->count = 0; \ + zpl__bh->capacity = cap; \ + *nx = cast(void *)(zpl__bh + 1); \ + } while (0) + + #define zpl_buffer_free(x) (zpl_free(ZPL_BUFFER_HEADER(x)->backing, ZPL_BUFFER_HEADER(x))) + + #define zpl_buffer_append(x, item) \ + do { (x)[zpl_buffer_count(x)++] = (item); } while (0) + + #define zpl_buffer_appendv(x, items, item_count) \ + do { \ + ZPL_ASSERT(zpl_size_of(*(items)) == zpl_size_of(*(x))); \ + ZPL_ASSERT(zpl_buffer_count(x) + item_count <= zpl_buffer_capacity(x)); \ + zpl_memcopy(&(x)[zpl_buffer_count(x)], (items), zpl_size_of(*(x)) * (item_count)); \ + zpl_buffer_count(x) += (item_count); \ + } while (0) + + #define zpl_buffer_copy_init(y, x) \ + do { \ + zpl_buffer_init(y, zpl_buffer_allocator(x), zpl_buffer_capacity(x)); \ + zpl_memcopy(y, x, zpl_buffer_capacity(x) * zpl_size_of(*x)); \ + zpl_buffer_count(y) = zpl_buffer_count(x); \ + } while (0) + + #define zpl_buffer_pop(x) \ + do { \ + ZPL_ASSERT(zpl_buffer_count(x) > 0); \ + zpl_buffer_count(x)--; \ + } while (0) + #define zpl_buffer_clear(x) \ + do { zpl_buffer_count(x) = 0; } while (0) + + ZPL_END_C_DECLS + // file: header/essentials/collections/list.h + + //////////////////////////////////////////////////////////////// + // + // Linked List + // + // zpl_list encapsulates pointer to data and points to the next and the previous element in the list. + // + // Available Procedures for zpl_list + // zpl_list_init + // zpl_list_add + // zpl_list_remove + + + ZPL_BEGIN_C_DECLS + + #if 0 + #define ZPL_IMPLEMENTATION + #include "zpl.h" + int main(void) + { + zpl_list s, *head, *cursor; + zpl_list_init(&s, "it is optional to call init: "); + head = cursor = &s; + + // since we can construct an element implicitly this way + // the second field gets overwritten once we add it to a list. + zpl_list a = {"hello"}; + cursor = zpl_list_add(cursor, &a); + + zpl_list b = {"world"}; + cursor = zpl_list_add(cursor, &b); + + zpl_list c = {"!!! OK"}; + cursor = zpl_list_add(cursor, &c); + + for (zpl_list *l=head; l; l=l->next) { + zpl_printf("%s ", cast(char *)l->ptr); + } + zpl_printf("\n"); + + return 0; + } + #endif + + + typedef struct zpl__list { + void const *ptr; + struct zpl__list *next, *prev; + } zpl_list; + + ZPL_DEF_INLINE void zpl_list_init(zpl_list *list, void const *ptr); + ZPL_DEF_INLINE zpl_list *zpl_list_add(zpl_list *list, zpl_list *item); + + // NOTE(zaklaus): Returns a pointer to the next node (or NULL if the removed node has no trailing node.) + ZPL_DEF_INLINE zpl_list *zpl_list_remove(zpl_list *list); + + + ZPL_IMPL_INLINE void zpl_list_init(zpl_list *list, void const *ptr) { + zpl_list list_ = { 0 }; + *list = list_; + list->ptr = ptr; + } + + ZPL_IMPL_INLINE zpl_list *zpl_list_add(zpl_list *list, zpl_list *item) { + item->next = NULL; + + if (list->next) { item->next = list->next; } + + list->next = item; + item->prev = list; + return item; + } + + ZPL_IMPL_INLINE zpl_list *zpl_list_remove(zpl_list *list) { + if (list->prev) { list->prev->next = list->next; } + + return list->next; + } + + ZPL_END_C_DECLS + // file: header/essentials/collections/ring.h + + //////////////////////////////////////////////////////////////// + // + // Instantiated Circular buffer + // + + /* + Buffer type and function declaration, call: ZPL_RING_DECLARE(PREFIX, FUNC, VALUE) + Buffer function definitions, call: ZPL_RING_DEFINE(PREFIX, FUNC, VALUE) + + PREFIX - a prefix for function prototypes e.g. extern, static, etc. + FUNC - the name will prefix function names + VALUE - the type of the value to be stored + + funcname_init(VALUE * pad, zpl_allocator a, zpl_isize max_size) + funcname_free(VALUE * pad) + funcname_full(VALUE * pad) + funcname_empty(VALUE * pad) + funcname_append(VALUE * pad, type data) + funcname_append_array(VALUE * pad, zpl_array(type) data) + funcname_get(VALUE * pad) + funcname_get_array(VALUE * pad, zpl_usize max_size, zpl_allocator a) + */ + ZPL_BEGIN_C_DECLS + + #define ZPL_RING(PREFIX, FUNC, VALUE) \ + ZPL_RING_DECLARE(PREFIX, FUNC, VALUE); \ + ZPL_RING_DEFINE(FUNC, VALUE); + + #define ZPL_RING_DECLARE(prefix,func,type) \ + typedef struct { \ + zpl_allocator backing; \ + zpl_buffer(type) buf; \ + zpl_usize head, tail; \ + zpl_usize capacity; \ + } ZPL_JOIN2(func, type); \ + \ + prefix void ZPL_JOIN2(func, init)(ZPL_JOIN2(func, type) * pad, zpl_allocator a, zpl_isize max_size); \ + prefix void ZPL_JOIN2(func, free)(ZPL_JOIN2(func, type) * pad); \ + prefix zpl_b32 ZPL_JOIN2(func, full)(ZPL_JOIN2(func, type) * pad); \ + prefix zpl_b32 ZPL_JOIN2(func, empty)(ZPL_JOIN2(func, type) * pad); \ + prefix void ZPL_JOIN2(func, append)(ZPL_JOIN2(func, type) * pad, type data); \ + prefix void ZPL_JOIN2(func, append_array)(ZPL_JOIN2(func, type) * pad, zpl_array(type) data); \ + prefix type *ZPL_JOIN2(func, get)(ZPL_JOIN2(func, type) * pad); \ + prefix zpl_array(type) \ + ZPL_JOIN2(func, get_array)(ZPL_JOIN2(func, type) * pad, zpl_usize max_size, zpl_allocator a); + + #define ZPL_RING_DEFINE(func,type) \ + void ZPL_JOIN2(func, init)(ZPL_JOIN2(func, type) * pad, zpl_allocator a, zpl_isize max_size) { \ + ZPL_JOIN2(func, type) pad_ = { 0 }; \ + *pad = pad_; \ + \ + pad->backing = a; \ + zpl_buffer_init(pad->buf, a, max_size + 1); \ + pad->capacity = max_size + 1; \ + pad->head = pad->tail = 0; \ + } \ + void ZPL_JOIN2(func, free)(ZPL_JOIN2(func, type) * pad) { \ + zpl_buffer_free(pad->buf); \ + } \ + \ + zpl_b32 ZPL_JOIN2(func, full)(ZPL_JOIN2(func, type) * pad) { \ + return ((pad->head + 1) % pad->capacity) == pad->tail; \ + } \ + \ + zpl_b32 ZPL_JOIN2(func, empty)(ZPL_JOIN2(func, type) * pad) { return pad->head == pad->tail; } \ + \ + void ZPL_JOIN2(func, append)(ZPL_JOIN2(func, type) * pad, type data) { \ + pad->buf[pad->head] = data; \ + pad->head = (pad->head + 1) % pad->capacity; \ + \ + if (pad->head == pad->tail) { pad->tail = (pad->tail + 1) % pad->capacity; } \ + } \ + \ + void ZPL_JOIN2(func, append_array)(ZPL_JOIN2(func, type) * pad, zpl_array(type) data) { \ + zpl_usize c = zpl_array_count(data); \ + for (zpl_usize i = 0; i < c; ++i) { ZPL_JOIN2(func, append)(pad, data[i]); } \ + } \ + \ + type *ZPL_JOIN2(func, get)(ZPL_JOIN2(func, type) * pad) { \ + if (ZPL_JOIN2(func, empty)(pad)) { return NULL; } \ + \ + type *data = &pad->buf[pad->tail]; \ + pad->tail = (pad->tail + 1) % pad->capacity; \ + \ + return data; \ + } \ + \ + zpl_array(type) \ + ZPL_JOIN2(func, get_array)(ZPL_JOIN2(func, type) * pad, zpl_usize max_size, zpl_allocator a) { \ + zpl_array(type) vals = 0; \ + zpl_array_init(vals, a); \ + while (--max_size && !ZPL_JOIN2(func, empty)(pad)) { \ + zpl_array_append(vals, *ZPL_JOIN2(func, get)(pad)); \ + } \ + return vals; \ + } + + ZPL_END_C_DECLS + // file: header/essentials/collections/hashtable.h + + /** @file hashtable.c + @brief Instantiated hash table + @defgroup hashtable Instantiated hash table + + + This is an attempt to implement a templated hash table + NOTE: The key is always a zpl_u64 for simplicity and you will _probably_ _never_ need anything bigger. + + Hash table type and function declaration, call: ZPL_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE) + Hash table function definitions, call: ZPL_TABLE_DEFINE(NAME, FUNC, VALUE) + + PREFIX - a prefix for function prototypes e.g. extern, static, etc. + NAME - Name of the Hash Table + FUNC - the name will prefix function names + VALUE - the type of the value to be stored + + tablename_init(NAME * h, zpl_allocator a); + tablename_destroy(NAME * h); + tablename_get(NAME * h, zpl_u64 key); + tablename_set(NAME * h, zpl_u64 key, VALUE value); + tablename_grow(NAME * h); + tablename_map(NAME * h, void (*map_proc)(zpl_u64 key, VALUE value)) + tablename_map_mut(NAME * h, void (*map_proc)(zpl_u64 key, VALUE * value)) + tablename_rehash(NAME * h, zpl_isize new_count); + tablename_remove(NAME * h, zpl_u64 key); + + @{ + */ + + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_hash_table_find_result { + zpl_isize hash_index; + zpl_isize entry_prev; + zpl_isize entry_index; + } zpl_hash_table_find_result; + + /** + * Combined macro for a quick delcaration + definition + */ + + #define ZPL_TABLE(PREFIX, NAME, FUNC, VALUE) \ + ZPL_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE); \ + ZPL_TABLE_DEFINE(NAME, FUNC, VALUE); + + /** + * Table delcaration macro that generates the interface + */ + + #define ZPL_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE) \ + typedef struct ZPL_JOIN2(NAME, Entry) { \ + zpl_u64 key; \ + zpl_isize next; \ + VALUE value; \ + } ZPL_JOIN2(NAME, Entry); \ + \ + typedef struct NAME { \ + zpl_array(zpl_isize) hashes; \ + zpl_array(ZPL_JOIN2(NAME, Entry)) entries; \ + } NAME; \ + \ + PREFIX void ZPL_JOIN2(FUNC, init) (NAME *h, zpl_allocator a); \ + PREFIX void ZPL_JOIN2(FUNC, destroy) (NAME *h); \ + PREFIX void ZPL_JOIN2(FUNC, clear) (NAME *h); \ + PREFIX VALUE *ZPL_JOIN2(FUNC, get) (NAME *h, zpl_u64 key); \ + PREFIX zpl_isize ZPL_JOIN2(FUNC, slot) (NAME *h, zpl_u64 key); \ + PREFIX void ZPL_JOIN2(FUNC, set) (NAME *h, zpl_u64 key, VALUE value); \ + PREFIX void ZPL_JOIN2(FUNC, grow) (NAME *h); \ + PREFIX void ZPL_JOIN2(FUNC, rehash) (NAME *h, zpl_isize new_count); \ + PREFIX void ZPL_JOIN2(FUNC, rehash_fast) (NAME *h); \ + PREFIX void ZPL_JOIN2(FUNC, map) (NAME *h, void (*map_proc) (zpl_u64 key, VALUE value)); \ + PREFIX void ZPL_JOIN2(FUNC, map_mut) (NAME *h, void (*map_proc) (zpl_u64 key, VALUE * value)); \ + PREFIX void ZPL_JOIN2(FUNC, remove) (NAME *h, zpl_u64 key); \ + PREFIX void ZPL_JOIN2(FUNC, remove_entry) (NAME *h, zpl_isize idx); + + /** + * Table definition interfaces that generates the implementation + */ + + #define ZPL_TABLE_DEFINE(NAME, FUNC, VALUE) \ + void ZPL_JOIN2(FUNC, init)(NAME * h, zpl_allocator a) { \ + zpl_array_init(h->hashes, a); \ + zpl_array_init(h->entries, a); \ + } \ + \ + void ZPL_JOIN2(FUNC, destroy)(NAME * h) { \ + if (h->entries) zpl_array_free(h->entries); \ + if (h->hashes) zpl_array_free(h->hashes); \ + } \ + \ + void ZPL_JOIN2(FUNC, clear)(NAME * h) { \ + for (int i = 0; i < zpl_array_count(h->hashes); i++) h->hashes[i] = -1; \ + zpl_array_clear(h->entries); \ + } \ + \ + zpl_isize ZPL_JOIN2(FUNC, slot)(NAME * h, zpl_u64 key) { \ + for (zpl_isize i = 0; i < zpl_array_count(h->entries); i++) { \ + if (h->entries[i].key == key) { \ + return i; \ + } \ + } \ + return -1; \ + } \ + \ + zpl_internal zpl_isize ZPL_JOIN2(FUNC, _add_entry)(NAME * h, zpl_u64 key) { \ + zpl_isize index; \ + ZPL_JOIN2(NAME, Entry) e = { 0 }; \ + e.key = key; \ + e.next = -1; \ + index = zpl_array_count(h->entries); \ + zpl_array_append(h->entries, e); \ + return index; \ + } \ + \ + zpl_internal zpl_hash_table_find_result ZPL_JOIN2(FUNC, _find)(NAME * h, zpl_u64 key) { \ + zpl_hash_table_find_result r = { -1, -1, -1 }; \ + if (zpl_array_count(h->hashes) > 0) { \ + r.hash_index = key % zpl_array_count(h->hashes); \ + r.entry_index = h->hashes[r.hash_index]; \ + while (r.entry_index >= 0) { \ + if (h->entries[r.entry_index].key == key) return r; \ + r.entry_prev = r.entry_index; \ + r.entry_index = h->entries[r.entry_index].next; \ + } \ + } \ + return r; \ + } \ + \ + zpl_internal zpl_b32 ZPL_JOIN2(FUNC, _full)(NAME * h) { \ + return 0.75f * zpl_array_count(h->hashes) < zpl_array_count(h->entries); \ + } \ + \ + void ZPL_JOIN2(FUNC, grow)(NAME * h) { \ + zpl_isize new_count = ZPL_ARRAY_GROW_FORMULA(zpl_array_count(h->entries)); \ + ZPL_JOIN2(FUNC, rehash)(h, new_count); \ + } \ + \ + void ZPL_JOIN2(FUNC, rehash)(NAME * h, zpl_isize new_count) { \ + zpl_isize i, j; \ + NAME nh = { 0 }; \ + ZPL_JOIN2(FUNC, init)(&nh, zpl_array_allocator(h->hashes)); \ + zpl_array_resize(nh.hashes, new_count); \ + zpl_array_reserve(nh.entries, zpl_array_count(h->entries)); \ + for (i = 0; i < new_count; i++) nh.hashes[i] = -1; \ + for (i = 0; i < zpl_array_count(h->entries); i++) { \ + ZPL_JOIN2(NAME, Entry) * e; \ + zpl_hash_table_find_result fr; \ + if (zpl_array_count(nh.hashes) == 0) ZPL_JOIN2(FUNC, grow)(&nh); \ + e = &h->entries[i]; \ + fr = ZPL_JOIN2(FUNC, _find)(&nh, e->key); \ + j = ZPL_JOIN2(FUNC, _add_entry)(&nh, e->key); \ + if (fr.entry_prev < 0) \ + nh.hashes[fr.hash_index] = j; \ + else \ + nh.entries[fr.entry_prev].next = j; \ + nh.entries[j].next = fr.entry_index; \ + nh.entries[j].value = e->value; \ + } \ + ZPL_JOIN2(FUNC, destroy)(h); \ + h->hashes = nh.hashes; \ + h->entries = nh.entries; \ + } \ + \ + void ZPL_JOIN2(FUNC, rehash_fast)(NAME * h) { \ + zpl_isize i; \ + for (i = 0; i < zpl_array_count(h->entries); i++) h->entries[i].next = -1; \ + for (i = 0; i < zpl_array_count(h->hashes); i++) h->hashes[i] = -1; \ + for (i = 0; i < zpl_array_count(h->entries); i++) { \ + ZPL_JOIN2(NAME, Entry) * e; \ + zpl_hash_table_find_result fr; \ + e = &h->entries[i]; \ + fr = ZPL_JOIN2(FUNC, _find)(h, e->key); \ + if (fr.entry_prev < 0) \ + h->hashes[fr.hash_index] = i; \ + else \ + h->entries[fr.entry_prev].next = i; \ + } \ + } \ + \ + VALUE *ZPL_JOIN2(FUNC, get)(NAME * h, zpl_u64 key) { \ + zpl_isize index = ZPL_JOIN2(FUNC, _find)(h, key).entry_index; \ + if (index >= 0) return &h->entries[index].value; \ + return NULL; \ + } \ + \ + void ZPL_JOIN2(FUNC, remove)(NAME * h, zpl_u64 key) { \ + zpl_hash_table_find_result fr = ZPL_JOIN2(FUNC, _find)(h, key); \ + if (fr.entry_index >= 0) { \ + zpl_array_remove_at(h->entries, fr.entry_index); \ + ZPL_JOIN2(FUNC, rehash_fast)(h); \ + } \ + } \ + \ + void ZPL_JOIN2(FUNC, remove_entry)(NAME * h, zpl_isize idx) { \ + zpl_array_remove_at(h->entries, idx); \ + } \ + \ + void ZPL_JOIN2(FUNC, map)(NAME * h, void (*map_proc)(zpl_u64 key, VALUE value)) { \ + ZPL_ASSERT_NOT_NULL(h); \ + ZPL_ASSERT_NOT_NULL(map_proc); \ + for (zpl_isize i = 0; i < zpl_array_count(h->entries); ++i) { \ + map_proc(h->entries[i].key, h->entries[i].value); \ + } \ + } \ + \ + void ZPL_JOIN2(FUNC, map_mut)(NAME * h, void (*map_proc)(zpl_u64 key, VALUE * value)) { \ + ZPL_ASSERT_NOT_NULL(h); \ + ZPL_ASSERT_NOT_NULL(map_proc); \ + for (zpl_isize i = 0; i < zpl_array_count(h->entries); ++i) { \ + map_proc(h->entries[i].key, &h->entries[i].value); \ + } \ + } \ + \ + void ZPL_JOIN2(FUNC, set)(NAME * h, zpl_u64 key, VALUE value) { \ + zpl_isize index; \ + zpl_hash_table_find_result fr; \ + if (zpl_array_count(h->hashes) == 0) ZPL_JOIN2(FUNC, grow)(h); \ + fr = ZPL_JOIN2(FUNC, _find)(h, key); \ + if (fr.entry_index >= 0) { \ + index = fr.entry_index; \ + } else { \ + index = ZPL_JOIN2(FUNC, _add_entry)(h, key); \ + if (fr.entry_prev >= 0) { \ + h->entries[fr.entry_prev].next = index; \ + } else { \ + h->hashes[fr.hash_index] = index; \ + } \ + } \ + h->entries[index].value = value; \ + if (ZPL_JOIN2(FUNC, _full)(h)) ZPL_JOIN2(FUNC, grow)(h); \ + } + + //! @} + + ZPL_END_C_DECLS +# if defined(ZPL_MODULE_CORE) + // file: header/core/memory_virtual.h + + + //////////////////////////////////////////////////////////////// + // + // Virtual Memory + // + // + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_virtual_memory { + void *data; + zpl_isize size; + } zpl_virtual_memory; + + //! Initialize virtual memory from existing data. + ZPL_DEF zpl_virtual_memory zpl_vm(void *data, zpl_isize size); + + //! Allocate virtual memory at address with size. + + //! @param addr The starting address of the region to reserve. If NULL, it lets operating system to decide where to allocate it. + //! @param size The size to serve. + ZPL_DEF zpl_virtual_memory zpl_vm_alloc(void *addr, zpl_isize size); + + //! Release the virtual memory. + ZPL_DEF zpl_b32 zpl_vm_free(zpl_virtual_memory vm); + + //! Trim virtual memory. + ZPL_DEF zpl_virtual_memory zpl_vm_trim(zpl_virtual_memory vm, zpl_isize lead_size, zpl_isize size); + + //! Purge virtual memory. + ZPL_DEF zpl_b32 zpl_vm_purge(zpl_virtual_memory vm); + + //! Retrieve VM's page size and alignment. + ZPL_DEF zpl_isize zpl_virtual_memory_page_size(zpl_isize *alignment_out); + + ZPL_END_C_DECLS + // file: header/core/string.h + + /** @file string.c + @brief String operations and library + @defgroup string String library + + Offers methods for c-string manipulation, but also a string library based on gb_string, which is c-string friendly. + + @{ + */ + + //////////////////////////////////////////////////////////////// + // + // Char Functions + // + // + + + ZPL_BEGIN_C_DECLS + + ZPL_DEF_INLINE char zpl_char_to_lower(char c); + ZPL_DEF_INLINE char zpl_char_to_upper(char c); + ZPL_DEF_INLINE zpl_b32 zpl_char_is_space(char c); + ZPL_DEF_INLINE zpl_b32 zpl_char_is_digit(char c); + ZPL_DEF_INLINE zpl_b32 zpl_char_is_hex_digit(char c); + ZPL_DEF_INLINE zpl_b32 zpl_char_is_alpha(char c); + ZPL_DEF_INLINE zpl_b32 zpl_char_is_alphanumeric(char c); + ZPL_DEF_INLINE zpl_i32 zpl_digit_to_int(char c); + ZPL_DEF_INLINE zpl_i32 zpl_hex_digit_to_int(char c); + ZPL_DEF_INLINE zpl_u8 zpl_char_to_hex_digit(char c); + ZPL_DEF_INLINE zpl_b32 zpl_char_is_control(char c); + + // NOTE: ASCII only + ZPL_DEF_INLINE void zpl_str_to_lower(char *str); + ZPL_DEF_INLINE void zpl_str_to_upper(char *str); + + ZPL_DEF_INLINE char const *zpl_str_trim(char const *str, zpl_b32 catch_newline); + ZPL_DEF_INLINE char const *zpl_str_skip(char const *str, char c); + ZPL_DEF_INLINE char const *zpl_str_skip_any(char const *str, char const*char_list); + ZPL_DEF_INLINE char const *zpl_str_skip_literal(char const *str, char c); + ZPL_DEF_INLINE char const *zpl_str_control_skip(char const *str, char c); + + ZPL_DEF_INLINE zpl_isize zpl_strlen(const char *str); + ZPL_DEF_INLINE zpl_isize zpl_strnlen(const char *str, zpl_isize max_len); + ZPL_DEF_INLINE zpl_i32 zpl_strcmp(const char *s1, const char *s2); + ZPL_DEF_INLINE zpl_i32 zpl_strncmp(const char *s1, const char *s2, zpl_isize len); + ZPL_DEF_INLINE char *zpl_strcpy(char *dest, const char *source); + ZPL_DEF_INLINE char *zpl_strcat(char *dest, const char *source); + ZPL_DEF_INLINE char *zpl_strncpy(char *dest, const char *source, zpl_isize len); + ZPL_DEF_INLINE zpl_isize zpl_strlcpy(char *dest, const char *source, zpl_isize len); + ZPL_DEF_INLINE char *zpl_strrev(char *str); // NOTE: ASCII only + ZPL_DEF_INLINE const char *zpl_strtok(char *output, const char *src, const char *delimit); + ZPL_DEF_INLINE const char *zpl_strntok(char *output, zpl_isize len, const char *src, const char *delimit); + + ZPL_DEF_INLINE char *zpl_strdup(zpl_allocator a, char *src, zpl_isize max_len); + ZPL_DEF_INLINE char **zpl_str_split_lines(zpl_allocator alloc, char *source, zpl_b32 strip_whitespace); + + #define zpl_str_expand(str) str, zpl_strlen(str) + #define zpl_str_advance_while(str, cond) \ + do { \ + ++str; \ + } while ((cond)); + + ZPL_DEF_INLINE zpl_b32 zpl_str_has_prefix(const char *str, const char *prefix); + ZPL_DEF_INLINE zpl_b32 zpl_str_has_suffix(const char *str, const char *suffix); + + ZPL_DEF_INLINE const char *zpl_char_first_occurence(const char *str, char c); + ZPL_DEF_INLINE const char *zpl_char_last_occurence(const char *str, char c); + #define zpl_strchr zpl_char_first_occurence + + ZPL_DEF_INLINE void zpl_str_concat(char *dest, zpl_isize dest_len, const char *src_a, zpl_isize src_a_len, const char *src_b, zpl_isize src_b_len); + + ZPL_DEF zpl_u64 zpl_str_to_u64(const char *str, char **end_ptr, zpl_i32 base); + ZPL_DEF zpl_i64 zpl_str_to_i64(const char *str, char **end_ptr, zpl_i32 base); + ZPL_DEF zpl_f64 zpl_str_to_f64(const char *str, char **end_ptr); + ZPL_DEF void zpl_i64_to_str(zpl_i64 value, char *string, zpl_i32 base); + ZPL_DEF void zpl_u64_to_str(zpl_u64 value, char *string, zpl_i32 base); + + ZPL_DEF_INLINE zpl_f32 zpl_str_to_f32(const char *str, char **end_ptr); + + //////////////////////////////////////////////////////////////// + // + // UTF-8 Handling + // + // + + // NOTE: Does not check if utf-8 string is valid + ZPL_IMPL_INLINE zpl_isize zpl_utf8_strlen(zpl_u8 const *str); + ZPL_IMPL_INLINE zpl_isize zpl_utf8_strnlen(zpl_u8 const *str, zpl_isize max_len); + + // NOTE: Windows doesn't handle 8 bit filenames well + ZPL_DEF zpl_u16 *zpl_utf8_to_ucs2(zpl_u16 *buffer, zpl_isize len, zpl_u8 const *str); + ZPL_DEF zpl_u8 *zpl_ucs2_to_utf8(zpl_u8 *buffer, zpl_isize len, zpl_u16 const *str); + ZPL_DEF zpl_u16 *zpl_utf8_to_ucs2_buf(zpl_u8 const *str); // NOTE: Uses locally persisting buffer + ZPL_DEF zpl_u8 *zpl_ucs2_to_utf8_buf(zpl_u16 const *str); // NOTE: Uses locally persisting buffer + + // NOTE: Returns size of codepoint in bytes + ZPL_DEF zpl_isize zpl_utf8_decode(zpl_u8 const *str, zpl_isize str_len, zpl_rune *codepoint); + ZPL_DEF zpl_isize zpl_utf8_codepoint_size(zpl_u8 const *str, zpl_isize str_len); + ZPL_DEF zpl_isize zpl_utf8_encode_rune(zpl_u8 buf[4], zpl_rune r); + + /* inlines */ + + ZPL_IMPL_INLINE char zpl_char_to_lower(char c) { + if (c >= 'A' && c <= 'Z') return 'a' + (c - 'A'); + return c; + } + + ZPL_IMPL_INLINE char zpl_char_to_upper(char c) { + if (c >= 'a' && c <= 'z') return 'A' + (c - 'a'); + return c; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_char_is_space(char c) { + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v') return true; + return false; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_char_is_digit(char c) { + if (c >= '0' && c <= '9') return true; + return false; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_char_is_hex_digit(char c) { + if (zpl_char_is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) return true; + return false; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_char_is_alpha(char c) { + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) return true; + return false; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_char_is_alphanumeric(char c) { return zpl_char_is_alpha(c) || zpl_char_is_digit(c); } + + ZPL_IMPL_INLINE zpl_i32 zpl_digit_to_int(char c) { return zpl_char_is_digit(c) ? c - '0' : c - 'W'; } + + ZPL_IMPL_INLINE zpl_i32 zpl_hex_digit_to_int(char c) { + if (zpl_char_is_digit(c)) + return zpl_digit_to_int(c); + else if (zpl_is_between(c, 'a', 'f')) + return c - 'a' + 10; + else if (zpl_is_between(c, 'A', 'F')) + return c - 'A' + 10; + return -1; + } + + ZPL_IMPL_INLINE zpl_u8 zpl_char_to_hex_digit(char c) { + if (c >= '0' && c <= '9') + return (zpl_u8)(c - '0'); + if (c >= 'a' && c <= 'f') + return (zpl_u8)(c - 'a'); + if (c >= 'A' && c <= 'F') + return (zpl_u8)(c - 'A'); + return 0; + } + + ZPL_IMPL_INLINE void zpl_str_to_lower(char *str) { + if (!str) return; + while (*str) { + *str = zpl_char_to_lower(*str); + str++; + } + } + + ZPL_IMPL_INLINE void zpl_str_to_upper(char *str) { + if (!str) return; + while (*str) { + *str = zpl_char_to_upper(*str); + str++; + } + } + + ZPL_IMPL_INLINE zpl_isize zpl_strlen(const char *str) { + if (str == NULL) { return 0; } + const char *p = str; + while (*str) str++; + return str-p; + } + + ZPL_IMPL_INLINE zpl_isize zpl_strnlen(const char *str, zpl_isize max_len) { + const char *end = cast(const char *) zpl_memchr(str, 0, max_len); + if (end) return end - str; + return max_len; + } + + ZPL_IMPL_INLINE zpl_isize zpl_utf8_strlen(zpl_u8 const *str) { + zpl_isize count = 0; + for (; *str; count++) { + zpl_u8 c = *str; + zpl_isize inc = 0; + if (c < 0x80) + inc = 1; + else if ((c & 0xe0) == 0xc0) + inc = 2; + else if ((c & 0xf0) == 0xe0) + inc = 3; + else if ((c & 0xf8) == 0xf0) + inc = 4; + else + return -1; + + str += inc; + } + return count; + } + + ZPL_IMPL_INLINE zpl_isize zpl_utf8_strnlen(zpl_u8 const *str, zpl_isize max_len) { + zpl_isize count = 0; + for (; *str && max_len > 0; count++) { + zpl_u8 c = *str; + zpl_isize inc = 0; + if (c < 0x80) + inc = 1; + else if ((c & 0xe0) == 0xc0) + inc = 2; + else if ((c & 0xf0) == 0xe0) + inc = 3; + else if ((c & 0xf8) == 0xf0) + inc = 4; + else + return -1; + + str += inc; + max_len -= inc; + } + return count; + } + + ZPL_IMPL_INLINE zpl_i32 zpl_strcmp(const char *s1, const char *s2) { + while (*s1 && (*s1 == *s2)) { s1++, s2++; } + return *(zpl_u8 *)s1 - *(zpl_u8 *)s2; + } + + ZPL_IMPL_INLINE char *zpl_strcpy(char *dest, const char *source) { + ZPL_ASSERT_NOT_NULL(dest); + if (source) { + char *str = dest; + while (*source) *str++ = *source++; + } + return dest; + } + + ZPL_IMPL_INLINE char *zpl_strcat(char *dest, const char *source) { + ZPL_ASSERT_NOT_NULL(dest); + if (source) { + char *str = dest; + while (*str) ++str; + while (*source) *str++ = *source++; + } + return dest; + } + + ZPL_IMPL_INLINE char *zpl_strncpy(char *dest, const char *source, zpl_isize len) { + ZPL_ASSERT_NOT_NULL(dest); + if (source) { + char *str = dest; + while (len > 0 && *source) { + *str++ = *source++; + len--; + } + while (len > 0) { + *str++ = '\0'; + len--; + } + } + return dest; + } + + ZPL_IMPL_INLINE zpl_isize zpl_strlcpy(char *dest, const char *source, zpl_isize len) { + zpl_isize result = 0; + ZPL_ASSERT_NOT_NULL(dest); + if (source) { + const char *source_start = source; + char *str = dest; + while (len > 0 && *source) { + *str++ = *source++; + len--; + } + while (len > 0) { + *str++ = '\0'; + len--; + } + + result = source - source_start; + } + return result; + } + + ZPL_IMPL_INLINE char *zpl_strrev(char *str) { + zpl_isize len = zpl_strlen(str); + char *a = str + 0; + char *b = str + len - 1; + len /= 2; + while (len--) { + zpl_swap(char, *a, *b); + a++, b--; + } + return str; + } + + ZPL_IMPL_INLINE zpl_i32 zpl_strncmp(const char *s1, const char *s2, zpl_isize len) { + for (; len > 0; s1++, s2++, len--) { + if (*s1 != *s2) + return ((s1 < s2) ? -1 : +1); + else if (*s1 == '\0') + return 0; + } + return 0; + } + + ZPL_IMPL_INLINE const char *zpl_strtok(char *output, const char *src, const char *delimit) { + while (*src && zpl_char_first_occurence(delimit, *src) == NULL) *output++ = *src++; + + *output = 0; + return *src ? src + 1 : src; + } + + ZPL_IMPL_INLINE const char *zpl_strntok(char *output, zpl_isize len, const char *src, const char *delimit) { + ZPL_ASSERT(len > 0); + *(output+len-1) = 0; + while (*src && zpl_char_first_occurence(delimit, *src) == NULL && len > 0) { + *output++ = *src++; + len --; + } + + if (len > 0) + *output = 0; + return *src ? src + 1 : src; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_char_is_control(char c) { + return !!zpl_strchr("\"\\/bfnrt", c); + } + + ZPL_IMPL_INLINE zpl_b32 zpl__is_special_char(char c) { return !!zpl_strchr("<>:/", c); } + ZPL_IMPL_INLINE zpl_b32 zpl__is_assign_char(char c) { return !!zpl_strchr(":=|", c); } + ZPL_IMPL_INLINE zpl_b32 zpl__is_delim_char(char c) { return !!zpl_strchr(",|\n", c); } + + + ZPL_IMPL_INLINE char const *zpl_str_control_skip(char const *str, char c) { + while ((*str && *str != c) || (*(str - 1) == '\\' && *str == c && zpl_char_is_control(c))) { ++str; } + + return str; + } + + + ZPL_IMPL_INLINE zpl_b32 zpl_str_has_prefix(const char *str, const char *prefix) { + while (*prefix) { + if (*str++ != *prefix++) return false; + } + return true; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_str_has_suffix(const char *str, const char *suffix) { + zpl_isize i = zpl_strlen(str); + zpl_isize j = zpl_strlen(suffix); + if (j <= i) return zpl_strcmp(str + i - j, suffix) == 0; + return false; + } + + ZPL_IMPL_INLINE const char *zpl_char_first_occurence(const char *s, char c) { + char ch = c; + for (; *s != ch; s++) { + if (*s == '\0') return NULL; + } + return s; + } + + ZPL_IMPL_INLINE const char *zpl_char_last_occurence(const char *s, char c) { + char *result = (char*)NULL; + do { + if (*s == c) result = (char *)s; + } while (*s++); + + return result; + } + + ZPL_IMPL_INLINE char const *zpl_str_trim(char const *str, zpl_b32 catch_newline) + { + while (*str && zpl_char_is_space(*str) && (!catch_newline || (catch_newline && *str != '\n'))) { ++str; } + return str; + } + + ZPL_IMPL_INLINE char const *zpl_str_skip(char const *str, char c) { + while (*str && *str != c) { ++str; } + return str; + } + + ZPL_IMPL_INLINE char const *zpl_str_skip_any(char const *str, char const*char_list) { + char const *closest_ptr = cast(char const *) zpl_ptr_add((void*)str, zpl_strlen(str)); + zpl_isize char_list_count = zpl_strlen(char_list); + for (zpl_isize i = 0; i < char_list_count; i++) { + char const *p = zpl_str_skip(str, char_list[i]); + closest_ptr = zpl_min(closest_ptr, p); + } + return closest_ptr; + } + + ZPL_IMPL_INLINE char const *zpl_str_skip_literal(char const *str, char c) { + if (*str == '\0' || *str == c) + return str; + str++; + + while ((*str && *str != c) || (*str == c && *(str-1) == '\\')) { ++str; } + return str; + } + + ZPL_IMPL_INLINE void zpl_str_concat(char *dest, zpl_isize dest_len, const char *src_a, zpl_isize src_a_len, const char *src_b, + zpl_isize src_b_len) { + ZPL_ASSERT(dest_len >= src_a_len + src_b_len + 1); + if (dest) { + zpl_memcopy(dest, src_a, src_a_len); + zpl_memcopy(dest + src_a_len, src_b, src_b_len); + dest[src_a_len + src_b_len] = '\0'; + } + } + + ZPL_IMPL_INLINE zpl_f32 zpl_str_to_f32(const char *str, char **end_ptr) { + zpl_f64 f = zpl_str_to_f64(str, end_ptr); + zpl_f32 r = cast(zpl_f32) f; + return r; + } + + ZPL_IMPL_INLINE char *zpl_strdup(zpl_allocator a, char *src, zpl_isize max_len) { + ZPL_ASSERT_NOT_NULL(src); + zpl_isize len = zpl_strlen(src); + char *dest = cast(char *) zpl_alloc(a, max_len); + zpl_memset(dest + len, 0, max_len - len); + zpl_strncpy(dest, src, max_len); + + return dest; + } + + ZPL_IMPL_INLINE char **zpl_str_split_lines(zpl_allocator alloc, char *source, zpl_b32 strip_whitespace) { + char **lines = NULL, *p = source, *pd = p; + zpl_array_init(lines, alloc); + + while (*p) { + if (*pd == '\n') { + *pd = 0; + if (*(pd - 1) == '\r') *(pd - 1) = 0; + if (strip_whitespace && (pd - p) == 0) { + p = pd + 1; + continue; + } + zpl_array_append(lines, p); + p = pd + 1; + } + ++pd; + } + return lines; + } + + ZPL_END_C_DECLS + // file: header/core/stringlib.h + + + ZPL_BEGIN_C_DECLS + + typedef char *zpl_string; + + typedef struct zpl_string_header { + zpl_allocator allocator; + zpl_isize length; + zpl_isize capacity; + } zpl_string_header; + + #define ZPL_STRING_HEADER(str) (cast(zpl_string_header *)(str) - 1) + + ZPL_DEF zpl_string zpl_string_make_reserve(zpl_allocator a, zpl_isize capacity); + ZPL_DEF zpl_string zpl_string_make_length(zpl_allocator a, void const *str, zpl_isize num_bytes); + ZPL_DEF zpl_string zpl_string_sprintf(zpl_allocator a, char *buf, zpl_isize num_bytes, const char *fmt, ...); + ZPL_DEF zpl_string zpl_string_sprintf_buf(zpl_allocator a, const char *fmt, ...); // NOTE: Uses locally persistent buffer + ZPL_DEF zpl_string zpl_string_append_length(zpl_string str, void const *other, zpl_isize num_bytes); + ZPL_DEF zpl_string zpl_string_appendc(zpl_string str, const char *other); + ZPL_DEF zpl_string zpl_string_join(zpl_allocator a, const char **parts, zpl_isize count, const char *glue); + ZPL_DEF zpl_string zpl_string_set(zpl_string str, const char *cstr); + ZPL_DEF zpl_string zpl_string_make_space_for(zpl_string str, zpl_isize add_len); + ZPL_DEF zpl_isize zpl_string_allocation_size(zpl_string const str); + ZPL_DEF zpl_b32 zpl_string_are_equal(zpl_string const lhs, zpl_string const rhs); + ZPL_DEF zpl_string zpl_string_trim(zpl_string str, const char *cut_set); + ZPL_DEF zpl_string zpl_string_append_rune(zpl_string str, zpl_rune r); + ZPL_DEF zpl_string zpl_string_append_fmt(zpl_string str, const char *fmt, ...); + + ZPL_DEF_INLINE zpl_string zpl_string_make(zpl_allocator a, const char *str); + ZPL_DEF_INLINE void zpl_string_free(zpl_string str); + ZPL_DEF_INLINE void zpl_string_clear(zpl_string str); + ZPL_DEF_INLINE zpl_string zpl_string_duplicate(zpl_allocator a, zpl_string const str); + ZPL_DEF_INLINE zpl_isize zpl_string_length(zpl_string const str); + ZPL_DEF_INLINE zpl_isize zpl_string_capacity(zpl_string const str); + ZPL_DEF_INLINE zpl_isize zpl_string_available_space(zpl_string const str); + ZPL_DEF_INLINE zpl_string zpl_string_append(zpl_string str, zpl_string const other); + ZPL_DEF_INLINE zpl_string zpl_string_trim_space(zpl_string str); // Whitespace ` \t\r\n\v\f` + ZPL_DEF_INLINE void zpl__set_string_length(zpl_string str, zpl_isize len); + ZPL_DEF_INLINE void zpl__set_string_capacity(zpl_string str, zpl_isize cap); + + ZPL_IMPL_INLINE void zpl__set_string_length(zpl_string str, zpl_isize len) { ZPL_STRING_HEADER(str)->length = len; } + ZPL_IMPL_INLINE void zpl__set_string_capacity(zpl_string str, zpl_isize cap) { ZPL_STRING_HEADER(str)->capacity = cap; } + ZPL_IMPL_INLINE zpl_string zpl_string_make(zpl_allocator a, const char *str) { + zpl_isize len = str ? zpl_strlen(str) : 0; + return zpl_string_make_length(a, str, len); + } + + ZPL_IMPL_INLINE void zpl_string_free(zpl_string str) { + if (str) { + zpl_string_header *header = ZPL_STRING_HEADER(str); + zpl_free(header->allocator, header); + } + } + + ZPL_IMPL_INLINE zpl_string zpl_string_duplicate(zpl_allocator a, zpl_string const str) { + return zpl_string_make_length(a, str, zpl_string_length(str)); + } + + ZPL_IMPL_INLINE zpl_isize zpl_string_length(zpl_string const str) { return ZPL_STRING_HEADER(str)->length; } + ZPL_IMPL_INLINE zpl_isize zpl_string_capacity(zpl_string const str) { return ZPL_STRING_HEADER(str)->capacity; } + + ZPL_IMPL_INLINE zpl_isize zpl_string_available_space(zpl_string const str) { + zpl_string_header *h = ZPL_STRING_HEADER(str); + if (h->capacity > h->length) return h->capacity - h->length; + return 0; + } + + ZPL_IMPL_INLINE void zpl_string_clear(zpl_string str) { + zpl__set_string_length(str, 0); + str[0] = '\0'; + } + + ZPL_IMPL_INLINE zpl_string zpl_string_append(zpl_string str, zpl_string const other) { + return zpl_string_append_length(str, other, zpl_string_length(other)); + } + + ZPL_IMPL_INLINE zpl_string zpl_string_trim_space(zpl_string str) { return zpl_string_trim(str, " \t\r\n\v\f"); } + + + ZPL_END_C_DECLS + // file: header/core/file.h + + /** @file file.c + @brief File handling + @defgroup fileio File handling + + File I/O operations as well as path and folder structure manipulation methods. With threading enabled, it also offers async read/write methods. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef zpl_u32 zpl_file_mode; + + typedef enum zpl_file_mode_flag { + ZPL_FILE_MODE_READ = ZPL_BIT(0), + ZPL_FILE_MODE_WRITE = ZPL_BIT(1), + ZPL_FILE_MODE_APPEND = ZPL_BIT(2), + ZPL_FILE_MODE_RW = ZPL_BIT(3), + ZPL_FILE_MODES = ZPL_FILE_MODE_READ | ZPL_FILE_MODE_WRITE | ZPL_FILE_MODE_APPEND | ZPL_FILE_MODE_RW, + } zpl_file_mode_flag; + + // NOTE: Only used internally and for the file operations + typedef enum zpl_seek_whence_type { + ZPL_SEEK_WHENCE_BEGIN = 0, + ZPL_SEEK_WHENCE_CURRENT = 1, + ZPL_SEEK_WHENCE_END = 2, + } zpl_seek_whence_type; + + typedef enum zpl_file_error { + ZPL_FILE_ERROR_NONE, + ZPL_FILE_ERROR_INVALID, + ZPL_FILE_ERROR_INVALID_FILENAME, + ZPL_FILE_ERROR_EXISTS, + ZPL_FILE_ERROR_NOT_EXISTS, + ZPL_FILE_ERROR_PERMISSION, + ZPL_FILE_ERROR_TRUNCATION_FAILURE, + ZPL_FILE_ERROR_NOT_EMPTY, + ZPL_FILE_ERROR_NAME_TOO_LONG, + ZPL_FILE_ERROR_UNKNOWN, + } zpl_file_error; + + typedef union zpl_file_descriptor { + void *p; + zpl_intptr i; + zpl_uintptr u; + } zpl_file_descriptor; + + typedef struct zpl_file_operations zpl_file_operations; + + #define ZPL_FILE_OPEN_PROC(name) zpl_file_error name(zpl_file_descriptor *fd, zpl_file_operations *ops, zpl_file_mode mode, char const *filename) + #define ZPL_FILE_READ_AT_PROC(name) zpl_b32 name(zpl_file_descriptor fd, void *buffer, zpl_isize size, zpl_i64 offset, zpl_isize *bytes_read, zpl_b32 stop_at_newline) + #define ZPL_FILE_WRITE_AT_PROC(name) zpl_b32 name(zpl_file_descriptor fd, void const *buffer, zpl_isize size, zpl_i64 offset, zpl_isize *bytes_written) + #define ZPL_FILE_SEEK_PROC(name) zpl_b32 name(zpl_file_descriptor fd, zpl_i64 offset, zpl_seek_whence_type whence, zpl_i64 *new_offset) + #define ZPL_FILE_CLOSE_PROC(name) void name(zpl_file_descriptor fd) + + typedef ZPL_FILE_OPEN_PROC(zpl_file_open_proc); + typedef ZPL_FILE_READ_AT_PROC(zpl_file_read_proc); + typedef ZPL_FILE_WRITE_AT_PROC(zpl_file_write_proc); + typedef ZPL_FILE_SEEK_PROC(zpl_file_seek_proc); + typedef ZPL_FILE_CLOSE_PROC(zpl_file_close_proc); + + struct zpl_file_operations { + zpl_file_read_proc *read_at; + zpl_file_write_proc *write_at; + zpl_file_seek_proc *seek; + zpl_file_close_proc *close; + }; + + extern zpl_file_operations const zpl_default_file_operations; + + typedef zpl_u64 zpl_file_time; + typedef enum zpl_dir_type { + ZPL_DIR_TYPE_FILE, + ZPL_DIR_TYPE_FOLDER, + ZPL_DIR_TYPE_UNKNOWN, + } zpl_dir_type; + + struct zpl_dir_info; + + typedef struct zpl_dir_entry { + char const *filename; + struct zpl_dir_info *dir_info; + zpl_u8 type; + } zpl_dir_entry; + + typedef struct zpl_dir_info { + char const *fullpath; + zpl_dir_entry *entries; // zpl_array + + // Internals + char **filenames; // zpl_array + zpl_string buf; + } zpl_dir_info; + + typedef struct zpl_file { + zpl_file_operations ops; + zpl_file_descriptor fd; + zpl_b32 is_temp; + + char const *filename; + zpl_file_time last_write_time; + zpl_dir_entry *dir; + } zpl_file; + + typedef enum zpl_file_standard_type { + ZPL_FILE_STANDARD_INPUT, + ZPL_FILE_STANDARD_OUTPUT, + ZPL_FILE_STANDARD_ERROR, + + ZPL_FILE_STANDARD_COUNT, + } zpl_file_standard_type; + + #define ZPL_STDIO_IN zpl_file_get_standard(ZPL_FILE_STANDARD_INPUT) + #define ZPL_STDIO_OUT zpl_file_get_standard(ZPL_FILE_STANDARD_OUTPUT) + #define ZPL_STDIO_ERR zpl_file_get_standard(ZPL_FILE_STANDARD_ERROR) + + /** + * Get standard file I/O. + * @param std Check zpl_file_standard_type + * @return File handle to standard I/O + */ + ZPL_DEF zpl_file *zpl_file_get_standard(zpl_file_standard_type std); + + /** + * Connects a system handle to a zpl file. + * @param file Pointer to zpl file + * @param handle Low-level OS handle to connect + */ + ZPL_DEF void zpl_file_connect_handle(zpl_file *file, void *handle); + + /** + * Creates a new file + * @param file + * @param filename + */ + ZPL_DEF zpl_file_error zpl_file_create(zpl_file *file, char const *filename); + + /** + * Opens a file + * @param file + * @param filename + */ + ZPL_DEF zpl_file_error zpl_file_open(zpl_file *file, char const *filename); + + /** + * Opens a file using a specified mode + * @param file + * @param mode Access mode to use + * @param filename + */ + ZPL_DEF zpl_file_error zpl_file_open_mode(zpl_file *file, zpl_file_mode mode, char const *filename); + + /** + * Constructs a new file from data + * @param file + * @param fd Low-level file descriptor to use + * @param ops File operations to rely upon + * @param filename + */ + ZPL_DEF zpl_file_error zpl_file_new(zpl_file *file, zpl_file_descriptor fd, zpl_file_operations ops, char const *filename); + + /** + * Returns a size of the file + * @param file + * @return File size + */ + ZPL_DEF zpl_i64 zpl_file_size(zpl_file *file); + + /** + * Returns the currently opened file's name + * @param file + */ + ZPL_DEF char const *zpl_file_name(zpl_file *file); + + /** + * Truncates the file by a specified size + * @param file + * @param size Size to truncate + */ + ZPL_DEF zpl_file_error zpl_file_truncate(zpl_file *file, zpl_i64 size); + + /** + * Checks whether a file's been changed since the last check + * @param file + */ + ZPL_DEF zpl_b32 zpl_file_has_changed(zpl_file *file); + + /** + * Retrieves a directory listing relative to the file + * @param file + */ + ZPL_DEF void zpl_file_dirinfo_refresh(zpl_file *file); + + /** + * Creates a temporary file + * @param file + */ + ZPL_DEF zpl_file_error zpl_file_temp(zpl_file *file); + + /** + * Closes the file + * @param file + */ + ZPL_DEF zpl_file_error zpl_file_close(zpl_file *file); + + /** + * Reads file safely + * @param file + * @param buffer Buffer to read to + * @param size Size to read + * @param offset Offset to read from + * @param bytes_read How much data we've actually read + */ + ZPL_DEF_INLINE zpl_b32 zpl_file_read_at_check(zpl_file *file, void *buffer, zpl_isize size, zpl_i64 offset, zpl_isize *bytes_read); + + /** + * Writes to file safely + * @param file + * @param buffer Buffer to read from + * @param size Size to write + * @param offset Offset to write to + * @param bytes_written How much data we've actually written + */ + ZPL_DEF_INLINE zpl_b32 zpl_file_write_at_check(zpl_file *file, void const *buffer, zpl_isize size, zpl_i64 offset, zpl_isize *bytes_written); + + + /** + * Reads file at a specific offset + * @param file + * @param buffer Buffer to read to + * @param size Size to read + * @param offset Offset to read from + * @param bytes_read How much data we've actually read + */ + ZPL_DEF_INLINE zpl_b32 zpl_file_read_at(zpl_file *file, void *buffer, zpl_isize size, zpl_i64 offset); + + /** + * Writes to file at a specific offset + * @param file + * @param buffer Buffer to read from + * @param size Size to write + * @param offset Offset to write to + * @param bytes_written How much data we've actually written + */ + ZPL_DEF_INLINE zpl_b32 zpl_file_write_at(zpl_file *file, void const *buffer, zpl_isize size, zpl_i64 offset); + + /** + * Seeks the file cursor from the beginning of file to a specific position + * @param file + * @param offset Offset to seek to + */ + ZPL_DEF_INLINE zpl_i64 zpl_file_seek(zpl_file *file, zpl_i64 offset); + + /** + * Seeks the file cursor to the end of the file + * @param file + */ + ZPL_DEF_INLINE zpl_i64 zpl_file_seek_to_end(zpl_file *file); + + /** + * Skips N bytes at the current position + * @param file + * @param bytes Bytes to skip + */ + ZPL_DEF_INLINE zpl_i64 zpl_file_skip(zpl_file *file, zpl_i64 bytes); // NOTE: Skips a certain amount of bytes + + /** + * Returns the length from the beginning of the file we've read so far + * @param file + * @return Our current position in file + */ + ZPL_DEF_INLINE zpl_i64 zpl_file_tell(zpl_file *file); + + /** + * Reads from a file + * @param file + * @param buffer Buffer to read to + * @param size Size to read + */ + ZPL_DEF_INLINE zpl_b32 zpl_file_read(zpl_file *file, void *buffer, zpl_isize size); + + /** + * Writes to a file + * @param file + * @param buffer Buffer to read from + * @param size Size to read + */ + ZPL_DEF_INLINE zpl_b32 zpl_file_write(zpl_file *file, void const *buffer, zpl_isize size); + + + typedef struct zpl_file_contents { + zpl_allocator allocator; + void *data; + zpl_isize size; + } zpl_file_contents; + + /** + * Reads the whole file contents + * @param a Allocator to use + * @param zero_terminate End the read data with null terminator + * @param filepath Path to the file + * @return File contents data + */ + ZPL_DEF zpl_file_contents zpl_file_read_contents(zpl_allocator a, zpl_b32 zero_terminate, char const *filepath); + + /** + * Frees the file content data previously read + * @param fc + */ + ZPL_DEF void zpl_file_free_contents(zpl_file_contents *fc); + + /** + * Writes content to a file + */ + ZPL_DEF zpl_b32 zpl_file_write_contents(char const* filepath, void const* buffer, zpl_isize size, zpl_file_error* err); + + /** + * Reads the file as array of lines + * + * Make sure you free both the returned buffer and the lines (zpl_array) + * @param alloc Allocator to use + * @param lines Reference to zpl_array container we store lines to + * @param filename Path to the file + * @param strip_whitespace Strip whitespace when we split to lines? + * @return File content we've read itself + */ + ZPL_DEF char *zpl_file_read_lines(zpl_allocator alloc, zpl_array(char *)*lines, char const *filename, zpl_b32 strip_whitespace); + + //! @} + + /* inlines */ + + + ZPL_IMPL_INLINE zpl_b32 zpl_file_read_at_check(zpl_file *f, void *buffer, zpl_isize size, zpl_i64 offset, zpl_isize *bytes_read) { + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + return f->ops.read_at(f->fd, buffer, size, offset, bytes_read, false); + } + + ZPL_IMPL_INLINE zpl_b32 zpl_file_write_at_check(zpl_file *f, void const *buffer, zpl_isize size, zpl_i64 offset, zpl_isize *bytes_written) { + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + return f->ops.write_at(f->fd, buffer, size, offset, bytes_written); + } + + ZPL_IMPL_INLINE zpl_b32 zpl_file_read_at(zpl_file *f, void *buffer, zpl_isize size, zpl_i64 offset) { + return zpl_file_read_at_check(f, buffer, size, offset, NULL); + } + + ZPL_IMPL_INLINE zpl_b32 zpl_file_write_at(zpl_file *f, void const *buffer, zpl_isize size, zpl_i64 offset) { + return zpl_file_write_at_check(f, buffer, size, offset, NULL); + } + + ZPL_IMPL_INLINE zpl_i64 zpl_file_seek(zpl_file *f, zpl_i64 offset) { + zpl_i64 new_offset = 0; + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + f->ops.seek(f->fd, offset, ZPL_SEEK_WHENCE_BEGIN, &new_offset); + return new_offset; + } + + ZPL_IMPL_INLINE zpl_i64 zpl_file_seek_to_end(zpl_file *f) { + zpl_i64 new_offset = 0; + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + f->ops.seek(f->fd, 0, ZPL_SEEK_WHENCE_END, &new_offset); + return new_offset; + } + + // NOTE: Skips a certain amount of bytes + ZPL_IMPL_INLINE zpl_i64 zpl_file_skip(zpl_file *f, zpl_i64 bytes) { + zpl_i64 new_offset = 0; + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + f->ops.seek(f->fd, bytes, ZPL_SEEK_WHENCE_CURRENT, &new_offset); + return new_offset; + } + + ZPL_IMPL_INLINE zpl_i64 zpl_file_tell(zpl_file *f) { + zpl_i64 new_offset = 0; + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + f->ops.seek(f->fd, 0, ZPL_SEEK_WHENCE_CURRENT, &new_offset); + return new_offset; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_file_read(zpl_file *f, void *buffer, zpl_isize size) { + zpl_i64 cur_offset = zpl_file_tell(f); + zpl_b32 result = zpl_file_read_at(f, buffer, size, zpl_file_tell(f)); + zpl_file_seek(f, cur_offset + size); + return result; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_file_write(zpl_file *f, void const *buffer, zpl_isize size) { + zpl_i64 cur_offset = zpl_file_tell(f); + zpl_b32 result = zpl_file_write_at(f, buffer, size, zpl_file_tell(f)); + zpl_file_seek(f, cur_offset + size); + return result; + } + + ZPL_END_C_DECLS + // file: header/core/file_stream.h + + /** @file file_stream.c + @brief File stream + @defgroup fileio File stream + + File streaming operations on memory. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef enum { + /* Allows us to write to the buffer directly. Beware: you can not append a new data! */ + ZPL_FILE_STREAM_WRITABLE = ZPL_BIT(0), + + /* Clones the input buffer so you can write (zpl_file_write*) data into it. */ + /* Since we work with a clone, the buffer size can dynamically grow as well. */ + ZPL_FILE_STREAM_CLONE_WRITABLE = ZPL_BIT(1), + } zpl_file_stream_flags; + + /** + * Opens a new memory stream + * @param file + * @param allocator + */ + ZPL_DEF zpl_b8 zpl_file_stream_new(zpl_file* file, zpl_allocator allocator); + + /** + * Opens a memory stream over an existing buffer + * @param file + * @param allocator + * @param buffer Memory to create stream from + * @param size Buffer's size + * @param flags + */ + ZPL_DEF zpl_b8 zpl_file_stream_open(zpl_file* file, zpl_allocator allocator, zpl_u8 *buffer, zpl_isize size, zpl_file_stream_flags flags); + + /** + * Retrieves the stream's underlying buffer and buffer size. + * @param file memory stream + * @param size (Optional) buffer size + */ + ZPL_DEF zpl_u8 *zpl_file_stream_buf(zpl_file* file, zpl_isize *size); + + extern zpl_file_operations const zpl_memory_file_operations; + + //! @} + + ZPL_END_C_DECLS + // file: header/core/file_misc.h + + + ZPL_BEGIN_C_DECLS + + #ifndef ZPL_PATH_SEPARATOR + # if defined(ZPL_SYSTEM_WINDOWS) + # define ZPL_PATH_SEPARATOR '\\' + # else + # define ZPL_PATH_SEPARATOR '/' + # endif + #endif + + #ifndef ZPL_MAX_PATH + # if defined(ZPL_SYSTEM_WINDOWS) + # define ZPL_MAX_PATH MAX_PATH + # elif defined(ZPL_SYSTEM_UNIX) && !defined(ZPL_SYSTEM_EMSCRIPTEN) + # define ZPL_MAX_PATH PATH_MAX + # else + # define ZPL_MAX_PATH 4096 + # endif + #endif + + /** + * Checks if file/directory exists + * @param filepath + */ + ZPL_DEF zpl_b32 zpl_fs_exists(char const *filepath); + + /** + * Retrieves node's type (file, folder, ...) + * @param path + */ + ZPL_DEF zpl_u8 zpl_fs_get_type(char const *path); + + /** + * Retrieves file's last write time + * @param filepath + */ + ZPL_DEF zpl_file_time zpl_fs_last_write_time(char const *filepath); + + /** + * Copies the file to a directory + * @param existing_filename + * @param new_filename + * @param fail_if_exists + */ + ZPL_DEF zpl_b32 zpl_fs_copy(char const *existing_filename, char const *new_filename, zpl_b32 fail_if_exists); + + /** + * Moves the file to a directory + * @param existing_filename + * @param new_filename + */ + ZPL_DEF zpl_b32 zpl_fs_move(char const *existing_filename, char const *new_filename); + + /** + * Removes a file from a directory + * @param filename + */ + ZPL_DEF zpl_b32 zpl_fs_remove(char const *filename); + + ZPL_DEF_INLINE zpl_b32 zpl_path_is_absolute(char const *path); + ZPL_DEF_INLINE zpl_b32 zpl_path_is_relative(char const *path); + ZPL_DEF_INLINE zpl_b32 zpl_path_is_root(char const *path); + + ZPL_DEF_INLINE char const *zpl_path_base_name(char const *path); + ZPL_DEF_INLINE char const *zpl_path_extension(char const *path); + + ZPL_DEF void zpl_path_fix_slashes(char *path); + + ZPL_DEF zpl_file_error zpl_path_mkdir(char const *path, zpl_i32 mode); + ZPL_DEF zpl_isize zpl_path_mkdir_recursive(char const *path, zpl_i32 mode); + ZPL_DEF zpl_file_error zpl_path_rmdir(char const *path); + + ZPL_DEF char *zpl_path_get_full_name(zpl_allocator a, char const *path); + + /** + * Returns file paths terminated by newline (\n) + * @param alloc [description] + * @param dirname [description] + * @param recurse [description] + * @return [description] + */ + ZPL_DEF /*zpl_string*/char * zpl_path_dirlist(zpl_allocator alloc, char const *dirname, zpl_b32 recurse); + + /** + * Initialize dirinfo from specified path + * @param dir [description] + * @param path [description] + */ + ZPL_DEF void zpl_dirinfo_init(zpl_dir_info *dir, char const *path); + ZPL_DEF void zpl_dirinfo_free(zpl_dir_info *dir); + + /** + * Analyze the entry's dirinfo + * @param dir_entry [description] + */ + ZPL_DEF void zpl_dirinfo_step(zpl_dir_entry *dir_entry); + + + /* inlines */ + + ZPL_IMPL_INLINE zpl_b32 zpl_path_is_absolute(char const *path) { + zpl_b32 result = false; + ZPL_ASSERT_NOT_NULL(path); + #if defined(ZPL_SYSTEM_WINDOWS) + result = (zpl_strlen(path) > 2) && zpl_char_is_alpha(path[0]) && (path[1] == ':' && path[2] == ZPL_PATH_SEPARATOR); + #else + result = (zpl_strlen(path) > 0 && path[0] == ZPL_PATH_SEPARATOR); + #endif + return result; + } + + ZPL_IMPL_INLINE zpl_b32 zpl_path_is_relative(char const *path) { return !zpl_path_is_absolute(path); } + + ZPL_IMPL_INLINE zpl_b32 zpl_path_is_root(char const *path) { + zpl_b32 result = false; + ZPL_ASSERT_NOT_NULL(path); + #if defined(ZPL_SYSTEM_WINDOWS) + result = zpl_path_is_absolute(path) && (zpl_strlen(path) == 3); + #else + result = zpl_path_is_absolute(path) && (zpl_strlen(path) == 1); + #endif + return result; + } + + ZPL_IMPL_INLINE char const *zpl_path_base_name(char const *path) { + char const *ls; + ZPL_ASSERT_NOT_NULL(path); + zpl_path_fix_slashes((char *)path); + ls = zpl_char_last_occurence(path, ZPL_PATH_SEPARATOR); + return (ls == NULL) ? path : ls + 1; + } + + ZPL_IMPL_INLINE char const *zpl_path_extension(char const *path) { + char const *ld; + ZPL_ASSERT_NOT_NULL(path); + ld = zpl_char_last_occurence(path, '.'); + return (ld == NULL) ? NULL : ld + 1; + } + + ZPL_END_C_DECLS + // file: header/core/file_tar.h + + /** @file file_tar.c + @brief Tar archiving module + @defgroup fileio Tar module + + Allows to easily pack/unpack files. + Based on: https://github.com/rxi/microtar/ + + Disclaimer: The pack method does not support file permissions nor GID/UID information. Only regular files are supported. + Use zpl_tar_pack_dir to pack an entire directory recursively. Empty folders are ignored. + + @{ + */ + + + ZPL_BEGIN_C_DECLS + + typedef enum { + ZPL_TAR_ERROR_NONE, + ZPL_TAR_ERROR_INTERRUPTED, + ZPL_TAR_ERROR_IO_ERROR, + ZPL_TAR_ERROR_BAD_CHECKSUM, + ZPL_TAR_ERROR_FILE_NOT_FOUND, + ZPL_TAR_ERROR_INVALID_INPUT, + } zpl_tar_errors; + + typedef enum { + ZPL_TAR_TYPE_REGULAR = '0', + ZPL_TAR_TYPE_LINK = '1', + ZPL_TAR_TYPE_SYMBOL = '2', + ZPL_TAR_TYPE_CHR = '3', + ZPL_TAR_TYPE_BLK = '4', + ZPL_TAR_TYPE_DIR = '5', + ZPL_TAR_TYPE_FIFO = '6' + } zpl_tar_file_type; + + typedef struct { + char type; + char *path; + zpl_i64 offset; + zpl_i64 length; + zpl_isize error; + } zpl_tar_record; + + #define ZPL_TAR_UNPACK_PROC(name) zpl_isize name(zpl_file *archive, zpl_tar_record *file, void* user_data) + typedef ZPL_TAR_UNPACK_PROC(zpl_tar_unpack_proc); + + /** + * @brief Packs a list of files + * Packs a list of provided files. Note that this method only supports regular files + * and does not provide extended info such as GID/UID or permissions. + * @param archive archive we pack files into + * @param paths list of files + * @param paths_len number of files provided + * @return error + */ + ZPL_DEF zpl_isize zpl_tar_pack(zpl_file *archive, char const **paths, zpl_isize paths_len); + + /** + * @brief Packs an entire directory + * Packs an entire directory of files recursively. + * @param archive archive we pack files to + * @param path folder to pack + * @param alloc memory allocator to use (ex. zpl_heap()) + * @return error + */ + ZPL_DEF zpl_isize zpl_tar_pack_dir(zpl_file *archive, char const *path, zpl_allocator alloc); + + /** + * @brief Unpacks an existing archive + * Unpacks an existing archive. Users provide a callback in which information about file is provided. + * Library does not unpack files to the filesystem nor reads any file data. + * @param archive archive we unpack files from + * @param unpack_proc callback we call per each file parsed + * @param user_data user provided data + * @return error + */ + ZPL_DEF zpl_isize zpl_tar_unpack(zpl_file *archive, zpl_tar_unpack_proc *unpack_proc, void *user_data); + + /** + * @brief Unpacks an existing archive into directory + * Unpacks an existing archive into directory. The folder structure will be re-created automatically. + * @param archive archive we unpack files from + * @param dest directory to unpack files to + * @return error + */ + ZPL_DEF_INLINE zpl_isize zpl_tar_unpack_dir(zpl_file *archive, char const *dest); + + ZPL_DEF ZPL_TAR_UNPACK_PROC(zpl_tar_default_list_file); + ZPL_DEF ZPL_TAR_UNPACK_PROC(zpl_tar_default_unpack_file); + + //! @} + + ZPL_IMPL_INLINE zpl_isize zpl_tar_unpack_dir(zpl_file *archive, char const *dest) { + return zpl_tar_unpack(archive, zpl_tar_default_unpack_file, cast(void*)dest); + } + + ZPL_END_C_DECLS + // file: header/core/print.h + + /** @file print.c + @brief Printing methods + @defgroup print Printing methods + + Various printing methods. + @{ + */ + + ZPL_BEGIN_C_DECLS + + #ifndef ZPL_PRINTF_MAXLEN + #define ZPL_PRINTF_MAXLEN 65536 + #endif + + ZPL_DEF zpl_isize zpl_printf(char const *fmt, ...); + ZPL_DEF zpl_isize zpl_printf_va(char const *fmt, va_list va); + ZPL_DEF zpl_isize zpl_printf_err(char const *fmt, ...); + ZPL_DEF zpl_isize zpl_printf_err_va(char const *fmt, va_list va); + ZPL_DEF zpl_isize zpl_fprintf(zpl_file *f, char const *fmt, ...); + ZPL_DEF zpl_isize zpl_fprintf_va(zpl_file *f, char const *fmt, va_list va); + + // NOTE: A locally persisting buffer is used internally + ZPL_DEF char *zpl_bprintf(char const *fmt, ...); + + // NOTE: A locally persisting buffer is used internally + ZPL_DEF char *zpl_bprintf_va(char const *fmt, va_list va); + + ZPL_DEF zpl_isize zpl_asprintf(zpl_allocator allocator, char **buffer, char const *fmt, ...); + ZPL_DEF zpl_isize zpl_asprintf_va(zpl_allocator allocator, char **buffer, char const *fmt, va_list va); + + ZPL_DEF zpl_isize zpl_snprintf(char *str, zpl_isize n, char const *fmt, ...); + ZPL_DEF zpl_isize zpl_snprintf_va(char *str, zpl_isize n, char const *fmt, va_list va); + + ZPL_END_C_DECLS + // file: header/core/time.h + + /** @file time.c + @brief Time helper methods. + @defgroup time Time helpers + + Helper methods for retrieving the current time in many forms under different precisions. It also offers a simple to use timer library. + + @{ + */ + + + ZPL_BEGIN_C_DECLS + + //! Return CPU timestamp. + ZPL_DEF zpl_u64 zpl_rdtsc(void); + + //! Return relative time (in seconds) since the application start. + ZPL_DEF zpl_f64 zpl_time_rel(void); + + //! Return relative time since the application start. + ZPL_DEF zpl_u64 zpl_time_rel_ms(void); + + //! Return time (in seconds) since 1601-01-01 UTC. + ZPL_DEF zpl_f64 zpl_time_utc(void); + + //! Return time since 1601-01-01 UTC. + ZPL_DEF zpl_u64 zpl_time_utc_ms(void); + + //! Return local system time since 1601-01-01 + ZPL_DEF zpl_u64 zpl_time_tz_ms(void); + + //! Return local system time in seconds since 1601-01-01 + ZPL_DEF zpl_f64 zpl_time_tz(void); + + //! Convert Win32 epoch (1601-01-01 UTC) to UNIX (1970-01-01 UTC) + ZPL_DEF_INLINE zpl_u64 zpl_time_win32_to_unix(zpl_u64 ms); + + //! Convert UNIX (1970-01-01 UTC) to Win32 epoch (1601-01-01 UTC) + ZPL_DEF_INLINE zpl_u64 zpl_time_unix_to_win32(zpl_u64 ms); + + //! Sleep for specified number of milliseconds. + ZPL_DEF void zpl_sleep_ms(zpl_u32 ms); + + //! Sleep for specified number of seconds. + ZPL_DEF_INLINE void zpl_sleep(zpl_f32 s); + + // Deprecated methods + ZPL_DEPRECATED_FOR(10.9.0, zpl_time_rel) + ZPL_DEF_INLINE zpl_f64 zpl_time_now(void); + + ZPL_DEPRECATED_FOR(10.9.0, zpl_time_utc) + ZPL_DEF_INLINE zpl_f64 zpl_utc_time_now(void); + + + #ifndef ZPL__UNIX_TO_WIN32_EPOCH + #define ZPL__UNIX_TO_WIN32_EPOCH 11644473600000ull + #endif + + ZPL_IMPL_INLINE zpl_u64 zpl_time_win32_to_unix(zpl_u64 ms) { + return ms - ZPL__UNIX_TO_WIN32_EPOCH; + } + + ZPL_IMPL_INLINE zpl_u64 zpl_time_unix_to_win32(zpl_u64 ms) { + return ms + ZPL__UNIX_TO_WIN32_EPOCH; + } + + ZPL_IMPL_INLINE void zpl_sleep(zpl_f32 s) { + zpl_sleep_ms((zpl_u32)(s * 1000)); + } + + ZPL_IMPL_INLINE zpl_f64 zpl_time_now() { + return zpl_time_rel(); + } + + ZPL_IMPL_INLINE zpl_f64 zpl_utc_time_now() { + return zpl_time_utc(); + } + + ZPL_END_C_DECLS + // file: header/core/random.h + + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_random { + zpl_u32 offsets[8]; + zpl_u32 value; + } zpl_random; + + // NOTE: Generates from numerous sources to produce a decent pseudo-random seed + ZPL_DEF void zpl_random_init(zpl_random *r); + ZPL_DEF zpl_u32 zpl_random_gen_u32(zpl_random *r); + ZPL_DEF zpl_u32 zpl_random_gen_u32_unique(zpl_random *r); + ZPL_DEF zpl_u64 zpl_random_gen_u64(zpl_random *r); // NOTE: (zpl_random_gen_u32() << 32) | zpl_random_gen_u32() + ZPL_DEF zpl_isize zpl_random_gen_isize(zpl_random *r); + ZPL_DEF zpl_i64 zpl_random_range_i64(zpl_random *r, zpl_i64 lower_inc, zpl_i64 higher_inc); + ZPL_DEF zpl_isize zpl_random_range_isize(zpl_random *r, zpl_isize lower_inc, zpl_isize higher_inc); + ZPL_DEF zpl_f64 zpl_random_range_f64(zpl_random *r, zpl_f64 lower_inc, zpl_f64 higher_inc); + + ZPL_END_C_DECLS + // file: header/core/misc.h + + /** @file misc.c + @brief Various other stuff + @defgroup misc Various other stuff + + Methods that don't belong anywhere but are still very useful in many occasions. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + ZPL_DEF void zpl_yield(void); + + //! Returns allocated buffer + ZPL_DEF const char *zpl_get_env(const char *name); + ZPL_DEF const char *zpl_get_env_buf(const char *name); + ZPL_DEF zpl_string zpl_get_env_str(const char *name); + ZPL_DEF void zpl_set_env(const char *name, const char *value); + ZPL_DEF void zpl_unset_env(const char *name); + + ZPL_DEF zpl_u32 zpl_system_command(const char *command, zpl_usize buffer_len, char *buffer); + ZPL_DEF zpl_string zpl_system_command_str(const char *command, zpl_allocator backing); + + ZPL_DEF_INLINE zpl_u16 zpl_endian_swap16(zpl_u16 i); + ZPL_DEF_INLINE zpl_u32 zpl_endian_swap32(zpl_u32 i); + ZPL_DEF_INLINE zpl_u64 zpl_endian_swap64(zpl_u64 i); + + ZPL_DEF_INLINE zpl_isize zpl_count_set_bits(zpl_u64 mask); + + //! @} + //$$ + + ZPL_IMPL_INLINE zpl_u16 zpl_endian_swap16(zpl_u16 i) { + return (i>>8) | (i<<8); + } + + ZPL_IMPL_INLINE zpl_u32 zpl_endian_swap32(zpl_u32 i) { + return (i>>24) |(i<<24) | + ((i&0x00ff0000u)>>8) | ((i&0x0000ff00u)<<8); + } + + ZPL_IMPL_INLINE zpl_u64 zpl_endian_swap64(zpl_u64 i) { + return (i>>56) | (i<<56) | + ((i&0x00ff000000000000ull)>>40) | ((i&0x000000000000ff00ull)<<40) | + ((i&0x0000ff0000000000ull)>>24) | ((i&0x0000000000ff0000ull)<<24) | + ((i&0x000000ff00000000ull)>>8) | ((i&0x00000000ff000000ull)<<8); + } + + ZPL_IMPL_INLINE zpl_i32 zpl_next_pow2(zpl_i32 x) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } + + ZPL_IMPL_INLINE void zpl_bit_set(zpl_u32* x, zpl_u32 bit) { *x = *x | (1 << bit); } + ZPL_IMPL_INLINE zpl_b8 zpl_bit_get(zpl_u32 x, zpl_u32 bit) { return (x & (1 << bit)); } + ZPL_IMPL_INLINE void zpl_bit_reset(zpl_u32* x, zpl_u32 bit) { *x = *x & ~(1 << bit); } + + ZPL_IMPL_INLINE zpl_isize zpl_count_set_bits(zpl_u64 mask) { + zpl_isize count = 0; + while (mask) { + count += (mask & 1); + mask >>= 1; + } + return count; + } + + ZPL_END_C_DECLS + // file: header/core/sort.h + + /** @file sort.c + @brief Sorting and searching methods. + @defgroup sort Sorting and searching + + Methods for sorting arrays using either Quick/Merge-sort combo or Radix sort. It also contains simple implementation of binary search, as well as an easy to use API to define your own comparators. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + #define ZPL_COMPARE_PROC(name) int name(void const *a, void const *b) + typedef ZPL_COMPARE_PROC(zpl_compare_proc); + + #define ZPL_COMPARE_PROC_PTR(def) ZPL_COMPARE_PROC((*def)) + + // Procedure pointers + // NOTE: The offset parameter specifies the offset in the structure + // e.g. zpl_i32_cmp(zpl_offset_of(Thing, value)) + // Use 0 if it's just the type instead. + + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_i16_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_u8_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_i32_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_i64_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_isize_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_str_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_f32_cmp(zpl_isize offset)); + ZPL_DEF ZPL_COMPARE_PROC_PTR(zpl_f64_cmp(zpl_isize offset)); + + // TODO: Better sorting algorithms + + //! Sorts an array. + + //! Uses quick sort for large arrays but insertion sort for small ones. + #define zpl_sort_array(array, count, compare_proc) zpl_sort(array, count, zpl_size_of(*(array)), compare_proc) + + //! Perform sorting operation on a memory location with a specified item count and size. + ZPL_DEF void zpl_sort(void *base, zpl_isize count, zpl_isize size, zpl_compare_proc compare_proc); + + // NOTE: the count of temp == count of items + #define zpl_radix_sort(Type) zpl_radix_sort_##Type + #define ZPL_RADIX_SORT_PROC(Type) void zpl_radix_sort(Type)(zpl_##Type * items, zpl_##Type * temp, zpl_isize count) + + ZPL_DEF ZPL_RADIX_SORT_PROC(u8); + ZPL_DEF ZPL_RADIX_SORT_PROC(u16); + ZPL_DEF ZPL_RADIX_SORT_PROC(u32); + ZPL_DEF ZPL_RADIX_SORT_PROC(u64); + + //! Performs binary search on an array. + + //! Returns index or -1 if not found + #define zpl_binary_search_array(array, count, key, compare_proc) \ + zpl_binary_search(array, count, zpl_size_of(*(array)), key, compare_proc) + + //! Performs binary search on a memory location with specified item count and size. + ZPL_DEF_INLINE zpl_isize zpl_binary_search(void const *base, zpl_isize count, zpl_isize size, void const *key, + zpl_compare_proc compare_proc); + + #define zpl_shuffle_array(array, count) zpl_shuffle(array, count, zpl_size_of(*(array))) + + //! Shuffles a memory. + ZPL_DEF void zpl_shuffle(void *base, zpl_isize count, zpl_isize size); + + #define zpl_reverse_array(array, count) zpl_reverse(array, count, zpl_size_of(*(array))) + + //! Reverses memory's contents + ZPL_DEF void zpl_reverse(void *base, zpl_isize count, zpl_isize size); + + //! @} + + + ZPL_IMPL_INLINE zpl_isize zpl_binary_search(void const *base, zpl_isize count, zpl_isize size, void const *key, + zpl_compare_proc compare_proc) { + zpl_isize start = 0; + zpl_isize end = count; + + while (start < end) { + zpl_isize mid = start + (end - start) / 2; + zpl_isize result = compare_proc(key, cast(zpl_u8 *) base + mid * size); + if (result < 0) + end = mid; + else if (result > 0) + start = mid + 1; + else + return mid; + } + + return -1; + } + + ZPL_END_C_DECLS +# endif +#endif + +#if defined(ZPL_MODULE_HASHING) + // file: header/hashing.h + + /** @file hashing.c + @brief Hashing and Checksum Functions + @defgroup hashing Hashing and Checksum Functions + + Several hashing methods used by zpl internally but possibly useful outside of it. Contains: adler32, crc32/64, fnv32/64/a and murmur32/64 + + @{ + */ + + + ZPL_BEGIN_C_DECLS + + ZPL_DEF zpl_u32 zpl_adler32(void const *data, zpl_isize len); + + ZPL_DEF zpl_u32 zpl_crc32(void const *data, zpl_isize len); + ZPL_DEF zpl_u64 zpl_crc64(void const *data, zpl_isize len); + + // These use FNV-1 algorithm + ZPL_DEF zpl_u32 zpl_fnv32(void const *data, zpl_isize len); + ZPL_DEF zpl_u64 zpl_fnv64(void const *data, zpl_isize len); + ZPL_DEF zpl_u32 zpl_fnv32a(void const *data, zpl_isize len); + ZPL_DEF zpl_u64 zpl_fnv64a(void const *data, zpl_isize len); + + ZPL_DEF zpl_u8 *zpl_base64_encode(zpl_allocator a, void const *data, zpl_isize len); + ZPL_DEF zpl_u8 *zpl_base64_decode(zpl_allocator a, void const *data, zpl_isize len); + + //! Based on MurmurHash3 + ZPL_DEF zpl_u32 zpl_murmur32_seed(void const *data, zpl_isize len, zpl_u32 seed); + + //! Based on MurmurHash2 + ZPL_DEF zpl_u64 zpl_murmur64_seed(void const *data, zpl_isize len, zpl_u64 seed); + + //! Default seed of 0x9747b28c + ZPL_DEF_INLINE zpl_u32 zpl_murmur32(void const *data, zpl_isize len); + + //! Default seed of 0x9747b28c + ZPL_DEF_INLINE zpl_u64 zpl_murmur64(void const *data, zpl_isize len); + + //! @} + + ZPL_IMPL_INLINE zpl_u32 zpl_murmur32(void const *data, zpl_isize len) { return zpl_murmur32_seed(data, len, 0x9747b28c); } + ZPL_IMPL_INLINE zpl_u64 zpl_murmur64(void const *data, zpl_isize len) { return zpl_murmur64_seed(data, len, 0x9747b28c); } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_REGEX) + // file: header/regex.h + + /** @file regex.c + @brief Regular expressions parser. + @defgroup regex Regex processor + + Port of gb_regex with several bugfixes applied. This is a simple regex library and is fast to perform. + + Supported Matching: + @n ^ - Beginning of string + @n $ - End of string + @n . - Match one (anything) + @n | - Branch (or) + @n () - Capturing group + @n [] - Any character included in set + @n [^] - Any character excluded from set + @n + - One or more (greedy) + @n +? - One or more (non-greedy) + @n * - Zero or more (greedy) + @n *? - Zero or more (non-greedy) + @n ? - Zero or once + @n [BACKSLASH]XX - Hex decimal digit (must be 2 digits) + @n [BACKSLASH]meta - Meta character + @n [BACKSLASH]s - Whitespace + @n [BACKSLASH]S - Not whitespace + @n [BACKSLASH]d - Digit + @n [BACKSLASH]D - Not digit + @n [BACKSLASH]a - Alphabetic character + @n [BACKSLASH]l - Lower case letter + @n [BACKSLASH]u - Upper case letter + @n [BACKSLASH]w - Word + @n [BACKSLASH]W - Not word + @n [BACKSLASH]x - Hex Digit + @n [BACKSLASH]p - Printable ASCII character + @n --Whitespace-- + @n [BACKSLASH]t - Tab + @n [BACKSLASH]n - New line + @n [BACKSLASH]r - Return carriage + @n [BACKSLASH]v - Vertical Tab + @n [BACKSLASH]f - Form feed + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_re { + zpl_allocator backing; + zpl_isize capture_count; + char *buf; + zpl_isize buf_len, buf_cap; + zpl_b32 can_realloc; + } zpl_re; + + typedef struct zpl_re_capture { + char const *str; + zpl_isize len; + } zpl_re_capture; + + #define zplRegexError zpl_regex_error + typedef enum zpl_regex_error { + ZPL_RE_ERROR_NONE, + ZPL_RE_ERROR_NO_MATCH, + ZPL_RE_ERROR_TOO_LONG, + ZPL_RE_ERROR_MISMATCHED_CAPTURES, + ZPL_RE_ERROR_MISMATCHED_BLOCKS, + ZPL_RE_ERROR_BRANCH_FAILURE, + ZPL_RE_ERROR_INVALID_QUANTIFIER, + ZPL_RE_ERROR_INTERNAL_FAILURE, + } zpl_regex_error; + + //! Compile regex pattern. + ZPL_DEF zpl_regex_error zpl_re_compile(zpl_re *re, zpl_allocator backing, char const *pattern, zpl_isize pattern_len); + + //! Compile regex pattern using a buffer. + ZPL_DEF zpl_regex_error zpl_re_compile_from_buffer(zpl_re *re, char const *pattern, zpl_isize pattern_len, void *buffer, zpl_isize buffer_len); + + //! Destroy regex object. + ZPL_DEF void zpl_re_destroy(zpl_re *re); + + //! Retrieve number of retrievable captures. + ZPL_DEF zpl_isize zpl_re_capture_count(zpl_re *re); + + //! Match input string and output captures of the occurence. + ZPL_DEF zpl_b32 zpl_re_match(zpl_re *re, char const *str, zpl_isize str_len, zpl_re_capture *captures, zpl_isize max_capture_count, zpl_isize *offset); + + //! Match all occurences in an input string and output them into captures. Array of captures is allocated on the heap and needs to be freed afterwards. + ZPL_DEF zpl_b32 zpl_re_match_all(zpl_re *re, char const *str, zpl_isize str_len, zpl_isize max_capture_count, zpl_re_capture **out_captures); + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_DLL) + // file: header/dll.h + + /** @file dll.c + @brief DLL Handling + @defgroup dll DLL handling + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef void *zpl_dll_handle; + typedef void (*zpl_dll_proc)(void); + + ZPL_DEF zpl_dll_handle zpl_dll_load(char const *filepath); + ZPL_DEF void zpl_dll_unload(zpl_dll_handle dll); + ZPL_DEF zpl_dll_proc zpl_dll_proc_address(zpl_dll_handle dll, char const *proc_name); + + //! @} + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_OPTS) + // file: header/opts.h + + /** @file opts.c + @brief CLI options processor + @defgroup cli CLI options processor + + Opts is a CLI options parser, it can parse flags, switches and arguments from command line + and offers an easy way to express input errors as well as the ability to display help screen. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef enum { + ZPL_OPTS_STRING, + ZPL_OPTS_FLOAT, + ZPL_OPTS_FLAG, + ZPL_OPTS_INT, + } zpl_opts_types; + + typedef struct { + char const *name, *lname, *desc; + zpl_u8 type; + zpl_b32 met, pos; + + //! values + union { + zpl_string text; + zpl_i64 integer; + zpl_f64 real; + }; + } zpl_opts_entry; + + typedef enum { + ZPL_OPTS_ERR_VALUE, + ZPL_OPTS_ERR_OPTION, + ZPL_OPTS_ERR_EXTRA_VALUE, + ZPL_OPTS_ERR_MISSING_VALUE, + } zpl_opts_err_type; + + typedef struct { + char *val; + zpl_u8 type; + } zpl_opts_err; + + typedef struct { + zpl_allocator alloc; + zpl_opts_entry *entries; ///< zpl_array + zpl_opts_err *errors; ///< zpl_array + zpl_opts_entry **positioned; ///< zpl_array + char const *appname; + } zpl_opts; + + //! Initializes options parser. + + //! Initializes CLI options parser using specified memory allocator and provided application name. + //! @param opts Options parser to initialize. + //! @param allocator Memory allocator to use. (ex. zpl_heap()) + //! @param app Application name displayed in help screen. + ZPL_DEF void zpl_opts_init(zpl_opts *opts, zpl_allocator allocator, char const *app); + + //! Releases the resources used by options parser. + ZPL_DEF void zpl_opts_free(zpl_opts *opts); + + //! Registers an option. + + //! Registers an option with its short and long name, specifies option's type and its description. + //! @param opts Options parser to add to. + //! @param lname Shorter name of option. (ex. "f") + //! @param name Full name of option. (ex. "foo") Note that rest of the module uses longer names to manipulate opts. + //! @param desc Description shown in the help screen. + //! @param type Option's type (see zpl_opts_types) + //! @see zpl_opts_types + ZPL_DEF void zpl_opts_add(zpl_opts *opts, char const *name, char const *lname, const char *desc, zpl_u8 type); + + //! Registers option as positional. + + //! Registers added option as positional, so that we can pass it anonymously. Arguments are expected on the command input in the same order they were registered as. + //! @param opts + //! @param name Name of already registered option. + ZPL_DEF void zpl_opts_positional_add(zpl_opts *opts, char const *name); + + //! Compiles CLI arguments. + + // This method takes CLI arguments as input and processes them based on rules that were set up. + //! @param opts + //! @param argc Argument count in an array. + //! @param argv Array of arguments. + ZPL_DEF zpl_b32 zpl_opts_compile(zpl_opts *opts, int argc, char **argv); + + //! Prints out help screen. + + //! Prints out help screen with example usage of application as well as with all the flags available. + ZPL_DEF void zpl_opts_print_help(zpl_opts *opts); + + //! Prints out parsing errors. + + //! Prints out possible errors caused by CLI input. + ZPL_DEF void zpl_opts_print_errors(zpl_opts *opts); + + //! Fetches a string from an option. + + //! @param opts + //! @param name Name of an option. + //! @param fallback Fallback string we return if option wasn't found. + ZPL_DEF zpl_string zpl_opts_string(zpl_opts *opts, char const *name, char const *fallback); + + //! Fetches a real number from an option. + + //! @param opts + //! @param name Name of an option. + //! @param fallback Fallback real number we return if option was not found. + ZPL_DEF zpl_f64 zpl_opts_real(zpl_opts *opts, char const *name, zpl_f64 fallback); + + //! Fetches an integer number from an option. + + //! @param opts + //! @param name Name of an option. + //! @param fallback Fallback integer number we return if option was not found. + ZPL_DEF zpl_i64 zpl_opts_integer(zpl_opts *opts, char const *name, zpl_i64 fallback); + + //! Checks whether an option was used. + + //! @param opts + //! @param name Name of an option. + ZPL_DEF zpl_b32 zpl_opts_has_arg(zpl_opts *opts, char const *name); + + //! Checks whether all positionals have been passed in. + ZPL_DEF zpl_b32 zpl_opts_positionals_filled(zpl_opts *opts); + + //! @} + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_PROCESS) + // file: header/process.h + + /** @file process.c + @brief Process creation and manipulation methods + @defgroup process Process creation and manipulation methods + + Gives you the ability to create a new process, wait for it to end or terminate it. + It also exposes standard I/O with configurable options. + + @{ + */ + + ZPL_BEGIN_C_DECLS + // TODO(zaklaus): Add Linux support + + typedef enum { + ZPL_PR_OPTS_COMBINE_STD_OUTPUT = ZPL_BIT(1), + ZPL_PR_OPTS_INHERIT_ENV = ZPL_BIT(2), + ZPL_PR_OPTS_CUSTOM_ENV = ZPL_BIT(3), + } zpl_pr_opts; + + typedef struct { + zpl_file in, out, err; + void *f_stdin, *f_stdout, *f_stderr; + #ifdef ZPL_SYSTEM_WINDOWS + void *win32_handle; + #else + // todo + #endif + } zpl_pr; + + typedef struct { + char *con_title; + char *workdir; + + zpl_isize env_count; + char **env; // format: "var=name" + + zpl_u32 posx, posy; + zpl_u32 resx, resy; + zpl_u32 bufx, bufy; + zpl_u32 fill_attr; + zpl_u32 flags; + zpl_b32 show_window; + } zpl_pr_si; + + ZPL_DEF zpl_i32 zpl_pr_create(zpl_pr *process, const char **args, zpl_isize argc, zpl_pr_si si, zpl_pr_opts options); + ZPL_DEF void zpl_pr_destroy(zpl_pr *process); + ZPL_DEF void zpl_pr_terminate(zpl_pr *process, zpl_i32 err_code); + ZPL_DEF zpl_i32 zpl_pr_join(zpl_pr *process); + + //! @} + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_MATH) + // file: header/math.h + + /** @file math.c + @brief Math operations + @defgroup math Math operations + + OpenGL gamedev friendly library for math. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef union zpl_vec2 { + struct { + zpl_f32 x, y; + }; + struct { + zpl_f32 s, t; + }; + zpl_f32 e[2]; + } zpl_vec2; + + typedef union zpl_vec3 { + struct { + zpl_f32 x, y, z; + }; + struct { + zpl_f32 r, g, b; + }; + struct { + zpl_f32 s, t, p; + }; + + zpl_vec2 xy; + zpl_vec2 st; + zpl_f32 e[3]; + } zpl_vec3; + + typedef union zpl_vec4 { + struct { + zpl_f32 x, y, z, w; + }; + struct { + zpl_f32 r, g, b, a; + }; + struct { + zpl_f32 s, t, p, q; + }; + struct { + zpl_vec2 xy, zw; + }; + struct { + zpl_vec2 st, pq; + }; + zpl_vec3 xyz; + zpl_vec3 rgb; + zpl_f32 e[4]; + } zpl_vec4; + + typedef union zpl_mat2 { + struct { + zpl_vec2 x, y; + }; + zpl_vec2 col[2]; + zpl_f32 e[4]; + } zpl_mat2; + + typedef union zpl_mat3 { + struct { + zpl_vec3 x, y, z; + }; + zpl_vec3 col[3]; + zpl_f32 e[9]; + } zpl_mat3; + + typedef union zpl_mat4 { + struct { + zpl_vec4 x, y, z, w; + }; + zpl_vec4 col[4]; + zpl_f32 e[16]; + } zpl_mat4; + + typedef union zpl_quat { + struct { + zpl_f32 x, y, z, w; + }; + zpl_vec4 xyzw; + zpl_vec3 xyz; + zpl_f32 e[4]; + } zpl_quat; + + typedef union zpl_plane { + struct { + zpl_f32 a, b, c, d; + }; + zpl_vec4 xyzw; + zpl_vec3 n; + zpl_f32 e[4]; + } zpl_plane; + + typedef struct zpl_frustum { + zpl_plane x1; + zpl_plane x2; + zpl_plane y1; + zpl_plane y2; + zpl_plane z1; + zpl_plane z2; + } zpl_frustum; + + typedef zpl_f32 zpl_float2[2]; + typedef zpl_f32 zpl_float3[3]; + typedef zpl_f32 zpl_float4[4]; + + typedef struct zpl_rect2 { + zpl_vec2 pos, dim; + } zpl_rect2; + typedef struct zpl_rect3 { + zpl_vec3 pos, dim; + } zpl_rect3; + + typedef struct zpl_aabb2 { + zpl_vec2 min, max; + } zpl_aabb2; + typedef struct zpl_aabb3 { + zpl_vec3 min, max; + } zpl_aabb3; + + typedef short zpl_half; + + #ifndef ZPL_CONSTANTS + #define ZPL_CONSTANTS + #define ZPL_EPSILON 1.19209290e-7f + #define ZPL_ZERO 0.0f + #define ZPL_ONE 1.0f + #define ZPL_TWO_THIRDS 0.666666666666666666666666666666666666667f + + #define ZPL_TAU 6.28318530717958647692528676655900576f + #define ZPL_PI 3.14159265358979323846264338327950288f + #define ZPL_ONE_OVER_TAU 0.636619772367581343075535053490057448f + #define ZPL_ONE_OVER_PI 0.159154943091895335768883763372514362f + + #define ZPL_TAU_OVER_2 3.14159265358979323846264338327950288f + #define ZPL_TAU_OVER_4 1.570796326794896619231321691639751442f + #define ZPL_TAU_OVER_8 0.785398163397448309615660845819875721f + + #define ZPL_E 2.71828182845904523536f + #define ZPL_SQRT_TWO 1.41421356237309504880168872420969808f + #define ZPL_SQRT_THREE 1.73205080756887729352744634150587236f + #define ZPL_SQRT_FIVE 2.23606797749978969640917366873127623f + + #define ZPL_LOG_TWO 0.693147180559945309417232121458176568f + #define ZPL_LOG_TEN 2.30258509299404568401799145468436421f + #endif // ZPL_CONSTANTS + + #ifndef zpl_square + #define zpl_square(x) ((x) * (x)) + #endif + + #ifndef zpl_cube + #define zpl_cube(x) ((x) * (x) * (x)) + #endif + + #ifndef zpl_sign + #define zpl_sign(x) ((x) >= 0.0f ? 1.0f : -1.0f) + #endif + + #ifndef zpl_sign0 + #define zpl_sign0(x) ((x == 0.0f) ? 0.0f : ((x) >= 0.0f ? 1.0f : -1.0f)) + #endif + + ZPL_DEF zpl_f32 zpl_to_radians(zpl_f32 degrees); + ZPL_DEF zpl_f32 zpl_to_degrees(zpl_f32 radians); + + /* NOTE: Because to interpolate angles */ + ZPL_DEF zpl_f32 zpl_angle_diff(zpl_f32 radians_a, zpl_f32 radians_b); + + ZPL_DEF zpl_f32 zpl_copy_sign(zpl_f32 x, zpl_f32 y); + ZPL_DEF zpl_f32 zpl_remainder(zpl_f32 x, zpl_f32 y); + ZPL_DEF zpl_f32 zpl_mod(zpl_f32 x, zpl_f32 y); + ZPL_DEF zpl_f64 zpl_copy_sign64(zpl_f64 x, zpl_f64 y); + ZPL_DEF zpl_f64 zpl_floor64(zpl_f64 x); + ZPL_DEF zpl_f64 zpl_ceil64(zpl_f64 x); + ZPL_DEF zpl_f64 zpl_round64(zpl_f64 x); + ZPL_DEF zpl_f64 zpl_remainder64(zpl_f64 x, zpl_f64 y); + ZPL_DEF zpl_f64 zpl_abs64(zpl_f64 x); + ZPL_DEF zpl_f64 zpl_sign64(zpl_f64 x); + ZPL_DEF zpl_f64 zpl_mod64(zpl_f64 x, zpl_f64 y); + ZPL_DEF zpl_f32 zpl_sqrt(zpl_f32 a); + ZPL_DEF zpl_f32 zpl_rsqrt(zpl_f32 a); + ZPL_DEF zpl_f32 zpl_quake_rsqrt(zpl_f32 a); /* NOTE: It's probably better to use 1.0f/zpl_sqrt(a) + * And for simd, there is usually isqrt functions too! + */ + ZPL_DEF zpl_f32 zpl_hypot(zpl_f32 x, zpl_f32 y); + ZPL_DEF zpl_f32 zpl_sin(zpl_f32 radians); + ZPL_DEF zpl_f32 zpl_cos(zpl_f32 radians); + ZPL_DEF zpl_f32 zpl_tan(zpl_f32 radians); + ZPL_DEF zpl_f32 zpl_arcsin(zpl_f32 a); + ZPL_DEF zpl_f32 zpl_arccos(zpl_f32 a); + ZPL_DEF zpl_f32 zpl_arctan(zpl_f32 a); + ZPL_DEF zpl_f32 zpl_arctan2(zpl_f32 y, zpl_f32 x); + + ZPL_DEF zpl_f32 zpl_exp(zpl_f32 x); + ZPL_DEF zpl_f32 zpl_exp2(zpl_f32 x); + ZPL_DEF zpl_f32 zpl_log(zpl_f32 x); + ZPL_DEF zpl_f32 zpl_log2(zpl_f32 x); + ZPL_DEF zpl_f32 zpl_fast_exp(zpl_f32 x); /* NOTE: Only valid from -1 <= x <= +1 */ + ZPL_DEF zpl_f32 zpl_fast_exp2(zpl_f32 x); /* NOTE: Only valid from -1 <= x <= +1 */ + ZPL_DEF zpl_f32 zpl_pow(zpl_f32 x, zpl_f32 y); /* x^y */ + + ZPL_DEF zpl_f32 zpl_round(zpl_f32 x); + ZPL_DEF zpl_f32 zpl_floor(zpl_f32 x); + ZPL_DEF zpl_f32 zpl_ceil(zpl_f32 x); + + ZPL_DEF zpl_f32 zpl_half_to_float(zpl_half value); + ZPL_DEF zpl_half zpl_float_to_half(zpl_f32 value); + + ZPL_DEF zpl_vec2 zpl_vec2f_zero(void); + ZPL_DEF zpl_vec2 zpl_vec2f(zpl_f32 x, zpl_f32 y); + ZPL_DEF zpl_vec2 zpl_vec2fv(zpl_f32 x[2]); + + ZPL_DEF zpl_vec3 zpl_vec3f_zero(void); + ZPL_DEF zpl_vec3 zpl_vec3f(zpl_f32 x, zpl_f32 y, zpl_f32 z); + ZPL_DEF zpl_vec3 zpl_vec3fv(zpl_f32 x[3]); + + ZPL_DEF zpl_vec4 zpl_vec4f_zero(void); + ZPL_DEF zpl_vec4 zpl_vec4f(zpl_f32 x, zpl_f32 y, zpl_f32 z, zpl_f32 w); + ZPL_DEF zpl_vec4 zpl_vec4fv(zpl_f32 x[4]); + + ZPL_DEF zpl_f32 zpl_vec2_max(zpl_vec2 v); + ZPL_DEF zpl_f32 zpl_vec2_side(zpl_vec2 p, zpl_vec2 q, zpl_vec2 r); + ZPL_DEF void zpl_vec2_add(zpl_vec2 *d, zpl_vec2 v0, zpl_vec2 v1); + ZPL_DEF void zpl_vec2_sub(zpl_vec2 *d, zpl_vec2 v0, zpl_vec2 v1); + ZPL_DEF void zpl_vec2_mul(zpl_vec2 *d, zpl_vec2 v, zpl_f32 s); + ZPL_DEF void zpl_vec2_div(zpl_vec2 *d, zpl_vec2 v, zpl_f32 s); + + ZPL_DEF zpl_f32 zpl_vec3_max(zpl_vec3 v); + ZPL_DEF void zpl_vec3_add(zpl_vec3 *d, zpl_vec3 v0, zpl_vec3 v1); + ZPL_DEF void zpl_vec3_sub(zpl_vec3 *d, zpl_vec3 v0, zpl_vec3 v1); + ZPL_DEF void zpl_vec3_mul(zpl_vec3 *d, zpl_vec3 v, zpl_f32 s); + ZPL_DEF void zpl_vec3_div(zpl_vec3 *d, zpl_vec3 v, zpl_f32 s); + + ZPL_DEF void zpl_vec4_add(zpl_vec4 *d, zpl_vec4 v0, zpl_vec4 v1); + ZPL_DEF void zpl_vec4_sub(zpl_vec4 *d, zpl_vec4 v0, zpl_vec4 v1); + ZPL_DEF void zpl_vec4_mul(zpl_vec4 *d, zpl_vec4 v, zpl_f32 s); + ZPL_DEF void zpl_vec4_div(zpl_vec4 *d, zpl_vec4 v, zpl_f32 s); + + ZPL_DEF void zpl_vec2_addeq(zpl_vec2 *d, zpl_vec2 v); + ZPL_DEF void zpl_vec2_subeq(zpl_vec2 *d, zpl_vec2 v); + ZPL_DEF void zpl_vec2_muleq(zpl_vec2 *d, zpl_f32 s); + ZPL_DEF void zpl_vec2_diveq(zpl_vec2 *d, zpl_f32 s); + + ZPL_DEF void zpl_vec3_addeq(zpl_vec3 *d, zpl_vec3 v); + ZPL_DEF void zpl_vec3_subeq(zpl_vec3 *d, zpl_vec3 v); + ZPL_DEF void zpl_vec3_muleq(zpl_vec3 *d, zpl_f32 s); + ZPL_DEF void zpl_vec3_diveq(zpl_vec3 *d, zpl_f32 s); + + ZPL_DEF void zpl_vec4_addeq(zpl_vec4 *d, zpl_vec4 v); + ZPL_DEF void zpl_vec4_subeq(zpl_vec4 *d, zpl_vec4 v); + ZPL_DEF void zpl_vec4_muleq(zpl_vec4 *d, zpl_f32 s); + ZPL_DEF void zpl_vec4_diveq(zpl_vec4 *d, zpl_f32 s); + + ZPL_DEF zpl_f32 zpl_vec2_dot(zpl_vec2 v0, zpl_vec2 v1); + ZPL_DEF zpl_f32 zpl_vec3_dot(zpl_vec3 v0, zpl_vec3 v1); + ZPL_DEF zpl_f32 zpl_vec4_dot(zpl_vec4 v0, zpl_vec4 v1); + + ZPL_DEF void zpl_vec2_cross(zpl_f32 *d, zpl_vec2 v0, zpl_vec2 v1); + ZPL_DEF void zpl_vec3_cross(zpl_vec3 *d, zpl_vec3 v0, zpl_vec3 v1); + + ZPL_DEF zpl_f32 zpl_vec2_mag2(zpl_vec2 v); + ZPL_DEF zpl_f32 zpl_vec3_mag2(zpl_vec3 v); + ZPL_DEF zpl_f32 zpl_vec4_mag2(zpl_vec4 v); + + ZPL_DEF zpl_f32 zpl_vec2_mag(zpl_vec2 v); + ZPL_DEF zpl_f32 zpl_vec3_mag(zpl_vec3 v); + ZPL_DEF zpl_f32 zpl_vec4_mag(zpl_vec4 v); + + ZPL_DEF void zpl_vec2_norm(zpl_vec2 *d, zpl_vec2 v); + ZPL_DEF void zpl_vec3_norm(zpl_vec3 *d, zpl_vec3 v); + ZPL_DEF void zpl_vec4_norm(zpl_vec4 *d, zpl_vec4 v); + + ZPL_DEF void zpl_vec2_norm0(zpl_vec2 *d, zpl_vec2 v); + ZPL_DEF void zpl_vec3_norm0(zpl_vec3 *d, zpl_vec3 v); + ZPL_DEF void zpl_vec4_norm0(zpl_vec4 *d, zpl_vec4 v); + + ZPL_DEF void zpl_vec2_reflect(zpl_vec2 *d, zpl_vec2 i, zpl_vec2 n); + ZPL_DEF void zpl_vec3_reflect(zpl_vec3 *d, zpl_vec3 i, zpl_vec3 n); + ZPL_DEF void zpl_vec2_refract(zpl_vec2 *d, zpl_vec2 i, zpl_vec2 n, zpl_f32 eta); + ZPL_DEF void zpl_vec3_refract(zpl_vec3 *d, zpl_vec3 i, zpl_vec3 n, zpl_f32 eta); + + ZPL_DEF zpl_f32 zpl_vec2_aspect_ratio(zpl_vec2 v); + + ZPL_DEF void zpl_mat2_identity(zpl_mat2 *m); + ZPL_DEF void zpl_float22_identity(zpl_f32 m[2][2]); + + ZPL_DEF void zpl_mat2_transpose(zpl_mat2 *m); + ZPL_DEF void zpl_mat2_mul(zpl_mat2 *out, zpl_mat2 *m1, zpl_mat2 *m2); + ZPL_DEF void zpl_mat2_mul_vec2(zpl_vec2 *out, zpl_mat2 *m, zpl_vec2 in); + ZPL_DEF void zpl_mat2_inverse(zpl_mat2 *out, zpl_mat2 *in); + ZPL_DEF zpl_f32 zpl_mat2_determinate(zpl_mat2 *m); + + ZPL_DEF zpl_mat2 *zpl_mat2_v(zpl_vec2 m[2]); + ZPL_DEF zpl_mat2 *zpl_mat2_f(zpl_f32 m[2][2]); + ZPL_DEF zpl_float2 *zpl_float22_m(zpl_mat2 *m); + ZPL_DEF zpl_float2 *zpl_float22_v(zpl_vec2 m[2]); + ZPL_DEF zpl_float2 *zpl_float22_4(zpl_f32 m[4]); + + ZPL_DEF void zpl_float22_transpose(zpl_f32 (*vec)[2]); + ZPL_DEF void zpl_float22_mul(zpl_f32 (*out)[2], zpl_f32 (*mat1)[2], zpl_f32 (*mat2)[2]); + ZPL_DEF void zpl_float22_mul_vec2(zpl_vec2 *out, zpl_f32 m[2][2], zpl_vec2 in); + + ZPL_DEF void zpl_mat3_identity(zpl_mat3 *m); + ZPL_DEF void zpl_float33_identity(zpl_f32 m[3][3]); + + ZPL_DEF void zpl_mat3_transpose(zpl_mat3 *m); + ZPL_DEF void zpl_mat3_mul(zpl_mat3 *out, zpl_mat3 *m1, zpl_mat3 *m2); + ZPL_DEF void zpl_mat3_mul_vec3(zpl_vec3 *out, zpl_mat3 *m, zpl_vec3 in); + ZPL_DEF void zpl_mat3_inverse(zpl_mat3 *out, zpl_mat3 *in); + ZPL_DEF zpl_f32 zpl_mat3_determinate(zpl_mat3 *m); + + ZPL_DEF zpl_mat3 *zpl_mat3_v(zpl_vec3 m[3]); + ZPL_DEF zpl_mat3 *zpl_mat3_f(zpl_f32 m[3][3]); + + ZPL_DEF zpl_float3 *zpl_float33_m(zpl_mat3 *m); + ZPL_DEF zpl_float3 *zpl_float33_v(zpl_vec3 m[3]); + ZPL_DEF zpl_float3 *zpl_float33_9(zpl_f32 m[9]); + + ZPL_DEF void zpl_float33_transpose(zpl_f32 (*vec)[3]); + ZPL_DEF void zpl_float33_mul(zpl_f32 (*out)[3], zpl_f32 (*mat1)[3], zpl_f32 (*mat2)[3]); + ZPL_DEF void zpl_float33_mul_vec3(zpl_vec3 *out, zpl_f32 m[3][3], zpl_vec3 in); + + ZPL_DEF void zpl_mat4_identity(zpl_mat4 *m); + ZPL_DEF void zpl_float44_identity(zpl_f32 m[4][4]); + ZPL_DEF void zpl_mat4_copy(zpl_mat4* out, zpl_mat4* m); + + ZPL_DEF void zpl_mat4_transpose(zpl_mat4 *m); + ZPL_DEF void zpl_mat4_mul(zpl_mat4 *out, zpl_mat4 *m1, zpl_mat4 *m2); + ZPL_DEF void zpl_mat4_mul_vec4(zpl_vec4 *out, zpl_mat4 *m, zpl_vec4 in); + ZPL_DEF void zpl_mat4_inverse(zpl_mat4 *out, zpl_mat4 *in); + + ZPL_DEF zpl_mat4 *zpl_mat4_v(zpl_vec4 m[4]); + ZPL_DEF zpl_mat4 *zpl_mat4_f(zpl_f32 m[4][4]); + + ZPL_DEF zpl_float4 *zpl_float44_m(zpl_mat4 *m); + ZPL_DEF zpl_float4 *zpl_float44_v(zpl_vec4 m[4]); + ZPL_DEF zpl_float4 *zpl_float44_16(zpl_f32 m[16]); + + ZPL_DEF void zpl_float44_transpose(zpl_f32 (*vec)[4]); + ZPL_DEF void zpl_float44_mul(zpl_f32 (*out)[4], zpl_f32 (*mat1)[4], zpl_f32 (*mat2)[4]); + ZPL_DEF void zpl_float44_mul_vec4(zpl_vec4 *out, zpl_f32 m[4][4], zpl_vec4 in); + + ZPL_DEF void zpl_mat4_axis_angle(zpl_mat4* out, zpl_vec3 v, zpl_f32 angle_radians); + ZPL_DEF void zpl_mat4_to_translate(zpl_mat4* out, zpl_vec3 v); + ZPL_DEF void zpl_mat4_to_rotate(zpl_mat4* out, zpl_vec3 v, zpl_f32 angle_radians); + ZPL_DEF void zpl_mat4_to_scale(zpl_mat4* out, zpl_vec3 v); + ZPL_DEF void zpl_mat4_to_scalef(zpl_mat4* out, zpl_f32 s); + ZPL_DEF void zpl_mat4_translate(zpl_mat4* out, zpl_vec3 v); + ZPL_DEF void zpl_mat4_rotate(zpl_mat4* out, zpl_vec3 v, zpl_f32 angle_radians); + ZPL_DEF void zpl_mat4_scale(zpl_mat4* out, zpl_vec3 v); + ZPL_DEF void zpl_mat4_scalef(zpl_mat4 *out, zpl_f32 s); + ZPL_DEF void zpl_mat4_ortho2d(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top); + ZPL_DEF void zpl_mat4_ortho3d(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top, zpl_f32 z_near, zpl_f32 z_far); + ZPL_DEF void zpl_mat4_perspective(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near, zpl_f32 z_far); + ZPL_DEF void zpl_mat4_infinite_perspective(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near); + + ZPL_DEF void zpl_mat4_ortho2d_dx(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top); + ZPL_DEF void zpl_mat4_ortho3d_dx(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top, zpl_f32 z_near, zpl_f32 z_far); + ZPL_DEF void zpl_mat4_perspective_dx(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near, zpl_f32 z_far); + ZPL_DEF void zpl_mat4_infinite_perspective_dx(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near); + + ZPL_DEF void zpl_mat4_look_at(zpl_mat4 *out, zpl_vec3 eye, zpl_vec3 centre, zpl_vec3 up); + + ZPL_DEF void zpl_mat4_look_at_lh(zpl_mat4 *out, zpl_vec3 eye, zpl_vec3 centre, zpl_vec3 up); + + ZPL_DEF zpl_quat zpl_quatf(zpl_f32 x, zpl_f32 y, zpl_f32 z, zpl_f32 w); + ZPL_DEF zpl_quat zpl_quatfv(zpl_f32 e[4]); + ZPL_DEF zpl_quat zpl_quat_axis_angle(zpl_vec3 axis, zpl_f32 angle_radians); + ZPL_DEF zpl_quat zpl_quat_euler_angles(zpl_f32 pitch, zpl_f32 yaw, zpl_f32 roll); + ZPL_DEF zpl_quat zpl_quat_identity(void); + + ZPL_DEF void zpl_quat_add(zpl_quat *d, zpl_quat q0, zpl_quat q1); + ZPL_DEF void zpl_quat_sub(zpl_quat *d, zpl_quat q0, zpl_quat q1); + ZPL_DEF void zpl_quat_mul(zpl_quat *d, zpl_quat q0, zpl_quat q1); + ZPL_DEF void zpl_quat_div(zpl_quat *d, zpl_quat q0, zpl_quat q1); + + ZPL_DEF void zpl_quat_mulf(zpl_quat *d, zpl_quat q, zpl_f32 s); + ZPL_DEF void zpl_quat_divf(zpl_quat *d, zpl_quat q, zpl_f32 s); + + ZPL_DEF void zpl_quat_addeq(zpl_quat *d, zpl_quat q); + ZPL_DEF void zpl_quat_subeq(zpl_quat *d, zpl_quat q); + ZPL_DEF void zpl_quat_muleq(zpl_quat *d, zpl_quat q); + ZPL_DEF void zpl_quat_diveq(zpl_quat *d, zpl_quat q); + + ZPL_DEF void zpl_quat_muleqf(zpl_quat *d, zpl_f32 s); + ZPL_DEF void zpl_quat_diveqf(zpl_quat *d, zpl_f32 s); + + ZPL_DEF zpl_f32 zpl_quat_dot(zpl_quat q0, zpl_quat q1); + ZPL_DEF zpl_f32 zpl_quat_mag(zpl_quat q); + + ZPL_DEF void zpl_quat_norm(zpl_quat *d, zpl_quat q); + ZPL_DEF void zpl_quat_conj(zpl_quat *d, zpl_quat q); + ZPL_DEF void zpl_quat_inverse(zpl_quat *d, zpl_quat q); + + ZPL_DEF void zpl_quat_axis(zpl_vec3 *axis, zpl_quat q); + ZPL_DEF zpl_f32 zpl_quat_angle(zpl_quat q); + + ZPL_DEF zpl_f32 zpl_quat_pitch(zpl_quat q); + ZPL_DEF zpl_f32 zpl_quat_yaw(zpl_quat q); + ZPL_DEF zpl_f32 zpl_quat_roll(zpl_quat q); + + /* NOTE: Rotate v by q */ + ZPL_DEF void zpl_quat_rotate_vec3(zpl_vec3 *d, zpl_quat q, zpl_vec3 v); + ZPL_DEF void zpl_mat4_from_quat(zpl_mat4 *out, zpl_quat q); + ZPL_DEF void zpl_quat_from_mat4(zpl_quat *out, zpl_mat4 *m); + + /* Plane math. */ + ZPL_DEF zpl_f32 zpl_plane_distance(zpl_plane* p, zpl_vec3 v); + + /* Frustum culling. */ + ZPL_DEF void zpl_frustum_create(zpl_frustum* out, zpl_mat4* camera, zpl_mat4* proj); + ZPL_DEF zpl_b8 zpl_frustum_sphere_inside(zpl_frustum* frustum, zpl_vec3 center, zpl_f32 radius); + ZPL_DEF zpl_b8 zpl_frustum_point_inside(zpl_frustum* frustum, zpl_vec3 point); + ZPL_DEF zpl_b8 zpl_frustum_box_inside(zpl_frustum* frustum, zpl_aabb3 box); + + /* Interpolations */ + ZPL_DEF zpl_f32 zpl_lerp(zpl_f32 a, zpl_f32 b, zpl_f32 t); + ZPL_DEF zpl_f32 zpl_unlerp(zpl_f32 t, zpl_f32 a, zpl_f32 b); + ZPL_DEF zpl_f32 zpl_smooth_step(zpl_f32 a, zpl_f32 b, zpl_f32 t); + ZPL_DEF zpl_f32 zpl_smoother_step(zpl_f32 a, zpl_f32 b, zpl_f32 t); + + ZPL_DEF void zpl_vec2_lerp(zpl_vec2 *d, zpl_vec2 a, zpl_vec2 b, zpl_f32 t); + ZPL_DEF void zpl_vec3_lerp(zpl_vec3 *d, zpl_vec3 a, zpl_vec3 b, zpl_f32 t); + ZPL_DEF void zpl_vec4_lerp(zpl_vec4 *d, zpl_vec4 a, zpl_vec4 b, zpl_f32 t); + + ZPL_DEF void zpl_vec2_cslerp(zpl_vec2 *d, zpl_vec2 a, zpl_vec2 v0, zpl_vec2 b, zpl_vec2 v1, zpl_f32 t); + ZPL_DEF void zpl_vec3_cslerp(zpl_vec3 *d, zpl_vec3 a, zpl_vec3 v0, zpl_vec3 b, zpl_vec3 v1, zpl_f32 t); + ZPL_DEF void zpl_vec2_dcslerp(zpl_vec2 *d, zpl_vec2 a, zpl_vec2 v0, zpl_vec2 b, zpl_vec2 v1, zpl_f32 t); + ZPL_DEF void zpl_vec3_dcslerp(zpl_vec3 *d, zpl_vec3 a, zpl_vec3 v0, zpl_vec3 b, zpl_vec3 v1, zpl_f32 t); + + ZPL_DEF void zpl_quat_lerp(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t); + ZPL_DEF void zpl_quat_nlerp(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t); + ZPL_DEF void zpl_quat_slerp(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t); + ZPL_DEF void zpl_quat_nquad(zpl_quat *d, zpl_quat p, zpl_quat a, zpl_quat b, zpl_quat q, zpl_f32 t); + ZPL_DEF void zpl_quat_squad(zpl_quat *d, zpl_quat p, zpl_quat a, zpl_quat b, zpl_quat q, zpl_f32 t); + ZPL_DEF void zpl_quat_slerp_approx(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t); + ZPL_DEF void zpl_quat_squad_approx(zpl_quat *d, zpl_quat p, zpl_quat a, zpl_quat b, zpl_quat q, zpl_f32 t); + + /* rects */ + ZPL_DEF zpl_rect2 zpl_rect2f(zpl_vec2 pos, zpl_vec2 dim); + ZPL_DEF zpl_rect3 zpl_rect3f(zpl_vec3 pos, zpl_vec3 dim); + + ZPL_DEF zpl_aabb2 zpl_aabb2f(zpl_f32 minx, zpl_f32 miny, zpl_f32 maxx, zpl_f32 maxy); + ZPL_DEF zpl_aabb3 zpl_aabb3f(zpl_f32 minx, zpl_f32 miny, zpl_f32 minz, zpl_f32 maxx, zpl_f32 maxy, zpl_f32 maxz); + + ZPL_DEF zpl_aabb2 zpl_aabb2_rect2(zpl_rect2 a); + ZPL_DEF zpl_aabb3 zpl_aabb3_rect3(zpl_rect3 a); + ZPL_DEF zpl_rect2 zpl_rect2_aabb2(zpl_aabb2 a); + ZPL_DEF zpl_rect3 zpl_rect3_aabb3(zpl_aabb3 a); + + ZPL_DEF int zpl_rect2_contains(zpl_rect2 a, zpl_f32 x, zpl_f32 y); + ZPL_DEF int zpl_rect2_contains_vec2(zpl_rect2 a, zpl_vec2 p); + ZPL_DEF int zpl_rect2_intersects(zpl_rect2 a, zpl_rect2 b); + ZPL_DEF int zpl_rect2_intersection_result(zpl_rect2 a, zpl_rect2 b, zpl_rect2 *intersection); + ZPL_DEF int zpl_aabb2_contains(zpl_aabb2 a, zpl_f32 x, zpl_f32 y); + ZPL_DEF int zpl_aabb3_contains(zpl_aabb3 a, zpl_f32 x, zpl_f32 y, zpl_f32 z); + + /* rectangle partitioning: based on https://halt.software/dead-simple-layouts/ */ + ZPL_DEF zpl_aabb2 zpl_aabb2_cut_left(zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_cut_right(zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_cut_top(zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_cut_bottom(zpl_aabb2 *a, zpl_f32 b); + + ZPL_DEF zpl_aabb2 zpl_aabb2_get_left(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_get_right(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_get_top(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_get_bottom(const zpl_aabb2 *a, zpl_f32 b); + + ZPL_DEF zpl_aabb2 zpl_aabb2_add_left(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_add_right(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_add_top(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_add_bottom(const zpl_aabb2 *a, zpl_f32 b); + + ZPL_DEF zpl_aabb2 zpl_aabb2_contract(const zpl_aabb2 *a, zpl_f32 b); + ZPL_DEF zpl_aabb2 zpl_aabb2_expand(const zpl_aabb2 *a, zpl_f32 b); + + //! @} + ZPL_END_C_DECLS + #if defined(__cplusplus) + ZPL_INLINE bool operator==(zpl_vec2 a, zpl_vec2 b) { return (a.x == b.x) && (a.y == b.y); } + ZPL_INLINE bool operator!=(zpl_vec2 a, zpl_vec2 b) { return !operator==(a, b); } + + ZPL_INLINE zpl_vec2 operator+(zpl_vec2 a) { return a; } + ZPL_INLINE zpl_vec2 operator-(zpl_vec2 a) { zpl_vec2 r = {-a.x, -a.y}; return r; } + + ZPL_INLINE zpl_vec2 operator+(zpl_vec2 a, zpl_vec2 b) { zpl_vec2 r; zpl_vec2_add(&r, a, b); return r; } + ZPL_INLINE zpl_vec2 operator-(zpl_vec2 a, zpl_vec2 b) { zpl_vec2 r; zpl_vec2_sub(&r, a, b); return r; } + + ZPL_INLINE zpl_vec2 operator*(zpl_vec2 a, float scalar) { zpl_vec2 r; zpl_vec2_mul(&r, a, scalar); return r; } + ZPL_INLINE zpl_vec2 operator*(float scalar, zpl_vec2 a) { return operator*(a, scalar); } + + ZPL_INLINE zpl_vec2 operator/(zpl_vec2 a, float scalar) { return operator*(a, 1.0f/scalar); } + + /* Hadamard Product */ + ZPL_INLINE zpl_vec2 operator*(zpl_vec2 a, zpl_vec2 b) { zpl_vec2 r = {a.x*b.x, a.y*b.y}; return r; } + ZPL_INLINE zpl_vec2 operator/(zpl_vec2 a, zpl_vec2 b) { zpl_vec2 r = {a.x/b.x, a.y/b.y}; return r; } + + ZPL_INLINE zpl_vec2 &operator+=(zpl_vec2 &a, zpl_vec2 b) { return (a = a + b); } + ZPL_INLINE zpl_vec2 &operator-=(zpl_vec2 &a, zpl_vec2 b) { return (a = a - b); } + ZPL_INLINE zpl_vec2 &operator*=(zpl_vec2 &a, float scalar) { return (a = a * scalar); } + ZPL_INLINE zpl_vec2 &operator/=(zpl_vec2 &a, float scalar) { return (a = a / scalar); } + + + ZPL_INLINE bool operator==(zpl_vec3 a, zpl_vec3 b) { return (a.x == b.x) && (a.y == b.y) && (a.z == b.z); } + ZPL_INLINE bool operator!=(zpl_vec3 a, zpl_vec3 b) { return !operator==(a, b); } + + ZPL_INLINE zpl_vec3 operator+(zpl_vec3 a) { return a; } + ZPL_INLINE zpl_vec3 operator-(zpl_vec3 a) { zpl_vec3 r = {-a.x, -a.y, -a.z}; return r; } + + ZPL_INLINE zpl_vec3 operator+(zpl_vec3 a, zpl_vec3 b) { zpl_vec3 r; zpl_vec3_add(&r, a, b); return r; } + ZPL_INLINE zpl_vec3 operator-(zpl_vec3 a, zpl_vec3 b) { zpl_vec3 r; zpl_vec3_sub(&r, a, b); return r; } + + ZPL_INLINE zpl_vec3 operator*(zpl_vec3 a, float scalar) { zpl_vec3 r; zpl_vec3_mul(&r, a, scalar); return r; } + ZPL_INLINE zpl_vec3 operator*(float scalar, zpl_vec3 a) { return operator*(a, scalar); } + + ZPL_INLINE zpl_vec3 operator/(zpl_vec3 a, float scalar) { return operator*(a, 1.0f/scalar); } + + /* Hadamard Product */ + ZPL_INLINE zpl_vec3 operator*(zpl_vec3 a, zpl_vec3 b) { zpl_vec3 r = {a.x*b.x, a.y*b.y, a.z*b.z}; return r; } + ZPL_INLINE zpl_vec3 operator/(zpl_vec3 a, zpl_vec3 b) { zpl_vec3 r = {a.x/b.x, a.y/b.y, a.z/b.z}; return r; } + + ZPL_INLINE zpl_vec3 &operator+=(zpl_vec3 &a, zpl_vec3 b) { return (a = a + b); } + ZPL_INLINE zpl_vec3 &operator-=(zpl_vec3 &a, zpl_vec3 b) { return (a = a - b); } + ZPL_INLINE zpl_vec3 &operator*=(zpl_vec3 &a, float scalar) { return (a = a * scalar); } + ZPL_INLINE zpl_vec3 &operator/=(zpl_vec3 &a, float scalar) { return (a = a / scalar); } + + + ZPL_INLINE bool operator==(zpl_vec4 a, zpl_vec4 b) { return (a.x == b.x) && (a.y == b.y) && (a.z == b.z) && (a.w == b.w); } + ZPL_INLINE bool operator!=(zpl_vec4 a, zpl_vec4 b) { return !operator==(a, b); } + + ZPL_INLINE zpl_vec4 operator+(zpl_vec4 a) { return a; } + ZPL_INLINE zpl_vec4 operator-(zpl_vec4 a) { zpl_vec4 r = {-a.x, -a.y, -a.z, -a.w}; return r; } + + ZPL_INLINE zpl_vec4 operator+(zpl_vec4 a, zpl_vec4 b) { zpl_vec4 r; zpl_vec4_add(&r, a, b); return r; } + ZPL_INLINE zpl_vec4 operator-(zpl_vec4 a, zpl_vec4 b) { zpl_vec4 r; zpl_vec4_sub(&r, a, b); return r; } + + ZPL_INLINE zpl_vec4 operator*(zpl_vec4 a, float scalar) { zpl_vec4 r; zpl_vec4_mul(&r, a, scalar); return r; } + ZPL_INLINE zpl_vec4 operator*(float scalar, zpl_vec4 a) { return operator*(a, scalar); } + + ZPL_INLINE zpl_vec4 operator/(zpl_vec4 a, float scalar) { return operator*(a, 1.0f/scalar); } + + /* Hadamard Product */ + ZPL_INLINE zpl_vec4 operator*(zpl_vec4 a, zpl_vec4 b) { zpl_vec4 r = {a.x*b.x, a.y*b.y, a.z*b.z, a.w*b.w}; return r; } + ZPL_INLINE zpl_vec4 operator/(zpl_vec4 a, zpl_vec4 b) { zpl_vec4 r = {a.x/b.x, a.y/b.y, a.z/b.z, a.w/b.w}; return r; } + + ZPL_INLINE zpl_vec4 &operator+=(zpl_vec4 &a, zpl_vec4 b) { return (a = a + b); } + ZPL_INLINE zpl_vec4 &operator-=(zpl_vec4 &a, zpl_vec4 b) { return (a = a - b); } + ZPL_INLINE zpl_vec4 &operator*=(zpl_vec4 &a, float scalar) { return (a = a * scalar); } + ZPL_INLINE zpl_vec4 &operator/=(zpl_vec4 &a, float scalar) { return (a = a / scalar); } + + + ZPL_INLINE zpl_mat2 operator+(zpl_mat2 const &a, zpl_mat2 const &b) { + int i, j; + zpl_mat2 r = {0}; + for (j = 0; j < 2; j++) { + for (i = 0; i < 2; i++) + r.e[2*j+i] = a.e[2*j+i] + b.e[2*j+i]; + } + return r; + } + + ZPL_INLINE zpl_mat2 operator-(zpl_mat2 const &a, zpl_mat2 const &b) { + int i, j; + zpl_mat2 r = {0}; + for (j = 0; j < 2; j++) { + for (i = 0; i < 2; i++) + r.e[2*j+i] = a.e[2*j+i] - b.e[2*j+i]; + } + return r; + } + + ZPL_INLINE zpl_mat2 operator*(zpl_mat2 const &a, zpl_mat2 const &b) { zpl_mat2 r; zpl_mat2_mul(&r, (zpl_mat2 *)&a, (zpl_mat2 *)&b); return r; } + ZPL_INLINE zpl_vec2 operator*(zpl_mat2 const &a, zpl_vec2 v) { zpl_vec2 r; zpl_mat2_mul_vec2(&r, (zpl_mat2 *)&a, v); return r; } + ZPL_INLINE zpl_mat2 operator*(zpl_mat2 const &a, float scalar) { + zpl_mat2 r = {0}; + int i; + for (i = 0; i < 2*2; i++) r.e[i] = a.e[i] * scalar; + return r; + } + ZPL_INLINE zpl_mat2 operator*(float scalar, zpl_mat2 const &a) { return operator*(a, scalar); } + ZPL_INLINE zpl_mat2 operator/(zpl_mat2 const &a, float scalar) { return operator*(a, 1.0f/scalar); } + + ZPL_INLINE zpl_mat2& operator+=(zpl_mat2& a, zpl_mat2 const &b) { return (a = a + b); } + ZPL_INLINE zpl_mat2& operator-=(zpl_mat2& a, zpl_mat2 const &b) { return (a = a - b); } + ZPL_INLINE zpl_mat2& operator*=(zpl_mat2& a, zpl_mat2 const &b) { return (a = a * b); } + + + + ZPL_INLINE zpl_mat3 operator+(zpl_mat3 const &a, zpl_mat3 const &b) { + int i, j; + zpl_mat3 r = {0}; + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) + r.e[3*j+i] = a.e[3*j+i] + b.e[3*j+i]; + } + return r; + } + + ZPL_INLINE zpl_mat3 operator-(zpl_mat3 const &a, zpl_mat3 const &b) { + int i, j; + zpl_mat3 r = {0}; + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) + r.e[3*j+i] = a.e[3*j+i] - b.e[3*j+i]; + } + return r; + } + + ZPL_INLINE zpl_mat3 operator*(zpl_mat3 const &a, zpl_mat3 const &b) { zpl_mat3 r; zpl_mat3_mul(&r, (zpl_mat3 *)&a, (zpl_mat3 *)&b); return r; } + ZPL_INLINE zpl_vec3 operator*(zpl_mat3 const &a, zpl_vec3 v) { zpl_vec3 r; zpl_mat3_mul_vec3(&r, (zpl_mat3 *)&a, v); return r; } + ZPL_INLINE zpl_mat3 operator*(zpl_mat3 const &a, float scalar) { + zpl_mat3 r = {0}; + int i; + for (i = 0; i < 3*3; i++) r.e[i] = a.e[i] * scalar; + return r; + } + ZPL_INLINE zpl_mat3 operator*(float scalar, zpl_mat3 const &a) { return operator*(a, scalar); } + ZPL_INLINE zpl_mat3 operator/(zpl_mat3 const &a, float scalar) { return operator*(a, 1.0f/scalar); } + + ZPL_INLINE zpl_mat3& operator+=(zpl_mat3& a, zpl_mat3 const &b) { return (a = a + b); } + ZPL_INLINE zpl_mat3& operator-=(zpl_mat3& a, zpl_mat3 const &b) { return (a = a - b); } + ZPL_INLINE zpl_mat3& operator*=(zpl_mat3& a, zpl_mat3 const &b) { return (a = a * b); } + + + + ZPL_INLINE zpl_mat4 operator+(zpl_mat4 const &a, zpl_mat4 const &b) { + int i, j; + zpl_mat4 r = {0}; + for (j = 0; j < 4; j++) { + for (i = 0; i < 4; i++) + r.e[4*j+i] = a.e[4*j+i] + b.e[4*j+i]; + } + return r; + } + + ZPL_INLINE zpl_mat4 operator-(zpl_mat4 const &a, zpl_mat4 const &b) { + int i, j; + zpl_mat4 r = {0}; + for (j = 0; j < 4; j++) { + for (i = 0; i < 4; i++) + r.e[4*j+i] = a.e[4*j+i] - b.e[4*j+i]; + } + return r; + } + + ZPL_INLINE zpl_mat4 operator*(zpl_mat4 const &a, zpl_mat4 const &b) { zpl_mat4 r; zpl_mat4_mul(&r, (zpl_mat4 *)&a, (zpl_mat4 *)&b); return r; } + ZPL_INLINE zpl_vec4 operator*(zpl_mat4 const &a, zpl_vec4 v) { zpl_vec4 r; zpl_mat4_mul_vec4(&r, (zpl_mat4 *)&a, v); return r; } + ZPL_INLINE zpl_mat4 operator*(zpl_mat4 const &a, float scalar) { + zpl_mat4 r = {0}; + int i; + for (i = 0; i < 4*4; i++) r.e[i] = a.e[i] * scalar; + return r; + } + ZPL_INLINE zpl_mat4 operator*(float scalar, zpl_mat4 const &a) { return operator*(a, scalar); } + ZPL_INLINE zpl_mat4 operator/(zpl_mat4 const &a, float scalar) { return operator*(a, 1.0f/scalar); } + + ZPL_INLINE zpl_mat4& operator+=(zpl_mat4 &a, zpl_mat4 const &b) { return (a = a + b); } + ZPL_INLINE zpl_mat4& operator-=(zpl_mat4 &a, zpl_mat4 const &b) { return (a = a - b); } + ZPL_INLINE zpl_mat4& operator*=(zpl_mat4 &a, zpl_mat4 const &b) { return (a = a * b); } + + + + ZPL_INLINE bool operator==(zpl_quat a, zpl_quat b) { return a.xyzw == b.xyzw; } + ZPL_INLINE bool operator!=(zpl_quat a, zpl_quat b) { return !operator==(a, b); } + + ZPL_INLINE zpl_quat operator+(zpl_quat q) { return q; } + ZPL_INLINE zpl_quat operator-(zpl_quat q) { return zpl_quatf(-q.x, -q.y, -q.z, -q.w); } + + ZPL_INLINE zpl_quat operator+(zpl_quat a, zpl_quat b) { zpl_quat r; zpl_quat_add(&r, a, b); return r; } + ZPL_INLINE zpl_quat operator-(zpl_quat a, zpl_quat b) { zpl_quat r; zpl_quat_sub(&r, a, b); return r; } + + ZPL_INLINE zpl_quat operator*(zpl_quat a, zpl_quat b) { zpl_quat r; zpl_quat_mul(&r, a, b); return r; } + ZPL_INLINE zpl_quat operator*(zpl_quat q, float s) { zpl_quat r; zpl_quat_mulf(&r, q, s); return r; } + ZPL_INLINE zpl_quat operator*(float s, zpl_quat q) { return operator*(q, s); } + ZPL_INLINE zpl_quat operator/(zpl_quat q, float s) { zpl_quat r; zpl_quat_divf(&r, q, s); return r; } + + ZPL_INLINE zpl_quat &operator+=(zpl_quat &a, zpl_quat b) { zpl_quat_addeq(&a, b); return a; } + ZPL_INLINE zpl_quat &operator-=(zpl_quat &a, zpl_quat b) { zpl_quat_subeq(&a, b); return a; } + ZPL_INLINE zpl_quat &operator*=(zpl_quat &a, zpl_quat b) { zpl_quat_muleq(&a, b); return a; } + ZPL_INLINE zpl_quat &operator/=(zpl_quat &a, zpl_quat b) { zpl_quat_diveq(&a, b); return a; } + + ZPL_INLINE zpl_quat &operator*=(zpl_quat &a, float b) { zpl_quat_muleqf(&a, b); return a; } + ZPL_INLINE zpl_quat &operator/=(zpl_quat &a, float b) { zpl_quat_diveqf(&a, b); return a; } + + /* Rotate v by a */ + ZPL_INLINE zpl_vec3 operator*(zpl_quat q, zpl_vec3 v) { zpl_vec3 r; zpl_quat_rotate_vec3(&r, q, v); return r; } + #endif +#endif + +#if defined(ZPL_MODULE_PARSER) + // file: header/adt.h + + ZPL_BEGIN_C_DECLS + + typedef enum zpl_adt_type { + ZPL_ADT_TYPE_UNINITIALISED, /* node was not initialised, this is a programming error! */ + ZPL_ADT_TYPE_ARRAY, + ZPL_ADT_TYPE_OBJECT, + ZPL_ADT_TYPE_STRING, + ZPL_ADT_TYPE_MULTISTRING, + ZPL_ADT_TYPE_INTEGER, + ZPL_ADT_TYPE_REAL, + } zpl_adt_type; + + typedef enum zpl_adt_props { + ZPL_ADT_PROPS_NONE, + ZPL_ADT_PROPS_NAN, + ZPL_ADT_PROPS_NAN_NEG, + ZPL_ADT_PROPS_INFINITY, + ZPL_ADT_PROPS_INFINITY_NEG, + ZPL_ADT_PROPS_FALSE, + ZPL_ADT_PROPS_TRUE, + ZPL_ADT_PROPS_NULL, + ZPL_ADT_PROPS_IS_EXP, + ZPL_ADT_PROPS_IS_HEX, + + // Used internally so that people can fill in real numbers they plan to write. + ZPL_ADT_PROPS_IS_PARSED_REAL, + } zpl_adt_props; + + typedef enum zpl_adt_naming_style { + ZPL_ADT_NAME_STYLE_DOUBLE_QUOTE, + ZPL_ADT_NAME_STYLE_SINGLE_QUOTE, + ZPL_ADT_NAME_STYLE_NO_QUOTES, + } zpl_adt_naming_style; + + typedef enum zpl_adt_assign_style { + ZPL_ADT_ASSIGN_STYLE_COLON, + ZPL_ADT_ASSIGN_STYLE_EQUALS, + ZPL_ADT_ASSIGN_STYLE_LINE, + } zpl_adt_assign_style; + + typedef enum zpl_adt_delim_style { + ZPL_ADT_DELIM_STYLE_COMMA, + ZPL_ADT_DELIM_STYLE_LINE, + ZPL_ADT_DELIM_STYLE_NEWLINE, + } zpl_adt_delim_style; + + typedef enum zpl_adt_error { + ZPL_ADT_ERROR_NONE, + ZPL_ADT_ERROR_INTERNAL, + ZPL_ADT_ERROR_ALREADY_CONVERTED, + ZPL_ADT_ERROR_INVALID_TYPE, + ZPL_ADT_ERROR_OUT_OF_MEMORY, + } zpl_adt_error; + + typedef struct zpl_adt_node { + char const *name; + struct zpl_adt_node *parent; + + /* properties */ + zpl_u8 type :4; + zpl_u8 props :4; + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + zpl_u8 cfg_mode :1; + zpl_u8 name_style :2; + zpl_u8 assign_style:2; + zpl_u8 delim_style :2; + zpl_u8 delim_line_width :4; + zpl_u8 assign_line_width:4; + #endif + + /* adt data */ + union { + char const *string; + struct zpl_adt_node *nodes; ///< zpl_array + struct { + union { + zpl_f64 real; + zpl_i64 integer; + }; + + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + /* number analysis */ + zpl_i32 base; + zpl_i32 base2; + zpl_u8 base2_offset:4; + zpl_i8 exp :4; + zpl_u8 neg_zero :1; + zpl_u8 lead_digit:1; + #endif + }; + }; + } zpl_adt_node; + + /* ADT NODE LIMITS + * delimiter and assignment segment width is limited to 128 whitespace symbols each. + * real number limits decimal position to 128 places. + * real number exponent is limited to 64 digits. + */ + + /** + * @brief Initialise an ADT object or array + * + * @param node + * @param backing Memory allocator used for descendants + * @param name Node's name + * @param is_array + * @return error code + */ + ZPL_DEF zpl_u8 zpl_adt_make_branch(zpl_adt_node *node, zpl_allocator backing, char const *name, zpl_b32 is_array); + + /** + * @brief Destroy an ADT branch and its descendants + * + * @param node + * @return error code + */ + ZPL_DEF zpl_u8 zpl_adt_destroy_branch(zpl_adt_node *node); + + /** + * @brief Initialise an ADT leaf + * + * @param node + * @param name Node's name + * @param type Node's type (use zpl_adt_make_branch for container nodes) + * @return error code + */ + ZPL_DEF zpl_u8 zpl_adt_make_leaf(zpl_adt_node *node, char const *name, zpl_u8 type); + + + /** + * @brief Fetch a node using provided URI string. + * + * This method uses a basic syntax to fetch a node from the ADT. The following features are available + * to retrieve the data: + * + * - "a/b/c" navigates through objects "a" and "b" to get to "c" + * - "arr/[foo=123]/bar" iterates over "arr" to find any object with param "foo" that matches the value "123", then gets its field called "bar" + * - "arr/3" retrieves the 4th element in "arr" + * - "arr/[apple]" retrieves the first element of value "apple" in "arr" + * + * @param node ADT node + * @param uri Locator string as described above + * @return zpl_adt_node* + * + * @see code/apps/examples/json_get.c + */ + ZPL_DEF zpl_adt_node *zpl_adt_query(zpl_adt_node *node, char const *uri); + + /** + * @brief Find a field node within an object by the given name. + * + * @param node + * @param name + * @param deep_search Perform search recursively + * @return zpl_adt_node * node + */ + ZPL_DEF zpl_adt_node *zpl_adt_find(zpl_adt_node *node, char const *name, zpl_b32 deep_search); + + /** + * @brief Allocate an unitialised node within a container at a specified index. + * + * @param parent + * @param index + * @return zpl_adt_node * node + */ + ZPL_DEF zpl_adt_node *zpl_adt_alloc_at(zpl_adt_node *parent, zpl_isize index); + + /** + * @brief Allocate an unitialised node within a container. + * + * @param parent + * @return zpl_adt_node * node + */ + ZPL_DEF zpl_adt_node *zpl_adt_alloc(zpl_adt_node *parent); + + /** + * @brief Move an existing node to a new container at a specified index. + * + * @param node + * @param new_parent + * @param index + * @return zpl_adt_node * node + */ + ZPL_DEF zpl_adt_node *zpl_adt_move_node_at(zpl_adt_node *node, zpl_adt_node *new_parent, zpl_isize index); + + /** + * @brief Move an existing node to a new container. + * + * @param node + * @param new_parent + * @return zpl_adt_node * node + */ + ZPL_DEF zpl_adt_node *zpl_adt_move_node(zpl_adt_node *node, zpl_adt_node *new_parent); + + /** + * @brief Swap two nodes. + * + * @param node + * @param other_node + * @return + */ + ZPL_DEF void zpl_adt_swap_nodes(zpl_adt_node *node, zpl_adt_node *other_node); + + /** + * @brief Remove node from container. + * + * @param node + * @return + */ + ZPL_DEF void zpl_adt_remove_node(zpl_adt_node *node); + + /** + * @brief Initialise a node as an object + * + * @param obj + * @param name + * @param backing + * @return + */ + ZPL_DEF zpl_b8 zpl_adt_set_obj(zpl_adt_node *obj, char const *name, zpl_allocator backing); + + /** + * @brief Initialise a node as an array + * + * @param obj + * @param name + * @param backing + * @return + */ + ZPL_DEF zpl_b8 zpl_adt_set_arr(zpl_adt_node *obj, char const *name, zpl_allocator backing); + + /** + * @brief Initialise a node as a string + * + * @param obj + * @param name + * @param value + * @return + */ + ZPL_DEF zpl_b8 zpl_adt_set_str(zpl_adt_node *obj, char const *name, char const *value); + + /** + * @brief Initialise a node as a float + * + * @param obj + * @param name + * @param value + * @return + */ + ZPL_DEF zpl_b8 zpl_adt_set_flt(zpl_adt_node *obj, char const *name, zpl_f64 value); + + /** + * @brief Initialise a node as a signed integer + * + * @param obj + * @param name + * @param value + * @return + */ + ZPL_DEF zpl_b8 zpl_adt_set_int(zpl_adt_node *obj, char const *name, zpl_i64 value); + + /** + * @brief Append a new node to a container as an object + * + * @param parent + * @param name + * @return* + */ + ZPL_DEF zpl_adt_node *zpl_adt_append_obj(zpl_adt_node *parent, char const *name); + + /** + * @brief Append a new node to a container as an array + * + * @param parent + * @param name + * @return* + */ + ZPL_DEF zpl_adt_node *zpl_adt_append_arr(zpl_adt_node *parent, char const *name); + + /** + * @brief Append a new node to a container as a string + * + * @param parent + * @param name + * @param value + * @return* + */ + ZPL_DEF zpl_adt_node *zpl_adt_append_str(zpl_adt_node *parent, char const *name, char const *value); + + /** + * @brief Append a new node to a container as a float + * + * @param parent + * @param name + * @param value + * @return* + */ + ZPL_DEF zpl_adt_node *zpl_adt_append_flt(zpl_adt_node *parent, char const *name, zpl_f64 value); + + /** + * @brief Append a new node to a container as a signed integer + * + * @param parent + * @param name + * @param value + * @return* + */ + ZPL_DEF zpl_adt_node *zpl_adt_append_int(zpl_adt_node *parent, char const *name, zpl_i64 value); + + /* parser helpers */ + + /** + * @brief Parses a text and stores the result into an unitialised node. + * + * @param node + * @param base + * @return* + */ + ZPL_DEF char *zpl_adt_parse_number(zpl_adt_node *node, char* base); + + /** + * @brief Parses a text and stores the result into an unitialised node. + * This function expects the entire input to be a number. + * + * @param node + * @param base + * @return* + */ + ZPL_DEF char *zpl_adt_parse_number_strict(zpl_adt_node *node, char* base_str); + + /** + * @brief Parses and converts an existing string node into a number. + * + * @param node + * @return + */ + ZPL_DEF zpl_adt_error zpl_adt_str_to_number(zpl_adt_node *node); + + /** + * @brief Parses and converts an existing string node into a number. + * This function expects the entire input to be a number. + * + * @param node + * @return + */ + ZPL_DEF zpl_adt_error zpl_adt_str_to_number_strict(zpl_adt_node *node); + + /** + * @brief Prints a number into a file stream. + * + * The provided file handle can also be a memory mapped stream. + * + * @see zpl_file_stream_new + * @param file + * @param node + * @return + */ + ZPL_DEF zpl_adt_error zpl_adt_print_number(zpl_file *file, zpl_adt_node *node); + + /** + * @brief Prints a string into a file stream. + * + * The provided file handle can also be a memory mapped stream. + * + * @see zpl_file_stream_new + * @param file + * @param node + * @param escaped_chars + * @param escape_symbol + * @return + */ + ZPL_DEF zpl_adt_error zpl_adt_print_string(zpl_file *file, zpl_adt_node *node, char const *escaped_chars, char const *escape_symbol); + + /* extensions */ + + #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define zpl_adt_append(parent, name, value) _Generic((value), \ + char*: zpl_adt_append_str, \ + char const*: zpl_adt_append_str, \ + zpl_f64: zpl_adt_append_flt, \ + default: zpl_adt_append_int)(parent, name, value) + #define zpl_adt_set(obj, name, value) _Generic((value), \ + char*: zpl_adt_set_str, \ + char const*: zpl_adt_set_str, \ + zpl_f64: zpl_adt_set_flt, \ + default: zpl_adt_set_int)(obj, name, value) + #endif + + /* deprecated */ + + ZPL_DEPRECATED_FOR(18.0.0, zpl_adt_query) + ZPL_IMPL_INLINE zpl_adt_node *zpl_adt_get(zpl_adt_node *node, char const *uri) { + return zpl_adt_query(node, uri); + } + + ZPL_DEPRECATED_FOR(13.3.0, zpl_adt_str_to_number) + ZPL_IMPL_INLINE void zpl_adt_str_to_flt(zpl_adt_node *node) { + (void)zpl_adt_str_to_number(node); + } + + ZPL_DEPRECATED_FOR(17.0.0, zpl_adt_append_obj) + ZPL_IMPL_INLINE zpl_adt_node *zpl_adt_inset_obj(zpl_adt_node *parent, char const *name) { + return zpl_adt_append_obj(parent, name); + } + + ZPL_DEPRECATED_FOR(17.0.0, zpl_adt_append_arr) + ZPL_IMPL_INLINE zpl_adt_node *zpl_adt_inset_arr(zpl_adt_node *parent, char const *name) { + return zpl_adt_append_arr(parent, name); + } + + ZPL_DEPRECATED_FOR(17.0.0, zpl_adt_append_str) + ZPL_IMPL_INLINE zpl_adt_node *zpl_adt_inset_str(zpl_adt_node *parent, char const *name, char const *value) { + return zpl_adt_append_str(parent, name, value); + } + + ZPL_DEPRECATED_FOR(17.0.0, zpl_adt_append_flt) + ZPL_IMPL_INLINE zpl_adt_node *zpl_adt_inset_flt(zpl_adt_node *parent, char const *name, zpl_f64 value) { + return zpl_adt_append_flt(parent, name, value); + } + + ZPL_DEPRECATED_FOR(17.0.0, zpl_adt_append_int) + ZPL_IMPL_INLINE zpl_adt_node *zpl_adt_inset_int(zpl_adt_node *parent, char const *name, zpl_i64 value) { + return zpl_adt_append_int(parent, name, value); + } + + ZPL_END_C_DECLS + + /* parsers */ + // file: header/parsers/json.h + + + ZPL_BEGIN_C_DECLS + + typedef enum zpl_json_error { + ZPL_JSON_ERROR_NONE, + ZPL_JSON_ERROR_INTERNAL, + ZPL_JSON_ERROR_INVALID_NAME, + ZPL_JSON_ERROR_INVALID_VALUE, + ZPL_JSON_ERROR_INVALID_ASSIGNMENT, + ZPL_JSON_ERROR_UNKNOWN_KEYWORD, + ZPL_JSON_ERROR_ARRAY_LEFT_OPEN, + ZPL_JSON_ERROR_OBJECT_END_PAIR_MISMATCHED, + ZPL_JSON_ERROR_OUT_OF_MEMORY, + } zpl_json_error; + + typedef enum zpl_json_indent_style { + ZPL_JSON_INDENT_STYLE_COMPACT = -1000, + } zpl_json_indent_style; + + typedef zpl_adt_node zpl_json_object; + + ZPL_DEF zpl_u8 zpl_json_parse(zpl_json_object *root, char *text, zpl_allocator allocator); + ZPL_DEF void zpl_json_free(zpl_json_object *obj); + ZPL_DEF zpl_b8 zpl_json_write(zpl_file *file, zpl_json_object *obj, zpl_isize indent); + ZPL_DEF zpl_string zpl_json_write_string(zpl_allocator a, zpl_json_object *obj, zpl_isize indent); + + ZPL_END_C_DECLS + // file: header/parsers/csv.h + + + ZPL_BEGIN_C_DECLS + + typedef enum zpl_csv_error { + ZPL_CSV_ERROR_NONE, + ZPL_CSV_ERROR_INTERNAL, + ZPL_CSV_ERROR_UNEXPECTED_END_OF_INPUT, + ZPL_CSV_ERROR_MISMATCHED_ROWS, + } zpl_csv_error; + + typedef zpl_adt_node zpl_csv_object; + + ZPL_DEF_INLINE zpl_u8 zpl_csv_parse(zpl_csv_object *root, char *text, zpl_allocator allocator, zpl_b32 has_header); + ZPL_DEF zpl_u8 zpl_csv_parse_delimiter(zpl_csv_object *root, char *text, zpl_allocator allocator, zpl_b32 has_header, char delim); + ZPL_DEF void zpl_csv_free(zpl_csv_object *obj); + + ZPL_DEF_INLINE void zpl_csv_write(zpl_file *file, zpl_csv_object *obj); + ZPL_DEF_INLINE zpl_string zpl_csv_write_string(zpl_allocator a, zpl_csv_object *obj); + ZPL_DEF void zpl_csv_write_delimiter(zpl_file *file, zpl_csv_object *obj, char delim); + ZPL_DEF zpl_string zpl_csv_write_string_delimiter(zpl_allocator a, zpl_csv_object *obj, char delim); + + /* inline */ + + ZPL_IMPL_INLINE zpl_u8 zpl_csv_parse(zpl_csv_object *root, char *text, zpl_allocator allocator, zpl_b32 has_header) { + return zpl_csv_parse_delimiter(root, text, allocator, has_header, ','); + } + + ZPL_IMPL_INLINE void zpl_csv_write(zpl_file *file, zpl_csv_object *obj) { + zpl_csv_write_delimiter(file, obj, ','); + } + + ZPL_IMPL_INLINE zpl_string zpl_csv_write_string(zpl_allocator a, zpl_csv_object *obj) { + return zpl_csv_write_string_delimiter(a, obj, ','); + } + + + ZPL_END_C_DECLS + // file: header/parsers/uri.h + + + ZPL_BEGIN_C_DECLS + + typedef zpl_adt_node zpl_uri_object; + + typedef enum zpl_uri_error { + ZPL_URI_ERROR_NONE, + ZPL_URI_ERROR_INTERNAL, + } zpl_uri_error; + + ZPL_DEF zpl_u8 zpl_uri_init(zpl_adt_node *root, char *origin, zpl_allocator a); + ZPL_DEF zpl_u8 zpl_uri_parse(zpl_adt_node *root, char *text, zpl_allocator a); + ZPL_DEF void zpl_uri_write(zpl_file *f, zpl_adt_node *obj); + ZPL_DEF zpl_string zpl_uri_write_string(zpl_allocator a, zpl_adt_node *obj); + ZPL_DEF void zpl_uri_free(zpl_adt_node *obj); + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_SOCKET) + // file: header/socket.h + + ZPL_BEGIN_C_DECLS + + typedef int zpl_socket; + + typedef struct zpl_socket_addr { + char data[128]; + } zpl_socket_addr; + + typedef enum zpl_socket_protocol { + ZPL_SOCKET_TCP = 0, + ZPL_SOCKET_UDP = 1, + } zpl_socket_protocol; + + typedef enum zpl_socket_mode { + ZPL_SOCKET_BIND = 0, + ZPL_SOCKET_CONNECT = 1, + } zpl_socket_mode; + + typedef enum zpl_socket_flags { + ZPL_SOCKET_DEFAULT = 0, + ZPL_SOCKET_NON_BLOCKING = 0x01, + ZPL_SOCKET_NO_DELAY = 0x02, + } zpl_socket_flags; + + /** + * Initializes socket functionality + * @return 0 on success + */ + ZPL_DEF zpl_i32 zpl_socket_init(void); + + /** + * Creates a new socket configured according to the given parameters + * @param protocol Protocol of the socket, either ZPL_SOCKET_TCP or ZPL_SOCKET_UDP for TCP or UDP respectively + * @param mode Mode of the socket (bind or connect), either ZPL_SOCKET_BIND or ZPL_SOCKET_CONNECT + * @param flags Configuration flags, either ZPL_SOCKET_DEFAULT or a bitwise combination of flags + * @param host Host/address as a string, can be IPv4, IPv6, etc... + * @param service Service/port as a string, e.g. "1728" or "http" + * @return socket handle, or -1 on failure + */ + ZPL_DEF zpl_socket zpl_socket_create(zpl_i32 protocol, zpl_i32 mode, char flags, const char *host, const char *service); + + /** + * Closes the given socket + * @param socket Socket handle + */ + ZPL_DEF void zpl_socket_close(zpl_socket socket); + + /** + * Terminates socket functionality + */ + ZPL_DEF void zpl_socket_terminate(void); + + /** + * Configures the given socket (must be ZPL_SOCKET_TCP + ZPL_SOCKET_BIND) to listen for new connections with the given backlog + * @param socket Socket handle + * @param backlog Size of the backlog + * @return 0 on success, non-zero on failure + */ + ZPL_DEF zpl_i32 zpl_socket_listen(zpl_socket socket, zpl_i32 backlog); + + /** + * Uses the given socket (must be zpl_socket_listen) to accept a new incoming connection, optionally returning its address + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr to store the address + * @return a socket handle for the new connection, or -1 on failure (e.g. if there are no new connections) + */ + ZPL_DEF zpl_socket zpl_socket_accept(zpl_socket socket, zpl_socket_addr *addr); + + /** + * Writes the address the given socket is bound to into the given address pointer, useful when automatically assigning a port + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr to store the address + * @return 0 on success, non-zero on failure + */ + ZPL_DEF zpl_i32 zpl_socket_get_address(zpl_socket socket, zpl_socket_addr *addr); + + /** + * Writes the host/address and service/port of the given address into given buffers (pointer + size), one buffer may be NULL + * @param addr Pointer to zpl_socket_addr containing the address + * @param host Buffer to store the host/address string + * @param host_size Size of the host buffer + * @param service Buffer to store the service/port string + * @param service_size Size of the service buffer + * @return 0 on success, non-zero on failure + */ + ZPL_DEF zpl_i32 zpl_socket_get_address_info(zpl_socket_addr *addr, char *host, zpl_i32 host_size, char *service, zpl_i32 service_size); + + /** + * Uses the given socket (either ZPL_SOCKET_CONNECT or returned by zpl_socket_accept) to send the given data + * @param socket Socket handle + * @param data Pointer to the data to be sent + * @param size Size of the data + * @return how much data was actually sent (may be less than data size), or -1 on failure + */ + ZPL_DEF zpl_i32 zpl_socket_send(zpl_socket socket, const char *data, zpl_i32 size); + + /** + * Receives data using the given socket (either ZPL_SOCKET_CONNECT or returned by zpl_socket_accept) into the given buffer + * @param socket Socket handle + * @param buffer Pointer to the buffer to receive the data + * @param size Size of the buffer + * @return the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive) + */ + ZPL_DEF zpl_i32 zpl_socket_receive(zpl_socket socket, char *buffer, zpl_i32 size); + + /** + * Uses the given socket to send the given data to the given zpl_socket_addr + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr containing the address to send the data to + * @param data Pointer to the data to be sent + * @param size Size of the data + * @return how much data was actually sent (may be less than data size), or -1 on failure + */ + ZPL_DEF zpl_i32 zpl_socket_send_to(zpl_socket socket, zpl_socket_addr *addr, const char *data, zpl_i32 size); + + /** + * Receives data using the given socket into the given buffer, optionally returning the sender's address + * @param socket Socket handle + * @param addr Pointer to zpl_socket_addr to store the sender's address + * @param buffer Pointer to the buffer to receive the data + * @param size Size of the buffer + * @return the number of bytes received, 0 on orderly shutdown, or -1 on failure (e.g. no data to receive) + */ + ZPL_DEF zpl_i32 zpl_socket_receive_from(zpl_socket socket, zpl_socket_addr *addr, char *buffer, zpl_i32 size); + + /** + * Waits either until the given socket has new data to receive or the given time (in seconds) has passed + * @param socket Socket handle + * @param time Time to wait in seconds + * @return 1 if new data is available, 0 if timeout was reached, and -1 on error + */ + ZPL_DEF zpl_i32 zpl_socket_select(zpl_socket socket, zpl_f64 time); + + /** + * Waits either until a socket in the given list has new data to receive or the given time (in seconds) has passed + * @param sockets Array of socket handles + * @param count Number of sockets in the array + * @param time Time to wait in seconds + * @return 1 or more if new data is available, 0 if timeout was reached, and -1 on error + */ + ZPL_DEF zpl_i32 zpl_socket_multi_select(zpl_socket *sockets, zpl_i32 count, zpl_f64 time); + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_THREADING) +# if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_MACOS) +# include +# endif + +# if !defined(zpl_thread_local) +# if defined(_MSC_VER) && _MSC_VER >= 1300 +# define zpl_thread_local __declspec(thread) +# elif defined(__GNUC__) +# define zpl_thread_local __thread +# elif defined(__TINYC__) +# define zpl_thread_local +# else +# define zpl_thread_local thread_local +# endif +# endif + + // file: header/threading/atomic.h + + // Atomics + + // TODO: Be specific with memory order? + // e.g. relaxed, acquire, release, acquire_release + + #if !defined(__STDC_NO_ATOMICS__) && !defined(__cplusplus) && !defined(ZPL_COMPILER_MSVC) && !defined(ZPL_COMPILER_TINYC) + # define zpl_atomic(X) volatile _Atomic(X) + #else + // TODO: Fix once C++ guys bother to add C atomics to std. + //# include + # define zpl_atomic(X) volatile X /*std::atomic*/ + #endif + + #if defined(__STDC_NO_ATOMICS__) || defined(__cplusplus) || defined(ZPL_COMPILER_MSVC) + #define zpl_atomicarg(X) volatile X + #else + #define zpl_atomicarg(X) X + #endif + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_COMPILER_MSVC) + typedef struct zpl_atomic32 { zpl_atomic(zpl_i32) value; } zpl_atomic32; + typedef struct zpl_atomic64 { zpl_atomic(zpl_i64) value; } zpl_atomic64; + typedef struct zpl_atomic_ptr { zpl_atomic(void*) value; } zpl_atomic_ptr; + #else + # if defined(ZPL_ARCH_32_BIT) + # define ZPL_ATOMIC_PTR_ALIGNMENT 4 + # elif defined(ZPL_ARCH_64_BIT) + # define ZPL_ATOMIC_PTR_ALIGNMENT 8 + # else + # error Unknown architecture + # endif + + typedef struct zpl_atomic32 { zpl_atomic(zpl_i32) value; } __attribute__ ((aligned(4))) zpl_atomic32; + typedef struct zpl_atomic64 { zpl_atomic(zpl_i64) value; } __attribute__ ((aligned(8))) zpl_atomic64; + typedef struct zpl_atomic_ptr { zpl_atomic(void*) value; } __attribute__ ((aligned(ZPL_ATOMIC_PTR_ALIGNMENT))) zpl_atomic_ptr; + #endif + + ZPL_DEF zpl_i32 zpl_atomic32_load (zpl_atomic32 const *a); + ZPL_DEF void zpl_atomic32_store (zpl_atomic32 *a, zpl_atomicarg(zpl_i32) value); + ZPL_DEF zpl_i32 zpl_atomic32_compare_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) expected, zpl_atomicarg(zpl_i32) desired); + ZPL_DEF zpl_i32 zpl_atomic32_exchange (zpl_atomic32 *a, zpl_atomicarg(zpl_i32) desired); + ZPL_DEF zpl_i32 zpl_atomic32_fetch_add (zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand); + ZPL_DEF zpl_i32 zpl_atomic32_fetch_and (zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand); + ZPL_DEF zpl_i32 zpl_atomic32_fetch_or (zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand); + ZPL_DEF zpl_b32 zpl_atomic32_spin_lock (zpl_atomic32 *a, zpl_isize time_out); // NOTE: time_out = -1 as default + ZPL_DEF void zpl_atomic32_spin_unlock (zpl_atomic32 *a); + ZPL_DEF zpl_b32 zpl_atomic32_try_acquire_lock(zpl_atomic32 *a); + + + ZPL_DEF zpl_i64 zpl_atomic64_load (zpl_atomic64 const *a); + ZPL_DEF void zpl_atomic64_store (zpl_atomic64 *a, zpl_atomicarg(zpl_i64) value); + ZPL_DEF zpl_i64 zpl_atomic64_compare_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) expected, zpl_atomicarg(zpl_i64) desired); + ZPL_DEF zpl_i64 zpl_atomic64_exchange (zpl_atomic64 *a, zpl_atomicarg(zpl_i64) desired); + ZPL_DEF zpl_i64 zpl_atomic64_fetch_add (zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand); + ZPL_DEF zpl_i64 zpl_atomic64_fetch_and (zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand); + ZPL_DEF zpl_i64 zpl_atomic64_fetch_or (zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand); + ZPL_DEF zpl_b32 zpl_atomic64_spin_lock (zpl_atomic64 *a, zpl_isize time_out); // NOTE: time_out = -1 as default + ZPL_DEF void zpl_atomic64_spin_unlock (zpl_atomic64 *a); + ZPL_DEF zpl_b32 zpl_atomic64_try_acquire_lock(zpl_atomic64 *a); + + + ZPL_DEF void *zpl_atomic_ptr_load (zpl_atomic_ptr const *a); + ZPL_DEF void zpl_atomic_ptr_store (zpl_atomic_ptr *a, zpl_atomicarg(void *)value); + ZPL_DEF void *zpl_atomic_ptr_compare_exchange(zpl_atomic_ptr *a, zpl_atomicarg(void *)expected, zpl_atomicarg(void *)desired); + ZPL_DEF void *zpl_atomic_ptr_exchange (zpl_atomic_ptr *a, zpl_atomicarg(void *)desired); + ZPL_DEF void *zpl_atomic_ptr_fetch_add (zpl_atomic_ptr *a, zpl_atomicarg(void *)operand); + ZPL_DEF void *zpl_atomic_ptr_fetch_and (zpl_atomic_ptr *a, zpl_atomicarg(void *)operand); + ZPL_DEF void *zpl_atomic_ptr_fetch_or (zpl_atomic_ptr *a, zpl_atomicarg(void *)operand); + ZPL_DEF zpl_b32 zpl_atomic_ptr_spin_lock (zpl_atomic_ptr *a, zpl_isize time_out); // NOTE: time_out = -1 as default + ZPL_DEF void zpl_atomic_ptr_spin_unlock (zpl_atomic_ptr *a); + ZPL_DEF zpl_b32 zpl_atomic_ptr_try_acquire_lock(zpl_atomic_ptr *a); + + ZPL_END_C_DECLS + // file: header/threading/fence.h + + // Fences + + ZPL_BEGIN_C_DECLS + + ZPL_DEF void zpl_yield_thread(void); + ZPL_DEF void zpl_mfence (void); + ZPL_DEF void zpl_sfence (void); + ZPL_DEF void zpl_lfence (void); + + ZPL_END_C_DECLS + // file: header/threading/sem.h + + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_SYSTEM_MACOS) + # include + #elif defined(ZPL_SYSTEM_UNIX) + # include + #endif + + #if defined(ZPL_SYSTEM_WINDOWS) + typedef struct zpl_semaphore { void *win32_handle; } zpl_semaphore; + #elif defined(ZPL_SYSTEM_MACOS) + typedef struct zpl_semaphore { semaphore_t osx_handle; } zpl_semaphore; + #elif defined(ZPL_SYSTEM_UNIX) + typedef struct zpl_semaphore { sem_t unix_handle; } zpl_semaphore; + #else + # error + #endif + + ZPL_DEF void zpl_semaphore_init (zpl_semaphore *s); + ZPL_DEF void zpl_semaphore_destroy(zpl_semaphore *s); + ZPL_DEF void zpl_semaphore_post (zpl_semaphore *s, zpl_i32 count); + ZPL_DEF void zpl_semaphore_release(zpl_semaphore *s); // NOTE: zpl_semaphore_post(s, 1) + ZPL_DEF void zpl_semaphore_wait (zpl_semaphore *s); + ZPL_DEF zpl_i32 zpl_semaphore_trywait(zpl_semaphore *s); + + ZPL_END_C_DECLS + // file: header/threading/mutex.h + + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_mutex { + #if defined(ZPL_SYSTEM_WINDOWS) + zpl_u64 win32_critical_section[sizeof(zpl_usize) / 2 + 1]; + #else + pthread_mutex_t pthread_mutex; + #endif + } zpl_mutex; + + ZPL_DEF zpl_b32 zpl_mutex_init (zpl_mutex *m); + ZPL_DEF zpl_b32 zpl_mutex_destroy (zpl_mutex *m); + ZPL_DEF void zpl_mutex_lock (zpl_mutex *m); + ZPL_DEF zpl_b32 zpl_mutex_try_lock(zpl_mutex *m); + ZPL_DEF void zpl_mutex_unlock (zpl_mutex *m); + + ZPL_END_C_DECLS + // file: header/threading/thread.h + + #ifdef ZPL_EDITOR + #include + #else + struct zpl_thread; + #endif + + ZPL_BEGIN_C_DECLS + + typedef zpl_isize (*zpl_thread_proc)(struct zpl_thread *thread); + + typedef struct zpl_thread { + #if defined(ZPL_SYSTEM_WINDOWS) + void * win32_handle; + #else + pthread_t posix_handle; + #endif + + zpl_thread_proc proc; + void * user_data; + zpl_isize user_index; + zpl_isize return_value; + + zpl_semaphore semaphore; + zpl_isize stack_size; + zpl_b32 is_running; + zpl_b32 nowait; + } zpl_thread; + + ZPL_DEF void zpl_thread_init (zpl_thread *t); + ZPL_DEF void zpl_thread_init_nowait (zpl_thread *t); + ZPL_DEF void zpl_thread_destroy (zpl_thread *t); + ZPL_DEF zpl_b32 zpl_thread_start (zpl_thread *t, zpl_thread_proc proc, void *data); + ZPL_DEF zpl_b32 zpl_thread_start_with_stack(zpl_thread *t, zpl_thread_proc proc, void *data, zpl_isize stack_size); + ZPL_DEF void zpl_thread_join (zpl_thread *t); + ZPL_DEF zpl_b32 zpl_thread_is_running (zpl_thread const *t); + ZPL_DEF zpl_u32 zpl_thread_current_id (void); + ZPL_DEF void zpl_thread_set_name (zpl_thread *t, char const *name); + + ZPL_END_C_DECLS + // file: header/threading/sync.h + + // NOTE: Thread Merge Operation + // Based on Sean Barrett's stb_sync + + ZPL_BEGIN_C_DECLS + + typedef struct zpl_sync { + zpl_i32 target; // Target Number of threads + zpl_i32 current; // Threads to hit + zpl_i32 waiting; // Threads waiting + + zpl_mutex start; + zpl_mutex mutex; + zpl_semaphore release; + } zpl_sync; + + ZPL_DEF void zpl_sync_init (zpl_sync *s); + ZPL_DEF void zpl_sync_destroy (zpl_sync *s); + ZPL_DEF void zpl_sync_set_target (zpl_sync *s, zpl_i32 count); + ZPL_DEF void zpl_sync_release (zpl_sync *s); + ZPL_DEF zpl_i32 zpl_sync_reach (zpl_sync *s); + ZPL_DEF void zpl_sync_reach_and_wait(zpl_sync *s); + + ZPL_END_C_DECLS + // file: header/threading/affinity.h + + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_SYSTEM_WINDOWS) || defined (ZPL_SYSTEM_CYGWIN) + + typedef struct zpl_affinity { + zpl_b32 is_accurate; + zpl_isize core_count; + zpl_isize thread_count; + + # define ZPL_WIN32_MAX_THREADS (8 * zpl_size_of(zpl_usize)) + zpl_usize core_masks[ZPL_WIN32_MAX_THREADS]; + } zpl_affinity; + + #elif defined(ZPL_SYSTEM_OSX) + + typedef struct zpl_affinity { + zpl_b32 is_accurate; + zpl_isize core_count; + zpl_isize thread_count; + zpl_isize threads_per_core; + } zpl_affinity; + + #elif defined(ZPL_SYSTEM_LINUX) || defined(ZPL_SYSTEM_FREEBSD) || defined(ZPL_SYSTEM_EMSCRIPTEN) || defined(ZPL_SYSTEM_OPENBSD) + + typedef struct zpl_affinity { + zpl_b32 is_accurate; + zpl_isize core_count; + zpl_isize thread_count; + zpl_isize threads_per_core; + } zpl_affinity; + + #else + # error TODO: Unknown system + #endif + + ZPL_DEF void zpl_affinity_init (zpl_affinity *a); + ZPL_DEF void zpl_affinity_destroy(zpl_affinity *a); + ZPL_DEF zpl_b32 zpl_affinity_set (zpl_affinity *a, zpl_isize core, zpl_isize thread); + ZPL_DEF zpl_isize zpl_affinity_thread_count_for_core(zpl_affinity *a, zpl_isize core); + + ZPL_END_C_DECLS + +# if defined(ZPL_MODULE_JOBS) + // file: header/jobs.h + + /** @file threadpool.c + @brief Job system + @defgroup jobs Job system + + This job system follows thread pool pattern to minimize the costs of thread initialization. + It reuses fixed number of threads to process variable number of jobs. + + @{ + */ + + ZPL_BEGIN_C_DECLS + + typedef void (*zpl_jobs_proc)(void *data); + + #define ZPL_INVALID_JOB ZPL_U32_MAX + + #ifndef ZPL_JOBS_MAX_QUEUE + #define ZPL_JOBS_MAX_QUEUE 100 + #endif + + #ifdef ZPL_JOBS_ENABLE_DEBUG + #define ZPL_JOBS_DEBUG + #endif + + typedef enum { + ZPL_JOBS_STATUS_READY, + ZPL_JOBS_STATUS_BUSY, + ZPL_JOBS_STATUS_WAITING, + ZPL_JOBS_STATUS_TERM, + } zpl_jobs_status; + + typedef enum { + ZPL_JOBS_PRIORITY_REALTIME, + ZPL_JOBS_PRIORITY_HIGH, + ZPL_JOBS_PRIORITY_NORMAL, + ZPL_JOBS_PRIORITY_LOW, + ZPL_JOBS_PRIORITY_IDLE, + ZPL_JOBS_MAX_PRIORITIES, + } zpl_jobs_priority; + + typedef struct { + zpl_jobs_proc proc; + void *data; + } zpl_thread_job; + + ZPL_RING_DECLARE(extern, zpl__jobs_ring_, zpl_thread_job); + + typedef struct { + zpl_thread thread; + zpl_atomic32 status; + zpl_thread_job job; + #ifdef ZPL_JOBS_DEBUG + zpl_u32 hits; + zpl_u32 idle; + #endif + } zpl_thread_worker; + + typedef struct { + zpl__jobs_ring_zpl_thread_job jobs; ///< zpl_ring + zpl_u32 chance; + #ifdef ZPL_JOBS_DEBUG + zpl_u32 hits; + #endif + } zpl_thread_queue; + + typedef struct { + zpl_allocator alloc; + zpl_u32 max_threads, max_jobs, counter; + zpl_thread_worker *workers; ///< zpl_buffer + zpl_thread_queue queues[ZPL_JOBS_MAX_PRIORITIES]; + } zpl_jobs_system; + + //! Initialize thread pool with specified amount of fixed threads. + ZPL_DEF void zpl_jobs_init(zpl_jobs_system *pool, zpl_allocator a, zpl_u32 max_threads); + + //! Initialize thread pool with specified amount of fixed threads and custom job limit. + ZPL_DEF void zpl_jobs_init_with_limit(zpl_jobs_system *pool, zpl_allocator a, zpl_u32 max_threads, zpl_u32 max_jobs); + + //! Release the resources use by thread pool. + ZPL_DEF void zpl_jobs_free(zpl_jobs_system *pool); + + //! Enqueue a job with specified data and custom priority. + ZPL_DEF zpl_b32 zpl_jobs_enqueue_with_priority(zpl_jobs_system *pool, zpl_jobs_proc proc, void *data, zpl_jobs_priority priority); + + //! Enqueue a job with specified data. + ZPL_DEF zpl_b32 zpl_jobs_enqueue(zpl_jobs_system *pool, zpl_jobs_proc proc, void *data); + + //! Check if the work queue is empty. + ZPL_DEF zpl_b32 zpl_jobs_empty(zpl_jobs_system *pool, zpl_jobs_priority priority); + + ZPL_DEF zpl_b32 zpl_jobs_empty_all(zpl_jobs_system *pool); + ZPL_DEF zpl_b32 zpl_jobs_full_all(zpl_jobs_system *pool); + + //! Check if the work queue is full. + ZPL_DEF zpl_b32 zpl_jobs_full(zpl_jobs_system *pool, zpl_jobs_priority priority); + + //! Check if all workers are done. + ZPL_DEF zpl_b32 zpl_jobs_done(zpl_jobs_system *pool); + + //! Process all jobs and check all threads. Should be called by Main Thread in a tight loop. + ZPL_DEF zpl_b32 zpl_jobs_process(zpl_jobs_system *pool); + + ZPL_END_C_DECLS +# endif +#else +# if !defined(zpl_thread_local) +# define zpl_thread_local +# endif +#endif + +#if defined(ZPL_COMPILER_MSVC) +# pragma warning(pop) +#endif + +#if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic pop +#endif + +#if defined(ZPL_IMPLEMENTATION) && !defined(ZPL_IMPLEMENTATION_DONE) +#define ZPL_IMPLEMENTATION_DONE + +#if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wattributes" +# pragma GCC diagnostic ignored "-Wunused-value" +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wwrite-strings" +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# pragma GCC diagnostic ignored "-Wmissing-braces" +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +# pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +# pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4201) +# pragma warning(disable : 4996) // Disable deprecated POSIX functions warning +# pragma warning(disable : 4127) // Conditional expression is constant +# pragma warning(disable : 4204) // non-constant field initializer +# pragma warning(disable : 4756) // -INFINITY +# pragma warning(disable : 4056) // -INFINITY +# pragma warning(disable : 4702) // unreachable code +#endif + +/* general purpose includes */ + +#include + +// NOTE: Ensure we use standard methods for these calls if we use ZPL_PICO +#if !defined(ZPL_PICO_CUSTOM_ROUTINES) +# if !defined(ZPL_MODULE_CORE) +# define zpl__strlen strlen +# define zpl__printf_err(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) +# define zpl__printf_err_va(fmt, va) vfprintf(stderr, fmt, va) +# else +# define zpl__strlen zpl_strlen +# define zpl__printf_err(fmt, ...) zpl_printf_err(fmt, __VA_ARGS__) +# define zpl__printf_err_va(fmt, va) zpl_printf_err_va(fmt, va) +# endif +#endif + +#include + +#if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_MACOS) +# include +#elif defined(ZPL_SYSTEM_WINDOWS) +# if !defined(ZPL_NO_WINDOWS_H) +# ifndef WIN32_LEAN_AND_MEAN +# ifndef NOMINMAX +# define NOMINMAX +# endif + +# define WIN32_LEAN_AND_MEAN +# define WIN32_MEAN_AND_LEAN +# define VC_EXTRALEAN +# endif +# include +# undef NOMINMAX +# undef WIN32_LEAN_AND_MEAN +# undef WIN32_MEAN_AND_LEAN +# undef VC_EXTRALEAN +# endif +#endif + +#if defined(ZPL_MODULE_ESSENTIALS) + // file: source/essentials/debug.c + + + ZPL_BEGIN_C_DECLS + + void zpl_assert_handler(char const *condition, char const *file, zpl_i32 line, char const *msg, ...) { + zpl__printf_err("%s:(%d): Assert Failure: ", file, line); + + if (condition) zpl__printf_err("`%s` ", condition); + + if (msg) { + va_list va; + va_start(va, msg); + zpl__printf_err_va(msg, va); + va_end(va); + } + + zpl__printf_err("%s", "\n"); + } + + zpl_i32 zpl_assert_crash(char const *condition) { + ZPL_PANIC(condition); + return 0; + } + + #if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_MACOS) + # include + #endif + + #if defined(ZPL_SYSTEM_WINDOWS) + void zpl_exit(zpl_u32 code) { ExitProcess(code); } + #else + # include + void zpl_exit(zpl_u32 code) { exit(code); } + #endif + + ZPL_END_C_DECLS + // file: source/essentials/memory.c + + + #include + + ZPL_BEGIN_C_DECLS + + + void zpl_memswap(void *i, void *j, zpl_isize size) { + if (i == j) return; + + if (size == 4) { + zpl_swap(zpl_u32, *cast(zpl_u32 *) i, *cast(zpl_u32 *) j); + } else if (size == 8) { + zpl_swap(zpl_u64, *cast(zpl_u64 *) i, *cast(zpl_u64 *) j); + } else if (size < 8) { + zpl_u8 *a = cast(zpl_u8 *) i; + zpl_u8 *b = cast(zpl_u8 *) j; + if (a != b) { + while (size--) { zpl_swap(zpl_u8, *a++, *b++); } + } + } else { + char buffer[256]; + + while (size > zpl_size_of(buffer)) { + zpl_memswap(i, j, zpl_size_of(buffer)); + i = zpl_pointer_add(i, zpl_size_of(buffer)); + j = zpl_pointer_add(j, zpl_size_of(buffer)); + size -= zpl_size_of(buffer); + } + + zpl_memcopy(buffer, i, size); + zpl_memcopy(i, j, size); + zpl_memcopy(j, buffer, size); + } + } + + void const *zpl_memchr(void const *data, zpl_u8 c, zpl_isize n) { + zpl_u8 const *s = cast(zpl_u8 const *) data; + while ((cast(zpl_uintptr) s & (sizeof(zpl_usize) - 1)) && n && *s != c) { + s++; + n--; + } + if (n && *s != c) { + zpl_isize const *w; + zpl_isize k = ZPL__ONES * c; + w = cast(zpl_isize const *) s; + while (n >= zpl_size_of(zpl_isize) && !ZPL__HAS_ZERO(*w ^ k)) { + w++; + n -= zpl_size_of(zpl_isize); + } + s = cast(zpl_u8 const *) w; + while (n && *s != c) { + s++; + n--; + } + } + + return n ? cast(void const *) s : NULL; + } + + void const *zpl_memrchr(void const *data, zpl_u8 c, zpl_isize n) { + zpl_u8 const *s = cast(zpl_u8 const *) data; + while (n--) { + if (s[n] == c) return cast(void const *)(s + n); + } + return NULL; + } + + void *zpl_memcopy(void *dest, void const *source, zpl_isize n) { + if (dest == NULL) { return NULL; } + + return memcpy(dest, source, n); + + // TODO: Re-work the whole method + #if 0 + #if defined(_MSC_VER) + __movsb(cast(zpl_u8 *) dest, cast(zpl_u8 *) source, n); + #elif defined(ZPL_CPU_X86) && !defined(ZPL_SYSTEM_EMSCRIPTEN) + zpl_u8 *__dest8 = cast(zpl_u8 *) dest; + zpl_u8 *__source8 = cast(zpl_u8 *) source; + __asm__ __volatile__("rep movsb" : "+D"(__dest8), "+S"(__source8), "+c"(n) : : "memory"); + #elif defined(ZPL_CPU_ARM) + return memcpy(dest, source, n); + #else + zpl_u8 *d = cast(zpl_u8 *) dest; + zpl_u8 const *s = cast(zpl_u8 const *) source; + zpl_u32 w, x; + + for (; cast(zpl_uintptr) s % 4 && n; n--) *d++ = *s++; + + if (cast(zpl_uintptr) d % 4 == 0) { + for (; n >= 16; s += 16, d += 16, n -= 16) { + *cast(zpl_u32 *)(d + 0) = *cast(zpl_u32 *)(s + 0); + *cast(zpl_u32 *)(d + 4) = *cast(zpl_u32 *)(s + 4); + *cast(zpl_u32 *)(d + 8) = *cast(zpl_u32 *)(s + 8); + *cast(zpl_u32 *)(d + 12) = *cast(zpl_u32 *)(s + 12); + } + if (n & 8) { + *cast(zpl_u32 *)(d + 0) = *cast(zpl_u32 *)(s + 0); + *cast(zpl_u32 *)(d + 4) = *cast(zpl_u32 *)(s + 4); + d += 8; + s += 8; + } + if (n & 4) { + *cast(zpl_u32 *)(d + 0) = *cast(zpl_u32 *)(s + 0); + d += 4; + s += 4; + } + if (n & 2) { + *d++ = *s++; + *d++ = *s++; + } + if (n & 1) { *d = *s; } + return dest; + } + + if (n >= 32) { + #if __BYTE_ORDER == __BIG_ENDIAN + #define LS << + #define RS >> + #else + #define LS >> + #define RS << + #endif + switch (cast(zpl_uintptr) d % 4) { + case 1: { + w = *cast(zpl_u32 *) s; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + n -= 3; + while (n > 16) { + x = *cast(zpl_u32 *)(s + 1); + *cast(zpl_u32 *)(d + 0) = (w LS 24) | (x RS 8); + w = *cast(zpl_u32 *)(s + 5); + *cast(zpl_u32 *)(d + 4) = (x LS 24) | (w RS 8); + x = *cast(zpl_u32 *)(s + 9); + *cast(zpl_u32 *)(d + 8) = (w LS 24) | (x RS 8); + w = *cast(zpl_u32 *)(s + 13); + *cast(zpl_u32 *)(d + 12) = (x LS 24) | (w RS 8); + + s += 16; + d += 16; + n -= 16; + } + } break; + case 2: { + w = *cast(zpl_u32 *) s; + *d++ = *s++; + *d++ = *s++; + n -= 2; + while (n > 17) { + x = *cast(zpl_u32 *)(s + 2); + *cast(zpl_u32 *)(d + 0) = (w LS 16) | (x RS 16); + w = *cast(zpl_u32 *)(s + 6); + *cast(zpl_u32 *)(d + 4) = (x LS 16) | (w RS 16); + x = *cast(zpl_u32 *)(s + 10); + *cast(zpl_u32 *)(d + 8) = (w LS 16) | (x RS 16); + w = *cast(zpl_u32 *)(s + 14); + *cast(zpl_u32 *)(d + 12) = (x LS 16) | (w RS 16); + + s += 16; + d += 16; + n -= 16; + } + } break; + case 3: { + w = *cast(zpl_u32 *) s; + *d++ = *s++; + n -= 1; + while (n > 18) { + x = *cast(zpl_u32 *)(s + 3); + *cast(zpl_u32 *)(d + 0) = (w LS 8) | (x RS 24); + w = *cast(zpl_u32 *)(s + 7); + *cast(zpl_u32 *)(d + 4) = (x LS 8) | (w RS 24); + x = *cast(zpl_u32 *)(s + 11); + *cast(zpl_u32 *)(d + 8) = (w LS 8) | (x RS 24); + w = *cast(zpl_u32 *)(s + 15); + *cast(zpl_u32 *)(d + 12) = (x LS 8) | (w RS 24); + + s += 16; + d += 16; + n -= 16; + } + } break; + default: break; // NOTE: Do nowt! + } + #undef LS + #undef RS + if (n & 16) { + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + } + if (n & 8) { + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + } + if (n & 4) { + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + } + if (n & 2) { + *d++ = *s++; + *d++ = *s++; + } + if (n & 1) { *d = *s; } + } + + #endif + #endif + + return dest; + } + + ZPL_END_C_DECLS + // file: source/essentials/memory_custom.c + + + #ifndef _IOSC11_SOURCE + #define _IOSC11_SOURCE + #endif + + #include + + #if defined(ZPL_SYSTEM_WINDOWS) + # include + #endif + + // include errno.h for MinGW + #if defined(ZPL_COMPILER_GCC) || (defined(ZPL_COMPILER_TINYC) && defined(ZPL_SYSTEM_WINDOWS)) + # include + #endif + + #if defined(ZPL_COMPILER_MINGW) + # ifdef __MINGW32__ + # define _aligned_malloc __mingw_aligned_malloc + # define _aligned_free __mingw_aligned_free + # endif //MINGW + #endif + + ZPL_BEGIN_C_DECLS + + char *zpl_alloc_str(zpl_allocator a, char const *str) { + return zpl_alloc_str_len(a, str, zpl__strlen(str)); + } + + //////////////////////////////////////////////////////////////// + // + // Custom Allocation + // + // + + // + // Heap Allocator + // + + #define ZPL_HEAP_STATS_MAGIC 0xDEADC0DE + + typedef struct zpl__heap_stats { + zpl_u32 magic; + zpl_isize used_memory; + zpl_isize alloc_count; + } zpl__heap_stats; + + zpl_global zpl__heap_stats zpl__heap_stats_info; + + void zpl_heap_stats_init(void) { + zpl_zero_item(&zpl__heap_stats_info); + zpl__heap_stats_info.magic = ZPL_HEAP_STATS_MAGIC; + } + zpl_isize zpl_heap_stats_used_memory(void) { + ZPL_ASSERT_MSG(zpl__heap_stats_info.magic == ZPL_HEAP_STATS_MAGIC, "zpl_heap_stats is not initialised yet, call zpl_heap_stats_init first!"); + return zpl__heap_stats_info.used_memory; + } + zpl_isize zpl_heap_stats_alloc_count(void) { + ZPL_ASSERT_MSG(zpl__heap_stats_info.magic == ZPL_HEAP_STATS_MAGIC, "zpl_heap_stats is not initialised yet, call zpl_heap_stats_init first!"); + return zpl__heap_stats_info.alloc_count; + } + void zpl_heap_stats_check(void) { + ZPL_ASSERT_MSG(zpl__heap_stats_info.magic == ZPL_HEAP_STATS_MAGIC, "zpl_heap_stats is not initialised yet, call zpl_heap_stats_init first!"); + ZPL_ASSERT(zpl__heap_stats_info.used_memory == 0); + ZPL_ASSERT(zpl__heap_stats_info.alloc_count == 0); + } + + typedef struct zpl__heap_alloc_info { + zpl_isize size; + void *physical_start; + } zpl__heap_alloc_info; + + ZPL_ALLOCATOR_PROC(zpl_heap_allocator_proc) { + void *ptr = NULL; + zpl_unused(allocator_data); + zpl_unused(old_size); + if (!alignment) alignment = ZPL_DEFAULT_MEMORY_ALIGNMENT; + + # ifdef ZPL_HEAP_ANALYSIS + zpl_isize alloc_info_size = zpl_size_of(zpl__heap_alloc_info); + zpl_isize alloc_info_remainder = (alloc_info_size % alignment); + zpl_isize track_size = zpl_max(alloc_info_size, alignment) + alloc_info_remainder; + switch (type) { + case ZPL_ALLOCATION_FREE: { + if (!old_memory) break; + zpl__heap_alloc_info *alloc_info = cast(zpl__heap_alloc_info *)old_memory - 1; + zpl__heap_stats_info.used_memory -= alloc_info->size; + zpl__heap_stats_info.alloc_count--; + old_memory = alloc_info->physical_start; + } break; + case ZPL_ALLOCATION_ALLOC: { + size += track_size; + } break; + default: break; + } + # endif + + switch (type) { + #if defined(ZPL_COMPILER_MSVC) || (defined(ZPL_COMPILER_GCC) && defined(ZPL_SYSTEM_WINDOWS)) || (defined(ZPL_COMPILER_TINYC) && defined(ZPL_SYSTEM_WINDOWS)) + case ZPL_ALLOCATION_ALLOC: + ptr = _aligned_malloc(size, alignment); + if (flags & ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) zpl_zero_size(ptr, size); + break; + case ZPL_ALLOCATION_FREE: _aligned_free(old_memory); break; + case ZPL_ALLOCATION_RESIZE: { + zpl_allocator a = zpl_heap_allocator(); + ptr = zpl_default_resize_align(a, old_memory, old_size, size, alignment); + } break; + + #elif defined(ZPL_SYSTEM_LINUX) && !defined(ZPL_CPU_ARM) && !defined(ZPL_COMPILER_TINYC) + case ZPL_ALLOCATION_ALLOC: { + ptr = aligned_alloc(alignment, (size + alignment - 1) & ~(alignment - 1)); + + if (flags & ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) { zpl_zero_size(ptr, size); } + } break; + + case ZPL_ALLOCATION_FREE: { + free(old_memory); + } break; + + case ZPL_ALLOCATION_RESIZE: { + zpl_allocator a = zpl_heap_allocator(); + ptr = zpl_default_resize_align(a, old_memory, old_size, size, alignment); + } break; + #else + case ZPL_ALLOCATION_ALLOC: { + posix_memalign(&ptr, alignment, size); + + if (flags & ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) { zpl_zero_size(ptr, size); } + } break; + + case ZPL_ALLOCATION_FREE: { + free(old_memory); + } break; + + case ZPL_ALLOCATION_RESIZE: { + zpl_allocator a = zpl_heap_allocator( ); + ptr = zpl_default_resize_align(a, old_memory, old_size, size, alignment); + } break; + #endif + + case ZPL_ALLOCATION_FREE_ALL: break; + } + + # ifdef ZPL_HEAP_ANALYSIS + if (type == ZPL_ALLOCATION_ALLOC) { + zpl__heap_alloc_info *alloc_info = cast(zpl__heap_alloc_info *)(cast(char *)ptr + alloc_info_remainder); + zpl_zero_item(alloc_info); + alloc_info->size = size - track_size; + alloc_info->physical_start = ptr; + ptr = cast(void*)(alloc_info + 1); + zpl__heap_stats_info.used_memory += alloc_info->size; + zpl__heap_stats_info.alloc_count++; + } + # endif + + return ptr; + } + + // + // Arena Allocator + // + + ZPL_ALLOCATOR_PROC(zpl_arena_allocator_proc) { + zpl_arena *arena = cast(zpl_arena *) allocator_data; + void *ptr = NULL; + + zpl_unused(old_size); + + switch (type) { + case ZPL_ALLOCATION_ALLOC: { + void *end = zpl_pointer_add(arena->physical_start, arena->total_allocated); + zpl_isize total_size = zpl_align_forward_i64(size, alignment); + + // NOTE: Out of memory + if (arena->total_allocated + total_size > cast(zpl_isize) arena->total_size) { + return NULL; + } + + ptr = zpl_align_forward(end, alignment); + arena->total_allocated += total_size; + if (flags & ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) zpl_zero_size(ptr, size); + } break; + + case ZPL_ALLOCATION_FREE: + // NOTE: Free all at once + // Use Temp_Arena_Memory if you want to free a block + break; + + case ZPL_ALLOCATION_FREE_ALL: arena->total_allocated = 0; break; + + case ZPL_ALLOCATION_RESIZE: { + // TODO: Check if ptr is on top of stack and just extend + zpl_allocator a = zpl_arena_allocator(arena); + ptr = zpl_default_resize_align(a, old_memory, old_size, size, alignment); + } break; + } + return ptr; + } + + // + // Pool Allocator + // + + void zpl_pool_init_align(zpl_pool *pool, zpl_allocator backing, zpl_isize num_blocks, zpl_isize block_size, zpl_isize block_align) { + zpl_isize actual_block_size, pool_size, block_index; + void *data, *curr; + zpl_uintptr *end; + + zpl_zero_item(pool); + + pool->backing = backing; + pool->block_size = block_size; + pool->block_align = block_align; + pool->num_blocks = num_blocks; + + actual_block_size = block_size + block_align; + pool_size = num_blocks * actual_block_size; + + data = zpl_alloc_align(backing, pool_size, block_align); + + // NOTE: Init intrusive freelist + curr = data; + for (block_index = 0; block_index < num_blocks - 1; block_index++) { + zpl_uintptr *next = cast(zpl_uintptr *) curr; + *next = cast(zpl_uintptr) curr + actual_block_size; + curr = zpl_pointer_add(curr, actual_block_size); + } + + end = cast(zpl_uintptr *) curr; + *end = cast(zpl_uintptr) NULL; + + pool->physical_start = data; + pool->free_list = data; + } + + ZPL_ALLOCATOR_PROC(zpl_pool_allocator_proc) { + zpl_pool *pool = cast(zpl_pool *) allocator_data; + void *ptr = NULL; + + zpl_unused(old_size); + + switch (type) { + case ZPL_ALLOCATION_ALLOC: { + zpl_uintptr next_free; + ZPL_ASSERT(size == pool->block_size); + ZPL_ASSERT(alignment == pool->block_align); + ZPL_ASSERT(pool->free_list != NULL); + + next_free = *cast(zpl_uintptr *) pool->free_list; + ptr = pool->free_list; + pool->free_list = cast(void *) next_free; + pool->total_size += pool->block_size; + if (flags & ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) zpl_zero_size(ptr, size); + } break; + + case ZPL_ALLOCATION_FREE: { + zpl_uintptr *next; + if (old_memory == NULL) return NULL; + + next = cast(zpl_uintptr *) old_memory; + *next = cast(zpl_uintptr) pool->free_list; + pool->free_list = old_memory; + pool->total_size -= pool->block_size; + } break; + + case ZPL_ALLOCATION_FREE_ALL: { + zpl_isize actual_block_size, block_index; + void *curr; + zpl_uintptr *end; + + actual_block_size = pool->block_size + pool->block_align; + pool->total_size = 0; + + // NOTE: Init intrusive freelist + curr = pool->physical_start; + for (block_index = 0; block_index < pool->num_blocks - 1; block_index++) { + zpl_uintptr *next = cast(zpl_uintptr *) curr; + *next = cast(zpl_uintptr) curr + actual_block_size; + curr = zpl_pointer_add(curr, actual_block_size); + } + + end = cast(zpl_uintptr *) curr; + *end = cast(zpl_uintptr) NULL; + pool->free_list = pool->physical_start; + } break; + + case ZPL_ALLOCATION_RESIZE: + // NOTE: Cannot resize + ZPL_PANIC("You cannot resize something allocated by with a pool."); + break; + } + + return ptr; + } + + + // + // Scratch Memory Allocator + // + + void zpl_scratch_memory_init(zpl_scratch_memory *s, void *start, zpl_isize size) { + s->physical_start = start; + s->total_size = size; + s->alloc_point = start; + s->free_point = start; + } + + zpl_b32 zpl_scratch_memory_is_in_use(zpl_scratch_memory *s, void *ptr) { + if (s->free_point == s->alloc_point) return false; + if (s->alloc_point > s->free_point) return ptr >= s->free_point && ptr < s->alloc_point; + return ptr >= s->free_point || ptr < s->alloc_point; + } + + zpl_allocator zpl_scratch_allocator(zpl_scratch_memory *s) { + zpl_allocator a; + a.proc = zpl_scratch_allocator_proc; + a.data = s; + return a; + } + + ZPL_ALLOCATOR_PROC(zpl_scratch_allocator_proc) { + zpl_scratch_memory *s = cast(zpl_scratch_memory *) allocator_data; + void *ptr = NULL; + ZPL_ASSERT_NOT_NULL(s); + + switch (type) { + case ZPL_ALLOCATION_ALLOC: { + void *pt = s->alloc_point; + zpl_allocation_header_ev *header = cast(zpl_allocation_header_ev *) pt; + void *data = zpl_align_forward(header + 1, alignment); + void *end = zpl_pointer_add(s->physical_start, s->total_size); + + ZPL_ASSERT(alignment % 4 == 0); + size = ((size + 3) / 4) * 4; + pt = zpl_pointer_add(pt, size); + + // NOTE: Wrap around + if (pt > end) { + header->size = zpl_pointer_diff(header, end) | ZPL_ISIZE_HIGH_BIT; + pt = s->physical_start; + header = cast(zpl_allocation_header_ev *) pt; + data = zpl_align_forward(header + 1, alignment); + pt = zpl_pointer_add(pt, size); + } + + if (!zpl_scratch_memory_is_in_use(s, pt)) { + zpl_allocation_header_fill(header, pt, zpl_pointer_diff(header, pt)); + s->alloc_point = cast(zpl_u8 *) pt; + ptr = data; + } + + if (flags & ZPL_ALLOCATOR_FLAG_CLEAR_TO_ZERO) zpl_zero_size(ptr, size); + } break; + + case ZPL_ALLOCATION_FREE: { + if (old_memory) { + void *end = zpl_pointer_add(s->physical_start, s->total_size); + if (old_memory < s->physical_start || old_memory >= end) { + ZPL_ASSERT(false); + } else { + // NOTE: Mark as free + zpl_allocation_header_ev *h = zpl_allocation_header(old_memory); + ZPL_ASSERT((h->size & ZPL_ISIZE_HIGH_BIT) == 0); + h->size = h->size | ZPL_ISIZE_HIGH_BIT; + + while (s->free_point != s->alloc_point) { + zpl_allocation_header_ev *header = cast(zpl_allocation_header_ev *) s->free_point; + if ((header->size & ZPL_ISIZE_HIGH_BIT) == 0) break; + + s->free_point = zpl_pointer_add(s->free_point, h->size & (~ZPL_ISIZE_HIGH_BIT)); + if (s->free_point == end) s->free_point = s->physical_start; + } + } + } + } break; + + case ZPL_ALLOCATION_FREE_ALL: + s->alloc_point = s->physical_start; + s->free_point = s->physical_start; + break; + + case ZPL_ALLOCATION_RESIZE: + ptr = zpl_default_resize_align(zpl_scratch_allocator(s), old_memory, old_size, size, alignment); + break; + } + + return ptr; + } + + // + // Stack Memory Allocator + // + ZPL_ALLOCATOR_PROC(zpl_stack_allocator_proc) { + zpl_stack_memory *s = cast(zpl_stack_memory *) allocator_data; + void *ptr = NULL; + ZPL_ASSERT_NOT_NULL(s); + zpl_unused(old_size); + zpl_unused(flags); + + switch (type) { + case ZPL_ALLOCATION_ALLOC: { + size += ZPL_STACK_ALLOC_OFFSET; + zpl_u64 alloc_offset = s->allocated; + + void *curr = + cast(zpl_u64 *) zpl_align_forward(cast(zpl_u64 *) zpl_pointer_add(s->physical_start, s->allocated), alignment); + + if (cast(zpl_u64 *) zpl_pointer_add(curr, size) > cast(zpl_u64 *) zpl_pointer_add(s->physical_start, s->total_size)) { + if (s->backing.proc) { + void *old_start = s->physical_start; + s->physical_start = + zpl_resize_align(s->backing, s->physical_start, s->total_size, s->total_size + size, alignment); + curr = cast(zpl_u64 *) + zpl_align_forward(cast(zpl_u64 *) zpl_pointer_add(s->physical_start, s->allocated), alignment); + s->total_size = zpl_pointer_diff(old_start, s->physical_start); + } else { + ZPL_PANIC("Can not resize stack's memory! Allocator not defined!"); + } + } + + s->allocated = zpl_pointer_diff(s->physical_start, curr) + size; + + *(zpl_u64 *)curr = alloc_offset; + curr = zpl_pointer_add(curr, ZPL_STACK_ALLOC_OFFSET); + + ptr = curr; + } break; + + case ZPL_ALLOCATION_FREE: { + if (old_memory) { + void *curr = old_memory; + curr = zpl_pointer_sub(curr, ZPL_STACK_ALLOC_OFFSET); + + zpl_u64 alloc_offset = *(zpl_u64 *)curr; + s->allocated = (zpl_usize)alloc_offset; + } + } break; + + case ZPL_ALLOCATION_FREE_ALL: { + s->allocated = 0; + } break; + + case ZPL_ALLOCATION_RESIZE: { + ZPL_PANIC("You cannot resize something allocated by a stack."); + } break; + } + return ptr; + } + + ZPL_END_C_DECLS +# if defined(ZPL_MODULE_CORE) + // file: source/core/memory_virtual.c + + //////////////////////////////////////////////////////////////// + // + // Virtual Memory + // + // + + ZPL_BEGIN_C_DECLS + + zpl_virtual_memory zpl_vm(void *data, zpl_isize size) { + zpl_virtual_memory vm; + vm.data = data; + vm.size = size; + return vm; + } + + #if defined(ZPL_SYSTEM_WINDOWS) + zpl_virtual_memory zpl_vm_alloc(void *addr, zpl_isize size) { + zpl_virtual_memory vm; + ZPL_ASSERT(size > 0); + vm.data = VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + vm.size = size; + return vm; + } + + zpl_b32 zpl_vm_free(zpl_virtual_memory vm) { + MEMORY_BASIC_INFORMATION info; + while (vm.size > 0) { + if (VirtualQuery(vm.data, &info, zpl_size_of(info)) == 0) return false; + if (info.BaseAddress != vm.data || info.AllocationBase != vm.data || info.State != MEM_COMMIT || + info.RegionSize > cast(zpl_usize) vm.size) { + return false; + } + if (VirtualFree(vm.data, 0, MEM_RELEASE) == 0) return false; + vm.data = zpl_pointer_add(vm.data, info.RegionSize); + vm.size -= info.RegionSize; + } + return true; + } + + zpl_virtual_memory zpl_vm_trim(zpl_virtual_memory vm, zpl_isize lead_size, zpl_isize size) { + zpl_virtual_memory new_vm = { 0 }; + void *ptr; + ZPL_ASSERT(vm.size >= lead_size + size); + + ptr = zpl_pointer_add(vm.data, lead_size); + + zpl_vm_free(vm); + new_vm = zpl_vm_alloc(ptr, size); + if (new_vm.data == ptr) return new_vm; + if (new_vm.data) zpl_vm_free(new_vm); + return new_vm; + } + + zpl_b32 zpl_vm_purge(zpl_virtual_memory vm) { + VirtualAlloc(vm.data, vm.size, MEM_RESET, PAGE_READWRITE); + // NOTE: Can this really fail? + return true; + } + + zpl_isize zpl_virtual_memory_page_size(zpl_isize *alignment_out) { + SYSTEM_INFO info; + GetSystemInfo(&info); + if (alignment_out) *alignment_out = info.dwAllocationGranularity; + return info.dwPageSize; + } + + #else + # include + + # ifndef MAP_ANONYMOUS + # define MAP_ANONYMOUS MAP_ANON + # endif + + zpl_virtual_memory zpl_vm_alloc(void *addr, zpl_isize size) { + zpl_virtual_memory vm; + ZPL_ASSERT(size > 0); + vm.data = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + vm.size = size; + return vm; + } + + zpl_b32 zpl_vm_free(zpl_virtual_memory vm) { + munmap(vm.data, vm.size); + return true; + } + + zpl_virtual_memory zpl_vm_trim(zpl_virtual_memory vm, zpl_isize lead_size, zpl_isize size) { + void *ptr; + zpl_isize trail_size; + ZPL_ASSERT(vm.size >= lead_size + size); + + ptr = zpl_pointer_add(vm.data, lead_size); + trail_size = vm.size - lead_size - size; + + if (lead_size != 0) zpl_vm_free(zpl_vm(vm.data, lead_size)); + if (trail_size != 0) zpl_vm_free(zpl_vm(ptr, trail_size)); + return zpl_vm(ptr, size); + } + + zpl_b32 zpl_vm_purge(zpl_virtual_memory vm) { + int err = madvise(vm.data, vm.size, MADV_DONTNEED); + return err != 0; + } + + zpl_isize zpl_virtual_memory_page_size(zpl_isize *alignment_out) { + // TODO: Is this always true? + zpl_isize result = cast(zpl_isize) sysconf(_SC_PAGE_SIZE); + if (alignment_out) *alignment_out = result; + return result; + } + + #endif + + ZPL_END_C_DECLS + // file: source/core/string.c + + //////////////////////////////////////////////////////////////// + // + // Char things + // + // + + ZPL_BEGIN_C_DECLS + + zpl_internal zpl_isize zpl__scan_zpl_i64(const char *text, zpl_i32 base, zpl_i64 *value) { + const char *text_begin = text; + zpl_i64 result = 0; + zpl_b32 negative = false; + + if (*text == '-') { + negative = true; + text++; + } + + if (base == 16 && zpl_strncmp(text, "0x", 2) == 0) text += 2; + + for (;;) { + zpl_i64 v; + if (zpl_char_is_digit(*text)) + v = *text - '0'; + else if (base == 16 && zpl_char_is_hex_digit(*text)) + v = zpl_hex_digit_to_int(*text); + else + break; + + result *= base; + result += v; + text++; + } + + if (value) { + if (negative) result = -result; + *value = result; + } + + return (text - text_begin); + } + + zpl_internal zpl_isize zpl__scan_zpl_u64(const char *text, zpl_i32 base, zpl_u64 *value) { + const char *text_begin = text; + zpl_u64 result = 0; + + if (base == 16 && zpl_strncmp(text, "0x", 2) == 0) text += 2; + + for (;;) { + zpl_u64 v; + if (zpl_char_is_digit(*text)) + v = *text - '0'; + else if (base == 16 && zpl_char_is_hex_digit(*text)) + v = zpl_hex_digit_to_int(*text); + else { + break; + } + + result *= base; + result += v; + text++; + } + + if (value) *value = result; + + return (text - text_begin); + } + + // TODO: Make better + zpl_u64 zpl_str_to_u64(const char *str, char **end_ptr, zpl_i32 base) { + zpl_isize len; + zpl_u64 value = 0; + + if (!base) { + if ((zpl_strlen(str) > 2) && (zpl_strncmp(str, "0x", 2) == 0)) + base = 16; + else + base = 10; + } + + len = zpl__scan_zpl_u64(str, base, &value); + if (end_ptr) *end_ptr = (char *)str + len; + return value; + } + + zpl_i64 zpl_str_to_i64(const char *str, char **end_ptr, zpl_i32 base) { + zpl_isize len; + zpl_i64 value; + + if (!base) { + if ((zpl_strlen(str) > 2) && (zpl_strncmp(str, "0x", 2) == 0)) + base = 16; + else + base = 10; + } + + len = zpl__scan_zpl_i64(str, base, &value); + if (end_ptr) *end_ptr = (char *)str + len; + return value; + } + + // TODO: Are these good enough for characters? + zpl_global const char zpl__num_to_char_table[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "@$"; + + void zpl_i64_to_str(zpl_i64 value, char *string, zpl_i32 base) { + char *buf = string; + zpl_b32 negative = false; + zpl_u64 v; + + if (value < 0) { + negative = true; + value = -value; + } + + v = cast(zpl_u64) value; + if (v != 0) { + while (v > 0) { + *buf++ = zpl__num_to_char_table[v % base]; + v /= base; + } + } else { + *buf++ = '0'; + } + if (negative) *buf++ = '-'; + *buf = '\0'; + zpl_strrev(string); + } + + void zpl_u64_to_str(zpl_u64 value, char *string, zpl_i32 base) { + char *buf = string; + + if (value) { + while (value > 0) { + *buf++ = zpl__num_to_char_table[value % base]; + value /= base; + } + } else { + *buf++ = '0'; + } + *buf = '\0'; + + zpl_strrev(string); + } + + zpl_f64 zpl_str_to_f64(const char *str, char **end_ptr) { + zpl_f64 result, value, sign, scale; + zpl_i32 frac; + + while (zpl_char_is_space(*str)) { str++; } + + sign = 1.0; + if (*str == '-') { + sign = -1.0; + str++; + } else if (*str == '+') { + str++; + } + + for (value = 0.0; zpl_char_is_digit(*str); str++) { value = value * 10.0 + (*str - '0'); } + + if (*str == '.') { + zpl_f64 pow10 = 10.0; + str++; + while (zpl_char_is_digit(*str)) { + value += (*str - '0') / pow10; + pow10 *= 10.0; + str++; + } + } + + frac = 0; + scale = 1.0; + if ((*str == 'e') || (*str == 'E')) { + zpl_u32 exp; + + str++; + if (*str == '-') { + frac = 1; + str++; + } else if (*str == '+') { + str++; + } + + for (exp = 0; zpl_char_is_digit(*str); str++) { exp = exp * 10 + (*str - '0'); } + if (exp > 308) exp = 308; + + while (exp >= 50) { + scale *= 1e50; + exp -= 50; + } + while (exp >= 8) { + scale *= 1e8; + exp -= 8; + } + while (exp > 0) { + scale *= 10.0; + exp -= 1; + } + } + + result = sign * (frac ? (value / scale) : (value * scale)); + + if (end_ptr) *end_ptr = cast(char *) str; + + return result; + } + + + + //////////////////////////////////////////////////////////////// + // + // Windows UTF-8 Handling + // + // + + zpl_u16 *zpl_utf8_to_ucs2(zpl_u16 *buffer, zpl_isize len, zpl_u8 const *str) { + zpl_rune c; + zpl_isize i = 0; + len--; + while (*str) { + if (i >= len) return NULL; + if (!(*str & 0x80)) { + buffer[i++] = *str++; + } else if ((*str & 0xe0) == 0xc0) { + if (*str < 0xc2) return NULL; + c = (*str++ & 0x1f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + buffer[i++] = cast(zpl_u16)(c + (*str++ & 0x3f)); + } else if ((*str & 0xf0) == 0xe0) { + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL; + if (*str == 0xed && str[1] > 0x9f) // str[1] < 0x80 is checked below + return NULL; + c = (*str++ & 0x0f) << 12; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + buffer[i++] = cast(zpl_u16)(c + (*str++ & 0x3f)); + } else if ((*str & 0xf8) == 0xf0) { + if (*str > 0xf4) return NULL; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return NULL; + if (*str == 0xf4 && str[1] > 0x8f) // str[1] < 0x80 is checked below + return NULL; + c = (*str++ & 0x07) << 18; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 12; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f); + // UTF-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xfffff800) == 0xd800) return NULL; + if (c >= 0x10000) { + c -= 0x10000; + if (i + 2 > len) return NULL; + buffer[i++] = 0xd800 | (0x3ff & (c >> 10)); + buffer[i++] = 0xdc00 | (0x3ff & (c)); + } + } else { + return NULL; + } + } + buffer[i] = 0; + return buffer; + } + + zpl_u8 *zpl_ucs2_to_utf8(zpl_u8 *buffer, zpl_isize len, zpl_u16 const *str) { + zpl_isize i = 0; + len--; + while (*str) { + if (*str < 0x80) { + if (i + 1 > len) return NULL; + buffer[i++] = (char)*str++; + } else if (*str < 0x800) { + if (i + 2 > len) return NULL; + buffer[i++] = cast(char)(0xc0 + (*str >> 6)); + buffer[i++] = cast(char)(0x80 + (*str & 0x3f)); + str += 1; + } else if (*str >= 0xd800 && *str < 0xdc00) { + zpl_rune c; + if (i + 4 > len) return NULL; + c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000; + buffer[i++] = cast(char)(0xf0 + (c >> 18)); + buffer[i++] = cast(char)(0x80 + ((c >> 12) & 0x3f)); + buffer[i++] = cast(char)(0x80 + ((c >> 6) & 0x3f)); + buffer[i++] = cast(char)(0x80 + ((c)&0x3f)); + str += 2; + } else if (*str >= 0xdc00 && *str < 0xe000) { + return NULL; + } else { + if (i + 3 > len) return NULL; + buffer[i++] = 0xe0 + (*str >> 12); + buffer[i++] = 0x80 + ((*str >> 6) & 0x3f); + buffer[i++] = 0x80 + ((*str) & 0x3f); + str += 1; + } + } + buffer[i] = 0; + return buffer; + } + + zpl_u16 *zpl_utf8_to_ucs2_buf(zpl_u8 const *str) { // NOTE: Uses locally persisting buffer + zpl_local_persist zpl_u16 buf[4096]; + return zpl_utf8_to_ucs2(buf, zpl_count_of(buf), str); + } + + zpl_u8 *zpl_ucs2_to_utf8_buf(zpl_u16 const *str) { // NOTE: Uses locally persisting buffer + zpl_local_persist zpl_u8 buf[4096]; + return zpl_ucs2_to_utf8(buf, zpl_count_of(buf), str); + } + + zpl_global zpl_u8 const zpl__utf8_first[256] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6F + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7F + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8F + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9F + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xA0-0xAF + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xB0-0xBF + 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xC0-0xCF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xD0-0xDF + 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xE0-0xEF + 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xF0-0xFF + }; + + + typedef struct zpl_utf8_accept_range { + zpl_u8 lo, hi; + } zpl_utf8_accept_range; + + zpl_global zpl_utf8_accept_range const zpl__utf8_accept_ranges[] = { + { 0x80, 0xbf }, { 0xa0, 0xbf }, { 0x80, 0x9f }, { 0x90, 0xbf }, { 0x80, 0x8f }, + }; + + zpl_isize zpl_utf8_decode(zpl_u8 const *str, zpl_isize str_len, zpl_rune *codepoint_out) { + + zpl_isize width = 0; + zpl_rune codepoint = ZPL_RUNE_INVALID; + + if (str_len > 0) { + zpl_u8 s0 = str[0]; + zpl_u8 x = zpl__utf8_first[s0], sz; + zpl_u8 b1, b2, b3; + zpl_utf8_accept_range accept; + if (x >= 0xf0) { + zpl_rune mask = (cast(zpl_rune) x << 31) >> 31; + codepoint = (cast(zpl_rune) s0 & (~mask)) | (ZPL_RUNE_INVALID & mask); + width = 1; + goto end; + } + if (s0 < 0x80) { + codepoint = s0; + width = 1; + goto end; + } + + sz = x & 7; + accept = zpl__utf8_accept_ranges[x >> 4]; + if (str_len < sz) goto invalid_codepoint; + + b1 = str[1]; + if (b1 < accept.lo || accept.hi < b1) goto invalid_codepoint; + + if (sz == 2) { + codepoint = (cast(zpl_rune) s0 & 0x1f) << 6 | (cast(zpl_rune) b1 & 0x3f); + width = 2; + goto end; + } + + b2 = str[2]; + if (!zpl_is_between(b2, 0x80, 0xbf)) goto invalid_codepoint; + + if (sz == 3) { + codepoint = (cast(zpl_rune) s0 & 0x1f) << 12 | (cast(zpl_rune) b1 & 0x3f) << 6 | (cast(zpl_rune) b2 & 0x3f); + width = 3; + goto end; + } + + b3 = str[3]; + if (!zpl_is_between(b3, 0x80, 0xbf)) goto invalid_codepoint; + + codepoint = (cast(zpl_rune) s0 & 0x07) << 18 | (cast(zpl_rune) b1 & 0x3f) << 12 | (cast(zpl_rune) b2 & 0x3f) << 6 | + (cast(zpl_rune) b3 & 0x3f); + width = 4; + goto end; + + invalid_codepoint: + codepoint = ZPL_RUNE_INVALID; + width = 1; + } + + end: + if (codepoint_out) *codepoint_out = codepoint; + return width; + } + + zpl_isize zpl_utf8_codepoint_size(zpl_u8 const *str, zpl_isize str_len) { + zpl_isize i = 0; + for (; i < str_len && str[i]; i++) { + if ((str[i] & 0xc0) != 0x80) break; + } + return i + 1; + } + + zpl_isize zpl_utf8_encode_rune(zpl_u8 buf[4], zpl_rune r) { + zpl_u32 i = cast(zpl_u32) r; + zpl_u8 mask = 0x3f; + if (i <= (1 << 7) - 1) { + buf[0] = cast(zpl_u8) r; + return 1; + } + if (i <= (1 << 11) - 1) { + buf[0] = 0xc0 | cast(zpl_u8)(r >> 6); + buf[1] = 0x80 | (cast(zpl_u8)(r) & mask); + return 2; + } + + // Invalid or Surrogate range + if (i > ZPL_RUNE_MAX || zpl_is_between(i, 0xd800, 0xdfff)) { + r = ZPL_RUNE_INVALID; + + buf[0] = 0xe0 | cast(zpl_u8)(r >> 12); + buf[1] = 0x80 | (cast(zpl_u8)(r >> 6) & mask); + buf[2] = 0x80 | (cast(zpl_u8)(r) & mask); + return 3; + } + + if (i <= (1 << 16) - 1) { + buf[0] = 0xe0 | cast(zpl_u8)(r >> 12); + buf[1] = 0x80 | (cast(zpl_u8)(r >> 6) & mask); + buf[2] = 0x80 | (cast(zpl_u8)(r) & mask); + return 3; + } + + buf[0] = 0xf0 | cast(zpl_u8)(r >> 18); + buf[1] = 0x80 | (cast(zpl_u8)(r >> 12) & mask); + buf[2] = 0x80 | (cast(zpl_u8)(r >> 6) & mask); + buf[3] = 0x80 | (cast(zpl_u8)(r) & mask); + return 4; + } + + ZPL_END_C_DECLS + // file: source/core/stringlib.c + + + ZPL_BEGIN_C_DECLS + + zpl_string zpl_string_make_reserve(zpl_allocator a, zpl_isize capacity) { + zpl_isize header_size = zpl_size_of(zpl_string_header); + void *ptr = zpl_alloc(a, header_size + capacity + 1); + + zpl_string str; + zpl_string_header *header; + + if (ptr == NULL) return NULL; + zpl_zero_size(ptr, header_size + capacity + 1); + + str = cast(char *) ptr + header_size; + header = ZPL_STRING_HEADER(str); + header->allocator = a; + header->length = 0; + header->capacity = capacity; + str[capacity] = '\0'; + + return str; + } + + + zpl_string zpl_string_make_length(zpl_allocator a, void const *init_str, zpl_isize num_bytes) { + zpl_isize header_size = zpl_size_of(zpl_string_header); + void *ptr = zpl_alloc(a, header_size + num_bytes + 1); + + zpl_string str; + zpl_string_header *header; + + if (ptr == NULL) return NULL; + if (!init_str) zpl_zero_size(ptr, header_size + num_bytes + 1); + + str = cast(char *) ptr + header_size; + header = ZPL_STRING_HEADER(str); + header->allocator = a; + header->length = num_bytes; + header->capacity = num_bytes; + if (num_bytes && init_str) zpl_memcopy(str, init_str, num_bytes); + str[num_bytes] = '\0'; + + return str; + } + + zpl_string zpl_string_sprintf_buf(zpl_allocator a, const char *fmt, ...) { + zpl_local_persist zpl_thread_local char buf[ZPL_PRINTF_MAXLEN] = { 0 }; + va_list va; + va_start(va, fmt); + zpl_snprintf_va(buf, ZPL_PRINTF_MAXLEN, fmt, va); + va_end(va); + + return zpl_string_make(a, buf); + } + + zpl_string zpl_string_sprintf(zpl_allocator a, char *buf, zpl_isize num_bytes, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + zpl_snprintf_va(buf, num_bytes, fmt, va); + va_end(va); + + return zpl_string_make(a, buf); + } + + zpl_string zpl_string_append_length(zpl_string str, void const *other, zpl_isize other_len) { + if (other_len > 0) { + zpl_isize curr_len = zpl_string_length(str); + + str = zpl_string_make_space_for(str, other_len); + if (str == NULL) return NULL; + + zpl_memcopy(str + curr_len, other, other_len); + str[curr_len + other_len] = '\0'; + zpl__set_string_length(str, curr_len + other_len); + } + return str; + } + + ZPL_ALWAYS_INLINE zpl_string zpl_string_appendc(zpl_string str, const char *other) { + return zpl_string_append_length(str, other, zpl_strlen(other)); + } + + ZPL_ALWAYS_INLINE zpl_string zpl_string_join(zpl_allocator a, const char **parts, zpl_isize count, const char *glue) { + zpl_string ret; + zpl_isize i; + + ret = zpl_string_make(a, NULL); + + for (i=0; i= add_len) { + return str; + } else { + zpl_isize new_len, old_size, new_size; + void *ptr, *new_ptr; + zpl_allocator a = ZPL_STRING_HEADER(str)->allocator; + zpl_string_header *header; + + new_len = zpl_string_length(str) + add_len; + ptr = ZPL_STRING_HEADER(str); + old_size = zpl_size_of(zpl_string_header) + zpl_string_length(str) + 1; + new_size = zpl_size_of(zpl_string_header) + new_len + 1; + + new_ptr = zpl_resize(a, ptr, old_size, new_size); + if (new_ptr == NULL) return NULL; + + header = cast(zpl_string_header *) new_ptr; + header->allocator = a; + + str = cast(zpl_string)(header + 1); + zpl__set_string_capacity(str, new_len); + + return str; + } + } + + zpl_isize zpl_string_allocation_size(zpl_string const str) { + zpl_isize cap = zpl_string_capacity(str); + return zpl_size_of(zpl_string_header) + cap; + } + + zpl_b32 zpl_string_are_equal(zpl_string const lhs, zpl_string const rhs) { + zpl_isize lhs_len, rhs_len, i; + lhs_len = zpl_string_length(lhs); + rhs_len = zpl_string_length(rhs); + if (lhs_len != rhs_len) return false; + + for (i = 0; i < lhs_len; i++) { + if (lhs[i] != rhs[i]) return false; + } + + return true; + } + + zpl_string zpl_string_trim(zpl_string str, const char *cut_set) { + char *start, *end, *start_pos, *end_pos; + zpl_isize len; + + start_pos = start = str; + end_pos = end = str + zpl_string_length(str) - 1; + + while (start_pos <= end && zpl_char_first_occurence(cut_set, *start_pos)) start_pos++; + while (end_pos > start_pos && zpl_char_first_occurence(cut_set, *end_pos)) end_pos--; + + len = cast(zpl_isize)((start_pos > end_pos) ? 0 : ((end_pos - start_pos) + 1)); + + if (str != start_pos) zpl_memmove(str, start_pos, len); + str[len] = '\0'; + + zpl__set_string_length(str, len); + + return str; + } + + zpl_string zpl_string_append_rune(zpl_string str, zpl_rune r) { + if (r >= 0) { + zpl_u8 buf[8] = { 0 }; + zpl_isize len = zpl_utf8_encode_rune(buf, r); + return zpl_string_append_length(str, buf, len); + } + + return str; + } + + zpl_string zpl_string_append_fmt(zpl_string str, const char *fmt, ...) { + zpl_isize res; + char buf[ZPL_PRINTF_MAXLEN] = { 0 }; + va_list va; + va_start(va, fmt); + res = zpl_snprintf_va(buf, zpl_count_of(buf) - 1, fmt, va) - 1; + va_end(va); + return zpl_string_append_length(str, buf, res); + } + + ZPL_END_C_DECLS + // file: source/core/file.c + + + //////////////////////////////////////////////////////////////// + // + // File Handling + // + // + #include + + #ifdef ZPL_SYSTEM_MACOS + # include + #endif + + #ifdef ZPL_SYSTEM_CYGWIN + # include + #endif + + #if defined(ZPL_SYSTEM_WINDOWS) && !defined(ZPL_COMPILER_GCC) + #include + #endif + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_SYSTEM_WINDOWS) || defined (ZPL_SYSTEM_CYGWIN) + + zpl_internal wchar_t *zpl__alloc_utf8_to_ucs2(zpl_allocator a, char const *text, zpl_isize *w_len_) { + wchar_t *w_text = NULL; + zpl_isize len = 0, w_len = 0, w_len1 = 0; + if (text == NULL) { + if (w_len_) *w_len_ = w_len; + return NULL; + } + len = zpl_strlen(text); + if (len == 0) { + if (w_len_) *w_len_ = w_len; + return NULL; + } + w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, cast(int) len, NULL, 0); + if (w_len == 0) { + if (w_len_) *w_len_ = w_len; + return NULL; + } + w_text = zpl_alloc_array(a, wchar_t, w_len + 1); + w_len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text, cast(int) len, w_text, cast(int) w_len); + if (w_len1 == 0) { + zpl_free(a, w_text); + if (w_len_) *w_len_ = 0; + return NULL; + } + w_text[w_len] = 0; + if (w_len_) *w_len_ = w_len; + return w_text; + } + + zpl_internal ZPL_FILE_SEEK_PROC(zpl__win32_file_seek) { + LARGE_INTEGER li_offset; + li_offset.QuadPart = offset; + if (!SetFilePointerEx(fd.p, li_offset, &li_offset, whence)) { return false; } + + if (new_offset) *new_offset = li_offset.QuadPart; + return true; + } + + zpl_internal ZPL_FILE_READ_AT_PROC(zpl__win32_file_read) { + zpl_unused(stop_at_newline); + zpl_b32 result = false; + zpl__win32_file_seek(fd, offset, ZPL_SEEK_WHENCE_BEGIN, NULL); + DWORD size_ = cast(DWORD)(size > ZPL_I32_MAX ? ZPL_I32_MAX : size); + DWORD bytes_read_; + if (ReadFile(fd.p, buffer, size_, &bytes_read_, NULL)) { + if (bytes_read) *bytes_read = bytes_read_; + result = true; + } + + return result; + } + + zpl_internal ZPL_FILE_WRITE_AT_PROC(zpl__win32_file_write) { + DWORD size_ = cast(DWORD)(size > ZPL_I32_MAX ? ZPL_I32_MAX : size); + DWORD bytes_written_; + zpl__win32_file_seek(fd, offset, ZPL_SEEK_WHENCE_BEGIN, NULL); + if (WriteFile(fd.p, buffer, size_, &bytes_written_, NULL)) { + if (bytes_written) *bytes_written = bytes_written_; + return true; + } + return false; + } + + zpl_internal ZPL_FILE_CLOSE_PROC(zpl__win32_file_close) { CloseHandle(fd.p); } + + zpl_file_operations const zpl_default_file_operations = { zpl__win32_file_read, zpl__win32_file_write, + zpl__win32_file_seek, zpl__win32_file_close }; + + ZPL_NEVER_INLINE ZPL_FILE_OPEN_PROC(zpl__win32_file_open) { + DWORD desired_access; + DWORD creation_disposition; + void *handle; + wchar_t *w_text; + + switch (mode & ZPL_FILE_MODES) { + case ZPL_FILE_MODE_READ: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + break; + case ZPL_FILE_MODE_WRITE: + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case ZPL_FILE_MODE_APPEND: + desired_access = GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + case ZPL_FILE_MODE_READ | ZPL_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + break; + case ZPL_FILE_MODE_WRITE | ZPL_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case ZPL_FILE_MODE_APPEND | ZPL_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + default: ZPL_PANIC("Invalid file mode"); return ZPL_FILE_ERROR_INVALID; + } + + w_text = zpl__alloc_utf8_to_ucs2(zpl_heap_allocator( ), filename, NULL); + handle = CreateFileW(w_text, desired_access, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, creation_disposition, + FILE_ATTRIBUTE_NORMAL, NULL); + + zpl_free(zpl_heap_allocator( ), w_text); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError( ); + switch (err) { + case ERROR_FILE_NOT_FOUND: return ZPL_FILE_ERROR_NOT_EXISTS; + case ERROR_FILE_EXISTS: return ZPL_FILE_ERROR_EXISTS; + case ERROR_ALREADY_EXISTS: return ZPL_FILE_ERROR_EXISTS; + case ERROR_ACCESS_DENIED: return ZPL_FILE_ERROR_PERMISSION; + } + return ZPL_FILE_ERROR_INVALID; + } + + if (mode & ZPL_FILE_MODE_APPEND) { + LARGE_INTEGER offset = { 0 }; + if (!SetFilePointerEx(handle, offset, NULL, ZPL_SEEK_WHENCE_END)) { + CloseHandle(handle); + return ZPL_FILE_ERROR_INVALID; + } + } + + fd->p = handle; + *ops = zpl_default_file_operations; + return ZPL_FILE_ERROR_NONE; + } + + #else // POSIX + # include + + zpl_internal ZPL_FILE_SEEK_PROC(zpl__posix_file_seek) { + # if defined(ZPL_SYSTEM_OSX) + zpl_i64 res = lseek(fd.i, offset, whence); + # else // TODO(ZaKlaus): @fixme lseek64 + zpl_i64 res = lseek(fd.i, offset, whence); + # endif + if (res < 0) return false; + if (new_offset) *new_offset = res; + return true; + } + + zpl_internal ZPL_FILE_READ_AT_PROC(zpl__posix_file_read) { + zpl_unused(stop_at_newline); + zpl_isize res = pread(fd.i, buffer, size, offset); + if (res < 0) return false; + if (bytes_read) *bytes_read = res; + return true; + } + + zpl_internal ZPL_FILE_WRITE_AT_PROC(zpl__posix_file_write) { + zpl_isize res; + zpl_i64 curr_offset = 0; + zpl__posix_file_seek(fd, 0, ZPL_SEEK_WHENCE_CURRENT, &curr_offset); + if (curr_offset == offset) { + // NOTE: Writing to stdout et al. doesn't like pwrite for numerous reasons + res = write(cast(int) fd.i, buffer, size); + } else { + res = pwrite(cast(int) fd.i, buffer, size, offset); + } + if (res < 0) return false; + if (bytes_written) *bytes_written = res; + return true; + } + + zpl_internal ZPL_FILE_CLOSE_PROC(zpl__posix_file_close) { close(fd.i); } + + zpl_file_operations const zpl_default_file_operations = { zpl__posix_file_read, zpl__posix_file_write, + zpl__posix_file_seek, zpl__posix_file_close }; + + ZPL_NEVER_INLINE ZPL_FILE_OPEN_PROC(zpl__posix_file_open) { + zpl_i32 os_mode; + switch (mode & ZPL_FILE_MODES) { + case ZPL_FILE_MODE_READ: os_mode = O_RDONLY; break; + case ZPL_FILE_MODE_WRITE: os_mode = O_WRONLY | O_CREAT | O_TRUNC; break; + case ZPL_FILE_MODE_APPEND: os_mode = O_WRONLY | O_APPEND | O_CREAT; break; + case ZPL_FILE_MODE_READ | ZPL_FILE_MODE_RW: os_mode = O_RDWR; break; + case ZPL_FILE_MODE_WRITE | ZPL_FILE_MODE_RW: os_mode = O_RDWR | O_CREAT | O_TRUNC; break; + case ZPL_FILE_MODE_APPEND | ZPL_FILE_MODE_RW: os_mode = O_RDWR | O_APPEND | O_CREAT; break; + default: ZPL_PANIC("Invalid file mode"); return ZPL_FILE_ERROR_INVALID; + } + + fd->i = open(filename, os_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd->i < 0) { + // TODO: More file errors + return ZPL_FILE_ERROR_INVALID; + } + + *ops = zpl_default_file_operations; + return ZPL_FILE_ERROR_NONE; + } + + #endif + + zpl_file_error zpl_file_new(zpl_file *f, zpl_file_descriptor fd, zpl_file_operations ops, char const *filename) { + zpl_file_error err = ZPL_FILE_ERROR_NONE; + zpl_isize len = zpl_strlen(filename); + + f->ops = ops; + f->fd = fd; + f->dir = NULL; + f->last_write_time = 0; + f->filename = zpl_alloc_array(zpl_heap_allocator( ), char, len + 1); + zpl_memcopy(cast(char *) f->filename, cast(char *) filename, len + 1); + + return err; + } + + zpl_file_error zpl_file_open_mode(zpl_file *f, zpl_file_mode mode, char const *filename) { + zpl_file file_ = {0}; + *f = file_; + zpl_file_error err; + #if defined(ZPL_SYSTEM_WINDOWS) || defined(ZPL_SYSTEM_CYGWIN) + err = zpl__win32_file_open(&f->fd, &f->ops, mode, filename); + #else + err = zpl__posix_file_open(&f->fd, &f->ops, mode, filename); + #endif + if (err == ZPL_FILE_ERROR_NONE) return zpl_file_new(f, f->fd, f->ops, filename); + return err; + } + + zpl_internal void zpl__dirinfo_free_entry(zpl_dir_entry *entry); + + zpl_file_error zpl_file_close(zpl_file *f) { + if (!f) return ZPL_FILE_ERROR_INVALID; + + if (f->filename) zpl_free(zpl_heap_allocator( ), cast(char *) f->filename); + + #if defined(ZPL_SYSTEM_WINDOWS) + if (f->fd.p == INVALID_HANDLE_VALUE) return ZPL_FILE_ERROR_INVALID; + #else + if (f->fd.i < 0) return ZPL_FILE_ERROR_INVALID; + #endif + + if (f->is_temp) + { + f->ops.close(f->fd); + return ZPL_FILE_ERROR_NONE; + } + + if (!f->ops.read_at) f->ops = zpl_default_file_operations; + f->ops.close(f->fd); + + if (f->dir) { + zpl__dirinfo_free_entry(f->dir); + zpl_mfree(f->dir); + f->dir = NULL; + } + + return ZPL_FILE_ERROR_NONE; + } + + + zpl_file_error zpl_file_create(zpl_file *f, char const *filename) { + return zpl_file_open_mode(f, ZPL_FILE_MODE_WRITE | ZPL_FILE_MODE_RW, filename); + } + + zpl_file_error zpl_file_open(zpl_file *f, char const *filename) { + return zpl_file_open_mode(f, ZPL_FILE_MODE_READ, filename); + } + + char const *zpl_file_name(zpl_file *f) { return f->filename ? f->filename : ""; } + + zpl_b32 zpl_file_has_changed(zpl_file *f) { + if (f->is_temp) + return false; + zpl_b32 result = false; + zpl_file_time last_write_time = zpl_fs_last_write_time(f->filename); + if (f->last_write_time != last_write_time) { + result = true; + f->last_write_time = last_write_time; + } + return result; + } + + // TODO: Is this a bad idea? + zpl_global zpl_b32 zpl__std_file_set = false; + zpl_global zpl_file zpl__std_files[ZPL_FILE_STANDARD_COUNT] = { { 0 } }; + + #if defined(ZPL_SYSTEM_WINDOWS) || defined(ZPL_SYSTEM_CYGWIN) + + zpl_file *zpl_file_get_standard(zpl_file_standard_type std) { + if (!zpl__std_file_set) { + #define ZPL__SET_STD_FILE(type, v) \ + zpl__std_files[type].fd.p = v; \ + zpl__std_files[type].ops = zpl_default_file_operations + ZPL__SET_STD_FILE(ZPL_FILE_STANDARD_INPUT, GetStdHandle(STD_INPUT_HANDLE)); + ZPL__SET_STD_FILE(ZPL_FILE_STANDARD_OUTPUT, GetStdHandle(STD_OUTPUT_HANDLE)); + ZPL__SET_STD_FILE(ZPL_FILE_STANDARD_ERROR, GetStdHandle(STD_ERROR_HANDLE)); + #undef ZPL__SET_STD_FILE + zpl__std_file_set = true; + } + return &zpl__std_files[std]; + } + + void zpl_file_connect_handle(zpl_file *file, void *handle) { + ZPL_ASSERT_NOT_NULL(file); + ZPL_ASSERT_NOT_NULL(handle); + + if (file->is_temp) + return; + + zpl_zero_item(file); + + file->fd.p = handle; + file->ops = zpl_default_file_operations; + } + + zpl_file_error zpl_file_truncate(zpl_file *f, zpl_i64 size) { + zpl_file_error err = ZPL_FILE_ERROR_NONE; + zpl_i64 prev_offset = zpl_file_tell(f); + zpl_file_seek(f, size); + if (!SetEndOfFile(f)) err = ZPL_FILE_ERROR_TRUNCATION_FAILURE; + zpl_file_seek(f, prev_offset); + return err; + } + + zpl_b32 zpl_fs_exists(char const *name) { + WIN32_FIND_DATAW data; + wchar_t *w_text; + void *handle; + zpl_b32 found = false; + zpl_allocator a = zpl_heap_allocator( ); + + w_text = zpl__alloc_utf8_to_ucs2(a, name, NULL); + if (w_text == NULL) { return false; } + handle = FindFirstFileW(w_text, &data); + zpl_free(a, w_text); + found = handle != INVALID_HANDLE_VALUE; + if (found) FindClose(handle); + return found; + } + + #else // POSIX + + zpl_file *zpl_file_get_standard(zpl_file_standard_type std) { + if (!zpl__std_file_set) { + #define ZPL__SET_STD_FILE(type, v) \ + zpl__std_files[type].fd.i = v; \ + zpl__std_files[type].ops = zpl_default_file_operations + ZPL__SET_STD_FILE(ZPL_FILE_STANDARD_INPUT, 0); + ZPL__SET_STD_FILE(ZPL_FILE_STANDARD_OUTPUT, 1); + ZPL__SET_STD_FILE(ZPL_FILE_STANDARD_ERROR, 2); + #undef ZPL__SET_STD_FILE + zpl__std_file_set = true; + } + return &zpl__std_files[std]; + } + + zpl_file_error zpl_file_truncate(zpl_file *f, zpl_i64 size) { + zpl_file_error err = ZPL_FILE_ERROR_NONE; + int i = ftruncate(f->fd.i, size); + if (i != 0) err = ZPL_FILE_ERROR_TRUNCATION_FAILURE; + return err; + } + + zpl_b32 zpl_fs_exists(char const *name) { return access(name, F_OK) != -1; } + + #endif + + zpl_i64 zpl_file_size(zpl_file *f) { + zpl_i64 size = 0; + zpl_i64 prev_offset = zpl_file_tell(f); + zpl_file_seek_to_end(f); + size = zpl_file_tell(f); + zpl_file_seek(f, prev_offset); + return size; + } + + zpl_file_error zpl_file_temp(zpl_file *file) { + zpl_zero_item(file); + FILE *fd = NULL; + + #if (defined(ZPL_SYSTEM_WINDOWS) && !defined(ZPL_SYSTEM_TINYC)) && !defined(ZPL_COMPILER_GCC) + errno_t errcode = tmpfile_s(&fd); + + if (errcode != 0) { + fd = NULL; + } + #else + fd = tmpfile(); + #endif + + if (fd == NULL) { return ZPL_FILE_ERROR_INVALID; } + + #if defined(ZPL_SYSTEM_WINDOWS) && !defined(ZPL_COMPILER_GCC) + file->fd.i = _get_osfhandle(_fileno(fd)); + #else + file->fd.i = fileno(fd); + #endif + file->ops = zpl_default_file_operations; + file->is_temp = true; + return ZPL_FILE_ERROR_NONE; + } + + zpl_file_contents zpl_file_read_contents(zpl_allocator a, zpl_b32 zero_terminate, char const *filepath) { + zpl_file_contents result = { 0 }; + zpl_file file = { 0 }; + + result.allocator = a; + + zpl_u8 entry_type = zpl_fs_get_type(filepath); + + /* ignore folders */ + if (entry_type == ZPL_DIR_TYPE_FOLDER) { + return result; + } + + if (zpl_file_open(&file, filepath) == ZPL_FILE_ERROR_NONE) { + zpl_isize file_size = cast(zpl_isize) zpl_file_size(&file); + if (file_size > 0) { + result.data = zpl_alloc(a, zero_terminate ? file_size + 1 : file_size); + result.size = file_size; + zpl_file_read_at(&file, result.data, result.size, 0); + if (zero_terminate) { + zpl_u8 *str = cast(zpl_u8 *) result.data; + str[file_size] = '\0'; + } + } + zpl_file_close(&file); + } + + return result; + } + + void zpl_file_free_contents(zpl_file_contents *fc) { + ZPL_ASSERT_NOT_NULL(fc->data); + zpl_free(fc->allocator, fc->data); + fc->data = NULL; + fc->size = 0; + } + + zpl_b32 zpl_file_write_contents(char const* filepath, void const* buffer, zpl_isize size, zpl_file_error* err) { + zpl_file f = { 0 }; + zpl_file_error open_err; + zpl_b32 write_ok; + open_err = zpl_file_open_mode(&f, ZPL_FILE_MODE_WRITE, filepath); + + if (open_err != ZPL_FILE_ERROR_NONE) + { + if (err) + *err = open_err; + + return false; + } + + write_ok = zpl_file_write(&f, buffer, size); + zpl_file_close(&f); + return write_ok; + } + + char *zpl_file_read_lines(zpl_allocator alloc, zpl_array(char *)*lines, char const *filename, zpl_b32 strip_whitespace) { + zpl_file f = { 0 }; + zpl_file_open(&f, filename); + zpl_isize fsize = (zpl_isize)zpl_file_size(&f); + + char *contents = (char *)zpl_alloc(alloc, fsize + 1); + zpl_file_read(&f, contents, fsize); + contents[fsize] = 0; + *lines = zpl_str_split_lines(alloc, contents, strip_whitespace); + zpl_file_close(&f); + + return contents; + } + + #if !defined(_WINDOWS_) && defined(ZPL_SYSTEM_WINDOWS) + ZPL_IMPORT DWORD WINAPI GetFullPathNameA(char const *lpFileName, DWORD nBufferLength, char *lpBuffer, char **lpFilePart); + ZPL_IMPORT DWORD WINAPI GetFullPathNameW(wchar_t const *lpFileName, DWORD nBufferLength, wchar_t *lpBuffer, wchar_t **lpFilePart); + #endif + + ZPL_END_C_DECLS + // file: source/core/file_stream.c + + + //////////////////////////////////////////////////////////////// + // + // Memory streaming + // + // + + ZPL_BEGIN_C_DECLS + + typedef struct { + zpl_u8 magic; + zpl_u8 *buf; //< zpl_array OR plain buffer if we can't write + zpl_isize cursor; + zpl_allocator alloc; + + zpl_file_stream_flags flags; + zpl_isize cap; + } zpl__memory_fd; + + #define ZPL__FILE_STREAM_FD_MAGIC 37 + + ZPL_DEF_INLINE zpl_file_descriptor zpl__file_stream_fd_make(zpl__memory_fd* d); + ZPL_DEF_INLINE zpl__memory_fd *zpl__file_stream_from_fd(zpl_file_descriptor fd); + + ZPL_IMPL_INLINE zpl_file_descriptor zpl__file_stream_fd_make(zpl__memory_fd* d) { + zpl_file_descriptor fd = {0}; + fd.p = (void*)d; + return fd; + } + + ZPL_IMPL_INLINE zpl__memory_fd *zpl__file_stream_from_fd(zpl_file_descriptor fd) { + zpl__memory_fd *d = (zpl__memory_fd*)fd.p; + ZPL_ASSERT(d->magic == ZPL__FILE_STREAM_FD_MAGIC); + return d; + } + + zpl_b8 zpl_file_stream_new(zpl_file* file, zpl_allocator allocator) { + ZPL_ASSERT_NOT_NULL(file); + zpl__memory_fd *d = (zpl__memory_fd*)zpl_alloc(allocator, zpl_size_of(zpl__memory_fd)); + if (!d) return false; + zpl_zero_item(file); + d->magic = ZPL__FILE_STREAM_FD_MAGIC; + d->alloc = allocator; + d->flags = ZPL_FILE_STREAM_CLONE_WRITABLE; + d->cap = 0; + if (!zpl_array_init(d->buf, allocator)) return false; + file->ops = zpl_memory_file_operations; + file->fd = zpl__file_stream_fd_make(d); + file->dir = NULL; + file->last_write_time = 0; + file->filename = NULL; + file->is_temp = true; + return true; + } + zpl_b8 zpl_file_stream_open(zpl_file* file, zpl_allocator allocator, zpl_u8 *buffer, zpl_isize size, zpl_file_stream_flags flags) { + ZPL_ASSERT_NOT_NULL(file); + zpl__memory_fd *d = (zpl__memory_fd*)zpl_alloc(allocator, zpl_size_of(zpl__memory_fd)); + if (!d) return false; + zpl_zero_item(file); + d->magic = ZPL__FILE_STREAM_FD_MAGIC; + d->alloc = allocator; + d->flags = flags; + if (d->flags & ZPL_FILE_STREAM_CLONE_WRITABLE) { + if (!zpl_array_init_reserve(d->buf, allocator, size)) return false; + zpl_memcopy(d->buf, buffer, size); + d->cap = zpl_array_count(d->buf) = size; + } else { + d->buf = buffer; + d->cap = size; + } + file->ops = zpl_memory_file_operations; + file->fd = zpl__file_stream_fd_make(d); + file->dir = NULL; + file->last_write_time = 0; + file->filename = NULL; + file->is_temp = true; + return true; + } + + zpl_u8 *zpl_file_stream_buf(zpl_file* file, zpl_isize *size) { + ZPL_ASSERT_NOT_NULL(file); + zpl__memory_fd *d = zpl__file_stream_from_fd(file->fd); + if (size) *size = d->cap; + return d->buf; + } + + zpl_internal ZPL_FILE_SEEK_PROC(zpl__memory_file_seek) { + zpl__memory_fd *d = zpl__file_stream_from_fd(fd); + zpl_isize buflen = d->cap; + + if (whence == ZPL_SEEK_WHENCE_BEGIN) + d->cursor = 0; + else if (whence == ZPL_SEEK_WHENCE_END) + d->cursor = buflen; + + d->cursor = zpl_max(0, zpl_clamp(d->cursor + offset, 0, buflen)); + if (new_offset) *new_offset = d->cursor; + return true; + } + + zpl_internal ZPL_FILE_READ_AT_PROC(zpl__memory_file_read) { + zpl_unused(stop_at_newline); + zpl__memory_fd *d = zpl__file_stream_from_fd(fd); + zpl_memcopy(buffer, d->buf + offset, size); + if (bytes_read) *bytes_read = size; + return true; + } + + zpl_internal ZPL_FILE_WRITE_AT_PROC(zpl__memory_file_write) { + zpl__memory_fd *d = zpl__file_stream_from_fd(fd); + if (!(d->flags & (ZPL_FILE_STREAM_CLONE_WRITABLE|ZPL_FILE_STREAM_WRITABLE))) + return false; + zpl_isize buflen = d->cap; + zpl_isize extralen = zpl_max(0, size-(buflen-offset)); + zpl_isize rwlen = size-extralen; + zpl_isize new_cap = buflen+extralen; + if (d->flags & ZPL_FILE_STREAM_CLONE_WRITABLE) { + if(zpl_array_capacity(d->buf) < new_cap) { + if (!zpl_array_grow(d->buf, (zpl_i64)(new_cap))) return false; + } + } + zpl_memcopy(d->buf + offset, buffer, rwlen); + + if ((d->flags & ZPL_FILE_STREAM_CLONE_WRITABLE) && extralen > 0) { + zpl_memcopy(d->buf + offset + rwlen, zpl_ptr_add_const(buffer, rwlen), extralen); + d->cap = zpl_array_count(d->buf) = new_cap; + } else { + extralen = 0; + } + + if (bytes_written) *bytes_written = (rwlen+extralen); + return true; + } + + zpl_internal ZPL_FILE_CLOSE_PROC(zpl__memory_file_close) { + zpl__memory_fd *d = zpl__file_stream_from_fd(fd); + zpl_allocator alloc = d->alloc; + if (d->flags & ZPL_FILE_STREAM_CLONE_WRITABLE) + zpl_array_free(d->buf); + zpl_free(alloc, d); + } + + zpl_file_operations const zpl_memory_file_operations = { zpl__memory_file_read, zpl__memory_file_write, + zpl__memory_file_seek, zpl__memory_file_close }; + + ZPL_END_C_DECLS + // file: source/core/file_misc.c + + + #if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_MACOS) + # include + #endif + + #if defined(ZPL_SYSTEM_UNIX) && !defined(ZPL_SYSTEM_FREEBSD) && !defined(ZPL_SYSTEM_OPENBSD) && !defined(ZPL_SYSTEM_CYGWIN) && !defined(ZPL_SYSTEM_EMSCRIPTEN) + # include + #endif + + #if defined(ZPL_SYSTEM_WINDOWS) + # include + # include + #endif + + #if defined(ZPL_SYSTEM_CYGWIN) + # include + # include + # include + #endif + + ZPL_BEGIN_C_DECLS + + + #if defined(ZPL_SYSTEM_WINDOWS) || defined(ZPL_SYSTEM_CYGWIN) + zpl_file_time zpl_fs_last_write_time(char const *filepath) { + ULARGE_INTEGER li = { 0 }; + FILETIME last_write_time = { 0 }; + WIN32_FILE_ATTRIBUTE_DATA data = { 0 }; + zpl_allocator a = zpl_heap_allocator( ); + + wchar_t *w_text = zpl__alloc_utf8_to_ucs2(a, filepath, NULL); + if (w_text == NULL) { return 0; } + if (GetFileAttributesExW(w_text, GetFileExInfoStandard, &data)) last_write_time = data.ftLastWriteTime; + + zpl_free(a, w_text); + + li.LowPart = last_write_time.dwLowDateTime; + li.HighPart = last_write_time.dwHighDateTime; + return cast(zpl_file_time) li.QuadPart; + } + + zpl_b32 zpl_fs_copy(char const *existing_filename, char const *new_filename, zpl_b32 fail_if_exists) { + zpl_b32 result = false; + zpl_allocator a = zpl_heap_allocator( ); + + wchar_t *w_old = zpl__alloc_utf8_to_ucs2(a, existing_filename, NULL); + if (w_old == NULL) { return false; } + + wchar_t *w_new = zpl__alloc_utf8_to_ucs2(a, new_filename, NULL); + if (w_new != NULL) { result = CopyFileW(w_old, w_new, fail_if_exists); } + + zpl_free(a, w_old); + zpl_free(a, w_new); + return result; + } + + zpl_b32 zpl_fs_move(char const *existing_filename, char const *new_filename) { + zpl_b32 result = false; + zpl_allocator a = zpl_heap_allocator( ); + + wchar_t *w_old = zpl__alloc_utf8_to_ucs2(a, existing_filename, NULL); + if (w_old == NULL) { return false; } + + wchar_t *w_new = zpl__alloc_utf8_to_ucs2(a, new_filename, NULL); + if (w_new != NULL) { result = MoveFileW(w_old, w_new); } + + zpl_free(a, w_old); + zpl_free(a, w_new); + return result; + } + + zpl_b32 zpl_fs_remove(char const *filename) { + zpl_b32 result = false; + zpl_allocator a = zpl_heap_allocator( ); + + wchar_t *w_filename = zpl__alloc_utf8_to_ucs2(a, filename, NULL); + if (w_filename == NULL) { return false; } + + result = DeleteFileW(w_filename); + + zpl_free(a, w_filename); + return result; + } + + #else + + zpl_file_time zpl_fs_last_write_time(char const *filepath) { + time_t result = 0; + struct stat file_stat; + + if (stat(filepath, &file_stat)) result = file_stat.st_mtime; + + return cast(zpl_file_time) result; + } + + # if defined(ZPL_SYSTEM_FREEBSD) + # include + # include + # include + # endif + + + zpl_b32 zpl_fs_copy(char const *existing_filename, char const *new_filename, zpl_b32 fail_if_exists) { + zpl_unused(fail_if_exists); + # if defined(ZPL_SYSTEM_OSX) + return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0; + # elif defined(ZPL_SYSTEM_OPENBSD) + ZPL_NOT_IMPLEMENTED; + return 0; + # elif defined(ZPL_SYSTEM_EMSCRIPTEN) + ZPL_NOT_IMPLEMENTED; + return 0; + # else + int existing_fd = open(existing_filename, O_RDONLY, 0); + struct stat stat_existing; + fstat(existing_fd, &stat_existing); + + zpl_isize size; + int new_fd = open(new_filename, O_WRONLY | O_CREAT, stat_existing.st_mode); + + # if defined(ZPL_SYSTEM_FREEBSD) + size = sendfile(new_fd, existing_fd, 0, stat_existing.st_size, NULL, 0, 0); + # else + size = sendfile(new_fd, existing_fd, 0, stat_existing.st_size); + # endif + + close(new_fd); + close(existing_fd); + + return size == stat_existing.st_size; + # endif + } + + zpl_b32 zpl_fs_move(char const *existing_filename, char const *new_filename) { + if (link(existing_filename, new_filename) == 0) { return (unlink(existing_filename) != -1); } + return false; + } + + zpl_b32 zpl_fs_remove(char const *filename) { + # if defined(ZPL_SYSTEM_OSX) || defined(ZPL_SYSTEM_EMSCRIPTEN) + return (unlink(filename) != -1); + # else + return (remove(filename) == 0); + # endif + } + + #endif + + char *zpl_path_get_full_name(zpl_allocator a, char const *path) { + #if defined(ZPL_SYSTEM_WINDOWS) + wchar_t *w_path = NULL; + wchar_t *w_fullpath = NULL; + zpl_isize w_len = 0; + zpl_isize new_len = 0; + zpl_isize new_len1 = 0; + char *new_path = 0; + + w_path = zpl__alloc_utf8_to_ucs2(zpl_heap_allocator( ), path, NULL); + if (w_path == NULL) { return NULL; } + + w_len = GetFullPathNameW(w_path, 0, NULL, NULL); + if (w_len == 0) { return NULL; } + + w_fullpath = zpl_alloc_array(zpl_heap_allocator( ), wchar_t, w_len + 1); + GetFullPathNameW(w_path, cast(int) w_len, w_fullpath, NULL); + w_fullpath[w_len] = 0; + + zpl_free(zpl_heap_allocator( ), w_path); + + new_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w_fullpath, cast(int) w_len, NULL, 0, NULL, NULL); + + if (new_len == 0) { + zpl_free(zpl_heap_allocator( ), w_fullpath); + return NULL; + } + + new_path = zpl_alloc_array(a, char, new_len); + new_len1 = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w_fullpath, cast(int) w_len, new_path, + cast(int) new_len, NULL, NULL); + + if (new_len1 == 0) { + zpl_free(zpl_heap_allocator( ), w_fullpath); + zpl_free(a, new_path); + return NULL; + } + + new_path[new_len] = 0; + return new_path; + #else + char *p, *result, *fullpath = NULL; + zpl_isize len; + p = realpath(path, NULL); + fullpath = p; + if (p == NULL) { + // NOTE(bill): File does not exist + fullpath = cast(char *) path; + } + + len = zpl_strlen(fullpath); + + result = zpl_alloc_array(a, char, len + 1); + zpl_memmove(result, fullpath, len); + result[len] = 0; + zpl_free(a, p); + + return result; + #endif + } + + zpl_file_error zpl_path_mkdir(char const *path, zpl_i32 mode) { + zpl_i32 error = 0; + #if defined(ZPL_SYSTEM_WINDOWS) + error = _wmkdir((const wchar_t *)zpl_utf8_to_ucs2_buf((const zpl_u8 *)path)); + #else + error = mkdir(path, (mode_t)mode); + #endif + + if (error == 0) { return ZPL_FILE_ERROR_NONE; } + + switch (errno) { + case EPERM: + case EACCES: return ZPL_FILE_ERROR_PERMISSION; + case EEXIST: return ZPL_FILE_ERROR_EXISTS; + case ENAMETOOLONG: return ZPL_FILE_ERROR_NAME_TOO_LONG; + } + + return ZPL_FILE_ERROR_UNKNOWN; + } + + zpl_isize zpl_path_mkdir_recursive(char const *path, zpl_i32 mode) { + char tmp[ZPL_MAX_PATH] = {0}; + char *p = 0; + zpl_isize len = zpl_strlen(path); + + if (len > zpl_size_of(tmp)-1) { + return -1; + } + zpl_strcpy(tmp, path); + zpl_path_fix_slashes(tmp); + for (p = tmp + 1; *p; p++) { + if (*p == ZPL_PATH_SEPARATOR) { + *p = 0; + zpl_path_mkdir(tmp, mode); + *p = ZPL_PATH_SEPARATOR; + } + } + zpl_path_mkdir(tmp, mode); + return 0; + } + + zpl_file_error zpl_path_rmdir(char const *path) { + zpl_i32 error = 0; + #if defined(ZPL_SYSTEM_WINDOWS) + error = _wrmdir((const wchar_t *)zpl_utf8_to_ucs2_buf((const zpl_u8 *)path)); + #else + error = rmdir(path); + #endif + + if (error == 0) { return ZPL_FILE_ERROR_NONE; } + + switch (errno) { + case EPERM: + case EACCES: return ZPL_FILE_ERROR_PERMISSION; + case ENOENT: return ZPL_FILE_ERROR_NOT_EXISTS; + case ENOTEMPTY: return ZPL_FILE_ERROR_NOT_EMPTY; + case ENAMETOOLONG: return ZPL_FILE_ERROR_NAME_TOO_LONG; + } + + return ZPL_FILE_ERROR_UNKNOWN; + } + + void zpl__file_direntry(zpl_allocator alloc, char const *dirname, zpl_string *output, zpl_b32 recurse) { + #if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_OSX) + DIR *d, *cd; + struct dirent *dir; + d = opendir(dirname); + + if (d) { + while ((dir = readdir(d))) { + if (dir == 0) break; + if (!zpl_strncmp(dir->d_name, "..", 2)) continue; + if (dir->d_name[0] == '.' && dir->d_name[1] == 0) continue; + + zpl_string dirpath = zpl_string_make(alloc, dirname); + dirpath = zpl_string_appendc(dirpath, "/"); + dirpath = zpl_string_appendc(dirpath, dir->d_name); + + *output = zpl_string_appendc(*output, dirpath); + *output = zpl_string_appendc(*output, "\n"); + + if (recurse && (cd = opendir(dirpath)) != NULL && dir->d_type == DT_DIR) { zpl__file_direntry(alloc, dirpath, output, recurse); } + zpl_string_free(dirpath); + } + } + #elif defined(ZPL_SYSTEM_WINDOWS) + zpl_usize length = zpl_strlen(dirname); + struct _wfinddata_t data; + zpl_intptr findhandle; + + char directory[MAX_PATH] = { 0 }; + zpl_strncpy(directory, dirname, length); + + // keeping it native + for (zpl_usize i = 0; i < length; i++) { + if (directory[i] == '/') directory[i] = '\\'; + } + + // remove trailing slashses + if (directory[length - 1] == '\\') { directory[length - 1] = '\0'; } + + // attach search pattern + zpl_string findpath = zpl_string_make(alloc, directory); + findpath = zpl_string_appendc(findpath, "\\"); + findpath = zpl_string_appendc(findpath, "*"); + + findhandle = _wfindfirst((const wchar_t *)zpl_utf8_to_ucs2_buf((const zpl_u8 *)findpath), &data); + zpl_string_free(findpath); + + if (findhandle != -1) { + do { + char *filename = (char *)zpl_ucs2_to_utf8_buf((const zpl_u16 *)data.name); + if (!zpl_strncmp(filename, "..", 2)) continue; + if (filename[0] == '.' && filename[1] == 0) continue; + + zpl_string dirpath = zpl_string_make(alloc, directory); + dirpath = zpl_string_appendc(dirpath, "\\"); + dirpath = zpl_string_appendc(dirpath, filename); + DWORD attrs = GetFileAttributesW((const wchar_t *)zpl_utf8_to_ucs2_buf((const zpl_u8 *)dirpath)); + + *output = zpl_string_appendc(*output, dirpath); + *output = zpl_string_appendc(*output, "\n"); + + if (recurse && (data.attrib & _A_SUBDIR) && !(attrs & FILE_ATTRIBUTE_REPARSE_POINT)) { zpl__file_direntry(alloc, dirpath, output, recurse); } + + zpl_string_free(dirpath); + } while (_wfindnext(findhandle, &data) != -1); + _findclose(findhandle); + } + #else + // TODO: Implement other OSes + #endif + } + + zpl_string zpl_path_dirlist(zpl_allocator alloc, char const *dirname, zpl_b32 recurse) { + zpl_string buf = zpl_string_make_reserve(alloc, 4); + zpl__file_direntry(alloc, dirname, &buf, recurse); + return buf; + } + + void zpl_dirinfo_init(zpl_dir_info *dir, char const *path) { + ZPL_ASSERT_NOT_NULL(dir); + + zpl_dir_info dir_ = {0}; + *dir = dir_; + dir->fullpath = (char const*)zpl_malloc(zpl_strlen(path)); + zpl_strcpy((char *)dir->fullpath, path); + + + zpl_string dirlist = zpl_path_dirlist(zpl_heap(), path, false); + char **files=zpl_str_split_lines(zpl_heap(), dirlist, false); + dir->filenames = files; + dir->buf = dirlist; + + zpl_array_init(dir->entries, zpl_heap()); + + for (zpl_i32 i=0; ientries, entry); + } + } + + zpl_internal void zpl__dirinfo_free_entry(zpl_dir_entry *entry) { + if (entry->dir_info) { + zpl_dirinfo_free(entry->dir_info); + zpl_mfree(entry->dir_info); + entry->dir_info = NULL; + } + } + + void zpl_dirinfo_free(zpl_dir_info *dir) { + ZPL_ASSERT_NOT_NULL(dir); + + for (zpl_isize i = 0; i < zpl_array_count(dir->entries); ++i) { + zpl__dirinfo_free_entry(dir->entries + i); + } + + zpl_array_free(dir->entries); + zpl_array_free(dir->filenames); + zpl_string_free(dir->buf); + zpl_mfree((void *)dir->fullpath); + } + + + zpl_u8 zpl_fs_get_type(char const *path) { + #ifdef ZPL_SYSTEM_WINDOWS + DWORD attrs = GetFileAttributesW((const wchar_t *)zpl_utf8_to_ucs2_buf((const zpl_u8 *)path)); + + if (attrs == INVALID_FILE_ATTRIBUTES) { + return ZPL_DIR_TYPE_UNKNOWN; + } + + if (attrs & FILE_ATTRIBUTE_DIRECTORY) + return ZPL_DIR_TYPE_FOLDER; + else + return ZPL_DIR_TYPE_FILE; + + #else + struct stat s; + if( stat(path,&s) == 0 ) + { + if(s.st_mode & S_IFDIR) + return ZPL_DIR_TYPE_FOLDER; + else + return ZPL_DIR_TYPE_FILE; + } + #endif + + return ZPL_DIR_TYPE_UNKNOWN; + } + + void zpl_dirinfo_step(zpl_dir_entry *entry) { + if (entry->dir_info) { + zpl__dirinfo_free_entry(entry); + } + + entry->dir_info = (zpl_dir_info *)zpl_malloc(sizeof(zpl_dir_info)); + zpl_dir_info dir_ = {0}; + *entry->dir_info = dir_; + + zpl_local_persist char buf[128] = {0}; + char const *path = entry->filename; + + if (entry->type != ZPL_DIR_TYPE_FOLDER) { + zpl_path_fix_slashes((char *)path); + char const* slash = zpl_char_last_occurence(path, ZPL_PATH_SEPARATOR); + zpl_strncpy(buf, path, slash-path); + path = buf; + } + + zpl_dirinfo_init(entry->dir_info, path); + } + + void zpl_file_dirinfo_refresh(zpl_file *file) { + if (file->is_temp) + return; + + if (file->dir) { + zpl__dirinfo_free_entry(file->dir); + zpl_mfree(file->dir); + file->dir = NULL; + } + + file->dir = (zpl_dir_entry *)zpl_malloc(sizeof(zpl_dir_entry)); + zpl_dir_entry dir_ = {0}; + *file->dir = dir_; + file->dir->filename = file->filename; + file->dir->type = ZPL_DIR_TYPE_FILE; + + zpl_dirinfo_step(file->dir); + } + + void zpl_path_fix_slashes(char *path) { + #ifdef ZPL_SYSTEM_WINDOWS + char *p = path; + + while (*p != '\0') { + if (*p == '/') + *p = '\\'; + + ++p; + } + #endif + } + + ZPL_END_C_DECLS + // file: source/core/file_tar.c + + + typedef struct { + char name[100]; + char mode[8]; + char owner[8]; + char group[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char type; + char linkname[100]; + char _padding[255]; + } zpl__tar_header; + + zpl_internal zpl_usize zpl__tar_checksum(zpl__tar_header *hr) { + zpl_usize i; + zpl_usize res = 256; + zpl_u8 *p = cast(zpl_u8*)(hr); + for (i = 0; i < cast(zpl_usize)zpl_offset_of(zpl__tar_header, checksum); i++) + res += p[i]; + for (i = cast(zpl_usize)zpl_offset_of(zpl__tar_header, type); i < cast(zpl_usize)zpl_size_of(zpl__tar_header); i++) + res += p[i]; + return res; + } + + zpl_internal zpl_b32 zpl__tar_write_null(zpl_file *archive, zpl_isize cnt) { + char *out = zpl_bprintf("%*r", cnt, '\0'); + if (!zpl_file_write(archive, out, cnt)) + return 0; + return 1; + } + + zpl_isize zpl_tar_pack(zpl_file *archive, char const **paths, zpl_isize paths_len) { + ZPL_ASSERT_NOT_NULL(archive); + ZPL_ASSERT_NOT_NULL(paths); + + for (zpl_isize i = 0; i < paths_len; i++) { + ZPL_ASSERT_NOT_NULL(paths[i]); + zpl__tar_header hr = {0}; + zpl_file file; + zpl_file_error ferr = zpl_file_open_mode(&file, ZPL_FILE_MODE_READ, paths[i]); + if (ferr == ZPL_FILE_ERROR_NOT_EXISTS) { + return -(ZPL_TAR_ERROR_FILE_NOT_FOUND); + } else if (ferr != ZPL_FILE_ERROR_NONE) { + return -(ZPL_TAR_ERROR_IO_ERROR); + } + + zpl_i64 file_size = zpl_file_size(&file); + zpl_snprintf(hr.name, 12, "%s", paths[i]); + zpl_snprintf(hr.size, 12, "%o", file_size); + zpl_snprintf(hr.mode, 8, "%o", 0664); + zpl_snprintf(hr.mtime, 12, "%o", zpl_fs_last_write_time(paths[i])); + hr.type = ZPL_TAR_TYPE_REGULAR; + zpl_snprintf(hr.checksum, 8, "%o", zpl__tar_checksum(&hr)); + + zpl_file_write(archive, cast(void*)(&hr), zpl_size_of(zpl__tar_header)); + + // write data + { + zpl_i64 remaining_data = file_size; + zpl_i64 total_data = zpl_align_forward_i64(remaining_data, 512); + zpl_i64 padding = (total_data-file_size); + char buf[4096] = {0}; + zpl_i64 pos = 0; + zpl_isize bytes_read = 0; + do { + if (!zpl_file_read_at_check(&file, buf, 4096, pos, &bytes_read)) { + zpl_file_close(&file); + return -(ZPL_TAR_ERROR_IO_ERROR); + } else if (bytes_read == 0) { + break; + } + + zpl_file_write(archive, buf, bytes_read); + pos += bytes_read; + remaining_data -= bytes_read; + } + while (remaining_data > 0); + + if (padding > 0) { + if (!zpl__tar_write_null(archive, padding)) { + zpl_file_close(&file); + return -(ZPL_TAR_ERROR_IO_ERROR); + } + } + } + + zpl_file_close(&file); + } + + if (!zpl__tar_write_null(archive, zpl_size_of(zpl__tar_header) * 2)) { + return -(ZPL_TAR_ERROR_IO_ERROR); + } + + return 0; + } + + zpl_isize zpl_tar_pack_dir(zpl_file *archive, char const *path, zpl_allocator alloc) { + zpl_string filelst = zpl_path_dirlist(alloc, path, true); + char const **files = cast(char const**)zpl_str_split_lines(alloc, filelst, false); + zpl_isize err = zpl_tar_pack(archive, files, zpl_array_count(files)); + zpl_string_free(filelst); + zpl_array_free(files); + return err; + } + + zpl_isize zpl_tar_unpack(zpl_file *archive, zpl_tar_unpack_proc *unpack_proc, void *user_data) { + ZPL_ASSERT_NOT_NULL(archive); + ZPL_ASSERT_NOT_NULL(unpack_proc); + + zpl_i64 pos = zpl_file_tell(archive); + zpl__tar_header hr = {0}; + zpl_isize err = ZPL_TAR_ERROR_NONE; + + do { + if (!zpl_file_read(archive, cast(void*)&hr, zpl_size_of(hr))) { + err = ZPL_TAR_ERROR_IO_ERROR; + break; + } + else if (*hr.checksum == 0) { + break; + } + pos = zpl_file_tell(archive); + + zpl_tar_record rec = {0}; + rec.type = hr.type; + rec.path = hr.name; + rec.offset = pos; + rec.length = zpl_str_to_i64(hr.size, 0, 8); + rec.error = ZPL_TAR_ERROR_NONE; + + zpl_usize checksum1 = cast(zpl_usize)(zpl_str_to_i64(hr.checksum, 0, 8)); + zpl_usize checksum2 = zpl__tar_checksum(&hr); + rec.error = (checksum1 != checksum2) ? cast(zpl_isize)ZPL_TAR_ERROR_BAD_CHECKSUM : rec.error; + + rec.error = unpack_proc(archive, &rec, user_data); + + if (rec.error > 0) { + err = ZPL_TAR_ERROR_INTERRUPTED; + break; + } + + /* tar rounds files to 512 byte boundary */ + zpl_file_seek(archive, pos + zpl_align_forward_i64(rec.length, 512)); + } + while(err == ZPL_TAR_ERROR_NONE); + + return -(err); + } + + ZPL_TAR_UNPACK_PROC(zpl_tar_default_list_file) { + (void)archive; + (void)user_data; + if (file->error != ZPL_TAR_ERROR_NONE) + return 0; /* skip file */ + + if (file->type != ZPL_TAR_TYPE_REGULAR) + return 0; /* we only care about regular files */ + + /* proceed as usual */ + zpl_printf("name: %s, offset: %d, length: %d\n", file->path, file->offset, file->length); + return 0; + } + + ZPL_TAR_UNPACK_PROC(zpl_tar_default_unpack_file) { + if (file->error != ZPL_TAR_ERROR_NONE) + return 0; /* skip file */ + + if (file->type != ZPL_TAR_TYPE_REGULAR) + return 0; /* we only care about regular files */ + + if (!zpl_strncmp(file->path, "..", 2)) + return 0; + + char tmp[ZPL_MAX_PATH] = {0}; + char *base_path = cast(char*)user_data; + zpl_isize base_len = zpl_strlen(base_path); + zpl_isize len = zpl_strlen(file->path); + ZPL_ASSERT(base_len+len-2 < ZPL_MAX_PATH); /* todo: account for missing leading path sep */ + + zpl_strcpy(tmp, base_path); + zpl_path_fix_slashes(tmp); /* todo: need to do twice as base_path is checked before concat */ + + if (*tmp && tmp[base_len-1] != ZPL_PATH_SEPARATOR) { + char sep[2] = {ZPL_PATH_SEPARATOR, 0}; + zpl_strcat(tmp, sep); + } + zpl_strcat(tmp, file->path); + zpl_path_fix_slashes(tmp); + + const char *last_slash = zpl_char_last_occurence(tmp, ZPL_PATH_SEPARATOR); + + if (last_slash) { + zpl_isize i = cast(zpl_isize)(last_slash-tmp); + tmp[i] = 0; + zpl_path_mkdir_recursive(tmp, 0755); + tmp[i] = ZPL_PATH_SEPARATOR; + } + + zpl_file f; + zpl_file_create(&f, tmp); + { + char buf[4096] = {0}; + zpl_isize remaining_data = file->length; + zpl_isize bytes_read = 0; + zpl_i64 pos = file->offset; + do { + if (!zpl_file_read_at_check(archive, buf, zpl_min(4096, remaining_data), pos, &bytes_read)) { + zpl_file_close(&f); + return 1; + } else if (bytes_read == 0) { + break; + } + + zpl_file_write(&f, buf, bytes_read); + pos += bytes_read; + remaining_data -= bytes_read; + } + while (remaining_data > 0); + } + zpl_file_close(&f); + return 0; + } + // file: source/core/print.c + + + ZPL_BEGIN_C_DECLS + + zpl_isize zpl_printf_va(char const *fmt, va_list va) { + return zpl_fprintf_va(zpl_file_get_standard(ZPL_FILE_STANDARD_OUTPUT), fmt, va); + } + + zpl_isize zpl_printf_err_va(char const *fmt, va_list va) { + return zpl_fprintf_va(zpl_file_get_standard(ZPL_FILE_STANDARD_ERROR), fmt, va); + } + + zpl_isize zpl_fprintf_va(struct zpl_file *f, char const *fmt, va_list va) { + zpl_local_persist zpl_thread_local char buf[ZPL_PRINTF_MAXLEN]; + zpl_isize len = zpl_snprintf_va(buf, zpl_size_of(buf), fmt, va); + zpl_b32 res = zpl_file_write(f, buf, len - 1); // NOTE: prevent extra whitespace + return res ? len : -1; + } + + char *zpl_bprintf_va(char const *fmt, va_list va) { + zpl_local_persist zpl_thread_local char buffer[ZPL_PRINTF_MAXLEN]; + zpl_snprintf_va(buffer, zpl_size_of(buffer), fmt, va); + return buffer; + } + + zpl_isize zpl_asprintf_va(zpl_allocator allocator, char **buffer, char const *fmt, va_list va) { + zpl_local_persist zpl_thread_local char tmp[ZPL_PRINTF_MAXLEN]; + ZPL_ASSERT_NOT_NULL(buffer); + zpl_isize res; + res = zpl_snprintf_va(tmp, zpl_size_of(tmp), fmt, va); + *buffer = zpl_alloc_str(allocator, tmp); + return res; + } + + zpl_isize zpl_printf(char const *fmt, ...) { + zpl_isize res; + va_list va; + va_start(va, fmt); + res = zpl_printf_va(fmt, va); + va_end(va); + return res; + } + + zpl_isize zpl_printf_err(char const *fmt, ...) { + zpl_isize res; + va_list va; + va_start(va, fmt); + res = zpl_printf_err_va(fmt, va); + va_end(va); + return res; + } + + zpl_isize zpl_fprintf(struct zpl_file *f, char const *fmt, ...) { + zpl_isize res; + va_list va; + va_start(va, fmt); + res = zpl_fprintf_va(f, fmt, va); + va_end(va); + return res; + } + + char *zpl_bprintf(char const *fmt, ...) { + va_list va; + char *str; + va_start(va, fmt); + str = zpl_bprintf_va(fmt, va); + va_end(va); + return str; + } + + zpl_isize zpl_asprintf(zpl_allocator allocator, char **buffer, char const *fmt, ...) { + zpl_isize res; + va_list va; + va_start(va, fmt); + res = zpl_asprintf_va(allocator, buffer, fmt, va); + va_end(va); + return res; + } + + zpl_isize zpl_snprintf(char *str, zpl_isize n, char const *fmt, ...) { + zpl_isize res; + va_list va; + va_start(va, fmt); + res = zpl_snprintf_va(str, n, fmt, va); + va_end(va); + return res; + } + + + enum { + ZPL_FMT_MINUS = ZPL_BIT(0), + ZPL_FMT_PLUS = ZPL_BIT(1), + ZPL_FMT_ALT = ZPL_BIT(2), + ZPL_FMT_SPACE = ZPL_BIT(3), + ZPL_FMT_ZERO = ZPL_BIT(4), + + ZPL_FMT_CHAR = ZPL_BIT(5), + ZPL_FMT_SHORT = ZPL_BIT(6), + ZPL_FMT_INT = ZPL_BIT(7), + ZPL_FMT_LONG = ZPL_BIT(8), + ZPL_FMT_LLONG = ZPL_BIT(9), + ZPL_FMT_SIZE = ZPL_BIT(10), + ZPL_FMT_INTPTR = ZPL_BIT(11), + + ZPL_FMT_UNSIGNED = ZPL_BIT(12), + ZPL_FMT_LOWER = ZPL_BIT(13), + ZPL_FMT_UPPER = ZPL_BIT(14), + ZPL_FMT_WIDTH = ZPL_BIT(15), + + ZPL_FMT_DONE = ZPL_BIT(30), + + ZPL_FMT_INTS = + ZPL_FMT_CHAR | ZPL_FMT_SHORT | ZPL_FMT_INT | + ZPL_FMT_LONG | ZPL_FMT_LLONG | ZPL_FMT_SIZE | ZPL_FMT_INTPTR + }; + + typedef struct { + zpl_i32 base; + zpl_i32 flags; + zpl_i32 width; + zpl_i32 precision; + } zpl__format_info; + + zpl_internal zpl_isize zpl__print_string(char *text, zpl_isize max_len, zpl__format_info *info, char const *str) { + zpl_isize res = 0, len = 0; + zpl_isize remaining = max_len; + char *begin = text; + + if (str == NULL && max_len >= 6) { + res += zpl_strlcpy(text, "(null)", 6); + return res; + } + + if (info && info->precision >= 0) + len = zpl_strnlen(str, info->precision); + else + len = zpl_strlen(str); + + if (info && (info->width == 0 && info->flags & ZPL_FMT_WIDTH)) { + return res; + } + + if (info && (info->width == 0 || info->flags & ZPL_FMT_MINUS)) { + if (info->precision > 0) len = info->precision < len ? info->precision : len; + if (res+len > max_len) return res; + res += zpl_strlcpy(text, str, len); + text += res; + + if (info->width > res) { + zpl_isize padding = info->width - len; + + char pad = (info->flags & ZPL_FMT_ZERO) ? '0' : ' '; + while (padding-- > 0 && remaining-- > 0) *text++ = pad, res++; + } + } else { + if (info && (info->width > res)) { + zpl_isize padding = info->width - len; + char pad = (info->flags & ZPL_FMT_ZERO) ? '0' : ' '; + while (padding-- > 0 && remaining-- > 0) *text++ = pad, res++; + } + + if (res+len > max_len) return res; + res += zpl_strlcpy(text, str, len); + } + + if (info) { + if (info->flags & ZPL_FMT_UPPER) + zpl_str_to_upper(begin); + else if (info->flags & ZPL_FMT_LOWER) + zpl_str_to_lower(begin); + } + + return res; + } + + zpl_internal zpl_isize zpl__print_char(char *text, zpl_isize max_len, zpl__format_info *info, char arg) { + char str[2] = ""; + str[0] = arg; + return zpl__print_string(text, max_len, info, str); + } + + zpl_internal zpl_isize zpl__print_repeated_char(char *text, zpl_isize max_len, zpl__format_info *info, char arg) { + zpl_isize res = 0; + zpl_i32 rem = (info) ? (info->width > 0) ? info->width : 1 : 1; + res = rem; + while (rem-- > 0) *text++ = arg; + + return res; + } + + zpl_internal zpl_isize zpl__print_i64(char *text, zpl_isize max_len, zpl__format_info *info, zpl_i64 value) { + char num[130]; + zpl_i64_to_str(value, num, info ? info->base : 10); + return zpl__print_string(text, max_len, info, num); + } + + zpl_internal zpl_isize zpl__print_u64(char *text, zpl_isize max_len, zpl__format_info *info, zpl_u64 value) { + char num[130]; + zpl_u64_to_str(value, num, info ? info->base : 10); + return zpl__print_string(text, max_len, info, num); + } + + zpl_internal zpl_isize zpl__print_f64(char *text, zpl_isize max_len, zpl__format_info *info, zpl_b32 is_hexadecimal, zpl_f64 arg) { + // TODO: Handle exponent notation + zpl_isize width, len, remaining = max_len; + char *text_begin = text; + + if (arg) { + zpl_u64 value; + if (arg < 0) { + if (remaining > 1) *text = '-', remaining--; + text++; + arg = -arg; + } else if (info->flags & ZPL_FMT_MINUS) { + if (remaining > 1) *text = '+', remaining--; + text++; + } + + value = cast(zpl_u64) arg; + len = zpl__print_u64(text, remaining, NULL, value); + text += len; + + if (len >= remaining) + remaining = zpl_min(remaining, 1); + else + remaining -= len; + arg -= value; + + if (info->precision < 0) info->precision = 6; + + if ((info->flags & ZPL_FMT_ALT) || info->precision > 0) { + zpl_i64 mult = 10; + if (remaining > 1) *text = '.', remaining--; + text++; + while (info->precision-- > 0) { + value = cast(zpl_u64)(arg * mult); + len = zpl__print_u64(text, remaining, NULL, value); + text += len; + if (len >= remaining) + remaining = zpl_min(remaining, 1); + else + remaining -= len; + arg -= cast(zpl_f64) value / mult; + mult *= 10; + } + } + } else { + if (remaining > 1) *text = '0', remaining--; + text++; + if (info->flags & ZPL_FMT_ALT) { + if (remaining > 1) *text = '.', remaining--; + text++; + } + } + + width = info->width - (text - text_begin); + if (width > 0) { + char fill = (info->flags & ZPL_FMT_ZERO) ? '0' : ' '; + char *end = text + remaining - 1; + len = (text - text_begin); + + for (len = (text - text_begin); len--;) { + if ((text_begin + len + width) < end) *(text_begin + len + width) = *(text_begin + len); + } + + len = width; + text += len; + if (len >= remaining) + remaining = zpl_min(remaining, 1); + else + remaining -= len; + + while (len--) { + if (text_begin + len < end) text_begin[len] = fill; + } + } + + return (text - text_begin); + } + + ZPL_NEVER_INLINE zpl_isize zpl_snprintf_va(char *text, zpl_isize max_len, char const *fmt, va_list va) { + char const *text_begin = text; + zpl_isize remaining = max_len, res; + + while (*fmt) { + zpl__format_info info = { 0 }; + zpl_isize len = 0; + info.precision = -1; + + while (*fmt && *fmt != '%' && remaining) *text++ = *fmt++; + + if (*fmt == '%') { + do { + switch (*++fmt) { + case '-': {info.flags |= ZPL_FMT_MINUS; break;} + case '+': {info.flags |= ZPL_FMT_PLUS; break;} + case '#': {info.flags |= ZPL_FMT_ALT; break;} + case ' ': {info.flags |= ZPL_FMT_SPACE; break;} + case '0': {info.flags |= (ZPL_FMT_ZERO|ZPL_FMT_WIDTH); break;} + default: {info.flags |= ZPL_FMT_DONE; break;} + } + } while (!(info.flags & ZPL_FMT_DONE)); + } + + // NOTE: Optional Width + if (*fmt == '*') { + int width = va_arg(va, int); + if (width < 0) { + info.flags |= ZPL_FMT_MINUS; + info.width = -width; + } else { + info.width = width; + } + info.flags |= ZPL_FMT_WIDTH; + fmt++; + } else { + info.width = cast(zpl_i32) zpl_str_to_i64(fmt, cast(char **) & fmt, 10); + if (info.width != 0) { + info.flags |= ZPL_FMT_WIDTH; + } + } + + // NOTE: Optional Precision + if (*fmt == '.') { + fmt++; + if (*fmt == '*') { + info.precision = va_arg(va, int); + fmt++; + } else { + info.precision = cast(zpl_i32) zpl_str_to_i64(fmt, cast(char **) & fmt, 10); + } + info.flags &= ~ZPL_FMT_ZERO; + } + + switch (*fmt++) { + case 'h': + if (*fmt == 'h') { // hh => char + info.flags |= ZPL_FMT_CHAR; + fmt++; + } else { // h => short + info.flags |= ZPL_FMT_SHORT; + } + break; + + case 'l': + if (*fmt == 'l') { // ll => long long + info.flags |= ZPL_FMT_LLONG; + fmt++; + } else { // l => long + info.flags |= ZPL_FMT_LONG; + } + break; + + break; + + case 'z': // NOTE: zpl_usize + info.flags |= ZPL_FMT_UNSIGNED; + // fallthrough + case 't': // NOTE: zpl_isize + info.flags |= ZPL_FMT_SIZE; + break; + + default: fmt--; break; + } + + switch (*fmt) { + case 'u': + info.flags |= ZPL_FMT_UNSIGNED; + // fallthrough + case 'd': + case 'i': info.base = 10; break; + + case 'o': info.base = 8; break; + + case 'x': + info.base = 16; + info.flags |= (ZPL_FMT_UNSIGNED | ZPL_FMT_LOWER); + break; + + case 'X': + info.base = 16; + info.flags |= (ZPL_FMT_UNSIGNED | ZPL_FMT_UPPER); + break; + + case 'f': + case 'F': + case 'g': + case 'G': len = zpl__print_f64(text, remaining, &info, 0, va_arg(va, zpl_f64)); break; + + case 'a': + case 'A': + len = zpl__print_f64(text, remaining, &info, 1, va_arg(va, zpl_f64)); break; + + case 'c': len = zpl__print_char(text, remaining, &info, cast(char) va_arg(va, int)); break; + + case 's': len = zpl__print_string(text, remaining, &info, va_arg(va, char *)); break; + + case 'r': len = zpl__print_repeated_char(text, remaining, &info, va_arg(va, int)); break; + + case 'p': + info.base = 16; + info.flags |= (ZPL_FMT_LOWER | ZPL_FMT_UNSIGNED | ZPL_FMT_ALT | ZPL_FMT_INTPTR); + break; + + case '%': len = zpl__print_char(text, remaining, &info, '%'); break; + + default: fmt--; break; + } + + fmt++; + + if (info.base != 0) { + if (info.flags & ZPL_FMT_UNSIGNED) { + zpl_u64 value = 0; + switch (info.flags & ZPL_FMT_INTS) { + case ZPL_FMT_CHAR: value = cast(zpl_u64) cast(zpl_u8) va_arg(va, int); break; + case ZPL_FMT_SHORT: value = cast(zpl_u64) cast(zpl_u16) va_arg(va, int); break; + case ZPL_FMT_LONG: value = cast(zpl_u64) va_arg(va, unsigned long); break; + case ZPL_FMT_LLONG: value = cast(zpl_u64) va_arg(va, unsigned long long); break; + case ZPL_FMT_SIZE: value = cast(zpl_u64) va_arg(va, zpl_usize); break; + case ZPL_FMT_INTPTR: value = cast(zpl_u64) va_arg(va, zpl_uintptr); break; + default: value = cast(zpl_u64) va_arg(va, unsigned int); break; + } + + len = zpl__print_u64(text, remaining, &info, value); + + } else { + zpl_i64 value = 0; + switch (info.flags & ZPL_FMT_INTS) { + case ZPL_FMT_CHAR: value = cast(zpl_i64) cast(zpl_i8) va_arg(va, int); break; + case ZPL_FMT_SHORT: value = cast(zpl_i64) cast(zpl_i16) va_arg(va, int); break; + case ZPL_FMT_LONG: value = cast(zpl_i64) va_arg(va, long); break; + case ZPL_FMT_LLONG: value = cast(zpl_i64) va_arg(va, long long); break; + case ZPL_FMT_SIZE: value = cast(zpl_i64) va_arg(va, zpl_usize); break; + case ZPL_FMT_INTPTR: value = cast(zpl_i64) va_arg(va, zpl_uintptr); break; + default: value = cast(zpl_i64) va_arg(va, int); break; + } + + len = zpl__print_i64(text, remaining, &info, value); + } + } + + text += len; + if (len >= remaining) + remaining = zpl_min(remaining, 1); + else + remaining -= len; + } + + *text++ = '\0'; + res = (text - text_begin); + return (res >= max_len || res < 0) ? -1 : res; + } + + ZPL_END_C_DECLS + // file: source/core/time.c + + + #if defined(ZPL_SYSTEM_MACOS) || ZPL_SYSTEM_UNIX + # include + # include + #endif + + #if defined(ZPL_SYSTEM_MACOS) + # include + # include + # include + #endif + + #if defined(ZPL_SYSTEM_EMSCRIPTEN) + # include + #endif + + #if defined(ZPL_SYSTEM_WINDOWS) + # include + #endif + + ZPL_BEGIN_C_DECLS + + //! @} + //$$ + //////////////////////////////////////////////////////////////// + // + // Time + // + // + + #if defined(ZPL_COMPILER_MSVC) && !defined(__clang__) + zpl_u64 zpl_rdtsc(void) { return __rdtsc( ); } + #elif defined(__i386__) + zpl_u64 zpl_rdtsc(void) { + zpl_u64 x; + __asm__ volatile(".byte 0x0f, 0x31" : "=A"(x)); + return x; + } + #elif defined(__x86_64__) + zpl_u64 zpl_rdtsc(void) { + zpl_u32 hi, lo; + __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); + return (cast(zpl_u64) lo) | ((cast(zpl_u64) hi) << 32); + } + #elif defined(__powerpc__) + zpl_u64 zpl_rdtsc(void) { + zpl_u64 result = 0; + zpl_u32 upper, lower, tmp; + __asm__ volatile("0: \n" + "\tmftbu %0 \n" + "\tmftb %1 \n" + "\tmftbu %2 \n" + "\tcmpw %2,%0 \n" + "\tbne 0b \n" + : "=r"(upper), "=r"(lower), "=r"(tmp)); + result = upper; + result = result << 32; + result = result | lower; + + return result; + } + #elif defined(ZPL_SYSTEM_EMSCRIPTEN) + zpl_u64 zpl_rdtsc(void) { + return (zpl_u64)(emscripten_get_now() * 1e+6); + } + #elif defined(ZPL_CPU_ARM) && !defined(ZPL_COMPILER_TINYC) + zpl_u64 zpl_rdtsc(void) { + # if defined(__aarch64__) + int64_t r = 0; + asm volatile("mrs %0, cntvct_el0" : "=r"(r)); + # elif (__ARM_ARCH >= 6) + uint32_t r = 0; + uint32_t pmccntr; + uint32_t pmuseren; + uint32_t pmcntenset; + + // Read the user mode perf monitor counter access permissions. + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren)); + if (pmuseren & 1) { // Allows reading perfmon counters for user mode code. + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset)); + if (pmcntenset & 0x80000000ul) { // Is it counting? + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr)); + // The counter is set up to count every 64th cycle + return ((int64_t)pmccntr) * 64; // Should optimize to << 6 + } + } + # else + # error "No suitable method for zpl_rdtsc for this cpu type" + # endif + + return r; + } + #else + zpl_u64 zpl_rdtsc(void) { + ZPL_PANIC("zpl_rdtsc is not supported on this particular setup"); + return -0; + } + #endif + + #if defined(ZPL_SYSTEM_WINDOWS) || defined(ZPL_SYSTEM_CYGWIN) + + zpl_u64 zpl_time_rel_ms(void) { + zpl_local_persist LARGE_INTEGER win32_perf_count_freq = { 0 }; + zpl_u64 result; + LARGE_INTEGER counter; + zpl_local_persist LARGE_INTEGER win32_perf_counter = { 0 }; + if (!win32_perf_count_freq.QuadPart) { + QueryPerformanceFrequency(&win32_perf_count_freq); + ZPL_ASSERT(win32_perf_count_freq.QuadPart != 0); + QueryPerformanceCounter(&win32_perf_counter); + } + + QueryPerformanceCounter(&counter); + + result = (counter.QuadPart - win32_perf_counter.QuadPart) * 1000 / (win32_perf_count_freq.QuadPart); + return result; + } + + zpl_u64 zpl_time_utc_ms(void) { + FILETIME ft; + ULARGE_INTEGER li; + + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + return li.QuadPart / 1000; + } + + zpl_u64 zpl_time_tz_ms(void) { + FILETIME ft; + SYSTEMTIME st, lst; + ULARGE_INTEGER li; + + GetSystemTime(&st); + SystemTimeToTzSpecificLocalTime(NULL, &st, &lst); + SystemTimeToFileTime(&lst, &ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + return li.QuadPart / 1000; + } + + void zpl_sleep_ms(zpl_u32 ms) { Sleep(ms); } + + #else + + # if defined(ZPL_SYSTEM_LINUX) || defined(ZPL_SYSTEM_FREEBSD) || defined(ZPL_SYSTEM_OPENBSD) || defined(ZPL_SYSTEM_EMSCRIPTEN) + zpl_u64 zpl__unix_gettime(void) { + struct timespec t; + zpl_u64 result; + + clock_gettime(1 /*CLOCK_MONOTONIC*/, &t); + result = 1000 * t.tv_sec + 1.0e-6 * t.tv_nsec; + return result; + } + # endif + + zpl_u64 zpl_time_rel_ms(void) { + # if defined(ZPL_SYSTEM_OSX) + zpl_u64 result; + + zpl_local_persist zpl_u64 timebase = 0; + zpl_local_persist zpl_u64 timestart = 0; + + if (!timestart) { + mach_timebase_info_data_t tb = { 0 }; + mach_timebase_info(&tb); + timebase = tb.numer; + timebase /= tb.denom; + timestart = mach_absolute_time(); + } + + // NOTE: mach_absolute_time() returns things in nanoseconds + result = 1.0e-6 * (mach_absolute_time() - timestart) * timebase; + return result; + # else + zpl_local_persist zpl_u64 unix_timestart = 0.0; + + if (!unix_timestart) { unix_timestart = zpl__unix_gettime( ); } + + zpl_u64 now = zpl__unix_gettime( ); + + return (now - unix_timestart); + # endif + } + + zpl_u64 zpl_time_utc_ms(void) { + struct timespec t; + # if defined(ZPL_SYSTEM_OSX) + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self( ), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self( ), cclock); + t.tv_sec = mts.tv_sec; + t.tv_nsec = mts.tv_nsec; + # else + clock_gettime(0 /*CLOCK_REALTIME*/, &t); + # endif + return ((zpl_u64)t.tv_sec * 1000 + t.tv_nsec * 1e-6 + ZPL__UNIX_TO_WIN32_EPOCH); + } + + void zpl_sleep_ms(zpl_u32 ms) { + struct timespec req = { cast(time_t)(ms * 1e-3), cast(long)((ms % 1000) * 1e6) }; + struct timespec rem = { 0, 0 }; + nanosleep(&req, &rem); + } + + zpl_u64 zpl_time_tz_ms(void) { + struct tm t; + zpl_u64 result = zpl_time_utc_ms() - ZPL__UNIX_TO_WIN32_EPOCH; + zpl_u16 ms = result % 1000; + result *= 1e-3; + localtime_r((const time_t*)&result, &t); + result = (zpl_u64)mktime(&t); + return (result - timezone + t.tm_isdst * 3600) * 1000 + ms + ZPL__UNIX_TO_WIN32_EPOCH; + } + #endif + + zpl_f64 zpl_time_rel(void) { + return (zpl_f64)(zpl_time_rel_ms() * 1e-3); + } + + zpl_f64 zpl_time_utc(void) { + return (zpl_f64)(zpl_time_utc_ms() * 1e-3); + } + + zpl_f64 zpl_time_tz(void) { + return (zpl_f64)(zpl_time_tz_ms() * 1e-3); + } + + + + ZPL_END_C_DECLS + // file: source/core/random.c + + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_MODULE_THREADING) + zpl_global zpl_atomic32 zpl__random_shared_counter = {0}; + #else + zpl_global zpl_i32 zpl__random_shared_counter = 0; + #endif + + zpl_internal zpl_u32 zpl__get_noise_from_time(void) { + zpl_u32 accum = 0; + zpl_f64 start, remaining, end, curr = 0; + zpl_u64 interval = 100000ll; + + start = zpl_time_rel(); + remaining = (interval - cast(zpl_u64)(interval*start)%interval) / cast(zpl_f64)interval; + end = start + remaining; + + do { + curr = zpl_time_rel(); + accum += cast(zpl_u32)curr; + } while (curr >= end); + return accum; + } + + // NOTE: Partly from http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/ + // But the generation is even more random-er-est + + zpl_internal ZPL_ALWAYS_INLINE zpl_u32 zpl__permute_qpr(zpl_u32 x) { + zpl_local_persist zpl_u32 const prime = 4294967291; // 2^32 - 5 + if (x >= prime) { + return x; + } else { + zpl_u32 residue = cast(zpl_u32)(cast(zpl_u64) x * x) % prime; + if (x <= prime / 2) + return residue; + else + return prime - residue; + } + } + + zpl_internal ZPL_ALWAYS_INLINE zpl_u32 zpl__permute_with_offset(zpl_u32 x, zpl_u32 offset) { + return (zpl__permute_qpr(x) + offset) ^ 0x5bf03635; + } + + + void zpl_random_init(zpl_random *r) { + zpl_u64 time, tick; + zpl_isize i, j; + zpl_u32 x = 0; + r->value = 0; + + r->offsets[0] = zpl__get_noise_from_time(); + #ifdef ZPL_MODULE_THREADING + r->offsets[1] = zpl_atomic32_fetch_add(&zpl__random_shared_counter, 1); + r->offsets[2] = zpl_thread_current_id(); + r->offsets[3] = zpl_thread_current_id() * 3 + 1; + #else + r->offsets[1] = zpl__random_shared_counter++; + r->offsets[2] = 0; + r->offsets[3] = 1; + #endif + time = zpl_time_tz_ms(); + r->offsets[4] = cast(zpl_u32)(time >> 32); + r->offsets[5] = cast(zpl_u32)time; + r->offsets[6] = zpl__get_noise_from_time(); + tick = zpl_rdtsc(); + r->offsets[7] = cast(zpl_u32)(tick ^ (tick >> 32)); + + for (j = 0; j < 4; j++) { + for (i = 0; i < zpl_count_of(r->offsets); i++) { + r->offsets[i] = x = zpl__permute_with_offset(x, r->offsets[i]); + } + } + } + + zpl_u32 zpl_random_gen_u32(zpl_random *r) { + zpl_u32 x = r->value; + zpl_u32 carry = 1; + zpl_isize i; + for (i = 0; i < zpl_count_of(r->offsets); i++) { + x = zpl__permute_with_offset(x, r->offsets[i]); + if (carry > 0) { + carry = ++r->offsets[i] ? 0 : 1; + } + } + + r->value = x; + return x; + } + + zpl_u32 zpl_random_gen_u32_unique(zpl_random *r) { + zpl_u32 x = r->value; + zpl_isize i; + r->value++; + for (i = 0; i < zpl_count_of(r->offsets); i++) { + x = zpl__permute_with_offset(x, r->offsets[i]); + } + + return x; + } + + zpl_u64 zpl_random_gen_u64(zpl_random *r) { + return ((cast(zpl_u64)zpl_random_gen_u32(r)) << 32) | zpl_random_gen_u32(r); + } + + + zpl_isize zpl_random_gen_isize(zpl_random *r) { + #if defined(ZPL_ARCH_32_BIT) + zpl_u32 u = zpl_random_gen_u32(r); + #else + zpl_u64 u = zpl_random_gen_u64(r); + #endif + zpl_isize i; + zpl_memcopy(&i, &u, zpl_size_of(u)); + return i; + } + + + zpl_i64 zpl_random_range_i64(zpl_random *r, zpl_i64 lower_inc, zpl_i64 higher_inc) { + zpl_u64 u = zpl_random_gen_u64(r); + zpl_i64 diff = higher_inc-lower_inc+1; + u %= diff; + zpl_i64 i; + zpl_memcopy(&i, &u, zpl_size_of(u)); + i += lower_inc; + return i; + } + + zpl_isize zpl_random_range_isize(zpl_random *r, zpl_isize lower_inc, zpl_isize higher_inc) { + #if defined(ZPL_ARCH_32_BIT) + zpl_u32 u = zpl_random_gen_u32(r); + #else + zpl_u64 u = zpl_random_gen_u64(r); + #endif + zpl_isize diff = higher_inc-lower_inc+1; + u %= diff; + zpl_isize i; + zpl_memcopy(&i, &u, zpl_size_of(u)); + i += lower_inc; + return i; + } + + ZPL_ALWAYS_INLINE zpl_f64 zpl__random_copy_sign64(zpl_f64 x, zpl_f64 y) { + zpl_i64 ix=0, iy=0; + zpl_memcopy(&ix, &x, zpl_size_of(zpl_i64)); + zpl_memcopy(&iy, &y, zpl_size_of(zpl_i64)); + + ix &= 0x7fffffffffffffff; + ix |= iy & 0x8000000000000000; + + zpl_f64 r = 0.0; + zpl_memcopy(&r, &ix, zpl_size_of(zpl_f64)); + return r; + } + + zpl_f64 zpl_random_range_f64(zpl_random *r, zpl_f64 lower_inc, zpl_f64 higher_inc) { + zpl_f64 f = cast(zpl_f64)zpl_random_gen_u64(r) / cast(zpl_f64)ZPL_U64_MAX; + zpl_f64 diff = higher_inc-lower_inc; + + f *= diff; + f += lower_inc; + return f; + } + + ZPL_END_C_DECLS + // file: source/core/misc.c + + + ZPL_BEGIN_C_DECLS + + void zpl_yield(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + Sleep(0); + # else + sched_yield(); + # endif + } + + const char *zpl_get_env(const char *name) { + char *buffer = NULL; + const char *ptr = zpl_get_env_buf(name); + + if (ptr == NULL) { + return NULL; + } + + zpl_isize ptr_size = zpl_strlen(ptr); + buffer = (char *)zpl_malloc(ptr_size * sizeof(char)+1); + zpl_memcopy((char *)buffer, ptr, ptr_size+1); + return buffer; + } + + const char *zpl_get_env_buf(const char *name) { + # ifdef ZPL_SYSTEM_WINDOWS + zpl_local_persist wchar_t wbuffer[32767] = {0}; + zpl_local_persist char buffer[32767] = {0}; + + if (!GetEnvironmentVariableW( + cast(LPCWSTR)zpl_utf8_to_ucs2_buf(cast(const zpl_u8 *)name), + cast(LPWSTR)wbuffer, 32767)) { + return NULL; + } + + zpl_ucs2_to_utf8(cast(zpl_u8*)buffer, 32767, cast(const zpl_u16*)wbuffer); + + return (const char *)buffer; + # else + return (const char *)getenv(name); + # endif + } + + zpl_string zpl_get_env_str(const char *name) { + const char *buf = zpl_get_env_buf(name); + + if (buf == NULL) { + return NULL; + } + + zpl_string str = zpl_string_make(zpl_heap(), buf); + return str; + } + + void zpl_set_env(const char *name, const char *value) { + # if defined(ZPL_SYSTEM_WINDOWS) + SetEnvironmentVariableA(name, value); + # else + setenv(name, value, 1); + # endif + } + + void zpl_unset_env(const char *name) { + # if defined(ZPL_SYSTEM_WINDOWS) + SetEnvironmentVariableA(name, NULL); + # else + unsetenv(name); + # endif + } + + #if !defined(ZPL_SYSTEM_WINDOWS) + extern char **environ; + #endif + + zpl_u32 zpl_system_command(const char *command, zpl_usize buffer_len, char *buffer) { + # if defined(ZPL_SYSTEM_EMSCRIPTEN) + ZPL_PANIC("zpl_system_command not supported"); + # else + + # if defined(ZPL_SYSTEM_WINDOWS) + FILE *handle = _popen(command, "r"); + # else + FILE *handle = popen(command, "r"); + # endif + + if(!handle) return 0; + + int c; + zpl_usize i=0; + while ((c = getc(handle)) != EOF && i++ < buffer_len) { + *buffer++ = c; + } + + # if defined(ZPL_SYSTEM_WINDOWS) + _pclose(handle); + # else + pclose(handle); + # endif + + # endif + + return 1; + } + + zpl_string zpl_system_command_str(const char *command, zpl_allocator backing) { + # if defined(ZPL_SYSTEM_EMSCRIPTEN) + ZPL_PANIC("zpl_system_command not supported"); + # else + + # if defined(ZPL_SYSTEM_WINDOWS) + FILE *handle = _popen(command, "r"); + # else + FILE *handle = popen(command, "r"); + # endif + + if(!handle) return NULL; + + zpl_string output = zpl_string_make_reserve(backing, 4); + + int c; + while ((c = getc(handle)) != EOF) { + char ins[2] = {(char)c,0}; + output = zpl_string_appendc(output, ins); + } + + # if defined(ZPL_SYSTEM_WINDOWS) + _pclose(handle); + # else + pclose(handle); + # endif + return output; + # endif + return NULL; + } + + ZPL_END_C_DECLS + // file: source/core/sort.c + + + ZPL_BEGIN_C_DECLS + + #define ZPL__COMPARE_PROC(Type) \ + zpl_global zpl_isize Type##__cmp_offset; \ + ZPL_COMPARE_PROC(Type##__cmp) { \ + Type const p = *cast(Type const *) zpl_pointer_add_const(a, Type##__cmp_offset); \ + Type const q = *cast(Type const *) zpl_pointer_add_const(b, Type##__cmp_offset); \ + return p < q ? -1 : p > q; \ + } \ + ZPL_COMPARE_PROC_PTR(Type##_cmp(zpl_isize offset)) { \ + Type##__cmp_offset = offset; \ + return &Type##__cmp; \ + } + + ZPL__COMPARE_PROC(zpl_u8); + ZPL__COMPARE_PROC(zpl_i16); + ZPL__COMPARE_PROC(zpl_i32); + ZPL__COMPARE_PROC(zpl_i64); + ZPL__COMPARE_PROC(zpl_isize); + ZPL__COMPARE_PROC(zpl_f32); + ZPL__COMPARE_PROC(zpl_f64); + + // NOTE: str_cmp is special as it requires a funny type and funny comparison + zpl_global zpl_isize zpl__str_cmp_offset; + ZPL_COMPARE_PROC(zpl__str_cmp) { + char const *p = *cast(char const **) zpl_pointer_add_const(a, zpl__str_cmp_offset); + char const *q = *cast(char const **) zpl_pointer_add_const(b, zpl__str_cmp_offset); + return zpl_strcmp(p, q); + } + ZPL_COMPARE_PROC_PTR(zpl_str_cmp(zpl_isize offset)) { + zpl__str_cmp_offset = offset; + return &zpl__str_cmp; + } + + #undef ZPL__COMPARE_PROC + + // TODO: Make user definable? + #define ZPL__SORT_STACK_SIZE 64 + #define zpl__SORT_INSERT_SORT_TRESHOLD 8 + + #define ZPL__SORT_PUSH(_base, _limit) \ + do { \ + stack_ptr[0] = (_base); \ + stack_ptr[1] = (_limit); \ + stack_ptr += 2; \ + } while (0) + + #define ZPL__SORT_POP(_base, _limit) \ + do { \ + stack_ptr -= 2; \ + (_base) = stack_ptr[0]; \ + (_limit) = stack_ptr[1]; \ + } while (0) + + void zpl_sort(void *base_, zpl_isize count, zpl_isize size, zpl_compare_proc cmp) { + zpl_u8 *i, *j; + zpl_u8 *base = cast(zpl_u8 *) base_; + zpl_u8 *limit = base + count * size; + zpl_isize threshold = zpl__SORT_INSERT_SORT_TRESHOLD * size; + + // NOTE: Prepare the stack + zpl_u8 *stack[ZPL__SORT_STACK_SIZE] = { 0 }; + zpl_u8 **stack_ptr = stack; + + for (;;) { + if ((limit - base) > threshold) { + // NOTE: Quick sort + i = base + size; + j = limit - size; + + zpl_memswap(((limit - base) / size / 2) * size + base, base, size); + if (cmp(i, j) > 0) zpl_memswap(i, j, size); + if (cmp(base, j) > 0) zpl_memswap(base, j, size); + if (cmp(i, base) > 0) zpl_memswap(i, base, size); + + for (;;) { + do + i += size; + while (cmp(i, base) < 0); + do + j -= size; + while (cmp(j, base) > 0); + if (i > j) break; + zpl_memswap(i, j, size); + } + + zpl_memswap(base, j, size); + + if (j - base > limit - i) { + ZPL__SORT_PUSH(base, j); + base = i; + } else { + ZPL__SORT_PUSH(i, limit); + limit = j; + } + } else { + // NOTE: Insertion sort + for (j = base, i = j + size; i < limit; j = i, i += size) { + for (; cmp(j, j + size) > 0; j -= size) { + zpl_memswap(j, j + size, size); + if (j == base) break; + } + } + + if (stack_ptr == stack) break; // NOTE: Sorting is done! + ZPL__SORT_POP(base, limit); + } + } + } + + #undef ZPL__SORT_PUSH + #undef ZPL__SORT_POP + + #define ZPL_RADIX_SORT_PROC_GEN(Type) \ + ZPL_RADIX_SORT_PROC(Type) { \ + zpl_##Type *source = items; \ + zpl_##Type *dest = temp; \ + zpl_isize byte_index, i, byte_max = 8 * zpl_size_of(zpl_##Type); \ + for (byte_index = 0; byte_index < byte_max; byte_index += 8) { \ + zpl_isize offsets[256] = { 0 }; \ + zpl_isize total = 0; \ + /* NOTE: First pass - count how many of each key */ \ + for (i = 0; i < count; i++) { \ + zpl_##Type radix_value = source[i]; \ + zpl_##Type radix_piece = (radix_value >> byte_index) & 0xff; \ + offsets[radix_piece]++; \ + } \ + /* NOTE: Change counts to offsets */ \ + for (i = 0; i < zpl_count_of(offsets); i++) { \ + zpl_isize skcount = offsets[i]; \ + offsets[i] = total; \ + total += skcount; \ + } \ + /* NOTE: Second pass - place elements into the right location */ \ + for (i = 0; i < count; i++) { \ + zpl_##Type radix_value = source[i]; \ + zpl_##Type radix_piece = (radix_value >> byte_index) & 0xff; \ + dest[offsets[radix_piece]++] = source[i]; \ + } \ + zpl_swap(zpl_##Type *, source, dest); \ + } \ + } + + ZPL_RADIX_SORT_PROC_GEN(u8); + ZPL_RADIX_SORT_PROC_GEN(u16); + ZPL_RADIX_SORT_PROC_GEN(u32); + ZPL_RADIX_SORT_PROC_GEN(u64); + + void zpl_shuffle(void *base, zpl_isize count, zpl_isize size) { + zpl_u8 *a; + zpl_isize i, j; + zpl_random random; + zpl_random_init(&random); + + a = cast(zpl_u8 *) base + (count - 1) * size; + for (i = count; i > 1; i--) { + j = zpl_random_gen_isize(&random) % i; + zpl_memswap(a, cast(zpl_u8 *) base + j * size, size); + a -= size; + } + } + + void zpl_reverse(void *base, zpl_isize count, zpl_isize size) { + zpl_isize i, j = count - 1; + for (i = 0; i < j; i++, j++) zpl_memswap(cast(zpl_u8 *) base + i * size, cast(zpl_u8 *) base + j * size, size); + } + + ZPL_END_C_DECLS +# endif +#endif + +#if defined(ZPL_MODULE_HASHING) + // file: source/hashing.c + + //////////////////////////////////////////////////////////////// + // + // Hashing functions + // + // + + ZPL_BEGIN_C_DECLS + + zpl_u32 zpl_adler32(void const *data, zpl_isize len) { + zpl_u32 const MOD_ALDER = 65521; + zpl_u32 a = 1, b = 0; + zpl_isize i, block_len; + zpl_u8 const *bytes = cast(zpl_u8 const *) data; + + block_len = len % 5552; + + while (len) { + for (i = 0; i + 7 < block_len; i += 8) { + a += bytes[0], b += a; + a += bytes[1], b += a; + a += bytes[2], b += a; + a += bytes[3], b += a; + a += bytes[4], b += a; + a += bytes[5], b += a; + a += bytes[6], b += a; + a += bytes[7], b += a; + + bytes += 8; + } + for (; i < block_len; i++) a += *bytes++, b += a; + + a %= MOD_ALDER, b %= MOD_ALDER; + len -= block_len; + block_len = 5552; + } + + return (b << 16) | a; + } + + zpl_global zpl_u32 const zpl__crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, + 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, + 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, + 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, + 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, + 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, + 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, + 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, + 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, + 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; + + zpl_global zpl_u64 const zpl__crc64_table[256] = { + 0x0000000000000000ull, 0x7ad870c830358979ull, 0xf5b0e190606b12f2ull, 0x8f689158505e9b8bull, 0xc038e5739841b68full, 0xbae095bba8743ff6ull, + 0x358804e3f82aa47dull, 0x4f50742bc81f2d04ull, 0xab28ecb46814fe75ull, 0xd1f09c7c5821770cull, 0x5e980d24087fec87ull, 0x24407dec384a65feull, + 0x6b1009c7f05548faull, 0x11c8790fc060c183ull, 0x9ea0e857903e5a08ull, 0xe478989fa00bd371ull, 0x7d08ff3b88be6f81ull, 0x07d08ff3b88be6f8ull, + 0x88b81eabe8d57d73ull, 0xf2606e63d8e0f40aull, 0xbd301a4810ffd90eull, 0xc7e86a8020ca5077ull, 0x4880fbd87094cbfcull, 0x32588b1040a14285ull, + 0xd620138fe0aa91f4ull, 0xacf86347d09f188dull, 0x2390f21f80c18306ull, 0x594882d7b0f40a7full, 0x1618f6fc78eb277bull, 0x6cc0863448deae02ull, + 0xe3a8176c18803589ull, 0x997067a428b5bcf0ull, 0xfa11fe77117cdf02ull, 0x80c98ebf2149567bull, + 0x0fa11fe77117cdf0ull, 0x75796f2f41224489ull, 0x3a291b04893d698dull, 0x40f16bccb908e0f4ull, 0xcf99fa94e9567b7full, 0xb5418a5cd963f206ull, + 0x513912c379682177ull, 0x2be1620b495da80eull, 0xa489f35319033385ull, 0xde51839b2936bafcull, 0x9101f7b0e12997f8ull, 0xebd98778d11c1e81ull, + 0x64b116208142850aull, 0x1e6966e8b1770c73ull, 0x8719014c99c2b083ull, 0xfdc17184a9f739faull, 0x72a9e0dcf9a9a271ull, 0x08719014c99c2b08ull, + 0x4721e43f0183060cull, 0x3df994f731b68f75ull, 0xb29105af61e814feull, 0xc849756751dd9d87ull, 0x2c31edf8f1d64ef6ull, 0x56e99d30c1e3c78full, + 0xd9810c6891bd5c04ull, 0xa3597ca0a188d57dull, 0xec09088b6997f879ull, 0x96d1784359a27100ull, 0x19b9e91b09fcea8bull, 0x636199d339c963f2ull, + 0xdf7adabd7a6e2d6full, 0xa5a2aa754a5ba416ull, 0x2aca3b2d1a053f9dull, 0x50124be52a30b6e4ull, 0x1f423fcee22f9be0ull, 0x659a4f06d21a1299ull, + 0xeaf2de5e82448912ull, 0x902aae96b271006bull, 0x74523609127ad31aull, 0x0e8a46c1224f5a63ull, 0x81e2d7997211c1e8ull, 0xfb3aa75142244891ull, + 0xb46ad37a8a3b6595ull, 0xceb2a3b2ba0eececull, 0x41da32eaea507767ull, 0x3b024222da65fe1eull, 0xa2722586f2d042eeull, 0xd8aa554ec2e5cb97ull, + 0x57c2c41692bb501cull, 0x2d1ab4dea28ed965ull, 0x624ac0f56a91f461ull, 0x1892b03d5aa47d18ull, 0x97fa21650afae693ull, 0xed2251ad3acf6feaull, + 0x095ac9329ac4bc9bull, 0x7382b9faaaf135e2ull, 0xfcea28a2faafae69ull, 0x8632586aca9a2710ull, 0xc9622c4102850a14ull, 0xb3ba5c8932b0836dull, + 0x3cd2cdd162ee18e6ull, 0x460abd1952db919full, 0x256b24ca6b12f26dull, 0x5fb354025b277b14ull, 0xd0dbc55a0b79e09full, 0xaa03b5923b4c69e6ull, + 0xe553c1b9f35344e2ull, 0x9f8bb171c366cd9bull, 0x10e3202993385610ull, 0x6a3b50e1a30ddf69ull, 0x8e43c87e03060c18ull, 0xf49bb8b633338561ull, + 0x7bf329ee636d1eeaull, 0x012b592653589793ull, 0x4e7b2d0d9b47ba97ull, 0x34a35dc5ab7233eeull, 0xbbcbcc9dfb2ca865ull, 0xc113bc55cb19211cull, + 0x5863dbf1e3ac9decull, 0x22bbab39d3991495ull, 0xadd33a6183c78f1eull, 0xd70b4aa9b3f20667ull, 0x985b3e827bed2b63ull, 0xe2834e4a4bd8a21aull, + 0x6debdf121b863991ull, 0x1733afda2bb3b0e8ull, 0xf34b37458bb86399ull, 0x8993478dbb8deae0ull, 0x06fbd6d5ebd3716bull, 0x7c23a61ddbe6f812ull, + 0x3373d23613f9d516ull, 0x49aba2fe23cc5c6full, 0xc6c333a67392c7e4ull, 0xbc1b436e43a74e9dull, 0x95ac9329ac4bc9b5ull, 0xef74e3e19c7e40ccull, + 0x601c72b9cc20db47ull, 0x1ac40271fc15523eull, 0x5594765a340a7f3aull, 0x2f4c0692043ff643ull, 0xa02497ca54616dc8ull, 0xdafce7026454e4b1ull, + 0x3e847f9dc45f37c0ull, 0x445c0f55f46abeb9ull, 0xcb349e0da4342532ull, 0xb1eceec59401ac4bull, 0xfebc9aee5c1e814full, 0x8464ea266c2b0836ull, + 0x0b0c7b7e3c7593bdull, 0x71d40bb60c401ac4ull, 0xe8a46c1224f5a634ull, 0x927c1cda14c02f4dull, 0x1d148d82449eb4c6ull, 0x67ccfd4a74ab3dbfull, + 0x289c8961bcb410bbull, 0x5244f9a98c8199c2ull, 0xdd2c68f1dcdf0249ull, 0xa7f41839ecea8b30ull, 0x438c80a64ce15841ull, 0x3954f06e7cd4d138ull, + 0xb63c61362c8a4ab3ull, 0xcce411fe1cbfc3caull, 0x83b465d5d4a0eeceull, 0xf96c151de49567b7ull, 0x76048445b4cbfc3cull, 0x0cdcf48d84fe7545ull, + 0x6fbd6d5ebd3716b7ull, 0x15651d968d029fceull, 0x9a0d8ccedd5c0445ull, 0xe0d5fc06ed698d3cull, 0xaf85882d2576a038ull, 0xd55df8e515432941ull, + 0x5a3569bd451db2caull, 0x20ed197575283bb3ull, 0xc49581ead523e8c2ull, 0xbe4df122e51661bbull, 0x3125607ab548fa30ull, 0x4bfd10b2857d7349ull, + 0x04ad64994d625e4dull, 0x7e7514517d57d734ull, 0xf11d85092d094cbfull, 0x8bc5f5c11d3cc5c6ull, 0x12b5926535897936ull, 0x686de2ad05bcf04full, + 0xe70573f555e26bc4ull, 0x9ddd033d65d7e2bdull, 0xd28d7716adc8cfb9ull, 0xa85507de9dfd46c0ull, 0x273d9686cda3dd4bull, 0x5de5e64efd965432ull, + 0xb99d7ed15d9d8743ull, 0xc3450e196da80e3aull, 0x4c2d9f413df695b1ull, 0x36f5ef890dc31cc8ull, 0x79a59ba2c5dc31ccull, 0x037deb6af5e9b8b5ull, + 0x8c157a32a5b7233eull, 0xf6cd0afa9582aa47ull, 0x4ad64994d625e4daull, 0x300e395ce6106da3ull, 0xbf66a804b64ef628ull, 0xc5bed8cc867b7f51ull, + 0x8aeeace74e645255ull, 0xf036dc2f7e51db2cull, 0x7f5e4d772e0f40a7ull, 0x05863dbf1e3ac9deull, 0xe1fea520be311aafull, 0x9b26d5e88e0493d6ull, + 0x144e44b0de5a085dull, 0x6e963478ee6f8124ull, 0x21c640532670ac20ull, 0x5b1e309b16452559ull, 0xd476a1c3461bbed2ull, 0xaeaed10b762e37abull, + 0x37deb6af5e9b8b5bull, 0x4d06c6676eae0222ull, 0xc26e573f3ef099a9ull, 0xb8b627f70ec510d0ull, 0xf7e653dcc6da3dd4ull, 0x8d3e2314f6efb4adull, + 0x0256b24ca6b12f26ull, 0x788ec2849684a65full, 0x9cf65a1b368f752eull, 0xe62e2ad306bafc57ull, 0x6946bb8b56e467dcull, 0x139ecb4366d1eea5ull, + 0x5ccebf68aecec3a1ull, 0x2616cfa09efb4ad8ull, 0xa97e5ef8cea5d153ull, 0xd3a62e30fe90582aull, 0xb0c7b7e3c7593bd8ull, 0xca1fc72bf76cb2a1ull, + 0x45775673a732292aull, 0x3faf26bb9707a053ull, 0x70ff52905f188d57ull, 0x0a2722586f2d042eull, 0x854fb3003f739fa5ull, 0xff97c3c80f4616dcull, + 0x1bef5b57af4dc5adull, 0x61372b9f9f784cd4ull, 0xee5fbac7cf26d75full, 0x9487ca0fff135e26ull, 0xdbd7be24370c7322ull, 0xa10fceec0739fa5bull, + 0x2e675fb4576761d0ull, 0x54bf2f7c6752e8a9ull, 0xcdcf48d84fe75459ull, 0xb71738107fd2dd20ull, 0x387fa9482f8c46abull, 0x42a7d9801fb9cfd2ull, + 0x0df7adabd7a6e2d6ull, 0x772fdd63e7936bafull, 0xf8474c3bb7cdf024ull, 0x829f3cf387f8795dull, 0x66e7a46c27f3aa2cull, 0x1c3fd4a417c62355ull, + 0x935745fc4798b8deull, 0xe98f353477ad31a7ull, 0xa6df411fbfb21ca3ull, 0xdc0731d78f8795daull, 0x536fa08fdfd90e51ull, 0x29b7d047efec8728ull, + }; + + zpl_u32 zpl_crc32(void const *data, zpl_isize len) { + zpl_isize remaining; + zpl_u32 result = ~(cast(zpl_u32) 0); + zpl_u8 const *c = cast(zpl_u8 const *) data; + for (remaining = len; remaining--; c++) result = (result >> 8) ^ (zpl__crc32_table[(result ^ *c) & 0xff]); + return ~result; + } + + zpl_u64 zpl_crc64(void const *data, zpl_isize len) { + zpl_isize remaining; + zpl_u64 result = (cast(zpl_u64)0); + zpl_u8 const *c = cast(zpl_u8 const *) data; + for (remaining = len; remaining--; c++) result = (result >> 8) ^ (zpl__crc64_table[(result ^ *c) & 0xff]); + return result; + } + + zpl_u32 zpl_fnv32(void const *data, zpl_isize len) { + zpl_isize i; + zpl_u32 h = 0x811c9dc5; + zpl_u8 const *c = cast(zpl_u8 const *) data; + + for (i = 0; i < len; i++) h = (h * 0x01000193) ^ c[i]; + + return h; + } + + zpl_u64 zpl_fnv64(void const *data, zpl_isize len) { + zpl_isize i; + zpl_u64 h = 0xcbf29ce484222325ull; + zpl_u8 const *c = cast(zpl_u8 const *) data; + + for (i = 0; i < len; i++) h = (h * 0x100000001b3ll) ^ c[i]; + + return h; + } + + zpl_u32 zpl_fnv32a(void const *data, zpl_isize len) { + zpl_isize i; + zpl_u32 h = 0x811c9dc5; + zpl_u8 const *c = cast(zpl_u8 const *) data; + + for (i = 0; i < len; i++) h = (h ^ c[i]) * 0x01000193; + + return h; + } + + zpl_u64 zpl_fnv64a(void const *data, zpl_isize len) { + zpl_isize i; + zpl_u64 h = 0xcbf29ce484222325ull; + zpl_u8 const *c = cast(zpl_u8 const *) data; + + for (i = 0; i < len; i++) h = (h ^ c[i]) * 0x100000001b3ll; + + return h; + } + + // base64 implementation based on https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/ + // + zpl_global zpl_u8 zpl__base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + + /* generated table based on: */ + #if 0 + void zpl__base64_decode_table() { + zpl_i32 inv[80]; + zpl_isize i; + + zpl_memset(inv, -1, zpl_size_of(inv)); + + for (i=0; i < zpl_size_of(zpl__base64_chars)-1; i++) { + inv[zpl__base64_chars[i]-43] = i; + } + } + #endif + /* === */ + zpl_global zpl_i32 zpl__base64_dec_table[] = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51 }; + + zpl_isize zpl__base64_encoded_size(zpl_isize len) { + zpl_isize ret = len; + + if (len % 3 != 0) { + ret += 3 - (len % 3); + } + + ret /= 3; + ret *= 4; + + return ret; + } + + zpl_isize zpl__base64_decoded_size(void const *data) { + zpl_isize len, ret, i; + const zpl_u8 *s = cast(const zpl_u8 *)data; + + if (s == NULL) { + return 0; + } + + len = zpl_strlen(cast(const char*)s); + ret = len / 4 * 3; + + for (i=len; i-- > 0;) { + if (s[i] == '=') { + ret--; + } else { + break; + } + } + + return ret; + } + + zpl_b32 zpl__base64_valid_char(zpl_u8 c) { + if (c >= '0' && c <= '9') + return true; + if (c >= 'A' && c <= 'Z') + return true; + if (c >= 'a' && c <= 'z') + return true; + if (c == '+' || c == '/' || c == '=') + return true; + + return false; + } + + zpl_u8 *zpl_base64_encode(zpl_allocator a, void const *data, zpl_isize len) { + const zpl_u8 *s = cast(const zpl_u8*)data; + zpl_u8 *ret = NULL; + zpl_isize enc_len, i, j, v; + + if (data == NULL || len == 0) { + return NULL; + } + + enc_len = zpl__base64_encoded_size(len); + ret = cast(zpl_u8 *)zpl_alloc(a, enc_len+1); + ret[enc_len] = 0; + + for (i=0, j=0; i < len; i+=3, j+=4) { + v = s[i]; + v = (i+1 < len) ? (v << 8 | s[i+1]) : (v << 8); + v = (i+2 < len) ? (v << 8 | s[i+2]) : (v << 8); + + ret[j] = zpl__base64_chars[(v >> 18) & 0x3F]; + ret[j+1] = zpl__base64_chars[(v >> 12) & 0x3F]; + + if (i+1 < len) + ret[j+2] = zpl__base64_chars[(v >> 6) & 0x3F]; + + else ret[j+2] = '='; + + if (i+2 < len) + ret[j+3] = zpl__base64_chars[v & 0x3F]; + + else ret[j+3] = '='; + + } + + return ret; + } + + zpl_u8 *zpl_base64_decode(zpl_allocator a, void const *data, zpl_isize len) { + const zpl_u8 *s = cast(const zpl_u8*)data; + zpl_u8 *ret = NULL; + zpl_isize alen, i, j, v; + + if (data == NULL) { + return NULL; + } + + alen = zpl__base64_decoded_size(s); + ret = cast(zpl_u8 *)zpl_alloc(a, alen+1); + + ZPL_ASSERT_NOT_NULL(ret); + + ret[alen] = 0; + + for (i=0; i> 16) & 0xFF; + + if (s[i+2] != '=') + ret[j+1] = (v >> 8) & 0xFF; + + if (s[i+3] != '=') + ret[j+2] = v & 0xFF; + } + + return ret; + } + + zpl_u32 zpl_murmur32_seed(void const *data, zpl_isize len, zpl_u32 seed) { + zpl_u32 const c1 = 0xcc9e2d51; + zpl_u32 const c2 = 0x1b873593; + zpl_u32 const r1 = 15; + zpl_u32 const r2 = 13; + zpl_u32 const m = 5; + zpl_u32 const n = 0xe6546b64; + + zpl_isize i, nblocks = len / 4; + zpl_u32 hash = seed, k1 = 0; + zpl_u32 const *blocks = cast(zpl_u32 const *) data; + zpl_u8 const *tail = cast(zpl_u8 const *)(data) + nblocks * 4; + + for (i = 0; i < nblocks; i++) { + zpl_u32 k = blocks[i]; + k *= c1; + k = (k << r1) | (k >> (32 - r1)); + k *= c2; + + hash ^= k; + hash = ((hash << r2) | (hash >> (32 - r2))) * m + n; + } + + switch (len & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: + k1 ^= tail[0]; + + k1 *= c1; + k1 = (k1 << r1) | (k1 >> (32 - r1)); + k1 *= c2; + hash ^= k1; + } + + hash ^= len; + hash ^= (hash >> 16); + hash *= 0x85ebca6b; + hash ^= (hash >> 13); + hash *= 0xc2b2ae35; + hash ^= (hash >> 16); + + return hash; + } + + zpl_u64 zpl_murmur64_seed(void const *data_, zpl_isize len, zpl_u64 seed) { + zpl_u64 const m = 0xc6a4a7935bd1e995ULL; + zpl_i32 const r = 47; + + zpl_u64 h = seed ^ (len * m); + + zpl_u64 const *data = cast(zpl_u64 const *) data_; + zpl_u8 const *data2 = cast(zpl_u8 const *) data_; + zpl_u64 const *end = data + (len / 8); + + while (data != end) { + zpl_u64 k = *data++; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + switch (len & 7) { + case 7: h ^= cast(zpl_u64)(data2[6]) << 48; + case 6: h ^= cast(zpl_u64)(data2[5]) << 40; + case 5: h ^= cast(zpl_u64)(data2[4]) << 32; + case 4: h ^= cast(zpl_u64)(data2[3]) << 24; + case 3: h ^= cast(zpl_u64)(data2[2]) << 16; + case 2: h ^= cast(zpl_u64)(data2[1]) << 8; + case 1: h ^= cast(zpl_u64)(data2[0]); + h *= m; + }; + + h ^= h >> r; + h *= m; + h ^= h >> r; + + return h; + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_REGEX) + // file: source/regex.c + + + ZPL_BEGIN_C_DECLS + + typedef enum zplreOp { + ZPL_RE_OP_BEGIN_CAPTURE, + ZPL_RE_OP_END_CAPTURE, + + ZPL_RE_OP_BEGINNING_OF_LINE, + ZPL_RE_OP_END_OF_LINE, + + ZPL_RE_OP_EXACT_MATCH, + ZPL_RE_OP_META_MATCH, + + ZPL_RE_OP_ANY, + ZPL_RE_OP_ANY_OF, + ZPL_RE_OP_ANY_BUT, + + ZPL_RE_OP_ZERO_OR_MORE, + ZPL_RE_OP_ONE_OR_MORE, + ZPL_RE_OP_ZERO_OR_MORE_SHORTEST, + ZPL_RE_OP_ONE_OR_MORE_SHORTEST, + ZPL_RE_OP_ZERO_OR_ONE, + + ZPL_RE_OP_BRANCH_START, + ZPL_RE_OP_BRANCH_END + } zplreOp; + + typedef enum zplreCode { + ZPL_RE_CODE_NULL = 0x0000, + ZPL_RE_CODE_WHITESPACE = 0x0100, + ZPL_RE_CODE_NOT_WHITESPACE = 0x0200, + ZPL_RE_CODE_DIGIT = 0x0300, + ZPL_RE_CODE_NOT_DIGIT = 0x0400, + ZPL_RE_CODE_ALPHA = 0x0500, + ZPL_RE_CODE_LOWER = 0x0600, + ZPL_RE_CODE_UPPER = 0x0700, + ZPL_RE_CODE_WORD = 0x0800, + ZPL_RE_CODE_NOT_WORD = 0x0900, + + ZPL_RE_CODE_XDIGIT = 0x0a00, + ZPL_RE_CODE_PRINTABLE = 0x0b00, + } zplreCode; + + typedef struct { + zpl_isize op, offset; + } zpl_re_ctx; + + enum { + ZPL_RE__NO_MATCH = -1, + ZPL_RE__INTERNAL_FAILURE = -2, + }; + + static char const ZPL_RE__META_CHARS[] = "^$()[].*+?|\\"; + static char const ZPL_RE__WHITESPACE[] = " \r\t\n\v\f"; + #define ZPL_RE__LITERAL(str) (str), zpl_size_of(str)-1 + + static zpl_re_ctx zpl_re__exec_single(zpl_re *re, zpl_isize op, char const *str, zpl_isize str_len, zpl_isize offset, zpl_re_capture *captures, zpl_isize max_capture_count); + static zpl_re_ctx zpl_re__exec(zpl_re *re, zpl_isize op, char const *str, zpl_isize str_len, zpl_isize offset, zpl_re_capture *captures, zpl_isize max_capture_count); + + static zpl_re_ctx zpl_re__ctx_no_match(zpl_isize op) { + zpl_re_ctx c; + c.op = op; + c.offset = ZPL_RE__NO_MATCH; + return c; + } + + static zpl_re_ctx zpl_re__ctx_internal_failure(zpl_isize op) { + zpl_re_ctx c; + c.op = op; + c.offset = ZPL_RE__INTERNAL_FAILURE; + return c; + } + + static zpl_u8 zpl_re__hex(char const *s) { + return ((zpl_char_to_hex_digit(*s) << 4) & 0xf0) | (zpl_char_to_hex_digit(*(s+1)) & 0x0f); + } + + static zpl_isize zpl_re__strfind(char const *s, zpl_isize len, char c, zpl_isize offset) { + if (offset < len) { + char const *found = (char const *)zpl_memchr(s+offset, c, len-offset); + if (found) + return found - s; + } + + return -1; + } + + static zpl_b32 zpl_re__match_escape(char c, int code) { + switch (code) { + case ZPL_RE_CODE_NULL: return c == 0; + case ZPL_RE_CODE_WHITESPACE: return zpl_re__strfind(ZPL_RE__LITERAL(ZPL_RE__WHITESPACE), c, 0) >= 0; + case ZPL_RE_CODE_NOT_WHITESPACE: return zpl_re__strfind(ZPL_RE__LITERAL(ZPL_RE__WHITESPACE), c, 0) < 0; + case ZPL_RE_CODE_DIGIT: return (c >= '0' && c <= '9'); + case ZPL_RE_CODE_NOT_DIGIT: return !(c >= '0' && c <= '9'); + case ZPL_RE_CODE_ALPHA: return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + case ZPL_RE_CODE_LOWER: return (c >= 'a' && c <= 'z'); + case ZPL_RE_CODE_UPPER: return (c >= 'A' && c <= 'Z'); + + /* TODO(bill): Make better? */ + case ZPL_RE_CODE_WORD: return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'; + case ZPL_RE_CODE_NOT_WORD: return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'); + + /* TODO(bill): Maybe replace with between tests? */ + case ZPL_RE_CODE_XDIGIT: return zpl_re__strfind(ZPL_RE__LITERAL("0123456789ABCDEFabcdef"), c, 0) >= 0; + case ZPL_RE_CODE_PRINTABLE: return c >= 0x20 && c <= 0x7e; + default: break; + } + + return 0; + } + + static zpl_re_ctx zpl_re__consume(zpl_re *re, zpl_isize op, char const *str, zpl_isize str_len, zpl_isize offset, zpl_re_capture *captures, zpl_isize max_capture_count, zpl_b32 is_greedy) + { + zpl_re_ctx c, best_c, next_c; + + c.op = op; + c.offset = offset; + + best_c.op = ZPL_RE__NO_MATCH; + best_c.offset = offset; + + for (;;) { + c = zpl_re__exec_single(re, op, str, str_len, c.offset, 0, 0); + if (c.offset > str_len || c.offset == -1) break; + if (c.op >= re->buf_len) return c; + + next_c = zpl_re__exec(re, c.op, str, str_len, c.offset, captures, max_capture_count); + if (next_c.offset <= str_len) { + if (captures) + zpl_re__exec(re, c.op, str, str_len, c.offset, captures, max_capture_count); + + best_c = next_c; + if (!is_greedy) break; + } + + if (best_c.op > re->buf_len) + best_c.op = c.op; + + } + + return best_c; + } + + static zpl_re_ctx zpl_re__exec_single(zpl_re *re, zpl_isize op, char const *str, zpl_isize str_len, zpl_isize offset, zpl_re_capture *captures, zpl_isize max_capture_count) { + zpl_re_ctx ctx; + zpl_isize buffer_len; + zpl_isize match_len; + zpl_isize next_op; + zpl_isize skip; + + switch (re->buf[op++]) { + case ZPL_RE_OP_BEGIN_CAPTURE: { + zpl_u8 capture = re->buf[op++]; + if (captures && (capture < max_capture_count)) + captures[capture].str = str + offset; + } break; + + case ZPL_RE_OP_END_CAPTURE: { + zpl_u8 capture = re->buf[op++]; + if (captures && (capture < max_capture_count)) + captures[capture].len = (str + offset) - captures[capture].str; + } break; + + case ZPL_RE_OP_BEGINNING_OF_LINE: { + if (offset != 0) + return zpl_re__ctx_no_match(op); + } break; + + case ZPL_RE_OP_END_OF_LINE: { + if (offset != str_len) + return zpl_re__ctx_no_match(op); + } break; + + case ZPL_RE_OP_BRANCH_START: { + skip = re->buf[op++]; + ctx = zpl_re__exec(re, op, str, str_len, offset, captures, max_capture_count); + if (ctx.offset <= str_len) { + offset = ctx.offset; + op = ctx.op; + } else { + ctx = zpl_re__exec(re, op + skip, str, str_len, offset, captures, max_capture_count); + offset = ctx.offset; + op = ctx.op; + } + } break; + + case ZPL_RE_OP_BRANCH_END: { + skip = re->buf[op++]; + op += skip; + } break; + + case ZPL_RE_OP_ANY: { + if (offset < str_len) { + offset++; + break; + } + return zpl_re__ctx_no_match(op); + } break; + + case ZPL_RE_OP_ANY_OF: { + zpl_isize i; + char cin = str[offset]; + buffer_len = re->buf[op++]; + + if (offset >= str_len) + return zpl_re__ctx_no_match(op + buffer_len); + + for (i = 0; i < buffer_len; i++) { + char cmatch = (char)re->buf[op+i]; + if (!cmatch) { + i++; + if (zpl_re__match_escape(cin, re->buf[op+i] << 8)) + break; + } else if (cin == cmatch) { + break; + } + } + + if (i == buffer_len) + return zpl_re__ctx_no_match(op + buffer_len); + + offset++; + op += buffer_len; + } break; + + case ZPL_RE_OP_ANY_BUT: { + zpl_isize i; + char cin = str[offset]; + buffer_len = re->buf[op++]; + + if (offset >= str_len) + return zpl_re__ctx_no_match(op + buffer_len); + + for (i = 0; i < buffer_len; i++) { + char cmatch = (char)re->buf[op + i]; + if (!cmatch) { + i++; + if (zpl_re__match_escape(cin, re->buf[op+i] << 8)) + return zpl_re__ctx_no_match(op + buffer_len); + } else if (cin == cmatch) { + return zpl_re__ctx_no_match(op + buffer_len); + } + } + + offset++; + op += buffer_len; + } break; + + case ZPL_RE_OP_EXACT_MATCH: { + match_len = re->buf[op++]; + + if ((match_len > (str_len - offset)) || + zpl_strncmp(str+offset, (const char*)re->buf + op, match_len) != 0) + return zpl_re__ctx_no_match(op + match_len); + + op += match_len; + offset += match_len; + } break; + + case ZPL_RE_OP_META_MATCH: { + char cin = (char)re->buf[op++]; + char cmatch = str[offset++]; + + if (!cin) { + if (zpl_re__match_escape(cmatch, re->buf[op++] << 8)) + break; + } + else if (cin == cmatch) break; + + return zpl_re__ctx_no_match(op); + } break; + + case ZPL_RE_OP_ZERO_OR_MORE: { + ctx = zpl_re__consume(re, op, str, str_len, offset, captures, max_capture_count, 1); + offset = ctx.offset; + op = ctx.op; + } break; + + case ZPL_RE_OP_ONE_OR_MORE: { + ctx = zpl_re__exec_single(re, op, str, str_len, offset, captures, max_capture_count); + + if (ctx.offset > str_len) + return ctx; + + ctx = zpl_re__consume(re, op, str, str_len, offset, captures, max_capture_count, 1); + offset = ctx.offset; + op = ctx.op; + } break; + + case ZPL_RE_OP_ZERO_OR_MORE_SHORTEST: { + ctx = zpl_re__consume(re, op, str, str_len, offset, captures, max_capture_count, 0); + offset = ctx.offset; + op = ctx.op; + } break; + + case ZPL_RE_OP_ONE_OR_MORE_SHORTEST: { + ctx = zpl_re__exec_single(re, op, str, str_len, offset, captures, max_capture_count); + + if (ctx.offset > str_len) + return ctx; + + ctx = zpl_re__consume(re, op, str, str_len, offset, captures, max_capture_count, 0); + offset = ctx.offset; + op = ctx.op; + } break; + + case ZPL_RE_OP_ZERO_OR_ONE: { + ctx = zpl_re__exec_single(re, op, str, str_len, offset, captures, max_capture_count); + + if (ctx.offset <= str_len) { + zpl_re_ctx possible_ctx = zpl_re__exec(re, ctx.op, str, str_len, ctx.offset, captures, max_capture_count); + + if (possible_ctx.offset <= str_len) { + op = possible_ctx.op; + offset = possible_ctx.offset; + break; + } + } + + next_op = ctx.op; + ctx = zpl_re__exec(re, next_op, str, str_len, offset, captures, max_capture_count); + + if (ctx.offset <= str_len) { + op = ctx.op; + offset = ctx.offset; + break; + } + return zpl_re__ctx_no_match(op); + } break; + + default: { + return zpl_re__ctx_internal_failure(op); + } break; + } + + ctx.op = op; + ctx.offset = offset; + + return ctx; + } + + static zpl_re_ctx zpl_re__exec(zpl_re *re, zpl_isize op, char const *str, zpl_isize str_len, zpl_isize offset, zpl_re_capture *captures, zpl_isize max_capture_count) { + zpl_re_ctx c; + c.op = op; + c.offset = offset; + + while (c.op < re->buf_len) { + c = zpl_re__exec_single(re, c.op, str, str_len, c.offset, captures, max_capture_count); + + if (c.offset > str_len || c.offset == -1) + break; + } + + return c; + } + + static zpl_regex_error zpl_re__emit_ops(zpl_re *re, zpl_isize op_count, ...) { + va_list va; + + if (re->buf_len + op_count > re->buf_cap) { + if (!re->can_realloc) { + return ZPL_RE_ERROR_TOO_LONG; + } + else { + zpl_isize new_cap = (re->buf_cap*2) + op_count; + re->buf = (char *)zpl_resize(re->backing, re->buf, re->buf_cap, new_cap); + re->buf_cap = new_cap; + } + } + + va_start(va, op_count); + for (zpl_isize i = 0; i < op_count; i++) + { + zpl_i32 v = va_arg(va, zpl_i32); + if (v > 256) + return ZPL_RE_ERROR_TOO_LONG; + re->buf[re->buf_len++] = (char)v; + } + va_end(va); + + return ZPL_RE_ERROR_NONE; + } + + static zpl_regex_error zpl_re__emit_ops_buffer(zpl_re *re, zpl_isize op_count, char const *buffer) { + if (re->buf_len + op_count > re->buf_cap) { + if (!re->can_realloc) { + return ZPL_RE_ERROR_TOO_LONG; + } + else { + zpl_isize new_cap = (re->buf_cap*2) + op_count; + re->buf = (char *)zpl_resize(re->backing, re->buf, re->buf_cap, new_cap); + re->buf_cap = new_cap; + } + } + + for (zpl_isize i = 0; i < op_count; i++) + { + re->buf[re->buf_len++] = buffer[i]; + } + + return ZPL_RE_ERROR_NONE; + } + + static int zpl_re__encode_escape(char code) { + switch (code) { + default: break; /* NOTE(bill): It's a normal character */ + + /* TODO(bill): Are there anymore? */ + case 't': return '\t'; + case 'n': return '\n'; + case 'r': return '\r'; + case 'f': return '\f'; + case 'v': return '\v'; + + case '0': return ZPL_RE_CODE_NULL; + + case 's': return ZPL_RE_CODE_WHITESPACE; + case 'S': return ZPL_RE_CODE_NOT_WHITESPACE; + + case 'd': return ZPL_RE_CODE_DIGIT; + case 'D': return ZPL_RE_CODE_NOT_DIGIT; + + case 'a': return ZPL_RE_CODE_ALPHA; + case 'l': return ZPL_RE_CODE_LOWER; + case 'u': return ZPL_RE_CODE_UPPER; + + case 'w': return ZPL_RE_CODE_WORD; + case 'W': return ZPL_RE_CODE_NOT_WORD; + + case 'x': return ZPL_RE_CODE_XDIGIT; + case 'p': return ZPL_RE_CODE_PRINTABLE; + } + return code; + } + + static zpl_regex_error zpl_re__parse_group(zpl_re *re, char const *pattern, zpl_isize len, zpl_isize offset, zpl_isize *new_offset) { + zpl_regex_error err = ZPL_RE_ERROR_NONE; + char buffer[256] = {0}; + zpl_isize buffer_len = 0, buffer_cap = zpl_size_of(buffer); + zpl_b32 closed = 0; + zplreOp op = ZPL_RE_OP_ANY_OF; + + if (pattern[offset] == '^') { + offset++; + op = ZPL_RE_OP_ANY_BUT; + } + + while(!closed && + err == ZPL_RE_ERROR_NONE && + offset < len) + { + if (pattern[offset] == ']') { + err = zpl_re__emit_ops(re, 2, (zpl_i32)op, (zpl_i32)buffer_len); + if (err) break; + + err = zpl_re__emit_ops_buffer(re, buffer_len, (const char*)buffer); + if (err) break; + offset++; + closed = 1; + break; + } + + if (buffer_len >= buffer_cap) + return ZPL_RE_ERROR_TOO_LONG; + + if (pattern[offset] == '\\') { + offset++; + + if ((offset + 1 < len) && zpl_char_is_hex_digit(*(pattern+offset))) { + buffer[buffer_len++] = zpl_re__hex((pattern+offset)); + offset++; + } + else if (offset < len) { + zpl_i32 code = zpl_re__encode_escape(pattern[offset]); + + if (!code || code > 0xff) { + buffer[buffer_len++] = 0; + + if (buffer_len >= buffer_cap) + return ZPL_RE_ERROR_TOO_LONG; + + buffer[buffer_len++] = (code >> 8) & 0xff; + } + else { + buffer[buffer_len++] = code & 0xff; + } + } + } + else { + buffer[buffer_len++] = (unsigned char)pattern[offset]; + } + + offset++; + } + + if (err) return err; + if (!closed) return ZPL_RE_ERROR_MISMATCHED_BLOCKS; + if (new_offset) *new_offset = offset; + return ZPL_RE_ERROR_NONE; + } + + static zpl_regex_error zpl_re__compile_quantifier(zpl_re *re, zpl_isize last_buf_len, unsigned char quantifier) { + zpl_regex_error err; + zpl_isize move_size; + + if ((re->buf[last_buf_len] == ZPL_RE_OP_EXACT_MATCH) && + (re->buf[last_buf_len+1] > 1)) + { + unsigned char last_char = re->buf[re->buf_len-1]; + + re->buf[last_buf_len+1]--; + re->buf_len--; + err = zpl_re__emit_ops(re, 4, (zpl_i32)quantifier, (zpl_i32)ZPL_RE_OP_EXACT_MATCH, 1, (zpl_i32)last_char); + if (err) return err; + return ZPL_RE_ERROR_NONE; + } + + move_size = re->buf_len - last_buf_len + 1; + + err = zpl_re__emit_ops(re, 1, 0); + if (err) return err; + + zpl_memmove(re->buf+last_buf_len+1, re->buf+last_buf_len, move_size); + re->buf[last_buf_len] = quantifier; + + return ZPL_RE_ERROR_NONE; + } + + static zpl_regex_error zpl_re__parse(zpl_re *re, char const *pattern, zpl_isize len, zpl_isize offset, zpl_isize level, zpl_isize *new_offset) { + zpl_regex_error err = ZPL_RE_ERROR_NONE; + zpl_isize last_buf_len = re->buf_len; + zpl_isize branch_begin = re->buf_len; + zpl_isize branch_op = -1; + + while (offset < len) { + switch (pattern[offset++]) { + case '^': { + err = zpl_re__emit_ops(re, 1, ZPL_RE_OP_BEGINNING_OF_LINE); + if (err) return err; + } break; + + case '$': { + err = zpl_re__emit_ops(re, 1, ZPL_RE_OP_END_OF_LINE); + if (err) return err; + } break; + + case '(': { + zpl_isize capture = re->capture_count++; + last_buf_len = re->buf_len; + err = zpl_re__emit_ops(re, 2, ZPL_RE_OP_BEGIN_CAPTURE, (zpl_i32)capture); + if (err) return err; + + err = zpl_re__parse(re, pattern, len, offset, level+1, &offset); + + if ((offset > len) || (pattern[offset-1] != ')')) + return ZPL_RE_ERROR_MISMATCHED_CAPTURES; + + err = zpl_re__emit_ops(re, 2, ZPL_RE_OP_END_CAPTURE, (zpl_i32)capture); + if (err) return err; + } break; + + case ')': { + if (branch_op != -1) + re->buf[branch_op + 1] = (unsigned char)(re->buf_len - (branch_op+2)); + + if (level == 0) + return ZPL_RE_ERROR_MISMATCHED_CAPTURES; + + if (new_offset) *new_offset = offset; + return ZPL_RE_ERROR_NONE; + } break; + + case '[': { + last_buf_len = re->buf_len; + err = zpl_re__parse_group(re, pattern, len, offset, &offset); + if (offset > len) + return err; + } break; + + /* NOTE(bill): Branching magic! */ + case '|': { + if (branch_begin >= re->buf_len) { + return ZPL_RE_ERROR_BRANCH_FAILURE; + } else { + zpl_isize size = re->buf_len - branch_begin; + err = zpl_re__emit_ops(re, 4, 0, 0, ZPL_RE_OP_BRANCH_END, 0); + if (err) return err; + + zpl_memmove(re->buf + branch_begin + 2, re->buf + branch_begin, size); + re->buf[branch_begin] = ZPL_RE_OP_BRANCH_START; + re->buf[branch_begin+1] = (size+2) & 0xff; + branch_op = re->buf_len-2; + } + } break; + + case '.': { + last_buf_len = re->buf_len; + err = zpl_re__emit_ops(re, 1, ZPL_RE_OP_ANY); + if (err) return err; + } break; + + case '*': + case '+': + { + unsigned char quantifier = ZPL_RE_OP_ONE_OR_MORE; + if (pattern[offset-1] == '*') + quantifier = ZPL_RE_OP_ZERO_OR_MORE; + + if (last_buf_len >= re->buf_len) + return ZPL_RE_ERROR_INVALID_QUANTIFIER; + if ((re->buf[last_buf_len] < ZPL_RE_OP_EXACT_MATCH) || + (re->buf[last_buf_len] > ZPL_RE_OP_ANY_BUT)) + return ZPL_RE_ERROR_INVALID_QUANTIFIER; + + if ((offset < len) && (pattern[offset] == '?')) { + quantifier = ZPL_RE_OP_ONE_OR_MORE_SHORTEST; + offset++; + } + + err = zpl_re__compile_quantifier(re, last_buf_len, quantifier); + if (err) return err; + } break; + + case '?': { + if (last_buf_len >= re->buf_len) + return ZPL_RE_ERROR_INVALID_QUANTIFIER; + if ((re->buf[last_buf_len] < ZPL_RE_OP_EXACT_MATCH) || + (re->buf[last_buf_len] > ZPL_RE_OP_ANY_BUT)) + return ZPL_RE_ERROR_INVALID_QUANTIFIER; + + err = zpl_re__compile_quantifier(re, last_buf_len, + (unsigned char)ZPL_RE_OP_ZERO_OR_ONE); + if (err) return err; + } break; + + case '\\': { + last_buf_len = re->buf_len; + if ((offset+1 < len) && zpl_char_is_hex_digit(*(pattern+offset))) { + unsigned char hex_value = zpl_re__hex((pattern+offset)); + offset += 2; + err = zpl_re__emit_ops(re, 2, ZPL_RE_OP_META_MATCH, (int)hex_value); + if (err) return err; + } else if (offset < len) { + int code = zpl_re__encode_escape(pattern[offset++]); + if (!code || (code > 0xff)) { + err = zpl_re__emit_ops(re, 3, ZPL_RE_OP_META_MATCH, 0, (int)((code >> 8) & 0xff)); + if (err) return err; + } else { + err = zpl_re__emit_ops(re, 2, ZPL_RE_OP_META_MATCH, (int)code); + if (err) return err; + } + } + } break; + + /* NOTE(bill): Exact match */ + default: { + char const *match_start; + zpl_isize size = 0; + offset--; + match_start = pattern+offset; + while ((offset < len) && + (zpl_re__strfind(ZPL_RE__LITERAL(ZPL_RE__META_CHARS), pattern[offset], 0) < 0)) { + size++, offset++; + } + + last_buf_len = re->buf_len; + err = zpl_re__emit_ops(re, 2, ZPL_RE_OP_EXACT_MATCH, (int)size); + if (err) return err; + err = zpl_re__emit_ops_buffer(re, size, (char const *)match_start); + if (err) return err; + } break; + } + } + + if (new_offset) *new_offset = offset; + return ZPL_RE_ERROR_NONE; + } + + zpl_regex_error zpl_re_compile_from_buffer(zpl_re *re, char const *pattern, zpl_isize pattern_len, void *buffer, zpl_isize buffer_len) { + zpl_regex_error err; + re->capture_count = 0; + re->buf = (char *)buffer; + re->buf_len = 0; + re->buf_cap = re->buf_len; + re->can_realloc = 0; + + err = zpl_re__parse(re, pattern, pattern_len, 0, 0, 0); + return err; + } + + zpl_regex_error zpl_re_compile(zpl_re *re, zpl_allocator backing, char const *pattern, zpl_isize pattern_len) { + zpl_regex_error err; + zpl_isize cap = pattern_len+128; + zpl_isize offset = 0; + + re->backing = backing; + re->capture_count = 0; + re->buf = (char *)zpl_alloc(backing, cap); + re->buf_len = 0; + re->buf_cap = cap; + re->can_realloc = 1; + + err = zpl_re__parse(re, pattern, pattern_len, 0, 0, &offset); + + if (offset != pattern_len) + zpl_free(backing, re->buf); + + return err; + } + + zpl_isize zpl_re_capture_count(zpl_re *re) { return re->capture_count; } + + zpl_b32 zpl_re_match(zpl_re *re, char const *str, zpl_isize len, zpl_re_capture *captures, zpl_isize max_capture_count, zpl_isize *offset) { + if (re && re->buf_len > 0) { + if (re->buf[0] == ZPL_RE_OP_BEGINNING_OF_LINE) { + zpl_re_ctx c = zpl_re__exec(re, 0, str, len, 0, captures, max_capture_count); + if (c.offset >= 0 && c.offset <= len) { if (offset) *offset = c.offset; return 1; }; + if (c.offset == ZPL_RE__INTERNAL_FAILURE) return 0; + } else { + zpl_isize i; + for (i = 0; i < len; i++) { + zpl_re_ctx c = zpl_re__exec(re, 0, str, len, i, captures, max_capture_count); + if (c.offset >= 0 && c.offset <= len) { if (offset) *offset = c.offset; return 1; }; + if (c.offset == ZPL_RE__INTERNAL_FAILURE) return 0; + } + } + return 0; + } + return 1; + } + + + zpl_b32 zpl_re_match_all(zpl_re *re, char const *str, zpl_isize str_len, zpl_isize max_capture_count, + zpl_re_capture **out_captures) + { + char *end = (char *)str + str_len; + char *p = (char *)str; + + zpl_buffer_make(zpl_re_capture, cps, zpl_heap(), max_capture_count); + + zpl_isize offset = 0; + + while (p < end) + { + zpl_b32 ok = zpl_re_match(re, p, end - p, cps, max_capture_count, &offset); + if (!ok) { + zpl_buffer_free(cps); + return false; + } + + p += offset; + + for (zpl_isize i = 0; i < max_capture_count; i++) { + zpl_array_append(*out_captures, cps[i]); + } + } + + zpl_buffer_free(cps); + + return true; + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_DLL) + // file: source/dll.c + + + #if defined(ZPL_SYSTEM_UNIX) || defined(ZPL_SYSTEM_MACOS) + # include + #endif + + ZPL_BEGIN_C_DECLS + + //////////////////////////////////////////////////////////////// + // + // DLL Handling + // + // + + #if defined(ZPL_SYSTEM_WINDOWS) + zpl_dll_handle zpl_dll_load(char const *filepath) { + return cast(zpl_dll_handle) LoadLibraryA(filepath); + } + + void zpl_dll_unload(zpl_dll_handle dll) { + FreeLibrary(cast(HMODULE) dll); + } + + zpl_dll_proc zpl_dll_proc_address(zpl_dll_handle dll, char const *proc_name) { + return cast(zpl_dll_proc) GetProcAddress(cast(HMODULE) dll, proc_name); + } + + #else // POSIX + + zpl_dll_handle zpl_dll_load(char const *filepath) { + return cast(zpl_dll_handle) dlopen(filepath, RTLD_LAZY | RTLD_GLOBAL); + } + + void zpl_dll_unload(zpl_dll_handle dll) { + dlclose(dll); + } + + zpl_dll_proc zpl_dll_proc_address(zpl_dll_handle dll, char const *proc_name) { + return cast(zpl_dll_proc) dlsym(dll, proc_name); + } + + #endif + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_OPTS) + // file: source/opts.c + + //////////////////////////////////////////////////////////////// + // + // CLI Options + // + // + + ZPL_BEGIN_C_DECLS + + void zpl_opts_init(zpl_opts *opts, zpl_allocator a, char const *app) { + zpl_opts opts_ = { 0 }; + *opts = opts_; + opts->alloc = a; + opts->appname = app; + + zpl_array_init(opts->entries, a); + zpl_array_init(opts->positioned, a); + zpl_array_init(opts->errors, a); + } + + void zpl_opts_free(zpl_opts *opts) { + for (zpl_i32 i = 0; i < zpl_array_count(opts->entries); ++i) { + zpl_opts_entry *e = opts->entries + i; + if (e->type == ZPL_OPTS_STRING) { + zpl_string_free(e->text); + } + } + + zpl_array_free(opts->entries); + zpl_array_free(opts->positioned); + zpl_array_free(opts->errors); + } + + void zpl_opts_add(zpl_opts *opts, char const *name, char const *lname, const char *desc, zpl_u8 type) { + zpl_opts_entry e = { 0 }; + + e.name = name; + e.lname = lname; + e.desc = desc; + e.type = type; + e.met = false; + e.pos = false; + + zpl_array_append(opts->entries, e); + } + + zpl_opts_entry *zpl__opts_find(zpl_opts *opts, char const *name, zpl_usize len, zpl_b32 longname) { + zpl_opts_entry *e = 0; + + for (int i = 0; i < zpl_array_count(opts->entries); ++i) { + e = opts->entries + i; + char const *n = (longname ? e->lname : e->name); + if(!n) continue; + + if (zpl_strnlen(name, len) == zpl_strlen(n) && !zpl_strncmp(n, name, len)) { return e; } + } + + return NULL; + } + + void zpl_opts_positional_add(zpl_opts *opts, char const *name) { + zpl_opts_entry *e = zpl__opts_find(opts, name, zpl_strlen(name), true); + + if (e) { + e->pos = true; + zpl_array_append_at(opts->positioned, e, 0); + } + } + + zpl_b32 zpl_opts_positionals_filled(zpl_opts *opts) { return zpl_array_count(opts->positioned) == 0; } + + zpl_string zpl_opts_string(zpl_opts *opts, char const *name, char const *fallback) { + zpl_opts_entry *e = zpl__opts_find(opts, name, zpl_strlen(name), true); + + return (char *)((e && e->met) ? e->text : fallback); + } + + zpl_f64 zpl_opts_real(zpl_opts *opts, char const *name, zpl_f64 fallback) { + zpl_opts_entry *e = zpl__opts_find(opts, name, zpl_strlen(name), true); + + return (e && e->met) ? e->real : fallback; + } + + zpl_i64 zpl_opts_integer(zpl_opts *opts, char const *name, zpl_i64 fallback) { + zpl_opts_entry *e = zpl__opts_find(opts, name, zpl_strlen(name), true); + + return (e && e->met) ? e->integer : fallback; + } + + void zpl__opts_set_value(zpl_opts *opts, zpl_opts_entry *t, char *b) { + t->met = true; + + switch (t->type) { + case ZPL_OPTS_STRING: { + t->text = zpl_string_make(opts->alloc, b); + } break; + + case ZPL_OPTS_FLOAT: { + t->real = zpl_str_to_f64(b, NULL); + } break; + + case ZPL_OPTS_INT: { + t->integer = zpl_str_to_i64(b, NULL, 10); + } break; + } + + for (zpl_isize i=0; i < zpl_array_count(opts->positioned); i++) { + if (!zpl_strcmp(opts->positioned[i]->lname, t->lname)) { + zpl_array_remove_at(opts->positioned, i); + break; + } + } + } + + zpl_b32 zpl_opts_has_arg(zpl_opts *opts, char const *name) { + zpl_opts_entry *e = zpl__opts_find(opts, name, zpl_strlen(name), true); + + if (e) { return e->met; } + + return false; + } + + void zpl_opts_print_help(zpl_opts *opts) { + zpl_printf("USAGE: %s", opts->appname); + + for (zpl_isize i = zpl_array_count(opts->entries); i >= 0; --i) { + zpl_opts_entry *e = opts->entries + i; + + if (e->pos == (zpl_b32) true) { zpl_printf(" [%s]", e->lname); } + } + + zpl_printf("\nOPTIONS:\n"); + + for (zpl_isize i = 0; i < zpl_array_count(opts->entries); ++i) { + zpl_opts_entry *e = opts->entries + i; + + if(e->name) { + if(e->lname) { zpl_printf("\t-%s, --%s: %s\n", e->name, e->lname, e->desc); } + else { zpl_printf("\t-%s: %s\n", e->name, e->desc); } + } else { zpl_printf("\t--%s: %s\n", e->lname, e->desc); } + } + } + + void zpl_opts_print_errors(zpl_opts *opts) { + for (int i = 0; i < zpl_array_count(opts->errors); ++i) { + zpl_opts_err *err = (opts->errors + i); + + zpl_printf("ERROR: "); + + switch (err->type) { + case ZPL_OPTS_ERR_OPTION: zpl_printf("Invalid option \"%s\"", err->val); break; + + case ZPL_OPTS_ERR_VALUE: zpl_printf("Invalid value \"%s\"", err->val); break; + + case ZPL_OPTS_ERR_MISSING_VALUE: zpl_printf("Missing value for option \"%s\"", err->val); break; + + case ZPL_OPTS_ERR_EXTRA_VALUE: zpl_printf("Extra value for option \"%s\"", err->val); break; + } + + zpl_printf("\n"); + } + } + + void zpl__opts_push_error(zpl_opts *opts, char *b, zpl_u8 errtype) { + zpl_opts_err err = { 0 }; + err.val = b; + err.type = errtype; + zpl_array_append(opts->errors, err); + } + + zpl_b32 zpl_opts_compile(zpl_opts *opts, int argc, char **argv) { + zpl_b32 had_errors = false; + for (int i = 1; i < argc; ++i) { + char *p = argv[i]; + + if (*p) { + p = cast(char *)zpl_str_trim(p, false); + if (*p == '-') { + zpl_opts_entry *t = 0; + zpl_b32 checkln = false; + if (*(p + 1) == '-') { + checkln = true; + ++p; + } + + char *b = p + 1, *e = b; + + while (zpl_char_is_alphanumeric(*e) || *e == '-' || *e == '_') { ++e; } + + t = zpl__opts_find(opts, b, (e - b), checkln); + + if (t) { + char *ob = b; + b = e; + + /**/ if (*e == '=') { + if (t->type == ZPL_OPTS_FLAG) { + *e = '\0'; + zpl__opts_push_error(opts, ob, ZPL_OPTS_ERR_EXTRA_VALUE); + had_errors = true; + continue; + } + + b = e = e + 1; + } else if (*e == '\0') { + char *sp = argv[i+1]; + + if (sp && *sp != '-' && (zpl_array_count(opts->positioned) < 1 || t->type != ZPL_OPTS_FLAG)) { + if (t->type == ZPL_OPTS_FLAG) { + zpl__opts_push_error(opts, b, ZPL_OPTS_ERR_EXTRA_VALUE); + had_errors = true; + continue; + } + + p = sp; + b = e = sp; + ++i; + } else { + if (t->type != ZPL_OPTS_FLAG) { + zpl__opts_push_error(opts, ob, ZPL_OPTS_ERR_MISSING_VALUE); + had_errors = true; + continue; + } + t->met = true; + continue; + } + } + + e = cast(char *)zpl_str_control_skip(e, '\0'); + zpl__opts_set_value(opts, t, b); + } else { + zpl__opts_push_error(opts, b, ZPL_OPTS_ERR_OPTION); + had_errors = true; + } + } else if (zpl_array_count(opts->positioned)) { + zpl_opts_entry *l = zpl_array_back(opts->positioned); + zpl_array_pop(opts->positioned); + zpl__opts_set_value(opts, l, p); + } else { + zpl__opts_push_error(opts, p, ZPL_OPTS_ERR_VALUE); + had_errors = true; + } + } + } + return !had_errors; + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_PROCESS) + // file: source/process.c + + //////////////////////////////////////////////////////////////// + // + // Process creation and manipulation methods + // + // + + ZPL_BEGIN_C_DECLS + + static ZPL_ALWAYS_INLINE void zpl__pr_close_file_handle(zpl_file *f) { + ZPL_ASSERT_NOT_NULL(f); + f->fd.p = NULL; + } + + static ZPL_ALWAYS_INLINE void zpl__pr_close_file_handles(zpl_pr *process) { + ZPL_ASSERT_NOT_NULL(process); + + zpl__pr_close_file_handle(&process->in); + zpl__pr_close_file_handle(&process->out); + zpl__pr_close_file_handle(&process->err); + + process->f_stdin = process->f_stdout = process->f_stderr = NULL; + + #ifdef ZPL_SYSTEM_WINDOWS + process->win32_handle = NULL; + #else + ZPL_NOT_IMPLEMENTED; + #endif + } + + enum { + ZPL_PR_HANDLE_MODE_READ, + ZPL_PR_HANDLE_MODE_WRITE, + ZPL_PR_HANDLE_MODES, + }; + + void *zpl__pr_open_handle(zpl_u8 type, const char *mode, void **handle) { + #ifdef ZPL_SYSTEM_WINDOWS + void *pipes[ZPL_PR_HANDLE_MODES]; + zpl_i32 fd; + + const zpl_u32 flag_inherit = 0x00000001; + SECURITY_ATTRIBUTES sa = {zpl_size_of(sa), 0, 1}; + + if (!CreatePipe(&pipes[0], &pipes[1], cast(LPSECURITY_ATTRIBUTES)&sa, 0)) { + return NULL; + } + + if (!SetHandleInformation(pipes[type], flag_inherit, 0)) { + return NULL; + } + + fd = _open_osfhandle(cast(zpl_intptr)pipes[type], 0); + + if (fd != -1) { + *handle = pipes[1-type]; + return _fdopen(fd, mode); + } + + return NULL; + #else + ZPL_NOT_IMPLEMENTED; + return NULL; + #endif + } + + zpl_i32 zpl_pr_create(zpl_pr *process, const char **args, zpl_isize argc, zpl_pr_si si, zpl_pr_opts options) { + ZPL_ASSERT_NOT_NULL(process); + zpl_zero_item(process); + + #ifdef ZPL_SYSTEM_WINDOWS + zpl_string cli, env; + zpl_b32 c_env=false; + STARTUPINFOW psi = {0}; + PROCESS_INFORMATION pi = {0}; + zpl_i32 err_code = 0; + zpl_allocator a = zpl_heap(); + const zpl_u32 use_std_handles = 0x00000100; + + psi.cb = zpl_size_of(psi); + psi.dwFlags = use_std_handles | si.flags; + + if (options & ZPL_PR_OPTS_CUSTOM_ENV) { + env = zpl_string_join(zpl_heap(), cast(const char**)si.env, si.env_count, "\0\0"); + env = zpl_string_appendc(env, "\0"); + c_env = true; + } + else if (!(options & ZPL_PR_OPTS_INHERIT_ENV)) { + env = (zpl_string)"\0\0\0\0"; + } else { + env = (zpl_string)NULL; + } + + process->f_stdin = zpl__pr_open_handle(ZPL_PR_HANDLE_MODE_WRITE, "wb", &psi.hStdInput); + process->f_stdout = zpl__pr_open_handle(ZPL_PR_HANDLE_MODE_READ, "rb", &psi.hStdOutput); + + if (options & ZPL_PR_OPTS_COMBINE_STD_OUTPUT) { + process->f_stderr = process->f_stdout; + psi.hStdError = psi.hStdOutput; + } else { + process->f_stderr = zpl__pr_open_handle(ZPL_PR_HANDLE_MODE_READ, "rb", &psi.hStdError); + } + + cli = zpl_string_join(zpl_heap(), args, argc, " "); + + psi.dwX = si.posx; + psi.dwY = si.posy; + psi.dwXSize = si.resx; + psi.dwYSize = si.resy; + psi.dwXCountChars = si.bufx; + psi.dwYCountChars = si.bufy; + psi.dwFillAttribute = si.fill_attr; + psi.wShowWindow = si.show_window; + + wchar_t *w_cli = zpl__alloc_utf8_to_ucs2(a, cli, NULL); + wchar_t *w_workdir = zpl__alloc_utf8_to_ucs2(a, si.workdir, NULL); + + if (!CreateProcessW( + NULL, + w_cli, + NULL, + NULL, + 1, + 0, + env, + w_workdir, + cast(LPSTARTUPINFOW)&psi, + cast(LPPROCESS_INFORMATION)&pi + )) { + err_code = -1; + goto pr_free_data; + } + + process->win32_handle = pi.hProcess; + CloseHandle(pi.hThread); + + zpl_file_connect_handle(&process->in, process->f_stdin); + zpl_file_connect_handle(&process->out, process->f_stdout); + zpl_file_connect_handle(&process->err, process->f_stderr); + + pr_free_data: + zpl_string_free(cli); + zpl_free(a, w_cli); + zpl_free(a, w_workdir); + + if (c_env) + zpl_string_free(env); + + return err_code; + + #else + ZPL_NOT_IMPLEMENTED; + return -1; + #endif + } + + + zpl_i32 zpl_pr_join(zpl_pr *process) { + zpl_i32 ret_code; + + ZPL_ASSERT_NOT_NULL(process); + + #ifdef ZPL_SYSTEM_WINDOWS + if (process->f_stdin) { + fclose(cast(FILE *)process->f_stdin); + } + + WaitForSingleObject(process->win32_handle, INFINITE); + + if (!GetExitCodeProcess(process->win32_handle, cast(LPDWORD)&ret_code)) { + zpl_pr_destroy(process); + return -1; + } + + zpl_pr_destroy(process); + + return ret_code; + #else + ZPL_NOT_IMPLEMENTED; + ret_code = -1; + return ret_code; + #endif + } + + void zpl_pr_destroy(zpl_pr *process) { + ZPL_ASSERT_NOT_NULL(process); + + #ifdef ZPL_SYSTEM_WINDOWS + if (process->f_stdin) { + fclose(cast(FILE *)process->f_stdin); + } + + fclose(cast(FILE *)process->f_stdout); + + if (process->f_stderr != process->f_stdout) { + fclose(cast(FILE *)process->f_stderr); + } + + CloseHandle(process->win32_handle); + + zpl__pr_close_file_handles(process); + #else + ZPL_NOT_IMPLEMENTED; + #endif + } + + void zpl_pr_terminate(zpl_pr *process, zpl_i32 err_code) { + ZPL_ASSERT_NOT_NULL(process); + + #ifdef ZPL_SYSTEM_WINDOWS + TerminateProcess(process->win32_handle, cast(UINT)err_code); + zpl_pr_destroy(process); + #else + ZPL_NOT_IMPLEMENTED; + #endif + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_MATH) + // file: source/math.c + + + #if defined(ZPL_COMPILER_TINYC) && defined(ZPL_NO_MATH_H) + #undef ZPL_NO_MATH_H + #endif + + #if !defined(ZPL_NO_MATH_H) + # include + #endif + + ZPL_BEGIN_C_DECLS + + //////////////////////////////////////////////////////////////// + // + // Math + // + + zpl_f32 zpl_to_radians(zpl_f32 degrees) { return degrees * ZPL_TAU / 360.0f; } + zpl_f32 zpl_to_degrees(zpl_f32 radians) { return radians * 360.0f / ZPL_TAU; } + + zpl_f32 zpl_angle_diff(zpl_f32 radians_a, zpl_f32 radians_b) { + zpl_f32 delta = zpl_mod(radians_b - radians_a, ZPL_TAU); + delta = zpl_mod(delta + 1.5f * ZPL_TAU, ZPL_TAU); + delta -= 0.5f * ZPL_TAU; + return delta; + } + + zpl_f32 zpl_copy_sign(zpl_f32 x, zpl_f32 y) { + zpl_i32 ix, iy; + zpl_f32 r; + zpl_memcopy(&ix, &x, zpl_size_of(x)); + zpl_memcopy(&iy, &y, zpl_size_of(y)); + + ix &= 0x7fffffff; + ix |= iy & 0x80000000; + zpl_memcopy(&r, &ix, zpl_size_of(ix)); + return r; + } + + zpl_f32 zpl_remainder(zpl_f32 x, zpl_f32 y) { return x - (zpl_round(x / y) * y); } + + zpl_f32 zpl_mod(zpl_f32 x, zpl_f32 y) { + zpl_f32 result; + y = zpl_abs(y); + result = zpl_remainder(zpl_abs(x), y); + if (zpl_sign(result) > 0.0f) result += y; + return zpl_copy_sign(result, x); + } + + zpl_f64 zpl_copy_sign64(zpl_f64 x, zpl_f64 y) { + zpl_i64 ix, iy; + zpl_f64 r; + zpl_memcopy(&ix, &x, zpl_size_of(x)); + zpl_memcopy(&iy, &y, zpl_size_of(y)); + + ix &= 0x7fffffffffffffff; + ix |= iy & 0x8000000000000000; + zpl_memcopy(&r, &ix, zpl_size_of(ix)); + return r; + } + + zpl_f64 zpl_floor64(zpl_f64 x) { return cast(zpl_f64)((x >= 0.0) ? cast(zpl_i64) x : cast(zpl_i64)(x - 0.9999999999999999)); } + zpl_f64 zpl_ceil64(zpl_f64 x) { return cast(zpl_f64)((x < 0) ? cast(zpl_i64) x : (cast(zpl_i64) x) + 1); } + zpl_f64 zpl_round64(zpl_f64 x) { return cast(zpl_f64)((x >= 0.0) ? zpl_floor64(x + 0.5) : zpl_ceil64(x - 0.5)); } + zpl_f64 zpl_remainder64(zpl_f64 x, zpl_f64 y) { return x - (zpl_round64(x / y) * y); } + zpl_f64 zpl_abs64(zpl_f64 x) { return x < 0 ? -x : x; } + zpl_f64 zpl_sign64(zpl_f64 x) { return x < 0 ? -1.0 : +1.0; } + + zpl_f64 zpl_mod64(zpl_f64 x, zpl_f64 y) { + zpl_f64 result; + y = zpl_abs64(y); + result = zpl_remainder64(zpl_abs64(x), y); + if (zpl_sign64(result)) result += y; + return zpl_copy_sign64(result, x); + } + + zpl_f32 zpl_quake_rsqrt(zpl_f32 a) { + union { + int i; + zpl_f32 f; + } t; + zpl_f32 x2; + zpl_f32 const three_halfs = 1.5f; + + x2 = a * 0.5f; + t.f = a; + t.i = 0x5f375a86 - (t.i >> 1); /* What the fuck? */ + t.f = t.f * (three_halfs - (x2 * t.f * t.f)); /* 1st iteration */ + t.f = t.f * (three_halfs - (x2 * t.f * t.f)); /* 2nd iteration, this can be removed */ + + return t.f; + } + + #if defined(ZPL_NO_MATH_H) + # if defined(_MSC_VER) + + zpl_f32 zpl_rsqrt(zpl_f32 a) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(a))); } + zpl_f32 zpl_sqrt(zpl_f32 a) { return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(a))); }; + + zpl_f32 zpl_sin(zpl_f32 a) { + static zpl_f32 const a0 = +1.91059300966915117e-31f; + static zpl_f32 const a1 = +1.00086760103908896f; + static zpl_f32 const a2 = -1.21276126894734565e-2f; + static zpl_f32 const a3 = -1.38078780785773762e-1f; + static zpl_f32 const a4 = -2.67353392911981221e-2f; + static zpl_f32 const a5 = +2.08026600266304389e-2f; + static zpl_f32 const a6 = -3.03996055049204407e-3f; + static zpl_f32 const a7 = +1.38235642404333740e-4f; + return a0 + a * (a1 + a * (a2 + a * (a3 + a * (a4 + a * (a5 + a * (a6 + a * a7)))))); + } + + zpl_f32 zpl_cos(zpl_f32 a) { + static zpl_f32 const a0 = +1.00238601909309722f; + static zpl_f32 const a1 = -3.81919947353040024e-2f; + static zpl_f32 const a2 = -3.94382342128062756e-1f; + static zpl_f32 const a3 = -1.18134036025221444e-1f; + static zpl_f32 const a4 = +1.07123798512170878e-1f; + static zpl_f32 const a5 = -1.86637164165180873e-2f; + static zpl_f32 const a6 = +9.90140908664079833e-4f; + static zpl_f32 const a7 = -5.23022132118824778e-14f; + return a0 + a * (a1 + a * (a2 + a * (a3 + a * (a4 + a * (a5 + a * (a6 + a * a7)))))); + } + + zpl_f32 zpl_tan(zpl_f32 radians) { + zpl_f32 rr = radians * radians; + zpl_f32 a = 9.5168091e-03f; + a *= rr; + a += 2.900525e-03f; + a *= rr; + a += 2.45650893e-02f; + a *= rr; + a += 5.33740603e-02f; + a *= rr; + a += 1.333923995e-01f; + a *= rr; + a += 3.333314036e-01f; + a *= rr; + a += 1.0f; + a *= radians; + return a; + } + + zpl_f32 zpl_arcsin(zpl_f32 a) { return zpl_arctan2(a, zpl_sqrt((1.0f + a) * (1.0f - a))); } + zpl_f32 zpl_arccos(zpl_f32 a) { return zpl_arctan2(zpl_sqrt((1.0f + a) * (1.0f - a)), a); } + + zpl_f32 zpl_arctan(zpl_f32 a) { + zpl_f32 u = a * a; + zpl_f32 u2 = u * u; + zpl_f32 u3 = u2 * u; + zpl_f32 u4 = u3 * u; + zpl_f32 f = 1.0f + 0.33288950512027f * u - 0.08467922817644f * u2 + 0.03252232640125f * u3 - 0.00749305860992f * u4; + return a / f; + } + + zpl_f32 zpl_arctan2(zpl_f32 y, zpl_f32 x) { + if (zpl_abs(x) > zpl_abs(y)) { + zpl_f32 a = zpl_arctan(y / x); + if (x > 0.0f) + return a; + else + return y > 0.0f ? a + ZPL_TAU_OVER_2 : a - ZPL_TAU_OVER_2; + } else { + zpl_f32 a = zpl_arctan(x / y); + if (x > 0.0f) + return y > 0.0f ? ZPL_TAU_OVER_4 - a : -ZPL_TAU_OVER_4 - a; + else + return y > 0.0f ? ZPL_TAU_OVER_4 + a : -ZPL_TAU_OVER_4 + a; + } + } + + zpl_f32 zpl_exp(zpl_f32 a) { + union { + zpl_f32 f; + int i; + } u, v; + u.i = (int)(6051102 * a + 1056478197); + v.i = (int)(1056478197 - 6051102 * a); + return u.f / v.f; + } + + zpl_f32 zpl_log(zpl_f32 a) { + union { + zpl_f32 f; + int i; + } u = { a }; + return (u.i - 1064866805) * 8.262958405176314e-8f; /* 1 / 12102203.0; */ + } + + zpl_f32 zpl_pow(zpl_f32 a, zpl_f32 b) { + int flipped = 0, e; + zpl_f32 f, r = 1.0f; + if (b < 0) { + flipped = 1; + b = -b; + } + + e = (int)b; + f = zpl_exp(b - e); + + while (e) { + if (e & 1) r *= a; + a *= a; + e >>= 1; + } + + r *= f; + return flipped ? 1.0f / r : r; + } + # else + + zpl_f32 zpl_rsqrt(zpl_f32 a) { return 1.0f / __builtin_sqrt(a); } + zpl_f32 zpl_sqrt(zpl_f32 a) { return __builtin_sqrt(a); } + zpl_f32 zpl_sin(zpl_f32 radians) { return __builtin_sinf(radians); } + zpl_f32 zpl_cos(zpl_f32 radians) { return __builtin_cosf(radians); } + zpl_f32 zpl_tan(zpl_f32 radians) { return __builtin_tanf(radians); } + zpl_f32 zpl_arcsin(zpl_f32 a) { return __builtin_asinf(a); } + zpl_f32 zpl_arccos(zpl_f32 a) { return __builtin_acosf(a); } + zpl_f32 zpl_arctan(zpl_f32 a) { return __builtin_atanf(a); } + zpl_f32 zpl_arctan2(zpl_f32 y, zpl_f32 x) { return __builtin_atan2f(y, x); } + + zpl_f32 zpl_exp(zpl_f32 x) { return __builtin_expf(x); } + zpl_f32 zpl_log(zpl_f32 x) { return __builtin_logf(x); } + + // TODO: Should this be zpl_exp(y * zpl_log(x)) ??? + zpl_f32 zpl_pow(zpl_f32 x, zpl_f32 y) { return __builtin_powf(x, y); } + zpl_f32 zpl_hypot(zpl_f32 x, zpl_f32 y){ return __builtin_sqrt(zpl_square(x) + zpl_square(y)); } + + # endif + #else + zpl_f32 zpl_rsqrt(zpl_f32 a) { return 1.0f / sqrtf(a); } + zpl_f32 zpl_sqrt(zpl_f32 a) { return sqrtf(a); }; + zpl_f32 zpl_sin(zpl_f32 radians) { return sinf(radians); }; + zpl_f32 zpl_cos(zpl_f32 radians) { return cosf(radians); }; + zpl_f32 zpl_tan(zpl_f32 radians) { return tanf(radians); }; + zpl_f32 zpl_arcsin(zpl_f32 a) { return asinf(a); }; + zpl_f32 zpl_arccos(zpl_f32 a) { return acosf(a); }; + zpl_f32 zpl_arctan(zpl_f32 a) { return atanf(a); }; + zpl_f32 zpl_arctan2(zpl_f32 y, zpl_f32 x) { return atan2f(y, x); }; + + zpl_f32 zpl_exp(zpl_f32 x) { return expf(x); } + zpl_f32 zpl_log(zpl_f32 x) { return logf(x); } + zpl_f32 zpl_pow(zpl_f32 x, zpl_f32 y) { return powf(x, y); } + zpl_f32 zpl_hypot(zpl_f32 x, zpl_f32 y) { return sqrtf(zpl_square(x) + zpl_square(y)); } + #endif + + zpl_f32 zpl_exp2(zpl_f32 x) { return zpl_exp(ZPL_LOG_TWO * x); } + zpl_f32 zpl_log2(zpl_f32 x) { return zpl_log(x) / ZPL_LOG_TWO; } + + zpl_f32 zpl_fast_exp(zpl_f32 x) { + /* NOTE: Only works in the range -1 <= x <= +1 */ + zpl_f32 e = 1.0f + x * (1.0f + x * 0.5f * (1.0f + x * 0.3333333333f * (1.0f + x * 0.25f * (1.0f + x * 0.2f)))); + return e; + } + + zpl_f32 zpl_fast_exp2(zpl_f32 x) { return zpl_fast_exp(ZPL_LOG_TWO * x); } + + zpl_f32 zpl_round(zpl_f32 x) { return (float)((x >= 0.0f) ? zpl_floor(x + 0.5f) : zpl_ceil(x - 0.5f)); } + zpl_f32 zpl_floor(zpl_f32 x) { return (float)((x >= 0.0f) ? (int)x : (int)(x - 0.9999999999999999f)); } + zpl_f32 zpl_ceil(zpl_f32 x) { return (float)((x < 0.0f) ? (int)x : ((int)x) + 1); } + + zpl_f32 zpl_half_to_float(zpl_half value) { + union { + unsigned int i; + zpl_f32 f; + } result; + int s = (value >> 15) & 0x001; + int e = (value >> 10) & 0x01f; + int m = value & 0x3ff; + + if (e == 0) { + if (m == 0) { + /* Plus or minus zero */ + result.i = (unsigned int)(s << 31); + return result.f; + } else { + /* Denormalized number */ + while (!(m & 0x00000400)) { + m <<= 1; + e -= 1; + } + + e += 1; + m &= ~0x00000400; + } + } else if (e == 31) { + if (m == 0) { + /* Positive or negative infinity */ + result.i = (unsigned int)((s << 31) | 0x7f800000); + return result.f; + } else { + /* Nan */ + result.i = (unsigned int)((s << 31) | 0x7f800000 | (m << 13)); + return result.f; + } + } + + e = e + (127 - 15); + m = m << 13; + + result.i = (unsigned int)((s << 31) | (e << 23) | m); + return result.f; + } + + zpl_half zpl_float_to_half(zpl_f32 value) { + union { + unsigned int i; + zpl_f32 f; + } v; + int i, s, e, m; + + v.f = value; + i = (int)v.i; + + s = (i >> 16) & 0x00008000; + e = ((i >> 23) & 0x000000ff) - (127 - 15); + m = i & 0x007fffff; + + if (e <= 0) { + if (e < -10) return (zpl_half)s; + m = (m | 0x00800000) >> (1 - e); + + if (m & 0x00001000) m += 0x00002000; + + return (zpl_half)(s | (m >> 13)); + } else if (e == 0xff - (127 - 15)) { + if (m == 0) { + return (zpl_half)(s | 0x7c00); /* NOTE: infinity */ + } else { + /* NOTE: NAN */ + m >>= 13; + return (zpl_half)(s | 0x7c00 | m | (m == 0)); + } + } else { + if (m & 0x00001000) { + m += 0x00002000; + if (m & 0x00800000) { + m = 0; + e += 1; + } + } + + if (e > 30) { + zpl_f32 volatile f = 1e12f; + int j; + for (j = 0; j < 10; j++) f *= f; /* NOTE: Cause overflow */ + + return (zpl_half)(s | 0x7c00); + } + + return (zpl_half)(s | (e << 10) | (m >> 13)); + } + } + + #define ZPL_VEC2_2OP(a, c, post) \ + a->x = c.x post; \ + a->y = c.y post; + + #define ZPL_VEC2_3OP(a, b, op, c, post) \ + a->x = b.x op c.x post; \ + a->y = b.y op c.y post; + + #define ZPL_VEC3_2OP(a, c, post) \ + a->x = c.x post; \ + a->y = c.y post; \ + a->z = c.z post; + + #define ZPL_VEC3_3OP(a, b, op, c, post) \ + a->x = b.x op c.x post; \ + a->y = b.y op c.y post; \ + a->z = b.z op c.z post; + + #define ZPL_VEC4_2OP(a, c, post) \ + a->x = c.x post; \ + a->y = c.y post; \ + a->z = c.z post; \ + a->w = c.w post; + + #define ZPL_VEC4_3OP(a, b, op, c, post) \ + a->x = b.x op c.x post; \ + a->y = b.y op c.y post; \ + a->z = b.z op c.z post; \ + a->w = b.w op c.w post; + + zpl_vec2 zpl_vec2f_zero(void) { + zpl_vec2 v = { 0, 0 }; + return v; + } + zpl_vec2 zpl_vec2f(zpl_f32 x, zpl_f32 y) { + zpl_vec2 v; + v.x = x; + v.y = y; + return v; + } + zpl_vec2 zpl_vec2fv(zpl_f32 x[2]) { + zpl_vec2 v; + v.x = x[0]; + v.y = x[1]; + return v; + } + + zpl_vec3 zpl_vec3f_zero(void) { + zpl_vec3 v = { 0, 0, 0 }; + return v; + } + zpl_vec3 zpl_vec3f(zpl_f32 x, zpl_f32 y, zpl_f32 z) { + zpl_vec3 v; + v.x = x; + v.y = y; + v.z = z; + return v; + } + zpl_vec3 zpl_vec3fv(zpl_f32 x[3]) { + zpl_vec3 v; + v.x = x[0]; + v.y = x[1]; + v.z = x[2]; + return v; + } + + zpl_vec4 zpl_vec4f_zero(void) { + zpl_vec4 v = { 0, 0, 0, 0 }; + return v; + } + zpl_vec4 zpl_vec4f(zpl_f32 x, zpl_f32 y, zpl_f32 z, zpl_f32 w) { + zpl_vec4 v; + v.x = x; + v.y = y; + v.z = z; + v.w = w; + return v; + } + zpl_vec4 zpl_vec4fv(zpl_f32 x[4]) { + zpl_vec4 v; + v.x = x[0]; + v.y = x[1]; + v.z = x[2]; + v.w = x[3]; + return v; + } + + zpl_f32 zpl_vec2_max(zpl_vec2 v) { return zpl_max(v.x, v.y); } + zpl_f32 zpl_vec2_side(zpl_vec2 p, zpl_vec2 q, zpl_vec2 r) { return ((q.x - p.x) * (r.y - p.y) - (r.x - p.x) * (q.y - p.y)); } + + void zpl_vec2_add(zpl_vec2 *d, zpl_vec2 v0, zpl_vec2 v1) { ZPL_VEC2_3OP(d, v0, +, v1, +0); } + void zpl_vec2_sub(zpl_vec2 *d, zpl_vec2 v0, zpl_vec2 v1) { ZPL_VEC2_3OP(d, v0, -, v1, +0); } + void zpl_vec2_mul(zpl_vec2 *d, zpl_vec2 v, zpl_f32 s) { ZPL_VEC2_2OP(d, v, *s); } + void zpl_vec2_div(zpl_vec2 *d, zpl_vec2 v, zpl_f32 s) { ZPL_VEC2_2OP(d, v, / s); } + + zpl_f32 zpl_vec3_max(zpl_vec3 v) { return zpl_max3(v.x, v.y, v.z); } + + void zpl_vec3_add(zpl_vec3 *d, zpl_vec3 v0, zpl_vec3 v1) { ZPL_VEC3_3OP(d, v0, +, v1, +0); } + void zpl_vec3_sub(zpl_vec3 *d, zpl_vec3 v0, zpl_vec3 v1) { ZPL_VEC3_3OP(d, v0, -, v1, +0); } + void zpl_vec3_mul(zpl_vec3 *d, zpl_vec3 v, zpl_f32 s) { ZPL_VEC3_2OP(d, v, *s); } + void zpl_vec3_div(zpl_vec3 *d, zpl_vec3 v, zpl_f32 s) { ZPL_VEC3_2OP(d, v, / s); } + + void zpl_vec4_add(zpl_vec4 *d, zpl_vec4 v0, zpl_vec4 v1) { ZPL_VEC4_3OP(d, v0, +, v1, +0); } + void zpl_vec4_sub(zpl_vec4 *d, zpl_vec4 v0, zpl_vec4 v1) { ZPL_VEC4_3OP(d, v0, -, v1, +0); } + void zpl_vec4_mul(zpl_vec4 *d, zpl_vec4 v, zpl_f32 s) { ZPL_VEC4_2OP(d, v, *s); } + void zpl_vec4_div(zpl_vec4 *d, zpl_vec4 v, zpl_f32 s) { ZPL_VEC4_2OP(d, v, / s); } + + void zpl_vec2_addeq(zpl_vec2 *d, zpl_vec2 v) { ZPL_VEC2_3OP(d, (*d), +, v, +0); } + void zpl_vec2_subeq(zpl_vec2 *d, zpl_vec2 v) { ZPL_VEC2_3OP(d, (*d), -, v, +0); } + void zpl_vec2_muleq(zpl_vec2 *d, zpl_f32 s) { ZPL_VEC2_2OP(d, (*d), *s); } + void zpl_vec2_diveq(zpl_vec2 *d, zpl_f32 s) { ZPL_VEC2_2OP(d, (*d), / s); } + + void zpl_vec3_addeq(zpl_vec3 *d, zpl_vec3 v) { ZPL_VEC3_3OP(d, (*d), +, v, +0); } + void zpl_vec3_subeq(zpl_vec3 *d, zpl_vec3 v) { ZPL_VEC3_3OP(d, (*d), -, v, +0); } + void zpl_vec3_muleq(zpl_vec3 *d, zpl_f32 s) { ZPL_VEC3_2OP(d, (*d), *s); } + void zpl_vec3_diveq(zpl_vec3 *d, zpl_f32 s) { ZPL_VEC3_2OP(d, (*d), / s); } + + void zpl_vec4_addeq(zpl_vec4 *d, zpl_vec4 v) { ZPL_VEC4_3OP(d, (*d), +, v, +0); } + void zpl_vec4_subeq(zpl_vec4 *d, zpl_vec4 v) { ZPL_VEC4_3OP(d, (*d), -, v, +0); } + void zpl_vec4_muleq(zpl_vec4 *d, zpl_f32 s) { ZPL_VEC4_2OP(d, (*d), *s); } + void zpl_vec4_diveq(zpl_vec4 *d, zpl_f32 s) { ZPL_VEC4_2OP(d, (*d), / s); } + + #undef ZPL_VEC2_2OP + #undef ZPL_VEC2_3OP + #undef ZPL_VEC3_3OP + #undef ZPL_VEC3_2OP + #undef ZPL_VEC4_2OP + #undef ZPL_VEC4_3OP + + zpl_f32 zpl_vec2_dot(zpl_vec2 v0, zpl_vec2 v1) { return v0.x * v1.x + v0.y * v1.y; } + zpl_f32 zpl_vec3_dot(zpl_vec3 v0, zpl_vec3 v1) { return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z; } + zpl_f32 zpl_vec4_dot(zpl_vec4 v0, zpl_vec4 v1) { return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z + v0.w * v1.w; } + + void zpl_vec2_cross(zpl_f32 *d, zpl_vec2 v0, zpl_vec2 v1) { *d = v0.x * v1.y - v1.x * v0.y; } + void zpl_vec3_cross(zpl_vec3 *d, zpl_vec3 v0, zpl_vec3 v1) { + d->x = v0.y * v1.z - v0.z * v1.y; + d->y = v0.z * v1.x - v0.x * v1.z; + d->z = v0.x * v1.y - v0.y * v1.x; + } + + zpl_f32 zpl_vec2_mag2(zpl_vec2 v) { return zpl_vec2_dot(v, v); } + zpl_f32 zpl_vec3_mag2(zpl_vec3 v) { return zpl_vec3_dot(v, v); } + zpl_f32 zpl_vec4_mag2(zpl_vec4 v) { return zpl_vec4_dot(v, v); } + + /* TODO: Create custom sqrt function */ + zpl_f32 zpl_vec2_mag(zpl_vec2 v) { return zpl_sqrt(zpl_vec2_dot(v, v)); } + zpl_f32 zpl_vec3_mag(zpl_vec3 v) { return zpl_sqrt(zpl_vec3_dot(v, v)); } + zpl_f32 zpl_vec4_mag(zpl_vec4 v) { return zpl_sqrt(zpl_vec4_dot(v, v)); } + + void zpl_vec2_norm(zpl_vec2 *d, zpl_vec2 v) { + zpl_f32 inv_mag = zpl_rsqrt(zpl_vec2_dot(v, v)); + zpl_vec2_mul(d, v, inv_mag); + } + void zpl_vec3_norm(zpl_vec3 *d, zpl_vec3 v) { + zpl_f32 inv_mag = zpl_rsqrt(zpl_vec3_dot(v, v)); + zpl_vec3_mul(d, v, inv_mag); + } + void zpl_vec4_norm(zpl_vec4 *d, zpl_vec4 v) { + zpl_f32 inv_mag = zpl_rsqrt(zpl_vec4_dot(v, v)); + zpl_vec4_mul(d, v, inv_mag); + } + + void zpl_vec2_norm0(zpl_vec2 *d, zpl_vec2 v) { + zpl_f32 mag = zpl_vec2_mag(v); + if (mag > 0) + zpl_vec2_div(d, v, mag); + else + *d = zpl_vec2f_zero( ); + } + void zpl_vec3_norm0(zpl_vec3 *d, zpl_vec3 v) { + zpl_f32 mag = zpl_vec3_mag(v); + if (mag > 0) + zpl_vec3_div(d, v, mag); + else + *d = zpl_vec3f_zero( ); + } + void zpl_vec4_norm0(zpl_vec4 *d, zpl_vec4 v) { + zpl_f32 mag = zpl_vec4_mag(v); + if (mag > 0) + zpl_vec4_div(d, v, mag); + else + *d = zpl_vec4f_zero( ); + } + + void zpl_vec2_reflect(zpl_vec2 *d, zpl_vec2 i, zpl_vec2 n) { + zpl_vec2 b = n; + zpl_vec2_muleq(&b, 2.0f * zpl_vec2_dot(n, i)); + zpl_vec2_sub(d, i, b); + } + + void zpl_vec3_reflect(zpl_vec3 *d, zpl_vec3 i, zpl_vec3 n) { + zpl_vec3 b = n; + zpl_vec3_muleq(&b, 2.0f * zpl_vec3_dot(n, i)); + zpl_vec3_sub(d, i, b); + } + + void zpl_vec2_refract(zpl_vec2 *d, zpl_vec2 i, zpl_vec2 n, zpl_f32 eta) { + zpl_vec2 a, b; + zpl_f32 dv, k; + + dv = zpl_vec2_dot(n, i); + k = 1.0f - eta * eta * (1.0f - dv * dv); + zpl_vec2_mul(&a, i, eta); + zpl_vec2_mul(&b, n, eta * dv * zpl_sqrt(k)); + zpl_vec2_sub(d, a, b); + zpl_vec2_muleq(d, (float)(k >= 0.0f)); + } + + void zpl_vec3_refract(zpl_vec3 *d, zpl_vec3 i, zpl_vec3 n, zpl_f32 eta) { + zpl_vec3 a, b; + zpl_f32 dv, k; + + dv = zpl_vec3_dot(n, i); + k = 1.0f - eta * eta * (1.0f - dv * dv); + zpl_vec3_mul(&a, i, eta); + zpl_vec3_mul(&b, n, eta * dv * zpl_sqrt(k)); + zpl_vec3_sub(d, a, b); + zpl_vec3_muleq(d, (float)(k >= 0.0f)); + } + + zpl_f32 zpl_vec2_aspect_ratio(zpl_vec2 v) { return (v.y < 0.0001f) ? 0.0f : v.x / v.y; } + + void zpl_mat2_transpose(zpl_mat2 *m) { zpl_float22_transpose(zpl_float22_m(m)); } + void zpl_mat2_identity(zpl_mat2 *m) { zpl_float22_identity(zpl_float22_m(m)); } + void zpl_mat2_mul(zpl_mat2 *out, zpl_mat2 *m1, zpl_mat2 *m2) { + zpl_float22_mul(zpl_float22_m(out), zpl_float22_m(m1), zpl_float22_m(m2)); + } + + void zpl_float22_identity(zpl_f32 m[2][2]) { + m[0][0] = 1; + m[0][1] = 0; + m[1][0] = 0; + m[1][1] = 1; + } + + void zpl_mat2_copy(zpl_mat2* out, zpl_mat2* m) { + zpl_memcopy(out, m, sizeof(zpl_mat3)); + } + + void zpl_mat2_mul_vec2(zpl_vec2 *out, zpl_mat2 *m, zpl_vec2 in) { zpl_float22_mul_vec2(out, zpl_float22_m(m), in); } + + zpl_mat2 *zpl_mat2_v(zpl_vec2 m[2]) { return (zpl_mat2 *)m; } + zpl_mat2 *zpl_mat2_f(zpl_f32 m[2][2]) { return (zpl_mat2 *)m; } + + zpl_float2 *zpl_float22_m(zpl_mat2 *m) { return (zpl_float2 *)m; } + zpl_float2 *zpl_float22_v(zpl_vec2 m[2]) { return (zpl_float2 *)m; } + zpl_float2 *zpl_float22_4(zpl_f32 m[4]) { return (zpl_float2 *)m; } + + void zpl_float22_transpose(zpl_f32 (*vec)[2]) { + int i, j; + for (j = 0; j < 2; j++) { + for (i = j + 1; i < 2; i++) { + zpl_f32 t = vec[i][j]; + vec[i][j] = vec[j][i]; + vec[j][i] = t; + } + } + } + + void zpl_float22_mul(zpl_f32 (*out)[2], zpl_f32 (*mat1)[2], zpl_f32 (*mat2)[2]) { + int i, j; + zpl_f32 temp1[2][2], temp2[2][2]; + if (mat1 == out) { + zpl_memcopy(temp1, mat1, sizeof(temp1)); + mat1 = temp1; + } + if (mat2 == out) { + zpl_memcopy(temp2, mat2, sizeof(temp2)); + mat2 = temp2; + } + for (j = 0; j < 2; j++) { + for (i = 0; i < 2; i++) { out[j][i] = mat1[0][i] * mat2[j][0] + mat1[1][i] * mat2[j][1]; } + } + } + + void zpl_float22_mul_vec2(zpl_vec2 *out, zpl_f32 m[2][2], zpl_vec2 v) { + out->x = m[0][0] * v.x + m[0][1] * v.y; + out->y = m[1][0] * v.x + m[1][1] * v.y; + } + + zpl_f32 zpl_mat2_determinate(zpl_mat2 *m) { + zpl_float2 *e = zpl_float22_m(m); + return e[0][0] * e[1][1] - e[1][0] * e[0][1]; + } + + void zpl_mat2_inverse(zpl_mat2 *out, zpl_mat2 *in) { + zpl_float2 *o = zpl_float22_m(out); + zpl_float2 *i = zpl_float22_m(in); + + zpl_f32 ood = 1.0f / zpl_mat2_determinate(in); + + o[0][0] = +i[1][1] * ood; + o[0][1] = -i[0][1] * ood; + o[1][0] = -i[1][0] * ood; + o[1][1] = +i[0][0] * ood; + } + + void zpl_mat3_transpose(zpl_mat3 *m) { zpl_float33_transpose(zpl_float33_m(m)); } + void zpl_mat3_identity(zpl_mat3 *m) { zpl_float33_identity(zpl_float33_m(m)); } + + void zpl_mat3_copy(zpl_mat3* out, zpl_mat3* m) { + zpl_memcopy(out, m, sizeof(zpl_mat3)); + } + + void zpl_mat3_mul(zpl_mat3 *out, zpl_mat3 *m1, zpl_mat3 *m2) { + zpl_float33_mul(zpl_float33_m(out), zpl_float33_m(m1), zpl_float33_m(m2)); + } + + void zpl_float33_identity(zpl_f32 m[3][3]) { + m[0][0] = 1; + m[0][1] = 0; + m[0][2] = 0; + m[1][0] = 0; + m[1][1] = 1; + m[1][2] = 0; + m[2][0] = 0; + m[2][1] = 0; + m[2][2] = 1; + } + + void zpl_mat3_mul_vec3(zpl_vec3 *out, zpl_mat3 *m, zpl_vec3 in) { zpl_float33_mul_vec3(out, zpl_float33_m(m), in); } + + zpl_mat3 *zpl_mat3_v(zpl_vec3 m[3]) { return (zpl_mat3 *)m; } + zpl_mat3 *zpl_mat3_f(zpl_f32 m[3][3]) { return (zpl_mat3 *)m; } + + zpl_float3 *zpl_float33_m(zpl_mat3 *m) { return (zpl_float3 *)m; } + zpl_float3 *zpl_float33_v(zpl_vec3 m[3]) { return (zpl_float3 *)m; } + zpl_float3 *zpl_float33_9(zpl_f32 m[9]) { return (zpl_float3 *)m; } + + void zpl_float33_transpose(zpl_f32 (*vec)[3]) { + int i, j; + for (j = 0; j < 3; j++) { + for (i = j + 1; i < 3; i++) { + zpl_f32 t = vec[i][j]; + vec[i][j] = vec[j][i]; + vec[j][i] = t; + } + } + } + + void zpl_float33_mul(zpl_f32 (*out)[3], zpl_f32 (*mat1)[3], zpl_f32 (*mat2)[3]) { + int i, j; + zpl_f32 temp1[3][3], temp2[3][3]; + if (mat1 == out) { + zpl_memcopy(temp1, mat1, sizeof(temp1)); + mat1 = temp1; + } + if (mat2 == out) { + zpl_memcopy(temp2, mat2, sizeof(temp2)); + mat2 = temp2; + } + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) { + out[j][i] = mat1[0][i] * mat2[j][0] + mat1[1][i] * mat2[j][1] + mat1[2][i] * mat2[j][2]; + } + } + } + + void zpl_float33_mul_vec3(zpl_vec3 *out, zpl_f32 m[3][3], zpl_vec3 v) { + out->x = m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z; + out->y = m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z; + out->z = m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z; + } + + zpl_f32 zpl_mat3_determinate(zpl_mat3 *m) { + zpl_float3 *e = zpl_float33_m(m); + zpl_f32 d = + +e[0][0] * (e[1][1] * e[2][2] - e[1][2] * e[2][1]) + -e[0][1] * (e[1][0] * e[2][2] - e[1][2] * e[2][0]) + +e[0][2] * (e[1][0] * e[2][1] - e[1][1] * e[2][0]); + return d; + } + + void zpl_mat3_inverse(zpl_mat3 *out, zpl_mat3 *in) { + zpl_float3 *o = zpl_float33_m(out); + zpl_float3 *i = zpl_float33_m(in); + + zpl_f32 ood = 1.0f / zpl_mat3_determinate(in); + + o[0][0] = +(i[1][1] * i[2][2] - i[2][1] * i[1][2]) * ood; + o[0][1] = -(i[1][0] * i[2][2] - i[2][0] * i[1][2]) * ood; + o[0][2] = +(i[1][0] * i[2][1] - i[2][0] * i[1][1]) * ood; + o[1][0] = -(i[0][1] * i[2][2] - i[2][1] * i[0][2]) * ood; + o[1][1] = +(i[0][0] * i[2][2] - i[2][0] * i[0][2]) * ood; + o[1][2] = -(i[0][0] * i[2][1] - i[2][0] * i[0][1]) * ood; + o[2][0] = +(i[0][1] * i[1][2] - i[1][1] * i[0][2]) * ood; + o[2][1] = -(i[0][0] * i[1][2] - i[1][0] * i[0][2]) * ood; + o[2][2] = +(i[0][0] * i[1][1] - i[1][0] * i[0][1]) * ood; + } + + void zpl_mat4_transpose(zpl_mat4 *m) { zpl_float44_transpose(zpl_float44_m(m)); } + void zpl_mat4_identity(zpl_mat4 *m) { zpl_float44_identity(zpl_float44_m(m)); } + + void zpl_mat4_copy(zpl_mat4* out, zpl_mat4* m) { + zpl_memcopy(out, m, sizeof(zpl_mat4)); + } + + + void zpl_mat4_mul(zpl_mat4 *out, zpl_mat4 *m1, zpl_mat4 *m2) { + zpl_float44_mul(zpl_float44_m(out), zpl_float44_m(m1), zpl_float44_m(m2)); + } + + void zpl_float44_identity(zpl_f32 m[4][4]) { + m[0][0] = 1; + m[0][1] = 0; + m[0][2] = 0; + m[0][3] = 0; + m[1][0] = 0; + m[1][1] = 1; + m[1][2] = 0; + m[1][3] = 0; + m[2][0] = 0; + m[2][1] = 0; + m[2][2] = 1; + m[2][3] = 0; + m[3][0] = 0; + m[3][1] = 0; + m[3][2] = 0; + m[3][3] = 1; + } + + void zpl_mat4_mul_vec4(zpl_vec4 *out, zpl_mat4 *m, zpl_vec4 in) { zpl_float44_mul_vec4(out, zpl_float44_m(m), in); } + + zpl_mat4 *zpl_mat4_v(zpl_vec4 m[4]) { return (zpl_mat4 *)m; } + zpl_mat4 *zpl_mat4_f(zpl_f32 m[4][4]) { return (zpl_mat4 *)m; } + + zpl_float4 *zpl_float44_m(zpl_mat4 *m) { return (zpl_float4 *)m; } + zpl_float4 *zpl_float44_v(zpl_vec4 m[4]) { return (zpl_float4 *)m; } + zpl_float4 *zpl_float44_16(zpl_f32 m[16]) { return (zpl_float4 *)m; } + + void zpl_float44_transpose(zpl_f32 (*vec)[4]) { + zpl_f32 tmp; + tmp = vec[1][0]; + vec[1][0] = vec[0][1]; + vec[0][1] = tmp; + tmp = vec[2][0]; + vec[2][0] = vec[0][2]; + vec[0][2] = tmp; + tmp = vec[3][0]; + vec[3][0] = vec[0][3]; + vec[0][3] = tmp; + tmp = vec[2][1]; + vec[2][1] = vec[1][2]; + vec[1][2] = tmp; + tmp = vec[3][1]; + vec[3][1] = vec[1][3]; + vec[1][3] = tmp; + tmp = vec[3][2]; + vec[3][2] = vec[2][3]; + vec[2][3] = tmp; + } + + void zpl_float44_mul(zpl_f32 (*out)[4], zpl_f32 (*mat1)[4], zpl_f32 (*mat2)[4]) { + int i, j; + zpl_f32 temp1[4][4], temp2[4][4]; + if (mat1 == out) { + zpl_memcopy(temp1, mat1, sizeof(temp1)); + mat1 = temp1; + } + if (mat2 == out) { + zpl_memcopy(temp2, mat2, sizeof(temp2)); + mat2 = temp2; + } + for (j = 0; j < 4; j++) { + for (i = 0; i < 4; i++) { + out[j][i] = + mat1[0][i] * mat2[j][0] + mat1[1][i] * mat2[j][1] + +mat1[2][i] * mat2[j][2] + mat1[3][i] * mat2[j][3]; + } + } + } + + void zpl_float44_mul_vec4(zpl_vec4 *out, zpl_f32 m[4][4], zpl_vec4 v) { + out->x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z + m[3][0] * v.w; + out->y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z + m[3][1] * v.w; + out->z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z + m[3][2] * v.w; + out->w = m[0][3] * v.x + m[1][3] * v.y + m[2][3] * v.z + m[3][3] * v.w; + } + + void zpl_mat4_inverse(zpl_mat4 *out, zpl_mat4 *in) { + zpl_float4 *o = zpl_float44_m(out); + zpl_float4 *m = zpl_float44_m(in); + + zpl_f32 ood; + + zpl_f32 sf00 = m[2][2] * m[3][3] - m[3][2] * m[2][3]; + zpl_f32 sf01 = m[2][1] * m[3][3] - m[3][1] * m[2][3]; + zpl_f32 sf02 = m[2][1] * m[3][2] - m[3][1] * m[2][2]; + zpl_f32 sf03 = m[2][0] * m[3][3] - m[3][0] * m[2][3]; + zpl_f32 sf04 = m[2][0] * m[3][2] - m[3][0] * m[2][2]; + zpl_f32 sf05 = m[2][0] * m[3][1] - m[3][0] * m[2][1]; + zpl_f32 sf06 = m[1][2] * m[3][3] - m[3][2] * m[1][3]; + zpl_f32 sf07 = m[1][1] * m[3][3] - m[3][1] * m[1][3]; + zpl_f32 sf08 = m[1][1] * m[3][2] - m[3][1] * m[1][2]; + zpl_f32 sf09 = m[1][0] * m[3][3] - m[3][0] * m[1][3]; + zpl_f32 sf10 = m[1][0] * m[3][2] - m[3][0] * m[1][2]; + zpl_f32 sf11 = m[1][1] * m[3][3] - m[3][1] * m[1][3]; + zpl_f32 sf12 = m[1][0] * m[3][1] - m[3][0] * m[1][1]; + zpl_f32 sf13 = m[1][2] * m[2][3] - m[2][2] * m[1][3]; + zpl_f32 sf14 = m[1][1] * m[2][3] - m[2][1] * m[1][3]; + zpl_f32 sf15 = m[1][1] * m[2][2] - m[2][1] * m[1][2]; + zpl_f32 sf16 = m[1][0] * m[2][3] - m[2][0] * m[1][3]; + zpl_f32 sf17 = m[1][0] * m[2][2] - m[2][0] * m[1][2]; + zpl_f32 sf18 = m[1][0] * m[2][1] - m[2][0] * m[1][1]; + + o[0][0] = +(m[1][1] * sf00 - m[1][2] * sf01 + m[1][3] * sf02); + o[1][0] = -(m[1][0] * sf00 - m[1][2] * sf03 + m[1][3] * sf04); + o[2][0] = +(m[1][0] * sf01 - m[1][1] * sf03 + m[1][3] * sf05); + o[3][0] = -(m[1][0] * sf02 - m[1][1] * sf04 + m[1][2] * sf05); + + o[0][1] = -(m[0][1] * sf00 - m[0][2] * sf01 + m[0][3] * sf02); + o[1][1] = +(m[0][0] * sf00 - m[0][2] * sf03 + m[0][3] * sf04); + o[2][1] = -(m[0][0] * sf01 - m[0][1] * sf03 + m[0][3] * sf05); + o[3][1] = +(m[0][0] * sf02 - m[0][1] * sf04 + m[0][2] * sf05); + + o[0][2] = +(m[0][1] * sf06 - m[0][2] * sf07 + m[0][3] * sf08); + o[1][2] = -(m[0][0] * sf06 - m[0][2] * sf09 + m[0][3] * sf10); + o[2][2] = +(m[0][0] * sf11 - m[0][1] * sf09 + m[0][3] * sf12); + o[3][2] = -(m[0][0] * sf08 - m[0][1] * sf10 + m[0][2] * sf12); + + o[0][3] = -(m[0][1] * sf13 - m[0][2] * sf14 + m[0][3] * sf15); + o[1][3] = +(m[0][0] * sf13 - m[0][2] * sf16 + m[0][3] * sf17); + o[2][3] = -(m[0][0] * sf14 - m[0][1] * sf16 + m[0][3] * sf18); + o[3][3] = +(m[0][0] * sf15 - m[0][1] * sf17 + m[0][2] * sf18); + + ood = 1.0f / (m[0][0] * o[0][0] + m[0][1] * o[1][0] + m[0][2] * o[2][0] + m[0][3] * o[3][0]); + + o[0][0] *= ood; o[1][0] *= ood; o[2][0] *= ood; o[3][0] *= ood; + o[0][1] *= ood; o[1][1] *= ood; o[2][1] *= ood; o[3][1] *= ood; + o[0][2] *= ood; o[1][2] *= ood; o[2][2] *= ood; o[3][2] *= ood; + o[0][3] *= ood; o[1][3] *= ood; o[2][3] *= ood; o[3][3] *= ood; + } + + void zpl_mat4_axis_angle(zpl_mat4 *out, zpl_vec3 v, zpl_f32 angle_radians) { + zpl_f32 c, s; + zpl_vec3 axis, t; + zpl_float4 *rot; + + c = zpl_cos(angle_radians); + s = zpl_sin(angle_radians); + + zpl_vec3_norm(&axis, v); + zpl_vec3_mul(&t, axis, 1.0f - c); + + zpl_mat4_identity(out); + rot = zpl_float44_m(out); + + rot[0][0] = c + t.x * axis.x; + rot[0][1] = 0 + t.x * axis.y + s * axis.z; + rot[0][2] = 0 + t.x * axis.z - s * axis.y; + rot[0][3] = 0; + + rot[1][0] = 0 + t.y * axis.x - s * axis.z; + rot[1][1] = c + t.y * axis.y; + rot[1][2] = 0 + t.y * axis.z + s * axis.x; + rot[1][3] = 0; + + rot[2][0] = 0 + t.z * axis.x + s * axis.y; + rot[2][1] = 0 + t.z * axis.y - s * axis.x; + rot[2][2] = c + t.z * axis.z; + rot[2][3] = 0; + } + + void zpl_mat4_to_translate(zpl_mat4* out, zpl_vec3 v) { + zpl_mat4_identity(out); + out->col[3].xyz = v; + } + + void zpl_mat4_to_rotate(zpl_mat4* out, zpl_vec3 v, zpl_f32 angle_radians) { + zpl_mat4_axis_angle(out, v, angle_radians); + } + + void zpl_mat4_to_scale(zpl_mat4* out, zpl_vec3 v) { + zpl_mat4_identity(out); + out->col[0].x = v.x; + out->col[1].y = v.y; + out->col[2].z = v.z; + } + void zpl_mat4_to_scalef(zpl_mat4* out, zpl_f32 s) { + zpl_mat4_identity(out); + out->col[0].x = s; + out->col[1].y = s; + out->col[2].z = s; + } + + void zpl_mat4_translate(zpl_mat4* m, zpl_vec3 v) { + zpl_mat4 mm; + zpl_mat4_to_translate(&mm, v); + zpl_mat4_mul(m, m, &mm); + } + + void zpl_mat4_rotate(zpl_mat4* m, zpl_vec3 v, zpl_f32 angle_radians) { + zpl_mat4 mm; + zpl_mat4_axis_angle(&mm,v, angle_radians); + zpl_mat4_mul(m, m, &mm); + } + + void zpl_mat4_scale(zpl_mat4* m, zpl_vec3 v) { + zpl_mat4 mm; + zpl_mat4_to_scale(&mm, v); + zpl_mat4_mul(m, m, &mm); + } + + void zpl_mat4_scalef(zpl_mat4* m, zpl_f32 s) { + zpl_mat4 mm; + zpl_mat4_to_scalef(&mm, s); + zpl_mat4_mul(m, m, &mm); + } + + void zpl_mat4_ortho2d(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top) { + zpl_float4 *m; + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = 2.0f / (right - left); + m[1][1] = 2.0f / (top - bottom); + m[2][2] = -1.0f; + m[3][0] = -(right + left) / (right - left); + m[3][1] = -(top + bottom) / (top - bottom); + } + + void zpl_mat4_ortho3d(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top, zpl_f32 z_near, zpl_f32 z_far) { + zpl_float4 *m; + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = +2.0f / (right - left); + m[1][1] = +2.0f / (top - bottom); + m[2][2] = -2.0f / (z_far - z_near); + m[3][0] = -(right + left) / (right - left); + m[3][1] = -(top + bottom) / (top - bottom); + m[3][2] = -(z_far + z_near) / (z_far - z_near); + } + + void zpl_mat4_perspective(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near, zpl_f32 z_far) { + zpl_f32 tan_half_fovy = zpl_tan(0.5f * fovy); + zpl_mat4 zero_mat = { 0 }; + zpl_float4 *m = zpl_float44_m(out); + *out = zero_mat; + + m[0][0] = 1.0f / (aspect * tan_half_fovy); + m[1][1] = 1.0f / (tan_half_fovy); + m[2][2] = -(z_far + z_near) / (z_far - z_near); + m[2][3] = -1.0f; + m[3][2] = -2.0f * z_far * z_near / (z_far - z_near); + } + + void zpl_mat4_infinite_perspective(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near) { + zpl_f32 range = zpl_tan(0.5f * fovy) * z_near; + zpl_f32 left = -range * aspect; + zpl_f32 right = range * aspect; + zpl_f32 bottom = -range; + zpl_f32 top = range; + zpl_mat4 zero_mat = { 0 }; + zpl_float4 *m = zpl_float44_m(out); + *out = zero_mat; + + m[0][0] = (2.0f * z_near) / (right - left); + m[1][1] = (2.0f * z_near) / (top - bottom); + m[2][2] = -1.0f; + m[2][3] = -1.0f; + m[3][2] = -2.0f * z_near; + } + + void zpl_mat4_ortho2d_dx(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top) { + zpl_float4 *m; + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = 2.0f / (right - left); + m[1][1] = 2.0f / (top - bottom); + m[2][2] = -1.0f; + m[3][0] = -(right + left) / (right - left); + m[3][1] = -(top + bottom) / (top - bottom); + } + + void zpl_mat4_ortho3d_dx(zpl_mat4 *out, zpl_f32 left, zpl_f32 right, zpl_f32 bottom, zpl_f32 top, zpl_f32 z_near, zpl_f32 z_far) { + zpl_float4 *m; + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = +2.0f / (right - left); + m[1][1] = +2.0f / (top - bottom); + m[2][2] = -1.0f / (z_far - z_near); + m[3][0] = -(right + left) / (right - left); + m[3][1] = -(top + bottom) / (top - bottom); + m[3][2] = -( z_near) / (z_far - z_near); + } + + void zpl_mat4_perspective_dx(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near, zpl_f32 z_far) { + zpl_f32 tan_half_fovy = zpl_tan(0.5f * fovy); + zpl_mat4 zero_mat = { 0 }; + zpl_float4 *m = zpl_float44_m(out); + *out = zero_mat; + + m[0][0] = 1.0f / (aspect * tan_half_fovy); + m[1][1] = 1.0f / (tan_half_fovy); + m[2][2] = -(z_far ) / (z_far - z_near); + m[2][3] = -1.0f; + m[3][2] = - z_near / (z_far - z_near); + } + + void zpl_mat4_infinite_perspective_dx(zpl_mat4 *out, zpl_f32 fovy, zpl_f32 aspect, zpl_f32 z_near) { + zpl_f32 tan_half_fovy = zpl_tan(0.5f * fovy); + zpl_mat4 zero_mat = { 0 }; + zpl_float4 *m = zpl_float44_m(out); + *out = zero_mat; + + m[0][0] = 1.0f / (aspect * tan_half_fovy); + m[1][1] = 1.0f / (tan_half_fovy); + m[2][2] = -1.0f; + m[2][3] = -1.0f; + m[3][2] = - z_near; + } + + + + void zpl_mat4_look_at(zpl_mat4 *out, zpl_vec3 eye, zpl_vec3 centre, zpl_vec3 up) { + zpl_vec3 f, s, u; + zpl_float4 *m; + + zpl_vec3_sub(&f, centre, eye); + zpl_vec3_norm(&f, f); + + zpl_vec3_cross(&s, f, up); + zpl_vec3_norm(&s, s); + + zpl_vec3_cross(&u, s, f); + + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = +s.x; + m[1][0] = +s.y; + m[2][0] = +s.z; + + m[0][1] = +u.x; + m[1][1] = +u.y; + m[2][1] = +u.z; + + m[0][2] = -f.x; + m[1][2] = -f.y; + m[2][2] = -f.z; + + m[3][0] = -zpl_vec3_dot(s, eye); + m[3][1] = -zpl_vec3_dot(u, eye); + m[3][2] = +zpl_vec3_dot(f, eye); + } + + void zpl_mat4_look_at_lh(zpl_mat4 *out, zpl_vec3 eye, zpl_vec3 centre, zpl_vec3 up) { + zpl_vec3 f, s, u; + zpl_float4 *m; + + zpl_vec3_sub(&f, centre, eye); + zpl_vec3_norm(&f, f); + + zpl_vec3_cross(&s, up, f); + zpl_vec3_norm(&s, s); + + zpl_vec3_cross(&u, f, s); + + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = +s.x; + m[1][0] = +s.y; + m[2][0] = +s.z; + + m[0][1] = +u.x; + m[1][1] = +u.y; + m[2][1] = +u.z; + + m[0][2] = +f.x; + m[1][2] = +f.y; + m[2][2] = +f.z; + + m[3][0] = -zpl_vec3_dot(s, eye); + m[3][1] = -zpl_vec3_dot(u, eye); + m[3][2] = -zpl_vec3_dot(f, eye); + } + + zpl_quat zpl_quatf(zpl_f32 x, zpl_f32 y, zpl_f32 z, zpl_f32 w) { + zpl_quat q; + q.x = x; + q.y = y; + q.z = z; + q.w = w; + return q; + } + zpl_quat zpl_quatfv(zpl_f32 e[4]) { + zpl_quat q; + q.x = e[0]; + q.y = e[1]; + q.z = e[2]; + q.w = e[3]; + return q; + } + + zpl_quat zpl_quat_axis_angle(zpl_vec3 axis, zpl_f32 angle_radians) { + zpl_quat q; + zpl_vec3_norm(&q.xyz, axis); + zpl_vec3_muleq(&q.xyz, zpl_sin(0.5f * angle_radians)); + q.w = zpl_cos(0.5f * angle_radians); + return q; + } + + zpl_quat zpl_quat_euler_angles(zpl_f32 pitch, zpl_f32 yaw, zpl_f32 roll) { + /* TODO: Do without multiplication, i.e. make it faster */ + zpl_quat q, p, y, r; + p = zpl_quat_axis_angle(zpl_vec3f(1, 0, 0), pitch); + y = zpl_quat_axis_angle(zpl_vec3f(0, 1, 0), yaw); + r = zpl_quat_axis_angle(zpl_vec3f(0, 0, 1), roll); + + zpl_quat_mul(&q, y, p); + zpl_quat_muleq(&q, r); + + return q; + } + + zpl_quat zpl_quat_identity(void) { + zpl_quat q = { 0, 0, 0, 1 }; + return q; + } + + void zpl_quat_add(zpl_quat *d, zpl_quat q0, zpl_quat q1) { zpl_vec4_add(&d->xyzw, q0.xyzw, q1.xyzw); } + void zpl_quat_sub(zpl_quat *d, zpl_quat q0, zpl_quat q1) { zpl_vec4_sub(&d->xyzw, q0.xyzw, q1.xyzw); } + + void zpl_quat_mul(zpl_quat *d, zpl_quat q0, zpl_quat q1) { + d->x = q0.w * q1.x + q0.x * q1.w + q0.y * q1.z - q0.z * q1.y; + d->y = q0.w * q1.y - q0.x * q1.z + q0.y * q1.w + q0.z * q1.x; + d->z = q0.w * q1.z + q0.x * q1.y - q0.y * q1.x + q0.z * q1.w; + d->w = q0.w * q1.w - q0.x * q1.x - q0.y * q1.y - q0.z * q1.z; + } + + void zpl_quat_div(zpl_quat *d, zpl_quat q0, zpl_quat q1) { + zpl_quat iq1; + zpl_quat_inverse(&iq1, q1); + zpl_quat_mul(d, q0, iq1); + } + + void zpl_quat_mulf(zpl_quat *d, zpl_quat q0, zpl_f32 s) { zpl_vec4_mul(&d->xyzw, q0.xyzw, s); } + void zpl_quat_divf(zpl_quat *d, zpl_quat q0, zpl_f32 s) { zpl_vec4_div(&d->xyzw, q0.xyzw, s); } + + void zpl_quat_addeq(zpl_quat *d, zpl_quat q) { zpl_vec4_addeq(&d->xyzw, q.xyzw); } + void zpl_quat_subeq(zpl_quat *d, zpl_quat q) { zpl_vec4_subeq(&d->xyzw, q.xyzw); } + void zpl_quat_muleq(zpl_quat *d, zpl_quat q) { zpl_quat_mul(d, *d, q); } + void zpl_quat_diveq(zpl_quat *d, zpl_quat q) { zpl_quat_div(d, *d, q); } + + void zpl_quat_muleqf(zpl_quat *d, zpl_f32 s) { zpl_vec4_muleq(&d->xyzw, s); } + void zpl_quat_diveqf(zpl_quat *d, zpl_f32 s) { zpl_vec4_diveq(&d->xyzw, s); } + + zpl_f32 zpl_quat_dot(zpl_quat q0, zpl_quat q1) { + zpl_f32 r = zpl_vec3_dot(q0.xyz, q1.xyz) + q0.w * q1.w; + return r; + } + zpl_f32 zpl_quat_mag(zpl_quat q) { + zpl_f32 r = zpl_sqrt(zpl_quat_dot(q, q)); + return r; + } + + void zpl_quat_norm(zpl_quat *d, zpl_quat q) { zpl_quat_divf(d, q, zpl_quat_mag(q)); } + + void zpl_quat_conj(zpl_quat *d, zpl_quat q) { + d->xyz = zpl_vec3f(-q.x, -q.y, -q.z); + d->w = q.w; + } + void zpl_quat_inverse(zpl_quat *d, zpl_quat q) { + zpl_quat_conj(d, q); + zpl_quat_diveqf(d, zpl_quat_dot(q, q)); + } + + void zpl_quat_axis(zpl_vec3 *axis, zpl_quat q) { + zpl_quat n; + zpl_quat_norm(&n, q); + zpl_vec3_div(axis, n.xyz, zpl_sin(zpl_arccos(q.w))); + } + + zpl_f32 zpl_quat_angle(zpl_quat q) { + zpl_f32 mag = zpl_quat_mag(q); + zpl_f32 c = q.w * (1.0f / mag); + zpl_f32 angle = 2.0f * zpl_arccos(c); + return angle; + } + + zpl_f32 zpl_quat_roll(zpl_quat q) { + return zpl_arctan2(2.0f * q.x * q.y + q.z * q.w, q.x * q.x + q.w * q.w - q.y * q.y - q.z * q.z); + } + zpl_f32 zpl_quat_pitch(zpl_quat q) { + return zpl_arctan2(2.0f * q.y * q.z + q.w * q.x, q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z); + } + zpl_f32 zpl_quat_yaw(zpl_quat q) { return zpl_arcsin(-2.0f * (q.x * q.z - q.w * q.y)); } + + void zpl_quat_rotate_vec3(zpl_vec3 *d, zpl_quat q, zpl_vec3 v) { + /* zpl_vec3 t = 2.0f * cross(q.xyz, v); + * *d = q.w*t + v + cross(q.xyz, t); + */ + zpl_vec3 t, p; + zpl_vec3_cross(&t, q.xyz, v); + zpl_vec3_muleq(&t, 2.0f); + + zpl_vec3_cross(&p, q.xyz, t); + + zpl_vec3_mul(d, t, q.w); + zpl_vec3_addeq(d, v); + zpl_vec3_addeq(d, p); + } + + void zpl_mat4_from_quat(zpl_mat4 *out, zpl_quat q) { + zpl_float4 *m; + zpl_quat a; + zpl_f32 xx, yy, zz, xy, xz, yz, wx, wy, wz; + + zpl_quat_norm(&a, q); + xx = a.x * a.x; + yy = a.y * a.y; + zz = a.z * a.z; + xy = a.x * a.y; + xz = a.x * a.z; + yz = a.y * a.z; + wx = a.w * a.x; + wy = a.w * a.y; + wz = a.w * a.z; + + zpl_mat4_identity(out); + m = zpl_float44_m(out); + + m[0][0] = 1.0f - 2.0f * (yy + zz); + m[0][1] = 2.0f * (xy + wz); + m[0][2] = 2.0f * (xz - wy); + + m[1][0] = 2.0f * (xy - wz); + m[1][1] = 1.0f - 2.0f * (xx + zz); + m[1][2] = 2.0f * (yz + wx); + + m[2][0] = 2.0f * (xz + wy); + m[2][1] = 2.0f * (yz - wx); + m[2][2] = 1.0f - 2.0f * (xx + yy); + } + + void zpl_quat_from_mat4(zpl_quat *out, zpl_mat4 *mat) { + zpl_float4 *m; + zpl_f32 four_x_squared_minus_1, four_y_squared_minus_1, four_z_squared_minus_1, four_w_squared_minus_1, + four_biggest_squared_minus_1; + int biggest_index = 0; + zpl_f32 biggest_value, mult; + + m = zpl_float44_m(mat); + + four_x_squared_minus_1 = m[0][0] - m[1][1] - m[2][2]; + four_y_squared_minus_1 = m[1][1] - m[0][0] - m[2][2]; + four_z_squared_minus_1 = m[2][2] - m[0][0] - m[1][1]; + four_w_squared_minus_1 = m[0][0] + m[1][1] + m[2][2]; + + four_biggest_squared_minus_1 = four_w_squared_minus_1; + if (four_x_squared_minus_1 > four_biggest_squared_minus_1) { + four_biggest_squared_minus_1 = four_x_squared_minus_1; + biggest_index = 1; + } + if (four_y_squared_minus_1 > four_biggest_squared_minus_1) { + four_biggest_squared_minus_1 = four_y_squared_minus_1; + biggest_index = 2; + } + if (four_z_squared_minus_1 > four_biggest_squared_minus_1) { + four_biggest_squared_minus_1 = four_z_squared_minus_1; + biggest_index = 3; + } + + biggest_value = zpl_sqrt(four_biggest_squared_minus_1 + 1.0f) * 0.5f; + mult = 0.25f / biggest_value; + + switch (biggest_index) { + case 0: + out->w = biggest_value; + out->x = (m[1][2] - m[2][1]) * mult; + out->y = (m[2][0] - m[0][2]) * mult; + out->z = (m[0][1] - m[1][0]) * mult; + break; + case 1: + out->w = (m[1][2] - m[2][1]) * mult; + out->x = biggest_value; + out->y = (m[0][1] + m[1][0]) * mult; + out->z = (m[2][0] + m[0][2]) * mult; + break; + case 2: + out->w = (m[2][0] - m[0][2]) * mult; + out->x = (m[0][1] + m[1][0]) * mult; + out->y = biggest_value; + out->z = (m[1][2] + m[2][1]) * mult; + break; + case 3: + out->w = (m[0][1] - m[1][0]) * mult; + out->x = (m[2][0] + m[0][2]) * mult; + out->y = (m[1][2] + m[2][1]) * mult; + out->z = biggest_value; + break; + } + } + + zpl_f32 zpl_plane_distance(zpl_plane* p, zpl_vec3 v) { + return (p->a * v.x + p->b * v.y + p->c * v.z + p->d); + } + + void zpl_frustum_create(zpl_frustum* out, zpl_mat4* camera, zpl_mat4* proj) { + zpl_mat4 pv; + + zpl_mat4_mul(&pv, camera, proj); + + zpl_plane* fp = 0; + zpl_f32 rmag; + + fp = &out->x1; + fp->a = pv.x.w + pv.x.x; + fp->b = pv.y.w + pv.x.y; + fp->c = pv.z.w + pv.x.z; + fp->d = pv.w.w + pv.x.w; + + rmag = zpl_rsqrt(zpl_square(fp->a) + zpl_square(fp->b) + zpl_square(fp->c)); + + fp->a *= rmag; + fp->b *= rmag; + fp->c *= rmag; + fp->d *= rmag; + + fp = &out->x2; + + fp->a = pv.x.w - pv.x.x; + fp->b = pv.y.w - pv.x.y; + fp->c = pv.z.w - pv.x.z; + fp->d = pv.w.w - pv.x.w; + + rmag = zpl_rsqrt(zpl_square(fp->a) + zpl_square(fp->b) + zpl_square(fp->c)); + + fp->a *= rmag; + fp->b *= rmag; + fp->c *= rmag; + fp->d *= rmag; + + fp = &out->y1; + + fp->a = pv.x.w - pv.y.x; + fp->b = pv.y.w - pv.y.y; + fp->c = pv.z.w - pv.y.w; + fp->d = pv.w.w - pv.y.z; + + rmag = zpl_rsqrt(zpl_square(fp->a) + zpl_square(fp->b) + zpl_square(fp->c)); + + fp->a *= rmag; + fp->b *= rmag; + fp->c *= rmag; + fp->d *= rmag; + + fp = &out->y2; + + fp->a = pv.x.w + pv.y.x; + fp->b = pv.y.w + pv.y.y; + fp->c = pv.z.w + pv.y.z; + fp->d = pv.w.w + pv.y.w; + + rmag = zpl_rsqrt(zpl_square(fp->a) + zpl_square(fp->b) + zpl_square(fp->c)); + + fp->a *= rmag; + fp->b *= rmag; + fp->c *= rmag; + fp->d *= rmag;; + + fp = &out->z1; + + fp->a = pv.x.w + pv.z.x; + fp->b = pv.y.w + pv.z.y; + fp->c = pv.z.w + pv.z.z; + fp->d = pv.w.w + pv.z.w; + + rmag = zpl_rsqrt(zpl_square(fp->a) + zpl_square(fp->b) + zpl_square(fp->c)); + + fp->a *= rmag; + fp->b *= rmag; + fp->c *= rmag; + fp->d *= rmag; + + fp = &out->z2; + + fp->a = pv.x.w - pv.z.x; + fp->b = pv.y.w - pv.z.y; + fp->c = pv.z.w - pv.z.z; + fp->d = pv.w.w - pv.z.w; + + rmag = zpl_rsqrt(zpl_square(fp->a) + zpl_square(fp->b) + zpl_square(fp->c)); + + fp->a *= rmag; + fp->b *= rmag; + fp->c *= rmag; + fp->d *= rmag; + } + + zpl_b8 zpl_frustum_sphere_inside(zpl_frustum* frustum, zpl_vec3 center, zpl_f32 radius) { + if (zpl_plane_distance(&frustum->x1, center) <= -radius) return 0; + if (zpl_plane_distance(&frustum->x2, center) <= -radius) return 0; + if (zpl_plane_distance(&frustum->y1, center) <= -radius) return 0; + if (zpl_plane_distance(&frustum->y2, center) <= -radius) return 0; + if (zpl_plane_distance(&frustum->z1, center) <= -radius) return 0; + if (zpl_plane_distance(&frustum->z2, center) <= -radius) return 0; + + return 1; + } + + zpl_b8 zpl_frustum_point_inside(zpl_frustum* frustum, zpl_vec3 point) { + return zpl_frustum_sphere_inside(frustum, point, 0.0f); + } + + zpl_b8 zpl_frustum_box_inside(zpl_frustum* frustum, zpl_aabb3 aabb) { + zpl_vec3 box, center; + zpl_vec3 v, b; + zpl_vec3_sub(&box, aabb.max, aabb.min); + zpl_vec3_diveq(&box, 2.0f); + zpl_vec3_add(¢er, aabb.min, box); + + b = zpl_vec3f(-box.x, -box.y, -box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(+box.x, -box.y, -box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(-box.x, +box.y, -box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(+box.x, +box.y, -box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(+box.x, +box.y, +box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(-box.x, +box.y, +box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(-box.x, -box.y, +box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + b = zpl_vec3f(+box.x, -box.y, +box.z); + zpl_vec3_add(&v, b, center); + + if (zpl_frustum_point_inside(frustum, v)) return 1; + + return 0; + } + + zpl_f32 zpl_lerp(zpl_f32 a, zpl_f32 b, zpl_f32 t) { return a * (1.0f - t) + b * t; } + zpl_f32 zpl_unlerp(zpl_f32 t, zpl_f32 a, zpl_f32 b) { return (t - a) / (b - a); } + zpl_f32 zpl_smooth_step(zpl_f32 a, zpl_f32 b, zpl_f32 t) { + zpl_f32 x = (t - a) / (b - a); + return x * x * (3.0f - 2.0f * x); + } + zpl_f32 zpl_smoother_step(zpl_f32 a, zpl_f32 b, zpl_f32 t) { + zpl_f32 x = (t - a) / (b - a); + return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f); + } + + #define ZPL_VEC_LERPN(N, d, a, b, t) \ + zpl_vec##N db; \ + zpl_vec##N##_sub(&db, b, a); \ + zpl_vec##N##_muleq(&db, t); \ + zpl_vec##N##_add(d, a, db) + + void zpl_vec2_lerp(zpl_vec2 *d, zpl_vec2 a, zpl_vec2 b, zpl_f32 t) { ZPL_VEC_LERPN(2, d, a, b, t); } + void zpl_vec3_lerp(zpl_vec3 *d, zpl_vec3 a, zpl_vec3 b, zpl_f32 t) { ZPL_VEC_LERPN(3, d, a, b, t); } + void zpl_vec4_lerp(zpl_vec4 *d, zpl_vec4 a, zpl_vec4 b, zpl_f32 t) { ZPL_VEC_LERPN(4, d, a, b, t); } + + #undef ZPL_VEC_LERPN + + void zpl_vec2_cslerp(zpl_vec2 *d, zpl_vec2 a, zpl_vec2 v0, zpl_vec2 b, zpl_vec2 v1, zpl_f32 t) { + zpl_f32 t2 = t * t; + zpl_f32 ti = (t - 1); + zpl_f32 ti2 = ti * ti; + + zpl_f32 h00 = (1 + 2 * t) * ti2; + zpl_f32 h10 = t * ti2; + zpl_f32 h01 = t2 * (3 - 2 * t); + zpl_f32 h11 = t2 * ti; + + d->x = h00 * a.x + h10 * v0.x + h01 * b.x + h11 * v1.x; + d->y = h00 * a.y + h10 * v0.y + h01 * b.y + h11 * v1.y; + } + + void zpl_vec3_cslerp(zpl_vec3 *d, zpl_vec3 a, zpl_vec3 v0, zpl_vec3 b, zpl_vec3 v1, zpl_f32 t) { + zpl_f32 t2 = t * t; + zpl_f32 ti = (t - 1); + zpl_f32 ti2 = ti * ti; + + zpl_f32 h00 = (1 + 2 * t) * ti2; + zpl_f32 h10 = t * ti2; + zpl_f32 h01 = t2 * (3 - 2 * t); + zpl_f32 h11 = t2 * ti; + + d->x = h00 * a.x + h10 * v0.x + h01 * b.x + h11 * v1.x; + d->y = h00 * a.y + h10 * v0.y + h01 * b.y + h11 * v1.y; + d->z = h00 * a.z + h10 * v0.z + h01 * b.z + h11 * v1.z; + } + + void zpl_vec2_dcslerp(zpl_vec2 *d, zpl_vec2 a, zpl_vec2 v0, zpl_vec2 b, zpl_vec2 v1, zpl_f32 t) { + zpl_f32 t2 = t * t; + + zpl_f32 dh00 = 6 * t2 - 6 * t; + zpl_f32 dh10 = 3 * t2 - 4 * t + 1; + zpl_f32 dh01 = -6 * t2 + 6 * t; + zpl_f32 dh11 = 3 * t2 - 2 * t; + + d->x = dh00 * a.x + dh10 * v0.x + dh01 * b.x + dh11 * v1.x; + d->y = dh00 * a.y + dh10 * v0.y + dh01 * b.y + dh11 * v1.y; + } + + void zpl_vec3_dcslerp(zpl_vec3 *d, zpl_vec3 a, zpl_vec3 v0, zpl_vec3 b, zpl_vec3 v1, zpl_f32 t) { + zpl_f32 t2 = t * t; + + zpl_f32 dh00 = 6 * t2 - 6 * t; + zpl_f32 dh10 = 3 * t2 - 4 * t + 1; + zpl_f32 dh01 = -6 * t2 + 6 * t; + zpl_f32 dh11 = 3 * t2 - 2 * t; + + d->x = dh00 * a.x + dh10 * v0.x + dh01 * b.x + dh11 * v1.x; + d->y = dh00 * a.y + dh10 * v0.y + dh01 * b.y + dh11 * v1.y; + d->z = dh00 * a.z + dh10 * v0.z + dh01 * b.z + dh11 * v1.z; + } + + void zpl_quat_lerp(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t) { zpl_vec4_lerp(&d->xyzw, a.xyzw, b.xyzw, t); } + void zpl_quat_nlerp(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t) { + zpl_quat_lerp(d, a, b, t); + zpl_quat_norm(d, *d); + } + + void zpl_quat_slerp(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t) { + zpl_quat x, y, z; + zpl_f32 cos_theta, angle; + zpl_f32 s1, s0, is; + + z = b; + cos_theta = zpl_quat_dot(a, b); + + if (cos_theta < 0.0f) { + z = zpl_quatf(-b.x, -b.y, -b.z, -b.w); + cos_theta = -cos_theta; + } + + if (cos_theta > 1.0f) { + /* NOTE: Use lerp not nlerp as it's not a real angle or they are not normalized */ + zpl_quat_lerp(d, a, b, t); + } + + angle = zpl_arccos(cos_theta); + + s1 = zpl_sin((1.0f - t) * angle); + s0 = zpl_sin(t * angle); + is = 1.0f / zpl_sin(angle); + zpl_quat_mulf(&x, a, s1); + zpl_quat_mulf(&y, z, s0); + zpl_quat_add(d, x, y); + zpl_quat_muleqf(d, is); + } + + void zpl_quat_slerp_approx(zpl_quat *d, zpl_quat a, zpl_quat b, zpl_f32 t) { + /* NOTE: Derived by taylor expanding the geometric interpolation equation + * Even works okay for nearly anti-parallel versors!!! + */ + /* NOTE: Extra interations cannot be used as they require angle^4 which is not worth it to approximate */ + zpl_f32 tp = t + (1.0f - zpl_quat_dot(a, b)) / 3.0f * t * (-2.0f * t * t + 3.0f * t - 1.0f); + zpl_quat_nlerp(d, a, b, tp); + } + + void zpl_quat_nquad(zpl_quat *d, zpl_quat p, zpl_quat a, zpl_quat b, zpl_quat q, zpl_f32 t) { + zpl_quat x, y; + zpl_quat_nlerp(&x, p, q, t); + zpl_quat_nlerp(&y, a, b, t); + zpl_quat_nlerp(d, x, y, 2.0f * t * (1.0f - t)); + } + + void zpl_quat_squad(zpl_quat *d, zpl_quat p, zpl_quat a, zpl_quat b, zpl_quat q, zpl_f32 t) { + zpl_quat x, y; + zpl_quat_slerp(&x, p, q, t); + zpl_quat_slerp(&y, a, b, t); + zpl_quat_slerp(d, x, y, 2.0f * t * (1.0f - t)); + } + + void zpl_quat_squad_approx(zpl_quat *d, zpl_quat p, zpl_quat a, zpl_quat b, zpl_quat q, zpl_f32 t) { + zpl_quat x, y; + zpl_quat_slerp_approx(&x, p, q, t); + zpl_quat_slerp_approx(&y, a, b, t); + zpl_quat_slerp_approx(d, x, y, 2.0f * t * (1.0f - t)); + } + + zpl_rect2 zpl_rect2f(zpl_vec2 pos, zpl_vec2 dim) { + zpl_rect2 r; + r.pos = pos; + r.dim = dim; + return r; + } + + zpl_rect3 zpl_rect3f(zpl_vec3 pos, zpl_vec3 dim) { + zpl_rect3 r; + r.pos = pos; + r.dim = dim; + return r; + } + + zpl_aabb2 zpl_aabb2f(zpl_f32 minx, zpl_f32 miny, zpl_f32 maxx, zpl_f32 maxy) { + zpl_aabb2 r; + r.min = zpl_vec2f(minx, miny); + r.max = zpl_vec2f(maxx, maxy); + return r; + } + zpl_aabb3 zpl_aabb3f(zpl_f32 minx, zpl_f32 miny, zpl_f32 minz, zpl_f32 maxx, zpl_f32 maxy, zpl_f32 maxz) { + zpl_aabb3 r; + r.min = zpl_vec3f(minx, miny, minz); + r.max = zpl_vec3f(maxx, maxy, maxz); + return r; + } + + zpl_aabb2 zpl_aabb2_rect2(zpl_rect2 a) { + zpl_aabb2 r; + r.min = a.pos; + zpl_vec2_add(&r.max, a.pos, a.dim); + return r; + } + zpl_aabb3 zpl_aabb3_rect3(zpl_rect3 a) { + zpl_aabb3 r; + r.min = a.pos; + zpl_vec3_add(&r.max, a.pos, a.dim); + return r; + } + + zpl_rect2 zpl_rect2_aabb2(zpl_aabb2 a) { + zpl_rect2 r; + r.pos = a.min; + zpl_vec2_sub(&r.dim, a.max, a.min); + return r; + } + zpl_rect3 zpl_rect3_aabb3(zpl_aabb3 a) { + zpl_rect3 r; + r.pos = a.min; + zpl_vec3_sub(&r.dim, a.max, a.min); + return r; + } + + int zpl_rect2_contains(zpl_rect2 a, zpl_f32 x, zpl_f32 y) { + zpl_f32 min_x = zpl_min(a.pos.x, a.pos.x + a.dim.x); + zpl_f32 max_x = zpl_max(a.pos.x, a.pos.x + a.dim.x); + zpl_f32 min_y = zpl_min(a.pos.y, a.pos.y + a.dim.y); + zpl_f32 max_y = zpl_max(a.pos.y, a.pos.y + a.dim.y); + int result = (x >= min_x) & (x < max_x) & (y >= min_y) & (y < max_y); + return result; + } + + int zpl_rect2_contains_vec2(zpl_rect2 a, zpl_vec2 p) { return zpl_rect2_contains(a, p.x, p.y); } + + int zpl_rect2_intersects(zpl_rect2 a, zpl_rect2 b) { + zpl_rect2 r = { 0 }; + return zpl_rect2_intersection_result(a, b, &r); + } + + int zpl_rect2_intersection_result(zpl_rect2 a, zpl_rect2 b, zpl_rect2 *intersection) { + zpl_f32 a_min_x = zpl_min(a.pos.x, a.pos.x + a.dim.x); + zpl_f32 a_max_x = zpl_max(a.pos.x, a.pos.x + a.dim.x); + zpl_f32 a_min_y = zpl_min(a.pos.y, a.pos.y + a.dim.y); + zpl_f32 a_max_y = zpl_max(a.pos.y, a.pos.y + a.dim.y); + + zpl_f32 b_min_x = zpl_min(b.pos.x, b.pos.x + b.dim.x); + zpl_f32 b_max_x = zpl_max(b.pos.x, b.pos.x + b.dim.x); + zpl_f32 b_min_y = zpl_min(b.pos.y, b.pos.y + b.dim.y); + zpl_f32 b_max_y = zpl_max(b.pos.y, b.pos.y + b.dim.y); + + zpl_f32 x0 = zpl_max(a_min_x, b_min_x); + zpl_f32 y0 = zpl_max(a_min_y, b_min_y); + zpl_f32 x1 = zpl_min(a_max_x, b_max_x); + zpl_f32 y1 = zpl_min(a_max_y, b_max_y); + + if ((x0 < x1) && (y0 < y1)) { + zpl_rect2 r = zpl_rect2f(zpl_vec2f(x0, y0), zpl_vec2f(x1 - x0, y1 - y0)); + *intersection = r; + return 1; + } else { + zpl_rect2 r = { 0 }; + *intersection = r; + return 0; + } + } + + int zpl_aabb2_contains(zpl_aabb2 a, zpl_f32 x, zpl_f32 y) { + return (zpl_is_between_limit(x, a.min.x, a.max.x) && zpl_is_between_limit(y, a.min.y, a.max.y)); + } + + int zpl_aabb3_contains(zpl_aabb3 a, zpl_f32 x, zpl_f32 y, zpl_f32 z) { + return (zpl_is_between_limit(x, a.min.x, a.max.x) && zpl_is_between_limit(y, a.min.y, a.max.y) && zpl_is_between_limit(z, a.min.z, a.max.z)); + } + + zpl_aabb2 zpl_aabb2_cut_left(zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 minx = a->min.x; + a->min.x = zpl_min(a->max.x, a->min.x + b); + return zpl_aabb2f(minx, a->min.y, a->min.x, a->max.y); + } + zpl_aabb2 zpl_aabb2_cut_right(zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 maxx = a->max.x; + a->max.x = zpl_max(a->min.x, a->max.x - b); + return zpl_aabb2f(a->max.x, a->min.y, maxx, a->max.y); + } + zpl_aabb2 zpl_aabb2_cut_top(zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 miny = a->min.y; + a->min.y = zpl_min(a->max.y, a->min.y + b); + return zpl_aabb2f(a->min.x, miny, a->max.x, a->min.y); + } + zpl_aabb2 zpl_aabb2_cut_bottom(zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 maxy = a->max.y; + a->max.y = zpl_max(a->min.y, a->max.y - b); + return zpl_aabb2f(a->min.x, a->max.y, a->max.x, maxy); + } + + zpl_aabb2 zpl_aabb2_get_left(const zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 minx = a->min.x; + zpl_f32 aminx = zpl_min(a->max.x, a->min.x + b); + return zpl_aabb2f(minx, a->min.y, aminx, a->max.y); + } + zpl_aabb2 zpl_aabb2_get_right(const zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 maxx = a->max.x; + zpl_f32 amaxx = zpl_max(a->min.x, a->max.x - b); + return zpl_aabb2f(amaxx, a->min.y, maxx, a->max.y); + } + zpl_aabb2 zpl_aabb2_get_top(const zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 miny = a->min.y; + zpl_f32 aminy = zpl_min(a->max.y, a->min.y + b); + return zpl_aabb2f(a->min.x, miny, a->max.x, aminy); + } + zpl_aabb2 zpl_aabb2_get_bottom(const zpl_aabb2 *a, zpl_f32 b) { + zpl_f32 maxy = a->max.y; + zpl_f32 amaxy = zpl_max(a->min.y, a->max.y - b); + return zpl_aabb2f(a->min.x, amaxy, a->max.x, maxy); + } + + zpl_aabb2 zpl_aabb2_add_left(const zpl_aabb2 *a, zpl_f32 b) { + return zpl_aabb2f(a->min.x-b, a->min.y, a->min.x, a->max.y); + } + zpl_aabb2 zpl_aabb2_add_right(const zpl_aabb2 *a, zpl_f32 b) { + return zpl_aabb2f(a->max.x, a->min.y, a->max.x+b, a->max.y); + } + zpl_aabb2 zpl_aabb2_add_top(const zpl_aabb2 *a, zpl_f32 b) { + return zpl_aabb2f(a->min.x, a->min.y-b, a->max.x, a->min.y); + } + zpl_aabb2 zpl_aabb2_add_bottom(const zpl_aabb2 *a, zpl_f32 b) { + return zpl_aabb2f(a->min.x, a->max.y, a->max.x, a->max.y+b); + } + + zpl_aabb2 zpl_aabb2_contract(const zpl_aabb2 *a, zpl_f32 b) { + zpl_aabb2 r = *a; + zpl_vec2 vb = zpl_vec2f(b, b); + zpl_vec2_addeq(&r.min, vb); + zpl_vec2_subeq(&r.max, vb); + + if (zpl_vec2_mag2(r.min) > zpl_vec2_mag2(r.max)) { + return zpl_aabb2f(0,0,0,0); + } + return r; + } + zpl_aabb2 zpl_aabb2_expand(const zpl_aabb2 *a, zpl_f32 b) { + return zpl_aabb2_contract(a, -b); + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_THREADING) + // file: source/threading/fence.c + + + ZPL_BEGIN_C_DECLS + + #if defined(_MSC_VER) + /* Microsoft C/C++-compatible compiler */ + # include + #elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) + /* GCC-compatible compiler, targeting x86/x86-64 */ + # include + #elif defined(__GNUC__) && defined(__ARM_NEON__) + /* GCC-compatible compiler, targeting ARM with NEON */ + # include + #elif defined(__GNUC__) && defined(__IWMMXT__) + /* GCC-compatible compiler, targeting ARM with WMMX */ + # include + #elif (defined(__GNUC__) || defined(__xlC__)) && (defined(__VEC__) || defined(__ALTIVEC__)) + /* XLC or GCC-compatible compiler, targeting PowerPC with VMX/VSX */ + # include + #elif defined(__GNUC__) && defined(__SPE__) + /* GCC-compatible compiler, targeting PowerPC with SPE */ + # include + #endif + + void zpl_yield_thread(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + _mm_pause(); + # elif defined(ZPL_SYSTEM_OSX) || defined(ZPL_COMPILER_TINYC) + __asm__ volatile ("" : : : "memory"); + # elif defined(ZPL_CPU_X86) + _mm_pause(); + # endif + } + + void zpl_mfence(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + _ReadWriteBarrier(); + # elif defined(ZPL_COMPILER_TINYC) + __asm__ volatile ("" : : : "memory"); + # elif defined(ZPL_SYSTEM_OSX) + __sync_synchronize(); + # elif defined(ZPL_CPU_X86) + _mm_mfence(); + # endif + } + + void zpl_sfence(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + _WriteBarrier(); + # elif defined(ZPL_SYSTEM_OSX) || defined(ZPL_COMPILER_TINYC) + __asm__ volatile ("" : : : "memory"); + # elif defined(ZPL_CPU_X86) + _mm_sfence(); + # endif + } + + void zpl_lfence(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + _ReadBarrier(); + # elif defined(ZPL_SYSTEM_OSX) || defined(ZPL_COMPILER_TINYC) + __asm__ volatile ("" : : : "memory"); + # elif defined(ZPL_CPU_X86) + _mm_lfence(); + # endif + } + + ZPL_END_C_DECLS + // file: source/threading/atomic.c + + + ZPL_BEGIN_C_DECLS + + //////////////////////////////////////////////////////////////// + // + // Concurrency + // + // + // IMPORTANT TODO: Use compiler intrinsics for the atomics + + #if defined(ZPL_COMPILER_MSVC) && !defined(ZPL_COMPILER_CLANG) + zpl_i32 zpl_atomic32_load (zpl_atomic32 const *a) { return a->value; } + void zpl_atomic32_store(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) value) { a->value = value; } + + zpl_i32 zpl_atomic32_compare_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) expected, zpl_atomicarg(zpl_i32) desired) { + return _InterlockedCompareExchange(cast(long *)a, desired, expected); + } + zpl_i32 zpl_atomic32_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) desired) { + return _InterlockedExchange(cast(long *)a, desired); + } + zpl_i32 zpl_atomic32_fetch_add(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + return _InterlockedExchangeAdd(cast(long *)a, operand); + } + zpl_i32 zpl_atomic32_fetch_and(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + return _InterlockedAnd(cast(long *)a, operand); + } + zpl_i32 zpl_atomic32_fetch_or(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + return _InterlockedOr(cast(long *)a, operand); + } + + zpl_i64 zpl_atomic64_load(zpl_atomic64 const *a) { + # if defined(ZPL_ARCH_64_BIT) + return a->value; + # elif ZPL_CPU_X86 + // NOTE: The most compatible way to get an atomic 64-bit load on x86 is with cmpxchg8b + zpl_atomicarg(zpl_i64) result; + __asm { + mov esi, a; + mov ebx, eax; + mov ecx, edx; + lock cmpxchg8b [esi]; + mov dword ptr result, eax; + mov dword ptr result[4], edx; + } + return result; + # else + # error TODO: atomics for this CPU + # endif + } + + void zpl_atomic64_store(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) value) { + # if defined(ZPL_ARCH_64_BIT) + a->value = value; + # elif ZPL_CPU_X86 + // NOTE: The most compatible way to get an atomic 64-bit store on x86 is with cmpxchg8b + __asm { + mov esi, a; + mov ebx, dword ptr value; + mov ecx, dword ptr value[4]; + retry: + cmpxchg8b [esi]; + jne retry; + } + # else + # error TODO: atomics for this CPU + # endif + } + + zpl_i64 zpl_atomic64_compare_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) expected, zpl_atomicarg(zpl_i64) desired) { + return _InterlockedCompareExchange64(cast(zpl_atomicarg(zpl_i64) *)a, desired, expected); + } + + zpl_i64 zpl_atomic64_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) desired) { + # if defined(ZPL_ARCH_64_BIT) + return _InterlockedExchange64(cast(zpl_atomicarg(zpl_i64) *)a, desired); + # elif ZPL_CPU_X86 + zpl_atomicarg(zpl_i64) expected = a->value; + for (;;) { + zpl_atomicarg(zpl_i64) original = _InterlockedCompareExchange64(cast(zpl_atomicarg(zpl_i64) *)a, desired, expected); + if (original == expected) + return original; + expected = original; + } + # else + # error TODO: atomics for this CPU + # endif + } + + zpl_i64 zpl_atomic64_fetch_add(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + # if defined(ZPL_ARCH_64_BIT) + return _InterlockedExchangeAdd64(cast(zpl_atomicarg(zpl_i64) *)a, operand); + # elif ZPL_CPU_X86 + zpl_atomicarg(zpl_i64) expected = a->value; + for (;;) { + zpl_atomicarg(zpl_i64) original = _InterlockedCompareExchange64(cast(zpl_atomicarg(zpl_i64) *)a, expected + operand, expected); + if (original == expected) + return original; + expected = original; + } + # else + # error TODO: atomics for this CPU + # endif + } + + zpl_i64 zpl_atomic64_fetch_and(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + # if defined(ZPL_ARCH_64_BIT) + return _InterlockedAnd64(cast(zpl_atomicarg(zpl_i64) *)a, operand); + # elif ZPL_CPU_X86 + zpl_atomicarg(zpl_i64) expected = a->value; + for (;;) { + zpl_atomicarg(zpl_i64) original = _InterlockedCompareExchange64(cast(zpl_atomicarg(zpl_i64) *)a, expected & operand, expected); + if (original == expected) + return original; + expected = original; + } + # else + # error TODO: atomics for this CPU + # endif + } + + zpl_i64 zpl_atomic64_fetch_or(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + # if defined(ZPL_ARCH_64_BIT) + return _InterlockedOr64(cast(zpl_atomicarg(zpl_i64) *)a, operand); + # elif ZPL_CPU_X86 + zpl_atomicarg(zpl_i64) expected = a->value; + for (;;) { + zpl_atomicarg(zpl_i64) original = _InterlockedCompareExchange64(cast(zpl_atomicarg(zpl_i64) *)a, expected | operand, expected); + if (original == expected) + return original; + expected = original; + } + # else + # error TODO: atomics for this CPU + # endif + } + + #elif defined(ZPL_CPU_X86) + + zpl_i32 zpl_atomic32_load (zpl_atomic32 const *a) { return a->value; } + void zpl_atomic32_store(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) value) { a->value = value; } + + zpl_i32 zpl_atomic32_compare_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) expected, zpl_atomicarg(zpl_i32) desired) { + zpl_atomicarg(zpl_i32) original; + __asm__( + "lock; cmpxchgl %2, %1" + : "=a"(original), "+m"(a->value) + : "q"(desired), "0"(expected) + ); + return original; + } + + zpl_i32 zpl_atomic32_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) desired) { + // NOTE: No lock prefix is necessary for xchgl + zpl_atomicarg(zpl_i32) original; + __asm__( + "xchgl %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(desired) + ); + return original; + } + + zpl_i32 zpl_atomic32_fetch_add(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + zpl_atomicarg(zpl_i32) original; + __asm__( + "lock; xaddl %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(operand) + ); + return original; + } + + zpl_i32 zpl_atomic32_fetch_and(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + zpl_atomicarg(zpl_i32) original; + zpl_atomicarg(zpl_i32) tmp; + __asm__( + "1: movl %1, %0\n" + " movl %0, %2\n" + " andl %3, %2\n" + " lock; cmpxchgl %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(tmp) + : "r"(operand) + ); + return original; + } + + zpl_i32 zpl_atomic32_fetch_or(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + zpl_atomicarg(zpl_i32) original; + zpl_atomicarg(zpl_i32) temp; + __asm__( + "1: movl %1, %0\n" + " movl %0, %2\n" + " orl %3, %2\n" + " lock; cmpxchgl %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(temp) + : "r"(operand) + ); + return original; + } + + + zpl_i64 zpl_atomic64_load(zpl_atomic64 const *a) { + # if defined(ZPL_ARCH_64_BIT) + return a->value; + # else + zpl_atomicarg(zpl_i64) original; + __asm__( + "movl %%ebx, %%eax\n" + "movl %%ecx, %%edx\n" + "lock; cmpxchg8b %1" + : "=&A"(original) + : "m"(a->value) + ); + return original; + # endif + } + + void zpl_atomic64_store(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) value) { + # if defined(ZPL_ARCH_64_BIT) + a->value = value; + # else + zpl_atomicarg(zpl_i64) expected = a->value; + __asm__( + "1: cmpxchg8b %0\n" + " jne 1b" + : "=m"(a->value) + : "b"((zpl_atomicarg(zpl_i32))value), "c"((zpl_atomicarg(zpl_i32))(value >> 32)), "A"(expected) + ); + # endif + } + + zpl_i64 zpl_atomic64_compare_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) expected, zpl_atomicarg(zpl_i64) desired) { + # if defined(ZPL_ARCH_64_BIT) + zpl_atomicarg(zpl_i64) original; + __asm__( + "lock; cmpxchgq %2, %1" + : "=a"(original), "+m"(a->value) + : "q"(desired), "0"(expected) + ); + return original; + # else + zpl_atomicarg(zpl_i64) original; + __asm__( + "lock; cmpxchg8b %1" + : "=A"(original), "+m"(a->value) + : "b"((zpl_atomicarg(zpl_i32))desired), "c"((zpl_atomicarg(zpl_i32))(desired >> 32)), "0"(expected) + ); + return original; + # endif + } + + zpl_i64 zpl_atomic64_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) desired) { + # if defined(ZPL_ARCH_64_BIT) + zpl_atomicarg(zpl_i64) original; + __asm__( + "xchgq %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(desired) + ); + return original; + # else + zpl_atomicarg(zpl_i64) original = a->value; + for (;;) { + zpl_atomicarg(zpl_i64) previous = zpl_atomic64_compare_exchange(a, original, desired); + if (original == previous) + return original; + original = previous; + } + # endif + } + + zpl_i64 zpl_atomic64_fetch_add(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + # if defined(ZPL_ARCH_64_BIT) + zpl_atomicarg(zpl_i64) original; + __asm__( + "lock; xaddq %0, %1" + : "=r"(original), "+m"(a->value) + : "0"(operand) + ); + return original; + # else + for (;;) { + zpl_atomicarg(zpl_i64) original = a->value; + if (zpl_atomic64_compare_exchange(a, original, original + operand) == original) + return original; + } + # endif + } + + zpl_i64 zpl_atomic64_fetch_and(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + # if defined(ZPL_ARCH_64_BIT) + zpl_atomicarg(zpl_i64) original; + zpl_atomicarg(zpl_i64) tmp; + __asm__( + "1: movq %1, %0\n" + " movq %0, %2\n" + " andq %3, %2\n" + " lock; cmpxchgq %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(tmp) + : "r"(operand) + ); + return original; + # else + for (;;) { + zpl_atomicarg(zpl_i64) original = a->value; + if (zpl_atomic64_compare_exchange(a, original, original & operand) == original) + return original; + } + # endif + } + + zpl_i64 zpl_atomic64_fetch_or(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + # if defined(ZPL_ARCH_64_BIT) + zpl_atomicarg(zpl_i64) original; + zpl_atomicarg(zpl_i64) temp; + __asm__( + "1: movq %1, %0\n" + " movq %0, %2\n" + " orq %3, %2\n" + " lock; cmpxchgq %2, %1\n" + " jne 1b" + : "=&a"(original), "+m"(a->value), "=&r"(temp) + : "r"(operand) + ); + return original; + # else + for (;;) { + zpl_atomicarg(zpl_i64) original = a->value; + if (zpl_atomic64_compare_exchange(a, original, original | operand) == original) + return original; + } + # endif + } + + #elif !defined(ZPL_COMPILER_MSVC) + zpl_i32 zpl_atomic32_load (zpl_atomic32 const *a) { + return __atomic_load_n((zpl_i32*)&a->value, __ATOMIC_SEQ_CST); + } + void zpl_atomic32_store(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) value) { + __atomic_store((zpl_i32*)&a->value, (zpl_i32*)&value, __ATOMIC_SEQ_CST); + } + + zpl_i32 zpl_atomic32_compare_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) expected, zpl_atomicarg(zpl_i32) desired) { + return __atomic_compare_exchange_n((zpl_i32*)&a->value, (zpl_i32*)&expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + } + + zpl_i32 zpl_atomic32_exchange(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) desired) { + return __atomic_exchange_n((zpl_i32*)&a->value, desired, __ATOMIC_SEQ_CST); + } + + zpl_i32 zpl_atomic32_fetch_add(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + return __atomic_fetch_add((zpl_i32*)&a->value, operand, __ATOMIC_SEQ_CST); + } + + zpl_i32 zpl_atomic32_fetch_and(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + return __atomic_fetch_and((zpl_i32*)&a->value, operand, __ATOMIC_SEQ_CST); + } + + zpl_i32 zpl_atomic32_fetch_or(zpl_atomic32 *a, zpl_atomicarg(zpl_i32) operand) { + return __atomic_fetch_or((zpl_i32*)&a->value, operand, __ATOMIC_SEQ_CST); + } + + zpl_i64 zpl_atomic64_load(zpl_atomic64 const *a) { + return __atomic_load_n((zpl_i64*)&a->value, __ATOMIC_SEQ_CST); + } + + void zpl_atomic64_store(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) value) { + __atomic_store((zpl_i64*)&a->value, (zpl_i64*)&value, __ATOMIC_SEQ_CST); + } + + zpl_i64 zpl_atomic64_compare_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) expected, zpl_atomicarg(zpl_i64) desired) { + return __atomic_compare_exchange_n((zpl_i64*)&a->value, (zpl_i64*)&expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + } + + zpl_i64 zpl_atomic64_exchange(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) desired) { + return __atomic_exchange_n((zpl_i64*)&a->value, desired, __ATOMIC_SEQ_CST); + } + + zpl_i64 zpl_atomic64_fetch_add(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + return __atomic_fetch_add((zpl_i64*)&a->value, operand, __ATOMIC_SEQ_CST); + } + + zpl_i64 zpl_atomic64_fetch_and(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + return __atomic_fetch_and((zpl_i64*)&a->value, operand, __ATOMIC_SEQ_CST); + } + + zpl_i64 zpl_atomic64_fetch_or(zpl_atomic64 *a, zpl_atomicarg(zpl_i64) operand) { + return __atomic_fetch_or((zpl_i64*)&a->value, operand, __ATOMIC_SEQ_CST); + } + + #else + # error TODO: Implement Atomics for this CPU + #endif + + + + zpl_b32 zpl_atomic32_spin_lock(zpl_atomic32 *a, zpl_isize time_out) { + zpl_atomicarg(zpl_i32) old_value = zpl_atomic32_compare_exchange(a, 1, 0); + zpl_i32 counter = 0; + while (old_value != 0 && (time_out < 0 || counter++ < time_out)) { + zpl_yield_thread(); + old_value = zpl_atomic32_compare_exchange(a, 1, 0); + zpl_mfence(); + } + return old_value == 0; + } + + void zpl_atomic32_spin_unlock(zpl_atomic32 *a) { + zpl_atomic32_store(a, 0); + zpl_mfence(); + } + + zpl_b32 zpl_atomic64_spin_lock(zpl_atomic64 *a, zpl_isize time_out) { + zpl_atomicarg(zpl_i64) old_value = zpl_atomic64_compare_exchange(a, 1, 0); + zpl_atomicarg(zpl_i64) counter = 0; + while (old_value != 0 && (time_out < 0 || counter++ < time_out)) { + zpl_yield_thread(); + old_value = zpl_atomic64_compare_exchange(a, 1, 0); + zpl_mfence(); + } + return old_value == 0; + } + + void zpl_atomic64_spin_unlock(zpl_atomic64 *a) { + zpl_atomic64_store(a, 0); + zpl_mfence(); + } + + zpl_b32 zpl_atomic32_try_acquire_lock(zpl_atomic32 *a) { + zpl_atomicarg(zpl_i32) old_value; + zpl_yield_thread(); + old_value = zpl_atomic32_compare_exchange(a, 1, 0); + zpl_mfence(); + return old_value == 0; + } + + zpl_b32 zpl_atomic64_try_acquire_lock(zpl_atomic64 *a) { + zpl_atomicarg(zpl_i64) old_value; + zpl_yield_thread(); + old_value = zpl_atomic64_compare_exchange(a, 1, 0); + zpl_mfence(); + return old_value == 0; + } + + + + #if defined(ZPL_ARCH_32_BIT) + + void* zpl_atomic_ptr_load(zpl_atomic_ptr const *a) { + return (void *)cast(zpl_intptr)zpl_atomic32_load(cast(zpl_atomic32 const *)a); + } + void zpl_atomic_ptr_store(zpl_atomic_ptr *a, zpl_atomicarg(void *)value) { + zpl_atomic32_store(cast(zpl_atomic32 *)a, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)value); + } + void* zpl_atomic_ptr_compare_exchange(zpl_atomic_ptr *a, zpl_atomicarg(void *)expected, zpl_atomicarg(void *)desired) { + return (void *)cast(zpl_intptr)zpl_atomic32_compare_exchange(cast(zpl_atomic32 *)a, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)expected, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)desired); + } + void* zpl_atomic_ptr_exchange(zpl_atomic_ptr *a, zpl_atomicarg(void *)desired) { + return (void *)cast(zpl_intptr)zpl_atomic32_exchange(cast(zpl_atomic32 *)a, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)desired); + } + void* zpl_atomic_ptr_fetch_add(zpl_atomic_ptr *a, zpl_atomicarg(void *)operand) { + return (void *)cast(zpl_intptr)zpl_atomic32_fetch_add(cast(zpl_atomic32 *)a, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)operand); + } + void* zpl_atomic_ptr_fetch_and(zpl_atomic_ptr *a, zpl_atomicarg(void *)operand) { + return (void *)cast(zpl_intptr)zpl_atomic32_fetch_and(cast(zpl_atomic32 *)a, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)operand); + } + void* zpl_atomic_ptr_fetch_or(zpl_atomic_ptr *a, zpl_atomicarg(void *)operand) { + return (void *)cast(zpl_intptr)zpl_atomic32_fetch_or(cast(zpl_atomic32 *)a, cast(zpl_atomicarg(zpl_i32))cast(zpl_intptr)operand); + } + zpl_b32 zpl_atomic_ptr_spin_lock(zpl_atomic_ptr *a, zpl_isize time_out) { + return zpl_atomic32_spin_lock(cast(zpl_atomic32 *)a, time_out); + } + void zpl_atomic_ptr_spin_unlock(zpl_atomic_ptr *a) { + zpl_atomic32_spin_unlock(cast(zpl_atomic32 *)a); + } + zpl_b32 zpl_atomic_ptr_try_acquire_lock(zpl_atomic_ptr *a) { + return zpl_atomic32_try_acquire_lock(cast(zpl_atomic32 *)a); + } + + #elif defined(ZPL_ARCH_64_BIT) + + void* zpl_atomic_ptr_load(zpl_atomic_ptr const *a) { + return (void *)cast(zpl_intptr)zpl_atomic64_load(cast(zpl_atomic64 const *)a); + } + void zpl_atomic_ptr_store(zpl_atomic_ptr *a, zpl_atomicarg(void *)value) { + zpl_atomic64_store(cast(zpl_atomic64 *)a, cast(zpl_i64)cast(zpl_intptr)value); + } + void* zpl_atomic_ptr_compare_exchange(zpl_atomic_ptr *a, zpl_atomicarg(void *)expected, zpl_atomicarg(void *)desired) { + return (void *)cast(zpl_intptr)zpl_atomic64_compare_exchange(cast(zpl_atomic64 *)a, cast(zpl_i64)cast(zpl_intptr)expected, cast(zpl_i64)cast(zpl_intptr)desired); + } + void* zpl_atomic_ptr_exchange(zpl_atomic_ptr *a, zpl_atomicarg(void *)desired) { + return (void *)cast(zpl_intptr)zpl_atomic64_exchange(cast(zpl_atomic64 *)a, cast(zpl_i64)cast(zpl_intptr)desired); + } + void* zpl_atomic_ptr_fetch_add(zpl_atomic_ptr *a, zpl_atomicarg(void *)operand) { + return (void *)cast(zpl_intptr)zpl_atomic64_fetch_add(cast(zpl_atomic64 *)a, cast(zpl_i64)cast(zpl_intptr)operand); + } + void* zpl_atomic_ptr_fetch_and(zpl_atomic_ptr *a, zpl_atomicarg(void *)operand) { + return (void *)cast(zpl_intptr)zpl_atomic64_fetch_and(cast(zpl_atomic64 *)a, cast(zpl_i64)cast(zpl_intptr)operand); + } + void* zpl_atomic_ptr_fetch_or(zpl_atomic_ptr *a, zpl_atomicarg(void *)operand) { + return (void *)cast(zpl_intptr)zpl_atomic64_fetch_or(cast(zpl_atomic64 *)a, cast(zpl_i64)cast(zpl_intptr)operand); + } + zpl_b32 zpl_atomic_ptr_spin_lock(zpl_atomic_ptr *a, zpl_isize time_out) { + return zpl_atomic64_spin_lock(cast(zpl_atomic64 *)a, time_out); + } + void zpl_atomic_ptr_spin_unlock(zpl_atomic_ptr *a) { + zpl_atomic64_spin_unlock(cast(zpl_atomic64 *)a); + } + zpl_b32 zpl_atomic_ptr_try_acquire_lock(zpl_atomic_ptr *a) { + return zpl_atomic64_try_acquire_lock(cast(zpl_atomic64 *)a); + } + + #endif + + ZPL_END_C_DECLS + // file: source/threading/sem.c + + + ZPL_BEGIN_C_DECLS + + void zpl_semaphore_release(zpl_semaphore *s) { zpl_semaphore_post(s, 1); } + + #if defined(ZPL_SYSTEM_WINDOWS) + + void zpl_semaphore_init (zpl_semaphore *s) { s->win32_handle = CreateSemaphoreA(NULL, 0, ZPL_I32_MAX, NULL); } + void zpl_semaphore_destroy(zpl_semaphore *s) { CloseHandle(s->win32_handle); } + void zpl_semaphore_post (zpl_semaphore *s, zpl_i32 count) { ReleaseSemaphore(s->win32_handle, count, NULL); } + void zpl_semaphore_wait (zpl_semaphore *s) { WaitForSingleObject(s->win32_handle, INFINITE); } + zpl_i32 zpl_semaphore_trywait(zpl_semaphore *s) { int r = WaitForSingleObject(s->win32_handle, 0); return r; } + + #elif defined(ZPL_SYSTEM_OSX) + + void zpl_semaphore_init (zpl_semaphore *s) { semaphore_create(mach_task_self(), &s->osx_handle, SYNC_POLICY_FIFO, 0); } + void zpl_semaphore_destroy(zpl_semaphore *s) { semaphore_destroy(mach_task_self(), s->osx_handle); } + void zpl_semaphore_post (zpl_semaphore *s, zpl_i32 count) { while (count --> 0) semaphore_signal(s->osx_handle); } + void zpl_semaphore_wait (zpl_semaphore *s) { semaphore_wait(s->osx_handle); } + zpl_i32 zpl_semaphore_trywait(zpl_semaphore *s) { mach_timespec_t t; t.tv_sec = t.tv_nsec = 0; kern_return_t r = semaphore_timedwait(s->osx_handle, t); return r; } + + #elif defined(ZPL_SYSTEM_UNIX) + + void zpl_semaphore_init (zpl_semaphore *s) { sem_init(&s->unix_handle, 0, 0); } + void zpl_semaphore_destroy(zpl_semaphore *s) { sem_destroy(&s->unix_handle); } + void zpl_semaphore_post (zpl_semaphore *s, zpl_i32 count) { while (count --> 0) sem_post(&s->unix_handle); } + void zpl_semaphore_wait (zpl_semaphore *s) { int i; do { i = sem_wait(&s->unix_handle); } while (i == -1 && errno == EINTR); } + zpl_i32 zpl_semaphore_trywait(zpl_semaphore *s) { int r = sem_trywait(&s->unix_handle); return r; } + + #else + # error Semaphores for this OS are not implemented + #endif + + ZPL_END_C_DECLS + // file: source/threading/mutex.c + + + ZPL_BEGIN_C_DECLS + + zpl_b32 zpl_mutex_init(zpl_mutex *m) { + # if defined(ZPL_SYSTEM_WINDOWS) + InitializeCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); + return 1; + # else + return pthread_mutex_init(&m->pthread_mutex, NULL); + # endif + } + + zpl_b32 zpl_mutex_destroy(zpl_mutex *m) { + # if defined(ZPL_SYSTEM_WINDOWS) + DeleteCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); + return 1; + # else + return pthread_mutex_destroy(&m->pthread_mutex); + # endif + } + + void zpl_mutex_lock(zpl_mutex *m) { + # if defined(ZPL_SYSTEM_WINDOWS) + EnterCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); + # else + pthread_mutex_lock(&m->pthread_mutex); + # endif + } + + zpl_b32 zpl_mutex_try_lock(zpl_mutex *m) { + # if defined(ZPL_SYSTEM_WINDOWS) + return TryEnterCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); + # else + return pthread_mutex_trylock(&m->pthread_mutex); + # endif + } + + void zpl_mutex_unlock(zpl_mutex *m) { + # if defined(ZPL_SYSTEM_WINDOWS) + LeaveCriticalSection((CRITICAL_SECTION*)m->win32_critical_section); + # else + pthread_mutex_unlock(&m->pthread_mutex); + # endif + } + + ZPL_END_C_DECLS + // file: source/threading/thread.c + + + ZPL_BEGIN_C_DECLS + + zpl_b32 zpl_thread_is_running(zpl_thread const *t) { return t->is_running != 0; } + + void zpl_thread_init_nowait(zpl_thread *t) { + zpl_zero_item(t); + + # if defined(ZPL_SYSTEM_WINDOWS) + t->win32_handle = INVALID_HANDLE_VALUE; + # endif + + t->nowait = true; + } + + void zpl_thread_init(zpl_thread *t) { + zpl_thread_init_nowait(t); + + t->nowait = false; + zpl_semaphore_init(&t->semaphore); + } + + void zpl_thread_destroy(zpl_thread *t) { + # if defined(ZPL_SYSTEM_WINDOWS) + if (t->win32_handle != INVALID_HANDLE_VALUE) + zpl_thread_join(t); + # else + if (t->posix_handle) + zpl_thread_join(t); + # endif + if (!t->nowait) + zpl_semaphore_destroy(&t->semaphore); + } + + static void zpl__thread_run(zpl_thread *t) { + if (!t->nowait) + zpl_semaphore_release(&t->semaphore); + t->return_value = t->proc(t); + } + + #if defined(ZPL_SYSTEM_WINDOWS) + static DWORD __stdcall zpl__thread_proc(void *arg) { + zpl_thread *t = cast(zpl_thread *)arg; + t->is_running = true; + zpl__thread_run(t); + t->is_running = false; + return 0; + } + #else + static void *zpl__thread_proc(void *arg) { + zpl_thread *t = cast(zpl_thread *)arg; + t->is_running = true; + zpl__thread_run(t); + t->is_running = false; + return NULL; + } + #endif + + zpl_b32 zpl_thread_start(zpl_thread *t, zpl_thread_proc proc, void *user_data) { + return zpl_thread_start_with_stack(t, proc, user_data, 0); + } + + zpl_b32 zpl_thread_start_with_stack(zpl_thread *t, zpl_thread_proc proc, void *user_data, zpl_isize stack_size) { + ZPL_ASSERT(!t->is_running); + ZPL_ASSERT(proc != NULL); + t->proc = proc; + t->user_data = user_data; + t->stack_size = stack_size; + + # if defined(ZPL_SYSTEM_WINDOWS) + t->win32_handle = CreateThread(NULL, stack_size, zpl__thread_proc, t, 0, NULL); + ZPL_ASSERT_MSG(t->win32_handle != NULL, "CreateThread: GetLastError"); + # else + { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + if (stack_size != 0) + pthread_attr_setstacksize(&attr, stack_size); + zpl_b32 res = pthread_create(&t->posix_handle, &attr, zpl__thread_proc, t); + if (res != 0) { + return res; + } + pthread_attr_destroy(&attr); + } + # endif + if (!t->nowait) + zpl_semaphore_wait(&t->semaphore); + return 1; + } + + void zpl_thread_join(zpl_thread *t) { + # if defined(ZPL_SYSTEM_WINDOWS) + WaitForSingleObject(t->win32_handle, INFINITE); + CloseHandle(t->win32_handle); + t->win32_handle = INVALID_HANDLE_VALUE; + # else + pthread_join(t->posix_handle, NULL); + t->posix_handle = 0; + # endif + } + + zpl_u32 zpl_thread_current_id(void) { + zpl_u32 thread_id; + # if defined(ZPL_SYSTEM_WINDOWS) + # if defined(ZPL_ARCH_32_BIT) && defined(ZPL_CPU_X86) + thread_id = (cast(zpl_u32 *)__readfsdword(24))[9]; + # elif defined(ZPL_ARCH_64_BIT) && defined(ZPL_CPU_X86) + thread_id = (cast(zpl_u32 *)__readgsqword(48))[18]; + # else + thread_id = GetCurrentThreadId(); + # endif + + # elif defined(ZPL_SYSTEM_OSX) && defined(ZPL_ARCH_64_BIT) + thread_id = pthread_mach_thread_np(pthread_self()); + # elif defined(ZPL_ARCH_32_BIT) && defined(ZPL_CPU_X86) + __asm__("mov %%gs:0x08,%0" : "=r"(thread_id)); + # elif defined(ZPL_ARCH_64_BIT) && defined(ZPL_CPU_X86) + __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); + # elif defined(__ARM_ARCH) + thread_id = pthread_self(); + # else + # error Unsupported architecture for zpl_thread_current_id() + # endif + + return thread_id; + } + + void zpl_thread_set_name(zpl_thread *t, char const *name) { + # if defined(ZPL_COMPILER_MSVC) + # pragma pack(push, 8) + typedef struct { + DWORD type; + char const *name; + DWORD id; + DWORD flags; + } zplprivThreadName; + # pragma pack(pop) + + zplprivThreadName tn; + tn.type = 0x1000; + tn.name = name; + tn.id = GetThreadId(cast(HANDLE)t->win32_handle); + tn.flags = 0; + + __try { + RaiseException(0x406d1388, 0, zpl_size_of(tn)/4, cast(ULONG_PTR *)&tn); + } __except(1 /*EXCEPTION_EXECUTE_HANDLER*/) { + } + + # elif defined(ZPL_SYSTEM_WINDOWS) && !defined(ZPL_COMPILER_MSVC) + zpl_unused(t); + zpl_unused(name); + // IMPORTANT TODO: Set thread name for GCC/Clang on windows + return; + # elif defined(ZPL_SYSTEM_OSX) + // TODO: Test if this works + pthread_setname_np(name); + # else + zpl_unused(t); + zpl_unused(name); + // TODO: Test if this works + // pthread_set_name_np(t->posix_handle, name); + # endif + } + + ZPL_END_C_DECLS + // file: source/threading/sync.c + + + ZPL_BEGIN_C_DECLS + + void zpl_sync_init(zpl_sync *s) { + zpl_zero_item(s); + zpl_mutex_init(&s->mutex); + zpl_mutex_init(&s->start); + zpl_semaphore_init(&s->release); + } + + void zpl_sync_destroy(zpl_sync *s) { + if (s->waiting) { + ZPL_PANIC("Cannot destroy while threads are waiting!"); + } + + zpl_mutex_destroy(&s->mutex); + zpl_mutex_destroy(&s->start); + zpl_semaphore_destroy(&s->release); + } + + void zpl_sync_set_target(zpl_sync *s, zpl_i32 count) { + zpl_mutex_lock(&s->start); + + zpl_mutex_lock(&s->mutex); + ZPL_ASSERT(s->target == 0); + s->target = count; + s->current = 0; + s->waiting = 0; + zpl_mutex_unlock(&s->mutex); + } + + void zpl_sync_release(zpl_sync *s) { + if (s->waiting) { + zpl_semaphore_release(&s->release); + } else { + s->target = 0; + zpl_mutex_unlock(&s->start); + } + } + + zpl_i32 zpl_sync_reach(zpl_sync *s) { + zpl_i32 n; + zpl_mutex_lock(&s->mutex); + ZPL_ASSERT(s->current < s->target); + n = ++s->current; // NOTE: Record this value to avoid possible race if `return s->current` was done + if (s->current == s->target) + zpl_sync_release(s); + zpl_mutex_unlock(&s->mutex); + return n; + } + + void zpl_sync_reach_and_wait(zpl_sync *s) { + zpl_mutex_lock(&s->mutex); + ZPL_ASSERT(s->current < s->target); + s->current++; + if (s->current == s->target) { + zpl_sync_release(s); + zpl_mutex_unlock(&s->mutex); + } else { + s->waiting++; // NOTE: Waiting, so one more waiter + zpl_mutex_unlock(&s->mutex); // NOTE: Release the mutex to other threads + zpl_semaphore_wait(&s->release); // NOTE: Wait for merge completion + zpl_mutex_lock(&s->mutex); // NOTE: On merge completion, lock mutex + s->waiting--; // NOTE: Done waiting + zpl_sync_release(s); // NOTE: Restart the next waiter + zpl_mutex_unlock(&s->mutex); + } + } + + ZPL_END_C_DECLS + // file: source/threading/affinity.c + + + #if defined(ZPL_SYSTEM_MACOS) + # include + #endif + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_SYSTEM_WINDOWS) || defined(ZPL_SYSTEM_CYGWIN) + + void zpl_affinity_init(zpl_affinity *a) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *start_processor_info = NULL; + DWORD length = 0; + zpl_b32 result = GetLogicalProcessorInformation(NULL, &length); + + zpl_zero_item(a); + + if (!result && GetLastError() == 122l /*ERROR_INSUFFICIENT_BUFFER*/ && length > 0) { + start_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)zpl_alloc(zpl_heap_allocator(), length); + result = GetLogicalProcessorInformation(start_processor_info, &length); + if (result) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *end_processor_info, *processor_info; + + a->is_accurate = true; + a->core_count = 0; + a->thread_count = 0; + end_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)zpl_pointer_add(start_processor_info, length); + + for (processor_info = start_processor_info; + processor_info < end_processor_info; + processor_info++) { + if (processor_info->Relationship == RelationProcessorCore) { + zpl_isize thread = zpl_count_set_bits(processor_info->ProcessorMask); + if (thread == 0) { + a->is_accurate = false; + } else if (a->thread_count + thread > ZPL_WIN32_MAX_THREADS) { + a->is_accurate = false; + } else { + ZPL_ASSERT(a->core_count <= a->thread_count && + a->thread_count < ZPL_WIN32_MAX_THREADS); + a->core_masks[a->core_count++] = processor_info->ProcessorMask; + a->thread_count += thread; + } + } + } + } + + zpl_free(zpl_heap_allocator(), start_processor_info); + } + + ZPL_ASSERT(a->core_count <= a->thread_count); + if (a->thread_count == 0) { + a->is_accurate = false; + a->core_count = 1; + a->thread_count = 1; + a->core_masks[0] = 1; + } + + } + + void zpl_affinity_destroy(zpl_affinity *a) { + zpl_unused(a); + } + + zpl_b32 zpl_affinity_set(zpl_affinity *a, zpl_isize core, zpl_isize thread) { + zpl_usize available_mask, check_mask = 1; + ZPL_ASSERT(thread < zpl_affinity_thread_count_for_core(a, core)); + + available_mask = a->core_masks[core]; + for (;;) { + if ((available_mask & check_mask) != 0) { + if (thread-- == 0) { + zpl_usize result = SetThreadAffinityMask(GetCurrentThread(), check_mask); + return result != 0; + } + } + check_mask <<= 1; // NOTE: Onto the next bit + } + } + + zpl_isize zpl_affinity_thread_count_for_core(zpl_affinity *a, zpl_isize core) { + ZPL_ASSERT(core >= 0 && core < a->core_count); + return zpl_count_set_bits(a->core_masks[core]); + } + + #elif defined(ZPL_SYSTEM_MACOS) + void zpl_affinity_init(zpl_affinity *a) { + zpl_usize count, count_size = zpl_size_of(count); + + a->is_accurate = false; + a->thread_count = 1; + a->core_count = 1; + a->threads_per_core = 1; + + if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) { + if (count > 0) { + a->thread_count = count; + // Get # of physical cores + if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) { + if (count > 0) { + a->core_count = count; + a->threads_per_core = a->thread_count / count; + if (a->threads_per_core < 1) + a->threads_per_core = 1; + else + a->is_accurate = true; + } + } + } + } + + } + + void zpl_affinity_destroy(zpl_affinity *a) { + zpl_unused(a); + } + + zpl_b32 zpl_affinity_set(zpl_affinity *a, zpl_isize core, zpl_isize thread_index) { + zpl_isize index; + thread_t thread; + thread_affinity_policy_data_t info; + kern_return_t result; + + ZPL_ASSERT(core < a->core_count); + ZPL_ASSERT(thread_index < a->threads_per_core); + + index = core * a->threads_per_core + thread_index; + thread = mach_thread_self(); + info.affinity_tag = cast(integer_t)index; + result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT); + return result == KERN_SUCCESS; + } + + zpl_isize zpl_affinity_thread_count_for_core(zpl_affinity *a, zpl_isize core) { + ZPL_ASSERT(core >= 0 && core < a->core_count); + return a->threads_per_core; + } + + #elif defined(ZPL_SYSTEM_LINUX) || defined(ZPL_SYSTEM_FREEBSD) || defined(ZPL_SYSTEM_OPENBSD) + void zpl_affinity_init(zpl_affinity *a) { + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + + a->is_accurate = a->core_count > 0; + a->core_count = a->is_accurate ? a->core_count : 1; + a->thread_count = a->core_count; + } + + void zpl_affinity_destroy(zpl_affinity *a) { + zpl_unused(a); + } + + zpl_b32 zpl_affinity_set(zpl_affinity * a, zpl_isize core, zpl_isize thread_index) { + zpl_unused(a); + zpl_unused(core); + zpl_unused(thread_index); + return true; + } + + zpl_isize zpl_affinity_thread_count_for_core(zpl_affinity *a, zpl_isize core) { + ZPL_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; + } + + #elif defined(ZPL_SYSTEM_EMSCRIPTEN) + # error No affinity implementation for Emscripten + #else + # error TODO: Unknown system + #endif + + ZPL_END_C_DECLS + +# if defined(ZPL_MODULE_JOBS) + // file: source/jobs.c + + /////////////////////////////////////////////////////////////// + // + // Thread Pool + // + + ZPL_BEGIN_C_DECLS + + ZPL_RING_DEFINE(zpl__jobs_ring_, zpl_thread_job); + + zpl_global const zpl_u32 zpl__jobs_chances[ZPL_JOBS_MAX_PRIORITIES] = { + 2, 3, 5, 7, 11 + }; + + zpl_isize zpl__jobs_entry(struct zpl_thread *thread) { + zpl_thread_worker *tw = (zpl_thread_worker *)thread->user_data; + + for (;;) { + zpl_u32 status = zpl_atomic32_load(&tw->status); + + switch (status) { + case ZPL_JOBS_STATUS_READY: { + zpl_atomic32_store(&tw->status, ZPL_JOBS_STATUS_BUSY); + tw->job.proc(tw->job.data); + zpl_atomic32_compare_exchange(&tw->status, ZPL_JOBS_STATUS_BUSY, ZPL_JOBS_STATUS_WAITING); + + # ifdef ZPL_JOBS_DEBUG + ++tw->hits; + # endif + } break; + + case ZPL_JOBS_STATUS_WAITING: { + # ifdef ZPL_JOBS_DEBUG + ++tw->idle; + # endif + zpl_yield(); + } break; + + case ZPL_JOBS_STATUS_TERM: { + return 0; + } break; + } + } + + return 0; + } + + void zpl_jobs_init(zpl_jobs_system *pool, zpl_allocator a, zpl_u32 max_threads) { + zpl_jobs_init_with_limit(pool, a, max_threads, ZPL_JOBS_MAX_QUEUE); + } + + void zpl_jobs_init_with_limit(zpl_jobs_system *pool, zpl_allocator a, zpl_u32 max_threads, zpl_u32 max_jobs) { + zpl_jobs_system pool_ = { 0 }; + *pool = pool_; + + pool->alloc = a; + pool->max_threads = max_threads; + pool->max_jobs = max_jobs; + pool->counter = 0; + + zpl_buffer_init(pool->workers, a, max_threads); + + for (zpl_usize i = 0; i < ZPL_JOBS_MAX_PRIORITIES; ++i) { + zpl_thread_queue *q = &pool->queues[i]; + zpl__jobs_ring_init(&q->jobs, a, max_jobs); + q->chance = zpl__jobs_chances[i]; + } + + for (zpl_usize i = 0; i < max_threads; ++i) { + zpl_thread_worker worker_ = { 0 }; + zpl_thread_worker *tw = pool->workers + i; + *tw = worker_; + + zpl_thread_init(&tw->thread); + zpl_atomic32_store(&tw->status, ZPL_JOBS_STATUS_WAITING); + zpl_thread_start(&tw->thread, zpl__jobs_entry, (void *)tw); + } + } + + void zpl_jobs_free(zpl_jobs_system *pool) { + for (zpl_usize i = 0; i < pool->max_threads; ++i) { + zpl_thread_worker *tw = pool->workers + i; + + zpl_atomic32_store(&tw->status, ZPL_JOBS_STATUS_TERM); + zpl_thread_destroy(&tw->thread); + } + + zpl_buffer_free(pool->workers); + + for (zpl_usize i = 0; i < ZPL_JOBS_MAX_PRIORITIES; ++i) { + zpl_thread_queue *q = &pool->queues[i]; + zpl__jobs_ring_free(&q->jobs); + } + } + + zpl_b32 zpl_jobs_enqueue_with_priority(zpl_jobs_system *pool, zpl_jobs_proc proc, void *data, zpl_jobs_priority priority) { + ZPL_ASSERT(priority >= 0 && priority < ZPL_JOBS_MAX_PRIORITIES); + ZPL_ASSERT_NOT_NULL(proc); + zpl_thread_job job = {0}; + job.proc = proc; + job.data = data; + + if (!zpl_jobs_full(pool, priority)) { + zpl__jobs_ring_append(&pool->queues[priority].jobs, job); + return true; + } + return false; + } + + zpl_b32 zpl_jobs_enqueue(zpl_jobs_system *pool, zpl_jobs_proc proc, void *data) { + return zpl_jobs_enqueue_with_priority(pool, proc, data, ZPL_JOBS_PRIORITY_NORMAL); + } + + zpl_b32 zpl_jobs_empty(zpl_jobs_system *pool, zpl_jobs_priority priority) { + ZPL_ASSERT(priority >= 0 && priority < ZPL_JOBS_MAX_PRIORITIES); + return zpl__jobs_ring_empty(&pool->queues[priority].jobs); + } + + zpl_b32 zpl_jobs_full(zpl_jobs_system *pool, zpl_jobs_priority priority) { + ZPL_ASSERT(priority >= 0 && priority < ZPL_JOBS_MAX_PRIORITIES); + return zpl__jobs_ring_full(&pool->queues[priority].jobs); + } + + zpl_b32 zpl_jobs_done(zpl_jobs_system *pool) { + for (zpl_usize i = 0; i < pool->max_threads; ++i) { + zpl_thread_worker *tw = pool->workers + i; + if (zpl_atomic32_load(&tw->status) != ZPL_JOBS_STATUS_WAITING) { + return false; + } + } + + return zpl_jobs_empty_all(pool); + } + + zpl_b32 zpl_jobs_empty_all(zpl_jobs_system *pool) { + for (zpl_usize i = 0; i < ZPL_JOBS_MAX_PRIORITIES; ++i) { + if (!zpl_jobs_empty(pool, (zpl_jobs_priority)i)) { + return false; + } + } + return true; + } + + zpl_b32 zpl_jobs_full_all(zpl_jobs_system *pool) { + for (zpl_usize i = 0; i < ZPL_JOBS_MAX_PRIORITIES; ++i) { + if (!zpl_jobs_full(pool, (zpl_jobs_priority)i)) { + return false; + } + } + return true; + } + + zpl_b32 zpl_jobs_process(zpl_jobs_system *pool) { + if (zpl_jobs_empty_all(pool)) { + return false; + } + // NOTE: Process the jobs + for (zpl_usize i = 0; i < pool->max_threads; ++i) { + zpl_thread_worker *tw = pool->workers + i; + zpl_u32 status = zpl_atomic32_load(&tw->status); + zpl_b32 last_empty = false; + + if (status == ZPL_JOBS_STATUS_WAITING) { + for (zpl_usize j = 0; j < ZPL_JOBS_MAX_PRIORITIES; ++j) { + zpl_thread_queue *q = &pool->queues[j]; + if (zpl_jobs_empty(pool, (zpl_jobs_priority)j)) { + last_empty = (j+1 == ZPL_JOBS_MAX_PRIORITIES); + continue; + } + if (!last_empty && ((pool->counter++ % q->chance) != 0)) { + continue; + } + + last_empty = false; + tw->job = *zpl__jobs_ring_get(&q->jobs); + zpl_atomic32_store(&tw->status, ZPL_JOBS_STATUS_READY); + # ifdef ZPL_JOBS_DEBUG + ++q->hits; + # endif + break; + } + } + } + + return true; + } + + ZPL_END_C_DECLS +# endif +#endif + +#if defined(ZPL_MODULE_PARSER) + // file: source/adt.c + + ZPL_BEGIN_C_DECLS + + #define zpl__adt_fprintf(s_, fmt_, ...) \ + do { \ + if (zpl_fprintf(s_, fmt_, ##__VA_ARGS__) < 0) return ZPL_ADT_ERROR_OUT_OF_MEMORY; \ + } while (0) + + zpl_u8 zpl_adt_make_branch(zpl_adt_node *node, zpl_allocator backing, char const *name, zpl_b32 is_array) { + zpl_u8 type = ZPL_ADT_TYPE_OBJECT; + if (is_array) { + type = ZPL_ADT_TYPE_ARRAY; + } + zpl_adt_node *parent = node->parent; + zpl_zero_item(node); + node->type = type; + node->name = name; + node->parent = parent; + if (!zpl_array_init(node->nodes, backing)) + return ZPL_ADT_ERROR_OUT_OF_MEMORY; + return 0; + } + + zpl_u8 zpl_adt_destroy_branch(zpl_adt_node *node) { + ZPL_ASSERT_NOT_NULL(node); + if ((node->type == ZPL_ADT_TYPE_OBJECT || node->type == ZPL_ADT_TYPE_ARRAY) && node->nodes) { + for (zpl_isize i = 0; i < zpl_array_count(node->nodes); ++i) { zpl_adt_destroy_branch(node->nodes + i); } + + zpl_array_free(node->nodes); + } + return 0; + } + + zpl_u8 zpl_adt_make_leaf(zpl_adt_node *node, char const *name, zpl_u8 type) { + ZPL_ASSERT(type != ZPL_ADT_TYPE_OBJECT && type != ZPL_ADT_TYPE_ARRAY); + zpl_adt_node *parent = node->parent; + zpl_zero_item(node); + node->type = type; + node->name = name; + node->parent = parent; + return 0; + } + + zpl_adt_node *zpl_adt_find(zpl_adt_node *node, char const *name, zpl_b32 deep_search) { + if (node->type != ZPL_ADT_TYPE_OBJECT) { + return NULL; + } + + for (zpl_isize i = 0; i < zpl_array_count(node->nodes); i++) { + if (!zpl_strcmp(node->nodes[i].name, name)) { + return (node->nodes + i); + } + } + + if (deep_search) { + for (zpl_isize i = 0; i < zpl_array_count(node->nodes); i++) { + zpl_adt_node *res = zpl_adt_find(node->nodes + i, name, deep_search); + + if (res != NULL) + return res; + } + } + + return NULL; + } + + zpl_internal zpl_adt_node *zpl__adt_get_value(zpl_adt_node *node, char const *value) { + switch (node->type) { + case ZPL_ADT_TYPE_MULTISTRING: + case ZPL_ADT_TYPE_STRING: { + if (node->string && !zpl_strcmp(node->string, value)) { + return node; + } + } break; + case ZPL_ADT_TYPE_INTEGER: + case ZPL_ADT_TYPE_REAL: { + char back[4096]={0}; + zpl_file tmp; + + /* allocate a file descriptor for a memory-mapped number to string conversion, input source buffer is not cloned, however. */ + zpl_file_stream_open(&tmp, zpl_heap(), (zpl_u8*)back, zpl_size_of(back), ZPL_FILE_STREAM_WRITABLE); + zpl_adt_print_number(&tmp, node); + + zpl_isize fsize=0; + zpl_u8* buf = zpl_file_stream_buf(&tmp, &fsize); + + if (!zpl_strcmp((char const *)buf, value)) { + zpl_file_close(&tmp); + return node; + } + + zpl_file_close(&tmp); + } break; + default: break; /* node doesn't support value based lookup */ + } + + return NULL; + } + + zpl_internal zpl_adt_node *zpl__adt_get_field(zpl_adt_node *node, char *name, char *value) { + for (zpl_isize i = 0; i < zpl_array_count(node->nodes); i++) { + if (!zpl_strcmp(node->nodes[i].name, name)) { + zpl_adt_node *child = &node->nodes[i]; + if (zpl__adt_get_value(child, value)) { + return node; /* this object does contain a field of a specified value! */ + } + } + } + + return NULL; + } + + zpl_adt_node *zpl_adt_query(zpl_adt_node *node, char const *uri) { + ZPL_ASSERT_NOT_NULL(uri); + + if (*uri == '/') { + uri++; + } + + if (*uri == 0) { + return node; + } + + if (!node || (node->type != ZPL_ADT_TYPE_OBJECT && node->type != ZPL_ADT_TYPE_ARRAY)) { + return NULL; + } + + #if defined ZPL_ADT_URI_DEBUG || 0 + zpl_printf("uri: %s\n", uri); + #endif + + char *p=(char*)uri, *b=p, *e=p; + zpl_adt_node *found_node=NULL; + + b = p; + p = e = (char*)zpl_str_skip(p, '/'); + char *buf = zpl_bprintf("%.*s", (int)(e - b), b); + + /* handle field value lookup */ + if (*b == '[') { + char *l_p=buf+1,*l_b=l_p,*l_e=l_p,*l_b2=l_p,*l_e2=l_p; + l_e = (char*)zpl_str_skip(l_p, '='); + l_e2 = (char*)zpl_str_skip(l_p, ']'); + + if ((!*l_e && node->type != ZPL_ADT_TYPE_ARRAY) || !*l_e2) { + ZPL_ASSERT_MSG(0, "Invalid field value lookup"); + return NULL; + } + + *l_e2 = 0; + + /* [field=value] */ + if (*l_e) { + *l_e = 0; + l_b2 = l_e+1; + + /* run a value comparison against our own fields */ + if (node->type == ZPL_ADT_TYPE_OBJECT) { + found_node = zpl__adt_get_field(node, l_b, l_b2); + } + + /* run a value comparison against any child that is an object node */ + else if (node->type == ZPL_ADT_TYPE_ARRAY) { + for (zpl_isize i = 0; i < zpl_array_count(node->nodes); i++) { + zpl_adt_node *child = &node->nodes[i]; + if (child->type != ZPL_ADT_TYPE_OBJECT) { + continue; + } + + found_node = zpl__adt_get_field(child, l_b, l_b2); + + if (found_node) + break; + } + } + } + /* [value] */ + else { + for (zpl_isize i = 0; i < zpl_array_count(node->nodes); i++) { + zpl_adt_node *child = &node->nodes[i]; + if (zpl__adt_get_value(child, l_b2)) { + found_node = child; + break; /* we found a matching value in array, ignore the rest of it */ + } + } + } + + /* go deeper if uri continues */ + if (*e) { + return zpl_adt_query(found_node, e+1); + } + } + /* handle field name lookup */ + else if (node->type == ZPL_ADT_TYPE_OBJECT) { + found_node = zpl_adt_find(node, buf, false); + + /* go deeper if uri continues */ + if (*e) { + return zpl_adt_query(found_node, e+1); + } + } + /* handle array index lookup */ + else { + zpl_isize idx = (zpl_isize)zpl_str_to_i64(buf, NULL, 10); + if (idx >= 0 && idx < zpl_array_count(node->nodes)) { + found_node = &node->nodes[idx]; + + /* go deeper if uri continues */ + if (*e) { + return zpl_adt_query(found_node, e+1); + } + } + } + + return found_node; + } + + zpl_adt_node *zpl_adt_alloc_at(zpl_adt_node *parent, zpl_isize index) { + if (!parent || (parent->type != ZPL_ADT_TYPE_OBJECT && parent->type != ZPL_ADT_TYPE_ARRAY)) { + return NULL; + } + + if (!parent->nodes) + return NULL; + + if (index < 0 || index > zpl_array_count(parent->nodes)) + return NULL; + + zpl_adt_node o = {0}; + o.parent = parent; + if (!zpl_array_append_at(parent->nodes, o, index)) + return NULL; + + return parent->nodes + index; + } + + zpl_adt_node *zpl_adt_alloc(zpl_adt_node *parent) { + if (!parent || (parent->type != ZPL_ADT_TYPE_OBJECT && parent->type != ZPL_ADT_TYPE_ARRAY)) { + return NULL; + } + + if (!parent->nodes) + return NULL; + + return zpl_adt_alloc_at(parent, zpl_array_count(parent->nodes)); + } + + + zpl_b8 zpl_adt_set_obj(zpl_adt_node *obj, char const *name, zpl_allocator backing) { + return zpl_adt_make_branch(obj, backing, name, 0); + } + zpl_b8 zpl_adt_set_arr(zpl_adt_node *obj, char const *name, zpl_allocator backing) { + return zpl_adt_make_branch(obj, backing, name, 1); + } + zpl_b8 zpl_adt_set_str(zpl_adt_node *obj, char const *name, char const *value) { + zpl_adt_make_leaf(obj, name, ZPL_ADT_TYPE_STRING); + obj->string = value; + return true; + } + zpl_b8 zpl_adt_set_flt(zpl_adt_node *obj, char const *name, zpl_f64 value) { + zpl_adt_make_leaf(obj, name, ZPL_ADT_TYPE_REAL); + obj->real = value; + return true; + } + zpl_b8 zpl_adt_set_int(zpl_adt_node *obj, char const *name, zpl_i64 value) { + zpl_adt_make_leaf(obj, name, ZPL_ADT_TYPE_INTEGER); + obj->integer = value; + return true; + } + + zpl_adt_node *zpl_adt_move_node_at(zpl_adt_node *node, zpl_adt_node *new_parent, zpl_isize index) { + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(new_parent); + zpl_adt_node *old_parent = node->parent; + zpl_adt_node *new_node = zpl_adt_alloc_at(new_parent, index); + *new_node = *node; + new_node->parent = new_parent; + if (old_parent) { + zpl_adt_remove_node(node); + } + return new_node; + } + + zpl_adt_node *zpl_adt_move_node(zpl_adt_node *node, zpl_adt_node *new_parent) { + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(new_parent); + ZPL_ASSERT(new_parent->type == ZPL_ADT_TYPE_ARRAY || new_parent->type == ZPL_ADT_TYPE_OBJECT); + return zpl_adt_move_node_at(node, new_parent, zpl_array_count(new_parent->nodes)); + } + + void zpl_adt_swap_nodes(zpl_adt_node *node, zpl_adt_node *other_node) { + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(other_node); + zpl_adt_node *parent = node->parent; + zpl_adt_node *other_parent = other_node->parent; + zpl_isize index = (zpl_pointer_diff(parent->nodes, node) / zpl_size_of(zpl_adt_node)); + zpl_isize index2 = (zpl_pointer_diff(other_parent->nodes, other_node) / zpl_size_of(zpl_adt_node)); + zpl_adt_node temp = parent->nodes[index]; + temp.parent = other_parent; + other_parent->nodes[index2].parent = parent; + parent->nodes[index] = other_parent->nodes[index2]; + other_parent->nodes[index2] = temp; + } + + void zpl_adt_remove_node(zpl_adt_node *node) { + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(node->parent); + zpl_adt_node *parent = node->parent; + zpl_isize index = (zpl_pointer_diff(parent->nodes, node) / zpl_size_of(zpl_adt_node)); + zpl_array_remove_at(parent->nodes, index); + } + + + zpl_adt_node *zpl_adt_append_obj(zpl_adt_node *parent, char const *name) { + zpl_adt_node *o = zpl_adt_alloc(parent); + if (!o) return NULL; + if (zpl_adt_set_obj(o, name, ZPL_ARRAY_HEADER(parent->nodes)->allocator)) { + zpl_adt_remove_node(o); + return NULL; + } + return o; + } + zpl_adt_node *zpl_adt_append_arr(zpl_adt_node *parent, char const *name) { + zpl_adt_node *o = zpl_adt_alloc(parent); + if (!o) return NULL; + if (zpl_adt_set_arr(o, name, ZPL_ARRAY_HEADER(parent->nodes)->allocator)) { + zpl_adt_remove_node(o); + return NULL; + } + return o; + } + zpl_adt_node *zpl_adt_append_str(zpl_adt_node *parent, char const *name, char const *value) { + zpl_adt_node *o = zpl_adt_alloc(parent); + if (!o) return NULL; + zpl_adt_set_str(o, name, value); + return o; + } + zpl_adt_node *zpl_adt_append_flt(zpl_adt_node *parent, char const *name, zpl_f64 value) { + zpl_adt_node *o = zpl_adt_alloc(parent); + if (!o) return NULL; + zpl_adt_set_flt(o, name, value); + return o; + } + zpl_adt_node *zpl_adt_append_int(zpl_adt_node *parent, char const *name, zpl_i64 value) { + zpl_adt_node *o = zpl_adt_alloc(parent); + if (!o) return NULL; + zpl_adt_set_int(o, name, value); + return o; + } + + /* parser helpers */ + char *zpl_adt_parse_number_strict(zpl_adt_node *node, char* base_str) { + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(base_str); + char *p = base_str, *e = p; + + while (*e) + ++e; + + while (*p && (zpl_strchr("eE.+-", *p) || zpl_char_is_hex_digit(*p))) { + ++p; + } + + if (p >= e) { + return zpl_adt_parse_number(node, base_str); + } + + return base_str; + } + + char *zpl_adt_parse_number(zpl_adt_node *node, char* base_str) { + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(base_str); + char *p = base_str, *e = p; + + zpl_i32 base=0; + zpl_i32 base2=0; + zpl_u8 base2_offset=0; + zpl_i8 exp=0,orig_exp=0; + zpl_u8 neg_zero=0; + zpl_u8 lead_digit=0; + zpl_u8 node_type=0; + zpl_u8 node_props=0; + + /* skip false positives and special cases */ + if (!!zpl_strchr("eE", *p) || (!!zpl_strchr(".+-", *p) && !zpl_char_is_hex_digit(*(p+1)) && *(p+1) != '.')) { + return ++base_str; + } + + node_type = ZPL_ADT_TYPE_INTEGER; + neg_zero = false; + + zpl_isize ib = 0; + char buf[48] = { 0 }; + + if (*e == '+') + ++e; + else if (*e == '-') { + buf[ib++] = *e++; + } + + if (*e == '.') { + node_type = ZPL_ADT_TYPE_REAL; + node_props = ZPL_ADT_PROPS_IS_PARSED_REAL; + lead_digit = false; + buf[ib++] = '0'; + do { + buf[ib++] = *e; + } while (zpl_char_is_digit(*++e)); + } else { + if (!zpl_strncmp(e, "0x", 2) || !zpl_strncmp(e, "0X", 2)) { node_props = ZPL_ADT_PROPS_IS_HEX; } + + /* bail if ZPL_ADT_PROPS_IS_HEX is unset but we get 'x' on input */ + if (zpl_char_to_lower(*e) == 'x' && (node_props != ZPL_ADT_PROPS_IS_HEX)) { + return ++base_str; + } + + while (zpl_char_is_hex_digit(*e) || zpl_char_to_lower(*e) == 'x') { buf[ib++] = *e++; } + + if (*e == '.') { + node_type = ZPL_ADT_TYPE_REAL; + lead_digit = true; + zpl_u32 step = 0; + + do { + buf[ib++] = *e; + ++step; + } while (zpl_char_is_digit(*++e)); + + if (step < 2) { buf[ib++] = '0'; } + } + } + + /* check if we have a dot here, this is a false positive (IP address, ...) */ + if (*e == '.') { + return ++base_str; + } + + zpl_f32 eb = 10; + char expbuf[6] = { 0 }; + zpl_isize expi = 0; + + if (*e && !!zpl_strchr("eE", *e)) { + ++e; + if (*e == '+' || *e == '-' || zpl_char_is_digit(*e)) { + if (*e == '-') { eb = 0.1f; } + if (!zpl_char_is_digit(*e)) { ++e; } + while (zpl_char_is_digit(*e)) { expbuf[expi++] = *e++; } + } + + orig_exp = exp = (zpl_u8)zpl_str_to_i64(expbuf, NULL, 10); + } + + if (node_type == ZPL_ADT_TYPE_INTEGER) { + node->integer = zpl_str_to_i64(buf, 0, 0); + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + /* special case: negative zero */ + if (node->integer == 0 && buf[0] == '-') { + neg_zero = true; + } + #endif + while (orig_exp-- > 0) { node->integer *= (zpl_i64)eb; } + } else { + node->real = zpl_str_to_f64(buf, 0); + + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + char *q = buf, *base_string = q, *base_string2 = q; + base_string = cast(char *)zpl_str_skip(base_string, '.'); + *base_string = '\0'; + base_string2 = base_string + 1; + char *base_string_off = base_string2; + while (*base_string_off++ == '0') base2_offset++; + + base = (zpl_i32)zpl_str_to_i64(q, 0, 0); + base2 = (zpl_i32)zpl_str_to_i64(base_string2, 0, 0); + if (exp) { + exp = exp * (!(eb == 10.0f) ? -1 : 1); + node_props = ZPL_ADT_PROPS_IS_EXP; + } + + /* special case: negative zero */ + if (base == 0 && buf[0] == '-') { + neg_zero = true; + } + #endif + while (orig_exp-- > 0) { node->real *= eb; } + } + + node->type = node_type; + node->props = node_props; + + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + node->base = base; + node->base2 = base2; + node->base2_offset = base2_offset; + node->exp = exp; + node->neg_zero = neg_zero; + node->lead_digit = lead_digit; + #else + zpl_unused(base); + zpl_unused(base2); + zpl_unused(base2_offset); + zpl_unused(exp); + zpl_unused(neg_zero); + zpl_unused(lead_digit); + #endif + return e; + } + + zpl_adt_error zpl_adt_print_number(zpl_file *file, zpl_adt_node *node) { + ZPL_ASSERT_NOT_NULL(file); + ZPL_ASSERT_NOT_NULL(node); + if (node->type != ZPL_ADT_TYPE_INTEGER && node->type != ZPL_ADT_TYPE_REAL) { + return ZPL_ADT_ERROR_INVALID_TYPE; + } + + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (node->neg_zero) { + zpl__adt_fprintf(file, "-"); + } + #endif + + switch (node->type) { + case ZPL_ADT_TYPE_INTEGER: { + if (node->props == ZPL_ADT_PROPS_IS_HEX) { + zpl__adt_fprintf(file, "0x%llx", (long long)node->integer); + } else { + zpl__adt_fprintf(file, "%lld", (long long)node->integer); + } + } break; + + case ZPL_ADT_TYPE_REAL: { + if (node->props == ZPL_ADT_PROPS_NAN) { + zpl__adt_fprintf(file, "NaN"); + } else if (node->props == ZPL_ADT_PROPS_NAN_NEG) { + zpl__adt_fprintf(file, "-NaN"); + } else if (node->props == ZPL_ADT_PROPS_INFINITY) { + zpl__adt_fprintf(file, "Infinity"); + } else if (node->props == ZPL_ADT_PROPS_INFINITY_NEG) { + zpl__adt_fprintf(file, "-Infinity"); + } else if (node->props == ZPL_ADT_PROPS_TRUE) { + zpl__adt_fprintf(file, "true"); + } else if (node->props == ZPL_ADT_PROPS_FALSE) { + zpl__adt_fprintf(file, "false"); + } else if (node->props == ZPL_ADT_PROPS_NULL) { + zpl__adt_fprintf(file, "null"); + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + } else if (node->props == ZPL_ADT_PROPS_IS_EXP) { + zpl__adt_fprintf(file, "%lld.%0*d%llde%lld", (long long)node->base, node->base2_offset, 0, (long long)node->base2, (long long)node->exp); + } else if (node->props == ZPL_ADT_PROPS_IS_PARSED_REAL) { + if (!node->lead_digit) + zpl__adt_fprintf(file, ".%0*d%lld", node->base2_offset, 0, (long long)node->base2); + else + zpl__adt_fprintf(file, "%lld.%0*d%lld", (long long int)node->base2_offset, 0, (int)node->base, (long long)node->base2); + #endif + } else { + zpl__adt_fprintf(file, "%f", node->real); + } + } break; + } + + return ZPL_ADT_ERROR_NONE; + } + + zpl_adt_error zpl_adt_print_string(zpl_file *file, zpl_adt_node *node, char const *escaped_chars, char const *escape_symbol) { + ZPL_ASSERT_NOT_NULL(file); + ZPL_ASSERT_NOT_NULL(node); + ZPL_ASSERT_NOT_NULL(escaped_chars); + if (node->type != ZPL_ADT_TYPE_STRING && node->type != ZPL_ADT_TYPE_MULTISTRING) { + return ZPL_ADT_ERROR_INVALID_TYPE; + } + + /* escape string */ + char const* p = node->string, *b = p; + + if (!p) + return ZPL_ADT_ERROR_NONE; + + do { + p = zpl_str_skip_any(p, escaped_chars); + zpl__adt_fprintf(file, "%.*s", zpl_ptr_diff(b, p), b); + if (*p && !!zpl_strchr(escaped_chars, *p)) { + zpl__adt_fprintf(file, "%s%c", escape_symbol, *p); + p++; + } + b = p; + } while (*p); + + return ZPL_ADT_ERROR_NONE; + } + + zpl_adt_error zpl_adt_str_to_number(zpl_adt_node *node) { + ZPL_ASSERT(node); + + if (node->type == ZPL_ADT_TYPE_REAL || node->type == ZPL_ADT_TYPE_INTEGER) return ZPL_ADT_ERROR_ALREADY_CONVERTED; /* this is already converted/parsed */ + if (node->type != ZPL_ADT_TYPE_STRING && node->type != ZPL_ADT_TYPE_MULTISTRING) { + return ZPL_ADT_ERROR_INVALID_TYPE; + } + + zpl_adt_parse_number(node, (char *)node->string); + + return ZPL_ADT_ERROR_NONE; + } + + zpl_adt_error zpl_adt_str_to_number_strict(zpl_adt_node *node) { + ZPL_ASSERT(node); + + if (node->type == ZPL_ADT_TYPE_REAL || node->type == ZPL_ADT_TYPE_INTEGER) return ZPL_ADT_ERROR_ALREADY_CONVERTED; /* this is already converted/parsed */ + if (node->type != ZPL_ADT_TYPE_STRING && node->type != ZPL_ADT_TYPE_MULTISTRING) { + return ZPL_ADT_ERROR_INVALID_TYPE; + } + + zpl_adt_parse_number_strict(node, (char *)node->string); + + return ZPL_ADT_ERROR_NONE; + } + + #undef zpl__adt_fprintf + + ZPL_END_C_DECLS + + /* parsers */ + // file: source/parsers/json.c + + //////////////////////////////////////////////////////////////// + // + // JSON5 Parser + // + // + + + #ifdef ZPL_JSON_DEBUG + #define ZPL_JSON_ASSERT(msg) ZPL_PANIC(msg) + #else + #define ZPL_JSON_ASSERT(msg) + #endif + + ZPL_BEGIN_C_DECLS + + char *zpl__json_parse_object(zpl_adt_node *obj, char *base, zpl_allocator a, zpl_u8 *err_code); + char *zpl__json_parse_array(zpl_adt_node *obj, char *base, zpl_allocator a, zpl_u8 *err_code); + char *zpl__json_parse_value(zpl_adt_node *obj, char *base, zpl_allocator a, zpl_u8 *err_code); + char *zpl__json_parse_name(zpl_adt_node *obj, char *base, zpl_u8 *err_code); + char *zpl__json_trim(char *base, zpl_b32 catch_newline); + zpl_b8 zpl__json_write_value(zpl_file *f, zpl_adt_node *o, zpl_adt_node *t, zpl_isize indent, zpl_b32 is_inline, zpl_b32 is_last); + + #define zpl__json_fprintf(s_, fmt_, ...) \ + do { \ + if (zpl_fprintf(s_, fmt_, ##__VA_ARGS__) < 0) return false; \ + } while (0) + + #define zpl___ind(x) if (x > 0) zpl__json_fprintf(f, "%*r", x, ' '); + + zpl_u8 zpl_json_parse(zpl_adt_node *root, char *text, zpl_allocator a) { + zpl_u8 err_code = ZPL_JSON_ERROR_NONE; + ZPL_ASSERT(root); + ZPL_ASSERT(text); + zpl_zero_item(root); + text = zpl__json_trim(text, true); + + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (!zpl_strchr("{[", *text)) { + root->cfg_mode = true; + } + #endif + + zpl__json_parse_object(root, text, a, &err_code); + return err_code; + } + + void zpl_json_free(zpl_adt_node *obj) { + zpl_adt_destroy_branch(obj); + } + + zpl_string zpl_json_write_string(zpl_allocator a, zpl_adt_node *obj, zpl_isize indent) { + zpl_file tmp; + if (!zpl_file_stream_new(&tmp, a)) + return NULL; + if (!zpl_json_write(&tmp, obj, indent)) + return NULL; + zpl_isize fsize; + zpl_u8* buf = zpl_file_stream_buf(&tmp, &fsize); + zpl_string output = zpl_string_make_length(a, (char *)buf, fsize); + zpl_file_close(&tmp); + return output; + } + + /* private */ + + #define zpl__json_append_node(x, item)\ + do {\ + if (!zpl_array_append(x, item)) {\ + *err_code = ZPL_JSON_ERROR_OUT_OF_MEMORY;\ + return NULL;\ + }\ + if (item.type == ZPL_ADT_TYPE_OBJECT || item.type == ZPL_ADT_TYPE_ARRAY) {\ + for (zpl_isize i = 0; i < zpl_array_count(item.nodes); i++)\ + item.nodes[i].parent = zpl_array_end(x);\ + }\ + } while (0); + + static ZPL_ALWAYS_INLINE zpl_b32 zpl__json_is_assign_char(char c) { return !!zpl_strchr(":=|", c); } + static ZPL_ALWAYS_INLINE zpl_b32 zpl__json_is_delim_char(char c) { return !!zpl_strchr(",|\n", c); } + static ZPL_ALWAYS_INLINE const char *zpl__json_string_space(zpl_isize indent) { return indent == ZPL_JSON_INDENT_STYLE_COMPACT ? "" : " "; } + static ZPL_ALWAYS_INLINE const char *zpl__json_string_eol(zpl_adt_node *o, zpl_isize indent) { + zpl_b8 force_new_line = false; + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + force_new_line = (o->delim_style != ZPL_ADT_DELIM_STYLE_COMMA); + #endif + return !force_new_line && indent == ZPL_JSON_INDENT_STYLE_COMPACT ? "" : "\n"; + } + ZPL_DEF_INLINE zpl_b32 zpl__json_validate_name(char const *str, char *err); + + #define jx(x) !zpl_char_is_hex_digit(str[x]) + ZPL_IMPL_INLINE zpl_b32 zpl__json_validate_name(char const *str, char *err) { + while (*str) { + /* todo: refactor name validation. */ + if ((str[0] == '\\' && !zpl_char_is_control(str[1])) && + (str[0] == '\\' && jx(1) && jx(2) && jx(3) && jx(4))) { + if (err) *err = *str; + return false; + } + + ++str; + } + + return true; + } + #undef jx + + char *zpl__json_parse_array(zpl_adt_node *obj, char *base, zpl_allocator a, zpl_u8 *err_code) { + ZPL_ASSERT(obj && base); + char *p = base; + + obj->type = ZPL_ADT_TYPE_ARRAY; + if (!zpl_array_init(obj->nodes, a)) { + *err_code = ZPL_JSON_ERROR_OUT_OF_MEMORY; + return NULL; + } + + while (*p) { + p = zpl__json_trim(p, false); + + if (*p == ']') { + return p; + } + + zpl_adt_node elem = { 0 }; + p = zpl__json_parse_value(&elem, p, a, err_code); + + if (*err_code != ZPL_JSON_ERROR_NONE) { return NULL; } + + zpl__json_append_node(obj->nodes, elem); + + p = zpl__json_trim(p, false); + + if (*p == ',') { + ++p; + continue; + } else { + if (*p != ']') { + ZPL_JSON_ASSERT("end of array unfulfilled"); + *err_code = ZPL_JSON_ERROR_ARRAY_LEFT_OPEN; + return NULL; + } + return p; + } + } + + *err_code = ZPL_JSON_ERROR_INTERNAL; + return NULL; + } + + char *zpl__json_parse_value(zpl_adt_node *obj, char *base, zpl_allocator a, zpl_u8 *err_code) { + ZPL_ASSERT(obj && base); + char *p = base, *b = p, *e = p; + + /* handle quoted strings */ + if (!!zpl_strchr("`\"'", *p)) { + char c = *p; + obj->type = (c == '`') ? ZPL_ADT_TYPE_MULTISTRING : ZPL_ADT_TYPE_STRING; + b = e = p + 1; + obj->string = b; + e = cast(char *)zpl_str_skip_literal(e, c); + *e = '\0', p = e + 1; + } else if (zpl_char_is_alpha(*p) || (*p == '-' && !zpl_char_is_digit(*(p + 1)))) { + /* handle constants */ + if (zpl_str_has_prefix(p, "true")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->props = ZPL_ADT_PROPS_TRUE; + obj->real = 1; + p += 4; + } else if (zpl_str_has_prefix(p, "false")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->props = ZPL_ADT_PROPS_FALSE; + obj->real = 0; + p += 5; + } else if (zpl_str_has_prefix(p, "null")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->props = ZPL_ADT_PROPS_NULL; + obj->real = 0; + p += 4; + } else if (zpl_str_has_prefix(p, "Infinity")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->real = ZPL_INFINITY; + obj->props = ZPL_ADT_PROPS_INFINITY; + p += 8; + } else if (zpl_str_has_prefix(p, "-Infinity")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->real = -ZPL_INFINITY; + obj->props = ZPL_ADT_PROPS_INFINITY_NEG; + p += 9; + } else if (zpl_str_has_prefix(p, "NaN")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->real = ZPL_NAN; + obj->props = ZPL_ADT_PROPS_NAN; + p += 3; + } else if (zpl_str_has_prefix(p, "-NaN")) { + obj->type = ZPL_ADT_TYPE_REAL; + obj->real = -ZPL_NAN; + obj->props = ZPL_ADT_PROPS_NAN_NEG; + p += 4; + } else { + ZPL_JSON_ASSERT("unknown keyword"); + *err_code = ZPL_JSON_ERROR_UNKNOWN_KEYWORD; + return NULL; + } + } else if (zpl_char_is_digit(*p) || *p == '+' || *p == '-' || *p == '.') { + /* handle numbers */ + /* defer operation to our helper method. */ + p = zpl_adt_parse_number(obj, p); + } else if (!!zpl_strchr("[{", *p)) { + /* handle compound objects */ + p = zpl__json_parse_object(obj, p, a, err_code); + ++p; + } + + return p; + } + + char *zpl__json_parse_object(zpl_adt_node *obj, char *base, zpl_allocator a, zpl_u8 *err_code) { + ZPL_ASSERT(obj && base); + char *p = base; + + p = zpl__json_trim(p, false); + /**/ if (*p == '{') { ++p; } + else if (*p == '[') { /* special case for when we call this func on an array. */ + ++p; + obj->type = ZPL_ADT_TYPE_ARRAY; + return zpl__json_parse_array(obj, p, a, err_code); + } + + if (!zpl_array_init(obj->nodes, a)) { + *err_code = ZPL_JSON_ERROR_OUT_OF_MEMORY; + return NULL; + } + obj->type = ZPL_ADT_TYPE_OBJECT; + + do { + zpl_adt_node node = { 0 }; + p = zpl__json_trim(p, false); + if (*p == '}' && obj->type == ZPL_ADT_TYPE_OBJECT) return p; + else if (*p == ']' && obj->type == ZPL_ADT_TYPE_ARRAY) return p; + else if (!!zpl_strchr("}]", *p)) { + ZPL_JSON_ASSERT("mismatched end pair"); + *err_code = ZPL_JSON_ERROR_OBJECT_END_PAIR_MISMATCHED; + return NULL; + } + + /* First, we parse the key, then we proceed to the value itself. */ + p = zpl__json_parse_name(&node, p, err_code); + if (err_code && *err_code != ZPL_JSON_ERROR_NONE) { return NULL; } + p = zpl__json_trim(p + 1, false); + p = zpl__json_parse_value(&node, p, a, err_code); + if (err_code && *err_code != ZPL_JSON_ERROR_NONE) { return NULL; } + + zpl__json_append_node(obj->nodes, node); + + char *end_p = p; zpl_unused(end_p); + p = zpl__json_trim(p, true); + + /* this code analyses the keyvalue pair delimiter used in the packet. */ + if (zpl__json_is_delim_char(*p)) { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + zpl_adt_node *n = zpl_array_end(obj->nodes); + n->delim_style = ZPL_ADT_DELIM_STYLE_COMMA; + + if (*p == '\n') + n->delim_style = ZPL_ADT_DELIM_STYLE_NEWLINE; + else if (*p == '|') { + n->delim_style = ZPL_ADT_DELIM_STYLE_LINE; + n->delim_line_width = cast(zpl_u8)(p-end_p); + } + #endif + if (*p == 0) { + return p; + } + ++p; + } + p = zpl__json_trim(p, false); + } while (*p); + return p; + } + + char *zpl__json_parse_name(zpl_adt_node *node, char *base, zpl_u8 *err_code) { + char *p = base, *b = p, *e = p; + zpl_u8 name_style=0; + + if (*p == '"' || *p == '\'' || zpl_char_is_alpha(*p) || *p == '_' || *p == '$') { + if (*p == '"' || *p == '\'') { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (*p == '"') { + node->name_style = ZPL_ADT_NAME_STYLE_DOUBLE_QUOTE; + } else if (*p == '\'') { + node->name_style = ZPL_ADT_NAME_STYLE_SINGLE_QUOTE; + } + #endif + char c = *p; + b = ++p; + e = cast(char *)zpl_str_control_skip(b, c); + node->name = b; + + /* we can safely null-terminate here, since "e" points to the quote pair end. */ + *e++ = '\0'; + } + else { + b = e = p; + zpl_str_advance_while(e, *e && (zpl_char_is_alphanumeric(*e) || *e == '_') && !zpl_char_is_space(*e) && !zpl__json_is_assign_char(*e)); + node->name = b; + name_style = ZPL_ADT_NAME_STYLE_NO_QUOTES; + /* we defer null-termination as it can potentially wipe our assign char as well. */ + } + + char *assign_p = e; zpl_unused(assign_p); + p = zpl__json_trim(e, false); + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + node->assign_line_width = cast(zpl_u8)(p-assign_p); + #endif + + if (*p && !zpl__json_is_assign_char(*p)) { + ZPL_JSON_ASSERT("invalid assignment"); + *err_code = ZPL_JSON_ERROR_INVALID_ASSIGNMENT; + return NULL; + } + else + { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (*p == '=') + node->assign_style = ZPL_ADT_ASSIGN_STYLE_EQUALS; + else if (*p == '|') + node->assign_style = ZPL_ADT_ASSIGN_STYLE_LINE; + else node->assign_style = ZPL_ADT_ASSIGN_STYLE_COLON; + #endif + } + + /* since we already know the assign style, we can cut it here for unquoted names */ + if (name_style == ZPL_ADT_NAME_STYLE_NO_QUOTES && *e) { + *e = '\0'; + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + node->name_style = name_style; + #endif + } + } + + if (node->name && !zpl__json_validate_name(node->name, NULL)) { + ZPL_JSON_ASSERT("invalid name"); + *err_code = ZPL_JSON_ERROR_INVALID_NAME; + return NULL; + } + + return p; + } + + char *zpl__json_trim(char *base, zpl_b32 catch_newline) { + ZPL_ASSERT_NOT_NULL(base); + char *p = base; + do { + if (zpl_str_has_prefix(p, "//")) { + const char *e = zpl_str_skip(p, '\n'); + p += (e-p); + } + else if (zpl_str_has_prefix(p, "/*")) { + const char *e = zpl_str_skip(p+2, '*'); + if (*e && *(e+1) == '/') { + e+=2; /* advance past end comment block */ + p += (e-p); + } + } + else if (*p == '\n' && catch_newline) { + return p; + } + else if (!zpl_char_is_space(*p)) { + return p; + } + } while (*p++); + return NULL; + } + + zpl_b8 zpl_json_write(zpl_file *f, zpl_adt_node *o, zpl_isize indent) { + if (!o) + return true; + + ZPL_ASSERT(o->type == ZPL_ADT_TYPE_OBJECT || o->type == ZPL_ADT_TYPE_ARRAY); + + zpl___ind(indent - 4); + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (!o->cfg_mode) + #else + if (1) + #endif + zpl__json_fprintf(f, "%c%s", o->type == ZPL_ADT_TYPE_OBJECT ? '{' : '[', zpl__json_string_eol(o, indent)); + else + { + indent -= 4; + } + + if (o->nodes) { + zpl_isize cnt = zpl_array_count(o->nodes); + + for (int i = 0; i < cnt; ++i) { + if (!zpl__json_write_value(f, o->nodes + i, o, indent, false, !(i < cnt - 1))) return false; + } + } + + zpl___ind(indent); + + if (indent > 0) { + zpl__json_fprintf(f, "%c", o->type == ZPL_ADT_TYPE_OBJECT ? '}' : ']'); + } else { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (!o->cfg_mode) + #endif + zpl__json_fprintf(f, "%c%s", o->type == ZPL_ADT_TYPE_OBJECT ? '}' : ']', zpl__json_string_eol(o, indent)); + } + + return true; + } + + zpl_b8 zpl__json_write_value(zpl_file *f, zpl_adt_node *o, zpl_adt_node *t, zpl_isize indent, zpl_b32 is_inline, zpl_b32 is_last) { + zpl_adt_node *node = o; + if (indent != ZPL_JSON_INDENT_STYLE_COMPACT) indent += 4; + + if (!is_inline) { + zpl___ind(indent); + + if (t->type != ZPL_ADT_TYPE_ARRAY) { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + switch (node->name_style) { + case ZPL_ADT_NAME_STYLE_DOUBLE_QUOTE: { + zpl__json_fprintf(f, "\"%s\"", node->name); + } break; + + case ZPL_ADT_NAME_STYLE_SINGLE_QUOTE: { + zpl__json_fprintf(f, "\'%s\'", node->name); + } break; + + case ZPL_ADT_NAME_STYLE_NO_QUOTES: { + zpl__json_fprintf(f, "%s", node->name); + } break; + } + + if (o->assign_style == ZPL_ADT_ASSIGN_STYLE_COLON) + zpl__json_fprintf(f, ":%s", zpl__json_string_space(indent)); + else { + if (indent != ZPL_JSON_INDENT_STYLE_COMPACT) + zpl___ind(zpl_max(o->assign_line_width, 1)); + + if (o->assign_style == ZPL_ADT_ASSIGN_STYLE_EQUALS) + zpl__json_fprintf(f, "=%s", zpl__json_string_space(indent)); + else if (o->assign_style == ZPL_ADT_ASSIGN_STYLE_LINE) { + zpl__json_fprintf(f, "|%s", zpl__json_string_space(indent)); + } + } + #else + zpl__json_fprintf(f, "\"%s\":%s", node->name, zpl__json_string_space(indent)); + #endif + } + } + + switch (node->type) { + case ZPL_ADT_TYPE_STRING: { + zpl__json_fprintf(f, "\""); + if (zpl_adt_print_string(f, node, "\"", "\\")) return false; + zpl__json_fprintf(f, "\""); + } break; + + case ZPL_ADT_TYPE_MULTISTRING: { + zpl__json_fprintf(f, "`"); + if (zpl_adt_print_string(f, node, "`", "\\")) return false; + zpl__json_fprintf(f, "`"); + } break; + + case ZPL_ADT_TYPE_ARRAY: { + zpl__json_fprintf(f, "["); + zpl_isize elemn = zpl_array_count(node->nodes); + for (int j = 0; j < elemn; ++j) { + zpl_isize ind = ((node->nodes + j)->type == ZPL_ADT_TYPE_OBJECT || (node->nodes + j)->type == ZPL_ADT_TYPE_ARRAY) ? 0 : -4; + if (!zpl__json_write_value(f, node->nodes + j, o, indent == ZPL_JSON_INDENT_STYLE_COMPACT ? indent : ind, true, true)) return false; + + if (j < elemn - 1) { zpl__json_fprintf(f, ", "); } + } + zpl__json_fprintf(f, "]"); + } break; + + case ZPL_ADT_TYPE_REAL: + case ZPL_ADT_TYPE_INTEGER: { + if (zpl_adt_print_number(f, node)) return false; + } break; + + case ZPL_ADT_TYPE_OBJECT: { + if (!zpl_json_write(f, node, indent)) return false; + } break; + } + + if (!is_inline) { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + if (o->delim_style != ZPL_ADT_DELIM_STYLE_COMMA) { + if (o->delim_style == ZPL_ADT_DELIM_STYLE_NEWLINE) + zpl__json_fprintf(f, "\n"); + else if (o->delim_style == ZPL_ADT_DELIM_STYLE_LINE) { + zpl___ind(o->delim_line_width); + zpl__json_fprintf(f, "|\n"); + } + } + else { + if (!is_last) { + zpl__json_fprintf(f, ",%s", zpl__json_string_eol(o, indent)); + } else { + zpl__json_fprintf(f, "%s", zpl__json_string_eol(o, indent)); + } + } + #else + if (!is_last) { + zpl__json_fprintf(f, ",%s", zpl__json_string_eol(o, indent)); + } else { + zpl__json_fprintf(f, "%s", zpl__json_string_eol(o, indent)); + } + #endif + } + + return true; + } + + #undef zpl__json_fprintf + #undef zpl___ind + #undef zpl__json_append_node + + ZPL_END_C_DECLS + // file: source/parsers/csv.c + + + #ifdef ZPL_CSV_DEBUG + #define ZPL_CSV_ASSERT(msg) ZPL_PANIC(msg) + #else + #define ZPL_CSV_ASSERT(msg) + #endif + + + ZPL_BEGIN_C_DECLS + + zpl_u8 zpl_csv_parse_delimiter(zpl_csv_object *root, char *text, zpl_allocator allocator, zpl_b32 has_header, char delim) { + zpl_csv_error err = ZPL_CSV_ERROR_NONE; + ZPL_ASSERT_NOT_NULL(root); + ZPL_ASSERT_NOT_NULL(text); + zpl_zero_item(root); + zpl_adt_make_branch(root, allocator, NULL, has_header ? false : true); + char *p = text, *b = p, *e = p; + zpl_isize colc = 0, total_colc = 0; + + do { + char d = 0; + p = cast(char *)zpl_str_trim(p, false); + if (*p == 0) break; + zpl_adt_node row_item = {0}; + row_item.type = ZPL_ADT_TYPE_STRING; + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + row_item.name_style = ZPL_ADT_NAME_STYLE_NO_QUOTES; + #endif + + /* handle string literals */ + if (*p == '"') { + p = b = e = p+1; + row_item.string = b; + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + row_item.name_style = ZPL_ADT_NAME_STYLE_DOUBLE_QUOTE; + #endif + do { + e = cast(char *)zpl_str_skip(e, '"'); + if (*e && *(e+1) == '"') { + e += 2; + } + else break; + } while (*e); + if (*e == 0) { + ZPL_CSV_ASSERT("unmatched quoted string"); + err = ZPL_CSV_ERROR_UNEXPECTED_END_OF_INPUT; + return err; + } + *e = 0; + p = cast(char *)zpl_str_trim(e+1, true); + d = *p; + + /* unescape escaped quotes (so that unescaped text escapes :) */ + { + char *ep = b; + do { + if (*ep == '"' && *(ep+1) == '"') { + zpl_memmove(ep, ep+1, zpl_strlen(ep)); + } + ep++; + } while (*ep); + } + } + else if (*p == delim) { + d = *p; + row_item.string = ""; + } + else if (*p) { + /* regular data */ + b = e = p; + row_item.string = b; + do { + e++; + } while (*e && *e != delim && *e != '\n'); + if (*e) { + p = cast(char *)zpl_str_trim(e, true); + while (zpl_char_is_space(*(e-1))) { e--; } + d = *p; + *e = 0; + } + else { + d = 0; + p = e; + } + + /* check if number and process if so */ + zpl_b32 skip_number = false; + char *num_p = b; + do { + if (!zpl_char_is_hex_digit(*num_p) && (!zpl_strchr("+-.eExX", *num_p))) { + skip_number = true; + break; + } + } while (*num_p++); + + if (!skip_number) { + zpl_adt_str_to_number(&row_item); + } + } + + if (colc >= zpl_array_count(root->nodes)) { + zpl_adt_append_arr(root, NULL); + } + + zpl_array_append(root->nodes[colc].nodes, row_item); + + if (d == delim) { + colc++; + p++; + } + else if (d == '\n' || d == 0) { + /* check if number of rows is not mismatched */ + if (total_colc < colc) total_colc = colc; + else if (total_colc != colc) { + ZPL_CSV_ASSERT("mismatched rows"); + err = ZPL_CSV_ERROR_MISMATCHED_ROWS; + return err; + } + colc = 0; + if (d != 0) p++; + } + } while(*p); + + if (zpl_array_count(root->nodes) == 0) { + ZPL_CSV_ASSERT("unexpected end of input. stream is empty."); + err = ZPL_CSV_ERROR_UNEXPECTED_END_OF_INPUT; + return err; + } + + /* consider first row as a header. */ + if (has_header) { + for (zpl_isize i = 0; i < zpl_array_count(root->nodes); i++) { + zpl_csv_object *col = root->nodes + i; + zpl_csv_object *hdr = col->nodes; + col->name = hdr->string; + zpl_array_remove_at(col->nodes, 0); + } + } + + return err; + } + void zpl_csv_free(zpl_csv_object *obj) { + zpl_adt_destroy_branch(obj); + } + + void zpl__csv_write_record(zpl_file *file, zpl_csv_object *node) { + switch (node->type) { + case ZPL_ADT_TYPE_STRING: { + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + switch (node->name_style) { + case ZPL_ADT_NAME_STYLE_DOUBLE_QUOTE: { + zpl_fprintf(file, "\""); + zpl_adt_print_string(file, node, "\"", "\""); + zpl_fprintf(file, "\""); + } break; + + case ZPL_ADT_NAME_STYLE_NO_QUOTES: { + #endif + zpl_fprintf(file, "%s", node->string); + #ifndef ZPL_PARSER_DISABLE_ANALYSIS + } break; + } + #endif + } break; + + case ZPL_ADT_TYPE_REAL: + case ZPL_ADT_TYPE_INTEGER: { + zpl_adt_print_number(file, node); + } break; + } + } + + void zpl__csv_write_header(zpl_file *file, zpl_csv_object *header) { + zpl_csv_object temp = *header; + temp.string = temp.name; + temp.type = ZPL_ADT_TYPE_STRING; + zpl__csv_write_record(file, &temp); + } + + void zpl_csv_write_delimiter(zpl_file *file, zpl_csv_object *obj, char delimiter) { + ZPL_ASSERT_NOT_NULL(file); + ZPL_ASSERT_NOT_NULL(obj); + ZPL_ASSERT(obj->nodes); + zpl_isize cols = zpl_array_count(obj->nodes); + if (cols == 0) return; + + zpl_isize rows = zpl_array_count(obj->nodes[0].nodes); + if (rows == 0) return; + + zpl_b32 has_headers = obj->nodes[0].name != NULL; + + if (has_headers) { + for (zpl_isize i = 0; i < cols; i++) { + zpl__csv_write_header(file, &obj->nodes[i]); + if (i+1 != cols) { + zpl_fprintf(file, "%c", delimiter); + } + } + zpl_fprintf(file, "\n"); + } + + for (zpl_isize r = 0; r < rows; r++) { + for (zpl_isize i = 0; i < cols; i++) { + zpl__csv_write_record(file, &obj->nodes[i].nodes[r]); + if (i+1 != cols) { + zpl_fprintf(file, "%c", delimiter); + } + } + zpl_fprintf(file, "\n"); + } + } + + zpl_string zpl_csv_write_string_delimiter(zpl_allocator a, zpl_csv_object *obj, char delimiter) { + zpl_file tmp; + zpl_file_stream_new(&tmp, a); + zpl_csv_write_delimiter(&tmp, obj, delimiter); + zpl_isize fsize; + zpl_u8* buf = zpl_file_stream_buf(&tmp, &fsize); + zpl_string output = zpl_string_make_length(a, (char *)buf, fsize); + zpl_file_close(&tmp); + return output; + } + + ZPL_END_C_DECLS + // file: source/parsers/uri.c + + ZPL_BEGIN_C_DECLS + + void zpl__uri_decode_str(char *text) { + char buf[ZPL_PRINTF_MAXLEN] = {0}, *p = buf; + char *tp = text; + + char ch = -1; + while (*tp) { + ch = *tp++; + + if (ch != '%') { + if (ch == '+') { + *(p++) = ' '; + } else { + *(p++) = ch; + } + } else { + char hex[3] = {0}; + hex[0] = tp[0]; + hex[1] = tp[1]; + char b = (char)zpl_str_to_i64(hex, NULL, 16); + *(p++) = b; + tp += 2; + } + } + + zpl_strcpy(text, buf); + text[p-buf] = 0; + } + + zpl_u8 zpl_uri_init(zpl_adt_node *root, char *origin, zpl_allocator a) { + zpl_u8 err_code = ZPL_URI_ERROR_NONE; + ZPL_ASSERT_NOT_NULL(root); + zpl_zero_item(root); + zpl_adt_set_obj(root, NULL, a); + if (origin) + zpl_adt_append_str(root, "__zpl_origin__", origin); + return err_code; + } + + zpl_u8 zpl_uri_parse(zpl_adt_node *root, char *text, zpl_allocator a) { + zpl_u8 err_code = ZPL_URI_ERROR_NONE; + ZPL_ASSERT_NOT_NULL(root); + ZPL_ASSERT_NOT_NULL(text); + zpl_zero_item(root); + text = (char *)zpl_str_trim(text, false); + + zpl_adt_set_obj(root, NULL, a); + + char *p = text, *b = p; + + if (*p == 0) { + return err_code; + } + + // NOTE(zaklaus): grab URI origin + if (*p != '?') { + while (*p && *p != '?' && !zpl_char_is_space(*p)) {++p;} + char c = *p; + *p = 0; + + zpl_adt_append_str(root, "__zpl_origin__", b); + + if (!c) { + // NOTE(zaklaus): URI has no query params, bail + return err_code; + } + } + + b = ++p; + + // NOTE(zaklaus): extract query params + while (*p && !zpl_char_is_space(*p)) { + // NOTE(zaklaus): get param name + b = p; + while (*p && (*p != '&' && *p != '?' && *p != '=' && !zpl_char_is_space(*p))) { ++p; } + char c = *p; + *p = 0; + + char *field_name = b; + char *field_value = ""; + zpl__uri_decode_str(field_name); + + if (c == '=') { + // NOTE(zaklaus): read param value + ++p; + b = p; + while (*p && (*p != '&' && *p != '?' && !zpl_char_is_space(*p))) { ++p; } + c = *p; + *p = 0; + + field_value = b; + zpl__uri_decode_str(field_value); + zpl_adt_node *obj = zpl_adt_append_str(root, field_name, field_value); + zpl_adt_str_to_number_strict(obj); + } else { + zpl_adt_node *obj = zpl_adt_append_flt(root, field_name, 1); + obj->props = ZPL_ADT_PROPS_TRUE; + } + + + if (!c) { + break; + } + + ++p; + } + + return err_code; + } + + zpl_adt_error zpl__uri_print_str(zpl_file *file, const char *text) { + ZPL_ASSERT_NOT_NULL(file); + + if (!text) { + return ZPL_ADT_ERROR_NONE; + } + + const char *p = text; + char buf[10] = {0}; + zpl_u8 ch; + + while (*p) { + ch = (zpl_u8) *p++; + if (ch == ' ') { + zpl_fprintf(file, "%s", "+"); + } else if (zpl_char_is_alphanumeric(ch) || zpl_strchr("-_.~", ch)) { + zpl_fprintf(file, "%c", ch); + } else { + zpl_snprintf(buf, zpl_size_of(buf), "%02X", ch); + zpl_fprintf(file, "%c%s", '%', buf); + } + } + + return ZPL_ADT_ERROR_NONE; + } + + void zpl_uri_write(zpl_file *f, zpl_adt_node *obj) { + ZPL_ASSERT_NOT_NULL(f); + ZPL_ASSERT_NOT_NULL(obj); + ZPL_ASSERT(obj->type == ZPL_ADT_TYPE_OBJECT); + + zpl_adt_node *origin = NULL; + + // NOTE(zaklaus): write URI origin if available + { + origin = zpl_adt_query(obj, "__zpl_origin__"); + if (origin) { + zpl_fprintf(f, origin->string); + } + } + + // NOTE(zaklaus): write params + if (zpl_array_count(obj->nodes) > 0) + zpl_fprintf(f, "%s", "?"); + + for (zpl_isize i = 0; i < zpl_array_count(obj->nodes); i++) { + zpl_adt_node *n = (obj->nodes+i); + if (origin == n) continue; + + zpl__uri_print_str(f, n->name); + + if (n->type == ZPL_ADT_TYPE_STRING) { + zpl_fprintf(f, "%c", '='); + zpl__uri_print_str(f, n->string); + } else if (n->type == ZPL_ADT_TYPE_INTEGER || n->type == ZPL_ADT_TYPE_REAL) { + if (n->props != ZPL_ADT_PROPS_TRUE) { + zpl_fprintf(f, "%c", '='); + // TODO: ensure the output is URI-encoded + zpl_adt_print_number(f, n); + } + } + + if (i+1 < zpl_array_count(obj->nodes)) { + zpl_fprintf(f, "%s", "&"); + } + } + } + + void zpl_uri_free(zpl_adt_node *obj) { + zpl_adt_destroy_branch(obj); + } + + zpl_string zpl_uri_write_string(zpl_allocator a, zpl_adt_node *obj) { + zpl_file tmp; + zpl_file_stream_new(&tmp, a); + zpl_uri_write(&tmp, obj); + zpl_isize fsize; + zpl_u8* buf = zpl_file_stream_buf(&tmp, &fsize); + zpl_string output = zpl_string_make_length(a, (char *)buf, fsize); + zpl_file_close(&tmp); + return output; + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_MODULE_SOCKET) + // file: source/socket.c + + ZPL_BEGIN_C_DECLS + + #if defined(ZPL_SYSTEM_WINDOWS) + # include + # pragma comment(lib, "ws2_32.lib") + typedef int socklen_t; + #else //unix + # include + # include + # include + # include + #ifndef TCP_NODELAY + # include + # include + # endif + #endif + + ZPL_DEF zpl_socket zpl_socket_init(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + WSADATA winsock_data = {0}; + return WSAStartup(MAKEWORD(2, 2), &winsock_data) != NO_ERROR; + # endif + return 0; + } + + ZPL_DEF zpl_socket zpl_socket_create(zpl_i32 protocol, zpl_i32 mode, char flags, const char *host, const char *service) { + struct addrinfo *result, hints = { + (mode == ZPL_SOCKET_BIND) ? AI_PASSIVE : 0, + AF_UNSPEC, + (protocol == ZPL_SOCKET_UDP) ? SOCK_DGRAM : SOCK_STREAM, + 0, 0, 0, 0, 0 + }; + + if (getaddrinfo(host, service, &hints, &result) != 0) { + return -1; + } + # if defined(ZPL_SYSTEM_WINDOWS) + zpl_socket sock = (zpl_socket)socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == INVALID_SOCKET) { + freeaddrinfo(result); + return -1; + } + if (sock > INT_MAX) { + closesocket(sock); + freeaddrinfo(result); + return -1; + } + # else + zpl_socket sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == -1) { + freeaddrinfo(result); + return -1; + } + # endif + + if (result->ai_family == AF_INET6) { + zpl_i32 no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&no, sizeof(no)); + } + + if (protocol == ZPL_SOCKET_TCP) { + int nodelay = (flags & ZPL_SOCKET_NO_DELAY); + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)); + } + + if (mode == ZPL_SOCKET_BIND) { + if (bind(sock, result->ai_addr, (int)result->ai_addrlen) != 0) { + freeaddrinfo(result); + return -1; + } + } + + if (flags & ZPL_SOCKET_NON_BLOCKING) { + # if defined(ZPL_SYSTEM_WINDOWS) + DWORD non_blocking = 1; + if (ioctlsocket(sock, FIONBIO, &non_blocking)) { + freeaddrinfo(result); + return -1; + } + # else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { + freeaddrinfo(result); + return -1; + } + # endif + } + + if (mode == ZPL_SOCKET_CONNECT) { + if (connect(sock, result->ai_addr, (int)result->ai_addrlen) != 0 && !(flags & ZPL_SOCKET_NON_BLOCKING)) { + freeaddrinfo(result); + return -1; + } + } + + freeaddrinfo(result); + return sock; + } + + ZPL_DEF void zpl_socket_close(zpl_socket socket) { + # if defined(ZPL_SYSTEM_WINDOWS) + closesocket(socket); + # else + close(socket); + # endif + } + + ZPL_DEF void zpl_socket_terminate(void) { + # if defined(ZPL_SYSTEM_WINDOWS) + WSACleanup(); + # endif + } + + ZPL_DEF zpl_i32 zpl_socket_listen(zpl_socket socket, zpl_i32 backlog) { + return listen(socket, backlog); + } + + ZPL_DEF zpl_socket zpl_socket_accept(zpl_socket socket, zpl_socket_addr *addr) { + # if defined(ZPL_SYSTEM_WINDOWS) + int len = sizeof(*addr); + zpl_socket sock = (zpl_socket)accept(socket, (struct sockaddr *)addr, &len); + if (sock == INVALID_SOCKET) { + return -1; + } + if (sock > INT_MAX) { + closesocket(sock); + return -1; + } + # else + socklen_t len = sizeof(*addr); + zpl_socket sock = accept(socket, (struct sockaddr *)addr, &len); + if (sock == -1) { + return -1; + } + # endif + return sock; + } + + ZPL_DEF zpl_i32 zpl_socket_get_address(zpl_socket socket, zpl_socket_addr *addr) { + return getsockname(socket, (struct sockaddr *)addr, (socklen_t *)sizeof(*addr)); + } + + ZPL_DEF zpl_i32 zpl_socket_get_address_info(zpl_socket_addr *addr, char *host, zpl_i32 host_size, char *service, zpl_i32 service_size) { + return getnameinfo((struct sockaddr *)addr, (socklen_t)sizeof(*addr), host, host_size, service, service_size, 0); + } + + ZPL_DEF zpl_i32 zpl_socket_send(zpl_socket socket, const char *data, zpl_i32 size) { + return send(socket, data, size, 0); + } + + ZPL_DEF zpl_i32 zpl_socket_receive(zpl_socket socket, char *buffer, zpl_i32 size) { + return recv(socket, buffer, size, 0); + } + + ZPL_DEF zpl_i32 zpl_socket_send_to(zpl_socket socket, zpl_socket_addr *addr, const char *data, zpl_i32 size) { + return sendto(socket, data, size, 0, (struct sockaddr *)addr, (socklen_t)sizeof(*addr)); + } + + ZPL_DEF zpl_i32 zpl_socket_receive_from(zpl_socket socket, zpl_socket_addr *addr, char *buffer, zpl_i32 size) { + return recvfrom(socket, buffer, size, 0, (struct sockaddr *)addr, (socklen_t *)sizeof(*addr)); + } + + ZPL_DEF zpl_i32 zpl_socket_select(zpl_socket socket, zpl_f64 time) { + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + if (socket > -1) FD_SET(socket, &fds); + + tv.tv_sec = (long)time; + tv.tv_usec = (long)((time - tv.tv_sec) * 1000000); + + return select(socket + 1, &fds, 0, 0, &tv); + } + + ZPL_DEF zpl_i32 zpl_socket_multi_select(zpl_socket *sockets, zpl_i32 count, zpl_f64 time) { + fd_set fds; + struct timeval tv; + zpl_i32 i, max = -1; + + FD_ZERO(&fds); + for (i = 0; i < count; ++i) { + if (sockets[i] > max) max = sockets[i]; + if (sockets[i] > -1) FD_SET(sockets[i], &fds); + } + + tv.tv_sec = (long)time; + tv.tv_usec = (long)((time - tv.tv_sec) * 1000000); + + return select(max + 1, &fds, 0, 0, &tv); + } + + ZPL_END_C_DECLS +#endif + +#if defined(ZPL_COMPILER_MSVC) +# pragma warning(pop) +#endif + +#if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic pop +#endif + +#endif // ZPL_IMPLEMENTATION + +#if !defined(ZPL_PICO_CUSTOM_ROUTINES) +# undef zpl__printf_err +# undef zpl__printf_err_va +# undef zpl__strlen +#endif + +#if defined(ZPL_EXPOSE_TYPES) + typedef zpl_u8 u8; + typedef zpl_i8 i8; + typedef zpl_u16 u16; + typedef zpl_i16 i16; + typedef zpl_u32 u32; + typedef zpl_i32 i32; + typedef zpl_u64 u64; + typedef zpl_i64 i64; + typedef zpl_b8 b8; + typedef zpl_b16 b16; + typedef zpl_b32 b32; + typedef zpl_f32 f32; + typedef zpl_f64 f64; + typedef zpl_rune rune; + typedef zpl_usize usize; + typedef zpl_isize isize; + typedef zpl_uintptr uintptr; + typedef zpl_intptr intptr; +#endif // ZPL_EXPOSE_TYPES + +#endif // ZPL_H + +// TOC: +// zpl.h +// zpl_hedley.h +// header/opts.h +// header/essentials/helpers.h +// header/essentials/memory.h +// header/essentials/memory_custom.h +// header/essentials/types.h +// header/essentials/collections/buffer.h +// header/essentials/collections/list.h +// header/essentials/collections/hashtable.h +// header/essentials/collections/ring.h +// header/essentials/collections/array.h +// header/essentials/debug.h +// header/process.h +// header/threading/fence.h +// header/threading/mutex.h +// header/threading/sync.h +// header/threading/affinity.h +// header/threading/atomic.h +// header/threading/thread.h +// header/threading/sem.h +// header/math.h +// header/jobs.h +// header/parsers/json.h +// header/parsers/csv.h +// header/parsers/uri.h +// header/dll.h +// header/adt.h +// header/core/file_tar.h +// header/core/memory_virtual.h +// header/core/random.h +// header/core/file_stream.h +// header/core/string.h +// header/core/misc.h +// header/core/file.h +// header/core/stringlib.h +// header/core/sort.h +// header/core/print.h +// header/core/system.h +// header/core/file_misc.h +// header/core/time.h +// header/hashing.h +// header/regex.h +// header/socket.h +// source/hashing.c +// source/adt.c +// source/process.c +// source/essentials/debug.c +// source/essentials/memory_custom.c +// source/essentials/memory.c +// source/dll.c +// source/regex.c +// source/threading/mutex.c +// source/threading/affinity.c +// source/threading/atomic.c +// source/threading/sync.c +// source/threading/thread.c +// source/threading/fence.c +// source/threading/sem.c +// source/parsers/csv.c +// source/parsers/uri.c +// source/parsers/json.c +// source/jobs.c +// source/core/file_stream.c +// source/core/stringlib.c +// source/core/misc.c +// source/core/file_misc.c +// source/core/file.c +// source/core/memory_virtual.c +// source/core/print.c +// source/core/time.c +// source/core/string.c +// source/core/random.c +// source/core/sort.c +// source/core/file_tar.c +// source/opts.c +// source/math.c +// source/socket.c diff --git a/thirdparty/stb/truetype/stb_truetype.odin b/thirdparty/stb/truetype/stb_truetype.odin index f128ad0..59bbcc4 100644 --- a/thirdparty/stb/truetype/stb_truetype.odin +++ b/thirdparty/stb/truetype/stb_truetype.odin @@ -40,27 +40,27 @@ when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { // CUSTOM: ODIN COMPATIBLE ALLOCATOR //----------------------------------------------------------------------------- -gbAllocationType :: enum(i32) { +zpl_allocator_type :: enum(i32) { Alloc, Free, FreeAll, Resize, } -gbAllocatorProc :: #type proc(allocator_data: rawptr, type: gbAllocationType, +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 -gbAllocator :: struct { - procedure: gbAllocatorProc, +zpl_allocator :: struct { + procedure: zpl_allocator_proc, data: rawptr, } @(default_calling_convention="c", link_prefix="stbtt_") foreign stbtt { - SetAllocator :: proc(allocator : gbAllocator) --- + SetAllocator :: proc(allocator : zpl_allocator) --- } //----------------------------------------------------------------------------- diff --git a/vefontcache/parser.odin b/vefontcache/parser.odin index 9815870..631f02a 100644 --- a/vefontcache/parser.odin +++ b/vefontcache/parser.odin @@ -61,7 +61,7 @@ Parser_Context :: struct { parser_stbtt_allocator_proc :: proc( allocator_data : rawptr, - type : stbtt.gbAllocationType, + type : stbtt.zpl_allocator_type, size : c.ssize_t, alignment : c.ssize_t, old_memory : rawptr, @@ -86,13 +86,13 @@ parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind, allocator := con ctx.kind = kind ctx.lib_backing = allocator - stbtt_allocator := stbtt.gbAllocator { parser_stbtt_allocator_proc, & ctx.lib_backing } + 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.gbAllocator { parser_stbtt_allocator_proc, & ctx.lib_backing } + stbtt_allocator := stbtt.zpl_allocator { parser_stbtt_allocator_proc, & ctx.lib_backing } stbtt.SetAllocator( stbtt_allocator ) }