瀏覽代碼

Merge branch 'master' of https://github.com/floooh/sokol

Garett Bass 4 年之前
父節點
當前提交
d84f79130d
共有 8 個文件被更改,包括 343 次插入56 次删除
  1. 3 0
      .github/workflows/main.yml
  2. 25 3
      CHANGELOG.md
  3. 1 1
      README.md
  4. 130 10
      sokol_app.h
  5. 92 2
      sokol_audio.h
  6. 3 3
      sokol_gfx.h
  7. 86 34
      util/sokol_imgui.h
  8. 3 3
      util/sokol_shape.h

+ 3 - 0
.github/workflows/main.yml

@@ -185,6 +185,9 @@ jobs:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v1
+        - uses: actions/setup-java@v1
+          with:
+            java-version: '8'
         - name: prepare
           run: |
             mkdir workspace

+ 25 - 3
CHANGELOG.md

@@ -1,8 +1,30 @@
 ## Updates
 
-> NOTE: this list will usually only be updated with changes that affect the public APIs
-
-- **13-Fev-2021**: A new utility header [sokol_nuklear.h](https://github.com/floooh/sokol/blob/master/util/sokol_nuklear.h)
+- **22-Feb-2021**: Mouse input latency in sokol_app.h's macOS backend has been
+  quite significantly reduced, please see the detailed explanation [in this
+  PR](https://github.com/floooh/sokol/pull/483). Many thanks to @randrew for
+  the PR!
+
+- **19-Feb-2021**: sokol_app.h learned some Windows-specific config options
+to redirect stdout/stderr to the parent terminal or a separate console
+window, and allow outputting UTF-8 encoded text. For details, search for
+"WINDOWS CONSOLE OUTPUT" in
+[sokol_app.h](https://github.com/floooh/sokol/blob/master/sokol_app.h). Many
+thanks to @garettbass for the initial PR!
+
+- **17-Feb-2021**: When compiled for iOS, the sokol_audio.h CoreAudio backend now
+uses the **AVAudioSession** class to activate and deactivate audio output as needed.
+This fixes sokol_audio.h for iPhones (so far, sokol_audio.h accidentally only worked
+for iPads). Please see [this issue](https://github.com/floooh/sokol/issues/431) for details.
+A somewhat unfortunate side effect of this fix is that sokol_audio.h must now be compiled
+as Objective-C when targetting iOS, also note that a new framework must be linked: ```AVFoundation```.
+Many thanks to @oviano for providing the PR!
+
+- **14-Feb-2021**: The Dear ImGui rendering backend in [sokol_imgui.h](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h) has been rewritten to only do a single
+buffer-update per frame each for vertex- and index-data. This addresses performance-problems
+with sg_append_buffer() in the GL backend on some platforms (see [this issue](https://github.com/floooh/sokol/issues/399) for details.
+
+- **13-Feb-2021**: A new utility header [sokol_nuklear.h](https://github.com/floooh/sokol/blob/master/util/sokol_nuklear.h)
 has been added which implements a rendering backend for [Nuklear](https://github.com/Immediate-Mode-UI/Nuklear)
 on top of sokol_gfx.h. Also see the new sample [nuklear-sapp](https://floooh.github.io/sokol-html5/nuklear-sapp.html).
 Many thanks to **@wmerrifield** for the PR!

+ 1 - 1
README.md

@@ -6,7 +6,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) (**12-Feb-2021**: new utility header **sokol_nuklear.h**)
+[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**22-Feb-2021** sokol_app.h: mouse latency reduction on macOS)
 
 ## Examples and Related Projects
 

+ 130 - 10
sokol_app.h

@@ -828,6 +828,36 @@
 
     NOTE: SOKOL_NO_ENTRY is currently not supported on Android.
 
+    WINDOWS CONSOLE OUTPUT
+    ======================
+    On Windows, regular windowed applications don't show any stdout/stderr text
+    output, which can be a bit of a hassle for printf() debugging or generally
+    logging text to the console. Also, console output by default uses a local
+    codepage setting and thus international UTF-8 encoded text is printed
+    as garbage.
+
+    To help with these issues, sokol_app.h can be configured at startup
+    via the following Windows-specific sapp_desc flags:
+
+        sapp_desc.win32_console_utf8 (default: false)
+            When set to true, the output console codepage will be switched
+            to UTF-8 (and restored to the original codepage on exit)
+
+        sapp_desc.win32_console_attach (default: false)
+            When set to true, stdout and stderr will be attached to the
+            console of the parent process (if the parent process actually
+            has a console). This means that if the application was started
+            in a command line window, stdout and stderr output will be printed
+            to the terminal, just like a regular command line program. But if
+            the application is started via double-click, it will behave like
+            a regular UI application, and stdout/stderr will not be visible.
+
+        sapp_desc.win32_console_create (default: false)
+            When set to true, a new console window will be created and
+            stdout/stderr will be redirected to that console window. It
+            doesn't matter if the application is started from the command
+            line or via double-click.
+
     TEMP NOTE DUMP
     ==============
     - onscreen keyboard support on Android requires Java :(, should we even bother?
@@ -1120,13 +1150,17 @@ typedef struct sapp_desc {
     int max_dropped_files;              /* max number of dropped files to process (default: 1) */
     int max_dropped_file_path_length;   /* max length in bytes of a dropped UTF-8 file path (default: 2048) */
 
+    /* backend-specific options */
+    bool gl_force_gles2;                /* if true, setup GLES2/WebGL even if GLES3/WebGL2 is available */
+    bool win32_console_utf8;            /* if true, set the output console codepage to UTF-8 */
+    bool win32_console_create;          /* if true, attach stdout/stderr to a new console window */
+    bool win32_console_attach;          /* if true, attach stdout/stderr to parent process */
     const char* html5_canvas_name;      /* the name (id) of the HTML5 canvas element, default is "canvas" */
     bool html5_canvas_resize;           /* if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked */
     bool html5_preserve_drawing_buffer; /* HTML5 only: whether to preserve default framebuffer content between frames */
     bool html5_premultiplied_alpha;     /* HTML5 only: whether the rendered pixels use premultiplied alpha convention */
     bool html5_ask_leave_site;          /* initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) */
     bool ios_keyboard_resizes_canvas;   /* if true, showing the iOS keyboard shrinks the canvas */
-    bool gl_force_gles2;                /* if true, setup GLES2/WebGL even if GLES3/WebGL2 is available */
 } sapp_desc;
 
 /* HTML5 specific: request and response structs for
@@ -1454,6 +1488,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
         #pragma warning(disable:4055)   /* 'type cast': from data pointer */
         #pragma warning(disable:4505)   /* unreferenced local function has been removed */
         #pragma warning(disable:4115)   /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */
+        #pragma warning(disable:4996)   /* 'freopen': This function or variable may be unsafe. */
     #endif
     #ifndef WIN32_LEAN_AND_MEAN
         #define WIN32_LEAN_AND_MEAN
@@ -1464,9 +1499,14 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
     #include <windows.h>
     #include <windowsx.h>
     #include <shellapi.h>
-    #if !defined(SOKOL_WIN32_FORCE_MAIN)
-        #pragma comment (linker, "/subsystem:windows")
+    #if !defined(SOKOL_NO_ENTRY)    // if SOKOL_NO_ENTRY is defined, it's the applications' responsibility to use the right subsystem
+        #if defined(SOKOL_WIN32_FORCE_MAIN)
+            #pragma comment (linker, "/subsystem:console")
+        #else
+            #pragma comment (linker, "/subsystem:windows")
+        #endif
     #endif
+    #include <stdio.h>  /* freopen() */
 
     #pragma comment (lib, "kernel32")
     #pragma comment (lib, "user32")
@@ -1694,6 +1734,7 @@ typedef struct {
 typedef struct {
     HWND hwnd;
     HDC dc;
+    UINT orig_codepage;
     LONG mouse_locked_x, mouse_locked_y;
     bool is_win10_or_greater;
     bool in_create_window;
@@ -2917,9 +2958,9 @@ _SOKOL_PRIVATE void _sapp_macos_update_window_title(void) {
     [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]];
 }
 
-_SOKOL_PRIVATE void _sapp_macos_update_mouse(void) {
+_SOKOL_PRIVATE void _sapp_macos_update_mouse(NSEvent* event) {
     if (!_sapp.mouse.locked) {
-        const NSPoint mouse_pos = [_sapp.macos.window mouseLocationOutsideOfEventStream];
+        const NSPoint mouse_pos = event.locationInWindow;
         float new_x = mouse_pos.x * _sapp.dpi_scale;
         float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1;
         /* don't update dx/dy in the very first update */
@@ -2961,19 +3002,16 @@ _SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) {
         stack with calls to sapp_show_mouse()
     */
     if (_sapp.mouse.locked) {
-        [NSEvent setMouseCoalescingEnabled:NO];
         CGAssociateMouseAndMouseCursorPosition(NO);
         CGDisplayHideCursor(kCGDirectMainDisplay);
     }
     else {
         CGDisplayShowCursor(kCGDirectMainDisplay);
         CGAssociateMouseAndMouseCursorPosition(YES);
-        [NSEvent setMouseCoalescingEnabled:YES];
     }
 }
 
 _SOKOL_PRIVATE void _sapp_macos_frame(void) {
-    _sapp_macos_update_mouse();
     _sapp_frame();
     if (_sapp.quit_requested || _sapp.quit_ordered) {
         [_sapp.macos.window performClose:nil];
@@ -3083,6 +3121,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     }
     [_sapp.macos.window makeKeyAndOrderFront:nil];
     _sapp_macos_update_dimensions();
+    [NSEvent setMouseCoalescingEnabled:NO];
 }
 
 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender {
@@ -3156,7 +3195,9 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
                             backing:(NSBackingStoreType)backingStoreType
                               defer:(BOOL)flag {
     if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) {
-        [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
+        #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+            [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
+        #endif
     }
     return self;
 }
@@ -3170,6 +3211,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
 }
 
 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+    #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
     NSPasteboard *pboard = [sender draggingPasteboard];
     if ([pboard.types containsObject:NSPasteboardTypeFileURL]) {
         _sapp_clear_drop_buffer();
@@ -3195,6 +3237,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
         }
         return YES;
     }
+    #endif
     return NO;
 }
 @end
@@ -3227,8 +3270,44 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
 }
 #endif
 
+_SOKOL_PRIVATE void _sapp_macos_poll_input_events() {
+    const NSEventMask mask = NSEventMaskLeftMouseDown |
+                             NSEventMaskLeftMouseUp|
+                             NSEventMaskRightMouseDown |
+                             NSEventMaskRightMouseUp |
+                             NSEventMaskMouseMoved |
+                             NSEventMaskLeftMouseDragged |
+                             NSEventMaskRightMouseDragged |
+                             NSEventMaskMouseEntered |
+                             NSEventMaskMouseExited |
+                             NSEventMaskKeyDown |
+                             NSEventMaskKeyUp |
+                             NSEventMaskCursorUpdate |
+                             NSEventMaskScrollWheel |
+                             NSEventMaskTabletPoint |
+                             NSEventMaskTabletProximity |
+                             NSEventMaskOtherMouseDown |
+                             NSEventMaskOtherMouseUp |
+                             NSEventMaskOtherMouseDragged |
+                             NSEventMaskPressure |
+                             NSEventMaskDirectTouch;
+    @autoreleasepool {
+        for (;;) {
+            // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend,
+            // see: https://github.com/floooh/sokol/issues/486
+            NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES];
+            if (event == nil) {
+                break;
+            }
+            [NSApp sendEvent:event];
+        }
+    }
+}
+
 - (void)drawRect:(NSRect)rect {
     _SOKOL_UNUSED(rect);
+    /* Catch any last-moment input events */
+    _sapp_macos_poll_input_events();
     _sapp_macos_frame();
     #if !defined(SOKOL_METAL)
     [[_sapp.macos.view openGLContext] flushBuffer];
@@ -3260,6 +3339,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     [super updateTrackingAreas];
 }
 - (void)mouseEntered:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     /* don't send mouse enter/leave while dragging (so that it behaves the same as
        on Windows while SetCapture is active
     */
@@ -3268,39 +3348,47 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     }
 }
 - (void)mouseExited:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (0 == _sapp.macos.mouse_buttons) {
         _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mod(event.modifierFlags));
     }
 }
 - (void)mouseDown:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mod(event.modifierFlags));
     _sapp.macos.mouse_buttons |= (1<<SAPP_MOUSEBUTTON_LEFT);
 }
 - (void)mouseUp:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mod(event.modifierFlags));
     _sapp.macos.mouse_buttons &= ~(1<<SAPP_MOUSEBUTTON_LEFT);
 }
 - (void)rightMouseDown:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_RIGHT, _sapp_macos_mod(event.modifierFlags));
     _sapp.macos.mouse_buttons |= (1<<SAPP_MOUSEBUTTON_RIGHT);
 }
 - (void)rightMouseUp:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_RIGHT, _sapp_macos_mod(event.modifierFlags));
     _sapp.macos.mouse_buttons &= ~(1<<SAPP_MOUSEBUTTON_RIGHT);
 }
 - (void)otherMouseDown:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (2 == event.buttonNumber) {
         _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_MIDDLE, _sapp_macos_mod(event.modifierFlags));
         _sapp.macos.mouse_buttons |= (1<<SAPP_MOUSEBUTTON_MIDDLE);
     }
 }
 - (void)otherMouseUp:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (2 == event.buttonNumber) {
         _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_MIDDLE, _sapp_macos_mod(event.modifierFlags));
         _sapp.macos.mouse_buttons &= (1<<SAPP_MOUSEBUTTON_MIDDLE);
     }
 }
 - (void)otherMouseDragged:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (2 == event.buttonNumber) {
         if (_sapp.mouse.locked) {
             _sapp.mouse.dx = [event deltaX];
@@ -3310,6 +3398,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     }
 }
 - (void)mouseMoved:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (_sapp.mouse.locked) {
         _sapp.mouse.dx = [event deltaX];
         _sapp.mouse.dy = [event deltaY];
@@ -3317,6 +3406,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID , _sapp_macos_mod(event.modifierFlags));
 }
 - (void)mouseDragged:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (_sapp.mouse.locked) {
         _sapp.mouse.dx = [event deltaX];
         _sapp.mouse.dy = [event deltaY];
@@ -3324,6 +3414,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID , _sapp_macos_mod(event.modifierFlags));
 }
 - (void)rightMouseDragged:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (_sapp.mouse.locked) {
         _sapp.mouse.dx = [event deltaX];
         _sapp.mouse.dy = [event deltaY];
@@ -3331,6 +3422,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
     _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mod(event.modifierFlags));
 }
 - (void)scrollWheel:(NSEvent*)event {
+    _sapp_macos_update_mouse(event);
     if (_sapp_events_enabled()) {
         float dx = (float) event.scrollingDeltaX;
         float dy = (float) event.scrollingDeltaY;
@@ -3574,7 +3666,7 @@ _SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) {
         _sapp.ios.view.device = _sapp.ios.mtl_device;
         _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
         _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
-        _sapp.ios.view.sampleCount = _sapp.sample_count;
+        _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count;
         if (_sapp.desc.high_dpi) {
             _sapp.ios.view.contentScaleFactor = 2.0;
         }
@@ -6090,6 +6182,32 @@ _SOKOL_PRIVATE void _sapp_win32_destroy_window(void) {
     UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL));
 }
 
+_SOKOL_PRIVATE void _sapp_win32_init_console(void) {
+    if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) {
+        BOOL con_valid = FALSE;
+        if (_sapp.desc.win32_console_create) {
+            con_valid = AllocConsole();
+        }
+        else if (_sapp.desc.win32_console_attach) {
+            con_valid = AttachConsole(ATTACH_PARENT_PROCESS);
+        }
+        if (con_valid) {
+            freopen("CON", "w", stdout);
+            freopen("CON", "w", stderr);
+        }
+    }
+    if (_sapp.desc.win32_console_utf8) {
+        _sapp.win32.orig_codepage = GetConsoleOutputCP();
+        SetConsoleOutputCP(CP_UTF8);
+    }
+}
+
+_SOKOL_PRIVATE void _sapp_win32_restore_console(void) {
+    if (_sapp.desc.win32_console_utf8) {
+        SetConsoleOutputCP(_sapp.win32.orig_codepage);
+    }
+}
+
 _SOKOL_PRIVATE void _sapp_win32_init_dpi(void) {
 
     typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void);
@@ -6240,6 +6358,7 @@ _SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) {
 
 _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) {
     _sapp_init_state(desc);
+    _sapp_win32_init_console();
     _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater();
     _sapp_win32_uwp_init_keytable();
     _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide));
@@ -6303,6 +6422,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) {
         _sapp_wgl_shutdown();
     #endif
     _sapp_win32_destroy_window();
+    _sapp_win32_restore_console();
     _sapp_discard_state();
 }
 

+ 92 - 2
sokol_audio.h

@@ -41,7 +41,8 @@
 
     - Windows: WASAPI
     - Linux: ALSA (link with asound)
-    - macOS/iOS: CoreAudio (link with AudioToolbox)
+    - macOS: CoreAudio (link with AudioToolbox)
+    - iOS: CoreAudio+AVAudioSession (link with AudioToolbox and AVFoundation)
     - emscripten: WebAudio with ScriptProcessorNode
     - Android: OpenSLES (link with OpenSLES)
 
@@ -307,9 +308,13 @@
     THE COREAUDIO BACKEND
     =====================
     The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined).
-    Since the CoreAudio API is implemented in C (not Objective-C) the
+    Since the CoreAudio API is implemented in C (not Objective-C) on macOS the
     implementation part of Sokol Audio can be included into a C source file.
 
+    However on iOS, Sokol Audio must be compiled as Objective-C due to it's
+    reliance on the AVAudioSession object. The iOS code path support both
+    being compiled with or without ARC (Automatic Reference Counting).
+
     For thread synchronisation, the CoreAudio backend will use the
     pthread_mutex_* functions.
 
@@ -509,7 +514,16 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
 #if defined(SOKOL_DUMMY_BACKEND)
     // No audio API needed for SOKOL_DUMMY_BACKEND
 #elif defined(__APPLE__)
+    #include <TargetConditionals.h>
     #include <AudioToolbox/AudioToolbox.h>
+    #if TARGET_OS_IOS
+        #if !defined(__cplusplus)
+            #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
+                #error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
+            #endif
+        #endif
+        #include <AVFoundation/AVFoundation.h>
+    #endif
 #elif (defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
     #define ALSA_PCM_NEW_HW_PARAMS_API
     #include <alsa/asoundlib.h>
@@ -597,6 +611,9 @@ typedef struct {
 
 typedef struct {
     AudioQueueRef ca_audio_queue;
+    #if TARGET_OS_IOS
+    id ca_interruption_handler;
+    #endif
 } _saudio_backend_t;
 
 /*=== ALSA BACKEND DECLARATIONS ==============================================*/
@@ -972,6 +989,57 @@ _SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
 /*=== COREAUDIO BACKEND IMPLEMENTATION =======================================*/
 #elif defined(__APPLE__)
 
+#if TARGET_OS_IOS
+#if __has_feature(objc_arc)
+#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; }
+#else
+#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
+#endif
+
+@interface _saudio_interruption_handler : NSObject { }
+@end
+
+@implementation _saudio_interruption_handler
+-(id)init {
+    self = [super init];
+    AVAudioSession* session = [AVAudioSession sharedInstance];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session];
+    return self;
+}
+
+-(void)dealloc {
+    [self remove_handler];
+    #if !__has_feature(objc_arc)
+    [super dealloc];
+    #endif
+}
+
+-(void)remove_handler {
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil];
+}
+
+-(void)handle_interruption:(NSNotification*)notification {
+    AVAudioSession* session = [AVAudioSession sharedInstance];
+    SOKOL_ASSERT(session);
+    NSDictionary* dict = notification.userInfo;
+    SOKOL_ASSERT(dict);
+    NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
+    switch (type) {
+        case AVAudioSessionInterruptionTypeBegan:
+            AudioQueuePause(_saudio.backend.ca_audio_queue);
+            [session setActive:false error:nil];
+            break;
+        case AVAudioSessionInterruptionTypeEnded:
+            [session setActive:true error:nil];
+            AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
+            break;
+        default:
+            break;
+    }
+}
+@end
+#endif
+
 /* NOTE: the buffer data callback is called on a separate thread! */
 _SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, AudioQueueRef queue, AudioQueueBufferRef buffer) {
     _SOKOL_UNUSED(user_data);
@@ -994,6 +1062,17 @@ _SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, AudioQueueRef qu
 _SOKOL_PRIVATE bool _saudio_backend_init(void) {
     SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue);
 
+    #if TARGET_OS_IOS
+    /* activate audio session */
+    AVAudioSession* session = [AVAudioSession sharedInstance];
+    SOKOL_ASSERT(session != nil);
+    [session setCategory: AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
+    [session setActive:true error:nil];
+
+    /* create interruption handler */
+    _saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init];
+    #endif
+
     /* create an audio queue with fp32 samples */
     AudioStreamBasicDescription fmt;
     memset(&fmt, 0, sizeof(fmt));
@@ -1033,6 +1112,17 @@ _SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
     AudioQueueStop(_saudio.backend.ca_audio_queue, true);
     AudioQueueDispose(_saudio.backend.ca_audio_queue, false);
     _saudio.backend.ca_audio_queue = NULL;
+    #if TARGET_OS_IOS
+    /* remove interruption handler */
+    if (_saudio.backend.ca_interruption_handler != nil) {
+        [_saudio.backend.ca_interruption_handler remove_handler];
+        _SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler);
+    }
+    /* deactivate audio session */
+    AVAudioSession* session = [AVAudioSession sharedInstance];
+    SOKOL_ASSERT(session);
+    [session setActive:false error:nil];;
+    #endif
 }
 
 /*=== ALSA BACKEND IMPLEMENTATION ============================================*/

+ 3 - 3
sokol_gfx.h

@@ -99,17 +99,17 @@
 
     --- start rendering to the default frame buffer with:
 
-            sg_begin_default_pass(const sg_pass_action* actions, int width, int height)
+            sg_begin_default_pass(const sg_pass_action* action, int width, int height)
 
         ...or alternatively with:
 
-            sg_begin_default_passf(const sg_pass_action* actions, float width, float height)
+            sg_begin_default_passf(const sg_pass_action* action, float width, float height)
 
         ...which takes the framebuffer width and height as float values.
 
     --- or start rendering to an offscreen framebuffer with:
 
-            sg_begin_pass(sg_pass pass, const sg_pass_action* actions)
+            sg_begin_pass(sg_pass pass, const sg_pass_action* action)
 
     --- set the pipeline state for the next draw call with:
 

+ 86 - 34
util/sokol_imgui.h

@@ -42,6 +42,8 @@
     to override defaults:
 
     SOKOL_ASSERT(c)     - your own assert macro (default: assert(c))
+    SOKOL_MALLOC(s)     - your own malloc function (default: malloc(s))
+    SOKOL_FREE(p)       - your own free function (default: free(p))
     SOKOL_IMGUI_API_DECL- public function declaration prefix (default: extern)
     SOKOL_API_DECL      - same as SOKOL_IMGUI_API_DECL
     SOKOL_API_IMPL      - public function implementation prefix (default: -)
@@ -286,6 +288,11 @@ inline void simgui_setup(const simgui_desc_t& desc) { return simgui_setup(&desc)
     #include <assert.h>
     #define SOKOL_ASSERT(c) assert(c)
 #endif
+#ifndef SOKOL_MALLOC
+    #include <stdlib.h>
+    #define SOKOL_MALLOC(s) malloc(s)
+    #define SOKOL_FREE(p) free(p)
+#endif
 #ifndef _SOKOL_PRIVATE
     #if defined(__GNUC__) || defined(__clang__)
         #define _SOKOL_PRIVATE __attribute__((unused)) static
@@ -312,6 +319,10 @@ typedef struct {
     sg_shader shd;
     sg_pipeline pip;
     bool is_osx;    // return true if running on OSX (or HTML5 OSX), needed for copy/paste
+
+    sg_range vertices;
+    sg_range indices;
+
     #if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
     bool btn_down[SAPP_MAX_MOUSEBUTTONS];
     bool btn_up[SAPP_MAX_MOUSEBUTTONS];
@@ -1617,6 +1628,15 @@ SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) {
        since sokol_gfx.h will do its own default-value handling
     */
 
+    /* allocate an intermediate vertex- and index-buffer */
+    SOKOL_ASSERT(_simgui.desc.max_vertices > 0);
+    _simgui.vertices.size = (size_t)_simgui.desc.max_vertices * sizeof(ImDrawVert);
+    _simgui.vertices.ptr = SOKOL_MALLOC(_simgui.vertices.size);
+    SOKOL_ASSERT(_simgui.vertices.ptr);
+    _simgui.indices.size = (size_t)_simgui.desc.max_vertices * 3 * sizeof(ImDrawIdx);
+    _simgui.indices.ptr = SOKOL_MALLOC(_simgui.indices.size);
+    SOKOL_ASSERT(_simgui.indices.ptr);
+
     /* initialize Dear ImGui */
     #if defined(__cplusplus)
         ImGui::CreateContext();
@@ -1672,7 +1692,7 @@ SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) {
     sg_buffer_desc vb_desc;
     memset(&vb_desc, 0, sizeof(vb_desc));
     vb_desc.usage = SG_USAGE_STREAM;
-    vb_desc.size = (size_t)_simgui.desc.max_vertices * sizeof(ImDrawVert);
+    vb_desc.size = _simgui.vertices.size;
     vb_desc.label = "sokol-imgui-vertices";
     _simgui.vbuf = sg_make_buffer(&vb_desc);
 
@@ -1680,7 +1700,7 @@ SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) {
     memset(&ib_desc, 0, sizeof(ib_desc));
     ib_desc.type = SG_BUFFERTYPE_INDEXBUFFER;
     ib_desc.usage = SG_USAGE_STREAM;
-    ib_desc.size = (size_t)_simgui.desc.max_vertices * 3 * sizeof(uint16_t);
+    ib_desc.size = _simgui.indices.size;
     ib_desc.label = "sokol-imgui-indices";
     _simgui.ibuf = sg_make_buffer(&ib_desc);
 
@@ -1814,6 +1834,10 @@ SOKOL_API_IMPL void simgui_shutdown(void) {
     sg_destroy_buffer(_simgui.ibuf);
     sg_destroy_buffer(_simgui.vbuf);
     sg_pop_debug_group();
+    SOKOL_ASSERT(_simgui.vertices.ptr);
+    SOKOL_FREE((void*)_simgui.vertices.ptr);
+    SOKOL_ASSERT(_simgui.indices.ptr);
+    SOKOL_FREE((void*)_simgui.indices.ptr);
 }
 
 #if !defined(SOKOL_IMGUI_NO_SOKOL_APP)
@@ -1887,10 +1911,58 @@ SOKOL_API_IMPL void simgui_render(void) {
     if (draw_data->CmdListsCount == 0) {
         return;
     }
+    /* copy vertices and indices into an intermediate buffer so that
+       they can be updated with a single sg_update_buffer() call each
+       (sg_append_buffer() has performance problems on some GL platforms),
+       also keep track of valid number of command lists in case of a
+       buffer overflow
+    */
+    size_t all_vtx_size = 0;
+    size_t all_idx_size = 0;
+    int cmd_list_count = 0;
+    for (int cl_index = 0; cl_index < draw_data->CmdListsCount; cl_index++, cmd_list_count++) {
+        ImDrawList* cl = draw_data->CmdLists[cl_index];
+        #if defined(__cplusplus)
+            const size_t vtx_size = cl->VtxBuffer.size() * sizeof(ImDrawVert);
+            const size_t idx_size = cl->IdxBuffer.size() * sizeof(ImDrawIdx);
+            const ImDrawVert* vtx_ptr = &cl->VtxBuffer.front();
+            const ImDrawIdx* idx_ptr = &cl->IdxBuffer.front();
+        #else
+            const size_t vtx_size = (size_t)cl->VtxBuffer.Size * sizeof(ImDrawVert);
+            const size_t idx_size = (size_t)cl->IdxBuffer.Size * sizeof(ImDrawIdx);
+            const ImDrawVert* vtx_ptr = cl->VtxBuffer.Data;
+            const ImDrawIdx* idx_ptr = cl->IdxBuffer.Data;
+        #endif
+
+        /* check for buffer overflow */
+        if (((all_vtx_size + vtx_size) > _simgui.vertices.size) ||
+            ((all_idx_size + idx_size) > _simgui.indices.size))
+        {
+            break;
+        }
+
+        /* copy vertices and indices into common buffers */
+        void* dst_vtx_ptr = (void*) (((uint8_t*)_simgui.vertices.ptr) + all_vtx_size);
+        void* dst_idx_ptr = (void*) (((uint8_t*)_simgui.indices.ptr) + all_idx_size);
+        memcpy(dst_vtx_ptr, vtx_ptr, vtx_size);
+        memcpy(dst_idx_ptr, idx_ptr, idx_size);
+        all_vtx_size += vtx_size;
+        all_idx_size += idx_size;
+    }
+    if (0 == cmd_list_count) {
+        return;
+    }
 
-    /* render the ImGui command list */
+    /* update the sokol-gfx vertex- and index-buffer */
     sg_push_debug_group("sokol-imgui");
+    sg_range vtx_data = _simgui.vertices;
+    vtx_data.size = all_vtx_size;
+    sg_range idx_data = _simgui.indices;
+    idx_data.size = all_idx_size;
+    sg_update_buffer(_simgui.vbuf, &vtx_data);
+    sg_update_buffer(_simgui.ibuf, &idx_data);
 
+    /* render the ImGui command list */
     const float dpi_scale = _simgui.desc.dpi_scale;
     const int fb_width = (int) (io->DisplaySize.x * dpi_scale);
     const int fb_height = (int) (io->DisplaySize.y * dpi_scale);
@@ -1911,38 +1983,9 @@ SOKOL_API_IMPL void simgui_render(void) {
     bind.fs_images[0].id = (uint32_t)(uintptr_t)tex_id;
     int vb_offset = 0;
     int ib_offset = 0;
-    for (int cl_index = 0; cl_index < draw_data->CmdListsCount; cl_index++) {
-        ImDrawList* cl = draw_data->CmdLists[cl_index];
+    for (int cl_index = 0; cl_index < cmd_list_count; cl_index++) {
+        const ImDrawList* cl = draw_data->CmdLists[cl_index];
 
-        /* append vertices and indices to buffers, record start offsets in draw state */
-        #if defined(__cplusplus)
-            const size_t vtx_size = cl->VtxBuffer.size() * sizeof(ImDrawVert);
-            const size_t idx_size = cl->IdxBuffer.size() * sizeof(ImDrawIdx);
-            const ImDrawVert* vtx_ptr = &cl->VtxBuffer.front();
-            const ImDrawIdx* idx_ptr = &cl->IdxBuffer.front();
-        #else
-            const size_t vtx_size = (size_t)cl->VtxBuffer.Size * sizeof(ImDrawVert);
-            const size_t idx_size = (size_t)cl->IdxBuffer.Size * sizeof(ImDrawIdx);
-            const ImDrawVert* vtx_ptr = cl->VtxBuffer.Data;
-            const ImDrawIdx* idx_ptr = cl->IdxBuffer.Data;
-        #endif
-        if (vtx_ptr) {
-            const sg_range vtx_range = { vtx_ptr, vtx_size };
-            vb_offset = sg_append_buffer(bind.vertex_buffers[0], &vtx_range);
-        }
-        if (idx_ptr) {
-            const sg_range idx_range = { idx_ptr, idx_size };
-            ib_offset = sg_append_buffer(bind.index_buffer, &idx_range);
-        }
-        /* don't render anything if the buffer is in overflow state (this is also
-            checked internally in sokol_gfx, draw calls that attempt to draw with
-            overflowed buffers will be silently dropped)
-        */
-        if (sg_query_buffer_overflow(bind.vertex_buffers[0]) ||
-            sg_query_buffer_overflow(bind.index_buffer))
-        {
-            break;
-        }
         bind.vertex_buffer_offsets[0] = vb_offset;
         bind.index_buffer_offset = ib_offset;
         sg_apply_bindings(&bind);
@@ -1981,6 +2024,15 @@ SOKOL_API_IMPL void simgui_render(void) {
             }
             base_element += (int)pcmd->ElemCount;
         }
+        #if defined(__cplusplus)
+            const size_t vtx_size = cl->VtxBuffer.size() * sizeof(ImDrawVert);
+            const size_t idx_size = cl->IdxBuffer.size() * sizeof(ImDrawIdx);
+        #else
+            const size_t vtx_size = (size_t)cl->VtxBuffer.Size * sizeof(ImDrawVert);
+            const size_t idx_size = (size_t)cl->IdxBuffer.Size * sizeof(ImDrawIdx);
+        #endif
+        vb_offset += (int)vtx_size;
+        ib_offset += (int)idx_size;
     }
     sg_apply_viewport(0, 0, fb_width, fb_height, true);
     sg_apply_scissor_rect(0, 0, fb_width, fb_height, true);

+ 3 - 3
util/sokol_shape.h

@@ -36,7 +36,7 @@
     ================
     sokol_shape.h creates vertices and indices for simple shapes and
     builds structs which can be plugged into sokol-gfx resource
-    creation descriptor structs.
+    creation functions:
 
     The following shape types are supported:
 
@@ -154,7 +154,7 @@
     You can also provide additional creation parameters, like a common vertex
     color, a debug-helper to randomize colors, tell the shape builder function
     to merge the new shape with the previous shape into the same draw-element-range,
-    or a 4x4 transform matrix to move, rotate and scale the generated matrices:
+    or a 4x4 transform matrix to move, rotate and scale the generated vertices:
 
     ```c
     sshape_buffer_t buf = ...;
@@ -179,7 +179,7 @@
     assert(buf.valid);
     ```
 
-    The following helper functions are offered to build a packed
+    The following helper functions can be used to build a packed
     color value or to convert from external matrix types:
 
     ```c