Browse Source

Replace SOKOL_LOG with runtime callbacks.

Also removes SOKOL_LOG from sokol_fontstash.h and sokol_args.h because it's not used there.
Manuel Floruß 3 years ago
parent
commit
3797da8c7f
8 changed files with 585 additions and 222 deletions
  1. 134 72
      sokol_app.h
  2. 1 11
      sokol_args.h
  3. 95 31
      sokol_audio.h
  4. 81 18
      sokol_fetch.h
  5. 131 63
      sokol_gfx.h
  6. 71 7
      util/sokol_debugtext.h
  7. 0 9
      util/sokol_fontstash.h
  8. 72 11
      util/sokol_gl.h

+ 134 - 72
sokol_app.h

@@ -28,7 +28,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_ABORT()       - called after an unrecoverable error (default: abort())
         SOKOL_WIN32_FORCE_MAIN  - define this on Win32 to use a main() entry point instead of WinMain
@@ -204,7 +203,7 @@
             The fail callback is called when a fatal error is encountered
             during start which doesn't allow the program to continue.
             Providing a callback here gives you a chance to show an error message
-            to the user. The default behaviour is SOKOL_LOG(msg)
+            to the user. The default behaviour is SAPP_LOG(msg)
 
         As you can see, those 'standard callbacks' don't have a user_data
         argument, so any data that needs to be preserved between callbacks
@@ -1029,7 +1028,7 @@
                 .allocator = {
                     .alloc = my_alloc,
                     .free = my_free,
-                    .user_data = ...;
+                    .user_data = ...,
                 }
             };
         }
@@ -1039,6 +1038,29 @@
     This only affects memory allocation calls done by sokol_app.h
     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("sapp says: \s\n", message);
+        }
+
+        sapp_desc sokol_main(int argc, char* argv[]) {
+            return (sapp_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.
+
+
     TEMP NOTE DUMP
     ==============
     - onscreen keyboard support on Android requires Java :(, should we even bother?
@@ -1424,6 +1446,17 @@ typedef struct sapp_allocator {
     void* user_data;
 } sapp_allocator;
 
+/*
+    sapp_logger
+
+    Used in sapp_desc to provide custom log callbacks to sokol_app.h.
+    Default behavior is SAPP_LOG_DEFAULT(message).
+*/
+typedef struct sapp_logger {
+    void (*log_cb)(const char* message, void* user_data);
+    void* user_data;
+} sapp_logger;
+
 typedef struct sapp_desc {
     void (*init_cb)(void);                  // these are the user-provided callbacks without user data
     void (*frame_cb)(void);
@@ -1453,6 +1486,7 @@ typedef struct sapp_desc {
     int max_dropped_file_path_length;   // max length in bytes of a dropped UTF-8 file path (default: 2048)
     sapp_icon_desc icon;                // the initial window icon to set
     sapp_allocator allocator;           // optional memory allocation overrides (default: malloc/free)
+    sapp_logger logger;                 // optional log callback overrides (default: SAPP_LOG(message))
 
     /* backend-specific options */
     bool gl_force_gles2;                // if true, setup GLES2/WebGL even if GLES3/WebGL2 is available
@@ -1757,19 +1791,35 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
 #ifndef SOKOL_UNREACHABLE
     #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
 #endif
-#ifndef SOKOL_LOG
+
+#if defined(SOKOL_LOG)
+#error "SOKOL_LOG macro is no longer supported, please use sg_desc.logger to override log functions"
+#endif
+#ifndef SOKOL_NO_LOG
     #ifdef SOKOL_DEBUG
+        #define SOKOL_NO_LOG 0
+    #else
+        #define SOKOL_NO_LOG 1
+    #endif
+#endif
+#if !SOKOL_NO_LOG
+    #define SAPP_LOG(s) { SOKOL_ASSERT(s); _sapp_log(s); }
+    #ifndef SAPP_LOG_DEFAULT
         #if defined(__ANDROID__)
             #include <android/log.h>
-            #define SOKOL_LOG(s) { SOKOL_ASSERT(s); __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s); }
+            #define SAPP_LOG_DEFAULT(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s)
         #else
             #include <stdio.h>
-            #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+            #define SAPP_LOG_DEFAULT(s) puts(s)
         #endif
-    #else
-        #define SOKOL_LOG(s)
     #endif
+#else
+    #define SAPP_LOG(s)
 #endif
+#ifndef SAPP_LOG_DEFAULT
+    #define SAPP_LOG_DEFAULT(s)
+#endif
+
 #ifndef SOKOL_ABORT
     #define SOKOL_ABORT() abort()
 #endif
@@ -2719,6 +2769,18 @@ _SOKOL_PRIVATE void _sapp_free(void* ptr) {
     }
 }
 
+#if !SOKOL_NO_LOG
+_SOKOL_PRIVATE void _sapp_log(const char* msg) {
+    SOKOL_ASSERT(msg);
+    if (_sapp.desc.logger.log_cb) {
+        _sapp.desc.logger.log_cb(msg, _sapp.desc.logger.user_data);
+    }
+    else {
+        SAPP_LOG_DEFAULT(msg);
+    }
+}
+#endif
+
 _SOKOL_PRIVATE void _sapp_fail(const char* msg) {
     if (_sapp.desc.fail_cb) {
         _sapp.desc.fail_cb(msg);
@@ -2727,7 +2789,7 @@ _SOKOL_PRIVATE void _sapp_fail(const char* msg) {
         _sapp.desc.fail_userdata_cb(msg, _sapp.desc.user_data);
     }
     else {
-        SOKOL_LOG(msg);
+        SAPP_LOG(msg);
     }
     SOKOL_ABORT();
 }
@@ -2952,7 +3014,7 @@ _SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) {
     SOKOL_ASSERT(desc->pixels.size > 0);
     const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t);
     if (wh_size != desc->pixels.size) {
-        SOKOL_LOG("Image data size mismatch (must be width*height*4 bytes)\n");
+        SAPP_LOG("Image data size mismatch (must be width*height*4 bytes)\n");
         return false;
     }
     return true;
@@ -3777,7 +3839,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
         for (int i = 0; i < _sapp.drop.num_files; i++) {
             NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]];
             if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) {
-                SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n");
+                SAPP_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n");
                 drop_failed = true;
                 break;
             }
@@ -4472,7 +4534,7 @@ EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) {
         return;
     }
     if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) {
-        SOKOL_LOG("sokol_app.h: dropped file path too long!\n");
+        SAPP_LOG("sokol_app.h: dropped file path too long!\n");
         _sapp.drop.num_files = 0;
     }
 }
@@ -6079,7 +6141,7 @@ _SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) {
         // ===
         //
         // ...just retry with the DEBUG flag switched off
-        SOKOL_LOG("sokol_app.h: D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.\n");
+        SAPP_LOG("sokol_app.h: D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.\n");
         create_flags &= ~D3D11_CREATE_DEVICE_DEBUG;
         hr = D3D11CreateDeviceAndSwapChain(
             NULL,                           /* pAdapter (use default) */
@@ -6112,16 +6174,16 @@ _SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) {
                 _SAPP_SAFE_RELEASE(dxgi_factory);
             }
             else {
-                SOKOL_LOG("sokol_app.h: could not obtain IDXGIFactory object.\n");
+                SAPP_LOG("sokol_app.h: could not obtain IDXGIFactory object.\n");
             }
             _SAPP_SAFE_RELEASE(dxgi_adapter);
         }
         else {
-            SOKOL_LOG("sokol_app.h: could not obtain IDXGIAdapter object.\n");
+            SAPP_LOG("sokol_app.h: could not obtain IDXGIAdapter object.\n");
         }
     }
     else {
-        SOKOL_LOG("sokol_app.h: could not obtain IDXGIDevice1 interface\n");
+        SAPP_LOG("sokol_app.h: could not obtain IDXGIDevice1 interface\n");
     }
 }
 
@@ -6670,7 +6732,7 @@ _SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) {
             _sapp.win32.hwnd    // hwndTarget
         };
         if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
-            SOKOL_LOG("RegisterRawInputDevices() failed (on mouse lock).\n");
+            SAPP_LOG("RegisterRawInputDevices() failed (on mouse lock).\n");
         }
         /* in case the raw mouse device only supports absolute position reporting,
            we need to skip the dx/dy compution for the first WM_INPUT event
@@ -6681,7 +6743,7 @@ _SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) {
         /* disable raw input for mouse */
         const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
         if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
-            SOKOL_LOG("RegisterRawInputDevices() failed (on mouse unlock).\n");
+            SAPP_LOG("RegisterRawInputDevices() failed (on mouse unlock).\n");
         }
 
         /* let the mouse roam freely again */
@@ -6821,7 +6883,7 @@ _SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) {
         WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR));
         DragQueryFileW(hdrop, i, buffer, num_chars);
         if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) {
-            SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n");
+            SAPP_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)\n");
             drop_failed = true;
         }
         _sapp_free(buffer);
@@ -6997,7 +7059,7 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM
                     UINT size = sizeof(_sapp.win32.raw_input_data);
                     // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata
                     if ((UINT)-1 == GetRawInputData(ri, RID_INPUT, &_sapp.win32.raw_input_data, &size, sizeof(RAWINPUTHEADER))) {
-                        SOKOL_LOG("GetRawInputData() failed\n");
+                        SAPP_LOG("GetRawInputData() failed\n");
                         break;
                     }
                     const RAWINPUT* raw_mouse_data = (const RAWINPUT*) &_sapp.win32.raw_input_data;
@@ -7348,7 +7410,7 @@ _SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) {
         return _sapp.clipboard.buffer;
     }
     if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) {
-        SOKOL_LOG("sokol_app.h: clipboard string didn't fit into clipboard buffer\n");
+        SAPP_LOG("sokol_app.h: clipboard string didn't fit into clipboard buffer\n");
     }
     GlobalUnlock(object);
     CloseClipboard();
@@ -8700,16 +8762,16 @@ _SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) {
     if (_sapp.android.display != EGL_NO_DISPLAY) {
         eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
         if (_sapp.android.surface != EGL_NO_SURFACE) {
-            SOKOL_LOG("Destroying egl surface");
+            SAPP_LOG("Destroying egl surface");
             eglDestroySurface(_sapp.android.display, _sapp.android.surface);
             _sapp.android.surface = EGL_NO_SURFACE;
         }
         if (_sapp.android.context != EGL_NO_CONTEXT) {
-            SOKOL_LOG("Destroying egl context");
+            SAPP_LOG("Destroying egl context");
             eglDestroyContext(_sapp.android.display, _sapp.android.context);
             _sapp.android.context = EGL_NO_CONTEXT;
         }
-        SOKOL_LOG("Terminating egl display");
+        SAPP_LOG("Terminating egl display");
         eglTerminate(_sapp.android.display);
         _sapp.android.display = EGL_NO_DISPLAY;
     }
@@ -8750,7 +8812,7 @@ _SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) {
 _SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) {
     if (_sapp_events_enabled()) {
         _sapp_init_event(type);
-        SOKOL_LOG("event_cb()");
+        SAPP_LOG("event_cb()");
         _sapp_call_event(&_sapp.event);
     }
 }
@@ -8796,18 +8858,18 @@ _SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool
     _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width;
     if (win_changed || fb_changed || force_update) {
         if (!_sapp.first_frame) {
-            SOKOL_LOG("SAPP_EVENTTYPE_RESIZED");
+            SAPP_LOG("SAPP_EVENTTYPE_RESIZED");
             _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED);
         }
     }
 }
 
 _SOKOL_PRIVATE void _sapp_android_cleanup(void) {
-    SOKOL_LOG("Cleaning up");
+    SAPP_LOG("Cleaning up");
     if (_sapp.android.surface != EGL_NO_SURFACE) {
         /* egl context is bound, cleanup gracefully */
         if (_sapp.init_called && !_sapp.cleanup_called) {
-            SOKOL_LOG("cleanup_cb()");
+            SAPP_LOG("cleanup_cb()");
             _sapp_call_cleanup();
         }
     }
@@ -8844,22 +8906,22 @@ _SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) {
     sapp_event_type type = SAPP_EVENTTYPE_INVALID;
     switch (action) {
         case AMOTION_EVENT_ACTION_DOWN:
-            SOKOL_LOG("Touch: down");
+            SAPP_LOG("Touch: down");
         case AMOTION_EVENT_ACTION_POINTER_DOWN:
-            SOKOL_LOG("Touch: ptr down");
+            SAPP_LOG("Touch: ptr down");
             type = SAPP_EVENTTYPE_TOUCHES_BEGAN;
             break;
         case AMOTION_EVENT_ACTION_MOVE:
             type = SAPP_EVENTTYPE_TOUCHES_MOVED;
             break;
         case AMOTION_EVENT_ACTION_UP:
-            SOKOL_LOG("Touch: up");
+            SAPP_LOG("Touch: up");
         case AMOTION_EVENT_ACTION_POINTER_UP:
-            SOKOL_LOG("Touch: ptr up");
+            SAPP_LOG("Touch: ptr up");
             type = SAPP_EVENTTYPE_TOUCHES_ENDED;
             break;
         case AMOTION_EVENT_ACTION_CANCEL:
-            SOKOL_LOG("Touch: cancel");
+            SAPP_LOG("Touch: cancel");
             type = SAPP_EVENTTYPE_TOUCHES_CANCELLED;
             break;
         default:
@@ -8910,7 +8972,7 @@ _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) {
     _SOKOL_UNUSED(fd);
     _SOKOL_UNUSED(data);
     if ((events & ALOOPER_EVENT_INPUT) == 0) {
-        SOKOL_LOG("_sapp_android_input_cb() encountered unsupported event");
+        SAPP_LOG("_sapp_android_input_cb() encountered unsupported event");
         return 1;
     }
     SOKOL_ASSERT(_sapp.android.current.input);
@@ -8931,13 +8993,13 @@ _SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) {
 _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) {
     _SOKOL_UNUSED(data);
     if ((events & ALOOPER_EVENT_INPUT) == 0) {
-        SOKOL_LOG("_sapp_android_main_cb() encountered unsupported event");
+        SAPP_LOG("_sapp_android_main_cb() encountered unsupported event");
         return 1;
     }
 
     _sapp_android_msg_t msg;
     if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) {
-        SOKOL_LOG("Could not write to read_from_main_fd");
+        SAPP_LOG("Could not write to read_from_main_fd");
         return 1;
     }
 
@@ -8945,7 +9007,7 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) {
     switch (msg) {
         case _SOKOL_ANDROID_MSG_CREATE:
             {
-                SOKOL_LOG("MSG_CREATE");
+                SAPP_LOG("MSG_CREATE");
                 SOKOL_ASSERT(!_sapp.valid);
                 bool result = _sapp_android_init_egl();
                 SOKOL_ASSERT(result); _SOKOL_UNUSED(result);
@@ -8954,36 +9016,36 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) {
             }
             break;
         case _SOKOL_ANDROID_MSG_RESUME:
-            SOKOL_LOG("MSG_RESUME");
+            SAPP_LOG("MSG_RESUME");
             _sapp.android.has_resumed = true;
             _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED);
             break;
         case _SOKOL_ANDROID_MSG_PAUSE:
-            SOKOL_LOG("MSG_PAUSE");
+            SAPP_LOG("MSG_PAUSE");
             _sapp.android.has_resumed = false;
             _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED);
             break;
         case _SOKOL_ANDROID_MSG_FOCUS:
-            SOKOL_LOG("MSG_FOCUS");
+            SAPP_LOG("MSG_FOCUS");
             _sapp.android.has_focus = true;
             break;
         case _SOKOL_ANDROID_MSG_NO_FOCUS:
-            SOKOL_LOG("MSG_NO_FOCUS");
+            SAPP_LOG("MSG_NO_FOCUS");
             _sapp.android.has_focus = false;
             break;
         case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW:
-            SOKOL_LOG("MSG_SET_NATIVE_WINDOW");
+            SAPP_LOG("MSG_SET_NATIVE_WINDOW");
             if (_sapp.android.current.window != _sapp.android.pending.window) {
                 if (_sapp.android.current.window != NULL) {
                     _sapp_android_cleanup_egl_surface();
                 }
                 if (_sapp.android.pending.window != NULL) {
-                    SOKOL_LOG("Creating egl surface ...");
+                    SAPP_LOG("Creating egl surface ...");
                     if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) {
-                        SOKOL_LOG("... ok!");
+                        SAPP_LOG("... ok!");
                         _sapp_android_update_dimensions(_sapp.android.pending.window, true);
                     } else {
-                        SOKOL_LOG("... failed!");
+                        SAPP_LOG("... failed!");
                         _sapp_android_shutdown();
                     }
                 }
@@ -8991,7 +9053,7 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) {
             _sapp.android.current.window = _sapp.android.pending.window;
             break;
         case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE:
-            SOKOL_LOG("MSG_SET_INPUT_QUEUE");
+            SAPP_LOG("MSG_SET_INPUT_QUEUE");
             if (_sapp.android.current.input != _sapp.android.pending.input) {
                 if (_sapp.android.current.input != NULL) {
                     AInputQueue_detachLooper(_sapp.android.current.input);
@@ -9008,13 +9070,13 @@ _SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) {
             _sapp.android.current.input = _sapp.android.pending.input;
             break;
         case _SOKOL_ANDROID_MSG_DESTROY:
-            SOKOL_LOG("MSG_DESTROY");
+            SAPP_LOG("MSG_DESTROY");
             _sapp_android_cleanup();
             _sapp.valid = false;
             _sapp.android.is_thread_stopping = true;
             break;
         default:
-            SOKOL_LOG("Unknown msg type received");
+            SAPP_LOG("Unknown msg type received");
             break;
     }
     pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */
@@ -9032,17 +9094,17 @@ _SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) {
     SOKOL_ASSERT(_sapp.valid);
     /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */
     if (shown) {
-        SOKOL_LOG("Showing keyboard");
+        SAPP_LOG("Showing keyboard");
         ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED);
     } else {
-        SOKOL_LOG("Hiding keyboard");
+        SAPP_LOG("Hiding keyboard");
         ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS);
     }
 }
 
 _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) {
     _SOKOL_UNUSED(arg);
-    SOKOL_LOG("Loop thread started");
+    SAPP_LOG("Loop thread started");
 
     _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/);
     ALooper_addFd(_sapp.android.looper,
@@ -9087,38 +9149,38 @@ _SOKOL_PRIVATE void* _sapp_android_loop(void* arg) {
     _sapp.android.is_thread_stopped = true;
     pthread_cond_broadcast(&_sapp.android.pt.cond);
     pthread_mutex_unlock(&_sapp.android.pt.mutex);
-    SOKOL_LOG("Loop thread done");
+    SAPP_LOG("Loop thread done");
     return NULL;
 }
 
 /* android main/ui thread */
 _SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) {
     if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) {
-        SOKOL_LOG("Could not write to write_from_main_fd");
+        SAPP_LOG("Could not write to write_from_main_fd");
     }
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onStart()");
+    SAPP_LOG("NativeActivity onStart()");
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onResume()");
+    SAPP_LOG("NativeActivity onResume()");
     _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME);
 }
 
 _SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onSaveInstanceState()");
+    SAPP_LOG("NativeActivity onSaveInstanceState()");
     *out_size = 0;
     return NULL;
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onWindowFocusChanged()");
+    SAPP_LOG("NativeActivity onWindowFocusChanged()");
     if (has_focus) {
         _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS);
     } else {
@@ -9128,13 +9190,13 @@ _SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activ
 
 _SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onPause()");
+    SAPP_LOG("NativeActivity onPause()");
     _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE);
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onStop()");
+    SAPP_LOG("NativeActivity onStop()");
 }
 
 _SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) {
@@ -9149,14 +9211,14 @@ _SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) {
 
 _SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onNativeWindowCreated()");
+    SAPP_LOG("NativeActivity onNativeWindowCreated()");
     _sapp_android_msg_set_native_window(window);
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) {
     _SOKOL_UNUSED(activity);
     _SOKOL_UNUSED(window);
-    SOKOL_LOG("NativeActivity onNativeWindowDestroyed()");
+    SAPP_LOG("NativeActivity onNativeWindowDestroyed()");
     _sapp_android_msg_set_native_window(NULL);
 }
 
@@ -9172,26 +9234,26 @@ _SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) {
 
 _SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onInputQueueCreated()");
+    SAPP_LOG("NativeActivity onInputQueueCreated()");
     _sapp_android_msg_set_input_queue(queue);
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) {
     _SOKOL_UNUSED(activity);
     _SOKOL_UNUSED(queue);
-    SOKOL_LOG("NativeActivity onInputQueueDestroyed()");
+    SAPP_LOG("NativeActivity onInputQueueDestroyed()");
     _sapp_android_msg_set_input_queue(NULL);
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onConfigurationChanged()");
+    SAPP_LOG("NativeActivity onConfigurationChanged()");
     /* see android:configChanges in manifest */
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) {
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onLowMemory()");
+    SAPP_LOG("NativeActivity onLowMemory()");
 }
 
 _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) {
@@ -9204,7 +9266,7 @@ _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) {
      * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity?
      */
     _SOKOL_UNUSED(activity);
-    SOKOL_LOG("NativeActivity onDestroy()");
+    SAPP_LOG("NativeActivity onDestroy()");
 
     /* send destroy msg */
     pthread_mutex_lock(&_sapp.android.pt.mutex);
@@ -9221,7 +9283,7 @@ _SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) {
     close(_sapp.android.pt.read_from_main_fd);
     close(_sapp.android.pt.write_from_main_fd);
 
-    SOKOL_LOG("NativeActivity done");
+    SAPP_LOG("NativeActivity done");
 
     /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */
     exit(0);
@@ -9231,7 +9293,7 @@ JNIEXPORT
 void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) {
     _SOKOL_UNUSED(saved_state);
     _SOKOL_UNUSED(saved_state_size);
-    SOKOL_LOG("NativeActivity onCreate()");
+    SAPP_LOG("NativeActivity onCreate()");
 
     // the NativeActity pointer needs to be available inside sokol_main()
     // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state()
@@ -9245,7 +9307,7 @@ void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size
 
     int pipe_fd[2];
     if (pipe(pipe_fd) != 0) {
-        SOKOL_LOG("Could not create thread pipe");
+        SAPP_LOG("Could not create thread pipe");
         return;
     }
     _sapp.android.pt.read_from_main_fd = pipe_fd[0];
@@ -9293,7 +9355,7 @@ void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size
     activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed;
     activity->callbacks->onLowMemory = _sapp_android_on_low_memory;
 
-    SOKOL_LOG("NativeActivity successfully created");
+    SAPP_LOG("NativeActivity successfully created");
 
     /* NOT A BUG: do NOT call sapp_discard_state() */
 }
@@ -11161,7 +11223,7 @@ _SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) {
                 ((src_count == 6) && (src_chr != '/')) ||
                 ((src_count == 7) && (src_chr != '/')))
             {
-                SOKOL_LOG("sokol_app.h: dropped file URI doesn't start with file://");
+                SAPP_LOG("sokol_app.h: dropped file URI doesn't start with file://");
                 err = true;
                 break;
             }
@@ -11194,7 +11256,7 @@ _SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) {
                 *dst_ptr++ = dst_chr;
             }
             else {
-                SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)");
+                SAPP_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)");
                 err = true;
                 break;
             }

+ 1 - 11
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?
@@ -382,14 +380,6 @@ inline void sargs_setup(const sargs_desc& desc) { return sargs_setup(&desc); }
     #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__)

+ 95 - 31
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 SAUDIO_LOG_DEFAULT(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 */
@@ -527,14 +560,34 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
+
+#if defined(SOKOL_LOG)
+#error "SOKOL_LOG macro is no longer supported, please use saudio_desc.logger to override log functions"
+#endif
+#ifndef SOKOL_NO_LOG
     #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+        #define SOKOL_NO_LOG 0
     #else
-        #define SOKOL_LOG(s)
+        #define SOKOL_NO_LOG 1
     #endif
 #endif
+#if !SOKOL_NO_LOG
+    #define SAUDIO_LOG(s) { SOKOL_ASSERT(s); _saudio_log(s); }
+    #ifndef SAUDIO_LOG_DEFAULT
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SAUDIO_LOG_DEFAULT(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_AUDIO", s)
+        #else
+            #include <stdio.h>
+            #define SAUDIO_LOG_DEFAULT(s) puts(s)
+        #endif
+    #endif
+#else
+    #define SAUDIO_LOG(s)
+#endif
+#ifndef SAUDIO_LOG_DEFAULT
+    #define SAUDIO_LOG_DEFAULT(s)
+#endif
 
 #ifndef _SOKOL_PRIVATE
     #if defined(__GNUC__) || defined(__clang__)
@@ -984,6 +1037,17 @@ _SOKOL_PRIVATE void _saudio_free(void* ptr) {
     }
 }
 
+#if !SOKOL_NO_LOG
+_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 {
+        SAUDIO_LOG_DEFAULT(msg);
+    }
+}
+#endif
+
 /*=== MUTEX IMPLEMENTATION ===================================================*/
 #if defined(_SAUDIO_NOTHREADS)
 
@@ -1386,7 +1450,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 +1463,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 +1497,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 +1690,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 +1714,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 +1724,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 +1734,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 +1749,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 +1778,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 +1806,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;
@@ -2069,14 +2133,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 +2152,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 +2258,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;
         }

+ 81 - 18
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
@@ -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 SFETCH_LOG_DEFAULT(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: SFETCH_LOG_DEFAULT(message)) */
 } sfetch_desc_t;
 
 /* a request handle to identify an active fetch request, returned by sfetch_send() */
@@ -1035,13 +1067,33 @@ inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfe
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
+
+#if defined(SOKOL_LOG)
+#error "SOKOL_LOG macro is no longer supported, please use sfetch_desc.logger to override log functions"
+#endif
+#ifndef SOKOL_NO_LOG
     #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+        #define SOKOL_NO_LOG 0
     #else
-        #define SOKOL_LOG(s)
+        #define SOKOL_NO_LOG 1
+    #endif
+#endif
+#if !SOKOL_NO_LOG
+    #define SFETCH_LOG(s) { SOKOL_ASSERT(s); _sfetch_log(s); }
+    #ifndef SFETCH_LOG_DEFAULT
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SFETCH_LOG_DEFAULT(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_FETCH", s)
+        #else
+            #include <stdio.h>
+            #define SFETCH_LOG_DEFAULT(s) puts(s)
+        #endif
     #endif
+#else
+    #define SFETCH_LOG(s)
+#endif
+#ifndef SFETCH_LOG_DEFAULT
+    #define SFETCH_LOG_DEFAULT(s)
 #endif
 
 #ifndef _SOKOL_PRIVATE
@@ -1288,6 +1340,17 @@ _SOKOL_PRIVATE void _sfetch_free(void* ptr) {
     }
 }
 
+#if !SOKOL_NO_LOG
+_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 {
+        SFETCH_LOG_DEFAULT(msg);
+    }
+}
+#endif
+
 _SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) {
     return _sfetch;
 }
@@ -1716,7 +1779,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(
@@ -2234,7 +2297,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 +2442,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 +2506,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 +2578,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)) {

+ 131 - 63
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
@@ -2452,6 +2472,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 SG_LOG_DEFAULT(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 +2495,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;
@@ -2656,13 +2688,38 @@ 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
+
+#if defined(SOKOL_LOG)
+#error "SOKOL_LOG macro is no longer supported, please use sg_desc.logger to override log functions"
+#endif
+#ifndef SOKOL_NO_LOG
     #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+        #define SOKOL_NO_LOG 0
+    #else
+        #define SOKOL_NO_LOG 1
+    #endif
+#endif
+#if !SOKOL_NO_LOG
+    #if defined(__ANDROID__)
+        #include <android/log.h> // __android_log_write
     #else
-        #define SOKOL_LOG(s)
+        #include <stdio.h> // puts
+    #endif
+    #define SG_LOG(s) { SOKOL_ASSERT(s); _sg_log(s); }
+    #ifndef SG_LOG_DEFAULT
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SG_LOG_DEFAULT(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_GFX", s)
+        #else
+            #include <stdio.h>
+            #define SG_LOG_DEFAULT(s) puts(s)
+        #endif
     #endif
+#else
+    #define SG_LOG(s)
+#endif
+#ifndef SG_LOG_DEFAULT
+    #define SG_LOG_DEFAULT(s)
 #endif
 
 #ifndef _SOKOL_PRIVATE
@@ -4319,6 +4376,17 @@ _SOKOL_PRIVATE void _sg_free(void* ptr) {
     }
 }
 
+#if !SOKOL_NO_LOG
+_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 {
+        SG_LOG_DEFAULT(msg);
+    }
+}
+#endif
+
 _SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) {
     return 0 == str->buf[0];
 }
@@ -6560,16 +6628,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 +6841,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 +6884,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 +7032,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 +7138,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 +7185,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 */
@@ -8626,7 +8694,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 +8753,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 +8769,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 +8833,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 +8842,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 +8871,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 +8916,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 +8934,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 +8956,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 +8988,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 +9027,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 +9065,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 +9107,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 +9239,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 +9258,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 +9283,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 +9317,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 +9398,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 +9431,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 +9731,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 +9747,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 +9799,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");
                 }
             }
         }
@@ -10604,7 +10672,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;
@@ -10763,7 +10831,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 +10841,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 +10883,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 */
@@ -10943,7 +11011,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;
     }
 
@@ -11937,7 +12005,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 +12264,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 +12283,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 +12421,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 +12437,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 +14216,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 +15200,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 +15216,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 +15232,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 +15248,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 +15264,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 +15411,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 +15648,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 +15664,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 +15680,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 +15696,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 +15712,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 +15949,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) {

+ 71 - 7
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 SDTX_LOG_DEFAULT(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: SDTX_LOG_DEFAULT(message))
 } sdtx_desc_t;
 
 /* initialization/shutdown */
@@ -655,14 +687,35 @@ inline sdtx_context sdtx_make_context(const sdtx_context_desc_t& desc) { return
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
+
+#if defined(SOKOL_LOG)
+#error "SOKOL_LOG macro is no longer supported, please use sg_desc.logger to override log functions"
+#endif
+#ifndef SOKOL_NO_LOG
     #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+        #define SOKOL_NO_LOG 0
     #else
-        #define SOKOL_LOG(s)
+        #define SOKOL_NO_LOG 1
+    #endif
+#endif
+#if !SOKOL_NO_LOG
+    #define SDTX_LOG(s) { SOKOL_ASSERT(s); _sdtx_log(s); }
+    #ifndef SDTX_LOG_DEFAULT
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SDTX_LOG_DEFAULT(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_DEBUGTEXT", s)
+        #else
+            #include <stdio.h>
+            #define SDTX_LOG_DEFAULT(s) puts(s)
+        #endif
     #endif
+#else
+    #define SDTX_LOG(s)
+#endif
+#ifndef SDTX_LOG_DEFAULT
+    #define SDTX_LOG_DEFAULT(s)
 #endif
+
 #ifndef SOKOL_UNREACHABLE
     #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
 #endif
@@ -3510,6 +3563,17 @@ static void _sdtx_free(void* ptr) {
     }
 }
 
+#if !SOKOL_NO_LOG
+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 {
+        SDTX_LOG_DEFAULT(msg);
+    }
+}
+#endif
+
 /*=== CONTEXT POOL ===========================================================*/
 static void _sdtx_init_pool(_sdtx_pool_t* pool, int num) {
     SOKOL_ASSERT(pool && (num >= 1));
@@ -4017,7 +4081,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 +4089,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);

+ 0 - 9
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:
@@ -273,14 +272,6 @@ SOKOL_FONTSTASH_API_DECL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, ui
     #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

+ 72 - 11
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 SGL_LOG_DEFAULT(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: SGL_LOG_DEFAULT(message))
 } sgl_desc_t;
 
 /* the default context handle */
@@ -830,16 +863,33 @@ inline sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
-#ifndef SOKOL_LOG
+
+#if defined(SOKOL_LOG)
+#error "SOKOL_LOG macro is no longer supported, please use sgl_desc_t.logger to override log functions"
+#endif
+#ifndef SOKOL_NO_LOG
     #ifdef SOKOL_DEBUG
-        #include <stdio.h>
-        #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+        #define SOKOL_NO_LOG 0
     #else
-        #define SOKOL_LOG(s)
+        #define SOKOL_NO_LOG 1
     #endif
 #endif
-#ifndef SOKOL_UNREACHABLE
-    #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
+#if !SOKOL_NO_LOG
+    #define SGL_LOG(s) { SOKOL_ASSERT(s); _sgl_log(s); }
+    #ifndef SGL_LOG_DEFAULT
+        #if defined(__ANDROID__)
+            #include <android/log.h>
+            #define SGL_LOG_DEFAULT(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_GL", s)
+        #else
+            #include <stdio.h>
+            #define SGL_LOG_DEFAULT(s) puts(s)
+        #endif
+    #endif
+#else
+    #define SGL_LOG(s)
+#endif
+#ifndef SGL_LOG_DEFAULT
+    #define SGL_LOG_DEFAULT(s)
 #endif
 
 #define _sgl_def(val, def) (((val) == 0) ? (def) : (val))
@@ -2340,6 +2390,17 @@ static void _sgl_free(void* ptr) {
     }
 }
 
+#if !SOKOL_NO_LOG
+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 {
+        SGL_LOG_DEFAULT(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 +2637,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 +2651,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 +2778,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 +3359,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);