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

Merge branch 'master' into sokol-spine

Andre Weissflog 2 жил өмнө
parent
commit
45e9f933b7

+ 6 - 5
.github/workflows/gen_bindings.yml

@@ -31,7 +31,7 @@ jobs:
       - name: prepare
         run: |
           sudo apt-get update
-          sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
+          sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev
       - name: test_linux
         run: |
           cd tests
@@ -99,7 +99,7 @@ jobs:
         if: runner.os == 'Linux'
         run: |
             sudo apt-get update
-            sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
+            sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev
       - name: build
         run: zig build
 
@@ -107,7 +107,8 @@ jobs:
     needs: gen-bindings
     strategy:
       matrix:
-        os: [ubuntu-latest, macos-latest, windows-latest]
+        #os: [ubuntu-latest, macos-latest, windows-latest]
+        os: [ubuntu-latest, macos-latest ]
     runs-on: ${{matrix.os}}
     steps:
       - uses: jiro4989/setup-nim-action@v1
@@ -122,7 +123,7 @@ jobs:
         name: prepare
         run: |
             sudo apt-get update
-            sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
+            sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev
       - name: build
         run: |
           nimble install -Y
@@ -148,7 +149,7 @@ jobs:
         name: prepare-linux
         run: |
           sudo apt-get update
-          sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
+          sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev
           curl -L https://github.com/odin-lang/Odin/releases/download/dev-2022-08/odin-ubuntu-amd64-dev-2022-08.zip --output odin.zip
           unzip odin.zip
           chmod a+x odin

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

@@ -37,7 +37,7 @@ jobs:
         - name: prepare
           run: |
             sudo apt-get update
-            sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
+            sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev
         - name: test_linux
           run: |
             cd tests

+ 66 - 0
CHANGELOG.md

@@ -1,5 +1,71 @@
 ## Updates
 
+- **22-Oct-2022** All sokol headers now allow to override logging with a
+  callback function (installed in the setup call) instead of defining a SOKOL_LOG
+  macro. Overriding SOKOL_LOG still works as default fallback, but this is no
+  longer documented, consider this deprecated. Many thanks to github user
+  @Manuzor for the PR (see https://github.com/floooh/sokol/pull/721 for details)
+
+- **21-Oct-2022** RGB9E5 pixel format support in sokol_gfx.h and a GLES2 related
+  bugfix in the sokol_app.h Android backend:
+  - sokol_gfx.h now supports RGB9E5 textures (3*9 bit RGB + 5 bit shared exponent),
+    this works in all backends except GLES2 and WebGL1 (use ```sg_query_pixelformat()```
+    to check for runtime support). Many thanks to github user @allcreater for the PR!
+  - a bugfix in the sokol_app.h Android backend: when forcing a GLES2 context via
+    sapp_desc.gl_force_gles2, the Android backend correctly created a GLES2 context,
+    but then didn't communicate this through the function ```sapp_gles2()``` (which
+    still returned false in this case). This caused the sokol_gfx.h GL backend to
+    use the GLES3 code path instead GLES2 (which surprisingly seemed to have worked
+    fine, at least for the sokol samples which force GLES2).
+
+- **19-Oct-2022** Some fixes in the embedded Javascript code blocks (via EM_JS)
+  in sokol_app.h, sokol_args.h, sokol_audio.h and sokol_fetch.h:
+  - the JS code has been 'modernized' (e.g. const and let instead of var,
+    ```() => { ... }``` instead of ```function () { ... }``` for callbacks)
+  - false positives in the Closure static analysis have been supressed
+    via inline hints
+
+- **16-Oct-2022** The Odin bindings generator and the generated bindings have
+  been simplified (the Odin binding now don't have separate wrapper functions).
+  Requires the latest Odin release. Also note: On M1 Macs I'm currently seeing
+  what looks like an ABI problem (in functions which pass color values to the C
+  side as uint8_t, the colors come out wrong). This also happened with the
+  previous binding version, so it looks like a regression in Odin. Might be
+  related to this recent bugfix (which I haven't tested yet):
+  https://github.com/odin-lang/Odin/issues/2121 Many thanks to @thePHTest for the
+  PR! (https://github.com/floooh/sokol/pull/719)
+
+- **15-Oct-2022**
+    - fixes for Emscripten 3.1.24: the sokol headers now use the new
+    **EM_JS_DEPS()** macro to declare 'indirect dependencies on JS library functions'.
+    This is a (much more robust) follow-up fix to the Emscripten related fixes from 10-Sep-2022.
+    The new Emscripten SDK also displays a couple of Javascript "static analyzer" warnings
+    by the Closure compiler (used in release mode to optimize and minify the generated
+    JS code). I fixed a couple of those warnings, but some warnings persist (all of them
+    false positives). Not sure yet if these can be fixed or need to be supressed, but
+    that's for another time.
+    - the webkitAudioContext() fallback in sokol_audio.h's Emscripten backend
+    has been removed (only AudioContext is supported now), the fallback also
+    triggered a Closure warning, so it probably never worked as intended anyway.
+    - I also had to undo an older workaround in sokol_app.h on iOS (https://github.com/floooh/sokol/issues/645)
+    because this is now triggering a Metal validation layer error (https://github.com/floooh/sokol/issues/726).
+    The original case is no longer reproducible, so undoing the old workaround seems to
+    be a quick fix. Eventually I want to get rid of MTKView though, and go down to
+    CAMetalLayer.
+
+- **08-Oct-2022** sokol_app.h Android backend: the ```sapp_touchpoint``` struct
+  now has a new item ```sapp_android_tooltype android_tooltype;```. This exposes the
+  result of the Android NDK function ```AMotionEvent_getToolType()```.
+  Many thanks to @Wertzui123 for the initial PR (https://github.com/floooh/sokol/pull/717).
+
+- **25-Sep-2022**: sokol_app.h on Linux now optionally supports EGL instead of
+  GLX for the window system glue code and can create a GLES2 or GLES3 context
+  instead of a 'desktop GL' context.
+  To get EGL+GLES2/GLES3, just define SOKOL_GLES2 or SOKOL_GLES3 to compile the
+  implementation. To get EGL+GL, define SOKOL_GLCORE33 *and* SOKOL_FORCE_EGL.
+  By default, defining just SOKOL_GLCORE33 uses GLX for the window system glue
+  (just as before). Many thanks to GH user @billzez for the PR!
+
 - **10-Sep-2022**: sokol_app.h and sokol_args.h has been fixed for Emscripten 3.21, those headers
   used the Emscripten Javascript helper function ```ccall()``` which is now part of the
   'legacy runtime' and causes linker errors. Instead of ```ccall()``` sokol_app.h and sokol_args.h

+ 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) (**10-Sep-2022** an important compatibility fix for Emscripten 3.21 in sokol_app.h and sokol_args.h)
+[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**22-Oct-2022** runtime logging callback in all sokol headers)
 
 [![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)
 

+ 4 - 4
bindgen/gen_nim.py

@@ -423,7 +423,7 @@ def funcdecl_args_c(decl, prefix):
             s += ", "
         arg_name = param_decl['name']
         arg_type = check_override(f'{func_name}.{arg_name}', default=param_decl['type'])
-        s += f"{arg_name}:{as_nim_type(arg_type, prefix)}"
+        s += f"{as_camel_case(arg_name, prefix)}:{as_nim_type(arg_type, prefix)}"
     return s
 
 # returns Nim function args (pass structs by value)
@@ -435,7 +435,7 @@ def funcdecl_args_nim(decl, prefix):
             s += ", "
         arg_name = param_decl['name']
         arg_type = check_override(f'{func_name}.{arg_name}', default=param_decl['type'])
-        s += f"{arg_name}:{as_nim_type(arg_type, prefix, struct_ptr_as_value=True)}"
+        s += f"{as_camel_case(arg_name, prefix)}:{as_nim_type(arg_type, prefix, struct_ptr_as_value=True)}"
     return s
 
 def funcdecl_result(decl, prefix):
@@ -476,14 +476,14 @@ def gen_array_converters(decl, prefix):
             array_base_type = as_nim_type(array_type, prefix)
             if is_1d_array_type(field['type']):
                 n = array_sizes[0]
-                l(f'converter to_{struct_name}_{field_name}*[N:static[int]](items: array[N, {array_base_type}]): array[{n}, {array_base_type}] =')
+                l(f'converter to{struct_name}{field_name}*[N:static[int]](items: array[N, {array_base_type}]): array[{n}, {array_base_type}] =')
                 l(f'  static: assert(N < {n})')
                 l(f'  for index,item in items.pairs: result[index]=item')
                 l('')
             elif is_2d_array_type(field['type']):
                 x = array_sizes[1]
                 y = array_sizes[0]
-                l(f'converter to_{struct_name}_{field_name}*[Y:static[int], X:static[int]](items: array[Y, array[X, {array_base_type}]]): array[{y}, array[{x}, {array_base_type}]] =')
+                l(f'converter to{struct_name}{field_name}*[Y:static[int], X:static[int]](items: array[Y, array[X, {array_base_type}]]): array[{y}, array[{x}, {array_base_type}]] =')
                 l(f'  static: assert(X < {x})')
                 l(f'  static: assert(Y < {y})')
                 l(f'  for indexY,itemY in items.pairs:')

+ 28 - 70
bindgen/gen_odin.py

@@ -85,13 +85,14 @@ ignores = [
 overrides = {
     'context':                              'ctx',  # reserved keyword
     'sapp_sgcontext':                       'sapp_sgctx',
+    'sapp_sgcontext':                       'sapp_sgctx',
     'sg_context_desc.color_format':         'int',
     'sg_context_desc.depth_format':         'int',
     'SGL_NO_ERROR':                         'SGL_ERROR_NO_ERROR',
 }
 
 prim_types = {
-    'int':          'i32',
+    'int':          'c.int',
     'bool':         'bool',
     'char':         'u8',
     'int8_t':       'i8',
@@ -208,6 +209,9 @@ def enum_default_item(enum_name):
 def is_prim_type(s):
     return s in prim_types
 
+def is_int_type(s):
+    return s == "int"
+
 def is_struct_type(s):
     return s in struct_types
 
@@ -323,18 +327,12 @@ def funcdecl_args_c(decl, prefix):
             s += ', '
         param_name = param_decl['name']
         param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
-        s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}"
-    return s
-
-def funcdecl_args_odin(decl, prefix):
-    s = ''
-    func_name = decl['name']
-    for param_decl in decl['params']:
-        if s != '':
-            s += ', '
-        param_name = param_decl['name']
-        param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
-        s += f"{param_name}: {map_type(param_type, prefix, 'odin_arg')}"
+        if is_const_struct_ptr(param_type):
+            s += f"#by_ptr {param_name}: {map_type(param_type, prefix, 'odin_arg')}"
+        elif is_int_type(param_type):
+            s += f"#any_int {param_name}: {map_type(param_type, prefix, 'c_arg')}"
+        else:
+            s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}"
     return s
 
 def funcptr_args_c(field_type, prefix):
@@ -363,12 +361,6 @@ def funcdecl_result_c(decl, prefix):
     res_c_type = decl_type[:decl_type.index('(')].strip()
     return map_type(check_override(f'{func_name}.RESULT', default=res_c_type), prefix, 'c_arg')
 
-def funcdecl_result_odin(decl, prefix):
-    func_name = decl['name']
-    decl_type = decl['type']
-    res_c_type = decl_type[:decl_type.index('(')].strip()
-    return map_type(check_override(f'{func_name}.RESULT', default=res_c_type), prefix, 'odin_arg')
-
 def get_system_libs(module, platform, backend):
     if module in system_libs:
         if platform in system_libs[module]:
@@ -378,7 +370,7 @@ def get_system_libs(module, platform, backend):
                     return f", {libs}"
     return ''
 
-def gen_c_imports(inp, prefix):
+def gen_c_imports(inp, c_prefix, prefix):
     clib_prefix = f'sokol_{inp["module"]}'
     clib_import = f'{clib_prefix}_clib'
     windows_d3d11_libs = get_system_libs(prefix, 'windows', 'd3d11')
@@ -386,6 +378,7 @@ def gen_c_imports(inp, prefix):
     macos_metal_libs = get_system_libs(prefix, 'macos', 'metal')
     macos_gl_libs = get_system_libs(prefix, 'macos', 'gl')
     linux_gl_libs = get_system_libs(prefix, 'linux', 'gl')
+    l( 'import "core:c"')
     l( 'when ODIN_OS == .Windows {')
     l( '    when #config(SOKOL_USE_GL,false) {')
     l(f'        when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_gl_debug.lib"{windows_gl_libs} }} }}')
@@ -417,7 +410,12 @@ def gen_c_imports(inp, prefix):
     l(f'    when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_linux_x64_gl_debug.a"{linux_gl_libs} }} }}')
     l(f'    else                    {{ foreign import {clib_import} {{ "{clib_prefix}_linux_x64_gl_release.a"{linux_gl_libs} }} }}')
     l( '}')
-    l( '@(default_calling_convention="c")')
+
+    # Need to special case sapp_sg to avoid Odin's context keyword
+    if c_prefix == "sapp_sg":
+        l(f'@(default_calling_convention="c")')
+    else:
+        l(f'@(default_calling_convention="c", link_prefix="{c_prefix}")')
     l(f"foreign {clib_import} {{")
     prefix = inp['prefix']
     for decl in inp['decls']:
@@ -425,7 +423,12 @@ def gen_c_imports(inp, prefix):
             args = funcdecl_args_c(decl, prefix)
             res_type = funcdecl_result_c(decl, prefix)
             res_str = '' if res_type == '' else f'-> {res_type}'
-            l(f"    {decl['name']} :: proc({args}) {res_str} ---")
+            # Need to special case sapp_sg to avoid Odin's context keyword
+            if c_prefix == "sapp_sg":
+                l(f'    @(link_name="{decl["name"]}")')
+                l(f"    {check_override(as_snake_case(decl['name'], c_prefix))} :: proc({args}) {res_str} ---")
+            else:
+                l(f"    {as_snake_case(decl['name'], c_prefix)} :: proc({args}) {res_str} ---")
     l('}')
 
 def gen_consts(decl, prefix):
@@ -459,49 +462,6 @@ def gen_enum(decl, prefix):
                 l(f"    {item_name},")
     l('}')
 
-def gen_func(decl, prefix):
-    c_func_name = decl['name']
-    args = funcdecl_args_odin(decl, prefix)
-    res_type = funcdecl_result_odin(decl, prefix)
-    res_str = '' if res_type == '' else f'-> {res_type}'
-    if res_type != funcdecl_result_c(decl, prefix):
-        # cast needed for return type
-        res_cast = f'cast({res_type})'
-    else:
-        res_cast = ''
-    l(f"{as_snake_case(check_override(decl['name']), prefix)} :: proc({args}) {res_str} {{")
-
-    # workaround for 'cannot take the pointer address of 'x' which is a procedure parameter
-    for param_decl in decl['params']:
-        arg_name = param_decl['name']
-        arg_type = check_override(f'{c_func_name}.{arg_name}', default=param_decl['type'])
-        if is_const_struct_ptr(arg_type):
-            l(f'    _{arg_name} := {arg_name}')
-    s = '    '
-    if res_type == '':
-        # void result
-        s += f"{c_func_name}("
-    else:
-        s += f"return {res_cast}{c_func_name}("
-    for i, param_decl in enumerate(decl['params']):
-        if i > 0:
-            s += ', '
-        arg_name = param_decl['name']
-        arg_type = check_override(f'{c_func_name}.{arg_name}', default=param_decl['type'])
-        if is_const_struct_ptr(arg_type):
-            s += f"&_{arg_name}"
-        else:
-            odin_arg_type = map_type(arg_type, prefix, 'odin_arg')
-            c_arg_type = map_type(arg_type, prefix, 'c_arg')
-            if odin_arg_type != c_arg_type:
-                cast = f'cast({c_arg_type})'
-            else:
-                cast = ''
-            s += f'{cast}{arg_name}'
-    s += ')'
-    l(s)
-    l('}')
-
 def gen_imports(dep_prefixes):
     for dep_prefix in dep_prefixes:
         dep_module_name = module_names[dep_prefix]
@@ -517,7 +477,7 @@ def gen_helpers(inp):
         l('    putr(strings.unsafe_string_to_cstring(fstr), len(fstr))')
         l('}')
 
-def gen_module(inp, dep_prefixes):
+def gen_module(inp, c_prefix, dep_prefixes):
     pre_parse(inp)
     l('// machine generated, do not edit')
     l('')
@@ -525,7 +485,7 @@ def gen_module(inp, dep_prefixes):
     gen_imports(dep_prefixes)
     gen_helpers(inp)
     prefix = inp['prefix']
-    gen_c_imports(inp, prefix)
+    gen_c_imports(inp, c_prefix, prefix)
     for decl in inp['decls']:
         if not decl['is_dep']:
             kind = decl['kind']
@@ -536,8 +496,6 @@ def gen_module(inp, dep_prefixes):
                     gen_struct(decl, prefix)
                 elif kind == 'enum':
                     gen_enum(decl, prefix)
-                elif kind == 'func':
-                    gen_func(decl, prefix)
 
 def pre_parse(inp):
     global struct_types
@@ -571,7 +529,7 @@ def gen(c_header_path, c_prefix, dep_c_prefixes):
     csource_path = get_csource_path(c_prefix)
     module_name = module_names[c_prefix]
     ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes)
-    gen_module(ir, dep_c_prefixes)
+    gen_module(ir, c_prefix, dep_c_prefixes)
     with open(f"{module_root}/{ir['module']}/{ir['module']}.odin", 'w', newline='\n') as f_outp:
         f_outp.write(out_lines)
 

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 265 - 157
sokol_app.h


+ 13 - 18
sokol_args.h

@@ -16,7 +16,6 @@
     Optionally provide the following defines with your own implementations:
 
     SOKOL_ASSERT(c)     - your own assert macro (default: assert(c))
-    SOKOL_LOG(msg)      - your own logging functions (default: puts(msg))
     SOKOL_ARGS_API_DECL - public function declaration prefix (default: extern)
     SOKOL_API_DECL      - same as SOKOL_ARGS_API_DECL
     SOKOL_API_IMPL      - public function implementation prefix (default: -)
@@ -236,7 +235,7 @@
                 .allocator = {
                     .alloc = my_alloc,
                     .free = my_free,
-                    .user_data = ...;
+                    .user_data = ...,
                 }
             });
         ...
@@ -246,7 +245,6 @@
     This only affects memory allocation calls done by sokol_args.h
     itself though, not any allocations in OS libraries.
 
-
     TODO
     ====
     - parsing errors?
@@ -375,21 +373,13 @@ inline void sargs_setup(const sargs_desc& desc) { return sargs_setup(&desc); }
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
-    #endif
-#endif
 
 #ifndef _SOKOL_PRIVATE
     #if defined(__GNUC__) || defined(__clang__)
@@ -692,6 +682,11 @@ _SOKOL_PRIVATE bool _sargs_parse_cargs(int argc, const char** argv) {
 #ifdef __cplusplus
 extern "C" {
 #endif
+
+#if defined(EM_JS_DEPS)
+EM_JS_DEPS(sokol_audio, "$withStackSave,$allocateUTF8OnStack");
+#endif
+
 EMSCRIPTEN_KEEPALIVE void _sargs_add_kvp(const char* key, const char* val) {
     SOKOL_ASSERT(_sargs.valid && key && val);
     if (_sargs.num_args >= _sargs.max_args) {
@@ -723,13 +718,13 @@ EMSCRIPTEN_KEEPALIVE void _sargs_add_kvp(const char* key, const char* val) {
 
 /* JS function to extract arguments from the page URL */
 EM_JS(void, sargs_js_parse_url, (void), {
-    var params = new URLSearchParams(window.location.search).entries();
-    for (var p = params.next(); !p.done; p = params.next()) {
-        var key = p.value[0];
-        var val = p.value[1];
+    const params = new URLSearchParams(window.location.search).entries();
+    for (let p = params.next(); !p.done; p = params.next()) {
+        const key = p.value[0];
+        const val = p.value[1];
         withStackSave(() => {
-            var key_cstr = allocateUTF8OnStack(key);
-            var val_cstr = allocateUTF8OnStack(val);
+            const key_cstr = allocateUTF8OnStack(key);
+            const val_cstr = allocateUTF8OnStack(val);
             __sargs_add_kvp(key_cstr, val_cstr)
         });
     }

+ 97 - 50
sokol_audio.h

@@ -17,7 +17,6 @@
 
     SOKOL_DUMMY_BACKEND - use a dummy backend
     SOKOL_ASSERT(c)     - your own assert macro (default: assert(c))
-    SOKOL_LOG(msg)      - your own logging function (default: puts(msg))
     SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern)
     SOKOL_API_DECL      - same as SOKOL_AUDIO_API_DECL
     SOKOL_API_IMPL      - public function implementation prefix (default: -)
@@ -383,7 +382,7 @@
                 .allocator = {
                     .alloc = my_alloc,
                     .free = my_free,
-                    .user_data = ...;
+                    .user_data = ...,
                 }
             });
         ...
@@ -397,6 +396,28 @@
     was called, so you don't need to worry about thread-safety.
 
 
+    LOG FUNCTION OVERRIDE
+    =====================
+    You can override the log function at initialization time like this:
+
+        void my_log(const char* message, void* user_data) {
+            printf("saudio says: \s\n", message);
+        }
+
+        ...
+            saudio_setup(&(saudio_desc){
+                // ...
+                .logger = {
+                    .log_cb = my_log,
+                    .user_data = ...,
+                }
+            });
+        ...
+
+    If no overrides are provided, puts will be used on most platforms.
+    On Android, __android_log_write will be used instead.
+
+
     LICENSE
     =======
 
@@ -459,6 +480,17 @@ typedef struct saudio_allocator {
     void* user_data;
 } saudio_allocator;
 
+/*
+    saudio_logger
+
+    Used in saudio_desc to provide custom log callbacks to sokol_audio.h.
+    Default behavior is SOKOL_LOG(message).
+*/
+typedef struct saudio_logger {
+    void (*log_cb)(const char* message, void* user_data);
+    void* user_data;
+} saudio_logger;
+
 typedef struct saudio_desc {
     int sample_rate;        // requested sample rate
     int num_channels;       // number of channels, default: 1 (mono)
@@ -469,6 +501,7 @@ typedef struct saudio_desc {
     void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); //... and with user data
     void* user_data;        // optional user data argument for stream_userdata_cb
     saudio_allocator allocator;     // optional allocation override functions
+    saudio_logger logger;   // optional log override functions
 } saudio_desc;
 
 /* setup sokol-audio */
@@ -520,19 +553,26 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
+
+#if !defined(SOKOL_DEBUG)
+    #define SAUDIO_LOG(s)
+#else
+    #define SAUDIO_LOG(s) _saudio_log(s)
+    #ifndef SOKOL_LOG
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_AUDIO", s)
+        #else
+            #include <stdio.h>
+            #define SOKOL_LOG(s) puts(s)
+        #endif
     #endif
 #endif
 
@@ -984,6 +1024,17 @@ _SOKOL_PRIVATE void _saudio_free(void* ptr) {
     }
 }
 
+#if defined(SOKOL_DEBUG)
+_SOKOL_PRIVATE void _saudio_log(const char* msg) {
+    SOKOL_ASSERT(msg);
+    if (_saudio.desc.logger.log_cb) {
+        _saudio.desc.logger.log_cb(msg, _saudio.desc.logger.user_data);
+    } else {
+        SOKOL_LOG(msg);
+    }
+}
+#endif
+
 /*=== MUTEX IMPLEMENTATION ===================================================*/
 #if defined(_SAUDIO_NOTHREADS)
 
@@ -1386,7 +1437,7 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
     int dir; uint32_t rate;
     int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0);
     if (rc < 0) {
-        SOKOL_LOG("sokol_audio.h: snd_pcm_open() failed");
+        SAUDIO_LOG("sokol_audio.h: snd_pcm_open() failed");
         return false;
     }
 
@@ -1399,26 +1450,26 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
     snd_pcm_hw_params_any(_saudio.backend.device, params);
     snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED);
     if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) {
-        SOKOL_LOG("sokol_audio.h: float samples not supported");
+        SAUDIO_LOG("sokol_audio.h: float samples not supported");
         goto error;
     }
     if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) {
-        SOKOL_LOG("sokol_audio.h: requested buffer size not supported");
+        SAUDIO_LOG("sokol_audio.h: requested buffer size not supported");
         goto error;
     }
     if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) {
-        SOKOL_LOG("sokol_audio.h: requested channel count not supported");
+        SAUDIO_LOG("sokol_audio.h: requested channel count not supported");
         goto error;
     }
     /* let ALSA pick a nearby sampling rate */
     rate = (uint32_t) _saudio.sample_rate;
     dir = 0;
     if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) {
-        SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params_set_rate_near() failed");
+        SAUDIO_LOG("sokol_audio.h: snd_pcm_hw_params_set_rate_near() failed");
         goto error;
     }
     if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) {
-        SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params() failed");
+        SAUDIO_LOG("sokol_audio.h: snd_pcm_hw_params() failed");
         goto error;
     }
 
@@ -1433,7 +1484,7 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
 
     /* create the buffer-streaming start thread */
     if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) {
-        SOKOL_LOG("sokol_audio.h: pthread_create() failed");
+        SAUDIO_LOG("sokol_audio.h: pthread_create() failed");
         goto error;
     }
 
@@ -1626,17 +1677,17 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
     #endif
     _saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0);
     if (0 == _saudio.backend.thread.buffer_end_event) {
-        SOKOL_LOG("sokol_audio wasapi: failed to create buffer_end_event");
+        SAUDIO_LOG("sokol_audio wasapi: failed to create buffer_end_event");
         goto error;
     }
     #if defined(_SAUDIO_UWP)
         _saudio.backend.interface_activation_mutex = CreateMutexA(NULL, FALSE, "interface_activation_mutex");
         if (_saudio.backend.interface_activation_mutex == NULL) {
-            SOKOL_LOG("sokol_audio wasapi: failed to create interface activation mutex");
+            SAUDIO_LOG("sokol_audio wasapi: failed to create interface activation mutex");
             goto error;
         }
         if (FAILED(StringFromIID(_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_Devinterface_Audio_Render), &_saudio.backend.interface_activation_audio_interface_uid_string))) {
-            SOKOL_LOG("sokol_audio wasapi: failed to get default audio device ID string");
+            SAUDIO_LOG("sokol_audio wasapi: failed to get default audio device ID string");
             goto error;
         }
 
@@ -1650,7 +1701,7 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
         static IActivateAudioInterfaceCompletionHandler completion_handler_interface = { &completion_handler_interface_vtable };
 
         if (FAILED(ActivateAudioInterfaceAsync(_saudio.backend.interface_activation_audio_interface_uid_string, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), NULL, &completion_handler_interface, &_saudio.backend.interface_activation_operation))) {
-            SOKOL_LOG("sokol_audio wasapi: failed to get default audio device ID string");
+            SAUDIO_LOG("sokol_audio wasapi: failed to get default audio device ID string");
             goto error;
         }
         while (!(_saudio.backend.audio_client)) {
@@ -1660,7 +1711,7 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
         }
 
         if (!(_saudio.backend.interface_activation_success)) {
-            SOKOL_LOG("sokol_audio wasapi: interface activation failed. Unable to get audio client");
+            SAUDIO_LOG("sokol_audio wasapi: interface activation failed. Unable to get audio client");
             goto error;
         }
 
@@ -1670,14 +1721,14 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
             _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator),
             (void**)&_saudio.backend.device_enumerator)))
         {
-            SOKOL_LOG("sokol_audio wasapi: failed to create device enumerator");
+            SAUDIO_LOG("sokol_audio wasapi: failed to create device enumerator");
             goto error;
         }
         if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator,
             eRender, eConsole,
             &_saudio.backend.device)))
         {
-            SOKOL_LOG("sokol_audio wasapi: GetDefaultAudioEndPoint failed");
+            SAUDIO_LOG("sokol_audio wasapi: GetDefaultAudioEndPoint failed");
             goto error;
         }
         if (FAILED(IMMDevice_Activate(_saudio.backend.device,
@@ -1685,7 +1736,7 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
             CLSCTX_ALL, 0,
             (void**)&_saudio.backend.audio_client)))
         {
-            SOKOL_LOG("sokol_audio wasapi: device activate failed");
+            SAUDIO_LOG("sokol_audio wasapi: device activate failed");
             goto error;
         }
     #endif
@@ -1714,22 +1765,22 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
         AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
         dur, 0, (WAVEFORMATEX*)&fmtex, 0)))
     {
-        SOKOL_LOG("sokol_audio wasapi: audio client initialize failed");
+        SAUDIO_LOG("sokol_audio wasapi: audio client initialize failed");
         goto error;
     }
     if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) {
-        SOKOL_LOG("sokol_audio wasapi: audio client get buffer size failed");
+        SAUDIO_LOG("sokol_audio wasapi: audio client get buffer size failed");
         goto error;
     }
     if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client,
         _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient),
         (void**)&_saudio.backend.render_client)))
     {
-        SOKOL_LOG("sokol_audio wasapi: audio client GetService failed");
+        SAUDIO_LOG("sokol_audio wasapi: audio client GetService failed");
         goto error;
     }
     if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) {
-        SOKOL_LOG("sokol_audio wasapi: audio client SetEventHandle failed");
+        SAUDIO_LOG("sokol_audio wasapi: audio client SetEventHandle failed");
         goto error;
     }
     _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
@@ -1742,7 +1793,7 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
     /* create streaming thread */
     _saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0);
     if (0 == _saudio.backend.thread.thread_handle) {
-        SOKOL_LOG("sokol_audio wasapi: CreateThread failed");
+        SAUDIO_LOG("sokol_audio wasapi: CreateThread failed");
         goto error;
     }
     return true;
@@ -1811,12 +1862,6 @@ EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size),
             latencyHint: 'interactive',
         });
     }
-    else if (typeof webkitAudioContext !== 'undefined') {
-        Module._saudio_context = new webkitAudioContext({
-            sampleRate: sample_rate,
-            latencyHint: 'interactive',
-        });
-    }
     else {
         Module._saudio_context = null;
         console.log('sokol_audio.h: no WebAudio support');
@@ -1824,14 +1869,14 @@ EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size),
     if (Module._saudio_context) {
         console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate);
         Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels);
-        Module._saudio_node.onaudioprocess = function pump_audio(event) {
-            var num_frames = event.outputBuffer.length;
-            var ptr = __saudio_emsc_pull(num_frames);
+        Module._saudio_node.onaudioprocess = (event) => {
+            const num_frames = event.outputBuffer.length;
+            const ptr = __saudio_emsc_pull(num_frames);
             if (ptr) {
-                var num_channels = event.outputBuffer.numberOfChannels;
-                for (var chn = 0; chn < num_channels; chn++) {
-                    var chan = event.outputBuffer.getChannelData(chn);
-                    for (var i = 0; i < num_frames; i++) {
+                const num_channels = event.outputBuffer.numberOfChannels;
+                for (let chn = 0; chn < num_channels; chn++) {
+                    const chan = event.outputBuffer.getChannelData(chn);
+                    for (let i = 0; i < num_frames; i++) {
                         chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)]
                     }
                 }
@@ -1840,7 +1885,7 @@ EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size),
         Module._saudio_node.connect(Module._saudio_context.destination);
 
         // in some browsers, WebAudio needs to be activated on a user action
-        var resume_webaudio = function() {
+        const resume_webaudio = () => {
             if (Module._saudio_context) {
                 if (Module._saudio_context.state === 'suspended') {
                     Module._saudio_context.resume();
@@ -1859,11 +1904,13 @@ EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size),
 
 /* shutdown the WebAudioContext and ScriptProcessorNode */
 EM_JS(void, saudio_js_shutdown, (void), {
-    if (Module._saudio_context !== null) {
+    \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
+    const ctx = Module._saudio_context;
+    if (ctx !== null) {
         if (Module._saudio_node) {
             Module._saudio_node.disconnect();
         }
-        Module._saudio_context.close();
+        ctx.close();
         Module._saudio_context = null;
         Module._saudio_node = null;
     }
@@ -2069,14 +2116,14 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
     /* Create engine */
     const SLEngineOption opts[] = { { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE } };
     if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) {
-        SOKOL_LOG("sokol_audio opensles: slCreateEngine failed");
+        SAUDIO_LOG("sokol_audio opensles: slCreateEngine failed");
         _saudio_backend_shutdown();
         return false;
     }
 
     (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE);
     if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) {
-        SOKOL_LOG("sokol_audio opensles: GetInterface->Engine failed");
+        SAUDIO_LOG("sokol_audio opensles: GetInterface->Engine failed");
         _saudio_backend_shutdown();
         return false;
     }
@@ -2088,14 +2135,14 @@ _SOKOL_PRIVATE bool _saudio_backend_init(void) {
 
         if( (*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS)
         {
-            SOKOL_LOG("sokol_audio opensles: CreateOutputMix failed");
+            SAUDIO_LOG("sokol_audio opensles: CreateOutputMix failed");
             _saudio_backend_shutdown();
             return false;
         }
         (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE);
 
         if((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) {
-            SOKOL_LOG("sokol_audio opensles: GetInterface->OutputMixVol failed");
+            SAUDIO_LOG("sokol_audio opensles: GetInterface->OutputMixVol failed");
         }
     }
 
@@ -2194,7 +2241,7 @@ SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
            the requested packet size
         */
         if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) {
-            SOKOL_LOG("sokol_audio.h: actual backend buffer size isn't multiple of requested packet size");
+            SAUDIO_LOG("sokol_audio.h: actual backend buffer size isn't multiple of requested packet size");
             _saudio_backend_shutdown();
             return;
         }

+ 86 - 37
sokol_fetch.h

@@ -16,7 +16,6 @@
     Optionally provide the following defines with your own implementations:
 
     SOKOL_ASSERT(c)             - your own assert macro (default: assert(c))
-    SOKOL_LOG(msg)              - your own logging function (default: puts(msg))
     SOKOL_UNREACHABLE()         - a guard macro for unreachable code (default: assert(false))
     SOKOL_FETCH_API_DECL        - public function declaration prefix (default: extern)
     SOKOL_API_DECL              - same as SOKOL_FETCH_API_DECL
@@ -119,7 +118,7 @@
     (4) pump the sokol-fetch message queues, and invoke response callbacks
         by calling:
 
-        sfetch_dowork(); 
+        sfetch_dowork();
 
         In an event-driven app this should be called in the event loop. If you
         use sokol-app this would be in your frame_cb function.
@@ -810,7 +809,7 @@
                 .allocator = {
                     .alloc = my_alloc,
                     .free = my_free,
-                    .user_data = ...;
+                    .user_data = ...,
                 }
             });
         ...
@@ -824,6 +823,28 @@
     was called, so you don't need to worry about thread-safety.
 
 
+    LOG FUNCTION OVERRIDE
+    =====================
+    You can override the log function at initialization time like this:
+
+        void my_log(const char* message, void* user_data) {
+            printf("sfetch says: \s\n", message);
+        }
+
+        ...
+            sfetch_setup(&(sfetch_desc_t){
+                // ...
+                .logger = {
+                    .log_cb = my_log,
+                    .user_data = ...,
+                }
+            });
+        ...
+
+    If no overrides are provided, puts will be used on most platforms.
+    On Android, __android_log_write will be used instead.
+
+
     FUTURE PLANS / V2.0 IDEA DUMP
     =============================
     - An optional polling API (as alternative to callback API)
@@ -903,6 +924,16 @@ typedef struct sfetch_allocator_t {
     void* user_data;
 } sfetch_allocator_t;
 
+/*
+    sfetch_logger_t
+
+    Used in sfetch_desc_t to provide custom log callbacks to sokol_fetch.h.
+    Default behavior is SOKOL_LOG(message).
+*/
+typedef struct sfetch_logger_t {
+    void (*log_cb)(const char* message, void* user_data);
+    void* user_data;
+} sfetch_logger_t;
 
 /* configuration values for sfetch_setup() */
 typedef struct sfetch_desc_t {
@@ -910,6 +941,7 @@ typedef struct sfetch_desc_t {
     uint32_t num_channels;          /* number of channels to fetch requests in parallel (default: 1) */
     uint32_t num_lanes;             /* max number of requests active on the same channel (default: 1) */
     sfetch_allocator_t allocator;   /* optional memory allocation overrides (default: malloc/free) */
+    sfetch_logger_t logger;         /* optional log function overrides (default: SOKOL_LOG(message)) */
 } sfetch_desc_t;
 
 /* a request handle to identify an active fetch request, returned by sfetch_send() */
@@ -1028,19 +1060,26 @@ inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfe
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
+
+#if !defined(SOKOL_DEBUG)
+    #define SFETCH_LOG(s)
+#else
+    #define SFETCH_LOG(s) _sfetch_log(s)
+    #ifndef SOKOL_LOG
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_FETCH", s)
+        #else
+            #include <stdio.h>
+            #define SOKOL_LOG(s) puts(s)
+        #endif
     #endif
 #endif
 
@@ -1288,6 +1327,17 @@ _SOKOL_PRIVATE void _sfetch_free(void* ptr) {
     }
 }
 
+#if defined(SOKOL_DEBUG)
+_SOKOL_PRIVATE void _sfetch_log(const char* msg) {
+    if (_sfetch->desc.logger.log_cb) {
+        _sfetch->desc.logger.log_cb(msg, _sfetch->desc.logger.user_data);
+    }
+    else {
+        SOKOL_LOG(msg);
+    }
+}
+#endif
+
 _SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) {
     return _sfetch;
 }
@@ -1716,7 +1766,7 @@ _SOKOL_PRIVATE bool _sfetch_win32_utf8_to_wide(const char* src, wchar_t* dst, in
 _SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) {
     wchar_t w_path[SFETCH_MAX_PATH];
     if (!_sfetch_win32_utf8_to_wide(path->buf, w_path, sizeof(w_path))) {
-        SOKOL_LOG("_sfetch_file_open: error converting UTF-8 path to wide string");
+        SFETCH_LOG("_sfetch_file_open: error converting UTF-8 path to wide string");
         return 0;
     }
     _sfetch_file_handle_t h = CreateFileW(
@@ -2002,17 +2052,17 @@ _SOKOL_PRIVATE void* _sfetch_channel_thread_func(void* arg) {
 #if _SFETCH_PLATFORM_EMSCRIPTEN
 /*=== embedded Javascript helper functions ===================================*/
 EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cstr), {
-    var path_str = UTF8ToString(path_cstr);
-    var req = new XMLHttpRequest();
+    const path_str = UTF8ToString(path_cstr);
+    const req = new XMLHttpRequest();
     req.open('HEAD', path_str);
     req.onreadystatechange = function() {
-        if (this.readyState == this.DONE) {
-            if (this.status == 200) {
-                var content_length = this.getResponseHeader('Content-Length');
+        if (req.readyState == XMLHttpRequest.DONE) {
+            if (req.status == 200) {
+                const content_length = req.getResponseHeader('Content-Length');
                 __sfetch_emsc_head_response(slot_id, content_length);
             }
             else {
-                __sfetch_emsc_failed_http_status(slot_id, this.status);
+                __sfetch_emsc_failed_http_status(slot_id, req.status);
             }
         }
     };
@@ -2021,19 +2071,19 @@ EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cst
 
 /* if bytes_to_read != 0, a range-request will be sent, otherwise a normal request */
 EM_JS(void, sfetch_js_send_get_request, (uint32_t slot_id, const char* path_cstr, uint32_t offset, uint32_t bytes_to_read, void* buf_ptr, uint32_t buf_size), {
-    var path_str = UTF8ToString(path_cstr);
-    var req = new XMLHttpRequest();
+    const path_str = UTF8ToString(path_cstr);
+    const req = new XMLHttpRequest();
     req.open('GET', path_str);
     req.responseType = 'arraybuffer';
-    var need_range_request = (bytes_to_read > 0);
+    const need_range_request = (bytes_to_read > 0);
     if (need_range_request) {
         req.setRequestHeader('Range', 'bytes='+offset+'-'+(offset+bytes_to_read-1));
     }
     req.onreadystatechange = function() {
-        if (this.readyState == this.DONE) {
-            if ((this.status == 206) || ((this.status == 200) && !need_range_request)) {
-                var u8_array = new Uint8Array(req.response);
-                var content_fetched_size = u8_array.length;
+        if (req.readyState == XMLHttpRequest.DONE) {
+            if ((req.status == 206) || ((req.status == 200) && !need_range_request)) {
+                const u8_array = new Uint8Array(\x2F\x2A\x2A @type {!ArrayBuffer} \x2A\x2F (req.response));
+                const content_fetched_size = u8_array.length;
                 if (content_fetched_size <= buf_size) {
                     HEAPU8.set(u8_array, buf_ptr);
                     __sfetch_emsc_get_response(slot_id, bytes_to_read, content_fetched_size);
@@ -2043,7 +2093,7 @@ EM_JS(void, sfetch_js_send_get_request, (uint32_t slot_id, const char* path_cstr
                 }
             }
             else {
-                __sfetch_emsc_failed_http_status(slot_id, this.status);
+                __sfetch_emsc_failed_http_status(slot_id, req.status);
             }
         }
     };
@@ -2234,7 +2284,7 @@ _SOKOL_PRIVATE bool _sfetch_channel_send(_sfetch_channel_t* chn, uint32_t slot_i
         return true;
     }
     else {
-        SOKOL_LOG("sfetch_send: user_sent queue is full)");
+        SFETCH_LOG("sfetch_send: user_sent queue is full)");
         return false;
     }
 }
@@ -2379,35 +2429,35 @@ _SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_
 _SOKOL_PRIVATE bool _sfetch_validate_request(_sfetch_t* ctx, const sfetch_request_t* req) {
     #if defined(SOKOL_DEBUG)
         if (req->channel >= ctx->desc.num_channels) {
-            SOKOL_LOG("_sfetch_validate_request: request.channel too big!");
+            SFETCH_LOG("_sfetch_validate_request: request.channel too big!");
             return false;
         }
         if (!req->path) {
-            SOKOL_LOG("_sfetch_validate_request: request.path is null!");
+            SFETCH_LOG("_sfetch_validate_request: request.path is null!");
             return false;
         }
         if (strlen(req->path) >= (SFETCH_MAX_PATH-1)) {
-            SOKOL_LOG("_sfetch_validate_request: request.path is too long (must be < SFETCH_MAX_PATH-1)");
+            SFETCH_LOG("_sfetch_validate_request: request.path is too long (must be < SFETCH_MAX_PATH-1)");
             return false;
         }
         if (!req->callback) {
-            SOKOL_LOG("_sfetch_validate_request: request.callback missing");
+            SFETCH_LOG("_sfetch_validate_request: request.callback missing");
             return false;
         }
         if (req->chunk_size > req->buffer_size) {
-            SOKOL_LOG("_sfetch_validate_request: request.chunk_size is greater request.buffer_size)");
+            SFETCH_LOG("_sfetch_validate_request: request.chunk_size is greater request.buffer_size)");
             return false;
         }
         if (req->user_data_ptr && (req->user_data_size == 0)) {
-            SOKOL_LOG("_sfetch_validate_request: request.user_data_ptr is set, but request.user_data_size is null");
+            SFETCH_LOG("_sfetch_validate_request: request.user_data_ptr is set, but request.user_data_size is null");
             return false;
         }
         if (!req->user_data_ptr && (req->user_data_size > 0)) {
-            SOKOL_LOG("_sfetch_validate_request: request.user_data_ptr is null, but request.user_data_size is not");
+            SFETCH_LOG("_sfetch_validate_request: request.user_data_ptr is null, but request.user_data_size is not");
             return false;
         }
         if (req->user_data_size > SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) {
-            SOKOL_LOG("_sfetch_validate_request: request.user_data_size is too big (see SFETCH_MAX_USERDATA_UINT64");
+            SFETCH_LOG("_sfetch_validate_request: request.user_data_size is too big (see SFETCH_MAX_USERDATA_UINT64");
             return false;
         }
     #else
@@ -2443,7 +2493,7 @@ SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc_) {
     /* replace zero-init items with default values */
     if (ctx->desc.num_channels > SFETCH_MAX_CHANNELS) {
         ctx->desc.num_channels = SFETCH_MAX_CHANNELS;
-        SOKOL_LOG("sfetch_setup: clamping num_channels to SFETCH_MAX_CHANNELS");
+        SFETCH_LOG("sfetch_setup: clamping num_channels to SFETCH_MAX_CHANNELS");
     }
 
     /* setup the global request item pool */
@@ -2515,7 +2565,7 @@ SOKOL_API_IMPL sfetch_handle_t sfetch_send(const sfetch_request_t* request) {
 
     uint32_t slot_id = _sfetch_pool_item_alloc(&ctx->pool, request);
     if (0 == slot_id) {
-        SOKOL_LOG("sfetch_send: request pool exhausted (too many active requests)");
+        SFETCH_LOG("sfetch_send: request pool exhausted (too many active requests)");
         return invalid_handle;
     }
     if (!_sfetch_channel_send(&ctx->chn[request->channel], slot_id)) {
@@ -2605,4 +2655,3 @@ SOKOL_API_IMPL void sfetch_cancel(sfetch_handle_t h) {
 }
 
 #endif /* SOKOL_FETCH_IMPL */
-

+ 136 - 67
sokol_gfx.h

@@ -40,7 +40,6 @@
     Optionally provide the following defines with your own implementations:
 
     SOKOL_ASSERT(c)             - your own assert macro (default: assert(c))
-    SOKOL_LOG(msg)              - your own logging function (default: puts(msg))
     SOKOL_UNREACHABLE()         - a guard macro for unreachable code (default: assert(false))
     SOKOL_GFX_API_DECL          - public function declaration prefix (default: extern)
     SOKOL_API_DECL              - same as SOKOL_GFX_API_DECL
@@ -679,7 +678,7 @@
                 .allocator = {
                     .alloc = my_alloc,
                     .free = my_free,
-                    .user_data = ...;
+                    .user_data = ...,
                 }
             });
         ...
@@ -690,6 +689,27 @@
     itself though, not any allocations in OS libraries.
 
 
+    LOG FUNCTION OVERRIDE
+    =====================
+    You can override the log function at initialization time like this:
+
+        void my_log(const char* message, void* user_data) {
+            printf("sg says: \s\n", message);
+        }
+
+        ...
+            sg_setup(&(sg_desc){
+                // ...
+                .logger = {
+                    .log_cb = my_log,
+                    .user_data = ...,
+                }
+            });
+        ...
+
+    If no overrides are provided, puts will be used on most platforms.
+    On Android, __android_log_write will be used instead.
+
     TODO:
     ====
     - talk about asynchronous resource creation
@@ -959,6 +979,8 @@ typedef enum sg_pixel_format {
     SG_PIXELFORMAT_ETC2_RG11,
     SG_PIXELFORMAT_ETC2_RG11SN,
 
+    SG_PIXELFORMAT_RGB9E5,
+
     _SG_PIXELFORMAT_NUM,
     _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF
 } sg_pixel_format;
@@ -2452,6 +2474,17 @@ typedef struct sg_allocator {
     void* user_data;
 } sg_allocator;
 
+/*
+    sg_logger
+
+    Used in sg_desc to provide custom log callbacks to sokol_gfx.h.
+    Default behavior is SOKOL_LOG(message).
+*/
+typedef struct sg_logger {
+    void (*log_cb)(const char* message, void* user_data);
+    void* user_data;
+} sg_logger;
+
 typedef struct sg_desc {
     uint32_t _start_canary;
     int buffer_pool_size;
@@ -2464,6 +2497,7 @@ typedef struct sg_desc {
     int staging_buffer_size;
     int sampler_cache_size;
     sg_allocator allocator;
+    sg_logger logger; // optional log function override
     sg_context_desc context;
     uint32_t _end_canary;
 } sg_desc;
@@ -2637,7 +2671,7 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
@@ -2656,12 +2690,19 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_
 #ifndef SOKOL_UNREACHABLE
     #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
+
+#if !defined(SOKOL_DEBUG)
+    #define SG_LOG(s)
+#else
+    #define SG_LOG(s) _sg_log(s)
+    #ifndef SOKOL_LOG
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_GFX", s)
+        #else
+            #include <stdio.h>
+            #define SOKOL_LOG(s) puts(s)
+        #endif
     #endif
 #endif
 
@@ -2982,6 +3023,8 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_
         #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D
         #define GL_R11F_G11F_B10F 0x8C3A
         #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
+        #define GL_RGB9_E5 0x8C3D
+        #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
         #define GL_RGBA32UI 0x8D70
         #define GL_RGB32UI 0x8D71
         #define GL_RGBA16UI 0x8D76
@@ -4319,6 +4362,17 @@ _SOKOL_PRIVATE void _sg_free(void* ptr) {
     }
 }
 
+#if defined(SOKOL_DEBUG)
+_SOKOL_PRIVATE void _sg_log(const char* msg) {
+    SOKOL_ASSERT(msg);
+    if (_sg.desc.logger.log_cb) {
+        _sg.desc.logger.log_cb(msg, _sg.desc.logger.user_data);
+    } else {
+        SOKOL_LOG(msg);
+    }
+}
+#endif
+
 _SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) {
     return 0 == str->buf[0];
 }
@@ -4552,6 +4606,7 @@ _SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) {
         case SG_PIXELFORMAT_BGRA8:
         case SG_PIXELFORMAT_RGB10A2:
         case SG_PIXELFORMAT_RG11B10F:
+        case SG_PIXELFORMAT_RGB9E5:
             return 4;
 
         case SG_PIXELFORMAT_RG32UI:
@@ -5414,6 +5469,8 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) {
             return GL_UNSIGNED_INT_2_10_10_10_REV;
         case SG_PIXELFORMAT_RG11B10F:
             return GL_UNSIGNED_INT_10F_11F_11F_REV;
+        case SG_PIXELFORMAT_RGB9E5:
+            return GL_UNSIGNED_INT_5_9_9_9_REV;
         #endif
         case SG_PIXELFORMAT_DEPTH:
             return GL_UNSIGNED_SHORT;
@@ -5483,6 +5540,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) {
                 return GL_RGBA_INTEGER;
         #endif
         case SG_PIXELFORMAT_RG11B10F:
+        case SG_PIXELFORMAT_RGB9E5:
             return GL_RGB;
         case SG_PIXELFORMAT_DEPTH:
             return GL_DEPTH_COMPONENT;
@@ -5571,6 +5629,7 @@ _SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) {
             case SG_PIXELFORMAT_RGBA8SI:    return GL_RGBA8I;
             case SG_PIXELFORMAT_RGB10A2:    return GL_RGB10_A2;
             case SG_PIXELFORMAT_RG11B10F:   return GL_R11F_G11F_B10F;
+            case SG_PIXELFORMAT_RGB9E5:     return GL_RGB9_E5;
             case SG_PIXELFORMAT_RG32UI:     return GL_RG32UI;
             case SG_PIXELFORMAT_RG32SI:     return GL_RG32I;
             case SG_PIXELFORMAT_RG32F:      return GL_RG32F;
@@ -5683,6 +5742,7 @@ _SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) {
     if (!_sg.gl.gles2) {
         _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
         _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
         _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
         _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
         #if !defined(SOKOL_GLES3)
@@ -6560,16 +6620,16 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_
 
     /* check if texture format is support */
     if (!_sg_gl_supported_texture_format(img->cmn.pixel_format)) {
-        SOKOL_LOG("texture format not supported by GL context\n");
+        SG_LOG("texture format not supported by GL context\n");
         return SG_RESOURCESTATE_FAILED;
     }
     /* check for optional texture types */
     if ((img->cmn.type == SG_IMAGETYPE_3D) && !_sg.features.imagetype_3d) {
-        SOKOL_LOG("3D textures not supported by GL context\n");
+        SG_LOG("3D textures not supported by GL context\n");
         return SG_RESOURCESTATE_FAILED;
     }
     if ((img->cmn.type == SG_IMAGETYPE_ARRAY) && !_sg.features.imagetype_array) {
-        SOKOL_LOG("array textures not supported by GL context\n");
+        SG_LOG("array textures not supported by GL context\n");
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -6773,7 +6833,7 @@ _SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* s
         if (log_len > 0) {
             GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len);
             glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf);
-            SOKOL_LOG(log_buf);
+            SG_LOG(log_buf);
             _sg_free(log_buf);
         }
         glDeleteShader(gl_shd);
@@ -6816,7 +6876,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const s
         if (log_len > 0) {
             GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len);
             glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf);
-            SOKOL_LOG(log_buf);
+            SG_LOG(log_buf);
             _sg_free(log_buf);
         }
         glDeleteProgram(gl_prog);
@@ -6964,8 +7024,8 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg
             pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true;
         }
         else {
-            SOKOL_LOG("Vertex attribute not found in shader: ");
-            SOKOL_LOG(_sg_strptr(&shd->gl.attrs[attr_index].name));
+            SG_LOG("Vertex attribute not found in shader: ");
+            SG_LOG(_sg_strptr(&shd->gl.attrs[attr_index].name));
         }
     }
     return SG_RESOURCESTATE_VALID;
@@ -7070,7 +7130,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_
 
     /* check if framebuffer is complete */
     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
-        SOKOL_LOG("Framebuffer completeness check failed!\n");
+        SG_LOG("Framebuffer completeness check failed!\n");
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -7117,7 +7177,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_
                 }
                 /* check if framebuffer is complete */
                 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
-                    SOKOL_LOG("Framebuffer completeness check failed (msaa resolve buffer)!\n");
+                    SG_LOG("Framebuffer completeness check failed (msaa resolve buffer)!\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
                 /* setup color attachments for the framebuffer */
@@ -8294,6 +8354,7 @@ _SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_pixel_format(sg_pixel_format fmt) {
         case SG_PIXELFORMAT_BGRA8:          return DXGI_FORMAT_B8G8R8A8_UNORM;
         case SG_PIXELFORMAT_RGB10A2:        return DXGI_FORMAT_R10G10B10A2_UNORM;
         case SG_PIXELFORMAT_RG11B10F:       return DXGI_FORMAT_R11G11B10_FLOAT;
+        case SG_PIXELFORMAT_RGB9E5:         return DXGI_FORMAT_R9G9B9E5_SHAREDEXP;
         case SG_PIXELFORMAT_RG32UI:         return DXGI_FORMAT_R32G32_UINT;
         case SG_PIXELFORMAT_RG32SI:         return DXGI_FORMAT_R32G32_SINT;
         case SG_PIXELFORMAT_RG32F:          return DXGI_FORMAT_R32G32_FLOAT;
@@ -8626,7 +8687,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, cons
         }
         HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_desc, init_data_ptr, &buf->d3d11.buf);
         if (!(SUCCEEDED(hr) && buf->d3d11.buf)) {
-            SOKOL_LOG("failed to create D3D11 buffer\n");
+            SG_LOG("failed to create D3D11 buffer\n");
             return SG_RESOURCESTATE_FAILED;
         }
     }
@@ -8685,7 +8746,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
         /* create only a depth-texture */
         SOKOL_ASSERT(!injected);
         if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) {
-            SOKOL_LOG("trying to create a D3D11 depth-texture with unsupported pixel format\n");
+            SG_LOG("trying to create a D3D11 depth-texture with unsupported pixel format\n");
             return SG_RESOURCESTATE_FAILED;
         }
         D3D11_TEXTURE2D_DESC d3d11_desc;
@@ -8701,7 +8762,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
         d3d11_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0);
         hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_desc, NULL, &img->d3d11.texds);
         if (!(SUCCEEDED(hr) && img->d3d11.texds)) {
-            SOKOL_LOG("failed to create D3D11 texture 2D\n");
+            SG_LOG("failed to create D3D11 texture 2D\n");
             return SG_RESOURCESTATE_FAILED;
         }
     }
@@ -8765,7 +8826,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
                 }
                 if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) {
                     /* trying to create a texture format that's not supported by D3D */
-                    SOKOL_LOG("trying to create a D3D11 texture with unsupported pixel format\n");
+                    SG_LOG("trying to create a D3D11 texture with unsupported pixel format\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
                 d3d11_tex_desc.SampleDesc.Count = 1;
@@ -8774,7 +8835,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
 
                 hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d);
                 if (!(SUCCEEDED(hr) && img->d3d11.tex2d)) {
-                    SOKOL_LOG("failed to create D3D11 texture 2D\n");
+                    SG_LOG("failed to create D3D11 texture 2D\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
             }
@@ -8803,7 +8864,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
                 }
                 hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex2d, &d3d11_srv_desc, &img->d3d11.srv);
                 if (!(SUCCEEDED(hr) && img->d3d11.srv)) {
-                    SOKOL_LOG("failed to create D3D11 resource view\n");
+                    SG_LOG("failed to create D3D11 resource view\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
             }
@@ -8848,12 +8909,12 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
                 }
                 if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) {
                     /* trying to create a texture format that's not supported by D3D */
-                    SOKOL_LOG("trying to create a D3D11 texture with unsupported pixel format\n");
+                    SG_LOG("trying to create a D3D11 texture with unsupported pixel format\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
                 hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d);
                 if (!(SUCCEEDED(hr) && img->d3d11.tex3d)) {
-                    SOKOL_LOG("failed to create D3D11 texture 3D\n");
+                    SG_LOG("failed to create D3D11 texture 3D\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
             }
@@ -8866,7 +8927,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
                 d3d11_srv_desc.Texture3D.MipLevels = (UINT)img->cmn.num_mipmaps;
                 hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex3d, &d3d11_srv_desc, &img->d3d11.srv);
                 if (!(SUCCEEDED(hr) && img->d3d11.srv)) {
-                    SOKOL_LOG("failed to create D3D11 resource view\n");
+                    SG_LOG("failed to create D3D11 resource view\n");
                     return SG_RESOURCESTATE_FAILED;
                 }
             }
@@ -8888,7 +8949,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
             d3d11_tex_desc.SampleDesc.Quality = (UINT)D3D11_STANDARD_MULTISAMPLE_PATTERN;
             hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, NULL, &img->d3d11.texmsaa);
             if (!(SUCCEEDED(hr) && img->d3d11.texmsaa)) {
-                SOKOL_LOG("failed to create D3D11 texture 2D\n");
+                SG_LOG("failed to create D3D11 texture 2D\n");
                 return SG_RESOURCESTATE_FAILED;
             }
         }
@@ -8920,7 +8981,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const
         d3d11_smp_desc.MaxLOD = desc->max_lod;
         hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &img->d3d11.smp);
         if (!(SUCCEEDED(hr) && img->d3d11.smp)) {
-            SOKOL_LOG("failed to create D3D11 sampler state\n");
+            SG_LOG("failed to create D3D11 sampler state\n");
             return SG_RESOURCESTATE_FAILED;
         }
     }
@@ -8959,7 +9020,7 @@ _SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) {
             _sg.d3d11.d3dcompiler_dll = LoadLibraryA("d3dcompiler_47.dll");
             if (0 == _sg.d3d11.d3dcompiler_dll) {
                 /* don't attempt to load missing DLL in the future */
-                SOKOL_LOG("failed to load d3dcompiler_47.dll!\n");
+                SG_LOG("failed to load d3dcompiler_47.dll!\n");
                 _sg.d3d11.d3dcompiler_dll_load_failed = true;
                 return false;
             }
@@ -8997,7 +9058,7 @@ _SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_stage_desc* st
         &output,    /* ppCode */
         &errors_or_warnings);   /* ppErrorMsgs */
     if (errors_or_warnings) {
-        SOKOL_LOG((LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings));
+        SG_LOG((LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings));
         _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL;
     }
     if (FAILED(hr)) {
@@ -9039,7 +9100,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, cons
             cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
             hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &d3d11_stage->cbufs[ub_index]);
             if (!(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index])) {
-                SOKOL_LOG("failed to create D3D11 buffer\n");
+                SG_LOG("failed to create D3D11 buffer\n");
                 return SG_RESOURCESTATE_FAILED;
             }
         }
@@ -9171,7 +9232,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip,
         shd->d3d11.vs_blob_length,  /* BytecodeLength */
         &pip->d3d11.il);
     if (!(SUCCEEDED(hr) && pip->d3d11.il)) {
-        SOKOL_LOG("failed to create D3D11 input layout\n");
+        SG_LOG("failed to create D3D11 input layout\n");
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -9190,7 +9251,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip,
     rs_desc.AntialiasedLineEnable = FALSE;
     hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs);
     if (!(SUCCEEDED(hr) && pip->d3d11.rs)) {
-        SOKOL_LOG("failed to create D3D11 rasterizer state\n");
+        SG_LOG("failed to create D3D11 rasterizer state\n");
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -9215,7 +9276,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip,
     dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare);
     hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss);
     if (!(SUCCEEDED(hr) && pip->d3d11.dss)) {
-        SOKOL_LOG("failed to create D3D11 depth stencil state\n");
+        SG_LOG("failed to create D3D11 depth stencil state\n");
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -9249,7 +9310,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip,
     }
     hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs);
     if (!(SUCCEEDED(hr) && pip->d3d11.bs)) {
-        SOKOL_LOG("failed to create D3D11 blend state\n");
+        SG_LOG("failed to create D3D11 blend state\n");
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -9330,7 +9391,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima
         SOKOL_ASSERT(d3d11_res);
         HRESULT hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, d3d11_res, &d3d11_rtv_desc, &pass->d3d11.color_atts[i].rtv);
         if (!(SUCCEEDED(hr) && pass->d3d11.color_atts[i].rtv)) {
-            SOKOL_LOG("failed to create D3D11 render target view\n");
+            SG_LOG("failed to create D3D11 render target view\n");
             return SG_RESOURCESTATE_FAILED;
         }
     }
@@ -9363,7 +9424,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_ima
         SOKOL_ASSERT(d3d11_res);
         HRESULT hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, d3d11_res, &d3d11_dsv_desc, &pass->d3d11.ds_att.dsv);
         if (!(SUCCEEDED(hr) && pass->d3d11.ds_att.dsv)) {
-            SOKOL_LOG("failed to create D3D11 depth stencil view\n");
+            SG_LOG("failed to create D3D11 depth stencil view\n");
             return SG_RESOURCESTATE_FAILED;
         }
     }
@@ -9663,7 +9724,7 @@ _SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* d
         memcpy(d3d11_msr.pData, data->ptr, data->size);
         _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0);
     } else {
-        SOKOL_LOG("failed to map buffer while updating!\n");
+        SG_LOG("failed to map buffer while updating!\n");
     }
 }
 
@@ -9679,7 +9740,7 @@ _SOKOL_PRIVATE int _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* da
         memcpy(dst_ptr, data->ptr, data->size);
         _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0);
     } else {
-        SOKOL_LOG("failed to map buffer while appending!\n");
+        SG_LOG("failed to map buffer while appending!\n");
     }
     /* NOTE: this alignment is a requirement from WebGPU, but we want identical behaviour across all backend */
     return _sg_roundup((int)data->size, 4);
@@ -9731,7 +9792,7 @@ _SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data
                     }
                     _sg_d3d11_Unmap(_sg.d3d11.ctx, d3d11_res, subres_index);
                 } else {
-                    SOKOL_LOG("failed to map texture!\n");
+                    SG_LOG("failed to map texture!\n");
                 }
             }
         }
@@ -9850,6 +9911,7 @@ _SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) {
         case SG_PIXELFORMAT_BGRA8:                  return MTLPixelFormatBGRA8Unorm;
         case SG_PIXELFORMAT_RGB10A2:                return MTLPixelFormatRGB10A2Unorm;
         case SG_PIXELFORMAT_RG11B10F:               return MTLPixelFormatRG11B10Float;
+        case SG_PIXELFORMAT_RGB9E5:                 return MTLPixelFormatRGB9E5Float;
         case SG_PIXELFORMAT_RG32UI:                 return MTLPixelFormatRG32Uint;
         case SG_PIXELFORMAT_RG32SI:                 return MTLPixelFormatRG32Sint;
         case SG_PIXELFORMAT_RG32F:                  return MTLPixelFormatRG32Float;
@@ -10345,9 +10407,11 @@ _SOKOL_PRIVATE void _sg_mtl_init_caps(void) {
     _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
     _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
     #if defined(_SG_TARGET_MACOS)
+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
         _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
         _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
     #else
+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
         _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
         _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
     #endif
@@ -10604,7 +10668,7 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc,
     mtl_desc.textureType = _sg_mtl_texture_type(img->cmn.type);
     mtl_desc.pixelFormat = _sg_mtl_pixel_format(img->cmn.pixel_format);
     if (MTLPixelFormatInvalid == mtl_desc.pixelFormat) {
-        SOKOL_LOG("Unsupported texture pixel format!\n");
+        SG_LOG("Unsupported texture pixel format!\n");
         return false;
     }
     mtl_desc.width = (NSUInteger)img->cmn.width;
@@ -10623,6 +10687,9 @@ _SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc,
         mtl_desc.arrayLength = 1;
     }
     mtl_desc.usage = MTLTextureUsageShaderRead;
+    if (img->cmn.render_target) {
+        mtl_desc.usage |= MTLTextureUsageRenderTarget;
+    }
     MTLResourceOptions res_options = 0;
     if (img->cmn.usage != SG_USAGE_IMMUTABLE) {
         res_options |= MTLResourceCPUCacheModeWriteCombined;
@@ -10763,7 +10830,7 @@ _SOKOL_PRIVATE id<MTLLibrary> _sg_mtl_compile_library(const char* src) {
         error:&err
     ];
     if (err) {
-        SOKOL_LOG([err.localizedDescription UTF8String]);
+        SG_LOG([err.localizedDescription UTF8String]);
     }
     return lib;
 }
@@ -10773,7 +10840,7 @@ _SOKOL_PRIVATE id<MTLLibrary> _sg_mtl_library_from_bytecode(const void* ptr, siz
     dispatch_data_t lib_data = dispatch_data_create(ptr, num_bytes, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
     id<MTLLibrary> lib = [_sg.mtl.device newLibraryWithData:lib_data error:&err];
     if (err) {
-        SOKOL_LOG([err.localizedDescription UTF8String]);
+        SG_LOG([err.localizedDescription UTF8String]);
     }
     _SG_OBJC_RELEASE(lib_data);
     return lib;
@@ -10815,11 +10882,11 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const
         goto failed;
     }
     if (nil == vs_func) {
-        SOKOL_LOG("vertex shader entry function not found\n");
+        SG_LOG("vertex shader entry function not found\n");
         goto failed;
     }
     if (nil == fs_func) {
-        SOKOL_LOG("fragment shader entry function not found\n");
+        SG_LOG("fragment shader entry function not found\n");
         goto failed;
     }
     /* it is legal to call _sg_mtl_add_resource with a nil value, this will return a special 0xFFFFFFFF index */
@@ -10909,7 +10976,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s
     rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func);
     SOKOL_ASSERT(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX);
     rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func);
-    rp_desc.sampleCount = (NSUInteger)desc->sample_count;
+    rp_desc.rasterSampleCount = (NSUInteger)desc->sample_count;
     rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled;
     rp_desc.alphaToOneEnabled = NO;
     rp_desc.rasterizationEnabled = YES;
@@ -10943,7 +11010,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _s
     _SG_OBJC_RELEASE(rp_desc);
     if (nil == mtl_rps) {
         SOKOL_ASSERT(err);
-        SOKOL_LOG([err.localizedDescription UTF8String]);
+        SG_LOG([err.localizedDescription UTF8String]);
         return SG_RESOURCESTATE_FAILED;
     }
 
@@ -11665,6 +11732,7 @@ _SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) {
         case SG_PIXELFORMAT_RG16SN:
         case SG_PIXELFORMAT_RGBA16:
         case SG_PIXELFORMAT_RGBA16SN:
+        case SG_PIXELFORMAT_RGB9E5:
         case SG_PIXELFORMAT_PVRTC_RGB_2BPP:
         case SG_PIXELFORMAT_PVRTC_RGB_4BPP:
         case SG_PIXELFORMAT_PVRTC_RGBA_2BPP:
@@ -11812,6 +11880,7 @@ _SOKOL_PRIVATE void _sg_wgpu_init_caps(void) {
     _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
     _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]);
     _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
+    /* FIXME: missing SG_PIXELFORMAT_RG11B10F */
     _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
     _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
     _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]);
@@ -11937,7 +12006,7 @@ _SOKOL_PRIVATE void _sg_wgpu_ubpool_mapped_callback(WGPUBufferMapAsyncStatus sta
     }
     /* FIXME: better handling for this */
     if (WGPUBufferMapAsyncStatus_Success != status) {
-        SOKOL_LOG("Mapping uniform buffer failed!\n");
+        SG_LOG("Mapping uniform buffer failed!\n");
         SOKOL_ASSERT(false);
     }
     SOKOL_ASSERT(data && (data_len == _sg.wgpu.ub.num_bytes));
@@ -12196,7 +12265,7 @@ _SOKOL_PRIVATE uint32_t _sg_wgpu_staging_copy_to_buffer(WGPUBuffer dst_buf, uint
     SOKOL_ASSERT(data_num_bytes > 0);
     uint32_t copy_num_bytes = _sg_roundup(data_num_bytes, 4);
     if ((_sg.wgpu.staging.offset + copy_num_bytes) >= _sg.wgpu.staging.num_bytes) {
-        SOKOL_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_buffer())!\n");
+        SG_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_buffer())!\n");
         return false;
     }
     const int cur = _sg.wgpu.staging.cur;
@@ -12215,7 +12284,7 @@ _SOKOL_PRIVATE bool _sg_wgpu_staging_copy_to_texture(_sg_image_t* img, const sg_
     SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc);
     uint32_t num_bytes = _sg_wgpu_image_data_buffer_size(img);
     if ((_sg.wgpu.staging.offset + num_bytes) >= _sg.wgpu.staging.num_bytes) {
-        SOKOL_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_texture)!\n");
+        SG_LOG("WGPU: Per frame staging buffer full (in _sg_wgpu_staging_copy_to_texture)!\n");
         return false;
     }
     const int cur = _sg.wgpu.staging.cur;
@@ -12353,7 +12422,7 @@ _SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) {
 }
 
 _SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) {
-    SOKOL_LOG("_sg_wgpu_reset_state_cache: FIXME\n");
+    SG_LOG("_sg_wgpu_reset_state_cache: FIXME\n");
 }
 
 _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_context(_sg_context_t* ctx) {
@@ -12369,7 +12438,7 @@ _SOKOL_PRIVATE void _sg_wgpu_destroy_context(_sg_context_t* ctx) {
 
 _SOKOL_PRIVATE void _sg_wgpu_activate_context(_sg_context_t* ctx) {
     (void)ctx;
-    SOKOL_LOG("_sg_wgpu_activate_context: FIXME\n");
+    SG_LOG("_sg_wgpu_activate_context: FIXME\n");
 }
 
 _SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
@@ -14148,14 +14217,14 @@ _SOKOL_PRIVATE void _sg_validate_begin(void) {
 _SOKOL_PRIVATE void _sg_validate(bool cond, _sg_validate_error_t err) {
     if (!cond) {
         _sg.validate_error = err;
-        SOKOL_LOG(_sg_validate_string(err));
+        SG_LOG(_sg_validate_string(err));
     }
 }
 
 _SOKOL_PRIVATE bool _sg_validate_end(void) {
     if (_sg.validate_error != _SG_VALIDATE_SUCCESS) {
         #if !defined(SOKOL_VALIDATE_NON_FATAL)
-            SOKOL_LOG("^^^^  SOKOL-GFX VALIDATION FAILED, TERMINATING ^^^^");
+            SG_LOG("^^^^  SOKOL-GFX VALIDATION FAILED, TERMINATING ^^^^");
             SOKOL_ASSERT(false);
         #endif
         return false;
@@ -15132,7 +15201,7 @@ _SOKOL_PRIVATE bool _sg_uninit_buffer(sg_buffer buf_id) {
             return true;
         }
         else {
-            SOKOL_LOG("_sg_uninit_buffer: active context mismatch (must be same as for creation)");
+            SG_LOG("_sg_uninit_buffer: active context mismatch (must be same as for creation)");
             _SG_TRACE_NOARGS(err_context_mismatch);
         }
     }
@@ -15148,7 +15217,7 @@ _SOKOL_PRIVATE bool _sg_uninit_image(sg_image img_id) {
             return true;
         }
         else {
-            SOKOL_LOG("_sg_uninit_image: active context mismatch (must be same as for creation)");
+            SG_LOG("_sg_uninit_image: active context mismatch (must be same as for creation)");
             _SG_TRACE_NOARGS(err_context_mismatch);
         }
     }
@@ -15164,7 +15233,7 @@ _SOKOL_PRIVATE bool _sg_uninit_shader(sg_shader shd_id) {
             return true;
         }
         else {
-            SOKOL_LOG("_sg_uninit_shader: active context mismatch (must be same as for creation)");
+            SG_LOG("_sg_uninit_shader: active context mismatch (must be same as for creation)");
             _SG_TRACE_NOARGS(err_context_mismatch);
         }
     }
@@ -15180,7 +15249,7 @@ _SOKOL_PRIVATE bool _sg_uninit_pipeline(sg_pipeline pip_id) {
             return true;
         }
         else {
-            SOKOL_LOG("_sg_uninit_pipeline: active context mismatch (must be same as for creation)");
+            SG_LOG("_sg_uninit_pipeline: active context mismatch (must be same as for creation)");
             _SG_TRACE_NOARGS(err_context_mismatch);
         }
     }
@@ -15196,7 +15265,7 @@ _SOKOL_PRIVATE bool _sg_uninit_pass(sg_pass pass_id) {
             return true;
         }
         else {
-            SOKOL_LOG("_sg_uninit_pass: active context mismatch (must be same as for creation)");
+            SG_LOG("_sg_uninit_pass: active context mismatch (must be same as for creation)");
             _SG_TRACE_NOARGS(err_context_mismatch);
         }
     }
@@ -15343,7 +15412,7 @@ SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace
         _sg.hooks = *trace_hooks;
     #else
         static sg_trace_hooks old_hooks;
-        SOKOL_LOG("sg_install_trace_hooks() called, but SG_TRACE_HOOKS is not defined!");
+        SG_LOG("sg_install_trace_hooks() called, but SG_TRACE_HOOKS is not defined!");
     #endif
     return old_hooks;
 }
@@ -15580,7 +15649,7 @@ SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) {
         _sg_init_buffer(buf_id, &desc_def);
     }
     else {
-        SOKOL_LOG("buffer pool exhausted!");
+        SG_LOG("buffer pool exhausted!");
         _SG_TRACE_NOARGS(err_buffer_pool_exhausted);
     }
     _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id);
@@ -15596,7 +15665,7 @@ SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) {
         _sg_init_image(img_id, &desc_def);
     }
     else {
-        SOKOL_LOG("image pool exhausted!");
+        SG_LOG("image pool exhausted!");
         _SG_TRACE_NOARGS(err_image_pool_exhausted);
     }
     _SG_TRACE_ARGS(make_image, &desc_def, img_id);
@@ -15612,7 +15681,7 @@ SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) {
         _sg_init_shader(shd_id, &desc_def);
     }
     else {
-        SOKOL_LOG("shader pool exhausted!");
+        SG_LOG("shader pool exhausted!");
         _SG_TRACE_NOARGS(err_shader_pool_exhausted);
     }
     _SG_TRACE_ARGS(make_shader, &desc_def, shd_id);
@@ -15628,7 +15697,7 @@ SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) {
         _sg_init_pipeline(pip_id, &desc_def);
     }
     else {
-        SOKOL_LOG("pipeline pool exhausted!");
+        SG_LOG("pipeline pool exhausted!");
         _SG_TRACE_NOARGS(err_pipeline_pool_exhausted);
     }
     _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id);
@@ -15644,7 +15713,7 @@ SOKOL_API_IMPL sg_pass sg_make_pass(const sg_pass_desc* desc) {
         _sg_init_pass(pass_id, &desc_def);
     }
     else {
-        SOKOL_LOG("pass pool exhausted!");
+        SG_LOG("pass pool exhausted!");
         _SG_TRACE_NOARGS(err_pass_pool_exhausted);
     }
     _SG_TRACE_ARGS(make_pass, &desc_def, pass_id);
@@ -15881,7 +15950,7 @@ SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instance
     SOKOL_ASSERT(num_instances >= 0);
     #if defined(SOKOL_DEBUG)
         if (!_sg.bindings_valid) {
-            SOKOL_LOG("attempting to draw without resource bindings");
+            SG_LOG("attempting to draw without resource bindings");
         }
     #endif
     if (!_sg.pass_valid) {

+ 10 - 1
tests/CMakeLists.txt

@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD 17)  # needed for UWP
 # SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU, SOKOL_DUMMY
 set(SOKOL_BACKEND "SOKOL_DUMMY_BACKEND" CACHE STRING "Select 3D backend API")
 set_property(CACHE SOKOL_BACKEND PROPERTY STRINGS SOKOL_GLCORE33 SOKOL_METAL SOKOL_D3D11 SOKOL_DUMMY_BACKEND)
+option(SOKOL_FORCE_EGL "Force EGL with GLCORE33 backend" OFF)
 option(USE_ARC "Enable/disable ARC" OFF)
 option(USE_ANALYZER "Enable/disable clang analyzer" OFF)
 
@@ -29,6 +30,7 @@ else()
 endif()
 
 message(">> SOKOL_BACKEND: ${SOKOL_BACKEND}")
+message(">> SOKOL_FORCE_EGL: ${SOKOL_FORCE_EGL}")
 if (OSX_IOS OR OSX_MACOS)
     if (USE_ARC)
         message(">> ObjC ARC ENABLED")
@@ -85,7 +87,11 @@ elseif (ANDROID)
 elseif (LINUX)
     set(THREADS_PREFER_PTHREAD_FLAG ON)
     find_package(Threads REQUIRED)
-    set(system_libs ${system_libs} X11 Xi Xcursor GL asound dl m Threads::Threads)
+    if ((SOKOL_BACKEND STREQUAL SOKOL_GLES3) OR SOKOL_FORCE_EGL)
+        set(system_libs ${system_libs} X11 Xi Xcursor EGL GL asound dl m Threads::Threads)
+    else()
+        set(system_libs ${system_libs} X11 Xi Xcursor GL asound dl m Threads::Threads)
+    endif()
 elseif (OSX_MACOS)
     set(exe_type MACOSX_BUNDLE)
     if (USE_ARC)
@@ -105,6 +111,9 @@ elseif (WINDOWS)
 endif()
 
 macro(configure_common target)
+    if (SOKOL_FORCE_EGL)
+        target_compile_definitions(${target} PRIVATE SOKOL_FORCE_EGL)
+    endif()
     target_compile_definitions(${target} PRIVATE ${SOKOL_BACKEND})
     target_link_options(${target} PRIVATE ${link_flags})
     target_link_libraries(${target} PRIVATE ${system_libs})

+ 2 - 1
tests/analyze_linux.sh

@@ -3,4 +3,5 @@ set -e
 source test_common.sh
 prepare
 
-analyze linux_gl_analyze SOKOL_GLCORE33 Debug
+analyze linux_gl_analyze SOKOL_GLCORE33 Debug
+analyze linux_gles3_analyze SOKOL_GLES3 Debug

+ 10 - 0
tests/test_common.sh

@@ -44,6 +44,16 @@ build() {
     cd ../..
 }
 
+build_force_egl() {
+    cfg=$1
+    backend=$2
+    mode=$3
+    mkdir -p build/$cfg && cd build/$cfg
+    cmake -GNinja -DSOKOL_BACKEND=$backend -DSOKOL_FORCE_EGL=ON -DCMAKE_BUILD_TYPE=$mode ../..
+    cmake --build .
+    cd ../..
+}
+
 analyze() {
     cfg=$1
     backend=$2

+ 4 - 0
tests/test_linux.sh

@@ -4,5 +4,9 @@ source test_common.sh
 prepare
 build linux_gl_debug SOKOL_GLCORE33 Debug
 build linux_gl_release SOKOL_GLCORE33 Release
+build linux_gles3_debug SOKOL_GLES3 Debug
+build linux_gles3_release SOKOL_GLES3 Release
+build_force_egl linux_gl_egl_debug SOKOL_GLCORE33 Debug
+build_force_egl linux_gl_egl_release SOKOL_GLCORE33 Release
 runtest linux_gl_debug
 

+ 61 - 10
util/sokol_debugtext.h

@@ -31,7 +31,6 @@
     SOKOL_DEBUGTEXT_API_DECL    - public function declaration prefix (default: extern)
     SOKOL_API_DECL      - same as SOKOL_DEBUGTEXT_API_DECL
     SOKOL_API_IMPL      - public function implementation prefix (default: -)
-    SOKOL_LOG(msg)      - your own logging function (default: puts(msg))
     SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false))
 
     If sokol_debugtext.h is compiled as a DLL, define the following before
@@ -396,6 +395,27 @@
     If no overrides are provided, malloc and free will be used.
 
 
+    LOG FUNCTION OVERRIDE
+    =====================
+    You can override the log function at initialization time like this:
+
+        void my_log(const char* message, void* user_data) {
+            printf("sdtx says: \s\n", message);
+        }
+
+        ...
+            sdtx_setup(&(sdtx_desc_t){
+                // ...
+                .logger = {
+                    .log_cb = my_log,
+                    .user_data = ...,
+                }
+            });
+        ...
+
+    If no overrides are provided, puts will be used on most platforms.
+    On Android, __android_log_write will be used instead.
+
     LICENSE
     =======
     zlib/libpng license
@@ -543,6 +563,17 @@ typedef struct sdtx_allocator_t {
     void* user_data;
 } sdtx_allocator_t;
 
+/*
+    sdtx_logger_t
+
+    Used in sdtx_desc_t to provide custom log callbacks to sokol_debugtext.h.
+    Default behavior is SOKOL_LOG(message).
+*/
+typedef struct sdtx_logger_t {
+    void (*log_cb)(const char* message, void* user_data);
+    void* user_data;
+} sdtx_logger_t;
+
 /*
     sdtx_desc_t
 
@@ -565,6 +596,7 @@ typedef struct sdtx_desc_t {
     sdtx_font_desc_t fonts[SDTX_MAX_FONTS]; // up to 8 fonts descriptions
     sdtx_context_desc_t context;            // the default context creation parameters
     sdtx_allocator_t allocator;             // optional memory allocation overrides (default: malloc/free)
+    sdtx_logger_t logger;                   // optional log override functions (default: SOKOL_LOG(message))
 } sdtx_desc_t;
 
 /* initialization/shutdown */
@@ -648,21 +680,29 @@ inline sdtx_context sdtx_make_context(const sdtx_context_desc_t& desc) { return
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
+
+#if !defined(SOKOL_DEBUG)
+    #define SDTX_LOG(s)
+#else
+    #define SDTX_LOG(s) _sdtx_log(s)
+    #ifndef SOKOL_LOG
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_DEBUGTEXT", s)
+        #else
+            #include <stdio.h>
+            #define SOKOL_LOG(s) puts(s)
+        #endif
     #endif
 #endif
+
 #ifndef SOKOL_UNREACHABLE
     #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
 #endif
@@ -3510,6 +3550,17 @@ static void _sdtx_free(void* ptr) {
     }
 }
 
+#if defined(SOKOL_DEBUG)
+static void _sdtx_log(const char* msg) {
+    SOKOL_ASSERT(msg);
+    if (_sdtx.desc.logger.log_cb) {
+        _sdtx.desc.logger.log_cb(msg, _sdtx.desc.logger.user_data);
+    } else {
+        SOKOL_LOG(msg);
+    }
+}
+#endif
+
 /*=== CONTEXT POOL ===========================================================*/
 static void _sdtx_init_pool(_sdtx_pool_t* pool, int num) {
     SOKOL_ASSERT(pool && (num >= 1));
@@ -4017,7 +4068,7 @@ SOKOL_API_IMPL sdtx_context sdtx_make_context(const sdtx_context_desc_t* desc) {
         _sdtx_init_context(ctx_id, desc);
     }
     else {
-        SOKOL_LOG("sokol_debugtext.h: context pool exhausted!");
+        SDTX_LOG("sokol_debugtext.h: context pool exhausted!");
     }
     return ctx_id;
 }
@@ -4025,7 +4076,7 @@ SOKOL_API_IMPL sdtx_context sdtx_make_context(const sdtx_context_desc_t* desc) {
 SOKOL_API_IMPL void sdtx_destroy_context(sdtx_context ctx_id) {
     SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
     if (_sdtx_is_default_context(ctx_id)) {
-        SOKOL_LOG("sokol_debugtext.h: cannot destroy default context");
+        SDTX_LOG("sokol_debugtext.h: cannot destroy default context");
         return;
     }
     _sdtx_destroy_context(ctx_id);

+ 1 - 10
util/sokol_fontstash.h

@@ -31,7 +31,6 @@
     SOKOL_FONTSTASH_API_DECL    - public function declaration prefix (default: extern)
     SOKOL_API_DECL      - same as SOKOL_FONTSTASH_API_DECL
     SOKOL_API_IMPL      - public function implementation prefix (default: -)
-    SOKOL_LOG(msg)      - your own logging function (default: puts(msg))
     SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false))
 
     Include the following headers before including sokol_fontstash.h:
@@ -266,21 +265,13 @@ SOKOL_FONTSTASH_API_DECL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, ui
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
-    #endif
-#endif
 #ifndef SOKOL_UNREACHABLE
     #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
 #endif

+ 1 - 0
util/sokol_gfx_imgui.h

@@ -1153,6 +1153,7 @@ _SOKOL_PRIVATE const char* _sg_imgui_pixelformat_string(sg_pixel_format fmt) {
         case SG_PIXELFORMAT_ETC2_RGBA8: return "SG_PIXELFORMAT_ETC2_RGBA8";
         case SG_PIXELFORMAT_ETC2_RG11: return "SG_PIXELFORMAT_ETC2_RG11";
         case SG_PIXELFORMAT_ETC2_RG11SN: return "SG_PIXELFORMAT_ETC2_RG11SN";
+        case SG_PIXELFORMAT_RGB9E5: return "SG_PIXELFORMAT_RGB9E5";
         default: return "???";
     }
 }

+ 63 - 15
util/sokol_gl.h

@@ -30,7 +30,6 @@
     SOKOL_GL_API_DECL   - public function declaration prefix (default: extern)
     SOKOL_API_DECL      - same as SOKOL_GL_API_DECL
     SOKOL_API_IMPL      - public function implementation prefix (default: -)
-    SOKOL_LOG(msg)      - your own logging function (default: puts(msg))
     SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false))
 
     If sokol_gl.h is compiled as a DLL, define the following before
@@ -573,6 +572,28 @@
     If no overrides are provided, malloc and free will be used.
 
 
+    LOG FUNCTION OVERRIDE
+    =====================
+    You can override the log function at initialization time like this:
+
+        void my_log(const char* message, void* user_data) {
+            printf("sgl says: \s\n", message);
+        }
+
+        ...
+            sgl_setup(&(sgl_desc_t){
+                // ...
+                .logger = {
+                    .log_cb = my_log,
+                    .user_data = ...,
+                }
+            });
+        ...
+
+    If no overrides are provided, puts will be used on most platforms.
+    On Android, __android_log_write will be used instead.
+
+
     LICENSE
     =======
     zlib/libpng license
@@ -675,6 +696,17 @@ typedef struct sgl_allocator_t {
     void* user_data;
 } sgl_allocator_t;
 
+/*
+    sgl_logger_t
+
+    Used in sgl_desc_t to provide custom log callbacks to sokol_gl.h.
+    Default behavior is SOKOL_LOG(message).
+*/
+typedef struct sgl_logger_t {
+    void (*log_cb)(const char* message, void* user_data);
+    void* user_data;
+} sgl_logger_t;
+
 typedef struct sgl_desc_t {
     int max_vertices;               // default: 64k
     int max_commands;               // default: 16k
@@ -685,6 +717,7 @@ typedef struct sgl_desc_t {
     int sample_count;
     sg_face_winding face_winding;   // default: SG_FACEWINDING_CCW
     sgl_allocator_t allocator;      // optional memory allocation overrides (default: malloc/free)
+    sgl_logger_t logger;            // optional memory allocation overrides (default: SOKOL_LOG(message))
 } sgl_desc_t;
 
 /* the default context handle */
@@ -823,24 +856,28 @@ inline sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
-    #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
-    #else
-        #define SOKOL_LOG(s)
+
+#if !defined(SOKOL_DEBUG)
+    #define SGL_LOG(s)
+#else
+    #define SGL_LOG(s) _sgl_log(s)
+    #ifndef SOKOL_LOG
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_GL", s)
+        #else
+            #include <stdio.h>
+            #define SOKOL_LOG(s) puts(s)
+        #endif
     #endif
 #endif
-#ifndef SOKOL_UNREACHABLE
-    #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
-#endif
 
 #define _sgl_def(val, def) (((val) == 0) ? (def) : (val))
 #define _SGL_INIT_COOKIE (0xABCDABCD)
@@ -2340,6 +2377,17 @@ static void _sgl_free(void* ptr) {
     }
 }
 
+#if defined(SOKOL_DEBUG)
+static void _sgl_log(const char* msg) {
+    SOKOL_ASSERT(msg);
+    if (_sgl.desc.logger.log_cb) {
+        _sgl.desc.logger.log_cb(msg, _sgl.desc.logger.user_data);
+    } else {
+        SOKOL_LOG(msg);
+    }
+}
+#endif
+
 static void _sgl_init_pool(_sgl_pool_t* pool, int num) {
     SOKOL_ASSERT(pool && (num >= 1));
     /* slot 0 is reserved for the 'invalid id', so bump the pool size by 1 */
@@ -2576,7 +2624,7 @@ static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_d
         else {
             pip->pip[i] = sg_make_pipeline(&desc);
             if (pip->pip[i].id == SG_INVALID_ID) {
-                SOKOL_LOG("sokol_gl.h: failed to create pipeline object");
+                SGL_LOG("sokol_gl.h: failed to create pipeline object");
                 pip->slot.state = SG_RESOURCESTATE_FAILED;
             }
         }
@@ -2590,7 +2638,7 @@ static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc, const sgl_c
         _sgl_init_pipeline(pip_id, desc, ctx_desc);
     }
     else {
-        SOKOL_LOG("sokol_gl.h: pipeline pool exhausted!");
+        SGL_LOG("sokol_gl.h: pipeline pool exhausted!");
     }
     return pip_id;
 }
@@ -2717,7 +2765,7 @@ static sgl_context _sgl_make_context(const sgl_context_desc_t* desc) {
         _sgl_init_context(ctx_id, desc);
     }
     else {
-        SOKOL_LOG("sokol_gl.h: context pool exhausted!");
+        SGL_LOG("sokol_gl.h: context pool exhausted!");
     }
     return ctx_id;
 }
@@ -3298,7 +3346,7 @@ SOKOL_API_IMPL sgl_context sgl_make_context(const sgl_context_desc_t* desc) {
 SOKOL_API_IMPL void sgl_destroy_context(sgl_context ctx_id) {
     SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie);
     if (_sgl_is_default_context(ctx_id)) {
-        SOKOL_LOG("sokol_gl.h: cannot destroy default context");
+        SGL_LOG("sokol_gl.h: cannot destroy default context");
         return;
     }
     _sgl_destroy_context(ctx_id);

+ 1 - 1
util/sokol_imgui.h

@@ -379,7 +379,7 @@ inline void simgui_new_frame(const simgui_frame_desc_t& desc) { return simgui_ne
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT

+ 1 - 1
util/sokol_memtrack.h

@@ -115,7 +115,7 @@ SOKOL_MEMTRACK_API_DECL void smemtrack_free(void* ptr, void* user_data);
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef _SOKOL_PRIVATE

+ 1 - 1
util/sokol_nuklear.h

@@ -267,7 +267,7 @@ inline void snk_setup(const snk_desc_t& desc) { return snk_setup(&desc); }
 #endif
 #ifndef SOKOL_DEBUG
     #ifndef NDEBUG
-        #define SOKOL_DEBUG (1)
+        #define SOKOL_DEBUG
     #endif
 #endif
 #ifndef SOKOL_ASSERT

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно