2
0
Эх сурвалжийг харах

Merge branch 'master' into sgfx-wgpu

Andre Weissflog 1 жил өмнө
parent
commit
9a1421dd1e

+ 1 - 1
.github/workflows/gen_bindings.yml

@@ -227,7 +227,7 @@ jobs:
       - uses: actions/download-artifact@v3
         with:
           name: ignore-me-rust
-          path: src/sokol
+          path: src
       - uses: dtolnay/rust-toolchain@master
         with:
           toolchain: stable

+ 64 - 0
CHANGELOG.md

@@ -1,5 +1,69 @@
 ## Updates
 
+#### 03-Oct-2023
+
+- sokol_app.h win/gl: PR https://github.com/floooh/sokol/pull/886 has been merged, this makes
+  GL context initialization on Windows slightly more efficient. Many thanks to @dtrebilco!
+
+#### 25-Sep-2023
+
+- The allocator callback functions in all headers that support custom allocators have been renamed
+  from `alloc` and `free` to `alloc_fn` and `free_fn`, this is because the symbol `free` is quite
+  likely to collide with a preprocessor macro of the same name if the standard C allocator is
+  replaced with a custom allocator.
+
+  This is a breaking change only if you've been providing your own allocator functions to
+  the sokol headers.
+
+  See issue https://github.com/floooh/sokol/issues/903 and PR https://github.com/floooh/sokol/pull/908
+  for details.
+
+#### 23-Sep-2023
+
+- sokol_gfx.h gl: Allow to inject an external GL framebuffer id into the sokol-gfx default
+  pass. See PR https://github.com/floooh/sokol/pull/899 and issue https://github.com/floooh/sokol/issues/892
+  for details. Many thanks to @danielchasehooper for the discussion and PR!
+
+  Further down the road I want to make the whole topic more flexible while at the same time
+  simplifying the sokol-gfx API, see here: https://github.com/floooh/sokol/issues/904
+
+#### 22-Sep-2023
+
+- sokol_gfx.h: Fixed a Metal validation error on Intel Macs when creating textures (Intel Macs
+  have unified memory, but don't support textures in shared storage mode). This was a regression
+  in the image/sampler split update in mid-July 2023. Fixes issue https://github.com/floooh/sokol/issues/905
+  via PR https://github.com/floooh/sokol/pull/907.
+
+#### 19-Sep-2023
+
+- sokol_fetch.h: fixed a minor issue where a request that was cancelled before it was dispatched
+  had an incomplete response state set in the response callback (the `finished`, `failed` and
+  `error_code` fields were not set). This fixes issue https://github.com/floooh/sokol/issues/882
+  via PR https://github.com/floooh/sokol/pull/898
+
+#### 18-Sep-2023
+
+- PR https://github.com/floooh/sokol/pull/893 has been merged, this fixes a minor issue
+  in the GL backend when using an injected texture as framebuffer attachment.
+- Issue https://github.com/floooh/sokol/issues/884 has been fixed via PR https://github.com/floooh/sokol/pull/894,
+  this adds missing error code paths in the Metal backend when Metal object creation fails.
+- Clarified `sapp_run()` behaviour in the sokol_app.h documentation header (search for `OPTIONAL: DON'T HIJACK main()`)
+- sokol_args.h now fully supports "key-only args", see issue https://github.com/floooh/sokol/issues/876 for details,
+  fixed via PR https://github.com/floooh/sokol/pull/896
+
+#### 17-Sep-2023
+
+- The sokol-gfx Metal backend now adds debug labels to Metal resource objects and
+  also passes through the `sg_push/pop_debug_group()` calls. If you use the push/pop
+  debug group calls, please be aware of the following limitations:
+
+  - a push inside a render pass must have an associated pop inside the same render pass
+  - a push outside any render pass must have an associated pop outside any render pass
+  - Metal will ignore any push/pop calls outside render passes (this is because in Metal
+    these are MTLCommandEncoder methods)
+
+  Associated issue: https://github.com/floooh/sokol/issues/889, and PR: https://github.com/floooh/sokol/pull/890.
+
 #### 09-Sep-2023
 
 - a small PR has been merged which fixes a redundant glBindFramebuffer() in the GLES3 backend

+ 1 - 1
README.md

@@ -4,7 +4,7 @@ Simple
 [STB-style](https://github.com/nothings/stb/blob/master/docs/stb_howto.txt)
 cross-platform libraries for C and C++, written in C.
 
-[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**26-Jul-2023** proper image/sampler pair support in sokol_nuklear.h)
+[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**25-Sep-2023** POTENTIALLY BREAKING: allocator callbacks have been renamed)
 
 [![Build](/../../actions/workflows/main.yml/badge.svg)](/../../actions/workflows/main.yml) [![Bindings](/../../actions/workflows/gen_bindings.yml/badge.svg)](/../../actions/workflows/gen_bindings.yml) [![build](https://github.com/floooh/sokol-zig/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-zig/actions/workflows/main.yml) [![build](https://github.com/floooh/sokol-nim/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-nim/actions/workflows/main.yml) [![Odin](https://github.com/floooh/sokol-odin/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-odin/actions/workflows/main.yml)[![Rust](https://github.com/floooh/sokol-rust/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-rust/actions/workflows/main.yml)
 

+ 2 - 0
bindgen/gen_rust.py

@@ -355,6 +355,8 @@ def funcptr_result_c(field_type):
     res_type = field_type[: field_type.index("(*)")].strip()
     if res_type == "void":
         return ""
+    elif is_prim_type(res_type):
+        return f" -> {as_rust_prim_type(res_type)}"
     elif util.is_const_void_ptr(res_type):
         return " -> *const core::ffi::c_void"
     elif util.is_void_ptr(res_type):

+ 2 - 0
bindgen/gen_zig.py

@@ -271,6 +271,8 @@ def funcptr_result_c(field_type):
     res_type = field_type[:field_type.index('(*)')].strip()
     if res_type == 'void':
         return 'void'
+    elif is_prim_type(res_type):
+        return as_zig_prim_type(res_type)
     elif util.is_const_void_ptr(res_type):
         return '?*const anyopaque'
     elif util.is_void_ptr(res_type):

+ 97 - 35
sokol_app.h

@@ -951,9 +951,11 @@
 
     OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY)
     ======================================================
+    NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android.
+
     In its default configuration, sokol_app.h "hijacks" the platform's
     standard main() function. This was done because different platforms
-    have different main functions which are not compatible with
+    have different entry point conventions which are not compatible with
     C's main() (for instance WinMain on Windows has completely different
     arguments). However, this "main hijacking" posed a problem for
     usage scenarios like integrating sokol_app.h with other languages than
@@ -965,12 +967,30 @@
     - instead provide the standard main() function of the platform
     - from the main function, call the function ```sapp_run()``` which
       takes a pointer to an ```sapp_desc``` structure.
-    - ```sapp_run()``` takes over control and calls the provided init-, frame-,
-      shutdown- and event-callbacks just like in the default model, it
-      will only return when the application quits (or not at all on some
-      platforms, like emscripten)
+    - from here on```sapp_run()``` takes over control and calls the provided
+      init-, frame-, event- and cleanup-callbacks just like in the default model.
+
+    sapp_run() behaves differently across platforms:
+
+        - on some platforms, sapp_run() will return when the application quits
+        - on other platforms, sapp_run() will never return, even when the
+          application quits (the operating system is free to simply terminate
+          the application at any time)
+        - on Emscripten specifically, sapp_run() will return immediately while
+          the frame callback keeps being called
+
+    This different behaviour of sapp_run() essentially means that there shouldn't
+    be any code *after* sapp_run(), because that may either never be called, or in
+    case of Emscripten will be called at an unexpected time (at application start).
 
-    NOTE: SOKOL_NO_ENTRY is currently not supported on Android.
+    An application also should not depend on the cleanup-callback being called
+    when cross-platform compatibility is required.
+
+    Since sapp_run() returns immediately on Emscripten you shouldn't activate
+    the 'EXIT_RUNTIME' linker option (this is disabled by default when compiling
+    for the browser target), since the C/C++ exit runtime would be called immediately at
+    application start, causing any global objects to be destroyed and global
+    variables to be zeroed.
 
     WINDOWS CONSOLE OUTPUT
     ======================
@@ -1019,8 +1039,8 @@
             return (sapp_desc){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...,
                 }
             };
@@ -1473,12 +1493,12 @@ typedef struct sapp_icon_desc {
 
     Used in sapp_desc to provide custom memory-alloc and -free functions
     to sokol_app.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fb and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct sapp_allocator {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sapp_allocator;
 
@@ -2857,10 +2877,9 @@ _SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void* _sapp_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_sapp.desc.allocator.alloc) {
-        ptr = _sapp.desc.allocator.alloc(size, _sapp.desc.allocator.user_data);
-    }
-    else {
+    if (_sapp.desc.allocator.alloc_fn) {
+        ptr = _sapp.desc.allocator.alloc_fn(size, _sapp.desc.allocator.user_data);
+    } else {
         ptr = malloc(size);
     }
     if (0 == ptr) {
@@ -2876,8 +2895,8 @@ _SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) {
 }
 
 _SOKOL_PRIVATE void _sapp_free(void* ptr) {
-    if (_sapp.desc.allocator.free) {
-        _sapp.desc.allocator.free(ptr, _sapp.desc.allocator.user_data);
+    if (_sapp.desc.allocator.free_fn) {
+        _sapp.desc.allocator.free_fn(ptr, _sapp.desc.allocator.user_data);
     }
     else {
         free(ptr);
@@ -2981,7 +3000,7 @@ _SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) {
 }
 
 _SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     sapp_desc res = *desc;
     res.sample_count = _sapp_def(res.sample_count, 1);
     res.swap_interval = _sapp_def(res.swap_interval, 1);
@@ -6626,11 +6645,54 @@ _SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) {
     return value;
 }
 
+_SOKOL_PRIVATE void _sapp_wgl_attribiv(int pixel_format, int num_attribs, const int* attribs, int* results) {
+    SOKOL_ASSERT(_sapp.wgl.arb_pixel_format);
+    if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, num_attribs, attribs, results)) {
+        _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED);
+    }
+}
+
 _SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) {
     SOKOL_ASSERT(_sapp.win32.dc);
     SOKOL_ASSERT(_sapp.wgl.arb_pixel_format);
     const _sapp_gl_fbconfig* closest;
 
+    #define _sapp_wgl_num_query_tags (12)
+    const int query_tags[_sapp_wgl_num_query_tags] = {
+      WGL_SUPPORT_OPENGL_ARB,
+      WGL_DRAW_TO_WINDOW_ARB,
+      WGL_PIXEL_TYPE_ARB,
+      WGL_ACCELERATION_ARB,
+      WGL_DOUBLE_BUFFER_ARB,
+      WGL_RED_BITS_ARB,
+      WGL_GREEN_BITS_ARB,
+      WGL_BLUE_BITS_ARB,
+      WGL_ALPHA_BITS_ARB,
+      WGL_DEPTH_BITS_ARB,
+      WGL_STENCIL_BITS_ARB,
+      WGL_SAMPLES_ARB,
+    };
+    const int result_support_opengl_index = 0;
+    const int result_draw_to_window_index = 1;
+    const int result_pixel_type_index = 2;
+    const int result_acceleration_index = 3;
+    const int result_double_buffer_index = 4;
+    const int result_red_bits_index = 5;
+    const int result_green_bits_index = 6;
+    const int result_blue_bits_index = 7;
+    const int result_alpha_bits_index = 8;
+    const int result_depth_bits_index = 9;
+    const int result_stencil_bits_index = 10;
+    const int result_samples_index = 11;
+
+    int query_results[_sapp_wgl_num_query_tags] = {0};
+    // Drop the last item if multisample extension is not supported.
+    //  If in future querying with multiple extensions, will have to shuffle index values to have active extensions on the end.
+    int query_count = _sapp_wgl_num_query_tags;
+    if (!_sapp.wgl.arb_multisample) {
+        query_count = _sapp_wgl_num_query_tags - 1;
+    }
+
     int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB);
     _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig));
     SOKOL_ASSERT(usable_configs);
@@ -6639,27 +6701,27 @@ _SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) {
         const int n = i + 1;
         _sapp_gl_fbconfig* u = usable_configs + usable_count;
         _sapp_gl_init_fbconfig(u);
-        if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) {
-            continue;
-        }
-        if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) {
-            continue;
-        }
-        if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) {
+        _sapp_wgl_attribiv(n, query_count, query_tags, query_results);
+
+        if (query_results[result_support_opengl_index] == 0
+          || query_results[result_draw_to_window_index] == 0
+          || query_results[result_pixel_type_index] != WGL_TYPE_RGBA_ARB
+          || query_results[result_acceleration_index] == WGL_NO_ACCELERATION_ARB)
+        {
             continue;
         }
-        u->red_bits     = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB);
-        u->green_bits   = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB);
-        u->blue_bits    = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB);
-        u->alpha_bits   = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB);
-        u->depth_bits   = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB);
-        u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB);
-        if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) {
+        u->red_bits     = query_results[result_red_bits_index];
+        u->green_bits   = query_results[result_green_bits_index];
+        u->blue_bits    = query_results[result_blue_bits_index];
+        u->alpha_bits   = query_results[result_alpha_bits_index];
+        u->depth_bits   = query_results[result_depth_bits_index];
+        u->stencil_bits = query_results[result_stencil_bits_index];
+        if (query_results[result_double_buffer_index]) {
             u->doublebuffer = true;
         }
-        if (_sapp.wgl.arb_multisample) {
-            u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB);
-        }
+
+        u->samples = query_results[result_samples_index]; // NOTE: If arb_multisample is not supported  - just takes the default 0
+
         u->handle = (uintptr_t)n;
         usable_count++;
     }

+ 75 - 53
sokol_args.h

@@ -35,11 +35,22 @@
 
     When running as WebAssembly app, arguments are taken from the page URL:
 
-    https://floooh.github.io/tiny8bit/kc85.html?type=kc85_3&mod=m022&snapshot=kc85/jungle.kcc
+        https://floooh.github.io/tiny8bit/kc85.html?type=kc85_3&mod=m022&snapshot=kc85/jungle.kcc
 
     The same arguments provided to a command line app:
 
-    kc85 type=kc85_3 mod=m022 snapshot=kc85/jungle.kcc
+        kc85 type=kc85_3 mod=m022 snapshot=kc85/jungle.kcc
+
+    You can also use standalone keys without value:
+
+        https://floooh.github.io/tiny8bit/kc85.html?bla&blub
+
+    On the command line:
+
+        kc85 bla blub
+
+    Such value-less keys are reported as the value being an empty string, but they
+    can be tested with `sapp_exists("bla")` or `sapp_boolean("blub")`.
 
     ARGUMENT FORMATTING
     ===================
@@ -57,6 +68,12 @@
 
         key=value
 
+    or
+
+        key
+
+    When a key has no value, the value will be assigned an empty string.
+
     Key/value pairs are separated by 'whitespace', valid whitespace
     characters are space and tab.
 
@@ -71,9 +88,6 @@
 
     The 'key' string must be a simple string without escape sequences or whitespace.
 
-    Currently 'single keys' without values are not allowed, but may be
-    in the future.
-
     The 'value' string can be quoted, and quoted value strings can contain
     whitespace:
 
@@ -123,7 +137,7 @@
                 ...
             }
 
-            // check if a key's value is "true", "yes" or "on"
+            // check if a key's value is "true", "yes" or "on" or if this is a standalone key
             if (sargs_boolean("joystick_enabled")) {
                 ...
             }
@@ -183,23 +197,23 @@
         Return true between sargs_setup() and sargs_shutdown()
 
     bool sargs_exists(const char* key)
-        Test if a key arg exists.
+        Test if an argument exists by its key name.
 
     const char* sargs_value(const char* key)
-        Return value associated with key. Returns an empty
-        string ("") if the key doesn't exist.
+        Return value associated with key. Returns an empty string ("") if the
+        key doesn't exist, or if the key doesn't have a value.
 
     const char* sargs_value_def(const char* key, const char* default)
-        Return value associated with key, or the provided default
-        value if the value doesn't exist.
+        Return value associated with key, or the provided default value if the
+        key doesn't exist, or this is a value-less key.
 
     bool sargs_equals(const char* key, const char* val);
         Return true if the value associated with key matches
         the 'val' argument.
 
     bool sargs_boolean(const char* key)
-        Return true if the value string of 'key' is one
-        of 'true', 'yes', 'on'.
+        Return true if the value string of 'key' is one of 'true', 'yes', 'on',
+        or this is a key without value.
 
     int sargs_find(const char* key)
         Find argument by key name and return its index, or -1 if not found.
@@ -213,7 +227,7 @@
 
     const char* sargs_value_at(int index)
         Return the value of argument at index. Returns empty string
-        if index is outside range.
+        if the key at index has no value, or the index is out-of-range.
 
 
     MEMORY ALLOCATION OVERRIDE
@@ -233,8 +247,8 @@
             sargs_setup(&(sargs_desc){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...,
                 }
             });
@@ -302,12 +316,12 @@ extern "C" {
 
     Used in sargs_desc to provide custom memory-alloc and -free functions
     to sokol_args.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct sargs_allocator {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sargs_allocator;
 
@@ -327,13 +341,13 @@ SOKOL_ARGS_API_DECL void sargs_shutdown(void);
 SOKOL_ARGS_API_DECL bool sargs_isvalid(void);
 /* test if an argument exists by key name */
 SOKOL_ARGS_API_DECL bool sargs_exists(const char* key);
-/* get value by key name, return empty string if key doesn't exist */
+/* get value by key name, return empty string if key doesn't exist or an existing key has no value */
 SOKOL_ARGS_API_DECL const char* sargs_value(const char* key);
-/* get value by key name, return provided default if key doesn't exist */
+/* get value by key name, return provided default if key doesn't exist or has no value */
 SOKOL_ARGS_API_DECL const char* sargs_value_def(const char* key, const char* def);
 /* return true if val arg matches the value associated with key */
 SOKOL_ARGS_API_DECL bool sargs_equals(const char* key, const char* val);
-/* return true if key's value is "true", "yes" or "on" */
+/* return true if key's value is "true", "yes", "on" or an existing key has no value */
 SOKOL_ARGS_API_DECL bool sargs_boolean(const char* key);
 /* get index of arg by key name, return -1 if not exists */
 SOKOL_ARGS_API_DECL int sargs_find(const char* key);
@@ -433,10 +447,9 @@ _SOKOL_PRIVATE void _sargs_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void* _sargs_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_sargs.allocator.alloc) {
-        ptr = _sargs.allocator.alloc(size, _sargs.allocator.user_data);
-    }
-    else {
+    if (_sargs.allocator.alloc_fn) {
+        ptr = _sargs.allocator.alloc_fn(size, _sargs.allocator.user_data);
+    } else {
         ptr = malloc(size);
     }
     SOKOL_ASSERT(ptr);
@@ -450,10 +463,9 @@ _SOKOL_PRIVATE void* _sargs_malloc_clear(size_t size) {
 }
 
 _SOKOL_PRIVATE void _sargs_free(void* ptr) {
-    if (_sargs.allocator.free) {
-        _sargs.allocator.free(ptr, _sargs.allocator.user_data);
-    }
-    else {
+    if (_sargs.allocator.free_fn) {
+        _sargs.allocator.free_fn(ptr, _sargs.allocator.user_data);
+    } else {
         free(ptr);
     }
 }
@@ -486,8 +498,8 @@ _SOKOL_PRIVATE bool _sargs_val_expected(void) {
     return 0 != (_sargs.parse_state & _SARGS_EXPECT_VAL);
 }
 
-_SOKOL_PRIVATE void _sargs_expect_sep(void) {
-    _sargs.parse_state = _SARGS_EXPECT_SEP;
+_SOKOL_PRIVATE void _sargs_expect_sep_or_key(void) {
+    _sargs.parse_state = _SARGS_EXPECT_SEP | _SARGS_EXPECT_KEY;
 }
 
 _SOKOL_PRIVATE bool _sargs_any_expected(void) {
@@ -524,14 +536,17 @@ _SOKOL_PRIVATE bool _sargs_is_whitespace(char c) {
 }
 
 _SOKOL_PRIVATE void _sargs_start_key(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
+    SOKOL_ASSERT((_sargs.num_args >= 0) && (_sargs.num_args < _sargs.max_args));
     _sargs.parse_state = _SARGS_PARSING_KEY;
     _sargs.args[_sargs.num_args].key = _sargs.buf_pos;
 }
 
 _SOKOL_PRIVATE void _sargs_end_key(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
+    SOKOL_ASSERT((_sargs.num_args >= 0) && (_sargs.num_args < _sargs.max_args));
     _sargs_putc(0);
+    // declare val as empty string in case this is a key-only arg
+    _sargs.args[_sargs.num_args].val = _sargs.buf_pos - 1;
+    _sargs.num_args++;
     _sargs.parse_state = 0;
 }
 
@@ -540,15 +555,13 @@ _SOKOL_PRIVATE bool _sargs_parsing_key(void) {
 }
 
 _SOKOL_PRIVATE void _sargs_start_val(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
+    SOKOL_ASSERT((_sargs.num_args > 0) && (_sargs.num_args <= _sargs.max_args));
     _sargs.parse_state = _SARGS_PARSING_VAL;
-    _sargs.args[_sargs.num_args].val = _sargs.buf_pos;
+    _sargs.args[_sargs.num_args - 1].val = _sargs.buf_pos;
 }
 
 _SOKOL_PRIVATE void _sargs_end_val(void) {
-    SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
     _sargs_putc(0);
-    _sargs.num_args++;
     _sargs.parse_state = 0;
 }
 
@@ -596,7 +609,12 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
         if (_sargs_any_expected()) {
             if (!_sargs_is_whitespace(c)) {
                 /* start of key, value or separator */
-                if (_sargs_key_expected()) {
+                if (_sargs_is_separator(c)) {
+                    /* skip separator and expect value */
+                    _sargs_expect_val();
+                    continue;
+                }
+                else if (_sargs_key_expected()) {
                     /* start of new key */
                     _sargs_start_key();
                 }
@@ -608,13 +626,6 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
                     }
                     _sargs_start_val();
                 }
-                else {
-                    /* separator */
-                    if (_sargs_is_separator(c)) {
-                        _sargs_expect_val();
-                        continue;
-                    }
-                }
             }
             else {
                 /* skip white space */
@@ -629,7 +640,7 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
                     _sargs_expect_val();
                 }
                 else {
-                    _sargs_expect_sep();
+                    _sargs_expect_sep_or_key();
                 }
                 continue;
             }
@@ -657,7 +668,7 @@ _SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
     }
     if (_sargs_parsing_key()) {
         _sargs_end_key();
-        _sargs_expect_sep();
+        _sargs_expect_sep_or_key();
     }
     else if (_sargs_parsing_val() && !_sargs_in_quotes()) {
         _sargs_end_val();
@@ -823,7 +834,13 @@ SOKOL_API_IMPL const char* sargs_value_def(const char* key, const char* def) {
     SOKOL_ASSERT(_sargs.valid && key && def);
     int arg_index = sargs_find(key);
     if (-1 != arg_index) {
-        return sargs_value_at(arg_index);
+        const char* res = sargs_value_at(arg_index);
+        SOKOL_ASSERT(res);
+        if (res[0] == 0) {
+            return def;
+        } else {
+            return res;
+        }
     }
     else {
         return def;
@@ -836,10 +853,15 @@ SOKOL_API_IMPL bool sargs_equals(const char* key, const char* val) {
 }
 
 SOKOL_API_IMPL bool sargs_boolean(const char* key) {
-    const char* val = sargs_value(key);
-    return (0 == strcmp("true", val)) ||
-           (0 == strcmp("yes", val)) ||
-           (0 == strcmp("on", val));
+    if (sargs_exists(key)) {
+        const char* val = sargs_value(key);
+        return (0 == strcmp("true", val)) ||
+               (0 == strcmp("yes", val)) ||
+               (0 == strcmp("on", val)) ||
+               (0 == strcmp("", val));
+    } else {
+        return false;
+    }
 }
 
 #endif /* SOKOL_ARGS_IMPL */

+ 12 - 14
sokol_audio.h

@@ -397,8 +397,8 @@
             saudio_setup(&(saudio_desc){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...,
                 }
             });
@@ -575,12 +575,12 @@ typedef struct saudio_logger {
 
     Used in saudio_desc to provide custom memory-alloc and -free functions
     to sokol_audio.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct saudio_allocator {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } saudio_allocator;
 
@@ -1146,10 +1146,9 @@ _SOKOL_PRIVATE void _saudio_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void* _saudio_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_saudio.desc.allocator.alloc) {
-        ptr = _saudio.desc.allocator.alloc(size, _saudio.desc.allocator.user_data);
-    }
-    else {
+    if (_saudio.desc.allocator.alloc_fn) {
+        ptr = _saudio.desc.allocator.alloc_fn(size, _saudio.desc.allocator.user_data);
+    } else {
         ptr = malloc(size);
     }
     if (0 == ptr) {
@@ -1165,10 +1164,9 @@ _SOKOL_PRIVATE void* _saudio_malloc_clear(size_t size) {
 }
 
 _SOKOL_PRIVATE void _saudio_free(void* ptr) {
-    if (_saudio.desc.allocator.free) {
-        _saudio.desc.allocator.free(ptr, _saudio.desc.allocator.user_data);
-    }
-    else {
+    if (_saudio.desc.allocator.free_fn) {
+        _saudio.desc.allocator.free_fn(ptr, _saudio.desc.allocator.user_data);
+    } else {
         free(ptr);
     }
 }
@@ -2485,7 +2483,7 @@ void _saudio_backend_shutdown(void) {
 SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
     SOKOL_ASSERT(!_saudio.valid);
     SOKOL_ASSERT(desc);
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     _saudio_clear(&_saudio, sizeof(_saudio));
     _saudio.desc = *desc;
     _saudio.stream_cb = desc->stream_cb;

+ 28 - 18
sokol_fetch.h

@@ -820,8 +820,8 @@
             sfetch_setup(&(sfetch_desc_t){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...,
                 }
             });
@@ -1024,8 +1024,8 @@ typedef struct sfetch_range_t {
     override one function but not the other).
 */
 typedef struct sfetch_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sfetch_allocator_t;
 
@@ -1424,10 +1424,9 @@ _SOKOL_PRIVATE void _sfetch_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void* _sfetch_malloc_with_allocator(const sfetch_allocator_t* allocator, size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (allocator->alloc) {
-        ptr = allocator->alloc(size, allocator->user_data);
-    }
-    else {
+    if (allocator->alloc_fn) {
+        ptr = allocator->alloc_fn(size, allocator->user_data);
+    } else {
         ptr = malloc(size);
     }
     if (0 == ptr) {
@@ -1447,10 +1446,9 @@ _SOKOL_PRIVATE void* _sfetch_malloc_clear(size_t size) {
 }
 
 _SOKOL_PRIVATE void _sfetch_free(void* ptr) {
-    if (_sfetch->desc.allocator.free) {
-        _sfetch->desc.allocator.free(ptr, _sfetch->desc.allocator.user_data);
-    }
-    else {
+    if (_sfetch->desc.allocator.free_fn) {
+        _sfetch->desc.allocator.free_fn(ptr, _sfetch->desc.allocator.user_data);
+    } else {
         free(ptr);
     }
 }
@@ -2457,6 +2455,12 @@ _SOKOL_PRIVATE void _sfetch_invoke_response_callback(_sfetch_item_t* item) {
     item->callback(&response);
 }
 
+_SOKOL_PRIVATE void _sfetch_cancel_item(_sfetch_item_t* item) {
+    item->state = _SFETCH_STATE_FAILED;
+    item->user.finished = true;
+    item->user.error_code = SFETCH_ERROR_CANCELLED;
+}
+
 /* per-frame channel stuff: move requests in and out of the IO threads, call response callbacks */
 _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_t* pool) {
 
@@ -2469,9 +2473,16 @@ _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_
         _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id);
         SOKOL_ASSERT(item);
         SOKOL_ASSERT(item->state == _SFETCH_STATE_ALLOCATED);
+        // if the item was cancelled early, kick it out immediately
+        if (item->user.cancel) {
+            _sfetch_cancel_item(item);
+            _sfetch_invoke_response_callback(item);
+            _sfetch_pool_item_free(pool, slot_id);
+            continue;
+        }
         item->state = _SFETCH_STATE_DISPATCHED;
         item->lane = _sfetch_ring_dequeue(&chn->free_lanes);
-        /* if no buffer provided yet, invoke response callback to do so */
+        // if no buffer provided yet, invoke response callback to do so
         if (0 == item->buffer.ptr) {
             _sfetch_invoke_response_callback(item);
         }
@@ -2498,8 +2509,7 @@ _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_
             item->user.cont = false;
         }
         if (item->user.cancel) {
-            item->state = _SFETCH_STATE_FAILED;
-            item->user.finished = true;
+            _sfetch_cancel_item(item);
         }
         switch (item->state) {
             case _SFETCH_STATE_DISPATCHED:
@@ -2541,7 +2551,7 @@ _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_
         item->user.fetched_offset = item->thread.fetched_offset;
         item->user.fetched_size = item->thread.fetched_size;
         if (item->user.cancel) {
-            item->user.error_code = SFETCH_ERROR_CANCELLED;
+            _sfetch_cancel_item(item);
         }
         else {
             item->user.error_code = item->thread.error_code;
@@ -2558,7 +2568,7 @@ _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_
         }
         _sfetch_invoke_response_callback(item);
 
-        /* when the request is finish, free the lane for another request,
+        /* when the request is finished, free the lane for another request,
            otherwise feed it back into the incoming queue
         */
         if (item->user.finished) {
@@ -2608,7 +2618,7 @@ _SOKOL_PRIVATE bool _sfetch_validate_request(_sfetch_t* ctx, const sfetch_reques
 }
 
 _SOKOL_PRIVATE sfetch_desc_t _sfetch_desc_defaults(const sfetch_desc_t* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     sfetch_desc_t res = *desc;
     res.max_requests = _sfetch_def(desc->max_requests, 128);
     res.num_channels = _sfetch_def(desc->num_channels, 1);

+ 142 - 29
sokol_gfx.h

@@ -946,8 +946,8 @@
             sg_setup(&(sg_desc){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...,
                 }
             });
@@ -3059,7 +3059,10 @@ typedef struct sg_frame_stats {
     _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED, "Map() failed when updating buffer (d3d11)") \
     _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_APPEND_BUFFER_FAILED, "Map() failed when appending to buffer (d3d11)") \
     _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED, "Map() failed when updating image (d3d11)") \
+    _SG_LOGITEM_XMACRO(METAL_CREATE_BUFFER_FAILED, "failed to create buffer object (metal)") \
     _SG_LOGITEM_XMACRO(METAL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (metal)") \
+    _SG_LOGITEM_XMACRO(METAL_CREATE_TEXTURE_FAILED, "failed to create texture object (metal)") \
+    _SG_LOGITEM_XMACRO(METAL_CREATE_SAMPLER_FAILED, "failed to create sampler object (metal)") \
     _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_FAILED, "shader compilation failed (metal)") \
     _SG_LOGITEM_XMACRO(METAL_SHADER_CREATION_FAILED, "shader creation failed (metal)") \
     _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_OUTPUT, "") \
@@ -3067,6 +3070,7 @@ typedef struct sg_frame_stats {
     _SG_LOGITEM_XMACRO(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND, "fragment shader entry not found (metal)") \
     _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_FAILED, "failed to create render pipeline state (metal)") \
     _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_OUTPUT, "") \
+    _SG_LOGITEM_XMACRO(METAL_CREATE_DSS_FAILED, "failed to create depth stencil state (metal)") \
     _SG_LOGITEM_XMACRO(WGPU_BINDGROUPS_POOL_EXHAUSTED, "bindgroups pool exhausted (increase sg_desc.bindgroups_cache_size) (wgpu)") \
     _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE, "sg_desc.wgpu_bindgroups_cache_size must be > 1 (wgpu)") \
     _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_POW2, "sg_desc.wgpu_bindgroups_cache_size must be a power of 2 (wgpu)") \
@@ -3266,7 +3270,7 @@ typedef struct sg_frame_stats {
     _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_SMP_MIPMAPS, "sg_apply_bindings: image bound to fragment stage has mipmap_count == 1, but associated sampler mipmap filer is not SG_MIPMAPFILTER_NONE") \
     _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \
     _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_UB_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \
-    _SG_LOGITEM_XMACRO(VALIDATE_AUB_SIZE, "sg_apply_uniforms: data size exceeds declared uniform block size") \
+    _SG_LOGITEM_XMACRO(VALIDATE_AUB_SIZE, "sg_apply_uniforms: data size doesn't match declared uniform block size") \
     _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_USAGE, "sg_update_buffer: cannot update immutable buffer") \
     _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_SIZE, "sg_update_buffer: update size is bigger than buffer size") \
     _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_ONCE, "sg_update_buffer: only one update allowed per buffer and frame") \
@@ -3310,8 +3314,8 @@ typedef enum sg_log_item {
     .max_commit_listeners   1024
     .disable_validation     false
 
-    .allocator.alloc        0 (in this case, malloc() will be called)
-    .allocator.free         0 (in this case, free() will be called)
+    .allocator.alloc_fn     0 (in this case, malloc() will be called)
+    .allocator.free_fn      0 (in this case, free() will be called)
     .allocator.user_data    0
 
     .context.color_format: default value depends on selected backend:
@@ -3424,6 +3428,12 @@ typedef struct sg_wgpu_context_desc {
     void* user_data;
 } sg_wgpu_context_desc;
 
+typedef struct sg_gl_context_desc {
+    uint32_t (*default_framebuffer_cb)(void);
+    uint32_t (*default_framebuffer_userdata_cb)(void*);
+    void* user_data;
+} sg_gl_context_desc;
+
 typedef struct sg_context_desc {
     sg_pixel_format color_format;
     sg_pixel_format depth_format;
@@ -3431,6 +3441,7 @@ typedef struct sg_context_desc {
     sg_metal_context_desc metal;
     sg_d3d11_context_desc d3d11;
     sg_wgpu_context_desc wgpu;
+    sg_gl_context_desc gl;
 } sg_context_desc;
 
 /*
@@ -3452,12 +3463,12 @@ typedef struct sg_commit_listener {
 
     Used in sg_desc to provide custom memory-alloc and -free functions
     to sokol_gfx.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct sg_allocator {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sg_allocator;
 
@@ -4978,8 +4989,7 @@ typedef struct {
 
 typedef struct {
     bool valid;
-    bool has_unified_memory;
-    bool force_managed_storage_mode;
+    bool use_shared_storage_mode;
     const void*(*renderpass_descriptor_cb)(void);
     const void*(*renderpass_descriptor_userdata_cb)(void*);
     const void*(*drawable_cb)(void);
@@ -5317,8 +5327,8 @@ _SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void* _sg_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_sg.desc.allocator.alloc) {
-        ptr = _sg.desc.allocator.alloc(size, _sg.desc.allocator.user_data);
+    if (_sg.desc.allocator.alloc_fn) {
+        ptr = _sg.desc.allocator.alloc_fn(size, _sg.desc.allocator.user_data);
     } else {
         ptr = malloc(size);
     }
@@ -5335,8 +5345,8 @@ _SOKOL_PRIVATE void* _sg_malloc_clear(size_t size) {
 }
 
 _SOKOL_PRIVATE void _sg_free(void* ptr) {
-    if (_sg.desc.allocator.free) {
-        _sg.desc.allocator.free(ptr, _sg.desc.allocator.user_data);
+    if (_sg.desc.allocator.free_fn) {
+        _sg.desc.allocator.free_fn(ptr, _sg.desc.allocator.user_data);
     } else {
         free(ptr);
     }
@@ -7308,6 +7318,8 @@ _SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) {
 
 _SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) {
     _SOKOL_UNUSED(desc);
+    SOKOL_ASSERT(desc->context.gl.default_framebuffer_cb == 0 || desc->context.gl.default_framebuffer_userdata_cb == 0);
+
     // assumes that _sg.gl is already zero-initialized
     _sg.gl.valid = true;
 
@@ -7798,11 +7810,13 @@ _SOKOL_PRIVATE void _sg_gl_fb_attach_texture(const _sg_gl_attachment_t* gl_att,
     SOKOL_ASSERT(img);
     const GLuint gl_tex = img->gl.tex[0];
     SOKOL_ASSERT(gl_tex);
+    const GLuint gl_target = img->gl.target;
+    SOKOL_ASSERT(gl_target);
     const int mip_level = cmn_att->mip_level;
     const int slice = cmn_att->slice;
     switch (img->cmn.type) {
         case SG_IMAGETYPE_2D:
-            glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, GL_TEXTURE_2D, gl_tex, mip_level);
+            glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, gl_target, gl_tex, mip_level);
             break;
         case SG_IMAGETYPE_CUBE:
             glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, _sg_gl_cubeface_target(slice), gl_tex, mip_level);
@@ -7994,6 +8008,12 @@ _SOKOL_PRIVATE void _sg_gl_begin_pass(_sg_pass_t* pass, const sg_pass_action* ac
         #if defined(SOKOL_GLCORE33)
         glDisable(GL_FRAMEBUFFER_SRGB);
         #endif
+        if (_sg.desc.context.gl.default_framebuffer_userdata_cb) {
+            _sg.gl.cur_context->default_framebuffer = _sg.desc.context.gl.default_framebuffer_userdata_cb(_sg.desc.context.gl.user_data);
+        } else if (_sg.desc.context.gl.default_framebuffer_cb) {
+            _sg.gl.cur_context->default_framebuffer = _sg.desc.context.gl.default_framebuffer_cb();
+        }
+
         glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.cur_context->default_framebuffer);
     }
     glViewport(0, 0, w, h);
@@ -10576,12 +10596,13 @@ _SOKOL_PRIVATE MTLStoreAction _sg_mtl_store_action(sg_store_action a, bool resol
 
 _SOKOL_PRIVATE MTLResourceOptions _sg_mtl_resource_options_storage_mode_managed_or_shared(void) {
     #if defined(_SG_TARGET_MACOS)
-    if (_sg.mtl.force_managed_storage_mode || !_sg.mtl.has_unified_memory) {
-        return MTLResourceStorageModeManaged;
-    } else {
+    if (_sg.mtl.use_shared_storage_mode) {
         return MTLResourceStorageModeShared;
+    } else {
+        return MTLResourceStorageModeManaged;
     }
     #else
+        // MTLResourceStorageModeManaged is not even defined on iOS SDK
         return MTLResourceStorageModeShared;
     #endif
 }
@@ -11046,16 +11067,20 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) {
     _sg.features.mrt_independent_write_mask = true;
 
     _sg.features.image_clamp_to_border = false;
+    #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)
     if (@available(macOS 12.0, iOS 14.0, *)) {
         _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple7]
                                              || [_sg.mtl.device supportsFamily:MTLGPUFamilyApple8]
                                              || [_sg.mtl.device supportsFamily:MTLGPUFamilyMac2];
+        #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 130000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 160000)
         if (!_sg.features.image_clamp_to_border) {
             if (@available(macOS 13.0, iOS 16.0, *)) {
                 _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyMetal3];
             }
         }
+        #endif
     }
+    #endif
 
     #if defined(_SG_TARGET_MACOS)
         _sg.limits.max_image_size_2d = 16 * 1024;
@@ -11195,22 +11220,35 @@ _SOKOL_PRIVATE void _sg_mtl_setup_backend(const sg_desc* desc) {
     _sg.mtl.sem = dispatch_semaphore_create(SG_NUM_INFLIGHT_FRAMES);
     _sg.mtl.device = (__bridge id<MTLDevice>) desc->context.metal.device;
     _sg.mtl.cmd_queue = [_sg.mtl.device newCommandQueue];
+
     for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
         _sg.mtl.uniform_buffers[i] = [_sg.mtl.device
             newBufferWithLength:(NSUInteger)_sg.mtl.ub_size
             options:MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeShared
         ];
+        #if defined(SOKOL_DEBUG)
+            _sg.mtl.uniform_buffers[i].label = [NSString stringWithFormat:@"sg-uniform-buffer.%d", i];
+        #endif
     }
-    if (@available(macOS 10.15, iOS 13.0, *)) {
-        _sg.mtl.has_unified_memory = _sg.mtl.device.hasUnifiedMemory;
+
+    if (desc->mtl_force_managed_storage_mode) {
+        _sg.mtl.use_shared_storage_mode = false;
+    } else if (@available(macOS 10.15, iOS 13.0, *)) {
+        // on Intel Macs, always use managed resources even though the
+        // device says it supports unified memory (because of texture restrictions)
+        const bool is_apple_gpu = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple1];
+        if (!is_apple_gpu) {
+            _sg.mtl.use_shared_storage_mode = false;
+        } else {
+            _sg.mtl.use_shared_storage_mode = true;
+        }
     } else {
         #if defined(_SG_TARGET_MACOS)
-            _sg.mtl.has_unified_memory = false;
+            _sg.mtl.use_shared_storage_mode = false;
         #else
-            _sg.mtl.has_unified_memory = true;
+            _sg.mtl.use_shared_storage_mode = true;
         #endif
     }
-    _sg.mtl.force_managed_storage_mode = desc->mtl_force_managed_storage_mode;
     _sg_mtl_init_caps();
 }
 
@@ -11294,7 +11332,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const
             } else {
                 mtl_buf = [_sg.mtl.device newBufferWithLength:(NSUInteger)buf->cmn.size options:mtl_options];
             }
+            if (nil == mtl_buf) {
+                _SG_ERROR(METAL_CREATE_BUFFER_FAILED);
+                return SG_RESOURCESTATE_FAILED;
+            }
         }
+        #if defined(SOKOL_DEBUG)
+            if (desc->label) {
+                mtl_buf.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot];
+            }
+        #endif
         buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf);
         _SG_OBJC_RELEASE(mtl_buf);
     }
@@ -11434,10 +11481,20 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg
             mtl_tex = (__bridge id<MTLTexture>) desc->mtl_textures[slot];
         } else {
             mtl_tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc];
+            if (nil == mtl_tex) {
+                _SG_OBJC_RELEASE(mtl_desc);
+                _SG_ERROR(METAL_CREATE_TEXTURE_FAILED);
+                return SG_RESOURCESTATE_FAILED;
+            }
             if ((img->cmn.usage == SG_USAGE_IMMUTABLE) && !img->cmn.render_target) {
                 _sg_mtl_copy_image_data(img, mtl_tex, &desc->data);
             }
         }
+        #if defined(SOKOL_DEBUG)
+            if (desc->label) {
+                mtl_tex.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot];
+            }
+        #endif
         img->mtl.tex[slot] = _sg_mtl_add_resource(mtl_tex);
         _SG_OBJC_RELEASE(mtl_tex);
     }
@@ -11479,8 +11536,17 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_sampler(_sg_sampler_t* smp, cons
         mtl_desc.maxAnisotropy = desc->max_anisotropy;
         mtl_desc.normalizedCoordinates = YES;
         mtl_desc.compareFunction = _sg_mtl_compare_func(desc->compare);
+        #if defined(SOKOL_DEBUG)
+            if (desc->label) {
+                mtl_desc.label = [NSString stringWithUTF8String:desc->label];
+            }
+        #endif
         mtl_smp = [_sg.mtl.device newSamplerStateWithDescriptor:mtl_desc];
         _SG_OBJC_RELEASE(mtl_desc);
+        if (nil == mtl_smp) {
+            _SG_ERROR(METAL_CREATE_SAMPLER_FAILED);
+            return SG_RESOURCESTATE_FAILED;
+        }
     }
     smp->mtl.sampler_state = _sg_mtl_add_resource(mtl_smp);
     _SG_OBJC_RELEASE(mtl_smp);
@@ -11558,6 +11624,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const
         _SG_ERROR(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND);
         goto failed;
     }
+    #if defined(SOKOL_DEBUG)
+        if (desc->label) {
+            vs_lib.label = [NSString stringWithFormat:@"%s.vs", desc->label];
+            fs_lib.label = [NSString stringWithFormat:@"%s.fs", desc->label];
+        }
+    #endif
     // it is legal to call _sg_mtl_add_resource with a nil value, this will return a special 0xFFFFFFFF index
     shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib  = _sg_mtl_add_resource(vs_lib);
     _SG_OBJC_RELEASE(vs_lib);
@@ -11673,6 +11745,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s
         rp_desc.colorAttachments[i].sourceAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_alpha);
         rp_desc.colorAttachments[i].sourceRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_rgb);
     }
+    #if defined(SOKOL_DEBUG)
+        if (desc->label) {
+            rp_desc.label = [NSString stringWithFormat:@"%s", desc->label];
+        }
+    #endif
     NSError* err = NULL;
     id<MTLRenderPipelineState> mtl_rps = [_sg.mtl.device newRenderPipelineStateWithDescriptor:rp_desc error:&err];
     _SG_OBJC_RELEASE(rp_desc);
@@ -11682,6 +11759,8 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s
         _SG_LOGMSG(METAL_CREATE_RPS_OUTPUT, [err.localizedDescription UTF8String]);
         return SG_RESOURCESTATE_FAILED;
     }
+    pip->mtl.rps = _sg_mtl_add_resource(mtl_rps);
+    _SG_OBJC_RELEASE(mtl_rps);
 
     // depth-stencil-state
     MTLDepthStencilDescriptor* ds_desc = [[MTLDepthStencilDescriptor alloc] init];
@@ -11705,11 +11784,17 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s
         ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask;
         ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask;
     }
-    // FIXME: can this actually fail?
+    #if defined(SOKOL_DEBUG)
+        if (desc->label) {
+            ds_desc.label = [NSString stringWithFormat:@"%s.dss", desc->label];
+        }
+    #endif
     id<MTLDepthStencilState> mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc];
     _SG_OBJC_RELEASE(ds_desc);
-    pip->mtl.rps = _sg_mtl_add_resource(mtl_rps);
-    _SG_OBJC_RELEASE(mtl_rps);
+    if (nil == mtl_dss) {
+        _SG_ERROR(METAL_CREATE_DSS_FAILED);
+        return SG_RESOURCESTATE_FAILED;
+    }
     pip->mtl.dss = _sg_mtl_add_resource(mtl_dss);
     _SG_OBJC_RELEASE(mtl_dss);
     return SG_RESOURCESTATE_VALID;
@@ -12249,6 +12334,19 @@ _SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data*
     _sg_mtl_copy_image_data(img, mtl_tex, data);
 }
 
+_SOKOL_PRIVATE void _sg_mtl_push_debug_group(const char* name) {
+    SOKOL_ASSERT(name);
+    if (_sg.mtl.cmd_encoder) {
+        [_sg.mtl.cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]];
+    }
+}
+
+_SOKOL_PRIVATE void _sg_mtl_pop_debug_group(void) {
+    if (_sg.mtl.cmd_encoder) {
+        [_sg.mtl.cmd_encoder popDebugGroup];
+    }
+}
+
 // ██     ██ ███████ ██████   ██████  ██████  ██    ██     ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
 // ██     ██ ██      ██   ██ ██       ██   ██ ██    ██     ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
 // ██  █  ██ █████   ██████  ██   ███ ██████  ██    ██     ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
@@ -14592,6 +14690,20 @@ static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data)
     #endif
 }
 
+static inline void _sg_push_debug_group(const char* name) {
+    #if defined(SOKOL_METAL)
+    _sg_mtl_push_debug_group(name);
+    #else
+    _SOKOL_UNUSED(name);
+    #endif
+}
+
+static inline void _sg_pop_debug_group(void) {
+    #if defined(SOKOL_METAL)
+    _sg_mtl_pop_debug_group();
+    #endif
+}
+
 // ██████   ██████   ██████  ██
 // ██   ██ ██    ██ ██    ██ ██
 // ██████  ██    ██ ██    ██ ██
@@ -15769,7 +15881,7 @@ _SOKOL_PRIVATE bool _sg_validate_apply_uniforms(sg_shader_stage stage_index, int
         const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[stage_index];
         _SG_VALIDATE(ub_index < stage->num_uniform_blocks, VALIDATE_AUB_NO_UB_AT_SLOT);
 
-        // check that the provided data size doesn't exceed the uniform block size
+        // check that the provided data size matches the uniform block size
         _SG_VALIDATE(data->size == stage->uniform_blocks[ub_index].size, VALIDATE_AUB_SIZE);
 
         return _sg_validate_end();
@@ -16431,7 +16543,7 @@ _SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) {
 SOKOL_API_IMPL void sg_setup(const sg_desc* desc) {
     SOKOL_ASSERT(desc);
     SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0));
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg);
     _sg.desc = _sg_desc_defaults(desc);
     _sg_setup_pools(&_sg.pools, &_sg.desc);
@@ -17530,12 +17642,13 @@ SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data)
 SOKOL_API_IMPL void sg_push_debug_group(const char* name) {
     SOKOL_ASSERT(_sg.valid);
     SOKOL_ASSERT(name);
-    _SOKOL_UNUSED(name);
+    _sg_push_debug_group(name);
     _SG_TRACE_ARGS(push_debug_group, name);
 }
 
 SOKOL_API_IMPL void sg_pop_debug_group(void) {
     SOKOL_ASSERT(_sg.valid);
+    _sg_pop_debug_group();
     _SG_TRACE_NOARGS(pop_debug_group);
 }
 

+ 48 - 0
tests/functional/sokol_args_test.c

@@ -252,3 +252,51 @@ UTEST(sokol_args, escape_sequence) {
     TSTR(sargs_value_at(2), "val2\tval3");
     sargs_shutdown();
 }
+
+static char* argv_11[] = { "exe_name", "kvp0 kvp1", "kvp2 = val2", "kvp3", "kvp4=val4" };
+UTEST(sokol_args, key_only_args) {
+    sargs_setup(&(sargs_desc){
+        .argc = NUM_ARGS(argv_11),
+        .argv = argv_11,
+    });
+    T(sargs_isvalid());
+    T(sargs_num_args() == 5);
+    T(0 == sargs_find("kvp0"));
+    T(1 == sargs_find("kvp1"));
+    T(2 == sargs_find("kvp2"));
+    T(3 == sargs_find("kvp3"));
+    T(4 == sargs_find("kvp4"))
+    T(-1 == sargs_find("kvp5"));
+    T(-1 == sargs_find("val2"));
+    T(-1 == sargs_find("val4"));
+    T(sargs_exists("kvp0"));
+    T(sargs_exists("kvp1"));
+    T(sargs_exists("kvp2"));
+    T(sargs_exists("kvp3"));
+    T(sargs_exists("kvp4"));
+    T(!sargs_exists("kvp5"));
+    TSTR(sargs_value("kvp0"), "");
+    TSTR(sargs_value("kvp1"), "");
+    TSTR(sargs_value("kvp2"), "val2");
+    TSTR(sargs_value("kvp3"), "");
+    TSTR(sargs_value("kvp4"), "val4");
+    TSTR(sargs_value("kvp5"), "");
+    TSTR(sargs_value_def("kvp0", "bla0"), "bla0");
+    TSTR(sargs_value_def("kvp1", "bla1"), "bla1");
+    TSTR(sargs_value_def("kvp2", "bla2"), "val2");
+    TSTR(sargs_value_def("kvp3", "bla3"), "bla3");
+    TSTR(sargs_value_def("kvp4", "bla4"), "val4");
+    TSTR(sargs_value_def("kvp5", "bla5"), "bla5");
+    TSTR(sargs_key_at(0), "kvp0");
+    TSTR(sargs_key_at(1), "kvp1");
+    TSTR(sargs_key_at(2), "kvp2");
+    TSTR(sargs_key_at(3), "kvp3");
+    TSTR(sargs_key_at(4), "kvp4");
+    TSTR(sargs_key_at(5), "");
+    TSTR(sargs_value_at(0), "");
+    TSTR(sargs_value_at(1), "");
+    TSTR(sargs_value_at(2), "val2");
+    TSTR(sargs_value_at(3), "");
+    TSTR(sargs_value_at(4), "val4");
+    TSTR(sargs_value_at(5), "");
+}

+ 63 - 14
tests/functional/sokol_fetch_test.c

@@ -612,9 +612,9 @@ UTEST(sokol_fetch, load_file_chunked) {
 /* load N big files in small chunks interleaved on the same channel via lanes */
 #define LOAD_FILE_LANES_NUM_LANES (4)
 
-uint8_t load_file_lanes_chunk_buf[LOAD_FILE_LANES_NUM_LANES][8192];
-uint8_t load_file_lanes_content[LOAD_FILE_LANES_NUM_LANES][500000];
-int load_file_lanes_passed[LOAD_FILE_LANES_NUM_LANES];
+static uint8_t load_file_lanes_chunk_buf[LOAD_FILE_LANES_NUM_LANES][8192];
+static uint8_t load_file_lanes_content[LOAD_FILE_LANES_NUM_LANES][500000];
+static int load_file_lanes_passed[LOAD_FILE_LANES_NUM_LANES];
 static void load_file_lanes_callback(const sfetch_response_t* response) {
     assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES));
     if (response->fetched) {
@@ -669,9 +669,9 @@ UTEST(sokol_fetch, load_file_lanes) {
 #define LOAD_FILE_THROTTLE_NUM_PASSES (3)
 #define LOAD_FILE_THROTTLE_NUM_REQUESTS (12)    // lanes * passes
 
-uint8_t load_file_throttle_chunk_buf[LOAD_FILE_THROTTLE_NUM_LANES][128000];
-uint8_t load_file_throttle_content[LOAD_FILE_THROTTLE_NUM_PASSES][LOAD_FILE_THROTTLE_NUM_LANES][500000];
-int load_file_throttle_passed[LOAD_FILE_THROTTLE_NUM_LANES];
+static uint8_t load_file_throttle_chunk_buf[LOAD_FILE_THROTTLE_NUM_LANES][128000];
+static uint8_t load_file_throttle_content[LOAD_FILE_THROTTLE_NUM_PASSES][LOAD_FILE_THROTTLE_NUM_LANES][500000];
+static int load_file_throttle_passed[LOAD_FILE_THROTTLE_NUM_LANES];
 
 static void load_file_throttle_callback(const sfetch_response_t* response) {
     assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES));
@@ -733,8 +733,8 @@ UTEST(sokol_fetch, load_file_throttle) {
 
 /* test parallel fetches on multiple channels */
 #define LOAD_CHANNEL_NUM_CHANNELS (16)
-uint8_t load_channel_buf[LOAD_CHANNEL_NUM_CHANNELS][500000];
-bool load_channel_passed[LOAD_CHANNEL_NUM_CHANNELS];
+static uint8_t load_channel_buf[LOAD_CHANNEL_NUM_CHANNELS][500000];
+static bool load_channel_passed[LOAD_CHANNEL_NUM_CHANNELS];
 
 void load_channel_callback(const sfetch_response_t* response) {
     assert(response->channel < LOAD_CHANNEL_NUM_CHANNELS);
@@ -781,15 +781,13 @@ UTEST(sokol_fetch, load_channel) {
     sfetch_shutdown();
 }
 
-bool load_file_cancel_passed = false;
-void load_file_cancel_callback(const sfetch_response_t* response) {
+static bool load_file_cancel_passed = false;
+static void load_file_cancel_callback(const sfetch_response_t* response) {
     if (response->dispatched) {
         sfetch_cancel(response->handle);
     }
-    if (response->failed) {
-        if (response->cancelled && response->finished && (response->error_code == SFETCH_ERROR_CANCELLED)) {
-            load_file_cancel_passed = true;
-        }
+    if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) {
+        load_file_cancel_passed = true;
     }
 }
 
@@ -811,3 +809,54 @@ UTEST(sokol_fetch, load_file_cancel) {
     T(load_file_cancel_passed);
     sfetch_shutdown();
 }
+
+static bool load_file_cancel_before_dispatch_passed = false;
+static void load_file_cancel_before_dispatch_callback(const sfetch_response_t* response) {
+    // cancelled, finished, failed and error code must all be set
+    if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) {
+        load_file_cancel_before_dispatch_passed = true;
+    }
+}
+
+UTEST(sokol_fetch, load_file_cancel_before_dispatch) {
+    sfetch_setup(&(sfetch_desc_t){
+        .num_channels = 1,
+    });
+    sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
+        .path = "comsi.s3m",
+        .callback = load_file_cancel_before_dispatch_callback,
+    });
+    sfetch_cancel(h);
+    sfetch_dowork();
+    T(load_file_cancel_before_dispatch_passed);
+    sfetch_shutdown();
+}
+
+static bool load_file_cancel_after_dispatch_passed = false;
+static void load_file_cancel_after_dispatch_callback(const sfetch_response_t* response) {
+    // when cancelled, then finished, failed and error code must all be set
+    if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) {
+        load_file_cancel_after_dispatch_passed = true;
+    }
+}
+
+UTEST(sokol_fetch, load_file_cancel_after_dispatch) {
+    sfetch_setup(&(sfetch_desc_t){
+        .num_channels = 1,
+    });
+    sfetch_handle_t h = sfetch_send(&(sfetch_request_t){
+        .path = "comsi.s3m",
+        .callback = load_file_cancel_after_dispatch_callback,
+        .buffer = SFETCH_RANGE(load_file_buf),
+    });
+    int frame_count = 0;
+    const int max_frames = 10000;
+    while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) {
+        sfetch_dowork();
+        sfetch_cancel(h);
+        sleep_ms(1);
+    }
+    T(frame_count < max_frames);
+    T(load_file_cancel_after_dispatch_passed);
+    sfetch_shutdown();
+}

+ 10 - 10
util/sokol_debugtext.h

@@ -427,8 +427,8 @@
             sdtx_setup(&(sdtx_desc_t){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...;
                 }
             });
@@ -658,12 +658,12 @@ typedef struct sdtx_context_desc_t {
 
     Used in sdtx_desc_t to provide custom memory-alloc and -free functions
     to sokol_debugtext.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct sdtx_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sdtx_allocator_t;
 
@@ -3598,8 +3598,8 @@ static void _sdtx_clear(void* ptr, size_t size) {
 static void* _sdtx_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_sdtx.desc.allocator.alloc) {
-        ptr = _sdtx.desc.allocator.alloc(size, _sdtx.desc.allocator.user_data);
+    if (_sdtx.desc.allocator.alloc_fn) {
+        ptr = _sdtx.desc.allocator.alloc_fn(size, _sdtx.desc.allocator.user_data);
     } else {
         ptr = malloc(size);
     }
@@ -3616,8 +3616,8 @@ static void* _sdtx_malloc_clear(size_t size) {
 }
 
 static void _sdtx_free(void* ptr) {
-    if (_sdtx.desc.allocator.free) {
-        _sdtx.desc.allocator.free(ptr, _sdtx.desc.allocator.user_data);
+    if (_sdtx.desc.allocator.free_fn) {
+        _sdtx.desc.allocator.free_fn(ptr, _sdtx.desc.allocator.user_data);
     } else {
         free(ptr);
     }
@@ -4197,7 +4197,7 @@ SOKOL_API_IMPL void _sdtx_draw_layer(_sdtx_context_t* ctx, int layer_id) {
 
 
 static sdtx_desc_t _sdtx_desc_defaults(const sdtx_desc_t* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     sdtx_desc_t res = *desc;
     res.context_pool_size = _sdtx_def(res.context_pool_size, _SDTX_DEFAULT_CONTEXT_POOL_SIZE);
     res.printf_buf_size = _sdtx_def(res.printf_buf_size, _SDTX_DEFAULT_PRINTF_BUF_SIZE);

+ 10 - 10
util/sokol_fontstash.h

@@ -147,8 +147,8 @@
         FONScontext* fons_context = sfons_create(&(sfons_desc_t){
             ...
             .allocator = {
-                .alloc = my_alloc,
-                .free = my_free,
+                .alloc_fn = my_alloc,
+                .free_fn = my_free,
                 .user_data = ...,
             }
         });
@@ -213,15 +213,15 @@ extern "C" {
 
     Used in sfons_desc_t to provide custom memory-alloc and -free functions
     to sokol_fontstash.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 
     NOTE that this does not affect memory allocation calls inside
     fontstash.h
 */
 typedef struct sfons_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sfons_allocator_t;
 
@@ -1542,8 +1542,8 @@ static void _sfons_clear(void* ptr, size_t size) {
 static void* _sfons_malloc(const sfons_allocator_t* allocator, size_t size) {
     SOKOL_ASSERT(allocator && (size > 0));
     void* ptr;
-    if (allocator->alloc) {
-        ptr = allocator->alloc(size, allocator->user_data);
+    if (allocator->alloc_fn) {
+        ptr = allocator->alloc_fn(size, allocator->user_data);
     } else {
         ptr = malloc(size);
     }
@@ -1559,8 +1559,8 @@ static void* _sfons_malloc_clear(const sfons_allocator_t* allocator, size_t size
 
 static void _sfons_free(const sfons_allocator_t* allocator, void* ptr) {
     SOKOL_ASSERT(allocator);
-    if (allocator->free) {
-        allocator->free(ptr, allocator->user_data);
+    if (allocator->free_fn) {
+        allocator->free_fn(ptr, allocator->user_data);
     } else {
         free(ptr);
     }
@@ -1739,7 +1739,7 @@ static sfons_desc_t _sfons_desc_defaults(const sfons_desc_t* desc) {
 
 SOKOL_API_IMPL FONScontext* sfons_create(const sfons_desc_t* desc) {
     SOKOL_ASSERT(desc);
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     _sfons_t* sfons = (_sfons_t*) _sfons_malloc_clear(&desc->allocator, sizeof(_sfons_t));
     sfons->desc = _sfons_desc_defaults(desc);
     FONSparams params;

+ 11 - 11
util/sokol_gfx_imgui.h

@@ -73,8 +73,8 @@
 
             sg_imgui_init(&sg_imgui, &(sg_imgui_desc_t){
                 .allocator = {
-                    .alloc = my_malloc,
-                    .free = my_free,
+                    .alloc_fn = my_malloc,
+                    .free_fn = my_free,
                 }
             });
 
@@ -179,8 +179,8 @@
             sg_imgui_init(&(&ctx, &(sg_imgui_desc_t){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...;
                 }
             });
@@ -736,8 +736,8 @@ typedef struct sg_imgui_frame_stats_t {
     override one function but not the other).
 */
 typedef struct sg_imgui_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sg_imgui_allocator_t;
 
@@ -972,8 +972,8 @@ _SOKOL_PRIVATE void _sg_imgui_clear(void* ptr, size_t size) {
 _SOKOL_PRIVATE void* _sg_imgui_malloc(const sg_imgui_allocator_t* allocator, size_t size) {
     SOKOL_ASSERT(allocator && (size > 0));
     void* ptr;
-    if (allocator->alloc) {
-        ptr = allocator->alloc(size, allocator->user_data);
+    if (allocator->alloc_fn) {
+        ptr = allocator->alloc_fn(size, allocator->user_data);
     } else {
         ptr = malloc(size);
     }
@@ -989,8 +989,8 @@ _SOKOL_PRIVATE void* _sg_imgui_malloc_clear(const sg_imgui_allocator_t* allocato
 
 _SOKOL_PRIVATE void _sg_imgui_free(const sg_imgui_allocator_t* allocator, void* ptr) {
     SOKOL_ASSERT(allocator);
-    if (allocator->free) {
-        allocator->free(ptr, allocator->user_data);
+    if (allocator->free_fn) {
+        allocator->free_fn(ptr, allocator->user_data);
     } else {
         free(ptr);
     }
@@ -4343,7 +4343,7 @@ _SOKOL_PRIVATE void _sg_imgui_draw_frame_stats_panel(sg_imgui_t* ctx) {
 #define _sg_imgui_def(val, def) (((val) == 0) ? (def) : (val))
 
 _SOKOL_PRIVATE sg_imgui_desc_t _sg_imgui_desc_defaults(const sg_imgui_desc_t* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     sg_imgui_desc_t res = *desc;
     // FIXME: any additional default overrides would go here
     return res;

+ 9 - 9
util/sokol_gl.h

@@ -606,8 +606,8 @@
             sgl_setup(&(sgl_desc_t){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...;
                 }
             });
@@ -796,8 +796,8 @@ typedef struct sgl_context_desc_t {
     override one function but not the other).
 */
 typedef struct sgl_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sgl_allocator_t;
 
@@ -2437,8 +2437,8 @@ static void _sgl_clear(void* ptr, size_t size) {
 static void* _sgl_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_sgl.desc.allocator.alloc) {
-        ptr = _sgl.desc.allocator.alloc(size, _sgl.desc.allocator.user_data);
+    if (_sgl.desc.allocator.alloc_fn) {
+        ptr = _sgl.desc.allocator.alloc_fn(size, _sgl.desc.allocator.user_data);
     } else {
         ptr = malloc(size);
     }
@@ -2455,8 +2455,8 @@ static void* _sgl_malloc_clear(size_t size) {
 }
 
 static void _sgl_free(void* ptr) {
-    if (_sgl.desc.allocator.free) {
-        _sgl.desc.allocator.free(ptr, _sgl.desc.allocator.user_data);
+    if (_sgl.desc.allocator.free_fn) {
+        _sgl.desc.allocator.free_fn(ptr, _sgl.desc.allocator.user_data);
     } else {
         free(ptr);
     }
@@ -3217,7 +3217,7 @@ static _sgl_matrix_t* _sgl_matrix(_sgl_context_t* ctx) {
 
 // return sg_context_desc_t with patched defaults
 static sgl_desc_t _sgl_desc_defaults(const sgl_desc_t* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     sgl_desc_t res = *desc;
     res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES);
     res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS);

+ 10 - 10
util/sokol_imgui.h

@@ -288,8 +288,8 @@
             simgui_setup(&(simgui_desc_t){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...;
                 }
             });
@@ -467,12 +467,12 @@ typedef enum simgui_log_item_t {
 
     Used in simgui_desc_t to provide custom memory-alloc and -free functions
     to sokol_imgui.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct simgui_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } simgui_allocator_t;
 
@@ -1903,8 +1903,8 @@ static void _simgui_clear(void* ptr, size_t size) {
 static void* _simgui_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_simgui.desc.allocator.alloc) {
-        ptr = _simgui.desc.allocator.alloc(size, _simgui.desc.allocator.user_data);
+    if (_simgui.desc.allocator.alloc_fn) {
+        ptr = _simgui.desc.allocator.alloc_fn(size, _simgui.desc.allocator.user_data);
     } else {
         ptr = malloc(size);
     }
@@ -1921,8 +1921,8 @@ static void* _simgui_malloc_clear(size_t size) {
 }
 
 static void _simgui_free(void* ptr) {
-    if (_simgui.desc.allocator.free) {
-        _simgui.desc.allocator.free(ptr, _simgui.desc.allocator.user_data);
+    if (_simgui.desc.allocator.free_fn) {
+        _simgui.desc.allocator.free_fn(ptr, _simgui.desc.allocator.user_data);
     } else {
         free(ptr);
     }
@@ -2143,7 +2143,7 @@ static bool _simgui_is_osx(void) {
 }
 
 static simgui_desc_t _simgui_desc_defaults(const simgui_desc_t* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     simgui_desc_t res = *desc;
     res.max_vertices = _simgui_def(res.max_vertices, 65536);
     res.image_pool_size = _simgui_def(res.image_pool_size, 256);

+ 2 - 2
util/sokol_memtrack.h

@@ -27,8 +27,8 @@
         sg_setup(&(sg_desc){
             //...
             .allocator = {
-                .alloc = smemtrack_alloc,
-                .free = smemtrack_free,
+                .alloc_fn = smemtrack_alloc,
+                .free_fn = smemtrack_free,
             }
         });
 

+ 9 - 9
util/sokol_nuklear.h

@@ -223,8 +223,8 @@
             snk_setup(&(snk_desc_t){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...;
                 }
             });
@@ -383,12 +383,12 @@ typedef enum snk_log_item_t {
 
     Used in snk_desc_t to provide custom memory-alloc and -free functions
     to sokol_nuklear.h. If memory management should be overridden, both the
-    alloc and free function must be provided (e.g. it's not valid to
+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
     override one function but not the other).
 */
 typedef struct snk_allocator_t {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } snk_allocator_t;
 
@@ -1825,8 +1825,8 @@ static void _snk_clear(void* ptr, size_t size) {
 static void* _snk_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_snuklear.desc.allocator.alloc) {
-        ptr = _snuklear.desc.allocator.alloc(size, _snuklear.desc.allocator.user_data);
+    if (_snuklear.desc.allocator.alloc_fn) {
+        ptr = _snuklear.desc.allocator.alloc_fn(size, _snuklear.desc.allocator.user_data);
     } else {
         ptr = malloc(size);
     }
@@ -1843,8 +1843,8 @@ static void* _snk_malloc_clear(size_t size) {
 }
 
 static void _snk_free(void* ptr) {
-    if (_snuklear.desc.allocator.free) {
-        _snuklear.desc.allocator.free(ptr, _snuklear.desc.allocator.user_data);
+    if (_snuklear.desc.allocator.free_fn) {
+        _snuklear.desc.allocator.free_fn(ptr, _snuklear.desc.allocator.user_data);
     } else {
         free(ptr);
     }

+ 11 - 11
util/sokol_spine.h

@@ -202,8 +202,8 @@
 
         sspine_setup(&(sspine_desc){
             .allocator = {
-                .alloc = my_alloc,
-                .free = my_free,
+                .alloc_fn = my_alloc,
+                .free_fn = my_free,
                 .user_data = ...,
             },
             .logger = {
@@ -908,8 +908,8 @@
             sspine_setup(&(sspine_desc){
                 // ...
                 .allocator = {
-                    .alloc = my_alloc,
-                    .free = my_free,
+                    .alloc_fn = my_alloc,
+                    .free_fn = my_free,
                     .user_data = ...;
                 }
             });
@@ -1198,8 +1198,8 @@ typedef struct sspine_instance_desc {
 } sspine_instance_desc;
 
 typedef struct sspine_allocator {
-    void* (*alloc)(size_t size, void* user_data);
-    void (*free)(void* ptr, void* user_data);
+    void* (*alloc_fn)(size_t size, void* user_data);
+    void (*free_fn)(void* ptr, void* user_data);
     void* user_data;
 } sspine_allocator;
 
@@ -3030,8 +3030,8 @@ static sspine_string _sspine_string(const char* cstr) {
 static void* _sspine_malloc(size_t size) {
     SOKOL_ASSERT(size > 0);
     void* ptr;
-    if (_sspine.desc.allocator.alloc) {
-        ptr = _sspine.desc.allocator.alloc(size, _sspine.desc.allocator.user_data);
+    if (_sspine.desc.allocator.alloc_fn) {
+        ptr = _sspine.desc.allocator.alloc_fn(size, _sspine.desc.allocator.user_data);
     } else {
         ptr = malloc(size);
     }
@@ -3048,8 +3048,8 @@ static void* _sspine_malloc_clear(size_t size) {
 }
 
 static void _sspine_free(void* ptr) {
-    if (_sspine.desc.allocator.free) {
-        _sspine.desc.allocator.free(ptr, _sspine.desc.allocator.user_data);
+    if (_sspine.desc.allocator.free_fn) {
+        _sspine.desc.allocator.free_fn(ptr, _sspine.desc.allocator.user_data);
     } else {
         free(ptr);
     }
@@ -4529,7 +4529,7 @@ static void _sspine_draw_layer(_sspine_context_t* ctx, int layer, const sspine_l
 
 // return sspine_desc with patched defaults
 static sspine_desc _sspine_desc_defaults(const sspine_desc* desc) {
-    SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
     sspine_desc res = *desc;
     res.max_vertices = _sspine_def(desc->max_vertices, _SSPINE_DEFAULT_MAX_VERTICES);
     res.max_commands = _sspine_def(desc->max_commands, _SSPINE_DEFAULT_MAX_COMMANDS);