diff options
Diffstat (limited to 'v_windows/v/old/thirdparty/sokol')
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/sokol_app.h | 10475 | ||||
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/sokol_app2.h | 40 | ||||
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/sokol_audio.h | 2147 | ||||
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/sokol_gfx.h | 15672 | ||||
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/sokol_v.h | 20 | ||||
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/util/sokol_fontstash.h | 1785 | ||||
| -rw-r--r-- | v_windows/v/old/thirdparty/sokol/util/sokol_gl.h | 3297 | 
7 files changed, 33436 insertions, 0 deletions
diff --git a/v_windows/v/old/thirdparty/sokol/sokol_app.h b/v_windows/v/old/thirdparty/sokol/sokol_app.h new file mode 100644 index 0000000..ef4ac70 --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/sokol_app.h @@ -0,0 +1,10475 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) +#define SOKOL_APP_IMPL +#endif +#ifndef SOKOL_APP_INCLUDED +/* +    sokol_app.h -- cross-platform application wrapper + +    Project URL: https://github.com/floooh/sokol + +    Do this: +        #define SOKOL_IMPL or +        #define SOKOL_APP_IMPL +    before you include this file in *one* C or C++ file to create the +    implementation. + +    In the same place define one of the following to select the 3D-API +    which should be initialized by sokol_app.h (this must also match +    the backend selected for sokol_gfx.h if both are used in the same +    project): + +        #define SOKOL_GLCORE33 +        #define SOKOL_GLES2 +        #define SOKOL_GLES3 +        #define SOKOL_D3D11 +        #define SOKOL_METAL +        #define SOKOL_WGPU + +    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 +        SOKOL_NO_ENTRY      - define this if sokol_app.h shouldn't "hijack" the main() function +        SOKOL_APP_API_DECL  - public function declaration prefix (default: extern) +        SOKOL_API_DECL      - same as SOKOL_APP_API_DECL +        SOKOL_API_IMPL      - public function implementation prefix (default: -) +        SOKOL_CALLOC        - your own calloc function (default: calloc(n, s)) +        SOKOL_FREE          - your own free function (default: free(p)) + +    Optionally define the following to force debug checks and validations +    even in release mode: + +        SOKOL_DEBUG         - by default this is defined if _DEBUG is defined + +    If sokol_app.h is compiled as a DLL, define the following before +    including the declaration or implementation: + +        SOKOL_DLL + +    On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) +    or __declspec(dllimport) as needed. + +    For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp + +    Portions of the Windows and Linux GL initialization and event code have been +    taken from GLFW (http://www.glfw.org/) + +    iOS onscreen keyboard support 'inspired' by libgdx. + +    Link with the following system libraries: + +    - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit +    - on macOS with GL: Cocoa, QuartzCore, OpenGL +    - on iOS with Metal: Foundation, UIKit, Metal, MetalKit +    - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit +    - on Linux: X11, Xi, Xcursor, GL, dl, pthread, m(?) +    - on Android: GLESv3, EGL, log, android +    - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib +    - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined +        - link with the following libs: -lkernel32 -luser32 -lshell32 +        - additionally with the GL backend: -lgdi32 +        - additionally with the D3D11 backend: -ld3d11 -ldxgi -dxguid + +    On Linux, you also need to use the -pthread compiler and linker option, otherwise weird +    things will happen, see here for details: https://github.com/floooh/sokol/issues/376 + +    Building for UWP requires a recent Visual Studio toolchain and Windows SDK +    (at least VS2019 and Windows SDK 10.0.19041.0). When the UWP backend is +    selected, the sokol_app.h implementation must be compiled as C++17. + +    On macOS and iOS, the implementation must be compiled as Objective-C. + +    FEATURE OVERVIEW +    ================ +    sokol_app.h provides a minimalistic cross-platform API which +    implements the 'application-wrapper' parts of a 3D application: + +    - a common application entry function +    - creates a window and 3D-API context/device with a 'default framebuffer' +    - makes the rendered frame visible +    - provides keyboard-, mouse- and low-level touch-events +    - platforms: MacOS, iOS, HTML5, Win32, Linux, Android (TODO: RaspberryPi) +    - 3D-APIs: Metal, D3D11, GL3.2, GLES2, GLES3, WebGL, WebGL2 + +    FEATURE/PLATFORM MATRIX +    ======================= +                        | Windows | macOS | Linux |  iOS  | Android | UWP  | Raspi | HTML5 +    --------------------+---------+-------+-------+-------+---------+------+-------+------- +    gl 3.x              | YES     | YES   | YES   | ---   | ---     | ---  | ---   | --- +    gles2/webgl         | ---     | ---   | ---   | YES   | YES     | ---  | TODO  | YES +    gles3/webgl2        | ---     | ---   | ---   | YES   | YES     | ---  | ---   | YES +    metal               | ---     | YES   | ---   | YES   | ---     | ---  | ---   | --- +    d3d11               | YES     | ---   | ---   | ---   | ---     | YES  | ---   | --- +    KEY_DOWN            | YES     | YES   | YES   | SOME  | TODO    | YES  | TODO  | YES +    KEY_UP              | YES     | YES   | YES   | SOME  | TODO    | YES  | TODO  | YES +    CHAR                | YES     | YES   | YES   | YES   | TODO    | YES  | TODO  | YES +    MOUSE_DOWN          | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    MOUSE_UP            | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    MOUSE_SCROLL        | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    MOUSE_MOVE          | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    MOUSE_ENTER         | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    MOUSE_LEAVE         | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    TOUCHES_BEGAN       | ---     | ---   | ---   | YES   | YES     | TODO | ---   | YES +    TOUCHES_MOVED       | ---     | ---   | ---   | YES   | YES     | TODO | ---   | YES +    TOUCHES_ENDED       | ---     | ---   | ---   | YES   | YES     | TODO | ---   | YES +    TOUCHES_CANCELLED   | ---     | ---   | ---   | YES   | YES     | TODO | ---   | YES +    RESIZED             | YES     | YES   | YES   | YES   | YES     | YES  | ---   | YES +    ICONIFIED           | YES     | YES   | YES   | ---   | ---     | YES  | ---   | --- +    RESTORED            | YES     | YES   | YES   | ---   | ---     | YES  | ---   | --- +    SUSPENDED           | ---     | ---   | ---   | YES   | YES     | YES  | ---   | TODO +    RESUMED             | ---     | ---   | ---   | YES   | YES     | YES  | ---   | TODO +    QUIT_REQUESTED      | YES     | YES   | YES   | ---   | ---     | ---  | TODO  | YES +    UPDATE_CURSOR       | YES     | YES   | TODO  | ---   | ---     | TODO | ---   | TODO +    IME                 | TODO    | TODO? | TODO  | ???   | TODO    | ---  | ???   | ??? +    key repeat flag     | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    windowed            | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | YES +    fullscreen          | YES     | YES   | YES   | YES   | YES     | YES  | TODO  | --- +    mouse hide          | YES     | YES   | YES   | ---   | ---     | YES  | TODO  | TODO +    mouse lock          | YES     | YES   | YES   | ---   | ---     | TODO | TODO  | YES +    screen keyboard     | ---     | ---   | ---   | YES   | TODO    | TODO | ---   | YES +    swap interval       | YES     | YES   | YES   | YES   | TODO    | ---  | TODO  | YES +    high-dpi            | YES     | YES   | TODO  | YES   | YES     | YES  | TODO  | YES +    clipboard           | YES     | YES   | TODO  | ---   | ---     | TODO | ---   | YES +    MSAA                | YES     | YES   | YES   | YES   | YES     | TODO | TODO  | YES +    drag'n'drop         | YES     | YES   | YES   | ---   | ---     | TODO | TODO  | YES + +    TODO +    ==== +    - Linux: +        - clipboard support +    - UWP: +        - clipboard, mouselock +    - sapp_consume_event() on non-web platforms? + +    STEP BY STEP +    ============ +    --- Add a sokol_main() function to your code which returns a sapp_desc structure +        with initialization parameters and callback function pointers. This +        function is called very early, usually at the start of the +        platform's entry function (e.g. main or WinMain). You should do as +        little as possible here, since the rest of your code might be called +        from another thread (this depends on the platform): + +            sapp_desc sokol_main(int argc, char* argv[]) { +                return (sapp_desc) { +                    .width = 640, +                    .height = 480, +                    .init_cb = my_init_func, +                    .frame_cb = my_frame_func, +                    .cleanup_cb = my_cleanup_func, +                    .event_cb = my_event_func, +                    ... +                }; +            } + +        There are many more setup parameters, but these are the most important. +        For a complete list search for the sapp_desc structure declaration +        below. + +        DO NOT call any sokol-app function from inside sokol_main(), since +        sokol-app will not be initialized at this point. + +        The .width and .height parameters are the preferred size of the 3D +        rendering canvas. The actual size may differ from this depending on +        platform and other circumstances. Also the canvas size may change at +        any time (for instance when the user resizes the application window, +        or rotates the mobile device). + +        All provided function callbacks will be called from the same thread, +        but this may be different from the thread where sokol_main() was called. + +        .init_cb (void (*)(void)) +            This function is called once after the application window, +            3D rendering context and swap chain have been created. The +            function takes no arguments and has no return value. +        .frame_cb (void (*)(void)) +            This is the per-frame callback, which is usually called 60 +            times per second. This is where your application would update +            most of its state and perform all rendering. +        .cleanup_cb (void (*)(void)) +            The cleanup callback is called once right before the application +            quits. +        .event_cb (void (*)(const sapp_event* event)) +            The event callback is mainly for input handling, but is also +            used to communicate other types of events to the application. Keep the +            event_cb struct member zero-initialized if your application doesn't require +            event handling. +        .fail_cb (void (*)(const char* msg)) +            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) + +        As you can see, those 'standard callbacks' don't have a user_data +        argument, so any data that needs to be preserved between callbacks +        must live in global variables. If keeping state in global variables +        is not an option, there's an alternative set of callbacks with +        an additional user_data pointer argument: + +        .user_data (void*) +            The user-data argument for the callbacks below +        .init_userdata_cb (void (*)(void* user_data)) +        .frame_userdata_cb (void (*)(void* user_data)) +        .cleanup_userdata_cb (void (*)(void* user_data)) +        .event_cb (void(*)(const sapp_event* event, void* user_data)) +        .fail_cb (void(*)(const char* msg, void* user_data)) +            These are the user-data versions of the callback functions. You +            can mix those with the standard callbacks that don't have the +            user_data argument. + +        The function sapp_userdata() can be used to query the user_data +        pointer provided in the sapp_desc struct. + +        You can also call sapp_query_desc() to get a copy of the +        original sapp_desc structure. + +        NOTE that there's also an alternative compile mode where sokol_app.h +        doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. + +    --- Implement the initialization callback function (init_cb), this is called +        once after the rendering surface, 3D API and swap chain have been +        initialized by sokol_app. All sokol-app functions can be called +        from inside the initialization callback, the most useful functions +        at this point are: + +        int sapp_width(void) +        int sapp_height(void) +            Returns the current width and height of the default framebuffer in pixels, +            this may change from one frame to the next, and it may be different +            from the initial size provided in the sapp_desc struct. + +        float sapp_widthf(void) +        float sapp_heightf(void) +            These are alternatives to sapp_width() and sapp_height() which return +            the default framebuffer size as float values instead of integer. This +            may help to prevent casting back and forth between int and float +            in more strongly typed languages than C and C++. + +        int sapp_color_format(void) +        int sapp_depth_format(void) +            The color and depth-stencil pixelformats of the default framebuffer, +            as integer values which are compatible with sokol-gfx's +            sg_pixel_format enum (so that they can be plugged directly in places +            where sg_pixel_format is expected). Possible values are: + +                23 == SG_PIXELFORMAT_RGBA8 +                27 == SG_PIXELFORMAT_BGRA8 +                41 == SG_PIXELFORMAT_DEPTH +                42 == SG_PIXELFORMAT_DEPTH_STENCIL + +        int sapp_sample_count(void) +            Return the MSAA sample count of the default framebuffer. + +        bool sapp_gles2(void) +            Returns true if a GLES2 or WebGL context has been created. This +            is useful when a GLES3/WebGL2 context was requested but is not +            available so that sokol_app.h had to fallback to GLES2/WebGL. + +        const void* sapp_metal_get_device(void) +        const void* sapp_metal_get_renderpass_descriptor(void) +        const void* sapp_metal_get_drawable(void) +            If the Metal backend has been selected, these functions return pointers +            to various Metal API objects required for rendering, otherwise +            they return a null pointer. These void pointers are actually +            Objective-C ids converted with a (ARC) __bridge cast so that +            the ids can be tunnel through C code. Also note that the returned +            pointers to the renderpass-descriptor and drawable may change from one +            frame to the next, only the Metal device object is guaranteed to +            stay the same. + +        const void* sapp_macos_get_window(void) +            On macOS, get the NSWindow object pointer, otherwise a null pointer. +            Before being used as Objective-C object, the void* must be converted +            back with a (ARC) __bridge cast. + +        const void* sapp_ios_get_window(void) +            On iOS, get the UIWindow object pointer, otherwise a null pointer. +            Before being used as Objective-C object, the void* must be converted +            back with a (ARC) __bridge cast. + +        const void* sapp_win32_get_hwnd(void) +            On Windows, get the window's HWND, otherwise a null pointer. The +            HWND has been cast to a void pointer in order to be tunneled +            through code which doesn't include Windows.h. + +        const void* sapp_d3d11_get_device(void) +        const void* sapp_d3d11_get_device_context(void) +        const void* sapp_d3d11_get_render_target_view(void) +        const void* sapp_d3d11_get_depth_stencil_view(void) +            Similar to the sapp_metal_* functions, the sapp_d3d11_* functions +            return pointers to D3D11 API objects required for rendering, +            only if the D3D11 backend has been selected. Otherwise they +            return a null pointer. Note that the returned pointers to the +            render-target-view and depth-stencil-view may change from one +            frame to the next! + +        const void* sapp_wgpu_get_device(void) +        const void* sapp_wgpu_get_render_view(void) +        const void* sapp_wgpu_get_resolve_view(void) +        const void* sapp_wgpu_get_depth_stencil_view(void) +            These are the WebGPU-specific functions to get the WebGPU +            objects and values required for rendering. If sokol_app.h +            is not compiled with SOKOL_WGPU, these functions return null. + +        const void* sapp_android_get_native_activity(void); +            On Android, get the native activity ANativeActivity pointer, otherwise +            a null pointer. + +    --- Implement the frame-callback function, this function will be called +        on the same thread as the init callback, but might be on a different +        thread than the sokol_main() function. Note that the size of +        the rendering framebuffer might have changed since the frame callback +        was called last. Call the functions sapp_width() and sapp_height() +        each frame to get the current size. + +    --- Optionally implement the event-callback to handle input events. +        sokol-app provides the following type of input events: +            - a 'virtual key' was pressed down or released +            - a single text character was entered (provided as UTF-32 code point) +            - a mouse button was pressed down or released (left, right, middle) +            - mouse-wheel or 2D scrolling events +            - the mouse was moved +            - the mouse has entered or left the application window boundaries +            - low-level, portable multi-touch events (began, moved, ended, cancelled) +            - the application window was resized, iconified or restored +            - the application was suspended or restored (on mobile platforms) +            - the user or application code has asked to quit the application +            - a string was pasted to the system clipboard +            - one or more files have been dropped onto the application window + +        To explicitly 'consume' an event and prevent that the event is +        forwarded for further handling to the operating system, call +        sapp_consume_event() from inside the event handler (NOTE that +        this behaviour is currently only implemented for some HTML5 +        events, support for other platforms and event types will +        be added as needed, please open a github ticket and/or provide +        a PR if needed). + +        NOTE: Do *not* call any 3D API rendering functions in the event +        callback function, since the 3D API context may not be active when the +        event callback is called (it may work on some platforms and 3D APIs, +        but not others, and the exact behaviour may change between +        sokol-app versions). + +    --- Implement the cleanup-callback function, this is called once +        after the user quits the application (see the section +        "APPLICATION QUIT" for detailed information on quitting +        behaviour, and how to intercept a pending quit - for instance to show a +        "Really Quit?" dialog box). Note that the cleanup-callback isn't +        guaranteed to be called on the web and mobile platforms. + +    MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) +    ================================================ +    In normal mouse mode, no mouse movement events are reported when the +    mouse leaves the windows client area or hits the screen border (whether +    it's one or the other depends on the platform), and the mouse move events +    (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in +    framebuffer pixels in the sapp_event items mouse_x and mouse_y, and +    relative movement in framebuffer pixels in the sapp_event items mouse_dx +    and mouse_dy. + +    To get continuous mouse movement (also when the mouse leaves the window +    client area or hits the screen border), activate mouse-lock mode +    by calling: + +        sapp_lock_mouse(true) + +    When mouse lock is activated, the mouse pointer is hidden, the +    reported absolute mouse position (sapp_event.mouse_x/y) appears +    frozen, and the relative mouse movement in sapp_event.mouse_dx/dy +    no longer has a direct relation to framebuffer pixels but instead +    uses "raw mouse input" (what "raw mouse input" exactly means also +    differs by platform). + +    To deactivate mouse lock and return to normal mouse mode, call + +        sapp_lock_mouse(false) + +    And finally, to check if mouse lock is currently active, call + +        if (sapp_mouse_locked()) { ... } + +    On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() +    functions work as expected (mouse lock is activated or deactivated +    immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() +    also immediately returns the new state after sapp_lock_mouse() +    is called. + +    On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave +    differently, as dictated by the limitations of the HTML5 Pointer Lock API: + +        - sapp_lock_mouse(true) can be called at any time, but it will +          only take effect in a 'short-lived input event handler of a specific +          type', meaning when one of the following events happens: +            - SAPP_EVENTTYPE_MOUSE_DOWN +            - SAPP_EVENTTYPE_MOUSE_UP +            - SAPP_EVENTTYPE_MOUSE_SCROLL +            - SAPP_EVENTYTPE_KEY_UP +            - SAPP_EVENTTYPE_KEY_DOWN +        - The mouse lock/unlock action on the web platform is asynchronous, +          this means that sapp_mouse_locked() won't immediately return +          the new status after calling sapp_lock_mouse(), instead the +          reported status will only change when the pointer lock has actually +          been activated or deactivated in the browser. +        - On the web, mouse lock can be deactivated by the user at any time +          by pressing the Esc key. When this happens, sokol_app.h behaves +          the same as if sapp_lock_mouse(false) is called. + +    For things like camera manipulation it's most straightforward to lock +    and unlock the mouse right from the sokol_app.h event handler, for +    instance the following code enters and leaves mouse lock when the +    left mouse button is pressed and released, and then uses the relative +    movement information to manipulate a camera (taken from the +    cgltf-sapp.c sample in the sokol-samples repository +    at https://github.com/floooh/sokol-samples): + +        static void input(const sapp_event* ev) { +            switch (ev->type) { +                case SAPP_EVENTTYPE_MOUSE_DOWN: +                    if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { +                        sapp_lock_mouse(true); +                    } +                    break; + +                case SAPP_EVENTTYPE_MOUSE_UP: +                    if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { +                        sapp_lock_mouse(false); +                    } +                    break; + +                case SAPP_EVENTTYPE_MOUSE_MOVE: +                    if (sapp_mouse_locked()) { +                        cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); +                    } +                    break; + +                default: +                    break; +            } +        } + +    CLIPBOARD SUPPORT +    ================= +    Applications can send and receive UTF-8 encoded text data from and to the +    system clipboard. By default, clipboard support is disabled and +    must be enabled at startup via the following sapp_desc struct +    members: + +        sapp_desc.enable_clipboard  - set to true to enable clipboard support +        sapp_desc.clipboard_size    - size of the internal clipboard buffer in bytes + +    Enabling the clipboard will dynamically allocate a clipboard buffer +    for UTF-8 encoded text data of the requested size in bytes, the default +    size is 8 KBytes. Strings that don't fit into the clipboard buffer +    (including the terminating zero) will be silently clipped, so it's +    important that you provide a big enough clipboard size for your +    use case. + +    To send data to the clipboard, call sapp_set_clipboard_string() with +    a pointer to an UTF-8 encoded, null-terminated C-string. + +    NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be +    called from inside a 'short-lived event handler', and there are a few +    other HTML5-specific caveats to workaround. You'll basically have to +    tinker until it works in all browsers :/ (maybe the situation will +    improve when all browsers agree on and implement the new +    HTML5 navigator.clipboard API). + +    To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED +    event in your event handler function, and then call sapp_get_clipboard_string() +    to obtain the pasted UTF-8 encoded text. + +    NOTE that behaviour of sapp_get_clipboard_string() is slightly different +    depending on platform: + +        - on the HTML5 platform, the internal clipboard buffer will only be updated +          right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, +          and sapp_get_clipboard_string() will simply return the current content +          of the clipboard buffer +        - on 'native' platforms, the call to sapp_get_clipboard_string() will +          update the internal clipboard buffer with the most recent data +          from the system clipboard + +    Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, +    and then call sapp_get_clipboard_string() right in the event handler. + +    The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app +    as follows: + +        - on macOS: when the Cmd+V key is pressed down +        - on HTML5: when the browser sends a 'paste' event to the global 'window' object +        - on all other platforms: when the Ctrl+V key is pressed down + +    DRAG AND DROP SUPPORT +    ===================== +    PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 +    and on the native desktop platforms (Win32, Linux and macOS) because +    of security-related restrictions in the HTML5 drag'n'drop API. The +    WASM/HTML5 specifics are described at the end of this documentation +    section: + +    Like clipboard support, drag'n'drop support must be explicitly enabled +    at startup in the sapp_desc struct. + +        sapp_desc sokol_main() { +            return (sapp_desc) { +                .enable_dragndrop = true,   // default is false +                ... +            }; +        } + +    You can also adjust the maximum number of files that are accepted +    in a drop operation, and the maximum path length in bytes if needed: + +        sapp_desc sokol_main() { +            return (sapp_desc) { +                .enable_dragndrop = true,               // default is false +                .max_dropped_files = 8,                 // default is 1 +                .max_dropped_file_path_length = 8192,   // in bytes, default is 2048 +                ... +            }; +        } + +    When drag'n'drop is enabled, the event callback will be invoked with an +    event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on +    the application window. + +    After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the +    number of dropped files, and their absolute paths by calling separate +    functions: + +        void on_event(const sapp_event* ev) { +            if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { + +                // the mouse position where the drop happened +                float x = ev->mouse_x; +                float y = ev->mouse_y; + +                // get the number of files and their paths like this: +                const int num_dropped_files = sapp_get_num_dropped_files(); +                for (int i = 0; i < num_dropped_files; i++) { +                    const char* path = sapp_get_dropped_file_path(i); +                    ... +                } +            } +        } + +    The returned file paths are UTF-8 encoded strings. + +    You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() +    anywhere, also outside the event handler callback, but be aware that the +    file path strings will be overwritten with the next drop operation. + +    In any case, sapp_get_dropped_file_path() will never return a null pointer, +    instead an empty string "" will be returned if the drag'n'drop feature +    hasn't been enabled, the last drop-operation failed, or the file path index +    is out of range. + +    Drag'n'drop caveats: + +        - if more files are dropped in a single drop-action +          than sapp_desc.max_dropped_files, the additional +          files will be silently ignored +        - if any of the file paths is longer than +          sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 +          encoding) the entire drop operation will be silently ignored (this +          needs some sort of error feedback in the future) +        - no mouse positions are reported while the drag is in +          process, this may change in the future + +    Drag'n'drop on HTML5/WASM: + +    The HTML5 drag'n'drop API doesn't return file paths, but instead +    black-box 'file objects' which must be used to load the content +    of dropped files. This is the reason why sokol_app.h adds two +    HTML5-specific functions to the drag'n'drop API: + +        uint32_t sapp_html5_get_dropped_file_size(int index) +            Returns the size in bytes of a dropped file. + +        void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) +            Asynchronously loads the content of a dropped file into a +            provided memory buffer (which must be big enough to hold +            the file content) + +    To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED +    event is received: + +        sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ +            .dropped_file_index = 0, +            .callback = fetch_cb +            .buffer_ptr = buf, +            .buffer_size = buf_size, +            .user_data = ... +        }); + +    Make sure that the memory pointed to by 'buf' stays valid until the +    callback function is called! + +    As result of the asynchronous loading operation (no matter if succeeded or +    failed) the 'fetch_cb' function will be called: + +        void fetch_cb(const sapp_html5_fetch_response* response) { +            // IMPORTANT: check if the loading operation actually succeeded: +            if (response->succeeded) { +                // the size of the loaded file: +                const uint32_t num_bytes = response->fetched_size; +                // and the pointer to the data (same as 'buf' in the fetch-call): +                const void* ptr = response->buffer_ptr; +            } +            else { +                // on error check the error code: +                switch (response->error_code) { +                    case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: +                        ... +                        break; +                    case SAPP_HTML5_FETCH_ERROR_OTHER: +                        ... +                        break; +                } +            } +        } + +    Check the droptest-sapp example for a real-world example which works +    both on native platforms and the web: + +    https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c + +    HIGH-DPI RENDERING +    ================== +    You can set the sapp_desc.high_dpi flag during initialization to request +    a full-resolution framebuffer on HighDPI displays. The default behaviour +    is sapp_desc.high_dpi=false, this means that the application will +    render to a lower-resolution framebuffer on HighDPI displays and the +    rendered content will be upscaled by the window system composer. + +    In a HighDPI scenario, you still request the same window size during +    sokol_main(), but the framebuffer sizes returned by sapp_width() +    and sapp_height() will be scaled up according to the DPI scaling +    ratio. You can also get a DPI scaling factor with the function +    sapp_dpi_scale(). + +    Here's an example on a Mac with Retina display: + +    sapp_desc sokol_main() { +        return (sapp_desc) { +            .width = 640, +            .height = 480, +            .high_dpi = true, +            ... +        }; +    } + +    The functions sapp_width(), sapp_height() and sapp_dpi_scale() will +    return the following values: + +    sapp_width      -> 1280 +    sapp_height     -> 960 +    sapp_dpi_scale  -> 2.0 + +    If the high_dpi flag is false, or you're not running on a Retina display, +    the values would be: + +    sapp_width      -> 640 +    sapp_height     -> 480 +    sapp_dpi_scale  -> 1.0 + +    APPLICATION QUIT +    ================ +    Without special quit handling, a sokol_app.h application will quit +    'gracefully' when the user clicks the window close-button unless a +    platform's application model prevents this (e.g. on web or mobile). +    'Graceful exit' means that the application-provided cleanup callback will +    be called before the application quits. + +    On native desktop platforms sokol_app.h provides more control over the +    application-quit-process. It's possible to initiate a 'programmatic quit' +    from the application code, and a quit initiated by the application user can +    be intercepted (for instance to show a custom dialog box). + +    This 'programmatic quit protocol' is implemented through 3 functions +    and 1 event: + +        - sapp_quit(): This function simply quits the application without +          giving the user a chance to intervene. Usually this might +          be called when the user clicks the 'Ok' button in a 'Really Quit?' +          dialog box +        - sapp_request_quit(): Calling sapp_request_quit() will send the +          event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler +          callback, giving the user code a chance to intervene and cancel the +          pending quit process (for instance to show a 'Really Quit?' dialog +          box). If the event handler callback does nothing, the application +          will be quit as usual. To prevent this, call the function +          sapp_cancel_quit() from inside the event handler. +        - sapp_cancel_quit(): Cancels a pending quit request, either initiated +          by the user clicking the window close button, or programmatically +          by calling sapp_request_quit(). The only place where calling this +          function makes sense is from inside the event handler callback when +          the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. +        - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user +          clicks the window's close button or application code calls the +          sapp_request_quit() function. The event handler callback code can handle +          this event by calling sapp_cancel_quit() to cancel the quit. +          If the event is ignored, the application will quit as usual. + +    On the web platform, the quit behaviour differs from native platforms, +    because of web-specific restrictions: + +    A `programmatic quit` initiated by calling sapp_quit() or +    sapp_request_quit() will work as described above: the cleanup callback is +    called, platform-specific cleanup is performed (on the web +    this means that JS event handlers are unregisters), and then +    the request-animation-loop will be exited. However that's all. The +    web page itself will continue to exist (e.g. it's not possible to +    programmatically close the browser tab). + +    On the web it's also not possible to run custom code when the user +    closes a brower tab, so it's not possible to prevent this with a +    fancy custom dialog box. + +    Instead the standard "Leave Site?" dialog box can be activated (or +    deactivated) with the following function: + +        sapp_html5_ask_leave_site(bool ask); + +    The initial state of the associated internal flag can be provided +    at startup via sapp_desc.html5_ask_leave_site. + +    This feature should only be used sparingly in critical situations - for +    instance when the user would loose data - since popping up modal dialog +    boxes is considered quite rude in the web world. Note that there's no way +    to customize the content of this dialog box or run any code as a result +    of the user's decision. Also note that the user must have interacted with +    the site before the dialog box will appear. These are all security measures +    to prevent fishing. + +    The Dear ImGui HighDPI sample contains example code of how to +    implement a 'Really Quit?' dialog box with Dear ImGui (native desktop +    platforms only), and for showing the hardwired "Leave Site?" dialog box +    when running on the web platform: + +        https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html + +    FULLSCREEN +    ========== +    If the sapp_desc.fullscreen flag is true, sokol-app will try to create +    a fullscreen window on platforms with a 'proper' window system +    (mobile devices will always use fullscreen). The implementation details +    depend on the target platform, in general sokol-app will use a +    'soft approach' which doesn't interfere too much with the platform's +    window system (for instance borderless fullscreen window instead of +    a 'real' fullscreen mode). Such details might change over time +    as sokol-app is adapted for different needs. + +    The most important effect of fullscreen mode to keep in mind is that +    the requested canvas width and height will be ignored for the initial +    window size, calling sapp_width() and sapp_height() will instead return +    the resolution of the fullscreen canvas (however the provided size +    might still be used for the non-fullscreen window, in case the user can +    switch back from fullscreen- to windowed-mode). + +    To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). + +    To check if the application window is currently in fullscreen mode, +    call sapp_is_fullscreen(). + +    ONSCREEN KEYBOARD +    ================= +    On some platforms which don't provide a physical keyboard, sokol-app +    can display the platform's integrated onscreen keyboard for text +    input. To request that the onscreen keyboard is shown, call + +        sapp_show_keyboard(true); + +    Likewise, to hide the keyboard call: + +        sapp_show_keyboard(false); + +    Note that on the web platform, the keyboard can only be shown from +    inside an input handler. On such platforms, sapp_show_keyboard() +    will only work as expected when it is called from inside the +    sokol-app event callback function. When called from other places, +    an internal flag will be set, and the onscreen keyboard will be +    called at the next 'legal' opportunity (when the next input event +    is handled). + +    OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) +    ====================================================== +    In its default configuration, sokol_app.h "hijacks" the platform's +    standard main() function. This was done because different platforms +    have different main functions which are not compatible with +    C's main() (for instance WinMain on Windows has completely different +    arguments). However, this "main hijacking" posed a problem for +    usage scenarios like integrating sokol_app.h with other languages than +    C or C++, so an alternative SOKOL_NO_ENTRY mode has been added +    in which the user code provides the platform's main function: + +    - define SOKOL_NO_ENTRY before including the sokol_app.h implementation +    - do *not* provide a sokol_main() function +    - instead provide the standard main() function of the platform +    - from the main function, call the function ```sapp_run()``` which +      takes a pointer to an ```sapp_desc``` structure. +    - ```sapp_run()``` takes over control and calls the provided init-, frame-, +      shutdown- and event-callbacks just like in the default model, it +      will only return when the application quits (or not at all on some +      platforms, like emscripten) + +    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? +    - sapp_desc needs a bool whether to initialize depth-stencil surface +    - GL context initialization needs more control (at least what GL version to initialize) +    - application icon +    - the UPDATE_CURSOR event currently behaves differently between Win32 and OSX +      (Win32 sends the event each frame when the mouse moves and is inside the window +      client area, OSX sends it only once when the mouse enters the client area) +    - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy +      at the latest but should do it earlier, in onStop, as an app is "killable" after onStop +      on Android Honeycomb and later (it can't be done at the moment as the app may be started +      again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) + + +    LICENSE +    ======= +    zlib/libpng license + +    Copyright (c) 2018 Andre Weissflog + +    This software is provided 'as-is', without any express or implied warranty. +    In no event will the authors be held liable for any damages arising from the +    use of this software. + +    Permission is granted to anyone to use this software for any purpose, +    including commercial applications, and to alter it and redistribute it +    freely, subject to the following restrictions: + +        1. The origin of this software must not be misrepresented; you must not +        claim that you wrote the original software. If you use this software in a +        product, an acknowledgment in the product documentation would be +        appreciated but is not required. + +        2. Altered source versions must be plainly marked as such, and must not +        be misrepresented as being the original software. + +        3. This notice may not be removed or altered from any source +        distribution. +*/ +#define SOKOL_APP_INCLUDED (1) +#include <stdint.h> +#include <stdbool.h> + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) +#define SOKOL_APP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_APP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) +#define SOKOL_APP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_APP_API_DECL __declspec(dllimport) +#else +#define SOKOL_APP_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { +    SAPP_MAX_TOUCHPOINTS = 8, +    SAPP_MAX_MOUSEBUTTONS = 3, +    SAPP_MAX_KEYCODES = 512, +}; + +typedef enum sapp_event_type { +    SAPP_EVENTTYPE_INVALID, +    SAPP_EVENTTYPE_KEY_DOWN, +    SAPP_EVENTTYPE_KEY_UP, +    SAPP_EVENTTYPE_CHAR, +    SAPP_EVENTTYPE_MOUSE_DOWN, +    SAPP_EVENTTYPE_MOUSE_UP, +    SAPP_EVENTTYPE_MOUSE_SCROLL, +    SAPP_EVENTTYPE_MOUSE_MOVE, +    SAPP_EVENTTYPE_MOUSE_ENTER, +    SAPP_EVENTTYPE_MOUSE_LEAVE, +    SAPP_EVENTTYPE_TOUCHES_BEGAN, +    SAPP_EVENTTYPE_TOUCHES_MOVED, +    SAPP_EVENTTYPE_TOUCHES_ENDED, +    SAPP_EVENTTYPE_TOUCHES_CANCELLED, +    SAPP_EVENTTYPE_RESIZED, +    SAPP_EVENTTYPE_ICONIFIED, +    SAPP_EVENTTYPE_RESTORED, +    SAPP_EVENTTYPE_SUSPENDED, +    SAPP_EVENTTYPE_RESUMED, +    SAPP_EVENTTYPE_UPDATE_CURSOR, +    SAPP_EVENTTYPE_QUIT_REQUESTED, +    SAPP_EVENTTYPE_CLIPBOARD_PASTED, +    SAPP_EVENTTYPE_FILES_DROPPED, +    _SAPP_EVENTTYPE_NUM, +    _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF +} sapp_event_type; + +/* key codes are the same names and values as GLFW */ +typedef enum sapp_keycode { +    SAPP_KEYCODE_INVALID          = 0, +    SAPP_KEYCODE_SPACE            = 32, +    SAPP_KEYCODE_APOSTROPHE       = 39,  /* ' */ +    SAPP_KEYCODE_COMMA            = 44,  /* , */ +    SAPP_KEYCODE_MINUS            = 45,  /* - */ +    SAPP_KEYCODE_PERIOD           = 46,  /* . */ +    SAPP_KEYCODE_SLASH            = 47,  /* / */ +    SAPP_KEYCODE_0                = 48, +    SAPP_KEYCODE_1                = 49, +    SAPP_KEYCODE_2                = 50, +    SAPP_KEYCODE_3                = 51, +    SAPP_KEYCODE_4                = 52, +    SAPP_KEYCODE_5                = 53, +    SAPP_KEYCODE_6                = 54, +    SAPP_KEYCODE_7                = 55, +    SAPP_KEYCODE_8                = 56, +    SAPP_KEYCODE_9                = 57, +    SAPP_KEYCODE_SEMICOLON        = 59,  /* ; */ +    SAPP_KEYCODE_EQUAL            = 61,  /* = */ +    SAPP_KEYCODE_A                = 65, +    SAPP_KEYCODE_B                = 66, +    SAPP_KEYCODE_C                = 67, +    SAPP_KEYCODE_D                = 68, +    SAPP_KEYCODE_E                = 69, +    SAPP_KEYCODE_F                = 70, +    SAPP_KEYCODE_G                = 71, +    SAPP_KEYCODE_H                = 72, +    SAPP_KEYCODE_I                = 73, +    SAPP_KEYCODE_J                = 74, +    SAPP_KEYCODE_K                = 75, +    SAPP_KEYCODE_L                = 76, +    SAPP_KEYCODE_M                = 77, +    SAPP_KEYCODE_N                = 78, +    SAPP_KEYCODE_O                = 79, +    SAPP_KEYCODE_P                = 80, +    SAPP_KEYCODE_Q                = 81, +    SAPP_KEYCODE_R                = 82, +    SAPP_KEYCODE_S                = 83, +    SAPP_KEYCODE_T                = 84, +    SAPP_KEYCODE_U                = 85, +    SAPP_KEYCODE_V                = 86, +    SAPP_KEYCODE_W                = 87, +    SAPP_KEYCODE_X                = 88, +    SAPP_KEYCODE_Y                = 89, +    SAPP_KEYCODE_Z                = 90, +    SAPP_KEYCODE_LEFT_BRACKET     = 91,  /* [ */ +    SAPP_KEYCODE_BACKSLASH        = 92,  /* \ */ +    SAPP_KEYCODE_RIGHT_BRACKET    = 93,  /* ] */ +    SAPP_KEYCODE_GRAVE_ACCENT     = 96,  /* ` */ +    SAPP_KEYCODE_WORLD_1          = 161, /* non-US #1 */ +    SAPP_KEYCODE_WORLD_2          = 162, /* non-US #2 */ +    SAPP_KEYCODE_ESCAPE           = 256, +    SAPP_KEYCODE_ENTER            = 257, +    SAPP_KEYCODE_TAB              = 258, +    SAPP_KEYCODE_BACKSPACE        = 259, +    SAPP_KEYCODE_INSERT           = 260, +    SAPP_KEYCODE_DELETE           = 261, +    SAPP_KEYCODE_RIGHT            = 262, +    SAPP_KEYCODE_LEFT             = 263, +    SAPP_KEYCODE_DOWN             = 264, +    SAPP_KEYCODE_UP               = 265, +    SAPP_KEYCODE_PAGE_UP          = 266, +    SAPP_KEYCODE_PAGE_DOWN        = 267, +    SAPP_KEYCODE_HOME             = 268, +    SAPP_KEYCODE_END              = 269, +    SAPP_KEYCODE_CAPS_LOCK        = 280, +    SAPP_KEYCODE_SCROLL_LOCK      = 281, +    SAPP_KEYCODE_NUM_LOCK         = 282, +    SAPP_KEYCODE_PRINT_SCREEN     = 283, +    SAPP_KEYCODE_PAUSE            = 284, +    SAPP_KEYCODE_F1               = 290, +    SAPP_KEYCODE_F2               = 291, +    SAPP_KEYCODE_F3               = 292, +    SAPP_KEYCODE_F4               = 293, +    SAPP_KEYCODE_F5               = 294, +    SAPP_KEYCODE_F6               = 295, +    SAPP_KEYCODE_F7               = 296, +    SAPP_KEYCODE_F8               = 297, +    SAPP_KEYCODE_F9               = 298, +    SAPP_KEYCODE_F10              = 299, +    SAPP_KEYCODE_F11              = 300, +    SAPP_KEYCODE_F12              = 301, +    SAPP_KEYCODE_F13              = 302, +    SAPP_KEYCODE_F14              = 303, +    SAPP_KEYCODE_F15              = 304, +    SAPP_KEYCODE_F16              = 305, +    SAPP_KEYCODE_F17              = 306, +    SAPP_KEYCODE_F18              = 307, +    SAPP_KEYCODE_F19              = 308, +    SAPP_KEYCODE_F20              = 309, +    SAPP_KEYCODE_F21              = 310, +    SAPP_KEYCODE_F22              = 311, +    SAPP_KEYCODE_F23              = 312, +    SAPP_KEYCODE_F24              = 313, +    SAPP_KEYCODE_F25              = 314, +    SAPP_KEYCODE_KP_0             = 320, +    SAPP_KEYCODE_KP_1             = 321, +    SAPP_KEYCODE_KP_2             = 322, +    SAPP_KEYCODE_KP_3             = 323, +    SAPP_KEYCODE_KP_4             = 324, +    SAPP_KEYCODE_KP_5             = 325, +    SAPP_KEYCODE_KP_6             = 326, +    SAPP_KEYCODE_KP_7             = 327, +    SAPP_KEYCODE_KP_8             = 328, +    SAPP_KEYCODE_KP_9             = 329, +    SAPP_KEYCODE_KP_DECIMAL       = 330, +    SAPP_KEYCODE_KP_DIVIDE        = 331, +    SAPP_KEYCODE_KP_MULTIPLY      = 332, +    SAPP_KEYCODE_KP_SUBTRACT      = 333, +    SAPP_KEYCODE_KP_ADD           = 334, +    SAPP_KEYCODE_KP_ENTER         = 335, +    SAPP_KEYCODE_KP_EQUAL         = 336, +    SAPP_KEYCODE_LEFT_SHIFT       = 340, +    SAPP_KEYCODE_LEFT_CONTROL     = 341, +    SAPP_KEYCODE_LEFT_ALT         = 342, +    SAPP_KEYCODE_LEFT_SUPER       = 343, +    SAPP_KEYCODE_RIGHT_SHIFT      = 344, +    SAPP_KEYCODE_RIGHT_CONTROL    = 345, +    SAPP_KEYCODE_RIGHT_ALT        = 346, +    SAPP_KEYCODE_RIGHT_SUPER      = 347, +    SAPP_KEYCODE_MENU             = 348, +} sapp_keycode; + +typedef struct sapp_touchpoint { +    uintptr_t identifier; +    float pos_x; +    float pos_y; +    bool changed; +} sapp_touchpoint; + +typedef enum sapp_mousebutton { +    SAPP_MOUSEBUTTON_LEFT = 0x0, +    SAPP_MOUSEBUTTON_RIGHT = 0x1, +    SAPP_MOUSEBUTTON_MIDDLE = 0x2, +    SAPP_MOUSEBUTTON_INVALID = 0x100, +} sapp_mousebutton; + +enum { +    SAPP_MODIFIER_SHIFT = 0x1, +    SAPP_MODIFIER_CTRL = 0x2, +    SAPP_MODIFIER_ALT = 0x4, +    SAPP_MODIFIER_SUPER = 0x8 +}; + +typedef struct sapp_event { +    uint64_t frame_count; +    sapp_event_type type; +    sapp_keycode key_code; +    uint32_t char_code; +    bool key_repeat; +    uint32_t modifiers; +    sapp_mousebutton mouse_button; +    float mouse_x; +    float mouse_y; +    float mouse_dx; +    float mouse_dy; +    float scroll_x; +    float scroll_y; +    int num_touches; +    sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; +    int window_width; +    int window_height; +    int framebuffer_width; +    int framebuffer_height; +} sapp_event; + +typedef struct sapp_desc { +    void (*init_cb)(void);                  /* these are the user-provided callbacks without user data */ +    void (*frame_cb)(void); +    void (*cleanup_cb)(void); +    void (*event_cb)(const sapp_event*); +    void (*fail_cb)(const char*); + +    void* user_data;                        /* these are the user-provided callbacks with user data */ +    void (*init_userdata_cb)(void*); +    void (*frame_userdata_cb)(void*); +    void (*cleanup_userdata_cb)(void*); +    void (*event_userdata_cb)(const sapp_event*, void*); +    void (*fail_userdata_cb)(const char*, void*); + +    int width;                          /* the preferred width of the window / canvas */ +    int height;                         /* the preferred height of the window / canvas */ +    int sample_count;                   /* MSAA sample count */ +    int swap_interval;                  /* the preferred swap interval (ignored on some platforms) */ +    bool high_dpi;                      /* whether the rendering canvas is full-resolution on HighDPI displays */ +    bool fullscreen;                    /* whether the window should be created in fullscreen mode */ +    bool alpha;                         /* whether the framebuffer should have an alpha channel (ignored on some platforms) */ +    const char* window_title;           /* the window title as UTF-8 encoded string */ +    bool user_cursor;                   /* if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR */ +    bool enable_clipboard;              /* enable clipboard access, default is false */ +    int clipboard_size;                 /* max size of clipboard content in bytes */ +    bool enable_dragndrop;              /* enable file dropping (drag'n'drop), default is false */ +    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 */ + +    /* V patches */ +    bool __v_native_render;             /* V patch to allow for native rendering */ +} sapp_desc; + +/* HTML5 specific: request and response structs for +   asynchronously loading dropped-file content. +*/ +typedef enum sapp_html5_fetch_error { +    SAPP_HTML5_FETCH_ERROR_NO_ERROR, +    SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, +    SAPP_HTML5_FETCH_ERROR_OTHER, +} sapp_html5_fetch_error; + +typedef struct sapp_html5_fetch_response { +    bool succeeded;         /* true if the loading operation has succeeded */ +    sapp_html5_fetch_error error_code; +    int file_index;         /* index of the dropped file (0..sapp_get_num_dropped_filed()-1) */ +    uint32_t fetched_size;  /* size in bytes of loaded data */ +    void* buffer_ptr;       /* pointer to user-provided buffer which contains the loaded data */ +    uint32_t buffer_size;   /* size of user-provided buffer (buffer_size >= fetched_size) */ +    void* user_data;        /* user-provided user data pointer */ +} sapp_html5_fetch_response; + +typedef struct sapp_html5_fetch_request { +    int dropped_file_index;                 /* 0..sapp_get_num_dropped_files()-1 */ +    void (*callback)(const sapp_html5_fetch_response*);     /* response callback function pointer (required) */ +    void* buffer_ptr;                       /* pointer to buffer to load data into */ +    uint32_t buffer_size;                   /* size in bytes of buffer */ +    void* user_data;                        /* optional userdata pointer */ +} sapp_html5_fetch_request; + +/* user-provided functions */ +extern sapp_desc sokol_main(int argc, char* argv[]); + +/* returns true after sokol-app has been initialized */ +SOKOL_APP_API_DECL bool sapp_isvalid(void); +/* returns the current framebuffer width in pixels */ +SOKOL_APP_API_DECL int sapp_width(void); +/* same as sapp_width(), but returns float */ +SOKOL_APP_API_DECL float sapp_widthf(void); +/* returns the current framebuffer height in pixels */ +SOKOL_APP_API_DECL int sapp_height(void); +/* same as sapp_height(), but returns float */ +SOKOL_APP_API_DECL float sapp_heightf(void); +/* get default framebuffer color pixel format */ +SOKOL_APP_API_DECL int sapp_color_format(void); +/* get default framebuffer depth pixel format */ +SOKOL_APP_API_DECL int sapp_depth_format(void); +/* get default framebuffer sample count */ +SOKOL_APP_API_DECL int sapp_sample_count(void); +/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ +SOKOL_APP_API_DECL bool sapp_high_dpi(void); +/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ +SOKOL_APP_API_DECL float sapp_dpi_scale(void); +/* show or hide the mobile device onscreen keyboard */ +SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); +/* return true if the mobile device onscreen keyboard is currently shown */ +SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); +/* query fullscreen mode */ +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); +/* toggle fullscreen mode */ +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL void sapp_show_mouse(bool show); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL bool sapp_mouse_shown(); +/* enable/disable mouse-pointer-lock mode */ +SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); +/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ +SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* return the userdata pointer optionally provided in sapp_desc */ +SOKOL_APP_API_DECL void* sapp_userdata(void); +/* return a copy of the sapp_desc structure */ +SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); +/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_request_quit(void); +/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ +SOKOL_APP_API_DECL void sapp_cancel_quit(void); +/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUSTED) */ +SOKOL_APP_API_DECL void sapp_quit(void); +/* call from inside event callback to consume the current event (don't forward to platform) */ +SOKOL_APP_API_DECL void sapp_consume_event(void); +/* get the current frame counter (for comparison with sapp_event.frame_count) */ +SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); +/* write string into clipboard */ +SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); +/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ +SOKOL_APP_API_DECL const char* sapp_get_clipboard_string(void); +/* set the window title (only on desktop platforms) */ +SOKOL_APP_API_DECL void sapp_set_window_title(const char* str); +/* gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) */ +SOKOL_APP_API_DECL int sapp_get_num_dropped_files(void); +/* gets the dropped file paths */ +SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); + +/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ +SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); + +/* GL: return true when GLES2 fallback is active (to detect fallback from GLES3) */ +SOKOL_APP_API_DECL bool sapp_gles2(void); + +/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ +SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); +/* HTML5: get byte size of a dropped file */ +SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); +/* HTML5: asynchronously load the content of a dropped file */ +SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); + +/* Metal: get bridged pointer to Metal device object */ +SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); +/* Metal: get bridged pointer to this frame's renderpass descriptor */ +SOKOL_APP_API_DECL const void* sapp_metal_get_renderpass_descriptor(void); +/* Metal: get bridged pointer to current drawable */ +SOKOL_APP_API_DECL const void* sapp_metal_get_drawable(void); +/* macOS: get bridged pointer to macOS NSWindow */ +SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); +/* iOS: get bridged pointer to iOS UIWindow */ +SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); + +/* D3D11: get pointer to ID3D11Device object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); +/* D3D11: get pointer to ID3D11DeviceContext object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_device_context(void); +/* D3D11: get pointer to ID3D11RenderTargetView object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_target_view(void); +/* D3D11: get pointer to ID3D11DepthStencilView */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); +/* Win32: get the HWND window handle */ +SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); + +/* WebGPU: get WGPUDevice handle */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); +/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); +/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); +/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); + +/* Android: get native activity handle */ +SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } + +#endif + +// this WinRT specific hack is required when wWinMain is in a static library +#if defined(_MSC_VER) && defined(UNICODE) +#include <winapifamily.h> +#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#pragma comment(linker, "/include:wWinMain") +#endif +#endif + +#endif // SOKOL_APP_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_APP_IMPL +#define SOKOL_APP_IMPL_INCLUDED (1) + +#include <string.h> // memset +#include <stddef.h> // size_t + +/* check if the config defines are alright */ +#if defined(__APPLE__) +    // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting +    #if !defined(__cplusplus) +        #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) +            #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" +        #endif +    #endif +    #define _SAPP_APPLE (1) +    #include <TargetConditionals.h> +    #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE +        /* MacOS */ +        #define _SAPP_MACOS (1) +        #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) +        #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") +        #endif +    #else +        /* iOS or iOS Simulator */ +        #define _SAPP_IOS (1) +        #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) +        #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") +        #endif +    #endif +#elif defined(__EMSCRIPTEN__) +    /* emscripten (asm.js or wasm) */ +    #define _SAPP_EMSCRIPTEN (1) +    #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) && !defined(SOKOL_WGPU) +    #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3, SOKOL_GLES2 or SOKOL_WGPU") +    #endif +#elif defined(_WIN32) +    /* Windows (D3D11 or GL) */ +    #include <winapifamily.h> +    #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) +        #define _SAPP_UWP (1) +        #if !defined(SOKOL_D3D11) +        #error("sokol_app.h: unknown 3D API selected for UWP, must be SOKOL_D3D11") +        #endif +        #if !defined(__cplusplus) +        #error("sokol_app.h: UWP bindings require C++/17") +        #endif +    #else +        #define _SAPP_WIN32 (1) +        #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) +        #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") +        #endif +    #endif +#elif defined(__ANDROID__) +    /* Android */ +    #define _SAPP_ANDROID (1) +    #if !defined(SOKOL_GLES3) && !defined(SOKOL_GLES2) +    #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3 or SOKOL_GLES2") +    #endif +    #if defined(SOKOL_NO_ENTRY) +    #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") +    #endif +#elif defined(__linux__) || defined(__unix__) +    /* Linux */ +    #define _SAPP_LINUX (1) +    #if !defined(SOKOL_GLCORE33) +    #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33") +    #endif +#else +#error "sokol_app.h: Unknown platform" +#endif + +#ifndef SOKOL_API_IMPL +    #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG +    #ifndef NDEBUG +        #define SOKOL_DEBUG (1) +    #endif +#endif +#ifndef SOKOL_ASSERT +    #include <assert.h> +    #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE +    #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#if !defined(SOKOL_CALLOC) || !defined(SOKOL_FREE) +    #include <stdlib.h> +#endif +#if !defined(SOKOL_CALLOC) +    #define SOKOL_CALLOC(n,s) calloc(n,s) +#endif +#if !defined(SOKOL_FREE) +    #define SOKOL_FREE(p) free(p) +#endif +#ifndef SOKOL_LOG +    #ifdef SOKOL_DEBUG +        #if defined(__ANDROID__) +            #include <android/log.h> +            #define SOKOL_LOG(s) { SOKOL_ASSERT(s); __android_log_write(ANDROID_LOG_INFO, "SOKOL_APP", s); } +        #else +            #include <stdio.h> +            #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); } +        #endif +    #else +        #define SOKOL_LOG(s) +    #endif +#endif +#ifndef SOKOL_ABORT +    #include <stdlib.h> +    #define SOKOL_ABORT() abort() +#endif +#ifndef _SOKOL_PRIVATE +    #if defined(__GNUC__) || defined(__clang__) +        #define _SOKOL_PRIVATE __attribute__((unused)) static +    #else +        #define _SOKOL_PRIVATE static +    #endif +#endif +#ifndef _SOKOL_UNUSED +    #define _SOKOL_UNUSED(x) (void)(x) +#endif + +/*== PLATFORM SPECIFIC INCLUDES AND DEFINES ==================================*/ +#if defined(_SAPP_APPLE) +    #if defined(SOKOL_METAL) +        #import <Metal/Metal.h> +        #import <MetalKit/MetalKit.h> +    #endif +    #if defined(_SAPP_MACOS) +        #if !defined(SOKOL_METAL) +            #ifndef GL_SILENCE_DEPRECATION +            #define GL_SILENCE_DEPRECATION +            #endif +            #include <Cocoa/Cocoa.h> +        #endif +    #elif defined(_SAPP_IOS) +        #import <UIKit/UIKit.h> +        #if !defined(SOKOL_METAL) +            #import <GLKit/GLKit.h> +        #endif +    #endif +#elif defined(_SAPP_EMSCRIPTEN) +    #if defined(SOKOL_WGPU) +        #include <webgpu/webgpu.h> +    #endif +    #include <emscripten/emscripten.h> +    #include <emscripten/html5.h> +#elif defined(_SAPP_WIN32) +    #ifdef _MSC_VER +        #pragma warning(push) +        #pragma warning(disable:4201)   /* nonstandard extension used: nameless struct/union */ +        #pragma warning(disable:4204)   /* nonstandard extension used: non-constant aggregate initializer */ +        #pragma warning(disable:4054)   /* 'type cast': from function pointer */ +        #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) */ +    #endif +    #ifndef WIN32_LEAN_AND_MEAN +        #define WIN32_LEAN_AND_MEAN +    #endif +    #ifndef NOMINMAX +        #define NOMINMAX +    #endif +    #include <windows.h> +    #include <windowsx.h> +    #include <shellapi.h> +    #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_s() */ +    #include <wchar.h>  /* wcslen() */ + +    #pragma comment (lib, "kernel32") +    #pragma comment (lib, "user32") +    #pragma comment (lib, "shell32")    /* CommandLineToArgvW, DragQueryFileW, DragFinished */ +    #if defined(SOKOL_D3D11) +        #pragma comment (lib, "dxgi") +        #pragma comment (lib, "d3d11") +        #pragma comment (lib, "dxguid") +    #endif +    #if defined(SOKOL_GLCORE33) +        #pragma comment (lib, "gdi32") +    #endif + +    #if defined(SOKOL_D3D11) +        #ifndef D3D11_NO_HELPERS +            #define D3D11_NO_HELPERS +        #endif +        #include <d3d11.h> +        #include <dxgi.h> +        // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it +        #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) +    #endif +    #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ +        #define WM_MOUSEHWHEEL (0x020E) +    #endif +#elif defined(_SAPP_UWP) +    #ifndef NOMINMAX +        #define NOMINMAX +    #endif +    #ifdef _MSC_VER +        #pragma warning(push) +        #pragma warning(disable:4201)   /* nonstandard extension used: nameless struct/union */ +        #pragma warning(disable:4054)   /* 'type cast': from function pointer */ +        #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) */ +    #endif +    #include <windows.h> +    #include <winrt/Windows.ApplicationModel.Core.h> +    #include <winrt/Windows.Foundation.h> +    #include <winrt/Windows.Foundation.Collections.h> +    #include <winrt/Windows.Graphics.Display.h> +    #include <winrt/Windows.UI.Core.h> +    #include <winrt/Windows.UI.Composition.h> +    #include <winrt/Windows.UI.Input.h> +    #include <winrt/Windows.UI.ViewManagement.h> +    #include <winrt/Windows.System.h> +    #include <ppltasks.h> + +    #include <dxgi1_4.h> +    #include <d3d11_3.h> +    #include <DirectXMath.h> + +    #pragma comment (lib, "WindowsApp") +    #pragma comment (lib, "dxguid") +#elif defined(_SAPP_ANDROID) +    #include <pthread.h> +    #include <unistd.h> +    #include <android/native_activity.h> +    #include <android/looper.h> +    #include <EGL/egl.h> +#elif defined(_SAPP_LINUX) +    #define GL_GLEXT_PROTOTYPES +    #include <X11/Xlib.h> +    #include <X11/Xutil.h> +    #include <X11/XKBlib.h> +    #include <X11/keysym.h> +    #include <X11/Xresource.h> +    #include <X11/Xatom.h> +    #include <X11/extensions/XInput2.h> +    #include <X11/Xcursor/Xcursor.h> +    #include <X11/Xmd.h> /* CARD32 */ +    #include <dlfcn.h> /* dlopen, dlsym, dlclose */ +    #include <limits.h> /* LONG_MAX */ +    #include <pthread.h>    /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ +#endif + +/*== MACOS DECLARATIONS ======================================================*/ +#if defined(_SAPP_MACOS) +// __v_ +@interface SokolWindow : NSWindow { +} +@end +@interface MyView2 : NSView +@end + +MyView2* g_view; + +// A custom NSWindow interface to handle events in borderless windows. +@implementation SokolWindow +- (BOOL)canBecomeKeyWindow { return YES; } // needed for NSWindowStyleMaskBorderless +- (BOOL)canBecomeMainWindow { return YES; } +@end +// __v_ + +@interface _sapp_macos_app_delegate : NSObject<NSApplicationDelegate> +@end +@interface _sapp_macos_window : NSWindow +@end +@interface _sapp_macos_window_delegate : NSObject<NSWindowDelegate> +@end +#if defined(SOKOL_METAL) +    @interface _sapp_macos_view : MTKView +    @end +#elif defined(SOKOL_GLCORE33) +    @interface _sapp_macos_view : NSOpenGLView +    - (void)timerFired:(id)sender; +    @end +#endif // SOKOL_GLCORE33 + +typedef struct { +    uint32_t flags_changed_store; +    uint8_t mouse_buttons; +//    NSWindow* window; +//    SokolWindow* window; // __v_ +    _sapp_macos_window* window; // __v_ +    NSTrackingArea* tracking_area; +    _sapp_macos_app_delegate* app_dlg; +    _sapp_macos_window_delegate* win_dlg; +    _sapp_macos_view* view; +    #if defined(SOKOL_METAL) +        id<MTLDevice> mtl_device; +    #endif +} _sapp_macos_t; + +#endif // _SAPP_MACOS + +/*== IOS DECLARATIONS ========================================================*/ +#if defined(_SAPP_IOS) + +@interface _sapp_app_delegate : NSObject<UIApplicationDelegate> +@end +@interface _sapp_textfield_dlg : NSObject<UITextFieldDelegate> +- (void)keyboardWasShown:(NSNotification*)notif; +- (void)keyboardWillBeHidden:(NSNotification*)notif; +- (void)keyboardDidChangeFrame:(NSNotification*)notif; +@end +#if defined(SOKOL_METAL) +    @interface _sapp_ios_view : MTKView; +    @end +#else +    @interface _sapp_ios_view : GLKView +    @end +#endif + +typedef struct { +    UIWindow* window; +    _sapp_ios_view* view; +    UITextField* textfield; +    _sapp_textfield_dlg* textfield_dlg; +    #if defined(SOKOL_METAL) +        UIViewController* view_ctrl; +        id<MTLDevice> mtl_device; +    #else +        GLKViewController* view_ctrl; +        EAGLContext* eagl_ctx; +    #endif +    bool suspended; +} _sapp_ios_t; + +#endif // _SAPP_IOS + +/*== EMSCRIPTEN DECLARATIONS =================================================*/ +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(SOKOL_WGPU) +typedef struct { +    int state; +    WGPUDevice device; +    WGPUSwapChain swapchain; +    WGPUTextureFormat render_format; +    WGPUTexture msaa_tex; +    WGPUTexture depth_stencil_tex; +    WGPUTextureView swapchain_view; +    WGPUTextureView msaa_view; +    WGPUTextureView depth_stencil_view; +} _sapp_wgpu_t; +#endif + +typedef struct { +    bool textfield_created; +    bool wants_show_keyboard; +    bool wants_hide_keyboard; +    bool mouse_lock_requested; +    #if defined(SOKOL_WGPU) +    _sapp_wgpu_t wgpu; +    #endif +} _sapp_emsc_t; +#endif // _SAPP_EMSCRIPTEN + +/*== WIN32 DECLARATIONS ======================================================*/ +#if defined(SOKOL_D3D11) && (defined(_SAPP_WIN32) || defined(_SAPP_UWP)) +typedef struct { +    ID3D11Device* device; +    ID3D11DeviceContext* device_context; +    ID3D11Texture2D* rt; +    ID3D11RenderTargetView* rtv; +    ID3D11Texture2D* msaa_rt; +    ID3D11RenderTargetView* msaa_rtv; +    ID3D11Texture2D* ds; +    ID3D11DepthStencilView* dsv; +    DXGI_SWAP_CHAIN_DESC swap_chain_desc; +    IDXGISwapChain* swap_chain; +} _sapp_d3d11_t; +#endif + +/*== WIN32 DECLARATIONS ======================================================*/ +#if defined(_SAPP_WIN32) + +#ifndef DPI_ENUMS_DECLARED +typedef enum PROCESS_DPI_AWARENESS +{ +    PROCESS_DPI_UNAWARE = 0, +    PROCESS_SYSTEM_DPI_AWARE = 1, +    PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; +typedef enum MONITOR_DPI_TYPE { +    MDT_EFFECTIVE_DPI = 0, +    MDT_ANGULAR_DPI = 1, +    MDT_RAW_DPI = 2, +    MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; +#endif /*DPI_ENUMS_DECLARED*/ + +typedef struct { +    bool aware; +    float content_scale; +    float window_scale; +    float mouse_scale; +} _sapp_win32_dpi_t; + +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; +    bool iconified; +    bool mouse_tracked; +    uint8_t mouse_capture_mask; +    _sapp_win32_dpi_t dpi; +    bool raw_input_mousepos_valid; +    LONG raw_input_mousepos_x; +    LONG raw_input_mousepos_y; +    uint8_t raw_input_data[256]; +} _sapp_win32_t; + +#if defined(SOKOL_GLCORE33) +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); +typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); +typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); +typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); +typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); +typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); + +typedef struct { +    HINSTANCE opengl32; +    HGLRC gl_ctx; +    PFN_wglCreateContext CreateContext; +    PFN_wglDeleteContext DeleteContext; +    PFN_wglGetProcAddress GetProcAddress; +    PFN_wglGetCurrentDC GetCurrentDC; +    PFN_wglMakeCurrent MakeCurrent; +    PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; +    PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; +    PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; +    PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; +    PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; +    bool ext_swap_control; +    bool arb_multisample; +    bool arb_pixel_format; +    bool arb_create_context; +    bool arb_create_context_profile; +    HWND msg_hwnd; +    HDC msg_dc; +} _sapp_wgl_t; +#endif // SOKOL_GLCORE33 + +#endif // _SAPP_WIN32 + +/*== UWP DECLARATIONS ======================================================*/ +#if defined(_SAPP_UWP) + +typedef struct { +    float content_scale; +    float window_scale; +    float mouse_scale; +} _sapp_uwp_dpi_t; + +typedef struct { +    bool mouse_tracked; +    uint8_t mouse_buttons; +    _sapp_uwp_dpi_t dpi; +} _sapp_uwp_t; + +#endif // _SAPP_UWP + +/*== ANDROID DECLARATIONS ====================================================*/ + +#if defined(_SAPP_ANDROID) +typedef enum { +    _SOKOL_ANDROID_MSG_CREATE, +    _SOKOL_ANDROID_MSG_RESUME, +    _SOKOL_ANDROID_MSG_PAUSE, +    _SOKOL_ANDROID_MSG_FOCUS, +    _SOKOL_ANDROID_MSG_NO_FOCUS, +    _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, +    _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, +    _SOKOL_ANDROID_MSG_DESTROY, +} _sapp_android_msg_t; + +typedef struct { +    pthread_t thread; +    pthread_mutex_t mutex; +    pthread_cond_t cond; +    int read_from_main_fd; +    int write_from_main_fd; +} _sapp_android_pt_t; + +typedef struct { +    ANativeWindow* window; +    AInputQueue* input; +} _sapp_android_resources_t; + +typedef struct { +    ANativeActivity* activity; +    _sapp_android_pt_t pt; +    _sapp_android_resources_t pending; +    _sapp_android_resources_t current; +    ALooper* looper; +    bool is_thread_started; +    bool is_thread_stopping; +    bool is_thread_stopped; +    bool has_created; +    bool has_resumed; +    bool has_focus; +    EGLConfig config; +    EGLDisplay display; +    EGLContext context; +    EGLSurface surface; +} _sapp_android_t; + +#endif // _SAPP_ANDROID + +/*== LINUX DECLARATIONS ======================================================*/ +#if defined(_SAPP_LINUX) + +#define _SAPP_X11_XDND_VERSION (5) + +#define GLX_VENDOR 1 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE	0x8011 +#define GLX_DOUBLEBUFFER 5 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_SAMPLES 0x186a1 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 + +typedef XID GLXWindow; +typedef XID GLXDrawable; +typedef struct __GLXFBConfig* GLXFBConfig; +typedef struct __GLXcontext* GLXContext; +typedef void (*__GLXextproc)(void); + +typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); +typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); +typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); +typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); +typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); +typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); +typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); +typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); +typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); +typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); +typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); +typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); +typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); +typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); + +typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); + +typedef struct { +    bool available; +    int major_opcode; +    int event_base; +    int error_base; +    int major; +    int minor; +} _sapp_xi_t; + +typedef struct { +    int version; +    Window source; +    Atom format; +    Atom XdndAware; +    Atom XdndEnter; +    Atom XdndPosition; +    Atom XdndStatus; +    Atom XdndActionCopy; +    Atom XdndDrop; +    Atom XdndFinished; +    Atom XdndSelection; +    Atom XdndTypeList; +    Atom text_uri_list; +} _sapp_xdnd_t; + +typedef struct { +    uint8_t mouse_buttons; +    Display* display; +    int screen; +    Window root; +    Colormap colormap; +    Window window; +    Cursor hidden_cursor; +    int window_state; +    float dpi; +    unsigned char error_code; +    Atom UTF8_STRING; +    Atom WM_PROTOCOLS; +    Atom WM_DELETE_WINDOW; +    Atom WM_STATE; +    Atom NET_WM_NAME; +    Atom NET_WM_ICON_NAME; +    Atom NET_WM_STATE; +    Atom NET_WM_STATE_FULLSCREEN; +    _sapp_xi_t xi; +    _sapp_xdnd_t xdnd; +} _sapp_x11_t; + +typedef struct { +    void* libgl; +    int major; +    int minor; +    int event_base; +    int error_base; +    GLXContext ctx; +    GLXWindow window; + +    // GLX 1.3 functions +    PFNGLXGETFBCONFIGSPROC GetFBConfigs; +    PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; +    PFNGLXGETCLIENTSTRINGPROC GetClientString; +    PFNGLXQUERYEXTENSIONPROC QueryExtension; +    PFNGLXQUERYVERSIONPROC QueryVersion; +    PFNGLXDESTROYCONTEXTPROC DestroyContext; +    PFNGLXMAKECURRENTPROC MakeCurrent; +    PFNGLXSWAPBUFFERSPROC SwapBuffers; +    PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; +    PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; +    PFNGLXCREATEWINDOWPROC CreateWindow; +    PFNGLXDESTROYWINDOWPROC DestroyWindow; + +    // GLX 1.4 and extension functions +    PFNGLXGETPROCADDRESSPROC GetProcAddress; +    PFNGLXGETPROCADDRESSPROC GetProcAddressARB; +    PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; +    PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; +    PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + +    // extension availability +    bool EXT_swap_control; +    bool MESA_swap_control; +    bool ARB_multisample; +    bool ARB_create_context; +    bool ARB_create_context_profile; +} _sapp_glx_t; + +#endif // _SAPP_LINUX + +/*== COMMON DECLARATIONS =====================================================*/ + +/* helper macros */ +#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) + +#define _SAPP_MAX_TITLE_LENGTH (128) +/* NOTE: the pixel format values *must* be compatible with sg_pixel_format */ +#define _SAPP_PIXELFORMAT_RGBA8 (23) +#define _SAPP_PIXELFORMAT_BGRA8 (27) +#define _SAPP_PIXELFORMAT_DEPTH (41) +#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (42) + +#if defined(_SAPP_MACOS) || defined(_SAPP_IOS) +    // this is ARC compatible +    #if defined(__cplusplus) +        #define _SAPP_CLEAR(type, item) { item = (type) { }; } +    #else +        #define _SAPP_CLEAR(type, item) { item = (type) { 0 }; } +    #endif +#else +    #define _SAPP_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } +#endif + +typedef struct { +    bool enabled; +    int buf_size; +    char* buffer; +} _sapp_clipboard_t; + +typedef struct { +    bool enabled; +    int max_files; +    int max_path_length; +    int num_files; +    int buf_size; +    char* buffer; +} _sapp_drop_t; + +typedef struct { +    float x, y; +    float dx, dy; +    bool shown; +    bool locked; +    bool pos_valid; +} _sapp_mouse_t; + +typedef struct { +    sapp_desc desc; +    bool valid; +    bool fullscreen; +    bool gles2_fallback; +    bool first_frame; +    bool init_called; +    bool cleanup_called; +    bool quit_requested; +    bool quit_ordered; +    bool event_consumed; +    bool html5_ask_leave_site; +    bool onscreen_keyboard_shown; +    int window_width; +    int window_height; +    int framebuffer_width; +    int framebuffer_height; +    int sample_count; +    int swap_interval; +    float dpi_scale; +    uint64_t frame_count; +    sapp_event event; +    _sapp_mouse_t mouse; +    _sapp_clipboard_t clipboard; +    _sapp_drop_t drop; +    #if defined(_SAPP_MACOS) +        _sapp_macos_t macos; +    #elif defined(_SAPP_IOS) +        _sapp_ios_t ios; +    #elif defined(_SAPP_EMSCRIPTEN) +        _sapp_emsc_t emsc; +    #elif defined(_SAPP_WIN32) +        _sapp_win32_t win32; +        #if defined(SOKOL_D3D11) +            _sapp_d3d11_t d3d11; +        #elif defined(SOKOL_GLCORE33) +            _sapp_wgl_t wgl; +        #endif +    #elif defined(_SAPP_UWP) +            _sapp_uwp_t uwp; +        #if defined(SOKOL_D3D11) +            _sapp_d3d11_t d3d11; +        #endif +    #elif defined(_SAPP_ANDROID) +        _sapp_android_t android; +    #elif defined(_SAPP_LINUX) +        _sapp_x11_t x11; +        _sapp_glx_t glx; +    #endif +    char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; +    char window_title[_SAPP_MAX_TITLE_LENGTH];      /* UTF-8 */ +    wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH];   /* UTF-32 or UCS-2 */ +    sapp_keycode keycodes[SAPP_MAX_KEYCODES]; + +    /* V patches */ +    bool __v_native_render;             /* V patch to allow for native rendering */ +} _sapp_t; +static _sapp_t _sapp; + +/*=== PRIVATE HELPER FUNCTIONS ===============================================*/ +_SOKOL_PRIVATE void _sapp_fail(const char* msg) { +    if (_sapp.desc.fail_cb) { +        _sapp.desc.fail_cb(msg); +    } +    else if (_sapp.desc.fail_userdata_cb) { +        _sapp.desc.fail_userdata_cb(msg, _sapp.desc.user_data); +    } +    else { +        SOKOL_LOG(msg); +    } +    SOKOL_ABORT(); +} + +_SOKOL_PRIVATE void _sapp_call_init(void) { +    if (_sapp.desc.init_cb) { +        _sapp.desc.init_cb(); +    } +    else if (_sapp.desc.init_userdata_cb) { +        _sapp.desc.init_userdata_cb(_sapp.desc.user_data); +    } +    _sapp.init_called = true; +} + +_SOKOL_PRIVATE void _sapp_call_frame(void) { +	if (_sapp.__v_native_render) { +		return; +	} +    if (_sapp.init_called && !_sapp.cleanup_called) { +        if (_sapp.desc.frame_cb) { +            _sapp.desc.frame_cb(); +        } +        else if (_sapp.desc.frame_userdata_cb) { +            _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); +        } +    } +} + +// __v_ +_SOKOL_PRIVATE void _sapp_call_frame_native(void) { +//puts("_sapp_call_frame_native()"); +//printf("init called=%d cleanup_called=%d\n", _sapp.init_called,_sapp.cleanup_called); +    if (_sapp.init_called && !_sapp.cleanup_called) { +        if (_sapp.desc.frame_cb) { +            _sapp.desc.frame_cb(); +        } +        else if (_sapp.desc.frame_userdata_cb) { +            _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); +        } +   } +} + + +_SOKOL_PRIVATE void _sapp_call_cleanup(void) { +    if (!_sapp.cleanup_called) { +        if (_sapp.desc.cleanup_cb) { +            _sapp.desc.cleanup_cb(); +        } +        else if (_sapp.desc.cleanup_userdata_cb) { +            _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); +        } +        _sapp.cleanup_called = true; +    } +} + +_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { +    if (!_sapp.cleanup_called) { +        if (_sapp.desc.event_cb) { +            _sapp.desc.event_cb(e); +        } +        else if (_sapp.desc.event_userdata_cb) { +            _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); +        } +    } +    if (_sapp.event_consumed) { +        _sapp.event_consumed = false; +        return true; +    } +    else { +        return false; +    } +} + +_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { +    SOKOL_ASSERT(_sapp.drop.buffer); +    SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); +    int offset = index * _sapp.drop.max_path_length; +    SOKOL_ASSERT(offset < _sapp.drop.buf_size); +    return &_sapp.drop.buffer[offset]; +} + +/* Copy a string into a fixed size buffer with guaranteed zero- +   termination. + +   Return false if the string didn't fit into the buffer and had to be clamped. + +   FIXME: Currently UTF-8 strings might become invalid if the string +   is clamped, because the last zero-byte might be written into +   the middle of a multi-byte sequence. +*/ +_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { +    SOKOL_ASSERT(src && dst && (max_len > 0)); +    char* const end = &(dst[max_len-1]); +    char c = 0; +    for (int i = 0; i < max_len; i++) { +        c = *src; +        if (c != 0) { +            src++; +        } +        *dst++ = c; +    } +    /* truncated? */ +    if (c != 0) { +        *end = 0; +        return false; +    } +    else { +        return true; +    } +} + +_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* in_desc) { +    sapp_desc desc = *in_desc; +    desc.width = _sapp_def(desc.width, 640); +    desc.height = _sapp_def(desc.height, 480); +    desc.sample_count = _sapp_def(desc.sample_count, 1); +    desc.swap_interval = _sapp_def(desc.swap_interval, 1); +    desc.html5_canvas_name = _sapp_def(desc.html5_canvas_name, "canvas"); +    desc.clipboard_size = _sapp_def(desc.clipboard_size, 8192); +    desc.max_dropped_files = _sapp_def(desc.max_dropped_files, 1); +    desc.max_dropped_file_path_length = _sapp_def(desc.max_dropped_file_path_length, 2048); +    desc.window_title = _sapp_def(desc.window_title, "sokol_app"); +    return desc; +} + +_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { +    _SAPP_CLEAR(_sapp_t, _sapp); +    _sapp.desc = _sapp_desc_defaults(desc); +    _sapp.first_frame = true; +    _sapp.window_width = _sapp.desc.width; +    _sapp.window_height = _sapp.desc.height; +    _sapp.framebuffer_width = _sapp.window_width; +    _sapp.framebuffer_height = _sapp.window_height; +    _sapp.sample_count = _sapp.desc.sample_count; +    _sapp.swap_interval = _sapp.desc.swap_interval; +    _sapp.html5_canvas_selector[0] = '#'; +    _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); +    _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; +    _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; +    _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; +    if (_sapp.clipboard.enabled) { +        _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; +        _sapp.clipboard.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.clipboard.buf_size); +    } +    _sapp.drop.enabled = _sapp.desc.enable_dragndrop; +    if (_sapp.drop.enabled) { +        _sapp.drop.max_files = _sapp.desc.max_dropped_files; +        _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; +        _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; +        _sapp.drop.buffer = (char*) SOKOL_CALLOC(1, (size_t)_sapp.drop.buf_size); +    } +    _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); +    _sapp.desc.window_title = _sapp.window_title; +    _sapp.dpi_scale = 1.0f; +    _sapp.fullscreen = _sapp.desc.fullscreen; +    _sapp.mouse.shown = true; +    _sapp.__v_native_render = _sapp.desc.__v_native_render; +} + +_SOKOL_PRIVATE void _sapp_discard_state(void) { +    if (_sapp.clipboard.enabled) { +        SOKOL_ASSERT(_sapp.clipboard.buffer); +        SOKOL_FREE((void*)_sapp.clipboard.buffer); +    } +    if (_sapp.drop.enabled) { +        SOKOL_ASSERT(_sapp.drop.buffer); +        SOKOL_FREE((void*)_sapp.drop.buffer); +    } +    _SAPP_CLEAR(_sapp_t, _sapp); +} + +_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { +    memset(&_sapp.event, 0, sizeof(_sapp.event)); +    _sapp.event.type = type; +    _sapp.event.frame_count = _sapp.frame_count; +    _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; +    _sapp.event.window_width = _sapp.window_width; +    _sapp.event.window_height = _sapp.window_height; +    _sapp.event.framebuffer_width = _sapp.framebuffer_width; +    _sapp.event.framebuffer_height = _sapp.framebuffer_height; +    _sapp.event.mouse_x = _sapp.mouse.x; +    _sapp.event.mouse_y = _sapp.mouse.y; +    _sapp.event.mouse_dx = _sapp.mouse.dx; +    _sapp.event.mouse_dy = _sapp.mouse.dy; +} + +_SOKOL_PRIVATE bool _sapp_events_enabled(void) { +    /* only send events when an event callback is set, and the init function was called */ +    return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; +} + +_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { +    if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { +        return _sapp.keycodes[scan_code]; +    } +    else { +        return SAPP_KEYCODE_INVALID; +    } +} + +_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { +    if (_sapp.drop.enabled) { +        SOKOL_ASSERT(_sapp.drop.buffer); +        memset(_sapp.drop.buffer, 0, (size_t)_sapp.drop.buf_size); +    } +} + +_SOKOL_PRIVATE void _sapp_frame(void) { +    if (_sapp.first_frame) { +        _sapp.first_frame = false; +        _sapp_call_init(); +    } +    _sapp_call_frame(); +    _sapp.frame_count++; +} + +/*== MacOS/iOS ===============================================================*/ +#if defined(_SAPP_APPLE) + +#if __has_feature(objc_arc) +#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +/*== MacOS ===================================================================*/ +#if defined(_SAPP_MACOS) + +_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { +    _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; +    _sapp.keycodes[0x12] = SAPP_KEYCODE_1; +    _sapp.keycodes[0x13] = SAPP_KEYCODE_2; +    _sapp.keycodes[0x14] = SAPP_KEYCODE_3; +    _sapp.keycodes[0x15] = SAPP_KEYCODE_4; +    _sapp.keycodes[0x17] = SAPP_KEYCODE_5; +    _sapp.keycodes[0x16] = SAPP_KEYCODE_6; +    _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; +    _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; +    _sapp.keycodes[0x19] = SAPP_KEYCODE_9; +    _sapp.keycodes[0x00] = SAPP_KEYCODE_A; +    _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; +    _sapp.keycodes[0x08] = SAPP_KEYCODE_C; +    _sapp.keycodes[0x02] = SAPP_KEYCODE_D; +    _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; +    _sapp.keycodes[0x03] = SAPP_KEYCODE_F; +    _sapp.keycodes[0x05] = SAPP_KEYCODE_G; +    _sapp.keycodes[0x04] = SAPP_KEYCODE_H; +    _sapp.keycodes[0x22] = SAPP_KEYCODE_I; +    _sapp.keycodes[0x26] = SAPP_KEYCODE_J; +    _sapp.keycodes[0x28] = SAPP_KEYCODE_K; +    _sapp.keycodes[0x25] = SAPP_KEYCODE_L; +    _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; +    _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; +    _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; +    _sapp.keycodes[0x23] = SAPP_KEYCODE_P; +    _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; +    _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; +    _sapp.keycodes[0x01] = SAPP_KEYCODE_S; +    _sapp.keycodes[0x11] = SAPP_KEYCODE_T; +    _sapp.keycodes[0x20] = SAPP_KEYCODE_U; +    _sapp.keycodes[0x09] = SAPP_KEYCODE_V; +    _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; +    _sapp.keycodes[0x07] = SAPP_KEYCODE_X; +    _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; +    _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; +    _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; +    _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; +    _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; +    _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; +    _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; +    _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; +    _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; +    _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; +    _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; +    _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; +    _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; +    _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; +    _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; +    _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; +    _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; +    _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; +    _sapp.keycodes[0x77] = SAPP_KEYCODE_END; +    _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; +    _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; +    _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; +    _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; +    _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; +    _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; +    _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; +    _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; +    _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; +    _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; +    _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; +    _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; +    _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; +    _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; +    _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; +    _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; +    _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; +    _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; +    _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; +    _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; +    _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; +    _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; +    _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; +    _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; +    _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; +    _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; +    _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; +    _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; +    _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; +    _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; +    _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; +    _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; +    _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; +    _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; +    _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; +    _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; +    _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; +    _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; +    _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; +    _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; +    _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; +    _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; +    _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; +    _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; +    _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; +    _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; +    _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; +    _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; +    _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; +    _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; +    _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; +    _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; +    _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; +    _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; +    _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; +    _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; +    _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; +    _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; +} + +_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { +    // NOTE: it's safe to call [release] on a nil object +    _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); +    _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); +    _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); +    _SAPP_OBJC_RELEASE(_sapp.macos.view); +    #if defined(SOKOL_METAL) +        _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); +    #endif +    _SAPP_OBJC_RELEASE(_sapp.macos.window); +} + +_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { +    _sapp_init_state(desc); +    _sapp_macos_init_keytable(); +    [NSApplication sharedApplication]; +    NSApp.activationPolicy = NSApplicationActivationPolicyRegular; +    _sapp.macos.app_dlg = [[_sapp_macos_app_delegate alloc] init]; +    NSApp.delegate = _sapp.macos.app_dlg; +    [NSApp activateIgnoringOtherApps:YES]; +    [NSApp run]; +    // NOTE: [NSApp run] never returns, instead cleanup code +    // must be put into applicationWillTerminate +} + +/* MacOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { +    sapp_desc desc = sokol_main(argc, argv); +    _sapp_macos_run(&desc); +    return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE uint32_t _sapp_macos_mod(NSEventModifierFlags f) { +    uint32_t m = 0; +    if (f & NSEventModifierFlagShift) { +        m |= SAPP_MODIFIER_SHIFT; +    } +    if (f & NSEventModifierFlagControl) { +        m |= SAPP_MODIFIER_CTRL; +    } +    if (f & NSEventModifierFlagOption) { +        m |= SAPP_MODIFIER_ALT; +    } +    if (f & NSEventModifierFlagCommand) { +        m |= SAPP_MODIFIER_SUPER; +    } +    return m; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp.event.mouse_button = btn; +        _sapp.event.modifiers = mod; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp.event.key_code = key; +        _sapp.event.key_repeat = repeat; +        _sapp.event.modifiers = mod; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp_call_event(&_sapp.event); +    } +} + +/* NOTE: unlike the iOS version of this function, the macOS version +    can dynamically update the DPI scaling factor when a window is moved +    between HighDPI / LowDPI screens. +*/ +_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { +    #if defined(SOKOL_METAL) +        const NSRect fb_rect = [_sapp.macos.view bounds]; +        _sapp.framebuffer_width = fb_rect.size.width * _sapp.dpi_scale; +        _sapp.framebuffer_height = fb_rect.size.height * _sapp.dpi_scale; +    #elif defined(SOKOL_GLCORE33) +        const NSRect fb_rect = [_sapp.macos.view convertRectToBacking:[_sapp.macos.view frame]]; +        _sapp.framebuffer_width = fb_rect.size.width; +        _sapp.framebuffer_height = fb_rect.size.height; +    #endif +    const NSRect bounds = [_sapp.macos.view bounds]; +    _sapp.window_width = bounds.size.width; +    _sapp.window_height = bounds.size.height; +    if (_sapp.framebuffer_width == 0) { +        _sapp.framebuffer_width = 1; +    } +    if (_sapp.framebuffer_height == 0) { +        _sapp.framebuffer_height = 1; +    } +    if (_sapp.window_width == 0) { +        _sapp.window_width = 1; +    } +    if (_sapp.window_height == 0) { +        _sapp.window_height = 1; +    } +    _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; + +    /* NOTE: _sapp_macos_update_dimensions() isn't called each frame, but only +        when the window size actually changes, so resizing the MTKView's +        in each call is fine even when MTKView doesn't ignore setting an +        identical drawableSize. +    */ +    #if defined(SOKOL_METAL) +    CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; +    _sapp.macos.view.drawableSize = drawable_size; +    #endif +} + +_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { +    /* NOTE: the _sapp.fullscreen flag is also notified by the +       windowDidEnterFullscreen / windowDidExitFullscreen +       event handlers +    */ +    _sapp.fullscreen = !_sapp.fullscreen; +    [_sapp.macos.window toggleFullScreen:nil]; +} + +_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { +    @autoreleasepool { +        NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; +        [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; +        [pasteboard setString:@(str) forType:NSPasteboardTypeString]; +    } +} + +_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { +    SOKOL_ASSERT(_sapp.clipboard.buffer); +    @autoreleasepool { +        _sapp.clipboard.buffer[0] = 0; +        NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; +        if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { +            return _sapp.clipboard.buffer; +        } +        NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; +        if (!str) { +            return _sapp.clipboard.buffer; +        } +        _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); +    } +    return _sapp.clipboard.buffer; +} + +_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(NSEvent* event) { +    if (!_sapp.mouse.locked) { +        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 */ +        if (_sapp.mouse.pos_valid) { +            _sapp.mouse.dx = new_x - _sapp.mouse.x; +            _sapp.mouse.dy = new_y - _sapp.mouse.y; +        } +        _sapp.mouse.x = new_x; +        _sapp.mouse.y = new_y; +        _sapp.mouse.pos_valid = true; +    } +} + +_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { +    /* NOTE: this function is only called when the mouse visibility actually changes */ +    if (visible) { +        CGDisplayShowCursor(kCGDirectMainDisplay); +    } +    else { +        CGDisplayHideCursor(kCGDirectMainDisplay); +    } +} + +_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { +    if (lock == _sapp.mouse.locked) { +        return; +    } +    _sapp.mouse.dx = 0.0f; +    _sapp.mouse.dy = 0.0f; +    _sapp.mouse.locked = lock; +    /* +        NOTE that this code doesn't warp the mouse cursor to the window +        center as everybody else does it. This lead to a spike in the +        *second* mouse-moved event after the warp happened. The +        mouse centering doesn't seem to be required (mouse-moved events +        are reported correctly even when the cursor is at an edge of the screen). + +        NOTE also that the hide/show of the mouse cursor should properly +        stack with calls to sapp_show_mouse() +    */ +    if (_sapp.mouse.locked) { +        CGAssociateMouseAndMouseCursorPosition(NO); +        CGDisplayHideCursor(kCGDirectMainDisplay); +    } +    else { +        CGDisplayShowCursor(kCGDirectMainDisplay); +        CGAssociateMouseAndMouseCursorPosition(YES); +    } +} + +_SOKOL_PRIVATE void _sapp_macos_frame(void) { +    _sapp_frame(); +    if (_sapp.quit_requested || _sapp.quit_ordered) { +        [_sapp.macos.window performClose:nil]; +    } +} + +#include "sokol_app2.h" // __v_ + +@implementation _sapp_macos_app_delegate +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { +    _SOKOL_UNUSED(aNotification); +    if (_sapp.fullscreen) { +        NSRect screen_rect = NSScreen.mainScreen.frame; +        _sapp.window_width = screen_rect.size.width; +        _sapp.window_height = screen_rect.size.height; +    } +    if (_sapp.desc.high_dpi) { +        _sapp.framebuffer_width = 2 * _sapp.window_width; +        _sapp.framebuffer_height = 2 * _sapp.window_height; +    } +    else { +        _sapp.framebuffer_width = _sapp.window_width; +        _sapp.framebuffer_height = _sapp.window_height; +    } +    _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float) _sapp.window_width; +    const NSUInteger style =  _sapp.desc.fullscreen ? NSWindowStyleMaskBorderless : // __v_ +        NSWindowStyleMaskTitled | +        NSWindowStyleMaskClosable | +        NSWindowStyleMaskMiniaturizable | +        NSWindowStyleMaskResizable; +    NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); +    _sapp.macos.window = [[_sapp_macos_window alloc] +        initWithContentRect:window_rect +        styleMask:style +        backing:NSBackingStoreBuffered +        defer:NO]; +    _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate +    _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; +    _sapp.macos.window.acceptsMouseMovedEvents = YES; +    _sapp.macos.window.restorable = YES; + + + +   // _v__ +   _sapp.macos.window.backgroundColor = [NSColor whiteColor]; + +	// Quit menu +	NSMenu* menu_bar = [[NSMenu alloc] init]; +NSMenuItem* app_menu_item = [[NSMenuItem alloc] init]; +[menu_bar addItem:app_menu_item]; +NSApp.mainMenu = menu_bar; +NSMenu* app_menu = [[NSMenu alloc] init]; +NSString* window_title_as_nsstring = [NSString stringWithUTF8String:_sapp.window_title]; +// `quit_title` memory will be owned by the NSMenuItem, so no need to release it ourselves +NSString* quit_title =  [@"Quit " stringByAppendingString:window_title_as_nsstring]; +NSMenuItem* quit_menu_item = [[NSMenuItem alloc] +	initWithTitle:quit_title +	action:@selector(terminate:) +	keyEquivalent:@"q"]; +[app_menu addItem:quit_menu_item]; +app_menu_item.submenu = app_menu; +_SAPP_OBJC_RELEASE( window_title_as_nsstring ); +_SAPP_OBJC_RELEASE( app_menu ); +_SAPP_OBJC_RELEASE( app_menu_item ); +_SAPP_OBJC_RELEASE( menu_bar ); + + +  // _v__ + +    _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; +    _sapp.macos.window.delegate = _sapp.macos.win_dlg; +    #if defined(SOKOL_METAL) +        _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); +        _sapp.macos.view = [[_sapp_macos_view alloc] init]; +        [_sapp.macos.view updateTrackingAreas]; +        _sapp.macos.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; +        _sapp.macos.view.device = _sapp.macos.mtl_device; +        _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; +        _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; +        _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; +        _sapp.macos.view.autoResizeDrawable = false; +        _sapp.macos.window.contentView = _sapp.macos.view; +        [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; +        _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; +    #elif defined(SOKOL_GLCORE33) +        NSOpenGLPixelFormatAttribute attrs[32]; +        int i = 0; +        attrs[i++] = NSOpenGLPFAAccelerated; +        attrs[i++] = NSOpenGLPFADoubleBuffer; +        attrs[i++] = NSOpenGLPFAOpenGLProfile; attrs[i++] = NSOpenGLProfileVersion3_2Core; +        attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; +        attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; +        attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; +        attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; +        if (_sapp.sample_count > 1) { +            attrs[i++] = NSOpenGLPFAMultisample; +            attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; +            attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; +        } +        else { +            attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; +        } +        attrs[i++] = 0; +        NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; +        SOKOL_ASSERT(glpixelformat_obj != nil); + +        _sapp.macos.view = [[_sapp_macos_view alloc] +            initWithFrame:window_rect +            pixelFormat:glpixelformat_obj]; +        _SAPP_OBJC_RELEASE(glpixelformat_obj); +        [_sapp.macos.view updateTrackingAreas]; +        if (_sapp.desc.high_dpi) { +            [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; +        } +        else { +            [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; +        } + +        _sapp.macos.window.contentView = _sapp.macos.view; +        [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + +        NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 +            target:_sapp.macos.view +            selector:@selector(timerFired:) +            userInfo:nil +            repeats:YES]; +        [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; +        timer_obj = nil; +    #endif +    _sapp.valid = true; +   // __v_ +	if (!_sapp.__v_native_render) { +    if (_sapp.fullscreen) { +        // on GL, this already toggles a rendered frame, so set the valid flag before +        [_sapp.macos.window toggleFullScreen:self]; +    } +    else { +        [_sapp.macos.window center]; +    } +   } +  // __v C +	/////////////////////////////////////////////////////// +	// Create a child view for native rendering +	if (_sapp.__v_native_render) { + +	CGRect wRect = _sapp.macos.window.frame; +	NSView *contentView  =_sapp.macos.window.contentView; +	CGRect cRect = contentView.frame; + +	CGRect rect = CGRectMake(wRect.origin.x, wRect.origin.y, cRect.size.width, cRect.size.height); +	NSWindow *overlayWindow = [[NSWindow alloc]initWithContentRect:rect +	                                                     styleMask:NSBorderlessWindowMask +	                                                       backing:NSBackingStoreBuffered +	                                                         defer:NO]; +	//overlayWindow.backgroundColor = [NSColor whiteColor]; + +	//overlayWindow.backgroundColor = [[NSColor whiteColor] colorWithAlphaComponent:0]; +	[overlayWindow setOpaque:YES]; +	[_sapp.macos.window setIgnoresMouseEvents:NO]; +	g_view = [[MyView2 alloc] init]; +	        overlayWindow.contentView = g_view; + +	[   contentView addSubview:g_view]; +//[    _sapp.macos.window addChildWindow:overlayWindow ordered:NSWindowAbove]; +        [_sapp.macos.window center]; + +} +   ////////////////////////////////// + +    [_sapp.macos.window makeKeyAndOrderFront:nil]; +    _sapp_macos_update_dimensions(); +//    [NSEvent setMouseCoalescingEnabled:NO]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { +    _SOKOL_UNUSED(sender); +    return YES; +} + +- (void)applicationWillTerminate:(NSNotification*)notification { +    _SOKOL_UNUSED(notification); +    _sapp_call_cleanup(); +    _sapp_macos_discard_state(); +    _sapp_discard_state(); +} +@end + +@implementation _sapp_macos_window_delegate +- (BOOL)windowShouldClose:(id)sender { +    _SOKOL_UNUSED(sender); +    /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ +    if (!_sapp.quit_ordered) { +        /* if window should be closed and event handling is enabled, give user code +           a chance to intervene via sapp_cancel_quit() +        */ +        _sapp.quit_requested = true; +        _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); +        /* user code hasn't intervened, quit the app */ +        if (_sapp.quit_requested) { +            _sapp.quit_ordered = true; +        } +    } +    if (_sapp.quit_ordered) { +        return YES; +    } +    else { +        return NO; +    } +} + +- (void)windowDidResize:(NSNotification*)notification { +    _SOKOL_UNUSED(notification); +    _sapp_macos_update_dimensions(); +    if (!_sapp.first_frame) { +        _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); +    } +} + +- (void)windowDidMiniaturize:(NSNotification*)notification { +    _SOKOL_UNUSED(notification); +    _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification { +    _SOKOL_UNUSED(notification); +    _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification { +    _SOKOL_UNUSED(notification); +    _sapp.fullscreen = true; +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification { +    _SOKOL_UNUSED(notification); +    _sapp.fullscreen = false; +} +@end + +@implementation _sapp_macos_window + +// __v_ +- (BOOL)canBecomeKeyWindow { return YES; } // needed for NSWindowStyleMaskBorderless +- (BOOL)canBecomeMainWindow { return YES; } +// __v_ + +- (instancetype)initWithContentRect:(NSRect)contentRect +                          styleMask:(NSWindowStyleMask)style +                            backing:(NSBackingStoreType)backingStoreType +                              defer:(BOOL)flag { +    if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { +        #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 +            [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +        #endif +    } +    return self; +} + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { +    return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { +    return NSDragOperationCopy; +} + +- (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(); +        _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : pboard.pasteboardItems.count; +        bool drop_failed = false; +        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"); +                drop_failed = true; +                break; +            } +        } +        if (!drop_failed) { +            if (_sapp_events_enabled()) { +                _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); +                _sapp_call_event(&_sapp.event); +            } +        } +        else { +            _sapp_clear_drop_buffer(); +            _sapp.drop.num_files = 0; +        } +        return YES; +    } +    #endif +    return NO; +} +@end + +@implementation _sapp_macos_view +#if defined(SOKOL_GLCORE33) +/* NOTE: this is a hack/fix when the initial window size has been clipped by +    macOS because it didn't fit on the screen, in that case the +    frame size of the window is reported wrong if low-dpi rendering +    was requested (instead the high-dpi dimensions are returned) +    until the window is resized for the first time. + +    Hooking into reshape and getting the frame dimensions seems to report +    the correct dimensions. +*/ +- (void)reshape { +    _sapp_macos_update_dimensions(); +    [super reshape]; +} +- (void)timerFired:(id)sender { +    _SOKOL_UNUSED(sender); +    [self setNeedsDisplay:YES]; +} +- (void)prepareOpenGL { +    [super prepareOpenGL]; +    GLint swapInt = 1; +    NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; +    [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; +    [ctx makeCurrentContext]; +} +#endif + +_SOKOL_PRIVATE void _sapp_macos_poll_input_events() { +    /* + +    NOTE: late event polling temporarily out-commented to check if this +    causes infrequent and almost impossible to reproduce probelms with the +    window close events, see: +    https://github.com/floooh/sokol/pull/483#issuecomment-805148815 + + +    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]; +    #endif +} + +- (BOOL)isOpaque { +    return YES; +} +- (BOOL)canBecomeKeyView { +    return YES; +} +- (BOOL)acceptsFirstResponder { +    return YES; +} +- (BOOL)acceptsFirstMouse:(NSEvent *)event { +        return YES; +} + + +- (void)updateTrackingAreas { +    if (_sapp.macos.tracking_area != nil) { +        [self removeTrackingArea:_sapp.macos.tracking_area]; +        _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); +    } +    const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | +                                          NSTrackingActiveInKeyWindow | +                                          NSTrackingEnabledDuringMouseDrag | +                                          NSTrackingCursorUpdate | +                                          NSTrackingInVisibleRect | +                                          NSTrackingAssumeInside; +    _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; +    [self addTrackingArea:_sapp.macos.tracking_area]; +    [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 +    */ +    if (0 == _sapp.macos.mouse_buttons) { +        _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mod(event.modifierFlags)); +    } +} +- (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]; +            _sapp.mouse.dy = [event deltaY]; +        } +        _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_MIDDLE, _sapp_macos_mod(event.modifierFlags)); +    } +} +- (void)mouseMoved:(NSEvent*)event { +    _sapp_macos_update_mouse(event); +    if (_sapp.mouse.locked) { +        _sapp.mouse.dx = [event deltaX]; +        _sapp.mouse.dy = [event deltaY]; +    } +    _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]; +    } +    _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]; +    } +    _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; +        if (event.hasPreciseScrollingDeltas) { +            dx *= 0.1; +            dy *= 0.1; +        } +        if ((_sapp_absf(dx) > 0.0f) || (_sapp_absf(dy) > 0.0f)) { +            _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); +            _sapp.event.modifiers = _sapp_macos_mod(event.modifierFlags); +            _sapp.event.scroll_x = dx; +            _sapp.event.scroll_y = dy; +            _sapp_call_event(&_sapp.event); +        } +    } +} +- (void)keyDown:(NSEvent*)event { +    if (_sapp_events_enabled()) { +        const uint32_t mods = _sapp_macos_mod(event.modifierFlags); +        /* NOTE: macOS doesn't send keyUp events while the Cmd key is pressed, +            as a workaround, to prevent key presses from sticking we'll send +            a keyup event following right after the keydown if SUPER is also pressed +        */ +        const sapp_keycode key_code = _sapp_translate_key(event.keyCode); +        _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); +        if (0 != (mods & SAPP_MODIFIER_SUPER)) { +            _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, key_code, event.isARepeat, mods); +        } +        const NSString* chars = event.characters; +        const NSUInteger len = chars.length; +        if (len > 0) { +            _sapp_init_event(SAPP_EVENTTYPE_CHAR); +            _sapp.event.modifiers = mods; +            for (NSUInteger i = 0; i < len; i++) { +                const unichar codepoint = [chars characterAtIndex:i]; +                if ((codepoint & 0xFF00) == 0xF700) { +                    continue; +                } +                _sapp.event.char_code = codepoint; +                _sapp.event.key_repeat = event.isARepeat; +                _sapp_call_event(&_sapp.event); +            } +        } +        /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ +        if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { +            _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); +            _sapp_call_event(&_sapp.event); +        } +    } +} +- (void)keyUp:(NSEvent*)event { +    _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, +        _sapp_translate_key(event.keyCode), +        event.isARepeat, +        _sapp_macos_mod(event.modifierFlags)); +} +- (void)flagsChanged:(NSEvent*)event { +    const uint32_t old_f = _sapp.macos.flags_changed_store; +    const uint32_t new_f = event.modifierFlags; +    _sapp.macos.flags_changed_store = new_f; +    sapp_keycode key_code = SAPP_KEYCODE_INVALID; +    bool down = false; +    if ((new_f ^ old_f) & NSEventModifierFlagShift) { +        key_code = SAPP_KEYCODE_LEFT_SHIFT; +        down = 0 != (new_f & NSEventModifierFlagShift); +    } +    if ((new_f ^ old_f) & NSEventModifierFlagControl) { +        key_code = SAPP_KEYCODE_LEFT_CONTROL; +        down = 0 != (new_f & NSEventModifierFlagControl); +    } +    if ((new_f ^ old_f) & NSEventModifierFlagOption) { +        key_code = SAPP_KEYCODE_LEFT_ALT; +        down = 0 != (new_f & NSEventModifierFlagOption); +    } +    if ((new_f ^ old_f) & NSEventModifierFlagCommand) { +        key_code = SAPP_KEYCODE_LEFT_SUPER; +        down = 0 != (new_f & NSEventModifierFlagCommand); +    } +    if (key_code != SAPP_KEYCODE_INVALID) { +        _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, +            key_code, +            false, +            _sapp_macos_mod(event.modifierFlags)); +    } +} +- (void)cursorUpdate:(NSEvent*)event { +    _SOKOL_UNUSED(event); +    if (_sapp.desc.user_cursor) { +        _sapp_macos_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); +    } +} +@end + +#endif /* MacOS */ + +/*== iOS =====================================================================*/ +#if defined(_SAPP_IOS) + +_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { +    // NOTE: it's safe to call [release] on a nil object +    _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); +    _SAPP_OBJC_RELEASE(_sapp.ios.textfield); +    #if defined(SOKOL_METAL) +        _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); +        _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); +    #else +        _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); +        _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); +    #endif +    _SAPP_OBJC_RELEASE(_sapp.ios.view); +    _SAPP_OBJC_RELEASE(_sapp.ios.window); +} + +_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { +    _sapp_init_state(desc); +    static int argc = 1; +    static char* argv[] = { (char*)"sokol_app" }; +    UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); +} + +/* iOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { +    sapp_desc desc = sokol_main(argc, argv); +    _sapp_ios_run(&desc); +    return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet<UITouch *>* touches, UIEvent* event) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        NSEnumerator* enumerator = event.allTouches.objectEnumerator; +        UITouch* ios_touch; +        while ((ios_touch = [enumerator nextObject])) { +            if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { +                CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; +                sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; +                cur_point->identifier = (uintptr_t) ios_touch; +                cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; +                cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; +                cur_point->changed = [touches containsObject:ios_touch]; +            } +        } +        if (_sapp.event.num_touches > 0) { +            _sapp_call_event(&_sapp.event); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { +    CGRect screen_rect = UIScreen.mainScreen.bounds; +    _sapp.framebuffer_width = (int)(screen_rect.size.width * _sapp.dpi_scale); +    _sapp.framebuffer_height = (int)(screen_rect.size.height * _sapp.dpi_scale); +    _sapp.window_width = (int)screen_rect.size.width; +    _sapp.window_height = (int)screen_rect.size.height; +    int cur_fb_width, cur_fb_height; +    #if defined(SOKOL_METAL) +        const CGSize fb_size = _sapp.ios.view.drawableSize; +        cur_fb_width = (int) fb_size.width; +        cur_fb_height = (int) fb_size.height; +    #else +        cur_fb_width = (int) _sapp.ios.view.drawableWidth; +        cur_fb_height = (int) _sapp.ios.view.drawableHeight; +    #endif +    const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || +                             (_sapp.framebuffer_height != cur_fb_height); +    if (dim_changed) { +        #if defined(SOKOL_METAL) +            const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; +            _sapp.ios.view.drawableSize = drawable_size; +        #else +            // nothing to do here, GLKView correctly respects the view's contentScaleFactor +        #endif +        if (!_sapp.first_frame) { +            _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_ios_frame(void) { +    _sapp_ios_update_dimensions(); +    _sapp_frame(); +} + +_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { +    /* if not happened yet, create an invisible text field */ +    if (nil == _sapp.ios.textfield) { +        _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; +        _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; +        _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; +        _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; +        _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; +        _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; +        _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; +        _sapp.ios.textfield.hidden = YES; +        _sapp.ios.textfield.text = @"x"; +        _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; +        [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; + +        [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg +            selector:@selector(keyboardWasShown:) +            name:UIKeyboardDidShowNotification object:nil]; +        [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg +            selector:@selector(keyboardWillBeHidden:) +            name:UIKeyboardWillHideNotification object:nil]; +        [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg +            selector:@selector(keyboardDidChangeFrame:) +            name:UIKeyboardDidChangeFrameNotification object:nil]; +    } +    if (shown) { +        /* setting the text field as first responder brings up the onscreen keyboard */ +        [_sapp.ios.textfield becomeFirstResponder]; +    } +    else { +        [_sapp.ios.textfield resignFirstResponder]; +    } +} + +@implementation _sapp_app_delegate +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { +    CGRect screen_rect = UIScreen.mainScreen.bounds; +    _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; +    _sapp.window_width = screen_rect.size.width; +    _sapp.window_height = screen_rect.size.height; +    if (_sapp.desc.high_dpi) { +        _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; +    } +    else { +        _sapp.dpi_scale = 1.0f; +    } +    _sapp.framebuffer_width = _sapp.window_width * _sapp.dpi_scale; +    _sapp.framebuffer_height = _sapp.window_height * _sapp.dpi_scale; +    #if defined(SOKOL_METAL) +        _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); +        _sapp.ios.view = [[_sapp_ios_view alloc] init]; +        _sapp.ios.view.preferredFramesPerSecond = 60 / _sapp.swap_interval; +        _sapp.ios.view.device = _sapp.ios.mtl_device; +        _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; +        _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; +        _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; +        /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor +            and automatically renders at Retina resolution. We'll disable +            autoResize and instead do the resizing in _sapp_ios_update_dimensions() +        */ +        _sapp.ios.view.autoResizeDrawable = false; +        _sapp.ios.view.userInteractionEnabled = YES; +        _sapp.ios.view.multipleTouchEnabled = YES; +        _sapp.ios.view_ctrl = [[UIViewController alloc] init]; +        _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; +        _sapp.ios.view_ctrl.view = _sapp.ios.view; +        _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; +    #else +        if (_sapp.desc.gl_force_gles2) { +            _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; +            _sapp.gles2_fallback = true; +        } +        else { +            _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; +            if (_sapp.ios.eagl_ctx == nil) { +                _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; +                _sapp.gles2_fallback = true; +            } +        } +        _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; +        _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; +        _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; +        _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; +        GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; +        _sapp.ios.view.drawableMultisample = msaa; +        _sapp.ios.view.context = _sapp.ios.eagl_ctx; +        _sapp.ios.view.enableSetNeedsDisplay = NO; +        _sapp.ios.view.userInteractionEnabled = YES; +        _sapp.ios.view.multipleTouchEnabled = YES; +        // on GLKView, contentScaleFactor appears to work just fine! +        if (_sapp.desc.high_dpi) { +            _sapp.ios.view.contentScaleFactor = 2.0; +        } +        else { +            _sapp.ios.view.contentScaleFactor = 1.0; +        } +        _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; +        _sapp.ios.view_ctrl.view = _sapp.ios.view; +        _sapp.ios.view_ctrl.preferredFramesPerSecond = 60 / _sapp.swap_interval; +        _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; +    #endif +    [_sapp.ios.window makeKeyAndVisible]; + +    _sapp.valid = true; +    return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { +    if (!_sapp.ios.suspended) { +        _sapp.ios.suspended = true; +        _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); +    } +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { +    if (_sapp.ios.suspended) { +        _sapp.ios.suspended = false; +        _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); +    } +} + +/* NOTE: this method will rarely ever be called, iOS application +    which are terminated by the user are usually killed via signal 9 +    by the operating system. +*/ +- (void)applicationWillTerminate:(UIApplication *)application { +    _SOKOL_UNUSED(application); +    _sapp_call_cleanup(); +    _sapp_ios_discard_state(); +    _sapp_discard_state(); +} +@end + +@implementation _sapp_textfield_dlg +- (void)keyboardWasShown:(NSNotification*)notif { +    _sapp.onscreen_keyboard_shown = true; +    /* query the keyboard's size, and modify the content view's size */ +    if (_sapp.desc.ios_keyboard_resizes_canvas) { +        NSDictionary* info = notif.userInfo; +        CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; +        CGRect view_frame = UIScreen.mainScreen.bounds; +        view_frame.size.height -= kbd_h; +        _sapp.ios.view.frame = view_frame; +    } +} +- (void)keyboardWillBeHidden:(NSNotification*)notif { +    _sapp.onscreen_keyboard_shown = false; +    if (_sapp.desc.ios_keyboard_resizes_canvas) { +        _sapp.ios.view.frame = UIScreen.mainScreen.bounds; +    } +} +- (void)keyboardDidChangeFrame:(NSNotification*)notif { +    /* this is for the case when the screen rotation changes while the keyboard is open */ +    if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { +        NSDictionary* info = notif.userInfo; +        CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; +        CGRect view_frame = UIScreen.mainScreen.bounds; +        view_frame.size.height -= kbd_h; +        _sapp.ios.view.frame = view_frame; +    } +} +- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { +    if (_sapp_events_enabled()) { +        const NSUInteger len = string.length; +        if (len > 0) { +            for (NSUInteger i = 0; i < len; i++) { +                unichar c = [string characterAtIndex:i]; +                if (c >= 32) { +                    /* ignore surrogates for now */ +                    if ((c < 0xD800) || (c > 0xDFFF)) { +                        _sapp_init_event(SAPP_EVENTTYPE_CHAR); +                        _sapp.event.char_code = c; +                        _sapp_call_event(&_sapp.event); +                    } +                } +                if (c <= 32) { +                    sapp_keycode k = SAPP_KEYCODE_INVALID; +                    switch (c) { +                        case 10: k = SAPP_KEYCODE_ENTER; break; +                        case 32: k = SAPP_KEYCODE_SPACE; break; +                        default: break; +                    } +                    if (k != SAPP_KEYCODE_INVALID) { +                        _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); +                        _sapp.event.key_code = k; +                        _sapp_call_event(&_sapp.event); +                        _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); +                        _sapp.event.key_code = k; +                        _sapp_call_event(&_sapp.event); +                    } +                } +            } +        } +        else { +            /* this was a backspace */ +            _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); +            _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; +            _sapp_call_event(&_sapp.event); +            _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); +            _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; +            _sapp_call_event(&_sapp.event); +        } +    } +    return NO; +} +@end + +@implementation _sapp_ios_view +- (void)drawRect:(CGRect)rect { +    _SOKOL_UNUSED(rect); +    _sapp_ios_frame(); +} +- (BOOL)isOpaque { +    return YES; +} +- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event { +    _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); +} +- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event { +    _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); +} +- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event { +    _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); +} +- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent*)event { +    _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); +} +@end +#endif /* TARGET_OS_IPHONE */ + +#endif /* _SAPP_APPLE */ + +/*== EMSCRIPTEN ==============================================================*/ +#if defined(_SAPP_EMSCRIPTEN) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); + +/* this function is called from a JS event handler when the user hides +    the onscreen keyboard pressing the 'dismiss keyboard key' +*/ +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_notify_keyboard_hidden(void) { +    _sapp.onscreen_keyboard_shown = false; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { +    if (_sapp.clipboard.enabled) { +        _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); +        if (_sapp_events_enabled()) { +            _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); +            _sapp_call_event(&_sapp.event); +        } +    } +} + +/*  https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ +EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { +    return _sapp.html5_ask_leave_site ? 1 : 0; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { +    if (!_sapp.drop.enabled) { +        return; +    } +    if (num < 0) { +        num = 0; +    } +    if (num > _sapp.drop.max_files) { +        num = _sapp.drop.max_files; +    } +    _sapp.drop.num_files = num; +    _sapp_clear_drop_buffer(); +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { +    /* NOTE: name is only the filename part, not a path */ +    if (!_sapp.drop.enabled) { +        return; +    } +    if (0 == name) { +        return; +    } +    SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); +    if ((i < 0) || (i >= _sapp.drop.num_files)) { +        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.drop.num_files = 0; +    } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y) { +    if (!_sapp.drop.enabled) { +        return; +    } +    if (0 == _sapp.drop.num_files) { +        /* there was an error copying the filenames */ +        _sapp_clear_drop_buffer(); +        return; + +    } +    if (_sapp_events_enabled()) { +        _sapp.mouse.x = (float)x * _sapp.dpi_scale; +        _sapp.mouse.y = (float)y * _sapp.dpi_scale; +        _sapp.mouse.dx = 0.0f; +        _sapp.mouse.dy = 0.0f; +        _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); +        _sapp_call_event(&_sapp.event); +    } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { +    sapp_html5_fetch_response response; +    memset(&response, 0, sizeof(response)); +    response.succeeded = (0 != success); +    response.error_code = (sapp_html5_fetch_error) error_code; +    response.file_index = index; +    response.fetched_size = fetched_size; +    response.buffer_ptr = buf_ptr; +    response.buffer_size = buf_size; +    response.user_data = user_data; +    callback(&response); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* Javascript helper functions for mobile virtual keyboard input */ +EM_JS(void, sapp_js_create_textfield, (void), { +    var _sapp_inp = document.createElement("input"); +    _sapp_inp.type = "text"; +    _sapp_inp.id = "_sokol_app_input_element"; +    _sapp_inp.autocapitalize = "none"; +    _sapp_inp.addEventListener("focusout", function(_sapp_event) { +        __sapp_emsc_notify_keyboard_hidden() + +    }); +    document.body.append(_sapp_inp); +}); + +EM_JS(void, sapp_js_focus_textfield, (void), { +    document.getElementById("_sokol_app_input_element").focus(); +}); + +EM_JS(void, sapp_js_unfocus_textfield, (void), { +    document.getElementById("_sokol_app_input_element").blur(); +}); + +EM_JS(void, sapp_js_add_beforeunload_listener, (void), { +    Module.sokol_beforeunload = function(event) { +        if (__sapp_html5_get_ask_leave_site() != 0) { +            event.preventDefault(); +            event.returnValue = ' '; +        } +    }; +    window.addEventListener('beforeunload', Module.sokol_beforeunload); +}); + +EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { +    window.removeEventListener('beforeunload', Module.sokol_beforeunload); +}); + +EM_JS(void, sapp_js_add_clipboard_listener, (void), { +    Module.sokol_paste = function(event) { +        var pasted_str = event.clipboardData.getData('text'); +        ccall('_sapp_emsc_onpaste', 'void', ['string'], [pasted_str]); +    }; +    window.addEventListener('paste', Module.sokol_paste); +}); + +EM_JS(void, sapp_js_remove_clipboard_listener, (void), { +    window.removeEventListener('paste', Module.sokol_paste); +}); + +EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { +    var str = UTF8ToString(c_str); +    var ta = document.createElement('textarea'); +    ta.setAttribute('autocomplete', 'off'); +    ta.setAttribute('autocorrect', 'off'); +    ta.setAttribute('autocapitalize', 'off'); +    ta.setAttribute('spellcheck', 'false'); +    ta.style.left = -100 + 'px'; +    ta.style.top = -100 + 'px'; +    ta.style.height = 1; +    ta.style.width = 1; +    ta.value = str; +    document.body.appendChild(ta); +    ta.select(); +    document.execCommand('copy'); +    document.body.removeChild(ta); +}); + +_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { +    sapp_js_write_clipboard(str); +} + +EM_JS(void, sapp_js_add_dragndrop_listeners, (const char* canvas_name_cstr), { +    Module.sokol_drop_files = []; +    var canvas_name = UTF8ToString(canvas_name_cstr); +    var canvas = document.getElementById(canvas_name); +    Module.sokol_dragenter = function(event) { +        event.stopPropagation(); +        event.preventDefault(); +    }; +    Module.sokol_dragleave = function(event) { +        event.stopPropagation(); +        event.preventDefault(); +    }; +    Module.sokol_dragover = function(event) { +        event.stopPropagation(); +        event.preventDefault(); +    }; +    Module.sokol_drop = function(event) { +        event.stopPropagation(); +        event.preventDefault(); +        var files = event.dataTransfer.files; +        Module.sokol_dropped_files = files; +        __sapp_emsc_begin_drop(files.length); +        var i; +        for (i = 0; i < files.length; i++) { +            ccall('_sapp_emsc_drop', 'void', ['number', 'string'], [i, files[i].name]); +        } +        // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect +        __sapp_emsc_end_drop(event.clientX, event.clientY); +    }; +    canvas.addEventListener('dragenter', Module.sokol_dragenter, false); +    canvas.addEventListener('dragleave', Module.sokol_dragleave, false); +    canvas.addEventListener('dragover',  Module.sokol_dragover, false); +    canvas.addEventListener('drop',      Module.sokol_drop, false); +}); + +EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { +    if ((index < 0) || (index >= Module.sokol_dropped_files.length)) { +        return 0; +    } +    else { +        return Module.sokol_dropped_files[index].size; +    } +}); + +EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { +    var reader = new FileReader(); +    reader.onload = function(loadEvent) { +        var content = loadEvent.target.result; +        if (content.byteLength > buf_size) { +            // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL +            __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); +        } +        else { +            HEAPU8.set(new Uint8Array(content), buf_ptr); +            __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); +        } +    }; +    reader.onerror = function() { +        // SAPP_HTML5_FETCH_ERROR_OTHER +        __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); +    }; +    reader.readAsArrayBuffer(Module.sokol_dropped_files[index]); +}); + +EM_JS(void, sapp_js_remove_dragndrop_listeners, (const char* canvas_name_cstr), { +    var canvas_name = UTF8ToString(canvas_name_cstr); +    var canvas = document.getElementById(canvas_name); +    canvas.removeEventListener('dragenter', Module.sokol_dragenter); +    canvas.removeEventListener('dragleave', Module.sokol_dragleave); +    canvas.removeEventListener('dragover',  Module.sokol_dragover); +    canvas.removeEventListener('drop',      Module.sokol_drop); +}); + +/* called from the emscripten event handler to update the keyboard visibility +    state, this must happen from an JS input event handler, otherwise +    the request will be ignored by the browser +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_keyboard_state(void) { +    if (_sapp.emsc.wants_show_keyboard) { +        /* create input text field on demand */ +        if (!_sapp.emsc.textfield_created) { +            _sapp.emsc.textfield_created = true; +            sapp_js_create_textfield(); +        } +        /* focus the text input field, this will bring up the keyboard */ +        _sapp.onscreen_keyboard_shown = true; +        _sapp.emsc.wants_show_keyboard = false; +        sapp_js_focus_textfield(); +    } +    if (_sapp.emsc.wants_hide_keyboard) { +        /* unfocus the text input field */ +        if (_sapp.emsc.textfield_created) { +            _sapp.onscreen_keyboard_shown = false; +            _sapp.emsc.wants_hide_keyboard = false; +            sapp_js_unfocus_textfield(); +        } +    } +} + +/* actually showing the onscreen keyboard must be initiated from a JS +    input event handler, so we'll just keep track of the desired +    state, and the actual state change will happen with the next input event +*/ +_SOKOL_PRIVATE void _sapp_emsc_show_keyboard(bool show) { +    if (show) { +        _sapp.emsc.wants_show_keyboard = true; +    } +    else { +        _sapp.emsc.wants_hide_keyboard = true; +    } +} + +EM_JS(void, sapp_js_pointer_init, (const char* c_str_target), { +    // lookup and store canvas object by name +    var target_str = UTF8ToString(c_str_target); +    Module.sapp_emsc_target = document.getElementById(target_str); +    if (!Module.sapp_emsc_target) { +        console.log("sokol_app.h: invalid target:" + target_str); +    } +    if (!Module.sapp_emsc_target.requestPointerLock) { +        console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_str); +    } +}); + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { +    _SOKOL_UNUSED(emsc_type); +    _SOKOL_UNUSED(user_data); +    _sapp.mouse.locked = emsc_event->isActive; +    return EM_TRUE; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { +    _SOKOL_UNUSED(emsc_type); +    _SOKOL_UNUSED(reserved); +    _SOKOL_UNUSED(user_data); +    _sapp.mouse.locked = false; +    _sapp.emsc.mouse_lock_requested = false; +    return true; +} + +EM_JS(void, sapp_js_request_pointerlock, (void), { +    if (Module.sapp_emsc_target) { +        if (Module.sapp_emsc_target.requestPointerLock) { +            Module.sapp_emsc_target.requestPointerLock(); +        } +    } +}); + +EM_JS(void, sapp_js_exit_pointerlock, (void), { +    if (document.exitPointerLock) { +        document.exitPointerLock(); +    } +}); + +_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { +    if (lock) { +        /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ +        _sapp.emsc.mouse_lock_requested = true; +    } +    else { +        /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ +        _sapp.emsc.mouse_lock_requested = false; +        sapp_js_exit_pointerlock(); +    } +} + +/* called from inside event handlers to check if mouse lock had been requested, +   and if yes, actually enter mouse lock. +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { +    if (_sapp.emsc.mouse_lock_requested) { +        _sapp.emsc.mouse_lock_requested = false; +        sapp_js_request_pointerlock(); +    } +} + +#if defined(SOKOL_WGPU) +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void); +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void); +#endif + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { +    _SOKOL_UNUSED(event_type); +    _SOKOL_UNUSED(user_data); +    double w, h; +    emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); +    /* The above method might report zero when toggling HTML5 fullscreen, +       in that case use the window's inner width reported by the +       emscripten event. This works ok when toggling *into* fullscreen +       but doesn't properly restore the previous canvas size when switching +       back from fullscreen. + +       In general, due to the HTML5's fullscreen API's flaky nature it is +       recommended to use 'soft fullscreen' (stretching the WebGL canvas +       over the browser windows client rect) with a CSS definition like this: + +            position: absolute; +            top: 0px; +            left: 0px; +            margin: 0px; +            border: 0; +            width: 100%; +            height: 100%; +            overflow: hidden; +            display: block; +    */ +    if (w < 1.0) { +        w = ui_event->windowInnerWidth; +    } +    else { +        _sapp.window_width = (int) w; +    } +    if (h < 1.0) { +        h = ui_event->windowInnerHeight; +    } +    else { +        _sapp.window_height = (int) h; +    } +    if (_sapp.desc.high_dpi) { +        _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); +    } +    _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); +    _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); +    SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); +    emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); +    #if defined(SOKOL_WGPU) +        /* on WebGPU: recreate size-dependent rendering surfaces */ +        _sapp_emsc_wgpu_surfaces_discard(); +        _sapp_emsc_wgpu_surfaces_create(); +    #endif +    if (_sapp_events_enabled()) { +        _sapp_init_event(SAPP_EVENTTYPE_RESIZED); +        _sapp_call_event(&_sapp.event); +    } +    return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { +    _SOKOL_UNUSED(user_data); +    if (_sapp.mouse.locked) { +        _sapp.mouse.dx = (float) emsc_event->movementX; +        _sapp.mouse.dy = (float) emsc_event->movementY; +    } +    else { +        float new_x = emsc_event->targetX * _sapp.dpi_scale; +        float new_y = emsc_event->targetY * _sapp.dpi_scale; +        if (_sapp.mouse.pos_valid) { +            _sapp.mouse.dx = new_x - _sapp.mouse.x; +            _sapp.mouse.dy = new_y - _sapp.mouse.y; +        } +        _sapp.mouse.x = new_x; +        _sapp.mouse.y = new_y; +        _sapp.mouse.pos_valid = true; +    } +    if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { +        sapp_event_type type; +        bool is_button_event = false; +        switch (emsc_type) { +            case EMSCRIPTEN_EVENT_MOUSEDOWN: +                type = SAPP_EVENTTYPE_MOUSE_DOWN; +                is_button_event = true; +                break; +            case EMSCRIPTEN_EVENT_MOUSEUP: +                type = SAPP_EVENTTYPE_MOUSE_UP; +                is_button_event = true; +                break; +            case EMSCRIPTEN_EVENT_MOUSEMOVE: +                type = SAPP_EVENTTYPE_MOUSE_MOVE; +                break; +            case EMSCRIPTEN_EVENT_MOUSEENTER: +                type = SAPP_EVENTTYPE_MOUSE_ENTER; +                break; +            case EMSCRIPTEN_EVENT_MOUSELEAVE: +                type = SAPP_EVENTTYPE_MOUSE_LEAVE; +                break; +            default: +                type = SAPP_EVENTTYPE_INVALID; +                break; +        } +        if (type != SAPP_EVENTTYPE_INVALID) { +            _sapp_init_event(type); +            if (emsc_event->ctrlKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; +            } +            if (emsc_event->shiftKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; +            } +            if (emsc_event->altKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_ALT; +            } +            if (emsc_event->metaKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; +            } +            if (is_button_event) { +                switch (emsc_event->button) { +                    case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; +                    case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; +                    case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; +                    default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; +                } +            } +            else { +                _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; +            } +            _sapp_call_event(&_sapp.event); +        } +        /* mouse lock can only be activated in mouse button events (not in move, enter or leave) */ +        if (is_button_event) { +            _sapp_emsc_update_mouse_lock_state(); +        } +    } +    _sapp_emsc_update_keyboard_state(); +    return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelEvent* emsc_event, void* user_data) { +    _SOKOL_UNUSED(emsc_type); +    _SOKOL_UNUSED(user_data); +    if (_sapp_events_enabled()) { +        _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); +        if (emsc_event->mouse.ctrlKey) { +            _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; +        } +        if (emsc_event->mouse.shiftKey) { +            _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; +        } +        if (emsc_event->mouse.altKey) { +            _sapp.event.modifiers |= SAPP_MODIFIER_ALT; +        } +        if (emsc_event->mouse.metaKey) { +            _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; +        } +        /* see https://github.com/floooh/sokol/issues/339 */ +        float scale; +        switch (emsc_event->deltaMode) { +            case DOM_DELTA_PIXEL: scale = -0.04f; break; +            case DOM_DELTA_LINE:  scale = -1.33f; break; +            case DOM_DELTA_PAGE:  scale = -10.0f; break; // FIXME: this is a guess +            default:              scale = -0.1f; break;  // shouldn't happen +        } +        _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; +        _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; +        _sapp_call_event(&_sapp.event); +    } +    _sapp_emsc_update_keyboard_state(); +    _sapp_emsc_update_mouse_lock_state(); +    return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboardEvent* emsc_event, void* user_data) { +    _SOKOL_UNUSED(user_data); +    bool retval = true; +    if (_sapp_events_enabled()) { +        sapp_event_type type; +        switch (emsc_type) { +            case EMSCRIPTEN_EVENT_KEYDOWN: +                type = SAPP_EVENTTYPE_KEY_DOWN; +                break; +            case EMSCRIPTEN_EVENT_KEYUP: +                type = SAPP_EVENTTYPE_KEY_UP; +                break; +            case EMSCRIPTEN_EVENT_KEYPRESS: +                type = SAPP_EVENTTYPE_CHAR; +                break; +            default: +                type = SAPP_EVENTTYPE_INVALID; +                break; +        } +        if (type != SAPP_EVENTTYPE_INVALID) { +            bool send_keyup_followup = false; +            _sapp_init_event(type); +            _sapp.event.key_repeat = emsc_event->repeat; +            if (emsc_event->ctrlKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; +            } +            if (emsc_event->shiftKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; +            } +            if (emsc_event->altKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_ALT; +            } +            if (emsc_event->metaKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; +            } +            if (type == SAPP_EVENTTYPE_CHAR) { +                _sapp.event.char_code = emsc_event->charCode; +                /* workaround to make Cmd+V work on Safari */ +                if ((emsc_event->metaKey) && (emsc_event->charCode == 118)) { +                    retval = false; +                } +            } +            else { +                _sapp.event.key_code = _sapp_translate_key((int)emsc_event->keyCode); +                /* Special hack for macOS: if the Super key is pressed, macOS doesn't +                    send keyUp events. As a workaround, to prevent keys from +                    "sticking", we'll send a keyup event following a keydown +                    when the SUPER key is pressed +                */ +                if ((type == SAPP_EVENTTYPE_KEY_DOWN) && +                    (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && +                    (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && +                    (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) +                { +                    send_keyup_followup = true; +                } +                /* only forward a certain key ranges to the browser */ +                switch (_sapp.event.key_code) { +                    case SAPP_KEYCODE_WORLD_1: +                    case SAPP_KEYCODE_WORLD_2: +                    case SAPP_KEYCODE_ESCAPE: +                    case SAPP_KEYCODE_ENTER: +                    case SAPP_KEYCODE_TAB: +                    case SAPP_KEYCODE_BACKSPACE: +                    case SAPP_KEYCODE_INSERT: +                    case SAPP_KEYCODE_DELETE: +                    case SAPP_KEYCODE_RIGHT: +                    case SAPP_KEYCODE_LEFT: +                    case SAPP_KEYCODE_DOWN: +                    case SAPP_KEYCODE_UP: +                    case SAPP_KEYCODE_PAGE_UP: +                    case SAPP_KEYCODE_PAGE_DOWN: +                    case SAPP_KEYCODE_HOME: +                    case SAPP_KEYCODE_END: +                    case SAPP_KEYCODE_CAPS_LOCK: +                    case SAPP_KEYCODE_SCROLL_LOCK: +                    case SAPP_KEYCODE_NUM_LOCK: +                    case SAPP_KEYCODE_PRINT_SCREEN: +                    case SAPP_KEYCODE_PAUSE: +                    case SAPP_KEYCODE_F1: +                    case SAPP_KEYCODE_F2: +                    case SAPP_KEYCODE_F3: +                    case SAPP_KEYCODE_F4: +                    case SAPP_KEYCODE_F5: +                    case SAPP_KEYCODE_F6: +                    case SAPP_KEYCODE_F7: +                    case SAPP_KEYCODE_F8: +                    case SAPP_KEYCODE_F9: +                    case SAPP_KEYCODE_F10: +                    case SAPP_KEYCODE_F11: +                    case SAPP_KEYCODE_F12: +                    case SAPP_KEYCODE_F13: +                    case SAPP_KEYCODE_F14: +                    case SAPP_KEYCODE_F15: +                    case SAPP_KEYCODE_F16: +                    case SAPP_KEYCODE_F17: +                    case SAPP_KEYCODE_F18: +                    case SAPP_KEYCODE_F19: +                    case SAPP_KEYCODE_F20: +                    case SAPP_KEYCODE_F21: +                    case SAPP_KEYCODE_F22: +                    case SAPP_KEYCODE_F23: +                    case SAPP_KEYCODE_F24: +                    case SAPP_KEYCODE_F25: +                    case SAPP_KEYCODE_LEFT_SHIFT: +                    case SAPP_KEYCODE_LEFT_CONTROL: +                    case SAPP_KEYCODE_LEFT_ALT: +                    case SAPP_KEYCODE_LEFT_SUPER: +                    case SAPP_KEYCODE_RIGHT_SHIFT: +                    case SAPP_KEYCODE_RIGHT_CONTROL: +                    case SAPP_KEYCODE_RIGHT_ALT: +                    case SAPP_KEYCODE_RIGHT_SUPER: +                    case SAPP_KEYCODE_MENU: +                        /* consume the event */ +                        break; +                    default: +                        /* forward key to browser */ +                        retval = false; +                        break; +                } +            } +            if (_sapp_call_event(&_sapp.event)) { +                /* consume event via sapp_consume_event() */ +                retval = true; +            } +            if (send_keyup_followup) { +                _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; +                if (_sapp_call_event(&_sapp.event)) { +                    retval = true; +                } +            } +        } +    } +    _sapp_emsc_update_keyboard_state(); +    _sapp_emsc_update_mouse_lock_state(); +    return retval; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { +    _SOKOL_UNUSED(user_data); +    bool retval = true; +    if (_sapp_events_enabled()) { +        sapp_event_type type; +        switch (emsc_type) { +            case EMSCRIPTEN_EVENT_TOUCHSTART: +                type = SAPP_EVENTTYPE_TOUCHES_BEGAN; +                break; +            case EMSCRIPTEN_EVENT_TOUCHMOVE: +                type = SAPP_EVENTTYPE_TOUCHES_MOVED; +                break; +            case EMSCRIPTEN_EVENT_TOUCHEND: +                type = SAPP_EVENTTYPE_TOUCHES_ENDED; +                break; +            case EMSCRIPTEN_EVENT_TOUCHCANCEL: +                type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; +                break; +            default: +                type = SAPP_EVENTTYPE_INVALID; +                retval = false; +                break; +        } +        if (type != SAPP_EVENTTYPE_INVALID) { +            _sapp_init_event(type); +            if (emsc_event->ctrlKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; +            } +            if (emsc_event->shiftKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; +            } +            if (emsc_event->altKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_ALT; +            } +            if (emsc_event->metaKey) { +                _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; +            } +            _sapp.event.num_touches = emsc_event->numTouches; +            if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { +                _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; +            } +            for (int i = 0; i < _sapp.event.num_touches; i++) { +                const EmscriptenTouchPoint* src = &emsc_event->touches[i]; +                sapp_touchpoint* dst = &_sapp.event.touches[i]; +                dst->identifier = (uintptr_t)src->identifier; +                dst->pos_x = src->targetX * _sapp.dpi_scale; +                dst->pos_y = src->targetY * _sapp.dpi_scale; +                dst->changed = src->isChanged; +            } +            _sapp_call_event(&_sapp.event); +        } +    } +    _sapp_emsc_update_keyboard_state(); +    return retval; +} + +_SOKOL_PRIVATE void _sapp_emsc_keytable_init(void) { +    _sapp.keycodes[8]   = SAPP_KEYCODE_BACKSPACE; +    _sapp.keycodes[9]   = SAPP_KEYCODE_TAB; +    _sapp.keycodes[13]  = SAPP_KEYCODE_ENTER; +    _sapp.keycodes[16]  = SAPP_KEYCODE_LEFT_SHIFT; +    _sapp.keycodes[17]  = SAPP_KEYCODE_LEFT_CONTROL; +    _sapp.keycodes[18]  = SAPP_KEYCODE_LEFT_ALT; +    _sapp.keycodes[19]  = SAPP_KEYCODE_PAUSE; +    _sapp.keycodes[27]  = SAPP_KEYCODE_ESCAPE; +    _sapp.keycodes[32]  = SAPP_KEYCODE_SPACE; +    _sapp.keycodes[33]  = SAPP_KEYCODE_PAGE_UP; +    _sapp.keycodes[34]  = SAPP_KEYCODE_PAGE_DOWN; +    _sapp.keycodes[35]  = SAPP_KEYCODE_END; +    _sapp.keycodes[36]  = SAPP_KEYCODE_HOME; +    _sapp.keycodes[37]  = SAPP_KEYCODE_LEFT; +    _sapp.keycodes[38]  = SAPP_KEYCODE_UP; +    _sapp.keycodes[39]  = SAPP_KEYCODE_RIGHT; +    _sapp.keycodes[40]  = SAPP_KEYCODE_DOWN; +    _sapp.keycodes[45]  = SAPP_KEYCODE_INSERT; +    _sapp.keycodes[46]  = SAPP_KEYCODE_DELETE; +    _sapp.keycodes[48]  = SAPP_KEYCODE_0; +    _sapp.keycodes[49]  = SAPP_KEYCODE_1; +    _sapp.keycodes[50]  = SAPP_KEYCODE_2; +    _sapp.keycodes[51]  = SAPP_KEYCODE_3; +    _sapp.keycodes[52]  = SAPP_KEYCODE_4; +    _sapp.keycodes[53]  = SAPP_KEYCODE_5; +    _sapp.keycodes[54]  = SAPP_KEYCODE_6; +    _sapp.keycodes[55]  = SAPP_KEYCODE_7; +    _sapp.keycodes[56]  = SAPP_KEYCODE_8; +    _sapp.keycodes[57]  = SAPP_KEYCODE_9; +    _sapp.keycodes[59]  = SAPP_KEYCODE_SEMICOLON; +    _sapp.keycodes[64]  = SAPP_KEYCODE_EQUAL; +    _sapp.keycodes[65]  = SAPP_KEYCODE_A; +    _sapp.keycodes[66]  = SAPP_KEYCODE_B; +    _sapp.keycodes[67]  = SAPP_KEYCODE_C; +    _sapp.keycodes[68]  = SAPP_KEYCODE_D; +    _sapp.keycodes[69]  = SAPP_KEYCODE_E; +    _sapp.keycodes[70]  = SAPP_KEYCODE_F; +    _sapp.keycodes[71]  = SAPP_KEYCODE_G; +    _sapp.keycodes[72]  = SAPP_KEYCODE_H; +    _sapp.keycodes[73]  = SAPP_KEYCODE_I; +    _sapp.keycodes[74]  = SAPP_KEYCODE_J; +    _sapp.keycodes[75]  = SAPP_KEYCODE_K; +    _sapp.keycodes[76]  = SAPP_KEYCODE_L; +    _sapp.keycodes[77]  = SAPP_KEYCODE_M; +    _sapp.keycodes[78]  = SAPP_KEYCODE_N; +    _sapp.keycodes[79]  = SAPP_KEYCODE_O; +    _sapp.keycodes[80]  = SAPP_KEYCODE_P; +    _sapp.keycodes[81]  = SAPP_KEYCODE_Q; +    _sapp.keycodes[82]  = SAPP_KEYCODE_R; +    _sapp.keycodes[83]  = SAPP_KEYCODE_S; +    _sapp.keycodes[84]  = SAPP_KEYCODE_T; +    _sapp.keycodes[85]  = SAPP_KEYCODE_U; +    _sapp.keycodes[86]  = SAPP_KEYCODE_V; +    _sapp.keycodes[87]  = SAPP_KEYCODE_W; +    _sapp.keycodes[88]  = SAPP_KEYCODE_X; +    _sapp.keycodes[89]  = SAPP_KEYCODE_Y; +    _sapp.keycodes[90]  = SAPP_KEYCODE_Z; +    _sapp.keycodes[91]  = SAPP_KEYCODE_LEFT_SUPER; +    _sapp.keycodes[93]  = SAPP_KEYCODE_MENU; +    _sapp.keycodes[96]  = SAPP_KEYCODE_KP_0; +    _sapp.keycodes[97]  = SAPP_KEYCODE_KP_1; +    _sapp.keycodes[98]  = SAPP_KEYCODE_KP_2; +    _sapp.keycodes[99]  = SAPP_KEYCODE_KP_3; +    _sapp.keycodes[100] = SAPP_KEYCODE_KP_4; +    _sapp.keycodes[101] = SAPP_KEYCODE_KP_5; +    _sapp.keycodes[102] = SAPP_KEYCODE_KP_6; +    _sapp.keycodes[103] = SAPP_KEYCODE_KP_7; +    _sapp.keycodes[104] = SAPP_KEYCODE_KP_8; +    _sapp.keycodes[105] = SAPP_KEYCODE_KP_9; +    _sapp.keycodes[106] = SAPP_KEYCODE_KP_MULTIPLY; +    _sapp.keycodes[107] = SAPP_KEYCODE_KP_ADD; +    _sapp.keycodes[109] = SAPP_KEYCODE_KP_SUBTRACT; +    _sapp.keycodes[110] = SAPP_KEYCODE_KP_DECIMAL; +    _sapp.keycodes[111] = SAPP_KEYCODE_KP_DIVIDE; +    _sapp.keycodes[112] = SAPP_KEYCODE_F1; +    _sapp.keycodes[113] = SAPP_KEYCODE_F2; +    _sapp.keycodes[114] = SAPP_KEYCODE_F3; +    _sapp.keycodes[115] = SAPP_KEYCODE_F4; +    _sapp.keycodes[116] = SAPP_KEYCODE_F5; +    _sapp.keycodes[117] = SAPP_KEYCODE_F6; +    _sapp.keycodes[118] = SAPP_KEYCODE_F7; +    _sapp.keycodes[119] = SAPP_KEYCODE_F8; +    _sapp.keycodes[120] = SAPP_KEYCODE_F9; +    _sapp.keycodes[121] = SAPP_KEYCODE_F10; +    _sapp.keycodes[122] = SAPP_KEYCODE_F11; +    _sapp.keycodes[123] = SAPP_KEYCODE_F12; +    _sapp.keycodes[144] = SAPP_KEYCODE_NUM_LOCK; +    _sapp.keycodes[145] = SAPP_KEYCODE_SCROLL_LOCK; +    _sapp.keycodes[173] = SAPP_KEYCODE_MINUS; +    _sapp.keycodes[186] = SAPP_KEYCODE_SEMICOLON; +    _sapp.keycodes[187] = SAPP_KEYCODE_EQUAL; +    _sapp.keycodes[188] = SAPP_KEYCODE_COMMA; +    _sapp.keycodes[189] = SAPP_KEYCODE_MINUS; +    _sapp.keycodes[190] = SAPP_KEYCODE_PERIOD; +    _sapp.keycodes[191] = SAPP_KEYCODE_SLASH; +    _sapp.keycodes[192] = SAPP_KEYCODE_GRAVE_ACCENT; +    _sapp.keycodes[219] = SAPP_KEYCODE_LEFT_BRACKET; +    _sapp.keycodes[220] = SAPP_KEYCODE_BACKSLASH; +    _sapp.keycodes[221] = SAPP_KEYCODE_RIGHT_BRACKET; +    _sapp.keycodes[222] = SAPP_KEYCODE_APOSTROPHE; +    _sapp.keycodes[224] = SAPP_KEYCODE_LEFT_SUPER; +} + +#if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { +    _SOKOL_UNUSED(reserved); +    _SOKOL_UNUSED(user_data); +    sapp_event_type type; +    switch (emsc_type) { +        case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST:     type = SAPP_EVENTTYPE_SUSPENDED; break; +        case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; +        default:                                    type = SAPP_EVENTTYPE_INVALID; break; +    } +    if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { +        _sapp_init_event(type); +        _sapp_call_event(&_sapp.event); +    } +    return true; +} + +_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { +    EmscriptenWebGLContextAttributes attrs; +    emscripten_webgl_init_context_attributes(&attrs); +    attrs.alpha = _sapp.desc.alpha; +    attrs.depth = true; +    attrs.stencil = true; +    attrs.antialias = _sapp.sample_count > 1; +    attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; +    attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; +    attrs.enableExtensionsByDefault = true; +    #if defined(SOKOL_GLES3) +        if (_sapp.desc.gl_force_gles2) { +            attrs.majorVersion = 1; +            _sapp.gles2_fallback = true; +        } +        else { +            attrs.majorVersion = 2; +        } +    #endif +    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); +    if (!ctx) { +        attrs.majorVersion = 1; +        ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); +        _sapp.gles2_fallback = true; +    } +    emscripten_webgl_make_context_current(ctx); + +    /* some WebGL extension are not enabled automatically by emscripten */ +    emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); +} +#endif + +#if defined(SOKOL_WGPU) +#define _SAPP_EMSC_WGPU_STATE_INITIAL (0) +#define _SAPP_EMSC_WGPU_STATE_READY (1) +#define _SAPP_EMSC_WGPU_STATE_RUNNING (2) + +#if defined(__cplusplus) +extern "C" { +#endif +/* called when the asynchronous WebGPU device + swapchain init code in JS has finished */ +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_wgpu_ready(int device_id, int swapchain_id, int swapchain_fmt) { +    SOKOL_ASSERT(0 == _sapp.emsc.wgpu.device); +    _sapp.emsc.wgpu.device = (WGPUDevice) device_id; +    _sapp.emsc.wgpu.swapchain = (WGPUSwapChain) swapchain_id; +    _sapp.emsc.wgpu.render_format = (WGPUTextureFormat) swapchain_fmt; +    _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_READY; +} +#if defined(__cplusplus) +} // extern "C" +#endif + +/* embedded JS function to handle all the asynchronous WebGPU setup */ +EM_JS(void, sapp_js_wgpu_init, (), { +    WebGPU.initManagers(); +    // FIXME: the extension activation must be more clever here +    navigator.gpu.requestAdapter().then(function(adapter) { +        console.log("wgpu adapter extensions: " + adapter.extensions); +        adapter.requestDevice({ extensions: ["textureCompressionBC"]}).then(function(device) { +            var gpuContext = document.getElementById("canvas").getContext("gpupresent"); +            console.log("wgpu device extensions: " + adapter.extensions); +            gpuContext.getSwapChainPreferredFormat(device).then(function(fmt) { +                var swapChainDescriptor = { device: device, format: fmt }; +                var swapChain = gpuContext.configureSwapChain(swapChainDescriptor); +                var deviceId = WebGPU.mgrDevice.create(device); +                var swapChainId = WebGPU.mgrSwapChain.create(swapChain); +                var fmtId = WebGPU.TextureFormat.findIndex(function(elm) { return elm==fmt; }); +                console.log("wgpu device: " + device); +                console.log("wgpu swap chain: " + swapChain); +                console.log("wgpu preferred format: " + fmt + " (" + fmtId + ")"); +                __sapp_emsc_wgpu_ready(deviceId, swapChainId, fmtId); +            }); +        }); +    }); +}); + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_create(void) { +    SOKOL_ASSERT(_sapp.emsc.wgpu.device); +    SOKOL_ASSERT(_sapp.emsc.wgpu.swapchain); +    SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_tex); +    SOKOL_ASSERT(0 == _sapp.emsc.wgpu.depth_stencil_view); +    SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_tex); +    SOKOL_ASSERT(0 == _sapp.emsc.wgpu.msaa_view); + +    WGPUTextureDescriptor ds_desc; +    memset(&ds_desc, 0, sizeof(ds_desc)); +    ds_desc.usage = WGPUTextureUsage_OutputAttachment; +    ds_desc.dimension = WGPUTextureDimension_2D; +    ds_desc.size.width = (uint32_t) _sapp.framebuffer_width; +    ds_desc.size.height = (uint32_t) _sapp.framebuffer_height; +    ds_desc.size.depth = 1; +    ds_desc.arrayLayerCount = 1; +    ds_desc.format = WGPUTextureFormat_Depth24PlusStencil8; +    ds_desc.mipLevelCount = 1; +    ds_desc.sampleCount = _sapp.sample_count; +    _sapp.emsc.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &ds_desc); +    _sapp.emsc.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.emsc.wgpu.depth_stencil_tex, 0); + +    if (_sapp.sample_count > 1) { +        WGPUTextureDescriptor msaa_desc; +        memset(&msaa_desc, 0, sizeof(msaa_desc)); +        msaa_desc.usage = WGPUTextureUsage_OutputAttachment; +        msaa_desc.dimension = WGPUTextureDimension_2D; +        msaa_desc.size.width = (uint32_t) _sapp.framebuffer_width; +        msaa_desc.size.height = (uint32_t) _sapp.framebuffer_height; +        msaa_desc.size.depth = 1; +        msaa_desc.arrayLayerCount = 1; +        msaa_desc.format = _sapp.emsc.wgpu.render_format; +        msaa_desc.mipLevelCount = 1; +        msaa_desc.sampleCount = _sapp.sample_count; +        _sapp.emsc.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.emsc.wgpu.device, &msaa_desc); +        _sapp.emsc.wgpu.msaa_view = wgpuTextureCreateView(_sapp.emsc.wgpu.msaa_tex, 0); +    } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_surfaces_discard(void) { +    if (_sapp.emsc.wgpu.msaa_tex) { +        wgpuTextureRelease(_sapp.emsc.wgpu.msaa_tex); +        _sapp.emsc.wgpu.msaa_tex = 0; +    } +    if (_sapp.emsc.wgpu.msaa_view) { +        wgpuTextureViewRelease(_sapp.emsc.wgpu.msaa_view); +        _sapp.emsc.wgpu.msaa_view = 0; +    } +    if (_sapp.emsc.wgpu.depth_stencil_tex) { +        wgpuTextureRelease(_sapp.emsc.wgpu.depth_stencil_tex); +        _sapp.emsc.wgpu.depth_stencil_tex = 0; +    } +    if (_sapp.emsc.wgpu.depth_stencil_view) { +        wgpuTextureViewRelease(_sapp.emsc.wgpu.depth_stencil_view); +        _sapp.emsc.wgpu.depth_stencil_view = 0; +    } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_next_frame(void) { +    if (_sapp.emsc.wgpu.swapchain_view) { +        wgpuTextureViewRelease(_sapp.emsc.wgpu.swapchain_view); +    } +    _sapp.emsc.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.emsc.wgpu.swapchain); +} +#endif + +_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { +    emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); +    emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); +    emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); +    emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); +    emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); +    emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); +    emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); +    emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); +    emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); +    emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); +    emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); +    emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); +    emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); +    emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockchange_cb); +    emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockerror_cb); +    sapp_js_add_beforeunload_listener(); +    if (_sapp.clipboard.enabled) { +        sapp_js_add_clipboard_listener(); +    } +    if (_sapp.drop.enabled) { +        sapp_js_add_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); +    } +    #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +        emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); +        emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); +    #endif +} + +_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers() { +    emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); +    emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); +    emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); +    emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, 0); +    emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); +    emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); +    sapp_js_remove_beforeunload_listener(); +    if (_sapp.clipboard.enabled) { +        sapp_js_remove_clipboard_listener(); +    } +    if (_sapp.drop.enabled) { +        sapp_js_remove_dragndrop_listeners(&_sapp.html5_canvas_selector[1]); +    } +    #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +        emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); +        emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); +    #endif +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame(double time, void* userData) { +    _SOKOL_UNUSED(time); +    _SOKOL_UNUSED(userData); + +    #if defined(SOKOL_WGPU) +        /* +            on WebGPU, the emscripten frame callback will already be called while +            the asynchronous WebGPU device and swapchain initialization is still +            in progress +        */ +        switch (_sapp.emsc.wgpu.state) { +            case _SAPP_EMSC_WGPU_STATE_INITIAL: +                /* async JS init hasn't finished yet */ +                break; +            case _SAPP_EMSC_WGPU_STATE_READY: +                /* perform post-async init stuff */ +                _sapp_emsc_wgpu_surfaces_create(); +                _sapp.emsc.wgpu.state = _SAPP_EMSC_WGPU_STATE_RUNNING; +                break; +            case _SAPP_EMSC_WGPU_STATE_RUNNING: +                /* a regular frame */ +                _sapp_emsc_wgpu_next_frame(); +                _sapp_frame(); +                break; +        } +    #else +        /* WebGL code path */ +        _sapp_frame(); +    #endif + +    /* quit-handling */ +    if (_sapp.quit_requested) { +        _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); +        _sapp_call_event(&_sapp.event); +        if (_sapp.quit_requested) { +            _sapp.quit_ordered = true; +        } +    } +    if (_sapp.quit_ordered) { +        _sapp_emsc_unregister_eventhandlers(); +        _sapp_call_cleanup(); +        _sapp_discard_state(); +        return EM_FALSE; +    } +    return EM_TRUE; +} + +_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { +    _sapp_init_state(desc); +    sapp_js_pointer_init(&_sapp.html5_canvas_selector[1]); +    _sapp_emsc_keytable_init(); +    double w, h; +    if (_sapp.desc.html5_canvas_resize) { +        w = (double) _sapp.desc.width; +        h = (double) _sapp.desc.height; +    } +    else { +        emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); +        emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); +    } +    if (_sapp.desc.high_dpi) { +        _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); +    } +    _sapp.window_width = (int) w; +    _sapp.window_height = (int) h; +    _sapp.framebuffer_width = (int) (w * _sapp.dpi_scale); +    _sapp.framebuffer_height = (int) (h * _sapp.dpi_scale); +    emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); +    #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +        _sapp_emsc_webgl_init(); +    #elif defined(SOKOL_WGPU) +        sapp_js_wgpu_init(); +    #endif +    _sapp.valid = true; +    _sapp_emsc_register_eventhandlers(); + +    /* start the frame loop */ +    emscripten_request_animation_frame_loop(_sapp_emsc_frame, 0); + +    /* NOT A BUG: do not call _sapp_discard_state() here, instead this is +       called in _sapp_emsc_frame() when the application is ordered to quit +     */ +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { +    sapp_desc desc = sokol_main(argc, argv); +    _sapp_emsc_run(&desc); +    return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_EMSCRIPTEN */ + +/*== MISC GL SUPPORT FUNCTIONS ================================================*/ +#if defined(SOKOL_GLCORE33) +typedef struct { +    int         red_bits; +    int         green_bits; +    int         blue_bits; +    int         alpha_bits; +    int         depth_bits; +    int         stencil_bits; +    int         samples; +    bool        doublebuffer; +    uintptr_t   handle; +} _sapp_gl_fbconfig; + +_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { +    memset(fbconfig, 0, sizeof(_sapp_gl_fbconfig)); +    /* -1 means "don't care" */ +    fbconfig->red_bits = -1; +    fbconfig->green_bits = -1; +    fbconfig->blue_bits = -1; +    fbconfig->alpha_bits = -1; +    fbconfig->depth_bits = -1; +    fbconfig->stencil_bits = -1; +    fbconfig->samples = -1; +} + +_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { +    int missing, least_missing = 1000000; +    int color_diff, least_color_diff = 10000000; +    int extra_diff, least_extra_diff = 10000000; +    const _sapp_gl_fbconfig* current; +    const _sapp_gl_fbconfig* closest = 0; +    for (int i = 0;  i < count;  i++) { +        current = alternatives + i; +        if (desired->doublebuffer != current->doublebuffer) { +            continue; +        } +        missing = 0; +        if (desired->alpha_bits > 0 && current->alpha_bits == 0) { +            missing++; +        } +        if (desired->depth_bits > 0 && current->depth_bits == 0) { +            missing++; +        } +        if (desired->stencil_bits > 0 && current->stencil_bits == 0) { +            missing++; +        } +        if (desired->samples > 0 && current->samples == 0) { +            /* Technically, several multisampling buffers could be +                involved, but that's a lower level implementation detail and +                not important to us here, so we count them as one +            */ +            missing++; +        } + +        /* These polynomials make many small channel size differences matter +            less than one large channel size difference +            Calculate color channel size difference value +        */ +        color_diff = 0; +        if (desired->red_bits != -1) { +            color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); +        } +        if (desired->green_bits != -1) { +            color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); +        } +        if (desired->blue_bits != -1) { +            color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); +        } + +        /* Calculate non-color channel size difference value */ +        extra_diff = 0; +        if (desired->alpha_bits != -1) { +            extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); +        } +        if (desired->depth_bits != -1) { +            extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); +        } +        if (desired->stencil_bits != -1) { +            extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); +        } +        if (desired->samples != -1) { +            extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); +        } + +        /* Figure out if the current one is better than the best one found so far +            Least number of missing buffers is the most important heuristic, +            then color buffer size match and lastly size match for other buffers +        */ +        if (missing < least_missing) { +            closest = current; +        } +        else if (missing == least_missing) { +            if ((color_diff < least_color_diff) || +                (color_diff == least_color_diff && extra_diff < least_extra_diff)) +            { +                closest = current; +            } +        } +        if (current == closest) { +            least_missing = missing; +            least_color_diff = color_diff; +            least_extra_diff = extra_diff; +        } +    } +    return closest; +} +#endif + +/*== WINDOWS DESKTOP and UWP====================================================*/ +#if defined(_SAPP_WIN32) || defined(_SAPP_UWP) +_SOKOL_PRIVATE bool _sapp_win32_uwp_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { +    SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); +    memset(dst, 0, (size_t)dst_num_bytes); +    const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); +    const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); +    if ((dst_needed > 0) && (dst_needed < dst_chars)) { +        MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); +        return true; +    } +    else { +        /* input string doesn't fit into destination buffer */ +        return false; +    } +} + +_SOKOL_PRIVATE void _sapp_win32_uwp_app_event(sapp_event_type type) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_win32_uwp_init_keytable(void) { +    /* same as GLFW */ +    _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; +    _sapp.keycodes[0x002] = SAPP_KEYCODE_1; +    _sapp.keycodes[0x003] = SAPP_KEYCODE_2; +    _sapp.keycodes[0x004] = SAPP_KEYCODE_3; +    _sapp.keycodes[0x005] = SAPP_KEYCODE_4; +    _sapp.keycodes[0x006] = SAPP_KEYCODE_5; +    _sapp.keycodes[0x007] = SAPP_KEYCODE_6; +    _sapp.keycodes[0x008] = SAPP_KEYCODE_7; +    _sapp.keycodes[0x009] = SAPP_KEYCODE_8; +    _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; +    _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; +    _sapp.keycodes[0x030] = SAPP_KEYCODE_B; +    _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; +    _sapp.keycodes[0x020] = SAPP_KEYCODE_D; +    _sapp.keycodes[0x012] = SAPP_KEYCODE_E; +    _sapp.keycodes[0x021] = SAPP_KEYCODE_F; +    _sapp.keycodes[0x022] = SAPP_KEYCODE_G; +    _sapp.keycodes[0x023] = SAPP_KEYCODE_H; +    _sapp.keycodes[0x017] = SAPP_KEYCODE_I; +    _sapp.keycodes[0x024] = SAPP_KEYCODE_J; +    _sapp.keycodes[0x025] = SAPP_KEYCODE_K; +    _sapp.keycodes[0x026] = SAPP_KEYCODE_L; +    _sapp.keycodes[0x032] = SAPP_KEYCODE_M; +    _sapp.keycodes[0x031] = SAPP_KEYCODE_N; +    _sapp.keycodes[0x018] = SAPP_KEYCODE_O; +    _sapp.keycodes[0x019] = SAPP_KEYCODE_P; +    _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; +    _sapp.keycodes[0x013] = SAPP_KEYCODE_R; +    _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; +    _sapp.keycodes[0x014] = SAPP_KEYCODE_T; +    _sapp.keycodes[0x016] = SAPP_KEYCODE_U; +    _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; +    _sapp.keycodes[0x011] = SAPP_KEYCODE_W; +    _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; +    _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; +    _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; +    _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; +    _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; +    _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; +    _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; +    _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; +    _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; +    _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; +    _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; +    _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; +    _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; +    _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; +    _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; +    _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; +    _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; +    _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; +    _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; +    _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; +    _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; +    _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; +    _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; +    _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; +    _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; +    _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; +    _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; +    _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; +    _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; +    _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; +    _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; +    _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; +    _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; +    _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; +    _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; +    _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; +    _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; +    _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; +    _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; +    _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; +    _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; +    _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; +    _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; +    _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; +    _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; +    _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; +    _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; +    _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; +    _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; +    _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; +    _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; +    _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; +    _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; +    _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; +    _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; +    _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; +    _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; +    _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; +    _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; +    _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; +    _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; +    _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; +    _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; +    _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; +    _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; +    _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; +    _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; +    _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; +    _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; +    _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; +    _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; +    _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; +    _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; +    _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; +    _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; +    _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; +    _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; +    _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; +    _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; +    _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; +    _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; +    _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; +    _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; +    _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; +    _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; +} +#endif // _SAPP_WIN32 || _SAPP_UWP + +/*== WINDOWS DESKTOP===========================================================*/ +#if defined(_SAPP_WIN32) + +#if defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sapp_d3d11_Release(self) (self)->Release() +#else +#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) +#endif + +#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } + +static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { +    #if defined(__cplusplus) +        return self->GetBuffer(Buffer, riid, ppSurface); +    #else +        return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); +    #endif +} + +static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { +    #if defined(__cplusplus) +        return self->CreateRenderTargetView(pResource, pDesc, ppRTView); +    #else +        return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); +    #endif +} + +static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { +    #if defined(__cplusplus) +        return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); +    #else +        return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); +    #endif +} + +static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { +    #if defined(__cplusplus) +        return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); +    #else +        return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); +    #endif +} + +static inline void _sapp_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { +    #if defined(__cplusplus) +        self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); +    #else +        self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); +    #endif +} + +static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { +    #if defined(__cplusplus) +        return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); +    #else +        return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); +    #endif +} + +static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { +    #if defined(__cplusplus) +        return self->Present(SyncInterval, Flags); +    #else +        return self->lpVtbl->Present(self, SyncInterval, Flags); +    #endif +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { +    DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; +    sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; +    sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; +    sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; +    sc_desc->BufferDesc.RefreshRate.Numerator = 60; +    sc_desc->BufferDesc.RefreshRate.Denominator = 1; +    sc_desc->OutputWindow = _sapp.win32.hwnd; +    sc_desc->Windowed = true; +    if (_sapp.win32.is_win10_or_greater) { +        sc_desc->BufferCount = 2; +        sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; +    } +    else { +        sc_desc->BufferCount = 1; +        sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; +    } +    sc_desc->SampleDesc.Count = 1; +    sc_desc->SampleDesc.Quality = 0; +    sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; +    UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; +    #if defined(SOKOL_DEBUG) +        create_flags |= D3D11_CREATE_DEVICE_DEBUG; +    #endif +    D3D_FEATURE_LEVEL feature_level; +    HRESULT hr = D3D11CreateDeviceAndSwapChain( +        NULL,                           /* pAdapter (use default) */ +        D3D_DRIVER_TYPE_HARDWARE,       /* DriverType */ +        NULL,                           /* Software */ +        create_flags,                   /* Flags */ +        NULL,                           /* pFeatureLevels */ +        0,                              /* FeatureLevels */ +        D3D11_SDK_VERSION,              /* SDKVersion */ +        sc_desc,                        /* pSwapChainDesc */ +        &_sapp.d3d11.swap_chain,        /* ppSwapChain */ +        &_sapp.d3d11.device,            /* ppDevice */ +        &feature_level,                 /* pFeatureLevel */ +        &_sapp.d3d11.device_context);   /* ppImmediateContext */ +    _SOKOL_UNUSED(hr); +    SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { +    _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.device); +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { +    SOKOL_ASSERT(0 == _sapp.d3d11.rt); +    SOKOL_ASSERT(0 == _sapp.d3d11.rtv); +    SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); +    SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); +    SOKOL_ASSERT(0 == _sapp.d3d11.ds); +    SOKOL_ASSERT(0 == _sapp.d3d11.dsv); + +    HRESULT hr; + +    /* view for the swapchain-created framebuffer */ +    #ifdef __cplusplus +    hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); +    #else +    hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, &IID_ID3D11Texture2D, (void**)&_sapp.d3d11.rt); +    #endif +    SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); +    hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); +    SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); + +    /* common desc for MSAA and depth-stencil texture */ +    D3D11_TEXTURE2D_DESC tex_desc; +    memset(&tex_desc, 0, sizeof(tex_desc)); +    tex_desc.Width = (UINT)_sapp.framebuffer_width; +    tex_desc.Height = (UINT)_sapp.framebuffer_height; +    tex_desc.MipLevels = 1; +    tex_desc.ArraySize = 1; +    tex_desc.Usage = D3D11_USAGE_DEFAULT; +    tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; +    tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; +    tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + +    /* create MSAA texture and view if antialiasing requested */ +    if (_sapp.sample_count > 1) { +        tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; +        hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); +        SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); +        hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); +        SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); +    } + +    /* texture and view for the depth-stencil-surface */ +    tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; +    tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; +    hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); +    SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); +    hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); +    SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { +    _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); +    _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { +    if (_sapp.d3d11.swap_chain) { +        _sapp_d3d11_destroy_default_render_target(); +        _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); +        _sapp_d3d11_create_default_render_target(); +    } +} + +_SOKOL_PRIVATE void _sapp_d3d11_present(void) { +    /* do MSAA resolve if needed */ +    if (_sapp.sample_count > 1) { +        SOKOL_ASSERT(_sapp.d3d11.rt); +        SOKOL_ASSERT(_sapp.d3d11.msaa_rt); +        _sapp_d3d11_ResolveSubresource(_sapp.d3d11.device_context, (ID3D11Resource*)_sapp.d3d11.rt, 0, (ID3D11Resource*)_sapp.d3d11.msaa_rt, 0, DXGI_FORMAT_B8G8R8A8_UNORM); +    } +    _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, 0); +} + +#endif /* SOKOL_D3D11 */ + +#if defined(SOKOL_GLCORE33) +_SOKOL_PRIVATE void _sapp_wgl_init(void) { +    _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); +    if (!_sapp.wgl.opengl32) { +        _sapp_fail("Failed to load opengl32.dll\n"); +    } +    SOKOL_ASSERT(_sapp.wgl.opengl32); +    _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); +    SOKOL_ASSERT(_sapp.wgl.CreateContext); +    _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); +    SOKOL_ASSERT(_sapp.wgl.DeleteContext); +    _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); +    SOKOL_ASSERT(_sapp.wgl.GetProcAddress); +    _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); +    SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); +    _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); +    SOKOL_ASSERT(_sapp.wgl.MakeCurrent); + +    _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, +        L"SOKOLAPP", +        L"sokol-app message window", +        WS_CLIPSIBLINGS|WS_CLIPCHILDREN, +        0, 0, 1, 1, +        NULL, NULL, +        GetModuleHandleW(NULL), +        NULL); +    if (!_sapp.wgl.msg_hwnd) { +        _sapp_fail("Win32: failed to create helper window!\n"); +    } +    SOKOL_ASSERT(_sapp.wgl.msg_hwnd); +    ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); +    MSG msg; +    while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { +        TranslateMessage(&msg); +        DispatchMessageW(&msg); +    } +    _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); +    if (!_sapp.wgl.msg_dc) { +        _sapp_fail("Win32: failed to obtain helper window DC!\n"); +    } +} + +_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { +    SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); +    DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; +    FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; +} + +_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { +    SOKOL_ASSERT(ext && extensions); +    const char* start = extensions; +    while (true) { +        const char* where = strstr(start, ext); +        if (!where) { +            return false; +        } +        const char* terminator = where + strlen(ext); +        if ((where == start) || (*(where - 1) == ' ')) { +            if (*terminator == ' ' || *terminator == '\0') { +                break; +            } +        } +        start = terminator; +    } +    return true; +} + +_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { +    SOKOL_ASSERT(ext); +    if (_sapp.wgl.GetExtensionsStringEXT) { +        const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); +        if (extensions) { +            if (_sapp_wgl_has_ext(ext, extensions)) { +                return true; +            } +        } +    } +    if (_sapp.wgl.GetExtensionsStringARB) { +        const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); +        if (extensions) { +            if (_sapp_wgl_has_ext(ext, extensions)) { +                return true; +            } +        } +    } +    return false; +} + +_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { +    SOKOL_ASSERT(_sapp.wgl.msg_dc); +    PIXELFORMATDESCRIPTOR pfd; +    memset(&pfd, 0, sizeof(pfd)); +    pfd.nSize = sizeof(pfd); +    pfd.nVersion = 1; +    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; +    pfd.iPixelType = PFD_TYPE_RGBA; +    pfd.cColorBits = 24; +    if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { +        _sapp_fail("WGL: failed to set pixel format for dummy context\n"); +    } +    HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); +    if (!rc) { +        _sapp_fail("WGL: Failed to create dummy context\n"); +    } +    if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { +        _sapp_fail("WGL: Failed to make context current\n"); +    } +    _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); +    _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); +    _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); +    _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); +    _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); +    _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); +    _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); +    _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); +    _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); +    _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); +    _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); +    _sapp.wgl.DeleteContext(rc); +} + +_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { +    SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); +    int value = 0; +    if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { +        _sapp_fail("WGL: Failed to retrieve pixel format attribute\n"); +    } +    return value; +} + +_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { +    SOKOL_ASSERT(_sapp.win32.dc); +    SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); +    const _sapp_gl_fbconfig* closest; + +    int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); +    _sapp_gl_fbconfig* usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); +    SOKOL_ASSERT(usable_configs); +    int usable_count = 0; +    for (int i = 0; i < native_count; i++) { +        const int n = i + 1; +        _sapp_gl_fbconfig* u = usable_configs + usable_count; +        _sapp_gl_init_fbconfig(u); +        if (!_sapp_wgl_attrib(n, WGL_SUPPORT_OPENGL_ARB) || !_sapp_wgl_attrib(n, WGL_DRAW_TO_WINDOW_ARB)) { +            continue; +        } +        if (_sapp_wgl_attrib(n, WGL_PIXEL_TYPE_ARB) != WGL_TYPE_RGBA_ARB) { +            continue; +        } +        if (_sapp_wgl_attrib(n, WGL_ACCELERATION_ARB) == WGL_NO_ACCELERATION_ARB) { +            continue; +        } +        u->red_bits     = _sapp_wgl_attrib(n, WGL_RED_BITS_ARB); +        u->green_bits   = _sapp_wgl_attrib(n, WGL_GREEN_BITS_ARB); +        u->blue_bits    = _sapp_wgl_attrib(n, WGL_BLUE_BITS_ARB); +        u->alpha_bits   = _sapp_wgl_attrib(n, WGL_ALPHA_BITS_ARB); +        u->depth_bits   = _sapp_wgl_attrib(n, WGL_DEPTH_BITS_ARB); +        u->stencil_bits = _sapp_wgl_attrib(n, WGL_STENCIL_BITS_ARB); +        if (_sapp_wgl_attrib(n, WGL_DOUBLE_BUFFER_ARB)) { +            u->doublebuffer = true; +        } +        if (_sapp.wgl.arb_multisample) { +            u->samples = _sapp_wgl_attrib(n, WGL_SAMPLES_ARB); +        } +        u->handle = (uintptr_t)n; +        usable_count++; +    } +    SOKOL_ASSERT(usable_count > 0); +    _sapp_gl_fbconfig desired; +    _sapp_gl_init_fbconfig(&desired); +    desired.red_bits = 8; +    desired.green_bits = 8; +    desired.blue_bits = 8; +    desired.alpha_bits = 8; +    desired.depth_bits = 24; +    desired.stencil_bits = 8; +    desired.doublebuffer = true; +    desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; +    closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); +    int pixel_format = 0; +    if (closest) { +        pixel_format = (int) closest->handle; +    } +    SOKOL_FREE(usable_configs); +    return pixel_format; +} + +_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { +    int pixel_format = _sapp_wgl_find_pixel_format(); +    if (0 == pixel_format) { +        _sapp_fail("WGL: Didn't find matching pixel format.\n"); +    } +    PIXELFORMATDESCRIPTOR pfd; +    if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { +        _sapp_fail("WGL: Failed to retrieve PFD for selected pixel format!\n"); +    } +    if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { +        _sapp_fail("WGL: Failed to set selected pixel format!\n"); +    } +    if (!_sapp.wgl.arb_create_context) { +        _sapp_fail("WGL: ARB_create_context required!\n"); +    } +    if (!_sapp.wgl.arb_create_context_profile) { +        _sapp_fail("WGL: ARB_create_context_profile required!\n"); +    } +    const int attrs[] = { +        WGL_CONTEXT_MAJOR_VERSION_ARB, 3, +        WGL_CONTEXT_MINOR_VERSION_ARB, 3, +        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, +        WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, +        0, 0 +    }; +    _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); +    if (!_sapp.wgl.gl_ctx) { +        const DWORD err = GetLastError(); +        if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { +            _sapp_fail("WGL: Driver does not support OpenGL version 3.3\n"); +        } +        else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { +            _sapp_fail("WGL: Driver does not support the requested OpenGL profile"); +        } +        else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { +            _sapp_fail("WGL: The share context is not compatible with the requested context"); +        } +        else { +            _sapp_fail("WGL: Failed to create OpenGL context"); +        } +    } +    _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); +    if (_sapp.wgl.ext_swap_control) { +        /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ +        _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); +    } +} + +_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { +    SOKOL_ASSERT(_sapp.wgl.gl_ctx); +    _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); +    _sapp.wgl.gl_ctx = 0; +} + +_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { +    SOKOL_ASSERT(_sapp.win32.dc); +    /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ +    SwapBuffers(_sapp.win32.dc); +} +#endif /* SOKOL_GLCORE33 */ + +_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { +    SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); +    memset(dst, 0, (size_t)dst_num_bytes); +    const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); +    if (bytes_needed <= dst_num_bytes) { +        WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); +        return true; +    } +    else { +        return false; +    } +} + +_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { +    HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); +    MONITORINFO minfo; +    memset(&minfo, 0, sizeof(minfo)); +    minfo.cbSize = sizeof(MONITORINFO); +    GetMonitorInfo(monitor, &minfo); +    const RECT mr = minfo.rcMonitor; +    const int monitor_w = mr.right - mr.left; +    const int monitor_h = mr.bottom - mr.top; + +    const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; +    DWORD win_style; +    RECT rect = { 0, 0, 0, 0 }; + +    _sapp.fullscreen = !_sapp.fullscreen; +    if (!_sapp.fullscreen) { +        win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; +        rect.right = (int) ((float)_sapp.desc.width * _sapp.win32.dpi.window_scale); +        rect.bottom = (int) ((float)_sapp.desc.height * _sapp.win32.dpi.window_scale); +    } +    else { +        win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; +        rect.right = monitor_w; +        rect.bottom = monitor_h; +    } +    AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); +    int win_width = rect.right - rect.left; +    int win_height = rect.bottom - rect.top; +    if (!_sapp.fullscreen) { +        rect.left = (monitor_w - win_width) / 2; +        rect.top = (monitor_h - win_height) / 2; +    } + +    SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); +    SetWindowPos(_sapp.win32.hwnd, HWND_TOP, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); +} + +_SOKOL_PRIVATE void _sapp_win32_show_mouse(bool visible) { +    /* NOTE: this function is only called when the mouse visibility actually changes */ +    ShowCursor((BOOL)visible); +} + +_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { +    if (0 == _sapp.win32.mouse_capture_mask) { +        SetCapture(_sapp.win32.hwnd); +    } +    _sapp.win32.mouse_capture_mask |= btn_mask; +} + +_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { +    if (0 != _sapp.win32.mouse_capture_mask) { +        _sapp.win32.mouse_capture_mask &= ~btn_mask; +        if (0 == _sapp.win32.mouse_capture_mask) { +            ReleaseCapture(); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { +    if (lock == _sapp.mouse.locked) { +        return; +    } +    _sapp.mouse.dx = 0.0f; +    _sapp.mouse.dy = 0.0f; +    _sapp.mouse.locked = lock; +    _sapp_win32_release_mouse(0xFF); +    if (_sapp.mouse.locked) { +        /* store the current mouse position, so it can be restored when unlocked */ +        POINT pos; +        BOOL res = GetCursorPos(&pos); +        SOKOL_ASSERT(res); _SOKOL_UNUSED(res); +        _sapp.win32.mouse_locked_x = pos.x; +        _sapp.win32.mouse_locked_y = pos.y; + +        /* while the mouse is locked, make the mouse cursor invisible and +           confine the mouse movement to a small rectangle inside our window +           (so that we dont miss any mouse up events) +        */ +        RECT client_rect = { +            _sapp.win32.mouse_locked_x, +            _sapp.win32.mouse_locked_y, +            _sapp.win32.mouse_locked_x, +            _sapp.win32.mouse_locked_y +        }; +        ClipCursor(&client_rect); + +        /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ +        ShowCursor(FALSE); + +        /* enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) */ +        const RAWINPUTDEVICE rid = { +            0x01,   // usUsagePage: HID_USAGE_PAGE_GENERIC +            0x02,   // usUsage: HID_USAGE_GENERIC_MOUSE +            0,      // dwFlags +            _sapp.win32.hwnd    // hwndTarget +        }; +        if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { +            SOKOL_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 +        */ +        _sapp.win32.raw_input_mousepos_valid = false; +    } +    else { +        /* 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"); +        } + +        /* let the mouse roam freely again */ +        ClipCursor(NULL); +        ShowCursor(TRUE); + +        /* restore the 'pre-locked' mouse position */ +        BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); +        SOKOL_ASSERT(res); _SOKOL_UNUSED(res); +    } +} + +/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ +_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { +    RECT rect; +    if (GetClientRect(_sapp.win32.hwnd, &rect)) { +        _sapp.window_width = (int)((float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale); +        _sapp.window_height = (int)((float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale); +        const int fb_width = (int)((float)_sapp.window_width * _sapp.win32.dpi.content_scale); +        const int fb_height = (int)((float)_sapp.window_height * _sapp.win32.dpi.content_scale); +        if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) { +            _sapp.framebuffer_width = fb_width; +            _sapp.framebuffer_height = fb_height; +            /* prevent a framebuffer size of 0 when window is minimized */ +            if (_sapp.framebuffer_width == 0) { +                _sapp.framebuffer_width = 1; +            } +            if (_sapp.framebuffer_height == 0) { +                _sapp.framebuffer_height = 1; +            } +            return true; +        } +    } +    else { +        _sapp.window_width = _sapp.window_height = 1; +        _sapp.framebuffer_width = _sapp.framebuffer_height = 1; +    } +    return false; +} + +_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { +    uint32_t mods = 0; +    if (GetKeyState(VK_SHIFT) & (1<<15)) { +        mods |= SAPP_MODIFIER_SHIFT; +    } +    if (GetKeyState(VK_CONTROL) & (1<<15)) { +        mods |= SAPP_MODIFIER_CTRL; +    } +    if (GetKeyState(VK_MENU) & (1<<15)) { +        mods |= SAPP_MODIFIER_ALT; +    } +    if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { +        mods |= SAPP_MODIFIER_SUPER; +    } +    return mods; +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp.event.modifiers = _sapp_win32_mods(); +        _sapp.event.mouse_button = btn; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); +        _sapp.event.modifiers = _sapp_win32_mods(); +        _sapp.event.scroll_x = -x / 30.0f; +        _sapp.event.scroll_y = y / 30.0f; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { +    if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { +        _sapp_init_event(type); +        _sapp.event.modifiers = _sapp_win32_mods(); +        _sapp.event.key_code = _sapp.keycodes[vk]; +        _sapp.event.key_repeat = repeat; +        _sapp_call_event(&_sapp.event); +        /* check if a CLIPBOARD_PASTED event must be sent too */ +        if (_sapp.clipboard.enabled && +            (type == SAPP_EVENTTYPE_KEY_DOWN) && +            (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && +            (_sapp.event.key_code == SAPP_KEYCODE_V)) +        { +            _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); +            _sapp_call_event(&_sapp.event); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { +    if (_sapp_events_enabled() && (c >= 32)) { +        _sapp_init_event(SAPP_EVENTTYPE_CHAR); +        _sapp.event.modifiers = _sapp_win32_mods(); +        _sapp.event.char_code = c; +        _sapp.event.key_repeat = repeat; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { +    if (!_sapp.drop.enabled) { +        return; +    } +    _sapp_clear_drop_buffer(); +    bool drop_failed = false; +    const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); +    _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; +    for (UINT i = 0;  i < (UINT)_sapp.drop.num_files;  i++) { +        const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; +        WCHAR* buffer = (WCHAR*) SOKOL_CALLOC(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"); +            drop_failed = true; +        } +        SOKOL_FREE(buffer); +    } +    DragFinish(hdrop); +    if (!drop_failed) { +        if (_sapp_events_enabled()) { +            _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); +            _sapp_call_event(&_sapp.event); +        } +    } +    else { +        _sapp_clear_drop_buffer(); +        _sapp.drop.num_files = 0; +    } +} + +_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { +    if (!_sapp.win32.in_create_window) { +        switch (uMsg) { +            case WM_CLOSE: +                /* only give user a chance to intervene when sapp_quit() wasn't already called */ +                if (!_sapp.quit_ordered) { +                    /* if window should be closed and event handling is enabled, give user code +                        a change to intervene via sapp_cancel_quit() +                    */ +                    _sapp.quit_requested = true; +                    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); +                    /* if user code hasn't intervened, quit the app */ +                    if (_sapp.quit_requested) { +                        _sapp.quit_ordered = true; +                    } +                } +                if (_sapp.quit_ordered) { +                    PostQuitMessage(0); +                } +                return 0; +            case WM_SYSCOMMAND: +                switch (wParam & 0xFFF0) { +                    case SC_SCREENSAVE: +                    case SC_MONITORPOWER: +                        if (_sapp.fullscreen) { +                            /* disable screen saver and blanking in fullscreen mode */ +                            return 0; +                        } +                        break; +                    case SC_KEYMENU: +                        /* user trying to access menu via ALT */ +                        return 0; +                } +                break; +            case WM_ERASEBKGND: +                return 1; +            case WM_SIZE: +                { +                    const bool iconified = wParam == SIZE_MINIMIZED; +                    if (iconified != _sapp.win32.iconified) { +                        _sapp.win32.iconified = iconified; +                        if (iconified) { +                            _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_ICONIFIED); +                        } +                        else { +                            _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESTORED); +                        } +                    } +                } +                break; +            case WM_SETCURSOR: +                if (_sapp.desc.user_cursor) { +                    if (LOWORD(lParam) == HTCLIENT) { +                        _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); +                        return 1; +                    } +                } +                break; +            case WM_LBUTTONDOWN: +                _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); +                _sapp_win32_capture_mouse(1<<SAPP_MOUSEBUTTON_LEFT); +                break; +            case WM_RBUTTONDOWN: +                _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_RIGHT); +                _sapp_win32_capture_mouse(1<<SAPP_MOUSEBUTTON_RIGHT); +                break; +            case WM_MBUTTONDOWN: +                _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_MIDDLE); +                _sapp_win32_capture_mouse(1<<SAPP_MOUSEBUTTON_MIDDLE); +                break; +            case WM_LBUTTONUP: +                _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_LEFT); +                _sapp_win32_release_mouse(1<<SAPP_MOUSEBUTTON_LEFT); +                break; +            case WM_RBUTTONUP: +                _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_RIGHT); +                _sapp_win32_release_mouse(1<<SAPP_MOUSEBUTTON_RIGHT); +                break; +            case WM_MBUTTONUP: +                _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, SAPP_MOUSEBUTTON_MIDDLE); +                _sapp_win32_release_mouse(1<<SAPP_MOUSEBUTTON_MIDDLE); +                break; +            case WM_MOUSEMOVE: +                if (!_sapp.mouse.locked) { +                    const float new_x  = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; +                    const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; +                    /* don't update dx/dy in the very first event */ +                    if (_sapp.mouse.pos_valid) { +                        _sapp.mouse.dx = new_x - _sapp.mouse.x; +                        _sapp.mouse.dy = new_y - _sapp.mouse.y; +                    } +                    _sapp.mouse.x = new_x; +                    _sapp.mouse.y = new_y; +                    _sapp.mouse.pos_valid = true; +                    if (!_sapp.win32.mouse_tracked) { +                        _sapp.win32.mouse_tracked = true; +                        TRACKMOUSEEVENT tme; +                        memset(&tme, 0, sizeof(tme)); +                        tme.cbSize = sizeof(tme); +                        tme.dwFlags = TME_LEAVE; +                        tme.hwndTrack = _sapp.win32.hwnd; +                        TrackMouseEvent(&tme); +                        _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID); +                    } +                    _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); +                } +                break; +            case WM_INPUT: +                /* raw mouse input during mouse-lock */ +                if (_sapp.mouse.locked) { +                    HRAWINPUT ri = (HRAWINPUT) lParam; +                    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"); +                        break; +                    } +                    const RAWINPUT* raw_mouse_data = (const RAWINPUT*) &_sapp.win32.raw_input_data; +                    if (raw_mouse_data->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { +                        /* mouse only reports absolute position +                           NOTE: THIS IS UNTESTED, it's unclear from reading the +                           Win32 RawInput docs under which circumstances absolute +                           positions are sent. +                        */ +                        if (_sapp.win32.raw_input_mousepos_valid) { +                            LONG new_x = raw_mouse_data->data.mouse.lLastX; +                            LONG new_y = raw_mouse_data->data.mouse.lLastY; +                            _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); +                            _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); +                            _sapp.win32.raw_input_mousepos_x = new_x; +                            _sapp.win32.raw_input_mousepos_y = new_y; +                            _sapp.win32.raw_input_mousepos_valid = true; +                        } +                    } +                    else { +                        /* mouse reports movement delta (this seems to be the common case) */ +                        _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; +                        _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; +                    } +                    _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); +                } +                break; + +            case WM_MOUSELEAVE: +                if (!_sapp.mouse.locked) { +                    _sapp.win32.mouse_tracked = false; +                    _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); +                } +                break; +            case WM_MOUSEWHEEL: +                _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); +                break; +            case WM_MOUSEHWHEEL: +                _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); +                break; +            case WM_CHAR: +                _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); +                break; +            case WM_KEYDOWN: +            case WM_SYSKEYDOWN: +                _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); +                break; +            case WM_KEYUP: +            case WM_SYSKEYUP: +                _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); +                break; +            case WM_ENTERSIZEMOVE: +                SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); +                break; +            case WM_EXITSIZEMOVE: +                KillTimer(_sapp.win32.hwnd, 1); +                break; +            case WM_TIMER: +                _sapp_frame(); +                #if defined(SOKOL_D3D11) +                    _sapp_d3d11_present(); +                #endif +                #if defined(SOKOL_GLCORE33) +                    _sapp_wgl_swap_buffers(); +                #endif +                /* NOTE: resizing the swap-chain during resize leads to a substantial +                   memory spike (hundreds of megabytes for a few seconds). + +                if (_sapp_win32_update_dimensions()) { +                    #if defined(SOKOL_D3D11) +                    _sapp_d3d11_resize_default_render_target(); +                    #endif +                    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +                } +                */ +                break; +            case WM_DROPFILES: +                _sapp_win32_files_dropped((HDROP)wParam); +                break; +            default: +                break; +        } +    } +    return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +_SOKOL_PRIVATE void _sapp_win32_create_window(void) { +    WNDCLASSW wndclassw; +    memset(&wndclassw, 0, sizeof(wndclassw)); +    wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; +    wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; +    wndclassw.hInstance = GetModuleHandleW(NULL); +    wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); +    wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); +    wndclassw.lpszClassName = L"SOKOLAPP"; +    RegisterClassW(&wndclassw); + +    DWORD win_style; +    const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; +    RECT rect = { 0, 0, 0, 0 }; +    if (_sapp.fullscreen) { +        win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; +        rect.right = GetSystemMetrics(SM_CXSCREEN); +        rect.bottom = GetSystemMetrics(SM_CYSCREEN); +    } +    else { +        win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; +        rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); +        rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); +    } +    AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); +    const int win_width = rect.right - rect.left; +    const int win_height = rect.bottom - rect.top; +    _sapp.win32.in_create_window = true; +    _sapp.win32.hwnd = CreateWindowExW( +        win_ex_style,               /* dwExStyle */ +        L"SOKOLAPP",                /* lpClassName */ +        _sapp.window_title_wide,    /* lpWindowName */ +        win_style,                  /* dwStyle */ +        CW_USEDEFAULT,              /* X */ +        CW_USEDEFAULT,              /* Y */ +        win_width,                  /* nWidth */ +        win_height,                 /* nHeight */ +        NULL,                       /* hWndParent */ +        NULL,                       /* hMenu */ +        GetModuleHandle(NULL),      /* hInstance */ +        NULL);                      /* lParam */ +    ShowWindow(_sapp.win32.hwnd, SW_SHOW); +    _sapp.win32.in_create_window = false; +    _sapp.win32.dc = GetDC(_sapp.win32.hwnd); +    SOKOL_ASSERT(_sapp.win32.dc); +    _sapp_win32_update_dimensions(); + +    DragAcceptFiles(_sapp.win32.hwnd, 1); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { +    DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; +    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) { +            FILE* res_fp = 0; +            errno_t err; +            err = freopen_s(&res_fp, "CON", "w", stdout); +            err = freopen_s(&res_fp, "CON", "w", stderr); +            (void)err; +        } +    } +    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); +    typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); +    typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + +    SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; +    SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; +    GETDPIFORMONITOR_T fn_getdpiformonitor = 0; +    HINSTANCE user32 = LoadLibraryA("user32.dll"); +    if (user32) { +        fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); +    } +    HINSTANCE shcore = LoadLibraryA("shcore.dll"); +    if (shcore) { +        fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); +        fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); +    } +    if (fn_setprocessdpiawareness) { +        /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ +        PROCESS_DPI_AWARENESS process_dpi_awareness = PROCESS_SYSTEM_DPI_AWARE; +        _sapp.win32.dpi.aware = true; +        if (!_sapp.desc.high_dpi) { +            process_dpi_awareness = PROCESS_DPI_UNAWARE; +            _sapp.win32.dpi.aware = false; +        } +        fn_setprocessdpiawareness(process_dpi_awareness); +    } +    else if (fn_setprocessdpiaware) { +        fn_setprocessdpiaware(); +        _sapp.win32.dpi.aware = true; +    } +    /* get dpi scale factor for main monitor */ +    if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { +        POINT pt = { 1, 1 }; +        HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); +        UINT dpix, dpiy; +        HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); +        _SOKOL_UNUSED(hr); +        SOKOL_ASSERT(SUCCEEDED(hr)); +        /* clamp window scale to an integer factor */ +        _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; +    } +    else { +        _sapp.win32.dpi.window_scale = 1.0f; +    } +    if (_sapp.desc.high_dpi) { +        _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; +        _sapp.win32.dpi.mouse_scale = 1.0f; +    } +    else { +        _sapp.win32.dpi.content_scale = 1.0f; +        _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; +    } +    _sapp.dpi_scale = _sapp.win32.dpi.content_scale; +    if (user32) { +        FreeLibrary(user32); +    } +    if (shcore) { +        FreeLibrary(shcore); +    } +} + +_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { +    SOKOL_ASSERT(str); +    SOKOL_ASSERT(_sapp.win32.hwnd); +    SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); + +    wchar_t* wchar_buf = 0; +    const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); +    HANDLE object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); +    if (!object) { +        goto error; +    } +    wchar_buf = (wchar_t*) GlobalLock(object); +    if (!wchar_buf) { +        goto error; +    } +    if (!_sapp_win32_uwp_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { +        goto error; +    } +    GlobalUnlock(wchar_buf); +    wchar_buf = 0; +    if (!OpenClipboard(_sapp.win32.hwnd)) { +        goto error; +    } +    EmptyClipboard(); +    SetClipboardData(CF_UNICODETEXT, object); +    CloseClipboard(); +    return true; + +error: +    if (wchar_buf) { +        GlobalUnlock(object); +    } +    if (object) { +        GlobalFree(object); +    } +    return false; +} + +_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { +    SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); +    SOKOL_ASSERT(_sapp.win32.hwnd); +    if (!OpenClipboard(_sapp.win32.hwnd)) { +        /* silently ignore any errors and just return the current +           content of the local clipboard buffer +        */ +        return _sapp.clipboard.buffer; +    } +    HANDLE object = GetClipboardData(CF_UNICODETEXT); +    if (!object) { +        CloseClipboard(); +        return _sapp.clipboard.buffer; +    } +    const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); +    if (!wchar_buf) { +        CloseClipboard(); +        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"); +    } +    GlobalUnlock(object); +    CloseClipboard(); +    return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { +    _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); +    SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); +} + +/* don't laugh, but this seems to be the easiest and most robust +   way to check if we're running on Win10 + +   From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 +*/ +_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { +    HMODULE h = GetModuleHandleW(L"kernel32.dll"); +    if (NULL != h) { +        return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); +    } +    else { +        return false; +    } +} + +_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)); +    _sapp_win32_init_dpi(); +    _sapp_win32_create_window(); +    #if defined(SOKOL_D3D11) +        _sapp_d3d11_create_device_and_swapchain(); +        _sapp_d3d11_create_default_render_target(); +    #endif +    #if defined(SOKOL_GLCORE33) +        _sapp_wgl_init(); +        _sapp_wgl_load_extensions(); +        _sapp_wgl_create_context(); +    #endif +    _sapp.valid = true; + +    bool done = false; +    while (!(done || _sapp.quit_ordered)) { +        MSG msg; +        while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { +            if (WM_QUIT == msg.message) { +                done = true; +                continue; +            } +            else { +                TranslateMessage(&msg); +                DispatchMessage(&msg); +            } +        } +        _sapp_frame(); +        #if defined(SOKOL_D3D11) +            _sapp_d3d11_present(); +            if (IsIconic(_sapp.win32.hwnd)) { +                Sleep((DWORD)(16 * _sapp.swap_interval)); +            } +        #endif +        #if defined(SOKOL_GLCORE33) +            _sapp_wgl_swap_buffers(); +        #endif +        /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ +        if (_sapp_win32_update_dimensions()) { +            #if defined(SOKOL_D3D11) +            _sapp_d3d11_resize_default_render_target(); +            #endif +            _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +        } +        if (_sapp.quit_requested) { +            PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); +        } +    } +    _sapp_call_cleanup(); + +    #if defined(SOKOL_D3D11) +        _sapp_d3d11_destroy_default_render_target(); +        _sapp_d3d11_destroy_device_and_swapchain(); +    #else +        _sapp_wgl_destroy_context(); +        _sapp_wgl_shutdown(); +    #endif +    _sapp_win32_destroy_window(); +    _sapp_win32_restore_console(); +    _sapp_discard_state(); +} + +_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { +    int argc = 0; +    char** argv = 0; +    char* args; + +    LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); +    if (w_argv == NULL) { +        _sapp_fail("Win32: failed to parse command line"); +    } else { +        size_t size = wcslen(w_command_line) * 4; +        argv = (char**) SOKOL_CALLOC(1, ((size_t)argc + 1) * sizeof(char*) + size); +        SOKOL_ASSERT(argv); +        args = (char*) &argv[argc + 1]; +        int n; +        for (int i = 0; i < argc; ++i) { +            n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); +            if (n == 0) { +                _sapp_fail("Win32: failed to convert all arguments to utf8"); +                break; +            } +            argv[i] = args; +            size -= (size_t)n; +            args += n; +        } +        LocalFree(w_argv); +    } +    *o_argc = argc; +    return argv; +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(SOKOL_WIN32_FORCE_MAIN) +int main(int argc, char* argv[]) { +    sapp_desc desc = sokol_main(argc, argv); +    _sapp_win32_run(&desc); +    return 0; +} +#else +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { +    _SOKOL_UNUSED(hInstance); +    _SOKOL_UNUSED(hPrevInstance); +    _SOKOL_UNUSED(lpCmdLine); +    _SOKOL_UNUSED(nCmdShow); +    int argc_utf8 = 0; +    char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); +    sapp_desc desc = sokol_main(argc_utf8, argv_utf8); +    _sapp_win32_run(&desc); +    SOKOL_FREE(argv_utf8); +    return 0; +} +#endif /* SOKOL_WIN32_FORCE_MAIN */ +#endif /* SOKOL_NO_ENTRY */ + +#ifdef _MSC_VER +    #pragma warning(pop) +#endif + +#endif /* _SAPP_WIN32 */ + +/*== UWP ================================================================*/ +#if defined(_SAPP_UWP) + +// Helper functions +_SOKOL_PRIVATE void _sapp_uwp_configure_dpi(float monitor_dpi) { +    _sapp.uwp.dpi.window_scale = monitor_dpi / 96.0f; +    if (_sapp.desc.high_dpi) { +        _sapp.uwp.dpi.content_scale = _sapp.uwp.dpi.window_scale; +        _sapp.uwp.dpi.mouse_scale = 1.0f * _sapp.uwp.dpi.window_scale; +    } +    else { +        _sapp.uwp.dpi.content_scale = 1.0f; +        _sapp.uwp.dpi.mouse_scale = 1.0f; +    } +    _sapp.dpi_scale = _sapp.uwp.dpi.content_scale; +} + +_SOKOL_PRIVATE void _sapp_uwp_show_mouse(bool visible) { +    using namespace winrt::Windows::UI::Core; + +    /* NOTE: this function is only called when the mouse visibility actually changes */ +    CoreWindow::GetForCurrentThread().PointerCursor(visible ? +        CoreCursor(CoreCursorType::Arrow, 0) : +        CoreCursor(nullptr)); +} + +_SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow const& sender_window) { +    using namespace winrt::Windows::System; +    using namespace winrt::Windows::UI::Core; + +    uint32_t mods = 0; +    if ((sender_window.GetKeyState(VirtualKey::Shift) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { +        mods |= SAPP_MODIFIER_SHIFT; +    } +    if ((sender_window.GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { +        mods |= SAPP_MODIFIER_CTRL; +    } +    if ((sender_window.GetKeyState(VirtualKey::Menu) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) { +        mods |= SAPP_MODIFIER_ALT; +    } +    if (((sender_window.GetKeyState(VirtualKey::LeftWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down) || +        ((sender_window.GetKeyState(VirtualKey::RightWindows) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down)) +    { +        mods |= SAPP_MODIFIER_SUPER; +    } +    return mods; +} + +_SOKOL_PRIVATE void _sapp_uwp_mouse_event(sapp_event_type type, sapp_mousebutton btn, winrt::Windows::UI::Core::CoreWindow const& sender_window) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp.event.modifiers = _sapp_uwp_mods(sender_window); +        _sapp.event.mouse_button = btn; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_uwp_scroll_event(float delta, bool horizontal, winrt::Windows::UI::Core::CoreWindow const& sender_window) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); +        _sapp.event.modifiers = _sapp_uwp_mods(sender_window); +        _sapp.event.scroll_x = horizontal ? (-delta / 30.0f) : 0.0f; +        _sapp.event.scroll_y = horizontal ? 0.0f : (delta / 30.0f); +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_uwp_extract_mouse_button_events(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { + +    // we need to figure out ourselves what mouse buttons have been pressed and released, +    // because UWP doesn't properly send down/up mouse button events when multiple buttons +    // are pressed down, so we also need to check the mouse button state in other mouse events +    // to track what buttons have been pressed down and released +    // +    auto properties = args.CurrentPoint().Properties(); +    const uint8_t lmb_bit = (1 << SAPP_MOUSEBUTTON_LEFT); +    const uint8_t rmb_bit = (1 << SAPP_MOUSEBUTTON_RIGHT); +    const uint8_t mmb_bit = (1 << SAPP_MOUSEBUTTON_MIDDLE); +    uint8_t new_btns = 0; +    if (properties.IsLeftButtonPressed()) { +        new_btns |= lmb_bit; +    } +    if (properties.IsRightButtonPressed()) { +        new_btns |= rmb_bit; +    } +    if (properties.IsMiddleButtonPressed()) { +        new_btns |= mmb_bit; +    } +    const uint8_t old_btns = _sapp.uwp.mouse_buttons; +    const uint8_t chg_btns = new_btns ^ old_btns; + +    _sapp.uwp.mouse_buttons = new_btns; + +    sapp_event_type type = SAPP_EVENTTYPE_INVALID; +    sapp_mousebutton btn = SAPP_MOUSEBUTTON_INVALID; +    if (chg_btns & lmb_bit) { +        btn = SAPP_MOUSEBUTTON_LEFT; +        type = (new_btns & lmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; +    } +    if (chg_btns & rmb_bit) { +        btn = SAPP_MOUSEBUTTON_RIGHT; +        type = (new_btns & rmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; +    } +    if (chg_btns & mmb_bit) { +        btn = SAPP_MOUSEBUTTON_MIDDLE; +        type = (new_btns & mmb_bit) ? SAPP_EVENTTYPE_MOUSE_DOWN : SAPP_EVENTTYPE_MOUSE_UP; +    } +    if (type != SAPP_EVENTTYPE_INVALID) { +        _sapp_uwp_mouse_event(type, btn, sender); +    } +} + +_SOKOL_PRIVATE void _sapp_uwp_key_event(sapp_event_type type, winrt::Windows::UI::Core::CoreWindow const& sender_window, winrt::Windows::UI::Core::KeyEventArgs const& args) { +    auto key_status = args.KeyStatus(); +    uint32_t ext_scan_code = key_status.ScanCode | (key_status.IsExtendedKey ? 0x100 : 0); +    if (_sapp_events_enabled() && (ext_scan_code < SAPP_MAX_KEYCODES)) { +        _sapp_init_event(type); +        _sapp.event.modifiers = _sapp_uwp_mods(sender_window); +        _sapp.event.key_code = _sapp.keycodes[ext_scan_code]; +        _sapp.event.key_repeat = type == SAPP_EVENTTYPE_KEY_UP ? false : key_status.WasKeyDown; +        _sapp_call_event(&_sapp.event); +        /* check if a CLIPBOARD_PASTED event must be sent too */ +        if (_sapp.clipboard.enabled && +            (type == SAPP_EVENTTYPE_KEY_DOWN) && +            (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && +            (_sapp.event.key_code == SAPP_KEYCODE_V)) +        { +            _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); +            _sapp_call_event(&_sapp.event); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_uwp_char_event(uint32_t c, bool repeat, winrt::Windows::UI::Core::CoreWindow const& sender_window) { +    if (_sapp_events_enabled() && (c >= 32)) { +        _sapp_init_event(SAPP_EVENTTYPE_CHAR); +        _sapp.event.modifiers = _sapp_uwp_mods(sender_window); +        _sapp.event.char_code = c; +        _sapp.event.key_repeat = repeat; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_uwp_toggle_fullscreen(void) { +    auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); +    _sapp.fullscreen = appView.IsFullScreenMode(); +    if (!_sapp.fullscreen) { +        appView.TryEnterFullScreenMode(); +    } +    else { +        appView.ExitFullScreenMode(); +    } +    _sapp.fullscreen = appView.IsFullScreenMode(); +} + +namespace {/* Empty namespace to ensure internal linkage (same as _SOKOL_PRIVATE) */ + +// Controls all the DirectX device resources. +class DeviceResources { +public: +    // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. +    interface IDeviceNotify { +        virtual void OnDeviceLost() = 0; +        virtual void OnDeviceRestored() = 0; +    }; + +    DeviceResources(); +    ~DeviceResources(); +    void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); +    void SetLogicalSize(winrt::Windows::Foundation::Size logicalSize); +    void SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation); +    void SetDpi(float dpi); +    void ValidateDevice(); +    void HandleDeviceLost(); +    void RegisterDeviceNotify(IDeviceNotify* deviceNotify); +    void Trim(); +    void Present(); + +private: + +    // Swapchain Rotation Matrices (Z-rotation) +    static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation0 = { +        1.0f, 0.0f, 0.0f, 0.0f, +        0.0f, 1.0f, 0.0f, 0.0f, +        0.0f, 0.0f, 1.0f, 0.0f, +        0.0f, 0.0f, 0.0f, 1.0f +    }; +    static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation90 = { +        0.0f, 1.0f, 0.0f, 0.0f, +        -1.0f, 0.0f, 0.0f, 0.0f, +        0.0f, 0.0f, 1.0f, 0.0f, +        0.0f, 0.0f, 0.0f, 1.0f +    }; +    static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation180 = { +        -1.0f, 0.0f, 0.0f, 0.0f, +        0.0f, -1.0f, 0.0f, 0.0f, +        0.0f, 0.0f, 1.0f, 0.0f, +        0.0f, 0.0f, 0.0f, 1.0f +    }; +    static inline const DirectX::XMFLOAT4X4 DeviceResources::m_rotation270 = { +        0.0f, -1.0f, 0.0f, 0.0f, +        1.0f, 0.0f, 0.0f, 0.0f, +        0.0f, 0.0f, 1.0f, 0.0f, +        0.0f, 0.0f, 0.0f, 1.0f +    }; + +    void CreateDeviceResources(); +    void CreateWindowSizeDependentResources(); +    void UpdateRenderTargetSize(); +    DXGI_MODE_ROTATION ComputeDisplayRotation(); +    bool SdkLayersAvailable(); + +    // Direct3D objects. +    winrt::com_ptr<ID3D11Device3> m_d3dDevice; +    winrt::com_ptr<ID3D11DeviceContext3> m_d3dContext; +    winrt::com_ptr<IDXGISwapChain3> m_swapChain; + +    // Direct3D rendering objects. Required for 3D. +    winrt::com_ptr<ID3D11Texture2D1> m_d3dRenderTarget; +    winrt::com_ptr<ID3D11RenderTargetView1> m_d3dRenderTargetView; +    winrt::com_ptr<ID3D11Texture2D1> m_d3dMSAARenderTarget; +    winrt::com_ptr<ID3D11RenderTargetView1> m_d3dMSAARenderTargetView; +    winrt::com_ptr<ID3D11Texture2D1> m_d3dDepthStencil; +    winrt::com_ptr<ID3D11DepthStencilView> m_d3dDepthStencilView; +    D3D11_VIEWPORT m_screenViewport = { }; + +    // Cached reference to the Window. +    winrt::agile_ref< winrt::Windows::UI::Core::CoreWindow> m_window; + +    // Cached device properties. +    D3D_FEATURE_LEVEL m_d3dFeatureLevel = D3D_FEATURE_LEVEL_9_1; +    winrt::Windows::Foundation::Size m_d3dRenderTargetSize = { }; +    winrt::Windows::Foundation::Size m_outputSize = { }; +    winrt::Windows::Foundation::Size m_logicalSize = { }; +    winrt::Windows::Graphics::Display::DisplayOrientations m_nativeOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; +    winrt::Windows::Graphics::Display::DisplayOrientations m_currentOrientation = winrt::Windows::Graphics::Display::DisplayOrientations::None; +    float m_dpi = -1.0f; + +    // Transforms used for display orientation. +    DirectX::XMFLOAT4X4 m_orientationTransform3D; + +    // The IDeviceNotify can be held directly as it owns the DeviceResources. +    IDeviceNotify* m_deviceNotify = nullptr; +}; + +// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events. +struct App : winrt::implements<App, winrt::Windows::ApplicationModel::Core::IFrameworkViewSource, winrt::Windows::ApplicationModel::Core::IFrameworkView> { +public: +    // IFrameworkViewSource Methods +    winrt::Windows::ApplicationModel::Core::IFrameworkView CreateView() { return *this; } + +    // IFrameworkView Methods. +    virtual void Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView); +    virtual void SetWindow(winrt::Windows::UI::Core::CoreWindow const& window); +    virtual void Load(winrt::hstring const& entryPoint); +    virtual void Run(); +    virtual void Uninitialize(); + +protected: +    // Application lifecycle event handlers +    void OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args); +    void OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args); +    void OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args); + +    // Window event handlers +    void OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args); +    void OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args); + +    // Navigation event handlers +    void OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args); + +    // Input event handlers +    void OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); +    void OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args); +    void OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args); + +    // Pointer event handlers +    void OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); +    void OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); +    void OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); +    void OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); +    void OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); +    void OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args); + +    // DisplayInformation event handlers. +    void OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); +    void OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); +    void OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args); + +private: +    std::unique_ptr<DeviceResources> m_deviceResources; +    bool m_windowVisible = true; +}; + +DeviceResources::DeviceResources() { +    CreateDeviceResources(); +} + +DeviceResources::~DeviceResources() { +    // Cleanup Sokol Context +    _sapp.d3d11.device = nullptr; +    _sapp.d3d11.device_context = nullptr; +} + +void DeviceResources::CreateDeviceResources() { +    // This flag adds support for surfaces with a different color channel ordering +    // than the API default. It is required for compatibility with Direct2D. +    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + +    #if defined(_DEBUG) +    if (SdkLayersAvailable()) { +        // If the project is in a debug build, enable debugging via SDK Layers with this flag. +        creationFlags |= D3D11_CREATE_DEVICE_DEBUG; +    } +    #endif + +    // This array defines the set of DirectX hardware feature levels this app will support. +    // Note the ordering should be preserved. +    // Don't forget to declare your application's minimum required feature level in its +    // description.  All applications are assumed to support 9.1 unless otherwise stated. +    D3D_FEATURE_LEVEL featureLevels[] = { +        D3D_FEATURE_LEVEL_12_1, +        D3D_FEATURE_LEVEL_12_0, +        D3D_FEATURE_LEVEL_11_1, +        D3D_FEATURE_LEVEL_11_0, +        D3D_FEATURE_LEVEL_10_1, +        D3D_FEATURE_LEVEL_10_0, +        D3D_FEATURE_LEVEL_9_3, +        D3D_FEATURE_LEVEL_9_2, +        D3D_FEATURE_LEVEL_9_1 +    }; + +    // Create the Direct3D 11 API device object and a corresponding context. +    winrt::com_ptr<ID3D11Device> device; +    winrt::com_ptr<ID3D11DeviceContext> context; + +    HRESULT hr = D3D11CreateDevice( +        nullptr,                    // Specify nullptr to use the default adapter. +        D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver. +        0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. +        creationFlags,              // Set debug and Direct2D compatibility flags. +        featureLevels,              // List of feature levels this app can support. +        ARRAYSIZE(featureLevels),   // Size of the list above. +        D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. +        device.put(),               // Returns the Direct3D device created. +        &m_d3dFeatureLevel,         // Returns feature level of device created. +        context.put()               // Returns the device immediate context. +    ); + +    if (FAILED(hr)) { +        // If the initialization fails, fall back to the WARP device. +        // For more information on WARP, see: +        // https://go.microsoft.com/fwlink/?LinkId=286690 +        winrt::check_hresult( +            D3D11CreateDevice( +                nullptr, +                D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. +                0, +                creationFlags, +                featureLevels, +                ARRAYSIZE(featureLevels), +                D3D11_SDK_VERSION, +                device.put(), +                &m_d3dFeatureLevel, +                context.put() +            ) +        ); +    } + +    // Store pointers to the Direct3D 11.3 API device and immediate context. +    m_d3dDevice = device.as<ID3D11Device3>(); +    m_d3dContext = context.as<ID3D11DeviceContext3>(); + +    // Setup Sokol Context +    _sapp.d3d11.device = m_d3dDevice.get(); +    _sapp.d3d11.device_context = m_d3dContext.get(); +} + +void DeviceResources::CreateWindowSizeDependentResources() { +    // Cleanup Sokol Context (these are non-owning raw pointers) +    _sapp.d3d11.rt = nullptr; +    _sapp.d3d11.rtv = nullptr; +    _sapp.d3d11.msaa_rt = nullptr; +    _sapp.d3d11.msaa_rtv = nullptr; +    _sapp.d3d11.ds = nullptr; +    _sapp.d3d11.dsv = nullptr; + +    // Clear the previous window size specific context. +    ID3D11RenderTargetView* nullViews[] = { nullptr }; +    m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr); +    // these are smart pointers, setting to nullptr will delete the objects +    m_d3dRenderTarget = nullptr; +    m_d3dRenderTargetView = nullptr; +    m_d3dMSAARenderTarget = nullptr; +    m_d3dMSAARenderTargetView = nullptr; +    m_d3dDepthStencilView = nullptr; +    m_d3dDepthStencil = nullptr; +    m_d3dContext->Flush1(D3D11_CONTEXT_TYPE_ALL, nullptr); + +    UpdateRenderTargetSize(); + +    // The width and height of the swap chain must be based on the window's +    // natively-oriented width and height. If the window is not in the native +    // orientation, the dimensions must be reversed. +    DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation(); + +    bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270; +    m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width; +    m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height; + +    if (m_swapChain != nullptr) { +        // If the swap chain already exists, resize it. +        HRESULT hr = m_swapChain->ResizeBuffers( +            2, // Double-buffered swap chain. +            lround(m_d3dRenderTargetSize.Width), +            lround(m_d3dRenderTargetSize.Height), +            DXGI_FORMAT_B8G8R8A8_UNORM, +            0 +        ); + +        if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { +            // If the device was removed for any reason, a new device and swap chain will need to be created. +            HandleDeviceLost(); + +            // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method +            // and correctly set up the new device. +            return; +        } +        else { +            winrt::check_hresult(hr); +        } +    } +    else { +        // Otherwise, create a new one using the same adapter as the existing Direct3D device. +        DXGI_SCALING scaling = (_sapp.uwp.dpi.content_scale == _sapp.uwp.dpi.window_scale) ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH; +        DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 }; + +        swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width);      // Match the size of the window. +        swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height); +        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;              // This is the most common swap chain format. +        swapChainDesc.Stereo = false; +        swapChainDesc.SampleDesc.Count = 1;                             // Don't use multi-sampling. +        swapChainDesc.SampleDesc.Quality = 0; +        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; +        swapChainDesc.BufferCount = 2;                                  // Use double-buffering to minimize latency. +        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;    // All Microsoft Store apps must use this SwapEffect. +        swapChainDesc.Flags = 0; +        swapChainDesc.Scaling = scaling; +        swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + +        // This sequence obtains the DXGI factory that was used to create the Direct3D device above. +        winrt::com_ptr<IDXGIDevice3> dxgiDevice = m_d3dDevice.as<IDXGIDevice3>(); +        winrt::com_ptr<IDXGIAdapter> dxgiAdapter; +        winrt::check_hresult(dxgiDevice->GetAdapter(dxgiAdapter.put())); +        winrt::com_ptr<IDXGIFactory4> dxgiFactory; +        winrt::check_hresult(dxgiAdapter->GetParent(__uuidof(IDXGIFactory4), dxgiFactory.put_void())); +        winrt::com_ptr<IDXGISwapChain1> swapChain; +        winrt::check_hresult(dxgiFactory->CreateSwapChainForCoreWindow(m_d3dDevice.get(), m_window.get().as<::IUnknown>().get(), &swapChainDesc, nullptr, swapChain.put())); +        m_swapChain = swapChain.as<IDXGISwapChain3>(); + +        // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and +        // ensures that the application will only render after each VSync, minimizing power consumption. +        winrt::check_hresult(dxgiDevice->SetMaximumFrameLatency(1)); + +        // Setup Sokol Context +        winrt::check_hresult(swapChain->GetDesc(&_sapp.d3d11.swap_chain_desc)); +        _sapp.d3d11.swap_chain = m_swapChain.as<IDXGISwapChain3>().detach(); +    } + +    // Set the proper orientation for the swap chain, and generate 2D and +    // 3D matrix transformations for rendering to the rotated swap chain. +    // Note the rotation angle for the 2D and 3D transforms are different. +    // This is due to the difference in coordinate spaces.  Additionally, +    // the 3D matrix is specified explicitly to avoid rounding errors. +    switch (displayRotation) { +        case DXGI_MODE_ROTATION_IDENTITY: +            m_orientationTransform3D = m_rotation0; +            break; + +        case DXGI_MODE_ROTATION_ROTATE90: +            m_orientationTransform3D = m_rotation270; +            break; + +        case DXGI_MODE_ROTATION_ROTATE180: +            m_orientationTransform3D = m_rotation180; +            break; + +        case DXGI_MODE_ROTATION_ROTATE270: +            m_orientationTransform3D = m_rotation90; +            break; +    } +    winrt::check_hresult(m_swapChain->SetRotation(displayRotation)); + +    // Create a render target view of the swap chain back buffer. +    winrt::check_hresult(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&m_d3dRenderTarget))); +    winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dRenderTarget.get(), nullptr, m_d3dRenderTargetView.put())); + +    // Create MSAA texture and view if needed +    if (_sapp.sample_count > 1) { +        CD3D11_TEXTURE2D_DESC1 msaaTexDesc( +            DXGI_FORMAT_B8G8R8A8_UNORM, +            lround(m_d3dRenderTargetSize.Width), +            lround(m_d3dRenderTargetSize.Height), +            1,  // arraySize +            1,  // mipLevels +            D3D11_BIND_RENDER_TARGET, +            D3D11_USAGE_DEFAULT, +            0,  // cpuAccessFlags +            _sapp.sample_count, +            _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 +        ); +        winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&msaaTexDesc, nullptr, m_d3dMSAARenderTarget.put())); +        winrt::check_hresult(m_d3dDevice->CreateRenderTargetView1(m_d3dMSAARenderTarget.get(), nullptr, m_d3dMSAARenderTargetView.put())); +    } + +    // Create a depth stencil view for use with 3D rendering if needed. +    CD3D11_TEXTURE2D_DESC1 depthStencilDesc( +        DXGI_FORMAT_D24_UNORM_S8_UINT, +        lround(m_d3dRenderTargetSize.Width), +        lround(m_d3dRenderTargetSize.Height), +        1, // This depth stencil view has only one texture. +        1, // Use a single mipmap level. +        D3D11_BIND_DEPTH_STENCIL, +        D3D11_USAGE_DEFAULT, +        0,  // cpuAccessFlag +        _sapp.sample_count, +        _sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0 +    ); +    winrt::check_hresult(m_d3dDevice->CreateTexture2D1(&depthStencilDesc, nullptr, m_d3dDepthStencil.put())); + +    CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); +    winrt::check_hresult(m_d3dDevice->CreateDepthStencilView(m_d3dDepthStencil.get(), nullptr, m_d3dDepthStencilView.put())); + +    // Set sokol window and framebuffer sizes +    _sapp.window_width = (int) m_logicalSize.Width; +    _sapp.window_height = (int) m_logicalSize.Height; +    _sapp.framebuffer_width = lround(m_d3dRenderTargetSize.Width); +    _sapp.framebuffer_height = lround(m_d3dRenderTargetSize.Height); + +    // Setup Sokol Context +    _sapp.d3d11.rt = m_d3dRenderTarget.as<ID3D11Texture2D>().get(); +    _sapp.d3d11.rtv = m_d3dRenderTargetView.as<ID3D11RenderTargetView>().get(); +    _sapp.d3d11.ds = m_d3dDepthStencil.as<ID3D11Texture2D>().get(); +    _sapp.d3d11.dsv = m_d3dDepthStencilView.get(); +    if (_sapp.sample_count > 1) { +        _sapp.d3d11.msaa_rt = m_d3dMSAARenderTarget.as<ID3D11Texture2D>().get(); +        _sapp.d3d11.msaa_rtv = m_d3dMSAARenderTargetView.as<ID3D11RenderTargetView>().get(); +    } + +    // Sokol app is now valid +    _sapp.valid = true; +} + +// Determine the dimensions of the render target and whether it will be scaled down. +void DeviceResources::UpdateRenderTargetSize() { +    // Calculate the necessary render target size in pixels. +    m_outputSize.Width = m_logicalSize.Width * _sapp.uwp.dpi.content_scale; +    m_outputSize.Height = m_logicalSize.Height * _sapp.uwp.dpi.content_scale; + +    // Prevent zero size DirectX content from being created. +    m_outputSize.Width = std::max(m_outputSize.Width, 1.0f); +    m_outputSize.Height = std::max(m_outputSize.Height, 1.0f); +} + +// This method is called when the CoreWindow is created (or re-created). +void DeviceResources::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { +    auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); +    m_window = window; +    m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); +    m_nativeOrientation = currentDisplayInformation.NativeOrientation(); +    m_currentOrientation = currentDisplayInformation.CurrentOrientation(); +    m_dpi = currentDisplayInformation.LogicalDpi(); +    _sapp_uwp_configure_dpi(m_dpi); +    CreateWindowSizeDependentResources(); +} + +// This method is called in the event handler for the SizeChanged event. +void DeviceResources::SetLogicalSize(winrt::Windows::Foundation::Size logicalSize) { +    if (m_logicalSize != logicalSize) { +        m_logicalSize = logicalSize; +        CreateWindowSizeDependentResources(); +    } +} + +// This method is called in the event handler for the DpiChanged event. +void DeviceResources::SetDpi(float dpi) { +    if (dpi != m_dpi) { +        m_dpi = dpi; +        _sapp_uwp_configure_dpi(m_dpi); +        // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated. +        auto window = m_window.get(); +        m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height); +        CreateWindowSizeDependentResources(); +    } +} + +// This method is called in the event handler for the OrientationChanged event. +void DeviceResources::SetCurrentOrientation(winrt::Windows::Graphics::Display::DisplayOrientations currentOrientation) { +    if (m_currentOrientation != currentOrientation) { +        m_currentOrientation = currentOrientation; +        CreateWindowSizeDependentResources(); +    } +} + +// This method is called in the event handler for the DisplayContentsInvalidated event. +void DeviceResources::ValidateDevice() { +    // The D3D Device is no longer valid if the default adapter changed since the device +    // was created or if the device has been removed. + +    // First, get the information for the default adapter from when the device was created. +    winrt::com_ptr<IDXGIDevice3> dxgiDevice = m_d3dDevice.as< IDXGIDevice3>(); +    winrt::com_ptr<IDXGIAdapter> deviceAdapter; +    winrt::check_hresult(dxgiDevice->GetAdapter(deviceAdapter.put())); +    winrt::com_ptr<IDXGIFactory4> deviceFactory; +    winrt::check_hresult(deviceAdapter->GetParent(IID_PPV_ARGS(&deviceFactory))); +    winrt::com_ptr<IDXGIAdapter1> previousDefaultAdapter; +    winrt::check_hresult(deviceFactory->EnumAdapters1(0, previousDefaultAdapter.put())); +    DXGI_ADAPTER_DESC1 previousDesc; +    winrt::check_hresult(previousDefaultAdapter->GetDesc1(&previousDesc)); + +    // Next, get the information for the current default adapter. +    winrt::com_ptr<IDXGIFactory4> currentFactory; +    winrt::check_hresult(CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory))); +    winrt::com_ptr<IDXGIAdapter1> currentDefaultAdapter; +    winrt::check_hresult(currentFactory->EnumAdapters1(0, currentDefaultAdapter.put())); +    DXGI_ADAPTER_DESC1 currentDesc; +    winrt::check_hresult(currentDefaultAdapter->GetDesc1(¤tDesc)); + +    // If the adapter LUIDs don't match, or if the device reports that it has been removed, +    // a new D3D device must be created. +    if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart || +        previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart || +        FAILED(m_d3dDevice->GetDeviceRemovedReason())) +    { +        // Release references to resources related to the old device. +        dxgiDevice = nullptr; +        deviceAdapter = nullptr; +        deviceFactory = nullptr; +        previousDefaultAdapter = nullptr; + +        // Create a new device and swap chain. +        HandleDeviceLost(); +    } +} + +// Recreate all device resources and set them back to the current state. +void DeviceResources::HandleDeviceLost() { +    m_swapChain = nullptr; +    if (m_deviceNotify != nullptr) { +        m_deviceNotify->OnDeviceLost(); +    } +    CreateDeviceResources(); +    CreateWindowSizeDependentResources(); +    if (m_deviceNotify != nullptr) { +        m_deviceNotify->OnDeviceRestored(); +    } +} + +// Register our DeviceNotify to be informed on device lost and creation. +void DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) { +    m_deviceNotify = deviceNotify; +} + +// Call this method when the app suspends. It provides a hint to the driver that the app +// is entering an idle state and that temporary buffers can be reclaimed for use by other apps. +void DeviceResources::Trim() { +    m_d3dDevice.as<IDXGIDevice3>()->Trim(); +} + +// Present the contents of the swap chain to the screen. +void DeviceResources::Present() { + +    // MSAA resolve if needed +    if (_sapp.sample_count > 1) { +        m_d3dContext->ResolveSubresource(m_d3dRenderTarget.get(), 0, m_d3dMSAARenderTarget.get(), 0, DXGI_FORMAT_B8G8R8A8_UNORM); +        m_d3dContext->DiscardView1(m_d3dMSAARenderTargetView.get(), nullptr, 0); +    } + +    // The first argument instructs DXGI to block until VSync, putting the application +    // to sleep until the next VSync. This ensures we don't waste any cycles rendering +    // frames that will never be displayed to the screen. +    DXGI_PRESENT_PARAMETERS parameters = { 0 }; +    HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); + +    // Discard the contents of the render target. +    // This is a valid operation only when the existing contents will be entirely +    // overwritten. If dirty or scroll rects are used, this call should be removed. +    m_d3dContext->DiscardView1(m_d3dRenderTargetView.get(), nullptr, 0); + +    // Discard the contents of the depth stencil. +    m_d3dContext->DiscardView1(m_d3dDepthStencilView.get(), nullptr, 0); + +    // If the device was removed either by a disconnection or a driver upgrade, we +    // must recreate all device resources. +    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { +        HandleDeviceLost(); +    } +    else { +        winrt::check_hresult(hr); +    } +} + +// This method determines the rotation between the display device's native orientation and the +// current display orientation. +DXGI_MODE_ROTATION DeviceResources::ComputeDisplayRotation() { +    DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED; + +    // Note: NativeOrientation can only be Landscape or Portrait even though +    // the DisplayOrientations enum has other values. +    switch (m_nativeOrientation) { +        case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: +            switch (m_currentOrientation) { +                case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: +                    rotation = DXGI_MODE_ROTATION_IDENTITY; +                    break; + +                case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: +                    rotation = DXGI_MODE_ROTATION_ROTATE270; +                    break; + +                case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: +                    rotation = DXGI_MODE_ROTATION_ROTATE180; +                    break; + +                case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: +                    rotation = DXGI_MODE_ROTATION_ROTATE90; +                    break; +            } +            break; + +        case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: +            switch (m_currentOrientation) { +                case winrt::Windows::Graphics::Display::DisplayOrientations::Landscape: +                    rotation = DXGI_MODE_ROTATION_ROTATE90; +                    break; + +                case winrt::Windows::Graphics::Display::DisplayOrientations::Portrait: +                    rotation = DXGI_MODE_ROTATION_IDENTITY; +                    break; + +                case winrt::Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: +                    rotation = DXGI_MODE_ROTATION_ROTATE270; +                    break; + +                case winrt::Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: +                    rotation = DXGI_MODE_ROTATION_ROTATE180; +                    break; +            } +            break; +    } +    return rotation; +} + +// Check for SDK Layer support. +bool DeviceResources::SdkLayersAvailable() { +    #if defined(_DEBUG) +        HRESULT hr = D3D11CreateDevice( +            nullptr, +            D3D_DRIVER_TYPE_NULL,       // There is no need to create a real hardware device. +            0, +            D3D11_CREATE_DEVICE_DEBUG,  // Check for the SDK layers. +            nullptr,                    // Any feature level will do. +            0, +            D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Microsoft Store apps. +            nullptr,                    // No need to keep the D3D device reference. +            nullptr,                    // No need to know the feature level. +            nullptr                     // No need to keep the D3D device context reference. +        ); +        return SUCCEEDED(hr); +    #else +        return false; +    #endif +} + +// The first method called when the IFrameworkView is being created. +void App::Initialize(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView) { +    // Register event handlers for app lifecycle. This example includes Activated, so that we +    // can make the CoreWindow active and start rendering on the window. +    applicationView.Activated({ this, &App::OnActivated }); + +    winrt::Windows::ApplicationModel::Core::CoreApplication::Suspending({ this, &App::OnSuspending }); +    winrt::Windows::ApplicationModel::Core::CoreApplication::Resuming({ this, &App::OnResuming }); + +    // At this point we have access to the device. +    // We can create the device-dependent resources. +    m_deviceResources = std::make_unique<DeviceResources>(); +} + +// Called when the CoreWindow object is created (or re-created). +void App::SetWindow(winrt::Windows::UI::Core::CoreWindow const& window) { +    window.SizeChanged({ this, &App::OnWindowSizeChanged }); +    window.VisibilityChanged({ this, &App::OnVisibilityChanged }); + +    window.KeyDown({ this, &App::OnKeyDown }); +    window.KeyUp({ this, &App::OnKeyUp }); +    window.CharacterReceived({ this, &App::OnCharacterReceived }); + +    window.PointerEntered({ this, &App::OnPointerEntered }); +    window.PointerExited({ this, &App::OnPointerExited }); +    window.PointerPressed({ this, &App::OnPointerPressed }); +    window.PointerReleased({ this, &App::OnPointerReleased }); +    window.PointerMoved({ this, &App::OnPointerMoved }); +    window.PointerWheelChanged({ this, &App::OnPointerWheelChanged }); + +    auto currentDisplayInformation = winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); + +    currentDisplayInformation.DpiChanged({ this, &App::OnDpiChanged }); +    currentDisplayInformation.OrientationChanged({ this, &App::OnOrientationChanged }); +    winrt::Windows::Graphics::Display::DisplayInformation::DisplayContentsInvalidated({ this, &App::OnDisplayContentsInvalidated }); + +    winrt::Windows::UI::Core::SystemNavigationManager::GetForCurrentView().BackRequested({ this, &App::OnBackRequested }); + +    m_deviceResources->SetWindow(window); +} + +// Initializes scene resources, or loads a previously saved app state. +void App::Load(winrt::hstring const& entryPoint) { +    _SOKOL_UNUSED(entryPoint); +} + +// This method is called after the window becomes active. +void App::Run() { +    // NOTE: UWP will simply terminate an application, it's not possible to detect when an application is being closed +    while (true) { +        if (m_windowVisible) { +            winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent); +            _sapp_frame(); +            m_deviceResources->Present(); +        } +        else { +            winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessOneAndAllPending); +        } +    } +} + +// Required for IFrameworkView. +// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView +// class is torn down while the app is in the foreground. +void App::Uninitialize() { +    // empty +} + +// Application lifecycle event handlers. +void App::OnActivated(winrt::Windows::ApplicationModel::Core::CoreApplicationView const& applicationView, winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) { +    _SOKOL_UNUSED(args); +    _SOKOL_UNUSED(applicationView); +    auto appView = winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); +    auto targetSize = winrt::Windows::Foundation::Size((float)_sapp.desc.width, (float)_sapp.desc.height); +    appView.SetPreferredMinSize(targetSize); +    appView.TryResizeView(targetSize); + +    // Disabling this since it can only append the title to the app name (Title - Appname). +    // There's no way of just setting a string to be the window title. +    //appView.Title(_sapp.window_title_wide); + +    // Run() won't start until the CoreWindow is activated. +    winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread().Activate(); +    if (_sapp.desc.fullscreen) { +        appView.TryEnterFullScreenMode(); +    } +    _sapp.fullscreen = appView.IsFullScreenMode(); +} + +void App::OnSuspending(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::ApplicationModel::SuspendingEventArgs const& args) { +    _SOKOL_UNUSED(sender); +    _SOKOL_UNUSED(args); +    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_SUSPENDED); +} + +void App::OnResuming(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& args) { +    _SOKOL_UNUSED(args); +    _SOKOL_UNUSED(sender); +    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESUMED); +} + +void App::OnWindowSizeChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::WindowSizeChangedEventArgs const& args) { +    _SOKOL_UNUSED(args); +    m_deviceResources->SetLogicalSize(winrt::Windows::Foundation::Size(sender.Bounds().Width, sender.Bounds().Height)); +    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +} + +void App::OnVisibilityChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::VisibilityChangedEventArgs const& args) { +    _SOKOL_UNUSED(sender); +    m_windowVisible = args.Visible(); +    _sapp_win32_uwp_app_event(m_windowVisible ? SAPP_EVENTTYPE_RESTORED : SAPP_EVENTTYPE_ICONIFIED); +} + +void App::OnBackRequested(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Core::BackRequestedEventArgs const& args) { +    _SOKOL_UNUSED(sender); +    args.Handled(true); +} + +void App::OnKeyDown(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { +    auto status = args.KeyStatus(); +    _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_DOWN, sender, args); +} + +void App::OnKeyUp(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::KeyEventArgs const& args) { +    auto status = args.KeyStatus(); +    _sapp_uwp_key_event(SAPP_EVENTTYPE_KEY_UP, sender, args); +} + +void App::OnCharacterReceived(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args) { +    _sapp_uwp_char_event(args.KeyCode(), args.KeyStatus().WasKeyDown, sender); +} + +void App::OnPointerEntered(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { +    _SOKOL_UNUSED(args); +    _sapp.uwp.mouse_tracked = true; +    _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); +} + +void App::OnPointerExited(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { +    _SOKOL_UNUSED(args); +    _sapp.uwp.mouse_tracked = false; +    _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, sender); +} + +void App::OnPointerPressed(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { +    _sapp_uwp_extract_mouse_button_events(sender, args); +} + +// NOTE: for some reason this event handler is never called?? +void App::OnPointerReleased(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { +    _sapp_uwp_extract_mouse_button_events(sender, args); +} + +void App::OnPointerMoved(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { +    auto position = args.CurrentPoint().Position(); +    const float new_x = (float)(int)(position.X * _sapp.uwp.dpi.mouse_scale + 0.5f); +    const float new_y = (float)(int)(position.Y * _sapp.uwp.dpi.mouse_scale + 0.5f); +    // don't update dx/dy in the very first event +    if (_sapp.mouse.pos_valid) { +        _sapp.mouse.dx = new_x - _sapp.mouse.x; +        _sapp.mouse.dy = new_y - _sapp.mouse.y; +    } +    _sapp.mouse.x = new_x; +    _sapp.mouse.y = new_y; +    _sapp.mouse.pos_valid = true; +    if (!_sapp.uwp.mouse_tracked) { +        _sapp.uwp.mouse_tracked = true; +        _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, sender); +    } +    _sapp_uwp_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, sender); + +    // HACK for detecting multiple mouse button presses +    _sapp_uwp_extract_mouse_button_events(sender, args); +} + +void App::OnPointerWheelChanged(winrt::Windows::UI::Core::CoreWindow const& sender, winrt::Windows::UI::Core::PointerEventArgs const& args) { +    auto properties = args.CurrentPoint().Properties(); +    _sapp_uwp_scroll_event((float)properties.MouseWheelDelta(), properties.IsHorizontalMouseWheel(), sender); +} + +void App::OnDpiChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { +    // NOTE: UNTESTED +    _SOKOL_UNUSED(args); +    m_deviceResources->SetDpi(sender.LogicalDpi()); +    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +} + +void App::OnOrientationChanged(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { +    // NOTE: UNTESTED +    _SOKOL_UNUSED(args); +    m_deviceResources->SetCurrentOrientation(sender.CurrentOrientation()); +    _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_RESIZED); +} + +void App::OnDisplayContentsInvalidated(winrt::Windows::Graphics::Display::DisplayInformation const& sender, winrt::Windows::Foundation::IInspectable const& args) { +    // NOTE: UNTESTED +    _SOKOL_UNUSED(args); +    _SOKOL_UNUSED(sender); +    m_deviceResources->ValidateDevice(); +} + +} /* End empty namespace */ + +_SOKOL_PRIVATE void _sapp_uwp_run(const sapp_desc* desc) { +    _sapp_init_state(desc); +    _sapp_win32_uwp_init_keytable(); +    _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); +    winrt::Windows::ApplicationModel::Core::CoreApplication::Run(winrt::make<App>()); +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(UNICODE) +int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { +#else +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { +#endif +    _SOKOL_UNUSED(hInstance); +    _SOKOL_UNUSED(hPrevInstance); +    _SOKOL_UNUSED(lpCmdLine); +    _SOKOL_UNUSED(nCmdShow); +    sapp_desc desc = sokol_main(0, nullptr); +    _sapp_uwp_run(&desc); +    return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_UWP */ + +/*== Android ================================================================*/ +#if defined(_SAPP_ANDROID) + +/* android loop thread */ +_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { +    SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); +    SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); + +    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); +    if (display == EGL_NO_DISPLAY) { +        return false; +    } +    if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { +        return false; +    } + +    EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; +    const EGLint cfg_attributes[] = { +        EGL_SURFACE_TYPE, EGL_WINDOW_BIT, +        EGL_RED_SIZE, 8, +        EGL_GREEN_SIZE, 8, +        EGL_BLUE_SIZE, 8, +        EGL_ALPHA_SIZE, alpha_size, +        EGL_DEPTH_SIZE, 16, +        EGL_STENCIL_SIZE, 0, +        EGL_NONE, +    }; +    EGLConfig available_cfgs[32]; +    EGLint cfg_count; +    eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); +    SOKOL_ASSERT(cfg_count > 0); +    SOKOL_ASSERT(cfg_count <= 32); + +    /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ +    EGLConfig config; +    bool exact_cfg_found = false; +    for (int i = 0; i < cfg_count; ++i) { +        EGLConfig c = available_cfgs[i]; +        EGLint r, g, b, a, d; +        if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && +            eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && +            eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && +            eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && +            eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && +            r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { +            exact_cfg_found = true; +            config = c; +            break; +        } +    } +    if (!exact_cfg_found) { +        config = available_cfgs[0]; +    } + +    EGLint ctx_attributes[] = { +        #if defined(SOKOL_GLES3) +            EGL_CONTEXT_CLIENT_VERSION, _sapp.desc.gl_force_gles2 ? 2 : 3, +        #else +            EGL_CONTEXT_CLIENT_VERSION, 2, +        #endif +        EGL_NONE, +    }; +    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); +    if (context == EGL_NO_CONTEXT) { +        return false; +    } + +    _sapp.android.config = config; +    _sapp.android.display = display; +    _sapp.android.context = context; +    return true; +} + +_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"); +            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"); +            eglDestroyContext(_sapp.android.display, _sapp.android.context); +            _sapp.android.context = EGL_NO_CONTEXT; +        } +        SOKOL_LOG("Terminating egl display"); +        eglTerminate(_sapp.android.display); +        _sapp.android.display = EGL_NO_DISPLAY; +    } +} + +_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { +    SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); +    SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); +    SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); +    SOKOL_ASSERT(window); + +    /* TODO: set window flags */ +    /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ + +    /* create egl surface and make it current */ +    EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); +    if (surface == EGL_NO_SURFACE) { +        return false; +    } +    if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { +        return false; +    } +    _sapp.android.surface = surface; +    return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { +    if (_sapp.android.display == EGL_NO_DISPLAY) { +        return; +    } +    eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +    if (_sapp.android.surface != EGL_NO_SURFACE) { +        eglDestroySurface(_sapp.android.display, _sapp.android.surface); +        _sapp.android.surface = EGL_NO_SURFACE; +    } +} + +_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        SOKOL_LOG("event_cb()"); +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { +    SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); +    SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); +    SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); +    SOKOL_ASSERT(window); + +    const int32_t win_w = ANativeWindow_getWidth(window); +    const int32_t win_h = ANativeWindow_getHeight(window); +    SOKOL_ASSERT(win_w >= 0 && win_h >= 0); +    const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); +    _sapp.window_width = win_w; +    _sapp.window_height = win_h; +    if (win_changed || force_update) { +        if (!_sapp.desc.high_dpi) { +            const int32_t buf_w = win_w / 2; +            const int32_t buf_h = win_h / 2; +            EGLint format; +            EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); +            SOKOL_ASSERT(egl_result == EGL_TRUE); +            /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions +                as the ANativeWindow size results in weird display artefacts, that's +                why it's only called when the buffer geometry is different from +                the window size +            */ +            int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); +            SOKOL_ASSERT(result == 0); +        } +    } + +    /* query surface size */ +    EGLint fb_w, fb_h; +    EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); +    EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); +    SOKOL_ASSERT(egl_result_w == EGL_TRUE); +    SOKOL_ASSERT(egl_result_h == EGL_TRUE); +    const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); +    _sapp.framebuffer_width = fb_w; +    _sapp.framebuffer_height = fb_h; +    _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_android_app_event(SAPP_EVENTTYPE_RESIZED); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_android_cleanup(void) { +    SOKOL_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_call_cleanup(); +        } +    } +    /* always try to cleanup by destroying egl context */ +    _sapp_android_cleanup_egl(); +} + +_SOKOL_PRIVATE void _sapp_android_shutdown(void) { +    /* try to cleanup while we still have a surface and can call cleanup_cb() */ +    _sapp_android_cleanup(); +    /* request exit */ +    ANativeActivity_finish(_sapp.android.activity); +} + +_SOKOL_PRIVATE void _sapp_android_frame(void) { +    SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); +    SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); +    SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); +    _sapp_android_update_dimensions(_sapp.android.current.window, false); +    _sapp_frame(); +    eglSwapBuffers(_sapp.android.display, _sapp.android.surface); +} + +_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { +    if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { +        return false; +    } +    if (!_sapp_events_enabled()) { +        return false; +    } +    int32_t action_idx = AMotionEvent_getAction(e); +    int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; +    sapp_event_type type = SAPP_EVENTTYPE_INVALID; +    switch (action) { +        case AMOTION_EVENT_ACTION_DOWN: +            SOKOL_LOG("Touch: down"); +        case AMOTION_EVENT_ACTION_POINTER_DOWN: +            SOKOL_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"); +        case AMOTION_EVENT_ACTION_POINTER_UP: +            SOKOL_LOG("Touch: ptr up"); +            type = SAPP_EVENTTYPE_TOUCHES_ENDED; +            break; +        case AMOTION_EVENT_ACTION_CANCEL: +            SOKOL_LOG("Touch: cancel"); +            type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; +            break; +        default: +            break; +    } +    if (type == SAPP_EVENTTYPE_INVALID) { +        return false; +    } +    int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; +    _sapp_init_event(type); +    _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); +    if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { +        _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; +    } +    for (int32_t i = 0; i < _sapp.event.num_touches; i++) { +        sapp_touchpoint* dst = &_sapp.event.touches[i]; +        dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); +        dst->pos_x = (AMotionEvent_getRawX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; +        dst->pos_y = (AMotionEvent_getRawY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + +        if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || +            action == AMOTION_EVENT_ACTION_POINTER_UP) { +            dst->changed = (i == idx); +        } else { +            dst->changed = true; +        } +    } +    _sapp_call_event(&_sapp.event); +    return true; +} + +_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { +    if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { +        return false; +    } +    if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { +        /* FIXME: this should be hooked into a "really quit?" mechanism +           so the app can ask the user for confirmation, this is currently +           generally missing in sokol_app.h +        */ +        _sapp_android_shutdown(); +        return true; +    } +    return false; +} + +_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { +    if ((events & ALOOPER_EVENT_INPUT) == 0) { +        SOKOL_LOG("_sapp_android_input_cb() encountered unsupported event"); +        return 1; +    } +    SOKOL_ASSERT(_sapp.android.current.input); +    AInputEvent* event = NULL; +    while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { +        if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { +            continue; +        } +        int32_t handled = 0; +        if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { +            handled = 1; +        } +        AInputQueue_finishEvent(_sapp.android.current.input, event, handled); +    } +    return 1; +} + +_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { +    if ((events & ALOOPER_EVENT_INPUT) == 0) { +        SOKOL_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"); +        return 1; +    } + +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    switch (msg) { +        case _SOKOL_ANDROID_MSG_CREATE: +            { +                SOKOL_LOG("MSG_CREATE"); +                SOKOL_ASSERT(!_sapp.valid); +                bool result = _sapp_android_init_egl(); +                SOKOL_ASSERT(result); +                _sapp.valid = true; +                _sapp.android.has_created = true; +            } +            break; +        case _SOKOL_ANDROID_MSG_RESUME: +            SOKOL_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.android.has_resumed = false; +            _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); +            break; +        case _SOKOL_ANDROID_MSG_FOCUS: +            SOKOL_LOG("MSG_FOCUS"); +            _sapp.android.has_focus = true; +            break; +        case _SOKOL_ANDROID_MSG_NO_FOCUS: +            SOKOL_LOG("MSG_NO_FOCUS"); +            _sapp.android.has_focus = false; +            break; +        case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: +            SOKOL_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 ..."); +                    if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { +                        SOKOL_LOG("... ok!"); +                        _sapp_android_update_dimensions(_sapp.android.pending.window, true); +                    } else { +                        SOKOL_LOG("... failed!"); +                        _sapp_android_shutdown(); +                    } +                } +            } +            _sapp.android.current.window = _sapp.android.pending.window; +            break; +        case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: +            SOKOL_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); +                } +                if (_sapp.android.pending.input != NULL) { +                    AInputQueue_attachLooper( +                        _sapp.android.pending.input, +                        _sapp.android.looper, +                        ALOOPER_POLL_CALLBACK, +                        _sapp_android_input_cb, +                        NULL); /* data */ +                } +            } +            _sapp.android.current.input = _sapp.android.pending.input; +            break; +        case _SOKOL_ANDROID_MSG_DESTROY: +            SOKOL_LOG("MSG_DESTROY"); +            _sapp_android_cleanup(); +            _sapp.valid = false; +            _sapp.android.is_thread_stopping = true; +            break; +        default: +            SOKOL_LOG("Unknown msg type received"); +            break; +    } +    pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ +    pthread_mutex_unlock(&_sapp.android.pt.mutex); +    return 1; +} + +_SOKOL_PRIVATE bool _sapp_android_should_update(void) { +    bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; +    bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; +    return is_in_front && has_surface; +} + +_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"); +        ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); +    } else { +        SOKOL_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.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); +    ALooper_addFd(_sapp.android.looper, +        _sapp.android.pt.read_from_main_fd, +        ALOOPER_POLL_CALLBACK, +        ALOOPER_EVENT_INPUT, +        _sapp_android_main_cb, +        NULL); /* data */ + +    /* signal start to main thread */ +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    _sapp.android.is_thread_started = true; +    pthread_cond_broadcast(&_sapp.android.pt.cond); +    pthread_mutex_unlock(&_sapp.android.pt.mutex); + +    /* main loop */ +    while (!_sapp.android.is_thread_stopping) { +        /* sokol frame */ +        if (_sapp_android_should_update()) { +            _sapp_android_frame(); +        } + +        /* process all events (or stop early if app is requested to quit) */ +        bool process_events = true; +        while (process_events && !_sapp.android.is_thread_stopping) { +            bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); +            process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; +        } +    } + +    /* cleanup thread */ +    if (_sapp.android.current.input != NULL) { +        AInputQueue_detachLooper(_sapp.android.current.input); +    } + +    /* the following causes heap corruption on exit, why?? +    ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); +    ALooper_release(_sapp.android.looper);*/ + +    /* signal "destroyed" */ +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    _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"); +    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"); +    } +} + +_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { +    SOKOL_LOG("NativeActivity onStart()"); +} + +_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { +    SOKOL_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_LOG("NativeActivity onSaveInstanceState()"); +    *out_size = 0; +    return NULL; +} + +_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { +    SOKOL_LOG("NativeActivity onWindowFocusChanged()"); +    if (has_focus) { +        _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); +    } else { +        _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); +    } +} + +_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { +    SOKOL_LOG("NativeActivity onPause()"); +    _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); +} + +_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { +    SOKOL_LOG("NativeActivity onStop()"); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    _sapp.android.pending.window = window; +    _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); +    while (_sapp.android.current.window != window) { +        pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); +    } +    pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { +    SOKOL_LOG("NativeActivity onNativeWindowCreated()"); +    _sapp_android_msg_set_native_window(window); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { +    SOKOL_LOG("NativeActivity onNativeWindowDestroyed()"); +    _sapp_android_msg_set_native_window(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    _sapp.android.pending.input = input; +    _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); +    while (_sapp.android.current.input != input) { +        pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); +    } +    pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { +    SOKOL_LOG("NativeActivity onInputQueueCreated()"); +    _sapp_android_msg_set_input_queue(queue); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { +    SOKOL_LOG("NativeActivity onInputQueueDestroyed()"); +    _sapp_android_msg_set_input_queue(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { +    SOKOL_LOG("NativeActivity onConfigurationChanged()"); +    /* see android:configChanges in manifest */ +} + +_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { +    SOKOL_LOG("NativeActivity onLowMemory()"); +} + +_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { +    /* +     * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) +     * on my device (Moto X 2nd gen) when the app is removed from the task view +     * (TaskStackView: onTaskViewDismissed). +     * +     * However, if ANativeActivity_finish() is explicitly called from for example +     * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? +     */ +    SOKOL_LOG("NativeActivity onDestroy()"); + +    /* send destroy msg */ +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); +    while (!_sapp.android.is_thread_stopped) { +        pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); +    } +    pthread_mutex_unlock(&_sapp.android.pt.mutex); + +    /* clean up main thread */ +    pthread_cond_destroy(&_sapp.android.pt.cond); +    pthread_mutex_destroy(&_sapp.android.pt.mutex); + +    close(_sapp.android.pt.read_from_main_fd); +    close(_sapp.android.pt.write_from_main_fd); + +    SOKOL_LOG("NativeActivity done"); + +    /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ +    exit(0); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { +    SOKOL_LOG("NativeActivity onCreate()"); + +    sapp_desc desc = sokol_main(0, NULL); +    _sapp_init_state(&desc); + +    /* start loop thread */ +    _sapp.android.activity = activity; + +    int pipe_fd[2]; +    if (pipe(pipe_fd) != 0) { +        SOKOL_LOG("Could not create thread pipe"); +        return; +    } +    _sapp.android.pt.read_from_main_fd = pipe_fd[0]; +    _sapp.android.pt.write_from_main_fd = pipe_fd[1]; + +    pthread_mutex_init(&_sapp.android.pt.mutex, NULL); +    pthread_cond_init(&_sapp.android.pt.cond, NULL); + +    pthread_attr_t attr; +    pthread_attr_init(&attr); +    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); +    pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); +    pthread_attr_destroy(&attr); + +    /* wait until main loop has started */ +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    while (!_sapp.android.is_thread_started) { +        pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); +    } +    pthread_mutex_unlock(&_sapp.android.pt.mutex); + +    /* send create msg */ +    pthread_mutex_lock(&_sapp.android.pt.mutex); +    _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); +    while (!_sapp.android.has_created) { +        pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); +    } +    pthread_mutex_unlock(&_sapp.android.pt.mutex); + +    /* register for callbacks */ +    activity->callbacks->onStart = _sapp_android_on_start; +    activity->callbacks->onResume = _sapp_android_on_resume; +    activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; +    activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; +    activity->callbacks->onPause = _sapp_android_on_pause; +    activity->callbacks->onStop = _sapp_android_on_stop; +    activity->callbacks->onDestroy = _sapp_android_on_destroy; +    activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; +    /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ +    /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ +    activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; +    activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; +    activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; +    /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ +    activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; +    activity->callbacks->onLowMemory = _sapp_android_on_low_memory; + +    SOKOL_LOG("NativeActivity successfully created"); + +    /* NOT A BUG: do NOT call sapp_discard_state() */ +} + +#endif /* _SAPP_ANDROID */ + +/*== LINUX ==================================================================*/ +#if defined(_SAPP_LINUX) + +/* see GLFW's xkb_unicode.c */ +static const struct _sapp_x11_codepair { +  uint16_t keysym; +  uint16_t ucs; +} _sapp_x11_keysymtab[] = { +  { 0x01a1, 0x0104 }, +  { 0x01a2, 0x02d8 }, +  { 0x01a3, 0x0141 }, +  { 0x01a5, 0x013d }, +  { 0x01a6, 0x015a }, +  { 0x01a9, 0x0160 }, +  { 0x01aa, 0x015e }, +  { 0x01ab, 0x0164 }, +  { 0x01ac, 0x0179 }, +  { 0x01ae, 0x017d }, +  { 0x01af, 0x017b }, +  { 0x01b1, 0x0105 }, +  { 0x01b2, 0x02db }, +  { 0x01b3, 0x0142 }, +  { 0x01b5, 0x013e }, +  { 0x01b6, 0x015b }, +  { 0x01b7, 0x02c7 }, +  { 0x01b9, 0x0161 }, +  { 0x01ba, 0x015f }, +  { 0x01bb, 0x0165 }, +  { 0x01bc, 0x017a }, +  { 0x01bd, 0x02dd }, +  { 0x01be, 0x017e }, +  { 0x01bf, 0x017c }, +  { 0x01c0, 0x0154 }, +  { 0x01c3, 0x0102 }, +  { 0x01c5, 0x0139 }, +  { 0x01c6, 0x0106 }, +  { 0x01c8, 0x010c }, +  { 0x01ca, 0x0118 }, +  { 0x01cc, 0x011a }, +  { 0x01cf, 0x010e }, +  { 0x01d0, 0x0110 }, +  { 0x01d1, 0x0143 }, +  { 0x01d2, 0x0147 }, +  { 0x01d5, 0x0150 }, +  { 0x01d8, 0x0158 }, +  { 0x01d9, 0x016e }, +  { 0x01db, 0x0170 }, +  { 0x01de, 0x0162 }, +  { 0x01e0, 0x0155 }, +  { 0x01e3, 0x0103 }, +  { 0x01e5, 0x013a }, +  { 0x01e6, 0x0107 }, +  { 0x01e8, 0x010d }, +  { 0x01ea, 0x0119 }, +  { 0x01ec, 0x011b }, +  { 0x01ef, 0x010f }, +  { 0x01f0, 0x0111 }, +  { 0x01f1, 0x0144 }, +  { 0x01f2, 0x0148 }, +  { 0x01f5, 0x0151 }, +  { 0x01f8, 0x0159 }, +  { 0x01f9, 0x016f }, +  { 0x01fb, 0x0171 }, +  { 0x01fe, 0x0163 }, +  { 0x01ff, 0x02d9 }, +  { 0x02a1, 0x0126 }, +  { 0x02a6, 0x0124 }, +  { 0x02a9, 0x0130 }, +  { 0x02ab, 0x011e }, +  { 0x02ac, 0x0134 }, +  { 0x02b1, 0x0127 }, +  { 0x02b6, 0x0125 }, +  { 0x02b9, 0x0131 }, +  { 0x02bb, 0x011f }, +  { 0x02bc, 0x0135 }, +  { 0x02c5, 0x010a }, +  { 0x02c6, 0x0108 }, +  { 0x02d5, 0x0120 }, +  { 0x02d8, 0x011c }, +  { 0x02dd, 0x016c }, +  { 0x02de, 0x015c }, +  { 0x02e5, 0x010b }, +  { 0x02e6, 0x0109 }, +  { 0x02f5, 0x0121 }, +  { 0x02f8, 0x011d }, +  { 0x02fd, 0x016d }, +  { 0x02fe, 0x015d }, +  { 0x03a2, 0x0138 }, +  { 0x03a3, 0x0156 }, +  { 0x03a5, 0x0128 }, +  { 0x03a6, 0x013b }, +  { 0x03aa, 0x0112 }, +  { 0x03ab, 0x0122 }, +  { 0x03ac, 0x0166 }, +  { 0x03b3, 0x0157 }, +  { 0x03b5, 0x0129 }, +  { 0x03b6, 0x013c }, +  { 0x03ba, 0x0113 }, +  { 0x03bb, 0x0123 }, +  { 0x03bc, 0x0167 }, +  { 0x03bd, 0x014a }, +  { 0x03bf, 0x014b }, +  { 0x03c0, 0x0100 }, +  { 0x03c7, 0x012e }, +  { 0x03cc, 0x0116 }, +  { 0x03cf, 0x012a }, +  { 0x03d1, 0x0145 }, +  { 0x03d2, 0x014c }, +  { 0x03d3, 0x0136 }, +  { 0x03d9, 0x0172 }, +  { 0x03dd, 0x0168 }, +  { 0x03de, 0x016a }, +  { 0x03e0, 0x0101 }, +  { 0x03e7, 0x012f }, +  { 0x03ec, 0x0117 }, +  { 0x03ef, 0x012b }, +  { 0x03f1, 0x0146 }, +  { 0x03f2, 0x014d }, +  { 0x03f3, 0x0137 }, +  { 0x03f9, 0x0173 }, +  { 0x03fd, 0x0169 }, +  { 0x03fe, 0x016b }, +  { 0x047e, 0x203e }, +  { 0x04a1, 0x3002 }, +  { 0x04a2, 0x300c }, +  { 0x04a3, 0x300d }, +  { 0x04a4, 0x3001 }, +  { 0x04a5, 0x30fb }, +  { 0x04a6, 0x30f2 }, +  { 0x04a7, 0x30a1 }, +  { 0x04a8, 0x30a3 }, +  { 0x04a9, 0x30a5 }, +  { 0x04aa, 0x30a7 }, +  { 0x04ab, 0x30a9 }, +  { 0x04ac, 0x30e3 }, +  { 0x04ad, 0x30e5 }, +  { 0x04ae, 0x30e7 }, +  { 0x04af, 0x30c3 }, +  { 0x04b0, 0x30fc }, +  { 0x04b1, 0x30a2 }, +  { 0x04b2, 0x30a4 }, +  { 0x04b3, 0x30a6 }, +  { 0x04b4, 0x30a8 }, +  { 0x04b5, 0x30aa }, +  { 0x04b6, 0x30ab }, +  { 0x04b7, 0x30ad }, +  { 0x04b8, 0x30af }, +  { 0x04b9, 0x30b1 }, +  { 0x04ba, 0x30b3 }, +  { 0x04bb, 0x30b5 }, +  { 0x04bc, 0x30b7 }, +  { 0x04bd, 0x30b9 }, +  { 0x04be, 0x30bb }, +  { 0x04bf, 0x30bd }, +  { 0x04c0, 0x30bf }, +  { 0x04c1, 0x30c1 }, +  { 0x04c2, 0x30c4 }, +  { 0x04c3, 0x30c6 }, +  { 0x04c4, 0x30c8 }, +  { 0x04c5, 0x30ca }, +  { 0x04c6, 0x30cb }, +  { 0x04c7, 0x30cc }, +  { 0x04c8, 0x30cd }, +  { 0x04c9, 0x30ce }, +  { 0x04ca, 0x30cf }, +  { 0x04cb, 0x30d2 }, +  { 0x04cc, 0x30d5 }, +  { 0x04cd, 0x30d8 }, +  { 0x04ce, 0x30db }, +  { 0x04cf, 0x30de }, +  { 0x04d0, 0x30df }, +  { 0x04d1, 0x30e0 }, +  { 0x04d2, 0x30e1 }, +  { 0x04d3, 0x30e2 }, +  { 0x04d4, 0x30e4 }, +  { 0x04d5, 0x30e6 }, +  { 0x04d6, 0x30e8 }, +  { 0x04d7, 0x30e9 }, +  { 0x04d8, 0x30ea }, +  { 0x04d9, 0x30eb }, +  { 0x04da, 0x30ec }, +  { 0x04db, 0x30ed }, +  { 0x04dc, 0x30ef }, +  { 0x04dd, 0x30f3 }, +  { 0x04de, 0x309b }, +  { 0x04df, 0x309c }, +  { 0x05ac, 0x060c }, +  { 0x05bb, 0x061b }, +  { 0x05bf, 0x061f }, +  { 0x05c1, 0x0621 }, +  { 0x05c2, 0x0622 }, +  { 0x05c3, 0x0623 }, +  { 0x05c4, 0x0624 }, +  { 0x05c5, 0x0625 }, +  { 0x05c6, 0x0626 }, +  { 0x05c7, 0x0627 }, +  { 0x05c8, 0x0628 }, +  { 0x05c9, 0x0629 }, +  { 0x05ca, 0x062a }, +  { 0x05cb, 0x062b }, +  { 0x05cc, 0x062c }, +  { 0x05cd, 0x062d }, +  { 0x05ce, 0x062e }, +  { 0x05cf, 0x062f }, +  { 0x05d0, 0x0630 }, +  { 0x05d1, 0x0631 }, +  { 0x05d2, 0x0632 }, +  { 0x05d3, 0x0633 }, +  { 0x05d4, 0x0634 }, +  { 0x05d5, 0x0635 }, +  { 0x05d6, 0x0636 }, +  { 0x05d7, 0x0637 }, +  { 0x05d8, 0x0638 }, +  { 0x05d9, 0x0639 }, +  { 0x05da, 0x063a }, +  { 0x05e0, 0x0640 }, +  { 0x05e1, 0x0641 }, +  { 0x05e2, 0x0642 }, +  { 0x05e3, 0x0643 }, +  { 0x05e4, 0x0644 }, +  { 0x05e5, 0x0645 }, +  { 0x05e6, 0x0646 }, +  { 0x05e7, 0x0647 }, +  { 0x05e8, 0x0648 }, +  { 0x05e9, 0x0649 }, +  { 0x05ea, 0x064a }, +  { 0x05eb, 0x064b }, +  { 0x05ec, 0x064c }, +  { 0x05ed, 0x064d }, +  { 0x05ee, 0x064e }, +  { 0x05ef, 0x064f }, +  { 0x05f0, 0x0650 }, +  { 0x05f1, 0x0651 }, +  { 0x05f2, 0x0652 }, +  { 0x06a1, 0x0452 }, +  { 0x06a2, 0x0453 }, +  { 0x06a3, 0x0451 }, +  { 0x06a4, 0x0454 }, +  { 0x06a5, 0x0455 }, +  { 0x06a6, 0x0456 }, +  { 0x06a7, 0x0457 }, +  { 0x06a8, 0x0458 }, +  { 0x06a9, 0x0459 }, +  { 0x06aa, 0x045a }, +  { 0x06ab, 0x045b }, +  { 0x06ac, 0x045c }, +  { 0x06ae, 0x045e }, +  { 0x06af, 0x045f }, +  { 0x06b0, 0x2116 }, +  { 0x06b1, 0x0402 }, +  { 0x06b2, 0x0403 }, +  { 0x06b3, 0x0401 }, +  { 0x06b4, 0x0404 }, +  { 0x06b5, 0x0405 }, +  { 0x06b6, 0x0406 }, +  { 0x06b7, 0x0407 }, +  { 0x06b8, 0x0408 }, +  { 0x06b9, 0x0409 }, +  { 0x06ba, 0x040a }, +  { 0x06bb, 0x040b }, +  { 0x06bc, 0x040c }, +  { 0x06be, 0x040e }, +  { 0x06bf, 0x040f }, +  { 0x06c0, 0x044e }, +  { 0x06c1, 0x0430 }, +  { 0x06c2, 0x0431 }, +  { 0x06c3, 0x0446 }, +  { 0x06c4, 0x0434 }, +  { 0x06c5, 0x0435 }, +  { 0x06c6, 0x0444 }, +  { 0x06c7, 0x0433 }, +  { 0x06c8, 0x0445 }, +  { 0x06c9, 0x0438 }, +  { 0x06ca, 0x0439 }, +  { 0x06cb, 0x043a }, +  { 0x06cc, 0x043b }, +  { 0x06cd, 0x043c }, +  { 0x06ce, 0x043d }, +  { 0x06cf, 0x043e }, +  { 0x06d0, 0x043f }, +  { 0x06d1, 0x044f }, +  { 0x06d2, 0x0440 }, +  { 0x06d3, 0x0441 }, +  { 0x06d4, 0x0442 }, +  { 0x06d5, 0x0443 }, +  { 0x06d6, 0x0436 }, +  { 0x06d7, 0x0432 }, +  { 0x06d8, 0x044c }, +  { 0x06d9, 0x044b }, +  { 0x06da, 0x0437 }, +  { 0x06db, 0x0448 }, +  { 0x06dc, 0x044d }, +  { 0x06dd, 0x0449 }, +  { 0x06de, 0x0447 }, +  { 0x06df, 0x044a }, +  { 0x06e0, 0x042e }, +  { 0x06e1, 0x0410 }, +  { 0x06e2, 0x0411 }, +  { 0x06e3, 0x0426 }, +  { 0x06e4, 0x0414 }, +  { 0x06e5, 0x0415 }, +  { 0x06e6, 0x0424 }, +  { 0x06e7, 0x0413 }, +  { 0x06e8, 0x0425 }, +  { 0x06e9, 0x0418 }, +  { 0x06ea, 0x0419 }, +  { 0x06eb, 0x041a }, +  { 0x06ec, 0x041b }, +  { 0x06ed, 0x041c }, +  { 0x06ee, 0x041d }, +  { 0x06ef, 0x041e }, +  { 0x06f0, 0x041f }, +  { 0x06f1, 0x042f }, +  { 0x06f2, 0x0420 }, +  { 0x06f3, 0x0421 }, +  { 0x06f4, 0x0422 }, +  { 0x06f5, 0x0423 }, +  { 0x06f6, 0x0416 }, +  { 0x06f7, 0x0412 }, +  { 0x06f8, 0x042c }, +  { 0x06f9, 0x042b }, +  { 0x06fa, 0x0417 }, +  { 0x06fb, 0x0428 }, +  { 0x06fc, 0x042d }, +  { 0x06fd, 0x0429 }, +  { 0x06fe, 0x0427 }, +  { 0x06ff, 0x042a }, +  { 0x07a1, 0x0386 }, +  { 0x07a2, 0x0388 }, +  { 0x07a3, 0x0389 }, +  { 0x07a4, 0x038a }, +  { 0x07a5, 0x03aa }, +  { 0x07a7, 0x038c }, +  { 0x07a8, 0x038e }, +  { 0x07a9, 0x03ab }, +  { 0x07ab, 0x038f }, +  { 0x07ae, 0x0385 }, +  { 0x07af, 0x2015 }, +  { 0x07b1, 0x03ac }, +  { 0x07b2, 0x03ad }, +  { 0x07b3, 0x03ae }, +  { 0x07b4, 0x03af }, +  { 0x07b5, 0x03ca }, +  { 0x07b6, 0x0390 }, +  { 0x07b7, 0x03cc }, +  { 0x07b8, 0x03cd }, +  { 0x07b9, 0x03cb }, +  { 0x07ba, 0x03b0 }, +  { 0x07bb, 0x03ce }, +  { 0x07c1, 0x0391 }, +  { 0x07c2, 0x0392 }, +  { 0x07c3, 0x0393 }, +  { 0x07c4, 0x0394 }, +  { 0x07c5, 0x0395 }, +  { 0x07c6, 0x0396 }, +  { 0x07c7, 0x0397 }, +  { 0x07c8, 0x0398 }, +  { 0x07c9, 0x0399 }, +  { 0x07ca, 0x039a }, +  { 0x07cb, 0x039b }, +  { 0x07cc, 0x039c }, +  { 0x07cd, 0x039d }, +  { 0x07ce, 0x039e }, +  { 0x07cf, 0x039f }, +  { 0x07d0, 0x03a0 }, +  { 0x07d1, 0x03a1 }, +  { 0x07d2, 0x03a3 }, +  { 0x07d4, 0x03a4 }, +  { 0x07d5, 0x03a5 }, +  { 0x07d6, 0x03a6 }, +  { 0x07d7, 0x03a7 }, +  { 0x07d8, 0x03a8 }, +  { 0x07d9, 0x03a9 }, +  { 0x07e1, 0x03b1 }, +  { 0x07e2, 0x03b2 }, +  { 0x07e3, 0x03b3 }, +  { 0x07e4, 0x03b4 }, +  { 0x07e5, 0x03b5 }, +  { 0x07e6, 0x03b6 }, +  { 0x07e7, 0x03b7 }, +  { 0x07e8, 0x03b8 }, +  { 0x07e9, 0x03b9 }, +  { 0x07ea, 0x03ba }, +  { 0x07eb, 0x03bb }, +  { 0x07ec, 0x03bc }, +  { 0x07ed, 0x03bd }, +  { 0x07ee, 0x03be }, +  { 0x07ef, 0x03bf }, +  { 0x07f0, 0x03c0 }, +  { 0x07f1, 0x03c1 }, +  { 0x07f2, 0x03c3 }, +  { 0x07f3, 0x03c2 }, +  { 0x07f4, 0x03c4 }, +  { 0x07f5, 0x03c5 }, +  { 0x07f6, 0x03c6 }, +  { 0x07f7, 0x03c7 }, +  { 0x07f8, 0x03c8 }, +  { 0x07f9, 0x03c9 }, +  { 0x08a1, 0x23b7 }, +  { 0x08a2, 0x250c }, +  { 0x08a3, 0x2500 }, +  { 0x08a4, 0x2320 }, +  { 0x08a5, 0x2321 }, +  { 0x08a6, 0x2502 }, +  { 0x08a7, 0x23a1 }, +  { 0x08a8, 0x23a3 }, +  { 0x08a9, 0x23a4 }, +  { 0x08aa, 0x23a6 }, +  { 0x08ab, 0x239b }, +  { 0x08ac, 0x239d }, +  { 0x08ad, 0x239e }, +  { 0x08ae, 0x23a0 }, +  { 0x08af, 0x23a8 }, +  { 0x08b0, 0x23ac }, +  { 0x08bc, 0x2264 }, +  { 0x08bd, 0x2260 }, +  { 0x08be, 0x2265 }, +  { 0x08bf, 0x222b }, +  { 0x08c0, 0x2234 }, +  { 0x08c1, 0x221d }, +  { 0x08c2, 0x221e }, +  { 0x08c5, 0x2207 }, +  { 0x08c8, 0x223c }, +  { 0x08c9, 0x2243 }, +  { 0x08cd, 0x21d4 }, +  { 0x08ce, 0x21d2 }, +  { 0x08cf, 0x2261 }, +  { 0x08d6, 0x221a }, +  { 0x08da, 0x2282 }, +  { 0x08db, 0x2283 }, +  { 0x08dc, 0x2229 }, +  { 0x08dd, 0x222a }, +  { 0x08de, 0x2227 }, +  { 0x08df, 0x2228 }, +  { 0x08ef, 0x2202 }, +  { 0x08f6, 0x0192 }, +  { 0x08fb, 0x2190 }, +  { 0x08fc, 0x2191 }, +  { 0x08fd, 0x2192 }, +  { 0x08fe, 0x2193 }, +  { 0x09e0, 0x25c6 }, +  { 0x09e1, 0x2592 }, +  { 0x09e2, 0x2409 }, +  { 0x09e3, 0x240c }, +  { 0x09e4, 0x240d }, +  { 0x09e5, 0x240a }, +  { 0x09e8, 0x2424 }, +  { 0x09e9, 0x240b }, +  { 0x09ea, 0x2518 }, +  { 0x09eb, 0x2510 }, +  { 0x09ec, 0x250c }, +  { 0x09ed, 0x2514 }, +  { 0x09ee, 0x253c }, +  { 0x09ef, 0x23ba }, +  { 0x09f0, 0x23bb }, +  { 0x09f1, 0x2500 }, +  { 0x09f2, 0x23bc }, +  { 0x09f3, 0x23bd }, +  { 0x09f4, 0x251c }, +  { 0x09f5, 0x2524 }, +  { 0x09f6, 0x2534 }, +  { 0x09f7, 0x252c }, +  { 0x09f8, 0x2502 }, +  { 0x0aa1, 0x2003 }, +  { 0x0aa2, 0x2002 }, +  { 0x0aa3, 0x2004 }, +  { 0x0aa4, 0x2005 }, +  { 0x0aa5, 0x2007 }, +  { 0x0aa6, 0x2008 }, +  { 0x0aa7, 0x2009 }, +  { 0x0aa8, 0x200a }, +  { 0x0aa9, 0x2014 }, +  { 0x0aaa, 0x2013 }, +  { 0x0aae, 0x2026 }, +  { 0x0aaf, 0x2025 }, +  { 0x0ab0, 0x2153 }, +  { 0x0ab1, 0x2154 }, +  { 0x0ab2, 0x2155 }, +  { 0x0ab3, 0x2156 }, +  { 0x0ab4, 0x2157 }, +  { 0x0ab5, 0x2158 }, +  { 0x0ab6, 0x2159 }, +  { 0x0ab7, 0x215a }, +  { 0x0ab8, 0x2105 }, +  { 0x0abb, 0x2012 }, +  { 0x0abc, 0x2329 }, +  { 0x0abe, 0x232a }, +  { 0x0ac3, 0x215b }, +  { 0x0ac4, 0x215c }, +  { 0x0ac5, 0x215d }, +  { 0x0ac6, 0x215e }, +  { 0x0ac9, 0x2122 }, +  { 0x0aca, 0x2613 }, +  { 0x0acc, 0x25c1 }, +  { 0x0acd, 0x25b7 }, +  { 0x0ace, 0x25cb }, +  { 0x0acf, 0x25af }, +  { 0x0ad0, 0x2018 }, +  { 0x0ad1, 0x2019 }, +  { 0x0ad2, 0x201c }, +  { 0x0ad3, 0x201d }, +  { 0x0ad4, 0x211e }, +  { 0x0ad6, 0x2032 }, +  { 0x0ad7, 0x2033 }, +  { 0x0ad9, 0x271d }, +  { 0x0adb, 0x25ac }, +  { 0x0adc, 0x25c0 }, +  { 0x0add, 0x25b6 }, +  { 0x0ade, 0x25cf }, +  { 0x0adf, 0x25ae }, +  { 0x0ae0, 0x25e6 }, +  { 0x0ae1, 0x25ab }, +  { 0x0ae2, 0x25ad }, +  { 0x0ae3, 0x25b3 }, +  { 0x0ae4, 0x25bd }, +  { 0x0ae5, 0x2606 }, +  { 0x0ae6, 0x2022 }, +  { 0x0ae7, 0x25aa }, +  { 0x0ae8, 0x25b2 }, +  { 0x0ae9, 0x25bc }, +  { 0x0aea, 0x261c }, +  { 0x0aeb, 0x261e }, +  { 0x0aec, 0x2663 }, +  { 0x0aed, 0x2666 }, +  { 0x0aee, 0x2665 }, +  { 0x0af0, 0x2720 }, +  { 0x0af1, 0x2020 }, +  { 0x0af2, 0x2021 }, +  { 0x0af3, 0x2713 }, +  { 0x0af4, 0x2717 }, +  { 0x0af5, 0x266f }, +  { 0x0af6, 0x266d }, +  { 0x0af7, 0x2642 }, +  { 0x0af8, 0x2640 }, +  { 0x0af9, 0x260e }, +  { 0x0afa, 0x2315 }, +  { 0x0afb, 0x2117 }, +  { 0x0afc, 0x2038 }, +  { 0x0afd, 0x201a }, +  { 0x0afe, 0x201e }, +  { 0x0ba3, 0x003c }, +  { 0x0ba6, 0x003e }, +  { 0x0ba8, 0x2228 }, +  { 0x0ba9, 0x2227 }, +  { 0x0bc0, 0x00af }, +  { 0x0bc2, 0x22a5 }, +  { 0x0bc3, 0x2229 }, +  { 0x0bc4, 0x230a }, +  { 0x0bc6, 0x005f }, +  { 0x0bca, 0x2218 }, +  { 0x0bcc, 0x2395 }, +  { 0x0bce, 0x22a4 }, +  { 0x0bcf, 0x25cb }, +  { 0x0bd3, 0x2308 }, +  { 0x0bd6, 0x222a }, +  { 0x0bd8, 0x2283 }, +  { 0x0bda, 0x2282 }, +  { 0x0bdc, 0x22a2 }, +  { 0x0bfc, 0x22a3 }, +  { 0x0cdf, 0x2017 }, +  { 0x0ce0, 0x05d0 }, +  { 0x0ce1, 0x05d1 }, +  { 0x0ce2, 0x05d2 }, +  { 0x0ce3, 0x05d3 }, +  { 0x0ce4, 0x05d4 }, +  { 0x0ce5, 0x05d5 }, +  { 0x0ce6, 0x05d6 }, +  { 0x0ce7, 0x05d7 }, +  { 0x0ce8, 0x05d8 }, +  { 0x0ce9, 0x05d9 }, +  { 0x0cea, 0x05da }, +  { 0x0ceb, 0x05db }, +  { 0x0cec, 0x05dc }, +  { 0x0ced, 0x05dd }, +  { 0x0cee, 0x05de }, +  { 0x0cef, 0x05df }, +  { 0x0cf0, 0x05e0 }, +  { 0x0cf1, 0x05e1 }, +  { 0x0cf2, 0x05e2 }, +  { 0x0cf3, 0x05e3 }, +  { 0x0cf4, 0x05e4 }, +  { 0x0cf5, 0x05e5 }, +  { 0x0cf6, 0x05e6 }, +  { 0x0cf7, 0x05e7 }, +  { 0x0cf8, 0x05e8 }, +  { 0x0cf9, 0x05e9 }, +  { 0x0cfa, 0x05ea }, +  { 0x0da1, 0x0e01 }, +  { 0x0da2, 0x0e02 }, +  { 0x0da3, 0x0e03 }, +  { 0x0da4, 0x0e04 }, +  { 0x0da5, 0x0e05 }, +  { 0x0da6, 0x0e06 }, +  { 0x0da7, 0x0e07 }, +  { 0x0da8, 0x0e08 }, +  { 0x0da9, 0x0e09 }, +  { 0x0daa, 0x0e0a }, +  { 0x0dab, 0x0e0b }, +  { 0x0dac, 0x0e0c }, +  { 0x0dad, 0x0e0d }, +  { 0x0dae, 0x0e0e }, +  { 0x0daf, 0x0e0f }, +  { 0x0db0, 0x0e10 }, +  { 0x0db1, 0x0e11 }, +  { 0x0db2, 0x0e12 }, +  { 0x0db3, 0x0e13 }, +  { 0x0db4, 0x0e14 }, +  { 0x0db5, 0x0e15 }, +  { 0x0db6, 0x0e16 }, +  { 0x0db7, 0x0e17 }, +  { 0x0db8, 0x0e18 }, +  { 0x0db9, 0x0e19 }, +  { 0x0dba, 0x0e1a }, +  { 0x0dbb, 0x0e1b }, +  { 0x0dbc, 0x0e1c }, +  { 0x0dbd, 0x0e1d }, +  { 0x0dbe, 0x0e1e }, +  { 0x0dbf, 0x0e1f }, +  { 0x0dc0, 0x0e20 }, +  { 0x0dc1, 0x0e21 }, +  { 0x0dc2, 0x0e22 }, +  { 0x0dc3, 0x0e23 }, +  { 0x0dc4, 0x0e24 }, +  { 0x0dc5, 0x0e25 }, +  { 0x0dc6, 0x0e26 }, +  { 0x0dc7, 0x0e27 }, +  { 0x0dc8, 0x0e28 }, +  { 0x0dc9, 0x0e29 }, +  { 0x0dca, 0x0e2a }, +  { 0x0dcb, 0x0e2b }, +  { 0x0dcc, 0x0e2c }, +  { 0x0dcd, 0x0e2d }, +  { 0x0dce, 0x0e2e }, +  { 0x0dcf, 0x0e2f }, +  { 0x0dd0, 0x0e30 }, +  { 0x0dd1, 0x0e31 }, +  { 0x0dd2, 0x0e32 }, +  { 0x0dd3, 0x0e33 }, +  { 0x0dd4, 0x0e34 }, +  { 0x0dd5, 0x0e35 }, +  { 0x0dd6, 0x0e36 }, +  { 0x0dd7, 0x0e37 }, +  { 0x0dd8, 0x0e38 }, +  { 0x0dd9, 0x0e39 }, +  { 0x0dda, 0x0e3a }, +  { 0x0ddf, 0x0e3f }, +  { 0x0de0, 0x0e40 }, +  { 0x0de1, 0x0e41 }, +  { 0x0de2, 0x0e42 }, +  { 0x0de3, 0x0e43 }, +  { 0x0de4, 0x0e44 }, +  { 0x0de5, 0x0e45 }, +  { 0x0de6, 0x0e46 }, +  { 0x0de7, 0x0e47 }, +  { 0x0de8, 0x0e48 }, +  { 0x0de9, 0x0e49 }, +  { 0x0dea, 0x0e4a }, +  { 0x0deb, 0x0e4b }, +  { 0x0dec, 0x0e4c }, +  { 0x0ded, 0x0e4d }, +  { 0x0df0, 0x0e50 }, +  { 0x0df1, 0x0e51 }, +  { 0x0df2, 0x0e52 }, +  { 0x0df3, 0x0e53 }, +  { 0x0df4, 0x0e54 }, +  { 0x0df5, 0x0e55 }, +  { 0x0df6, 0x0e56 }, +  { 0x0df7, 0x0e57 }, +  { 0x0df8, 0x0e58 }, +  { 0x0df9, 0x0e59 }, +  { 0x0ea1, 0x3131 }, +  { 0x0ea2, 0x3132 }, +  { 0x0ea3, 0x3133 }, +  { 0x0ea4, 0x3134 }, +  { 0x0ea5, 0x3135 }, +  { 0x0ea6, 0x3136 }, +  { 0x0ea7, 0x3137 }, +  { 0x0ea8, 0x3138 }, +  { 0x0ea9, 0x3139 }, +  { 0x0eaa, 0x313a }, +  { 0x0eab, 0x313b }, +  { 0x0eac, 0x313c }, +  { 0x0ead, 0x313d }, +  { 0x0eae, 0x313e }, +  { 0x0eaf, 0x313f }, +  { 0x0eb0, 0x3140 }, +  { 0x0eb1, 0x3141 }, +  { 0x0eb2, 0x3142 }, +  { 0x0eb3, 0x3143 }, +  { 0x0eb4, 0x3144 }, +  { 0x0eb5, 0x3145 }, +  { 0x0eb6, 0x3146 }, +  { 0x0eb7, 0x3147 }, +  { 0x0eb8, 0x3148 }, +  { 0x0eb9, 0x3149 }, +  { 0x0eba, 0x314a }, +  { 0x0ebb, 0x314b }, +  { 0x0ebc, 0x314c }, +  { 0x0ebd, 0x314d }, +  { 0x0ebe, 0x314e }, +  { 0x0ebf, 0x314f }, +  { 0x0ec0, 0x3150 }, +  { 0x0ec1, 0x3151 }, +  { 0x0ec2, 0x3152 }, +  { 0x0ec3, 0x3153 }, +  { 0x0ec4, 0x3154 }, +  { 0x0ec5, 0x3155 }, +  { 0x0ec6, 0x3156 }, +  { 0x0ec7, 0x3157 }, +  { 0x0ec8, 0x3158 }, +  { 0x0ec9, 0x3159 }, +  { 0x0eca, 0x315a }, +  { 0x0ecb, 0x315b }, +  { 0x0ecc, 0x315c }, +  { 0x0ecd, 0x315d }, +  { 0x0ece, 0x315e }, +  { 0x0ecf, 0x315f }, +  { 0x0ed0, 0x3160 }, +  { 0x0ed1, 0x3161 }, +  { 0x0ed2, 0x3162 }, +  { 0x0ed3, 0x3163 }, +  { 0x0ed4, 0x11a8 }, +  { 0x0ed5, 0x11a9 }, +  { 0x0ed6, 0x11aa }, +  { 0x0ed7, 0x11ab }, +  { 0x0ed8, 0x11ac }, +  { 0x0ed9, 0x11ad }, +  { 0x0eda, 0x11ae }, +  { 0x0edb, 0x11af }, +  { 0x0edc, 0x11b0 }, +  { 0x0edd, 0x11b1 }, +  { 0x0ede, 0x11b2 }, +  { 0x0edf, 0x11b3 }, +  { 0x0ee0, 0x11b4 }, +  { 0x0ee1, 0x11b5 }, +  { 0x0ee2, 0x11b6 }, +  { 0x0ee3, 0x11b7 }, +  { 0x0ee4, 0x11b8 }, +  { 0x0ee5, 0x11b9 }, +  { 0x0ee6, 0x11ba }, +  { 0x0ee7, 0x11bb }, +  { 0x0ee8, 0x11bc }, +  { 0x0ee9, 0x11bd }, +  { 0x0eea, 0x11be }, +  { 0x0eeb, 0x11bf }, +  { 0x0eec, 0x11c0 }, +  { 0x0eed, 0x11c1 }, +  { 0x0eee, 0x11c2 }, +  { 0x0eef, 0x316d }, +  { 0x0ef0, 0x3171 }, +  { 0x0ef1, 0x3178 }, +  { 0x0ef2, 0x317f }, +  { 0x0ef3, 0x3181 }, +  { 0x0ef4, 0x3184 }, +  { 0x0ef5, 0x3186 }, +  { 0x0ef6, 0x318d }, +  { 0x0ef7, 0x318e }, +  { 0x0ef8, 0x11eb }, +  { 0x0ef9, 0x11f0 }, +  { 0x0efa, 0x11f9 }, +  { 0x0eff, 0x20a9 }, +  { 0x13a4, 0x20ac }, +  { 0x13bc, 0x0152 }, +  { 0x13bd, 0x0153 }, +  { 0x13be, 0x0178 }, +  { 0x20ac, 0x20ac }, +  { 0xfe50,    '`' }, +  { 0xfe51, 0x00b4 }, +  { 0xfe52,    '^' }, +  { 0xfe53,    '~' }, +  { 0xfe54, 0x00af }, +  { 0xfe55, 0x02d8 }, +  { 0xfe56, 0x02d9 }, +  { 0xfe57, 0x00a8 }, +  { 0xfe58, 0x02da }, +  { 0xfe59, 0x02dd }, +  { 0xfe5a, 0x02c7 }, +  { 0xfe5b, 0x00b8 }, +  { 0xfe5c, 0x02db }, +  { 0xfe5d, 0x037a }, +  { 0xfe5e, 0x309b }, +  { 0xfe5f, 0x309c }, +  { 0xfe63,    '/' }, +  { 0xfe64, 0x02bc }, +  { 0xfe65, 0x02bd }, +  { 0xfe66, 0x02f5 }, +  { 0xfe67, 0x02f3 }, +  { 0xfe68, 0x02cd }, +  { 0xfe69, 0xa788 }, +  { 0xfe6a, 0x02f7 }, +  { 0xfe6e,    ',' }, +  { 0xfe6f, 0x00a4 }, +  { 0xfe80,    'a' }, /* XK_dead_a */ +  { 0xfe81,    'A' }, /* XK_dead_A */ +  { 0xfe82,    'e' }, /* XK_dead_e */ +  { 0xfe83,    'E' }, /* XK_dead_E */ +  { 0xfe84,    'i' }, /* XK_dead_i */ +  { 0xfe85,    'I' }, /* XK_dead_I */ +  { 0xfe86,    'o' }, /* XK_dead_o */ +  { 0xfe87,    'O' }, /* XK_dead_O */ +  { 0xfe88,    'u' }, /* XK_dead_u */ +  { 0xfe89,    'U' }, /* XK_dead_U */ +  { 0xfe8a, 0x0259 }, +  { 0xfe8b, 0x018f }, +  { 0xfe8c, 0x00b5 }, +  { 0xfe90,    '_' }, +  { 0xfe91, 0x02c8 }, +  { 0xfe92, 0x02cc }, +  { 0xff80 /*XKB_KEY_KP_Space*/,     ' ' }, +  { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, +  { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, +  { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, +  { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, +  { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, +  { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, +  { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, +  { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, +  { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, +  { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, +  { 0xffaa /*XKB_KEY_KP_Multiply*/,  '*' }, +  { 0xffab /*XKB_KEY_KP_Add*/,       '+' }, +  { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, +  { 0xffad /*XKB_KEY_KP_Subtract*/,  '-' }, +  { 0xffae /*XKB_KEY_KP_Decimal*/,   '.' }, +  { 0xffaf /*XKB_KEY_KP_Divide*/,    '/' }, +  { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, +  { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, +  { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, +  { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, +  { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, +  { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, +  { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, +  { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, +  { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, +  { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, +  { 0xffbd /*XKB_KEY_KP_Equal*/,     '=' } +}; + +_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { +    _SOKOL_UNUSED(display); +    _sapp.x11.error_code = event->error_code; +    return 0; +} + +_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { +    _sapp.x11.error_code = Success; +    XSetErrorHandler(_sapp_x11_error_handler); +} + +_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { +    XSync(_sapp.x11.display, False); +    XSetErrorHandler(NULL); +} + +_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { +    _sapp.x11.UTF8_STRING             = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); +    _sapp.x11.WM_PROTOCOLS            = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); +    _sapp.x11.WM_DELETE_WINDOW        = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); +    _sapp.x11.WM_STATE                = XInternAtom(_sapp.x11.display, "WM_STATE", False); +    _sapp.x11.NET_WM_NAME             = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); +    _sapp.x11.NET_WM_ICON_NAME        = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); +    _sapp.x11.NET_WM_STATE            = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); +    _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); +    if (_sapp.drop.enabled) { +        _sapp.x11.xdnd.XdndAware        = XInternAtom(_sapp.x11.display, "XdndAware", False); +        _sapp.x11.xdnd.XdndEnter        = XInternAtom(_sapp.x11.display, "XdndEnter", False); +        _sapp.x11.xdnd.XdndPosition     = XInternAtom(_sapp.x11.display, "XdndPosition", False); +        _sapp.x11.xdnd.XdndStatus       = XInternAtom(_sapp.x11.display, "XdndStatus", False); +        _sapp.x11.xdnd.XdndActionCopy   = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); +        _sapp.x11.xdnd.XdndDrop         = XInternAtom(_sapp.x11.display, "XdndDrop", False); +        _sapp.x11.xdnd.XdndFinished     = XInternAtom(_sapp.x11.display, "XdndFinished", False); +        _sapp.x11.xdnd.XdndSelection    = XInternAtom(_sapp.x11.display, "XdndSelection", False); +        _sapp.x11.xdnd.XdndTypeList     = XInternAtom(_sapp.x11.display, "XdndTypeList", False); +        _sapp.x11.xdnd.text_uri_list    = XInternAtom(_sapp.x11.display, "text/uri-list", False); +    } + +    /* check Xi extension for raw mouse input */ +    if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { +        _sapp.x11.xi.major = 2; +        _sapp.x11.xi.minor = 0; +        if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { +            _sapp.x11.xi.available = true; +        } +    } +} + +_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { +    /* from GLFW: + +       NOTE: Default to the display-wide DPI as we don't currently have a policy +             for which monitor a window is considered to be on + +        _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * +                        25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); + +       NOTE: Basing the scale on Xft.dpi where available should provide the most +             consistent user experience (matches Qt, Gtk, etc), although not +             always the most accurate one +    */ +    char* rms = XResourceManagerString(_sapp.x11.display); +    if (rms) { +        XrmDatabase db = XrmGetStringDatabase(rms); +        if (db) { +            XrmValue value; +            char* type = NULL; +            if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { +                if (type && strcmp(type, "String") == 0) { +                    _sapp.x11.dpi = atof(value.addr); +                } +            } +            XrmDestroyDatabase(db); +        } +    } +} + +_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { +    SOKOL_ASSERT(ext); +    const char* start = extensions; +    while (true) { +        const char* where = strstr(start, ext); +        if (!where) { +            return false; +        } +        const char* terminator = where + strlen(ext); +        if ((where == start) || (*(where - 1) == ' ')) { +            if (*terminator == ' ' || *terminator == '\0') { +                break; +            } +        } +        start = terminator; +    } +    return true; +} + +_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { +    if (extensions) { +        return _sapp_glx_has_ext(ext, extensions); +    } +    else { +        return false; +    } +} + +_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) +{ +    if (_sapp.glx.GetProcAddress) { +        return (void*) _sapp.glx.GetProcAddress(procname); +    } +    else if (_sapp.glx.GetProcAddressARB) { +        return (void*) _sapp.glx.GetProcAddressARB(procname); +    } +    else { +        return dlsym(_sapp.glx.libgl, procname); +    } +} + +_SOKOL_PRIVATE void _sapp_glx_init() { +    const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; +    for (int i = 0; sonames[i]; i++) { +        _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); +        if (_sapp.glx.libgl) { +            break; +        } +    } +    if (!_sapp.glx.libgl) { +        _sapp_fail("GLX: failed to load libGL"); +    } +    _sapp.glx.GetFBConfigs          = (PFNGLXGETFBCONFIGSPROC)          dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); +    _sapp.glx.GetFBConfigAttrib     = (PFNGLXGETFBCONFIGATTRIBPROC)     dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); +    _sapp.glx.GetClientString       = (PFNGLXGETCLIENTSTRINGPROC)       dlsym(_sapp.glx.libgl, "glXGetClientString"); +    _sapp.glx.QueryExtension        = (PFNGLXQUERYEXTENSIONPROC)        dlsym(_sapp.glx.libgl, "glXQueryExtension"); +    _sapp.glx.QueryVersion          = (PFNGLXQUERYVERSIONPROC)          dlsym(_sapp.glx.libgl, "glXQueryVersion"); +    _sapp.glx.DestroyContext        = (PFNGLXDESTROYCONTEXTPROC)        dlsym(_sapp.glx.libgl, "glXDestroyContext"); +    _sapp.glx.MakeCurrent           = (PFNGLXMAKECURRENTPROC)           dlsym(_sapp.glx.libgl, "glXMakeCurrent"); +    _sapp.glx.SwapBuffers           = (PFNGLXSWAPBUFFERSPROC)           dlsym(_sapp.glx.libgl, "glXSwapBuffers"); +    _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); +    _sapp.glx.CreateWindow          = (PFNGLXCREATEWINDOWPROC)          dlsym(_sapp.glx.libgl, "glXCreateWindow"); +    _sapp.glx.DestroyWindow         = (PFNGLXDESTROYWINDOWPROC)         dlsym(_sapp.glx.libgl, "glXDestroyWindow"); +    _sapp.glx.GetProcAddress        = (PFNGLXGETPROCADDRESSPROC)        dlsym(_sapp.glx.libgl, "glXGetProcAddress"); +    _sapp.glx.GetProcAddressARB     = (PFNGLXGETPROCADDRESSPROC)        dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); +    _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); +    if (!_sapp.glx.GetFBConfigs || +        !_sapp.glx.GetFBConfigAttrib || +        !_sapp.glx.GetClientString || +        !_sapp.glx.QueryExtension || +        !_sapp.glx.QueryVersion || +        !_sapp.glx.DestroyContext || +        !_sapp.glx.MakeCurrent || +        !_sapp.glx.SwapBuffers || +        !_sapp.glx.QueryExtensionsString || +        !_sapp.glx.CreateWindow || +        !_sapp.glx.DestroyWindow || +        !_sapp.glx.GetProcAddress || +        !_sapp.glx.GetProcAddressARB || +        !_sapp.glx.GetVisualFromFBConfig) +    { +        _sapp_fail("GLX: failed to load required entry points"); +    } + +    if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { +        _sapp_fail("GLX: GLX extension not found"); +    } +    if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { +        _sapp_fail("GLX: Failed to query GLX version"); +    } +    if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { +        _sapp_fail("GLX: GLX version 1.3 is required"); +    } +    const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); +    if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { +        _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); +        _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; +    } +    if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { +        _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); +        _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; +    } +    _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); +    if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { +        _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); +        _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; +    } +    _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); +} + +_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { +    int value; +    _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); +    return value; +} + +_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig() { +    GLXFBConfig* native_configs; +    _sapp_gl_fbconfig* usable_configs; +    const _sapp_gl_fbconfig* closest; +    int i, native_count, usable_count; +    const char* vendor; +    bool trust_window_bit = true; + +    /* HACK: This is a (hopefully temporary) workaround for Chromium +           (VirtualBox GL) not setting the window bit on any GLXFBConfigs +    */ +    vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); +    if (vendor && strcmp(vendor, "Chromium") == 0) { +        trust_window_bit = false; +    } + +    native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); +    if (!native_configs || !native_count) { +        _sapp_fail("GLX: No GLXFBConfigs returned"); +    } + +    usable_configs = (_sapp_gl_fbconfig*) SOKOL_CALLOC((size_t)native_count, sizeof(_sapp_gl_fbconfig)); +    usable_count = 0; +    for (i = 0;  i < native_count;  i++) { +        const GLXFBConfig n = native_configs[i]; +        _sapp_gl_fbconfig* u = usable_configs + usable_count; +        _sapp_gl_init_fbconfig(u); + +        /* Only consider RGBA GLXFBConfigs */ +        if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { +            continue; +        } +        /* Only consider window GLXFBConfigs */ +        if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { +            if (trust_window_bit) { +                continue; +            } +        } +        u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); +        u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); +        u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); +        u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); +        u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); +        u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); +        if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { +            u->doublebuffer = true; +        } +        if (_sapp.glx.ARB_multisample) { +            u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); +        } +        u->handle = (uintptr_t) n; +        usable_count++; +    } +    _sapp_gl_fbconfig desired; +    _sapp_gl_init_fbconfig(&desired); +    desired.red_bits = 8; +    desired.green_bits = 8; +    desired.blue_bits = 8; +    desired.alpha_bits = 8; +    desired.depth_bits = 24; +    desired.stencil_bits = 8; +    desired.doublebuffer = true; +    desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; +    closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); +    GLXFBConfig result = 0; +    if (closest) { +        result = (GLXFBConfig) closest->handle; +    } +    XFree(native_configs); +    SOKOL_FREE(usable_configs); +    return result; +} + +_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { +    GLXFBConfig native = _sapp_glx_choosefbconfig(); +    if (0 == native) { +        _sapp_fail("GLX: Failed to find a suitable GLXFBConfig"); +    } +    XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); +    if (!result) { +        _sapp_fail("GLX: Failed to retrieve Visual for GLXFBConfig"); +    } +    *visual = result->visual; +    *depth = result->depth; +    XFree(result); +} + +_SOKOL_PRIVATE void _sapp_glx_create_context(void) { +    GLXFBConfig native = _sapp_glx_choosefbconfig(); +    if (0 == native){ +        _sapp_fail("GLX: Failed to find a suitable GLXFBConfig (2)"); +    } +    if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { +        _sapp_fail("GLX: ARB_create_context and ARB_create_context_profile required"); +    } +    _sapp_x11_grab_error_handler(); +    const int attribs[] = { +        GLX_CONTEXT_MAJOR_VERSION_ARB, 3, +        GLX_CONTEXT_MINOR_VERSION_ARB, 3, +        GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, +        GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, +        0, 0 +    }; +    _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); +    if (!_sapp.glx.ctx) { +        _sapp_fail("GLX: failed to create GL context"); +    } +    _sapp_x11_release_error_handler(); +    _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); +    if (!_sapp.glx.window) { +        _sapp_fail("GLX: failed to create window"); +    } +} + +_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { +    if (_sapp.glx.window) { +        _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); +        _sapp.glx.window = 0; +    } +    if (_sapp.glx.ctx) { +        _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); +        _sapp.glx.ctx = 0; +    } +} + +_SOKOL_PRIVATE void _sapp_glx_make_current(void) { +    _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); +} + +_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { +    _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); +} + +_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { +    _sapp_glx_make_current(); +    if (_sapp.glx.EXT_swap_control) { +        _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); +    } +    else if (_sapp.glx.MESA_swap_control) { +        _sapp.glx.SwapIntervalMESA(interval); +    } +} + +_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { +    XEvent event; +    memset(&event, 0, sizeof(event)); + +    event.type = ClientMessage; +    event.xclient.window = _sapp.x11.window; +    event.xclient.format = 32; +    event.xclient.message_type = type; +    event.xclient.data.l[0] = a; +    event.xclient.data.l[1] = b; +    event.xclient.data.l[2] = c; +    event.xclient.data.l[3] = d; +    event.xclient.data.l[4] = e; + +    XSendEvent(_sapp.x11.display, _sapp.x11.root, +               False, +               SubstructureNotifyMask | SubstructureRedirectMask, +               &event); +} + +_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { +    XWindowAttributes attribs; +    XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); +    _sapp.window_width = attribs.width; +    _sapp.window_height = attribs.height; +    _sapp.framebuffer_width = _sapp.window_width; +    _sapp.framebuffer_height = _sapp.window_height; +} + +_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { +    /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ +    if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { +        if (enable) { +            const int _NET_WM_STATE_ADD = 1; +            _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, +                                _NET_WM_STATE_ADD, +                                _sapp.x11.NET_WM_STATE_FULLSCREEN, +                                0, 1, 0); +        } +        else { +            const int _NET_WM_STATE_REMOVE = 0; +            _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, +                                _NET_WM_STATE_REMOVE, +                                _sapp.x11.NET_WM_STATE_FULLSCREEN, +                                0, 1, 0); +        } +    } +    XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { +    SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); +    const int w = 16; +    const int h = 16; +    XcursorImage* img = XcursorImageCreate(w, h); +    SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); +    img->xhot = 0; +    img->yhot = 0; +    const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); +    memset(img->pixels, 0, num_bytes); +    _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); +    XcursorImageDestroy(img); +} + +_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { +    _sapp.fullscreen = !_sapp.fullscreen; +    _sapp_x11_set_fullscreen(_sapp.fullscreen); +    _sapp_x11_query_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_show_mouse(bool show) { +    if (show) { +        XUndefineCursor(_sapp.x11.display, _sapp.x11.window); +    } +    else { +        XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); +    } +} + +_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { +    if (lock == _sapp.mouse.locked) { +        return; +    } +    _sapp.mouse.dx = 0.0f; +    _sapp.mouse.dy = 0.0f; +    _sapp.mouse.locked = lock; +    if (_sapp.mouse.locked) { +        if (_sapp.x11.xi.available) { +            XIEventMask em; +            unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro +            em.deviceid = XIAllMasterDevices; +            em.mask_len = sizeof(mask); +            em.mask = mask; +            XISetMask(mask, XI_RawMotion); +            XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); +        } +        XGrabPointer(_sapp.x11.display, // display +            _sapp.x11.window,           // grab_window +            True,                       // owner_events +            ButtonPressMask | ButtonReleaseMask | PointerMotionMask,    // event_mask +            GrabModeAsync,              // pointer_mode +            GrabModeAsync,              // keyboard_mode +            _sapp.x11.window,           // confine_to +            _sapp.x11.hidden_cursor,    // cursor +            CurrentTime);               // time +    } +    else { +        if (_sapp.x11.xi.available) { +            XIEventMask em; +            unsigned char mask[] = { 0 }; +            em.deviceid = XIAllMasterDevices; +            em.mask_len = sizeof(mask); +            em.mask = mask; +            XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); +        } +        XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); +        XUngrabPointer(_sapp.x11.display, CurrentTime); +    } +    XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { +    Xutf8SetWMProperties(_sapp.x11.display, +        _sapp.x11.window, +        _sapp.window_title, _sapp.window_title, +        NULL, 0, NULL, NULL, NULL); +    XChangeProperty(_sapp.x11.display, _sapp.x11.window, +        _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, +        PropModeReplace, +        (unsigned char*)_sapp.window_title, +        strlen(_sapp.window_title)); +    XChangeProperty(_sapp.x11.display, _sapp.x11.window, +        _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, +        PropModeReplace, +        (unsigned char*)_sapp.window_title, +        strlen(_sapp.window_title)); +    XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { +    _sapp.x11.colormap = XCreateColormap(_sapp.x11.display, _sapp.x11.root, visual, AllocNone); +    XSetWindowAttributes wa; +    memset(&wa, 0, sizeof(wa)); +    const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; +    wa.colormap = _sapp.x11.colormap; +    wa.border_pixel = 0; +    wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | +                    PointerMotionMask | ButtonPressMask | ButtonReleaseMask | +                    ExposureMask | FocusChangeMask | VisibilityChangeMask | +                    EnterWindowMask | LeaveWindowMask | PropertyChangeMask; +    _sapp_x11_grab_error_handler(); +    _sapp.x11.window = XCreateWindow(_sapp.x11.display, +                                     _sapp.x11.root, +                                     0, 0, +                                     (uint32_t)_sapp.window_width, +                                     (uint32_t)_sapp.window_height, +                                     0,     /* border width */ +                                     depth, /* color depth */ +                                     InputOutput, +                                     visual, +                                     wamask, +                                     &wa); +    _sapp_x11_release_error_handler(); +    if (!_sapp.x11.window) { +        _sapp_fail("X11: Failed to create window"); +    } +    Atom protocols[] = { +        _sapp.x11.WM_DELETE_WINDOW +    }; +    XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); + +    XSizeHints* hints = XAllocSizeHints(); +    hints->flags |= PWinGravity; +    hints->win_gravity = StaticGravity; +    XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); +    XFree(hints); + +    /* announce support for drag'n'drop */ +    if (_sapp.drop.enabled) { +        const Atom version = _SAPP_X11_XDND_VERSION; +        XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); +    } + +    _sapp_x11_update_window_title(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { +    if (_sapp.x11.window) { +        XUnmapWindow(_sapp.x11.display, _sapp.x11.window); +        XDestroyWindow(_sapp.x11.display, _sapp.x11.window); +        _sapp.x11.window = 0; +    } +    if (_sapp.x11.colormap) { +        XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); +        _sapp.x11.colormap = 0; +    } +    XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { +    XWindowAttributes wa; +    XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); +    return wa.map_state == IsViewable; +} + +_SOKOL_PRIVATE void _sapp_x11_show_window(void) { +    if (!_sapp_x11_window_visible()) { +        XMapWindow(_sapp.x11.display, _sapp.x11.window); +        XRaiseWindow(_sapp.x11.display, _sapp.x11.window); +        XFlush(_sapp.x11.display); +    } +} + +_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { +    XUnmapWindow(_sapp.x11.display, _sapp.x11.window); +    XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { +    Atom actualType; +    int actualFormat; +    unsigned long itemCount, bytesAfter; +    XGetWindowProperty(_sapp.x11.display, +                       window, +                       property, +                       0, +                       LONG_MAX, +                       False, +                       type, +                       &actualType, +                       &actualFormat, +                       &itemCount, +                       &bytesAfter, +                       value); +    return itemCount; +} + +_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { +    int result = WithdrawnState; +    struct { +        CARD32 state; +        Window icon; +    } *state = NULL; + +    if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { +        result = (int)state->state; +    } +    if (state) { +        XFree(state); +    } +    return result; +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_mod(uint32_t x11_mods) { +    uint32_t mods = 0; +    if (x11_mods & ShiftMask) { +        mods |= SAPP_MODIFIER_SHIFT; +    } +    if (x11_mods & ControlMask) { +        mods |= SAPP_MODIFIER_CTRL; +    } +    if (x11_mods & Mod1Mask) { +        mods |= SAPP_MODIFIER_ALT; +    } +    if (x11_mods & Mod4Mask) { +        mods |= SAPP_MODIFIER_SUPER; +    } +    return mods; +} + +_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { +    switch (event->xbutton.button) { +        case Button1: return SAPP_MOUSEBUTTON_LEFT; +        case Button2: return SAPP_MOUSEBUTTON_MIDDLE; +        case Button3: return SAPP_MOUSEBUTTON_RIGHT; +        default:      return SAPP_MOUSEBUTTON_INVALID; +    } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp.event.mouse_button = btn; +        _sapp.event.modifiers = mods; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); +        _sapp.event.modifiers = mods; +        _sapp.event.scroll_x = x; +        _sapp.event.scroll_y = y; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(type); +        _sapp.event.key_code = key; +        _sapp.event.key_repeat = repeat; +        _sapp.event.modifiers = mods; +        _sapp_call_event(&_sapp.event); +        /* check if a CLIPBOARD_PASTED event must be sent too */ +        if (_sapp.clipboard.enabled && +            (type == SAPP_EVENTTYPE_KEY_DOWN) && +            (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && +            (_sapp.event.key_code == SAPP_KEYCODE_V)) +        { +            _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); +            _sapp_call_event(&_sapp.event); +        } +    } +} + +_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { +    if (_sapp_events_enabled()) { +        _sapp_init_event(SAPP_EVENTTYPE_CHAR); +        _sapp.event.char_code = chr; +        _sapp.event.key_repeat = repeat; +        _sapp.event.modifiers = mods; +        _sapp_call_event(&_sapp.event); +    } +} + +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { +    int dummy; +    KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); +    SOKOL_ASSERT(keysyms); +    KeySym keysym = keysyms[0]; +    XFree(keysyms); +    switch (keysym) { +        case XK_Escape:         return SAPP_KEYCODE_ESCAPE; +        case XK_Tab:            return SAPP_KEYCODE_TAB; +        case XK_Shift_L:        return SAPP_KEYCODE_LEFT_SHIFT; +        case XK_Shift_R:        return SAPP_KEYCODE_RIGHT_SHIFT; +        case XK_Control_L:      return SAPP_KEYCODE_LEFT_CONTROL; +        case XK_Control_R:      return SAPP_KEYCODE_RIGHT_CONTROL; +        case XK_Meta_L: +        case XK_Alt_L:          return SAPP_KEYCODE_LEFT_ALT; +        case XK_Mode_switch:    /* Mapped to Alt_R on many keyboards */ +        case XK_ISO_Level3_Shift: /* AltGr on at least some machines */ +        case XK_Meta_R: +        case XK_Alt_R:          return SAPP_KEYCODE_RIGHT_ALT; +        case XK_Super_L:        return SAPP_KEYCODE_LEFT_SUPER; +        case XK_Super_R:        return SAPP_KEYCODE_RIGHT_SUPER; +        case XK_Menu:           return SAPP_KEYCODE_MENU; +        case XK_Num_Lock:       return SAPP_KEYCODE_NUM_LOCK; +        case XK_Caps_Lock:      return SAPP_KEYCODE_CAPS_LOCK; +        case XK_Print:          return SAPP_KEYCODE_PRINT_SCREEN; +        case XK_Scroll_Lock:    return SAPP_KEYCODE_SCROLL_LOCK; +        case XK_Pause:          return SAPP_KEYCODE_PAUSE; +        case XK_Delete:         return SAPP_KEYCODE_DELETE; +        case XK_BackSpace:      return SAPP_KEYCODE_BACKSPACE; +        case XK_Return:         return SAPP_KEYCODE_ENTER; +        case XK_Home:           return SAPP_KEYCODE_HOME; +        case XK_End:            return SAPP_KEYCODE_END; +        case XK_Page_Up:        return SAPP_KEYCODE_PAGE_UP; +        case XK_Page_Down:      return SAPP_KEYCODE_PAGE_DOWN; +        case XK_Insert:         return SAPP_KEYCODE_INSERT; +        case XK_Left:           return SAPP_KEYCODE_LEFT; +        case XK_Right:          return SAPP_KEYCODE_RIGHT; +        case XK_Down:           return SAPP_KEYCODE_DOWN; +        case XK_Up:             return SAPP_KEYCODE_UP; +        case XK_F1:             return SAPP_KEYCODE_F1; +        case XK_F2:             return SAPP_KEYCODE_F2; +        case XK_F3:             return SAPP_KEYCODE_F3; +        case XK_F4:             return SAPP_KEYCODE_F4; +        case XK_F5:             return SAPP_KEYCODE_F5; +        case XK_F6:             return SAPP_KEYCODE_F6; +        case XK_F7:             return SAPP_KEYCODE_F7; +        case XK_F8:             return SAPP_KEYCODE_F8; +        case XK_F9:             return SAPP_KEYCODE_F9; +        case XK_F10:            return SAPP_KEYCODE_F10; +        case XK_F11:            return SAPP_KEYCODE_F11; +        case XK_F12:            return SAPP_KEYCODE_F12; +        case XK_F13:            return SAPP_KEYCODE_F13; +        case XK_F14:            return SAPP_KEYCODE_F14; +        case XK_F15:            return SAPP_KEYCODE_F15; +        case XK_F16:            return SAPP_KEYCODE_F16; +        case XK_F17:            return SAPP_KEYCODE_F17; +        case XK_F18:            return SAPP_KEYCODE_F18; +        case XK_F19:            return SAPP_KEYCODE_F19; +        case XK_F20:            return SAPP_KEYCODE_F20; +        case XK_F21:            return SAPP_KEYCODE_F21; +        case XK_F22:            return SAPP_KEYCODE_F22; +        case XK_F23:            return SAPP_KEYCODE_F23; +        case XK_F24:            return SAPP_KEYCODE_F24; +        case XK_F25:            return SAPP_KEYCODE_F25; + +        case XK_KP_Divide:      return SAPP_KEYCODE_KP_DIVIDE; +        case XK_KP_Multiply:    return SAPP_KEYCODE_KP_MULTIPLY; +        case XK_KP_Subtract:    return SAPP_KEYCODE_KP_SUBTRACT; +        case XK_KP_Add:         return SAPP_KEYCODE_KP_ADD; + +        case XK_KP_Insert:      return SAPP_KEYCODE_KP_0; +        case XK_KP_End:         return SAPP_KEYCODE_KP_1; +        case XK_KP_Down:        return SAPP_KEYCODE_KP_2; +        case XK_KP_Page_Down:   return SAPP_KEYCODE_KP_3; +        case XK_KP_Left:        return SAPP_KEYCODE_KP_4; +        case XK_KP_Begin:       return SAPP_KEYCODE_KP_5; +        case XK_KP_Right:       return SAPP_KEYCODE_KP_6; +        case XK_KP_Home:        return SAPP_KEYCODE_KP_7; +        case XK_KP_Up:          return SAPP_KEYCODE_KP_8; +        case XK_KP_Page_Up:     return SAPP_KEYCODE_KP_9; +        case XK_KP_Delete:      return SAPP_KEYCODE_KP_DECIMAL; +        case XK_KP_Equal:       return SAPP_KEYCODE_KP_EQUAL; +        case XK_KP_Enter:       return SAPP_KEYCODE_KP_ENTER; + +        case XK_a:              return SAPP_KEYCODE_A; +        case XK_b:              return SAPP_KEYCODE_B; +        case XK_c:              return SAPP_KEYCODE_C; +        case XK_d:              return SAPP_KEYCODE_D; +        case XK_e:              return SAPP_KEYCODE_E; +        case XK_f:              return SAPP_KEYCODE_F; +        case XK_g:              return SAPP_KEYCODE_G; +        case XK_h:              return SAPP_KEYCODE_H; +        case XK_i:              return SAPP_KEYCODE_I; +        case XK_j:              return SAPP_KEYCODE_J; +        case XK_k:              return SAPP_KEYCODE_K; +        case XK_l:              return SAPP_KEYCODE_L; +        case XK_m:              return SAPP_KEYCODE_M; +        case XK_n:              return SAPP_KEYCODE_N; +        case XK_o:              return SAPP_KEYCODE_O; +        case XK_p:              return SAPP_KEYCODE_P; +        case XK_q:              return SAPP_KEYCODE_Q; +        case XK_r:              return SAPP_KEYCODE_R; +        case XK_s:              return SAPP_KEYCODE_S; +        case XK_t:              return SAPP_KEYCODE_T; +        case XK_u:              return SAPP_KEYCODE_U; +        case XK_v:              return SAPP_KEYCODE_V; +        case XK_w:              return SAPP_KEYCODE_W; +        case XK_x:              return SAPP_KEYCODE_X; +        case XK_y:              return SAPP_KEYCODE_Y; +        case XK_z:              return SAPP_KEYCODE_Z; +        case XK_1:              return SAPP_KEYCODE_1; +        case XK_2:              return SAPP_KEYCODE_2; +        case XK_3:              return SAPP_KEYCODE_3; +        case XK_4:              return SAPP_KEYCODE_4; +        case XK_5:              return SAPP_KEYCODE_5; +        case XK_6:              return SAPP_KEYCODE_6; +        case XK_7:              return SAPP_KEYCODE_7; +        case XK_8:              return SAPP_KEYCODE_8; +        case XK_9:              return SAPP_KEYCODE_9; +        case XK_0:              return SAPP_KEYCODE_0; +        case XK_space:          return SAPP_KEYCODE_SPACE; +        case XK_minus:          return SAPP_KEYCODE_MINUS; +        case XK_equal:          return SAPP_KEYCODE_EQUAL; +        case XK_bracketleft:    return SAPP_KEYCODE_LEFT_BRACKET; +        case XK_bracketright:   return SAPP_KEYCODE_RIGHT_BRACKET; +        case XK_backslash:      return SAPP_KEYCODE_BACKSLASH; +        case XK_semicolon:      return SAPP_KEYCODE_SEMICOLON; +        case XK_apostrophe:     return SAPP_KEYCODE_APOSTROPHE; +        case XK_grave:          return SAPP_KEYCODE_GRAVE_ACCENT; +        case XK_comma:          return SAPP_KEYCODE_COMMA; +        case XK_period:         return SAPP_KEYCODE_PERIOD; +        case XK_slash:          return SAPP_KEYCODE_SLASH; +        case XK_less:           return SAPP_KEYCODE_WORLD_1; /* At least in some layouts... */ +        default:                return SAPP_KEYCODE_INVALID; +    } +} + +_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { +    int min = 0; +    int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; +    int mid; + +    /* First check for Latin-1 characters (1:1 mapping) */ +    if ((keysym >= 0x0020 && keysym <= 0x007e) || +        (keysym >= 0x00a0 && keysym <= 0x00ff)) +    { +        return keysym; +    } + +    /* Also check for directly encoded 24-bit UCS characters */ +    if ((keysym & 0xff000000) == 0x01000000) { +        return keysym & 0x00ffffff; +    } + +    /* Binary search in table */ +    while (max >= min) { +        mid = (min + max) / 2; +        if (_sapp_x11_keysymtab[mid].keysym < keysym) { +            min = mid + 1; +        } +        else if (_sapp_x11_keysymtab[mid].keysym > keysym) { +            max = mid - 1; +        } +        else { +            return _sapp_x11_keysymtab[mid].ucs; +        } +    } + +    /* No matching Unicode value found */ +    return -1; +} + +_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { +    SOKOL_ASSERT(src); +    SOKOL_ASSERT(_sapp.drop.buffer); + +    _sapp_clear_drop_buffer(); +    _sapp.drop.num_files = 0; + +    /* +        src is (potentially percent-encoded) string made of one or multiple paths +        separated by \r\n, each path starting with 'file://' +    */ +    bool err = false; +    int src_count = 0; +    char src_chr = 0; +    char* dst_ptr = _sapp.drop.buffer; +    const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 +    while (0 != (src_chr = *src++)) { +        src_count++; +        char dst_chr = 0; +        /* check leading 'file://' */ +        if (src_count <= 7) { +            if (((src_count == 1) && (src_chr != 'f')) || +                ((src_count == 2) && (src_chr != 'i')) || +                ((src_count == 3) && (src_chr != 'l')) || +                ((src_count == 4) && (src_chr != 'e')) || +                ((src_count == 5) && (src_chr != ':')) || +                ((src_count == 6) && (src_chr != '/')) || +                ((src_count == 7) && (src_chr != '/'))) +            { +                SOKOL_LOG("sokol_app.h: dropped file URI doesn't start with file://"); +                err = true; +                break; +            } +        } +        else if (src_chr == '\r') { +            // skip +        } +        else if (src_chr == '\n') { +            src_chr = 0; +            src_count = 0; +            _sapp.drop.num_files++; +            // too many files is not an error +            if (_sapp.drop.num_files >= _sapp.drop.max_files) { +                break; +            } +            dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; +            dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); +        } +        else if ((src_chr == '%') && src[0] && src[1]) { +            // a percent-encoded byte (most like UTF-8 multibyte sequence) +            const char digits[3] = { src[0], src[1], 0 }; +            src += 2; +            dst_chr = (char) strtol(digits, 0, 16); +        } +        else { +            dst_chr = src_chr; +        } +        if (dst_chr) { +            // dst_end_ptr already has adjustment for terminating zero +            if (dst_ptr < dst_end_ptr) { +                *dst_ptr++ = dst_chr; +            } +            else { +                SOKOL_LOG("sokol_app.h: dropped file path too long (sapp_desc.max_dropped_file_path_length)"); +                err = true; +                break; +            } +        } +    } +    if (err) { +        _sapp_clear_drop_buffer(); +        _sapp.drop.num_files = 0; +        return false; +    } +    else { +        return true; +    } +} + +// XLib manual says keycodes are in the range [8, 255] inclusive. +// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html +static bool _sapp_x11_keycodes[256]; + +_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { +    Bool filtered = XFilterEvent(event, None); +    switch (event->type) { +        case GenericEvent: +            if (_sapp.mouse.locked && _sapp.x11.xi.available) { +                if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { +                    if (XGetEventData(_sapp.x11.display, &event->xcookie)) { +                        if (event->xcookie.evtype == XI_RawMotion) { +                            XIRawEvent* re = (XIRawEvent*) event->xcookie.data; +                            if (re->valuators.mask_len) { +                                const double* values = re->raw_values; +                                if (XIMaskIsSet(re->valuators.mask, 0)) { +                                    _sapp.mouse.dx = (float) *values; +                                    values++; +                                } +                                if (XIMaskIsSet(re->valuators.mask, 1)) { +                                    _sapp.mouse.dy = (float) *values; +                                } +                                _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xmotion.state)); +                            } +                        } +                        XFreeEventData(_sapp.x11.display, &event->xcookie); +                    } +                } +            } +            break; +        case FocusOut: +            /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ +            if (_sapp.mouse.locked) { +                _sapp_x11_lock_mouse(false); +            } +            break; +        case KeyPress: +            { +                int keycode = (int)event->xkey.keycode; +                const sapp_keycode key = _sapp_x11_translate_key(keycode); +                bool repeat = _sapp_x11_keycodes[keycode & 0xFF]; +                _sapp_x11_keycodes[keycode & 0xFF] = true; +                const uint32_t mods = _sapp_x11_mod(event->xkey.state); +                if (key != SAPP_KEYCODE_INVALID) { +                    _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); +                } +                KeySym keysym; +                XLookupString(&event->xkey, NULL, 0, &keysym, NULL); +                int32_t chr = _sapp_x11_keysym_to_unicode(keysym); +                if (chr > 0) { +                    _sapp_x11_char_event((uint32_t)chr, repeat, mods); +                } +            } +            break; +        case KeyRelease: +            { +                int keycode = (int)event->xkey.keycode; +                const sapp_keycode key = _sapp_x11_translate_key(keycode); +                _sapp_x11_keycodes[keycode & 0xFF] = false; +                if (key != SAPP_KEYCODE_INVALID) { +                    const uint32_t mods = _sapp_x11_mod(event->xkey.state); +                    _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); +                } +            } +            break; +        case ButtonPress: +            { +                const sapp_mousebutton btn = _sapp_x11_translate_button(event); +                const uint32_t mods = _sapp_x11_mod(event->xbutton.state); +                if (btn != SAPP_MOUSEBUTTON_INVALID) { +                    _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); +                    _sapp.x11.mouse_buttons |= (1 << btn); +                } +                else { +                    /* might be a scroll event */ +                    switch (event->xbutton.button) { +                        case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; +                        case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; +                        case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; +                        case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; +                    } +                } +            } +            break; +        case ButtonRelease: +            { +                const sapp_mousebutton btn = _sapp_x11_translate_button(event); +                if (btn != SAPP_MOUSEBUTTON_INVALID) { +                    _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, _sapp_x11_mod(event->xbutton.state)); +                    _sapp.x11.mouse_buttons &= ~(1 << btn); +                } +            } +            break; +        case EnterNotify: +            /* don't send enter/leave events while mouse button held down */ +            if (0 == _sapp.x11.mouse_buttons) { +                _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xcrossing.state)); +            } +            break; +        case LeaveNotify: +            if (0 == _sapp.x11.mouse_buttons) { +                _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xcrossing.state)); +            } +            break; +        case MotionNotify: +            if (!_sapp.mouse.locked) { +                const float new_x = (float) event->xmotion.x; +                const float new_y = (float) event->xmotion.y; +                if (_sapp.mouse.pos_valid) { +                    _sapp.mouse.dx = new_x - _sapp.mouse.x; +                    _sapp.mouse.dy = new_y - _sapp.mouse.y; +                } +                _sapp.mouse.x = new_x; +                _sapp.mouse.y = new_y; +                _sapp.mouse.pos_valid = true; +                _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mod(event->xmotion.state)); +            } +            break; +        case ConfigureNotify: +            if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { +                _sapp.window_width = event->xconfigure.width; +                _sapp.window_height = event->xconfigure.height; +                _sapp.framebuffer_width = _sapp.window_width; +                _sapp.framebuffer_height = _sapp.window_height; +                _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); +            } +            break; +        case PropertyNotify: +            if (event->xproperty.state == PropertyNewValue) { +                if (event->xproperty.atom == _sapp.x11.WM_STATE) { +                    const int state = _sapp_x11_get_window_state(); +                    if (state != _sapp.x11.window_state) { +                        _sapp.x11.window_state = state; +                        if (state == IconicState) { +                            _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); +                        } +                        else if (state == NormalState) { +                            _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); +                        } +                    } +                } +            } +            break; +        case ClientMessage: +            if (filtered) { +                return; +            } +            if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { +                const Atom protocol = (Atom)event->xclient.data.l[0]; +                if (protocol == _sapp.x11.WM_DELETE_WINDOW) { +                    _sapp.quit_requested = true; +                } +            } +            else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { +                const bool is_list = 0 != (event->xclient.data.l[1] & 1); +                _sapp.x11.xdnd.source  = (Window)event->xclient.data.l[0]; +                _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; +                _sapp.x11.xdnd.format  = None; +                if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { +                    return; +                } +                uint32_t count = 0; +                Atom* formats = 0; +                if (is_list) { +                    count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); +                } +                else { +                    count = 3; +                    formats = (Atom*) event->xclient.data.l + 2; +                } +                for (uint32_t i = 0; i < count; i++) { +                    if (formats[i] == _sapp.x11.xdnd.text_uri_list) { +                        _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; +                        break; +                    } +                } +                if (is_list && formats) { +                    XFree(formats); +                } +            } +            else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { +                if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { +                    return; +                } +                Time time = CurrentTime; +                if (_sapp.x11.xdnd.format) { +                    if (_sapp.x11.xdnd.version >= 1) { +                        time = (Time)event->xclient.data.l[2]; +                    } +                    XConvertSelection(_sapp.x11.display, +                                      _sapp.x11.xdnd.XdndSelection, +                                      _sapp.x11.xdnd.format, +                                      _sapp.x11.xdnd.XdndSelection, +                                      _sapp.x11.window, +                                      time); +                } +                else if (_sapp.x11.xdnd.version >= 2) { +                    XEvent reply; +                    memset(&reply, 0, sizeof(reply)); +                    reply.type = ClientMessage; +                    reply.xclient.window = _sapp.x11.window; +                    reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; +                    reply.xclient.format = 32; +                    reply.xclient.data.l[0] = (long)_sapp.x11.window; +                    reply.xclient.data.l[1] = 0;    // drag was rejected +                    reply.xclient.data.l[2] = None; +                    XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); +                    XFlush(_sapp.x11.display); +                } +            } +            else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { +                /* drag operation has moved over the window +                   FIXME: we could track the mouse position here, but +                   this isn't implemented on other platforms either so far +                */ +                if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { +                    return; +                } +                XEvent reply; +                memset(&reply, 0, sizeof(reply)); +                reply.type = ClientMessage; +                reply.xclient.window = _sapp.x11.xdnd.source; +                reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; +                reply.xclient.format = 32; +                reply.xclient.data.l[0] = (long)_sapp.x11.window; +                if (_sapp.x11.xdnd.format) { +                    /* reply that we are ready to copy the dragged data */ +                    reply.xclient.data.l[1] = 1;    // accept with no rectangle +                    if (_sapp.x11.xdnd.version >= 2) { +                        reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; +                    } +                } +                XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); +                XFlush(_sapp.x11.display); +            } +            break; +        case SelectionNotify: +            if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { +                char* data = 0; +                uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, +                                                                event->xselection.property, +                                                                event->xselection.target, +                                                                (unsigned char**) &data); +                if (_sapp.drop.enabled && result) { +                    if (_sapp_x11_parse_dropped_files_list(data)) { +                        if (_sapp_events_enabled()) { +                            _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); +                            _sapp_call_event(&_sapp.event); +                        } +                    } +                } +                if (_sapp.x11.xdnd.version >= 2) { +                    XEvent reply; +                    memset(&reply, 0, sizeof(reply)); +                    reply.type = ClientMessage; +                    reply.xclient.window = _sapp.x11.window; +                    reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; +                    reply.xclient.format = 32; +                    reply.xclient.data.l[0] = (long)_sapp.x11.window; +                    reply.xclient.data.l[1] = result; +                    reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; +                    XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); +                    XFlush(_sapp.x11.display); +                } +            } +            break; +        case DestroyNotify: +            break; +    } +} + +_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { +    /* The following lines are here to trigger a linker error instead of an +        obscure runtime error if the user has forgotten to add -pthread to +        the compiler or linker options. They have no other purpose. +    */ +    pthread_attr_t pthread_attr; +    pthread_attr_init(&pthread_attr); +    pthread_attr_destroy(&pthread_attr); + +    _sapp_init_state(desc); +    _sapp.x11.window_state = NormalState; + +    XInitThreads(); +    XrmInitialize(); +    _sapp.x11.display = XOpenDisplay(NULL); +    if (!_sapp.x11.display) { +        _sapp_fail("XOpenDisplay() failed!\n"); +    } +    _sapp.x11.screen = DefaultScreen(_sapp.x11.display); +    _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); +    XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); +    _sapp_x11_query_system_dpi(); +    _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; +    _sapp_x11_init_extensions(); +    _sapp_x11_create_hidden_cursor(); +    _sapp_glx_init(); +    Visual* visual = 0; +    int depth = 0; +    _sapp_glx_choose_visual(&visual, &depth); +    _sapp_x11_create_window(visual, depth); +    _sapp_glx_create_context(); +    _sapp.valid = true; +    _sapp_x11_show_window(); +    if (_sapp.fullscreen) { +        _sapp_x11_set_fullscreen(true); +    } +    _sapp_x11_query_window_size(); +    _sapp_glx_swapinterval(_sapp.swap_interval); +    XFlush(_sapp.x11.display); +    while (!_sapp.quit_ordered) { +        _sapp_glx_make_current(); +        int count = XPending(_sapp.x11.display); +        while (count--) { +            XEvent event; +            XNextEvent(_sapp.x11.display, &event); +            _sapp_x11_process_event(&event); +        } +        _sapp_frame(); +        _sapp_glx_swap_buffers(); +        XFlush(_sapp.x11.display); +        /* handle quit-requested, either from window or from sapp_request_quit() */ +        if (_sapp.quit_requested && !_sapp.quit_ordered) { +            /* give user code a chance to intervene */ +            _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); +            /* if user code hasn't intervened, quit the app */ +            if (_sapp.quit_requested) { +                _sapp.quit_ordered = true; +            } +        } +    } +    _sapp_call_cleanup(); +    _sapp_glx_destroy_context(); +    _sapp_x11_destroy_window(); +    XCloseDisplay(_sapp.x11.display); +    _sapp_discard_state(); +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { +    sapp_desc desc = sokol_main(argc, argv); +    _sapp_linux_run(&desc); +    return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_LINUX */ + +/*== PUBLIC API FUNCTIONS ====================================================*/ +#if defined(SOKOL_NO_ENTRY) +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { +    SOKOL_ASSERT(desc); +    #if defined(_SAPP_MACOS) +        _sapp_macos_run(desc); +    #elif defined(_SAPP_IOS) +        _sapp_ios_run(desc); +    #elif defined(_SAPP_EMSCRIPTEN) +        _sapp_emsc_run(desc); +    #elif defined(_SAPP_WIN32) +        _sapp_win32_run(desc); +    #elif defined(_SAPP_UWP) +        _sapp_uwp_run(desc); +    #elif defined(_SAPP_LINUX) +        _sapp_linux_run(desc); +    #else +        // calling sapp_run() directly is not supported on Android) +        _sapp_fail("sapp_run() not supported on this platform!"); +    #endif +} + +/* this is just a stub so the linker doesn't complain */ +sapp_desc sokol_main(int argc, char* argv[]) { +    _SOKOL_UNUSED(argc); +    _SOKOL_UNUSED(argv); +    sapp_desc desc; +    memset(&desc, 0, sizeof(desc)); +    return desc; +} +#else +/* likewise, in normal mode, sapp_run() is just an empty stub */ +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { +    _SOKOL_UNUSED(desc); +} +#endif + +SOKOL_API_IMPL bool sapp_isvalid(void) { +    return _sapp.valid; +} + +SOKOL_API_IMPL void* sapp_userdata(void) { +    return _sapp.desc.user_data; +} + +SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { +    return _sapp.desc; +} + +SOKOL_API_IMPL uint64_t sapp_frame_count(void) { +    return _sapp.frame_count; +} + +SOKOL_API_IMPL int sapp_width(void) { +    return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; +} + +SOKOL_API_IMPL float sapp_widthf(void) { +    return (float)sapp_width(); +} + +SOKOL_API_IMPL int sapp_height(void) { +    return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; +} + +SOKOL_API_IMPL float sapp_heightf(void) { +    return (float)sapp_height(); +} + +SOKOL_API_IMPL int sapp_color_format(void) { +    #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) +        switch (_sapp.emsc.wgpu.render_format) { +            case WGPUTextureFormat_RGBA8Unorm: +                return _SAPP_PIXELFORMAT_RGBA8; +            case WGPUTextureFormat_BGRA8Unorm: +                return _SAPP_PIXELFORMAT_BGRA8; +            default: +                SOKOL_UNREACHABLE; +                return 0; +        } +    #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) +        return _SAPP_PIXELFORMAT_BGRA8; +    #else +        return _SAPP_PIXELFORMAT_RGBA8; +    #endif +} + +SOKOL_API_IMPL int sapp_depth_format(void) { +    return _SAPP_PIXELFORMAT_DEPTH_STENCIL; +} + +SOKOL_API_IMPL int sapp_sample_count(void) { +    return _sapp.sample_count; +} + +SOKOL_API_IMPL bool sapp_high_dpi(void) { +    return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); +} + +SOKOL_API_IMPL float sapp_dpi_scale(void) { +    return _sapp.dpi_scale; +} + +SOKOL_API_IMPL bool sapp_gles2(void) { +    return _sapp.gles2_fallback; +} + +SOKOL_API_IMPL void sapp_show_keyboard(bool show) { +    #if defined(_SAPP_IOS) +    _sapp_ios_show_keyboard(show); +    #elif defined(_SAPP_EMSCRIPTEN) +    _sapp_emsc_show_keyboard(show); +    #elif defined(_SAPP_ANDROID) +    _sapp_android_show_keyboard(show); +    #else +    _SOKOL_UNUSED(show); +    #endif +} + +SOKOL_API_IMPL bool sapp_keyboard_shown(void) { +    return _sapp.onscreen_keyboard_shown; +} + +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void) { +    return _sapp.fullscreen; +} + +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void) { +    #if defined(_SAPP_MACOS) +    _sapp_macos_toggle_fullscreen(); +    #elif defined(_SAPP_WIN32) +    _sapp_win32_toggle_fullscreen(); +    #elif defined(_SAPP_UWP) +    _sapp_uwp_toggle_fullscreen(); +    #elif defined(_SAPP_LINUX) +    _sapp_x11_toggle_fullscreen(); +    #endif +} + +/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ +SOKOL_API_IMPL void sapp_show_mouse(bool show) { +    if (_sapp.mouse.shown != show) { +        #if defined(_SAPP_MACOS) +        _sapp_macos_show_mouse(show); +        #elif defined(_SAPP_WIN32) +        _sapp_win32_show_mouse(show); +        #elif defined(_SAPP_LINUX) +        _sapp_x11_show_mouse(show); +        #elif defined(_SAPP_UWP) +        _sapp_uwp_show_mouse(show); +        #endif +        _sapp.mouse.shown = show; +    } +} + +SOKOL_API_IMPL bool sapp_mouse_shown(void) { +    return _sapp.mouse.shown; +} + +SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { +    #if defined(_SAPP_MACOS) +    _sapp_macos_lock_mouse(lock); +    #elif defined(_SAPP_EMSCRIPTEN) +    _sapp_emsc_lock_mouse(lock); +    #elif defined(_SAPP_WIN32) +    _sapp_win32_lock_mouse(lock); +    #elif defined(_SAPP_LINUX) +    _sapp_x11_lock_mouse(lock); +    #else +    _sapp.mouse.locked = lock; +    #endif +} + +SOKOL_API_IMPL bool sapp_mouse_locked(void) { +    return _sapp.mouse.locked; +} + +SOKOL_API_IMPL void sapp_request_quit(void) { +    _sapp.quit_requested = true; +} + +SOKOL_API_IMPL void sapp_cancel_quit(void) { +    _sapp.quit_requested = false; +} + +SOKOL_API_IMPL void sapp_quit(void) { +    _sapp.quit_ordered = true; +} + +SOKOL_API_IMPL void sapp_consume_event(void) { +    _sapp.event_consumed = true; +} + +/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ +SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { +    SOKOL_ASSERT(_sapp.clipboard.enabled); +    if (!_sapp.clipboard.enabled) { +        return; +    } +    SOKOL_ASSERT(str); +    #if defined(_SAPP_MACOS) +        _sapp_macos_set_clipboard_string(str); +    #elif defined(_SAPP_EMSCRIPTEN) +        _sapp_emsc_set_clipboard_string(str); +    #elif defined(_SAPP_WIN32) +        _sapp_win32_set_clipboard_string(str); +    #else +        /* not implemented */ +    #endif +    _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); +} + +SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { +    SOKOL_ASSERT(_sapp.clipboard.enabled); +    if (!_sapp.clipboard.enabled) { +        return ""; +    } +    #if defined(_SAPP_MACOS) +        return _sapp_macos_get_clipboard_string(); +    #elif defined(_SAPP_EMSCRIPTEN) +        return _sapp.clipboard.buffer; +    #elif defined(_SAPP_WIN32) +        return _sapp_win32_get_clipboard_string(); +    #else +        /* not implemented */ +        return _sapp.clipboard.buffer; +    #endif +} + +SOKOL_API_IMPL void sapp_set_window_title(const char* title) { +    SOKOL_ASSERT(title); +    _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); +    #if defined(_SAPP_MACOS) +        _sapp_macos_update_window_title(); +    #elif defined(_SAPP_WIN32) +        _sapp_win32_update_window_title(); +    #elif defined(_SAPP_LINUX) +        _sapp_x11_update_window_title(); +    #endif +} + +SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { +    SOKOL_ASSERT(_sapp.drop.enabled); +    return _sapp.drop.num_files; +} + +SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { +    SOKOL_ASSERT(_sapp.drop.enabled); +    SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); +    SOKOL_ASSERT(_sapp.drop.buffer); +    if (!_sapp.drop.enabled) { +        return ""; +    } +    if ((index < 0) || (index >= _sapp.drop.max_files)) { +        return ""; +    } +    return (const char*) _sapp_dropped_file_path_ptr(index); +} + +SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { +    SOKOL_ASSERT(_sapp.drop.enabled); +    SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); +    #if defined(_SAPP_EMSCRIPTEN) +        if (!_sapp.drop.enabled) { +            return 0; +        } +        return sapp_js_dropped_file_size(index); +    #else +        (void)index; +        return 0; +    #endif +} + +SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { +    SOKOL_ASSERT(_sapp.drop.enabled); +    SOKOL_ASSERT(request); +    SOKOL_ASSERT(request->callback); +    SOKOL_ASSERT(request->buffer_ptr); +    SOKOL_ASSERT(request->buffer_size > 0); +    #if defined(_SAPP_EMSCRIPTEN) +        const int index = request->dropped_file_index; +        sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; +        if ((index < 0) || (index >= _sapp.drop.num_files)) { +            error_code = SAPP_HTML5_FETCH_ERROR_OTHER; +        } +        if (sapp_html5_get_dropped_file_size(index) > request->buffer_size) { +            error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; +        } +        if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { +            _sapp_emsc_invoke_fetch_cb(index, +                false, // success +                (int)error_code, +                request->callback, +                0, // fetched_size +                request->buffer_ptr, +                request->buffer_size, +                request->user_data); +        } +        else { +            sapp_js_fetch_dropped_file(index, +                request->callback, +                request->buffer_ptr, +                request->buffer_size, +                request->user_data); +        } +    #else +        (void)request; +    #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_device(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_METAL) +        #if defined(_SAPP_MACOS) +            const void* obj = (__bridge const void*) _sapp.macos.mtl_device; +        #else +            const void* obj = (__bridge const void*) _sapp.ios.mtl_device; +        #endif +        SOKOL_ASSERT(obj); +        return obj; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_renderpass_descriptor(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_METAL) +        #if defined(_SAPP_MACOS) +            const void* obj = (__bridge const void*) [_sapp.macos.view currentRenderPassDescriptor]; +        #else +            const void* obj = (__bridge const void*) [_sapp.ios.view currentRenderPassDescriptor]; +        #endif +        SOKOL_ASSERT(obj); +        return obj; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_drawable(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_METAL) +        #if defined(_SAPP_MACOS) +            const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; +        #else +            const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; +        #endif +        SOKOL_ASSERT(obj); +        return obj; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_macos_get_window(void) { +    #if defined(_SAPP_MACOS) +        const void* obj = (__bridge const void*) _sapp.macos.window; +        SOKOL_ASSERT(obj); +        return obj; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_ios_get_window(void) { +    #if defined(_SAPP_IOS) +        const void* obj = (__bridge const void*) _sapp.ios.window; +        SOKOL_ASSERT(obj); +        return obj; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_D3D11) +        return _sapp.d3d11.device; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_D3D11) +        return _sapp.d3d11.device_context; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_render_target_view(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_D3D11) +        if (_sapp.d3d11.msaa_rtv) { +            return _sapp.d3d11.msaa_rtv; +        } +        else { +            return _sapp.d3d11.rtv; +        } +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(SOKOL_D3D11) +        return _sapp.d3d11.dsv; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(_SAPP_WIN32) +        return _sapp.win32.hwnd; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) +        return (const void*) _sapp.emsc.wgpu.device; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) +        if (_sapp.sample_count > 1) { +            return (const void*) _sapp.emsc.wgpu.msaa_view; +        } +        else { +            return (const void*) _sapp.emsc.wgpu.swapchain_view; +        } +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) +        if (_sapp.sample_count > 1) { +            return (const void*) _sapp.emsc.wgpu.swapchain_view; +        } +        else { +            return 0; +        } +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) +        return (const void*) _sapp.emsc.wgpu.depth_stencil_view; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { +    SOKOL_ASSERT(_sapp.valid); +    #if defined(_SAPP_ANDROID) +        return (void*)_sapp.android.activity; +    #else +        return 0; +    #endif +} + +SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { +    _sapp.html5_ask_leave_site = ask; +} + +#endif /* SOKOL_APP_IMPL */ diff --git a/v_windows/v/old/thirdparty/sokol/sokol_app2.h b/v_windows/v/old/thirdparty/sokol/sokol_app2.h new file mode 100644 index 0000000..aa6d654 --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/sokol_app2.h @@ -0,0 +1,40 @@ + +@implementation MyView2 + +int __v_sokol_inited = 0; + +// Alternative drawRect which calls a frame function with native Cocoa calls +- (void)drawRect:(NSRect)rect { +	//puts("drawRect()"); +	if (__v_sokol_inited == 0) { +        _sapp_call_init(); +       __v_sokol_inited = 1; +      } +    _sapp_call_frame_native(); +} + +//- (BOOL)isOpaque { +//    return NO; +//} + +- (BOOL)canBecomeKeyView { +    return YES; +} +- (BOOL)acceptsFirstResponder { +    return YES; +} + +// - (void)mouseExited:(NSEvent*)event { +// } + +// - (void)mouseDown:(NSEvent*)event { +// } + +- (BOOL)acceptsFirstMouse:(NSEvent *)event { +	return YES; +} + + +@end + + diff --git a/v_windows/v/old/thirdparty/sokol/sokol_audio.h b/v_windows/v/old/thirdparty/sokol/sokol_audio.h new file mode 100644 index 0000000..faea817 --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/sokol_audio.h @@ -0,0 +1,2147 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL) +#define SOKOL_AUDIO_IMPL +#endif +#ifndef SOKOL_AUDIO_INCLUDED +/* +    sokol_audio.h -- cross-platform audio-streaming API + +    Project URL: https://github.com/floooh/sokol + +    Do this: +        #define SOKOL_IMPL or +        #define SOKOL_AUDIO_IMPL +    before you include this file in *one* C or C++ file to create the +    implementation. + +    Optionally provide the following defines with your own implementations: + +    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_MALLOC(s)     - your own malloc() implementation (default: malloc(s)) +    SOKOL_FREE(p)       - your own free() implementation (default: free(p)) +    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: -) + +    SAUDIO_RING_MAX_SLOTS           - max number of slots in the push-audio ring buffer (default 1024) +    SAUDIO_OSX_USE_SYSTEM_HEADERS   - define this to force inclusion of system headers on +                                      macOS instead of using embedded CoreAudio declarations + +    If sokol_audio.h is compiled as a DLL, define the following before +    including the declaration or implementation: + +    SOKOL_DLL + +    On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport) +    or __declspec(dllimport) as needed. + +    Link with the following libraries: + +    - on macOS: AudioToolbox +    - on iOS: AudioToolbox, AVFoundation +    - on Linux: asound +    - on Android: link with OpenSLES +    - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib +    - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32 + +    FEATURE OVERVIEW +    ================ +    You provide a mono- or stereo-stream of 32-bit float samples, which +    Sokol Audio feeds into platform-specific audio backends: + +    - Windows: WASAPI +    - Linux: ALSA +    - macOS: CoreAudio +    - iOS: CoreAudio+AVAudioSession +    - emscripten: WebAudio with ScriptProcessorNode +    - Android: OpenSLES + +    Sokol Audio will not do any buffer mixing or volume control, if you have +    multiple independent input streams of sample data you need to perform the +    mixing yourself before forwarding the data to Sokol Audio. + +    There are two mutually exclusive ways to provide the sample data: + +    1. Callback model: You provide a callback function, which will be called +       when Sokol Audio needs new samples. On all platforms except emscripten, +       this function is called from a separate thread. +    2. Push model: Your code pushes small blocks of sample data from your +       main loop or a thread you created. The pushed data is stored in +       a ring buffer where it is pulled by the backend code when +       needed. + +    The callback model is preferred because it is the most direct way to +    feed sample data into the audio backends and also has less moving parts +    (there is no ring buffer between your code and the audio backend). + +    Sometimes it is not possible to generate the audio stream directly in a +    callback function running in a separate thread, for such cases Sokol Audio +    provides the push-model as a convenience. + +    SOKOL AUDIO, SOLOUD AND MINIAUDIO +    ================================= +    The WASAPI, ALSA, OpenSLES and CoreAudio backend code has been taken from the +    SoLoud library (with some modifications, so any bugs in there are most +    likely my fault). If you need a more fully-featured audio solution, check +    out SoLoud, it's excellent: + +        https://github.com/jarikomppa/soloud + +    Another alternative which feature-wise is somewhere inbetween SoLoud and +    sokol-audio might be MiniAudio: + +        https://github.com/mackron/miniaudio + +    GLOSSARY +    ======== +    - stream buffer: +        The internal audio data buffer, usually provided by the backend API. The +        size of the stream buffer defines the base latency, smaller buffers have +        lower latency but may cause audio glitches. Bigger buffers reduce or +        eliminate glitches, but have a higher base latency. + +    - stream callback: +        Optional callback function which is called by Sokol Audio when it +        needs new samples. On Windows, macOS/iOS and Linux, this is called in +        a separate thread, on WebAudio, this is called per-frame in the +        browser thread. + +    - channel: +        A discrete track of audio data, currently 1-channel (mono) and +        2-channel (stereo) is supported and tested. + +    - sample: +        The magnitude of an audio signal on one channel at a given time. In +        Sokol Audio, samples are 32-bit float numbers in the range -1.0 to +        +1.0. + +    - frame: +        The tightly packed set of samples for all channels at a given time. +        For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples. + +    - packet: +        In Sokol Audio, a small chunk of audio data that is moved from the +        main thread to the audio streaming thread in order to decouple the +        rate at which the main thread provides new audio data, and the +        streaming thread consuming audio data. + +    WORKING WITH SOKOL AUDIO +    ======================== +    First call saudio_setup() with your preferred audio playback options. +    In most cases you can stick with the default values, these provide +    a good balance between low-latency and glitch-free playback +    on all audio backends. + +    If you want to use the callback-model, you need to provide a stream +    callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb, +    otherwise keep both function pointers zero-initialized. + +    Use push model and default playback parameters: + +        saudio_setup(&(saudio_desc){0}); + +    Use stream callback model and default playback parameters: + +        saudio_setup(&(saudio_desc){ +            .stream_cb = my_stream_callback +        }); + +    The standard stream callback doesn't have a user data argument, if you want +    that, use the alternative stream_userdata_cb and also set the user_data pointer: + +        saudio_setup(&(saudio_desc){ +            .stream_userdata_cb = my_stream_callback, +            .user_data = &my_data +        }); + +    The following playback parameters can be provided through the +    saudio_desc struct: + +    General parameters (both for stream-callback and push-model): + +        int sample_rate     -- the sample rate in Hz, default: 44100 +        int num_channels    -- number of channels, default: 1 (mono) +        int buffer_frames   -- number of frames in streaming buffer, default: 2048 + +    The stream callback prototype (either with or without userdata): + +        void (*stream_cb)(float* buffer, int num_frames, int num_channels) +        void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data) +            Function pointer to the user-provide stream callback. + +    Push-model parameters: + +        int packet_frames   -- number of frames in a packet, default: 128 +        int num_packets     -- number of packets in ring buffer, default: 64 + +    The sample_rate and num_channels parameters are only hints for the audio +    backend, it isn't guaranteed that those are the values used for actual +    playback. + +    To get the actual parameters, call the following functions after +    saudio_setup(): + +        int saudio_sample_rate(void) +        int saudio_channels(void); + +    It's unlikely that the number of channels will be different than requested, +    but a different sample rate isn't uncommon. + +    (NOTE: there's an yet unsolved issue when an audio backend might switch +    to a different sample rate when switching output devices, for instance +    plugging in a bluetooth headset, this case is currently not handled in +    Sokol Audio). + +    You can check if audio initialization was successful with +    saudio_isvalid(). If backend initialization failed for some reason +    (for instance when there's no audio device in the machine), this +    will return false. Not checking for success won't do any harm, all +    Sokol Audio function will silently fail when called after initialization +    has failed, so apart from missing audio output, nothing bad will happen. + +    Before your application exits, you should call + +        saudio_shutdown(); + +    This stops the audio thread (on Linux, Windows and macOS/iOS) and +    properly shuts down the audio backend. + +    THE STREAM CALLBACK MODEL +    ========================= +    To use Sokol Audio in stream-callback-mode, provide a callback function +    like this in the saudio_desc struct when calling saudio_setup(): + +    void stream_cb(float* buffer, int num_frames, int num_channels) { +        ... +    } + +    Or the alternative version with a user-data argument: + +    void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) { +        my_data_t* my_data = (my_data_t*) user_data; +        ... +    } + +    The job of the callback function is to fill the *buffer* with 32-bit +    float sample values. + +    To output silence, fill the buffer with zeros: + +        void stream_cb(float* buffer, int num_frames, int num_channels) { +            const int num_samples = num_frames * num_channels; +            for (int i = 0; i < num_samples; i++) { +                buffer[i] = 0.0f; +            } +        } + +    For stereo output (num_channels == 2), the samples for the left +    and right channel are interleaved: + +        void stream_cb(float* buffer, int num_frames, int num_channels) { +            assert(2 == num_channels); +            for (int i = 0; i < num_frames; i++) { +                buffer[2*i + 0] = ...;  // left channel +                buffer[2*i + 1] = ...;  // right channel +            } +        } + +    Please keep in mind that the stream callback function is running in a +    separate thread, if you need to share data with the main thread you need +    to take care yourself to make the access to the shared data thread-safe! + +    THE PUSH MODEL +    ============== +    To use the push-model for providing audio data, simply don't set (keep +    zero-initialized) the stream_cb field in the saudio_desc struct when +    calling saudio_setup(). + +    To provide sample data with the push model, call the saudio_push() +    function at regular intervals (for instance once per frame). You can +    call the saudio_expect() function to ask Sokol Audio how much room is +    in the ring buffer, but if you provide a continuous stream of data +    at the right sample rate, saudio_expect() isn't required (it's a simple +    way to sync/throttle your sample generation code with the playback +    rate though). + +    With saudio_push() you may need to maintain your own intermediate sample +    buffer, since pushing individual sample values isn't very efficient. +    The following example is from the MOD player sample in +    sokol-samples (https://github.com/floooh/sokol-samples): + +        const int num_frames = saudio_expect(); +        if (num_frames > 0) { +            const int num_samples = num_frames * saudio_channels(); +            read_samples(flt_buf, num_samples); +            saudio_push(flt_buf, num_frames); +        } + +    Another option is to ignore saudio_expect(), and just push samples as they +    are generated in small batches. In this case you *need* to generate the +    samples at the right sample rate: + +    The following example is taken from the Tiny Emulators project +    (https://github.com/floooh/chips-test), this is for mono playback, +    so (num_samples == num_frames): + +        // tick the sound generator +        if (ay38910_tick(&sys->psg)) { +            // new sample is ready +            sys->sample_buffer[sys->sample_pos++] = sys->psg.sample; +            if (sys->sample_pos == sys->num_samples) { +                // new sample packet is ready +                saudio_push(sys->sample_buffer, sys->num_samples); +                sys->sample_pos = 0; +            } +        } + +    THE WEBAUDIO BACKEND +    ==================== +    The WebAudio backend is currently using a ScriptProcessorNode callback to +    feed the sample data into WebAudio. ScriptProcessorNode has been +    deprecated for a while because it is running from the main thread, with +    the default initialization parameters it works 'pretty well' though. +    Ultimately Sokol Audio will use Audio Worklets, but this requires a few +    more things to fall into place (Audio Worklets implemented everywhere, +    SharedArrayBuffers enabled again, and I need to figure out a 'low-cost' +    solution in terms of implementation effort, since Audio Worklets are +    a lot more complex than ScriptProcessorNode if the audio data needs to come +    from the main thread). + +    The WebAudio backend is automatically selected when compiling for +    emscripten (__EMSCRIPTEN__ define exists). + +    https://developers.google.com/web/updates/2017/12/audio-worklet +    https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern + +    "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/ + +    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) 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. + +    The incoming floating point samples will be directly forwarded to +    CoreAudio without further conversion. + +    macOS and iOS applications that use Sokol Audio need to link with +    the AudioToolbox framework. + +    THE WASAPI BACKEND +    ================== +    The WASAPI backend is automatically selected when compiling on Windows +    (_WIN32 is defined). + +    For thread synchronisation a Win32 critical section is used. + +    WASAPI may use a different size for its own streaming buffer then requested, +    so the base latency may be slightly bigger. The current backend implementation +    converts the incoming floating point sample values to signed 16-bit +    integers. + +    The required Windows system DLLs are linked with #pragma comment(lib, ...), +    so you shouldn't need to add additional linker libs in the build process +    (otherwise this is a bug which should be fixed in sokol_audio.h). + +    THE ALSA BACKEND +    ================ +    The ALSA backend is automatically selected when compiling on Linux +    ('linux' is defined). + +    For thread synchronisation, the pthread_mutex_* functions are used. + +    Samples are directly forwarded to ALSA in 32-bit float format, no +    further conversion is taking place. + +    You need to link with the 'asound' library, and the <alsa/asoundlib.h> +    header must be present (usually both are installed with some sort +    of ALSA development package). + +    LICENSE +    ======= + +    zlib/libpng license + +    Copyright (c) 2018 Andre Weissflog + +    This software is provided 'as-is', without any express or implied warranty. +    In no event will the authors be held liable for any damages arising from the +    use of this software. + +    Permission is granted to anyone to use this software for any purpose, +    including commercial applications, and to alter it and redistribute it +    freely, subject to the following restrictions: + +        1. The origin of this software must not be misrepresented; you must not +        claim that you wrote the original software. If you use this software in a +        product, an acknowledgment in the product documentation would be +        appreciated but is not required. + +        2. Altered source versions must be plainly marked as such, and must not +        be misrepresented as being the original software. + +        3. This notice may not be removed or altered from any source +        distribution. +*/ +#define SOKOL_AUDIO_INCLUDED (1) +#include <stdint.h> +#include <stdbool.h> + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL) +#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_AUDIO_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL) +#define SOKOL_AUDIO_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_AUDIO_API_DECL __declspec(dllimport) +#else +#define SOKOL_AUDIO_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct saudio_desc { +    int sample_rate;        /* requested sample rate */ +    int num_channels;       /* number of channels, default: 1 (mono) */ +    int buffer_frames;      /* number of frames in streaming buffer */ +    int packet_frames;      /* number of frames in a packet */ +    int num_packets;        /* number of packets in packet queue */ +    void (*stream_cb)(float* buffer, int num_frames, int num_channels);  /* optional streaming callback (no user data) */ +    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_desc; + +/* setup sokol-audio */ +SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc); +/* shutdown sokol-audio */ +SOKOL_AUDIO_API_DECL void saudio_shutdown(void); +/* true after setup if audio backend was successfully initialized */ +SOKOL_AUDIO_API_DECL bool saudio_isvalid(void); +/* return the saudio_desc.user_data pointer */ +SOKOL_AUDIO_API_DECL void* saudio_userdata(void); +/* return a copy of the original saudio_desc struct */ +SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void); +/* actual sample rate */ +SOKOL_AUDIO_API_DECL int saudio_sample_rate(void); +/* return actual backend buffer size in number of frames */ +SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void); +/* actual number of channels */ +SOKOL_AUDIO_API_DECL int saudio_channels(void); +/* get current number of frames to fill packet queue */ +SOKOL_AUDIO_API_DECL int saudio_expect(void); +/* push sample frames from main thread, returns number of frames actually pushed */ +SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for c++ */ +inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); } + +#endif +#endif // SOKOL_AUDIO_INCLUDED + +/*=== IMPLEMENTATION =========================================================*/ +#ifdef SOKOL_AUDIO_IMPL +#define SOKOL_AUDIO_IMPL_INCLUDED (1) +#include <string.h> // memset, memcpy +#include <stddef.h> // size_t + +#ifndef SOKOL_API_IMPL +    #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG +    #ifndef NDEBUG +        #define SOKOL_DEBUG (1) +    #endif +#endif +#ifndef SOKOL_ASSERT +    #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_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__) +        #define _SOKOL_PRIVATE __attribute__((unused)) static +    #else +        #define _SOKOL_PRIVATE static +    #endif +#endif + +#ifndef _SOKOL_UNUSED +    #define _SOKOL_UNUSED(x) (void)(x) +#endif + +// platform detection defines +#if defined(SOKOL_DUMMY_BACKEND) +    // nothing +#elif defined(__APPLE__) +    #define _SAUDIO_APPLE (1) +    #include <TargetConditionals.h> +    #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +        #define _SAUDIO_IOS (1) +    #else +        #define _SAUDIO_MACOS (1) +    #endif +#elif defined(__EMSCRIPTEN__) +    #define _SAUDIO_EMSCRIPTEN +#elif defined(_WIN32) +    #define _SAUDIO_WINDOWS (1) +    #include <winapifamily.h> +    #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) +        #define _SAUDIO_UWP (1) +    #else +        #define _SAUDIO_WIN32 (1) +    #endif +#elif defined(__ANDROID__) +    #define _SAUDIO_ANDROID (1) +#elif defined(__linux__) || defined(__unix__) +    #define _SAUDIO_LINUX (1) +#else +#error "sokol_audio.h: Unknown platform" +#endif + +// platform-specific headers and definitions +#if defined(SOKOL_DUMMY_BACKEND) +    #define _SAUDIO_NOTHREADS (1) +#elif defined(_SAUDIO_WINDOWS) +    #define _SAUDIO_WINTHREADS (1) +    #ifndef WIN32_LEAN_AND_MEAN +    #define WIN32_LEAN_AND_MEAN +    #endif +    #ifndef NOMINMAX +    #define NOMINMAX +    #endif +    #include <windows.h> +    #include <synchapi.h> +    #if defined(_SAUDIO_UWP) +        #pragma comment (lib, "WindowsApp") +    #else +        #pragma comment (lib, "kernel32") +        #pragma comment (lib, "ole32") +    #endif +    #ifndef CINTERFACE +    #define CINTERFACE +    #endif +    #ifndef COBJMACROS +    #define COBJMACROS +    #endif +    #ifndef CONST_VTABLE +    #define CONST_VTABLE +    #endif +    #include <mmdeviceapi.h> +    #include <audioclient.h> +    static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; +    static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, { 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } }; +    static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, { 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } }; +    static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; +    static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2 } }; +    static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} }; +    #if defined(__cplusplus) +    #define _SOKOL_AUDIO_WIN32COM_ID(x) (x) +    #else +    #define _SOKOL_AUDIO_WIN32COM_ID(x) (&x) +    #endif +    /* fix for Visual Studio 2015 SDKs */ +    #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM +    #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 +    #endif +    #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY +    #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 +    #endif +    #ifdef _MSC_VER +        #pragma warning(push) +        #pragma warning(disable:4505)   /* unreferenced local function has been removed */ +    #endif +#elif defined(_SAUDIO_APPLE) +    #define _SAUDIO_PTHREADS (1) +    #include <pthread.h> +    #if defined(_SAUDIO_IOS) +        // always use system headers on iOS (for now at least) +        #if !defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) +            #define SAUDIO_OSX_USE_SYSTEM_HEADERS (1) +        #endif +        #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 <AudioToolbox/AudioToolbox.h> +        #include <AVFoundation/AVFoundation.h> +    #else +        #if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) +            #include <AudioToolbox/AudioToolbox.h> +        #endif +    #endif +#elif defined(_SAUDIO_ANDROID) +    #define _SAUDIO_PTHREADS (1) +    #include <pthread.h> +    #include "SLES/OpenSLES_Android.h" +#elif defined(_SAUDIO_LINUX) +    #define _SAUDIO_PTHREADS (1) +    #include <pthread.h> +    #define ALSA_PCM_NEW_HW_PARAMS_API +    #include <alsa/asoundlib.h> +#elif defined(__EMSCRIPTEN__) +    #define _SAUDIO_NOTHREADS (1) +    #include <emscripten/emscripten.h> +#endif + +#define _saudio_def(val, def) (((val) == 0) ? (def) : (val)) +#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) + +#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100) +#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048) +#define _SAUDIO_DEFAULT_PACKET_FRAMES (128) +#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4) + +#ifndef SAUDIO_RING_MAX_SLOTS +#define SAUDIO_RING_MAX_SLOTS (1024) +#endif + +/*=== MUTEX WRAPPER DECLARATIONS =============================================*/ +#if defined(_SAUDIO_PTHREADS) + +typedef struct { +    pthread_mutex_t mutex; +} _saudio_mutex_t; + +#elif defined(_SAUDIO_WINTHREADS) + +typedef struct { +    CRITICAL_SECTION critsec; +} _saudio_mutex_t; + +#elif defined(_SAUDIO_NOTHREADS) + +typedef struct { +    int dummy_mutex; +} _saudio_mutex_t; + +#endif + +/*=== DUMMY BACKEND DECLARATIONS =============================================*/ +#if defined(SOKOL_DUMMY_BACKEND) + +typedef struct { +    int dummy_backend; +} _saudio_backend_t; + +/*=== COREAUDIO BACKEND DECLARATIONS =========================================*/ +#elif defined(_SAUDIO_APPLE) + +#if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) + +typedef AudioQueueRef _saudio_AudioQueueRef; +typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef; +typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription; +typedef OSStatus _saudio_OSStatus; + +#define _saudio_kAudioFormatLinearPCM (kAudioFormatLinearPCM) +#define _saudio_kLinearPCMFormatFlagIsFloat (kLinearPCMFormatFlagIsFloat) +#define _saudio_kAudioFormatFlagIsPacked (kAudioFormatFlagIsPacked) + +#else + +// embedded AudioToolbox declarations +typedef uint32_t _saudio_AudioFormatID; +typedef uint32_t _saudio_AudioFormatFlags; +typedef int32_t _saudio_OSStatus; +typedef uint32_t _saudio_SMPTETimeType; +typedef uint32_t _saudio_SMPTETimeFlags; +typedef uint32_t _saudio_AudioTimeStampFlags; +typedef void* _saudio_CFRunLoopRef; +typedef void* _saudio_CFStringRef; +typedef void* _saudio_AudioQueueRef; + +#define _saudio_kAudioFormatLinearPCM ('lpcm') +#define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0) +#define _saudio_kAudioFormatFlagIsPacked (1U << 3) + +typedef struct _saudio_AudioStreamBasicDescription { +    double mSampleRate; +    _saudio_AudioFormatID mFormatID; +    _saudio_AudioFormatFlags mFormatFlags; +    uint32_t mBytesPerPacket; +    uint32_t mFramesPerPacket; +    uint32_t mBytesPerFrame; +    uint32_t mChannelsPerFrame; +    uint32_t mBitsPerChannel; +    uint32_t mReserved; +} _saudio_AudioStreamBasicDescription; + +typedef struct _saudio_AudioStreamPacketDescription { +    int64_t mStartOffset; +    uint32_t mVariableFramesInPacket; +    uint32_t mDataByteSize; +} _saudio_AudioStreamPacketDescription; + +typedef struct _saudio_SMPTETime { +    int16_t mSubframes; +    int16_t mSubframeDivisor; +    uint32_t mCounter; +    _saudio_SMPTETimeType mType; +    _saudio_SMPTETimeFlags mFlags; +    int16_t mHours; +    int16_t mMinutes; +    int16_t mSeconds; +    int16_t mFrames; +} _saudio_SMPTETime; + +typedef struct _saudio_AudioTimeStamp { +    double mSampleTime; +    uint64_t mHostTime; +    double mRateScalar; +    uint64_t mWordClockTime; +    _saudio_SMPTETime mSMPTETime; +    _saudio_AudioTimeStampFlags mFlags; +    uint32_t mReserved; +} _saudio_AudioTimeStamp; + +typedef struct _saudio_AudioQueueBuffer { +    const uint32_t mAudioDataBytesCapacity; +    void* const mAudioData; +    uint32_t mAudioDataByteSize; +    void * mUserData; +    const uint32_t mPacketDescriptionCapacity; +    _saudio_AudioStreamPacketDescription* const mPacketDescriptions; +    uint32_t mPacketDescriptionCount; +} _saudio_AudioQueueBuffer; +typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef; + +typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer); + +extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ); +extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate); +extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer); +extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs); +extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime); +extern _saudio_OSStatus AudioQueueStop(_saudio_AudioQueueRef inAQ, bool inImmediate); +#endif // SAUDIO_OSX_USE_SYSTEM_HEADERS + +typedef struct { +    _saudio_AudioQueueRef ca_audio_queue; +    #if defined(_SAUDIO_IOS) +    id ca_interruption_handler; +    #endif +} _saudio_backend_t; + +/*=== ALSA BACKEND DECLARATIONS ==============================================*/ +#elif defined(_SAUDIO_LINUX) + +typedef struct { +    snd_pcm_t* device; +    float* buffer; +    int buffer_byte_size; +    int buffer_frames; +    pthread_t thread; +    bool thread_stop; +} _saudio_backend_t; + +/*=== OpenSLES BACKEND DECLARATIONS ==============================================*/ +#elif defined(_SAUDIO_ANDROID) + +#define SAUDIO_NUM_BUFFERS 2 + +typedef struct { +    pthread_mutex_t mutex; +    pthread_cond_t cond; +    int count; +} _saudio_semaphore_t; + +typedef struct { +    SLObjectItf engine_obj; +    SLEngineItf engine; +    SLObjectItf output_mix_obj; +    SLVolumeItf output_mix_vol; +    SLDataLocator_OutputMix out_locator; +    SLDataSink dst_data_sink; +    SLObjectItf player_obj; +    SLPlayItf player; +    SLVolumeItf player_vol; +    SLAndroidSimpleBufferQueueItf player_buffer_queue; + +    int16_t* output_buffers[SAUDIO_NUM_BUFFERS]; +    float* src_buffer; +    int active_buffer; +    _saudio_semaphore_t buffer_sem; +    pthread_t thread; +    volatile int thread_stop; +    SLDataLocator_AndroidSimpleBufferQueue in_locator; +} _saudio_backend_t; + +/*=== WASAPI BACKEND DECLARATIONS ============================================*/ +#elif defined(_SAUDIO_WINDOWS) + +typedef struct { +    HANDLE thread_handle; +    HANDLE buffer_end_event; +    bool stop; +    UINT32 dst_buffer_frames; +    int src_buffer_frames; +    int src_buffer_byte_size; +    int src_buffer_pos; +    float* src_buffer; +} _saudio_wasapi_thread_data_t; + +typedef struct { +    #if defined(_SAUDIO_UWP) +        LPOLESTR interface_activation_audio_interface_uid_string; +        IActivateAudioInterfaceAsyncOperation* interface_activation_operation; +        BOOL interface_activation_success; +        HANDLE interface_activation_mutex; +    #else +        IMMDeviceEnumerator* device_enumerator; +        IMMDevice* device; +    #endif +    IAudioClient* audio_client; +    IAudioRenderClient* render_client; +    int si16_bytes_per_frame; +    _saudio_wasapi_thread_data_t thread; +} _saudio_backend_t; + +/*=== WEBAUDIO BACKEND DECLARATIONS ==========================================*/ +#elif defined(_SAUDIO_EMSCRIPTEN) + +typedef struct { +    uint8_t* buffer; +} _saudio_backend_t; + +#else +#error "unknown platform" +#endif + +/*=== GENERAL DECLARATIONS ===================================================*/ + +/* a ringbuffer structure */ +typedef struct { +    int head;  // next slot to write to +    int tail;  // next slot to read from +    int num;   // number of slots in queue +    int queue[SAUDIO_RING_MAX_SLOTS]; +} _saudio_ring_t; + +/* a packet FIFO structure */ +typedef struct { +    bool valid; +    int packet_size;            /* size of a single packets in bytes(!) */ +    int num_packets;            /* number of packet in fifo */ +    uint8_t* base_ptr;          /* packet memory chunk base pointer (dynamically allocated) */ +    int cur_packet;             /* current write-packet */ +    int cur_offset;             /* current byte-offset into current write packet */ +    _saudio_mutex_t mutex;      /* mutex for thread-safe access */ +    _saudio_ring_t read_queue;  /* buffers with data, ready to be streamed */ +    _saudio_ring_t write_queue; /* empty buffers, ready to be pushed to */ +} _saudio_fifo_t; + +/* sokol-audio state */ +typedef struct { +    bool valid; +    void (*stream_cb)(float* buffer, int num_frames, int num_channels); +    void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); +    void* user_data; +    int sample_rate;            /* sample rate */ +    int buffer_frames;          /* number of frames in streaming buffer */ +    int bytes_per_frame;        /* filled by backend */ +    int packet_frames;          /* number of frames in a packet */ +    int num_packets;            /* number of packets in packet queue */ +    int num_channels;           /* actual number of channels */ +    saudio_desc desc; +    _saudio_fifo_t fifo; +    _saudio_backend_t backend; +} _saudio_state_t; + +static _saudio_state_t _saudio; + +_SOKOL_PRIVATE bool _saudio_has_callback(void) { +    return (_saudio.stream_cb || _saudio.stream_userdata_cb); +} + +_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) { +    if (_saudio.stream_cb) { +        _saudio.stream_cb(buffer, num_frames, num_channels); +    } +    else if (_saudio.stream_userdata_cb) { +        _saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data); +    } +} + +/*=== MUTEX IMPLEMENTATION ===================================================*/ +#if defined(_SAUDIO_NOTHREADS) + +_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; } +_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; } +_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; } +_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; } + +#elif defined(_SAUDIO_PTHREADS) + +_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { +    pthread_mutexattr_t attr; +    pthread_mutexattr_init(&attr); +    pthread_mutex_init(&m->mutex, &attr); +} + +_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { +    pthread_mutex_destroy(&m->mutex); +} + +_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { +    pthread_mutex_lock(&m->mutex); +} + +_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { +    pthread_mutex_unlock(&m->mutex); +} + +#elif defined(_SAUDIO_WINTHREADS) + +_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { +    InitializeCriticalSection(&m->critsec); +} + +_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { +    DeleteCriticalSection(&m->critsec); +} + +_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { +    EnterCriticalSection(&m->critsec); +} + +_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { +    LeaveCriticalSection(&m->critsec); +} +#else +#error "unknown platform!" +#endif + +/*=== RING-BUFFER QUEUE IMPLEMENTATION =======================================*/ +_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) { +    return (i % ring->num); +} + +_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) { +    SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS); +    ring->head = 0; +    ring->tail = 0; +    /* one slot reserved to detect 'full' vs 'empty' */ +    ring->num = num_slots + 1; +} + +_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) { +    return _saudio_ring_idx(ring, ring->head + 1) == ring->tail; +} + +_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) { +    return ring->head == ring->tail; +} + +_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) { +    int count; +    if (ring->head >= ring->tail) { +        count = ring->head - ring->tail; +    } +    else { +        count = (ring->head + ring->num) - ring->tail; +    } +    SOKOL_ASSERT(count < ring->num); +    return count; +} + +_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) { +    SOKOL_ASSERT(!_saudio_ring_full(ring)); +    ring->queue[ring->head] = val; +    ring->head = _saudio_ring_idx(ring, ring->head + 1); +} + +_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) { +    SOKOL_ASSERT(!_saudio_ring_empty(ring)); +    int val = ring->queue[ring->tail]; +    ring->tail = _saudio_ring_idx(ring, ring->tail + 1); +    return val; +} + +/*---  a packet fifo for queueing audio data from main thread ----------------*/ +_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) { +    /* this must be called before initializing both the backend and the fifo itself! */ +    _saudio_mutex_init(&fifo->mutex); +} + +_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) { +    /* NOTE: there's a chicken-egg situation during the init phase where the +        streaming thread must be started before the fifo is actually initialized, +        thus the fifo init must already be protected from access by the fifo_read() func. +    */ +    _saudio_mutex_lock(&fifo->mutex); +    SOKOL_ASSERT((packet_size > 0) && (num_packets > 0)); +    fifo->packet_size = packet_size; +    fifo->num_packets = num_packets; +    fifo->base_ptr = (uint8_t*) SOKOL_MALLOC((size_t)(packet_size * num_packets)); +    SOKOL_ASSERT(fifo->base_ptr); +    fifo->cur_packet = -1; +    fifo->cur_offset = 0; +    _saudio_ring_init(&fifo->read_queue, num_packets); +    _saudio_ring_init(&fifo->write_queue, num_packets); +    for (int i = 0; i < num_packets; i++) { +        _saudio_ring_enqueue(&fifo->write_queue, i); +    } +    SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue)); +    SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets); +    SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue)); +    SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0); +    fifo->valid = true; +    _saudio_mutex_unlock(&fifo->mutex); +} + +_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) { +    SOKOL_ASSERT(fifo->base_ptr); +    SOKOL_FREE(fifo->base_ptr); +    fifo->base_ptr = 0; +    fifo->valid = false; +    _saudio_mutex_destroy(&fifo->mutex); +} + +_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) { +    _saudio_mutex_lock(&fifo->mutex); +    int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size); +    if (fifo->cur_packet != -1) { +        num_bytes += fifo->packet_size - fifo->cur_offset; +    } +    _saudio_mutex_unlock(&fifo->mutex); +    SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size))); +    return num_bytes; +} + +/* write new data to the write queue, this is called from main thread */ +_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) { +    /* returns the number of bytes written, this will be smaller then requested +        if the write queue runs full +    */ +    int all_to_copy = num_bytes; +    while (all_to_copy > 0) { +        /* need to grab a new packet? */ +        if (fifo->cur_packet == -1) { +            _saudio_mutex_lock(&fifo->mutex); +            if (!_saudio_ring_empty(&fifo->write_queue)) { +                fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue); +            } +            _saudio_mutex_unlock(&fifo->mutex); +            SOKOL_ASSERT(fifo->cur_offset == 0); +        } +        /* append data to current write packet */ +        if (fifo->cur_packet != -1) { +            int to_copy = all_to_copy; +            const int max_copy = fifo->packet_size - fifo->cur_offset; +            if (to_copy > max_copy) { +                to_copy = max_copy; +            } +            uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset; +            memcpy(dst, ptr, (size_t)to_copy); +            ptr += to_copy; +            fifo->cur_offset += to_copy; +            all_to_copy -= to_copy; +            SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size); +            SOKOL_ASSERT(all_to_copy >= 0); +        } +        else { +            /* early out if we're starving */ +            int bytes_copied = num_bytes - all_to_copy; +            SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes)); +            return bytes_copied; +        } +        /* if write packet is full, push to read queue */ +        if (fifo->cur_offset == fifo->packet_size) { +            _saudio_mutex_lock(&fifo->mutex); +            _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet); +            _saudio_mutex_unlock(&fifo->mutex); +            fifo->cur_packet = -1; +            fifo->cur_offset = 0; +        } +    } +    SOKOL_ASSERT(all_to_copy == 0); +    return num_bytes; +} + +/* read queued data, this is called form the stream callback (maybe separate thread) */ +_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) { +    /* NOTE: fifo_read might be called before the fifo is properly initialized */ +    _saudio_mutex_lock(&fifo->mutex); +    int num_bytes_copied = 0; +    if (fifo->valid) { +        SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size)); +        SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets)); +        const int num_packets_needed = num_bytes / fifo->packet_size; +        uint8_t* dst = ptr; +        /* either pull a full buffer worth of data, or nothing */ +        if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) { +            for (int i = 0; i < num_packets_needed; i++) { +                int packet_index = _saudio_ring_dequeue(&fifo->read_queue); +                _saudio_ring_enqueue(&fifo->write_queue, packet_index); +                const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size; +                memcpy(dst, src, (size_t)fifo->packet_size); +                dst += fifo->packet_size; +                num_bytes_copied += fifo->packet_size; +            } +            SOKOL_ASSERT(num_bytes == num_bytes_copied); +        } +    } +    _saudio_mutex_unlock(&fifo->mutex); +    return num_bytes_copied; +} + +/*=== DUMMY BACKEND IMPLEMENTATION ===========================================*/ +#if defined(SOKOL_DUMMY_BACKEND) +_SOKOL_PRIVATE bool _saudio_backend_init(void) { +    _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); +    return true; +}; +_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { }; + +/*=== COREAUDIO BACKEND IMPLEMENTATION =======================================*/ +#elif defined(_SAUDIO_APPLE) + +#if defined(_SAUDIO_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 // _SAUDIO_IOS + +/* NOTE: the buffer data callback is called on a separate thread! */ +_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, _saudio_AudioQueueRef queue, _saudio_AudioQueueBufferRef buffer) { +    _SOKOL_UNUSED(user_data); +    if (_saudio_has_callback()) { +        const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame; +        const int num_channels = _saudio.num_channels; +        _saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels); +    } +    else { +        uint8_t* ptr = (uint8_t*)buffer->mAudioData; +        int num_bytes = (int) buffer->mAudioDataByteSize; +        if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) { +            /* not enough read data available, fill the entire buffer with silence */ +            memset(ptr, 0, (size_t)num_bytes); +        } +    } +    AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); +} + +_SOKOL_PRIVATE bool _saudio_backend_init(void) { +    SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue); + +    #if defined(_SAUDIO_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 // _SAUDIO_IOS + +    /* create an audio queue with fp32 samples */ +    _saudio_AudioStreamBasicDescription fmt; +    memset(&fmt, 0, sizeof(fmt)); +    fmt.mSampleRate = (double) _saudio.sample_rate; +    fmt.mFormatID = _saudio_kAudioFormatLinearPCM; +    fmt.mFormatFlags = _saudio_kLinearPCMFormatFlagIsFloat | _saudio_kAudioFormatFlagIsPacked; +    fmt.mFramesPerPacket = 1; +    fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels; +    fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels; +    fmt.mBytesPerPacket = fmt.mBytesPerFrame; +    fmt.mBitsPerChannel = 32; +    _saudio_OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue); +    SOKOL_ASSERT((res == 0) && _saudio.backend.ca_audio_queue); + +    /* create 2 audio buffers */ +    for (int i = 0; i < 2; i++) { +        _saudio_AudioQueueBufferRef buf = NULL; +        const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame; +        res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf); +        SOKOL_ASSERT((res == 0) && buf); +        buf->mAudioDataByteSize = buf_byte_size; +        memset(buf->mAudioData, 0, buf->mAudioDataByteSize); +        AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL); +    } + +    /* init or modify actual playback parameters */ +    _saudio.bytes_per_frame = (int)fmt.mBytesPerFrame; + +    /* ...and start playback */ +    res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL); +    SOKOL_ASSERT(0 == res); + +    return true; +} + +_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 defined(_SAUDIO_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 // _SAUDIO_IOS +} + +/*=== ALSA BACKEND IMPLEMENTATION ============================================*/ +#elif defined(_SAUDIO_LINUX) + +/* the streaming callback runs in a separate thread */ +_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) { +    _SOKOL_UNUSED(param); +    while (!_saudio.backend.thread_stop) { +        /* snd_pcm_writei() will be blocking until it needs data */ +        int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames); +        if (write_res < 0) { +            /* underrun occurred */ +            snd_pcm_prepare(_saudio.backend.device); +        } +        else { +            /* fill the streaming buffer with new data */ +            if (_saudio_has_callback()) { +                _saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels); +            } +            else { +                if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) { +                    /* not enough read data available, fill the entire buffer with silence */ +                    memset(_saudio.backend.buffer, 0, (size_t)_saudio.backend.buffer_byte_size); +                } +            } +        } +    } +    return 0; +} + +_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"); +        return false; +    } + +    /* configuration works by restricting the 'configuration space' step +       by step, we require all parameters except the sample rate to +       match perfectly +    */ +    snd_pcm_hw_params_t* params = 0; +    snd_pcm_hw_params_alloca(¶ms); +    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"); +        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"); +        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"); +        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"); +        goto error; +    } +    if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) { +        SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params() failed"); +        goto error; +    } + +    /* read back actual sample rate and channels */ +    _saudio.sample_rate = (int)rate; +    _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); + +    /* allocate the streaming buffer */ +    _saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame; +    _saudio.backend.buffer_frames = _saudio.buffer_frames; +    _saudio.backend.buffer = (float*) SOKOL_MALLOC((size_t)_saudio.backend.buffer_byte_size); +    memset(_saudio.backend.buffer, 0, (size_t)_saudio.backend.buffer_byte_size); + +    /* 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"); +        goto error; +    } + +    return true; +error: +    if (_saudio.backend.device) { +        snd_pcm_close(_saudio.backend.device); +        _saudio.backend.device = 0; +    } +    return false; +}; + +_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { +    SOKOL_ASSERT(_saudio.backend.device); +    _saudio.backend.thread_stop = true; +    pthread_join(_saudio.backend.thread, 0); +    snd_pcm_drain(_saudio.backend.device); +    snd_pcm_close(_saudio.backend.device); +    SOKOL_FREE(_saudio.backend.buffer); +}; + +/*=== WASAPI BACKEND IMPLEMENTATION ==========================================*/ +#elif defined(_SAUDIO_WINDOWS) + +#if defined(_SAUDIO_UWP) +/* Minimal implementation of an IActivateAudioInterfaceCompletionHandler COM object in plain C. +   Meant to be a static singleton (always one reference when add/remove reference) +   and implements IUnknown and IActivateAudioInterfaceCompletionHandler when queryinterface'd + +   Do not know why but IActivateAudioInterfaceCompletionHandler's GUID is not the one system queries for, +   so I'm advertising the one actually requested. +*/ +_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_interface_completion_handler_queryinterface(IActivateAudioInterfaceCompletionHandler* instance, REFIID riid, void** ppvObject) { +    if (!ppvObject) { +        return E_POINTER; +    } + +    if (IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IActivateAudioInterface_Completion_Handler)) || IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(IID_IUnknown))) +    { +        *ppvObject = (void*)instance; +        return S_OK; +    } + +    *ppvObject = NULL; +    return E_NOINTERFACE; +} + +_SOKOL_PRIVATE ULONG STDMETHODCALLTYPE _saudio_interface_completion_handler_addref_release(IActivateAudioInterfaceCompletionHandler* instance) { +    _SOKOL_UNUSED(instance); +    return 1; +} + +_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_backend_activate_audio_interface_cb(IActivateAudioInterfaceCompletionHandler* instance, IActivateAudioInterfaceAsyncOperation* activateOperation) { +    _SOKOL_UNUSED(instance); +    WaitForSingleObject(_saudio.backend.interface_activation_mutex, INFINITE); +    _saudio.backend.interface_activation_success = TRUE; +    HRESULT activation_result; +    if (FAILED(activateOperation->lpVtbl->GetActivateResult(activateOperation, &activation_result, (IUnknown**)(&_saudio.backend.audio_client))) || FAILED(activation_result)) { +        _saudio.backend.interface_activation_success = FALSE; +    } + +    ReleaseMutex(_saudio.backend.interface_activation_mutex); +    return S_OK; +} +#endif // _SAUDIO_UWP + +/* fill intermediate buffer with new data and reset buffer_pos */ +_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) { +    if (_saudio_has_callback()) { +        _saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels); +    } +    else { +        if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) { +            /* not enough read data available, fill the entire buffer with silence */ +            memset(_saudio.backend.thread.src_buffer, 0, (size_t)_saudio.backend.thread.src_buffer_byte_size); +        } +    } +} + +_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) { +    BYTE* wasapi_buffer = 0; +    if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) { +        return; +    } +    SOKOL_ASSERT(wasapi_buffer); + +    /* convert float samples to int16_t, refill float buffer if needed */ +    const int num_samples = num_frames * _saudio.num_channels; +    int16_t* dst = (int16_t*) wasapi_buffer; +    int buffer_pos = _saudio.backend.thread.src_buffer_pos; +    const int buffer_float_size = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float); +    float* src = _saudio.backend.thread.src_buffer; +    for (int i = 0; i < num_samples; i++) { +        if (0 == buffer_pos) { +            _saudio_wasapi_fill_buffer(); +        } +        dst[i] = (int16_t) (src[buffer_pos] * 0x7FFF); +        buffer_pos += 1; +        if (buffer_pos == buffer_float_size) { +            buffer_pos = 0; +        } +    } +    _saudio.backend.thread.src_buffer_pos = buffer_pos; + +    IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0); +} + +_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) { +    (void)param; +    _saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames); +    IAudioClient_Start(_saudio.backend.audio_client); +    while (!_saudio.backend.thread.stop) { +        WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE); +        UINT32 padding = 0; +        if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) { +            continue; +        } +        SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding); +        int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding; +        if (num_frames > 0) { +            _saudio_wasapi_submit_buffer(num_frames); +        } +    } +    return 0; +} + +_SOKOL_PRIVATE void _saudio_wasapi_release(void) { +    if (_saudio.backend.thread.src_buffer) { +        SOKOL_FREE(_saudio.backend.thread.src_buffer); +        _saudio.backend.thread.src_buffer = 0; +    } +    if (_saudio.backend.render_client) { +        IAudioRenderClient_Release(_saudio.backend.render_client); +        _saudio.backend.render_client = 0; +    } +    if (_saudio.backend.audio_client) { +        IAudioClient_Release(_saudio.backend.audio_client); +        _saudio.backend.audio_client = 0; +    } +    #if defined(_SAUDIO_UWP) +        if (_saudio.backend.interface_activation_audio_interface_uid_string) { +            CoTaskMemFree(_saudio.backend.interface_activation_audio_interface_uid_string); +            _saudio.backend.interface_activation_audio_interface_uid_string = 0; +        } +        if (_saudio.backend.interface_activation_operation) { +            IActivateAudioInterfaceAsyncOperation_Release(_saudio.backend.interface_activation_operation); +            _saudio.backend.interface_activation_operation = 0; +        } +    #else +        if (_saudio.backend.device) { +            IMMDevice_Release(_saudio.backend.device); +            _saudio.backend.device = 0; +        } +        if (_saudio.backend.device_enumerator) { +            IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator); +            _saudio.backend.device_enumerator = 0; +        } +    #endif +    if (0 != _saudio.backend.thread.buffer_end_event) { +        CloseHandle(_saudio.backend.thread.buffer_end_event); +        _saudio.backend.thread.buffer_end_event = 0; +    } +} + +_SOKOL_PRIVATE bool _saudio_backend_init(void) { +    REFERENCE_TIME dur; +    /* UWP Threads are CoInitialized by default with a different threading model, and this call fails +    See https://github.com/Microsoft/cppwinrt/issues/6#issuecomment-253930637 */ +    #if defined(_SAUDIO_WIN32) +        /* CoInitializeEx could have been called elsewhere already, in which +            case the function returns with S_FALSE (thus it does not make much +            sense to check the result) +        */ +        HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); +        _SOKOL_UNUSED(hr); +    #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"); +        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"); +            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"); +            goto error; +        } + +        /* static instance of the fake COM object */ +        static IActivateAudioInterfaceCompletionHandlerVtbl completion_handler_interface_vtable = { +            _saudio_interface_completion_handler_queryinterface, +            _saudio_interface_completion_handler_addref_release, +            _saudio_interface_completion_handler_addref_release, +            _saudio_backend_activate_audio_interface_cb +        }; +        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"); +            goto error; +        } +        while (!(_saudio.backend.audio_client)) { +            if (WaitForSingleObject(_saudio.backend.interface_activation_mutex, 10) != WAIT_TIMEOUT) { +                ReleaseMutex(_saudio.backend.interface_activation_mutex); +            } +        } + +        if (!(_saudio.backend.interface_activation_success)) { +            SOKOL_LOG("sokol_audio wasapi: interface activation failed. Unable to get audio client"); +            goto error; +        } + +    #else +        if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator), +            0, CLSCTX_ALL, +            _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator), +            (void**)&_saudio.backend.device_enumerator))) +        { +            SOKOL_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"); +            goto error; +        } +        if (FAILED(IMMDevice_Activate(_saudio.backend.device, +            _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), +            CLSCTX_ALL, 0, +            (void**)&_saudio.backend.audio_client))) +        { +            SOKOL_LOG("sokol_audio wasapi: device activate failed"); +            goto error; +        } +    #endif +    WAVEFORMATEX fmt; +    memset(&fmt, 0, sizeof(fmt)); +    fmt.nChannels = (WORD)_saudio.num_channels; +    fmt.nSamplesPerSec = (DWORD)_saudio.sample_rate; +    fmt.wFormatTag = WAVE_FORMAT_PCM; +    fmt.wBitsPerSample = 16; +    fmt.nBlockAlign = (fmt.nChannels * fmt.wBitsPerSample) / 8; +    fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; +    dur = (REFERENCE_TIME) +        (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0))); +    if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client, +        AUDCLNT_SHAREMODE_SHARED, +        AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, +        dur, 0, &fmt, 0))) +    { +        SOKOL_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"); +        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"); +        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"); +        goto error; +    } +    _saudio.backend.si16_bytes_per_frame = _saudio.num_channels * (int)sizeof(int16_t); +    _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); +    _saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames; +    _saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame; + +    /* allocate an intermediate buffer for sample format conversion */ +    _saudio.backend.thread.src_buffer = (float*) SOKOL_MALLOC((size_t)_saudio.backend.thread.src_buffer_byte_size); +    SOKOL_ASSERT(_saudio.backend.thread.src_buffer); + +    /* 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"); +        goto error; +    } +    return true; +error: +    _saudio_wasapi_release(); +    return false; +} + +_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { +    if (_saudio.backend.thread.thread_handle) { +        _saudio.backend.thread.stop = true; +        SetEvent(_saudio.backend.thread.buffer_end_event); +        WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE); +        CloseHandle(_saudio.backend.thread.thread_handle); +        _saudio.backend.thread.thread_handle = 0; +    } +    if (_saudio.backend.audio_client) { +        IAudioClient_Stop(_saudio.backend.audio_client); +    } +    _saudio_wasapi_release(); + +    #if defined(_SAUDIO_WIN32) +        CoUninitialize(); +    #endif +} + +/*=== EMSCRIPTEN BACKEND IMPLEMENTATION ======================================*/ +#elif defined(_SAUDIO_EMSCRIPTEN) + +#ifdef __cplusplus +extern "C" { +#endif + +EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) { +    SOKOL_ASSERT(_saudio.backend.buffer); +    if (num_frames == _saudio.buffer_frames) { +        if (_saudio_has_callback()) { +            _saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels); +        } +        else { +            const int num_bytes = num_frames * _saudio.bytes_per_frame; +            if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) { +                /* not enough read data available, fill the entire buffer with silence */ +                memset(_saudio.backend.buffer, 0, (size_t)num_bytes); +            } +        } +        int res = (int) _saudio.backend.buffer; +        return res; +    } +    else { +        return 0; +    } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* setup the WebAudio context and attach a ScriptProcessorNode */ +EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), { +    Module._saudio_context = null; +    Module._saudio_node = null; +    if (typeof AudioContext !== 'undefined') { +        Module._saudio_context = new AudioContext({ +            sampleRate: sample_rate, +            latencyHint: 'interactive', +        }); +    } +    else if (typeof webkitAudioContext !== 'undefined') { +        Module._saudio_context = new webkitAudioContext({ +            sampleRate: sample_rate, +            latencyHint: 'interactive', +        }); +    } +    else { +        Module._saudio_context = null; +        console.log('sokol_audio.h: no WebAudio support'); +    } +    if (Module._saudio_context) { +        console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate); +        Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels); +        Module._saudio_node.onaudioprocess = function pump_audio(event) { +            var num_frames = event.outputBuffer.length; +            var ptr = __saudio_emsc_pull(num_frames); +            if (ptr) { +                var num_channels = event.outputBuffer.numberOfChannels; +                for (var chn = 0; chn < num_channels; chn++) { +                    var chan = event.outputBuffer.getChannelData(chn); +                    for (var i = 0; i < num_frames; i++) { +                        chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)] +                    } +                } +            } +        }; +        Module._saudio_node.connect(Module._saudio_context.destination); + +        // in some browsers, WebAudio needs to be activated on a user action +        var resume_webaudio = function() { +            if (Module._saudio_context) { +                if (Module._saudio_context.state === 'suspended') { +                    Module._saudio_context.resume(); +                } +            } +        }; +        document.addEventListener('click', resume_webaudio, {once:true}); +        document.addEventListener('touchstart', resume_webaudio, {once:true}); +        document.addEventListener('keydown', resume_webaudio, {once:true}); +        return 1; +    } +    else { +        return 0; +    } +}); + +/* shutdown the WebAudioContext and ScriptProcessorNode */ +EM_JS(void, saudio_js_shutdown, (void), { +    if (Module._saudio_context !== null) { +        if (Module._saudio_node) { +            Module._saudio_node.disconnect(); +        } +        Module._saudio_context.close(); +        Module._saudio_context = null; +        Module._saudio_node = null; +    } +}); + +/* get the actual sample rate back from the WebAudio context */ +EM_JS(int, saudio_js_sample_rate, (void), { +    if (Module._saudio_context) { +        return Module._saudio_context.sampleRate; +    } +    else { +        return 0; +    } +}); + +/* get the actual buffer size in number of frames */ +EM_JS(int, saudio_js_buffer_frames, (void), { +    if (Module._saudio_node) { +        return Module._saudio_node.bufferSize; +    } +    else { +        return 0; +    } +}); + +_SOKOL_PRIVATE bool _saudio_backend_init(void) { +    if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) { +        _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels; +        _saudio.sample_rate = saudio_js_sample_rate(); +        _saudio.buffer_frames = saudio_js_buffer_frames(); +        const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame); +        _saudio.backend.buffer = (uint8_t*) SOKOL_MALLOC(buf_size); +        return true; +    } +    else { +        return false; +    } +} + +_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { +    saudio_js_shutdown(); +    if (_saudio.backend.buffer) { +        SOKOL_FREE(_saudio.backend.buffer); +        _saudio.backend.buffer = 0; +    } +} + +/*=== ANDROID BACKEND IMPLEMENTATION ======================================*/ +#elif defined(_SAUDIO_ANDROID) + +#ifdef __cplusplus +extern "C" { +#endif + +_SOKOL_PRIVATE void _saudio_semaphore_init(_saudio_semaphore_t* sem) { +    sem->count = 0; +    int r = pthread_mutex_init(&sem->mutex, NULL); +    SOKOL_ASSERT(r == 0); + +    r = pthread_cond_init(&sem->cond, NULL); +    SOKOL_ASSERT(r == 0); + +    (void)(r); +} + +_SOKOL_PRIVATE void _saudio_semaphore_destroy(_saudio_semaphore_t* sem) +{ +    pthread_cond_destroy(&sem->cond); +    pthread_mutex_destroy(&sem->mutex); +} + +_SOKOL_PRIVATE void _saudio_semaphore_post(_saudio_semaphore_t* sem, int count) +{ +    int r = pthread_mutex_lock(&sem->mutex); +    SOKOL_ASSERT(r == 0); + +    for (int ii = 0; ii < count; ii++) { +        r = pthread_cond_signal(&sem->cond); +        SOKOL_ASSERT(r == 0); +    } + +    sem->count += count; +    r = pthread_mutex_unlock(&sem->mutex); +    SOKOL_ASSERT(r == 0); + +    (void)(r); +} + +_SOKOL_PRIVATE bool _saudio_semaphore_wait(_saudio_semaphore_t* sem) +{ +    int r = pthread_mutex_lock(&sem->mutex); +    SOKOL_ASSERT(r == 0); + +    while (r == 0 && sem->count <= 0) { +        r = pthread_cond_wait(&sem->cond, &sem->mutex); +    } + +    bool ok = (r == 0); +    if (ok) { +        --sem->count; +    } +    r = pthread_mutex_unlock(&sem->mutex); +    (void)(r); +    return ok; +} + +/* fill intermediate buffer with new data and reset buffer_pos */ +_SOKOL_PRIVATE void _saudio_opensles_fill_buffer(void) { +    int src_buffer_frames = _saudio.buffer_frames; +    if (_saudio_has_callback()) { +        _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels); +    } +    else { +        const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float); +        if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) { +            /* not enough read data available, fill the entire buffer with silence */ +            memset(_saudio.backend.src_buffer, 0x0, (size_t)src_buffer_byte_size); +        } +    } +} + +_SOKOL_PRIVATE void SLAPIENTRY _saudio_opensles_play_cb(SLPlayItf player, void *context, SLuint32 event) { +    (void)(context); +    (void)(player); + +    if (event & SL_PLAYEVENT_HEADATEND) { +        _saudio_semaphore_post(&_saudio.backend.buffer_sem, 1); +    } +} + +_SOKOL_PRIVATE void* _saudio_opensles_thread_fn(void* param) { +    while (!_saudio.backend.thread_stop)  { +        /* get next output buffer, advance, next buffer. */ +        int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer]; +        _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS; +        int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer]; + +        /* queue this buffer */ +        const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short); +        (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes); + +        /* fill the next buffer */ +        _saudio_opensles_fill_buffer(); +        const int num_samples = _saudio.num_channels * _saudio.buffer_frames; +        for (int i = 0; i < num_samples; ++i) { +            next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF); +        } + +        _saudio_semaphore_wait(&_saudio.backend.buffer_sem); +    } + +    return 0; +} + +_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { +    _saudio.backend.thread_stop = 1; +    pthread_join(_saudio.backend.thread, 0); + +    if (_saudio.backend.player_obj) { +        (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj); +    } + +    if (_saudio.backend.output_mix_obj) { +        (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj); +    } + +    if (_saudio.backend.engine_obj) { +        (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj); +    } + +    for (int i = 0; i < SAUDIO_NUM_BUFFERS; i++) { +        SOKOL_FREE(_saudio.backend.output_buffers[i]); +    } +    SOKOL_FREE(_saudio.backend.src_buffer); +} + +_SOKOL_PRIVATE bool _saudio_backend_init(void) { +    _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels; + +    for (int i = 0; i < SAUDIO_NUM_BUFFERS; ++i) { +        const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames; +        _saudio.backend.output_buffers[i] = (int16_t*) SOKOL_MALLOC((size_t)buffer_size_bytes); +        SOKOL_ASSERT(_saudio.backend.output_buffers[i]); +        memset(_saudio.backend.output_buffers[i], 0x0, (size_t)buffer_size_bytes); +    } + +    { +        const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames; +        _saudio.backend.src_buffer = (float*) SOKOL_MALLOC((size_t)buffer_size_bytes); +        SOKOL_ASSERT(_saudio.backend.src_buffer); +        memset(_saudio.backend.src_buffer, 0x0, (size_t)buffer_size_bytes); +    } + +    /* 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_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_backend_shutdown(); +        return false; +    } + +    /* Create output mix. */ +    { +        const SLInterfaceID ids[] = { SL_IID_VOLUME }; +        const SLboolean req[] = { SL_BOOLEAN_FALSE }; + +        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_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"); +        } +    } + +    /* android buffer queue */ +    _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; +    _saudio.backend.in_locator.numBuffers = SAUDIO_NUM_BUFFERS; + +    /* data format */ +    SLDataFormat_PCM format; +    format.formatType = SL_DATAFORMAT_PCM; +    format.numChannels = (SLuint32)_saudio.num_channels; +    format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000); +    format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; +    format.containerSize = 16; +    format.endianness = SL_BYTEORDER_LITTLEENDIAN; + +    if (_saudio.num_channels == 2) { +        format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; +    } else { +        format.channelMask = SL_SPEAKER_FRONT_CENTER; +    } + +    SLDataSource src; +    src.pLocator = &_saudio.backend.in_locator; +    src.pFormat = &format; + +    /* Output mix. */ +    _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX; +    _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj; + +    _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator; +    _saudio.backend.dst_data_sink.pFormat = NULL; + +    /* setup player */ +    { +        const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; +        const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE }; + +        (*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req); + +        (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE); + +        (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player); +        (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol); + +        (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue); +    } + +    /* begin */ +    { +        const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames; +        (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes); +        _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS; + +        (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_opensles_play_cb, NULL); +        (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND); +        (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING); +    } + +    /* create the buffer-streaming start thread */ +    if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_opensles_thread_fn, 0)) { +        _saudio_backend_shutdown(); +        return false; +    } + +    return true; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#else +#error "unsupported platform" +#endif + +/*=== PUBLIC API FUNCTIONS ===================================================*/ +SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) { +    SOKOL_ASSERT(!_saudio.valid); +    SOKOL_ASSERT(desc); +    memset(&_saudio, 0, sizeof(_saudio)); +    _saudio.desc = *desc; +    _saudio.stream_cb = desc->stream_cb; +    _saudio.stream_userdata_cb = desc->stream_userdata_cb; +    _saudio.user_data = desc->user_data; +    _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE); +    _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES); +    _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES); +    _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS); +    _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1); +    _saudio_fifo_init_mutex(&_saudio.fifo); +    if (_saudio_backend_init()) { +        /* the backend might not support the requested exact buffer size, +           make sure the actual buffer size is still a multiple of +           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_backend_shutdown(); +            return; +        } +        SOKOL_ASSERT(_saudio.bytes_per_frame > 0); +        _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets); +        _saudio.valid = true; +    } +} + +SOKOL_API_IMPL void saudio_shutdown(void) { +    if (_saudio.valid) { +        _saudio_backend_shutdown(); +        _saudio_fifo_shutdown(&_saudio.fifo); +        _saudio.valid = false; +    } +} + +SOKOL_API_IMPL bool saudio_isvalid(void) { +    return _saudio.valid; +} + +SOKOL_API_IMPL void* saudio_userdata(void) { +    return _saudio.desc.user_data; +} + +SOKOL_API_IMPL saudio_desc saudio_query_desc(void) { +    return _saudio.desc; +} + +SOKOL_API_IMPL int saudio_sample_rate(void) { +    return _saudio.sample_rate; +} + +SOKOL_API_IMPL int saudio_buffer_frames(void) { +    return _saudio.buffer_frames; +} + +SOKOL_API_IMPL int saudio_channels(void) { +    return _saudio.num_channels; +} + +SOKOL_API_IMPL int saudio_expect(void) { +    if (_saudio.valid) { +        const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame; +        return num_frames; +    } +    else { +        return 0; +    } +} + +SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) { +    SOKOL_ASSERT(frames && (num_frames > 0)); +    if (_saudio.valid) { +        const int num_bytes = num_frames * _saudio.bytes_per_frame; +        const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes); +        return num_written / _saudio.bytes_per_frame; +    } +    else { +        return 0; +    } +} + +#undef _saudio_def +#undef _saudio_def_flt + +#if defined(_SAUDIO_WINDOWS) +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif + +#endif /* SOKOL_AUDIO_IMPL */ diff --git a/v_windows/v/old/thirdparty/sokol/sokol_gfx.h b/v_windows/v/old/thirdparty/sokol/sokol_gfx.h new file mode 100644 index 0000000..9049ae2 --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/sokol_gfx.h @@ -0,0 +1,15672 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GFX_IMPL) +#define SOKOL_GFX_IMPL +#endif +#ifndef SOKOL_GFX_INCLUDED +/* +    sokol_gfx.h -- simple 3D API wrapper + +    Project URL: https://github.com/floooh/sokol + +    Example code: https://github.com/floooh/sokol-samples + +    Do this: +        #define SOKOL_IMPL or +        #define SOKOL_GFX_IMPL +    before you include this file in *one* C or C++ file to create the +    implementation. + +    In the same place define one of the following to select the rendering +    backend: +        #define SOKOL_GLCORE33 +        #define SOKOL_GLES2 +        #define SOKOL_GLES3 +        #define SOKOL_D3D11 +        #define SOKOL_METAL +        #define SOKOL_WGPU +        #define SOKOL_DUMMY_BACKEND + +    I.e. for the GL 3.3 Core Profile it should look like this: + +    #include ... +    #include ... +    #define SOKOL_IMPL +    #define SOKOL_GLCORE33 +    #include "sokol_gfx.h" + +    The dummy backend replaces the platform-specific backend code with empty +    stub functions. This is useful for writing tests that need to run on the +    command line. + +    Optionally provide the following defines with your own implementations: + +    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_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 +    SOKOL_API_IMPL              - public function implementation prefix (default: -) +    SOKOL_TRACE_HOOKS           - enable trace hook callbacks (search below for TRACE HOOKS) +    SOKOL_EXTERNAL_GL_LOADER    - indicates that you're using your own GL loader, in this case +                                  sokol_gfx.h will not include any platform GL headers and disable +                                  the integrated Win32 GL loader + +    If sokol_gfx.h is compiled as a DLL, define the following before +    including the declaration or implementation: + +    SOKOL_DLL + +    On Windows, SOKOL_DLL will define SOKOL_GFX_API_DECL as __declspec(dllexport) +    or __declspec(dllimport) as needed. + +    If you want to compile without deprecated structs and functions, +    define: + +    SOKOL_NO_DEPRECATED + +    Optionally define the following to force debug checks and validations +    even in release mode: + +    SOKOL_DEBUG         - by default this is defined if _DEBUG is defined + +    sokol_gfx DOES NOT: +    =================== +    - create a window or the 3D-API context/device, you must do this +      before sokol_gfx is initialized, and pass any required information +      (like 3D device pointers) to the sokol_gfx initialization call + +    - present the rendered frame, how this is done exactly usually depends +      on how the window and 3D-API context/device was created + +    - provide a unified shader language, instead 3D-API-specific shader +      source-code or shader-bytecode must be provided (for the "official" +      offline shader cross-compiler, see here: +      https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + +    STEP BY STEP +    ============ +    --- to initialize sokol_gfx, after creating a window and a 3D-API +        context/device, call: + +            sg_setup(const sg_desc*) + +    --- create resource objects (at least buffers, shaders and pipelines, +        and optionally images and passes): + +            sg_buffer sg_make_buffer(const sg_buffer_desc*) +            sg_image sg_make_image(const sg_image_desc*) +            sg_shader sg_make_shader(const sg_shader_desc*) +            sg_pipeline sg_make_pipeline(const sg_pipeline_desc*) +            sg_pass sg_make_pass(const sg_pass_desc*) + +    --- start rendering to the default frame buffer with: + +            sg_begin_default_pass(const sg_pass_action* action, int width, int height) + +        ...or alternatively with: + +            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* action) + +    --- set the pipeline state for the next draw call with: + +            sg_apply_pipeline(sg_pipeline pip) + +    --- fill an sg_bindings struct with the resource bindings for the next +        draw call (1..N vertex buffers, 0 or 1 index buffer, 0..N image objects +        to use as textures each on the vertex-shader- and fragment-shader-stage +        and then call + +            sg_apply_bindings(const sg_bindings* bindings) + +        to update the resource bindings + +    --- optionally update shader uniform data with: + +            sg_apply_uniforms(sg_shader_stage stage, int ub_index, const void* data, int num_bytes) + +    --- kick off a draw call with: + +            sg_draw(int base_element, int num_elements, int num_instances) + +        The sg_draw() function unifies all the different ways to render primitives +        in a single call (indexed vs non-indexed rendering, and instanced vs non-instanced +        rendering). In case of indexed rendering, base_element and num_element specify +        indices in the currently bound index buffer. In case of non-indexed rendering +        base_element and num_elements specify vertices in the currently bound +        vertex-buffer(s). To perform instanced rendering, the rendering pipeline +        must be setup for instancing (see sg_pipeline_desc below), a separate vertex buffer +        containing per-instance data must be bound, and the num_instances parameter +        must be > 1. + +    --- finish the current rendering pass with: + +            sg_end_pass() + +    --- when done with the current frame, call + +            sg_commit() + +    --- at the end of your program, shutdown sokol_gfx with: + +            sg_shutdown() + +    --- if you need to destroy resources before sg_shutdown(), call: + +            sg_destroy_buffer(sg_buffer buf) +            sg_destroy_image(sg_image img) +            sg_destroy_shader(sg_shader shd) +            sg_destroy_pipeline(sg_pipeline pip) +            sg_destroy_pass(sg_pass pass) + +    --- to set a new viewport rectangle, call + +            sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) + +        ...or if you want to specifiy the viewport rectangle with float values: + +            sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) + +    --- to set a new scissor rect, call: + +            sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) + +        ...or with float values: + +            sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) + +        Both sg_apply_viewport() and sg_apply_scissor_rect() must be called +        inside a rendering pass + +        Note that sg_begin_default_pass() and sg_begin_pass() will reset both the +        viewport and scissor rectangles to cover the entire framebuffer. + +    --- to update (overwrite) the content of buffer and image resources, call: + +            sg_update_buffer(sg_buffer buf, const sg_range* data) +            sg_update_image(sg_image img, const sg_image_data* data) + +        Buffers and images to be updated must have been created with +        SG_USAGE_DYNAMIC or SG_USAGE_STREAM + +        Only one update per frame is allowed for buffer and image resources when +        using the sg_update_*() functions. The rationale is to have a simple +        countermeasure to avoid the CPU scribbling over data the GPU is currently +        using, or the CPU having to wait for the GPU + +        Buffer and image updates can be partial, as long as a rendering +        operation only references the valid (updated) data in the +        buffer or image. + +    --- to append a chunk of data to a buffer resource, call: + +            int sg_append_buffer(sg_buffer buf, const sg_range* data) + +        The difference to sg_update_buffer() is that sg_append_buffer() +        can be called multiple times per frame to append new data to the +        buffer piece by piece, optionally interleaved with draw calls referencing +        the previously written data. + +        sg_append_buffer() returns a byte offset to the start of the +        written data, this offset can be assigned to +        sg_bindings.vertex_buffer_offsets[n] or +        sg_bindings.index_buffer_offset + +        Code example: + +        for (...) { +            const void* data = ...; +            const int num_bytes = ...; +            int offset = sg_append_buffer(buf, &(sg_range) { .ptr=data, .size=num_bytes }); +            bindings.vertex_buffer_offsets[0] = offset; +            sg_apply_pipeline(pip); +            sg_apply_bindings(&bindings); +            sg_apply_uniforms(...); +            sg_draw(...); +        } + +        A buffer to be used with sg_append_buffer() must have been created +        with SG_USAGE_DYNAMIC or SG_USAGE_STREAM. + +        If the application appends more data to the buffer then fits into +        the buffer, the buffer will go into the "overflow" state for the +        rest of the frame. + +        Any draw calls attempting to render an overflown buffer will be +        silently dropped (in debug mode this will also result in a +        validation error). + +        You can also check manually if a buffer is in overflow-state by calling + +            bool sg_query_buffer_overflow(sg_buffer buf) + +        NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of +        data will be 4-byte aligned in the destination buffer. This means +        that there will be gaps in index buffers containing 16-bit indices +        when the number of indices in a call to sg_append_buffer() is +        odd. This isn't a problem when each call to sg_append_buffer() +        is associated with one draw call, but will be problematic when +        a single indexed draw call spans several appended chunks of indices. + +    --- to check at runtime for optional features, limits and pixelformat support, +        call: + +            sg_features sg_query_features() +            sg_limits sg_query_limits() +            sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) + +    --- if you need to call into the underlying 3D-API directly, you must call: + +            sg_reset_state_cache() + +        ...before calling sokol_gfx functions again + +    --- you can inspect the original sg_desc structure handed to sg_setup() +        by calling sg_query_desc(). This will return an sg_desc struct with +        the default values patched in instead of any zero-initialized values + +    --- you can inspect various internal resource attributes via: + +            sg_buffer_info sg_query_buffer_info(sg_buffer buf) +            sg_image_info sg_query_image_info(sg_image img) +            sg_shader_info sg_query_shader_info(sg_shader shd) +            sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) +            sg_pass_info sg_query_pass_info(sg_pass pass) + +        ...please note that the returned info-structs are tied quite closely +        to sokol_gfx.h internals, and may change more often than other +        public API functions and structs. + +    --- you can ask at runtime what backend sokol_gfx.h has been compiled +        for, or whether the GLES3 backend had to fall back to GLES2 with: + +            sg_backend sg_query_backend(void) + +    --- you can query the default resource creation parameters through the functions + +            sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) +            sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) +            sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) +            sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) +            sg_pass_desc sg_query_pass_defaults(const sg_pass_desc* desc) + +        These functions take a pointer to a desc structure which may contain +        zero-initialized items for default values. These zero-init values +        will be replaced with their concrete values in the returned desc +        struct. + +    ON INITIALIZATION: +    ================== +    When calling sg_setup(), a pointer to an sg_desc struct must be provided +    which contains initialization options. These options provide two types +    of information to sokol-gfx: + +        (1) upper bounds and limits needed to allocate various internal +            data structures: +                - the max number of resources of each type that can +                  be alive at the same time, this is used for allocating +                  internal pools +                - the max overall size of uniform data that can be +                  updated per frame, including a worst-case alignment +                  per uniform update (this worst-case alignment is 256 bytes) +                - the max size of all dynamic resource updates (sg_update_buffer, +                  sg_append_buffer and sg_update_image) per frame +                - the max number of entries in the texture sampler cache +                  (how many unique texture sampler can exist at the same time) +            Not all of those limit values are used by all backends, but it is +            good practice to provide them none-the-less. + +        (2) 3D-API "context information" (sometimes also called "bindings"): +            sokol_gfx.h doesn't create or initialize 3D API objects which are +            closely related to the presentation layer (this includes the "rendering +            device", the swapchain, and any objects which depend on the +            swapchain). These API objects (or callback functions to obtain +            them, if those objects might change between frames), must +            be provided in a nested sg_context_desc struct inside the +            sg_desc struct. If sokol_gfx.h is used together with +            sokol_app.h, have a look at the sokol_glue.h header which provides +            a convenience function to get a sg_context_desc struct filled out +            with context information provided by sokol_app.h + +    See the documention block of the sg_desc struct below for more information. + +    BACKEND-SPECIFIC TOPICS: +    ======================== +    --- the GL backends need to know about the internal structure of uniform +        blocks, and the texture sampler-name and -type: + +            typedef struct { +                float mvp[16];      // model-view-projection matrix +                float offset0[2];   // some 2D vectors +                float offset1[2]; +                float offset2[2]; +            } params_t; + +            // uniform block structure and texture image definition in sg_shader_desc: +            sg_shader_desc desc = { +                // uniform block description (size and internal structure) +                .vs.uniform_blocks[0] = { +                    .size = sizeof(params_t), +                    .uniforms = { +                        [0] = { .name="mvp", .type=SG_UNIFORMTYPE_MAT4 }, +                        [1] = { .name="offset0", .type=SG_UNIFORMTYPE_VEC2 }, +                        ... +                    } +                }, +                // one texture on the fragment-shader-stage, GLES2/WebGL needs name and image type +                .fs.images[0] = { .name="tex", .type=SG_IMAGETYPE_ARRAY } +                ... +            }; + +    --- the Metal and D3D11 backends only need to know the size of uniform blocks, +        not their internal member structure, and they only need to know +        the type of a texture sampler, not its name: + +            sg_shader_desc desc = { +                .vs.uniform_blocks[0].size = sizeof(params_t), +                .fs.images[0].type = SG_IMAGETYPE_ARRAY, +                ... +            }; + +    --- when creating a shader object, GLES2/WebGL need to know the vertex +        attribute names as used in the vertex shader: + +            sg_shader_desc desc = { +                .attrs = { +                    [0] = { .name="position" }, +                    [1] = { .name="color1" } +                } +            }; + +        The vertex attribute names provided when creating a shader will be +        used later in sg_create_pipeline() for matching the vertex layout +        to vertex shader inputs. + +    --- on D3D11 you need to provide a semantic name and semantic index in the +        shader description struct instead (see the D3D11 documentation on +        D3D11_INPUT_ELEMENT_DESC for details): + +            sg_shader_desc desc = { +                .attrs = { +                    [0] = { .sem_name="POSITION", .sem_index=0 } +                    [1] = { .sem_name="COLOR", .sem_index=1 } +                } +            }; + +        The provided semantic information will be used later in sg_create_pipeline() +        to match the vertex layout to vertex shader inputs. + +    --- on D3D11, and when passing HLSL source code (instead of byte code) to shader +        creation, you can optionally define the shader model targets on the vertex +        stage: + +            sg_shader_Desc desc = { +                .vs = { +                    ... +                    .d3d11_target = "vs_5_0" +                }, +                .fs = { +                    ... +                    .d3d11_target = "ps_5_0" +                } +            }; + +        The default targets are "ps_4_0" and "fs_4_0". Note that those target names +        are only used when compiling shaders from source. They are ignored when +        creating a shader from bytecode. + +    --- on Metal, GL 3.3 or GLES3/WebGL2, you don't need to provide an attribute +        name or semantic name, since vertex attributes can be bound by their slot index +        (this is mandatory in Metal, and optional in GL): + +            sg_pipeline_desc desc = { +                .layout = { +                    .attrs = { +                        [0] = { .format=SG_VERTEXFORMAT_FLOAT3 }, +                        [1] = { .format=SG_VERTEXFORMAT_FLOAT4 } +                    } +                } +            }; + +    WORKING WITH CONTEXTS +    ===================== +    sokol-gfx allows to switch between different rendering contexts and +    associate resource objects with contexts. This is useful to +    create GL applications that render into multiple windows. + +    A rendering context keeps track of all resources created while +    the context is active. When the context is destroyed, all resources +    "belonging to the context" are destroyed as well. + +    A default context will be created and activated implicitly in +    sg_setup(), and destroyed in sg_shutdown(). So for a typical application +    which *doesn't* use multiple contexts, nothing changes, and calling +    the context functions isn't necessary. + +    Three functions have been added to work with contexts: + +    --- sg_context sg_setup_context(): +        This must be called once after a GL context has been created and +        made active. + +    --- void sg_activate_context(sg_context ctx) +        This must be called after making a different GL context active. +        Apart from 3D-API-specific actions, the call to sg_activate_context() +        will internally call sg_reset_state_cache(). + +    --- void sg_discard_context(sg_context ctx) +        This must be called right before a GL context is destroyed and +        will destroy all resources associated with the context (that +        have been created while the context was active) The GL context must be +        active at the time sg_discard_context(sg_context ctx) is called. + +    Also note that resources (buffers, images, shaders and pipelines) must +    only be used or destroyed while the same GL context is active that +    was also active while the resource was created (an exception is +    resource sharing on GL, such resources can be used while +    another context is active, but must still be destroyed under +    the same context that was active during creation). + +    For more information, check out the multiwindow-glfw sample: + +    https://github.com/floooh/sokol-samples/blob/master/glfw/multiwindow-glfw.c + +    TRACE HOOKS: +    ============ +    sokol_gfx.h optionally allows to install "trace hook" callbacks for +    each public API functions. When a public API function is called, and +    a trace hook callback has been installed for this function, the +    callback will be invoked with the parameters and result of the function. +    This is useful for things like debugging- and profiling-tools, or +    keeping track of resource creation and destruction. + +    To use the trace hook feature: + +    --- Define SOKOL_TRACE_HOOKS before including the implementation. + +    --- Setup an sg_trace_hooks structure with your callback function +        pointers (keep all function pointers you're not interested +        in zero-initialized), optionally set the user_data member +        in the sg_trace_hooks struct. + +    --- Install the trace hooks by calling sg_install_trace_hooks(), +        the return value of this function is another sg_trace_hooks +        struct which contains the previously set of trace hooks. +        You should keep this struct around, and call those previous +        functions pointers from your own trace callbacks for proper +        chaining. + +    As an example of how trace hooks are used, have a look at the +    imgui/sokol_gfx_imgui.h header which implements a realtime +    debugging UI for sokol_gfx.h on top of Dear ImGui. + +    A NOTE ON PORTABLE PACKED VERTEX FORMATS: +    ========================================= +    There are two things to consider when using packed +    vertex formats like UBYTE4, SHORT2, etc which need to work +    across all backends: + +    - D3D11 can only convert *normalized* vertex formats to +      floating point during vertex fetch, normalized formats +      have a trailing 'N', and are "normalized" to a range +      -1.0..+1.0 (for the signed formats) or 0.0..1.0 (for the +      unsigned formats): + +        - SG_VERTEXFORMAT_BYTE4N +        - SG_VERTEXFORMAT_UBYTE4N +        - SG_VERTEXFORMAT_SHORT2N +        - SG_VERTEXFORMAT_USHORT2N +        - SG_VERTEXFORMAT_SHORT4N +        - SG_VERTEXFORMAT_USHORT4N + +      D3D11 will not convert *non-normalized* vertex formats to floating point +      vertex shader inputs, those can only be uses with the *ivecn* vertex shader +      input types when D3D11 is used as backend (GL and Metal can use both formats) + +        - SG_VERTEXFORMAT_BYTE4, +        - SG_VERTEXFORMAT_UBYTE4 +        - SG_VERTEXFORMAT_SHORT2 +        - SG_VERTEXFORMAT_SHORT4 + +    - WebGL/GLES2 cannot use integer vertex shader inputs (int or ivecn) + +    - SG_VERTEXFORMAT_UINT10_N2 is not supported on WebGL/GLES2 + +    So for a vertex input layout which works on all platforms, only use the following +    vertex formats, and if needed "expand" the normalized vertex shader +    inputs in the vertex shader by multiplying with 127.0, 255.0, 32767.0 or +    65535.0: + +        - SG_VERTEXFORMAT_FLOAT, +        - SG_VERTEXFORMAT_FLOAT2, +        - SG_VERTEXFORMAT_FLOAT3, +        - SG_VERTEXFORMAT_FLOAT4, +        - SG_VERTEXFORMAT_BYTE4N, +        - SG_VERTEXFORMAT_UBYTE4N, +        - SG_VERTEXFORMAT_SHORT2N, +        - SG_VERTEXFORMAT_USHORT2N +        - SG_VERTEXFORMAT_SHORT4N, +        - SG_VERTEXFORMAT_USHORT4N + +    TODO: +    ==== +    - talk about asynchronous resource creation + +    zlib/libpng license + +    Copyright (c) 2018 Andre Weissflog + +    This software is provided 'as-is', without any express or implied warranty. +    In no event will the authors be held liable for any damages arising from the +    use of this software. + +    Permission is granted to anyone to use this software for any purpose, +    including commercial applications, and to alter it and redistribute it +    freely, subject to the following restrictions: + +        1. The origin of this software must not be misrepresented; you must not +        claim that you wrote the original software. If you use this software in a +        product, an acknowledgment in the product documentation would be +        appreciated but is not required. + +        2. Altered source versions must be plainly marked as such, and must not +        be misrepresented as being the original software. + +        3. This notice may not be removed or altered from any source +        distribution. +*/ +#define SOKOL_GFX_INCLUDED (1) +#include <stddef.h>     // size_t +#include <stdint.h> +#include <stdbool.h> + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GFX_API_DECL) +#define SOKOL_GFX_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GFX_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GFX_IMPL) +#define SOKOL_GFX_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GFX_API_DECL __declspec(dllimport) +#else +#define SOKOL_GFX_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* +    Resource id typedefs: + +    sg_buffer:      vertex- and index-buffers +    sg_image:       textures and render targets +    sg_shader:      vertex- and fragment-shaders, uniform blocks +    sg_pipeline:    associated shader and vertex-layouts, and render states +    sg_pass:        a bundle of render targets and actions on them +    sg_context:     a 'context handle' for switching between 3D-API contexts + +    Instead of pointers, resource creation functions return a 32-bit +    number which uniquely identifies the resource object. + +    The 32-bit resource id is split into a 16-bit pool index in the lower bits, +    and a 16-bit 'unique counter' in the upper bits. The index allows fast +    pool lookups, and combined with the unique-mask it allows to detect +    'dangling accesses' (trying to use an object which no longer exists, and +    its pool slot has been reused for a new object) + +    The resource ids are wrapped into a struct so that the compiler +    can complain when the wrong resource type is used. +*/ +typedef struct sg_buffer   { uint32_t id; } sg_buffer; +typedef struct sg_image    { uint32_t id; } sg_image; +typedef struct sg_shader   { uint32_t id; } sg_shader; +typedef struct sg_pipeline { uint32_t id; } sg_pipeline; +typedef struct sg_pass     { uint32_t id; } sg_pass; +typedef struct sg_context  { uint32_t id; } sg_context; + +/* +    sg_range is a pointer-size-pair struct used to pass memory blobs into +    sokol-gfx. When initialized from a value type (array or struct), you can +    use the SG_RANGE() macro to build an sg_range struct. For functions which +    take either a sg_range pointer, or a (C++) sg_range reference, use the +    SG_RANGE_REF macro as a solution which compiles both in C and C++. +*/ +typedef struct sg_range { +    const void* ptr; +    size_t size; +} sg_range; + +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221)   /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ +#pragma warning(disable:4204)   /* VS2015: nonstandard extension used: non-constant aggregate initializer */ +#endif +#if defined(__cplusplus) +#define SG_RANGE(x) sg_range{ &x, sizeof(x) } +#define SG_RANGE_REF(x) sg_range{ &x, sizeof(x) } +#else +#define SG_RANGE(x) (sg_range){ &x, sizeof(x) } +#define SG_RANGE_REF(x) &(sg_range){ &x, sizeof(x) } +#endif + +//  various compile-time constants +enum { +    SG_INVALID_ID = 0, +    SG_NUM_SHADER_STAGES = 2, +    SG_NUM_INFLIGHT_FRAMES = 2, +    SG_MAX_COLOR_ATTACHMENTS = 4, +    SG_MAX_SHADERSTAGE_BUFFERS = 8, +    SG_MAX_SHADERSTAGE_IMAGES = 12, +    SG_MAX_SHADERSTAGE_UBS = 4, +    SG_MAX_UB_MEMBERS = 16, +    SG_MAX_VERTEX_ATTRIBUTES = 16,      /* NOTE: actual max vertex attrs can be less on GLES2, see sg_limits! */ +    SG_MAX_MIPMAPS = 16, +    SG_MAX_TEXTUREARRAY_LAYERS = 128 +}; + +/* +    sg_color + +    An RGBA color value. +*/ +typedef struct sg_color { float r, g, b, a; } sg_color; + +/* +    sg_backend + +    The active 3D-API backend, use the function sg_query_backend() +    to get the currently active backend. + +    NOTE that SG_BACKEND_GLES2 will be returned if sokol-gfx was +    compiled with SOKOL_GLES3, but the runtime platform doesn't support +    GLES3/WebGL2 and sokol-gfx had to fallback to GLES2/WebGL. +*/ +typedef enum sg_backend { +    SG_BACKEND_GLCORE33, +    SG_BACKEND_GLES2, +    SG_BACKEND_GLES3, +    SG_BACKEND_D3D11, +    SG_BACKEND_METAL_IOS, +    SG_BACKEND_METAL_MACOS, +    SG_BACKEND_METAL_SIMULATOR, +    SG_BACKEND_WGPU, +    SG_BACKEND_DUMMY, +} sg_backend; + +/* +    sg_pixel_format + +    sokol_gfx.h basically uses the same pixel formats as WebGPU, since these +    are supported on most newer GPUs. GLES2 and WebGL only supports a much +    smaller subset of actually available pixel formats. Call +    sg_query_pixelformat() to check at runtime if a pixel format supports the +    desired features. + +    A pixelformat name consist of three parts: + +        - components (R, RG, RGB or RGBA) +        - bit width per component (8, 16 or 32) +        - component data type: +            - unsigned normalized (no postfix) +            - signed normalized (SN postfix) +            - unsigned integer (UI postfix) +            - signed integer (SI postfix) +            - float (F postfix) + +    Not all pixel formats can be used for everything, call sg_query_pixelformat() +    to inspect the capabilities of a given pixelformat. The function returns +    an sg_pixelformat_info struct with the following bool members: + +        - sample: the pixelformat can be sampled as texture at least with +                  nearest filtering +        - filter: the pixelformat can be samples as texture with linear +                  filtering +        - render: the pixelformat can be used for render targets +        - blend:  blending is supported when using the pixelformat for +                  render targets +        - msaa:   multisample-antialiasing is supported when using the +                  pixelformat for render targets +        - depth:  the pixelformat can be used for depth-stencil attachments + +    When targeting GLES2/WebGL, the only safe formats to use +    as texture are SG_PIXELFORMAT_R8 and SG_PIXELFORMAT_RGBA8. For rendering +    in GLES2/WebGL, only SG_PIXELFORMAT_RGBA8 is safe. All other formats +    must be checked via sg_query_pixelformats(). + +    The default pixel format for texture images is SG_PIXELFORMAT_RGBA8. + +    The default pixel format for render target images is platform-dependent: +        - for Metal and D3D11 it is SG_PIXELFORMAT_BGRA8 +        - for GL backends it is SG_PIXELFORMAT_RGBA8 + +    This is mainly because of the default framebuffer which is setup outside +    of sokol_gfx.h. On some backends, using BGRA for the default frame buffer +    allows more efficient frame flips. For your own offscreen-render-targets, +    use whatever renderable pixel format is convenient for you. +*/ +typedef enum sg_pixel_format { +    _SG_PIXELFORMAT_DEFAULT,    /* value 0 reserved for default-init */ +    SG_PIXELFORMAT_NONE, + +    SG_PIXELFORMAT_R8, +    SG_PIXELFORMAT_R8SN, +    SG_PIXELFORMAT_R8UI, +    SG_PIXELFORMAT_R8SI, + +    SG_PIXELFORMAT_R16, +    SG_PIXELFORMAT_R16SN, +    SG_PIXELFORMAT_R16UI, +    SG_PIXELFORMAT_R16SI, +    SG_PIXELFORMAT_R16F, +    SG_PIXELFORMAT_RG8, +    SG_PIXELFORMAT_RG8SN, +    SG_PIXELFORMAT_RG8UI, +    SG_PIXELFORMAT_RG8SI, + +    SG_PIXELFORMAT_R32UI, +    SG_PIXELFORMAT_R32SI, +    SG_PIXELFORMAT_R32F, +    SG_PIXELFORMAT_RG16, +    SG_PIXELFORMAT_RG16SN, +    SG_PIXELFORMAT_RG16UI, +    SG_PIXELFORMAT_RG16SI, +    SG_PIXELFORMAT_RG16F, +    SG_PIXELFORMAT_RGBA8, +    SG_PIXELFORMAT_RGBA8SN, +    SG_PIXELFORMAT_RGBA8UI, +    SG_PIXELFORMAT_RGBA8SI, +    SG_PIXELFORMAT_BGRA8, +    SG_PIXELFORMAT_RGB10A2, +    SG_PIXELFORMAT_RG11B10F, + +    SG_PIXELFORMAT_RG32UI, +    SG_PIXELFORMAT_RG32SI, +    SG_PIXELFORMAT_RG32F, +    SG_PIXELFORMAT_RGBA16, +    SG_PIXELFORMAT_RGBA16SN, +    SG_PIXELFORMAT_RGBA16UI, +    SG_PIXELFORMAT_RGBA16SI, +    SG_PIXELFORMAT_RGBA16F, + +    SG_PIXELFORMAT_RGBA32UI, +    SG_PIXELFORMAT_RGBA32SI, +    SG_PIXELFORMAT_RGBA32F, + +    SG_PIXELFORMAT_DEPTH, +    SG_PIXELFORMAT_DEPTH_STENCIL, + +    SG_PIXELFORMAT_BC1_RGBA, +    SG_PIXELFORMAT_BC2_RGBA, +    SG_PIXELFORMAT_BC3_RGBA, +    SG_PIXELFORMAT_BC4_R, +    SG_PIXELFORMAT_BC4_RSN, +    SG_PIXELFORMAT_BC5_RG, +    SG_PIXELFORMAT_BC5_RGSN, +    SG_PIXELFORMAT_BC6H_RGBF, +    SG_PIXELFORMAT_BC6H_RGBUF, +    SG_PIXELFORMAT_BC7_RGBA, +    SG_PIXELFORMAT_PVRTC_RGB_2BPP, +    SG_PIXELFORMAT_PVRTC_RGB_4BPP, +    SG_PIXELFORMAT_PVRTC_RGBA_2BPP, +    SG_PIXELFORMAT_PVRTC_RGBA_4BPP, +    SG_PIXELFORMAT_ETC2_RGB8, +    SG_PIXELFORMAT_ETC2_RGB8A1, +    SG_PIXELFORMAT_ETC2_RGBA8, +    SG_PIXELFORMAT_ETC2_RG11, +    SG_PIXELFORMAT_ETC2_RG11SN, + +    _SG_PIXELFORMAT_NUM, +    _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF +} sg_pixel_format; + +/* +    Runtime information about a pixel format, returned +    by sg_query_pixelformat(). +*/ +typedef struct sg_pixelformat_info { +    bool sample;        // pixel format can be sampled in shaders +    bool filter;        // pixel format can be sampled with filtering +    bool render;        // pixel format can be used as render target +    bool blend;         // alpha-blending is supported +    bool msaa;          // pixel format can be used as MSAA render target +    bool depth;         // pixel format is a depth format +    #if defined(SOKOL_ZIG_BINDINGS) +    uint32_t __pad[3]; +    #endif +} sg_pixelformat_info; + +/* +    Runtime information about available optional features, +    returned by sg_query_features() +*/ +typedef struct sg_features { +    bool instancing;                    // hardware instancing supported +    bool origin_top_left;               // framebuffer and texture origin is in top left corner +    bool multiple_render_targets;       // offscreen render passes can have multiple render targets attached +    bool msaa_render_targets;           // offscreen render passes support MSAA antialiasing +    bool imagetype_3d;                  // creation of SG_IMAGETYPE_3D images is supported +    bool imagetype_array;               // creation of SG_IMAGETYPE_ARRAY images is supported +    bool image_clamp_to_border;         // border color and clamp-to-border UV-wrap mode is supported +    bool mrt_independent_blend_state;   // multiple-render-target rendering can use per-render-target blend state +    bool mrt_independent_write_mask;    // multiple-render-target rendering can use per-render-target color write masks +    #if defined(SOKOL_ZIG_BINDINGS) +    uint32_t __pad[3]; +    #endif +} sg_features; + +/* +    Runtime information about resource limits, returned by sg_query_limit() +*/ +typedef struct sg_limits { +    int max_image_size_2d;          // max width/height of SG_IMAGETYPE_2D images +    int max_image_size_cube;        // max width/height of SG_IMAGETYPE_CUBE images +    int max_image_size_3d;          // max width/height/depth of SG_IMAGETYPE_3D images +    int max_image_size_array;       // max width/height of SG_IMAGETYPE_ARRAY images +    int max_image_array_layers;     // max number of layers in SG_IMAGETYPE_ARRAY images +    int max_vertex_attrs;           // <= SG_MAX_VERTEX_ATTRIBUTES (only on some GLES2 impls) +} sg_limits; + +/* +    sg_resource_state + +    The current state of a resource in its resource pool. +    Resources start in the INITIAL state, which means the +    pool slot is unoccupied and can be allocated. When a resource is +    created, first an id is allocated, and the resource pool slot +    is set to state ALLOC. After allocation, the resource is +    initialized, which may result in the VALID or FAILED state. The +    reason why allocation and initialization are separate is because +    some resource types (e.g. buffers and images) might be asynchronously +    initialized by the user application. If a resource which is not +    in the VALID state is attempted to be used for rendering, rendering +    operations will silently be dropped. + +    The special INVALID state is returned in sg_query_xxx_state() if no +    resource object exists for the provided resource id. +*/ +typedef enum sg_resource_state { +    SG_RESOURCESTATE_INITIAL, +    SG_RESOURCESTATE_ALLOC, +    SG_RESOURCESTATE_VALID, +    SG_RESOURCESTATE_FAILED, +    SG_RESOURCESTATE_INVALID, +    _SG_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF +} sg_resource_state; + +/* +    sg_usage + +    A resource usage hint describing the update strategy of +    buffers and images. This is used in the sg_buffer_desc.usage +    and sg_image_desc.usage members when creating buffers +    and images: + +    SG_USAGE_IMMUTABLE:     the resource will never be updated with +                            new data, instead the content of the +                            resource must be provided on creation +    SG_USAGE_DYNAMIC:       the resource will be updated infrequently +                            with new data (this could range from "once +                            after creation", to "quite often but not +                            every frame") +    SG_USAGE_STREAM:        the resource will be updated each frame +                            with new content + +    The rendering backends use this hint to prevent that the +    CPU needs to wait for the GPU when attempting to update +    a resource that might be currently accessed by the GPU. + +    Resource content is updated with the functions sg_update_buffer() or +    sg_append_buffer() for buffer objects, and sg_update_image() for image +    objects. For the sg_update_*() functions, only one update is allowed per +    frame and resource object, while sg_append_buffer() can be called +    multiple times per frame on the same buffer. The application must update +    all data required for rendering (this means that the update data can be +    smaller than the resource size, if only a part of the overall resource +    size is used for rendering, you only need to make sure that the data that +    *is* used is valid). + +    The default usage is SG_USAGE_IMMUTABLE. +*/ +typedef enum sg_usage { +    _SG_USAGE_DEFAULT,      /* value 0 reserved for default-init */ +    SG_USAGE_IMMUTABLE, +    SG_USAGE_DYNAMIC, +    SG_USAGE_STREAM, +    _SG_USAGE_NUM, +    _SG_USAGE_FORCE_U32 = 0x7FFFFFFF +} sg_usage; + +/* +    sg_buffer_type + +    This indicates whether a buffer contains vertex- or index-data, +    used in the sg_buffer_desc.type member when creating a buffer. + +    The default value is SG_BUFFERTYPE_VERTEXBUFFER. +*/ +typedef enum sg_buffer_type { +    _SG_BUFFERTYPE_DEFAULT,         /* value 0 reserved for default-init */ +    SG_BUFFERTYPE_VERTEXBUFFER, +    SG_BUFFERTYPE_INDEXBUFFER, +    _SG_BUFFERTYPE_NUM, +    _SG_BUFFERTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_buffer_type; + +/* +    sg_index_type + +    Indicates whether indexed rendering (fetching vertex-indices from an +    index buffer) is used, and if yes, the index data type (16- or 32-bits). +    This is used in the sg_pipeline_desc.index_type member when creating a +    pipeline object. + +    The default index type is SG_INDEXTYPE_NONE. +*/ +typedef enum sg_index_type { +    _SG_INDEXTYPE_DEFAULT,   /* value 0 reserved for default-init */ +    SG_INDEXTYPE_NONE, +    SG_INDEXTYPE_UINT16, +    SG_INDEXTYPE_UINT32, +    _SG_INDEXTYPE_NUM, +    _SG_INDEXTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_index_type; + +/* +    sg_image_type + +    Indicates the basic type of an image object (2D-texture, cubemap, +    3D-texture or 2D-array-texture). 3D- and array-textures are not supported +    on the GLES2/WebGL backend (use sg_query_features().imagetype_3d and +    sg_query_features().imagetype_array to check for support). The image type +    is used in the sg_image_desc.type member when creating an image, and +    in sg_shader_image_desc when describing a shader's texture sampler binding. + +    The default image type when creating an image is SG_IMAGETYPE_2D. +*/ +typedef enum sg_image_type { +    _SG_IMAGETYPE_DEFAULT,  /* value 0 reserved for default-init */ +    SG_IMAGETYPE_2D, +    SG_IMAGETYPE_CUBE, +    SG_IMAGETYPE_3D, +    SG_IMAGETYPE_ARRAY, +    _SG_IMAGETYPE_NUM, +    _SG_IMAGETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_image_type; + +/* +    sg_sampler_type + +    Indicates the basic data type of a shader's texture sampler which +    can be float , unsigned integer or signed integer. The sampler +    type is used in the sg_shader_image_desc to describe the +    sampler type of a shader's texture sampler binding. + +    The default sampler type is SG_SAMPLERTYPE_FLOAT. +*/ +typedef enum sg_sampler_type { +    _SG_SAMPLERTYPE_DEFAULT,  /* value 0 reserved for default-init */ +    SG_SAMPLERTYPE_FLOAT, +    SG_SAMPLERTYPE_SINT, +    SG_SAMPLERTYPE_UINT, +} sg_sampler_type; + +/* +    sg_cube_face + +    The cubemap faces. Use these as indices in the sg_image_desc.content +    array. +*/ +typedef enum sg_cube_face { +    SG_CUBEFACE_POS_X, +    SG_CUBEFACE_NEG_X, +    SG_CUBEFACE_POS_Y, +    SG_CUBEFACE_NEG_Y, +    SG_CUBEFACE_POS_Z, +    SG_CUBEFACE_NEG_Z, +    SG_CUBEFACE_NUM, +    _SG_CUBEFACE_FORCE_U32 = 0x7FFFFFFF +} sg_cube_face; + +/* +    sg_shader_stage + +    There are 2 shader stages: vertex- and fragment-shader-stage. +    Each shader stage consists of: + +    - one slot for a shader function (provided as source- or byte-code) +    - SG_MAX_SHADERSTAGE_UBS slots for uniform blocks +    - SG_MAX_SHADERSTAGE_IMAGES slots for images used as textures by +      the shader function +*/ +typedef enum sg_shader_stage { +    SG_SHADERSTAGE_VS, +    SG_SHADERSTAGE_FS, +    _SG_SHADERSTAGE_FORCE_U32 = 0x7FFFFFFF +} sg_shader_stage; + +/* +    sg_primitive_type + +    This is the common subset of 3D primitive types supported across all 3D +    APIs. This is used in the sg_pipeline_desc.primitive_type member when +    creating a pipeline object. + +    The default primitive type is SG_PRIMITIVETYPE_TRIANGLES. +*/ +typedef enum sg_primitive_type { +    _SG_PRIMITIVETYPE_DEFAULT,  /* value 0 reserved for default-init */ +    SG_PRIMITIVETYPE_POINTS, +    SG_PRIMITIVETYPE_LINES, +    SG_PRIMITIVETYPE_LINE_STRIP, +    SG_PRIMITIVETYPE_TRIANGLES, +    SG_PRIMITIVETYPE_TRIANGLE_STRIP, +    _SG_PRIMITIVETYPE_NUM, +    _SG_PRIMITIVETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_primitive_type; + +/* +    sg_filter + +    The filtering mode when sampling a texture image. This is +    used in the sg_image_desc.min_filter and sg_image_desc.mag_filter +    members when creating an image object. + +    The default filter mode is SG_FILTER_NEAREST. +*/ +typedef enum sg_filter { +    _SG_FILTER_DEFAULT, /* value 0 reserved for default-init */ +    SG_FILTER_NEAREST, +    SG_FILTER_LINEAR, +    SG_FILTER_NEAREST_MIPMAP_NEAREST, +    SG_FILTER_NEAREST_MIPMAP_LINEAR, +    SG_FILTER_LINEAR_MIPMAP_NEAREST, +    SG_FILTER_LINEAR_MIPMAP_LINEAR, +    _SG_FILTER_NUM, +    _SG_FILTER_FORCE_U32 = 0x7FFFFFFF +} sg_filter; + +/* +    sg_wrap + +    The texture coordinates wrapping mode when sampling a texture +    image. This is used in the sg_image_desc.wrap_u, .wrap_v +    and .wrap_w members when creating an image. + +    The default wrap mode is SG_WRAP_REPEAT. + +    NOTE: SG_WRAP_CLAMP_TO_BORDER is not supported on all backends +    and platforms. To check for support, call sg_query_features() +    and check the "clamp_to_border" boolean in the returned +    sg_features struct. + +    Platforms which don't support SG_WRAP_CLAMP_TO_BORDER will silently fall back +    to SG_WRAP_CLAMP_TO_EDGE without a validation error. + +    Platforms which support clamp-to-border are: + +        - all desktop GL platforms +        - Metal on macOS +        - D3D11 + +    Platforms which do not support clamp-to-border: + +        - GLES2/3 and WebGL/WebGL2 +        - Metal on iOS +*/ +typedef enum sg_wrap { +    _SG_WRAP_DEFAULT,   /* value 0 reserved for default-init */ +    SG_WRAP_REPEAT, +    SG_WRAP_CLAMP_TO_EDGE, +    SG_WRAP_CLAMP_TO_BORDER, +    SG_WRAP_MIRRORED_REPEAT, +    _SG_WRAP_NUM, +    _SG_WRAP_FORCE_U32 = 0x7FFFFFFF +} sg_wrap; + +/* +    sg_border_color + +    The border color to use when sampling a texture, and the UV wrap +    mode is SG_WRAP_CLAMP_TO_BORDER. + +    The default border color is SG_BORDERCOLOR_OPAQUE_BLACK +*/ +typedef enum sg_border_color { +    _SG_BORDERCOLOR_DEFAULT,    /* value 0 reserved for default-init */ +    SG_BORDERCOLOR_TRANSPARENT_BLACK, +    SG_BORDERCOLOR_OPAQUE_BLACK, +    SG_BORDERCOLOR_OPAQUE_WHITE, +    _SG_BORDERCOLOR_NUM, +    _SG_BORDERCOLOR_FORCE_U32 = 0x7FFFFFFF +} sg_border_color; + +/* +    sg_vertex_format + +    The data type of a vertex component. This is used to describe +    the layout of vertex data when creating a pipeline object. +*/ +typedef enum sg_vertex_format { +    SG_VERTEXFORMAT_INVALID, +    SG_VERTEXFORMAT_FLOAT, +    SG_VERTEXFORMAT_FLOAT2, +    SG_VERTEXFORMAT_FLOAT3, +    SG_VERTEXFORMAT_FLOAT4, +    SG_VERTEXFORMAT_BYTE4, +    SG_VERTEXFORMAT_BYTE4N, +    SG_VERTEXFORMAT_UBYTE4, +    SG_VERTEXFORMAT_UBYTE4N, +    SG_VERTEXFORMAT_SHORT2, +    SG_VERTEXFORMAT_SHORT2N, +    SG_VERTEXFORMAT_USHORT2N, +    SG_VERTEXFORMAT_SHORT4, +    SG_VERTEXFORMAT_SHORT4N, +    SG_VERTEXFORMAT_USHORT4N, +    SG_VERTEXFORMAT_UINT10_N2, +    _SG_VERTEXFORMAT_NUM, +    _SG_VERTEXFORMAT_FORCE_U32 = 0x7FFFFFFF +} sg_vertex_format; + +/* +    sg_vertex_step + +    Defines whether the input pointer of a vertex input stream is advanced +    'per vertex' or 'per instance'. The default step-func is +    SG_VERTEXSTEP_PER_VERTEX. SG_VERTEXSTEP_PER_INSTANCE is used with +    instanced-rendering. + +    The vertex-step is part of the vertex-layout definition +    when creating pipeline objects. +*/ +typedef enum sg_vertex_step { +    _SG_VERTEXSTEP_DEFAULT,     /* value 0 reserved for default-init */ +    SG_VERTEXSTEP_PER_VERTEX, +    SG_VERTEXSTEP_PER_INSTANCE, +    _SG_VERTEXSTEP_NUM, +    _SG_VERTEXSTEP_FORCE_U32 = 0x7FFFFFFF +} sg_vertex_step; + +/* +    sg_uniform_type + +    The data type of a uniform block member. This is used to +    describe the internal layout of uniform blocks when creating +    a shader object. +*/ +typedef enum sg_uniform_type { +    SG_UNIFORMTYPE_INVALID, +    SG_UNIFORMTYPE_FLOAT, +    SG_UNIFORMTYPE_FLOAT2, +    SG_UNIFORMTYPE_FLOAT3, +    SG_UNIFORMTYPE_FLOAT4, +    SG_UNIFORMTYPE_MAT4, +    _SG_UNIFORMTYPE_NUM, +    _SG_UNIFORMTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_uniform_type; + +/* +    sg_cull_mode + +    The face-culling mode, this is used in the +    sg_pipeline_desc.cull_mode member when creating a +    pipeline object. + +    The default cull mode is SG_CULLMODE_NONE +*/ +typedef enum sg_cull_mode { +    _SG_CULLMODE_DEFAULT,   /* value 0 reserved for default-init */ +    SG_CULLMODE_NONE, +    SG_CULLMODE_FRONT, +    SG_CULLMODE_BACK, +    _SG_CULLMODE_NUM, +    _SG_CULLMODE_FORCE_U32 = 0x7FFFFFFF +} sg_cull_mode; + +/* +    sg_face_winding + +    The vertex-winding rule that determines a front-facing primitive. This +    is used in the member sg_pipeline_desc.face_winding +    when creating a pipeline object. + +    The default winding is SG_FACEWINDING_CW (clockwise) +*/ +typedef enum sg_face_winding { +    _SG_FACEWINDING_DEFAULT,    /* value 0 reserved for default-init */ +    SG_FACEWINDING_CCW, +    SG_FACEWINDING_CW, +    _SG_FACEWINDING_NUM, +    _SG_FACEWINDING_FORCE_U32 = 0x7FFFFFFF +} sg_face_winding; + +/* +    sg_compare_func + +    The compare-function for depth- and stencil-ref tests. +    This is used when creating pipeline objects in the members: + +    sg_pipeline_desc +        .depth +            .compare +        .stencil +            .front.compare +            .back.compar + +    The default compare func for depth- and stencil-tests is +    SG_COMPAREFUNC_ALWAYS. +*/ +typedef enum sg_compare_func { +    _SG_COMPAREFUNC_DEFAULT,    /* value 0 reserved for default-init */ +    SG_COMPAREFUNC_NEVER, +    SG_COMPAREFUNC_LESS, +    SG_COMPAREFUNC_EQUAL, +    SG_COMPAREFUNC_LESS_EQUAL, +    SG_COMPAREFUNC_GREATER, +    SG_COMPAREFUNC_NOT_EQUAL, +    SG_COMPAREFUNC_GREATER_EQUAL, +    SG_COMPAREFUNC_ALWAYS, +    _SG_COMPAREFUNC_NUM, +    _SG_COMPAREFUNC_FORCE_U32 = 0x7FFFFFFF +} sg_compare_func; + +/* +    sg_stencil_op + +    The operation performed on a currently stored stencil-value when a +    comparison test passes or fails. This is used when creating a pipeline +    object in the members: + +    sg_pipeline_desc +        .stencil +            .front +                .fail_op +                .depth_fail_op +                .pass_op +            .back +                .fail_op +                .depth_fail_op +                .pass_op + +    The default value is SG_STENCILOP_KEEP. +*/ +typedef enum sg_stencil_op { +    _SG_STENCILOP_DEFAULT,      /* value 0 reserved for default-init */ +    SG_STENCILOP_KEEP, +    SG_STENCILOP_ZERO, +    SG_STENCILOP_REPLACE, +    SG_STENCILOP_INCR_CLAMP, +    SG_STENCILOP_DECR_CLAMP, +    SG_STENCILOP_INVERT, +    SG_STENCILOP_INCR_WRAP, +    SG_STENCILOP_DECR_WRAP, +    _SG_STENCILOP_NUM, +    _SG_STENCILOP_FORCE_U32 = 0x7FFFFFFF +} sg_stencil_op; + +/* +    sg_blend_factor + +    The source and destination factors in blending operations. +    This is used in the following members when creating a pipeline object: + +    sg_pipeline_desc +        .colors[i] +            .blend +                .src_factor_rgb +                .dst_factor_rgb +                .src_factor_alpha +                .dst_factor_alpha + +    The default value is SG_BLENDFACTOR_ONE for source +    factors, and SG_BLENDFACTOR_ZERO for destination factors. +*/ +typedef enum sg_blend_factor { +    _SG_BLENDFACTOR_DEFAULT,    /* value 0 reserved for default-init */ +    SG_BLENDFACTOR_ZERO, +    SG_BLENDFACTOR_ONE, +    SG_BLENDFACTOR_SRC_COLOR, +    SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR, +    SG_BLENDFACTOR_SRC_ALPHA, +    SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, +    SG_BLENDFACTOR_DST_COLOR, +    SG_BLENDFACTOR_ONE_MINUS_DST_COLOR, +    SG_BLENDFACTOR_DST_ALPHA, +    SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA, +    SG_BLENDFACTOR_SRC_ALPHA_SATURATED, +    SG_BLENDFACTOR_BLEND_COLOR, +    SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR, +    SG_BLENDFACTOR_BLEND_ALPHA, +    SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA, +    _SG_BLENDFACTOR_NUM, +    _SG_BLENDFACTOR_FORCE_U32 = 0x7FFFFFFF +} sg_blend_factor; + +/* +    sg_blend_op + +    Describes how the source and destination values are combined in the +    fragment blending operation. It is used in the following members when +    creating a pipeline object: + +    sg_pipeline_desc +        .colors[i] +            .blend +                .op_rgb +                .op_alpha + +    The default value is SG_BLENDOP_ADD. +*/ +typedef enum sg_blend_op { +    _SG_BLENDOP_DEFAULT,    /* value 0 reserved for default-init */ +    SG_BLENDOP_ADD, +    SG_BLENDOP_SUBTRACT, +    SG_BLENDOP_REVERSE_SUBTRACT, +    _SG_BLENDOP_NUM, +    _SG_BLENDOP_FORCE_U32 = 0x7FFFFFFF +} sg_blend_op; + +/* +    sg_color_mask + +    Selects the active color channels when writing a fragment color to the +    framebuffer. This is used in the members +    sg_pipeline_desc.colors[i].write_mask when creating a pipeline object. + +    The default colormask is SG_COLORMASK_RGBA (write all colors channels) + +    NOTE: since the color mask value 0 is reserved for the default value +    (SG_COLORMASK_RGBA), use SG_COLORMASK_NONE if all color channels +    should be disabled. +*/ +typedef enum sg_color_mask { +    _SG_COLORMASK_DEFAULT = 0,    /* value 0 reserved for default-init */ +    SG_COLORMASK_NONE   = 0x10,   /* special value for 'all channels disabled */ +    SG_COLORMASK_R      = 0x1, +    SG_COLORMASK_G      = 0x2, +    SG_COLORMASK_RG     = 0x3, +    SG_COLORMASK_B      = 0x4, +    SG_COLORMASK_RB     = 0x5, +    SG_COLORMASK_GB     = 0x6, +    SG_COLORMASK_RGB    = 0x7, +    SG_COLORMASK_A      = 0x8, +    SG_COLORMASK_RA     = 0x9, +    SG_COLORMASK_GA     = 0xA, +    SG_COLORMASK_RGA    = 0xB, +    SG_COLORMASK_BA     = 0xC, +    SG_COLORMASK_RBA    = 0xD, +    SG_COLORMASK_GBA    = 0xE, +    SG_COLORMASK_RGBA   = 0xF, +    _SG_COLORMASK_FORCE_U32 = 0x7FFFFFFF +} sg_color_mask; + +/* +    sg_action + +    Defines what action should be performed at the start of a render pass: + +    SG_ACTION_CLEAR:    clear the render target image +    SG_ACTION_LOAD:     load the previous content of the render target image +    SG_ACTION_DONTCARE: leave the render target image content undefined + +    This is used in the sg_pass_action structure. + +    The default action for all pass attachments is SG_ACTION_CLEAR, with the +    clear color rgba = {0.5f, 0.5f, 0.5f, 1.0f], depth=1.0 and stencil=0. + +    If you want to override the default behaviour, it is important to not +    only set the clear color, but the 'action' field as well (as long as this +    is in its _SG_ACTION_DEFAULT, the value fields will be ignored). +*/ +typedef enum sg_action { +    _SG_ACTION_DEFAULT, +    SG_ACTION_CLEAR, +    SG_ACTION_LOAD, +    SG_ACTION_DONTCARE, +    _SG_ACTION_NUM, +    _SG_ACTION_FORCE_U32 = 0x7FFFFFFF +} sg_action; + +/* +    sg_pass_action + +    The sg_pass_action struct defines the actions to be performed +    at the start of a rendering pass in the functions sg_begin_pass() +    and sg_begin_default_pass(). + +    A separate action and clear values can be defined for each +    color attachment, and for the depth-stencil attachment. + +    The default clear values are defined by the macros: + +    - SG_DEFAULT_CLEAR_RED:     0.5f +    - SG_DEFAULT_CLEAR_GREEN:   0.5f +    - SG_DEFAULT_CLEAR_BLUE:    0.5f +    - SG_DEFAULT_CLEAR_ALPHA:   1.0f +    - SG_DEFAULT_CLEAR_DEPTH:   1.0f +    - SG_DEFAULT_CLEAR_STENCIL: 0 +*/ +typedef struct sg_color_attachment_action { +    sg_action action; +    sg_color value; +} sg_color_attachment_action; + +typedef struct sg_depth_attachment_action { +    sg_action action; +    float value; +} sg_depth_attachment_action; + +typedef struct sg_stencil_attachment_action { +    sg_action action; +    uint8_t value; +} sg_stencil_attachment_action; + +typedef struct sg_pass_action { +    uint32_t _start_canary; +    sg_color_attachment_action colors[SG_MAX_COLOR_ATTACHMENTS]; +    sg_depth_attachment_action depth; +    sg_stencil_attachment_action stencil; +    uint32_t _end_canary; +} sg_pass_action; + +/* +    sg_bindings + +    The sg_bindings structure defines the resource binding slots +    of the sokol_gfx render pipeline, used as argument to the +    sg_apply_bindings() function. + +    A resource binding struct contains: + +    - 1..N vertex buffers +    - 0..N vertex buffer offsets +    - 0..1 index buffers +    - 0..1 index buffer offsets +    - 0..N vertex shader stage images +    - 0..N fragment shader stage images + +    The max number of vertex buffer and shader stage images +    are defined by the SG_MAX_SHADERSTAGE_BUFFERS and +    SG_MAX_SHADERSTAGE_IMAGES configuration constants. + +    The optional buffer offsets can be used to put different unrelated +    chunks of vertex- and/or index-data into the same buffer objects. +*/ +typedef struct sg_bindings { +    uint32_t _start_canary; +    sg_buffer vertex_buffers[SG_MAX_SHADERSTAGE_BUFFERS]; +    int vertex_buffer_offsets[SG_MAX_SHADERSTAGE_BUFFERS]; +    sg_buffer index_buffer; +    int index_buffer_offset; +    sg_image vs_images[SG_MAX_SHADERSTAGE_IMAGES]; +    sg_image fs_images[SG_MAX_SHADERSTAGE_IMAGES]; +    uint32_t _end_canary; +} sg_bindings; + +/* +    sg_buffer_desc + +    Creation parameters for sg_buffer objects, used in the +    sg_make_buffer() call. + +    The default configuration is: + +    .size:      0       (*must* be >0 for buffers without data) +    .type:      SG_BUFFERTYPE_VERTEXBUFFER +    .usage:     SG_USAGE_IMMUTABLE +    .data.ptr   0       (*must* be valid for immutable buffers) +    .data.size  0       (*must* be > 0 for immutable buffers) +    .label      0       (optional string label for trace hooks) + +    The label will be ignored by sokol_gfx.h, it is only useful +    when hooking into sg_make_buffer() or sg_init_buffer() via +    the sg_install_trace_hooks() function. + +    For immutable buffers which are initialized with initial data, +    keep the .size item zero-initialized, and set the size together with the +    pointer to the initial data in the .data item. + +    For mutable buffers without initial data, keep the .data item +    zero-initialized, and set the buffer size in the .size item instead. + +    You can also set both size values, but currently both size values must +    be identical (this may change in the future when the dynamic resource +    management may become more flexible). + +    ADVANCED TOPIC: Injecting native 3D-API buffers: + +    The following struct members allow to inject your own GL, Metal +    or D3D11 buffers into sokol_gfx: + +    .gl_buffers[SG_NUM_INFLIGHT_FRAMES] +    .mtl_buffers[SG_NUM_INFLIGHT_FRAMES] +    .d3d11_buffer + +    You must still provide all other struct items except the .data item, and +    these must match the creation parameters of the native buffers you +    provide. For SG_USAGE_IMMUTABLE, only provide a single native 3D-API +    buffer, otherwise you need to provide SG_NUM_INFLIGHT_FRAMES buffers +    (only for GL and Metal, not D3D11). Providing multiple buffers for GL and +    Metal is necessary because sokol_gfx will rotate through them when +    calling sg_update_buffer() to prevent lock-stalls. + +    Note that it is expected that immutable injected buffer have already been +    initialized with content, and the .content member must be 0! + +    Also you need to call sg_reset_state_cache() after calling native 3D-API +    functions, and before calling any sokol_gfx function. +*/ +typedef struct sg_buffer_desc { +    uint32_t _start_canary; +    size_t size; +    sg_buffer_type type; +    sg_usage usage; +    sg_range data; +    const char* label; +    /* GL specific */ +    uint32_t gl_buffers[SG_NUM_INFLIGHT_FRAMES]; +    /* Metal specific */ +    const void* mtl_buffers[SG_NUM_INFLIGHT_FRAMES]; +    /* D3D11 specific */ +    const void* d3d11_buffer; +    /* WebGPU specific */ +    const void* wgpu_buffer; +    uint32_t _end_canary; +} sg_buffer_desc; + +/* +    sg_image_data + +    Defines the content of an image through a 2D array of sg_range structs. +    The first array dimension is the cubemap face, and the second array +    dimension the mipmap level. +*/ +typedef struct sg_image_data { +    sg_range subimage[SG_CUBEFACE_NUM][SG_MAX_MIPMAPS]; +} sg_image_data; + +/* +    sg_image_desc + +    Creation parameters for sg_image objects, used in the sg_make_image() +    call. + +    The default configuration is: + +    .type:              SG_IMAGETYPE_2D +    .render_target:     false +    .width              0 (must be set to >0) +    .height             0 (must be set to >0) +    .num_slices         1 (3D textures: depth; array textures: number of layers) +    .num_mipmaps:       1 +    .usage:             SG_USAGE_IMMUTABLE +    .pixel_format:      SG_PIXELFORMAT_RGBA8 for textures, or sg_desc.context.color_format for render targets +    .sample_count:      1 for textures, or sg_desc.context.sample_count for render targets +    .min_filter:        SG_FILTER_NEAREST +    .mag_filter:        SG_FILTER_NEAREST +    .wrap_u:            SG_WRAP_REPEAT +    .wrap_v:            SG_WRAP_REPEAT +    .wrap_w:            SG_WRAP_REPEAT (only SG_IMAGETYPE_3D) +    .border_color       SG_BORDERCOLOR_OPAQUE_BLACK +    .max_anisotropy     1 (must be 1..16) +    .min_lod            0.0f +    .max_lod            FLT_MAX +    .data               an sg_image_data struct to define the initial content +    .label              0       (optional string label for trace hooks) + +    Q: Why is the default sample_count for render targets identical with the +    "default sample count" from sg_desc.context.sample_count? + +    A: So that it matches the default sample count in pipeline objects. Even +    though it is a bit strange/confusing that offscreen render targets by default +    get the same sample count as the default framebuffer, but it's better that +    an offscreen render target created with default parameters matches +    a pipeline object created with default parameters. + +    NOTE: + +    SG_IMAGETYPE_ARRAY and SG_IMAGETYPE_3D are not supported on WebGL/GLES2, +    use sg_query_features().imagetype_array and +    sg_query_features().imagetype_3d at runtime to check if array- and +    3D-textures are supported. + +    Images with usage SG_USAGE_IMMUTABLE must be fully initialized by +    providing a valid .data member which points to initialization data. + +    ADVANCED TOPIC: Injecting native 3D-API textures: + +    The following struct members allow to inject your own GL, Metal or D3D11 +    textures into sokol_gfx: + +    .gl_textures[SG_NUM_INFLIGHT_FRAMES] +    .mtl_textures[SG_NUM_INFLIGHT_FRAMES] +    .d3d11_texture +    .d3d11_shader_resource_view + +    For GL, you can also specify the texture target or leave it empty to use +    the default texture target for the image type (GL_TEXTURE_2D for +    SG_IMAGETYPE_2D etc) + +    For D3D11, you can provide either a D3D11 texture, or a +    shader-resource-view, or both. If only a texture is provided, a matching +    shader-resource-view will be created. If only a shader-resource-view is +    provided, the texture will be looked up from the shader-resource-view. + +    The same rules apply as for injecting native buffers (see sg_buffer_desc +    documentation for more details). +*/ +typedef struct sg_image_desc { +    uint32_t _start_canary; +    sg_image_type type; +    bool render_target; +    int width; +    int height; +    int num_slices; +    int num_mipmaps; +    sg_usage usage; +    sg_pixel_format pixel_format; +    int sample_count; +    sg_filter min_filter; +    sg_filter mag_filter; +    sg_wrap wrap_u; +    sg_wrap wrap_v; +    sg_wrap wrap_w; +    sg_border_color border_color; +    uint32_t max_anisotropy; +    float min_lod; +    float max_lod; +    sg_image_data data; +    const char* label; +    /* GL specific */ +    uint32_t gl_textures[SG_NUM_INFLIGHT_FRAMES]; +    uint32_t gl_texture_target; +    /* Metal specific */ +    const void* mtl_textures[SG_NUM_INFLIGHT_FRAMES]; +    /* D3D11 specific */ +    const void* d3d11_texture; +    const void* d3d11_shader_resource_view; +    /* WebGPU specific */ +    const void* wgpu_texture; +    uint32_t _end_canary; +} sg_image_desc; + +/* +    sg_shader_desc + +    The structure sg_shader_desc defines all creation parameters for shader +    programs, used as input to the sg_make_shader() function: + +    - reflection information for vertex attributes (vertex shader inputs): +        - vertex attribute name (required for GLES2, optional for GLES3 and GL) +        - a semantic name and index (required for D3D11) +    - for each shader-stage (vertex and fragment): +        - the shader source or bytecode +        - an optional entry function name +        - an optional compile target (only for D3D11 when source is provided, +          defaults are "vs_4_0" and "ps_4_0") +        - reflection info for each uniform block used by the shader stage: +            - the size of the uniform block in bytes +            - reflection info for each uniform block member (only required for GL backends): +                - member name +                - member type (SG_UNIFORMTYPE_xxx) +                - if the member is an array, the number of array items +        - reflection info for the texture images used by the shader stage: +            - the image type (SG_IMAGETYPE_xxx) +            - the sampler type (SG_SAMPLERTYPE_xxx, default is SG_SAMPLERTYPE_FLOAT) +            - the name of the texture sampler (required for GLES2, optional everywhere else) + +    For all GL backends, shader source-code must be provided. For D3D11 and Metal, +    either shader source-code or byte-code can be provided. + +    For D3D11, if source code is provided, the d3dcompiler_47.dll will be loaded +    on demand. If this fails, shader creation will fail. When compiling HLSL +    source code, you can provide an optional target string via +    sg_shader_stage_desc.d3d11_target, the default target is "vs_4_0" for the +    vertex shader stage and "ps_4_0" for the pixel shader stage. +*/ +typedef struct sg_shader_attr_desc { +    const char* name;           // GLSL vertex attribute name (only strictly required for GLES2) +    const char* sem_name;       // HLSL semantic name +    int sem_index;              // HLSL semantic index +} sg_shader_attr_desc; + +typedef struct sg_shader_uniform_desc { +    const char* name; +    sg_uniform_type type; +    int array_count; +} sg_shader_uniform_desc; + +typedef struct sg_shader_uniform_block_desc { +    size_t size; +    sg_shader_uniform_desc uniforms[SG_MAX_UB_MEMBERS]; +} sg_shader_uniform_block_desc; + +typedef struct sg_shader_image_desc { +    const char* name; +    sg_image_type image_type; +    sg_sampler_type sampler_type; +} sg_shader_image_desc; + +typedef struct sg_shader_stage_desc { +    const char* source; +    sg_range bytecode; +    const char* entry; +    const char* d3d11_target; +    sg_shader_uniform_block_desc uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; +    sg_shader_image_desc images[SG_MAX_SHADERSTAGE_IMAGES]; +} sg_shader_stage_desc; + +typedef struct sg_shader_desc { +    uint32_t _start_canary; +    sg_shader_attr_desc attrs[SG_MAX_VERTEX_ATTRIBUTES]; +    sg_shader_stage_desc vs; +    sg_shader_stage_desc fs; +    const char* label; +    uint32_t _end_canary; +} sg_shader_desc; + +/* +    sg_pipeline_desc + +    The sg_pipeline_desc struct defines all creation parameters for an +    sg_pipeline object, used as argument to the sg_make_pipeline() function: + +    - the vertex layout for all input vertex buffers +    - a shader object +    - the 3D primitive type (points, lines, triangles, ...) +    - the index type (none, 16- or 32-bit) +    - all the fixed-function-pipeline state (depth-, stencil-, blend-state, etc...) + +    If the vertex data has no gaps between vertex components, you can omit +    the .layout.buffers[].stride and layout.attrs[].offset items (leave them +    default-initialized to 0), sokol-gfx will then compute the offsets and +    strides from the vertex component formats (.layout.attrs[].format). +    Please note that ALL vertex attribute offsets must be 0 in order for the +    automatic offset computation to kick in. + +    The default configuration is as follows: + +    .shader:            0 (must be initialized with a valid sg_shader id!) +    .layout: +        .buffers[]:         vertex buffer layouts +            .stride:        0 (if no stride is given it will be computed) +            .step_func      SG_VERTEXSTEP_PER_VERTEX +            .step_rate      1 +        .attrs[]:           vertex attribute declarations +            .buffer_index   0 the vertex buffer bind slot +            .offset         0 (offsets can be omitted if the vertex layout has no gaps) +            .format         SG_VERTEXFORMAT_INVALID (must be initialized!) +    .depth: +        .pixel_format:      sg_desc.context.depth_format +        .compare:           SG_COMPAREFUNC_ALWAYS +        .write_enabled:     false +        .bias:              0.0f +        .bias_slope_scale:  0.0f +        .bias_clamp:        0.0f +    .stencil: +        .enabled:           false +        .front/back: +            .compare:       SG_COMPAREFUNC_ALWAYS +            .depth_fail_op: SG_STENCILOP_KEEP +            .pass_op:       SG_STENCILOP_KEEP +            .compare:       SG_COMPAREFUNC_ALWAYS +        .read_mask:         0 +        .write_mask:        0 +        .ref:               0 +    .color_count            1 +    .colors[0..color_count] +        .pixel_format       sg_desc.context.color_format +        .write_mask:        SG_COLORMASK_RGBA +        .blend: +            .enabled:           false +            .src_factor_rgb:    SG_BLENDFACTOR_ONE +            .dst_factor_rgb:    SG_BLENDFACTOR_ZERO +            .op_rgb:            SG_BLENDOP_ADD +            .src_factor_alpha:  SG_BLENDFACTOR_ONE +            .dst_factor_alpha:  SG_BLENDFACTOR_ZERO +            .op_alpha:          SG_BLENDOP_ADD +    .primitive_type:            SG_PRIMITIVETYPE_TRIANGLES +    .index_type:                SG_INDEXTYPE_NONE +    .cull_mode:                 SG_CULLMODE_NONE +    .face_winding:              SG_FACEWINDING_CW +    .sample_count:              sg_desc.context.sample_count +    .blend_color:               (sg_color) { 0.0f, 0.0f, 0.0f, 0.0f } +    .alpha_to_coverage_enabled: false +    .label  0       (optional string label for trace hooks) +*/ +typedef struct sg_buffer_layout_desc { +    int stride; +    sg_vertex_step step_func; +    int step_rate; +    #if defined(SOKOL_ZIG_BINDINGS) +    uint32_t __pad[2]; +    #endif +} sg_buffer_layout_desc; + +typedef struct sg_vertex_attr_desc { +    int buffer_index; +    int offset; +    sg_vertex_format format; +    #if defined(SOKOL_ZIG_BINDINGS) +    uint32_t __pad[2]; +    #endif +} sg_vertex_attr_desc; + +typedef struct sg_layout_desc { +    sg_buffer_layout_desc buffers[SG_MAX_SHADERSTAGE_BUFFERS]; +    sg_vertex_attr_desc attrs[SG_MAX_VERTEX_ATTRIBUTES]; +} sg_layout_desc; + +typedef struct sg_stencil_face_state { +    sg_compare_func compare; +    sg_stencil_op fail_op; +    sg_stencil_op depth_fail_op; +    sg_stencil_op pass_op; +} sg_stencil_face_state; + +typedef struct sg_stencil_state { +    bool enabled; +    sg_stencil_face_state front; +    sg_stencil_face_state back; +    uint8_t read_mask; +    uint8_t write_mask; +    uint8_t ref; +} sg_stencil_state; + +typedef struct sg_depth_state { +    sg_pixel_format pixel_format; +    sg_compare_func compare; +    bool write_enabled; +    float bias; +    float bias_slope_scale; +    float bias_clamp; +} sg_depth_state; + +typedef struct sg_blend_state { +    bool enabled; +    sg_blend_factor src_factor_rgb; +    sg_blend_factor dst_factor_rgb; +    sg_blend_op op_rgb; +    sg_blend_factor src_factor_alpha; +    sg_blend_factor dst_factor_alpha; +    sg_blend_op op_alpha; +} sg_blend_state; + +typedef struct sg_color_state { +    sg_pixel_format pixel_format; +    sg_color_mask write_mask; +    sg_blend_state blend; +} sg_color_state; + +typedef struct sg_pipeline_desc { +    uint32_t _start_canary; +    sg_shader shader; +    sg_layout_desc layout; +    sg_depth_state depth; +    sg_stencil_state stencil; +    int color_count; +    sg_color_state colors[SG_MAX_COLOR_ATTACHMENTS]; +    sg_primitive_type primitive_type; +    sg_index_type index_type; +    sg_cull_mode cull_mode; +    sg_face_winding face_winding; +    int sample_count; +    sg_color blend_color; +    bool alpha_to_coverage_enabled; +    const char* label; +    uint32_t _end_canary; +} sg_pipeline_desc; + +/* +    sg_pass_desc + +    Creation parameters for an sg_pass object, used as argument +    to the sg_make_pass() function. + +    A pass object contains 1..4 color-attachments and none, or one, +    depth-stencil-attachment. Each attachment consists of +    an image, and two additional indices describing +    which subimage the pass will render to: one mipmap index, and +    if the image is a cubemap, array-texture or 3D-texture, the +    face-index, array-layer or depth-slice. + +    Pass images must fulfill the following requirements: + +    All images must have: +    - been created as render target (sg_image_desc.render_target = true) +    - the same size +    - the same sample count + +    In addition, all color-attachment images must have the same pixel format. +*/ +typedef struct sg_pass_attachment_desc { +    sg_image image; +    int mip_level; +    int slice;      /* cube texture: face; array texture: layer; 3D texture: slice */ +} sg_pass_attachment_desc; + +typedef struct sg_pass_desc { +    uint32_t _start_canary; +    sg_pass_attachment_desc color_attachments[SG_MAX_COLOR_ATTACHMENTS]; +    sg_pass_attachment_desc depth_stencil_attachment; +    const char* label; +    uint32_t _end_canary; +} sg_pass_desc; + +/* +    sg_trace_hooks + +    Installable callback functions to keep track of the sokol-gfx calls, +    this is useful for debugging, or keeping track of resource creation +    and destruction. + +    Trace hooks are installed with sg_install_trace_hooks(), this returns +    another sg_trace_hooks struct with the previous set of +    trace hook function pointers. These should be invoked by the +    new trace hooks to form a proper call chain. +*/ +typedef struct sg_trace_hooks { +    void* user_data; +    void (*reset_state_cache)(void* user_data); +    void (*make_buffer)(const sg_buffer_desc* desc, sg_buffer result, void* user_data); +    void (*make_image)(const sg_image_desc* desc, sg_image result, void* user_data); +    void (*make_shader)(const sg_shader_desc* desc, sg_shader result, void* user_data); +    void (*make_pipeline)(const sg_pipeline_desc* desc, sg_pipeline result, void* user_data); +    void (*make_pass)(const sg_pass_desc* desc, sg_pass result, void* user_data); +    void (*destroy_buffer)(sg_buffer buf, void* user_data); +    void (*destroy_image)(sg_image img, void* user_data); +    void (*destroy_shader)(sg_shader shd, void* user_data); +    void (*destroy_pipeline)(sg_pipeline pip, void* user_data); +    void (*destroy_pass)(sg_pass pass, void* user_data); +    void (*update_buffer)(sg_buffer buf, const sg_range* data, void* user_data); +    void (*update_image)(sg_image img, const sg_image_data* data, void* user_data); +    void (*append_buffer)(sg_buffer buf, const sg_range* data, int result, void* user_data); +    void (*begin_default_pass)(const sg_pass_action* pass_action, int width, int height, void* user_data); +    void (*begin_pass)(sg_pass pass, const sg_pass_action* pass_action, void* user_data); +    void (*apply_viewport)(int x, int y, int width, int height, bool origin_top_left, void* user_data); +    void (*apply_scissor_rect)(int x, int y, int width, int height, bool origin_top_left, void* user_data); +    void (*apply_pipeline)(sg_pipeline pip, void* user_data); +    void (*apply_bindings)(const sg_bindings* bindings, void* user_data); +    void (*apply_uniforms)(sg_shader_stage stage, int ub_index, const sg_range* data, void* user_data); +    void (*draw)(int base_element, int num_elements, int num_instances, void* user_data); +    void (*end_pass)(void* user_data); +    void (*commit)(void* user_data); +    void (*alloc_buffer)(sg_buffer result, void* user_data); +    void (*alloc_image)(sg_image result, void* user_data); +    void (*alloc_shader)(sg_shader result, void* user_data); +    void (*alloc_pipeline)(sg_pipeline result, void* user_data); +    void (*alloc_pass)(sg_pass result, void* user_data); +    void (*dealloc_buffer)(sg_buffer buf_id, void* user_data); +    void (*dealloc_image)(sg_image img_id, void* user_data); +    void (*dealloc_shader)(sg_shader shd_id, void* user_data); +    void (*dealloc_pipeline)(sg_pipeline pip_id, void* user_data); +    void (*dealloc_pass)(sg_pass pass_id, void* user_data); +    void (*init_buffer)(sg_buffer buf_id, const sg_buffer_desc* desc, void* user_data); +    void (*init_image)(sg_image img_id, const sg_image_desc* desc, void* user_data); +    void (*init_shader)(sg_shader shd_id, const sg_shader_desc* desc, void* user_data); +    void (*init_pipeline)(sg_pipeline pip_id, const sg_pipeline_desc* desc, void* user_data); +    void (*init_pass)(sg_pass pass_id, const sg_pass_desc* desc, void* user_data); +    void (*uninit_buffer)(sg_buffer buf_id, void* user_data); +    void (*uninit_image)(sg_image img_id, void* user_data); +    void (*uninit_shader)(sg_shader shd_id, void* user_data); +    void (*uninit_pipeline)(sg_pipeline pip_id, void* user_data); +    void (*uninit_pass)(sg_pass pass_id, void* user_data); +    void (*fail_buffer)(sg_buffer buf_id, void* user_data); +    void (*fail_image)(sg_image img_id, void* user_data); +    void (*fail_shader)(sg_shader shd_id, void* user_data); +    void (*fail_pipeline)(sg_pipeline pip_id, void* user_data); +    void (*fail_pass)(sg_pass pass_id, void* user_data); +    void (*push_debug_group)(const char* name, void* user_data); +    void (*pop_debug_group)(void* user_data); +    void (*err_buffer_pool_exhausted)(void* user_data); +    void (*err_image_pool_exhausted)(void* user_data); +    void (*err_shader_pool_exhausted)(void* user_data); +    void (*err_pipeline_pool_exhausted)(void* user_data); +    void (*err_pass_pool_exhausted)(void* user_data); +    void (*err_context_mismatch)(void* user_data); +    void (*err_pass_invalid)(void* user_data); +    void (*err_draw_invalid)(void* user_data); +    void (*err_bindings_invalid)(void* user_data); +} sg_trace_hooks; + +/* +    sg_buffer_info +    sg_image_info +    sg_shader_info +    sg_pipeline_info +    sg_pass_info + +    These structs contain various internal resource attributes which +    might be useful for debug-inspection. Please don't rely on the +    actual content of those structs too much, as they are quite closely +    tied to sokol_gfx.h internals and may change more frequently than +    the other public API elements. + +    The *_info structs are used as the return values of the following functions: + +    sg_query_buffer_info() +    sg_query_image_info() +    sg_query_shader_info() +    sg_query_pipeline_info() +    sg_query_pass_info() +*/ +typedef struct sg_slot_info { +    sg_resource_state state;    /* the current state of this resource slot */ +    uint32_t res_id;        /* type-neutral resource if (e.g. sg_buffer.id) */ +    uint32_t ctx_id;        /* the context this resource belongs to */ +} sg_slot_info; + +typedef struct sg_buffer_info { +    sg_slot_info slot;              /* resource pool slot info */ +    uint32_t update_frame_index;    /* frame index of last sg_update_buffer() */ +    uint32_t append_frame_index;    /* frame index of last sg_append_buffer() */ +    int append_pos;                 /* current position in buffer for sg_append_buffer() */ +    bool append_overflow;           /* is buffer in overflow state (due to sg_append_buffer) */ +    int num_slots;                  /* number of renaming-slots for dynamically updated buffers */ +    int active_slot;                /* currently active write-slot for dynamically updated buffers */ +} sg_buffer_info; + +typedef struct sg_image_info { +    sg_slot_info slot;              /* resource pool slot info */ +    uint32_t upd_frame_index;       /* frame index of last sg_update_image() */ +    int num_slots;                  /* number of renaming-slots for dynamically updated images */ +    int active_slot;                /* currently active write-slot for dynamically updated images */ +    int width;                      /* image width */ +    int height;                     /* image height */ +} sg_image_info; + +typedef struct sg_shader_info { +    sg_slot_info slot;              /* resoure pool slot info */ +} sg_shader_info; + +typedef struct sg_pipeline_info { +    sg_slot_info slot;              /* resource pool slot info */ +} sg_pipeline_info; + +typedef struct sg_pass_info { +    sg_slot_info slot;              /* resource pool slot info */ +} sg_pass_info; + +/* +    sg_desc + +    The sg_desc struct contains configuration values for sokol_gfx, +    it is used as parameter to the sg_setup() call. + +    NOTE that all callback function pointers come in two versions, one without +    a userdata pointer, and one with a userdata pointer. You would +    either initialize one or the other depending on whether you pass data +    to your callbacks. + +    FIXME: explain the various configuration options + +    The default configuration is: + +    .buffer_pool_size       128 +    .image_pool_size        128 +    .shader_pool_size       32 +    .pipeline_pool_size     64 +    .pass_pool_size         16 +    .context_pool_size      16 +    .sampler_cache_size     64 +    .uniform_buffer_size    4 MB (4*1024*1024) +    .staging_buffer_size    8 MB (8*1024*1024) + +    .context.color_format: default value depends on selected backend: +        all GL backends:    SG_PIXELFORMAT_RGBA8 +        Metal and D3D11:    SG_PIXELFORMAT_BGRA8 +        WGPU:               *no default* (must be queried from WGPU swapchain) +    .context.depth_format   SG_PIXELFORMAT_DEPTH_STENCIL +    .context.sample_count   1 + +    GL specific: +        .context.gl.force_gles2 +            if this is true the GL backend will act in "GLES2 fallback mode" even +            when compiled with SOKOL_GLES3, this is useful to fall back +            to traditional WebGL if a browser doesn't support a WebGL2 context + +    Metal specific: +        (NOTE: All Objective-C object references are transferred through +        a bridged (const void*) to sokol_gfx, which will use a unretained +        bridged cast (__bridged id<xxx>) to retrieve the Objective-C +        references back. Since the bridge cast is unretained, the caller +        must hold a strong reference to the Objective-C object for the +        duration of the sokol_gfx call! + +        .context.metal.device +            a pointer to the MTLDevice object +        .context.metal.renderpass_descriptor_cb +        .context.metal_renderpass_descriptor_userdata_cb +            A C callback function to obtain the MTLRenderPassDescriptor for the +            current frame when rendering to the default framebuffer, will be called +            in sg_begin_default_pass(). +        .context.metal.drawable_cb +        .context.metal.drawable_userdata_cb +            a C callback function to obtain a MTLDrawable for the current +            frame when rendering to the default framebuffer, will be called in +            sg_end_pass() of the default pass +        .context.metal.user_data +            optional user data pointer passed to the userdata versions of +            callback functions + +    D3D11 specific: +        .context.d3d11.device +            a pointer to the ID3D11Device object, this must have been created +            before sg_setup() is called +        .context.d3d11.device_context +            a pointer to the ID3D11DeviceContext object +        .context.d3d11.render_target_view_cb +        .context.d3d11.render_target_view_userdata_cb +            a C callback function to obtain a pointer to the current +            ID3D11RenderTargetView object of the default framebuffer, +            this function will be called in sg_begin_pass() when rendering +            to the default framebuffer +        .context.d3d11.depth_stencil_view_cb +        .context.d3d11.depth_stencil_view_userdata_cb +            a C callback function to obtain a pointer to the current +            ID3D11DepthStencilView object of the default framebuffer, +            this function will be called in sg_begin_pass() when rendering +            to the default framebuffer +        .context.metal.user_data +            optional user data pointer passed to the userdata versions of +            callback functions + +    WebGPU specific: +        .context.wgpu.device +            a WGPUDevice handle +        .context.wgpu.render_format +            WGPUTextureFormat of the swap chain surface +        .context.wgpu.render_view_cb +        .context.wgpu.render_view_userdata_cb +            callback to get the current WGPUTextureView of the swapchain's +            rendering attachment (may be an MSAA surface) +        .context.wgpu.resolve_view_cb +        .context.wgpu.resolve_view_userdata_cb +            callback to get the current WGPUTextureView of the swapchain's +            MSAA-resolve-target surface, must return 0 if not MSAA rendering +        .context.wgpu.depth_stencil_view_cb +        .context.wgpu.depth_stencil_view_userdata_cb +            callback to get current default-pass depth-stencil-surface WGPUTextureView +            the pixel format of the default WGPUTextureView must be WGPUTextureFormat_Depth24Plus8 +        .context.metal.user_data +            optional user data pointer passed to the userdata versions of +            callback functions + +    When using sokol_gfx.h and sokol_app.h together, consider using the +    helper function sapp_sgcontext() in the sokol_glue.h header to +    initialize the sg_desc.context nested struct. sapp_sgcontext() returns +    a completely initialized sg_context_desc struct with information +    provided by sokol_app.h. +*/ +typedef struct sg_gl_context_desc { +    bool force_gles2; +} sg_gl_context_desc; + +typedef struct sg_metal_context_desc { +    const void* device; +    const void* (*renderpass_descriptor_cb)(void); +    const void* (*renderpass_descriptor_userdata_cb)(void*); +    const void* (*drawable_cb)(void); +    const void* (*drawable_userdata_cb)(void*); +    void* user_data; +} sg_metal_context_desc; + +typedef struct sg_d3d11_context_desc { +    const void* device; +    const void* device_context; +    const void* (*render_target_view_cb)(void); +    const void* (*render_target_view_userdata_cb)(void*); +    const void* (*depth_stencil_view_cb)(void); +    const void* (*depth_stencil_view_userdata_cb)(void*); +    void* user_data; +} sg_d3d11_context_desc; + +typedef struct sg_wgpu_context_desc { +    const void* device;                    /* WGPUDevice */ +    const void* (*render_view_cb)(void);   /* returns WGPUTextureView */ +    const void* (*render_view_userdata_cb)(void*); +    const void* (*resolve_view_cb)(void);  /* returns WGPUTextureView */ +    const void* (*resolve_view_userdata_cb)(void*); +    const void* (*depth_stencil_view_cb)(void);    /* returns WGPUTextureView, must be WGPUTextureFormat_Depth24Plus8 */ +    const void* (*depth_stencil_view_userdata_cb)(void*); +    void* user_data; +} sg_wgpu_context_desc; + +typedef struct sg_context_desc { +    sg_pixel_format color_format; +    sg_pixel_format depth_format; +    int sample_count; +    sg_gl_context_desc gl; +    sg_metal_context_desc metal; +    sg_d3d11_context_desc d3d11; +    sg_wgpu_context_desc wgpu; +} sg_context_desc; + +typedef struct sg_desc { +    uint32_t _start_canary; +    int buffer_pool_size; +    int image_pool_size; +    int shader_pool_size; +    int pipeline_pool_size; +    int pass_pool_size; +    int context_pool_size; +    int uniform_buffer_size; +    int staging_buffer_size; +    int sampler_cache_size; +    sg_context_desc context; +    uint32_t _end_canary; +} sg_desc; + +/* setup and misc functions */ +SOKOL_GFX_API_DECL void sg_setup(const sg_desc* desc); +SOKOL_GFX_API_DECL void sg_shutdown(void); +SOKOL_GFX_API_DECL bool sg_isvalid(void); +SOKOL_GFX_API_DECL void sg_reset_state_cache(void); +SOKOL_GFX_API_DECL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks); +SOKOL_GFX_API_DECL void sg_push_debug_group(const char* name); +SOKOL_GFX_API_DECL void sg_pop_debug_group(void); + +/* resource creation, destruction and updating */ +SOKOL_GFX_API_DECL sg_buffer sg_make_buffer(const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL sg_image sg_make_image(const sg_image_desc* desc); +SOKOL_GFX_API_DECL sg_shader sg_make_shader(const sg_shader_desc* desc); +SOKOL_GFX_API_DECL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL sg_pass sg_make_pass(const sg_pass_desc* desc); +SOKOL_GFX_API_DECL void sg_destroy_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_destroy_image(sg_image img); +SOKOL_GFX_API_DECL void sg_destroy_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_destroy_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_destroy_pass(sg_pass pass); +SOKOL_GFX_API_DECL void sg_update_buffer(sg_buffer buf, const sg_range* data); +SOKOL_GFX_API_DECL void sg_update_image(sg_image img, const sg_image_data* data); +SOKOL_GFX_API_DECL int sg_append_buffer(sg_buffer buf, const sg_range* data); +SOKOL_GFX_API_DECL bool sg_query_buffer_overflow(sg_buffer buf); + +/* rendering functions */ +SOKOL_GFX_API_DECL void sg_begin_default_pass(const sg_pass_action* pass_action, int width, int height); +SOKOL_GFX_API_DECL void sg_begin_default_passf(const sg_pass_action* pass_action, float width, float height); +SOKOL_GFX_API_DECL void sg_begin_pass(sg_pass pass, const sg_pass_action* pass_action); +SOKOL_GFX_API_DECL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_apply_bindings(const sg_bindings* bindings); +SOKOL_GFX_API_DECL void sg_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range* data); +SOKOL_GFX_API_DECL void sg_draw(int base_element, int num_elements, int num_instances); +SOKOL_GFX_API_DECL void sg_end_pass(void); +SOKOL_GFX_API_DECL void sg_commit(void); + +/* getting information */ +SOKOL_GFX_API_DECL sg_desc sg_query_desc(void); +SOKOL_GFX_API_DECL sg_backend sg_query_backend(void); +SOKOL_GFX_API_DECL sg_features sg_query_features(void); +SOKOL_GFX_API_DECL sg_limits sg_query_limits(void); +SOKOL_GFX_API_DECL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt); +/* get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID) */ +SOKOL_GFX_API_DECL sg_resource_state sg_query_buffer_state(sg_buffer buf); +SOKOL_GFX_API_DECL sg_resource_state sg_query_image_state(sg_image img); +SOKOL_GFX_API_DECL sg_resource_state sg_query_shader_state(sg_shader shd); +SOKOL_GFX_API_DECL sg_resource_state sg_query_pipeline_state(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_resource_state sg_query_pass_state(sg_pass pass); +/* get runtime information about a resource */ +SOKOL_GFX_API_DECL sg_buffer_info sg_query_buffer_info(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_info sg_query_image_info(sg_image img); +SOKOL_GFX_API_DECL sg_shader_info sg_query_shader_info(sg_shader shd); +SOKOL_GFX_API_DECL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_pass_info sg_query_pass_info(sg_pass pass); +/* get resource creation desc struct with their default values replaced */ +SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc); +SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc); +SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL sg_pass_desc sg_query_pass_defaults(const sg_pass_desc* desc); + +/* separate resource allocation and initialization (for async setup) */ +SOKOL_GFX_API_DECL sg_buffer sg_alloc_buffer(void); +SOKOL_GFX_API_DECL sg_image sg_alloc_image(void); +SOKOL_GFX_API_DECL sg_shader sg_alloc_shader(void); +SOKOL_GFX_API_DECL sg_pipeline sg_alloc_pipeline(void); +SOKOL_GFX_API_DECL sg_pass sg_alloc_pass(void); +SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf_id); +SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img_id); +SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd_id); +SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip_id); +SOKOL_GFX_API_DECL void sg_dealloc_pass(sg_pass pass_id); +SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL void sg_init_image(sg_image img_id, const sg_image_desc* desc); +SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc); +SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL void sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc); +SOKOL_GFX_API_DECL bool sg_uninit_buffer(sg_buffer buf_id); +SOKOL_GFX_API_DECL bool sg_uninit_image(sg_image img_id); +SOKOL_GFX_API_DECL bool sg_uninit_shader(sg_shader shd_id); +SOKOL_GFX_API_DECL bool sg_uninit_pipeline(sg_pipeline pip_id); +SOKOL_GFX_API_DECL bool sg_uninit_pass(sg_pass pass_id); +SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf_id); +SOKOL_GFX_API_DECL void sg_fail_image(sg_image img_id); +SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd_id); +SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip_id); +SOKOL_GFX_API_DECL void sg_fail_pass(sg_pass pass_id); + +/* rendering contexts (optional) */ +SOKOL_GFX_API_DECL sg_context sg_setup_context(void); +SOKOL_GFX_API_DECL void sg_activate_context(sg_context ctx_id); +SOKOL_GFX_API_DECL void sg_discard_context(sg_context ctx_id); + +/* Backend-specific helper functions, these may come in handy for mixing +   sokol-gfx rendering with 'native backend' rendering functions. + +   This group of functions will be expanded as needed. +*/ + +/* D3D11: return ID3D11Device */ +SOKOL_GFX_API_DECL const void* sg_d3d11_device(void); + +/* Metal: return __bridge-casted MTLDevice */ +SOKOL_GFX_API_DECL const void* sg_mtl_device(void); + +/* Metal: return __bridge-casted MTLRenderCommandEncoder in current pass (or zero if outside pass) */ +SOKOL_GFX_API_DECL const void* sg_mtl_render_command_encoder(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for c++ */ +inline void sg_setup(const sg_desc& desc) { return sg_setup(&desc); } + +inline sg_buffer sg_make_buffer(const sg_buffer_desc& desc) { return sg_make_buffer(&desc); } +inline sg_image sg_make_image(const sg_image_desc& desc) { return sg_make_image(&desc); } +inline sg_shader sg_make_shader(const sg_shader_desc& desc) { return sg_make_shader(&desc); } +inline sg_pipeline sg_make_pipeline(const sg_pipeline_desc& desc) { return sg_make_pipeline(&desc); } +inline sg_pass sg_make_pass(const sg_pass_desc& desc) { return sg_make_pass(&desc); } +inline void sg_update_image(sg_image img, const sg_image_data& data) { return sg_update_image(img, &data); } + +inline void sg_begin_default_pass(const sg_pass_action& pass_action, int width, int height) { return sg_begin_default_pass(&pass_action, width, height); } +inline void sg_begin_default_passf(const sg_pass_action& pass_action, float width, float height) { return sg_begin_default_passf(&pass_action, width, height); } +inline void sg_begin_pass(sg_pass pass, const sg_pass_action& pass_action) { return sg_begin_pass(pass, &pass_action); } +inline void sg_apply_bindings(const sg_bindings& bindings) { return sg_apply_bindings(&bindings); } +inline void sg_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range& data) { return sg_apply_uniforms(stage, ub_index, &data); } + +inline sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc& desc) { return sg_query_buffer_defaults(&desc); } +inline sg_image_desc sg_query_image_defaults(const sg_image_desc& desc) { return sg_query_image_defaults(&desc); } +inline sg_shader_desc sg_query_shader_defaults(const sg_shader_desc& desc) { return sg_query_shader_defaults(&desc); } +inline sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc& desc) { return sg_query_pipeline_defaults(&desc); } +inline sg_pass_desc sg_query_pass_defaults(const sg_pass_desc& desc) { return sg_query_pass_defaults(&desc); } + +inline void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc& desc) { return sg_init_buffer(buf_id, &desc); } +inline void sg_init_image(sg_image img_id, const sg_image_desc& desc) { return sg_init_image(img_id, &desc); } +inline void sg_init_shader(sg_shader shd_id, const sg_shader_desc& desc) { return sg_init_shader(shd_id, &desc); } +inline void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc& desc) { return sg_init_pipeline(pip_id, &desc); } +inline void sg_init_pass(sg_pass pass_id, const sg_pass_desc& desc) { return sg_init_pass(pass_id, &desc); } + +inline void sg_update_buffer(sg_buffer buf_id, const sg_range& data) { return sg_update_buffer(buf_id, &data); } +inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_append_buffer(buf_id, &data); } +#endif +#endif // SOKOL_GFX_INCLUDED + +/*--- IMPLEMENTATION ---------------------------------------------------------*/ +#ifdef SOKOL_GFX_IMPL +#define SOKOL_GFX_IMPL_INCLUDED (1) + +#if !(defined(SOKOL_GLCORE33)||defined(SOKOL_GLES2)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_DUMMY_BACKEND)) +#error "Please select a backend with SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND" +#endif +#include <string.h> /* memset */ +#include <float.h> /* FLT_MAX */ + +#ifndef SOKOL_API_IMPL +    #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG +    #ifndef NDEBUG +        #define SOKOL_DEBUG (1) +    #endif +#endif +#ifndef SOKOL_ASSERT +    #include <assert.h> +    #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_VALIDATE_BEGIN +    #define SOKOL_VALIDATE_BEGIN() _sg_validate_begin() +#endif +#ifndef SOKOL_VALIDATE +    #define SOKOL_VALIDATE(cond, err) _sg_validate((cond), err) +#endif +#ifndef SOKOL_VALIDATE_END +    #define SOKOL_VALIDATE_END() _sg_validate_end() +#endif +#ifndef SOKOL_UNREACHABLE +    #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#ifndef SOKOL_MALLOC +    #include <stdlib.h> +    #define SOKOL_MALLOC(s) malloc(s) +    #define SOKOL_FREE(p) free(p) +#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__) +        #define _SOKOL_PRIVATE __attribute__((unused)) static +    #else +        #define _SOKOL_PRIVATE static +    #endif +#endif + +#ifndef _SOKOL_UNUSED +    #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(SOKOL_TRACE_HOOKS) +#define _SG_TRACE_ARGS(fn, ...) if (_sg.hooks.fn) { _sg.hooks.fn(__VA_ARGS__, _sg.hooks.user_data); } +#define _SG_TRACE_NOARGS(fn) if (_sg.hooks.fn) { _sg.hooks.fn(_sg.hooks.user_data); } +#else +#define _SG_TRACE_ARGS(fn, ...) +#define _SG_TRACE_NOARGS(fn) +#endif + +/* default clear values */ +#ifndef SG_DEFAULT_CLEAR_RED +#define SG_DEFAULT_CLEAR_RED (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_GREEN +#define SG_DEFAULT_CLEAR_GREEN (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_BLUE +#define SG_DEFAULT_CLEAR_BLUE (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_ALPHA +#define SG_DEFAULT_CLEAR_ALPHA (1.0f) +#endif +#ifndef SG_DEFAULT_CLEAR_DEPTH +#define SG_DEFAULT_CLEAR_DEPTH (1.0f) +#endif +#ifndef SG_DEFAULT_CLEAR_STENCIL +#define SG_DEFAULT_CLEAR_STENCIL (0) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4115)   /* named type definition in parentheses */ +#pragma warning(disable:4505)   /* unreferenced local function has been removed */ +#pragma warning(disable:4201)   /* nonstandard extension used: nameless struct/union (needed by d3d11.h) */ +#pragma warning(disable:4054)   /* 'type cast': from function pointer */ +#pragma warning(disable:4055)   /* 'type cast': from data pointer */ +#endif + +#if defined(SOKOL_D3D11) +    #ifndef D3D11_NO_HELPERS +    #define D3D11_NO_HELPERS +    #endif +    #ifndef WIN32_LEAN_AND_MEAN +    #define WIN32_LEAN_AND_MEAN +    #endif +    #ifndef NOMINMAX +    #define NOMINMAX +    #endif +    #include <d3d11.h> +    #include <d3dcompiler.h> +    #ifdef _MSC_VER +    #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) +        #pragma comment (lib, "WindowsApp") +    #else +        #pragma comment (lib, "kernel32") +        #pragma comment (lib, "user32") +        #pragma comment (lib, "dxgi") +        #pragma comment (lib, "d3d11") +        #pragma comment (lib, "dxguid") +    #endif +    #endif +#elif defined(SOKOL_METAL) +    // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting +    #if !defined(__cplusplus) +        #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) +            #error "sokol_gfx.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" +        #endif +    #endif +    #include <TargetConditionals.h> +    #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE +        #define _SG_TARGET_MACOS (1) +    #else +        #define _SG_TARGET_IOS (1) +        #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR +            #define _SG_TARGET_IOS_SIMULATOR (1) +        #endif +    #endif +    #import <Metal/Metal.h> +#elif defined(SOKOL_WGPU) +    #if defined(__EMSCRIPTEN__) +        #include <webgpu/webgpu.h> +    #else +        #include <dawn/webgpu.h> +    #endif +#elif defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +    #define _SOKOL_ANY_GL (1) + +    // include platform specific GL headers (or on Win32: use an embedded GL loader) +    #if !defined(SOKOL_EXTERNAL_GL_LOADER) +        #if defined(_WIN32) +            #if defined(SOKOL_GLCORE33) && !defined(SOKOL_EXTERNAL_GL_LOADER) +                #ifndef WIN32_LEAN_AND_MEAN +                #define WIN32_LEAN_AND_MEAN +                #endif +                #ifndef NOMINMAX +                #define NOMINMAX +                #endif +                #include <windows.h> +                #define _SOKOL_USE_WIN32_GL_LOADER (1) +                #pragma comment (lib, "kernel32")   // GetProcAddress() +            #endif +        #elif defined(__APPLE__) +            #include <TargetConditionals.h> +            #ifndef GL_SILENCE_DEPRECATION +                #define GL_SILENCE_DEPRECATION +            #endif +            #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE +                #include <OpenGL/gl3.h> +            #else +                #include <OpenGLES/ES3/gl.h> +                #include <OpenGLES/ES3/glext.h> +            #endif +        #elif defined(__EMSCRIPTEN__) || defined(__ANDROID__) +            #if defined(SOKOL_GLES3) +                #include <GLES3/gl3.h> +            #elif defined(SOKOL_GLES2) +                #ifndef GL_EXT_PROTOTYPES +                #define GL_GLEXT_PROTOTYPES +                #endif +                #include <GLES2/gl2.h> +                #include <GLES2/gl2ext.h> +            #endif +        #elif defined(__linux__) || defined(__unix__) +            #define GL_GLEXT_PROTOTYPES +            #include <GL/gl.h> +        #endif +    #endif + +    // optional GL loader definitions (only on Win32) +    #if defined(_SOKOL_USE_WIN32_GL_LOADER) +        #define __gl_h_ 1 +        #define __gl32_h_ 1 +        #define __gl31_h_ 1 +        #define __GL_H__ 1 +        #define __glext_h_ 1 +        #define __GLEXT_H_ 1 +        #define __gltypes_h_ 1 +        #define __glcorearb_h_ 1 +        #define __gl_glcorearb_h_ 1 +        #define GL_APIENTRY APIENTRY + +        typedef unsigned int  GLenum; +        typedef unsigned int  GLuint; +        typedef int  GLsizei; +        typedef char  GLchar; +        typedef ptrdiff_t  GLintptr; +        typedef ptrdiff_t  GLsizeiptr; +        typedef double  GLclampd; +        typedef unsigned short  GLushort; +        typedef unsigned char  GLubyte; +        typedef unsigned char  GLboolean; +        typedef uint64_t  GLuint64; +        typedef double  GLdouble; +        typedef unsigned short  GLhalf; +        typedef float  GLclampf; +        typedef unsigned int  GLbitfield; +        typedef signed char  GLbyte; +        typedef short  GLshort; +        typedef void  GLvoid; +        typedef int64_t  GLint64; +        typedef float  GLfloat; +        typedef struct __GLsync * GLsync; +        typedef int  GLint; +        #define GL_INT_2_10_10_10_REV 0x8D9F +        #define GL_R32F 0x822E +        #define GL_PROGRAM_POINT_SIZE 0x8642 +        #define GL_STENCIL_ATTACHMENT 0x8D20 +        #define GL_DEPTH_ATTACHMENT 0x8D00 +        #define GL_COLOR_ATTACHMENT2 0x8CE2 +        #define GL_COLOR_ATTACHMENT0 0x8CE0 +        #define GL_R16F 0x822D +        #define GL_COLOR_ATTACHMENT22 0x8CF6 +        #define GL_DRAW_FRAMEBUFFER 0x8CA9 +        #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +        #define GL_NUM_EXTENSIONS 0x821D +        #define GL_INFO_LOG_LENGTH 0x8B84 +        #define GL_VERTEX_SHADER 0x8B31 +        #define GL_INCR 0x1E02 +        #define GL_DYNAMIC_DRAW 0x88E8 +        #define GL_STATIC_DRAW 0x88E4 +        #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +        #define GL_TEXTURE_CUBE_MAP 0x8513 +        #define GL_FUNC_SUBTRACT 0x800A +        #define GL_FUNC_REVERSE_SUBTRACT 0x800B +        #define GL_CONSTANT_COLOR 0x8001 +        #define GL_DECR_WRAP 0x8508 +        #define GL_R8 0x8229 +        #define GL_LINEAR_MIPMAP_LINEAR 0x2703 +        #define GL_ELEMENT_ARRAY_BUFFER 0x8893 +        #define GL_SHORT 0x1402 +        #define GL_DEPTH_TEST 0x0B71 +        #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +        #define GL_LINK_STATUS 0x8B82 +        #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +        #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +        #define GL_RGBA16F 0x881A +        #define GL_CONSTANT_ALPHA 0x8003 +        #define GL_READ_FRAMEBUFFER 0x8CA8 +        #define GL_TEXTURE0 0x84C0 +        #define GL_TEXTURE_MIN_LOD 0x813A +        #define GL_CLAMP_TO_EDGE 0x812F +        #define GL_UNSIGNED_SHORT_5_6_5 0x8363 +        #define GL_TEXTURE_WRAP_R 0x8072 +        #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +        #define GL_NEAREST_MIPMAP_NEAREST 0x2700 +        #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +        #define GL_SRC_ALPHA_SATURATE 0x0308 +        #define GL_STREAM_DRAW 0x88E0 +        #define GL_ONE 1 +        #define GL_NEAREST_MIPMAP_LINEAR 0x2702 +        #define GL_RGB10_A2 0x8059 +        #define GL_RGBA8 0x8058 +        #define GL_COLOR_ATTACHMENT1 0x8CE1 +        #define GL_RGBA4 0x8056 +        #define GL_RGB8 0x8051 +        #define GL_ARRAY_BUFFER 0x8892 +        #define GL_STENCIL 0x1802 +        #define GL_TEXTURE_2D 0x0DE1 +        #define GL_DEPTH 0x1801 +        #define GL_FRONT 0x0404 +        #define GL_STENCIL_BUFFER_BIT 0x00000400 +        #define GL_REPEAT 0x2901 +        #define GL_RGBA 0x1908 +        #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +        #define GL_DECR 0x1E03 +        #define GL_FRAGMENT_SHADER 0x8B30 +        #define GL_FLOAT 0x1406 +        #define GL_TEXTURE_MAX_LOD 0x813B +        #define GL_DEPTH_COMPONENT 0x1902 +        #define GL_ONE_MINUS_DST_ALPHA 0x0305 +        #define GL_COLOR 0x1800 +        #define GL_TEXTURE_2D_ARRAY 0x8C1A +        #define GL_TRIANGLES 0x0004 +        #define GL_UNSIGNED_BYTE 0x1401 +        #define GL_TEXTURE_MAG_FILTER 0x2800 +        #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +        #define GL_NONE 0 +        #define GL_SRC_COLOR 0x0300 +        #define GL_BYTE 0x1400 +        #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +        #define GL_LINE_STRIP 0x0003 +        #define GL_TEXTURE_3D 0x806F +        #define GL_CW 0x0900 +        #define GL_LINEAR 0x2601 +        #define GL_RENDERBUFFER 0x8D41 +        #define GL_GEQUAL 0x0206 +        #define GL_COLOR_BUFFER_BIT 0x00004000 +        #define GL_RGBA32F 0x8814 +        #define GL_BLEND 0x0BE2 +        #define GL_ONE_MINUS_SRC_ALPHA 0x0303 +        #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +        #define GL_TEXTURE_WRAP_T 0x2803 +        #define GL_TEXTURE_WRAP_S 0x2802 +        #define GL_TEXTURE_MIN_FILTER 0x2801 +        #define GL_LINEAR_MIPMAP_NEAREST 0x2701 +        #define GL_EXTENSIONS 0x1F03 +        #define GL_NO_ERROR 0 +        #define GL_REPLACE 0x1E01 +        #define GL_KEEP 0x1E00 +        #define GL_CCW 0x0901 +        #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +        #define GL_RGB 0x1907 +        #define GL_TRIANGLE_STRIP 0x0005 +        #define GL_FALSE 0 +        #define GL_ZERO 0 +        #define GL_CULL_FACE 0x0B44 +        #define GL_INVERT 0x150A +        #define GL_INT 0x1404 +        #define GL_UNSIGNED_INT 0x1405 +        #define GL_UNSIGNED_SHORT 0x1403 +        #define GL_NEAREST 0x2600 +        #define GL_SCISSOR_TEST 0x0C11 +        #define GL_LEQUAL 0x0203 +        #define GL_STENCIL_TEST 0x0B90 +        #define GL_DITHER 0x0BD0 +        #define GL_DEPTH_COMPONENT16 0x81A5 +        #define GL_EQUAL 0x0202 +        #define GL_FRAMEBUFFER 0x8D40 +        #define GL_RGB5 0x8050 +        #define GL_LINES 0x0001 +        #define GL_DEPTH_BUFFER_BIT 0x00000100 +        #define GL_SRC_ALPHA 0x0302 +        #define GL_INCR_WRAP 0x8507 +        #define GL_LESS 0x0201 +        #define GL_MULTISAMPLE 0x809D +        #define GL_FRAMEBUFFER_BINDING 0x8CA6 +        #define GL_BACK 0x0405 +        #define GL_ALWAYS 0x0207 +        #define GL_FUNC_ADD 0x8006 +        #define GL_ONE_MINUS_DST_COLOR 0x0307 +        #define GL_NOTEQUAL 0x0205 +        #define GL_DST_COLOR 0x0306 +        #define GL_COMPILE_STATUS 0x8B81 +        #define GL_RED 0x1903 +        #define GL_COLOR_ATTACHMENT3 0x8CE3 +        #define GL_DST_ALPHA 0x0304 +        #define GL_RGB5_A1 0x8057 +        #define GL_GREATER 0x0204 +        #define GL_POLYGON_OFFSET_FILL 0x8037 +        #define GL_TRUE 1 +        #define GL_NEVER 0x0200 +        #define GL_POINTS 0x0000 +        #define GL_ONE_MINUS_SRC_COLOR 0x0301 +        #define GL_MIRRORED_REPEAT 0x8370 +        #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +        #define GL_R11F_G11F_B10F 0x8C3A +        #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +        #define GL_RGBA32UI 0x8D70 +        #define GL_RGB32UI 0x8D71 +        #define GL_RGBA16UI 0x8D76 +        #define GL_RGB16UI 0x8D77 +        #define GL_RGBA8UI 0x8D7C +        #define GL_RGB8UI 0x8D7D +        #define GL_RGBA32I 0x8D82 +        #define GL_RGB32I 0x8D83 +        #define GL_RGBA16I 0x8D88 +        #define GL_RGB16I 0x8D89 +        #define GL_RGBA8I 0x8D8E +        #define GL_RGB8I 0x8D8F +        #define GL_RED_INTEGER 0x8D94 +        #define GL_RG 0x8227 +        #define GL_RG_INTEGER 0x8228 +        #define GL_R8 0x8229 +        #define GL_R16 0x822A +        #define GL_RG8 0x822B +        #define GL_RG16 0x822C +        #define GL_R16F 0x822D +        #define GL_R32F 0x822E +        #define GL_RG16F 0x822F +        #define GL_RG32F 0x8230 +        #define GL_R8I 0x8231 +        #define GL_R8UI 0x8232 +        #define GL_R16I 0x8233 +        #define GL_R16UI 0x8234 +        #define GL_R32I 0x8235 +        #define GL_R32UI 0x8236 +        #define GL_RG8I 0x8237 +        #define GL_RG8UI 0x8238 +        #define GL_RG16I 0x8239 +        #define GL_RG16UI 0x823A +        #define GL_RG32I 0x823B +        #define GL_RG32UI 0x823C +        #define GL_RGBA_INTEGER 0x8D99 +        #define GL_R8_SNORM 0x8F94 +        #define GL_RG8_SNORM 0x8F95 +        #define GL_RGB8_SNORM 0x8F96 +        #define GL_RGBA8_SNORM 0x8F97 +        #define GL_R16_SNORM 0x8F98 +        #define GL_RG16_SNORM 0x8F99 +        #define GL_RGB16_SNORM 0x8F9A +        #define GL_RGBA16_SNORM 0x8F9B +        #define GL_RGBA16 0x805B +        #define GL_MAX_TEXTURE_SIZE 0x0D33 +        #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +        #define GL_MAX_3D_TEXTURE_SIZE 0x8073 +        #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +        #define GL_MAX_VERTEX_ATTRIBS 0x8869 +        #define GL_CLAMP_TO_BORDER 0x812D +        #define GL_TEXTURE_BORDER_COLOR 0x1004 +        #define GL_CURRENT_PROGRAM 0x8B8D +    #endif + +    #ifndef GL_UNSIGNED_INT_2_10_10_10_REV +    #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +    #endif +    #ifndef GL_UNSIGNED_INT_24_8 +    #define GL_UNSIGNED_INT_24_8 0x84FA +    #endif +    #ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT +    #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +    #endif +    #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT +    #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +    #endif +    #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT +    #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +    #endif +    #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT +    #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +    #endif +    #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT +    #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +    #endif +    #ifndef GL_COMPRESSED_RED_RGTC1 +    #define GL_COMPRESSED_RED_RGTC1 0x8DBB +    #endif +    #ifndef GL_COMPRESSED_SIGNED_RED_RGTC1 +    #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +    #endif +    #ifndef GL_COMPRESSED_RED_GREEN_RGTC2 +    #define GL_COMPRESSED_RED_GREEN_RGTC2 0x8DBD +    #endif +    #ifndef GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 +    #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 0x8DBE +    #endif +    #ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB +    #define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +    #endif +    #ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB +    #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +    #endif +    #ifndef GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB +    #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +    #endif +    #ifndef GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB +    #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +    #endif +    #ifndef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG +    #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +    #endif +    #ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG +    #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +    #endif +    #ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG +    #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +    #endif +    #ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG +    #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +    #endif +    #ifndef GL_COMPRESSED_RGB8_ETC2 +    #define GL_COMPRESSED_RGB8_ETC2 0x9274 +    #endif +    #ifndef GL_COMPRESSED_RGBA8_ETC2_EAC +    #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +    #endif +    #ifndef GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 +    #define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +    #endif +    #ifndef GL_COMPRESSED_RG11_EAC +    #define GL_COMPRESSED_RG11_EAC 0x9272 +    #endif +    #ifndef GL_COMPRESSED_SIGNED_RG11_EAC +    #define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +    #endif +    #ifndef GL_DEPTH24_STENCIL8 +    #define GL_DEPTH24_STENCIL8 0x88F0 +    #endif +    #ifndef GL_HALF_FLOAT +    #define GL_HALF_FLOAT 0x140B +    #endif +    #ifndef GL_DEPTH_STENCIL +    #define GL_DEPTH_STENCIL 0x84F9 +    #endif +    #ifndef GL_LUMINANCE +    #define GL_LUMINANCE 0x1909 +    #endif + +    #ifdef SOKOL_GLES2 +        #ifdef GL_ANGLE_instanced_arrays +            #define _SOKOL_GL_INSTANCING_ENABLED +            #define glDrawArraysInstanced(mode, first, count, instancecount)  glDrawArraysInstancedANGLE(mode, first, count, instancecount) +            #define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedANGLE(mode, count, type, indices, instancecount) +            #define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorANGLE(index, divisor) +        #elif defined(GL_EXT_draw_instanced) && defined(GL_EXT_instanced_arrays) +            #define _SOKOL_GL_INSTANCING_ENABLED +            #define glDrawArraysInstanced(mode, first, count, instancecount)  glDrawArraysInstancedEXT(mode, first, count, instancecount) +            #define glDrawElementsInstanced(mode, count, type, indices, instancecount) glDrawElementsInstancedEXT(mode, count, type, indices, instancecount) +            #define glVertexAttribDivisor(index, divisor) glVertexAttribDivisorEXT(index, divisor) +        #else +            #define _SOKOL_GLES2_INSTANCING_ERROR "Select GL_ANGLE_instanced_arrays or (GL_EXT_draw_instanced & GL_EXT_instanced_arrays) to enable instancing in GLES2" +            #define glDrawArraysInstanced(mode, first, count, instancecount) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) +            #define glDrawElementsInstanced(mode, count, type, indices, instancecount) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) +            #define glVertexAttribDivisor(index, divisor) SOKOL_ASSERT(0 && _SOKOL_GLES2_INSTANCING_ERROR) +        #endif +    #else +        #define _SOKOL_GL_INSTANCING_ENABLED +    #endif +    #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } +#endif + +/*=== COMMON BACKEND STUFF ===================================================*/ + +/* resource pool slots */ +typedef struct { +    uint32_t id; +    uint32_t ctx_id; +    sg_resource_state state; +} _sg_slot_t; + +/* constants */ +enum { +    _SG_STRING_SIZE = 16, +    _SG_SLOT_SHIFT = 16, +    _SG_SLOT_MASK = (1<<_SG_SLOT_SHIFT)-1, +    _SG_MAX_POOL_SIZE = (1<<_SG_SLOT_SHIFT), +    _SG_DEFAULT_BUFFER_POOL_SIZE = 128, +    _SG_DEFAULT_IMAGE_POOL_SIZE = 128, +    _SG_DEFAULT_SHADER_POOL_SIZE = 32, +    _SG_DEFAULT_PIPELINE_POOL_SIZE = 64, +    _SG_DEFAULT_PASS_POOL_SIZE = 16, +    _SG_DEFAULT_CONTEXT_POOL_SIZE = 16, +    _SG_DEFAULT_SAMPLER_CACHE_CAPACITY = 64, +    _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024, +    _SG_DEFAULT_STAGING_SIZE = 8 * 1024 * 1024, +}; + +/* fixed-size string */ +typedef struct { +    char buf[_SG_STRING_SIZE]; +} _sg_str_t; + +/* helper macros */ +#define _sg_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sg_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) +#define _sg_min(a,b) ((a<b)?a:b) +#define _sg_max(a,b) ((a>b)?a:b) +#define _sg_clamp(v,v0,v1) ((v<v0)?(v0):((v>v1)?(v1):(v))) +#define _sg_fequal(val,cmp,delta) (((val-cmp)> -delta)&&((val-cmp)<delta)) + +typedef struct { +    int size; +    int append_pos; +    bool append_overflow; +    sg_buffer_type type; +    sg_usage usage; +    uint32_t update_frame_index; +    uint32_t append_frame_index; +    int num_slots; +    int active_slot; +} _sg_buffer_common_t; + +_SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) { +    cmn->size = (int)desc->size; +    cmn->append_pos = 0; +    cmn->append_overflow = false; +    cmn->type = desc->type; +    cmn->usage = desc->usage; +    cmn->update_frame_index = 0; +    cmn->append_frame_index = 0; +    cmn->num_slots = (cmn->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; +    cmn->active_slot = 0; +} + +typedef struct { +    sg_image_type type; +    bool render_target; +    int width; +    int height; +    int num_slices; +    int num_mipmaps; +    sg_usage usage; +    sg_pixel_format pixel_format; +    int sample_count; +    sg_filter min_filter; +    sg_filter mag_filter; +    sg_wrap wrap_u; +    sg_wrap wrap_v; +    sg_wrap wrap_w; +    sg_border_color border_color; +    uint32_t max_anisotropy; +    uint32_t upd_frame_index; +    int num_slots; +    int active_slot; +} _sg_image_common_t; + +_SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_image_desc* desc) { +    cmn->type = desc->type; +    cmn->render_target = desc->render_target; +    cmn->width = desc->width; +    cmn->height = desc->height; +    cmn->num_slices = desc->num_slices; +    cmn->num_mipmaps = desc->num_mipmaps; +    cmn->usage = desc->usage; +    cmn->pixel_format = desc->pixel_format; +    cmn->sample_count = desc->sample_count; +    cmn->min_filter = desc->min_filter; +    cmn->mag_filter = desc->mag_filter; +    cmn->wrap_u = desc->wrap_u; +    cmn->wrap_v = desc->wrap_v; +    cmn->wrap_w = desc->wrap_w; +    cmn->border_color = desc->border_color; +    cmn->max_anisotropy = desc->max_anisotropy; +    cmn->upd_frame_index = 0; +    cmn->num_slots = (cmn->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; +    cmn->active_slot = 0; +} + +typedef struct { +    size_t size; +} _sg_uniform_block_t; + +typedef struct { +    sg_image_type image_type; +    sg_sampler_type sampler_type; +} _sg_shader_image_t; + +typedef struct { +    int num_uniform_blocks; +    int num_images; +    _sg_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; +    _sg_shader_image_t images[SG_MAX_SHADERSTAGE_IMAGES]; +} _sg_shader_stage_t; + +typedef struct { +    _sg_shader_stage_t stage[SG_NUM_SHADER_STAGES]; +} _sg_shader_common_t; + +_SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_shader_desc* desc) { +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS) ? &desc->vs : &desc->fs; +        _sg_shader_stage_t* stage = &cmn->stage[stage_index]; +        SOKOL_ASSERT(stage->num_uniform_blocks == 0); +        for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { +            const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; +            if (0 == ub_desc->size) { +                break; +            } +            stage->uniform_blocks[ub_index].size = ub_desc->size; +            stage->num_uniform_blocks++; +        } +        SOKOL_ASSERT(stage->num_images == 0); +        for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { +            const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; +            if (img_desc->image_type == _SG_IMAGETYPE_DEFAULT) { +                break; +            } +            stage->images[img_index].image_type = img_desc->image_type; +            stage->images[img_index].sampler_type = img_desc->sampler_type; +            stage->num_images++; +        } +    } +} + +typedef struct { +    sg_shader shader_id; +    sg_index_type index_type; +    bool vertex_layout_valid[SG_MAX_SHADERSTAGE_BUFFERS]; +    int color_attachment_count; +    sg_pixel_format color_formats[SG_MAX_COLOR_ATTACHMENTS]; +    sg_pixel_format depth_format; +    int sample_count; +    float depth_bias; +    float depth_bias_slope_scale; +    float depth_bias_clamp; +    sg_color blend_color; +} _sg_pipeline_common_t; + +_SOKOL_PRIVATE void _sg_pipeline_common_init(_sg_pipeline_common_t* cmn, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(desc->color_count < SG_MAX_COLOR_ATTACHMENTS); +    cmn->shader_id = desc->shader; +    cmn->index_type = desc->index_type; +    for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { +        cmn->vertex_layout_valid[i] = false; +    } +    cmn->color_attachment_count = desc->color_count; +    for (int i = 0; i < cmn->color_attachment_count; i++) { +        cmn->color_formats[i] = desc->colors[i].pixel_format; +    } +    cmn->depth_format = desc->depth.pixel_format; +    cmn->sample_count = desc->sample_count; +    cmn->depth_bias = desc->depth.bias; +    cmn->depth_bias_slope_scale = desc->depth.bias_slope_scale; +    cmn->depth_bias_clamp = desc->depth.bias_clamp; +    cmn->blend_color = desc->blend_color; +} + +typedef struct { +    sg_image image_id; +    int mip_level; +    int slice; +} _sg_pass_attachment_common_t; + +typedef struct { +    int num_color_atts; +    _sg_pass_attachment_common_t color_atts[SG_MAX_COLOR_ATTACHMENTS]; +    _sg_pass_attachment_common_t ds_att; +} _sg_pass_common_t; + +_SOKOL_PRIVATE void _sg_pass_common_init(_sg_pass_common_t* cmn, const sg_pass_desc* desc) { +    const sg_pass_attachment_desc* att_desc; +    _sg_pass_attachment_common_t* att; +    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +        att_desc = &desc->color_attachments[i]; +        if (att_desc->image.id != SG_INVALID_ID) { +            cmn->num_color_atts++; +            att = &cmn->color_atts[i]; +            att->image_id = att_desc->image; +            att->mip_level = att_desc->mip_level; +            att->slice = att_desc->slice; +        } +    } +    att_desc = &desc->depth_stencil_attachment; +    if (att_desc->image.id != SG_INVALID_ID) { +        att = &cmn->ds_att; +        att->image_id = att_desc->image; +        att->mip_level = att_desc->mip_level; +        att->slice = att_desc->slice; +    } +} + +/*=== GENERIC SAMPLER CACHE ==================================================*/ + +/* +    this is used by the Metal and WGPU backends to reduce the +    number of sampler state objects created through the backend API +*/ +typedef struct { +    sg_filter min_filter; +    sg_filter mag_filter; +    sg_wrap wrap_u; +    sg_wrap wrap_v; +    sg_wrap wrap_w; +    sg_border_color border_color; +    uint32_t max_anisotropy; +    int min_lod;    /* orig min/max_lod is float, this is int(min/max_lod*1000.0) */ +    int max_lod; +    uintptr_t sampler_handle; +} _sg_sampler_cache_item_t; + +typedef struct { +    int capacity; +    int num_items; +    _sg_sampler_cache_item_t* items; +} _sg_sampler_cache_t; + +_SOKOL_PRIVATE void _sg_smpcache_init(_sg_sampler_cache_t* cache, int capacity) { +    SOKOL_ASSERT(cache && (capacity > 0)); +    memset(cache, 0, sizeof(_sg_sampler_cache_t)); +    cache->capacity = capacity; +    const size_t size = (size_t)cache->capacity * sizeof(_sg_sampler_cache_item_t); +    cache->items = (_sg_sampler_cache_item_t*) SOKOL_MALLOC(size); +    SOKOL_ASSERT(cache->items); +    memset(cache->items, 0, size); +} + +_SOKOL_PRIVATE void _sg_smpcache_discard(_sg_sampler_cache_t* cache) { +    SOKOL_ASSERT(cache && cache->items); +    SOKOL_FREE(cache->items); +    cache->items = 0; +    cache->num_items = 0; +    cache->capacity = 0; +} + +_SOKOL_PRIVATE int _sg_smpcache_minlod_int(float min_lod) { +    return (int) (min_lod * 1000.0f); +} + +_SOKOL_PRIVATE int _sg_smpcache_maxlod_int(float max_lod) { +    return (int) (_sg_clamp(max_lod, 0.0f, 1000.0f) * 1000.0f); +} + +_SOKOL_PRIVATE int _sg_smpcache_find_item(const _sg_sampler_cache_t* cache, const sg_image_desc* img_desc) { +    /* return matching sampler cache item index or -1 */ +    SOKOL_ASSERT(cache && cache->items); +    SOKOL_ASSERT(img_desc); +    const int min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); +    const int max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); +    for (int i = 0; i < cache->num_items; i++) { +        const _sg_sampler_cache_item_t* item = &cache->items[i]; +        if ((img_desc->min_filter == item->min_filter) && +            (img_desc->mag_filter == item->mag_filter) && +            (img_desc->wrap_u == item->wrap_u) && +            (img_desc->wrap_v == item->wrap_v) && +            (img_desc->wrap_w == item->wrap_w) && +            (img_desc->max_anisotropy == item->max_anisotropy) && +            (img_desc->border_color == item->border_color) && +            (min_lod == item->min_lod) && +            (max_lod == item->max_lod)) +        { +            return i; +        } +    } +    /* fallthrough: no matching cache item found */ +    return -1; +} + +_SOKOL_PRIVATE void _sg_smpcache_add_item(_sg_sampler_cache_t* cache, const sg_image_desc* img_desc, uintptr_t sampler_handle) { +    SOKOL_ASSERT(cache && cache->items); +    SOKOL_ASSERT(img_desc); +    SOKOL_ASSERT(cache->num_items < cache->capacity); +    const int item_index = cache->num_items++; +    _sg_sampler_cache_item_t* item = &cache->items[item_index]; +    item->min_filter = img_desc->min_filter; +    item->mag_filter = img_desc->mag_filter; +    item->wrap_u = img_desc->wrap_u; +    item->wrap_v = img_desc->wrap_v; +    item->wrap_w = img_desc->wrap_w; +    item->border_color = img_desc->border_color; +    item->max_anisotropy = img_desc->max_anisotropy; +    item->min_lod = _sg_smpcache_minlod_int(img_desc->min_lod); +    item->max_lod = _sg_smpcache_maxlod_int(img_desc->max_lod); +    item->sampler_handle = sampler_handle; +} + +_SOKOL_PRIVATE uintptr_t _sg_smpcache_sampler(_sg_sampler_cache_t* cache, int item_index) { +    SOKOL_ASSERT(cache && cache->items); +    SOKOL_ASSERT(item_index < cache->num_items); +    return cache->items[item_index].sampler_handle; +} + +/*=== DUMMY BACKEND DECLARATIONS =============================================*/ +#if defined(SOKOL_DUMMY_BACKEND) +typedef struct { +    _sg_slot_t slot; +    _sg_buffer_common_t cmn; +} _sg_dummy_buffer_t; +typedef _sg_dummy_buffer_t _sg_buffer_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_image_common_t cmn; +} _sg_dummy_image_t; +typedef _sg_dummy_image_t _sg_image_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_shader_common_t cmn; +} _sg_dummy_shader_t; +typedef _sg_dummy_shader_t _sg_shader_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_shader_t* shader; +    _sg_pipeline_common_t cmn; +} _sg_dummy_pipeline_t; +typedef _sg_dummy_pipeline_t _sg_pipeline_t; + +typedef struct { +    _sg_image_t* image; +} _sg_dummy_attachment_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pass_common_t cmn; +    struct { +        _sg_dummy_attachment_t color_atts[SG_MAX_COLOR_ATTACHMENTS]; +        _sg_dummy_attachment_t ds_att; +    } dmy; +} _sg_dummy_pass_t; +typedef _sg_dummy_pass_t _sg_pass_t; +typedef _sg_pass_attachment_common_t _sg_pass_attachment_t; + +typedef struct { +    _sg_slot_t slot; +} _sg_dummy_context_t; +typedef _sg_dummy_context_t _sg_context_t; + +/*== GL BACKEND DECLARATIONS =================================================*/ +#elif defined(_SOKOL_ANY_GL) +typedef struct { +    _sg_slot_t slot; +    _sg_buffer_common_t cmn; +    struct { +        GLuint buf[SG_NUM_INFLIGHT_FRAMES]; +        bool ext_buffers;   /* if true, external buffers were injected with sg_buffer_desc.gl_buffers */ +    } gl; +} _sg_gl_buffer_t; +typedef _sg_gl_buffer_t _sg_buffer_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_image_common_t cmn; +    struct { +        GLenum target; +        GLuint depth_render_buffer; +        GLuint msaa_render_buffer; +        GLuint tex[SG_NUM_INFLIGHT_FRAMES]; +        bool ext_textures;  /* if true, external textures were injected with sg_image_desc.gl_textures */ +    } gl; +} _sg_gl_image_t; +typedef _sg_gl_image_t _sg_image_t; + +typedef struct { +    GLint gl_loc; +    sg_uniform_type type; +    uint8_t count; +    uint16_t offset; +} _sg_gl_uniform_t; + +typedef struct { +    int num_uniforms; +    _sg_gl_uniform_t uniforms[SG_MAX_UB_MEMBERS]; +} _sg_gl_uniform_block_t; + +typedef struct { +    int gl_tex_slot; +} _sg_gl_shader_image_t; + +typedef struct { +    _sg_str_t name; +} _sg_gl_shader_attr_t; + +typedef struct { +    _sg_gl_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; +    _sg_gl_shader_image_t images[SG_MAX_SHADERSTAGE_IMAGES]; +} _sg_gl_shader_stage_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_shader_common_t cmn; +    struct { +        GLuint prog; +        _sg_gl_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; +        _sg_gl_shader_stage_t stage[SG_NUM_SHADER_STAGES]; +    } gl; +} _sg_gl_shader_t; +typedef _sg_gl_shader_t _sg_shader_t; + +typedef struct { +    int8_t vb_index;        /* -1 if attr is not enabled */ +    int8_t divisor;         /* -1 if not initialized */ +    uint8_t stride; +    uint8_t size; +    uint8_t normalized; +    int offset; +    GLenum type; +} _sg_gl_attr_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pipeline_common_t cmn; +    _sg_shader_t* shader; +    struct { +        _sg_gl_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; +        sg_depth_state depth; +        sg_stencil_state stencil; +        sg_primitive_type primitive_type; +        sg_blend_state blend; +        sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS]; +        sg_cull_mode cull_mode; +        sg_face_winding face_winding; +        int sample_count; +        bool alpha_to_coverage_enabled; +    } gl; +} _sg_gl_pipeline_t; +typedef _sg_gl_pipeline_t _sg_pipeline_t; + +typedef struct { +    _sg_image_t* image; +    GLuint gl_msaa_resolve_buffer; +} _sg_gl_attachment_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pass_common_t cmn; +    struct { +        GLuint fb; +        _sg_gl_attachment_t color_atts[SG_MAX_COLOR_ATTACHMENTS]; +        _sg_gl_attachment_t ds_att; +    } gl; +} _sg_gl_pass_t; +typedef _sg_gl_pass_t _sg_pass_t; +typedef _sg_pass_attachment_common_t _sg_pass_attachment_t; + +typedef struct { +    _sg_slot_t slot; +    #if !defined(SOKOL_GLES2) +    GLuint vao; +    #endif +    GLuint default_framebuffer; +} _sg_gl_context_t; +typedef _sg_gl_context_t _sg_context_t; + +typedef struct { +    _sg_gl_attr_t gl_attr; +    GLuint gl_vbuf; +} _sg_gl_cache_attr_t; + +typedef struct { +    GLenum target; +    GLuint texture; +} _sg_gl_texture_bind_slot; + +typedef struct { +    sg_depth_state depth; +    sg_stencil_state stencil; +    sg_blend_state blend; +    sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS]; +    sg_cull_mode cull_mode; +    sg_face_winding face_winding; +    bool polygon_offset_enabled; +    int sample_count; +    sg_color blend_color; +    bool alpha_to_coverage_enabled; +    _sg_gl_cache_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; +    GLuint vertex_buffer; +    GLuint index_buffer; +    GLuint stored_vertex_buffer; +    GLuint stored_index_buffer; +    GLuint prog; +    _sg_gl_texture_bind_slot textures[SG_MAX_SHADERSTAGE_IMAGES]; +    _sg_gl_texture_bind_slot stored_texture; +    int cur_ib_offset; +    GLenum cur_primitive_type; +    GLenum cur_index_type; +    GLenum cur_active_texture; +    _sg_pipeline_t* cur_pipeline; +    sg_pipeline cur_pipeline_id; +} _sg_gl_state_cache_t; + +typedef struct { +    bool valid; +    bool gles2; +    bool in_pass; +    int cur_pass_width; +    int cur_pass_height; +    _sg_context_t* cur_context; +    _sg_pass_t* cur_pass; +    sg_pass cur_pass_id; +    _sg_gl_state_cache_t cache; +    bool ext_anisotropic; +    GLint max_anisotropy; +    GLint max_combined_texture_image_units; +    #if _SOKOL_USE_WIN32_GL_LOADER +    HINSTANCE opengl32_dll; +    #endif +} _sg_gl_backend_t; + +/*== D3D11 BACKEND DECLARATIONS ==============================================*/ +#elif defined(SOKOL_D3D11) + +typedef struct { +    _sg_slot_t slot; +    _sg_buffer_common_t cmn; +    struct { +        ID3D11Buffer* buf; +    } d3d11; +} _sg_d3d11_buffer_t; +typedef _sg_d3d11_buffer_t _sg_buffer_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_image_common_t cmn; +    struct { +        DXGI_FORMAT format; +        ID3D11Texture2D* tex2d; +        ID3D11Texture3D* tex3d; +        ID3D11Texture2D* texds; +        ID3D11Texture2D* texmsaa; +        ID3D11ShaderResourceView* srv; +        ID3D11SamplerState* smp; +    } d3d11; +} _sg_d3d11_image_t; +typedef _sg_d3d11_image_t _sg_image_t; + +typedef struct { +    _sg_str_t sem_name; +    int sem_index; +} _sg_d3d11_shader_attr_t; + +typedef struct { +    ID3D11Buffer* cbufs[SG_MAX_SHADERSTAGE_UBS]; +} _sg_d3d11_shader_stage_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_shader_common_t cmn; +    struct { +        _sg_d3d11_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; +        _sg_d3d11_shader_stage_t stage[SG_NUM_SHADER_STAGES]; +        ID3D11VertexShader* vs; +        ID3D11PixelShader* fs; +        void* vs_blob; +        size_t vs_blob_length; +    } d3d11; +} _sg_d3d11_shader_t; +typedef _sg_d3d11_shader_t _sg_shader_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pipeline_common_t cmn; +    _sg_shader_t* shader; +    struct { +        UINT stencil_ref; +        UINT vb_strides[SG_MAX_SHADERSTAGE_BUFFERS]; +        D3D_PRIMITIVE_TOPOLOGY topology; +        DXGI_FORMAT index_format; +        ID3D11InputLayout* il; +        ID3D11RasterizerState* rs; +        ID3D11DepthStencilState* dss; +        ID3D11BlendState* bs; +    } d3d11; +} _sg_d3d11_pipeline_t; +typedef _sg_d3d11_pipeline_t _sg_pipeline_t; + +typedef struct { +    _sg_image_t* image; +    ID3D11RenderTargetView* rtv; +} _sg_d3d11_color_attachment_t; + +typedef struct { +    _sg_image_t* image; +    ID3D11DepthStencilView* dsv; +} _sg_d3d11_ds_attachment_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pass_common_t cmn; +    struct { +        _sg_d3d11_color_attachment_t color_atts[SG_MAX_COLOR_ATTACHMENTS]; +        _sg_d3d11_ds_attachment_t ds_att; +    } d3d11; +} _sg_d3d11_pass_t; +typedef _sg_d3d11_pass_t _sg_pass_t; +typedef _sg_pass_attachment_common_t _sg_pass_attachment_t; + +typedef struct { +    _sg_slot_t slot; +} _sg_d3d11_context_t; +typedef _sg_d3d11_context_t _sg_context_t; + +typedef struct { +    bool valid; +    ID3D11Device* dev; +    ID3D11DeviceContext* ctx; +    const void* (*rtv_cb)(void); +    const void* (*rtv_userdata_cb)(void*); +    const void* (*dsv_cb)(void); +    const void* (*dsv_userdata_cb)(void*); +    void* user_data; +    bool in_pass; +    bool use_indexed_draw; +    int cur_width; +    int cur_height; +    int num_rtvs; +    _sg_pass_t* cur_pass; +    sg_pass cur_pass_id; +    _sg_pipeline_t* cur_pipeline; +    sg_pipeline cur_pipeline_id; +    ID3D11RenderTargetView* cur_rtvs[SG_MAX_COLOR_ATTACHMENTS]; +    ID3D11DepthStencilView* cur_dsv; +    /* on-demand loaded d3dcompiler_47.dll handles */ +    HINSTANCE d3dcompiler_dll; +    bool d3dcompiler_dll_load_failed; +    pD3DCompile D3DCompile_func; +    /* the following arrays are used for unbinding resources, they will always contain zeroes */ +    ID3D11RenderTargetView* zero_rtvs[SG_MAX_COLOR_ATTACHMENTS]; +    ID3D11Buffer* zero_vbs[SG_MAX_SHADERSTAGE_BUFFERS]; +    UINT zero_vb_offsets[SG_MAX_SHADERSTAGE_BUFFERS]; +    UINT zero_vb_strides[SG_MAX_SHADERSTAGE_BUFFERS]; +    ID3D11Buffer* zero_cbs[SG_MAX_SHADERSTAGE_UBS]; +    ID3D11ShaderResourceView* zero_srvs[SG_MAX_SHADERSTAGE_IMAGES]; +    ID3D11SamplerState* zero_smps[SG_MAX_SHADERSTAGE_IMAGES]; +    /* global subresourcedata array for texture updates */ +    D3D11_SUBRESOURCE_DATA subres_data[SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS]; +} _sg_d3d11_backend_t; + +/*=== METAL BACKEND DECLARATIONS =============================================*/ +#elif defined(SOKOL_METAL) + +#if defined(_SG_TARGET_MACOS) || defined(_SG_TARGET_IOS_SIMULATOR) +#define _SG_MTL_UB_ALIGN (256) +#else +#define _SG_MTL_UB_ALIGN (16) +#endif +#define _SG_MTL_INVALID_SLOT_INDEX (0) + +typedef struct { +    uint32_t frame_index;   /* frame index at which it is safe to release this resource */ +    int slot_index; +} _sg_mtl_release_item_t; + +typedef struct { +    NSMutableArray* pool; +    int num_slots; +    int free_queue_top; +    int* free_queue; +    int release_queue_front; +    int release_queue_back; +    _sg_mtl_release_item_t* release_queue; +} _sg_mtl_idpool_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_buffer_common_t cmn; +    struct { +        int buf[SG_NUM_INFLIGHT_FRAMES];  /* index into _sg_mtl_pool */ +    } mtl; +} _sg_mtl_buffer_t; +typedef _sg_mtl_buffer_t _sg_buffer_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_image_common_t cmn; +    struct { +        int tex[SG_NUM_INFLIGHT_FRAMES]; +        int depth_tex; +        int msaa_tex; +        int sampler_state; +    } mtl; +} _sg_mtl_image_t; +typedef _sg_mtl_image_t _sg_image_t; + +typedef struct { +    int mtl_lib; +    int mtl_func; +} _sg_mtl_shader_stage_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_shader_common_t cmn; +    struct { +        _sg_mtl_shader_stage_t stage[SG_NUM_SHADER_STAGES]; +    } mtl; +} _sg_mtl_shader_t; +typedef _sg_mtl_shader_t _sg_shader_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pipeline_common_t cmn; +    _sg_shader_t* shader; +    struct { +        MTLPrimitiveType prim_type; +        int index_size; +        MTLIndexType index_type; +        MTLCullMode cull_mode; +        MTLWinding winding; +        uint32_t stencil_ref; +        int rps; +        int dss; +    } mtl; +} _sg_mtl_pipeline_t; +typedef _sg_mtl_pipeline_t _sg_pipeline_t; + +typedef struct { +    _sg_image_t* image; +} _sg_mtl_attachment_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pass_common_t cmn; +    struct { +        _sg_mtl_attachment_t color_atts[SG_MAX_COLOR_ATTACHMENTS]; +        _sg_mtl_attachment_t ds_att; +    } mtl; +} _sg_mtl_pass_t; +typedef _sg_mtl_pass_t _sg_pass_t; +typedef _sg_pass_attachment_common_t _sg_pass_attachment_t; + +typedef struct { +    _sg_slot_t slot; +} _sg_mtl_context_t; +typedef _sg_mtl_context_t _sg_context_t; + +/* resouce binding state cache */ +typedef struct { +    const _sg_pipeline_t* cur_pipeline; +    sg_pipeline cur_pipeline_id; +    const _sg_buffer_t* cur_indexbuffer; +    int cur_indexbuffer_offset; +    sg_buffer cur_indexbuffer_id; +    const _sg_buffer_t* cur_vertexbuffers[SG_MAX_SHADERSTAGE_BUFFERS]; +    int cur_vertexbuffer_offsets[SG_MAX_SHADERSTAGE_BUFFERS]; +    sg_buffer cur_vertexbuffer_ids[SG_MAX_SHADERSTAGE_BUFFERS]; +    const _sg_image_t* cur_vs_images[SG_MAX_SHADERSTAGE_IMAGES]; +    sg_image cur_vs_image_ids[SG_MAX_SHADERSTAGE_IMAGES]; +    const _sg_image_t* cur_fs_images[SG_MAX_SHADERSTAGE_IMAGES]; +    sg_image cur_fs_image_ids[SG_MAX_SHADERSTAGE_IMAGES]; +} _sg_mtl_state_cache_t; + +typedef struct { +    bool valid; +    const void*(*renderpass_descriptor_cb)(void); +    const void*(*renderpass_descriptor_userdata_cb)(void*); +    const void*(*drawable_cb)(void); +    const void*(*drawable_userdata_cb)(void*); +    void* user_data; +    uint32_t frame_index; +    uint32_t cur_frame_rotate_index; +    int ub_size; +    int cur_ub_offset; +    uint8_t* cur_ub_base_ptr; +    bool in_pass; +    bool pass_valid; +    int cur_width; +    int cur_height; +    _sg_mtl_state_cache_t state_cache; +    _sg_sampler_cache_t sampler_cache; +    _sg_mtl_idpool_t idpool; +    dispatch_semaphore_t sem; +    id<MTLDevice> device; +    id<MTLCommandQueue> cmd_queue; +    id<MTLCommandBuffer> cmd_buffer; +    id<MTLRenderCommandEncoder> cmd_encoder; +    id<MTLBuffer> uniform_buffers[SG_NUM_INFLIGHT_FRAMES]; +} _sg_mtl_backend_t; + +/*=== WGPU BACKEND DECLARATIONS ==============================================*/ +#elif defined(SOKOL_WGPU) + +#define _SG_WGPU_STAGING_ALIGN (256) +#define _SG_WGPU_STAGING_PIPELINE_SIZE (8) +#define _SG_WGPU_ROWPITCH_ALIGN (256) +#define _SG_WGPU_MAX_SHADERSTAGE_IMAGES (8) +#define _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE (1<<16) + +typedef struct { +    _sg_slot_t slot; +    _sg_buffer_common_t cmn; +    struct { +        WGPUBuffer buf; +    } wgpu; +} _sg_wgpu_buffer_t; +typedef _sg_wgpu_buffer_t _sg_buffer_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_image_common_t cmn; +    struct { +        WGPUTexture tex; +        WGPUTextureView tex_view; +        WGPUTexture msaa_tex; +        WGPUSampler sampler; +    } wgpu; +} _sg_wgpu_image_t; +typedef _sg_wgpu_image_t _sg_image_t; + +typedef struct { +    WGPUShaderModule module; +    WGPUBindGroupLayout bind_group_layout; +    _sg_str_t entry; +} _sg_wgpu_shader_stage_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_shader_common_t cmn; +    struct { +        _sg_wgpu_shader_stage_t stage[SG_NUM_SHADER_STAGES]; +    } wgpu; +} _sg_wgpu_shader_t; +typedef _sg_wgpu_shader_t _sg_shader_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pipeline_common_t cmn; +    _sg_shader_t* shader; +    struct { +        WGPURenderPipeline pip; +        uint32_t stencil_ref; +    } wgpu; +} _sg_wgpu_pipeline_t; +typedef _sg_wgpu_pipeline_t _sg_pipeline_t; + +typedef struct { +    _sg_image_t* image; +    WGPUTextureView render_tex_view; +    WGPUTextureView resolve_tex_view; +} _sg_wgpu_attachment_t; + +typedef struct { +    _sg_slot_t slot; +    _sg_pass_common_t cmn; +    struct { +        _sg_wgpu_attachment_t color_atts[SG_MAX_COLOR_ATTACHMENTS]; +        _sg_wgpu_attachment_t ds_att; +    } wgpu; +} _sg_wgpu_pass_t; +typedef _sg_wgpu_pass_t _sg_pass_t; +typedef _sg_pass_attachment_common_t _sg_pass_attachment_t; + +typedef struct { +    _sg_slot_t slot; +} _sg_wgpu_context_t; +typedef _sg_wgpu_context_t _sg_context_t; + +/* a pool of per-frame uniform buffers */ +typedef struct { +    WGPUBindGroupLayout bindgroup_layout; +    uint32_t num_bytes; +    uint32_t offset;    /* current offset into current frame's mapped uniform buffer */ +    uint32_t bind_offsets[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; +    WGPUBuffer buf;     /* the GPU-side uniform buffer */ +    WGPUBindGroup bindgroup; +    struct { +        int num; +        int cur; +        WGPUBuffer buf[_SG_WGPU_STAGING_PIPELINE_SIZE]; /* CPU-side staging buffers */ +        uint8_t* ptr[_SG_WGPU_STAGING_PIPELINE_SIZE];   /* if != 0, staging buffer currently mapped */ +    } stage; +} _sg_wgpu_ubpool_t; + +/* ...a similar pool (like uniform buffer pool) of dynamic-resource staging buffers */ +typedef struct { +    uint32_t num_bytes; +    uint32_t offset;    /* current offset into current frame's staging buffer */ +    int num;            /* number of staging buffers */ +    int cur;            /* this frame's staging buffer */ +    WGPUBuffer buf[_SG_WGPU_STAGING_PIPELINE_SIZE]; /* CPU-side staging buffers */ +    uint8_t* ptr[_SG_WGPU_STAGING_PIPELINE_SIZE];   /* if != 0, staging buffer currently mapped */ +} _sg_wgpu_stagingpool_t; + +/* the WGPU backend state */ +typedef struct { +    bool valid; +    bool in_pass; +    bool draw_indexed; +    int cur_width; +    int cur_height; +    WGPUDevice dev; +    WGPUTextureView (*render_view_cb)(void); +    WGPUTextureView (*render_view_userdata_cb)(void*); +    WGPUTextureView (*resolve_view_cb)(void); +    WGPUTextureView (*resolve_view_userdata_cb)(void*); +    WGPUTextureView (*depth_stencil_view_cb)(void); +    WGPUTextureView (*depth_stencil_view_userdata_cb)(void*); +    void* user_data; +    WGPUQueue queue; +    WGPUCommandEncoder render_cmd_enc; +    WGPUCommandEncoder staging_cmd_enc; +    WGPURenderPassEncoder pass_enc; +    WGPUBindGroup empty_bind_group; +    const _sg_pipeline_t* cur_pipeline; +    sg_pipeline cur_pipeline_id; +    _sg_sampler_cache_t sampler_cache; +    _sg_wgpu_ubpool_t ub; +    _sg_wgpu_stagingpool_t staging; +} _sg_wgpu_backend_t; +#endif + +/*=== RESOURCE POOL DECLARATIONS =============================================*/ + +/* this *MUST* remain 0 */ +#define _SG_INVALID_SLOT_INDEX (0) + +typedef struct { +    int size; +    int queue_top; +    uint32_t* gen_ctrs; +    int* free_queue; +} _sg_pool_t; + +typedef struct { +    _sg_pool_t buffer_pool; +    _sg_pool_t image_pool; +    _sg_pool_t shader_pool; +    _sg_pool_t pipeline_pool; +    _sg_pool_t pass_pool; +    _sg_pool_t context_pool; +    _sg_buffer_t* buffers; +    _sg_image_t* images; +    _sg_shader_t* shaders; +    _sg_pipeline_t* pipelines; +    _sg_pass_t* passes; +    _sg_context_t* contexts; +} _sg_pools_t; + +/*=== VALIDATION LAYER DECLARATIONS ==========================================*/ +typedef enum { +    /* special case 'validation was successful' */ +    _SG_VALIDATE_SUCCESS, + +    /* buffer creation */ +    _SG_VALIDATE_BUFFERDESC_CANARY, +    _SG_VALIDATE_BUFFERDESC_SIZE, +    _SG_VALIDATE_BUFFERDESC_DATA, +    _SG_VALIDATE_BUFFERDESC_DATA_SIZE, +    _SG_VALIDATE_BUFFERDESC_NO_DATA, + +    /* image creation */ +    _SG_VALIDATE_IMAGEDESC_CANARY, +    _SG_VALIDATE_IMAGEDESC_WIDTH, +    _SG_VALIDATE_IMAGEDESC_HEIGHT, +    _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT, +    _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, +    _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT, +    _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT, +    _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE, +    _SG_VALIDATE_IMAGEDESC_RT_NO_DATA, +    _SG_VALIDATE_IMAGEDESC_DATA, +    _SG_VALIDATE_IMAGEDESC_NO_DATA, + +    /* shader creation */ +    _SG_VALIDATE_SHADERDESC_CANARY, +    _SG_VALIDATE_SHADERDESC_SOURCE, +    _SG_VALIDATE_SHADERDESC_BYTECODE, +    _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE, +    _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, +    _SG_VALIDATE_SHADERDESC_NO_CONT_UBS, +    _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS, +    _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS, +    _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS, +    _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME, +    _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH, +    _SG_VALIDATE_SHADERDESC_IMG_NAME, +    _SG_VALIDATE_SHADERDESC_ATTR_NAMES, +    _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS, +    _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, + +    /* pipeline creation */ +    _SG_VALIDATE_PIPELINEDESC_CANARY, +    _SG_VALIDATE_PIPELINEDESC_SHADER, +    _SG_VALIDATE_PIPELINEDESC_NO_ATTRS, +    _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, +    _SG_VALIDATE_PIPELINEDESC_ATTR_NAME, +    _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, + +    /* pass creation */ +    _SG_VALIDATE_PASSDESC_CANARY, +    _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS, +    _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS, +    _SG_VALIDATE_PASSDESC_IMAGE, +    _SG_VALIDATE_PASSDESC_MIPLEVEL, +    _SG_VALIDATE_PASSDESC_FACE, +    _SG_VALIDATE_PASSDESC_LAYER, +    _SG_VALIDATE_PASSDESC_SLICE, +    _SG_VALIDATE_PASSDESC_IMAGE_NO_RT, +    _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT, +    _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT, +    _SG_VALIDATE_PASSDESC_IMAGE_SIZES, +    _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS, + +    /* sg_begin_pass validation */ +    _SG_VALIDATE_BEGINPASS_PASS, +    _SG_VALIDATE_BEGINPASS_IMAGE, + +    /* sg_apply_pipeline validation */ +    _SG_VALIDATE_APIP_PIPELINE_VALID_ID, +    _SG_VALIDATE_APIP_PIPELINE_EXISTS, +    _SG_VALIDATE_APIP_PIPELINE_VALID, +    _SG_VALIDATE_APIP_SHADER_EXISTS, +    _SG_VALIDATE_APIP_SHADER_VALID, +    _SG_VALIDATE_APIP_ATT_COUNT, +    _SG_VALIDATE_APIP_COLOR_FORMAT, +    _SG_VALIDATE_APIP_DEPTH_FORMAT, +    _SG_VALIDATE_APIP_SAMPLE_COUNT, + +    /* sg_apply_bindings validation */ +    _SG_VALIDATE_ABND_PIPELINE, +    _SG_VALIDATE_ABND_PIPELINE_EXISTS, +    _SG_VALIDATE_ABND_PIPELINE_VALID, +    _SG_VALIDATE_ABND_VBS, +    _SG_VALIDATE_ABND_VB_EXISTS, +    _SG_VALIDATE_ABND_VB_TYPE, +    _SG_VALIDATE_ABND_VB_OVERFLOW, +    _SG_VALIDATE_ABND_NO_IB, +    _SG_VALIDATE_ABND_IB, +    _SG_VALIDATE_ABND_IB_EXISTS, +    _SG_VALIDATE_ABND_IB_TYPE, +    _SG_VALIDATE_ABND_IB_OVERFLOW, +    _SG_VALIDATE_ABND_VS_IMGS, +    _SG_VALIDATE_ABND_VS_IMG_EXISTS, +    _SG_VALIDATE_ABND_VS_IMG_TYPES, +    _SG_VALIDATE_ABND_FS_IMGS, +    _SG_VALIDATE_ABND_FS_IMG_EXISTS, +    _SG_VALIDATE_ABND_FS_IMG_TYPES, + +    /* sg_apply_uniforms validation */ +    _SG_VALIDATE_AUB_NO_PIPELINE, +    _SG_VALIDATE_AUB_NO_UB_AT_SLOT, +    _SG_VALIDATE_AUB_SIZE, + +    /* sg_update_buffer validation */ +    _SG_VALIDATE_UPDATEBUF_USAGE, +    _SG_VALIDATE_UPDATEBUF_SIZE, +    _SG_VALIDATE_UPDATEBUF_ONCE, +    _SG_VALIDATE_UPDATEBUF_APPEND, + +    /* sg_append_buffer validation */ +    _SG_VALIDATE_APPENDBUF_USAGE, +    _SG_VALIDATE_APPENDBUF_SIZE, +    _SG_VALIDATE_APPENDBUF_UPDATE, + +    /* sg_update_image validation */ +    _SG_VALIDATE_UPDIMG_USAGE, +    _SG_VALIDATE_UPDIMG_NOTENOUGHDATA, +    _SG_VALIDATE_UPDIMG_SIZE, +    _SG_VALIDATE_UPDIMG_COMPRESSED, +    _SG_VALIDATE_UPDIMG_ONCE +} _sg_validate_error_t; + +/*=== GENERIC BACKEND STATE ==================================================*/ + +typedef struct { +    bool valid; +    sg_desc desc;       /* original desc with default values patched in */ +    uint32_t frame_index; +    sg_context active_context; +    sg_pass cur_pass; +    sg_pipeline cur_pipeline; +    bool pass_valid; +    bool bindings_valid; +    bool next_draw_valid; +    #if defined(SOKOL_DEBUG) +    _sg_validate_error_t validate_error; +    #endif +    _sg_pools_t pools; +    sg_backend backend; +    sg_features features; +    sg_limits limits; +    sg_pixelformat_info formats[_SG_PIXELFORMAT_NUM]; +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_backend_t gl; +    #elif defined(SOKOL_METAL) +    _sg_mtl_backend_t mtl; +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_backend_t d3d11; +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_backend_t wgpu; +    #endif +    #if defined(SOKOL_TRACE_HOOKS) +    sg_trace_hooks hooks; +    #endif +} _sg_state_t; +static _sg_state_t _sg; + +/*-- helper functions --------------------------------------------------------*/ + +_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { +    return 0 == str->buf[0]; +} + +_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { +    return &str->buf[0]; +} + +_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { +    SOKOL_ASSERT(dst); +    if (src) { +        #if defined(_MSC_VER) +        strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); +        #else +        strncpy(dst->buf, src, _SG_STRING_SIZE); +        #endif +        dst->buf[_SG_STRING_SIZE-1] = 0; +    } +    else { +        memset(dst->buf, 0, _SG_STRING_SIZE); +    } +} + +/* return byte size of a vertex format */ +_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { +    switch (fmt) { +        case SG_VERTEXFORMAT_FLOAT:     return 4; +        case SG_VERTEXFORMAT_FLOAT2:    return 8; +        case SG_VERTEXFORMAT_FLOAT3:    return 12; +        case SG_VERTEXFORMAT_FLOAT4:    return 16; +        case SG_VERTEXFORMAT_BYTE4:     return 4; +        case SG_VERTEXFORMAT_BYTE4N:    return 4; +        case SG_VERTEXFORMAT_UBYTE4:    return 4; +        case SG_VERTEXFORMAT_UBYTE4N:   return 4; +        case SG_VERTEXFORMAT_SHORT2:    return 4; +        case SG_VERTEXFORMAT_SHORT2N:   return 4; +        case SG_VERTEXFORMAT_USHORT2N:  return 4; +        case SG_VERTEXFORMAT_SHORT4:    return 8; +        case SG_VERTEXFORMAT_SHORT4N:   return 8; +        case SG_VERTEXFORMAT_USHORT4N:  return 8; +        case SG_VERTEXFORMAT_UINT10_N2: return 4; +        case SG_VERTEXFORMAT_INVALID:   return 0; +        default: +            SOKOL_UNREACHABLE; +            return -1; +    } +} + +/* return the byte size of a shader uniform */ +_SOKOL_PRIVATE int _sg_uniform_size(sg_uniform_type type, int count) { +    switch (type) { +        case SG_UNIFORMTYPE_INVALID:    return 0; +        case SG_UNIFORMTYPE_FLOAT:      return 4 * count; +        case SG_UNIFORMTYPE_FLOAT2:     return 8 * count; +        case SG_UNIFORMTYPE_FLOAT3:     return 12 * count; /* FIXME: std140??? */ +        case SG_UNIFORMTYPE_FLOAT4:     return 16 * count; +        case SG_UNIFORMTYPE_MAT4:       return 64 * count; +        default: +            SOKOL_UNREACHABLE; +            return -1; +    } +} + +/* return true if pixel format is a compressed format */ +_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_BC1_RGBA: +        case SG_PIXELFORMAT_BC2_RGBA: +        case SG_PIXELFORMAT_BC3_RGBA: +        case SG_PIXELFORMAT_BC4_R: +        case SG_PIXELFORMAT_BC4_RSN: +        case SG_PIXELFORMAT_BC5_RG: +        case SG_PIXELFORMAT_BC5_RGSN: +        case SG_PIXELFORMAT_BC6H_RGBF: +        case SG_PIXELFORMAT_BC6H_RGBUF: +        case SG_PIXELFORMAT_BC7_RGBA: +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +        case SG_PIXELFORMAT_ETC2_RGB8: +        case SG_PIXELFORMAT_ETC2_RGB8A1: +        case SG_PIXELFORMAT_ETC2_RGBA8: +        case SG_PIXELFORMAT_ETC2_RG11: +        case SG_PIXELFORMAT_ETC2_RG11SN: +            return true; +        default: +            return false; +    } +} + +/* return true if pixel format is a valid render target format */ +_SOKOL_PRIVATE bool _sg_is_valid_rendertarget_color_format(sg_pixel_format fmt) { +    const int fmt_index = (int) fmt; +    SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); +    return _sg.formats[fmt_index].render && !_sg.formats[fmt_index].depth; +} + +/* return true if pixel format is a valid depth format */ +_SOKOL_PRIVATE bool _sg_is_valid_rendertarget_depth_format(sg_pixel_format fmt) { +    const int fmt_index = (int) fmt; +    SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); +    return _sg.formats[fmt_index].render && _sg.formats[fmt_index].depth; +} + +/* return true if pixel format is a depth-stencil format */ +_SOKOL_PRIVATE bool _sg_is_depth_stencil_format(sg_pixel_format fmt) { +    return (SG_PIXELFORMAT_DEPTH_STENCIL == fmt); +} + +/* return the bytes-per-pixel for a pixel format */ +_SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_R8: +        case SG_PIXELFORMAT_R8SN: +        case SG_PIXELFORMAT_R8UI: +        case SG_PIXELFORMAT_R8SI: +            return 1; + +        case SG_PIXELFORMAT_R16: +        case SG_PIXELFORMAT_R16SN: +        case SG_PIXELFORMAT_R16UI: +        case SG_PIXELFORMAT_R16SI: +        case SG_PIXELFORMAT_R16F: +        case SG_PIXELFORMAT_RG8: +        case SG_PIXELFORMAT_RG8SN: +        case SG_PIXELFORMAT_RG8UI: +        case SG_PIXELFORMAT_RG8SI: +            return 2; + +        case SG_PIXELFORMAT_R32UI: +        case SG_PIXELFORMAT_R32SI: +        case SG_PIXELFORMAT_R32F: +        case SG_PIXELFORMAT_RG16: +        case SG_PIXELFORMAT_RG16SN: +        case SG_PIXELFORMAT_RG16UI: +        case SG_PIXELFORMAT_RG16SI: +        case SG_PIXELFORMAT_RG16F: +        case SG_PIXELFORMAT_RGBA8: +        case SG_PIXELFORMAT_RGBA8SN: +        case SG_PIXELFORMAT_RGBA8UI: +        case SG_PIXELFORMAT_RGBA8SI: +        case SG_PIXELFORMAT_BGRA8: +        case SG_PIXELFORMAT_RGB10A2: +        case SG_PIXELFORMAT_RG11B10F: +            return 4; + +        case SG_PIXELFORMAT_RG32UI: +        case SG_PIXELFORMAT_RG32SI: +        case SG_PIXELFORMAT_RG32F: +        case SG_PIXELFORMAT_RGBA16: +        case SG_PIXELFORMAT_RGBA16SN: +        case SG_PIXELFORMAT_RGBA16UI: +        case SG_PIXELFORMAT_RGBA16SI: +        case SG_PIXELFORMAT_RGBA16F: +            return 8; + +        case SG_PIXELFORMAT_RGBA32UI: +        case SG_PIXELFORMAT_RGBA32SI: +        case SG_PIXELFORMAT_RGBA32F: +            return 16; + +        default: +            SOKOL_UNREACHABLE; +            return 0; +    } +} + +_SOKOL_PRIVATE int _sg_roundup(int val, int round_to) { +    return (val+(round_to-1)) & ~(round_to-1); +} + +/* return row pitch for an image +    see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp +*/ +_SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) { +    int pitch; +    switch (fmt) { +        case SG_PIXELFORMAT_BC1_RGBA: +        case SG_PIXELFORMAT_BC4_R: +        case SG_PIXELFORMAT_BC4_RSN: +        case SG_PIXELFORMAT_ETC2_RGB8: +        case SG_PIXELFORMAT_ETC2_RGB8A1: +            pitch = ((width + 3) / 4) * 8; +            pitch = pitch < 8 ? 8 : pitch; +            break; +        case SG_PIXELFORMAT_BC2_RGBA: +        case SG_PIXELFORMAT_BC3_RGBA: +        case SG_PIXELFORMAT_BC5_RG: +        case SG_PIXELFORMAT_BC5_RGSN: +        case SG_PIXELFORMAT_BC6H_RGBF: +        case SG_PIXELFORMAT_BC6H_RGBUF: +        case SG_PIXELFORMAT_BC7_RGBA: +        case SG_PIXELFORMAT_ETC2_RGBA8: +        case SG_PIXELFORMAT_ETC2_RG11: +        case SG_PIXELFORMAT_ETC2_RG11SN: +            pitch = ((width + 3) / 4) * 16; +            pitch = pitch < 16 ? 16 : pitch; +            break; +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +            { +                const int block_size = 4*4; +                const int bpp = 4; +                int width_blocks = width / 4; +                width_blocks = width_blocks < 2 ? 2 : width_blocks; +                pitch = width_blocks * ((block_size * bpp) / 8); +            } +            break; +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +            { +                const int block_size = 8*4; +                const int bpp = 2; +                int width_blocks = width / 4; +                width_blocks = width_blocks < 2 ? 2 : width_blocks; +                pitch = width_blocks * ((block_size * bpp) / 8); +            } +            break; +        default: +            pitch = width * _sg_pixelformat_bytesize(fmt); +            break; +    } +    pitch = _sg_roundup(pitch, row_align); +    return pitch; +} + +/* compute the number of rows in a surface depending on pixel format */ +_SOKOL_PRIVATE int _sg_num_rows(sg_pixel_format fmt, int height) { +    int num_rows; +    switch (fmt) { +        case SG_PIXELFORMAT_BC1_RGBA: +        case SG_PIXELFORMAT_BC4_R: +        case SG_PIXELFORMAT_BC4_RSN: +        case SG_PIXELFORMAT_ETC2_RGB8: +        case SG_PIXELFORMAT_ETC2_RGB8A1: +        case SG_PIXELFORMAT_ETC2_RGBA8: +        case SG_PIXELFORMAT_ETC2_RG11: +        case SG_PIXELFORMAT_ETC2_RG11SN: +        case SG_PIXELFORMAT_BC2_RGBA: +        case SG_PIXELFORMAT_BC3_RGBA: +        case SG_PIXELFORMAT_BC5_RG: +        case SG_PIXELFORMAT_BC5_RGSN: +        case SG_PIXELFORMAT_BC6H_RGBF: +        case SG_PIXELFORMAT_BC6H_RGBUF: +        case SG_PIXELFORMAT_BC7_RGBA: +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +            num_rows = ((height + 3) / 4); +            break; +        default: +            num_rows = height; +            break; +    } +    if (num_rows < 1) { +        num_rows = 1; +    } +    return num_rows; +} + +/* return pitch of a 2D subimage / texture slice +    see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp +*/ +_SOKOL_PRIVATE int _sg_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align) { +    int num_rows = _sg_num_rows(fmt, height); +    return num_rows * _sg_row_pitch(fmt, width, row_align); +} + +/* capability table pixel format helper functions */ +_SOKOL_PRIVATE void _sg_pixelformat_all(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->filter = true; +    pfi->blend = true; +    pfi->render = true; +    pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_s(sg_pixelformat_info* pfi) { +    pfi->sample = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sf(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->filter = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sr(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_srmd(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->render = true; +    pfi->msaa = true; +    pfi->depth = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_srm(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->render = true; +    pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfrm(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->filter = true; +    pfi->render = true; +    pfi->msaa = true; +} +_SOKOL_PRIVATE void _sg_pixelformat_sbrm(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->blend = true; +    pfi->render = true; +    pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sbr(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->blend = true; +    pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfbr(sg_pixelformat_info* pfi) { +    pfi->sample = true; +    pfi->filter = true; +    pfi->blend = true; +    pfi->render = true; +} + +/* resolve pass action defaults into a new pass action struct */ +_SOKOL_PRIVATE void _sg_resolve_default_pass_action(const sg_pass_action* from, sg_pass_action* to) { +    SOKOL_ASSERT(from && to); +    *to = *from; +    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +        if (to->colors[i].action  == _SG_ACTION_DEFAULT) { +            to->colors[i].action = SG_ACTION_CLEAR; +            to->colors[i].value.r = SG_DEFAULT_CLEAR_RED; +            to->colors[i].value.g = SG_DEFAULT_CLEAR_GREEN; +            to->colors[i].value.b = SG_DEFAULT_CLEAR_BLUE; +            to->colors[i].value.a = SG_DEFAULT_CLEAR_ALPHA; +        } +    } +    if (to->depth.action == _SG_ACTION_DEFAULT) { +        to->depth.action = SG_ACTION_CLEAR; +        to->depth.value = SG_DEFAULT_CLEAR_DEPTH; +    } +    if (to->stencil.action == _SG_ACTION_DEFAULT) { +        to->stencil.action = SG_ACTION_CLEAR; +        to->stencil.value = SG_DEFAULT_CLEAR_STENCIL; +    } +} + +/*== DUMMY BACKEND IMPL ======================================================*/ +#if defined(SOKOL_DUMMY_BACKEND) + +_SOKOL_PRIVATE void _sg_dummy_setup_backend(const sg_desc* desc) { +    SOKOL_ASSERT(desc); +    _SOKOL_UNUSED(desc); +    _sg.backend = SG_BACKEND_DUMMY; +    for (int i = SG_PIXELFORMAT_R8; i < SG_PIXELFORMAT_BC1_RGBA; i++) { +        _sg.formats[i].sample = true; +        _sg.formats[i].filter = true; +        _sg.formats[i].render = true; +        _sg.formats[i].blend = true; +        _sg.formats[i].msaa = true; +    } +    _sg.formats[SG_PIXELFORMAT_DEPTH].depth = true; +    _sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL].depth = true; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_backend(void) { +    /* empty */ +} + +_SOKOL_PRIVATE void _sg_dummy_reset_state_cache(void) { +    /* empty*/ +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_destroy_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +} + +_SOKOL_PRIVATE void _sg_dummy_activate_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { +    SOKOL_ASSERT(buf && desc); +    _sg_buffer_common_init(&buf->cmn, desc); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_destroy_buffer(_sg_buffer_t* buf) { +    SOKOL_ASSERT(buf); +    _SOKOL_UNUSED(buf); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_image(_sg_image_t* img, const sg_image_desc* desc) { +    SOKOL_ASSERT(img && desc); +    _sg_image_common_init(&img->cmn, desc); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_destroy_image(_sg_image_t* img) { +    SOKOL_ASSERT(img); +    _SOKOL_UNUSED(img); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { +    SOKOL_ASSERT(shd && desc); +    _sg_shader_common_init(&shd->cmn, desc); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_destroy_shader(_sg_shader_t* shd) { +    SOKOL_ASSERT(shd); +    _SOKOL_UNUSED(shd); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(pip && desc); +    pip->shader = shd; +    _sg_pipeline_common_init(&pip->cmn, desc); +    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +        if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +            break; +        } +        SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +        pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_destroy_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    _SOKOL_UNUSED(pip); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pass(_sg_pass_t* pass, _sg_image_t** att_images, const sg_pass_desc* desc) { +    SOKOL_ASSERT(pass && desc); +    SOKOL_ASSERT(att_images && att_images[0]); + +    _sg_pass_common_init(&pass->cmn, desc); + +    const sg_pass_attachment_desc* att_desc; +    for (int i = 0; i < pass->cmn.num_color_atts; i++) { +        att_desc = &desc->color_attachments[i]; +        SOKOL_ASSERT(att_desc->image.id != SG_INVALID_ID); +        SOKOL_ASSERT(0 == pass->dmy.color_atts[i].image); +        SOKOL_ASSERT(att_images[i] && (att_images[i]->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(att_images[i]->cmn.pixel_format)); +        pass->dmy.color_atts[i].image = att_images[i]; +    } + +    SOKOL_ASSERT(0 == pass->dmy.ds_att.image); +    att_desc = &desc->depth_stencil_attachment; +    if (att_desc->image.id != SG_INVALID_ID) { +        const int ds_img_index = SG_MAX_COLOR_ATTACHMENTS; +        SOKOL_ASSERT(att_images[ds_img_index] && (att_images[ds_img_index]->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(att_images[ds_img_index]->cmn.pixel_format)); +        pass->dmy.ds_att.image = att_images[ds_img_index]; +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_destroy_pass(_sg_pass_t* pass) { +    SOKOL_ASSERT(pass); +    _SOKOL_UNUSED(pass); +} + +_SOKOL_PRIVATE _sg_image_t* _sg_dummy_pass_color_image(const _sg_pass_t* pass, int index) { +    SOKOL_ASSERT(pass && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); +    /* NOTE: may return null */ +    return pass->dmy.color_atts[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_dummy_pass_ds_image(const _sg_pass_t* pass) { +    /* NOTE: may return null */ +    SOKOL_ASSERT(pass); +    return pass->dmy.ds_att.image; +} + +_SOKOL_PRIVATE void _sg_dummy_begin_pass(_sg_pass_t* pass, const sg_pass_action* action, int w, int h) { +    SOKOL_ASSERT(action); +    _SOKOL_UNUSED(pass); +    _SOKOL_UNUSED(action); +    _SOKOL_UNUSED(w); +    _SOKOL_UNUSED(h); +} + +_SOKOL_PRIVATE void _sg_dummy_end_pass(void) { +    /* empty */ +} + +_SOKOL_PRIVATE void _sg_dummy_commit(void) { +    /* empty */ +} + +_SOKOL_PRIVATE void _sg_dummy_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +    _SOKOL_UNUSED(x); +    _SOKOL_UNUSED(y); +    _SOKOL_UNUSED(w); +    _SOKOL_UNUSED(h); +    _SOKOL_UNUSED(origin_top_left); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    _SOKOL_UNUSED(x); +    _SOKOL_UNUSED(y); +    _SOKOL_UNUSED(w); +    _SOKOL_UNUSED(h); +    _SOKOL_UNUSED(origin_top_left); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    _SOKOL_UNUSED(pip); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_bindings( +    _sg_pipeline_t* pip, +    _sg_buffer_t** vbs, const int* vb_offsets, int num_vbs, +    _sg_buffer_t* ib, int ib_offset, +    _sg_image_t** vs_imgs, int num_vs_imgs, +    _sg_image_t** fs_imgs, int num_fs_imgs) +{ +    SOKOL_ASSERT(pip); +    SOKOL_ASSERT(vbs && vb_offsets); +    SOKOL_ASSERT(vs_imgs); +    SOKOL_ASSERT(fs_imgs); +    _SOKOL_UNUSED(pip); +    _SOKOL_UNUSED(vbs); _SOKOL_UNUSED(vb_offsets); _SOKOL_UNUSED(num_vbs); +    _SOKOL_UNUSED(ib); _SOKOL_UNUSED(ib_offset); +    _SOKOL_UNUSED(vs_imgs); _SOKOL_UNUSED(num_vs_imgs); +    _SOKOL_UNUSED(fs_imgs); _SOKOL_UNUSED(num_fs_imgs); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    _SOKOL_UNUSED(stage_index); +    _SOKOL_UNUSED(ub_index); +    _SOKOL_UNUSED(data); +} + +_SOKOL_PRIVATE void _sg_dummy_draw(int base_element, int num_elements, int num_instances) { +    _SOKOL_UNUSED(base_element); +    _SOKOL_UNUSED(num_elements); +    _SOKOL_UNUSED(num_instances); +} + +_SOKOL_PRIVATE void _sg_dummy_update_buffer(_sg_buffer_t* buf, const sg_range* data) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    _SOKOL_UNUSED(data); +    if (++buf->cmn.active_slot >= buf->cmn.num_slots) { +        buf->cmn.active_slot = 0; +    } +} + +_SOKOL_PRIVATE int _sg_dummy_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    _SOKOL_UNUSED(data); +    if (new_frame) { +        if (++buf->cmn.active_slot >= buf->cmn.num_slots) { +            buf->cmn.active_slot = 0; +        } +    } +    /* NOTE: this is a requirement from WebGPU, but we want identical behaviour across all backend */ +    return _sg_roundup((int)data->size, 4); +} + +_SOKOL_PRIVATE void _sg_dummy_update_image(_sg_image_t* img, const sg_image_data* data) { +    SOKOL_ASSERT(img && data); +    _SOKOL_UNUSED(data); +    if (++img->cmn.active_slot >= img->cmn.num_slots) { +        img->cmn.active_slot = 0; +    } +} + +/*== GL BACKEND ==============================================================*/ +#elif defined(_SOKOL_ANY_GL) + +/*=== OPTIONAL GL LOADER FOR WIN32 ===========================================*/ +#if defined(_SOKOL_USE_WIN32_GL_LOADER) + +// X Macro list of GL function names and signatures +#define _SG_GL_FUNCS \ +    _SG_XMACRO(glBindVertexArray,                 void, (GLuint array)) \ +    _SG_XMACRO(glFramebufferTextureLayer,         void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ +    _SG_XMACRO(glGenFramebuffers,                 void, (GLsizei n, GLuint * framebuffers)) \ +    _SG_XMACRO(glBindFramebuffer,                 void, (GLenum target, GLuint framebuffer)) \ +    _SG_XMACRO(glBindRenderbuffer,                void, (GLenum target, GLuint renderbuffer)) \ +    _SG_XMACRO(glGetStringi,                      const GLubyte *, (GLenum name, GLuint index)) \ +    _SG_XMACRO(glClearBufferfi,                   void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ +    _SG_XMACRO(glClearBufferfv,                   void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ +    _SG_XMACRO(glClearBufferuiv,                  void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ +    _SG_XMACRO(glClearBufferiv,                   void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ +    _SG_XMACRO(glDeleteRenderbuffers,             void, (GLsizei n, const GLuint * renderbuffers)) \ +    _SG_XMACRO(glUniform4fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \ +    _SG_XMACRO(glUniform2fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \ +    _SG_XMACRO(glUseProgram,                      void, (GLuint program)) \ +    _SG_XMACRO(glShaderSource,                    void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ +    _SG_XMACRO(glLinkProgram,                     void, (GLuint program)) \ +    _SG_XMACRO(glGetUniformLocation,              GLint, (GLuint program, const GLchar * name)) \ +    _SG_XMACRO(glGetShaderiv,                     void, (GLuint shader, GLenum pname, GLint * params)) \ +    _SG_XMACRO(glGetProgramInfoLog,               void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ +    _SG_XMACRO(glGetAttribLocation,               GLint, (GLuint program, const GLchar * name)) \ +    _SG_XMACRO(glDisableVertexAttribArray,        void, (GLuint index)) \ +    _SG_XMACRO(glDeleteShader,                    void, (GLuint shader)) \ +    _SG_XMACRO(glDeleteProgram,                   void, (GLuint program)) \ +    _SG_XMACRO(glCompileShader,                   void, (GLuint shader)) \ +    _SG_XMACRO(glStencilFuncSeparate,             void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ +    _SG_XMACRO(glStencilOpSeparate,               void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ +    _SG_XMACRO(glRenderbufferStorageMultisample,  void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ +    _SG_XMACRO(glDrawBuffers,                     void, (GLsizei n, const GLenum * bufs)) \ +    _SG_XMACRO(glVertexAttribDivisor,             void, (GLuint index, GLuint divisor)) \ +    _SG_XMACRO(glBufferSubData,                   void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ +    _SG_XMACRO(glGenBuffers,                      void, (GLsizei n, GLuint * buffers)) \ +    _SG_XMACRO(glCheckFramebufferStatus,          GLenum, (GLenum target)) \ +    _SG_XMACRO(glFramebufferRenderbuffer,         void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ +    _SG_XMACRO(glCompressedTexImage2D,            void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ +    _SG_XMACRO(glCompressedTexImage3D,            void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ +    _SG_XMACRO(glActiveTexture,                   void, (GLenum texture)) \ +    _SG_XMACRO(glTexSubImage3D,                   void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ +    _SG_XMACRO(glUniformMatrix4fv,                void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ +    _SG_XMACRO(glRenderbufferStorage,             void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ +    _SG_XMACRO(glGenTextures,                     void, (GLsizei n, GLuint * textures)) \ +    _SG_XMACRO(glPolygonOffset,                   void, (GLfloat factor, GLfloat units)) \ +    _SG_XMACRO(glDrawElements,                    void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ +    _SG_XMACRO(glDeleteFramebuffers,              void, (GLsizei n, const GLuint * framebuffers)) \ +    _SG_XMACRO(glBlendEquationSeparate,           void, (GLenum modeRGB, GLenum modeAlpha)) \ +    _SG_XMACRO(glDeleteTextures,                  void, (GLsizei n, const GLuint * textures)) \ +    _SG_XMACRO(glGetProgramiv,                    void, (GLuint program, GLenum pname, GLint * params)) \ +    _SG_XMACRO(glBindTexture,                     void, (GLenum target, GLuint texture)) \ +    _SG_XMACRO(glTexImage3D,                      void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ +    _SG_XMACRO(glCreateShader,                    GLuint, (GLenum type)) \ +    _SG_XMACRO(glTexSubImage2D,                   void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ +    _SG_XMACRO(glClearDepth,                      void, (GLdouble depth)) \ +    _SG_XMACRO(glFramebufferTexture2D,            void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ +    _SG_XMACRO(glCreateProgram,                   GLuint, (void)) \ +    _SG_XMACRO(glViewport,                        void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ +    _SG_XMACRO(glDeleteBuffers,                   void, (GLsizei n, const GLuint * buffers)) \ +    _SG_XMACRO(glDrawArrays,                      void, (GLenum mode, GLint first, GLsizei count)) \ +    _SG_XMACRO(glDrawElementsInstanced,           void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ +    _SG_XMACRO(glVertexAttribPointer,             void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ +    _SG_XMACRO(glUniform1i,                       void, (GLint location, GLint v0)) \ +    _SG_XMACRO(glDisable,                         void, (GLenum cap)) \ +    _SG_XMACRO(glColorMask,                       void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ +    _SG_XMACRO(glColorMaski,                      void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ +    _SG_XMACRO(glBindBuffer,                      void, (GLenum target, GLuint buffer)) \ +    _SG_XMACRO(glDeleteVertexArrays,              void, (GLsizei n, const GLuint * arrays)) \ +    _SG_XMACRO(glDepthMask,                       void, (GLboolean flag)) \ +    _SG_XMACRO(glDrawArraysInstanced,             void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ +    _SG_XMACRO(glClearStencil,                    void, (GLint s)) \ +    _SG_XMACRO(glScissor,                         void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ +    _SG_XMACRO(glUniform3fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \ +    _SG_XMACRO(glGenRenderbuffers,                void, (GLsizei n, GLuint * renderbuffers)) \ +    _SG_XMACRO(glBufferData,                      void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ +    _SG_XMACRO(glBlendFuncSeparate,               void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ +    _SG_XMACRO(glTexParameteri,                   void, (GLenum target, GLenum pname, GLint param)) \ +    _SG_XMACRO(glGetIntegerv,                     void, (GLenum pname, GLint * data)) \ +    _SG_XMACRO(glEnable,                          void, (GLenum cap)) \ +    _SG_XMACRO(glBlitFramebuffer,                 void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ +    _SG_XMACRO(glStencilMask,                     void, (GLuint mask)) \ +    _SG_XMACRO(glAttachShader,                    void, (GLuint program, GLuint shader)) \ +    _SG_XMACRO(glGetError,                        GLenum, (void)) \ +    _SG_XMACRO(glClearColor,                      void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ +    _SG_XMACRO(glBlendColor,                      void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ +    _SG_XMACRO(glTexParameterf,                   void, (GLenum target, GLenum pname, GLfloat param)) \ +    _SG_XMACRO(glTexParameterfv,                  void, (GLenum target, GLenum pname, GLfloat* params)) \ +    _SG_XMACRO(glGetShaderInfoLog,                void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ +    _SG_XMACRO(glDepthFunc,                       void, (GLenum func)) \ +    _SG_XMACRO(glStencilOp ,                      void, (GLenum fail, GLenum zfail, GLenum zpass)) \ +    _SG_XMACRO(glStencilFunc,                     void, (GLenum func, GLint ref, GLuint mask)) \ +    _SG_XMACRO(glEnableVertexAttribArray,         void, (GLuint index)) \ +    _SG_XMACRO(glBlendFunc,                       void, (GLenum sfactor, GLenum dfactor)) \ +    _SG_XMACRO(glUniform1fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \ +    _SG_XMACRO(glReadBuffer,                      void, (GLenum src)) \ +    _SG_XMACRO(glClear,                           void, (GLbitfield mask)) \ +    _SG_XMACRO(glTexImage2D,                      void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ +    _SG_XMACRO(glGenVertexArrays,                 void, (GLsizei n, GLuint * arrays)) \ +    _SG_XMACRO(glFrontFace,                       void, (GLenum mode)) \ +    _SG_XMACRO(glCullFace,                        void, (GLenum mode)) + +// generate GL function pointer typedefs +#define _SG_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// generate GL function pointers +#define _SG_XMACRO(name, ret, args) static PFN_ ## name name; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// helper function to lookup GL functions in GL DLL +typedef PROC (WINAPI * _sg_wglGetProcAddress)(LPCSTR); +_SOKOL_PRIVATE void* _sg_gl_getprocaddr(const char* name, _sg_wglGetProcAddress wgl_getprocaddress) { +    void* proc_addr = (void*) wgl_getprocaddress(name); +    if (0 == proc_addr) { +        proc_addr = (void*) GetProcAddress(_sg.gl.opengl32_dll, name); +    } +    SOKOL_ASSERT(proc_addr); +    return proc_addr; +} + +// populate GL function pointers +_SOKOL_PRIVATE  void _sg_gl_load_opengl(void) { +    SOKOL_ASSERT(0 == _sg.gl.opengl32_dll); +    _sg.gl.opengl32_dll = LoadLibraryA("opengl32.dll"); +    SOKOL_ASSERT(_sg.gl.opengl32_dll); +    _sg_wglGetProcAddress wgl_getprocaddress = (_sg_wglGetProcAddress) GetProcAddress(_sg.gl.opengl32_dll, "wglGetProcAddress"); +    SOKOL_ASSERT(wgl_getprocaddress); +    #define _SG_XMACRO(name, ret, args) name = (PFN_ ## name) _sg_gl_getprocaddr(#name, wgl_getprocaddress); +    _SG_GL_FUNCS +    #undef _SG_XMACRO +} + +_SOKOL_PRIVATE void _sg_gl_unload_opengl(void) { +    SOKOL_ASSERT(_sg.gl.opengl32_dll); +    FreeLibrary(_sg.gl.opengl32_dll); +    _sg.gl.opengl32_dll = 0; +} +#endif // _SOKOL_USE_WIN32_GL_LOADER + +/*-- type translation --------------------------------------------------------*/ +_SOKOL_PRIVATE GLenum _sg_gl_buffer_target(sg_buffer_type t) { +    switch (t) { +        case SG_BUFFERTYPE_VERTEXBUFFER:    return GL_ARRAY_BUFFER; +        case SG_BUFFERTYPE_INDEXBUFFER:     return GL_ELEMENT_ARRAY_BUFFER; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_texture_target(sg_image_type t) { +    switch (t) { +        case SG_IMAGETYPE_2D:   return GL_TEXTURE_2D; +        case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP; +        #if !defined(SOKOL_GLES2) +        case SG_IMAGETYPE_3D:       return GL_TEXTURE_3D; +        case SG_IMAGETYPE_ARRAY:    return GL_TEXTURE_2D_ARRAY; +        #endif +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_usage(sg_usage u) { +    switch (u) { +        case SG_USAGE_IMMUTABLE:    return GL_STATIC_DRAW; +        case SG_USAGE_DYNAMIC:      return GL_DYNAMIC_DRAW; +        case SG_USAGE_STREAM:       return GL_STREAM_DRAW; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_shader_stage(sg_shader_stage stage) { +    switch (stage) { +        case SG_SHADERSTAGE_VS:     return GL_VERTEX_SHADER; +        case SG_SHADERSTAGE_FS:     return GL_FRAGMENT_SHADER; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLint _sg_gl_vertexformat_size(sg_vertex_format fmt) { +    switch (fmt) { +        case SG_VERTEXFORMAT_FLOAT:     return 1; +        case SG_VERTEXFORMAT_FLOAT2:    return 2; +        case SG_VERTEXFORMAT_FLOAT3:    return 3; +        case SG_VERTEXFORMAT_FLOAT4:    return 4; +        case SG_VERTEXFORMAT_BYTE4:     return 4; +        case SG_VERTEXFORMAT_BYTE4N:    return 4; +        case SG_VERTEXFORMAT_UBYTE4:    return 4; +        case SG_VERTEXFORMAT_UBYTE4N:   return 4; +        case SG_VERTEXFORMAT_SHORT2:    return 2; +        case SG_VERTEXFORMAT_SHORT2N:   return 2; +        case SG_VERTEXFORMAT_USHORT2N:  return 2; +        case SG_VERTEXFORMAT_SHORT4:    return 4; +        case SG_VERTEXFORMAT_SHORT4N:   return 4; +        case SG_VERTEXFORMAT_USHORT4N:  return 4; +        case SG_VERTEXFORMAT_UINT10_N2: return 4; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_vertexformat_type(sg_vertex_format fmt) { +    switch (fmt) { +        case SG_VERTEXFORMAT_FLOAT: +        case SG_VERTEXFORMAT_FLOAT2: +        case SG_VERTEXFORMAT_FLOAT3: +        case SG_VERTEXFORMAT_FLOAT4: +            return GL_FLOAT; +        case SG_VERTEXFORMAT_BYTE4: +        case SG_VERTEXFORMAT_BYTE4N: +            return GL_BYTE; +        case SG_VERTEXFORMAT_UBYTE4: +        case SG_VERTEXFORMAT_UBYTE4N: +            return GL_UNSIGNED_BYTE; +        case SG_VERTEXFORMAT_SHORT2: +        case SG_VERTEXFORMAT_SHORT2N: +        case SG_VERTEXFORMAT_SHORT4: +        case SG_VERTEXFORMAT_SHORT4N: +            return GL_SHORT; +        case SG_VERTEXFORMAT_USHORT2N: +        case SG_VERTEXFORMAT_USHORT4N: +            return GL_UNSIGNED_SHORT; +        case SG_VERTEXFORMAT_UINT10_N2: +            return GL_UNSIGNED_INT_2_10_10_10_REV; +        default: +            SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLboolean _sg_gl_vertexformat_normalized(sg_vertex_format fmt) { +    switch (fmt) { +        case SG_VERTEXFORMAT_BYTE4N: +        case SG_VERTEXFORMAT_UBYTE4N: +        case SG_VERTEXFORMAT_SHORT2N: +        case SG_VERTEXFORMAT_USHORT2N: +        case SG_VERTEXFORMAT_SHORT4N: +        case SG_VERTEXFORMAT_USHORT4N: +        case SG_VERTEXFORMAT_UINT10_N2: +            return GL_TRUE; +        default: +            return GL_FALSE; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_primitive_type(sg_primitive_type t) { +    switch (t) { +        case SG_PRIMITIVETYPE_POINTS:           return GL_POINTS; +        case SG_PRIMITIVETYPE_LINES:            return GL_LINES; +        case SG_PRIMITIVETYPE_LINE_STRIP:       return GL_LINE_STRIP; +        case SG_PRIMITIVETYPE_TRIANGLES:        return GL_TRIANGLES; +        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return GL_TRIANGLE_STRIP; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_index_type(sg_index_type t) { +    switch (t) { +        case SG_INDEXTYPE_NONE:     return 0; +        case SG_INDEXTYPE_UINT16:   return GL_UNSIGNED_SHORT; +        case SG_INDEXTYPE_UINT32:   return GL_UNSIGNED_INT; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_compare_func(sg_compare_func cmp) { +    switch (cmp) { +        case SG_COMPAREFUNC_NEVER:          return GL_NEVER; +        case SG_COMPAREFUNC_LESS:           return GL_LESS; +        case SG_COMPAREFUNC_EQUAL:          return GL_EQUAL; +        case SG_COMPAREFUNC_LESS_EQUAL:     return GL_LEQUAL; +        case SG_COMPAREFUNC_GREATER:        return GL_GREATER; +        case SG_COMPAREFUNC_NOT_EQUAL:      return GL_NOTEQUAL; +        case SG_COMPAREFUNC_GREATER_EQUAL:  return GL_GEQUAL; +        case SG_COMPAREFUNC_ALWAYS:         return GL_ALWAYS; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_stencil_op(sg_stencil_op op) { +    switch (op) { +        case SG_STENCILOP_KEEP:         return GL_KEEP; +        case SG_STENCILOP_ZERO:         return GL_ZERO; +        case SG_STENCILOP_REPLACE:      return GL_REPLACE; +        case SG_STENCILOP_INCR_CLAMP:   return GL_INCR; +        case SG_STENCILOP_DECR_CLAMP:   return GL_DECR; +        case SG_STENCILOP_INVERT:       return GL_INVERT; +        case SG_STENCILOP_INCR_WRAP:    return GL_INCR_WRAP; +        case SG_STENCILOP_DECR_WRAP:    return GL_DECR_WRAP; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_blend_factor(sg_blend_factor f) { +    switch (f) { +        case SG_BLENDFACTOR_ZERO:                   return GL_ZERO; +        case SG_BLENDFACTOR_ONE:                    return GL_ONE; +        case SG_BLENDFACTOR_SRC_COLOR:              return GL_SRC_COLOR; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return GL_ONE_MINUS_SRC_COLOR; +        case SG_BLENDFACTOR_SRC_ALPHA:              return GL_SRC_ALPHA; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return GL_ONE_MINUS_SRC_ALPHA; +        case SG_BLENDFACTOR_DST_COLOR:              return GL_DST_COLOR; +        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return GL_ONE_MINUS_DST_COLOR; +        case SG_BLENDFACTOR_DST_ALPHA:              return GL_DST_ALPHA; +        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return GL_ONE_MINUS_DST_ALPHA; +        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return GL_SRC_ALPHA_SATURATE; +        case SG_BLENDFACTOR_BLEND_COLOR:            return GL_CONSTANT_COLOR; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return GL_ONE_MINUS_CONSTANT_COLOR; +        case SG_BLENDFACTOR_BLEND_ALPHA:            return GL_CONSTANT_ALPHA; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return GL_ONE_MINUS_CONSTANT_ALPHA; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_blend_op(sg_blend_op op) { +    switch (op) { +        case SG_BLENDOP_ADD:                return GL_FUNC_ADD; +        case SG_BLENDOP_SUBTRACT:           return GL_FUNC_SUBTRACT; +        case SG_BLENDOP_REVERSE_SUBTRACT:   return GL_FUNC_REVERSE_SUBTRACT; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_filter(sg_filter f) { +    switch (f) { +        case SG_FILTER_NEAREST:                 return GL_NEAREST; +        case SG_FILTER_LINEAR:                  return GL_LINEAR; +        case SG_FILTER_NEAREST_MIPMAP_NEAREST:  return GL_NEAREST_MIPMAP_NEAREST; +        case SG_FILTER_NEAREST_MIPMAP_LINEAR:   return GL_NEAREST_MIPMAP_LINEAR; +        case SG_FILTER_LINEAR_MIPMAP_NEAREST:   return GL_LINEAR_MIPMAP_NEAREST; +        case SG_FILTER_LINEAR_MIPMAP_LINEAR:    return GL_LINEAR_MIPMAP_LINEAR; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_wrap(sg_wrap w) { +    switch (w) { +        case SG_WRAP_CLAMP_TO_EDGE:     return GL_CLAMP_TO_EDGE; +        #if defined(SOKOL_GLCORE33) +        case SG_WRAP_CLAMP_TO_BORDER:   return GL_CLAMP_TO_BORDER; +        #else +        case SG_WRAP_CLAMP_TO_BORDER:   return GL_CLAMP_TO_EDGE; +        #endif +        case SG_WRAP_REPEAT:            return GL_REPEAT; +        case SG_WRAP_MIRRORED_REPEAT:   return GL_MIRRORED_REPEAT; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_R8: +        case SG_PIXELFORMAT_R8UI: +        case SG_PIXELFORMAT_RG8: +        case SG_PIXELFORMAT_RG8UI: +        case SG_PIXELFORMAT_RGBA8: +        case SG_PIXELFORMAT_RGBA8UI: +        case SG_PIXELFORMAT_BGRA8: +            return GL_UNSIGNED_BYTE; +        case SG_PIXELFORMAT_R8SN: +        case SG_PIXELFORMAT_R8SI: +        case SG_PIXELFORMAT_RG8SN: +        case SG_PIXELFORMAT_RG8SI: +        case SG_PIXELFORMAT_RGBA8SN: +        case SG_PIXELFORMAT_RGBA8SI: +            return GL_BYTE; +        case SG_PIXELFORMAT_R16: +        case SG_PIXELFORMAT_R16UI: +        case SG_PIXELFORMAT_RG16: +        case SG_PIXELFORMAT_RG16UI: +        case SG_PIXELFORMAT_RGBA16: +        case SG_PIXELFORMAT_RGBA16UI: +            return GL_UNSIGNED_SHORT; +        case SG_PIXELFORMAT_R16SN: +        case SG_PIXELFORMAT_R16SI: +        case SG_PIXELFORMAT_RG16SN: +        case SG_PIXELFORMAT_RG16SI: +        case SG_PIXELFORMAT_RGBA16SN: +        case SG_PIXELFORMAT_RGBA16SI: +            return GL_SHORT; +        case SG_PIXELFORMAT_R16F: +        case SG_PIXELFORMAT_RG16F: +        case SG_PIXELFORMAT_RGBA16F: +            return GL_HALF_FLOAT; +        case SG_PIXELFORMAT_R32UI: +        case SG_PIXELFORMAT_RG32UI: +        case SG_PIXELFORMAT_RGBA32UI: +            return GL_UNSIGNED_INT; +        case SG_PIXELFORMAT_R32SI: +        case SG_PIXELFORMAT_RG32SI: +        case SG_PIXELFORMAT_RGBA32SI: +            return GL_INT; +        case SG_PIXELFORMAT_R32F: +        case SG_PIXELFORMAT_RG32F: +        case SG_PIXELFORMAT_RGBA32F: +            return GL_FLOAT; +        #if !defined(SOKOL_GLES2) +        case SG_PIXELFORMAT_RGB10A2: +            return GL_UNSIGNED_INT_2_10_10_10_REV; +        case SG_PIXELFORMAT_RG11B10F: +            return GL_UNSIGNED_INT_10F_11F_11F_REV; +        #endif +        case SG_PIXELFORMAT_DEPTH: +            return GL_UNSIGNED_SHORT; +        case SG_PIXELFORMAT_DEPTH_STENCIL: +            return GL_UNSIGNED_INT_24_8; +        default: +            SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_R8: +        case SG_PIXELFORMAT_R8SN: +        case SG_PIXELFORMAT_R16: +        case SG_PIXELFORMAT_R16SN: +        case SG_PIXELFORMAT_R16F: +        case SG_PIXELFORMAT_R32F: +            #if defined(SOKOL_GLES2) +                return GL_LUMINANCE; +            #else +            if (_sg.gl.gles2) { +                return GL_LUMINANCE; +            } +            else { +                return GL_RED; +            } +            #endif +        #if !defined(SOKOL_GLES2) +            case SG_PIXELFORMAT_R8UI: +            case SG_PIXELFORMAT_R8SI: +            case SG_PIXELFORMAT_R16UI: +            case SG_PIXELFORMAT_R16SI: +            case SG_PIXELFORMAT_R32UI: +            case SG_PIXELFORMAT_R32SI: +                return GL_RED_INTEGER; +            case SG_PIXELFORMAT_RG8: +            case SG_PIXELFORMAT_RG8SN: +            case SG_PIXELFORMAT_RG16: +            case SG_PIXELFORMAT_RG16SN: +            case SG_PIXELFORMAT_RG16F: +            case SG_PIXELFORMAT_RG32F: +                return GL_RG; +            case SG_PIXELFORMAT_RG8UI: +            case SG_PIXELFORMAT_RG8SI: +            case SG_PIXELFORMAT_RG16UI: +            case SG_PIXELFORMAT_RG16SI: +            case SG_PIXELFORMAT_RG32UI: +            case SG_PIXELFORMAT_RG32SI: +                return GL_RG_INTEGER; +        #endif +        case SG_PIXELFORMAT_RGBA8: +        case SG_PIXELFORMAT_RGBA8SN: +        case SG_PIXELFORMAT_RGBA16: +        case SG_PIXELFORMAT_RGBA16SN: +        case SG_PIXELFORMAT_RGBA16F: +        case SG_PIXELFORMAT_RGBA32F: +        case SG_PIXELFORMAT_RGB10A2: +            return GL_RGBA; +        #if !defined(SOKOL_GLES2) +            case SG_PIXELFORMAT_RGBA8UI: +            case SG_PIXELFORMAT_RGBA8SI: +            case SG_PIXELFORMAT_RGBA16UI: +            case SG_PIXELFORMAT_RGBA16SI: +            case SG_PIXELFORMAT_RGBA32UI: +            case SG_PIXELFORMAT_RGBA32SI: +                return GL_RGBA_INTEGER; +        #endif +        case SG_PIXELFORMAT_RG11B10F: +            return GL_RGB; +        case SG_PIXELFORMAT_DEPTH: +            return GL_DEPTH_COMPONENT; +        case SG_PIXELFORMAT_DEPTH_STENCIL: +            return GL_DEPTH_STENCIL; +        case SG_PIXELFORMAT_BC1_RGBA: +            return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; +        case SG_PIXELFORMAT_BC2_RGBA: +            return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; +        case SG_PIXELFORMAT_BC3_RGBA: +            return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; +        case SG_PIXELFORMAT_BC4_R: +            return GL_COMPRESSED_RED_RGTC1; +        case SG_PIXELFORMAT_BC4_RSN: +            return GL_COMPRESSED_SIGNED_RED_RGTC1; +        case SG_PIXELFORMAT_BC5_RG: +            return GL_COMPRESSED_RED_GREEN_RGTC2; +        case SG_PIXELFORMAT_BC5_RGSN: +            return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2; +        case SG_PIXELFORMAT_BC6H_RGBF: +            return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; +        case SG_PIXELFORMAT_BC6H_RGBUF: +            return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; +        case SG_PIXELFORMAT_BC7_RGBA: +            return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP: +            return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP: +            return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +            return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +            return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; +        case SG_PIXELFORMAT_ETC2_RGB8: +            return GL_COMPRESSED_RGB8_ETC2; +        case SG_PIXELFORMAT_ETC2_RGB8A1: +            return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; +        case SG_PIXELFORMAT_ETC2_RGBA8: +            return GL_COMPRESSED_RGBA8_ETC2_EAC; +        case SG_PIXELFORMAT_ETC2_RG11: +            return GL_COMPRESSED_RG11_EAC; +        case SG_PIXELFORMAT_ETC2_RG11SN: +            return GL_COMPRESSED_SIGNED_RG11_EAC; +        default: +            SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) { +    #if defined(SOKOL_GLES2) +    return _sg_gl_teximage_format(fmt); +    #else +    if (_sg.gl.gles2) { +        return _sg_gl_teximage_format(fmt); +    } +    else { +        switch (fmt) { +            case SG_PIXELFORMAT_R8:         return GL_R8; +            case SG_PIXELFORMAT_R8SN:       return GL_R8_SNORM; +            case SG_PIXELFORMAT_R8UI:       return GL_R8UI; +            case SG_PIXELFORMAT_R8SI:       return GL_R8I; +            #if !defined(SOKOL_GLES3) +                case SG_PIXELFORMAT_R16:        return GL_R16; +                case SG_PIXELFORMAT_R16SN:      return GL_R16_SNORM; +            #endif +            case SG_PIXELFORMAT_R16UI:      return GL_R16UI; +            case SG_PIXELFORMAT_R16SI:      return GL_R16I; +            case SG_PIXELFORMAT_R16F:       return GL_R16F; +            case SG_PIXELFORMAT_RG8:        return GL_RG8; +            case SG_PIXELFORMAT_RG8SN:      return GL_RG8_SNORM; +            case SG_PIXELFORMAT_RG8UI:      return GL_RG8UI; +            case SG_PIXELFORMAT_RG8SI:      return GL_RG8I; +            case SG_PIXELFORMAT_R32UI:      return GL_R32UI; +            case SG_PIXELFORMAT_R32SI:      return GL_R32I; +            case SG_PIXELFORMAT_R32F:       return GL_R32F; +            #if !defined(SOKOL_GLES3) +                case SG_PIXELFORMAT_RG16:       return GL_RG16; +                case SG_PIXELFORMAT_RG16SN:     return GL_RG16_SNORM; +            #endif +            case SG_PIXELFORMAT_RG16UI:     return GL_RG16UI; +            case SG_PIXELFORMAT_RG16SI:     return GL_RG16I; +            case SG_PIXELFORMAT_RG16F:      return GL_RG16F; +            case SG_PIXELFORMAT_RGBA8:      return GL_RGBA8; +            case SG_PIXELFORMAT_RGBA8SN:    return GL_RGBA8_SNORM; +            case SG_PIXELFORMAT_RGBA8UI:    return GL_RGBA8UI; +            case SG_PIXELFORMAT_RGBA8SI:    return GL_RGBA8I; +            case SG_PIXELFORMAT_RGB10A2:    return GL_RGB10_A2; +            case SG_PIXELFORMAT_RG11B10F:   return GL_R11F_G11F_B10F; +            case SG_PIXELFORMAT_RG32UI:     return GL_RG32UI; +            case SG_PIXELFORMAT_RG32SI:     return GL_RG32I; +            case SG_PIXELFORMAT_RG32F:      return GL_RG32F; +            #if !defined(SOKOL_GLES3) +                case SG_PIXELFORMAT_RGBA16:     return GL_RGBA16; +                case SG_PIXELFORMAT_RGBA16SN:   return GL_RGBA16_SNORM; +            #endif +            case SG_PIXELFORMAT_RGBA16UI:   return GL_RGBA16UI; +            case SG_PIXELFORMAT_RGBA16SI:   return GL_RGBA16I; +            case SG_PIXELFORMAT_RGBA16F:    return GL_RGBA16F; +            case SG_PIXELFORMAT_RGBA32UI:   return GL_RGBA32UI; +            case SG_PIXELFORMAT_RGBA32SI:   return GL_RGBA32I; +            case SG_PIXELFORMAT_RGBA32F:    return GL_RGBA32F; +            case SG_PIXELFORMAT_DEPTH:      return GL_DEPTH_COMPONENT16; +            case SG_PIXELFORMAT_DEPTH_STENCIL:      return GL_DEPTH24_STENCIL8; +            case SG_PIXELFORMAT_BC1_RGBA:           return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; +            case SG_PIXELFORMAT_BC2_RGBA:           return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; +            case SG_PIXELFORMAT_BC3_RGBA:           return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; +            case SG_PIXELFORMAT_BC4_R:              return GL_COMPRESSED_RED_RGTC1; +            case SG_PIXELFORMAT_BC4_RSN:            return GL_COMPRESSED_SIGNED_RED_RGTC1; +            case SG_PIXELFORMAT_BC5_RG:             return GL_COMPRESSED_RED_GREEN_RGTC2; +            case SG_PIXELFORMAT_BC5_RGSN:           return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2; +            case SG_PIXELFORMAT_BC6H_RGBF:          return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; +            case SG_PIXELFORMAT_BC6H_RGBUF:         return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; +            case SG_PIXELFORMAT_BC7_RGBA:           return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; +            case SG_PIXELFORMAT_PVRTC_RGB_2BPP:     return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; +            case SG_PIXELFORMAT_PVRTC_RGB_4BPP:     return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; +            case SG_PIXELFORMAT_PVRTC_RGBA_2BPP:    return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; +            case SG_PIXELFORMAT_PVRTC_RGBA_4BPP:    return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; +            case SG_PIXELFORMAT_ETC2_RGB8:          return GL_COMPRESSED_RGB8_ETC2; +            case SG_PIXELFORMAT_ETC2_RGB8A1:        return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; +            case SG_PIXELFORMAT_ETC2_RGBA8:         return GL_COMPRESSED_RGBA8_ETC2_EAC; +            case SG_PIXELFORMAT_ETC2_RG11:          return GL_COMPRESSED_RG11_EAC; +            case SG_PIXELFORMAT_ETC2_RG11SN:        return GL_COMPRESSED_SIGNED_RG11_EAC; +            default: SOKOL_UNREACHABLE; return 0; +        } +    } +    #endif +} + +_SOKOL_PRIVATE GLenum _sg_gl_cubeface_target(int face_index) { +    switch (face_index) { +        case 0: return GL_TEXTURE_CUBE_MAP_POSITIVE_X; +        case 1: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X; +        case 2: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y; +        case 3: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; +        case 4: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z; +        case 5: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE GLenum _sg_gl_depth_attachment_format(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_DEPTH:          return GL_DEPTH_COMPONENT16; +        case SG_PIXELFORMAT_DEPTH_STENCIL:  return GL_DEPTH24_STENCIL8; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +/* see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml */ +_SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); +    } +    else { +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8]); +    } +    #else +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8]); +    #endif +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); +        #if !defined(SOKOL_GLES3) +            _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]); +            _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]); +        #endif +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); +        #if !defined(SOKOL_GLES3) +            _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]); +            _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]); +        #endif +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); +    } +    #endif +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); +    } +    #endif +    if (has_bgra) { +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); +    } +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); +        #if !defined(SOKOL_GLES3) +            _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]); +            _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); +        #endif +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); +    } +    #endif +    // FIXME: WEBGL_depth_texture extension? +    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); +    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); +} + +/* FIXME: OES_half_float_blend */ +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_half_float(bool has_colorbuffer_half_float, bool has_texture_half_float_linear) { +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        if (has_texture_half_float_linear) { +            if (has_colorbuffer_half_float) { +                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); +                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); +                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +            else { +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R16F]); +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG16F]); +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +        } +        else { +            if (has_colorbuffer_half_float) { +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_R16F]); +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RG16F]); +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +            else { +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R16F]); +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG16F]); +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +        } +    } +    else { +    #endif +        /* GLES2 can only render to RGBA, and there's no RG format */ +        if (has_texture_half_float_linear) { +            if (has_colorbuffer_half_float) { +                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +            else { +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +            _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R16F]); +        } +        else { +            if (has_colorbuffer_half_float) { +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +            else { +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +            } +            _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R16F]); +        } +    #if !defined(SOKOL_GLES2) +    } +    #endif +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_float(bool has_colorbuffer_float, bool has_texture_float_linear, bool has_float_blend) { +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        if (has_texture_float_linear) { +            if (has_colorbuffer_float) { +                if (has_float_blend) { +                    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]); +                    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); +                    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +                } +                else { +                    _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_R32F]); +                    _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG32F]); +                    _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +                } +            } +            else { +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R32F]); +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG32F]); +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +            } +        } +        else { +            if (has_colorbuffer_float) { +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_R32F]); +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RG32F]); +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +            } +            else { +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R32F]); +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG32F]); +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +            } +        } +    } +    else { +    #endif +        /* GLES2 can only render to RGBA, and there's no RG format */ +        if (has_texture_float_linear) { +            if (has_colorbuffer_float) { +                if (has_float_blend) { +                    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +                } +                else { +                    _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +                } +            } +            else { +                _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +            } +            _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R32F]); +        } +        else { +            if (has_colorbuffer_float) { +                _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +            } +            else { +                _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +            } +            _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R32F]); +        } +    #if !defined(SOKOL_GLES2) +    } +    #endif +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_s3tc(void) { +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_rgtc(void) { +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_bptc(void) { +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_pvrtc(void) { +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_2BPP]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_4BPP]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_2BPP]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_4BPP]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_etc2(void) { +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RG11]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RG11SN]); +} + +_SOKOL_PRIVATE void _sg_gl_init_limits(void) { +    _SG_GL_CHECK_ERROR(); +    GLint gl_int; +    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_int); +    _SG_GL_CHECK_ERROR(); +    _sg.limits.max_image_size_2d = gl_int; +    _sg.limits.max_image_size_array = gl_int; +    glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &gl_int); +    _SG_GL_CHECK_ERROR(); +    _sg.limits.max_image_size_cube = gl_int; +    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_int); +    _SG_GL_CHECK_ERROR(); +    if (gl_int > SG_MAX_VERTEX_ATTRIBUTES) { +        gl_int = SG_MAX_VERTEX_ATTRIBUTES; +    } +    _sg.limits.max_vertex_attrs = gl_int; +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gl_int); +        _SG_GL_CHECK_ERROR(); +        _sg.limits.max_image_size_3d = gl_int; +        glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gl_int); +        _SG_GL_CHECK_ERROR(); +        _sg.limits.max_image_array_layers = gl_int; +    } +    #endif +    if (_sg.gl.ext_anisotropic) { +        glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_int); +        _SG_GL_CHECK_ERROR(); +        _sg.gl.max_anisotropy = gl_int; +    } +    else { +        _sg.gl.max_anisotropy = 1; +    } +    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gl_int); +    _SG_GL_CHECK_ERROR(); +    _sg.gl.max_combined_texture_image_units = gl_int; +} + +#if defined(SOKOL_GLCORE33) +_SOKOL_PRIVATE void _sg_gl_init_caps_glcore33(void) { +    _sg.backend = SG_BACKEND_GLCORE33; + +    _sg.features.origin_top_left = false; +    _sg.features.instancing = true; +    _sg.features.multiple_render_targets = true; +    _sg.features.msaa_render_targets = true; +    _sg.features.imagetype_3d = true; +    _sg.features.imagetype_array = true; +    _sg.features.image_clamp_to_border = true; +    _sg.features.mrt_independent_blend_state = false; +    _sg.features.mrt_independent_write_mask = true; + +    /* scan extensions */ +    bool has_s3tc = false;  /* BC1..BC3 */ +    bool has_rgtc = false;  /* BC4 and BC5 */ +    bool has_bptc = false;  /* BC6H and BC7 */ +    bool has_pvrtc = false; +    bool has_etc2 = false; +    GLint num_ext = 0; +    glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext); +    for (int i = 0; i < num_ext; i++) { +        const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i); +        if (ext) { +            if (strstr(ext, "_texture_compression_s3tc")) { +                has_s3tc = true; +            } +            else if (strstr(ext, "_texture_compression_rgtc")) { +                has_rgtc = true; +            } +            else if (strstr(ext, "_texture_compression_bptc")) { +                has_bptc = true; +            } +            else if (strstr(ext, "_texture_compression_pvrtc")) { +                has_pvrtc = true; +            } +            else if (strstr(ext, "_ES3_compatibility")) { +                has_etc2 = true; +            } +            else if (strstr(ext, "_texture_filter_anisotropic")) { +                _sg.gl.ext_anisotropic = true; +            } +        } +    } + +    /* limits */ +    _sg_gl_init_limits(); + +    /* pixel formats */ +    const bool has_bgra = false;    /* not a bug */ +    const bool has_colorbuffer_float = true; +    const bool has_colorbuffer_half_float = true; +    const bool has_texture_float_linear = true; /* FIXME??? */ +    const bool has_texture_half_float_linear = true; +    const bool has_float_blend = true; +    _sg_gl_init_pixelformats(has_bgra); +    _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); +    _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float, has_texture_half_float_linear); +    if (has_s3tc) { +        _sg_gl_init_pixelformats_s3tc(); +    } +    if (has_rgtc) { +        _sg_gl_init_pixelformats_rgtc(); +    } +    if (has_bptc) { +        _sg_gl_init_pixelformats_bptc(); +    } +    if (has_pvrtc) { +        _sg_gl_init_pixelformats_pvrtc(); +    } +    if (has_etc2) { +        _sg_gl_init_pixelformats_etc2(); +    } +} +#endif + +#if defined(SOKOL_GLES3) +_SOKOL_PRIVATE void _sg_gl_init_caps_gles3(void) { +    _sg.backend = SG_BACKEND_GLES3; + +    _sg.features.origin_top_left = false; +    _sg.features.instancing = true; +    _sg.features.multiple_render_targets = true; +    _sg.features.msaa_render_targets = true; +    _sg.features.imagetype_3d = true; +    _sg.features.imagetype_array = true; +    _sg.features.image_clamp_to_border = false; +    _sg.features.mrt_independent_blend_state = false; +    _sg.features.mrt_independent_write_mask = false; + +    bool has_s3tc = false;  /* BC1..BC3 */ +    bool has_rgtc = false;  /* BC4 and BC5 */ +    bool has_bptc = false;  /* BC6H and BC7 */ +    bool has_pvrtc = false; +    #if defined(__EMSCRIPTEN__) +        bool has_etc2 = false; +    #else +        bool has_etc2 = true; +    #endif +    bool has_colorbuffer_float = false; +    bool has_colorbuffer_half_float = false; +    bool has_texture_float_linear = false; +    bool has_float_blend = false; +    GLint num_ext = 0; +    glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext); +    for (int i = 0; i < num_ext; i++) { +        const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i); +        if (ext) { +            if (strstr(ext, "_texture_compression_s3tc")) { +                has_s3tc = true; +            } +            else if (strstr(ext, "_compressed_texture_s3tc")) { +                has_s3tc = true; +            } +            else if (strstr(ext, "_texture_compression_rgtc")) { +                has_rgtc = true; +            } +            else if (strstr(ext, "_texture_compression_bptc")) { +                has_bptc = true; +            } +            else if (strstr(ext, "_texture_compression_pvrtc")) { +                has_pvrtc = true; +            } +            else if (strstr(ext, "_compressed_texture_pvrtc")) { +                has_pvrtc = true; +            } +            else if (strstr(ext, "_compressed_texture_etc")) { +                has_etc2 = true; +            } +            else if (strstr(ext, "_color_buffer_float")) { +                has_colorbuffer_float = true; +            } +            else if (strstr(ext, "_color_buffer_half_float")) { +                has_colorbuffer_half_float = true; +            } +            else if (strstr(ext, "_texture_float_linear")) { +                has_texture_float_linear = true; +            } +            else if (strstr(ext, "_float_blend")) { +                has_float_blend = true; +            } +            else if (strstr(ext, "_texture_filter_anisotropic")) { +                _sg.gl.ext_anisotropic = true; +            } +        } +    } + +    /* on WebGL2, color_buffer_float also includes 16-bit formats +       see: https://developer.mozilla.org/en-US/docs/Web/API/EXT_color_buffer_float +    */ +    #if defined(__EMSCRIPTEN__) +    has_colorbuffer_half_float = has_colorbuffer_float; +    #endif + +    /* limits */ +    _sg_gl_init_limits(); + +    /* pixel formats */ +    const bool has_texture_half_float_linear = true; +    const bool has_bgra = false;    /* not a bug */ +    _sg_gl_init_pixelformats(has_bgra); +    _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); +    _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float, has_texture_half_float_linear); +    if (has_s3tc) { +        _sg_gl_init_pixelformats_s3tc(); +    } +    if (has_rgtc) { +        _sg_gl_init_pixelformats_rgtc(); +    } +    if (has_bptc) { +        _sg_gl_init_pixelformats_bptc(); +    } +    if (has_pvrtc) { +        _sg_gl_init_pixelformats_pvrtc(); +    } +    if (has_etc2) { +        _sg_gl_init_pixelformats_etc2(); +    } +} +#endif + +#if defined(SOKOL_GLES3) || defined(SOKOL_GLES2) +_SOKOL_PRIVATE void _sg_gl_init_caps_gles2(void) { +    _sg.backend = SG_BACKEND_GLES2; + +    bool has_s3tc = false;  /* BC1..BC3 */ +    bool has_rgtc = false;  /* BC4 and BC5 */ +    bool has_bptc = false;  /* BC6H and BC7 */ +    bool has_pvrtc = false; +    bool has_etc2 = false; +    bool has_texture_float = false; +    bool has_texture_float_linear = false; +    bool has_colorbuffer_float = false; +    bool has_float_blend = false; +    bool has_instancing = false; +    const char* ext = (const char*) glGetString(GL_EXTENSIONS); +    if (ext) { +        has_s3tc = strstr(ext, "_texture_compression_s3tc") || strstr(ext, "_compressed_texture_s3tc"); +        has_rgtc = strstr(ext, "_texture_compression_rgtc"); +        has_bptc = strstr(ext, "_texture_compression_bptc"); +        has_pvrtc = strstr(ext, "_texture_compression_pvrtc") || strstr(ext, "_compressed_texture_pvrtc"); +        has_etc2 = strstr(ext, "_compressed_texture_etc"); +        has_texture_float = strstr(ext, "_texture_float"); +        has_texture_float_linear = strstr(ext, "_texture_float_linear"); +        has_colorbuffer_float = strstr(ext, "_color_buffer_float"); +        has_float_blend = strstr(ext, "_float_blend"); +        /* don't bother with half_float support on WebGL1 +            has_texture_half_float = strstr(ext, "_texture_half_float"); +            has_texture_half_float_linear = strstr(ext, "_texture_half_float_linear"); +            has_colorbuffer_half_float = strstr(ext, "_color_buffer_half_float"); +        */ +        has_instancing = strstr(ext, "_instanced_arrays"); +        _sg.gl.ext_anisotropic = strstr(ext, "ext_anisotropic"); +    } + +    _sg.features.origin_top_left = false; +    #if defined(_SOKOL_GL_INSTANCING_ENABLED) +        _sg.features.instancing = has_instancing; +    #endif +    _sg.features.multiple_render_targets = false; +    _sg.features.msaa_render_targets = false; +    _sg.features.imagetype_3d = false; +    _sg.features.imagetype_array = false; +    _sg.features.image_clamp_to_border = false; +    _sg.features.mrt_independent_blend_state = false; +    _sg.features.mrt_independent_write_mask = false; + +    /* limits */ +    _sg_gl_init_limits(); + +    /* pixel formats */ +    const bool has_bgra = false;    /* not a bug */ +    const bool has_texture_half_float = false; +    const bool has_texture_half_float_linear = false; +    const bool has_colorbuffer_half_float = false; +    _sg_gl_init_pixelformats(has_bgra); +    if (has_texture_float) { +        _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); +    } +    if (has_texture_half_float) { +        _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float, has_texture_half_float_linear); +    } +    if (has_s3tc) { +        _sg_gl_init_pixelformats_s3tc(); +    } +    if (has_rgtc) { +        _sg_gl_init_pixelformats_rgtc(); +    } +    if (has_bptc) { +        _sg_gl_init_pixelformats_bptc(); +    } +    if (has_pvrtc) { +        _sg_gl_init_pixelformats_pvrtc(); +    } +    if (has_etc2) { +        _sg_gl_init_pixelformats_etc2(); +    } +    /* GLES2 doesn't allow multi-sampled render targets at all */ +    for (int i = 0; i < _SG_PIXELFORMAT_NUM; i++) { +        _sg.formats[i].msaa = false; +    } +} +#endif + +/*-- state cache implementation ----------------------------------------------*/ +_SOKOL_PRIVATE void _sg_gl_cache_clear_buffer_bindings(bool force) { +    if (force || (_sg.gl.cache.vertex_buffer != 0)) { +        glBindBuffer(GL_ARRAY_BUFFER, 0); +        _sg.gl.cache.vertex_buffer = 0; +    } +    if (force || (_sg.gl.cache.index_buffer != 0)) { +        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +        _sg.gl.cache.index_buffer = 0; +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_buffer(GLenum target, GLuint buffer) { +    SOKOL_ASSERT((GL_ARRAY_BUFFER == target) || (GL_ELEMENT_ARRAY_BUFFER == target)); +    if (target == GL_ARRAY_BUFFER) { +        if (_sg.gl.cache.vertex_buffer != buffer) { +            _sg.gl.cache.vertex_buffer = buffer; +            glBindBuffer(target, buffer); +        } +    } +    else { +        if (_sg.gl.cache.index_buffer != buffer) { +            _sg.gl.cache.index_buffer = buffer; +            glBindBuffer(target, buffer); +        } +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_store_buffer_binding(GLenum target) { +    if (target == GL_ARRAY_BUFFER) { +        _sg.gl.cache.stored_vertex_buffer = _sg.gl.cache.vertex_buffer; +    } +    else { +        _sg.gl.cache.stored_index_buffer = _sg.gl.cache.index_buffer; +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_buffer_binding(GLenum target) { +    if (target == GL_ARRAY_BUFFER) { +        if (_sg.gl.cache.stored_vertex_buffer != 0) { +            /* we only care restoring valid ids */ +            _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_vertex_buffer); +            _sg.gl.cache.stored_vertex_buffer = 0; +        } +    } +    else { +        if (_sg.gl.cache.stored_index_buffer != 0) { +            /* we only care restoring valid ids */ +            _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_index_buffer); +            _sg.gl.cache.stored_index_buffer = 0; +        } +    } +} + +/* called when from _sg_gl_destroy_buffer() */ +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_buffer(GLuint buf) { +    if (buf == _sg.gl.cache.vertex_buffer) { +        _sg.gl.cache.vertex_buffer = 0; +        glBindBuffer(GL_ARRAY_BUFFER, 0); +    } +    if (buf == _sg.gl.cache.index_buffer) { +        _sg.gl.cache.index_buffer = 0; +        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +    } +    if (buf == _sg.gl.cache.stored_vertex_buffer) { +        _sg.gl.cache.stored_vertex_buffer = 0; +    } +    if (buf == _sg.gl.cache.stored_index_buffer) { +        _sg.gl.cache.stored_index_buffer = 0; +    } +    for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { +        if (buf == _sg.gl.cache.attrs[i].gl_vbuf) { +            _sg.gl.cache.attrs[i].gl_vbuf = 0; +        } +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_active_texture(GLenum texture) { +    if (_sg.gl.cache.cur_active_texture != texture) { +        _sg.gl.cache.cur_active_texture = texture; +        glActiveTexture(texture); +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_clear_texture_bindings(bool force) { +    for (int i = 0; (i < SG_MAX_SHADERSTAGE_IMAGES) && (i < _sg.gl.max_combined_texture_image_units); i++) { +        if (force || (_sg.gl.cache.textures[i].texture != 0)) { +            GLenum gl_texture_slot = (GLenum) (GL_TEXTURE0 + i); +            glActiveTexture(gl_texture_slot); +            glBindTexture(GL_TEXTURE_2D, 0); +            glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +            #if !defined(SOKOL_GLES2) +            if (!_sg.gl.gles2) { +                glBindTexture(GL_TEXTURE_3D, 0); +                glBindTexture(GL_TEXTURE_2D_ARRAY, 0); +            } +            #endif +            _sg.gl.cache.textures[i].target = 0; +            _sg.gl.cache.textures[i].texture = 0; +            _sg.gl.cache.cur_active_texture = gl_texture_slot; +        } +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_texture(int slot_index, GLenum target, GLuint texture) { +    /* it's valid to call this function with target=0 and/or texture=0 +       target=0 will unbind the previous binding, texture=0 will clear +       the new binding +    */ +    SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); +    if (slot_index >= _sg.gl.max_combined_texture_image_units) { +        return; +    } +    _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.textures[slot_index]; +    if ((slot->target != target) || (slot->texture != texture)) { +        _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + slot_index)); +        /* if the target has changed, clear the previous binding on that target */ +        if ((target != slot->target) && (slot->target != 0)) { +            glBindTexture(slot->target, 0); +        } +        /* apply new binding (texture can be 0 to unbind) */ +        if (target != 0) { +            glBindTexture(target, texture); +        } +        slot->target = target; +        slot->texture = texture; +    } +} + +_SOKOL_PRIVATE void _sg_gl_cache_store_texture_binding(int slot_index) { +    SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); +    _sg.gl.cache.stored_texture = _sg.gl.cache.textures[slot_index]; +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_texture_binding(int slot_index) { +    SOKOL_ASSERT(slot_index < SG_MAX_SHADERSTAGE_IMAGES); +    _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.stored_texture; +    if (slot->texture != 0) { +        /* we only care restoring valid ids */ +        SOKOL_ASSERT(slot->target != 0); +        _sg_gl_cache_bind_texture(slot_index, slot->target, slot->texture); +        slot->target = 0; +        slot->texture = 0; +    } +} + +/* called from _sg_gl_destroy_texture() */ +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture(GLuint tex) { +    for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { +        _sg_gl_texture_bind_slot* slot = &_sg.gl.cache.textures[i]; +        if (tex == slot->texture) { +            _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + i)); +            glBindTexture(slot->target, 0); +            slot->target = 0; +            slot->texture = 0; +        } +    } +    if (tex == _sg.gl.cache.stored_texture.texture) { +        _sg.gl.cache.stored_texture.target = 0; +        _sg.gl.cache.stored_texture.texture = 0; +    } +} + +/* called from _sg_gl_destroy_shader() */ +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { +    if (prog == _sg.gl.cache.prog) { +        _sg.gl.cache.prog = 0; +        glUseProgram(0); +    } +} + +_SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) { +    if (_sg.gl.cur_context) { +        _SG_GL_CHECK_ERROR(); +        #if !defined(SOKOL_GLES2) +        if (!_sg.gl.gles2) { +            glBindVertexArray(_sg.gl.cur_context->vao); +            _SG_GL_CHECK_ERROR(); +        } +        #endif +        memset(&_sg.gl.cache, 0, sizeof(_sg.gl.cache)); +        _sg_gl_cache_clear_buffer_bindings(true); +        _SG_GL_CHECK_ERROR(); +        _sg_gl_cache_clear_texture_bindings(true); +        _SG_GL_CHECK_ERROR(); +        for (int i = 0; i < _sg.limits.max_vertex_attrs; i++) { +            _sg_gl_attr_t* attr = &_sg.gl.cache.attrs[i].gl_attr; +            attr->vb_index = -1; +            attr->divisor = -1; +            glDisableVertexAttribArray((GLuint)i); +            _SG_GL_CHECK_ERROR(); +        } +        _sg.gl.cache.cur_primitive_type = GL_TRIANGLES; + +        /* shader program */ +        glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&_sg.gl.cache.prog); +        _SG_GL_CHECK_ERROR(); + +        /* depth and stencil state */ +        _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS; +        _sg.gl.cache.stencil.front.compare = SG_COMPAREFUNC_ALWAYS; +        _sg.gl.cache.stencil.front.fail_op = SG_STENCILOP_KEEP; +        _sg.gl.cache.stencil.front.depth_fail_op = SG_STENCILOP_KEEP; +        _sg.gl.cache.stencil.front.pass_op = SG_STENCILOP_KEEP; +        _sg.gl.cache.stencil.back.compare = SG_COMPAREFUNC_ALWAYS; +        _sg.gl.cache.stencil.back.fail_op = SG_STENCILOP_KEEP; +        _sg.gl.cache.stencil.back.depth_fail_op = SG_STENCILOP_KEEP; +        _sg.gl.cache.stencil.back.pass_op = SG_STENCILOP_KEEP; +        glEnable(GL_DEPTH_TEST); +        glDepthFunc(GL_ALWAYS); +        glDepthMask(GL_FALSE); +        glDisable(GL_STENCIL_TEST); +        glStencilFunc(GL_ALWAYS, 0, 0); +        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); +        glStencilMask(0); + +        /* blend state */ +        _sg.gl.cache.blend.src_factor_rgb = SG_BLENDFACTOR_ONE; +        _sg.gl.cache.blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; +        _sg.gl.cache.blend.op_rgb = SG_BLENDOP_ADD; +        _sg.gl.cache.blend.src_factor_alpha = SG_BLENDFACTOR_ONE; +        _sg.gl.cache.blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; +        _sg.gl.cache.blend.op_alpha = SG_BLENDOP_ADD; +        glDisable(GL_BLEND); +        glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO); +        glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); +        glBlendColor(0.0f, 0.0f, 0.0f, 0.0f); + +        /* standalone state */ +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA; +        } +        _sg.gl.cache.cull_mode = SG_CULLMODE_NONE; +        _sg.gl.cache.face_winding = SG_FACEWINDING_CW; +        _sg.gl.cache.sample_count = 1; +        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); +        glPolygonOffset(0.0f, 0.0f); +        glDisable(GL_POLYGON_OFFSET_FILL); +        glDisable(GL_CULL_FACE); +        glFrontFace(GL_CW); +        glCullFace(GL_BACK); +        glEnable(GL_SCISSOR_TEST); +        glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); +        glEnable(GL_DITHER); +        glDisable(GL_POLYGON_OFFSET_FILL); +        #if defined(SOKOL_GLCORE33) +            glEnable(GL_MULTISAMPLE); +            glEnable(GL_PROGRAM_POINT_SIZE); +        #endif +    } +} + +_SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { +    /* assumes that _sg.gl is already zero-initialized */ +    _sg.gl.valid = true; +    #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +    _sg.gl.gles2 = desc->context.gl.force_gles2; +    #else +    _SOKOL_UNUSED(desc); +    _sg.gl.gles2 = false; +    #endif + +    #if defined(_SOKOL_USE_WIN32_GL_LOADER) +    _sg_gl_load_opengl(); +    #endif + +    /* clear initial GL error state */ +    #if defined(SOKOL_DEBUG) +        while (glGetError() != GL_NO_ERROR); +    #endif +    #if defined(SOKOL_GLCORE33) +        _sg_gl_init_caps_glcore33(); +    #elif defined(SOKOL_GLES3) +        if (_sg.gl.gles2) { +            _sg_gl_init_caps_gles2(); +        } +        else { +            _sg_gl_init_caps_gles3(); +        } +    #else +        _sg_gl_init_caps_gles2(); +    #endif +} + +_SOKOL_PRIVATE void _sg_gl_discard_backend(void) { +    SOKOL_ASSERT(_sg.gl.valid); +    _sg.gl.valid = false; +    #if defined(_SOKOL_USE_WIN32_GL_LOADER) +    _sg_gl_unload_opengl(); +    #endif +} + +_SOKOL_PRIVATE void _sg_gl_activate_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(_sg.gl.valid); +    /* NOTE: ctx can be 0 to unset the current context */ +    _sg.gl.cur_context = ctx; +    _sg_gl_reset_state_cache(); +} + +/*-- GL backend resource creation and destruction ----------------------------*/ +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    SOKOL_ASSERT(0 == ctx->default_framebuffer); +    _SG_GL_CHECK_ERROR(); +    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&ctx->default_framebuffer); +    _SG_GL_CHECK_ERROR(); +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        SOKOL_ASSERT(0 == ctx->vao); +        glGenVertexArrays(1, &ctx->vao); +        glBindVertexArray(ctx->vao); +        _SG_GL_CHECK_ERROR(); +    } +    #endif +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_destroy_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        if (ctx->vao) { +            glDeleteVertexArrays(1, &ctx->vao); +        } +        _SG_GL_CHECK_ERROR(); +    } +    #else +    _SOKOL_UNUSED(ctx); +    #endif +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { +    SOKOL_ASSERT(buf && desc); +    _SG_GL_CHECK_ERROR(); +    _sg_buffer_common_init(&buf->cmn, desc); +    buf->gl.ext_buffers = (0 != desc->gl_buffers[0]); +    GLenum gl_target = _sg_gl_buffer_target(buf->cmn.type); +    GLenum gl_usage  = _sg_gl_usage(buf->cmn.usage); +    for (int slot = 0; slot < buf->cmn.num_slots; slot++) { +        GLuint gl_buf = 0; +        if (buf->gl.ext_buffers) { +            SOKOL_ASSERT(desc->gl_buffers[slot]); +            gl_buf = desc->gl_buffers[slot]; +        } +        else { +            glGenBuffers(1, &gl_buf); +            _sg_gl_cache_store_buffer_binding(gl_target); +            _sg_gl_cache_bind_buffer(gl_target, gl_buf); +            glBufferData(gl_target, buf->cmn.size, 0, gl_usage); +            if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { +                SOKOL_ASSERT(desc->data.ptr); +                glBufferSubData(gl_target, 0, buf->cmn.size, desc->data.ptr); +            } +            _sg_gl_cache_restore_buffer_binding(gl_target); +        } +        buf->gl.buf[slot] = gl_buf; +    } +    _SG_GL_CHECK_ERROR(); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_destroy_buffer(_sg_buffer_t* buf) { +    SOKOL_ASSERT(buf); +    _SG_GL_CHECK_ERROR(); +    for (int slot = 0; slot < buf->cmn.num_slots; slot++) { +        if (buf->gl.buf[slot]) { +            _sg_gl_cache_invalidate_buffer(buf->gl.buf[slot]); +            if (!buf->gl.ext_buffers) { +                glDeleteBuffers(1, &buf->gl.buf[slot]); +            } +        } +    } +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE bool _sg_gl_supported_texture_format(sg_pixel_format fmt) { +    const int fmt_index = (int) fmt; +    SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM)); +    return _sg.formats[fmt_index].sample; +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_image_desc* desc) { +    SOKOL_ASSERT(img && desc); +    _SG_GL_CHECK_ERROR(); +    _sg_image_common_init(&img->cmn, desc); +    img->gl.ext_textures = (0 != desc->gl_textures[0]); + +    /* 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"); +        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"); +        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"); +        return SG_RESOURCESTATE_FAILED; +    } + +    #if !defined(SOKOL_GLES2) +    bool msaa = false; +    if (!_sg.gl.gles2) { +        msaa = (img->cmn.sample_count > 1) && (_sg.features.msaa_render_targets); +    } +    #endif + +    if (_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format)) { +        /* special case depth-stencil-buffer? */ +        SOKOL_ASSERT((img->cmn.usage == SG_USAGE_IMMUTABLE) && (img->cmn.num_slots == 1)); +        SOKOL_ASSERT(!img->gl.ext_textures);   /* cannot provide external texture for depth images */ +        glGenRenderbuffers(1, &img->gl.depth_render_buffer); +        glBindRenderbuffer(GL_RENDERBUFFER, img->gl.depth_render_buffer); +        GLenum gl_depth_format = _sg_gl_depth_attachment_format(img->cmn.pixel_format); +        #if !defined(SOKOL_GLES2) +        if (!_sg.gl.gles2 && msaa) { +            glRenderbufferStorageMultisample(GL_RENDERBUFFER, img->cmn.sample_count, gl_depth_format, img->cmn.width, img->cmn.height); +        } +        else +        #endif +        { +            glRenderbufferStorage(GL_RENDERBUFFER, gl_depth_format, img->cmn.width, img->cmn.height); +        } +    } +    else { +        /* regular color texture */ +        img->gl.target = _sg_gl_texture_target(img->cmn.type); +        const GLenum gl_internal_format = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + +        /* if this is a MSAA render target, need to create a separate render buffer */ +        #if !defined(SOKOL_GLES2) +        if (!_sg.gl.gles2 && img->cmn.render_target && msaa) { +            glGenRenderbuffers(1, &img->gl.msaa_render_buffer); +            glBindRenderbuffer(GL_RENDERBUFFER, img->gl.msaa_render_buffer); +            glRenderbufferStorageMultisample(GL_RENDERBUFFER, img->cmn.sample_count, gl_internal_format, img->cmn.width, img->cmn.height); +        } +        #endif + +        if (img->gl.ext_textures) { +            /* inject externally GL textures */ +            for (int slot = 0; slot < img->cmn.num_slots; slot++) { +                SOKOL_ASSERT(desc->gl_textures[slot]); +                img->gl.tex[slot] = desc->gl_textures[slot]; +            } +            if (desc->gl_texture_target) { +                img->gl.target = (GLenum)desc->gl_texture_target; +            } +        } +        else { +            /* create our own GL texture(s) */ +            const GLenum gl_format = _sg_gl_teximage_format(img->cmn.pixel_format); +            const bool is_compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format); +            for (int slot = 0; slot < img->cmn.num_slots; slot++) { +                glGenTextures(1, &img->gl.tex[slot]); +                SOKOL_ASSERT(img->gl.tex[slot]); +                _sg_gl_cache_store_texture_binding(0); +                _sg_gl_cache_bind_texture(0, img->gl.target, img->gl.tex[slot]); +                GLenum gl_min_filter = _sg_gl_filter(img->cmn.min_filter); +                GLenum gl_mag_filter = _sg_gl_filter(img->cmn.mag_filter); +                glTexParameteri(img->gl.target, GL_TEXTURE_MIN_FILTER, (GLint)gl_min_filter); +                glTexParameteri(img->gl.target, GL_TEXTURE_MAG_FILTER, (GLint)gl_mag_filter); +                if (_sg.gl.ext_anisotropic && (img->cmn.max_anisotropy > 1)) { +                    GLint max_aniso = (GLint) img->cmn.max_anisotropy; +                    if (max_aniso > _sg.gl.max_anisotropy) { +                        max_aniso = _sg.gl.max_anisotropy; +                    } +                    glTexParameteri(img->gl.target, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_aniso); +                } +                if (img->cmn.type == SG_IMAGETYPE_CUBE) { +                    glTexParameteri(img->gl.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); +                    glTexParameteri(img->gl.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +                } +                else { +                    glTexParameteri(img->gl.target, GL_TEXTURE_WRAP_S, (GLint)_sg_gl_wrap(img->cmn.wrap_u)); +                    glTexParameteri(img->gl.target, GL_TEXTURE_WRAP_T, (GLint)_sg_gl_wrap(img->cmn.wrap_v)); +                    #if !defined(SOKOL_GLES2) +                    if (!_sg.gl.gles2 && (img->cmn.type == SG_IMAGETYPE_3D)) { +                        glTexParameteri(img->gl.target, GL_TEXTURE_WRAP_R, (GLint)_sg_gl_wrap(img->cmn.wrap_w)); +                    } +                    #endif +                    #if defined(SOKOL_GLCORE33) +                    float border[4]; +                    switch (img->cmn.border_color) { +                        case SG_BORDERCOLOR_TRANSPARENT_BLACK: +                            border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 0.0f; +                            break; +                        case SG_BORDERCOLOR_OPAQUE_WHITE: +                            border[0] = 1.0f; border[1] = 1.0f; border[2] = 1.0f; border[3] = 1.0f; +                            break; +                        default: +                            border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 1.0f; +                            break; +                    } +                    glTexParameterfv(img->gl.target, GL_TEXTURE_BORDER_COLOR, border); +                    #endif +                } +                #if !defined(SOKOL_GLES2) +                if (!_sg.gl.gles2) { +                    /* GL spec has strange defaults for mipmap min/max lod: -1000 to +1000 */ +                    const float min_lod = _sg_clamp(desc->min_lod, 0.0f, 1000.0f); +                    const float max_lod = _sg_clamp(desc->max_lod, 0.0f, 1000.0f); +                    glTexParameterf(img->gl.target, GL_TEXTURE_MIN_LOD, min_lod); +                    glTexParameterf(img->gl.target, GL_TEXTURE_MAX_LOD, max_lod); +                } +                #endif +                const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1; +                int data_index = 0; +                for (int face_index = 0; face_index < num_faces; face_index++) { +                    for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, data_index++) { +                        GLenum gl_img_target = img->gl.target; +                        if (SG_IMAGETYPE_CUBE == img->cmn.type) { +                            gl_img_target = _sg_gl_cubeface_target(face_index); +                        } +                        const GLvoid* data_ptr = desc->data.subimage[face_index][mip_index].ptr; +                        const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; +                        int mip_width = img->cmn.width >> mip_index; +                        if (mip_width == 0) { +                            mip_width = 1; +                        } +                        int mip_height = img->cmn.height >> mip_index; +                        if (mip_height == 0) { +                            mip_height = 1; +                        } +                        if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { +                            if (is_compressed) { +                                glCompressedTexImage2D(gl_img_target, mip_index, gl_internal_format, +                                    mip_width, mip_height, 0, data_size, data_ptr); +                            } +                            else { +                                const GLenum gl_type = _sg_gl_teximage_type(img->cmn.pixel_format); +                                glTexImage2D(gl_img_target, mip_index, (GLint)gl_internal_format, +                                    mip_width, mip_height, 0, gl_format, gl_type, data_ptr); +                            } +                        } +                        #if !defined(SOKOL_GLES2) +                        else if (!_sg.gl.gles2 && ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type))) { +                            int mip_depth = img->cmn.num_slices; +                            if (SG_IMAGETYPE_3D == img->cmn.type) { +                                mip_depth >>= mip_index; +                            } +                            if (mip_depth == 0) { +                                mip_depth = 1; +                            } +                            if (is_compressed) { +                                glCompressedTexImage3D(gl_img_target, mip_index, gl_internal_format, +                                    mip_width, mip_height, mip_depth, 0, data_size, data_ptr); +                            } +                            else { +                                const GLenum gl_type = _sg_gl_teximage_type(img->cmn.pixel_format); +                                glTexImage3D(gl_img_target, mip_index, (GLint)gl_internal_format, +                                    mip_width, mip_height, mip_depth, 0, gl_format, gl_type, data_ptr); +                            } +                        } +                        #endif +                    } +                } +                _sg_gl_cache_restore_texture_binding(0); +            } +        } +    } +    _SG_GL_CHECK_ERROR(); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_destroy_image(_sg_image_t* img) { +    SOKOL_ASSERT(img); +    _SG_GL_CHECK_ERROR(); +    for (int slot = 0; slot < img->cmn.num_slots; slot++) { +        if (img->gl.tex[slot]) { +            _sg_gl_cache_invalidate_texture(img->gl.tex[slot]); +            if (!img->gl.ext_textures) { +                glDeleteTextures(1, &img->gl.tex[slot]); +            } +        } +    } +    if (img->gl.depth_render_buffer) { +        glDeleteRenderbuffers(1, &img->gl.depth_render_buffer); +    } +    if (img->gl.msaa_render_buffer) { +        glDeleteRenderbuffers(1, &img->gl.msaa_render_buffer); +    } +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* src) { +    SOKOL_ASSERT(src); +    _SG_GL_CHECK_ERROR(); +    GLuint gl_shd = glCreateShader(_sg_gl_shader_stage(stage)); +    glShaderSource(gl_shd, 1, &src, 0); +    glCompileShader(gl_shd); +    GLint compile_status = 0; +    glGetShaderiv(gl_shd, GL_COMPILE_STATUS, &compile_status); +    if (!compile_status) { +        /* compilation failed, log error and delete shader */ +        GLint log_len = 0; +        glGetShaderiv(gl_shd, GL_INFO_LOG_LENGTH, &log_len); +        if (log_len > 0) { +            GLchar* log_buf = (GLchar*) SOKOL_MALLOC((size_t)log_len); +            glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf); +            SOKOL_LOG(log_buf); +            SOKOL_FREE(log_buf); +        } +        glDeleteShader(gl_shd); +        gl_shd = 0; +    } +    _SG_GL_CHECK_ERROR(); +    return gl_shd; +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { +    SOKOL_ASSERT(shd && desc); +    SOKOL_ASSERT(!shd->gl.prog); +    _SG_GL_CHECK_ERROR(); + +    _sg_shader_common_init(&shd->cmn, desc); + +    /* copy vertex attribute names over, these are required for GLES2, and optional for GLES3 and GL3.x */ +    for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { +        _sg_strcpy(&shd->gl.attrs[i].name, desc->attrs[i].name); +    } + +    GLuint gl_vs = _sg_gl_compile_shader(SG_SHADERSTAGE_VS, desc->vs.source); +    GLuint gl_fs = _sg_gl_compile_shader(SG_SHADERSTAGE_FS, desc->fs.source); +    if (!(gl_vs && gl_fs)) { +        return SG_RESOURCESTATE_FAILED; +    } +    GLuint gl_prog = glCreateProgram(); +    glAttachShader(gl_prog, gl_vs); +    glAttachShader(gl_prog, gl_fs); +    glLinkProgram(gl_prog); +    glDeleteShader(gl_vs); +    glDeleteShader(gl_fs); +    _SG_GL_CHECK_ERROR(); + +    GLint link_status; +    glGetProgramiv(gl_prog, GL_LINK_STATUS, &link_status); +    if (!link_status) { +        GLint log_len = 0; +        glGetProgramiv(gl_prog, GL_INFO_LOG_LENGTH, &log_len); +        if (log_len > 0) { +            GLchar* log_buf = (GLchar*) SOKOL_MALLOC((size_t)log_len); +            glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf); +            SOKOL_LOG(log_buf); +            SOKOL_FREE(log_buf); +        } +        glDeleteProgram(gl_prog); +        return SG_RESOURCESTATE_FAILED; +    } +    shd->gl.prog = gl_prog; + +    /* resolve uniforms */ +    _SG_GL_CHECK_ERROR(); +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS)? &desc->vs : &desc->fs; +        _sg_gl_shader_stage_t* gl_stage = &shd->gl.stage[stage_index]; +        for (int ub_index = 0; ub_index < shd->cmn.stage[stage_index].num_uniform_blocks; ub_index++) { +            const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; +            SOKOL_ASSERT(ub_desc->size > 0); +            _sg_gl_uniform_block_t* ub = &gl_stage->uniform_blocks[ub_index]; +            SOKOL_ASSERT(ub->num_uniforms == 0); +            int cur_uniform_offset = 0; +            for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { +                const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; +                if (u_desc->type == SG_UNIFORMTYPE_INVALID) { +                    break; +                } +                _sg_gl_uniform_t* u = &ub->uniforms[u_index]; +                u->type = u_desc->type; +                u->count = (uint8_t) u_desc->array_count; +                u->offset = (uint16_t) cur_uniform_offset; +                cur_uniform_offset += _sg_uniform_size(u->type, u->count); +                if (u_desc->name) { +                    u->gl_loc = glGetUniformLocation(gl_prog, u_desc->name); +                } +                else { +                    u->gl_loc = u_index; +                } +                ub->num_uniforms++; +            } +            SOKOL_ASSERT(ub_desc->size == (size_t)cur_uniform_offset); +        } +    } + +    /* resolve image locations */ +    _SG_GL_CHECK_ERROR(); +    GLuint cur_prog = 0; +    glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&cur_prog); +    glUseProgram(gl_prog); +    int gl_tex_slot = 0; +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS)? &desc->vs : &desc->fs; +        _sg_gl_shader_stage_t* gl_stage = &shd->gl.stage[stage_index]; +        for (int img_index = 0; img_index < shd->cmn.stage[stage_index].num_images; img_index++) { +            const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; +            SOKOL_ASSERT(img_desc->image_type != _SG_IMAGETYPE_DEFAULT); +            _sg_gl_shader_image_t* gl_img = &gl_stage->images[img_index]; +            GLint gl_loc = img_index; +            if (img_desc->name) { +                gl_loc = glGetUniformLocation(gl_prog, img_desc->name); +            } +            if (gl_loc != -1) { +                gl_img->gl_tex_slot = gl_tex_slot++; +                glUniform1i(gl_loc, gl_img->gl_tex_slot); +            } +            else { +                gl_img->gl_tex_slot = -1; +            } +        } +    } +    /* it's legal to call glUseProgram with 0 */ +    glUseProgram(cur_prog); +    _SG_GL_CHECK_ERROR(); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_destroy_shader(_sg_shader_t* shd) { +    SOKOL_ASSERT(shd); +    _SG_GL_CHECK_ERROR(); +    if (shd->gl.prog) { +        _sg_gl_cache_invalidate_program(shd->gl.prog); +        glDeleteProgram(shd->gl.prog); +    } +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(pip && shd && desc); +    SOKOL_ASSERT(!pip->shader && pip->cmn.shader_id.id == SG_INVALID_ID); +    SOKOL_ASSERT(desc->shader.id == shd->slot.id); +    SOKOL_ASSERT(shd->gl.prog); +    pip->shader = shd; +    _sg_pipeline_common_init(&pip->cmn, desc); +    pip->gl.primitive_type = desc->primitive_type; +    pip->gl.depth = desc->depth; +    pip->gl.stencil = desc->stencil; +    // FIXME: blend color and write mask per draw-buffer-attachment (requires GL4) +    pip->gl.blend = desc->colors[0].blend; +    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +        pip->gl.color_write_mask[i] = desc->colors[i].write_mask; +    } +    pip->gl.cull_mode = desc->cull_mode; +    pip->gl.face_winding = desc->face_winding; +    pip->gl.sample_count = desc->sample_count; +    pip->gl.alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; + +    /* resolve vertex attributes */ +    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        pip->gl.attrs[attr_index].vb_index = -1; +    } +    for (int attr_index = 0; attr_index < _sg.limits.max_vertex_attrs; attr_index++) { +        const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +        if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +            break; +        } +        SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +        const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[a_desc->buffer_index]; +        const sg_vertex_step step_func = l_desc->step_func; +        const int step_rate = l_desc->step_rate; +        GLint attr_loc = attr_index; +        if (!_sg_strempty(&shd->gl.attrs[attr_index].name)) { +            attr_loc = glGetAttribLocation(pip->shader->gl.prog, _sg_strptr(&shd->gl.attrs[attr_index].name)); +        } +        SOKOL_ASSERT(attr_loc < (GLint)_sg.limits.max_vertex_attrs); +        if (attr_loc != -1) { +            _sg_gl_attr_t* gl_attr = &pip->gl.attrs[attr_loc]; +            SOKOL_ASSERT(gl_attr->vb_index == -1); +            gl_attr->vb_index = (int8_t) a_desc->buffer_index; +            if (step_func == SG_VERTEXSTEP_PER_VERTEX) { +                gl_attr->divisor = 0; +            } +            else { +                gl_attr->divisor = (int8_t) step_rate; +            } +            SOKOL_ASSERT(l_desc->stride > 0); +            gl_attr->stride = (uint8_t) l_desc->stride; +            gl_attr->offset = a_desc->offset; +            gl_attr->size = (uint8_t) _sg_gl_vertexformat_size(a_desc->format); +            gl_attr->type = _sg_gl_vertexformat_type(a_desc->format); +            gl_attr->normalized = _sg_gl_vertexformat_normalized(a_desc->format); +            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)); +        } +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_destroy_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    _SOKOL_UNUSED(pip); +    /* empty */ +} + +/* +    _sg_create_pass + +    att_imgs must point to a _sg_image* att_imgs[SG_MAX_COLOR_ATTACHMENTS+1] array, +    first entries are the color attachment images (or nullptr), last entry +    is the depth-stencil image (or nullptr). +*/ +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_t** att_images, const sg_pass_desc* desc) { +    SOKOL_ASSERT(pass && att_images && desc); +    SOKOL_ASSERT(att_images && att_images[0]); +    _SG_GL_CHECK_ERROR(); + +    _sg_pass_common_init(&pass->cmn, desc); + +    /* copy image pointers */ +    const sg_pass_attachment_desc* att_desc; +    for (int i = 0; i < pass->cmn.num_color_atts; i++) { +        att_desc = &desc->color_attachments[i]; +        SOKOL_ASSERT(att_desc->image.id != SG_INVALID_ID); +        SOKOL_ASSERT(0 == pass->gl.color_atts[i].image); +        SOKOL_ASSERT(att_images[i] && (att_images[i]->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(att_images[i]->cmn.pixel_format)); +        pass->gl.color_atts[i].image = att_images[i]; +    } +    SOKOL_ASSERT(0 == pass->gl.ds_att.image); +    att_desc = &desc->depth_stencil_attachment; +    if (att_desc->image.id != SG_INVALID_ID) { +        const int ds_img_index = SG_MAX_COLOR_ATTACHMENTS; +        SOKOL_ASSERT(att_images[ds_img_index] && (att_images[ds_img_index]->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(att_images[ds_img_index]->cmn.pixel_format)); +        pass->gl.ds_att.image = att_images[ds_img_index]; +    } + +    /* store current framebuffer binding (restored at end of function) */ +    GLuint gl_orig_fb; +    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&gl_orig_fb); + +    /* create a framebuffer object */ +    glGenFramebuffers(1, &pass->gl.fb); +    glBindFramebuffer(GL_FRAMEBUFFER, pass->gl.fb); + +    /* attach msaa render buffer or textures */ +    const bool is_msaa = (0 != att_images[0]->gl.msaa_render_buffer); +    if (is_msaa) { +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            const _sg_image_t* att_img = pass->gl.color_atts[i].image; +            if (att_img) { +                const GLuint gl_render_buffer = att_img->gl.msaa_render_buffer; +                SOKOL_ASSERT(gl_render_buffer); +                glFramebufferRenderbuffer(GL_FRAMEBUFFER, (GLenum)(GL_COLOR_ATTACHMENT0+i), GL_RENDERBUFFER, gl_render_buffer); +            } +        } +    } +    else { +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            const _sg_image_t* att_img = pass->gl.color_atts[i].image; +            const int mip_level = pass->cmn.color_atts[i].mip_level; +            const int slice = pass->cmn.color_atts[i].slice; +            if (att_img) { +                const GLuint gl_tex = att_img->gl.tex[0]; +                SOKOL_ASSERT(gl_tex); +                const GLenum gl_att = (GLenum)(GL_COLOR_ATTACHMENT0 + i); +                switch (att_img->cmn.type) { +                    case SG_IMAGETYPE_2D: +                        glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att, GL_TEXTURE_2D, gl_tex, mip_level); +                        break; +                    case SG_IMAGETYPE_CUBE: +                        glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att, _sg_gl_cubeface_target(slice), gl_tex, mip_level); +                        break; +                    default: +                        /* 3D- or array-texture */ +                        #if !defined(SOKOL_GLES2) +                        if (!_sg.gl.gles2) { +                            glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_att, gl_tex, mip_level, slice); +                        } +                        #endif +                        break; +                } +            } +        } +    } + +    /* attach depth-stencil buffer to framebuffer */ +    if (pass->gl.ds_att.image) { +        const GLuint gl_render_buffer = pass->gl.ds_att.image->gl.depth_render_buffer; +        SOKOL_ASSERT(gl_render_buffer); +        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gl_render_buffer); +        if (_sg_is_depth_stencil_format(pass->gl.ds_att.image->cmn.pixel_format)) { +            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gl_render_buffer); +        } +    } + +    /* check if framebuffer is complete */ +    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { +        SOKOL_LOG("Framebuffer completeness check failed!\n"); +        return SG_RESOURCESTATE_FAILED; +    } + +    /* setup color attachments for the framebuffer */ +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2) { +        GLenum att[SG_MAX_COLOR_ATTACHMENTS] = { +            GL_COLOR_ATTACHMENT0, +            GL_COLOR_ATTACHMENT1, +            GL_COLOR_ATTACHMENT2, +            GL_COLOR_ATTACHMENT3 +        }; +        glDrawBuffers(pass->cmn.num_color_atts, att); +    } +    #endif + +    /* create MSAA resolve framebuffers if necessary */ +    if (is_msaa) { +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            _sg_gl_attachment_t* gl_att = &pass->gl.color_atts[i]; +            _sg_pass_attachment_t* cmn_att = &pass->cmn.color_atts[i]; +            if (gl_att->image) { +                SOKOL_ASSERT(0 == gl_att->gl_msaa_resolve_buffer); +                glGenFramebuffers(1, &gl_att->gl_msaa_resolve_buffer); +                glBindFramebuffer(GL_FRAMEBUFFER, gl_att->gl_msaa_resolve_buffer); +                const GLuint gl_tex = gl_att->image->gl.tex[0]; +                SOKOL_ASSERT(gl_tex); +                switch (gl_att->image->cmn.type) { +                    case SG_IMAGETYPE_2D: +                        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, +                            GL_TEXTURE_2D, gl_tex, cmn_att->mip_level); +                        break; +                    case SG_IMAGETYPE_CUBE: +                        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, +                            _sg_gl_cubeface_target(cmn_att->slice), gl_tex, cmn_att->mip_level); +                        break; +                    default: +                        #if !defined(SOKOL_GLES2) +                        if (!_sg.gl.gles2) { +                            glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_tex, cmn_att->mip_level, cmn_att->slice); +                        } +                        #endif +                        break; +                } +                /* check if framebuffer is complete */ +                if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { +                    SOKOL_LOG("Framebuffer completeness check failed (msaa resolve buffer)!\n"); +                    return SG_RESOURCESTATE_FAILED; +                } +                /* setup color attachments for the framebuffer */ +                #if !defined(SOKOL_GLES2) +                if (!_sg.gl.gles2) { +                    const GLenum gl_draw_bufs = GL_COLOR_ATTACHMENT0; +                    glDrawBuffers(1, &gl_draw_bufs); +                } +                #endif +            } +        } +    } + +    /* restore original framebuffer binding */ +    glBindFramebuffer(GL_FRAMEBUFFER, gl_orig_fb); +    _SG_GL_CHECK_ERROR(); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_destroy_pass(_sg_pass_t* pass) { +    SOKOL_ASSERT(pass); +    _SG_GL_CHECK_ERROR(); +    if (0 != pass->gl.fb) { +        glDeleteFramebuffers(1, &pass->gl.fb); +    } +    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +        if (pass->gl.color_atts[i].gl_msaa_resolve_buffer) { +            glDeleteFramebuffers(1, &pass->gl.color_atts[i].gl_msaa_resolve_buffer); +        } +    } +    if (pass->gl.ds_att.gl_msaa_resolve_buffer) { +        glDeleteFramebuffers(1, &pass->gl.ds_att.gl_msaa_resolve_buffer); +    } +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE _sg_image_t* _sg_gl_pass_color_image(const _sg_pass_t* pass, int index) { +    SOKOL_ASSERT(pass && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); +    /* NOTE: may return null */ +    return pass->gl.color_atts[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_gl_pass_ds_image(const _sg_pass_t* pass) { +    /* NOTE: may return null */ +    SOKOL_ASSERT(pass); +    return pass->gl.ds_att.image; +} + +_SOKOL_PRIVATE void _sg_gl_begin_pass(_sg_pass_t* pass, const sg_pass_action* action, int w, int h) { +    /* FIXME: what if a texture used as render target is still bound, should we +       unbind all currently bound textures in begin pass? */ +    SOKOL_ASSERT(action); +    SOKOL_ASSERT(!_sg.gl.in_pass); +    _SG_GL_CHECK_ERROR(); +    _sg.gl.in_pass = true; +    _sg.gl.cur_pass = pass; /* can be 0 */ +    if (pass) { +        _sg.gl.cur_pass_id.id = pass->slot.id; +    } +    else { +        _sg.gl.cur_pass_id.id = SG_INVALID_ID; +    } +    _sg.gl.cur_pass_width = w; +    _sg.gl.cur_pass_height = h; + +    /* number of color attachments */ +    const int num_color_atts = pass ? pass->cmn.num_color_atts : 1; + +    /* bind the render pass framebuffer */ +    if (pass) { +        /* offscreen pass */ +        SOKOL_ASSERT(pass->gl.fb); +        glBindFramebuffer(GL_FRAMEBUFFER, pass->gl.fb); +    } +    else { +        /* default pass */ +        SOKOL_ASSERT(_sg.gl.cur_context); +        glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.cur_context->default_framebuffer); +    } +    glViewport(0, 0, w, h); +    glScissor(0, 0, w, h); + +    /* clear color and depth-stencil attachments if needed */ +    bool clear_color = false; +    for (int i = 0; i < num_color_atts; i++) { +        if (SG_ACTION_CLEAR == action->colors[i].action) { +            clear_color = true; +            break; +        } +    } +    const bool clear_depth = (action->depth.action == SG_ACTION_CLEAR); +    const bool clear_stencil = (action->stencil.action == SG_ACTION_CLEAR); + +    bool need_pip_cache_flush = false; +    if (clear_color) { +        bool need_color_mask_flush = false; +        // NOTE: not a bug to iterate over all possible color attachments +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            if (SG_COLORMASK_RGBA != _sg.gl.cache.color_write_mask[i]) { +                need_pip_cache_flush = true; +                need_color_mask_flush = true; +                _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA; +            } +        } +        if (need_color_mask_flush) { +            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); +        } +    } +    if (clear_depth) { +        if (!_sg.gl.cache.depth.write_enabled) { +            need_pip_cache_flush = true; +            _sg.gl.cache.depth.write_enabled = true; +            glDepthMask(GL_TRUE); +        } +        if (_sg.gl.cache.depth.compare != SG_COMPAREFUNC_ALWAYS) { +            need_pip_cache_flush = true; +            _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS; +            glDepthFunc(GL_ALWAYS); +        } +    } +    if (clear_stencil) { +        if (_sg.gl.cache.stencil.write_mask != 0xFF) { +            need_pip_cache_flush = true; +            _sg.gl.cache.stencil.write_mask = 0xFF; +            glStencilMask(0xFF); +        } +    } +    if (need_pip_cache_flush) { +        /* we messed with the state cache directly, need to clear cached +           pipeline to force re-evaluation in next sg_apply_pipeline() */ +        _sg.gl.cache.cur_pipeline = 0; +        _sg.gl.cache.cur_pipeline_id.id = SG_INVALID_ID; +    } +    bool use_mrt_clear = (0 != pass); +    #if defined(SOKOL_GLES2) +    use_mrt_clear = false; +    #else +    if (_sg.gl.gles2) { +        use_mrt_clear = false; +    } +    #endif +    if (!use_mrt_clear) { +        GLbitfield clear_mask = 0; +        if (clear_color) { +            clear_mask |= GL_COLOR_BUFFER_BIT; +            const sg_color c = action->colors[0].value; +            glClearColor(c.r, c.g, c.b, c.a); +        } +        if (clear_depth) { +            clear_mask |= GL_DEPTH_BUFFER_BIT; +            #ifdef SOKOL_GLCORE33 +            glClearDepth(action->depth.value); +            #else +            glClearDepthf(action->depth.value); +            #endif +        } +        if (clear_stencil) { +            clear_mask |= GL_STENCIL_BUFFER_BIT; +            glClearStencil(action->stencil.value); +        } +        if (0 != clear_mask) { +            glClear(clear_mask); +        } +    } +    #if !defined SOKOL_GLES2 +    else { +        SOKOL_ASSERT(pass); +        for (int i = 0; i < num_color_atts; i++) { +            if (action->colors[i].action == SG_ACTION_CLEAR) { +                glClearBufferfv(GL_COLOR, i, &action->colors[i].value.r); +            } +        } +        if (pass->gl.ds_att.image) { +            if (clear_depth && clear_stencil) { +                glClearBufferfi(GL_DEPTH_STENCIL, 0, action->depth.value, action->stencil.value); +            } +            else if (clear_depth) { +                glClearBufferfv(GL_DEPTH, 0, &action->depth.value); +            } +            else if (clear_stencil) { +                GLint val = (GLint) action->stencil.value; +                glClearBufferiv(GL_STENCIL, 0, &val); +            } +        } +    } +    #endif +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_end_pass(void) { +    SOKOL_ASSERT(_sg.gl.in_pass); +    _SG_GL_CHECK_ERROR(); + +    /* if this was an offscreen pass, and MSAA rendering was used, need +       to resolve into the pass images */ +    #if !defined(SOKOL_GLES2) +    if (!_sg.gl.gles2 && _sg.gl.cur_pass) { +        /* check if the pass object is still valid */ +        const _sg_pass_t* pass = _sg.gl.cur_pass; +        SOKOL_ASSERT(pass->slot.id == _sg.gl.cur_pass_id.id); +        bool is_msaa = (0 != _sg.gl.cur_pass->gl.color_atts[0].gl_msaa_resolve_buffer); +        if (is_msaa) { +            SOKOL_ASSERT(pass->gl.fb); +            glBindFramebuffer(GL_READ_FRAMEBUFFER, pass->gl.fb); +            SOKOL_ASSERT(pass->gl.color_atts[0].image); +            const int w = pass->gl.color_atts[0].image->cmn.width; +            const int h = pass->gl.color_atts[0].image->cmn.height; +            for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { +                const _sg_gl_attachment_t* gl_att = &pass->gl.color_atts[att_index]; +                if (gl_att->image) { +                    SOKOL_ASSERT(gl_att->gl_msaa_resolve_buffer); +                    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gl_att->gl_msaa_resolve_buffer); +                    glReadBuffer((GLenum)(GL_COLOR_ATTACHMENT0 + att_index)); +                    glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); +                } +                else { +                    break; +                } +            } +        } +    } +    #endif +    _sg.gl.cur_pass = 0; +    _sg.gl.cur_pass_id.id = SG_INVALID_ID; +    _sg.gl.cur_pass_width = 0; +    _sg.gl.cur_pass_height = 0; + +    SOKOL_ASSERT(_sg.gl.cur_context); +    glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.cur_context->default_framebuffer); +    _sg.gl.in_pass = false; +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.gl.in_pass); +    y = origin_top_left ? (_sg.gl.cur_pass_height - (y+h)) : y; +    glViewport(x, y, w, h); +} + +_SOKOL_PRIVATE void _sg_gl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.gl.in_pass); +    y = origin_top_left ? (_sg.gl.cur_pass_height - (y+h)) : y; +    glScissor(x, y, w, h); +} + +_SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); +    _SG_GL_CHECK_ERROR(); +    if ((_sg.gl.cache.cur_pipeline != pip) || (_sg.gl.cache.cur_pipeline_id.id != pip->slot.id)) { +        _sg.gl.cache.cur_pipeline = pip; +        _sg.gl.cache.cur_pipeline_id.id = pip->slot.id; +        _sg.gl.cache.cur_primitive_type = _sg_gl_primitive_type(pip->gl.primitive_type); +        _sg.gl.cache.cur_index_type = _sg_gl_index_type(pip->cmn.index_type); + +        /* update depth state */ +        { +            const sg_depth_state* state_ds = &pip->gl.depth; +            sg_depth_state* cache_ds = &_sg.gl.cache.depth; +            if (state_ds->compare != cache_ds->compare) { +                cache_ds->compare = state_ds->compare; +                glDepthFunc(_sg_gl_compare_func(state_ds->compare)); +            } +            if (state_ds->write_enabled != cache_ds->write_enabled) { +                cache_ds->write_enabled = state_ds->write_enabled; +                glDepthMask(state_ds->write_enabled); +            } +            if (!_sg_fequal(state_ds->bias, cache_ds->bias, 0.000001f) || +                !_sg_fequal(state_ds->bias_slope_scale, cache_ds->bias_slope_scale, 0.000001f)) +            { +                /* according to ANGLE's D3D11 backend: +                    D3D11 SlopeScaledDepthBias ==> GL polygonOffsetFactor +                    D3D11 DepthBias ==> GL polygonOffsetUnits +                    DepthBiasClamp has no meaning on GL +                */ +                cache_ds->bias = state_ds->bias; +                cache_ds->bias_slope_scale = state_ds->bias_slope_scale; +                glPolygonOffset(state_ds->bias_slope_scale, state_ds->bias); +                bool po_enabled = true; +                if (_sg_fequal(state_ds->bias, 0.0f, 0.000001f) && +                    _sg_fequal(state_ds->bias_slope_scale, 0.0f, 0.000001f)) +                { +                    po_enabled = false; +                } +                if (po_enabled != _sg.gl.cache.polygon_offset_enabled) { +                    _sg.gl.cache.polygon_offset_enabled = po_enabled; +                    if (po_enabled) { +                        glEnable(GL_POLYGON_OFFSET_FILL); +                    } +                    else { +                        glDisable(GL_POLYGON_OFFSET_FILL); +                    } +                } +            } +        } + +        /* update stencil state */ +        { +            const sg_stencil_state* state_ss = &pip->gl.stencil; +            sg_stencil_state* cache_ss = &_sg.gl.cache.stencil; +            if (state_ss->enabled != cache_ss->enabled) { +                cache_ss->enabled = state_ss->enabled; +                if (state_ss->enabled) { +                    glEnable(GL_STENCIL_TEST); +                } +                else { +                    glDisable(GL_STENCIL_TEST); +                } +            } +            if (state_ss->write_mask != cache_ss->write_mask) { +                cache_ss->write_mask = state_ss->write_mask; +                glStencilMask(state_ss->write_mask); +            } +            for (int i = 0; i < 2; i++) { +                const sg_stencil_face_state* state_sfs = (i==0)? &state_ss->front : &state_ss->back; +                sg_stencil_face_state* cache_sfs = (i==0)? &cache_ss->front : &cache_ss->back; +                GLenum gl_face = (i==0)? GL_FRONT : GL_BACK; +                if ((state_sfs->compare != cache_sfs->compare) || +                    (state_ss->read_mask != cache_ss->read_mask) || +                    (state_ss->ref != cache_ss->ref)) +                { +                    cache_sfs->compare = state_sfs->compare; +                    glStencilFuncSeparate(gl_face, +                        _sg_gl_compare_func(state_sfs->compare), +                        state_ss->ref, +                        state_ss->read_mask); +                } +                if ((state_sfs->fail_op != cache_sfs->fail_op) || +                    (state_sfs->depth_fail_op != cache_sfs->depth_fail_op) || +                    (state_sfs->pass_op != cache_sfs->pass_op)) +                { +                    cache_sfs->fail_op = state_sfs->fail_op; +                    cache_sfs->depth_fail_op = state_sfs->depth_fail_op; +                    cache_sfs->pass_op = state_sfs->pass_op; +                    glStencilOpSeparate(gl_face, +                        _sg_gl_stencil_op(state_sfs->fail_op), +                        _sg_gl_stencil_op(state_sfs->depth_fail_op), +                        _sg_gl_stencil_op(state_sfs->pass_op)); +                } +            } +            cache_ss->read_mask = state_ss->read_mask; +            cache_ss->ref = state_ss->ref; +        } + +        /* update blend state +            FIXME: separate blend state per color attachment not support, needs GL4 +        */ +        { +            const sg_blend_state* state_bs = &pip->gl.blend; +            sg_blend_state* cache_bs = &_sg.gl.cache.blend; +            if (state_bs->enabled != cache_bs->enabled) { +                cache_bs->enabled = state_bs->enabled; +                if (state_bs->enabled) { +                    glEnable(GL_BLEND); +                } +                else { +                    glDisable(GL_BLEND); +                } +            } +            if ((state_bs->src_factor_rgb != cache_bs->src_factor_rgb) || +                (state_bs->dst_factor_rgb != cache_bs->dst_factor_rgb) || +                (state_bs->src_factor_alpha != cache_bs->src_factor_alpha) || +                (state_bs->dst_factor_alpha != cache_bs->dst_factor_alpha)) +            { +                cache_bs->src_factor_rgb = state_bs->src_factor_rgb; +                cache_bs->dst_factor_rgb = state_bs->dst_factor_rgb; +                cache_bs->src_factor_alpha = state_bs->src_factor_alpha; +                cache_bs->dst_factor_alpha = state_bs->dst_factor_alpha; +                glBlendFuncSeparate(_sg_gl_blend_factor(state_bs->src_factor_rgb), +                    _sg_gl_blend_factor(state_bs->dst_factor_rgb), +                    _sg_gl_blend_factor(state_bs->src_factor_alpha), +                    _sg_gl_blend_factor(state_bs->dst_factor_alpha)); +            } +            if ((state_bs->op_rgb != cache_bs->op_rgb) || (state_bs->op_alpha != cache_bs->op_alpha)) { +                cache_bs->op_rgb = state_bs->op_rgb; +                cache_bs->op_alpha = state_bs->op_alpha; +                glBlendEquationSeparate(_sg_gl_blend_op(state_bs->op_rgb), _sg_gl_blend_op(state_bs->op_alpha)); +            } +        } + +        /* standalone state */ +        for (GLuint i = 0; i < (GLuint)pip->cmn.color_attachment_count; i++) { +            if (pip->gl.color_write_mask[i] != _sg.gl.cache.color_write_mask[i]) { +                const sg_color_mask cm = pip->gl.color_write_mask[i]; +                _sg.gl.cache.color_write_mask[i] = cm; +                #ifdef SOKOL_GLCORE33 +                    glColorMaski(i, +                                (cm & SG_COLORMASK_R) != 0, +                                (cm & SG_COLORMASK_G) != 0, +                                (cm & SG_COLORMASK_B) != 0, +                                (cm & SG_COLORMASK_A) != 0); +                #else +                    if (0 == i) { +                        glColorMask((cm & SG_COLORMASK_R) != 0, +                                    (cm & SG_COLORMASK_G) != 0, +                                    (cm & SG_COLORMASK_B) != 0, +                                    (cm & SG_COLORMASK_A) != 0); +                    } +                #endif +            } +        } + +        if (!_sg_fequal(pip->cmn.blend_color.r, _sg.gl.cache.blend_color.r, 0.0001f) || +            !_sg_fequal(pip->cmn.blend_color.g, _sg.gl.cache.blend_color.g, 0.0001f) || +            !_sg_fequal(pip->cmn.blend_color.b, _sg.gl.cache.blend_color.b, 0.0001f) || +            !_sg_fequal(pip->cmn.blend_color.a, _sg.gl.cache.blend_color.a, 0.0001f)) +        { +            sg_color c = pip->cmn.blend_color; +            _sg.gl.cache.blend_color = c; +            glBlendColor(c.r, c.g, c.b, c.a); +        } +        if (pip->gl.cull_mode != _sg.gl.cache.cull_mode) { +            _sg.gl.cache.cull_mode = pip->gl.cull_mode; +            if (SG_CULLMODE_NONE == pip->gl.cull_mode) { +                glDisable(GL_CULL_FACE); +            } +            else { +                glEnable(GL_CULL_FACE); +                GLenum gl_mode = (SG_CULLMODE_FRONT == pip->gl.cull_mode) ? GL_FRONT : GL_BACK; +                glCullFace(gl_mode); +            } +        } +        if (pip->gl.face_winding != _sg.gl.cache.face_winding) { +            _sg.gl.cache.face_winding = pip->gl.face_winding; +            GLenum gl_winding = (SG_FACEWINDING_CW == pip->gl.face_winding) ? GL_CW : GL_CCW; +            glFrontFace(gl_winding); +        } +        if (pip->gl.alpha_to_coverage_enabled != _sg.gl.cache.alpha_to_coverage_enabled) { +            _sg.gl.cache.alpha_to_coverage_enabled = pip->gl.alpha_to_coverage_enabled; +            if (pip->gl.alpha_to_coverage_enabled) { +                glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); +            } +            else { +                glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); +            } +        } +        #ifdef SOKOL_GLCORE33 +        if (pip->gl.sample_count != _sg.gl.cache.sample_count) { +            _sg.gl.cache.sample_count = pip->gl.sample_count; +            if (pip->gl.sample_count > 1) { +                glEnable(GL_MULTISAMPLE); +            } +            else { +                glDisable(GL_MULTISAMPLE); +            } +        } +        #endif + +        /* bind shader program */ +        if (pip->shader->gl.prog != _sg.gl.cache.prog) { +            _sg.gl.cache.prog = pip->shader->gl.prog; +            glUseProgram(pip->shader->gl.prog); +        } +    } +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_apply_bindings( +    _sg_pipeline_t* pip, +    _sg_buffer_t** vbs, const int* vb_offsets, int num_vbs, +    _sg_buffer_t* ib, int ib_offset, +    _sg_image_t** vs_imgs, int num_vs_imgs, +    _sg_image_t** fs_imgs, int num_fs_imgs) +{ +    SOKOL_ASSERT(pip); +    _SOKOL_UNUSED(num_fs_imgs); +    _SOKOL_UNUSED(num_vs_imgs); +    _SOKOL_UNUSED(num_vbs); +    _SG_GL_CHECK_ERROR(); + +    /* bind textures */ +    _SG_GL_CHECK_ERROR(); +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[stage_index]; +        const _sg_gl_shader_stage_t* gl_stage = &pip->shader->gl.stage[stage_index]; +        _sg_image_t** imgs = (stage_index == SG_SHADERSTAGE_VS)? vs_imgs : fs_imgs; +        SOKOL_ASSERT(((stage_index == SG_SHADERSTAGE_VS)? num_vs_imgs : num_fs_imgs) == stage->num_images); +        for (int img_index = 0; img_index < stage->num_images; img_index++) { +            const _sg_gl_shader_image_t* gl_shd_img = &gl_stage->images[img_index]; +            if (gl_shd_img->gl_tex_slot != -1) { +                _sg_image_t* img = imgs[img_index]; +                const GLuint gl_tex = img->gl.tex[img->cmn.active_slot]; +                SOKOL_ASSERT(img && img->gl.target); +                SOKOL_ASSERT((gl_shd_img->gl_tex_slot != -1) && gl_tex); +                _sg_gl_cache_bind_texture(gl_shd_img->gl_tex_slot, img->gl.target, gl_tex); +            } +        } +    } +    _SG_GL_CHECK_ERROR(); + +    /* index buffer (can be 0) */ +    const GLuint gl_ib = ib ? ib->gl.buf[ib->cmn.active_slot] : 0; +    _sg_gl_cache_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, gl_ib); +    _sg.gl.cache.cur_ib_offset = ib_offset; + +    /* vertex attributes */ +    for (GLuint attr_index = 0; attr_index < (GLuint)_sg.limits.max_vertex_attrs; attr_index++) { +        _sg_gl_attr_t* attr = &pip->gl.attrs[attr_index]; +        _sg_gl_cache_attr_t* cache_attr = &_sg.gl.cache.attrs[attr_index]; +        bool cache_attr_dirty = false; +        int vb_offset = 0; +        GLuint gl_vb = 0; +        if (attr->vb_index >= 0) { +            /* attribute is enabled */ +            SOKOL_ASSERT(attr->vb_index < num_vbs); +            _sg_buffer_t* vb = vbs[attr->vb_index]; +            SOKOL_ASSERT(vb); +            gl_vb = vb->gl.buf[vb->cmn.active_slot]; +            vb_offset = vb_offsets[attr->vb_index] + attr->offset; +            if ((gl_vb != cache_attr->gl_vbuf) || +                (attr->size != cache_attr->gl_attr.size) || +                (attr->type != cache_attr->gl_attr.type) || +                (attr->normalized != cache_attr->gl_attr.normalized) || +                (attr->stride != cache_attr->gl_attr.stride) || +                (vb_offset != cache_attr->gl_attr.offset) || +                (cache_attr->gl_attr.divisor != attr->divisor)) +            { +                _sg_gl_cache_bind_buffer(GL_ARRAY_BUFFER, gl_vb); +                glVertexAttribPointer(attr_index, attr->size, attr->type, +                    attr->normalized, attr->stride, +                    (const GLvoid*)(GLintptr)vb_offset); +                #if defined(_SOKOL_GL_INSTANCING_ENABLED) +                    if (_sg.features.instancing) { +                        glVertexAttribDivisor(attr_index, (GLuint)attr->divisor); +                    } +                #endif +                cache_attr_dirty = true; +            } +            if (cache_attr->gl_attr.vb_index == -1) { +                glEnableVertexAttribArray(attr_index); +                cache_attr_dirty = true; +            } +        } +        else { +            /* attribute is disabled */ +            if (cache_attr->gl_attr.vb_index != -1) { +                glDisableVertexAttribArray(attr_index); +                cache_attr_dirty = true; +            } +        } +        if (cache_attr_dirty) { +            cache_attr->gl_attr = *attr; +            cache_attr->gl_attr.offset = vb_offset; +            cache_attr->gl_vbuf = gl_vb; +        } +    } +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    SOKOL_ASSERT(_sg.gl.cache.cur_pipeline); +    SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->slot.id == _sg.gl.cache.cur_pipeline_id.id); +    SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->shader->slot.id == _sg.gl.cache.cur_pipeline->cmn.shader_id.id); +    SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks > ub_index); +    SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size == data->size); +    const _sg_gl_shader_stage_t* gl_stage = &_sg.gl.cache.cur_pipeline->shader->gl.stage[stage_index]; +    const _sg_gl_uniform_block_t* gl_ub = &gl_stage->uniform_blocks[ub_index]; +    for (int u_index = 0; u_index < gl_ub->num_uniforms; u_index++) { +        const _sg_gl_uniform_t* u = &gl_ub->uniforms[u_index]; +        SOKOL_ASSERT(u->type != SG_UNIFORMTYPE_INVALID); +        if (u->gl_loc == -1) { +            continue; +        } +        GLfloat* ptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); +        switch (u->type) { +            case SG_UNIFORMTYPE_INVALID: +                break; +            case SG_UNIFORMTYPE_FLOAT: +                glUniform1fv(u->gl_loc, u->count, ptr); +                break; +            case SG_UNIFORMTYPE_FLOAT2: +                glUniform2fv(u->gl_loc, u->count, ptr); +                break; +            case SG_UNIFORMTYPE_FLOAT3: +                glUniform3fv(u->gl_loc, u->count, ptr); +                break; +            case SG_UNIFORMTYPE_FLOAT4: +                glUniform4fv(u->gl_loc, u->count, ptr); +                break; +            case SG_UNIFORMTYPE_MAT4: +                glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, ptr); +                break; +            default: +                SOKOL_UNREACHABLE; +                break; +        } +    } +} + +_SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_instances) { +    const GLenum i_type = _sg.gl.cache.cur_index_type; +    const GLenum p_type = _sg.gl.cache.cur_primitive_type; +    if (0 != i_type) { +        /* indexed rendering */ +        const int i_size = (i_type == GL_UNSIGNED_SHORT) ? 2 : 4; +        const int ib_offset = _sg.gl.cache.cur_ib_offset; +        const GLvoid* indices = (const GLvoid*)(GLintptr)(base_element*i_size+ib_offset); +        if (num_instances == 1) { +            glDrawElements(p_type, num_elements, i_type, indices); +        } +        else { +            if (_sg.features.instancing) { +                glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances); +            } +        } +    } +    else { +        /* non-indexed rendering */ +        if (num_instances == 1) { +            glDrawArrays(p_type, base_element, num_elements); +        } +        else { +            if (_sg.features.instancing) { +                glDrawArraysInstanced(p_type, base_element, num_elements, num_instances); +            } +        } +    } +} + +_SOKOL_PRIVATE void _sg_gl_commit(void) { +    SOKOL_ASSERT(!_sg.gl.in_pass); +    /* "soft" clear bindings (only those that are actually bound) */ +    _sg_gl_cache_clear_buffer_bindings(false); +    _sg_gl_cache_clear_texture_bindings(false); +} + +_SOKOL_PRIVATE void _sg_gl_update_buffer(_sg_buffer_t* buf, const sg_range* data) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    /* only one update per buffer per frame allowed */ +    if (++buf->cmn.active_slot >= buf->cmn.num_slots) { +        buf->cmn.active_slot = 0; +    } +    GLenum gl_tgt = _sg_gl_buffer_target(buf->cmn.type); +    SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); +    GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot]; +    SOKOL_ASSERT(gl_buf); +    _SG_GL_CHECK_ERROR(); +    _sg_gl_cache_store_buffer_binding(gl_tgt); +    _sg_gl_cache_bind_buffer(gl_tgt, gl_buf); +    glBufferSubData(gl_tgt, 0, (GLsizeiptr)data->size, data->ptr); +    _sg_gl_cache_restore_buffer_binding(gl_tgt); +    _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE int _sg_gl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    if (new_frame) { +        if (++buf->cmn.active_slot >= buf->cmn.num_slots) { +            buf->cmn.active_slot = 0; +        } +    } +    GLenum gl_tgt = _sg_gl_buffer_target(buf->cmn.type); +    SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); +    GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot]; +    SOKOL_ASSERT(gl_buf); +    _SG_GL_CHECK_ERROR(); +    _sg_gl_cache_store_buffer_binding(gl_tgt); +    _sg_gl_cache_bind_buffer(gl_tgt, gl_buf); +    glBufferSubData(gl_tgt, buf->cmn.append_pos, (GLsizeiptr)data->size, data->ptr); +    _sg_gl_cache_restore_buffer_binding(gl_tgt); +    _SG_GL_CHECK_ERROR(); +    /* NOTE: this is a requirement from WebGPU, but we want identical behaviour across all backend */ +    return _sg_roundup((int)data->size, 4); +} + +_SOKOL_PRIVATE void _sg_gl_update_image(_sg_image_t* img, const sg_image_data* data) { +    SOKOL_ASSERT(img && data); +    /* only one update per image per frame allowed */ +    if (++img->cmn.active_slot >= img->cmn.num_slots) { +        img->cmn.active_slot = 0; +    } +    SOKOL_ASSERT(img->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); +    SOKOL_ASSERT(0 != img->gl.tex[img->cmn.active_slot]); +    _sg_gl_cache_store_texture_binding(0); +    _sg_gl_cache_bind_texture(0, img->gl.target, img->gl.tex[img->cmn.active_slot]); +    const GLenum gl_img_format = _sg_gl_teximage_format(img->cmn.pixel_format); +    const GLenum gl_img_type = _sg_gl_teximage_type(img->cmn.pixel_format); +    const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1; +    const int num_mips = img->cmn.num_mipmaps; +    for (int face_index = 0; face_index < num_faces; face_index++) { +        for (int mip_index = 0; mip_index < num_mips; mip_index++) { +            GLenum gl_img_target = img->gl.target; +            if (SG_IMAGETYPE_CUBE == img->cmn.type) { +                gl_img_target = _sg_gl_cubeface_target(face_index); +            } +            const GLvoid* data_ptr = data->subimage[face_index][mip_index].ptr; +            int mip_width = img->cmn.width >> mip_index; +            if (mip_width == 0) { +                mip_width = 1; +            } +            int mip_height = img->cmn.height >> mip_index; +            if (mip_height == 0) { +                mip_height = 1; +            } +            if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { +                glTexSubImage2D(gl_img_target, mip_index, +                    0, 0, +                    mip_width, mip_height, +                    gl_img_format, gl_img_type, +                    data_ptr); +            } +            #if !defined(SOKOL_GLES2) +            else if (!_sg.gl.gles2 && ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type))) { +                int mip_depth = img->cmn.num_slices >> mip_index; +                if (mip_depth == 0) { +                    mip_depth = 1; +                } +                glTexSubImage3D(gl_img_target, mip_index, +                    0, 0, 0, +                    mip_width, mip_height, mip_depth, +                    gl_img_format, gl_img_type, +                    data_ptr); + +            } +            #endif +        } +    } +    _sg_gl_cache_restore_texture_binding(0); +} + +/*== D3D11 BACKEND IMPLEMENTATION ============================================*/ +#elif defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sg_d3d11_AddRef(self) (self)->AddRef() +#else +#define _sg_d3d11_AddRef(self) (self)->lpVtbl->AddRef(self) +#endif + +#if defined(__cplusplus) +#define _sg_d3d11_Release(self) (self)->Release() +#else +#define _sg_d3d11_Release(self) (self)->lpVtbl->Release(self) +#endif + +/*-- D3D11 C/C++ wrappers ----------------------------------------------------*/ +static inline HRESULT _sg_d3d11_CheckFormatSupport(ID3D11Device* self, DXGI_FORMAT Format, UINT* pFormatSupport) { +    #if defined(__cplusplus) +        return self->CheckFormatSupport(Format, pFormatSupport); +    #else +        return self->lpVtbl->CheckFormatSupport(self, Format, pFormatSupport); +    #endif +} + +static inline void _sg_d3d11_OMSetRenderTargets(ID3D11DeviceContext* self, UINT NumViews, ID3D11RenderTargetView* const* ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) { +    #if defined(__cplusplus) +        self->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); +    #else +        self->lpVtbl->OMSetRenderTargets(self, NumViews, ppRenderTargetViews, pDepthStencilView); +    #endif +} + +static inline void _sg_d3d11_RSSetState(ID3D11DeviceContext* self, ID3D11RasterizerState* pRasterizerState) { +    #if defined(__cplusplus) +        self->RSSetState(pRasterizerState); +    #else +        self->lpVtbl->RSSetState(self, pRasterizerState); +    #endif +} + +static inline void _sg_d3d11_OMSetDepthStencilState(ID3D11DeviceContext* self, ID3D11DepthStencilState* pDepthStencilState, UINT StencilRef) { +    #if defined(__cplusplus) +        self->OMSetDepthStencilState(pDepthStencilState, StencilRef); +    #else +        self->lpVtbl->OMSetDepthStencilState(self, pDepthStencilState, StencilRef); +    #endif +} + +static inline void _sg_d3d11_OMSetBlendState(ID3D11DeviceContext* self, ID3D11BlendState* pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) { +    #if defined(__cplusplus) +        self->OMSetBlendState(pBlendState, BlendFactor, SampleMask); +    #else +        self->lpVtbl->OMSetBlendState(self, pBlendState, BlendFactor, SampleMask); +    #endif +} + +static inline void _sg_d3d11_IASetVertexBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppVertexBuffers, const UINT* pStrides, const UINT* pOffsets) { +    #if defined(__cplusplus) +        self->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); +    #else +        self->lpVtbl->IASetVertexBuffers(self, StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); +    #endif +} + +static inline void _sg_d3d11_IASetIndexBuffer(ID3D11DeviceContext* self, ID3D11Buffer* pIndexBuffer, DXGI_FORMAT Format, UINT Offset) { +    #if defined(__cplusplus) +        self->IASetIndexBuffer(pIndexBuffer, Format, Offset); +    #else +        self->lpVtbl->IASetIndexBuffer(self, pIndexBuffer, Format, Offset); +    #endif +} + +static inline void _sg_d3d11_IASetInputLayout(ID3D11DeviceContext* self, ID3D11InputLayout* pInputLayout) { +    #if defined(__cplusplus) +        self->IASetInputLayout(pInputLayout); +    #else +        self->lpVtbl->IASetInputLayout(self, pInputLayout); +    #endif +} + +static inline void _sg_d3d11_VSSetShader(ID3D11DeviceContext* self, ID3D11VertexShader* pVertexShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { +    #if defined(__cplusplus) +        self->VSSetShader(pVertexShader, ppClassInstances, NumClassInstances); +    #else +        self->lpVtbl->VSSetShader(self, pVertexShader, ppClassInstances, NumClassInstances); +    #endif +} + +static inline void _sg_d3d11_PSSetShader(ID3D11DeviceContext* self, ID3D11PixelShader* pPixelShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { +    #if defined(__cplusplus) +        self->PSSetShader(pPixelShader, ppClassInstances, NumClassInstances); +    #else +        self->lpVtbl->PSSetShader(self, pPixelShader, ppClassInstances, NumClassInstances); +    #endif +} + +static inline void _sg_d3d11_VSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { +    #if defined(__cplusplus) +        self->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +    #else +        self->lpVtbl->VSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); +    #endif +} + +static inline void _sg_d3d11_PSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { +    #if defined(__cplusplus) +        self->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); +    #else +        self->lpVtbl->PSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); +    #endif +} + +static inline void _sg_d3d11_VSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { +    #if defined(__cplusplus) +        self->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +    #else +        self->lpVtbl->VSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); +    #endif +} + +static inline void _sg_d3d11_PSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { +    #if defined(__cplusplus) +        self->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); +    #else +        self->lpVtbl->PSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); +    #endif +} + +static inline void _sg_d3d11_VSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { +    #if defined(__cplusplus) +        self->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); +    #else +        self->lpVtbl->VSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); +    #endif +} + +static inline void _sg_d3d11_PSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { +    #if defined(__cplusplus) +        self->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); +    #else +        self->lpVtbl->PSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateBuffer(ID3D11Device* self, const D3D11_BUFFER_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Buffer** ppBuffer) { +    #if defined(__cplusplus) +        return self->CreateBuffer(pDesc, pInitialData, ppBuffer); +    #else +        return self->lpVtbl->CreateBuffer(self, pDesc, pInitialData, ppBuffer); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { +    #if defined(__cplusplus) +        return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); +    #else +        return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateShaderResourceView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC* pDesc, ID3D11ShaderResourceView** ppSRView) { +    #if defined(__cplusplus) +        return self->CreateShaderResourceView(pResource, pDesc, ppSRView); +    #else +        return self->lpVtbl->CreateShaderResourceView(self, pResource, pDesc, ppSRView); +    #endif +} + +static inline void _sg_d3d11_GetResource(ID3D11View* self, ID3D11Resource** ppResource) { +    #if defined(__cplusplus) +        self->GetResource(ppResource); +    #else +        self->lpVtbl->GetResource(self, ppResource); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateTexture3D(ID3D11Device* self, const D3D11_TEXTURE3D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture3D** ppTexture3D) { +    #if defined(__cplusplus) +        return self->CreateTexture3D(pDesc, pInitialData, ppTexture3D); +    #else +        return self->lpVtbl->CreateTexture3D(self, pDesc, pInitialData, ppTexture3D); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateSamplerState(ID3D11Device* self, const D3D11_SAMPLER_DESC* pSamplerDesc, ID3D11SamplerState** ppSamplerState) { +    #if defined(__cplusplus) +        return self->CreateSamplerState(pSamplerDesc, ppSamplerState); +    #else +        return self->lpVtbl->CreateSamplerState(self, pSamplerDesc, ppSamplerState); +    #endif +} + +static inline LPVOID _sg_d3d11_GetBufferPointer(ID3D10Blob* self) { +    #if defined(__cplusplus) +        return self->GetBufferPointer(); +    #else +        return self->lpVtbl->GetBufferPointer(self); +    #endif +} + +static inline SIZE_T _sg_d3d11_GetBufferSize(ID3D10Blob* self) { +    #if defined(__cplusplus) +        return self->GetBufferSize(); +    #else +        return self->lpVtbl->GetBufferSize(self); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateVertexShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11VertexShader** ppVertexShader) { +    #if defined(__cplusplus) +        return self->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); +    #else +        return self->lpVtbl->CreateVertexShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); +    #endif +} + +static inline HRESULT _sg_d3d11_CreatePixelShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11PixelShader** ppPixelShader) { +    #if defined(__cplusplus) +        return self->CreatePixelShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); +    #else +        return self->lpVtbl->CreatePixelShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateInputLayout(ID3D11Device* self, const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs, UINT NumElements, const void* pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) { +    #if defined(__cplusplus) +        return self->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); +    #else +        return self->lpVtbl->CreateInputLayout(self, pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateRasterizerState(ID3D11Device* self, const D3D11_RASTERIZER_DESC* pRasterizerDesc, ID3D11RasterizerState** ppRasterizerState) { +    #if defined(__cplusplus) +        return self->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); +    #else +        return self->lpVtbl->CreateRasterizerState(self, pRasterizerDesc, ppRasterizerState); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateDepthStencilState(ID3D11Device* self, const D3D11_DEPTH_STENCIL_DESC* pDepthStencilDesc, ID3D11DepthStencilState** ppDepthStencilState) { +    #if defined(__cplusplus) +        return self->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); +    #else +        return self->lpVtbl->CreateDepthStencilState(self, pDepthStencilDesc, ppDepthStencilState); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateBlendState(ID3D11Device* self, const D3D11_BLEND_DESC* pBlendStateDesc, ID3D11BlendState** ppBlendState) { +    #if defined(__cplusplus) +        return self->CreateBlendState(pBlendStateDesc, ppBlendState); +    #else +        return self->lpVtbl->CreateBlendState(self, pBlendStateDesc, ppBlendState); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { +    #if defined(__cplusplus) +        return self->CreateRenderTargetView(pResource, pDesc, ppRTView); +    #else +        return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); +    #endif +} + +static inline HRESULT _sg_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { +    #if defined(__cplusplus) +        return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); +    #else +        return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); +    #endif +} + +static inline void _sg_d3d11_RSSetViewports(ID3D11DeviceContext* self, UINT NumViewports, const D3D11_VIEWPORT* pViewports) { +    #if defined(__cplusplus) +        self->RSSetViewports(NumViewports, pViewports); +    #else +        self->lpVtbl->RSSetViewports(self, NumViewports, pViewports); +    #endif +} + +static inline void _sg_d3d11_RSSetScissorRects(ID3D11DeviceContext* self, UINT NumRects, const D3D11_RECT* pRects) { +    #if defined(__cplusplus) +        self->RSSetScissorRects(NumRects, pRects); +    #else +        self->lpVtbl->RSSetScissorRects(self, NumRects, pRects); +    #endif +} + +static inline void _sg_d3d11_ClearRenderTargetView(ID3D11DeviceContext* self, ID3D11RenderTargetView* pRenderTargetView, const FLOAT ColorRGBA[4]) { +    #if defined(__cplusplus) +        self->ClearRenderTargetView(pRenderTargetView, ColorRGBA); +    #else +        self->lpVtbl->ClearRenderTargetView(self, pRenderTargetView, ColorRGBA); +    #endif +} + +static inline void _sg_d3d11_ClearDepthStencilView(ID3D11DeviceContext* self, ID3D11DepthStencilView* pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) { +    #if defined(__cplusplus) +        self->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); +    #else +        self->lpVtbl->ClearDepthStencilView(self, pDepthStencilView, ClearFlags, Depth, Stencil); +    #endif +} + +static inline void _sg_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { +    #if defined(__cplusplus) +        self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); +    #else +        self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); +    #endif +} + +static inline void _sg_d3d11_IASetPrimitiveTopology(ID3D11DeviceContext* self, D3D11_PRIMITIVE_TOPOLOGY Topology) { +    #if defined(__cplusplus) +        self->IASetPrimitiveTopology(Topology); +    #else +        self->lpVtbl->IASetPrimitiveTopology(self, Topology); +    #endif +} + +static inline void _sg_d3d11_UpdateSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, const D3D11_BOX* pDstBox, const void* pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) { +    #if defined(__cplusplus) +        self->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); +    #else +        self->lpVtbl->UpdateSubresource(self, pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); +    #endif +} + +static inline void _sg_d3d11_DrawIndexed(ID3D11DeviceContext* self, UINT IndexCount, UINT StartIndexLocation, INT  BaseVertexLocation) { +    #if defined(__cplusplus) +        self->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); +    #else +        self->lpVtbl->DrawIndexed(self, IndexCount, StartIndexLocation, BaseVertexLocation); +    #endif +} + +static inline void _sg_d3d11_DrawIndexedInstanced(ID3D11DeviceContext* self, UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) { +    #if defined(__cplusplus) +        self->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); +    #else +        self->lpVtbl->DrawIndexedInstanced(self, IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); +    #endif +} + +static inline void _sg_d3d11_Draw(ID3D11DeviceContext* self, UINT VertexCount, UINT StartVertexLocation) { +    #if defined(__cplusplus) +        self->Draw(VertexCount, StartVertexLocation); +    #else +        self->lpVtbl->Draw(self, VertexCount, StartVertexLocation); +    #endif +} + +static inline void _sg_d3d11_DrawInstanced(ID3D11DeviceContext* self, UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) { +    #if defined(__cplusplus) +        self->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); +    #else +        self->lpVtbl->DrawInstanced(self, VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); +    #endif +} + +static inline HRESULT _sg_d3d11_Map(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE* pMappedResource) { +    #if defined(__cplusplus) +        return self->Map(pResource, Subresource, MapType, MapFlags, pMappedResource); +    #else +        return self->lpVtbl->Map(self, pResource, Subresource, MapType, MapFlags, pMappedResource); +    #endif +} + +static inline void _sg_d3d11_Unmap(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource) { +    #if defined(__cplusplus) +        self->Unmap(pResource, Subresource); +    #else +        self->lpVtbl->Unmap(self, pResource, Subresource); +    #endif +} + +/*-- enum translation functions ----------------------------------------------*/ +_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_usage(sg_usage usg) { +    switch (usg) { +        case SG_USAGE_IMMUTABLE: +            return D3D11_USAGE_IMMUTABLE; +        case SG_USAGE_DYNAMIC: +        case SG_USAGE_STREAM: +            return D3D11_USAGE_DYNAMIC; +        default: +            SOKOL_UNREACHABLE; +            return (D3D11_USAGE) 0; +    } +} + +_SOKOL_PRIVATE UINT _sg_d3d11_cpu_access_flags(sg_usage usg) { +    switch (usg) { +        case SG_USAGE_IMMUTABLE: +            return 0; +        case SG_USAGE_DYNAMIC: +        case SG_USAGE_STREAM: +            return D3D11_CPU_ACCESS_WRITE; +        default: +            SOKOL_UNREACHABLE; +            return 0; +    } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_pixel_format(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_R8:             return DXGI_FORMAT_R8_UNORM; +        case SG_PIXELFORMAT_R8SN:           return DXGI_FORMAT_R8_SNORM; +        case SG_PIXELFORMAT_R8UI:           return DXGI_FORMAT_R8_UINT; +        case SG_PIXELFORMAT_R8SI:           return DXGI_FORMAT_R8_SINT; +        case SG_PIXELFORMAT_R16:            return DXGI_FORMAT_R16_UNORM; +        case SG_PIXELFORMAT_R16SN:          return DXGI_FORMAT_R16_SNORM; +        case SG_PIXELFORMAT_R16UI:          return DXGI_FORMAT_R16_UINT; +        case SG_PIXELFORMAT_R16SI:          return DXGI_FORMAT_R16_SINT; +        case SG_PIXELFORMAT_R16F:           return DXGI_FORMAT_R16_FLOAT; +        case SG_PIXELFORMAT_RG8:            return DXGI_FORMAT_R8G8_UNORM; +        case SG_PIXELFORMAT_RG8SN:          return DXGI_FORMAT_R8G8_SNORM; +        case SG_PIXELFORMAT_RG8UI:          return DXGI_FORMAT_R8G8_UINT; +        case SG_PIXELFORMAT_RG8SI:          return DXGI_FORMAT_R8G8_SINT; +        case SG_PIXELFORMAT_R32UI:          return DXGI_FORMAT_R32_UINT; +        case SG_PIXELFORMAT_R32SI:          return DXGI_FORMAT_R32_SINT; +        case SG_PIXELFORMAT_R32F:           return DXGI_FORMAT_R32_FLOAT; +        case SG_PIXELFORMAT_RG16:           return DXGI_FORMAT_R16G16_UNORM; +        case SG_PIXELFORMAT_RG16SN:         return DXGI_FORMAT_R16G16_SNORM; +        case SG_PIXELFORMAT_RG16UI:         return DXGI_FORMAT_R16G16_UINT; +        case SG_PIXELFORMAT_RG16SI:         return DXGI_FORMAT_R16G16_SINT; +        case SG_PIXELFORMAT_RG16F:          return DXGI_FORMAT_R16G16_FLOAT; +        case SG_PIXELFORMAT_RGBA8:          return DXGI_FORMAT_R8G8B8A8_UNORM; +        case SG_PIXELFORMAT_RGBA8SN:        return DXGI_FORMAT_R8G8B8A8_SNORM; +        case SG_PIXELFORMAT_RGBA8UI:        return DXGI_FORMAT_R8G8B8A8_UINT; +        case SG_PIXELFORMAT_RGBA8SI:        return DXGI_FORMAT_R8G8B8A8_SINT; +        case SG_PIXELFORMAT_BGRA8:          return DXGI_FORMAT_B8G8R8A8_UNORM; +        case SG_PIXELFORMAT_RGB10A2:        return DXGI_FORMAT_R10G10B10A2_UNORM; +        case SG_PIXELFORMAT_RG11B10F:       return DXGI_FORMAT_R11G11B10_FLOAT; +        case SG_PIXELFORMAT_RG32UI:         return DXGI_FORMAT_R32G32_UINT; +        case SG_PIXELFORMAT_RG32SI:         return DXGI_FORMAT_R32G32_SINT; +        case SG_PIXELFORMAT_RG32F:          return DXGI_FORMAT_R32G32_FLOAT; +        case SG_PIXELFORMAT_RGBA16:         return DXGI_FORMAT_R16G16B16A16_UNORM; +        case SG_PIXELFORMAT_RGBA16SN:       return DXGI_FORMAT_R16G16B16A16_SNORM; +        case SG_PIXELFORMAT_RGBA16UI:       return DXGI_FORMAT_R16G16B16A16_UINT; +        case SG_PIXELFORMAT_RGBA16SI:       return DXGI_FORMAT_R16G16B16A16_SINT; +        case SG_PIXELFORMAT_RGBA16F:        return DXGI_FORMAT_R16G16B16A16_FLOAT; +        case SG_PIXELFORMAT_RGBA32UI:       return DXGI_FORMAT_R32G32B32A32_UINT; +        case SG_PIXELFORMAT_RGBA32SI:       return DXGI_FORMAT_R32G32B32A32_SINT; +        case SG_PIXELFORMAT_RGBA32F:        return DXGI_FORMAT_R32G32B32A32_FLOAT; +        case SG_PIXELFORMAT_DEPTH:          return DXGI_FORMAT_D32_FLOAT; +        case SG_PIXELFORMAT_DEPTH_STENCIL:  return DXGI_FORMAT_D24_UNORM_S8_UINT; +        case SG_PIXELFORMAT_BC1_RGBA:       return DXGI_FORMAT_BC1_UNORM; +        case SG_PIXELFORMAT_BC2_RGBA:       return DXGI_FORMAT_BC2_UNORM; +        case SG_PIXELFORMAT_BC3_RGBA:       return DXGI_FORMAT_BC3_UNORM; +        case SG_PIXELFORMAT_BC4_R:          return DXGI_FORMAT_BC4_UNORM; +        case SG_PIXELFORMAT_BC4_RSN:        return DXGI_FORMAT_BC4_SNORM; +        case SG_PIXELFORMAT_BC5_RG:         return DXGI_FORMAT_BC5_UNORM; +        case SG_PIXELFORMAT_BC5_RGSN:       return DXGI_FORMAT_BC5_SNORM; +        case SG_PIXELFORMAT_BC6H_RGBF:      return DXGI_FORMAT_BC6H_SF16; +        case SG_PIXELFORMAT_BC6H_RGBUF:     return DXGI_FORMAT_BC6H_UF16; +        case SG_PIXELFORMAT_BC7_RGBA:       return DXGI_FORMAT_BC7_UNORM; +        default:                            return DXGI_FORMAT_UNKNOWN; +    }; +} + +_SOKOL_PRIVATE D3D11_PRIMITIVE_TOPOLOGY _sg_d3d11_primitive_topology(sg_primitive_type prim_type) { +    switch (prim_type) { +        case SG_PRIMITIVETYPE_POINTS:           return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; +        case SG_PRIMITIVETYPE_LINES:            return D3D11_PRIMITIVE_TOPOLOGY_LINELIST; +        case SG_PRIMITIVETYPE_LINE_STRIP:       return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP; +        case SG_PRIMITIVETYPE_TRIANGLES:        return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; +        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; +        default: SOKOL_UNREACHABLE; return (D3D11_PRIMITIVE_TOPOLOGY) 0; +    } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_index_format(sg_index_type index_type) { +    switch (index_type) { +        case SG_INDEXTYPE_NONE:     return DXGI_FORMAT_UNKNOWN; +        case SG_INDEXTYPE_UINT16:   return DXGI_FORMAT_R16_UINT; +        case SG_INDEXTYPE_UINT32:   return DXGI_FORMAT_R32_UINT; +        default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; +    } +} + +_SOKOL_PRIVATE D3D11_FILTER _sg_d3d11_filter(sg_filter min_f, sg_filter mag_f, uint32_t max_anisotropy) { +    if (max_anisotropy > 1) { +        return D3D11_FILTER_ANISOTROPIC; +    } +    else if (mag_f == SG_FILTER_NEAREST) { +        switch (min_f) { +            case SG_FILTER_NEAREST: +            case SG_FILTER_NEAREST_MIPMAP_NEAREST: +                return D3D11_FILTER_MIN_MAG_MIP_POINT; +            case SG_FILTER_LINEAR: +            case SG_FILTER_LINEAR_MIPMAP_NEAREST: +                return D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT; +            case SG_FILTER_NEAREST_MIPMAP_LINEAR: +                return D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR; +            case SG_FILTER_LINEAR_MIPMAP_LINEAR: +                return D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR; +            default: +                SOKOL_UNREACHABLE; break; +        } +    } +    else if (mag_f == SG_FILTER_LINEAR) { +        switch (min_f) { +            case SG_FILTER_NEAREST: +            case SG_FILTER_NEAREST_MIPMAP_NEAREST: +                return D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT; +            case SG_FILTER_LINEAR: +            case SG_FILTER_LINEAR_MIPMAP_NEAREST: +                return D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; +            case SG_FILTER_NEAREST_MIPMAP_LINEAR: +                return D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR; +            case SG_FILTER_LINEAR_MIPMAP_LINEAR: +                return D3D11_FILTER_MIN_MAG_MIP_LINEAR; +            default: +                SOKOL_UNREACHABLE; break; +        } +    } +    /* invalid value for mag filter */ +    SOKOL_UNREACHABLE; +    return D3D11_FILTER_MIN_MAG_MIP_POINT; +} + +_SOKOL_PRIVATE D3D11_TEXTURE_ADDRESS_MODE _sg_d3d11_address_mode(sg_wrap m) { +    switch (m) { +        case SG_WRAP_REPEAT:            return D3D11_TEXTURE_ADDRESS_WRAP; +        case SG_WRAP_CLAMP_TO_EDGE:     return D3D11_TEXTURE_ADDRESS_CLAMP; +        case SG_WRAP_CLAMP_TO_BORDER:   return D3D11_TEXTURE_ADDRESS_BORDER; +        case SG_WRAP_MIRRORED_REPEAT:   return D3D11_TEXTURE_ADDRESS_MIRROR; +        default: SOKOL_UNREACHABLE; return (D3D11_TEXTURE_ADDRESS_MODE) 0; +    } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_vertex_format(sg_vertex_format fmt) { +    switch (fmt) { +        case SG_VERTEXFORMAT_FLOAT:     return DXGI_FORMAT_R32_FLOAT; +        case SG_VERTEXFORMAT_FLOAT2:    return DXGI_FORMAT_R32G32_FLOAT; +        case SG_VERTEXFORMAT_FLOAT3:    return DXGI_FORMAT_R32G32B32_FLOAT; +        case SG_VERTEXFORMAT_FLOAT4:    return DXGI_FORMAT_R32G32B32A32_FLOAT; +        case SG_VERTEXFORMAT_BYTE4:     return DXGI_FORMAT_R8G8B8A8_SINT; +        case SG_VERTEXFORMAT_BYTE4N:    return DXGI_FORMAT_R8G8B8A8_SNORM; +        case SG_VERTEXFORMAT_UBYTE4:    return DXGI_FORMAT_R8G8B8A8_UINT; +        case SG_VERTEXFORMAT_UBYTE4N:   return DXGI_FORMAT_R8G8B8A8_UNORM; +        case SG_VERTEXFORMAT_SHORT2:    return DXGI_FORMAT_R16G16_SINT; +        case SG_VERTEXFORMAT_SHORT2N:   return DXGI_FORMAT_R16G16_SNORM; +        case SG_VERTEXFORMAT_USHORT2N:  return DXGI_FORMAT_R16G16_UNORM; +        case SG_VERTEXFORMAT_SHORT4:    return DXGI_FORMAT_R16G16B16A16_SINT; +        case SG_VERTEXFORMAT_SHORT4N:   return DXGI_FORMAT_R16G16B16A16_SNORM; +        case SG_VERTEXFORMAT_USHORT4N:  return DXGI_FORMAT_R16G16B16A16_UNORM; +        case SG_VERTEXFORMAT_UINT10_N2: return DXGI_FORMAT_R10G10B10A2_UNORM; +        default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; +    } +} + +_SOKOL_PRIVATE D3D11_INPUT_CLASSIFICATION _sg_d3d11_input_classification(sg_vertex_step step) { +    switch (step) { +        case SG_VERTEXSTEP_PER_VERTEX:      return D3D11_INPUT_PER_VERTEX_DATA; +        case SG_VERTEXSTEP_PER_INSTANCE:    return D3D11_INPUT_PER_INSTANCE_DATA; +        default: SOKOL_UNREACHABLE; return (D3D11_INPUT_CLASSIFICATION) 0; +    } +} + +_SOKOL_PRIVATE D3D11_CULL_MODE _sg_d3d11_cull_mode(sg_cull_mode m) { +    switch (m) { +        case SG_CULLMODE_NONE:      return D3D11_CULL_NONE; +        case SG_CULLMODE_FRONT:     return D3D11_CULL_FRONT; +        case SG_CULLMODE_BACK:      return D3D11_CULL_BACK; +        default: SOKOL_UNREACHABLE; return (D3D11_CULL_MODE) 0; +    } +} + +_SOKOL_PRIVATE D3D11_COMPARISON_FUNC _sg_d3d11_compare_func(sg_compare_func f) { +    switch (f) { +        case SG_COMPAREFUNC_NEVER:          return D3D11_COMPARISON_NEVER; +        case SG_COMPAREFUNC_LESS:           return D3D11_COMPARISON_LESS; +        case SG_COMPAREFUNC_EQUAL:          return D3D11_COMPARISON_EQUAL; +        case SG_COMPAREFUNC_LESS_EQUAL:     return D3D11_COMPARISON_LESS_EQUAL; +        case SG_COMPAREFUNC_GREATER:        return D3D11_COMPARISON_GREATER; +        case SG_COMPAREFUNC_NOT_EQUAL:      return D3D11_COMPARISON_NOT_EQUAL; +        case SG_COMPAREFUNC_GREATER_EQUAL:  return D3D11_COMPARISON_GREATER_EQUAL; +        case SG_COMPAREFUNC_ALWAYS:         return D3D11_COMPARISON_ALWAYS; +        default: SOKOL_UNREACHABLE; return (D3D11_COMPARISON_FUNC) 0; +    } +} + +_SOKOL_PRIVATE D3D11_STENCIL_OP _sg_d3d11_stencil_op(sg_stencil_op op) { +    switch (op) { +        case SG_STENCILOP_KEEP:         return D3D11_STENCIL_OP_KEEP; +        case SG_STENCILOP_ZERO:         return D3D11_STENCIL_OP_ZERO; +        case SG_STENCILOP_REPLACE:      return D3D11_STENCIL_OP_REPLACE; +        case SG_STENCILOP_INCR_CLAMP:   return D3D11_STENCIL_OP_INCR_SAT; +        case SG_STENCILOP_DECR_CLAMP:   return D3D11_STENCIL_OP_DECR_SAT; +        case SG_STENCILOP_INVERT:       return D3D11_STENCIL_OP_INVERT; +        case SG_STENCILOP_INCR_WRAP:    return D3D11_STENCIL_OP_INCR; +        case SG_STENCILOP_DECR_WRAP:    return D3D11_STENCIL_OP_DECR; +        default: SOKOL_UNREACHABLE; return (D3D11_STENCIL_OP) 0; +    } +} + +_SOKOL_PRIVATE D3D11_BLEND _sg_d3d11_blend_factor(sg_blend_factor f) { +    switch (f) { +        case SG_BLENDFACTOR_ZERO:                   return D3D11_BLEND_ZERO; +        case SG_BLENDFACTOR_ONE:                    return D3D11_BLEND_ONE; +        case SG_BLENDFACTOR_SRC_COLOR:              return D3D11_BLEND_SRC_COLOR; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return D3D11_BLEND_INV_SRC_COLOR; +        case SG_BLENDFACTOR_SRC_ALPHA:              return D3D11_BLEND_SRC_ALPHA; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return D3D11_BLEND_INV_SRC_ALPHA; +        case SG_BLENDFACTOR_DST_COLOR:              return D3D11_BLEND_DEST_COLOR; +        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return D3D11_BLEND_INV_DEST_COLOR; +        case SG_BLENDFACTOR_DST_ALPHA:              return D3D11_BLEND_DEST_ALPHA; +        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return D3D11_BLEND_INV_DEST_ALPHA; +        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return D3D11_BLEND_SRC_ALPHA_SAT; +        case SG_BLENDFACTOR_BLEND_COLOR:            return D3D11_BLEND_BLEND_FACTOR; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return D3D11_BLEND_INV_BLEND_FACTOR; +        case SG_BLENDFACTOR_BLEND_ALPHA:            return D3D11_BLEND_BLEND_FACTOR; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return D3D11_BLEND_INV_BLEND_FACTOR; +        default: SOKOL_UNREACHABLE; return (D3D11_BLEND) 0; +    } +} + +_SOKOL_PRIVATE D3D11_BLEND_OP _sg_d3d11_blend_op(sg_blend_op op) { +    switch (op) { +        case SG_BLENDOP_ADD:                return D3D11_BLEND_OP_ADD; +        case SG_BLENDOP_SUBTRACT:           return D3D11_BLEND_OP_SUBTRACT; +        case SG_BLENDOP_REVERSE_SUBTRACT:   return D3D11_BLEND_OP_REV_SUBTRACT; +        default: SOKOL_UNREACHABLE; return (D3D11_BLEND_OP) 0; +    } +} + +_SOKOL_PRIVATE UINT8 _sg_d3d11_color_write_mask(sg_color_mask m) { +    UINT8 res = 0; +    if (m & SG_COLORMASK_R) { +        res |= D3D11_COLOR_WRITE_ENABLE_RED; +    } +    if (m & SG_COLORMASK_G) { +        res |= D3D11_COLOR_WRITE_ENABLE_GREEN; +    } +    if (m & SG_COLORMASK_B) { +        res |= D3D11_COLOR_WRITE_ENABLE_BLUE; +    } +    if (m & SG_COLORMASK_A) { +        res |= D3D11_COLOR_WRITE_ENABLE_ALPHA; +    } +    return res; +} + +/* see: https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits#resource-limits-for-feature-level-11-hardware */ +_SOKOL_PRIVATE void _sg_d3d11_init_caps(void) { +    _sg.backend = SG_BACKEND_D3D11; + +    _sg.features.instancing = true; +    _sg.features.origin_top_left = true; +    _sg.features.multiple_render_targets = true; +    _sg.features.msaa_render_targets = true; +    _sg.features.imagetype_3d = true; +    _sg.features.imagetype_array = true; +    _sg.features.image_clamp_to_border = true; +    _sg.features.mrt_independent_blend_state = true; +    _sg.features.mrt_independent_write_mask = true; + +    _sg.limits.max_image_size_2d = 16 * 1024; +    _sg.limits.max_image_size_cube = 16 * 1024; +    _sg.limits.max_image_size_3d = 2 * 1024; +    _sg.limits.max_image_size_array = 16 * 1024; +    _sg.limits.max_image_array_layers = 2 * 1024; +    _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + +    /* see: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_format_support */ +    for (int fmt = (SG_PIXELFORMAT_NONE+1); fmt < _SG_PIXELFORMAT_NUM; fmt++) { +        UINT dxgi_fmt_caps = 0; +        const DXGI_FORMAT dxgi_fmt = _sg_d3d11_pixel_format((sg_pixel_format)fmt); +        if (dxgi_fmt != DXGI_FORMAT_UNKNOWN) { +            HRESULT hr = _sg_d3d11_CheckFormatSupport(_sg.d3d11.dev, dxgi_fmt, &dxgi_fmt_caps); +            SOKOL_ASSERT(SUCCEEDED(hr) || (E_FAIL == hr)); +            if (!SUCCEEDED(hr)) { +                dxgi_fmt_caps = 0; +            } +        } +        sg_pixelformat_info* info = &_sg.formats[fmt]; +        info->sample = 0 != (dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_TEXTURE2D); +        info->filter = 0 != (dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_SHADER_SAMPLE); +        info->render = 0 != (dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_RENDER_TARGET); +        info->blend  = 0 != (dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE); +        info->msaa   = 0 != (dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET); +        info->depth  = 0 != (dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL); +        if (info->depth) { +            info->render = true; +        } +    } +} + +_SOKOL_PRIVATE void _sg_d3d11_setup_backend(const sg_desc* desc) { +    /* assume _sg.d3d11 already is zero-initialized */ +    SOKOL_ASSERT(desc); +    SOKOL_ASSERT(desc->context.d3d11.device); +    SOKOL_ASSERT(desc->context.d3d11.device_context); +    SOKOL_ASSERT(desc->context.d3d11.render_target_view_cb || desc->context.d3d11.render_target_view_userdata_cb); +    SOKOL_ASSERT(desc->context.d3d11.depth_stencil_view_cb || desc->context.d3d11.depth_stencil_view_userdata_cb); +    _sg.d3d11.valid = true; +    _sg.d3d11.dev = (ID3D11Device*) desc->context.d3d11.device; +    _sg.d3d11.ctx = (ID3D11DeviceContext*) desc->context.d3d11.device_context; +    _sg.d3d11.rtv_cb = desc->context.d3d11.render_target_view_cb; +    _sg.d3d11.rtv_userdata_cb = desc->context.d3d11.render_target_view_userdata_cb; +    _sg.d3d11.dsv_cb = desc->context.d3d11.depth_stencil_view_cb; +    _sg.d3d11.dsv_userdata_cb = desc->context.d3d11.depth_stencil_view_userdata_cb; +    _sg.d3d11.user_data = desc->context.d3d11.user_data; +    _sg_d3d11_init_caps(); +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_backend(void) { +    SOKOL_ASSERT(_sg.d3d11.valid); +    _sg.d3d11.valid = false; +} + +_SOKOL_PRIVATE void _sg_d3d11_clear_state(void) { +    /* clear all the device context state, so that resource refs don't keep stuck in the d3d device context */ +    _sg_d3d11_OMSetRenderTargets(_sg.d3d11.ctx, SG_MAX_COLOR_ATTACHMENTS, _sg.d3d11.zero_rtvs, NULL); +    _sg_d3d11_RSSetState(_sg.d3d11.ctx, NULL); +    _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, NULL, 0); +    _sg_d3d11_OMSetBlendState(_sg.d3d11.ctx, NULL, NULL, 0xFFFFFFFF); +    _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_BUFFERS, _sg.d3d11.zero_vbs, _sg.d3d11.zero_vb_strides, _sg.d3d11.zero_vb_offsets); +    _sg_d3d11_IASetIndexBuffer(_sg.d3d11.ctx, NULL, DXGI_FORMAT_UNKNOWN, 0); +    _sg_d3d11_IASetInputLayout(_sg.d3d11.ctx, NULL); +    _sg_d3d11_VSSetShader(_sg.d3d11.ctx, NULL, NULL, 0); +    _sg_d3d11_PSSetShader(_sg.d3d11.ctx, NULL, NULL, 0); +    _sg_d3d11_VSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, _sg.d3d11.zero_cbs); +    _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, _sg.d3d11.zero_cbs); +    _sg_d3d11_VSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_srvs); +    _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_srvs); +    _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_smps); +    _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, _sg.d3d11.zero_smps); +} + +_SOKOL_PRIVATE void _sg_d3d11_reset_state_cache(void) { +    /* just clear the d3d11 device context state */ +    _sg_d3d11_clear_state(); +} + +_SOKOL_PRIVATE void _sg_d3d11_activate_context(_sg_context_t* ctx) { +    _SOKOL_UNUSED(ctx); +    _sg_d3d11_clear_state(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_destroy_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +    /* empty */ +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { +    SOKOL_ASSERT(buf && desc); +    SOKOL_ASSERT(!buf->d3d11.buf); +    _sg_buffer_common_init(&buf->cmn, desc); +    const bool injected = (0 != desc->d3d11_buffer); +    if (injected) { +        buf->d3d11.buf = (ID3D11Buffer*) desc->d3d11_buffer; +        _sg_d3d11_AddRef(buf->d3d11.buf); +    } +    else { +        D3D11_BUFFER_DESC d3d11_desc; +        memset(&d3d11_desc, 0, sizeof(d3d11_desc)); +        d3d11_desc.ByteWidth = (UINT)buf->cmn.size; +        d3d11_desc.Usage = _sg_d3d11_usage(buf->cmn.usage); +        d3d11_desc.BindFlags = buf->cmn.type == SG_BUFFERTYPE_VERTEXBUFFER ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER; +        d3d11_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(buf->cmn.usage); +        D3D11_SUBRESOURCE_DATA* init_data_ptr = 0; +        D3D11_SUBRESOURCE_DATA init_data; +        memset(&init_data, 0, sizeof(init_data)); +        if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { +            SOKOL_ASSERT(desc->data.ptr); +            init_data.pSysMem = desc->data.ptr; +            init_data_ptr = &init_data; +        } +        HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_desc, init_data_ptr, &buf->d3d11.buf); +        _SOKOL_UNUSED(hr); +        SOKOL_ASSERT(SUCCEEDED(hr) && buf->d3d11.buf); +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_destroy_buffer(_sg_buffer_t* buf) { +    SOKOL_ASSERT(buf); +    if (buf->d3d11.buf) { +        _sg_d3d11_Release(buf->d3d11.buf); +    } +} + +_SOKOL_PRIVATE void _sg_d3d11_fill_subres_data(const _sg_image_t* img, const sg_image_data* data) { +    const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; +    const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; +    int subres_index = 0; +    for (int face_index = 0; face_index < num_faces; face_index++) { +        for (int slice_index = 0; slice_index < num_slices; slice_index++) { +            for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) { +                SOKOL_ASSERT(subres_index < (SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS)); +                D3D11_SUBRESOURCE_DATA* subres_data = &_sg.d3d11.subres_data[subres_index]; +                const int mip_width = ((img->cmn.width>>mip_index)>0) ? img->cmn.width>>mip_index : 1; +                const int mip_height = ((img->cmn.height>>mip_index)>0) ? img->cmn.height>>mip_index : 1; +                const sg_range* subimg_data = &(data->subimage[face_index][mip_index]); +                const size_t slice_size = subimg_data->size / (size_t)num_slices; +                const size_t slice_offset = slice_size * (size_t)slice_index; +                const uint8_t* ptr = (const uint8_t*) subimg_data->ptr; +                subres_data->pSysMem = ptr + slice_offset; +                subres_data->SysMemPitch = (UINT)_sg_row_pitch(img->cmn.pixel_format, mip_width, 1); +                if (img->cmn.type == SG_IMAGETYPE_3D) { +                    /* FIXME? const int mip_depth = ((img->depth>>mip_index)>0) ? img->depth>>mip_index : 1; */ +                    subres_data->SysMemSlicePitch = (UINT)_sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); +                } +                else { +                    subres_data->SysMemSlicePitch = 0; +                } +            } +        } +    } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const sg_image_desc* desc) { +    SOKOL_ASSERT(img && desc); +    SOKOL_ASSERT(!img->d3d11.tex2d && !img->d3d11.tex3d && !img->d3d11.texds && !img->d3d11.texmsaa); +    SOKOL_ASSERT(!img->d3d11.srv && !img->d3d11.smp); +    HRESULT hr; +    _SOKOL_UNUSED(hr); + +    _sg_image_common_init(&img->cmn, desc); +    const bool injected = (0 != desc->d3d11_texture) || (0 != desc->d3d11_shader_resource_view); +    const bool msaa = (img->cmn.sample_count > 1); +    img->d3d11.format = _sg_d3d11_pixel_format(img->cmn.pixel_format); + +    /* special case depth-stencil buffer? */ +    if (_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format)) { +        /* 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"); +            return SG_RESOURCESTATE_FAILED; +        } +        D3D11_TEXTURE2D_DESC d3d11_desc; +        memset(&d3d11_desc, 0, sizeof(d3d11_desc)); +        d3d11_desc.Width = (UINT)img->cmn.width; +        d3d11_desc.Height = (UINT)img->cmn.height; +        d3d11_desc.MipLevels = 1; +        d3d11_desc.ArraySize = 1; +        d3d11_desc.Format = img->d3d11.format; +        d3d11_desc.Usage = D3D11_USAGE_DEFAULT; +        d3d11_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; +        d3d11_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; +        d3d11_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); +        hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_desc, NULL, &img->d3d11.texds); +        SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.texds); +    } +    else { +        /* create (or inject) color texture and shader-resource-view */ + +        /* prepare initial content pointers */ +        D3D11_SUBRESOURCE_DATA* init_data = 0; +        if (!injected && (img->cmn.usage == SG_USAGE_IMMUTABLE) && !img->cmn.render_target) { +            _sg_d3d11_fill_subres_data(img, &desc->data); +            init_data = _sg.d3d11.subres_data; +        } +        if (img->cmn.type != SG_IMAGETYPE_3D) { +            /* 2D-, cube- or array-texture */ +            /* if this is an MSAA render target, the following texture will be the 'resolve-texture' */ + +            /* first check for injected texture and/or resource view */ +            if (injected) { +                img->d3d11.tex2d = (ID3D11Texture2D*) desc->d3d11_texture; +                img->d3d11.srv = (ID3D11ShaderResourceView*) desc->d3d11_shader_resource_view; +                if (img->d3d11.tex2d) { +                    _sg_d3d11_AddRef(img->d3d11.tex2d); +                } +                else { +                    /* if only a shader-resource-view was provided, but no texture, lookup +                       the texture from the shader-resource-view, this also bumps the refcount +                    */ +                    SOKOL_ASSERT(img->d3d11.srv); +                    _sg_d3d11_GetResource((ID3D11View*)img->d3d11.srv, (ID3D11Resource**)&img->d3d11.tex2d); +                    SOKOL_ASSERT(img->d3d11.tex2d); +                } +                if (img->d3d11.srv) { +                    _sg_d3d11_AddRef(img->d3d11.srv); +                } +            } + +            /* if not injected, create texture */ +            if (0 == img->d3d11.tex2d) { +                D3D11_TEXTURE2D_DESC d3d11_tex_desc; +                memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); +                d3d11_tex_desc.Width = (UINT)img->cmn.width; +                d3d11_tex_desc.Height = (UINT)img->cmn.height; +                d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; +                switch (img->cmn.type) { +                    case SG_IMAGETYPE_ARRAY:    d3d11_tex_desc.ArraySize = (UINT)img->cmn.num_slices; break; +                    case SG_IMAGETYPE_CUBE:     d3d11_tex_desc.ArraySize = 6; break; +                    default:                    d3d11_tex_desc.ArraySize = 1; break; +                } +                d3d11_tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; +                d3d11_tex_desc.Format = img->d3d11.format; +                if (img->cmn.render_target) { +                    d3d11_tex_desc.Usage = D3D11_USAGE_DEFAULT; +                    if (!msaa) { +                        d3d11_tex_desc.BindFlags |= D3D11_BIND_RENDER_TARGET; +                    } +                    d3d11_tex_desc.CPUAccessFlags = 0; +                } +                else { +                    d3d11_tex_desc.Usage = _sg_d3d11_usage(img->cmn.usage); +                    d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); +                } +                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"); +                    return SG_RESOURCESTATE_FAILED; +                } +                d3d11_tex_desc.SampleDesc.Count = 1; +                d3d11_tex_desc.SampleDesc.Quality = 0; +                d3d11_tex_desc.MiscFlags = (img->cmn.type == SG_IMAGETYPE_CUBE) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0; + +                hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d); +                SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.tex2d); +            } + +            /* ...and similar, if not injected, create shader-resource-view */ +            if (0 == img->d3d11.srv) { +                D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; +                memset(&d3d11_srv_desc, 0, sizeof(d3d11_srv_desc)); +                d3d11_srv_desc.Format = img->d3d11.format; +                switch (img->cmn.type) { +                    case SG_IMAGETYPE_2D: +                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; +                        d3d11_srv_desc.Texture2D.MipLevels = (UINT)img->cmn.num_mipmaps; +                        break; +                    case SG_IMAGETYPE_CUBE: +                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; +                        d3d11_srv_desc.TextureCube.MipLevels = (UINT)img->cmn.num_mipmaps; +                        break; +                    case SG_IMAGETYPE_ARRAY: +                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; +                        d3d11_srv_desc.Texture2DArray.MipLevels = (UINT)img->cmn.num_mipmaps; +                        d3d11_srv_desc.Texture2DArray.ArraySize = (UINT)img->cmn.num_slices; +                        break; +                    default: +                        SOKOL_UNREACHABLE; break; +                } +                hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex2d, &d3d11_srv_desc, &img->d3d11.srv); +                SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.srv); +            } +        } +        else { +            /* 3D texture - same procedure, first check if injected, than create non-injected */ +            if (injected) { +                img->d3d11.tex3d = (ID3D11Texture3D*) desc->d3d11_texture; +                img->d3d11.srv = (ID3D11ShaderResourceView*) desc->d3d11_shader_resource_view; +                if (img->d3d11.tex3d) { +                    _sg_d3d11_AddRef(img->d3d11.tex3d); +                } +                else { +                    SOKOL_ASSERT(img->d3d11.srv); +                    _sg_d3d11_GetResource((ID3D11View*)img->d3d11.srv, (ID3D11Resource**)&img->d3d11.tex3d); +                    SOKOL_ASSERT(img->d3d11.tex3d); +                } +                if (img->d3d11.srv) { +                    _sg_d3d11_AddRef(img->d3d11.srv); +                } +            } + +            if (0 == img->d3d11.tex3d) { +                D3D11_TEXTURE3D_DESC d3d11_tex_desc; +                memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); +                d3d11_tex_desc.Width = (UINT)img->cmn.width; +                d3d11_tex_desc.Height = (UINT)img->cmn.height; +                d3d11_tex_desc.Depth = (UINT)img->cmn.num_slices; +                d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; +                d3d11_tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; +                d3d11_tex_desc.Format = img->d3d11.format; +                if (img->cmn.render_target) { +                    d3d11_tex_desc.Usage = D3D11_USAGE_DEFAULT; +                    if (!msaa) { +                        d3d11_tex_desc.BindFlags |= D3D11_BIND_RENDER_TARGET; +                    } +                    d3d11_tex_desc.CPUAccessFlags = 0; +                } +                else { +                    d3d11_tex_desc.Usage = _sg_d3d11_usage(img->cmn.usage); +                    d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); +                } +                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"); +                    return SG_RESOURCESTATE_FAILED; +                } +                hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d); +                SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.tex3d); +            } + +            if (0 == img->d3d11.srv) { +                D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; +                memset(&d3d11_srv_desc, 0, sizeof(d3d11_srv_desc)); +                d3d11_srv_desc.Format = img->d3d11.format; +                d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; +                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); +                SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.srv); +            } +        } + +        /* also need to create a separate MSAA render target texture? */ +        if (msaa) { +            D3D11_TEXTURE2D_DESC d3d11_tex_desc; +            memset(&d3d11_tex_desc, 0, sizeof(d3d11_tex_desc)); +            d3d11_tex_desc.Width = (UINT)img->cmn.width; +            d3d11_tex_desc.Height = (UINT)img->cmn.height; +            d3d11_tex_desc.MipLevels = 1; +            d3d11_tex_desc.ArraySize = 1; +            d3d11_tex_desc.Format = img->d3d11.format; +            d3d11_tex_desc.Usage = D3D11_USAGE_DEFAULT; +            d3d11_tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; +            d3d11_tex_desc.CPUAccessFlags = 0; +            d3d11_tex_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; +            d3d11_tex_desc.SampleDesc.Quality = (UINT)D3D11_STANDARD_MULTISAMPLE_PATTERN; +            hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, NULL, &img->d3d11.texmsaa); +            SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.texmsaa); +        } + +        /* sampler state object, note D3D11 implements an internal shared-pool for sampler objects */ +        D3D11_SAMPLER_DESC d3d11_smp_desc; +        memset(&d3d11_smp_desc, 0, sizeof(d3d11_smp_desc)); +        d3d11_smp_desc.Filter = _sg_d3d11_filter(img->cmn.min_filter, img->cmn.mag_filter, img->cmn.max_anisotropy); +        d3d11_smp_desc.AddressU = _sg_d3d11_address_mode(img->cmn.wrap_u); +        d3d11_smp_desc.AddressV = _sg_d3d11_address_mode(img->cmn.wrap_v); +        d3d11_smp_desc.AddressW = _sg_d3d11_address_mode(img->cmn.wrap_w); +        switch (img->cmn.border_color) { +            case SG_BORDERCOLOR_TRANSPARENT_BLACK: +                /* all 0.0f */ +                break; +            case SG_BORDERCOLOR_OPAQUE_WHITE: +                for (int i = 0; i < 4; i++) { +                    d3d11_smp_desc.BorderColor[i] = 1.0f; +                } +                break; +            default: +                /* opaque black */ +                d3d11_smp_desc.BorderColor[3] = 1.0f; +                break; +        } +        d3d11_smp_desc.MaxAnisotropy = img->cmn.max_anisotropy; +        d3d11_smp_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; +        d3d11_smp_desc.MinLOD = desc->min_lod; +        d3d11_smp_desc.MaxLOD = desc->max_lod; +        hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &img->d3d11.smp); +        SOKOL_ASSERT(SUCCEEDED(hr) && img->d3d11.smp); +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_destroy_image(_sg_image_t* img) { +    SOKOL_ASSERT(img); +    if (img->d3d11.tex2d) { +        _sg_d3d11_Release(img->d3d11.tex2d); +    } +    if (img->d3d11.tex3d) { +        _sg_d3d11_Release(img->d3d11.tex3d); +    } +    if (img->d3d11.texds) { +        _sg_d3d11_Release(img->d3d11.texds); +    } +    if (img->d3d11.texmsaa) { +        _sg_d3d11_Release(img->d3d11.texmsaa); +    } +    if (img->d3d11.srv) { +        _sg_d3d11_Release(img->d3d11.srv); +    } +    if (img->d3d11.smp) { +        _sg_d3d11_Release(img->d3d11.smp); +    } +} + +_SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) { +    /* on UWP, don't do anything (not tested) */ +    #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) +        return true; +    #else +        /* load DLL on demand */ +        if ((0 == _sg.d3d11.d3dcompiler_dll) && !_sg.d3d11.d3dcompiler_dll_load_failed) { +            _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.d3d11.d3dcompiler_dll_load_failed = true; +                return false; +            } +            /* look up function pointers */ +            _sg.d3d11.D3DCompile_func = (pD3DCompile)(void*) GetProcAddress(_sg.d3d11.d3dcompiler_dll, "D3DCompile"); +            SOKOL_ASSERT(_sg.d3d11.D3DCompile_func); +        } +        return 0 != _sg.d3d11.d3dcompiler_dll; +    #endif +} + +#if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) +#define _sg_d3d11_D3DCompile D3DCompile +#else +#define _sg_d3d11_D3DCompile _sg.d3d11.D3DCompile_func +#endif + +_SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_stage_desc* stage_desc) { +    if (!_sg_d3d11_load_d3dcompiler_dll()) { +        return NULL; +    } +    SOKOL_ASSERT(stage_desc->d3d11_target); +    ID3DBlob* output = NULL; +    ID3DBlob* errors_or_warnings = NULL; +    HRESULT hr = _sg_d3d11_D3DCompile( +        stage_desc->source,             /* pSrcData */ +        strlen(stage_desc->source),     /* SrcDataSize */ +        NULL,                           /* pSourceName */ +        NULL,                           /* pDefines */ +        NULL,                           /* pInclude */ +        stage_desc->entry ? stage_desc->entry : "main",     /* pEntryPoint */ +        stage_desc->d3d11_target,       /* pTarget (vs_5_0 or ps_5_0) */ +        D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_OPTIMIZATION_LEVEL3,   /* Flags1 */ +        0,          /* Flags2 */ +        &output,    /* ppCode */ +        &errors_or_warnings);   /* ppErrorMsgs */ +    if (errors_or_warnings) { +        SOKOL_LOG((LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); +        _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL; +    } +    if (FAILED(hr)) { +        /* just in case, usually output is NULL here */ +        if (output) { +            _sg_d3d11_Release(output); +            output = NULL; +        } +    } +    return output; +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { +    SOKOL_ASSERT(shd && desc); +    SOKOL_ASSERT(!shd->d3d11.vs && !shd->d3d11.fs && !shd->d3d11.vs_blob); +    HRESULT hr; +    _SOKOL_UNUSED(hr); + +    _sg_shader_common_init(&shd->cmn, desc); + +    /* copy vertex attribute semantic names and indices */ +    for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { +        _sg_strcpy(&shd->d3d11.attrs[i].sem_name, desc->attrs[i].sem_name); +        shd->d3d11.attrs[i].sem_index = desc->attrs[i].sem_index; +    } + +    /* shader stage uniform blocks and image slots */ +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; +        _sg_d3d11_shader_stage_t* d3d11_stage = &shd->d3d11.stage[stage_index]; +        for (int ub_index = 0; ub_index < cmn_stage->num_uniform_blocks; ub_index++) { +            const _sg_uniform_block_t* ub = &cmn_stage->uniform_blocks[ub_index]; + +            /* create a D3D constant buffer for each uniform block */ +            SOKOL_ASSERT(0 == d3d11_stage->cbufs[ub_index]); +            D3D11_BUFFER_DESC cb_desc; +            memset(&cb_desc, 0, sizeof(cb_desc)); +            cb_desc.ByteWidth = (UINT)_sg_roundup((int)ub->size, 16); +            cb_desc.Usage = D3D11_USAGE_DEFAULT; +            cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; +            hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &d3d11_stage->cbufs[ub_index]); +            SOKOL_ASSERT(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index]); +        } +    } + +    const void* vs_ptr = 0, *fs_ptr = 0; +    SIZE_T vs_length = 0, fs_length = 0; +    ID3DBlob* vs_blob = 0, *fs_blob = 0; +    if (desc->vs.bytecode.ptr && desc->fs.bytecode.ptr) { +        /* create from shader byte code */ +        vs_ptr = desc->vs.bytecode.ptr; +        fs_ptr = desc->fs.bytecode.ptr; +        vs_length = desc->vs.bytecode.size; +        fs_length = desc->fs.bytecode.size; +    } +    else { +        /* compile from shader source code */ +        vs_blob = _sg_d3d11_compile_shader(&desc->vs); +        fs_blob = _sg_d3d11_compile_shader(&desc->fs); +        if (vs_blob && fs_blob) { +            vs_ptr = _sg_d3d11_GetBufferPointer(vs_blob); +            vs_length = _sg_d3d11_GetBufferSize(vs_blob); +            fs_ptr = _sg_d3d11_GetBufferPointer(fs_blob); +            fs_length = _sg_d3d11_GetBufferSize(fs_blob); +        } +    } +    sg_resource_state result = SG_RESOURCESTATE_FAILED; +    if (vs_ptr && fs_ptr && (vs_length > 0) && (fs_length > 0)) { +        /* create the D3D vertex- and pixel-shader objects */ +        hr = _sg_d3d11_CreateVertexShader(_sg.d3d11.dev, vs_ptr, vs_length, NULL, &shd->d3d11.vs); +        bool vs_succeeded = SUCCEEDED(hr) && shd->d3d11.vs; +        hr = _sg_d3d11_CreatePixelShader(_sg.d3d11.dev, fs_ptr, fs_length, NULL, &shd->d3d11.fs); +        bool fs_succeeded = SUCCEEDED(hr) && shd->d3d11.fs; + +        /* need to store the vertex shader byte code, this is needed later in sg_create_pipeline */ +        if (vs_succeeded && fs_succeeded) { +            shd->d3d11.vs_blob_length = vs_length; +            shd->d3d11.vs_blob = SOKOL_MALLOC((size_t)vs_length); +            SOKOL_ASSERT(shd->d3d11.vs_blob); +            memcpy(shd->d3d11.vs_blob, vs_ptr, vs_length); +            result = SG_RESOURCESTATE_VALID; +        } +    } +    if (vs_blob) { +        _sg_d3d11_Release(vs_blob); vs_blob = 0; +    } +    if (fs_blob) { +        _sg_d3d11_Release(fs_blob); fs_blob = 0; +    } +    return result; +} + +_SOKOL_PRIVATE void _sg_d3d11_destroy_shader(_sg_shader_t* shd) { +    SOKOL_ASSERT(shd); +    if (shd->d3d11.vs) { +        _sg_d3d11_Release(shd->d3d11.vs); +    } +    if (shd->d3d11.fs) { +        _sg_d3d11_Release(shd->d3d11.fs); +    } +    if (shd->d3d11.vs_blob) { +        SOKOL_FREE(shd->d3d11.vs_blob); +    } +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; +        _sg_d3d11_shader_stage_t* d3d11_stage = &shd->d3d11.stage[stage_index]; +        for (int ub_index = 0; ub_index < cmn_stage->num_uniform_blocks; ub_index++) { +            if (d3d11_stage->cbufs[ub_index]) { +                _sg_d3d11_Release(d3d11_stage->cbufs[ub_index]); +            } +        } +    } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(pip && shd && desc); +    SOKOL_ASSERT(desc->shader.id == shd->slot.id); +    SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_VALID); +    SOKOL_ASSERT(shd->d3d11.vs_blob && shd->d3d11.vs_blob_length > 0); +    SOKOL_ASSERT(!pip->d3d11.il && !pip->d3d11.rs && !pip->d3d11.dss && !pip->d3d11.bs); + +    pip->shader = shd; +    _sg_pipeline_common_init(&pip->cmn, desc); +    pip->d3d11.index_format = _sg_d3d11_index_format(pip->cmn.index_type); +    pip->d3d11.topology = _sg_d3d11_primitive_topology(desc->primitive_type); +    pip->d3d11.stencil_ref = desc->stencil.ref; + +    /* create input layout object */ +    HRESULT hr; +    _SOKOL_UNUSED(hr); +    D3D11_INPUT_ELEMENT_DESC d3d11_comps[SG_MAX_VERTEX_ATTRIBUTES]; +    memset(d3d11_comps, 0, sizeof(d3d11_comps)); +    int attr_index = 0; +    for (; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +        if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +            break; +        } +        SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +        const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[a_desc->buffer_index]; +        const sg_vertex_step step_func = l_desc->step_func; +        const int step_rate = l_desc->step_rate; +        D3D11_INPUT_ELEMENT_DESC* d3d11_comp = &d3d11_comps[attr_index]; +        d3d11_comp->SemanticName = _sg_strptr(&shd->d3d11.attrs[attr_index].sem_name); +        d3d11_comp->SemanticIndex = (UINT)shd->d3d11.attrs[attr_index].sem_index; +        d3d11_comp->Format = _sg_d3d11_vertex_format(a_desc->format); +        d3d11_comp->InputSlot = (UINT)a_desc->buffer_index; +        d3d11_comp->AlignedByteOffset = (UINT)a_desc->offset; +        d3d11_comp->InputSlotClass = _sg_d3d11_input_classification(step_func); +        if (SG_VERTEXSTEP_PER_INSTANCE == step_func) { +            d3d11_comp->InstanceDataStepRate = (UINT)step_rate; +        } +        pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; +    } +    for (int layout_index = 0; layout_index < SG_MAX_SHADERSTAGE_BUFFERS; layout_index++) { +        if (pip->cmn.vertex_layout_valid[layout_index]) { +            const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[layout_index]; +            SOKOL_ASSERT(l_desc->stride > 0); +            pip->d3d11.vb_strides[layout_index] = (UINT)l_desc->stride; +        } +        else { +            pip->d3d11.vb_strides[layout_index] = 0; +        } +    } +    hr = _sg_d3d11_CreateInputLayout(_sg.d3d11.dev, +        d3d11_comps,                /* pInputElementDesc */ +        (UINT)attr_index,           /* NumElements */ +        shd->d3d11.vs_blob,         /* pShaderByteCodeWithInputSignature */ +        shd->d3d11.vs_blob_length,  /* BytecodeLength */ +        &pip->d3d11.il); +    SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.il); + +    /* create rasterizer state */ +    D3D11_RASTERIZER_DESC rs_desc; +    memset(&rs_desc, 0, sizeof(rs_desc)); +    rs_desc.FillMode = D3D11_FILL_SOLID; +    rs_desc.CullMode = _sg_d3d11_cull_mode(desc->cull_mode); +    rs_desc.FrontCounterClockwise = desc->face_winding == SG_FACEWINDING_CCW; +    rs_desc.DepthBias = (INT) pip->cmn.depth_bias; +    rs_desc.DepthBiasClamp = pip->cmn.depth_bias_clamp; +    rs_desc.SlopeScaledDepthBias = pip->cmn.depth_bias_slope_scale; +    rs_desc.DepthClipEnable = TRUE; +    rs_desc.ScissorEnable = TRUE; +    rs_desc.MultisampleEnable = desc->sample_count > 1; +    rs_desc.AntialiasedLineEnable = FALSE; +    hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs); +    SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.rs); + +    /* create depth-stencil state */ +    D3D11_DEPTH_STENCIL_DESC dss_desc; +    memset(&dss_desc, 0, sizeof(dss_desc)); +    dss_desc.DepthEnable = TRUE; +    dss_desc.DepthWriteMask = desc->depth.write_enabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; +    dss_desc.DepthFunc = _sg_d3d11_compare_func(desc->depth.compare); +    dss_desc.StencilEnable = desc->stencil.enabled; +    dss_desc.StencilReadMask = desc->stencil.read_mask; +    dss_desc.StencilWriteMask = desc->stencil.write_mask; +    const sg_stencil_face_state* sf = &desc->stencil.front; +    dss_desc.FrontFace.StencilFailOp = _sg_d3d11_stencil_op(sf->fail_op); +    dss_desc.FrontFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sf->depth_fail_op); +    dss_desc.FrontFace.StencilPassOp = _sg_d3d11_stencil_op(sf->pass_op); +    dss_desc.FrontFace.StencilFunc = _sg_d3d11_compare_func(sf->compare); +    const sg_stencil_face_state* sb = &desc->stencil.back; +    dss_desc.BackFace.StencilFailOp = _sg_d3d11_stencil_op(sb->fail_op); +    dss_desc.BackFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sb->depth_fail_op); +    dss_desc.BackFace.StencilPassOp = _sg_d3d11_stencil_op(sb->pass_op); +    dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare); +    hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss); +    SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.dss); + +    /* create blend state */ +    D3D11_BLEND_DESC bs_desc; +    memset(&bs_desc, 0, sizeof(bs_desc)); +    bs_desc.AlphaToCoverageEnable = desc->alpha_to_coverage_enabled; +    bs_desc.IndependentBlendEnable = TRUE; +    { +        int i = 0; +        for (i = 0; i < desc->color_count; i++) { +            const sg_blend_state* src = &desc->colors[i].blend; +            D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i]; +            dst->BlendEnable = src->enabled; +            dst->SrcBlend = _sg_d3d11_blend_factor(src->src_factor_rgb); +            dst->DestBlend = _sg_d3d11_blend_factor(src->dst_factor_rgb); +            dst->BlendOp = _sg_d3d11_blend_op(src->op_rgb); +            dst->SrcBlendAlpha = _sg_d3d11_blend_factor(src->src_factor_alpha); +            dst->DestBlendAlpha = _sg_d3d11_blend_factor(src->dst_factor_alpha); +            dst->BlendOpAlpha = _sg_d3d11_blend_op(src->op_alpha); +            dst->RenderTargetWriteMask = _sg_d3d11_color_write_mask(desc->colors[i].write_mask); +        } +        for (; i < 8; i++) { +            D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i]; +            dst->BlendEnable = FALSE; +            dst->SrcBlend = dst->SrcBlendAlpha = D3D11_BLEND_ONE; +            dst->DestBlend = dst->DestBlendAlpha = D3D11_BLEND_ZERO; +            dst->BlendOp = dst->BlendOpAlpha = D3D11_BLEND_OP_ADD; +            dst->RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; +        } +    } +    hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs); +    SOKOL_ASSERT(SUCCEEDED(hr) && pip->d3d11.bs); + +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_destroy_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    if (pip->d3d11.il) { +        _sg_d3d11_Release(pip->d3d11.il); +    } +    if (pip->d3d11.rs) { +        _sg_d3d11_Release(pip->d3d11.rs); +    } +    if (pip->d3d11.dss) { +        _sg_d3d11_Release(pip->d3d11.dss); +    } +    if (pip->d3d11.bs) { +        _sg_d3d11_Release(pip->d3d11.bs); +    } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pass(_sg_pass_t* pass, _sg_image_t** att_images, const sg_pass_desc* desc) { +    SOKOL_ASSERT(pass && desc); +    SOKOL_ASSERT(att_images && att_images[0]); +    SOKOL_ASSERT(_sg.d3d11.dev); + +    _sg_pass_common_init(&pass->cmn, desc); + +    for (int i = 0; i < pass->cmn.num_color_atts; i++) { +        const sg_pass_attachment_desc* att_desc = &desc->color_attachments[i]; +        _SOKOL_UNUSED(att_desc); +        SOKOL_ASSERT(att_desc->image.id != SG_INVALID_ID); +        _sg_image_t* att_img = att_images[i]; +        SOKOL_ASSERT(att_img && (att_img->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(att_img->cmn.pixel_format)); +        SOKOL_ASSERT(0 == pass->d3d11.color_atts[i].image); +        pass->d3d11.color_atts[i].image = att_img; + +        /* create D3D11 render-target-view */ +        const _sg_pass_attachment_t* cmn_att = &pass->cmn.color_atts[i]; +        SOKOL_ASSERT(0 == pass->d3d11.color_atts[i].rtv); +        ID3D11Resource* d3d11_res = 0; +        const bool is_msaa = att_img->cmn.sample_count > 1; +        D3D11_RENDER_TARGET_VIEW_DESC d3d11_rtv_desc; +        memset(&d3d11_rtv_desc, 0, sizeof(d3d11_rtv_desc)); +        d3d11_rtv_desc.Format = att_img->d3d11.format; +        if ((att_img->cmn.type == SG_IMAGETYPE_2D) || is_msaa) { +            if (is_msaa) { +                d3d11_res = (ID3D11Resource*) att_img->d3d11.texmsaa; +                d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS; +            } +            else { +                d3d11_res = (ID3D11Resource*) att_img->d3d11.tex2d; +                d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; +                d3d11_rtv_desc.Texture2D.MipSlice = (UINT)cmn_att->mip_level; +            } +        } +        else if ((att_img->cmn.type == SG_IMAGETYPE_CUBE) || (att_img->cmn.type == SG_IMAGETYPE_ARRAY)) { +            d3d11_res = (ID3D11Resource*) att_img->d3d11.tex2d; +            d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; +            d3d11_rtv_desc.Texture2DArray.MipSlice = (UINT)cmn_att->mip_level; +            d3d11_rtv_desc.Texture2DArray.FirstArraySlice = (UINT)cmn_att->slice; +            d3d11_rtv_desc.Texture2DArray.ArraySize = 1; +        } +        else { +            SOKOL_ASSERT(att_img->cmn.type == SG_IMAGETYPE_3D); +            d3d11_res = (ID3D11Resource*) att_img->d3d11.tex3d; +            d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D; +            d3d11_rtv_desc.Texture3D.MipSlice = (UINT)cmn_att->mip_level; +            d3d11_rtv_desc.Texture3D.FirstWSlice = (UINT)cmn_att->slice; +            d3d11_rtv_desc.Texture3D.WSize = 1; +        } +        SOKOL_ASSERT(d3d11_res); +        HRESULT hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, d3d11_res, &d3d11_rtv_desc, &pass->d3d11.color_atts[i].rtv); +        _SOKOL_UNUSED(hr); +        SOKOL_ASSERT(SUCCEEDED(hr) && pass->d3d11.color_atts[i].rtv); +    } + +    /* optional depth-stencil image */ +    SOKOL_ASSERT(0 == pass->d3d11.ds_att.image); +    SOKOL_ASSERT(0 == pass->d3d11.ds_att.dsv); +    if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) { +        const int ds_img_index = SG_MAX_COLOR_ATTACHMENTS; +        const sg_pass_attachment_desc* att_desc = &desc->depth_stencil_attachment; +        _SOKOL_UNUSED(att_desc); +        _sg_image_t* att_img = att_images[ds_img_index]; +        SOKOL_ASSERT(att_img && (att_img->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(att_img->cmn.pixel_format)); +        SOKOL_ASSERT(0 == pass->d3d11.ds_att.image); +        pass->d3d11.ds_att.image = att_img; + +        /* create D3D11 depth-stencil-view */ +        D3D11_DEPTH_STENCIL_VIEW_DESC d3d11_dsv_desc; +        memset(&d3d11_dsv_desc, 0, sizeof(d3d11_dsv_desc)); +        d3d11_dsv_desc.Format = att_img->d3d11.format; +        const bool is_msaa = att_img->cmn.sample_count > 1; +        if (is_msaa) { +            d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS; +        } +        else { +            d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; +        } +        ID3D11Resource* d3d11_res = (ID3D11Resource*) att_img->d3d11.texds; +        SOKOL_ASSERT(d3d11_res); +        HRESULT hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, d3d11_res, &d3d11_dsv_desc, &pass->d3d11.ds_att.dsv); +        _SOKOL_UNUSED(hr); +        SOKOL_ASSERT(SUCCEEDED(hr) && pass->d3d11.ds_att.dsv); +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_destroy_pass(_sg_pass_t* pass) { +    SOKOL_ASSERT(pass); +    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +        if (pass->d3d11.color_atts[i].rtv) { +            _sg_d3d11_Release(pass->d3d11.color_atts[i].rtv); +        } +    } +    if (pass->d3d11.ds_att.dsv) { +        _sg_d3d11_Release(pass->d3d11.ds_att.dsv); +    } +} + +_SOKOL_PRIVATE _sg_image_t* _sg_d3d11_pass_color_image(const _sg_pass_t* pass, int index) { +    SOKOL_ASSERT(pass && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); +    /* NOTE: may return null */ +    return pass->d3d11.color_atts[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_d3d11_pass_ds_image(const _sg_pass_t* pass) { +    /* NOTE: may return null */ +    SOKOL_ASSERT(pass); +    return pass->d3d11.ds_att.image; +} + +_SOKOL_PRIVATE void _sg_d3d11_begin_pass(_sg_pass_t* pass, const sg_pass_action* action, int w, int h) { +    SOKOL_ASSERT(action); +    SOKOL_ASSERT(!_sg.d3d11.in_pass); +    SOKOL_ASSERT(_sg.d3d11.rtv_cb || _sg.d3d11.rtv_userdata_cb); +    SOKOL_ASSERT(_sg.d3d11.dsv_cb || _sg.d3d11.dsv_userdata_cb); +    _sg.d3d11.in_pass = true; +    _sg.d3d11.cur_width = w; +    _sg.d3d11.cur_height = h; +    if (pass) { +        _sg.d3d11.cur_pass = pass; +        _sg.d3d11.cur_pass_id.id = pass->slot.id; +        _sg.d3d11.num_rtvs = 0; +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            _sg.d3d11.cur_rtvs[i] = pass->d3d11.color_atts[i].rtv; +            if (_sg.d3d11.cur_rtvs[i]) { +                _sg.d3d11.num_rtvs++; +            } +        } +        _sg.d3d11.cur_dsv = pass->d3d11.ds_att.dsv; +    } +    else { +        /* render to default frame buffer */ +        _sg.d3d11.cur_pass = 0; +        _sg.d3d11.cur_pass_id.id = SG_INVALID_ID; +        _sg.d3d11.num_rtvs = 1; +        if (_sg.d3d11.rtv_cb) { +            _sg.d3d11.cur_rtvs[0] = (ID3D11RenderTargetView*) _sg.d3d11.rtv_cb(); +        } +        else { +            _sg.d3d11.cur_rtvs[0] = (ID3D11RenderTargetView*) _sg.d3d11.rtv_userdata_cb(_sg.d3d11.user_data); +        } +        for (int i = 1; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            _sg.d3d11.cur_rtvs[i] = 0; +        } +        if (_sg.d3d11.dsv_cb) { +            _sg.d3d11.cur_dsv = (ID3D11DepthStencilView*) _sg.d3d11.dsv_cb(); +        } +        else { +            _sg.d3d11.cur_dsv = (ID3D11DepthStencilView*) _sg.d3d11.dsv_userdata_cb(_sg.d3d11.user_data); +        } +        SOKOL_ASSERT(_sg.d3d11.cur_rtvs[0] && _sg.d3d11.cur_dsv); +    } +    /* apply the render-target- and depth-stencil-views */ +    _sg_d3d11_OMSetRenderTargets(_sg.d3d11.ctx, SG_MAX_COLOR_ATTACHMENTS, _sg.d3d11.cur_rtvs, _sg.d3d11.cur_dsv); + +    /* set viewport and scissor rect to cover whole screen */ +    D3D11_VIEWPORT vp; +    memset(&vp, 0, sizeof(vp)); +    vp.Width = (FLOAT) w; +    vp.Height = (FLOAT) h; +    vp.MaxDepth = 1.0f; +    _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp); +    D3D11_RECT rect; +    rect.left = 0; +    rect.top = 0; +    rect.right = w; +    rect.bottom = h; +    _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); + +    /* perform clear action */ +    for (int i = 0; i < _sg.d3d11.num_rtvs; i++) { +        if (action->colors[i].action == SG_ACTION_CLEAR) { +            _sg_d3d11_ClearRenderTargetView(_sg.d3d11.ctx, _sg.d3d11.cur_rtvs[i], &action->colors[i].value.r); +        } +    } +    UINT ds_flags = 0; +    if (action->depth.action == SG_ACTION_CLEAR) { +        ds_flags |= D3D11_CLEAR_DEPTH; +    } +    if (action->stencil.action == SG_ACTION_CLEAR) { +        ds_flags |= D3D11_CLEAR_STENCIL; +    } +    if ((0 != ds_flags) && _sg.d3d11.cur_dsv) { +        _sg_d3d11_ClearDepthStencilView(_sg.d3d11.ctx, _sg.d3d11.cur_dsv, ds_flags, action->depth.value, action->stencil.value); +    } +} + +/* D3D11CalcSubresource only exists for C++ */ +_SOKOL_PRIVATE UINT _sg_d3d11_calcsubresource(UINT mip_slice, UINT array_slice, UINT mip_levels) { +    return mip_slice + array_slice * mip_levels; +} + +_SOKOL_PRIVATE void _sg_d3d11_end_pass(void) { +    SOKOL_ASSERT(_sg.d3d11.in_pass && _sg.d3d11.ctx); +    _sg.d3d11.in_pass = false; + +    /* need to resolve MSAA render target into texture? */ +    if (_sg.d3d11.cur_pass) { +        SOKOL_ASSERT(_sg.d3d11.cur_pass->slot.id == _sg.d3d11.cur_pass_id.id); +        for (int i = 0; i < _sg.d3d11.num_rtvs; i++) { +            _sg_pass_attachment_t* cmn_att = &_sg.d3d11.cur_pass->cmn.color_atts[i]; +            _sg_image_t* att_img = _sg.d3d11.cur_pass->d3d11.color_atts[i].image; +            SOKOL_ASSERT(att_img && (att_img->slot.id == cmn_att->image_id.id)); +            if (att_img->cmn.sample_count > 1) { +                /* FIXME: support MSAA resolve into 3D texture */ +                SOKOL_ASSERT(att_img->d3d11.tex2d && att_img->d3d11.texmsaa && !att_img->d3d11.tex3d); +                SOKOL_ASSERT(DXGI_FORMAT_UNKNOWN != att_img->d3d11.format); +                UINT dst_subres = _sg_d3d11_calcsubresource((UINT)cmn_att->mip_level, (UINT)cmn_att->slice, (UINT)att_img->cmn.num_mipmaps); +                _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx, +                    (ID3D11Resource*) att_img->d3d11.tex2d,     /* pDstResource */ +                    dst_subres,                                 /* DstSubresource */ +                    (ID3D11Resource*) att_img->d3d11.texmsaa,   /* pSrcResource */ +                    0,                                          /* SrcSubresource */ +                    att_img->d3d11.format); +            } +        } +    } +    _sg.d3d11.cur_pass = 0; +    _sg.d3d11.cur_pass_id.id = SG_INVALID_ID; +    _sg.d3d11.cur_pipeline = 0; +    _sg.d3d11.cur_pipeline_id.id = SG_INVALID_ID; +    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +        _sg.d3d11.cur_rtvs[i] = 0; +    } +    _sg.d3d11.cur_dsv = 0; +    _sg_d3d11_clear_state(); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(_sg.d3d11.in_pass); +    D3D11_VIEWPORT vp; +    vp.TopLeftX = (FLOAT) x; +    vp.TopLeftY = (FLOAT) (origin_top_left ? y : (_sg.d3d11.cur_height - (y + h))); +    vp.Width = (FLOAT) w; +    vp.Height = (FLOAT) h; +    vp.MinDepth = 0.0f; +    vp.MaxDepth = 1.0f; +    _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(_sg.d3d11.in_pass); +    D3D11_RECT rect; +    rect.left = x; +    rect.top = (origin_top_left ? y : (_sg.d3d11.cur_height - (y + h))); +    rect.right = x + w; +    rect.bottom = origin_top_left ? (y + h) : (_sg.d3d11.cur_height - y); +    _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(_sg.d3d11.in_pass); +    SOKOL_ASSERT(pip->d3d11.rs && pip->d3d11.bs && pip->d3d11.dss && pip->d3d11.il); + +    _sg.d3d11.cur_pipeline = pip; +    _sg.d3d11.cur_pipeline_id.id = pip->slot.id; +    _sg.d3d11.use_indexed_draw = (pip->d3d11.index_format != DXGI_FORMAT_UNKNOWN); + +    _sg_d3d11_RSSetState(_sg.d3d11.ctx, pip->d3d11.rs); +    _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, pip->d3d11.dss, pip->d3d11.stencil_ref); +    _sg_d3d11_OMSetBlendState(_sg.d3d11.ctx, pip->d3d11.bs, &pip->cmn.blend_color.r, 0xFFFFFFFF); +    _sg_d3d11_IASetPrimitiveTopology(_sg.d3d11.ctx, pip->d3d11.topology); +    _sg_d3d11_IASetInputLayout(_sg.d3d11.ctx, pip->d3d11.il); +    _sg_d3d11_VSSetShader(_sg.d3d11.ctx, pip->shader->d3d11.vs, NULL, 0); +    _sg_d3d11_VSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, pip->shader->d3d11.stage[SG_SHADERSTAGE_VS].cbufs); +    _sg_d3d11_PSSetShader(_sg.d3d11.ctx, pip->shader->d3d11.fs, NULL, 0); +    _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, pip->shader->d3d11.stage[SG_SHADERSTAGE_FS].cbufs); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_bindings( +    _sg_pipeline_t* pip, +    _sg_buffer_t** vbs, const int* vb_offsets, int num_vbs, +    _sg_buffer_t* ib, int ib_offset, +    _sg_image_t** vs_imgs, int num_vs_imgs, +    _sg_image_t** fs_imgs, int num_fs_imgs) +{ +    SOKOL_ASSERT(pip); +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(_sg.d3d11.in_pass); + +    /* gather all the D3D11 resources into arrays */ +    ID3D11Buffer* d3d11_ib = ib ? ib->d3d11.buf : 0; +    ID3D11Buffer* d3d11_vbs[SG_MAX_SHADERSTAGE_BUFFERS]; +    UINT d3d11_vb_offsets[SG_MAX_SHADERSTAGE_BUFFERS]; +    ID3D11ShaderResourceView* d3d11_vs_srvs[SG_MAX_SHADERSTAGE_IMAGES]; +    ID3D11SamplerState* d3d11_vs_smps[SG_MAX_SHADERSTAGE_IMAGES]; +    ID3D11ShaderResourceView* d3d11_fs_srvs[SG_MAX_SHADERSTAGE_IMAGES]; +    ID3D11SamplerState* d3d11_fs_smps[SG_MAX_SHADERSTAGE_IMAGES]; +    int i; +    for (i = 0; i < num_vbs; i++) { +        SOKOL_ASSERT(vbs[i]->d3d11.buf); +        d3d11_vbs[i] = vbs[i]->d3d11.buf; +        d3d11_vb_offsets[i] = (UINT)vb_offsets[i]; +    } +    for (; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { +        d3d11_vbs[i] = 0; +        d3d11_vb_offsets[i] = 0; +    } +    for (i = 0; i < num_vs_imgs; i++) { +        SOKOL_ASSERT(vs_imgs[i]->d3d11.srv); +        SOKOL_ASSERT(vs_imgs[i]->d3d11.smp); +        d3d11_vs_srvs[i] = vs_imgs[i]->d3d11.srv; +        d3d11_vs_smps[i] = vs_imgs[i]->d3d11.smp; +    } +    for (; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { +        d3d11_vs_srvs[i] = 0; +        d3d11_vs_smps[i] = 0; +    } +    for (i = 0; i < num_fs_imgs; i++) { +        SOKOL_ASSERT(fs_imgs[i]->d3d11.srv); +        SOKOL_ASSERT(fs_imgs[i]->d3d11.smp); +        d3d11_fs_srvs[i] = fs_imgs[i]->d3d11.srv; +        d3d11_fs_smps[i] = fs_imgs[i]->d3d11.smp; +    } +    for (; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { +        d3d11_fs_srvs[i] = 0; +        d3d11_fs_smps[i] = 0; +    } + +    _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_BUFFERS, d3d11_vbs, pip->d3d11.vb_strides, d3d11_vb_offsets); +    _sg_d3d11_IASetIndexBuffer(_sg.d3d11.ctx, d3d11_ib, pip->d3d11.index_format, (UINT)ib_offset); +    _sg_d3d11_VSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, d3d11_vs_srvs); +    _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, d3d11_vs_smps); +    _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, d3d11_fs_srvs); +    _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, d3d11_fs_smps); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    SOKOL_ASSERT(_sg.d3d11.ctx && _sg.d3d11.in_pass); +    SOKOL_ASSERT(_sg.d3d11.cur_pipeline && _sg.d3d11.cur_pipeline->slot.id == _sg.d3d11.cur_pipeline_id.id); +    SOKOL_ASSERT(_sg.d3d11.cur_pipeline->shader && _sg.d3d11.cur_pipeline->shader->slot.id == _sg.d3d11.cur_pipeline->cmn.shader_id.id); +    SOKOL_ASSERT(ub_index < _sg.d3d11.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); +    SOKOL_ASSERT(data->size == _sg.d3d11.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); +    ID3D11Buffer* cb = _sg.d3d11.cur_pipeline->shader->d3d11.stage[stage_index].cbufs[ub_index]; +    SOKOL_ASSERT(cb); +    _sg_d3d11_UpdateSubresource(_sg.d3d11.ctx, (ID3D11Resource*)cb, 0, NULL, data->ptr, 0, 0); +} + +_SOKOL_PRIVATE void _sg_d3d11_draw(int base_element, int num_elements, int num_instances) { +    SOKOL_ASSERT(_sg.d3d11.in_pass); +    if (_sg.d3d11.use_indexed_draw) { +        if (1 == num_instances) { +            _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); +        } +        else { +            _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); +        } +    } +    else { +        if (1 == num_instances) { +            _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); +        } +        else { +            _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0); +        } +    } +} + +_SOKOL_PRIVATE void _sg_d3d11_commit(void) { +    SOKOL_ASSERT(!_sg.d3d11.in_pass); +} + +_SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* data) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(buf->d3d11.buf); +    D3D11_MAPPED_SUBRESOURCE d3d11_msr; +    HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); +    _SOKOL_UNUSED(hr); +    SOKOL_ASSERT(SUCCEEDED(hr)); +    memcpy(d3d11_msr.pData, data->ptr, data->size); +    _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); +} + +_SOKOL_PRIVATE int _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(buf->d3d11.buf); +    D3D11_MAP map_type = new_frame ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE; +    D3D11_MAPPED_SUBRESOURCE d3d11_msr; +    HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, map_type, 0, &d3d11_msr); +    _SOKOL_UNUSED(hr); +    SOKOL_ASSERT(SUCCEEDED(hr)); +    uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; +    memcpy(dst_ptr, data->ptr, data->size); +    _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); +    /* NOTE: this is a requirement from WebGPU, but we want identical behaviour across all backend */ +    return _sg_roundup((int)data->size, 4); +} + +_SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data* data) { +    SOKOL_ASSERT(img && data); +    SOKOL_ASSERT(_sg.d3d11.ctx); +    SOKOL_ASSERT(img->d3d11.tex2d || img->d3d11.tex3d); +    ID3D11Resource* d3d11_res = 0; +    if (img->d3d11.tex3d) { +        d3d11_res = (ID3D11Resource*) img->d3d11.tex3d; +    } +    else { +        d3d11_res = (ID3D11Resource*) img->d3d11.tex2d; +    } +    SOKOL_ASSERT(d3d11_res); +    const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; +    const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; +    UINT subres_index = 0; +    HRESULT hr; +    _SOKOL_UNUSED(hr); +    D3D11_MAPPED_SUBRESOURCE d3d11_msr; +    for (int face_index = 0; face_index < num_faces; face_index++) { +        for (int slice_index = 0; slice_index < num_slices; slice_index++) { +            for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) { +                SOKOL_ASSERT(subres_index < (SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS)); +                const int mip_width = ((img->cmn.width>>mip_index)>0) ? img->cmn.width>>mip_index : 1; +                const int mip_height = ((img->cmn.height>>mip_index)>0) ? img->cmn.height>>mip_index : 1; +                const int src_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); +                const sg_range* subimg_data = &(data->subimage[face_index][mip_index]); +                const size_t slice_size = subimg_data->size / (size_t)num_slices; +                const size_t slice_offset = slice_size * (size_t)slice_index; +                const uint8_t* slice_ptr = ((const uint8_t*)subimg_data->ptr) + slice_offset; +                hr = _sg_d3d11_Map(_sg.d3d11.ctx, d3d11_res, subres_index, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); +                SOKOL_ASSERT(SUCCEEDED(hr)); +                /* FIXME: need to handle difference in depth-pitch for 3D textures as well! */ +                if (src_pitch == (int)d3d11_msr.RowPitch) { +                    memcpy(d3d11_msr.pData, slice_ptr, slice_size); +                } +                else { +                    SOKOL_ASSERT(src_pitch < (int)d3d11_msr.RowPitch); +                    const uint8_t* src_ptr = slice_ptr; +                    uint8_t* dst_ptr = (uint8_t*) d3d11_msr.pData; +                    for (int row_index = 0; row_index < mip_height; row_index++) { +                        memcpy(dst_ptr, src_ptr, (size_t)src_pitch); +                        src_ptr += src_pitch; +                        dst_ptr += d3d11_msr.RowPitch; +                    } +                } +                _sg_d3d11_Unmap(_sg.d3d11.ctx, d3d11_res, subres_index); +            } +        } +    } +} + +/*== METAL BACKEND IMPLEMENTATION ============================================*/ +#elif defined(SOKOL_METAL) + +#if __has_feature(objc_arc) +#define _SG_OBJC_RETAIN(obj) { } +#define _SG_OBJC_RELEASE(obj) { obj = nil; } +#define _SG_OBJC_RELEASE_WITH_NULL(obj) { obj = [NSNull null]; } +#else +#define _SG_OBJC_RETAIN(obj) { [obj retain]; } +#define _SG_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#define _SG_OBJC_RELEASE_WITH_NULL(obj) { [obj release]; obj = [NSNull null]; } +#endif + +/*-- enum translation functions ----------------------------------------------*/ +_SOKOL_PRIVATE MTLLoadAction _sg_mtl_load_action(sg_action a) { +    switch (a) { +        case SG_ACTION_CLEAR:       return MTLLoadActionClear; +        case SG_ACTION_LOAD:        return MTLLoadActionLoad; +        case SG_ACTION_DONTCARE:    return MTLLoadActionDontCare; +        default: SOKOL_UNREACHABLE; return (MTLLoadAction)0; +    } +} + +_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_buffer_resource_options(sg_usage usg) { +    switch (usg) { +        case SG_USAGE_IMMUTABLE: +            return MTLResourceStorageModeShared; +        case SG_USAGE_DYNAMIC: +        case SG_USAGE_STREAM: +            #if defined(_SG_TARGET_MACOS) +            return MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged; +            #else +            return MTLCPUCacheModeWriteCombined; +            #endif +        default: +            SOKOL_UNREACHABLE; +            return 0; +    } +} + +_SOKOL_PRIVATE MTLVertexStepFunction _sg_mtl_step_function(sg_vertex_step step) { +    switch (step) { +        case SG_VERTEXSTEP_PER_VERTEX:      return MTLVertexStepFunctionPerVertex; +        case SG_VERTEXSTEP_PER_INSTANCE:    return MTLVertexStepFunctionPerInstance; +        default: SOKOL_UNREACHABLE; return (MTLVertexStepFunction)0; +    } +} + +_SOKOL_PRIVATE MTLVertexFormat _sg_mtl_vertex_format(sg_vertex_format fmt) { +    switch (fmt) { +        case SG_VERTEXFORMAT_FLOAT:     return MTLVertexFormatFloat; +        case SG_VERTEXFORMAT_FLOAT2:    return MTLVertexFormatFloat2; +        case SG_VERTEXFORMAT_FLOAT3:    return MTLVertexFormatFloat3; +        case SG_VERTEXFORMAT_FLOAT4:    return MTLVertexFormatFloat4; +        case SG_VERTEXFORMAT_BYTE4:     return MTLVertexFormatChar4; +        case SG_VERTEXFORMAT_BYTE4N:    return MTLVertexFormatChar4Normalized; +        case SG_VERTEXFORMAT_UBYTE4:    return MTLVertexFormatUChar4; +        case SG_VERTEXFORMAT_UBYTE4N:   return MTLVertexFormatUChar4Normalized; +        case SG_VERTEXFORMAT_SHORT2:    return MTLVertexFormatShort2; +        case SG_VERTEXFORMAT_SHORT2N:   return MTLVertexFormatShort2Normalized; +        case SG_VERTEXFORMAT_USHORT2N:  return MTLVertexFormatUShort2Normalized; +        case SG_VERTEXFORMAT_SHORT4:    return MTLVertexFormatShort4; +        case SG_VERTEXFORMAT_SHORT4N:   return MTLVertexFormatShort4Normalized; +        case SG_VERTEXFORMAT_USHORT4N:  return MTLVertexFormatUShort4Normalized; +        case SG_VERTEXFORMAT_UINT10_N2: return MTLVertexFormatUInt1010102Normalized; +        default: SOKOL_UNREACHABLE; return (MTLVertexFormat)0; +    } +} + +_SOKOL_PRIVATE MTLPrimitiveType _sg_mtl_primitive_type(sg_primitive_type t) { +    switch (t) { +        case SG_PRIMITIVETYPE_POINTS:           return MTLPrimitiveTypePoint; +        case SG_PRIMITIVETYPE_LINES:            return MTLPrimitiveTypeLine; +        case SG_PRIMITIVETYPE_LINE_STRIP:       return MTLPrimitiveTypeLineStrip; +        case SG_PRIMITIVETYPE_TRIANGLES:        return MTLPrimitiveTypeTriangle; +        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return MTLPrimitiveTypeTriangleStrip; +        default: SOKOL_UNREACHABLE; return (MTLPrimitiveType)0; +    } +} + +_SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_R8:                     return MTLPixelFormatR8Unorm; +        case SG_PIXELFORMAT_R8SN:                   return MTLPixelFormatR8Snorm; +        case SG_PIXELFORMAT_R8UI:                   return MTLPixelFormatR8Uint; +        case SG_PIXELFORMAT_R8SI:                   return MTLPixelFormatR8Sint; +        case SG_PIXELFORMAT_R16:                    return MTLPixelFormatR16Unorm; +        case SG_PIXELFORMAT_R16SN:                  return MTLPixelFormatR16Snorm; +        case SG_PIXELFORMAT_R16UI:                  return MTLPixelFormatR16Uint; +        case SG_PIXELFORMAT_R16SI:                  return MTLPixelFormatR16Sint; +        case SG_PIXELFORMAT_R16F:                   return MTLPixelFormatR16Float; +        case SG_PIXELFORMAT_RG8:                    return MTLPixelFormatRG8Unorm; +        case SG_PIXELFORMAT_RG8SN:                  return MTLPixelFormatRG8Snorm; +        case SG_PIXELFORMAT_RG8UI:                  return MTLPixelFormatRG8Uint; +        case SG_PIXELFORMAT_RG8SI:                  return MTLPixelFormatRG8Sint; +        case SG_PIXELFORMAT_R32UI:                  return MTLPixelFormatR32Uint; +        case SG_PIXELFORMAT_R32SI:                  return MTLPixelFormatR32Sint; +        case SG_PIXELFORMAT_R32F:                   return MTLPixelFormatR32Float; +        case SG_PIXELFORMAT_RG16:                   return MTLPixelFormatRG16Unorm; +        case SG_PIXELFORMAT_RG16SN:                 return MTLPixelFormatRG16Snorm; +        case SG_PIXELFORMAT_RG16UI:                 return MTLPixelFormatRG16Uint; +        case SG_PIXELFORMAT_RG16SI:                 return MTLPixelFormatRG16Sint; +        case SG_PIXELFORMAT_RG16F:                  return MTLPixelFormatRG16Float; +        case SG_PIXELFORMAT_RGBA8:                  return MTLPixelFormatRGBA8Unorm; +        case SG_PIXELFORMAT_RGBA8SN:                return MTLPixelFormatRGBA8Snorm; +        case SG_PIXELFORMAT_RGBA8UI:                return MTLPixelFormatRGBA8Uint; +        case SG_PIXELFORMAT_RGBA8SI:                return MTLPixelFormatRGBA8Sint; +        case SG_PIXELFORMAT_BGRA8:                  return MTLPixelFormatBGRA8Unorm; +        case SG_PIXELFORMAT_RGB10A2:                return MTLPixelFormatRGB10A2Unorm; +        case SG_PIXELFORMAT_RG11B10F:               return MTLPixelFormatRG11B10Float; +        case SG_PIXELFORMAT_RG32UI:                 return MTLPixelFormatRG32Uint; +        case SG_PIXELFORMAT_RG32SI:                 return MTLPixelFormatRG32Sint; +        case SG_PIXELFORMAT_RG32F:                  return MTLPixelFormatRG32Float; +        case SG_PIXELFORMAT_RGBA16:                 return MTLPixelFormatRGBA16Unorm; +        case SG_PIXELFORMAT_RGBA16SN:               return MTLPixelFormatRGBA16Snorm; +        case SG_PIXELFORMAT_RGBA16UI:               return MTLPixelFormatRGBA16Uint; +        case SG_PIXELFORMAT_RGBA16SI:               return MTLPixelFormatRGBA16Sint; +        case SG_PIXELFORMAT_RGBA16F:                return MTLPixelFormatRGBA16Float; +        case SG_PIXELFORMAT_RGBA32UI:               return MTLPixelFormatRGBA32Uint; +        case SG_PIXELFORMAT_RGBA32SI:               return MTLPixelFormatRGBA32Sint; +        case SG_PIXELFORMAT_RGBA32F:                return MTLPixelFormatRGBA32Float; +        case SG_PIXELFORMAT_DEPTH:                  return MTLPixelFormatDepth32Float; +        case SG_PIXELFORMAT_DEPTH_STENCIL:          return MTLPixelFormatDepth32Float_Stencil8; +        #if defined(_SG_TARGET_MACOS) +        case SG_PIXELFORMAT_BC1_RGBA:               return MTLPixelFormatBC1_RGBA; +        case SG_PIXELFORMAT_BC2_RGBA:               return MTLPixelFormatBC2_RGBA; +        case SG_PIXELFORMAT_BC3_RGBA:               return MTLPixelFormatBC3_RGBA; +        case SG_PIXELFORMAT_BC4_R:                  return MTLPixelFormatBC4_RUnorm; +        case SG_PIXELFORMAT_BC4_RSN:                return MTLPixelFormatBC4_RSnorm; +        case SG_PIXELFORMAT_BC5_RG:                 return MTLPixelFormatBC5_RGUnorm; +        case SG_PIXELFORMAT_BC5_RGSN:               return MTLPixelFormatBC5_RGSnorm; +        case SG_PIXELFORMAT_BC6H_RGBF:              return MTLPixelFormatBC6H_RGBFloat; +        case SG_PIXELFORMAT_BC6H_RGBUF:             return MTLPixelFormatBC6H_RGBUfloat; +        case SG_PIXELFORMAT_BC7_RGBA:               return MTLPixelFormatBC7_RGBAUnorm; +        #else +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP:         return MTLPixelFormatPVRTC_RGB_2BPP; +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP:         return MTLPixelFormatPVRTC_RGB_4BPP; +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP:        return MTLPixelFormatPVRTC_RGBA_2BPP; +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP:        return MTLPixelFormatPVRTC_RGBA_4BPP; +        case SG_PIXELFORMAT_ETC2_RGB8:              return MTLPixelFormatETC2_RGB8; +        case SG_PIXELFORMAT_ETC2_RGB8A1:            return MTLPixelFormatETC2_RGB8A1; +        case SG_PIXELFORMAT_ETC2_RGBA8:             return MTLPixelFormatEAC_RGBA8; +        case SG_PIXELFORMAT_ETC2_RG11:              return MTLPixelFormatEAC_RG11Unorm; +        case SG_PIXELFORMAT_ETC2_RG11SN:            return MTLPixelFormatEAC_RG11Snorm; +        #endif +        default: return MTLPixelFormatInvalid; +    } +} + +_SOKOL_PRIVATE MTLColorWriteMask _sg_mtl_color_write_mask(sg_color_mask m) { +    MTLColorWriteMask mtl_mask = MTLColorWriteMaskNone; +    if (m & SG_COLORMASK_R) { +        mtl_mask |= MTLColorWriteMaskRed; +    } +    if (m & SG_COLORMASK_G) { +        mtl_mask |= MTLColorWriteMaskGreen; +    } +    if (m & SG_COLORMASK_B) { +        mtl_mask |= MTLColorWriteMaskBlue; +    } +    if (m & SG_COLORMASK_A) { +        mtl_mask |= MTLColorWriteMaskAlpha; +    } +    return mtl_mask; +} + +_SOKOL_PRIVATE MTLBlendOperation _sg_mtl_blend_op(sg_blend_op op) { +    switch (op) { +        case SG_BLENDOP_ADD:                return MTLBlendOperationAdd; +        case SG_BLENDOP_SUBTRACT:           return MTLBlendOperationSubtract; +        case SG_BLENDOP_REVERSE_SUBTRACT:   return MTLBlendOperationReverseSubtract; +        default: SOKOL_UNREACHABLE; return (MTLBlendOperation)0; +    } +} + +_SOKOL_PRIVATE MTLBlendFactor _sg_mtl_blend_factor(sg_blend_factor f) { +    switch (f) { +        case SG_BLENDFACTOR_ZERO:                   return MTLBlendFactorZero; +        case SG_BLENDFACTOR_ONE:                    return MTLBlendFactorOne; +        case SG_BLENDFACTOR_SRC_COLOR:              return MTLBlendFactorSourceColor; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return MTLBlendFactorOneMinusSourceColor; +        case SG_BLENDFACTOR_SRC_ALPHA:              return MTLBlendFactorSourceAlpha; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return MTLBlendFactorOneMinusSourceAlpha; +        case SG_BLENDFACTOR_DST_COLOR:              return MTLBlendFactorDestinationColor; +        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return MTLBlendFactorOneMinusDestinationColor; +        case SG_BLENDFACTOR_DST_ALPHA:              return MTLBlendFactorDestinationAlpha; +        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return MTLBlendFactorOneMinusDestinationAlpha; +        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return MTLBlendFactorSourceAlphaSaturated; +        case SG_BLENDFACTOR_BLEND_COLOR:            return MTLBlendFactorBlendColor; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return MTLBlendFactorOneMinusBlendColor; +        case SG_BLENDFACTOR_BLEND_ALPHA:            return MTLBlendFactorBlendAlpha; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return MTLBlendFactorOneMinusBlendAlpha; +        default: SOKOL_UNREACHABLE; return (MTLBlendFactor)0; +    } +} + +_SOKOL_PRIVATE MTLCompareFunction _sg_mtl_compare_func(sg_compare_func f) { +    switch (f) { +        case SG_COMPAREFUNC_NEVER:          return MTLCompareFunctionNever; +        case SG_COMPAREFUNC_LESS:           return MTLCompareFunctionLess; +        case SG_COMPAREFUNC_EQUAL:          return MTLCompareFunctionEqual; +        case SG_COMPAREFUNC_LESS_EQUAL:     return MTLCompareFunctionLessEqual; +        case SG_COMPAREFUNC_GREATER:        return MTLCompareFunctionGreater; +        case SG_COMPAREFUNC_NOT_EQUAL:      return MTLCompareFunctionNotEqual; +        case SG_COMPAREFUNC_GREATER_EQUAL:  return MTLCompareFunctionGreaterEqual; +        case SG_COMPAREFUNC_ALWAYS:         return MTLCompareFunctionAlways; +        default: SOKOL_UNREACHABLE; return (MTLCompareFunction)0; +    } +} + +_SOKOL_PRIVATE MTLStencilOperation _sg_mtl_stencil_op(sg_stencil_op op) { +    switch (op) { +        case SG_STENCILOP_KEEP:         return MTLStencilOperationKeep; +        case SG_STENCILOP_ZERO:         return MTLStencilOperationZero; +        case SG_STENCILOP_REPLACE:      return MTLStencilOperationReplace; +        case SG_STENCILOP_INCR_CLAMP:   return MTLStencilOperationIncrementClamp; +        case SG_STENCILOP_DECR_CLAMP:   return MTLStencilOperationDecrementClamp; +        case SG_STENCILOP_INVERT:       return MTLStencilOperationInvert; +        case SG_STENCILOP_INCR_WRAP:    return MTLStencilOperationIncrementWrap; +        case SG_STENCILOP_DECR_WRAP:    return MTLStencilOperationDecrementWrap; +        default: SOKOL_UNREACHABLE; return (MTLStencilOperation)0; +    } +} + +_SOKOL_PRIVATE MTLCullMode _sg_mtl_cull_mode(sg_cull_mode m) { +    switch (m) { +        case SG_CULLMODE_NONE:  return MTLCullModeNone; +        case SG_CULLMODE_FRONT: return MTLCullModeFront; +        case SG_CULLMODE_BACK:  return MTLCullModeBack; +        default: SOKOL_UNREACHABLE; return (MTLCullMode)0; +    } +} + +_SOKOL_PRIVATE MTLWinding _sg_mtl_winding(sg_face_winding w) { +    switch (w) { +        case SG_FACEWINDING_CW:     return MTLWindingClockwise; +        case SG_FACEWINDING_CCW:    return MTLWindingCounterClockwise; +        default: SOKOL_UNREACHABLE; return (MTLWinding)0; +    } +} + +_SOKOL_PRIVATE MTLIndexType _sg_mtl_index_type(sg_index_type t) { +    switch (t) { +        case SG_INDEXTYPE_UINT16:   return MTLIndexTypeUInt16; +        case SG_INDEXTYPE_UINT32:   return MTLIndexTypeUInt32; +        default: SOKOL_UNREACHABLE; return (MTLIndexType)0; +    } +} + +_SOKOL_PRIVATE int _sg_mtl_index_size(sg_index_type t) { +    switch (t) { +        case SG_INDEXTYPE_NONE:     return 0; +        case SG_INDEXTYPE_UINT16:   return 2; +        case SG_INDEXTYPE_UINT32:   return 4; +        default: SOKOL_UNREACHABLE; return 0; +    } +} + +_SOKOL_PRIVATE MTLTextureType _sg_mtl_texture_type(sg_image_type t) { +    switch (t) { +        case SG_IMAGETYPE_2D:       return MTLTextureType2D; +        case SG_IMAGETYPE_CUBE:     return MTLTextureTypeCube; +        case SG_IMAGETYPE_3D:       return MTLTextureType3D; +        case SG_IMAGETYPE_ARRAY:    return MTLTextureType2DArray; +        default: SOKOL_UNREACHABLE; return (MTLTextureType)0; +    } +} + +_SOKOL_PRIVATE bool _sg_mtl_is_pvrtc(sg_pixel_format fmt) { +    switch (fmt) { +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +            return true; +        default: +            return false; +    } +} + +_SOKOL_PRIVATE MTLSamplerAddressMode _sg_mtl_address_mode(sg_wrap w) { +    switch (w) { +        case SG_WRAP_REPEAT:            return MTLSamplerAddressModeRepeat; +        case SG_WRAP_CLAMP_TO_EDGE:     return MTLSamplerAddressModeClampToEdge; +        #if defined(_SG_TARGET_MACOS) +        case SG_WRAP_CLAMP_TO_BORDER:   return MTLSamplerAddressModeClampToBorderColor; +        #else +        /* clamp-to-border not supported on iOS, fall back to clamp-to-edge */ +        case SG_WRAP_CLAMP_TO_BORDER:   return MTLSamplerAddressModeClampToEdge; +        #endif +        case SG_WRAP_MIRRORED_REPEAT:   return MTLSamplerAddressModeMirrorRepeat; +        default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0; +    } +} + +#if defined(_SG_TARGET_MACOS) +_SOKOL_PRIVATE MTLSamplerBorderColor _sg_mtl_border_color(sg_border_color c) { +    switch (c) { +        case SG_BORDERCOLOR_TRANSPARENT_BLACK: return MTLSamplerBorderColorTransparentBlack; +        case SG_BORDERCOLOR_OPAQUE_BLACK: return MTLSamplerBorderColorOpaqueBlack; +        case SG_BORDERCOLOR_OPAQUE_WHITE: return MTLSamplerBorderColorOpaqueWhite; +        default: SOKOL_UNREACHABLE; return (MTLSamplerBorderColor)0; +    } +} +#endif + +_SOKOL_PRIVATE MTLSamplerMinMagFilter _sg_mtl_minmag_filter(sg_filter f) { +    switch (f) { +        case SG_FILTER_NEAREST: +        case SG_FILTER_NEAREST_MIPMAP_NEAREST: +        case SG_FILTER_NEAREST_MIPMAP_LINEAR: +            return MTLSamplerMinMagFilterNearest; +        case SG_FILTER_LINEAR: +        case SG_FILTER_LINEAR_MIPMAP_NEAREST: +        case SG_FILTER_LINEAR_MIPMAP_LINEAR: +            return MTLSamplerMinMagFilterLinear; +        default: +            SOKOL_UNREACHABLE; return (MTLSamplerMinMagFilter)0; +    } +} + +_SOKOL_PRIVATE MTLSamplerMipFilter _sg_mtl_mip_filter(sg_filter f) { +    switch (f) { +        case SG_FILTER_NEAREST: +        case SG_FILTER_LINEAR: +            return MTLSamplerMipFilterNotMipmapped; +        case SG_FILTER_NEAREST_MIPMAP_NEAREST: +        case SG_FILTER_LINEAR_MIPMAP_NEAREST: +            return MTLSamplerMipFilterNearest; +        case SG_FILTER_NEAREST_MIPMAP_LINEAR: +        case SG_FILTER_LINEAR_MIPMAP_LINEAR: +            return MTLSamplerMipFilterLinear; +        default: +            SOKOL_UNREACHABLE; return (MTLSamplerMipFilter)0; +    } +} + +/*-- a pool for all Metal resource objects, with deferred release queue -------*/ + +_SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { +    _sg.mtl.idpool.num_slots = 2 * +        ( +            2 * desc->buffer_pool_size + +            5 * desc->image_pool_size + +            4 * desc->shader_pool_size + +            2 * desc->pipeline_pool_size + +            desc->pass_pool_size +        ); +    _sg.mtl.idpool.pool = [NSMutableArray arrayWithCapacity:(NSUInteger)_sg.mtl.idpool.num_slots]; +    _SG_OBJC_RETAIN(_sg.mtl.idpool.pool); +    NSNull* null = [NSNull null]; +    for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { +        [_sg.mtl.idpool.pool addObject:null]; +    } +    SOKOL_ASSERT([_sg.mtl.idpool.pool count] == (NSUInteger)_sg.mtl.idpool.num_slots); +    /* a queue of currently free slot indices */ +    _sg.mtl.idpool.free_queue_top = 0; +    _sg.mtl.idpool.free_queue = (int*)SOKOL_MALLOC((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); +    /* pool slot 0 is reserved! */ +    for (int i = _sg.mtl.idpool.num_slots-1; i >= 1; i--) { +        _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = i; +    } +    /* a circular queue which holds release items (frame index +       when a resource is to be released, and the resource's +       pool index +    */ +    _sg.mtl.idpool.release_queue_front = 0; +    _sg.mtl.idpool.release_queue_back = 0; +    _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)SOKOL_MALLOC((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); +    for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { +        _sg.mtl.idpool.release_queue[i].frame_index = 0; +        _sg.mtl.idpool.release_queue[i].slot_index = _SG_MTL_INVALID_SLOT_INDEX; +    } +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_pool(void) { +    SOKOL_FREE(_sg.mtl.idpool.release_queue);  _sg.mtl.idpool.release_queue = 0; +    SOKOL_FREE(_sg.mtl.idpool.free_queue);     _sg.mtl.idpool.free_queue = 0; +    _SG_OBJC_RELEASE(_sg.mtl.idpool.pool); +} + +/* get a new free resource pool slot */ +_SOKOL_PRIVATE int _sg_mtl_alloc_pool_slot(void) { +    SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top > 0); +    const int slot_index = _sg.mtl.idpool.free_queue[--_sg.mtl.idpool.free_queue_top]; +    SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); +    return slot_index; +} + +/* put a free resource pool slot back into the free-queue */ +_SOKOL_PRIVATE void _sg_mtl_free_pool_slot(int slot_index) { +    SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top < _sg.mtl.idpool.num_slots); +    SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); +    _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = slot_index; +} + +/*  add an MTLResource to the pool, return pool index or 0 if input was 'nil' */ +_SOKOL_PRIVATE int _sg_mtl_add_resource(id res) { +    if (nil == res) { +        return _SG_MTL_INVALID_SLOT_INDEX; +    } +    const int slot_index = _sg_mtl_alloc_pool_slot(); +    SOKOL_ASSERT([NSNull null] == _sg.mtl.idpool.pool[(NSUInteger)slot_index]); +    _sg.mtl.idpool.pool[(NSUInteger)slot_index] = res; +    return slot_index; +} + +/*  mark an MTLResource for release, this will put the resource into the +    deferred-release queue, and the resource will then be released N frames later, +    the special pool index 0 will be ignored (this means that a nil +    value was provided to _sg_mtl_add_resource() +*/ +_SOKOL_PRIVATE void _sg_mtl_release_resource(uint32_t frame_index, int slot_index) { +    if (slot_index == _SG_MTL_INVALID_SLOT_INDEX) { +        return; +    } +    SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); +    SOKOL_ASSERT([NSNull null] != _sg.mtl.idpool.pool[(NSUInteger)slot_index]); +    int release_index = _sg.mtl.idpool.release_queue_front++; +    if (_sg.mtl.idpool.release_queue_front >= _sg.mtl.idpool.num_slots) { +        /* wrap-around */ +        _sg.mtl.idpool.release_queue_front = 0; +    } +    /* release queue full? */ +    SOKOL_ASSERT(_sg.mtl.idpool.release_queue_front != _sg.mtl.idpool.release_queue_back); +    SOKOL_ASSERT(0 == _sg.mtl.idpool.release_queue[release_index].frame_index); +    const uint32_t safe_to_release_frame_index = frame_index + SG_NUM_INFLIGHT_FRAMES + 1; +    _sg.mtl.idpool.release_queue[release_index].frame_index = safe_to_release_frame_index; +    _sg.mtl.idpool.release_queue[release_index].slot_index = slot_index; +} + +/* run garbage-collection pass on all resources in the release-queue */ +_SOKOL_PRIVATE void _sg_mtl_garbage_collect(uint32_t frame_index) { +    while (_sg.mtl.idpool.release_queue_back != _sg.mtl.idpool.release_queue_front) { +        if (frame_index < _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index) { +            /* don't need to check further, release-items past this are too young */ +            break; +        } +        /* safe to release this resource */ +        const int slot_index = _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index; +        SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); +        SOKOL_ASSERT(_sg.mtl.idpool.pool[(NSUInteger)slot_index] != [NSNull null]); +        _SG_OBJC_RELEASE_WITH_NULL(_sg.mtl.idpool.pool[(NSUInteger)slot_index]); +        /* put the now free pool index back on the free queue */ +        _sg_mtl_free_pool_slot(slot_index); +        /* reset the release queue slot and advance the back index */ +        _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index = 0; +        _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index = _SG_MTL_INVALID_SLOT_INDEX; +        _sg.mtl.idpool.release_queue_back++; +        if (_sg.mtl.idpool.release_queue_back >= _sg.mtl.idpool.num_slots) { +            /* wrap-around */ +            _sg.mtl.idpool.release_queue_back = 0; +        } +    } +} + +_SOKOL_PRIVATE id _sg_mtl_id(int slot_index) { +    return _sg.mtl.idpool.pool[(NSUInteger)slot_index]; +} + +_SOKOL_PRIVATE void _sg_mtl_init_sampler_cache(const sg_desc* desc) { +    SOKOL_ASSERT(desc->sampler_cache_size > 0); +    _sg_smpcache_init(&_sg.mtl.sampler_cache, desc->sampler_cache_size); +} + +/* destroy the sampler cache, and release all sampler objects */ +_SOKOL_PRIVATE void _sg_mtl_destroy_sampler_cache(uint32_t frame_index) { +    SOKOL_ASSERT(_sg.mtl.sampler_cache.items); +    SOKOL_ASSERT(_sg.mtl.sampler_cache.num_items <= _sg.mtl.sampler_cache.capacity); +    for (int i = 0; i < _sg.mtl.sampler_cache.num_items; i++) { +        _sg_mtl_release_resource(frame_index, (int)_sg_smpcache_sampler(&_sg.mtl.sampler_cache, i)); +    } +    _sg_smpcache_discard(&_sg.mtl.sampler_cache); +} + +/* +    create and add an MTLSamplerStateObject and return its resource pool index, +    reuse identical sampler state if one exists +*/ +_SOKOL_PRIVATE int _sg_mtl_create_sampler(id<MTLDevice> mtl_device, const sg_image_desc* img_desc) { +    SOKOL_ASSERT(img_desc); +    int index = _sg_smpcache_find_item(&_sg.mtl.sampler_cache, img_desc); +    if (index >= 0) { +        /* reuse existing sampler */ +        return (int)_sg_smpcache_sampler(&_sg.mtl.sampler_cache, index); +    } +    else { +        /* create a new Metal sampler state object and add to sampler cache */ +        MTLSamplerDescriptor* mtl_desc = [[MTLSamplerDescriptor alloc] init]; +        mtl_desc.sAddressMode = _sg_mtl_address_mode(img_desc->wrap_u); +        mtl_desc.tAddressMode = _sg_mtl_address_mode(img_desc->wrap_v); +        if (SG_IMAGETYPE_3D == img_desc->type) { +            mtl_desc.rAddressMode = _sg_mtl_address_mode(img_desc->wrap_w); +        } +        #if defined(_SG_TARGET_MACOS) +            mtl_desc.borderColor = _sg_mtl_border_color(img_desc->border_color); +        #endif +        mtl_desc.minFilter = _sg_mtl_minmag_filter(img_desc->min_filter); +        mtl_desc.magFilter = _sg_mtl_minmag_filter(img_desc->mag_filter); +        mtl_desc.mipFilter = _sg_mtl_mip_filter(img_desc->min_filter); +        mtl_desc.lodMinClamp = img_desc->min_lod; +        mtl_desc.lodMaxClamp = img_desc->max_lod; +        mtl_desc.maxAnisotropy = img_desc->max_anisotropy; +        mtl_desc.normalizedCoordinates = YES; +        id<MTLSamplerState> mtl_sampler = [mtl_device newSamplerStateWithDescriptor:mtl_desc]; +        _SG_OBJC_RELEASE(mtl_desc); +        int sampler_handle = _sg_mtl_add_resource(mtl_sampler); +        _sg_smpcache_add_item(&_sg.mtl.sampler_cache, img_desc, (uintptr_t)sampler_handle); +        return sampler_handle; +    } +} + +_SOKOL_PRIVATE void _sg_mtl_clear_state_cache(void) { +    memset(&_sg.mtl.state_cache, 0, sizeof(_sg.mtl.state_cache)); +} + +/* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */ +_SOKOL_PRIVATE void _sg_mtl_init_caps(void) { +    #if defined(_SG_TARGET_MACOS) +        _sg.backend = SG_BACKEND_METAL_MACOS; +    #elif defined(_SG_TARGET_IOS) +        #if defined(_SG_TARGET_IOS_SIMULATOR) +            _sg.backend = SG_BACKEND_METAL_SIMULATOR; +        #else +            _sg.backend = SG_BACKEND_METAL_IOS; +        #endif +    #endif +    _sg.features.instancing = true; +    _sg.features.origin_top_left = true; +    _sg.features.multiple_render_targets = true; +    _sg.features.msaa_render_targets = true; +    _sg.features.imagetype_3d = true; +    _sg.features.imagetype_array = true; +    #if defined(_SG_TARGET_MACOS) +        _sg.features.image_clamp_to_border = true; +    #else +        _sg.features.image_clamp_to_border = false; +    #endif +    _sg.features.mrt_independent_blend_state = true; +    _sg.features.mrt_independent_write_mask = true; + +    #if defined(_SG_TARGET_MACOS) +        _sg.limits.max_image_size_2d = 16 * 1024; +        _sg.limits.max_image_size_cube = 16 * 1024; +        _sg.limits.max_image_size_3d = 2 * 1024; +        _sg.limits.max_image_size_array = 16 * 1024; +        _sg.limits.max_image_array_layers = 2 * 1024; +    #else +        /* newer iOS devices support 16k textures */ +        _sg.limits.max_image_size_2d = 8 * 1024; +        _sg.limits.max_image_size_cube = 8 * 1024; +        _sg.limits.max_image_size_3d = 2 * 1024; +        _sg.limits.max_image_size_array = 8 * 1024; +        _sg.limits.max_image_array_layers = 2 * 1024; +    #endif +    _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8SN]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]); +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]); +    #else +        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16]); +        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16SN]); +    #endif +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8SN]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]); +    #else +        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R32F]); +    #endif +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]); +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]); +    #else +        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16]); +        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16SN]); +    #endif +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); +    #else +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); +    #endif +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); +    #else +        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]); +    #endif +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]); +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); +    #else +        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16]); +        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); +    #endif +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); +        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); +        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +    #else +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); +        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +    #endif +    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); +    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); +    #if defined(_SG_TARGET_MACOS) +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); +    #else +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_2BPP]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_4BPP]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_2BPP]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_4BPP]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RG11]); +        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RG11SN]); +    #endif +} + +/*-- main Metal backend state and functions ----------------------------------*/ +_SOKOL_PRIVATE void _sg_mtl_setup_backend(const sg_desc* desc) { +    /* assume already zero-initialized */ +    SOKOL_ASSERT(desc); +    SOKOL_ASSERT(desc->context.metal.device); +    SOKOL_ASSERT(desc->context.metal.renderpass_descriptor_cb || desc->context.metal.renderpass_descriptor_userdata_cb); +    SOKOL_ASSERT(desc->context.metal.drawable_cb || desc->context.metal.drawable_userdata_cb); +    SOKOL_ASSERT(desc->uniform_buffer_size > 0); +    _sg_mtl_init_pool(desc); +    _sg_mtl_init_sampler_cache(desc); +    _sg_mtl_clear_state_cache(); +    _sg.mtl.valid = true; +    _sg.mtl.renderpass_descriptor_cb = desc->context.metal.renderpass_descriptor_cb; +    _sg.mtl.renderpass_descriptor_userdata_cb = desc->context.metal.renderpass_descriptor_userdata_cb; +    _sg.mtl.drawable_cb = desc->context.metal.drawable_cb; +    _sg.mtl.drawable_userdata_cb = desc->context.metal.drawable_userdata_cb; +    _sg.mtl.user_data = desc->context.metal.user_data; +    _sg.mtl.frame_index = 1; +    _sg.mtl.ub_size = desc->uniform_buffer_size; +    _sg.mtl.sem = dispatch_semaphore_create(SG_NUM_INFLIGHT_FRAMES); +    _sg.mtl.device = (__bridge id<MTLDevice>) desc->context.metal.device; +    _sg.mtl.cmd_queue = [_sg.mtl.device newCommandQueue]; +    MTLResourceOptions res_opts = MTLResourceCPUCacheModeWriteCombined; +    #if defined(_SG_TARGET_MACOS) +    res_opts |= MTLResourceStorageModeManaged; +    #endif +    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { +        _sg.mtl.uniform_buffers[i] = [_sg.mtl.device +            newBufferWithLength:(NSUInteger)_sg.mtl.ub_size +            options:res_opts +        ]; +    } +    _sg_mtl_init_caps(); +} + +_SOKOL_PRIVATE void _sg_mtl_discard_backend(void) { +    SOKOL_ASSERT(_sg.mtl.valid); +    /* wait for the last frame to finish */ +    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { +        dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); +    } +    /* semaphore must be "relinquished" before destruction */ +    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { +        dispatch_semaphore_signal(_sg.mtl.sem); +    } +    _sg_mtl_destroy_sampler_cache(_sg.mtl.frame_index); +    _sg_mtl_garbage_collect(_sg.mtl.frame_index + SG_NUM_INFLIGHT_FRAMES + 2); +    _sg_mtl_destroy_pool(); +    _sg.mtl.valid = false; + +    _SG_OBJC_RELEASE(_sg.mtl.sem); +    _SG_OBJC_RELEASE(_sg.mtl.device); +    _SG_OBJC_RELEASE(_sg.mtl.cmd_queue); +    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { +        _SG_OBJC_RELEASE(_sg.mtl.uniform_buffers[i]); +    } +    /* NOTE: MTLCommandBuffer and MTLRenderCommandEncoder are auto-released */ +    _sg.mtl.cmd_buffer = nil; +    _sg.mtl.cmd_encoder = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_bind_uniform_buffers(void) { +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); +    for (int slot = 0; slot < SG_MAX_SHADERSTAGE_UBS; slot++) { +        [_sg.mtl.cmd_encoder +            setVertexBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] +            offset:0 +            atIndex:(NSUInteger)slot]; +        [_sg.mtl.cmd_encoder +            setFragmentBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] +            offset:0 +            atIndex:(NSUInteger)slot]; +    } +} + +_SOKOL_PRIVATE void _sg_mtl_reset_state_cache(void) { +    _sg_mtl_clear_state_cache(); + +    /* need to restore the uniform buffer binding (normally happens in +       _sg_mtl_begin_pass() +    */ +    if (nil != _sg.mtl.cmd_encoder) { +        _sg_mtl_bind_uniform_buffers(); +    } +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +    /* empty */ +} + +_SOKOL_PRIVATE void _sg_mtl_activate_context(_sg_context_t* ctx) { +    _SOKOL_UNUSED(ctx); +    _sg_mtl_clear_state_cache(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { +    SOKOL_ASSERT(buf && desc); +    _sg_buffer_common_init(&buf->cmn, desc); +    const bool injected = (0 != desc->mtl_buffers[0]); +    MTLResourceOptions mtl_options = _sg_mtl_buffer_resource_options(buf->cmn.usage); +    for (int slot = 0; slot < buf->cmn.num_slots; slot++) { +        id<MTLBuffer> mtl_buf; +        if (injected) { +            SOKOL_ASSERT(desc->mtl_buffers[slot]); +            mtl_buf = (__bridge id<MTLBuffer>) desc->mtl_buffers[slot]; +        } +        else { +            if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { +                SOKOL_ASSERT(desc->data.ptr); +                mtl_buf = [_sg.mtl.device newBufferWithBytes:desc->data.ptr length:(NSUInteger)buf->cmn.size options:mtl_options]; +            } +            else { +                mtl_buf = [_sg.mtl.device newBufferWithLength:(NSUInteger)buf->cmn.size options:mtl_options]; +            } +        } +        buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf); +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_buffer(_sg_buffer_t* buf) { +    SOKOL_ASSERT(buf); +    for (int slot = 0; slot < buf->cmn.num_slots; slot++) { +        /* it's valid to call release resource with '0' */ +        _sg_mtl_release_resource(_sg.mtl.frame_index, buf->mtl.buf[slot]); +    } +} + +_SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unretained id<MTLTexture> mtl_tex, const sg_image_data* data) { +    const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; +    const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; +    for (int face_index = 0; face_index < num_faces; face_index++) { +        for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { +            SOKOL_ASSERT(data->subimage[face_index][mip_index].ptr); +            SOKOL_ASSERT(data->subimage[face_index][mip_index].size > 0); +            const uint8_t* data_ptr = (const uint8_t*)data->subimage[face_index][mip_index].ptr; +            const int mip_width = _sg_max(img->cmn.width >> mip_index, 1); +            const int mip_height = _sg_max(img->cmn.height >> mip_index, 1); +            /* special case PVRTC formats: bytePerRow must be 0 */ +            int bytes_per_row = 0; +            int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); +            if (!_sg_mtl_is_pvrtc(img->cmn.pixel_format)) { +                bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); +            } +            MTLRegion region; +            if (img->cmn.type == SG_IMAGETYPE_3D) { +                const int mip_depth = _sg_max(img->cmn.num_slices >> mip_index, 1); +                region = MTLRegionMake3D(0, 0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height, (NSUInteger)mip_depth); +                /* FIXME: apparently the minimal bytes_per_image size for 3D texture +                 is 4 KByte... somehow need to handle this */ +            } +            else { +                region = MTLRegionMake2D(0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height); +            } +            for (int slice_index = 0; slice_index < num_slices; slice_index++) { +                const int mtl_slice_index = (img->cmn.type == SG_IMAGETYPE_CUBE) ? face_index : slice_index; +                const int slice_offset = slice_index * bytes_per_slice; +                SOKOL_ASSERT((slice_offset + bytes_per_slice) <= (int)data->subimage[face_index][mip_index].size); +                [mtl_tex replaceRegion:region +                    mipmapLevel:(NSUInteger)mip_index +                    slice:(NSUInteger)mtl_slice_index +                    withBytes:data_ptr + slice_offset +                    bytesPerRow:(NSUInteger)bytes_per_row +                    bytesPerImage:(NSUInteger)bytes_per_slice]; +            } +        } +    } +} + +/* +    FIXME: METAL RESOURCE STORAGE MODE FOR macOS AND iOS + +    For immutable textures on macOS, the recommended procedure is to create +    a MTLStorageModeManaged texture with the immutable content first, +    and then use the GPU to blit the content into a MTLStorageModePrivate +    texture before the first use. + +    On iOS use the same one-time-blit procedure, but from a +    MTLStorageModeShared to a MTLStorageModePrivate texture. + +    It probably makes sense to handle this in a separate 'resource manager' +    with a recycable pool of blit-source-textures? +*/ + +/* initialize MTLTextureDescritor with common attributes */ +_SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { +    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"); +        return false; +    } +    mtl_desc.width = (NSUInteger)img->cmn.width; +    mtl_desc.height = (NSUInteger)img->cmn.height; +    if (SG_IMAGETYPE_3D == img->cmn.type) { +        mtl_desc.depth = (NSUInteger)img->cmn.num_slices; +    } +    else { +        mtl_desc.depth = 1; +    } +    mtl_desc.mipmapLevelCount = (NSUInteger)img->cmn.num_mipmaps; +    if (SG_IMAGETYPE_ARRAY == img->cmn.type) { +        mtl_desc.arrayLength = (NSUInteger)img->cmn.num_slices; +    } +    else { +        mtl_desc.arrayLength = 1; +    } +    mtl_desc.usage = MTLTextureUsageShaderRead; +    if (img->cmn.usage != SG_USAGE_IMMUTABLE) { +        mtl_desc.cpuCacheMode = MTLCPUCacheModeWriteCombined; +    } +    #if defined(_SG_TARGET_MACOS) +        /* macOS: use managed textures */ +        mtl_desc.resourceOptions = MTLResourceStorageModeManaged; +        mtl_desc.storageMode = MTLStorageModeManaged; +    #else +        /* iOS: use CPU/GPU shared memory */ +        mtl_desc.resourceOptions = MTLResourceStorageModeShared; +        mtl_desc.storageMode = MTLStorageModeShared; +    #endif +    return true; +} + +/* initialize MTLTextureDescritor with rendertarget attributes */ +_SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { +    SOKOL_ASSERT(img->cmn.render_target); +    _SOKOL_UNUSED(img); +    /* reset the cpuCacheMode to 'default' */ +    mtl_desc.cpuCacheMode = MTLCPUCacheModeDefaultCache; +    /* render targets are only visible to the GPU */ +    mtl_desc.resourceOptions = MTLResourceStorageModePrivate; +    mtl_desc.storageMode = MTLStorageModePrivate; +    /* non-MSAA render targets are shader-readable */ +    mtl_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; +} + +/* initialize MTLTextureDescritor with MSAA attributes */ +_SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt_msaa(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { +    SOKOL_ASSERT(img->cmn.sample_count > 1); +    /* reset the cpuCacheMode to 'default' */ +    mtl_desc.cpuCacheMode = MTLCPUCacheModeDefaultCache; +    /* render targets are only visible to the GPU */ +    mtl_desc.resourceOptions = MTLResourceStorageModePrivate; +    mtl_desc.storageMode = MTLStorageModePrivate; +    /* MSAA render targets are not shader-readable (instead they are resolved) */ +    mtl_desc.usage = MTLTextureUsageRenderTarget; +    mtl_desc.textureType = MTLTextureType2DMultisample; +    mtl_desc.depth = 1; +    mtl_desc.arrayLength = 1; +    mtl_desc.mipmapLevelCount = 1; +    mtl_desc.sampleCount = (NSUInteger)img->cmn.sample_count; +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg_image_desc* desc) { +    SOKOL_ASSERT(img && desc); +    _sg_image_common_init(&img->cmn, desc); +    const bool injected = (0 != desc->mtl_textures[0]); +    const bool msaa = (img->cmn.sample_count > 1); + +    /* first initialize all Metal resource pool slots to 'empty' */ +    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { +        img->mtl.tex[i] = _sg_mtl_add_resource(nil); +    } +    img->mtl.sampler_state = _sg_mtl_add_resource(nil); +    img->mtl.depth_tex = _sg_mtl_add_resource(nil); +    img->mtl.msaa_tex = _sg_mtl_add_resource(nil); + +    /* initialize a Metal texture descriptor with common attributes */ +    MTLTextureDescriptor* mtl_desc = [[MTLTextureDescriptor alloc] init]; +    if (!_sg_mtl_init_texdesc_common(mtl_desc, img)) { +        _SG_OBJC_RELEASE(mtl_desc); +        return SG_RESOURCESTATE_FAILED; +    } + +    /* special case depth-stencil-buffer? */ +    if (_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format)) { +        /* depth-stencil buffer texture must always be a render target */ +        SOKOL_ASSERT(img->cmn.render_target); +        SOKOL_ASSERT(img->cmn.type == SG_IMAGETYPE_2D); +        SOKOL_ASSERT(img->cmn.num_mipmaps == 1); +        SOKOL_ASSERT(!injected); +        if (msaa) { +            _sg_mtl_init_texdesc_rt_msaa(mtl_desc, img); +        } +        else { +            _sg_mtl_init_texdesc_rt(mtl_desc, img); +        } +        id<MTLTexture> tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; +        SOKOL_ASSERT(nil != tex); +        img->mtl.depth_tex = _sg_mtl_add_resource(tex); +    } +    else { +        /* create the color texture +            In case this is a render target without MSAA, add the relevant +            render-target descriptor attributes. +            In case this is a render target *with* MSAA, the color texture +            will serve as MSAA-resolve target (not as render target), and rendering +            will go into a separate render target texture of type +            MTLTextureType2DMultisample. +        */ +        if (img->cmn.render_target && !msaa) { +            _sg_mtl_init_texdesc_rt(mtl_desc, img); +        } +        for (int slot = 0; slot < img->cmn.num_slots; slot++) { +            id<MTLTexture> tex; +            if (injected) { +                SOKOL_ASSERT(desc->mtl_textures[slot]); +                tex = (__bridge id<MTLTexture>) desc->mtl_textures[slot]; +            } +            else { +                tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; +                if ((img->cmn.usage == SG_USAGE_IMMUTABLE) && !img->cmn.render_target) { +                    _sg_mtl_copy_image_data(img, tex, &desc->data); +                } +            } +            img->mtl.tex[slot] = _sg_mtl_add_resource(tex); +        } + +        /* if MSAA color render target, create an additional MSAA render-surface texture */ +        if (img->cmn.render_target && msaa) { +            _sg_mtl_init_texdesc_rt_msaa(mtl_desc, img); +            id<MTLTexture> tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; +            img->mtl.msaa_tex = _sg_mtl_add_resource(tex); +        } + +        /* create (possibly shared) sampler state */ +        img->mtl.sampler_state = _sg_mtl_create_sampler(_sg.mtl.device, desc); +    } +    _SG_OBJC_RELEASE(mtl_desc); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_image(_sg_image_t* img) { +    SOKOL_ASSERT(img); +    /* it's valid to call release resource with a 'null resource' */ +    for (int slot = 0; slot < img->cmn.num_slots; slot++) { +        _sg_mtl_release_resource(_sg.mtl.frame_index, img->mtl.tex[slot]); +    } +    _sg_mtl_release_resource(_sg.mtl.frame_index, img->mtl.depth_tex); +    _sg_mtl_release_resource(_sg.mtl.frame_index, img->mtl.msaa_tex); +    /* NOTE: sampler state objects are shared and not released until shutdown */ +} + +_SOKOL_PRIVATE id<MTLLibrary> _sg_mtl_compile_library(const char* src) { +    NSError* err = NULL; +    id<MTLLibrary> lib = [_sg.mtl.device +        newLibraryWithSource:[NSString stringWithUTF8String:src] +        options:nil +        error:&err +    ]; +    if (err) { +        SOKOL_LOG([err.localizedDescription UTF8String]); +    } +    return lib; +} + +_SOKOL_PRIVATE id<MTLLibrary> _sg_mtl_library_from_bytecode(const void* ptr, size_t num_bytes) { +    NSError* err = NULL; +    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_OBJC_RELEASE(lib_data); +    return lib; +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { +    SOKOL_ASSERT(shd && desc); + +    _sg_shader_common_init(&shd->cmn, desc); + +    /* create metal libray objects and lookup entry functions */ +    id<MTLLibrary> vs_lib; +    id<MTLLibrary> fs_lib; +    id<MTLFunction> vs_func; +    id<MTLFunction> fs_func; +    const char* vs_entry = desc->vs.entry; +    const char* fs_entry = desc->fs.entry; +    if (desc->vs.bytecode.ptr && desc->fs.bytecode.ptr) { +        /* separate byte code provided */ +        vs_lib = _sg_mtl_library_from_bytecode(desc->vs.bytecode.ptr, desc->vs.bytecode.size); +        fs_lib = _sg_mtl_library_from_bytecode(desc->fs.bytecode.ptr, desc->fs.bytecode.size); +        if (nil == vs_lib || nil == fs_lib) { +            return SG_RESOURCESTATE_FAILED; +        } +        vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; +        fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; +    } +    else if (desc->vs.source && desc->fs.source) { +        /* separate sources provided */ +        vs_lib = _sg_mtl_compile_library(desc->vs.source); +        fs_lib = _sg_mtl_compile_library(desc->fs.source); +        if (nil == vs_lib || nil == fs_lib) { +            return SG_RESOURCESTATE_FAILED; +        } +        vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; +        fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; +    } +    else { +        return SG_RESOURCESTATE_FAILED; +    } +    if (nil == vs_func) { +        SOKOL_LOG("vertex shader entry function not found\n"); +        return SG_RESOURCESTATE_FAILED; +    } +    if (nil == fs_func) { +        SOKOL_LOG("fragment shader entry function not found\n"); +        return SG_RESOURCESTATE_FAILED; +    } +    /* it is legal to call _sg_mtl_add_resource with a nil value, this will return a special 0xFFFFFFFF index */ +    shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib  = _sg_mtl_add_resource(vs_lib); +    shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib  = _sg_mtl_add_resource(fs_lib); +    shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func = _sg_mtl_add_resource(vs_func); +    shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func = _sg_mtl_add_resource(fs_func); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_shader(_sg_shader_t* shd) { +    SOKOL_ASSERT(shd); +    /* it is valid to call _sg_mtl_release_resource with a 'null resource' */ +    _sg_mtl_release_resource(_sg.mtl.frame_index, shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); +    _sg_mtl_release_resource(_sg.mtl.frame_index, shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib); +    _sg_mtl_release_resource(_sg.mtl.frame_index, shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func); +    _sg_mtl_release_resource(_sg.mtl.frame_index, shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(pip && shd && desc); +    SOKOL_ASSERT(desc->shader.id == shd->slot.id); + +    pip->shader = shd; +    _sg_pipeline_common_init(&pip->cmn, desc); + +    sg_primitive_type prim_type = desc->primitive_type; +    pip->mtl.prim_type = _sg_mtl_primitive_type(prim_type); +    pip->mtl.index_size = _sg_mtl_index_size(pip->cmn.index_type); +    if (SG_INDEXTYPE_NONE != pip->cmn.index_type) { +        pip->mtl.index_type = _sg_mtl_index_type(pip->cmn.index_type); +    } +    pip->mtl.cull_mode = _sg_mtl_cull_mode(desc->cull_mode); +    pip->mtl.winding = _sg_mtl_winding(desc->face_winding); +    pip->mtl.stencil_ref = desc->stencil.ref; + +    /* create vertex-descriptor */ +    MTLVertexDescriptor* vtx_desc = [MTLVertexDescriptor vertexDescriptor]; +    for (NSUInteger attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +        if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +            break; +        } +        SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +        vtx_desc.attributes[attr_index].format = _sg_mtl_vertex_format(a_desc->format); +        vtx_desc.attributes[attr_index].offset = (NSUInteger)a_desc->offset; +        vtx_desc.attributes[attr_index].bufferIndex = (NSUInteger)(a_desc->buffer_index + SG_MAX_SHADERSTAGE_UBS); +        pip->cmn.vertex_layout_valid[a_desc->buffer_index] = true; +    } +    for (NSUInteger layout_index = 0; layout_index < SG_MAX_SHADERSTAGE_BUFFERS; layout_index++) { +        if (pip->cmn.vertex_layout_valid[layout_index]) { +            const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[layout_index]; +            const NSUInteger mtl_vb_slot = layout_index + SG_MAX_SHADERSTAGE_UBS; +            SOKOL_ASSERT(l_desc->stride > 0); +            vtx_desc.layouts[mtl_vb_slot].stride = (NSUInteger)l_desc->stride; +            vtx_desc.layouts[mtl_vb_slot].stepFunction = _sg_mtl_step_function(l_desc->step_func); +            vtx_desc.layouts[mtl_vb_slot].stepRate = (NSUInteger)l_desc->step_rate; +        } +    } + +    /* render-pipeline descriptor */ +    MTLRenderPipelineDescriptor* rp_desc = [[MTLRenderPipelineDescriptor alloc] init]; +    rp_desc.vertexDescriptor = vtx_desc; +    SOKOL_ASSERT(shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX); +    rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); +    SOKOL_ASSERT(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX); +    rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func); +    rp_desc.sampleCount = (NSUInteger)desc->sample_count; +    rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; +    rp_desc.alphaToOneEnabled = NO; +    rp_desc.rasterizationEnabled = YES; +    rp_desc.depthAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format); +    if (desc->depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL) { +        rp_desc.stencilAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format); +    } +    /* FIXME: this only works on macOS 10.13! +    for (int i = 0; i < (SG_MAX_SHADERSTAGE_UBS+SG_MAX_SHADERSTAGE_BUFFERS); i++) { +        rp_desc.vertexBuffers[i].mutability = MTLMutabilityImmutable; +    } +    for (int i = 0; i < SG_MAX_SHADERSTAGE_UBS; i++) { +        rp_desc.fragmentBuffers[i].mutability = MTLMutabilityImmutable; +    } +    */ +    for (NSUInteger i = 0; i < (NSUInteger)desc->color_count; i++) { +        SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); +        const sg_color_state* cs = &desc->colors[i]; +        rp_desc.colorAttachments[i].pixelFormat = _sg_mtl_pixel_format(cs->pixel_format); +        rp_desc.colorAttachments[i].writeMask = _sg_mtl_color_write_mask(cs->write_mask); +        rp_desc.colorAttachments[i].blendingEnabled = cs->blend.enabled; +        rp_desc.colorAttachments[i].alphaBlendOperation = _sg_mtl_blend_op(cs->blend.op_alpha); +        rp_desc.colorAttachments[i].rgbBlendOperation = _sg_mtl_blend_op(cs->blend.op_rgb); +        rp_desc.colorAttachments[i].destinationAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_alpha); +        rp_desc.colorAttachments[i].destinationRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_rgb); +        rp_desc.colorAttachments[i].sourceAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_alpha); +        rp_desc.colorAttachments[i].sourceRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_rgb); +    } +    NSError* err = NULL; +    id<MTLRenderPipelineState> mtl_rps = [_sg.mtl.device newRenderPipelineStateWithDescriptor:rp_desc error:&err]; +    _SG_OBJC_RELEASE(rp_desc); +    if (nil == mtl_rps) { +        SOKOL_ASSERT(err); +        SOKOL_LOG([err.localizedDescription UTF8String]); +        return SG_RESOURCESTATE_FAILED; +    } + +    /* depth-stencil-state */ +    MTLDepthStencilDescriptor* ds_desc = [[MTLDepthStencilDescriptor alloc] init]; +    ds_desc.depthCompareFunction = _sg_mtl_compare_func(desc->depth.compare); +    ds_desc.depthWriteEnabled = desc->depth.write_enabled; +    if (desc->stencil.enabled) { +        const sg_stencil_face_state* sb = &desc->stencil.back; +        ds_desc.backFaceStencil = [[MTLStencilDescriptor alloc] init]; +        ds_desc.backFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sb->fail_op); +        ds_desc.backFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sb->depth_fail_op); +        ds_desc.backFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sb->pass_op); +        ds_desc.backFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sb->compare); +        ds_desc.backFaceStencil.readMask = desc->stencil.read_mask; +        ds_desc.backFaceStencil.writeMask = desc->stencil.write_mask; +        const sg_stencil_face_state* sf = &desc->stencil.front; +        ds_desc.frontFaceStencil = [[MTLStencilDescriptor alloc] init]; +        ds_desc.frontFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sf->fail_op); +        ds_desc.frontFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sf->depth_fail_op); +        ds_desc.frontFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sf->pass_op); +        ds_desc.frontFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sf->compare); +        ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask; +        ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask; +    } +    id<MTLDepthStencilState> mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc]; +    _SG_OBJC_RELEASE(ds_desc); +    pip->mtl.rps = _sg_mtl_add_resource(mtl_rps); +    pip->mtl.dss = _sg_mtl_add_resource(mtl_dss); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    /* it's valid to call release resource with a 'null resource' */ +    _sg_mtl_release_resource(_sg.mtl.frame_index, pip->mtl.rps); +    _sg_mtl_release_resource(_sg.mtl.frame_index, pip->mtl.dss); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pass(_sg_pass_t* pass, _sg_image_t** att_images, const sg_pass_desc* desc) { +    SOKOL_ASSERT(pass && desc); +    SOKOL_ASSERT(att_images && att_images[0]); + +    _sg_pass_common_init(&pass->cmn, desc); + +    /* copy image pointers */ +    const sg_pass_attachment_desc* att_desc; +    for (int i = 0; i < pass->cmn.num_color_atts; i++) { +        att_desc = &desc->color_attachments[i]; +        if (att_desc->image.id != SG_INVALID_ID) { +            SOKOL_ASSERT(att_desc->image.id != SG_INVALID_ID); +            SOKOL_ASSERT(0 == pass->mtl.color_atts[i].image); +            SOKOL_ASSERT(att_images[i] && (att_images[i]->slot.id == att_desc->image.id)); +            SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(att_images[i]->cmn.pixel_format)); +            pass->mtl.color_atts[i].image = att_images[i]; +        } +    } +    SOKOL_ASSERT(0 == pass->mtl.ds_att.image); +    att_desc = &desc->depth_stencil_attachment; +    if (att_desc->image.id != SG_INVALID_ID) { +        const int ds_img_index = SG_MAX_COLOR_ATTACHMENTS; +        SOKOL_ASSERT(att_images[ds_img_index] && (att_images[ds_img_index]->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(att_images[ds_img_index]->cmn.pixel_format)); +        pass->mtl.ds_att.image = att_images[ds_img_index]; +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_pass(_sg_pass_t* pass) { +    SOKOL_ASSERT(pass); +    _SOKOL_UNUSED(pass); +} + +_SOKOL_PRIVATE _sg_image_t* _sg_mtl_pass_color_image(const _sg_pass_t* pass, int index) { +    SOKOL_ASSERT(pass && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); +    /* NOTE: may return null */ +    return pass->mtl.color_atts[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_mtl_pass_ds_image(const _sg_pass_t* pass) { +    /* NOTE: may return null */ +    SOKOL_ASSERT(pass); +    return pass->mtl.ds_att.image; +} + +_SOKOL_PRIVATE void _sg_mtl_begin_pass(_sg_pass_t* pass, const sg_pass_action* action, int w, int h) { +    SOKOL_ASSERT(action); +    SOKOL_ASSERT(!_sg.mtl.in_pass); +    SOKOL_ASSERT(_sg.mtl.cmd_queue); +    SOKOL_ASSERT(nil == _sg.mtl.cmd_encoder); +    SOKOL_ASSERT(_sg.mtl.renderpass_descriptor_cb || _sg.mtl.renderpass_descriptor_userdata_cb); +    _sg.mtl.in_pass = true; +    _sg.mtl.cur_width = w; +    _sg.mtl.cur_height = h; +    _sg_mtl_clear_state_cache(); + +    /* if this is the first pass in the frame, create a command buffer */ +    if (nil == _sg.mtl.cmd_buffer) { +        /* block until the oldest frame in flight has finished */ +        dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); +        _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBufferWithUnretainedReferences]; +    } + +    /* if this is first pass in frame, get uniform buffer base pointer */ +    if (0 == _sg.mtl.cur_ub_base_ptr) { +        _sg.mtl.cur_ub_base_ptr = (uint8_t*)[_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] contents]; +    } + +    /* initialize a render pass descriptor */ +    MTLRenderPassDescriptor* pass_desc = nil; +    if (pass) { +        /* offscreen render pass */ +        pass_desc = [MTLRenderPassDescriptor renderPassDescriptor]; +    } +    else { +        /* default render pass, call user-provided callback to provide render pass descriptor */ +        if (_sg.mtl.renderpass_descriptor_cb) { +            pass_desc = (__bridge MTLRenderPassDescriptor*) _sg.mtl.renderpass_descriptor_cb(); +        } +        else { +            pass_desc = (__bridge MTLRenderPassDescriptor*) _sg.mtl.renderpass_descriptor_userdata_cb(_sg.mtl.user_data); +        } + +    } +    if (pass_desc) { +        _sg.mtl.pass_valid = true; +    } +    else { +        /* default pass descriptor will not be valid if window is minimized, +           don't do any rendering in this case */ +        _sg.mtl.pass_valid = false; +        return; +    } +    if (pass) { +        /* setup pass descriptor for offscreen rendering */ +        SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_VALID); +        for (NSUInteger i = 0; i < (NSUInteger)pass->cmn.num_color_atts; i++) { +            const _sg_pass_attachment_t* cmn_att = &pass->cmn.color_atts[i]; +            const _sg_mtl_attachment_t* mtl_att = &pass->mtl.color_atts[i]; +            const _sg_image_t* att_img = mtl_att->image; +            SOKOL_ASSERT(att_img->slot.state == SG_RESOURCESTATE_VALID); +            SOKOL_ASSERT(att_img->slot.id == cmn_att->image_id.id); +            const bool is_msaa = (att_img->cmn.sample_count > 1); +            pass_desc.colorAttachments[i].loadAction = _sg_mtl_load_action(action->colors[i].action); +            pass_desc.colorAttachments[i].storeAction = is_msaa ? MTLStoreActionMultisampleResolve : MTLStoreActionStore; +            sg_color c = action->colors[i].value; +            pass_desc.colorAttachments[i].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a); +            if (is_msaa) { +                SOKOL_ASSERT(att_img->mtl.msaa_tex != _SG_MTL_INVALID_SLOT_INDEX); +                SOKOL_ASSERT(att_img->mtl.tex[mtl_att->image->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); +                pass_desc.colorAttachments[i].texture = _sg_mtl_id(att_img->mtl.msaa_tex); +                pass_desc.colorAttachments[i].resolveTexture = _sg_mtl_id(att_img->mtl.tex[att_img->cmn.active_slot]); +                pass_desc.colorAttachments[i].resolveLevel = (NSUInteger)cmn_att->mip_level; +                switch (att_img->cmn.type) { +                    case SG_IMAGETYPE_CUBE: +                    case SG_IMAGETYPE_ARRAY: +                        pass_desc.colorAttachments[i].resolveSlice = (NSUInteger)cmn_att->slice; +                        break; +                    case SG_IMAGETYPE_3D: +                        pass_desc.colorAttachments[i].resolveDepthPlane = (NSUInteger)cmn_att->slice; +                        break; +                    default: break; +                } +            } +            else { +                SOKOL_ASSERT(att_img->mtl.tex[att_img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); +                pass_desc.colorAttachments[i].texture = _sg_mtl_id(att_img->mtl.tex[att_img->cmn.active_slot]); +                pass_desc.colorAttachments[i].level = (NSUInteger)cmn_att->mip_level; +                switch (att_img->cmn.type) { +                    case SG_IMAGETYPE_CUBE: +                    case SG_IMAGETYPE_ARRAY: +                        pass_desc.colorAttachments[i].slice = (NSUInteger)cmn_att->slice; +                        break; +                    case SG_IMAGETYPE_3D: +                        pass_desc.colorAttachments[i].depthPlane = (NSUInteger)cmn_att->slice; +                        break; +                    default: break; +                } +            } +        } +        const _sg_image_t* ds_att_img = pass->mtl.ds_att.image; +        if (0 != ds_att_img) { +            SOKOL_ASSERT(ds_att_img->slot.state == SG_RESOURCESTATE_VALID); +            SOKOL_ASSERT(ds_att_img->slot.id == pass->cmn.ds_att.image_id.id); +            SOKOL_ASSERT(ds_att_img->mtl.depth_tex != _SG_MTL_INVALID_SLOT_INDEX); +            pass_desc.depthAttachment.texture = _sg_mtl_id(ds_att_img->mtl.depth_tex); +            pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.action); +            pass_desc.depthAttachment.clearDepth = action->depth.value; +            if (_sg_is_depth_stencil_format(ds_att_img->cmn.pixel_format)) { +                pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_att_img->mtl.depth_tex); +                pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.action); +                pass_desc.stencilAttachment.clearStencil = action->stencil.value; +            } +        } +    } +    else { +        /* setup pass descriptor for default rendering */ +        pass_desc.colorAttachments[0].loadAction = _sg_mtl_load_action(action->colors[0].action); +        sg_color c = action->colors[0].value; +        pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a); +        pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.action); +        pass_desc.depthAttachment.clearDepth = action->depth.value; +        pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.action); +        pass_desc.stencilAttachment.clearStencil = action->stencil.value; +    } + +    /* create a render command encoder, this might return nil if window is minimized */ +    _sg.mtl.cmd_encoder = [_sg.mtl.cmd_buffer renderCommandEncoderWithDescriptor:pass_desc]; +    if (nil == _sg.mtl.cmd_encoder) { +        _sg.mtl.pass_valid = false; +        return; +    } + +    /* bind the global uniform buffer, this only happens once per pass */ +    _sg_mtl_bind_uniform_buffers(); +} + +_SOKOL_PRIVATE void _sg_mtl_end_pass(void) { +    SOKOL_ASSERT(_sg.mtl.in_pass); +    _sg.mtl.in_pass = false; +    _sg.mtl.pass_valid = false; +    if (nil != _sg.mtl.cmd_encoder) { +        [_sg.mtl.cmd_encoder endEncoding]; +        /* NOTE: MTLRenderCommandEncoder is autoreleased */ +        _sg.mtl.cmd_encoder = nil; +    } +} + +_SOKOL_PRIVATE void _sg_mtl_commit(void) { +    SOKOL_ASSERT(!_sg.mtl.in_pass); +    SOKOL_ASSERT(!_sg.mtl.pass_valid); +    SOKOL_ASSERT(_sg.mtl.drawable_cb || _sg.mtl.drawable_userdata_cb); +    SOKOL_ASSERT(nil == _sg.mtl.cmd_encoder); +    SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + +    #if defined(_SG_TARGET_MACOS) +    [_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] didModifyRange:NSMakeRange(0, (NSUInteger)_sg.mtl.cur_ub_offset)]; +    #endif + +    /* present, commit and signal semaphore when done */ +    id<MTLDrawable> cur_drawable = nil; +    if (_sg.mtl.drawable_cb) { +        cur_drawable = (__bridge id<MTLDrawable>) _sg.mtl.drawable_cb(); +    } +    else { +        cur_drawable = (__bridge id<MTLDrawable>) _sg.mtl.drawable_userdata_cb(_sg.mtl.user_data); +    } +    [_sg.mtl.cmd_buffer presentDrawable:cur_drawable]; +    [_sg.mtl.cmd_buffer addCompletedHandler:^(id<MTLCommandBuffer> cmd_buffer) { +        _SOKOL_UNUSED(cmd_buffer); +        dispatch_semaphore_signal(_sg.mtl.sem); +    }]; +    [_sg.mtl.cmd_buffer commit]; + +    /* garbage-collect resources pending for release */ +    _sg_mtl_garbage_collect(_sg.mtl.frame_index); + +    /* rotate uniform buffer slot */ +    if (++_sg.mtl.cur_frame_rotate_index >= SG_NUM_INFLIGHT_FRAMES) { +        _sg.mtl.cur_frame_rotate_index = 0; +    } +    _sg.mtl.frame_index++; +    _sg.mtl.cur_ub_offset = 0; +    _sg.mtl.cur_ub_base_ptr = 0; +    /* NOTE: MTLCommandBuffer is autoreleased */ +    _sg.mtl.cmd_buffer = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.mtl.in_pass); +    if (!_sg.mtl.pass_valid) { +        return; +    } +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); +    MTLViewport vp; +    vp.originX = (double) x; +    vp.originY = (double) (origin_top_left ? y : (_sg.mtl.cur_height - (y + h))); +    vp.width   = (double) w; +    vp.height  = (double) h; +    vp.znear   = 0.0; +    vp.zfar    = 1.0; +    [_sg.mtl.cmd_encoder setViewport:vp]; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.mtl.in_pass); +    if (!_sg.mtl.pass_valid) { +        return; +    } +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); +    /* clip against framebuffer rect */ +    x = _sg_min(_sg_max(0, x), _sg.mtl.cur_width-1); +    y = _sg_min(_sg_max(0, y), _sg.mtl.cur_height-1); +    if ((x + w) > _sg.mtl.cur_width) { +        w = _sg.mtl.cur_width - x; +    } +    if ((y + h) > _sg.mtl.cur_height) { +        h = _sg.mtl.cur_height - y; +    } +    w = _sg_max(w, 1); +    h = _sg_max(h, 1); + +    MTLScissorRect r; +    r.x = (NSUInteger)x; +    r.y = (NSUInteger) (origin_top_left ? y : (_sg.mtl.cur_height - (y + h))); +    r.width = (NSUInteger)w; +    r.height = (NSUInteger)h; +    [_sg.mtl.cmd_encoder setScissorRect:r]; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); +    SOKOL_ASSERT(_sg.mtl.in_pass); +    if (!_sg.mtl.pass_valid) { +        return; +    } +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); + +    if ((_sg.mtl.state_cache.cur_pipeline != pip) || (_sg.mtl.state_cache.cur_pipeline_id.id != pip->slot.id)) { +        _sg.mtl.state_cache.cur_pipeline = pip; +        _sg.mtl.state_cache.cur_pipeline_id.id = pip->slot.id; +        sg_color c = pip->cmn.blend_color; +        [_sg.mtl.cmd_encoder setBlendColorRed:c.r green:c.g blue:c.b alpha:c.a]; +        [_sg.mtl.cmd_encoder setCullMode:pip->mtl.cull_mode]; +        [_sg.mtl.cmd_encoder setFrontFacingWinding:pip->mtl.winding]; +        [_sg.mtl.cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref]; +        [_sg.mtl.cmd_encoder setDepthBias:pip->cmn.depth_bias slopeScale:pip->cmn.depth_bias_slope_scale clamp:pip->cmn.depth_bias_clamp]; +        SOKOL_ASSERT(pip->mtl.rps != _SG_MTL_INVALID_SLOT_INDEX); +        [_sg.mtl.cmd_encoder setRenderPipelineState:_sg_mtl_id(pip->mtl.rps)]; +        SOKOL_ASSERT(pip->mtl.dss != _SG_MTL_INVALID_SLOT_INDEX); +        [_sg.mtl.cmd_encoder setDepthStencilState:_sg_mtl_id(pip->mtl.dss)]; +    } +} + +_SOKOL_PRIVATE void _sg_mtl_apply_bindings( +    _sg_pipeline_t* pip, +    _sg_buffer_t** vbs, const int* vb_offsets, int num_vbs, +    _sg_buffer_t* ib, int ib_offset, +    _sg_image_t** vs_imgs, int num_vs_imgs, +    _sg_image_t** fs_imgs, int num_fs_imgs) +{ +    _SOKOL_UNUSED(pip); +    SOKOL_ASSERT(_sg.mtl.in_pass); +    if (!_sg.mtl.pass_valid) { +        return; +    } +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); + +    /* store index buffer binding, this will be needed later in sg_draw() */ +    _sg.mtl.state_cache.cur_indexbuffer = ib; +    _sg.mtl.state_cache.cur_indexbuffer_offset = ib_offset; +    if (ib) { +        SOKOL_ASSERT(pip->cmn.index_type != SG_INDEXTYPE_NONE); +        _sg.mtl.state_cache.cur_indexbuffer_id.id = ib->slot.id; +    } +    else { +        SOKOL_ASSERT(pip->cmn.index_type == SG_INDEXTYPE_NONE); +        _sg.mtl.state_cache.cur_indexbuffer_id.id = SG_INVALID_ID; +    } + +    /* apply vertex buffers */ +    NSUInteger slot; +    for (slot = 0; slot < (NSUInteger)num_vbs; slot++) { +        const _sg_buffer_t* vb = vbs[slot]; +        if ((_sg.mtl.state_cache.cur_vertexbuffers[slot] != vb) || +            (_sg.mtl.state_cache.cur_vertexbuffer_offsets[slot] != vb_offsets[slot]) || +            (_sg.mtl.state_cache.cur_vertexbuffer_ids[slot].id != vb->slot.id)) +        { +            _sg.mtl.state_cache.cur_vertexbuffers[slot] = vb; +            _sg.mtl.state_cache.cur_vertexbuffer_offsets[slot] = vb_offsets[slot]; +            _sg.mtl.state_cache.cur_vertexbuffer_ids[slot].id = vb->slot.id; +            const NSUInteger mtl_slot = SG_MAX_SHADERSTAGE_UBS + slot; +            SOKOL_ASSERT(vb->mtl.buf[vb->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); +            [_sg.mtl.cmd_encoder setVertexBuffer:_sg_mtl_id(vb->mtl.buf[vb->cmn.active_slot]) +                offset:(NSUInteger)vb_offsets[slot] +                atIndex:mtl_slot]; +        } +    } + +    /* apply vertex shader images */ +    for (slot = 0; slot < (NSUInteger)num_vs_imgs; slot++) { +        const _sg_image_t* img = vs_imgs[slot]; +        if ((_sg.mtl.state_cache.cur_vs_images[slot] != img) || (_sg.mtl.state_cache.cur_vs_image_ids[slot].id != img->slot.id)) { +            _sg.mtl.state_cache.cur_vs_images[slot] = img; +            _sg.mtl.state_cache.cur_vs_image_ids[slot].id = img->slot.id; +            SOKOL_ASSERT(img->mtl.tex[img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); +            [_sg.mtl.cmd_encoder setVertexTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:slot]; +            SOKOL_ASSERT(img->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX); +            [_sg.mtl.cmd_encoder setVertexSamplerState:_sg_mtl_id(img->mtl.sampler_state) atIndex:slot]; +        } +    } + +    /* apply fragment shader images */ +    for (slot = 0; slot < (NSUInteger)num_fs_imgs; slot++) { +        const _sg_image_t* img = fs_imgs[slot]; +        if ((_sg.mtl.state_cache.cur_fs_images[slot] != img) || (_sg.mtl.state_cache.cur_fs_image_ids[slot].id != img->slot.id)) { +            _sg.mtl.state_cache.cur_fs_images[slot] = img; +            _sg.mtl.state_cache.cur_fs_image_ids[slot].id = img->slot.id; +            SOKOL_ASSERT(img->mtl.tex[img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); +            [_sg.mtl.cmd_encoder setFragmentTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:slot]; +            SOKOL_ASSERT(img->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX); +            [_sg.mtl.cmd_encoder setFragmentSamplerState:_sg_mtl_id(img->mtl.sampler_state) atIndex:slot]; +        } +    } +} + +_SOKOL_PRIVATE void _sg_mtl_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    SOKOL_ASSERT(_sg.mtl.in_pass); +    if (!_sg.mtl.pass_valid) { +        return; +    } +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); +    SOKOL_ASSERT(((size_t)_sg.mtl.cur_ub_offset + data->size) <= (size_t)_sg.mtl.ub_size); +    SOKOL_ASSERT((_sg.mtl.cur_ub_offset & (_SG_MTL_UB_ALIGN-1)) == 0); +    SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline && _sg.mtl.state_cache.cur_pipeline->shader); +    SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id); +    SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->shader->slot.id == _sg.mtl.state_cache.cur_pipeline->cmn.shader_id.id); +    SOKOL_ASSERT(ub_index < _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); +    SOKOL_ASSERT(data->size <= _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); + +    /* copy to global uniform buffer, record offset into cmd encoder, and advance offset */ +    uint8_t* dst = &_sg.mtl.cur_ub_base_ptr[_sg.mtl.cur_ub_offset]; +    memcpy(dst, data->ptr, data->size); +    if (stage_index == SG_SHADERSTAGE_VS) { +        [_sg.mtl.cmd_encoder setVertexBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:(NSUInteger)ub_index]; +    } +    else { +        [_sg.mtl.cmd_encoder setFragmentBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:(NSUInteger)ub_index]; +    } +    _sg.mtl.cur_ub_offset = _sg_roundup(_sg.mtl.cur_ub_offset + (int)data->size, _SG_MTL_UB_ALIGN); +} + +_SOKOL_PRIVATE void _sg_mtl_draw(int base_element, int num_elements, int num_instances) { +    SOKOL_ASSERT(_sg.mtl.in_pass); +    if (!_sg.mtl.pass_valid) { +        return; +    } +    SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); +    SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline && (_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id)); +    if (SG_INDEXTYPE_NONE != _sg.mtl.state_cache.cur_pipeline->cmn.index_type) { +        /* indexed rendering */ +        SOKOL_ASSERT(_sg.mtl.state_cache.cur_indexbuffer && (_sg.mtl.state_cache.cur_indexbuffer->slot.id == _sg.mtl.state_cache.cur_indexbuffer_id.id)); +        const _sg_buffer_t* ib = _sg.mtl.state_cache.cur_indexbuffer; +        SOKOL_ASSERT(ib->mtl.buf[ib->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); +        const NSUInteger index_buffer_offset = (NSUInteger) (_sg.mtl.state_cache.cur_indexbuffer_offset + base_element * _sg.mtl.state_cache.cur_pipeline->mtl.index_size); +        [_sg.mtl.cmd_encoder drawIndexedPrimitives:_sg.mtl.state_cache.cur_pipeline->mtl.prim_type +            indexCount:(NSUInteger)num_elements +            indexType:_sg.mtl.state_cache.cur_pipeline->mtl.index_type +            indexBuffer:_sg_mtl_id(ib->mtl.buf[ib->cmn.active_slot]) +            indexBufferOffset:index_buffer_offset +            instanceCount:(NSUInteger)num_instances]; +    } +    else { +        /* non-indexed rendering */ +        [_sg.mtl.cmd_encoder drawPrimitives:_sg.mtl.state_cache.cur_pipeline->mtl.prim_type +            vertexStart:(NSUInteger)base_element +            vertexCount:(NSUInteger)num_elements +            instanceCount:(NSUInteger)num_instances]; +    } +} + +_SOKOL_PRIVATE void _sg_mtl_update_buffer(_sg_buffer_t* buf, const sg_range* data) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    if (++buf->cmn.active_slot >= buf->cmn.num_slots) { +        buf->cmn.active_slot = 0; +    } +    __unsafe_unretained id<MTLBuffer> mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]); +    void* dst_ptr = [mtl_buf contents]; +    memcpy(dst_ptr, data->ptr, data->size); +    #if defined(_SG_TARGET_MACOS) +    [mtl_buf didModifyRange:NSMakeRange(0, data->size)]; +    #endif +} + +_SOKOL_PRIVATE int _sg_mtl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    if (new_frame) { +        if (++buf->cmn.active_slot >= buf->cmn.num_slots) { +            buf->cmn.active_slot = 0; +        } +    } +    __unsafe_unretained id<MTLBuffer> mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]); +    uint8_t* dst_ptr = (uint8_t*) [mtl_buf contents]; +    dst_ptr += buf->cmn.append_pos; +    memcpy(dst_ptr, data->ptr, data->size); +    #if defined(_SG_TARGET_MACOS) +    [mtl_buf didModifyRange:NSMakeRange((NSUInteger)buf->cmn.append_pos, (NSUInteger)data->size)]; +    #endif +    /* NOTE: this is a requirement from WebGPU, but we want identical behaviour across all backends */ +    return _sg_roundup((int)data->size, 4); +} + +_SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data* data) { +    SOKOL_ASSERT(img && data); +    if (++img->cmn.active_slot >= img->cmn.num_slots) { +        img->cmn.active_slot = 0; +    } +    __unsafe_unretained id<MTLTexture> mtl_tex = _sg_mtl_id(img->mtl.tex[img->cmn.active_slot]); +    _sg_mtl_copy_image_data(img, mtl_tex, data); +} + +/*== WEBGPU BACKEND IMPLEMENTATION ===========================================*/ +#elif defined(SOKOL_WGPU) + +_SOKOL_PRIVATE WGPUBufferUsageFlags _sg_wgpu_buffer_usage(sg_buffer_type t, sg_usage u) { +    WGPUBufferUsageFlags res = 0; +    if (SG_BUFFERTYPE_VERTEXBUFFER == t) { +        res |= WGPUBufferUsage_Vertex; +    } +    else { +        res |= WGPUBufferUsage_Index; +    } +    if (SG_USAGE_IMMUTABLE != u) { +        res |= WGPUBufferUsage_CopyDst; +    } +    return res; +} + +_SOKOL_PRIVATE WGPULoadOp _sg_wgpu_load_op(sg_action a) { +    switch (a) { +        case SG_ACTION_CLEAR: +        case SG_ACTION_DONTCARE: +            return WGPULoadOp_Clear; +        case SG_ACTION_LOAD: +            return WGPULoadOp_Load; +        default: +            SOKOL_UNREACHABLE; +            return (WGPULoadOp)0; +    } +} + +_SOKOL_PRIVATE WGPUTextureViewDimension _sg_wgpu_tex_viewdim(sg_image_type t) { +    switch (t) { +        case SG_IMAGETYPE_2D:       return WGPUTextureViewDimension_2D; +        case SG_IMAGETYPE_CUBE:     return WGPUTextureViewDimension_Cube; +        case SG_IMAGETYPE_3D:       return WGPUTextureViewDimension_3D; +        case SG_IMAGETYPE_ARRAY:    return WGPUTextureViewDimension_2DArray; +        default: SOKOL_UNREACHABLE; return WGPUTextureViewDimension_Force32; +    } +} + +_SOKOL_PRIVATE WGPUTextureComponentType _sg_wgpu_tex_comptype(sg_sampler_type t) { +    switch (t) { +        case SG_SAMPLERTYPE_FLOAT:  return WGPUTextureComponentType_Float; +        case SG_SAMPLERTYPE_SINT:   return WGPUTextureComponentType_Sint; +        case SG_SAMPLERTYPE_UINT:   return WGPUTextureComponentType_Uint; +        default: SOKOL_UNREACHABLE; return WGPUTextureComponentType_Force32; +    } +} + +_SOKOL_PRIVATE WGPUTextureDimension _sg_wgpu_tex_dim(sg_image_type t) { +    if (SG_IMAGETYPE_3D == t) { +        return WGPUTextureDimension_3D; +    } +    else { +        return WGPUTextureDimension_2D; +    } +} + +_SOKOL_PRIVATE WGPUAddressMode _sg_wgpu_sampler_addrmode(sg_wrap m) { +    switch (m) { +        case SG_WRAP_REPEAT: +            return WGPUAddressMode_Repeat; +        case SG_WRAP_CLAMP_TO_EDGE: +        case SG_WRAP_CLAMP_TO_BORDER: +            return WGPUAddressMode_ClampToEdge; +        case SG_WRAP_MIRRORED_REPEAT: +            return WGPUAddressMode_MirrorRepeat; +        default: +            SOKOL_UNREACHABLE; +            return WGPUAddressMode_Force32; +    } +} + +_SOKOL_PRIVATE WGPUFilterMode _sg_wgpu_sampler_minmagfilter(sg_filter f) { +    switch (f) { +        case SG_FILTER_NEAREST: +        case SG_FILTER_NEAREST_MIPMAP_NEAREST: +        case SG_FILTER_NEAREST_MIPMAP_LINEAR: +            return WGPUFilterMode_Nearest; +        case SG_FILTER_LINEAR: +        case SG_FILTER_LINEAR_MIPMAP_NEAREST: +        case SG_FILTER_LINEAR_MIPMAP_LINEAR: +            return WGPUFilterMode_Linear; +        default: +            SOKOL_UNREACHABLE; +            return WGPUFilterMode_Force32; +    } +} + +_SOKOL_PRIVATE WGPUFilterMode _sg_wgpu_sampler_mipfilter(sg_filter f) { +    switch (f) { +        case SG_FILTER_NEAREST: +        case SG_FILTER_LINEAR: +        case SG_FILTER_NEAREST_MIPMAP_NEAREST: +        case SG_FILTER_LINEAR_MIPMAP_NEAREST: +            return WGPUFilterMode_Nearest; +        case SG_FILTER_NEAREST_MIPMAP_LINEAR: +        case SG_FILTER_LINEAR_MIPMAP_LINEAR: +            return WGPUFilterMode_Linear; +        default: +            SOKOL_UNREACHABLE; +            return WGPUFilterMode_Force32; +    } +} + +_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_indexformat(sg_index_type t) { +    /* NOTE: there's no WGPUIndexFormat_None */ +    return (t == SG_INDEXTYPE_UINT16) ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32; +} + +_SOKOL_PRIVATE WGPUInputStepMode _sg_wgpu_stepmode(sg_vertex_step s) { +    return (s == SG_VERTEXSTEP_PER_VERTEX) ? WGPUInputStepMode_Vertex : WGPUInputStepMode_Instance; +} + +_SOKOL_PRIVATE WGPUVertexFormat _sg_wgpu_vertexformat(sg_vertex_format f) { +    switch (f) { +        case SG_VERTEXFORMAT_FLOAT:         return WGPUVertexFormat_Float; +        case SG_VERTEXFORMAT_FLOAT2:        return WGPUVertexFormat_Float2; +        case SG_VERTEXFORMAT_FLOAT3:        return WGPUVertexFormat_Float3; +        case SG_VERTEXFORMAT_FLOAT4:        return WGPUVertexFormat_Float4; +        case SG_VERTEXFORMAT_BYTE4:         return WGPUVertexFormat_Char4; +        case SG_VERTEXFORMAT_BYTE4N:        return WGPUVertexFormat_Char4Norm; +        case SG_VERTEXFORMAT_UBYTE4:        return WGPUVertexFormat_UChar4; +        case SG_VERTEXFORMAT_UBYTE4N:       return WGPUVertexFormat_UChar4Norm; +        case SG_VERTEXFORMAT_SHORT2:        return WGPUVertexFormat_Short2; +        case SG_VERTEXFORMAT_SHORT2N:       return WGPUVertexFormat_Short2Norm; +        case SG_VERTEXFORMAT_USHORT2N:      return WGPUVertexFormat_UShort2Norm; +        case SG_VERTEXFORMAT_SHORT4:        return WGPUVertexFormat_Short4; +        case SG_VERTEXFORMAT_SHORT4N:       return WGPUVertexFormat_Short4Norm; +        case SG_VERTEXFORMAT_USHORT4N:      return WGPUVertexFormat_UShort4Norm; +        /* FIXME! UINT10_N2 */ +        case SG_VERTEXFORMAT_UINT10_N2: +        default: +            SOKOL_UNREACHABLE; +            return WGPUVertexFormat_Force32; +    } +} + +_SOKOL_PRIVATE WGPUPrimitiveTopology _sg_wgpu_topology(sg_primitive_type t) { +    switch (t) { +        case SG_PRIMITIVETYPE_POINTS:           return WGPUPrimitiveTopology_PointList; +        case SG_PRIMITIVETYPE_LINES:            return WGPUPrimitiveTopology_LineList; +        case SG_PRIMITIVETYPE_LINE_STRIP:       return WGPUPrimitiveTopology_LineStrip; +        case SG_PRIMITIVETYPE_TRIANGLES:        return WGPUPrimitiveTopology_TriangleList; +        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return WGPUPrimitiveTopology_TriangleStrip; +        default: SOKOL_UNREACHABLE; return WGPUPrimitiveTopology_Force32; +    } +} + +_SOKOL_PRIVATE WGPUFrontFace _sg_wgpu_frontface(sg_face_winding fw) { +    return (fw == SG_FACEWINDING_CCW) ? WGPUFrontFace_CCW : WGPUFrontFace_CW; +} + +_SOKOL_PRIVATE WGPUCullMode _sg_wgpu_cullmode(sg_cull_mode cm) { +    switch (cm) { +        case SG_CULLMODE_NONE:      return WGPUCullMode_None; +        case SG_CULLMODE_FRONT:     return WGPUCullMode_Front; +        case SG_CULLMODE_BACK:      return WGPUCullMode_Back; +        default: SOKOL_UNREACHABLE; return WGPUCullMode_Force32; +    } +} + +_SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) { +    switch (p) { +        case SG_PIXELFORMAT_NONE:           return WGPUTextureFormat_Undefined; +        case SG_PIXELFORMAT_R8:             return WGPUTextureFormat_R8Unorm; +        case SG_PIXELFORMAT_R8SN:           return WGPUTextureFormat_R8Snorm; +        case SG_PIXELFORMAT_R8UI:           return WGPUTextureFormat_R8Uint; +        case SG_PIXELFORMAT_R8SI:           return WGPUTextureFormat_R8Sint; +        case SG_PIXELFORMAT_R16UI:          return WGPUTextureFormat_R16Uint; +        case SG_PIXELFORMAT_R16SI:          return WGPUTextureFormat_R16Sint; +        case SG_PIXELFORMAT_R16F:           return WGPUTextureFormat_R16Float; +        case SG_PIXELFORMAT_RG8:            return WGPUTextureFormat_RG8Unorm; +        case SG_PIXELFORMAT_RG8SN:          return WGPUTextureFormat_RG8Snorm; +        case SG_PIXELFORMAT_RG8UI:          return WGPUTextureFormat_RG8Uint; +        case SG_PIXELFORMAT_RG8SI:          return WGPUTextureFormat_RG8Sint; +        case SG_PIXELFORMAT_R32UI:          return WGPUTextureFormat_R32Uint; +        case SG_PIXELFORMAT_R32SI:          return WGPUTextureFormat_R32Sint; +        case SG_PIXELFORMAT_R32F:           return WGPUTextureFormat_R32Float; +        case SG_PIXELFORMAT_RG16UI:         return WGPUTextureFormat_RG16Uint; +        case SG_PIXELFORMAT_RG16SI:         return WGPUTextureFormat_RG16Sint; +        case SG_PIXELFORMAT_RG16F:          return WGPUTextureFormat_RG16Float; +        case SG_PIXELFORMAT_RGBA8:          return WGPUTextureFormat_RGBA8Unorm; +        case SG_PIXELFORMAT_RGBA8SN:        return WGPUTextureFormat_RGBA8Snorm; +        case SG_PIXELFORMAT_RGBA8UI:        return WGPUTextureFormat_RGBA8Uint; +        case SG_PIXELFORMAT_RGBA8SI:        return WGPUTextureFormat_RGBA8Sint; +        case SG_PIXELFORMAT_BGRA8:          return WGPUTextureFormat_BGRA8Unorm; +        case SG_PIXELFORMAT_RGB10A2:        return WGPUTextureFormat_RGB10A2Unorm; +        case SG_PIXELFORMAT_RG11B10F:       return WGPUTextureFormat_RG11B10Float; +        case SG_PIXELFORMAT_RG32UI:         return WGPUTextureFormat_RG32Uint; +        case SG_PIXELFORMAT_RG32SI:         return WGPUTextureFormat_RG32Sint; +        case SG_PIXELFORMAT_RG32F:          return WGPUTextureFormat_RG32Float; +        case SG_PIXELFORMAT_RGBA16UI:       return WGPUTextureFormat_RGBA16Uint; +        case SG_PIXELFORMAT_RGBA16SI:       return WGPUTextureFormat_RGBA16Sint; +        case SG_PIXELFORMAT_RGBA16F:        return WGPUTextureFormat_RGBA16Float; +        case SG_PIXELFORMAT_RGBA32UI:       return WGPUTextureFormat_RGBA32Uint; +        case SG_PIXELFORMAT_RGBA32SI:       return WGPUTextureFormat_RGBA32Sint; +        case SG_PIXELFORMAT_RGBA32F:        return WGPUTextureFormat_RGBA32Float; +        case SG_PIXELFORMAT_DEPTH:          return WGPUTextureFormat_Depth24Plus; +        case SG_PIXELFORMAT_DEPTH_STENCIL:  return WGPUTextureFormat_Depth24PlusStencil8; +        case SG_PIXELFORMAT_BC1_RGBA:       return WGPUTextureFormat_BC1RGBAUnorm; +        case SG_PIXELFORMAT_BC2_RGBA:       return WGPUTextureFormat_BC2RGBAUnorm; +        case SG_PIXELFORMAT_BC3_RGBA:       return WGPUTextureFormat_BC3RGBAUnorm; +        case SG_PIXELFORMAT_BC4_R:          return WGPUTextureFormat_BC4RUnorm; +        case SG_PIXELFORMAT_BC4_RSN:        return WGPUTextureFormat_BC4RSnorm; +        case SG_PIXELFORMAT_BC5_RG:         return WGPUTextureFormat_BC5RGUnorm; +        case SG_PIXELFORMAT_BC5_RGSN:       return WGPUTextureFormat_BC5RGSnorm; +        case SG_PIXELFORMAT_BC6H_RGBF:      return WGPUTextureFormat_BC6HRGBSfloat; +        case SG_PIXELFORMAT_BC6H_RGBUF:     return WGPUTextureFormat_BC6HRGBUfloat; +        case SG_PIXELFORMAT_BC7_RGBA:       return WGPUTextureFormat_BC7RGBAUnorm; + +        /* NOT SUPPORTED */ +        case SG_PIXELFORMAT_R16: +        case SG_PIXELFORMAT_R16SN: +        case SG_PIXELFORMAT_RG16: +        case SG_PIXELFORMAT_RG16SN: +        case SG_PIXELFORMAT_RGBA16: +        case SG_PIXELFORMAT_RGBA16SN: +        case SG_PIXELFORMAT_PVRTC_RGB_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGB_4BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: +        case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: +        case SG_PIXELFORMAT_ETC2_RGB8: +        case SG_PIXELFORMAT_ETC2_RGB8A1: +        case SG_PIXELFORMAT_ETC2_RGBA8: +        case SG_PIXELFORMAT_ETC2_RG11: +        case SG_PIXELFORMAT_ETC2_RG11SN: +        default: +            SOKOL_UNREACHABLE; +            return WGPUTextureFormat_Force32; +    } +} + +/* +FIXME ??? this isn't needed anywhere? +_SOKOL_PRIVATE WGPUTextureAspect _sg_wgpu_texture_aspect(sg_pixel_format fmt) { +    if (_sg_is_valid_rendertarget_depth_format(fmt)) { +        if (!_sg_is_depth_stencil_format(fmt)) { +            return WGPUTextureAspect_DepthOnly; +        } +    } +    return WGPUTextureAspect_All; +} +*/ + +_SOKOL_PRIVATE WGPUCompareFunction _sg_wgpu_comparefunc(sg_compare_func f) { +    switch (f) { +        case SG_COMPAREFUNC_NEVER:          return WGPUCompareFunction_Never; +        case SG_COMPAREFUNC_LESS:           return WGPUCompareFunction_Less; +        case SG_COMPAREFUNC_EQUAL:          return WGPUCompareFunction_Equal; +        case SG_COMPAREFUNC_LESS_EQUAL:     return WGPUCompareFunction_LessEqual; +        case SG_COMPAREFUNC_GREATER:        return WGPUCompareFunction_Greater; +        case SG_COMPAREFUNC_NOT_EQUAL:      return WGPUCompareFunction_NotEqual; +        case SG_COMPAREFUNC_GREATER_EQUAL:  return WGPUCompareFunction_GreaterEqual; +        case SG_COMPAREFUNC_ALWAYS:         return WGPUCompareFunction_Always; +        default: SOKOL_UNREACHABLE; return WGPUCompareFunction_Force32; +    } +} + +_SOKOL_PRIVATE WGPUStencilOperation _sg_wgpu_stencilop(sg_stencil_op op) { +    switch (op) { +        case SG_STENCILOP_KEEP:         return WGPUStencilOperation_Keep; +        case SG_STENCILOP_ZERO:         return WGPUStencilOperation_Zero; +        case SG_STENCILOP_REPLACE:      return WGPUStencilOperation_Replace; +        case SG_STENCILOP_INCR_CLAMP:   return WGPUStencilOperation_IncrementClamp; +        case SG_STENCILOP_DECR_CLAMP:   return WGPUStencilOperation_DecrementClamp; +        case SG_STENCILOP_INVERT:       return WGPUStencilOperation_Invert; +        case SG_STENCILOP_INCR_WRAP:    return WGPUStencilOperation_IncrementWrap; +        case SG_STENCILOP_DECR_WRAP:    return WGPUStencilOperation_DecrementWrap; +        default: SOKOL_UNREACHABLE; return WGPUStencilOperation_Force32; +    } +} + +_SOKOL_PRIVATE WGPUBlendOperation _sg_wgpu_blendop(sg_blend_op op) { +    switch (op) { +        case SG_BLENDOP_ADD:                return WGPUBlendOperation_Add; +        case SG_BLENDOP_SUBTRACT:           return WGPUBlendOperation_Subtract; +        case SG_BLENDOP_REVERSE_SUBTRACT:   return WGPUBlendOperation_ReverseSubtract; +        default: SOKOL_UNREACHABLE; return WGPUBlendOperation_Force32; +    } +} + +_SOKOL_PRIVATE WGPUBlendFactor _sg_wgpu_blendfactor(sg_blend_factor f) { +    switch (f) { +        case SG_BLENDFACTOR_ZERO:                   return WGPUBlendFactor_Zero; +        case SG_BLENDFACTOR_ONE:                    return WGPUBlendFactor_One; +        case SG_BLENDFACTOR_SRC_COLOR:              return WGPUBlendFactor_SrcColor; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return WGPUBlendFactor_OneMinusSrcColor; +        case SG_BLENDFACTOR_SRC_ALPHA:              return WGPUBlendFactor_SrcAlpha; +        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return WGPUBlendFactor_OneMinusSrcAlpha; +        case SG_BLENDFACTOR_DST_COLOR:              return WGPUBlendFactor_DstColor; +        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return WGPUBlendFactor_OneMinusDstColor; +        case SG_BLENDFACTOR_DST_ALPHA:              return WGPUBlendFactor_DstAlpha; +        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return WGPUBlendFactor_OneMinusDstAlpha; +        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return WGPUBlendFactor_SrcAlphaSaturated; +        case SG_BLENDFACTOR_BLEND_COLOR:            return WGPUBlendFactor_BlendColor; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return WGPUBlendFactor_OneMinusBlendColor; +        /* FIXME: separate blend alpha value not supported? */ +        case SG_BLENDFACTOR_BLEND_ALPHA:            return WGPUBlendFactor_BlendColor; +        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return WGPUBlendFactor_OneMinusBlendColor; +        default: +            SOKOL_UNREACHABLE; return WGPUBlendFactor_Force32; +    } +} + +_SOKOL_PRIVATE WGPUColorWriteMaskFlags _sg_wgpu_colorwritemask(uint8_t m) { +    WGPUColorWriteMaskFlags res = 0; +    if (0 != (m & SG_COLORMASK_R)) { +        res |= WGPUColorWriteMask_Red; +    } +    if (0 != (m & SG_COLORMASK_G)) { +        res |= WGPUColorWriteMask_Green; +    } +    if (0 != (m & SG_COLORMASK_B)) { +        res |= WGPUColorWriteMask_Blue; +    } +    if (0 != (m & SG_COLORMASK_A)) { +        res |= WGPUColorWriteMask_Alpha; +    } +    return res; +} + +_SOKOL_PRIVATE void _sg_wgpu_init_caps(void) { +    _sg.backend = SG_BACKEND_WGPU; +    _sg.features.instancing = true; +    _sg.features.origin_top_left = true; +    _sg.features.multiple_render_targets = true; +    _sg.features.msaa_render_targets = true; +    _sg.features.imagetype_3d = true; +    _sg.features.imagetype_array = true; +    _sg.features.image_clamp_to_border = false; +    _sg.features.mrt_independent_blend_state = true; +    _sg.features.mrt_independent_write_mask = true; + +    /* FIXME: max images size??? */ +    _sg.limits.max_image_size_2d = 8 * 1024; +    _sg.limits.max_image_size_cube = 8 * 1024; +    _sg.limits.max_image_size_3d = 2 * 1024; +    _sg.limits.max_image_size_array = 8 * 1024; +    _sg.limits.max_image_array_layers = 2 * 1024; +    _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); +    _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R32F]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); +    _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); +    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); +    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); +    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); +    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); +    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); + +    /* FIXME FIXME FIXME: need to check if BC texture compression is +        actually supported, currently the WebGPU C-API doesn't allow this +    */ +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); +    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); +} + +/* +    WGPU uniform buffer pool implementation: + +    At start of frame, a mapped buffer is grabbed from the pool, +    or a new buffer is created if there is no mapped buffer available. + +    At end of frame, the current buffer is unmapped before queue submit, +    and async-mapped immediately again. + +    UNIFORM BUFFER FIXME: + +    - As per WebGPU spec, it should be possible to create a Uniform|MapWrite +      buffer, but this isn't currently allowed in Dawn. +*/ +_SOKOL_PRIVATE void _sg_wgpu_ubpool_init(const sg_desc* desc) { + +    /* Add the max-uniform-update size (64 KB) to the requested buffer size, +       this is to prevent validation errors in the WebGPU implementation +       if the entire buffer size is used per frame. 64 KB is the allowed +       max uniform update size on NVIDIA +    */ +    _sg.wgpu.ub.num_bytes = desc->uniform_buffer_size + _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE; + +    WGPUBufferDescriptor ub_desc; +    memset(&ub_desc, 0, sizeof(ub_desc)); +    ub_desc.size = _sg.wgpu.ub.num_bytes; +    ub_desc.usage = WGPUBufferUsage_Uniform|WGPUBufferUsage_CopyDst; +    _sg.wgpu.ub.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &ub_desc); +    SOKOL_ASSERT(_sg.wgpu.ub.buf); + +    WGPUBindGroupLayoutBinding ub_bglb_desc[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; +    memset(ub_bglb_desc, 0, sizeof(ub_bglb_desc)); +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        WGPUShaderStage vis = (stage_index == SG_SHADERSTAGE_VS) ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment; +        for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { +            int bind_index = stage_index * SG_MAX_SHADERSTAGE_UBS + ub_index; +            ub_bglb_desc[stage_index][ub_index].binding = bind_index; +            ub_bglb_desc[stage_index][ub_index].visibility = vis; +            ub_bglb_desc[stage_index][ub_index].type = WGPUBindingType_UniformBuffer; +            ub_bglb_desc[stage_index][ub_index].hasDynamicOffset = true; +        } +    } + +    WGPUBindGroupLayoutDescriptor ub_bgl_desc; +    memset(&ub_bgl_desc, 0, sizeof(ub_bgl_desc)); +    ub_bgl_desc.bindingCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; +    ub_bgl_desc.bindings = &ub_bglb_desc[0][0]; +    _sg.wgpu.ub.bindgroup_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &ub_bgl_desc); +    SOKOL_ASSERT(_sg.wgpu.ub.bindgroup_layout); + +    WGPUBindGroupBinding ub_bgb[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; +    memset(ub_bgb, 0, sizeof(ub_bgb)); +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { +            int bind_index = stage_index * SG_MAX_SHADERSTAGE_UBS + ub_index; +            ub_bgb[stage_index][ub_index].binding = bind_index; +            ub_bgb[stage_index][ub_index].buffer = _sg.wgpu.ub.buf; +            // FIXME FIXME FIXME FIXME: HACK FOR VALIDATION BUG IN DAWN +            ub_bgb[stage_index][ub_index].size = (1<<16); +        } +    } +    WGPUBindGroupDescriptor bg_desc; +    memset(&bg_desc, 0, sizeof(bg_desc)); +    bg_desc.layout = _sg.wgpu.ub.bindgroup_layout; +    bg_desc.bindingCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; +    bg_desc.bindings = &ub_bgb[0][0]; +    _sg.wgpu.ub.bindgroup = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); +    SOKOL_ASSERT(_sg.wgpu.ub.bindgroup); +} + +_SOKOL_PRIVATE void _sg_wgpu_ubpool_discard(void) { +    if (_sg.wgpu.ub.buf) { +        wgpuBufferRelease(_sg.wgpu.ub.buf); +        _sg.wgpu.ub.buf = 0; +    } +    if (_sg.wgpu.ub.bindgroup) { +        wgpuBindGroupRelease(_sg.wgpu.ub.bindgroup); +        _sg.wgpu.ub.bindgroup = 0; +    } +    if (_sg.wgpu.ub.bindgroup_layout) { +        wgpuBindGroupLayoutRelease(_sg.wgpu.ub.bindgroup_layout); +        _sg.wgpu.ub.bindgroup_layout = 0; +    } +    for (int i = 0; i < _sg.wgpu.ub.stage.num; i++) { +        if (_sg.wgpu.ub.stage.buf[i]) { +            wgpuBufferRelease(_sg.wgpu.ub.stage.buf[i]); +            _sg.wgpu.ub.stage.buf[i] = 0; +            _sg.wgpu.ub.stage.ptr[i] = 0; +        } +    } +} + +_SOKOL_PRIVATE void _sg_wgpu_ubpool_mapped_callback(WGPUBufferMapAsyncStatus status, void* data, uint64_t data_len, void* user_data) { +    if (!_sg.wgpu.valid) { +        return; +    } +    /* FIXME: better handling for this */ +    if (WGPUBufferMapAsyncStatus_Success != status) { +        SOKOL_LOG("Mapping uniform buffer failed!\n"); +        SOKOL_ASSERT(false); +    } +    SOKOL_ASSERT(data && (data_len == _sg.wgpu.ub.num_bytes)); +    int index = (int)(intptr_t) user_data; +    SOKOL_ASSERT(index < _sg.wgpu.ub.stage.num); +    SOKOL_ASSERT(0 == _sg.wgpu.ub.stage.ptr[index]); +    _sg.wgpu.ub.stage.ptr[index] = (uint8_t*) data; +} + +_SOKOL_PRIVATE void _sg_wgpu_ubpool_next_frame(bool first_frame) { + +    /* immediately request a new mapping for the last frame's current staging buffer */ +    if (!first_frame) { +        WGPUBuffer ub_src = _sg.wgpu.ub.stage.buf[_sg.wgpu.ub.stage.cur]; +        wgpuBufferMapWriteAsync(ub_src, _sg_wgpu_ubpool_mapped_callback, (void*)(intptr_t)_sg.wgpu.ub.stage.cur); +    } + +    /* rewind per-frame offsets */ +    _sg.wgpu.ub.offset = 0; +    memset(&_sg.wgpu.ub.bind_offsets, 0, sizeof(_sg.wgpu.ub.bind_offsets)); + +    /* check if a mapped staging buffer is available, otherwise create one */ +    for (int i = 0; i < _sg.wgpu.ub.stage.num; i++) { +        if (_sg.wgpu.ub.stage.ptr[i]) { +            _sg.wgpu.ub.stage.cur = i; +            return; +        } +    } + +    /* no mapped uniform buffer available, create one */ +    SOKOL_ASSERT(_sg.wgpu.ub.stage.num < _SG_WGPU_STAGING_PIPELINE_SIZE); +    _sg.wgpu.ub.stage.cur = _sg.wgpu.ub.stage.num++; +    const int cur = _sg.wgpu.ub.stage.cur; + +    WGPUBufferDescriptor desc; +    memset(&desc, 0, sizeof(desc)); +    desc.size = _sg.wgpu.ub.num_bytes; +    desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_MapWrite; +    WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &desc); +    _sg.wgpu.ub.stage.buf[cur] = res.buffer; +    _sg.wgpu.ub.stage.ptr[cur] = (uint8_t*) res.data; +    SOKOL_ASSERT(_sg.wgpu.ub.stage.buf[cur]); +    SOKOL_ASSERT(_sg.wgpu.ub.stage.ptr[cur]); +    SOKOL_ASSERT(res.dataLength == _sg.wgpu.ub.num_bytes); +} + +_SOKOL_PRIVATE void _sg_wgpu_ubpool_flush(void) { +    /* unmap staging buffer and copy to uniform buffer */ +    const int cur = _sg.wgpu.ub.stage.cur; +    SOKOL_ASSERT(_sg.wgpu.ub.stage.ptr[cur]); +    _sg.wgpu.ub.stage.ptr[cur] = 0; +    WGPUBuffer src_buf = _sg.wgpu.ub.stage.buf[cur]; +    wgpuBufferUnmap(src_buf); +    if (_sg.wgpu.ub.offset > 0) { +        WGPUBuffer dst_buf = _sg.wgpu.ub.buf; +        wgpuCommandEncoderCopyBufferToBuffer(_sg.wgpu.render_cmd_enc, src_buf, 0, dst_buf, 0, _sg.wgpu.ub.offset); +    } +} + +/* helper function to compute number of bytes needed in staging buffer to copy image data */ +_SOKOL_PRIVATE uint32_t _sg_wgpu_image_data_buffer_size(const _sg_image_t* img) { +    uint32_t num_bytes = 0; +    const uint32_t num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; +    const uint32_t num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; +    for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { +        const uint32_t mip_width = _sg_max(img->cmn.width >> mip_index, 1); +        const uint32_t mip_height = _sg_max(img->cmn.height >> mip_index, 1); +        /* row-pitch must be 256-aligend */ +        const uint32_t bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, _SG_WGPU_ROWPITCH_ALIGN); +        num_bytes += bytes_per_slice * num_slices * num_faces; +    } +    return num_bytes; +} + +/* helper function to copy image data into a texture via a staging buffer, returns number of +   bytes copied +*/ +_SOKOL_PRIVATE uint32_t _sg_wgpu_copy_image_data(WGPUBuffer stg_buf, uint8_t* stg_base_ptr, uint32_t stg_base_offset, _sg_image_t* img, const sg_image_data* data) { +    SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); +    SOKOL_ASSERT(stg_buf && stg_base_ptr); +    SOKOL_ASSERT(img); +    SOKOL_ASSERT(data); +    uint32_t stg_offset = stg_base_offset; +    const uint32_t num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; +    const uint32_t num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; +    const sg_pixel_format fmt = img->cmn.pixel_format; +    WGPUBufferCopyView src_view; +    memset(&src_view, 0, sizeof(src_view)); +    src_view.buffer = stg_buf; +    WGPUTextureCopyView dst_view; +    memset(&dst_view, 0, sizeof(dst_view)); +    dst_view.texture = img->wgpu.tex; +    WGPUExtent3D extent; +    memset(&extent, 0, sizeof(extent)); + +    for (uint32_t face_index = 0; face_index < num_faces; face_index++) { +        for (uint32_t mip_index = 0; mip_index < (uint32_t)img->cmn.num_mipmaps; mip_index++) { +            SOKOL_ASSERT(data->subimage[face_index][mip_index].ptr); +            SOKOL_ASSERT(data->subimage[face_index][mip_index].size > 0); +            const uint8_t* src_base_ptr = (const uint8_t*)data->subimage[face_index][mip_index].ptr; +            SOKOL_ASSERT(src_base_ptr); +            uint8_t* dst_base_ptr = stg_base_ptr + stg_offset; + +            const uint32_t mip_width  = _sg_max(img->cmn.width >> mip_index, 1); +            const uint32_t mip_height = _sg_max(img->cmn.height >> mip_index, 1); +            const uint32_t mip_depth  = (img->cmn.type == SG_IMAGETYPE_3D) ? _sg_max(img->cmn.num_slices >> mip_index, 1) : 1; +            const uint32_t num_rows   = _sg_num_rows(fmt, mip_height); +            const uint32_t src_bytes_per_row   = _sg_row_pitch(fmt, mip_width, 1); +            const uint32_t dst_bytes_per_row   = _sg_row_pitch(fmt, mip_width, _SG_WGPU_ROWPITCH_ALIGN); +            const uint32_t src_bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, 1); +            const uint32_t dst_bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, _SG_WGPU_ROWPITCH_ALIGN); +            SOKOL_ASSERT((uint32_t)data->subimage[face_index][mip_index].size == (src_bytes_per_slice * num_slices)); +            SOKOL_ASSERT(src_bytes_per_row <= dst_bytes_per_row); +            SOKOL_ASSERT(src_bytes_per_slice == (src_bytes_per_row * num_rows)); +            SOKOL_ASSERT(dst_bytes_per_slice == (dst_bytes_per_row * num_rows)); +            _SOKOL_UNUSED(src_bytes_per_slice); + +            /* copy data into mapped staging buffer */ +            if (src_bytes_per_row == dst_bytes_per_row) { +                /* can do a single memcpy */ +                uint32_t num_bytes = data->subimage[face_index][mip_index].size; +                memcpy(dst_base_ptr, src_base_ptr, num_bytes); +            } +            else { +                /* src/dst pitch doesn't match, need to copy row by row */ +                uint8_t* dst_ptr = dst_base_ptr; +                const uint8_t* src_ptr = src_base_ptr; +                for (uint32_t slice_index = 0; slice_index < num_slices; slice_index++) { +                    SOKOL_ASSERT(dst_ptr == dst_base_ptr + slice_index * dst_bytes_per_slice); +                    for (uint32_t row_index = 0; row_index < num_rows; row_index++) { +                        memcpy(dst_ptr, src_ptr, src_bytes_per_row); +                        src_ptr += src_bytes_per_row; +                        dst_ptr += dst_bytes_per_row; +                    } +                } +            } + +            /* record the staging copy operation into command encoder */ +            src_view.imageHeight = mip_height; +            src_view.rowPitch = dst_bytes_per_row; +            dst_view.mipLevel = mip_index; +            extent.width = mip_width; +            extent.height = mip_height; +            extent.depth = mip_depth; +            SOKOL_ASSERT((img->cmn.type != SG_IMAGETYPE_CUBE) || (num_slices == 1)); +            for (uint32_t slice_index = 0; slice_index < num_slices; slice_index++) { +                const uint32_t layer_index = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? slice_index : face_index; +                src_view.offset = stg_offset; +                dst_view.arrayLayer = layer_index; +                wgpuCommandEncoderCopyBufferToTexture(_sg.wgpu.staging_cmd_enc, &src_view, &dst_view, &extent); +                stg_offset += dst_bytes_per_slice; +                SOKOL_ASSERT(stg_offset <= _sg.wgpu.staging.num_bytes); +            } +        } +    } +    SOKOL_ASSERT(stg_offset >= stg_base_offset); +    return (stg_offset - stg_base_offset); +} + +/* +    The WGPU staging buffer implementation: + +    Very similar to the uniform buffer pool, there's a pool of big +    per-frame staging buffers, each must be big enough to hold +    all data uploaded to dynamic resources for one frame. + +    Staging buffers are created on demand and reused, because the +    'frame pipeline depth' of WGPU isn't predictable. + +    The difference to the uniform buffer system is that there isn't +    a 1:1 relationship for source- and destination for the +    data-copy operation. There's always one staging buffer as copy-source +    per frame, but many copy-destinations (regular vertex/index buffers +    or images). Instead of one big copy-operation at the end of the frame, +    multiple copy-operations will be written throughout the frame. +*/ +_SOKOL_PRIVATE void _sg_wgpu_staging_init(const sg_desc* desc) { +    SOKOL_ASSERT(desc && (desc->staging_buffer_size > 0)); +    _sg.wgpu.staging.num_bytes = desc->staging_buffer_size; +    /* there's actually nothing more to do here */ +} + +_SOKOL_PRIVATE void _sg_wgpu_staging_discard(void) { +    for (int i = 0; i < _sg.wgpu.staging.num; i++) { +        if (_sg.wgpu.staging.buf[i]) { +            wgpuBufferRelease(_sg.wgpu.staging.buf[i]); +            _sg.wgpu.staging.buf[i] = 0; +            _sg.wgpu.staging.ptr[i] = 0; +        } +    } +} + +_SOKOL_PRIVATE void _sg_wgpu_staging_mapped_callback(WGPUBufferMapAsyncStatus status, void* data, uint64_t data_len, void* user_data) { +    if (!_sg.wgpu.valid) { +        return; +    } +    /* FIXME: better handling for this */ +    if (WGPUBufferMapAsyncStatus_Success != status) { +        SOKOL_ASSERT("Mapping staging buffer failed!\n"); +        SOKOL_ASSERT(false); +    } +    SOKOL_ASSERT(data && (data_len == _sg.wgpu.staging.num_bytes)); +    int index = (int)(intptr_t) user_data; +    SOKOL_ASSERT(index < _sg.wgpu.staging.num); +    SOKOL_ASSERT(0 == _sg.wgpu.staging.ptr[index]); +    _sg.wgpu.staging.ptr[index] = (uint8_t*) data; +} + +_SOKOL_PRIVATE void _sg_wgpu_staging_next_frame(bool first_frame) { + +    /* immediately request a new mapping for the last frame's current staging buffer */ +    if (!first_frame) { +        WGPUBuffer cur_buf = _sg.wgpu.staging.buf[_sg.wgpu.staging.cur]; +        wgpuBufferMapWriteAsync(cur_buf, _sg_wgpu_staging_mapped_callback, (void*)(intptr_t)_sg.wgpu.staging.cur); +    } + +    /* rewind staging-buffer offset */ +    _sg.wgpu.staging.offset = 0; + +    /* check if mapped staging buffer is available, otherwise create one */ +    for (int i = 0; i < _sg.wgpu.staging.num; i++) { +        if (_sg.wgpu.staging.ptr[i]) { +            _sg.wgpu.staging.cur = i; +            return; +        } +    } + +    /* no mapped buffer available, create one */ +    SOKOL_ASSERT(_sg.wgpu.staging.num < _SG_WGPU_STAGING_PIPELINE_SIZE); +    _sg.wgpu.staging.cur = _sg.wgpu.staging.num++; +    const int cur = _sg.wgpu.staging.cur; + +    WGPUBufferDescriptor desc; +    memset(&desc, 0, sizeof(desc)); +    desc.size = _sg.wgpu.staging.num_bytes; +    desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_MapWrite; +    WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &desc); +    _sg.wgpu.staging.buf[cur] = res.buffer; +    _sg.wgpu.staging.ptr[cur] = (uint8_t*) res.data; +    SOKOL_ASSERT(_sg.wgpu.staging.buf[cur]); +    SOKOL_ASSERT(_sg.wgpu.staging.ptr[cur]); +    SOKOL_ASSERT(res.dataLength == _sg.wgpu.staging.num_bytes); +} + +_SOKOL_PRIVATE uint32_t _sg_wgpu_staging_copy_to_buffer(WGPUBuffer dst_buf, uint32_t dst_buf_offset, const void* data, uint32_t data_num_bytes) { +    /* Copy a chunk of data into the staging buffer, and record a blit-operation into +        the command encoder, bump the offset for the next data chunk, return 0 if there +        was not enough room in the staging buffer, return the number of actually +        copied bytes on success. + +        NOTE: that the number of staging bytes to be copied must be a multiple of 4. + +    */ +    SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); +    SOKOL_ASSERT((dst_buf_offset & 3) == 0); +    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"); +        return false; +    } +    const int cur = _sg.wgpu.staging.cur; +    SOKOL_ASSERT(_sg.wgpu.staging.ptr[cur]); +    uint32_t stg_buf_offset = _sg.wgpu.staging.offset; +    uint8_t* stg_ptr = _sg.wgpu.staging.ptr[cur] + stg_buf_offset; +    memcpy(stg_ptr, data, data_num_bytes); +    WGPUBuffer stg_buf = _sg.wgpu.staging.buf[cur]; +    wgpuCommandEncoderCopyBufferToBuffer(_sg.wgpu.staging_cmd_enc, stg_buf, stg_buf_offset, dst_buf, dst_buf_offset, copy_num_bytes); +    _sg.wgpu.staging.offset = stg_buf_offset + copy_num_bytes; +    return copy_num_bytes; +} + +_SOKOL_PRIVATE bool _sg_wgpu_staging_copy_to_texture(_sg_image_t* img, const sg_image_data* data) { +    /* similar to _sg_wgpu_staging_copy_to_buffer(), but with image data instead */ +    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"); +        return false; +    } +    const int cur = _sg.wgpu.staging.cur; +    SOKOL_ASSERT(_sg.wgpu.staging.ptr[cur]); +    uint32_t stg_offset = _sg.wgpu.staging.offset; +    uint8_t* stg_ptr = _sg.wgpu.staging.ptr[cur]; +    WGPUBuffer stg_buf = _sg.wgpu.staging.buf[cur]; +    uint32_t bytes_copied = _sg_wgpu_copy_image_data(stg_buf, stg_ptr, stg_offset, img, data); +    _SOKOL_UNUSED(bytes_copied); +    SOKOL_ASSERT(bytes_copied == num_bytes); +    _sg.wgpu.staging.offset = _sg_roundup(stg_offset + num_bytes, _SG_WGPU_STAGING_ALIGN); +    return true; +} + +_SOKOL_PRIVATE void _sg_wgpu_staging_unmap(void) { +    /* called at end of frame before queue-submit */ +    const int cur = _sg.wgpu.staging.cur; +    SOKOL_ASSERT(_sg.wgpu.staging.ptr[cur]); +    _sg.wgpu.staging.ptr[cur] = 0; +    wgpuBufferUnmap(_sg.wgpu.staging.buf[cur]); +} + +/*--- WGPU sampler cache functions ---*/ +_SOKOL_PRIVATE void _sg_wgpu_init_sampler_cache(const sg_desc* desc) { +    SOKOL_ASSERT(desc->sampler_cache_size > 0); +    _sg_smpcache_init(&_sg.wgpu.sampler_cache, desc->sampler_cache_size); +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_sampler_cache(void) { +    SOKOL_ASSERT(_sg.wgpu.sampler_cache.items); +    SOKOL_ASSERT(_sg.wgpu.sampler_cache.num_items <= _sg.wgpu.sampler_cache.capacity); +    for (int i = 0; i < _sg.wgpu.sampler_cache.num_items; i++) { +        wgpuSamplerRelease((WGPUSampler)_sg_smpcache_sampler(&_sg.wgpu.sampler_cache, i)); +    } +    _sg_smpcache_discard(&_sg.wgpu.sampler_cache); +} + +_SOKOL_PRIVATE WGPUSampler _sg_wgpu_create_sampler(const sg_image_desc* img_desc) { +    SOKOL_ASSERT(img_desc); +    int index = _sg_smpcache_find_item(&_sg.wgpu.sampler_cache, img_desc); +    if (index >= 0) { +        /* reuse existing sampler */ +        return (WGPUSampler) _sg_smpcache_sampler(&_sg.wgpu.sampler_cache, index); +    } +    else { +        /* create a new WGPU sampler and add to sampler cache */ +        /* FIXME: anisotropic filtering not supported? */ +        WGPUSamplerDescriptor smp_desc; +        memset(&smp_desc, 0, sizeof(smp_desc)); +        smp_desc.addressModeU = _sg_wgpu_sampler_addrmode(img_desc->wrap_u); +        smp_desc.addressModeV = _sg_wgpu_sampler_addrmode(img_desc->wrap_v); +        smp_desc.addressModeW = _sg_wgpu_sampler_addrmode(img_desc->wrap_w); +        smp_desc.magFilter = _sg_wgpu_sampler_minmagfilter(img_desc->mag_filter); +        smp_desc.minFilter = _sg_wgpu_sampler_minmagfilter(img_desc->min_filter); +        smp_desc.mipmapFilter = _sg_wgpu_sampler_mipfilter(img_desc->min_filter); +        smp_desc.lodMinClamp = img_desc->min_lod; +        smp_desc.lodMaxClamp = img_desc->max_lod; +        WGPUSampler smp = wgpuDeviceCreateSampler(_sg.wgpu.dev, &smp_desc); +        SOKOL_ASSERT(smp); +        _sg_smpcache_add_item(&_sg.wgpu.sampler_cache, img_desc, (uintptr_t)smp); +        return smp; +    } +} + +/*--- WGPU backend API functions ---*/ +_SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { +    SOKOL_ASSERT(desc); +    SOKOL_ASSERT(desc->context.wgpu.device); +    SOKOL_ASSERT(desc->context.wgpu.render_view_cb || desc->context.wgpu.render_view_userdata_cb); +    SOKOL_ASSERT(desc->context.wgpu.resolve_view_cb || desc->context.wgpu.resolve_view_userdata_cb); +    SOKOL_ASSERT(desc->context.wgpu.depth_stencil_view_cb || desc->context.wgpu.depth_stencil_view_userdata_cb); +    SOKOL_ASSERT(desc->uniform_buffer_size > 0); +    SOKOL_ASSERT(desc->staging_buffer_size > 0); +    _sg.backend = SG_BACKEND_WGPU; +    _sg.wgpu.valid = true; +    _sg.wgpu.dev = (WGPUDevice) desc->context.wgpu.device; +    _sg.wgpu.render_view_cb = (WGPUTextureView(*)(void)) desc->context.wgpu.render_view_cb; +    _sg.wgpu.render_view_userdata_cb = (WGPUTextureView(*)(void*)) desc->context.wgpu.render_view_userdata_cb; +    _sg.wgpu.resolve_view_cb = (WGPUTextureView(*)(void)) desc->context.wgpu.resolve_view_cb; +    _sg.wgpu.resolve_view_userdata_cb = (WGPUTextureView(*)(void*)) desc->context.wgpu.resolve_view_userdata_cb; +    _sg.wgpu.depth_stencil_view_cb = (WGPUTextureView(*)(void)) desc->context.wgpu.depth_stencil_view_cb; +    _sg.wgpu.depth_stencil_view_userdata_cb = (WGPUTextureView(*)(void*)) desc->context.wgpu.depth_stencil_view_userdata_cb; +    _sg.wgpu.user_data = desc->context.wgpu.user_data; +    _sg.wgpu.queue = wgpuDeviceCreateQueue(_sg.wgpu.dev); +    SOKOL_ASSERT(_sg.wgpu.queue); + +    /* setup WebGPU features and limits */ +    _sg_wgpu_init_caps(); + +    /* setup the sampler cache, uniform and staging buffer pools */ +    _sg_wgpu_init_sampler_cache(&_sg.desc); +    _sg_wgpu_ubpool_init(desc); +    _sg_wgpu_ubpool_next_frame(true); +    _sg_wgpu_staging_init(desc); +    _sg_wgpu_staging_next_frame(true); + +    /* create an empty bind group for shader stages without bound images */ +    WGPUBindGroupLayoutDescriptor bgl_desc; +    memset(&bgl_desc, 0, sizeof(bgl_desc)); +    WGPUBindGroupLayout empty_bgl = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); +    SOKOL_ASSERT(empty_bgl); +    WGPUBindGroupDescriptor bg_desc; +    memset(&bg_desc, 0, sizeof(bg_desc)); +    bg_desc.layout = empty_bgl; +    _sg.wgpu.empty_bind_group = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); +    SOKOL_ASSERT(_sg.wgpu.empty_bind_group); +    wgpuBindGroupLayoutRelease(empty_bgl); + +    /* create initial per-frame command encoders */ +    WGPUCommandEncoderDescriptor cmd_enc_desc; +    memset(&cmd_enc_desc, 0, sizeof(cmd_enc_desc)); +    _sg.wgpu.render_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); +    SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); +    _sg.wgpu.staging_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); +    SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) { +    SOKOL_ASSERT(_sg.wgpu.valid); +    SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); +    SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); +    _sg.wgpu.valid = false; +    _sg_wgpu_ubpool_discard(); +    _sg_wgpu_staging_discard(); +    _sg_wgpu_destroy_sampler_cache(); +    wgpuBindGroupRelease(_sg.wgpu.empty_bind_group); +    wgpuCommandEncoderRelease(_sg.wgpu.render_cmd_enc); +    _sg.wgpu.render_cmd_enc = 0; +    wgpuCommandEncoderRelease(_sg.wgpu.staging_cmd_enc); +    _sg.wgpu.staging_cmd_enc = 0; +    if (_sg.wgpu.queue) { +        wgpuQueueRelease(_sg.wgpu.queue); +        _sg.wgpu.queue = 0; +    } +} + +_SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) { +    SOKOL_LOG("_sg_wgpu_reset_state_cache: FIXME\n"); +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _SOKOL_UNUSED(ctx); +} + +_SOKOL_PRIVATE void _sg_wgpu_activate_context(_sg_context_t* ctx) { +    (void)ctx; +    SOKOL_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) { +    SOKOL_ASSERT(buf && desc); +    const bool injected = (0 != desc->wgpu_buffer); +    _sg_buffer_common_init(&buf->cmn, desc); +    if (injected) { +        buf->wgpu.buf = (WGPUBuffer) desc->wgpu_buffer; +        wgpuBufferReference(buf->wgpu.buf); +    } +    else { +        WGPUBufferDescriptor wgpu_buf_desc; +        memset(&wgpu_buf_desc, 0, sizeof(wgpu_buf_desc)); +        wgpu_buf_desc.usage = _sg_wgpu_buffer_usage(buf->cmn.type, buf->cmn.usage); +        wgpu_buf_desc.size = buf->cmn.size; +        if (SG_USAGE_IMMUTABLE == buf->cmn.usage) { +            SOKOL_ASSERT(desc->data.ptr); +            WGPUCreateBufferMappedResult res = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &wgpu_buf_desc); +            buf->wgpu.buf = res.buffer; +            SOKOL_ASSERT(res.data && (res.dataLength == buf->cmn.size)); +            memcpy(res.data, desc->data.ptr, buf->cmn.size); +            wgpuBufferUnmap(res.buffer); +        } +        else { +            buf->wgpu.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &wgpu_buf_desc); +        } +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_buffer(_sg_buffer_t* buf) { +    SOKOL_ASSERT(buf); +    WGPUBuffer wgpu_buf = buf->wgpu.buf; +    if (0 != wgpu_buf) { +        wgpuBufferRelease(wgpu_buf); +    } +} + +_SOKOL_PRIVATE void _sg_wgpu_init_texdesc_common(WGPUTextureDescriptor* wgpu_tex_desc, const sg_image_desc* desc) { +    wgpu_tex_desc->usage = WGPUTextureUsage_Sampled|WGPUTextureUsage_CopyDst; +    wgpu_tex_desc->dimension = _sg_wgpu_tex_dim(desc->type); +    wgpu_tex_desc->size.width = desc->width; +    wgpu_tex_desc->size.height = desc->height; +    if (desc->type == SG_IMAGETYPE_3D) { +        wgpu_tex_desc->size.depth = desc->num_slices; +        wgpu_tex_desc->arrayLayerCount = 1; +    } +    else if (desc->type == SG_IMAGETYPE_CUBE) { +        wgpu_tex_desc->size.depth = 1; +        wgpu_tex_desc->arrayLayerCount = 6; +    } +    else { +        wgpu_tex_desc->size.depth = 1; +        wgpu_tex_desc->arrayLayerCount = desc->num_slices; +    } +    wgpu_tex_desc->format = _sg_wgpu_textureformat(desc->pixel_format); +    wgpu_tex_desc->mipLevelCount = desc->num_mipmaps; +    wgpu_tex_desc->sampleCount = 1; +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const sg_image_desc* desc) { +    SOKOL_ASSERT(img && desc); +    SOKOL_ASSERT(_sg.wgpu.dev); +    SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); + +    _sg_image_common_init(&img->cmn, desc); + +    const bool injected = (0 != desc->wgpu_texture); +    const bool is_msaa = desc->sample_count > 1; +    WGPUTextureDescriptor wgpu_tex_desc; +    memset(&wgpu_tex_desc, 0, sizeof(wgpu_tex_desc)); +    _sg_wgpu_init_texdesc_common(&wgpu_tex_desc, desc); +    if (_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format)) { +        SOKOL_ASSERT(img->cmn.render_target); +        SOKOL_ASSERT(img->cmn.type == SG_IMAGETYPE_2D); +        SOKOL_ASSERT(img->cmn.num_mipmaps == 1); +        SOKOL_ASSERT(!injected); +        /* NOTE: a depth-stencil texture will never be MSAA-resolved, so there +           won't be a separate MSAA- and resolve-texture +        */ +        wgpu_tex_desc.usage = WGPUTextureUsage_OutputAttachment; +        wgpu_tex_desc.sampleCount = desc->sample_count; +        img->wgpu.tex = wgpuDeviceCreateTexture(_sg.wgpu.dev, &wgpu_tex_desc); +        SOKOL_ASSERT(img->wgpu.tex); +    } +    else { +        if (injected) { +            img->wgpu.tex = (WGPUTexture) desc->wgpu_texture; +            wgpuTextureReference(img->wgpu.tex); +        } +        else { +            /* NOTE: in the MSAA-rendertarget case, both the MSAA texture *and* +               the resolve texture need OutputAttachment usage +            */ +            if (img->cmn.render_target) { +                wgpu_tex_desc.usage = WGPUTextureUsage_Sampled|WGPUTextureUsage_OutputAttachment; +            } +            img->wgpu.tex = wgpuDeviceCreateTexture(_sg.wgpu.dev, &wgpu_tex_desc); +            SOKOL_ASSERT(img->wgpu.tex); + +            /* copy content into texture via a throw-away staging buffer */ +            if (desc->usage == SG_USAGE_IMMUTABLE && !desc->render_target) { +                WGPUBufferDescriptor wgpu_buf_desc; +                memset(&wgpu_buf_desc, 0, sizeof(wgpu_buf_desc)); +                wgpu_buf_desc.size = _sg_wgpu_image_data_buffer_size(img); +                wgpu_buf_desc.usage = WGPUBufferUsage_CopySrc|WGPUBufferUsage_CopyDst; +                WGPUCreateBufferMappedResult map = wgpuDeviceCreateBufferMapped(_sg.wgpu.dev, &wgpu_buf_desc); +                SOKOL_ASSERT(map.buffer && map.data); +                uint32_t num_bytes = _sg_wgpu_copy_image_data(map.buffer, (uint8_t*)map.data, 0, img, &desc->data); +                _SOKOL_UNUSED(num_bytes); +                SOKOL_ASSERT(num_bytes == wgpu_buf_desc.size); +                wgpuBufferUnmap(map.buffer); +                wgpuBufferRelease(map.buffer); +            } +        } + +        /* create texture view object */ +        WGPUTextureViewDescriptor wgpu_view_desc; +        memset(&wgpu_view_desc, 0, sizeof(wgpu_view_desc)); +        wgpu_view_desc.dimension = _sg_wgpu_tex_viewdim(desc->type); +        img->wgpu.tex_view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_view_desc); + +        /* if render target and MSAA, then a separate texture in MSAA format is needed +           which will be resolved into the regular texture at the end of the +           offscreen-render pass +        */ +        if (desc->render_target && is_msaa) { +            wgpu_tex_desc.dimension = WGPUTextureDimension_2D; +            wgpu_tex_desc.size.depth = 1; +            wgpu_tex_desc.arrayLayerCount = 1; +            wgpu_tex_desc.mipLevelCount = 1; +            wgpu_tex_desc.usage = WGPUTextureUsage_OutputAttachment; +            wgpu_tex_desc.sampleCount = desc->sample_count; +            img->wgpu.msaa_tex = wgpuDeviceCreateTexture(_sg.wgpu.dev, &wgpu_tex_desc); +            SOKOL_ASSERT(img->wgpu.msaa_tex); +        } + +        /* create sampler via shared-sampler-cache */ +        img->wgpu.sampler = _sg_wgpu_create_sampler(desc); +        SOKOL_ASSERT(img->wgpu.sampler); +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_image(_sg_image_t* img) { +    SOKOL_ASSERT(img); +    if (img->wgpu.tex) { +        wgpuTextureRelease(img->wgpu.tex); +        img->wgpu.tex = 0; +    } +    if (img->wgpu.tex_view) { +        wgpuTextureViewRelease(img->wgpu.tex_view); +        img->wgpu.tex_view = 0; +    } +    if (img->wgpu.msaa_tex) { +        wgpuTextureRelease(img->wgpu.msaa_tex); +        img->wgpu.msaa_tex = 0; +    } +    /* NOTE: do *not* destroy the sampler from the shared-sampler-cache */ +    img->wgpu.sampler = 0; +} + +/* +    How BindGroups work in WebGPU: + +    - up to 4 bind groups can be bound simultaneously +    - up to 16 bindings per bind group +    - 'binding' slots are local per bind group +    - in the shader: +        layout(set=0, binding=1) corresponds to bind group 0, binding 1 + +    Now how to map this to sokol-gfx's bind model: + +    Reduce SG_MAX_SHADERSTAGE_IMAGES to 8, then: + +        1 bind group for all 8 uniform buffers +        1 bind group for vertex shader textures + samplers +        1 bind group for fragment shader textures + samples + +    Alternatively: + +        1 bind group for 8 uniform buffer slots +        1 bind group for 8 vs images + 8 vs samplers +        1 bind group for 12 fs images +        1 bind group for 12 fs samplers + +    I guess this means that we need to create BindGroups on the +    fly during sg_apply_bindings() :/ +*/ +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { +    SOKOL_ASSERT(shd && desc); +    SOKOL_ASSERT(desc->vs.bytecode.ptr && desc->fs.bytecode.ptr); +    _sg_shader_common_init(&shd->cmn, desc); + +    bool success = true; +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS) ? &desc->vs : &desc->fs; +        SOKOL_ASSERT((stage_desc->bytecode.size & 3) == 0); + +        _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; +        _sg_wgpu_shader_stage_t* wgpu_stage = &shd->wgpu.stage[stage_index]; + +        _sg_strcpy(&wgpu_stage->entry, stage_desc->entry); +        WGPUShaderModuleDescriptor wgpu_shdmod_desc; +        memset(&wgpu_shdmod_desc, 0, sizeof(wgpu_shdmod_desc)); +        wgpu_shdmod_desc.codeSize = stage_desc->bytecode.size >> 2; +        wgpu_shdmod_desc.code = (const uint32_t*) stage_desc->bytecode.ptr; +        wgpu_stage->module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc); +        if (0 == wgpu_stage->module) { +            success = false; +        } + +        /* create image/sampler bind group for the shader stage */ +        WGPUShaderStage vis = (stage_index == SG_SHADERSTAGE_VS) ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment; +        int num_imgs = cmn_stage->num_images; +        if (num_imgs > _SG_WGPU_MAX_SHADERSTAGE_IMAGES) { +            num_imgs = _SG_WGPU_MAX_SHADERSTAGE_IMAGES; +        } +        WGPUBindGroupLayoutBinding bglb_desc[_SG_WGPU_MAX_SHADERSTAGE_IMAGES * 2]; +        memset(bglb_desc, 0, sizeof(bglb_desc)); +        for (int img_index = 0; img_index < num_imgs; img_index++) { +            /* texture- and sampler-bindings */ +            WGPUBindGroupLayoutBinding* tex_desc = &bglb_desc[img_index*2 + 0]; +            WGPUBindGroupLayoutBinding* smp_desc = &bglb_desc[img_index*2 + 1]; + +            tex_desc->binding = img_index; +            tex_desc->visibility = vis; +            tex_desc->type = WGPUBindingType_SampledTexture; +            tex_desc->textureDimension = _sg_wgpu_tex_viewdim(cmn_stage->images[img_index].image_type); +            tex_desc->textureComponentType = _sg_wgpu_tex_comptype(cmn_stage->images[img_index].sampler_type); + +            smp_desc->binding = img_index + _SG_WGPU_MAX_SHADERSTAGE_IMAGES; +            smp_desc->visibility = vis; +            smp_desc->type = WGPUBindingType_Sampler; +        } +        WGPUBindGroupLayoutDescriptor img_bgl_desc; +        memset(&img_bgl_desc, 0, sizeof(img_bgl_desc)); +        img_bgl_desc.bindingCount = num_imgs * 2; +        img_bgl_desc.bindings = &bglb_desc[0]; +        wgpu_stage->bind_group_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &img_bgl_desc); +        SOKOL_ASSERT(wgpu_stage->bind_group_layout); +    } +    return success ? SG_RESOURCESTATE_VALID : SG_RESOURCESTATE_FAILED; +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_shader(_sg_shader_t* shd) { +    SOKOL_ASSERT(shd); +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        _sg_wgpu_shader_stage_t* wgpu_stage = &shd->wgpu.stage[stage_index]; +        if (wgpu_stage->module) { +            wgpuShaderModuleRelease(wgpu_stage->module); +            wgpu_stage->module = 0; +        } +        if (wgpu_stage->bind_group_layout) { +            wgpuBindGroupLayoutRelease(wgpu_stage->bind_group_layout); +            wgpu_stage->bind_group_layout = 0; +        } +    } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(pip && shd && desc); +    SOKOL_ASSERT(desc->shader.id == shd->slot.id); +    SOKOL_ASSERT(shd->wgpu.stage[SG_SHADERSTAGE_VS].bind_group_layout); +    SOKOL_ASSERT(shd->wgpu.stage[SG_SHADERSTAGE_FS].bind_group_layout); +    pip->shader = shd; +    _sg_pipeline_common_init(&pip->cmn, desc); +    pip->wgpu.stencil_ref = (uint32_t) desc->stencil.ref; + +    WGPUBindGroupLayout pip_bgl[3] = { +        _sg.wgpu.ub.bindgroup_layout, +        shd->wgpu.stage[SG_SHADERSTAGE_VS].bind_group_layout, +        shd->wgpu.stage[SG_SHADERSTAGE_FS].bind_group_layout +    }; +    WGPUPipelineLayoutDescriptor pl_desc; +    memset(&pl_desc, 0, sizeof(pl_desc)); +    pl_desc.bindGroupLayoutCount = 3; +    pl_desc.bindGroupLayouts = &pip_bgl[0]; +    WGPUPipelineLayout pip_layout = wgpuDeviceCreatePipelineLayout(_sg.wgpu.dev, &pl_desc); + +    WGPUVertexBufferLayoutDescriptor vb_desc[SG_MAX_SHADERSTAGE_BUFFERS]; +    memset(&vb_desc, 0, sizeof(vb_desc)); +    WGPUVertexAttributeDescriptor va_desc[SG_MAX_SHADERSTAGE_BUFFERS][SG_MAX_VERTEX_ATTRIBUTES]; +    memset(&va_desc, 0, sizeof(va_desc)); +    int vb_idx = 0; +    for (; vb_idx < SG_MAX_SHADERSTAGE_BUFFERS; vb_idx++) { +        const sg_buffer_layout_desc* src_vb_desc = &desc->layout.buffers[vb_idx]; +        if (0 == src_vb_desc->stride) { +            break; +        } +        vb_desc[vb_idx].arrayStride = src_vb_desc->stride; +        vb_desc[vb_idx].stepMode = _sg_wgpu_stepmode(src_vb_desc->step_func); +        /* NOTE: WebGPU has no support for vertex step rate (because that's +           not supported by Core Vulkan +        */ +        int va_idx = 0; +        for (int va_loc = 0; va_loc < SG_MAX_VERTEX_ATTRIBUTES; va_loc++) { +            const sg_vertex_attr_desc* src_va_desc = &desc->layout.attrs[va_loc]; +            if (SG_VERTEXFORMAT_INVALID == src_va_desc->format) { +                break; +            } +            pip->cmn.vertex_layout_valid[src_va_desc->buffer_index] = true; +            if (vb_idx == src_va_desc->buffer_index) { +                va_desc[vb_idx][va_idx].format = _sg_wgpu_vertexformat(src_va_desc->format); +                va_desc[vb_idx][va_idx].offset = src_va_desc->offset; +                va_desc[vb_idx][va_idx].shaderLocation = va_loc; +                va_idx++; +            } +        } +        vb_desc[vb_idx].attributeCount = va_idx; +        vb_desc[vb_idx].attributes = &va_desc[vb_idx][0]; +    } +    WGPUVertexStateDescriptor vx_state_desc; +    memset(&vx_state_desc, 0, sizeof(vx_state_desc)); +    vx_state_desc.indexFormat = _sg_wgpu_indexformat(desc->index_type); +    vx_state_desc.vertexBufferCount = vb_idx; +    vx_state_desc.vertexBuffers = vb_desc; + +    WGPURasterizationStateDescriptor rs_desc; +    memset(&rs_desc, 0, sizeof(rs_desc)); +    rs_desc.frontFace = _sg_wgpu_frontface(desc->face_winding); +    rs_desc.cullMode = _sg_wgpu_cullmode(desc->cull_mode); +    rs_desc.depthBias = (int32_t) desc->depth.bias; +    rs_desc.depthBiasClamp = desc->depth.bias_clamp; +    rs_desc.depthBiasSlopeScale = desc->depth.bias_slope_scale; + +    WGPUDepthStencilStateDescriptor ds_desc; +    memset(&ds_desc, 0, sizeof(ds_desc)); +    ds_desc.format = _sg_wgpu_textureformat(desc->depth.pixel_format); +    ds_desc.depthWriteEnabled = desc->depth.write_enabled; +    ds_desc.depthCompare = _sg_wgpu_comparefunc(desc->depth.compare); +    ds_desc.stencilReadMask = desc->stencil.read_mask; +    ds_desc.stencilWriteMask = desc->stencil.write_mask; +    ds_desc.stencilFront.compare = _sg_wgpu_comparefunc(desc->stencil.front.compare); +    ds_desc.stencilFront.failOp = _sg_wgpu_stencilop(desc->stencil.front.fail_op); +    ds_desc.stencilFront.depthFailOp = _sg_wgpu_stencilop(desc->stencil.front.depth_fail_op); +    ds_desc.stencilFront.passOp = _sg_wgpu_stencilop(desc->stencil.front.pass_op); +    ds_desc.stencilBack.compare = _sg_wgpu_comparefunc(desc->stencil.back.compare); +    ds_desc.stencilBack.failOp = _sg_wgpu_stencilop(desc->stencil.back.fail_op); +    ds_desc.stencilBack.depthFailOp = _sg_wgpu_stencilop(desc->stencil.back.depth_fail_op); +    ds_desc.stencilBack.passOp = _sg_wgpu_stencilop(desc->stencil.back.pass_op); + +    WGPUProgrammableStageDescriptor fs_desc; +    memset(&fs_desc, 0, sizeof(fs_desc)); +    fs_desc.module = shd->wgpu.stage[SG_SHADERSTAGE_FS].module; +    fs_desc.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].entry.buf; + +    WGPUColorStateDescriptor cs_desc[SG_MAX_COLOR_ATTACHMENTS]; +    memset(cs_desc, 0, sizeof(cs_desc)); +    for (uint32_t i = 0; i < desc->color_count; i++) { +        SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); +        cs_desc[i].format = _sg_wgpu_textureformat(desc->colors[i].pixel_format); +        cs_desc[i].colorBlend.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_rgb); +        cs_desc[i].colorBlend.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_rgb); +        cs_desc[i].colorBlend.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_rgb); +        cs_desc[i].alphaBlend.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_alpha); +        cs_desc[i].alphaBlend.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_alpha); +        cs_desc[i].alphaBlend.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_alpha); +        cs_desc[i].writeMask = _sg_wgpu_colorwritemask(desc->colors[i].write_mask); +    } + +    WGPURenderPipelineDescriptor pip_desc; +    memset(&pip_desc, 0, sizeof(pip_desc)); +    pip_desc.layout = pip_layout; +    pip_desc.vertexStage.module = shd->wgpu.stage[SG_SHADERSTAGE_VS].module; +    pip_desc.vertexStage.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].entry.buf; +    pip_desc.fragmentStage = &fs_desc; +    pip_desc.vertexState = &vx_state_desc; +    pip_desc.primitiveTopology  = _sg_wgpu_topology(desc->primitive_type); +    pip_desc.rasterizationState = &rs_desc; +    pip_desc.sampleCount = desc->sample_count; +    if (SG_PIXELFORMAT_NONE != desc->depth.pixel_format) { +        pip_desc.depthStencilState = &ds_desc; +    } +    pip_desc.colorStateCount = desc->color_count; +    pip_desc.colorStates = cs_desc; +    pip_desc.sampleMask = 0xFFFFFFFF;   /* FIXME: ??? */ +    pip->wgpu.pip = wgpuDeviceCreateRenderPipeline(_sg.wgpu.dev, &pip_desc); +    SOKOL_ASSERT(0 != pip->wgpu.pip); +    wgpuPipelineLayoutRelease(pip_layout); + +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    if (pip->wgpu.pip) { +        wgpuRenderPipelineRelease(pip->wgpu.pip); +        pip->wgpu.pip = 0; +    } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pass(_sg_pass_t* pass, _sg_image_t** att_images, const sg_pass_desc* desc) { +    SOKOL_ASSERT(pass && desc); +    SOKOL_ASSERT(att_images && att_images[0]); +    _sg_pass_common_init(&pass->cmn, desc); + +    /* copy image pointers and create render-texture views */ +    const sg_pass_attachment_desc* att_desc; +    for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { +        att_desc = &desc->color_attachments[i]; +        if (att_desc->image.id != SG_INVALID_ID) { +            SOKOL_ASSERT(att_desc->image.id != SG_INVALID_ID); +            SOKOL_ASSERT(0 == pass->wgpu.color_atts[i].image); +            _sg_image_t* img = att_images[i]; +            SOKOL_ASSERT(img && (img->slot.id == att_desc->image.id)); +            SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format)); +            pass->wgpu.color_atts[i].image = img; +            /* create a render-texture-view to render into the right sub-surface */ +            const bool is_msaa = img->cmn.sample_count > 1; +            WGPUTextureViewDescriptor view_desc; +            memset(&view_desc, 0, sizeof(view_desc)); +            view_desc.baseMipLevel = is_msaa ? 0 : att_desc->mip_level; +            view_desc.mipLevelCount = 1; +            view_desc.baseArrayLayer = is_msaa ? 0 : att_desc->slice; +            view_desc.arrayLayerCount = 1; +            WGPUTexture wgpu_tex = is_msaa ? img->wgpu.msaa_tex : img->wgpu.tex; +            SOKOL_ASSERT(wgpu_tex); +            pass->wgpu.color_atts[i].render_tex_view = wgpuTextureCreateView(wgpu_tex, &view_desc); +            SOKOL_ASSERT(pass->wgpu.color_atts[i].render_tex_view); +            /* ... and if needed a separate resolve texture view */ +            if (is_msaa) { +                view_desc.baseMipLevel = att_desc->mip_level; +                view_desc.baseArrayLayer = att_desc->slice; +                WGPUTexture wgpu_tex = img->wgpu.tex; +                pass->wgpu.color_atts[i].resolve_tex_view = wgpuTextureCreateView(wgpu_tex, &view_desc); +                SOKOL_ASSERT(pass->wgpu.color_atts[i].resolve_tex_view); +            } +        } +    } +    SOKOL_ASSERT(0 == pass->wgpu.ds_att.image); +    att_desc = &desc->depth_stencil_attachment; +    if (att_desc->image.id != SG_INVALID_ID) { +        const int ds_img_index = SG_MAX_COLOR_ATTACHMENTS; +        SOKOL_ASSERT(att_images[ds_img_index] && (att_images[ds_img_index]->slot.id == att_desc->image.id)); +        SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(att_images[ds_img_index]->cmn.pixel_format)); +        _sg_image_t* ds_img = att_images[ds_img_index]; +        pass->wgpu.ds_att.image = ds_img; +        /* create a render-texture view */ +        SOKOL_ASSERT(0 == att_desc->mip_level); +        SOKOL_ASSERT(0 == att_desc->slice); +        WGPUTextureViewDescriptor view_desc; +        memset(&view_desc, 0, sizeof(view_desc)); +        WGPUTexture wgpu_tex = ds_img->wgpu.tex; +        SOKOL_ASSERT(wgpu_tex); +        pass->wgpu.ds_att.render_tex_view = wgpuTextureCreateView(wgpu_tex, &view_desc); +        SOKOL_ASSERT(pass->wgpu.ds_att.render_tex_view); +    } +    return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_destroy_pass(_sg_pass_t* pass) { +    SOKOL_ASSERT(pass); +    for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { +        if (pass->wgpu.color_atts[i].render_tex_view) { +            wgpuTextureViewRelease(pass->wgpu.color_atts[i].render_tex_view); +            pass->wgpu.color_atts[i].render_tex_view = 0; +        } +        if (pass->wgpu.color_atts[i].resolve_tex_view) { +            wgpuTextureViewRelease(pass->wgpu.color_atts[i].resolve_tex_view); +            pass->wgpu.color_atts[i].resolve_tex_view = 0; +        } +    } +    if (pass->wgpu.ds_att.render_tex_view) { +        wgpuTextureViewRelease(pass->wgpu.ds_att.render_tex_view); +        pass->wgpu.ds_att.render_tex_view = 0; +    } +} + +_SOKOL_PRIVATE _sg_image_t* _sg_wgpu_pass_color_image(const _sg_pass_t* pass, int index) { +    SOKOL_ASSERT(pass && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); +    /* NOTE: may return null */ +    return pass->wgpu.color_atts[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_wgpu_pass_ds_image(const _sg_pass_t* pass) { +    /* NOTE: may return null */ +    SOKOL_ASSERT(pass); +    return pass->wgpu.ds_att.image; +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_pass(_sg_pass_t* pass, const sg_pass_action* action, int w, int h) { +    SOKOL_ASSERT(action); +    SOKOL_ASSERT(!_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); +    SOKOL_ASSERT(_sg.wgpu.dev); +    SOKOL_ASSERT(_sg.wgpu.render_view_cb || _sg.wgpu.render_view_userdata_cb); +    SOKOL_ASSERT(_sg.wgpu.resolve_view_cb || _sg.wgpu.resolve_view_userdata_cb); +    SOKOL_ASSERT(_sg.wgpu.depth_stencil_view_cb || _sg.wgpu.depth_stencil_view_userdata_cb); +    _sg.wgpu.in_pass = true; +    _sg.wgpu.cur_width = w; +    _sg.wgpu.cur_height = h; +    _sg.wgpu.cur_pipeline = 0; +    _sg.wgpu.cur_pipeline_id.id = SG_INVALID_ID; + +    SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); +    if (pass) { +        WGPURenderPassDescriptor wgpu_pass_desc; +        memset(&wgpu_pass_desc, 0, sizeof(wgpu_pass_desc)); +        WGPURenderPassColorAttachmentDescriptor wgpu_color_att_desc[SG_MAX_COLOR_ATTACHMENTS]; +        memset(&wgpu_color_att_desc, 0, sizeof(wgpu_color_att_desc)); +        SOKOL_ASSERT(pass->slot.state == SG_RESOURCESTATE_VALID); +        for (uint32_t i = 0; i < pass->cmn.num_color_atts; i++) { +            const _sg_wgpu_attachment_t* wgpu_att = &pass->wgpu.color_atts[i]; +            wgpu_color_att_desc[i].loadOp = _sg_wgpu_load_op(action->colors[i].action); +            wgpu_color_att_desc[i].storeOp = WGPUStoreOp_Store; +            wgpu_color_att_desc[i].clearColor.r = action->colors[i].value.r; +            wgpu_color_att_desc[i].clearColor.g = action->colors[i].value.g; +            wgpu_color_att_desc[i].clearColor.b = action->colors[i].value.b; +            wgpu_color_att_desc[i].clearColor.a = action->colors[i].value.a; +            wgpu_color_att_desc[i].attachment = wgpu_att->render_tex_view; +            if (wgpu_att->image->cmn.sample_count > 1) { +                wgpu_color_att_desc[i].resolveTarget = wgpu_att->resolve_tex_view; +            } +        } +        wgpu_pass_desc.colorAttachmentCount = pass->cmn.num_color_atts; +        wgpu_pass_desc.colorAttachments = &wgpu_color_att_desc[0]; +        if (pass->wgpu.ds_att.image) { +            WGPURenderPassDepthStencilAttachmentDescriptor wgpu_ds_att_desc; +            memset(&wgpu_ds_att_desc, 0, sizeof(wgpu_ds_att_desc)); +            wgpu_ds_att_desc.depthLoadOp = _sg_wgpu_load_op(action->depth.action); +            wgpu_ds_att_desc.clearDepth = action->depth.value; +            wgpu_ds_att_desc.stencilLoadOp = _sg_wgpu_load_op(action->stencil.action); +            wgpu_ds_att_desc.clearStencil = action->stencil.value; +            wgpu_ds_att_desc.attachment = pass->wgpu.ds_att.render_tex_view; +            wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att_desc; +            _sg.wgpu.pass_enc = wgpuCommandEncoderBeginRenderPass(_sg.wgpu.render_cmd_enc, &wgpu_pass_desc); +        } +    } +    else { +        /* default render pass */ +        WGPUTextureView wgpu_render_view = _sg.wgpu.render_view_cb ? _sg.wgpu.render_view_cb() : _sg.wgpu.render_view_userdata_cb(_sg.wgpu.user_data); +        WGPUTextureView wgpu_resolve_view = _sg.wgpu.resolve_view_cb ? _sg.wgpu.resolve_view_cb() : _sg.wgpu.resolve_view_userdata_cb(_sg.wgpu.user_data); +        WGPUTextureView wgpu_depth_stencil_view = _sg.wgpu.depth_stencil_view_cb ? _sg.wgpu.depth_stencil_view_cb() : _sg.wgpu.depth_stencil_view_userdata_cb(_sg.wgpu.user_data); + +        WGPURenderPassDescriptor pass_desc; +        memset(&pass_desc, 0, sizeof(pass_desc)); +        WGPURenderPassColorAttachmentDescriptor color_att_desc; +        memset(&color_att_desc, 0, sizeof(color_att_desc)); +        color_att_desc.loadOp = _sg_wgpu_load_op(action->colors[0].action); +        color_att_desc.clearColor.r = action->colors[0].value.r; +        color_att_desc.clearColor.g = action->colors[0].value.g; +        color_att_desc.clearColor.b = action->colors[0].value.b; +        color_att_desc.clearColor.a = action->colors[0].value.a; +        color_att_desc.attachment = wgpu_render_view; +        color_att_desc.resolveTarget = wgpu_resolve_view;   /* null if no MSAA rendering */ +        pass_desc.colorAttachmentCount = 1; +        pass_desc.colorAttachments = &color_att_desc; +        WGPURenderPassDepthStencilAttachmentDescriptor ds_att_desc; +        memset(&ds_att_desc, 0, sizeof(ds_att_desc)); +        ds_att_desc.attachment = wgpu_depth_stencil_view; +        SOKOL_ASSERT(0 != ds_att_desc.attachment); +        ds_att_desc.depthLoadOp = _sg_wgpu_load_op(action->depth.action); +        ds_att_desc.clearDepth = action->depth.value; +        ds_att_desc.stencilLoadOp = _sg_wgpu_load_op(action->stencil.action); +        ds_att_desc.clearStencil = action->stencil.value; +        pass_desc.depthStencilAttachment = &ds_att_desc; +        _sg.wgpu.pass_enc = wgpuCommandEncoderBeginRenderPass(_sg.wgpu.render_cmd_enc, &pass_desc); +    } +    SOKOL_ASSERT(_sg.wgpu.pass_enc); + +    /* initial uniform buffer binding (required even if no uniforms are set in the frame) */ +    wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, +                                      0, /* groupIndex 0 is reserved for uniform buffers */ +                                      _sg.wgpu.ub.bindgroup, +                                      SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS, +                                      &_sg.wgpu.ub.bind_offsets[0][0]); +} + +_SOKOL_PRIVATE void _sg_wgpu_end_pass(void) { +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    _sg.wgpu.in_pass = false; +    wgpuRenderPassEncoderEndPass(_sg.wgpu.pass_enc); +    wgpuRenderPassEncoderRelease(_sg.wgpu.pass_enc); +    _sg.wgpu.pass_enc = 0; +} + +_SOKOL_PRIVATE void _sg_wgpu_commit(void) { +    SOKOL_ASSERT(!_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.queue); +    SOKOL_ASSERT(_sg.wgpu.render_cmd_enc); +    SOKOL_ASSERT(_sg.wgpu.staging_cmd_enc); + +    /* finish and submit this frame's work */ +    _sg_wgpu_ubpool_flush(); +    _sg_wgpu_staging_unmap(); + +    WGPUCommandBuffer cmd_bufs[2]; + +    WGPUCommandBufferDescriptor cmd_buf_desc; +    memset(&cmd_buf_desc, 0, sizeof(cmd_buf_desc)); +    cmd_bufs[0] = wgpuCommandEncoderFinish(_sg.wgpu.staging_cmd_enc, &cmd_buf_desc); +    SOKOL_ASSERT(cmd_bufs[0]); +    wgpuCommandEncoderRelease(_sg.wgpu.staging_cmd_enc); +    _sg.wgpu.staging_cmd_enc = 0; + +    cmd_bufs[1] = wgpuCommandEncoderFinish(_sg.wgpu.render_cmd_enc, &cmd_buf_desc); +    SOKOL_ASSERT(cmd_bufs[1]); +    wgpuCommandEncoderRelease(_sg.wgpu.render_cmd_enc); +    _sg.wgpu.render_cmd_enc = 0; + +    wgpuQueueSubmit(_sg.wgpu.queue, 2, &cmd_bufs[0]); + +    wgpuCommandBufferRelease(cmd_bufs[0]); +    wgpuCommandBufferRelease(cmd_bufs[1]); + +    /* create a new render- and staging-command-encoders for next frame */ +    WGPUCommandEncoderDescriptor cmd_enc_desc; +    memset(&cmd_enc_desc, 0, sizeof(cmd_enc_desc)); +    _sg.wgpu.staging_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); +    _sg.wgpu.render_cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); + +    /* grab new staging buffers for uniform- and vertex/image-updates */ +    _sg_wgpu_ubpool_next_frame(false); +    _sg_wgpu_staging_next_frame(false); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    float xf = (float) x; +    float yf = (float) (origin_top_left ? y : (_sg.wgpu.cur_height - (y + h))); +    float wf = (float) w; +    float hf = (float) h; +    wgpuRenderPassEncoderSetViewport(_sg.wgpu.pass_enc, xf, yf, wf, hf, 0.0f, 1.0f); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); + +    /* clip against framebuffer rect */ +    x = _sg_min(_sg_max(0, x), _sg.wgpu.cur_width-1); +    y = _sg_min(_sg_max(0, y), _sg.wgpu.cur_height-1); +    if ((x + w) > _sg.wgpu.cur_width) { +        w = _sg.wgpu.cur_width - x; +    } +    if ((y + h) > _sg.wgpu.cur_height) { +        h = _sg.wgpu.cur_height - y; +    } +    w = _sg_max(w, 1); +    h = _sg_max(h, 1); + +    uint32_t sx = (uint32_t) x; +    uint32_t sy = origin_top_left ? y : (_sg.wgpu.cur_height - (y + h)); +    uint32_t sw = w; +    uint32_t sh = h; +    wgpuRenderPassEncoderSetScissorRect(_sg.wgpu.pass_enc, sx, sy, sw, sh); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    SOKOL_ASSERT(pip->wgpu.pip); +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    _sg.wgpu.draw_indexed = (pip->cmn.index_type != SG_INDEXTYPE_NONE); +    _sg.wgpu.cur_pipeline = pip; +    _sg.wgpu.cur_pipeline_id.id = pip->slot.id; +    wgpuRenderPassEncoderSetPipeline(_sg.wgpu.pass_enc, pip->wgpu.pip); +    wgpuRenderPassEncoderSetBlendColor(_sg.wgpu.pass_enc, (WGPUColor*)&pip->cmn.blend_color); +    wgpuRenderPassEncoderSetStencilReference(_sg.wgpu.pass_enc, pip->wgpu.stencil_ref); +} + +_SOKOL_PRIVATE WGPUBindGroup _sg_wgpu_create_images_bindgroup(WGPUBindGroupLayout bgl, _sg_image_t** imgs, int num_imgs) { +    SOKOL_ASSERT(_sg.wgpu.dev); +    SOKOL_ASSERT(num_imgs <= _SG_WGPU_MAX_SHADERSTAGE_IMAGES); +    WGPUBindGroupBinding img_bgb[_SG_WGPU_MAX_SHADERSTAGE_IMAGES * 2]; +    memset(&img_bgb, 0, sizeof(img_bgb)); +    for (int img_index = 0; img_index < num_imgs; img_index++) { +        WGPUBindGroupBinding* tex_bdg = &img_bgb[img_index*2 + 0]; +        WGPUBindGroupBinding* smp_bdg = &img_bgb[img_index*2 + 1]; +        tex_bdg->binding = img_index; +        tex_bdg->textureView = imgs[img_index]->wgpu.tex_view; +        smp_bdg->binding = img_index + _SG_WGPU_MAX_SHADERSTAGE_IMAGES; +        smp_bdg->sampler = imgs[img_index]->wgpu.sampler; +    } +    WGPUBindGroupDescriptor bg_desc; +    memset(&bg_desc, 0, sizeof(bg_desc)); +    bg_desc.layout = bgl; +    bg_desc.bindingCount = 2 * num_imgs; +    bg_desc.bindings = &img_bgb[0]; +    WGPUBindGroup bg = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); +    SOKOL_ASSERT(bg); +    return bg; +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_bindings( +    _sg_pipeline_t* pip, +    _sg_buffer_t** vbs, const int* vb_offsets, int num_vbs, +    _sg_buffer_t* ib, int ib_offset, +    _sg_image_t** vs_imgs, int num_vs_imgs, +    _sg_image_t** fs_imgs, int num_fs_imgs) +{ +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + +    /* index buffer */ +    if (ib) { +        wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.pass_enc, ib->wgpu.buf, ib_offset); +    } + +    /* vertex buffers */ +    for (uint32_t slot = 0; slot < (uint32_t)num_vbs; slot++) { +        wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.pass_enc, slot, vbs[slot]->wgpu.buf, (uint64_t)vb_offsets[slot]); +    } + +    /* need to create throw-away bind groups for images */ +    if (num_vs_imgs > 0) { +        if (num_vs_imgs > _SG_WGPU_MAX_SHADERSTAGE_IMAGES) { +            num_vs_imgs = _SG_WGPU_MAX_SHADERSTAGE_IMAGES; +        } +        WGPUBindGroupLayout vs_bgl = pip->shader->wgpu.stage[SG_SHADERSTAGE_VS].bind_group_layout; +        SOKOL_ASSERT(vs_bgl); +        WGPUBindGroup vs_img_bg = _sg_wgpu_create_images_bindgroup(vs_bgl, vs_imgs, num_vs_imgs); +        wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, 1, vs_img_bg, 0, 0); +        wgpuBindGroupRelease(vs_img_bg); +    } +    else { +        wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, 1, _sg.wgpu.empty_bind_group, 0, 0); +    } +    if (num_fs_imgs > 0) { +        if (num_fs_imgs > _SG_WGPU_MAX_SHADERSTAGE_IMAGES) { +            num_fs_imgs = _SG_WGPU_MAX_SHADERSTAGE_IMAGES; +        } +        WGPUBindGroupLayout fs_bgl = pip->shader->wgpu.stage[SG_SHADERSTAGE_FS].bind_group_layout; +        SOKOL_ASSERT(fs_bgl); +        WGPUBindGroup fs_img_bg = _sg_wgpu_create_images_bindgroup(fs_bgl, fs_imgs, num_fs_imgs); +        wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, 2, fs_img_bg, 0, 0); +        wgpuBindGroupRelease(fs_img_bg); +    } +    else { +        wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, 2, _sg.wgpu.empty_bind_group, 0, 0); +    } +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    SOKOL_ASSERT((_sg.wgpu.ub.offset + data->size) <= _sg.wgpu.ub.num_bytes); +    SOKOL_ASSERT((_sg.wgpu.ub.offset & (_SG_WGPU_STAGING_ALIGN-1)) == 0); +    SOKOL_ASSERT(_sg.wgpu.cur_pipeline && _sg.wgpu.cur_pipeline->shader); +    SOKOL_ASSERT(_sg.wgpu.cur_pipeline->slot.id == _sg.wgpu.cur_pipeline_id.id); +    SOKOL_ASSERT(_sg.wgpu.cur_pipeline->shader->slot.id == _sg.wgpu.cur_pipeline->cmn.shader_id.id); +    SOKOL_ASSERT(ub_index < _sg.wgpu.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); +    SOKOL_ASSERT(data->size <= _sg.wgpu.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); +    SOKOL_ASSERT(data->size <= _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE); +    SOKOL_ASSERT(0 != _sg.wgpu.ub.stage.ptr[_sg.wgpu.ub.stage.cur]); + +    uint8_t* dst_ptr = _sg.wgpu.ub.stage.ptr[_sg.wgpu.ub.stage.cur] + _sg.wgpu.ub.offset; +    memcpy(dst_ptr, data->ptr, data->size); +    _sg.wgpu.ub.bind_offsets[stage_index][ub_index] = _sg.wgpu.ub.offset; +    wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, +                                      0, /* groupIndex 0 is reserved for uniform buffers */ +                                      _sg.wgpu.ub.bindgroup, +                                      SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS, +                                      &_sg.wgpu.ub.bind_offsets[0][0]); +    _sg.wgpu.ub.offset = _sg_roundup(_sg.wgpu.ub.offset + data->size, _SG_WGPU_STAGING_ALIGN); +} + +_SOKOL_PRIVATE void _sg_wgpu_draw(int base_element, int num_elements, int num_instances) { +    SOKOL_ASSERT(_sg.wgpu.in_pass); +    SOKOL_ASSERT(_sg.wgpu.pass_enc); +    if (_sg.wgpu.draw_indexed) { +        wgpuRenderPassEncoderDrawIndexed(_sg.wgpu.pass_enc, num_elements, num_instances, base_element, 0, 0); +    } +    else { +        wgpuRenderPassEncoderDraw(_sg.wgpu.pass_enc, num_elements, num_instances, base_element, 0); +    } +} + +_SOKOL_PRIVATE void _sg_wgpu_update_buffer(_sg_buffer_t* buf, const sg_range* data) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    uint32_t copied_num_bytes = _sg_wgpu_staging_copy_to_buffer(buf->wgpu.buf, 0, data->ptr, data->size); +    SOKOL_ASSERT(copied_num_bytes > 0); _SOKOL_UNUSED(copied_num_bytes); +} + +_SOKOL_PRIVATE int _sg_wgpu_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); +    _SOKOL_UNUSED(new_frame); +    uint32_t copied_num_bytes = _sg_wgpu_staging_copy_to_buffer(buf->wgpu.buf, buf->cmn.append_pos, data->ptr, data->size); +    SOKOL_ASSERT(copied_num_bytes > 0); _SOKOL_UNUSED(copied_num_bytes); +    return (int)copied_num_bytes; +} + +_SOKOL_PRIVATE void _sg_wgpu_update_image(_sg_image_t* img, const sg_image_data* data) { +    SOKOL_ASSERT(img && data); +    bool success = _sg_wgpu_staging_copy_to_texture(img, data); +    SOKOL_ASSERT(success); +    _SOKOL_UNUSED(success); +} +#endif + +/*== BACKEND API WRAPPERS ====================================================*/ +static inline void _sg_setup_backend(const sg_desc* desc) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_setup_backend(desc); +    #elif defined(SOKOL_METAL) +    _sg_mtl_setup_backend(desc); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_setup_backend(desc); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_setup_backend(desc); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_setup_backend(desc); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_discard_backend(void) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_discard_backend(); +    #elif defined(SOKOL_METAL) +    _sg_mtl_discard_backend(); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_discard_backend(); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_discard_backend(); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_discard_backend(); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_reset_state_cache(void) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_reset_state_cache(); +    #elif defined(SOKOL_METAL) +    _sg_mtl_reset_state_cache(); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_reset_state_cache(); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_reset_state_cache(); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_reset_state_cache(); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_activate_context(_sg_context_t* ctx) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_activate_context(ctx); +    #elif defined(SOKOL_METAL) +    _sg_mtl_activate_context(ctx); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_activate_context(ctx); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_activate_context(ctx); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_activate_context(ctx); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline sg_resource_state _sg_create_context(_sg_context_t* ctx) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_create_context(ctx); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_create_context(ctx); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_create_context(ctx); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_create_context(ctx); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_create_context(ctx); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_destroy_context(_sg_context_t* ctx) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_destroy_context(ctx); +    #elif defined(SOKOL_METAL) +    _sg_mtl_destroy_context(ctx); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_destroy_context(ctx); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_destroy_context(ctx); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_destroy_context(ctx); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline sg_resource_state _sg_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_create_buffer(buf, desc); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_create_buffer(buf, desc); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_create_buffer(buf, desc); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_create_buffer(buf, desc); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_create_buffer(buf, desc); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_destroy_buffer(_sg_buffer_t* buf) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_destroy_buffer(buf); +    #elif defined(SOKOL_METAL) +    _sg_mtl_destroy_buffer(buf); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_destroy_buffer(buf); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_destroy_buffer(buf); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_destroy_buffer(buf); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline sg_resource_state _sg_create_image(_sg_image_t* img, const sg_image_desc* desc) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_create_image(img, desc); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_create_image(img, desc); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_create_image(img, desc); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_create_image(img, desc); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_create_image(img, desc); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_destroy_image(_sg_image_t* img) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_destroy_image(img); +    #elif defined(SOKOL_METAL) +    _sg_mtl_destroy_image(img); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_destroy_image(img); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_destroy_image(img); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_destroy_image(img); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline sg_resource_state _sg_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_create_shader(shd, desc); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_create_shader(shd, desc); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_create_shader(shd, desc); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_create_shader(shd, desc); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_create_shader(shd, desc); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_destroy_shader(_sg_shader_t* shd) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_destroy_shader(shd); +    #elif defined(SOKOL_METAL) +    _sg_mtl_destroy_shader(shd); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_destroy_shader(shd); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_destroy_shader(shd); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_destroy_shader(shd); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline sg_resource_state _sg_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_create_pipeline(pip, shd, desc); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_create_pipeline(pip, shd, desc); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_create_pipeline(pip, shd, desc); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_create_pipeline(pip, shd, desc); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_create_pipeline(pip, shd, desc); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_destroy_pipeline(_sg_pipeline_t* pip) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_destroy_pipeline(pip); +    #elif defined(SOKOL_METAL) +    _sg_mtl_destroy_pipeline(pip); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_destroy_pipeline(pip); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_destroy_pipeline(pip); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_destroy_pipeline(pip); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline sg_resource_state _sg_create_pass(_sg_pass_t* pass, _sg_image_t** att_images, const sg_pass_desc* desc) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_create_pass(pass, att_images, desc); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_create_pass(pass, att_images, desc); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_create_pass(pass, att_images, desc); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_create_pass(pass, att_images, desc); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_create_pass(pass, att_images, desc); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_destroy_pass(_sg_pass_t* pass) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_destroy_pass(pass); +    #elif defined(SOKOL_METAL) +    _sg_mtl_destroy_pass(pass); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_destroy_pass(pass); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_destroy_pass(pass); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_destroy_pass(pass); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline _sg_image_t* _sg_pass_color_image(const _sg_pass_t* pass, int index) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_pass_color_image(pass, index); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_pass_color_image(pass, index); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_pass_color_image(pass, index); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_pass_color_image(pass, index); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_pass_color_image(pass, index); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline _sg_image_t* _sg_pass_ds_image(const _sg_pass_t* pass) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_pass_ds_image(pass); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_pass_ds_image(pass); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_pass_ds_image(pass); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_pass_ds_image(pass); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_pass_ds_image(pass); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_begin_pass(_sg_pass_t* pass, const sg_pass_action* action, int w, int h) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_begin_pass(pass, action, w, h); +    #elif defined(SOKOL_METAL) +    _sg_mtl_begin_pass(pass, action, w, h); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_begin_pass(pass, action, w, h); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_begin_pass(pass, action, w, h); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_begin_pass(pass, action, w, h); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_end_pass(void) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_end_pass(); +    #elif defined(SOKOL_METAL) +    _sg_mtl_end_pass(); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_end_pass(); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_end_pass(); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_end_pass(); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_apply_viewport(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_METAL) +    _sg_mtl_apply_viewport(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_apply_viewport(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_apply_viewport(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_apply_viewport(x, y, w, h, origin_top_left); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_apply_scissor_rect(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_METAL) +    _sg_mtl_apply_scissor_rect(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_apply_scissor_rect(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_apply_scissor_rect(x, y, w, h, origin_top_left); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_apply_scissor_rect(x, y, w, h, origin_top_left); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_apply_pipeline(_sg_pipeline_t* pip) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_apply_pipeline(pip); +    #elif defined(SOKOL_METAL) +    _sg_mtl_apply_pipeline(pip); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_apply_pipeline(pip); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_apply_pipeline(pip); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_apply_pipeline(pip); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_apply_bindings( +    _sg_pipeline_t* pip, +    _sg_buffer_t** vbs, const int* vb_offsets, int num_vbs, +    _sg_buffer_t* ib, int ib_offset, +    _sg_image_t** vs_imgs, int num_vs_imgs, +    _sg_image_t** fs_imgs, int num_fs_imgs) +{ +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_apply_bindings(pip, vbs, vb_offsets, num_vbs, ib, ib_offset, vs_imgs, num_vs_imgs, fs_imgs, num_fs_imgs); +    #elif defined(SOKOL_METAL) +    _sg_mtl_apply_bindings(pip, vbs, vb_offsets, num_vbs, ib, ib_offset, vs_imgs, num_vs_imgs, fs_imgs, num_fs_imgs); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_apply_bindings(pip, vbs, vb_offsets, num_vbs, ib, ib_offset, vs_imgs, num_vs_imgs, fs_imgs, num_fs_imgs); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_apply_bindings(pip, vbs, vb_offsets, num_vbs, ib, ib_offset, vs_imgs, num_vs_imgs, fs_imgs, num_fs_imgs); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_apply_bindings(pip, vbs, vb_offsets, num_vbs, ib, ib_offset, vs_imgs, num_vs_imgs, fs_imgs, num_fs_imgs); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_apply_uniforms(stage_index, ub_index, data); +    #elif defined(SOKOL_METAL) +    _sg_mtl_apply_uniforms(stage_index, ub_index, data); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_apply_uniforms(stage_index, ub_index, data); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_apply_uniforms(stage_index, ub_index, data); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_apply_uniforms(stage_index, ub_index, data); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_draw(int base_element, int num_elements, int num_instances) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_draw(base_element, num_elements, num_instances); +    #elif defined(SOKOL_METAL) +    _sg_mtl_draw(base_element, num_elements, num_instances); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_draw(base_element, num_elements, num_instances); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_draw(base_element, num_elements, num_instances); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_draw(base_element, num_elements, num_instances); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_commit(void) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_commit(); +    #elif defined(SOKOL_METAL) +    _sg_mtl_commit(); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_commit(); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_commit(); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_commit(); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_update_buffer(_sg_buffer_t* buf, const sg_range* data) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_update_buffer(buf, data); +    #elif defined(SOKOL_METAL) +    _sg_mtl_update_buffer(buf, data); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_update_buffer(buf, data); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_update_buffer(buf, data); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_update_buffer(buf, data); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline int _sg_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { +    #if defined(_SOKOL_ANY_GL) +    return _sg_gl_append_buffer(buf, data, new_frame); +    #elif defined(SOKOL_METAL) +    return _sg_mtl_append_buffer(buf, data, new_frame); +    #elif defined(SOKOL_D3D11) +    return _sg_d3d11_append_buffer(buf, data, new_frame); +    #elif defined(SOKOL_WGPU) +    return _sg_wgpu_append_buffer(buf, data, new_frame); +    #elif defined(SOKOL_DUMMY_BACKEND) +    return _sg_dummy_append_buffer(buf, data, new_frame); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data) { +    #if defined(_SOKOL_ANY_GL) +    _sg_gl_update_image(img, data); +    #elif defined(SOKOL_METAL) +    _sg_mtl_update_image(img, data); +    #elif defined(SOKOL_D3D11) +    _sg_d3d11_update_image(img, data); +    #elif defined(SOKOL_WGPU) +    _sg_wgpu_update_image(img, data); +    #elif defined(SOKOL_DUMMY_BACKEND) +    _sg_dummy_update_image(img, data); +    #else +    #error("INVALID BACKEND"); +    #endif +} + +/*== RESOURCE POOLS ==========================================================*/ + +_SOKOL_PRIVATE void _sg_init_pool(_sg_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 */ +    pool->size = num + 1; +    pool->queue_top = 0; +    /* generation counters indexable by pool slot index, slot 0 is reserved */ +    size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; +    pool->gen_ctrs = (uint32_t*) SOKOL_MALLOC(gen_ctrs_size); +    SOKOL_ASSERT(pool->gen_ctrs); +    memset(pool->gen_ctrs, 0, gen_ctrs_size); +    /* it's not a bug to only reserve 'num' here */ +    pool->free_queue = (int*) SOKOL_MALLOC(sizeof(int) * (size_t)num); +    SOKOL_ASSERT(pool->free_queue); +    /* never allocate the zero-th pool item since the invalid id is 0 */ +    for (int i = pool->size-1; i >= 1; i--) { +        pool->free_queue[pool->queue_top++] = i; +    } +} + +_SOKOL_PRIVATE void _sg_discard_pool(_sg_pool_t* pool) { +    SOKOL_ASSERT(pool); +    SOKOL_ASSERT(pool->free_queue); +    SOKOL_FREE(pool->free_queue); +    pool->free_queue = 0; +    SOKOL_ASSERT(pool->gen_ctrs); +    SOKOL_FREE(pool->gen_ctrs); +    pool->gen_ctrs = 0; +    pool->size = 0; +    pool->queue_top = 0; +} + +_SOKOL_PRIVATE int _sg_pool_alloc_index(_sg_pool_t* pool) { +    SOKOL_ASSERT(pool); +    SOKOL_ASSERT(pool->free_queue); +    if (pool->queue_top > 0) { +        int slot_index = pool->free_queue[--pool->queue_top]; +        SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); +        return slot_index; +    } +    else { +        /* pool exhausted */ +        return _SG_INVALID_SLOT_INDEX; +    } +} + +_SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index) { +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size)); +    SOKOL_ASSERT(pool); +    SOKOL_ASSERT(pool->free_queue); +    SOKOL_ASSERT(pool->queue_top < pool->size); +    #ifdef SOKOL_DEBUG +    /* debug check against double-free */ +    for (int i = 0; i < pool->queue_top; i++) { +        SOKOL_ASSERT(pool->free_queue[i] != slot_index); +    } +    #endif +    pool->free_queue[pool->queue_top++] = slot_index; +    SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +_SOKOL_PRIVATE void _sg_reset_slot(_sg_slot_t* slot) { +    SOKOL_ASSERT(slot); +    memset(slot, 0, sizeof(_sg_slot_t)); +} + +_SOKOL_PRIVATE void _sg_reset_buffer(_sg_buffer_t* buf) { +    SOKOL_ASSERT(buf); +    _sg_slot_t slot = buf->slot; +    memset(buf, 0, sizeof(_sg_buffer_t)); +    buf->slot = slot; +    buf->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_image(_sg_image_t* img) { +    SOKOL_ASSERT(img); +    _sg_slot_t slot = img->slot; +    memset(img, 0, sizeof(_sg_image_t)); +    img->slot = slot; +    img->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_shader(_sg_shader_t* shd) { +    SOKOL_ASSERT(shd); +    _sg_slot_t slot = shd->slot; +    memset(shd, 0, sizeof(_sg_shader_t)); +    shd->slot = slot; +    shd->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_pipeline(_sg_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    _sg_slot_t slot = pip->slot; +    memset(pip, 0, sizeof(_sg_pipeline_t)); +    pip->slot = slot; +    pip->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_pass(_sg_pass_t* pass) { +    SOKOL_ASSERT(pass); +    _sg_slot_t slot = pass->slot; +    memset(pass, 0, sizeof(_sg_pass_t)); +    pass->slot = slot; +    pass->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_context(_sg_context_t* ctx) { +    SOKOL_ASSERT(ctx); +    _sg_slot_t slot = ctx->slot; +    memset(ctx, 0, sizeof(_sg_context_t)); +    ctx->slot = slot; +    ctx->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_setup_pools(_sg_pools_t* p, const sg_desc* desc) { +    SOKOL_ASSERT(p); +    SOKOL_ASSERT(desc); +    /* note: the pools here will have an additional item, since slot 0 is reserved */ +    SOKOL_ASSERT((desc->buffer_pool_size > 0) && (desc->buffer_pool_size < _SG_MAX_POOL_SIZE)); +    _sg_init_pool(&p->buffer_pool, desc->buffer_pool_size); +    size_t buffer_pool_byte_size = sizeof(_sg_buffer_t) * (size_t)p->buffer_pool.size; +    p->buffers = (_sg_buffer_t*) SOKOL_MALLOC(buffer_pool_byte_size); +    SOKOL_ASSERT(p->buffers); +    memset(p->buffers, 0, buffer_pool_byte_size); + +    SOKOL_ASSERT((desc->image_pool_size > 0) && (desc->image_pool_size < _SG_MAX_POOL_SIZE)); +    _sg_init_pool(&p->image_pool, desc->image_pool_size); +    size_t image_pool_byte_size = sizeof(_sg_image_t) * (size_t)p->image_pool.size; +    p->images = (_sg_image_t*) SOKOL_MALLOC(image_pool_byte_size); +    SOKOL_ASSERT(p->images); +    memset(p->images, 0, image_pool_byte_size); + +    SOKOL_ASSERT((desc->shader_pool_size > 0) && (desc->shader_pool_size < _SG_MAX_POOL_SIZE)); +    _sg_init_pool(&p->shader_pool, desc->shader_pool_size); +    size_t shader_pool_byte_size = sizeof(_sg_shader_t) * (size_t)p->shader_pool.size; +    p->shaders = (_sg_shader_t*) SOKOL_MALLOC(shader_pool_byte_size); +    SOKOL_ASSERT(p->shaders); +    memset(p->shaders, 0, shader_pool_byte_size); + +    SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SG_MAX_POOL_SIZE)); +    _sg_init_pool(&p->pipeline_pool, desc->pipeline_pool_size); +    size_t pipeline_pool_byte_size = sizeof(_sg_pipeline_t) * (size_t)p->pipeline_pool.size; +    p->pipelines = (_sg_pipeline_t*) SOKOL_MALLOC(pipeline_pool_byte_size); +    SOKOL_ASSERT(p->pipelines); +    memset(p->pipelines, 0, pipeline_pool_byte_size); + +    SOKOL_ASSERT((desc->pass_pool_size > 0) && (desc->pass_pool_size < _SG_MAX_POOL_SIZE)); +    _sg_init_pool(&p->pass_pool, desc->pass_pool_size); +    size_t pass_pool_byte_size = sizeof(_sg_pass_t) * (size_t)p->pass_pool.size; +    p->passes = (_sg_pass_t*) SOKOL_MALLOC(pass_pool_byte_size); +    SOKOL_ASSERT(p->passes); +    memset(p->passes, 0, pass_pool_byte_size); + +    SOKOL_ASSERT((desc->context_pool_size > 0) && (desc->context_pool_size < _SG_MAX_POOL_SIZE)); +    _sg_init_pool(&p->context_pool, desc->context_pool_size); +    size_t context_pool_byte_size = sizeof(_sg_context_t) * (size_t)p->context_pool.size; +    p->contexts = (_sg_context_t*) SOKOL_MALLOC(context_pool_byte_size); +    SOKOL_ASSERT(p->contexts); +    memset(p->contexts, 0, context_pool_byte_size); +} + +_SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) { +    SOKOL_ASSERT(p); +    SOKOL_FREE(p->contexts);    p->contexts = 0; +    SOKOL_FREE(p->passes);      p->passes = 0; +    SOKOL_FREE(p->pipelines);   p->pipelines = 0; +    SOKOL_FREE(p->shaders);     p->shaders = 0; +    SOKOL_FREE(p->images);      p->images = 0; +    SOKOL_FREE(p->buffers);     p->buffers = 0; +    _sg_discard_pool(&p->context_pool); +    _sg_discard_pool(&p->pass_pool); +    _sg_discard_pool(&p->pipeline_pool); +    _sg_discard_pool(&p->shader_pool); +    _sg_discard_pool(&p->image_pool); +    _sg_discard_pool(&p->buffer_pool); +} + +/* allocate the slot at slot_index: +    - bump the slot's generation counter +    - create a resource id from the generation counter and slot index +    - set the slot's id to this id +    - set the slot's state to ALLOC +    - return the resource id +*/ +_SOKOL_PRIVATE uint32_t _sg_slot_alloc(_sg_pool_t* pool, _sg_slot_t* slot, int slot_index) { +    /* FIXME: add handling for an overflowing generation counter, +       for now, just overflow (another option is to disable +       the slot) +    */ +    SOKOL_ASSERT(pool && pool->gen_ctrs); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size)); +    SOKOL_ASSERT((slot->state == SG_RESOURCESTATE_INITIAL) && (slot->id == SG_INVALID_ID)); +    uint32_t ctr = ++pool->gen_ctrs[slot_index]; +    slot->id = (ctr<<_SG_SLOT_SHIFT)|(slot_index & _SG_SLOT_MASK); +    slot->state = SG_RESOURCESTATE_ALLOC; +    return slot->id; +} + +/* extract slot index from id */ +_SOKOL_PRIVATE int _sg_slot_index(uint32_t id) { +    int slot_index = (int) (id & _SG_SLOT_MASK); +    SOKOL_ASSERT(_SG_INVALID_SLOT_INDEX != slot_index); +    return slot_index; +} + +/* returns pointer to resource by id without matching id check */ +_SOKOL_PRIVATE _sg_buffer_t* _sg_buffer_at(const _sg_pools_t* p, uint32_t buf_id) { +    SOKOL_ASSERT(p && (SG_INVALID_ID != buf_id)); +    int slot_index = _sg_slot_index(buf_id); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->buffer_pool.size)); +    return &p->buffers[slot_index]; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_image_at(const _sg_pools_t* p, uint32_t img_id) { +    SOKOL_ASSERT(p && (SG_INVALID_ID != img_id)); +    int slot_index = _sg_slot_index(img_id); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->image_pool.size)); +    return &p->images[slot_index]; +} + +_SOKOL_PRIVATE _sg_shader_t* _sg_shader_at(const _sg_pools_t* p, uint32_t shd_id) { +    SOKOL_ASSERT(p && (SG_INVALID_ID != shd_id)); +    int slot_index = _sg_slot_index(shd_id); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->shader_pool.size)); +    return &p->shaders[slot_index]; +} + +_SOKOL_PRIVATE _sg_pipeline_t* _sg_pipeline_at(const _sg_pools_t* p, uint32_t pip_id) { +    SOKOL_ASSERT(p && (SG_INVALID_ID != pip_id)); +    int slot_index = _sg_slot_index(pip_id); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->pipeline_pool.size)); +    return &p->pipelines[slot_index]; +} + +_SOKOL_PRIVATE _sg_pass_t* _sg_pass_at(const _sg_pools_t* p, uint32_t pass_id) { +    SOKOL_ASSERT(p && (SG_INVALID_ID != pass_id)); +    int slot_index = _sg_slot_index(pass_id); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->pass_pool.size)); +    return &p->passes[slot_index]; +} + +_SOKOL_PRIVATE _sg_context_t* _sg_context_at(const _sg_pools_t* p, uint32_t context_id) { +    SOKOL_ASSERT(p && (SG_INVALID_ID != context_id)); +    int slot_index = _sg_slot_index(context_id); +    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->context_pool.size)); +    return &p->contexts[slot_index]; +} + +/* returns pointer to resource with matching id check, may return 0 */ +_SOKOL_PRIVATE _sg_buffer_t* _sg_lookup_buffer(const _sg_pools_t* p, uint32_t buf_id) { +    if (SG_INVALID_ID != buf_id) { +        _sg_buffer_t* buf = _sg_buffer_at(p, buf_id); +        if (buf->slot.id == buf_id) { +            return buf; +        } +    } +    return 0; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_lookup_image(const _sg_pools_t* p, uint32_t img_id) { +    if (SG_INVALID_ID != img_id) { +        _sg_image_t* img = _sg_image_at(p, img_id); +        if (img->slot.id == img_id) { +            return img; +        } +    } +    return 0; +} + +_SOKOL_PRIVATE _sg_shader_t* _sg_lookup_shader(const _sg_pools_t* p, uint32_t shd_id) { +    SOKOL_ASSERT(p); +    if (SG_INVALID_ID != shd_id) { +        _sg_shader_t* shd = _sg_shader_at(p, shd_id); +        if (shd->slot.id == shd_id) { +            return shd; +        } +    } +    return 0; +} + +_SOKOL_PRIVATE _sg_pipeline_t* _sg_lookup_pipeline(const _sg_pools_t* p, uint32_t pip_id) { +    SOKOL_ASSERT(p); +    if (SG_INVALID_ID != pip_id) { +        _sg_pipeline_t* pip = _sg_pipeline_at(p, pip_id); +        if (pip->slot.id == pip_id) { +            return pip; +        } +    } +    return 0; +} + +_SOKOL_PRIVATE _sg_pass_t* _sg_lookup_pass(const _sg_pools_t* p, uint32_t pass_id) { +    SOKOL_ASSERT(p); +    if (SG_INVALID_ID != pass_id) { +        _sg_pass_t* pass = _sg_pass_at(p, pass_id); +        if (pass->slot.id == pass_id) { +            return pass; +        } +    } +    return 0; +} + +_SOKOL_PRIVATE _sg_context_t* _sg_lookup_context(const _sg_pools_t* p, uint32_t ctx_id) { +    SOKOL_ASSERT(p); +    if (SG_INVALID_ID != ctx_id) { +        _sg_context_t* ctx = _sg_context_at(p, ctx_id); +        if (ctx->slot.id == ctx_id) { +            return ctx; +        } +    } +    return 0; +} + +_SOKOL_PRIVATE void _sg_destroy_all_resources(_sg_pools_t* p, uint32_t ctx_id) { +    /*  this is a bit dumb since it loops over all pool slots to +        find the occupied slots, on the other hand it is only ever +        executed at shutdown +        NOTE: ONLY EXECUTE THIS AT SHUTDOWN +              ...because the free queues will not be reset +              and the resource slots not be cleared! +    */ +    for (int i = 1; i < p->buffer_pool.size; i++) { +        if (p->buffers[i].slot.ctx_id == ctx_id) { +            sg_resource_state state = p->buffers[i].slot.state; +            if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +                _sg_destroy_buffer(&p->buffers[i]); +            } +        } +    } +    for (int i = 1; i < p->image_pool.size; i++) { +        if (p->images[i].slot.ctx_id == ctx_id) { +            sg_resource_state state = p->images[i].slot.state; +            if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +                _sg_destroy_image(&p->images[i]); +            } +        } +    } +    for (int i = 1; i < p->shader_pool.size; i++) { +        if (p->shaders[i].slot.ctx_id == ctx_id) { +            sg_resource_state state = p->shaders[i].slot.state; +            if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +                _sg_destroy_shader(&p->shaders[i]); +            } +        } +    } +    for (int i = 1; i < p->pipeline_pool.size; i++) { +        if (p->pipelines[i].slot.ctx_id == ctx_id) { +            sg_resource_state state = p->pipelines[i].slot.state; +            if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +                _sg_destroy_pipeline(&p->pipelines[i]); +            } +        } +    } +    for (int i = 1; i < p->pass_pool.size; i++) { +        if (p->passes[i].slot.ctx_id == ctx_id) { +            sg_resource_state state = p->passes[i].slot.state; +            if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { +                _sg_destroy_pass(&p->passes[i]); +            } +        } +    } +} + +/*== VALIDATION LAYER ========================================================*/ +#if defined(SOKOL_DEBUG) +/* return a human readable string for an _sg_validate_error */ +_SOKOL_PRIVATE const char* _sg_validate_string(_sg_validate_error_t err) { +    switch (err) { +        /* buffer creation validation errors */ +        case _SG_VALIDATE_BUFFERDESC_CANARY:        return "sg_buffer_desc not initialized"; +        case _SG_VALIDATE_BUFFERDESC_SIZE:          return "sg_buffer_desc.size cannot be 0"; +        case _SG_VALIDATE_BUFFERDESC_DATA:          return "immutable buffers must be initialized with data (sg_buffer_desc.data.ptr and sg_buffer_desc.data.size)"; +        case _SG_VALIDATE_BUFFERDESC_DATA_SIZE:     return "immutable buffer data size differs from buffer size"; +        case _SG_VALIDATE_BUFFERDESC_NO_DATA:       return "dynamic/stream usage buffers cannot be initialized with data"; + +        /* image creation validation errros */ +        case _SG_VALIDATE_IMAGEDESC_CANARY:             return "sg_image_desc not initialized"; +        case _SG_VALIDATE_IMAGEDESC_WIDTH:              return "sg_image_desc.width must be > 0"; +        case _SG_VALIDATE_IMAGEDESC_HEIGHT:             return "sg_image_desc.height must be > 0"; +        case _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT:     return "invalid pixel format for render-target image"; +        case _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT:  return "invalid pixel format for non-render-target image"; +        case _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT:     return "non-render-target images cannot be multisampled"; +        case _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT: return "MSAA not supported for this pixel format"; +        case _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE:       return "render target images must be SG_USAGE_IMMUTABLE"; +        case _SG_VALIDATE_IMAGEDESC_RT_NO_DATA:         return "render target images cannot be initialized with data"; +        case _SG_VALIDATE_IMAGEDESC_DATA:               return "missing or invalid data for immutable image"; +        case _SG_VALIDATE_IMAGEDESC_NO_DATA:            return "dynamic/stream usage images cannot be initialized with data"; + +        /* shader creation */ +        case _SG_VALIDATE_SHADERDESC_CANARY:                return "sg_shader_desc not initialized"; +        case _SG_VALIDATE_SHADERDESC_SOURCE:                return "shader source code required"; +        case _SG_VALIDATE_SHADERDESC_BYTECODE:              return "shader byte code required"; +        case _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE:    return "shader source or byte code required"; +        case _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE:      return "shader byte code length (in bytes) required"; +        case _SG_VALIDATE_SHADERDESC_NO_CONT_UBS:           return "shader uniform blocks must occupy continuous slots"; +        case _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS:    return "uniform block members must occupy continuous slots"; +        case _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS:         return "GL backend requires uniform block member declarations"; +        case _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME:        return "uniform block member name missing"; +        case _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH:      return "size of uniform block members doesn't match uniform block size"; +        case _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS:          return "shader images must occupy continuous slots"; +        case _SG_VALIDATE_SHADERDESC_IMG_NAME:              return "GL backend requires uniform block member names"; +        case _SG_VALIDATE_SHADERDESC_ATTR_NAMES:            return "GLES2 backend requires vertex attribute names"; +        case _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS:        return "D3D11 backend requires vertex attribute semantics"; +        case _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG:  return "vertex attribute name/semantic string too long (max len 16)"; + +        /* pipeline creation */ +        case _SG_VALIDATE_PIPELINEDESC_CANARY:          return "sg_pipeline_desc not initialized"; +        case _SG_VALIDATE_PIPELINEDESC_SHADER:          return "sg_pipeline_desc.shader missing or invalid"; +        case _SG_VALIDATE_PIPELINEDESC_NO_ATTRS:        return "sg_pipeline_desc.layout.attrs is empty or not continuous"; +        case _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4:  return "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4"; +        case _SG_VALIDATE_PIPELINEDESC_ATTR_NAME:       return "GLES2/WebGL missing vertex attribute name in shader"; +        case _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS:  return "D3D11 missing vertex attribute semantics in shader"; + +        /* pass creation */ +        case _SG_VALIDATE_PASSDESC_CANARY:                  return "sg_pass_desc not initialized"; +        case _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS:           return "sg_pass_desc.color_attachments[0] must be valid"; +        case _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS:      return "color attachments must occupy continuous slots"; +        case _SG_VALIDATE_PASSDESC_IMAGE:                   return "pass attachment image is not valid"; +        case _SG_VALIDATE_PASSDESC_MIPLEVEL:                return "pass attachment mip level is bigger than image has mipmaps"; +        case _SG_VALIDATE_PASSDESC_FACE:                    return "pass attachment image is cubemap, but face index is too big"; +        case _SG_VALIDATE_PASSDESC_LAYER:                   return "pass attachment image is array texture, but layer index is too big"; +        case _SG_VALIDATE_PASSDESC_SLICE:                   return "pass attachment image is 3d texture, but slice value is too big"; +        case _SG_VALIDATE_PASSDESC_IMAGE_NO_RT:             return "pass attachment image must be render targets"; +        case _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT:   return "pass color-attachment images must have a renderable pixel format"; +        case _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT:   return "pass depth-attachment image must have depth pixel format"; +        case _SG_VALIDATE_PASSDESC_IMAGE_SIZES:             return "all pass attachments must have the same size"; +        case _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS:     return "all pass attachments must have the same sample count"; + +        /* sg_begin_pass */ +        case _SG_VALIDATE_BEGINPASS_PASS:       return "sg_begin_pass: pass must be valid"; +        case _SG_VALIDATE_BEGINPASS_IMAGE:      return "sg_begin_pass: one or more attachment images are not valid"; + +        /* sg_apply_pipeline */ +        case _SG_VALIDATE_APIP_PIPELINE_VALID_ID:   return "sg_apply_pipeline: invalid pipeline id provided"; +        case _SG_VALIDATE_APIP_PIPELINE_EXISTS:     return "sg_apply_pipeline: pipeline object no longer alive"; +        case _SG_VALIDATE_APIP_PIPELINE_VALID:      return "sg_apply_pipeline: pipeline object not in valid state"; +        case _SG_VALIDATE_APIP_SHADER_EXISTS:       return "sg_apply_pipeline: shader object no longer alive"; +        case _SG_VALIDATE_APIP_SHADER_VALID:        return "sg_apply_pipeline: shader object not in valid state"; +        case _SG_VALIDATE_APIP_ATT_COUNT:           return "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments"; +        case _SG_VALIDATE_APIP_COLOR_FORMAT:        return "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format"; +        case _SG_VALIDATE_APIP_DEPTH_FORMAT:        return "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format"; +        case _SG_VALIDATE_APIP_SAMPLE_COUNT:        return "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count"; + +        /* sg_apply_bindings */ +        case _SG_VALIDATE_ABND_PIPELINE:            return "sg_apply_bindings: must be called after sg_apply_pipeline"; +        case _SG_VALIDATE_ABND_PIPELINE_EXISTS:     return "sg_apply_bindings: currently applied pipeline object no longer alive"; +        case _SG_VALIDATE_ABND_PIPELINE_VALID:      return "sg_apply_bindings: currently applied pipeline object not in valid state"; +        case _SG_VALIDATE_ABND_VBS:                 return "sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts"; +        case _SG_VALIDATE_ABND_VB_EXISTS:           return "sg_apply_bindings: vertex buffer no longer alive"; +        case _SG_VALIDATE_ABND_VB_TYPE:             return "sg_apply_bindings: buffer in vertex buffer slot is not a SG_BUFFERTYPE_VERTEXBUFFER"; +        case _SG_VALIDATE_ABND_VB_OVERFLOW:         return "sg_apply_bindings: buffer in vertex buffer slot is overflown"; +        case _SG_VALIDATE_ABND_NO_IB:               return "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided"; +        case _SG_VALIDATE_ABND_IB:                  return "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided"; +        case _SG_VALIDATE_ABND_IB_EXISTS:           return "sg_apply_bindings: index buffer no longer alive"; +        case _SG_VALIDATE_ABND_IB_TYPE:             return "sg_apply_bindings: buffer in index buffer slot is not a SG_BUFFERTYPE_INDEXBUFFER"; +        case _SG_VALIDATE_ABND_IB_OVERFLOW:         return "sg_apply_bindings: buffer in index buffer slot is overflown"; +        case _SG_VALIDATE_ABND_VS_IMGS:             return "sg_apply_bindings: vertex shader image count doesn't match sg_shader_desc"; +        case _SG_VALIDATE_ABND_VS_IMG_EXISTS:       return "sg_apply_bindings: vertex shader image no longer alive"; +        case _SG_VALIDATE_ABND_VS_IMG_TYPES:        return "sg_apply_bindings: one or more vertex shader image types don't match sg_shader_desc"; +        case _SG_VALIDATE_ABND_FS_IMGS:             return "sg_apply_bindings: fragment shader image count doesn't match sg_shader_desc"; +        case _SG_VALIDATE_ABND_FS_IMG_EXISTS:       return "sg_apply_bindings: fragment shader image no longer alive"; +        case _SG_VALIDATE_ABND_FS_IMG_TYPES:        return "sg_apply_bindings: one or more fragment shader image types don't match sg_shader_desc"; + +        /* sg_apply_uniforms */ +        case _SG_VALIDATE_AUB_NO_PIPELINE:      return "sg_apply_uniforms: must be called after sg_apply_pipeline()"; +        case _SG_VALIDATE_AUB_NO_UB_AT_SLOT:    return "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot"; +        case _SG_VALIDATE_AUB_SIZE:             return "sg_apply_uniforms: data size exceeds declared uniform block size"; + +        /* sg_update_buffer */ +        case _SG_VALIDATE_UPDATEBUF_USAGE:      return "sg_update_buffer: cannot update immutable buffer"; +        case _SG_VALIDATE_UPDATEBUF_SIZE:       return "sg_update_buffer: update size is bigger than buffer size"; +        case _SG_VALIDATE_UPDATEBUF_ONCE:       return "sg_update_buffer: only one update allowed per buffer and frame"; +        case _SG_VALIDATE_UPDATEBUF_APPEND:     return "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame"; + +        /* sg_append_buffer */ +        case _SG_VALIDATE_APPENDBUF_USAGE:      return "sg_append_buffer: cannot append to immutable buffer"; +        case _SG_VALIDATE_APPENDBUF_SIZE:       return "sg_append_buffer: overall appended size is bigger than buffer size"; +        case _SG_VALIDATE_APPENDBUF_UPDATE:     return "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame"; + +        /* sg_update_image */ +        case _SG_VALIDATE_UPDIMG_USAGE:         return "sg_update_image: cannot update immutable image"; +        case _SG_VALIDATE_UPDIMG_NOTENOUGHDATA: return "sg_update_image: not enough subimage data provided"; +        case _SG_VALIDATE_UPDIMG_SIZE:          return "sg_update_image: provided subimage data size too big"; +        case _SG_VALIDATE_UPDIMG_COMPRESSED:    return "sg_update_image: cannot update images with compressed format"; +        case _SG_VALIDATE_UPDIMG_ONCE:          return "sg_update_image: only one update allowed per image and frame"; + +        default: return "unknown validation error"; +    } +} +#endif /* defined(SOKOL_DEBUG) */ + +/*-- validation checks -------------------------------------------------------*/ +#if defined(SOKOL_DEBUG) +_SOKOL_PRIVATE void _sg_validate_begin(void) { +    _sg.validate_error = _SG_VALIDATE_SUCCESS; +} + +_SOKOL_PRIVATE void _sg_validate(bool cond, _sg_validate_error_t err) { +    if (!cond) { +        _sg.validate_error = err; +        SOKOL_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("^^^^  VALIDATION FAILED, TERMINATING ^^^^"); +            SOKOL_ASSERT(false); +        #endif +        return false; +    } +    else { +        return true; +    } +} +#endif + +_SOKOL_PRIVATE bool _sg_validate_buffer_desc(const sg_buffer_desc* desc) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(desc); +        return true; +    #else +        SOKOL_ASSERT(desc); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_BUFFERDESC_CANARY); +        SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_BUFFERDESC_CANARY); +        SOKOL_VALIDATE(desc->size > 0, _SG_VALIDATE_BUFFERDESC_SIZE); +        bool injected = (0 != desc->gl_buffers[0]) || +                        (0 != desc->mtl_buffers[0]) || +                        (0 != desc->d3d11_buffer) || +                        (0 != desc->wgpu_buffer); +        if (!injected && (desc->usage == SG_USAGE_IMMUTABLE)) { +            SOKOL_VALIDATE((0 != desc->data.ptr) && (desc->data.size > 0), _SG_VALIDATE_BUFFERDESC_DATA); +            SOKOL_VALIDATE(desc->size == desc->data.size, _SG_VALIDATE_BUFFERDESC_DATA_SIZE); +        } +        else { +            SOKOL_VALIDATE(0 == desc->data.ptr, _SG_VALIDATE_BUFFERDESC_NO_DATA); +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(desc); +        return true; +    #else +        SOKOL_ASSERT(desc); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_IMAGEDESC_CANARY); +        SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_IMAGEDESC_CANARY); +        SOKOL_VALIDATE(desc->width > 0, _SG_VALIDATE_IMAGEDESC_WIDTH); +        SOKOL_VALIDATE(desc->height > 0, _SG_VALIDATE_IMAGEDESC_HEIGHT); +        const sg_pixel_format fmt = desc->pixel_format; +        const sg_usage usage = desc->usage; +        const bool injected = (0 != desc->gl_textures[0]) || +                              (0 != desc->mtl_textures[0]) || +                              (0 != desc->d3d11_texture) || +                              (0 != desc->wgpu_texture); +        if (desc->render_target) { +            SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM)); +            SOKOL_VALIDATE(_sg.formats[fmt].render, _SG_VALIDATE_IMAGEDESC_RT_PIXELFORMAT); +            /* on GLES2, sample count for render targets is completely ignored */ +            #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +            if (!_sg.gl.gles2) { +            #endif +                if (desc->sample_count > 1) { +                    SOKOL_VALIDATE(_sg.features.msaa_render_targets && _sg.formats[fmt].msaa, _SG_VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT); +                } +            #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +            } +            #endif +            SOKOL_VALIDATE(usage == SG_USAGE_IMMUTABLE, _SG_VALIDATE_IMAGEDESC_RT_IMMUTABLE); +            SOKOL_VALIDATE(desc->data.subimage[0][0].ptr==0, _SG_VALIDATE_IMAGEDESC_RT_NO_DATA); +        } +        else { +            SOKOL_VALIDATE(desc->sample_count <= 1, _SG_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); +            const bool valid_nonrt_fmt = !_sg_is_valid_rendertarget_depth_format(fmt); +            SOKOL_VALIDATE(valid_nonrt_fmt, _SG_VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); +            /* FIXME: should use the same "expected size" computation as in _sg_validate_update_image() here */ +            if (!injected && (usage == SG_USAGE_IMMUTABLE)) { +                const int num_faces = desc->type == SG_IMAGETYPE_CUBE ? 6:1; +                const int num_mips = desc->num_mipmaps; +                for (int face_index = 0; face_index < num_faces; face_index++) { +                    for (int mip_index = 0; mip_index < num_mips; mip_index++) { +                        const bool has_data = desc->data.subimage[face_index][mip_index].ptr != 0; +                        const bool has_size = desc->data.subimage[face_index][mip_index].size > 0; +                        SOKOL_VALIDATE(has_data && has_size, _SG_VALIDATE_IMAGEDESC_DATA); +                    } +                } +            } +            else { +                for (int face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { +                    for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { +                        const bool no_data = 0 == desc->data.subimage[face_index][mip_index].ptr; +                        const bool no_size = 0 == desc->data.subimage[face_index][mip_index].size; +                        SOKOL_VALIDATE(no_data && no_size, _SG_VALIDATE_IMAGEDESC_NO_DATA); +                    } +                } +            } +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(desc); +        return true; +    #else +        SOKOL_ASSERT(desc); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_SHADERDESC_CANARY); +        SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_SHADERDESC_CANARY); +        #if defined(SOKOL_GLES2) +            SOKOL_VALIDATE(0 != desc->attrs[0].name, _SG_VALIDATE_SHADERDESC_ATTR_NAMES); +        #elif defined(SOKOL_D3D11) +            SOKOL_VALIDATE(0 != desc->attrs[0].sem_name, _SG_VALIDATE_SHADERDESC_ATTR_SEMANTICS); +        #endif +        #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +            /* on GL, must provide shader source code */ +            SOKOL_VALIDATE(0 != desc->vs.source, _SG_VALIDATE_SHADERDESC_SOURCE); +            SOKOL_VALIDATE(0 != desc->fs.source, _SG_VALIDATE_SHADERDESC_SOURCE); +        #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) +            /* on Metal or D3D11, must provide shader source code or byte code */ +            SOKOL_VALIDATE((0 != desc->vs.source)||(0 != desc->vs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); +            SOKOL_VALIDATE((0 != desc->fs.source)||(0 != desc->fs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); +        #elif defined(SOKOL_WGPU) +            /* on WGPU byte code must be provided */ +            SOKOL_VALIDATE((0 != desc->vs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_BYTECODE); +            SOKOL_VALIDATE((0 != desc->fs.bytecode.ptr), _SG_VALIDATE_SHADERDESC_BYTECODE); +        #else +            /* Dummy Backend, don't require source or bytecode */ +        #endif +        for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { +            if (desc->attrs[i].name) { +                SOKOL_VALIDATE(strlen(desc->attrs[i].name) < _SG_STRING_SIZE, _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); +            } +            if (desc->attrs[i].sem_name) { +                SOKOL_VALIDATE(strlen(desc->attrs[i].sem_name) < _SG_STRING_SIZE, _SG_VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); +            } +        } +        /* if shader byte code, the size must also be provided */ +        if (0 != desc->vs.bytecode.ptr) { +            SOKOL_VALIDATE(desc->vs.bytecode.size > 0, _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); +        } +        if (0 != desc->fs.bytecode.ptr) { +            SOKOL_VALIDATE(desc->fs.bytecode.size > 0, _SG_VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); +        } +        for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +            const sg_shader_stage_desc* stage_desc = (stage_index == 0)? &desc->vs : &desc->fs; +            bool uniform_blocks_continuous = true; +            for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { +                const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; +                if (ub_desc->size > 0) { +                    SOKOL_VALIDATE(uniform_blocks_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_UBS); +                    bool uniforms_continuous = true; +                    int uniform_offset = 0; +                    int num_uniforms = 0; +                    for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { +                        const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; +                        if (u_desc->type != SG_UNIFORMTYPE_INVALID) { +                            SOKOL_VALIDATE(uniforms_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS); +                            #if defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +                            SOKOL_VALIDATE(0 != u_desc->name, _SG_VALIDATE_SHADERDESC_UB_MEMBER_NAME); +                            #endif +                            const int array_count = u_desc->array_count; +                            uniform_offset += _sg_uniform_size(u_desc->type, array_count); +                            num_uniforms++; +                        } +                        else { +                            uniforms_continuous = false; +                        } +                    } +                    #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +                    SOKOL_VALIDATE((size_t)uniform_offset == ub_desc->size, _SG_VALIDATE_SHADERDESC_UB_SIZE_MISMATCH); +                    SOKOL_VALIDATE(num_uniforms > 0, _SG_VALIDATE_SHADERDESC_NO_UB_MEMBERS); +                    #endif +                } +                else { +                    uniform_blocks_continuous = false; +                } +            } +            bool images_continuous = true; +            for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { +                const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; +                if (img_desc->image_type != _SG_IMAGETYPE_DEFAULT) { +                    SOKOL_VALIDATE(images_continuous, _SG_VALIDATE_SHADERDESC_NO_CONT_IMGS); +                    #if defined(SOKOL_GLES2) +                    SOKOL_VALIDATE(0 != img_desc->name, _SG_VALIDATE_SHADERDESC_IMG_NAME); +                    #endif +                } +                else { +                    images_continuous = false; +                } +            } +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(desc); +        return true; +    #else +        SOKOL_ASSERT(desc); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_PIPELINEDESC_CANARY); +        SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_PIPELINEDESC_CANARY); +        SOKOL_VALIDATE(desc->shader.id != SG_INVALID_ID, _SG_VALIDATE_PIPELINEDESC_SHADER); +        for (int buf_index = 0; buf_index < SG_MAX_SHADERSTAGE_BUFFERS; buf_index++) { +            const sg_buffer_layout_desc* l_desc = &desc->layout.buffers[buf_index]; +            if (l_desc->stride == 0) { +                continue; +            } +            SOKOL_VALIDATE((l_desc->stride & 3) == 0, _SG_VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); +        } +        SOKOL_VALIDATE(desc->layout.attrs[0].format != SG_VERTEXFORMAT_INVALID, _SG_VALIDATE_PIPELINEDESC_NO_ATTRS); +        const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); +        SOKOL_VALIDATE(0 != shd, _SG_VALIDATE_PIPELINEDESC_SHADER); +        if (shd) { +            SOKOL_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PIPELINEDESC_SHADER); +            bool attrs_cont = true; +            for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +                const sg_vertex_attr_desc* a_desc = &desc->layout.attrs[attr_index]; +                if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +                    attrs_cont = false; +                    continue; +                } +                SOKOL_VALIDATE(attrs_cont, _SG_VALIDATE_PIPELINEDESC_NO_ATTRS); +                SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +                #if defined(SOKOL_GLES2) +                /* on GLES2, vertex attribute names must be provided */ +                SOKOL_VALIDATE(!_sg_strempty(&shd->gl.attrs[attr_index].name), _SG_VALIDATE_PIPELINEDESC_ATTR_NAME); +                #elif defined(SOKOL_D3D11) +                /* on D3D11, semantic names (and semantic indices) must be provided */ +                SOKOL_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), _SG_VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); +                #endif +            } +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_pass_desc(const sg_pass_desc* desc) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(desc); +        return true; +    #else +        SOKOL_ASSERT(desc); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(desc->_start_canary == 0, _SG_VALIDATE_PASSDESC_CANARY); +        SOKOL_VALIDATE(desc->_end_canary == 0, _SG_VALIDATE_PASSDESC_CANARY); +        bool atts_cont = true; +        int width = -1, height = -1, sample_count = -1; +        for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { +            const sg_pass_attachment_desc* att = &desc->color_attachments[att_index]; +            if (att->image.id == SG_INVALID_ID) { +                SOKOL_VALIDATE(att_index > 0, _SG_VALIDATE_PASSDESC_NO_COLOR_ATTS); +                atts_cont = false; +                continue; +            } +            SOKOL_VALIDATE(atts_cont, _SG_VALIDATE_PASSDESC_NO_CONT_COLOR_ATTS); +            const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); +            SOKOL_ASSERT(img); +            SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PASSDESC_IMAGE); +            SOKOL_VALIDATE(att->mip_level < img->cmn.num_mipmaps, _SG_VALIDATE_PASSDESC_MIPLEVEL); +            if (img->cmn.type == SG_IMAGETYPE_CUBE) { +                SOKOL_VALIDATE(att->slice < 6, _SG_VALIDATE_PASSDESC_FACE); +            } +            else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { +                SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_LAYER); +            } +            else if (img->cmn.type == SG_IMAGETYPE_3D) { +                SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_SLICE); +            } +            SOKOL_VALIDATE(img->cmn.render_target, _SG_VALIDATE_PASSDESC_IMAGE_NO_RT); +            if (att_index == 0) { +                width = img->cmn.width >> att->mip_level; +                height = img->cmn.height >> att->mip_level; +                sample_count = img->cmn.sample_count; +            } +            else { +                SOKOL_VALIDATE(width == img->cmn.width >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +                SOKOL_VALIDATE(height == img->cmn.height >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +                SOKOL_VALIDATE(sample_count == img->cmn.sample_count, _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); +            } +            SOKOL_VALIDATE(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format), _SG_VALIDATE_PASSDESC_COLOR_INV_PIXELFORMAT); +        } +        if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) { +            const sg_pass_attachment_desc* att = &desc->depth_stencil_attachment; +            const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); +            SOKOL_ASSERT(img); +            SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_PASSDESC_IMAGE); +            SOKOL_VALIDATE(att->mip_level < img->cmn.num_mipmaps, _SG_VALIDATE_PASSDESC_MIPLEVEL); +            if (img->cmn.type == SG_IMAGETYPE_CUBE) { +                SOKOL_VALIDATE(att->slice < 6, _SG_VALIDATE_PASSDESC_FACE); +            } +            else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { +                SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_LAYER); +            } +            else if (img->cmn.type == SG_IMAGETYPE_3D) { +                SOKOL_VALIDATE(att->slice < img->cmn.num_slices, _SG_VALIDATE_PASSDESC_SLICE); +            } +            SOKOL_VALIDATE(img->cmn.render_target, _SG_VALIDATE_PASSDESC_IMAGE_NO_RT); +            SOKOL_VALIDATE(width == img->cmn.width >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +            SOKOL_VALIDATE(height == img->cmn.height >> att->mip_level, _SG_VALIDATE_PASSDESC_IMAGE_SIZES); +            SOKOL_VALIDATE(sample_count == img->cmn.sample_count, _SG_VALIDATE_PASSDESC_IMAGE_SAMPLE_COUNTS); +            SOKOL_VALIDATE(_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format), _SG_VALIDATE_PASSDESC_DEPTH_INV_PIXELFORMAT); +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_begin_pass(_sg_pass_t* pass) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(pass); +        return true; +    #else +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(pass->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_PASS); + +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            const _sg_pass_attachment_t* att = &pass->cmn.color_atts[i]; +            const _sg_image_t* img = _sg_pass_color_image(pass, i); +            if (img) { +                SOKOL_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_IMAGE); +                SOKOL_VALIDATE(img->slot.id == att->image_id.id, _SG_VALIDATE_BEGINPASS_IMAGE); +            } +        } +        const _sg_image_t* ds_img = _sg_pass_ds_image(pass); +        if (ds_img) { +            const _sg_pass_attachment_t* att = &pass->cmn.ds_att; +            SOKOL_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_BEGINPASS_IMAGE); +            SOKOL_VALIDATE(ds_img->slot.id == att->image_id.id, _SG_VALIDATE_BEGINPASS_IMAGE); +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_pipeline(sg_pipeline pip_id) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(pip_id); +        return true; +    #else +        SOKOL_VALIDATE_BEGIN(); +        /* the pipeline object must be alive and valid */ +        SOKOL_VALIDATE(pip_id.id != SG_INVALID_ID, _SG_VALIDATE_APIP_PIPELINE_VALID_ID); +        const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +        SOKOL_VALIDATE(pip != 0, _SG_VALIDATE_APIP_PIPELINE_EXISTS); +        if (!pip) { +            return SOKOL_VALIDATE_END(); +        } +        SOKOL_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_APIP_PIPELINE_VALID); +        /* the pipeline's shader must be alive and valid */ +        SOKOL_ASSERT(pip->shader); +        SOKOL_VALIDATE(pip->shader->slot.id == pip->cmn.shader_id.id, _SG_VALIDATE_APIP_SHADER_EXISTS); +        SOKOL_VALIDATE(pip->shader->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_APIP_SHADER_VALID); +        /* check that pipeline attributes match current pass attributes */ +        const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, _sg.cur_pass.id); +        if (pass) { +            /* an offscreen pass */ +            SOKOL_VALIDATE(pip->cmn.color_attachment_count == pass->cmn.num_color_atts, _SG_VALIDATE_APIP_ATT_COUNT); +            for (int i = 0; i < pip->cmn.color_attachment_count; i++) { +                const _sg_image_t* att_img = _sg_pass_color_image(pass, i); +                SOKOL_VALIDATE(pip->cmn.color_formats[i] == att_img->cmn.pixel_format, _SG_VALIDATE_APIP_COLOR_FORMAT); +                SOKOL_VALIDATE(pip->cmn.sample_count == att_img->cmn.sample_count, _SG_VALIDATE_APIP_SAMPLE_COUNT); +            } +            const _sg_image_t* att_dsimg = _sg_pass_ds_image(pass); +            if (att_dsimg) { +                SOKOL_VALIDATE(pip->cmn.depth_format == att_dsimg->cmn.pixel_format, _SG_VALIDATE_APIP_DEPTH_FORMAT); +            } +            else { +                SOKOL_VALIDATE(pip->cmn.depth_format == SG_PIXELFORMAT_NONE, _SG_VALIDATE_APIP_DEPTH_FORMAT); +            } +        } +        else { +            /* default pass */ +            SOKOL_VALIDATE(pip->cmn.color_attachment_count == 1, _SG_VALIDATE_APIP_ATT_COUNT); +            SOKOL_VALIDATE(pip->cmn.color_formats[0] == _sg.desc.context.color_format, _SG_VALIDATE_APIP_COLOR_FORMAT); +            SOKOL_VALIDATE(pip->cmn.depth_format == _sg.desc.context.depth_format, _SG_VALIDATE_APIP_DEPTH_FORMAT); +            SOKOL_VALIDATE(pip->cmn.sample_count == _sg.desc.context.sample_count, _SG_VALIDATE_APIP_SAMPLE_COUNT); +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(bindings); +        return true; +    #else +        SOKOL_VALIDATE_BEGIN(); + +        /* a pipeline object must have been applied */ +        SOKOL_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, _SG_VALIDATE_ABND_PIPELINE); +        const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); +        SOKOL_VALIDATE(pip != 0, _SG_VALIDATE_ABND_PIPELINE_EXISTS); +        if (!pip) { +            return SOKOL_VALIDATE_END(); +        } +        SOKOL_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, _SG_VALIDATE_ABND_PIPELINE_VALID); +        SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + +        /* has expected vertex buffers, and vertex buffers still exist */ +        for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++) { +            if (bindings->vertex_buffers[i].id != SG_INVALID_ID) { +                SOKOL_VALIDATE(pip->cmn.vertex_layout_valid[i], _SG_VALIDATE_ABND_VBS); +                /* buffers in vertex-buffer-slots must be of type SG_BUFFERTYPE_VERTEXBUFFER */ +                const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->vertex_buffers[i].id); +                SOKOL_VALIDATE(buf != 0, _SG_VALIDATE_ABND_VB_EXISTS); +                if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { +                    SOKOL_VALIDATE(SG_BUFFERTYPE_VERTEXBUFFER == buf->cmn.type, _SG_VALIDATE_ABND_VB_TYPE); +                    SOKOL_VALIDATE(!buf->cmn.append_overflow, _SG_VALIDATE_ABND_VB_OVERFLOW); +                } +            } +            else { +                /* vertex buffer provided in a slot which has no vertex layout in pipeline */ +                SOKOL_VALIDATE(!pip->cmn.vertex_layout_valid[i], _SG_VALIDATE_ABND_VBS); +            } +        } + +        /* index buffer expected or not, and index buffer still exists */ +        if (pip->cmn.index_type == SG_INDEXTYPE_NONE) { +            /* pipeline defines non-indexed rendering, but index buffer provided */ +            SOKOL_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, _SG_VALIDATE_ABND_IB); +        } +        else { +            /* pipeline defines indexed rendering, but no index buffer provided */ +            SOKOL_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, _SG_VALIDATE_ABND_NO_IB); +        } +        if (bindings->index_buffer.id != SG_INVALID_ID) { +            /* buffer in index-buffer-slot must be of type SG_BUFFERTYPE_INDEXBUFFER */ +            const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->index_buffer.id); +            SOKOL_VALIDATE(buf != 0, _SG_VALIDATE_ABND_IB_EXISTS); +            if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { +                SOKOL_VALIDATE(SG_BUFFERTYPE_INDEXBUFFER == buf->cmn.type, _SG_VALIDATE_ABND_IB_TYPE); +                SOKOL_VALIDATE(!buf->cmn.append_overflow, _SG_VALIDATE_ABND_IB_OVERFLOW); +            } +        } + +        /* has expected vertex shader images */ +        for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { +            _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_VS]; +            if (bindings->vs_images[i].id != SG_INVALID_ID) { +                SOKOL_VALIDATE(i < stage->num_images, _SG_VALIDATE_ABND_VS_IMGS); +                const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->vs_images[i].id); +                SOKOL_VALIDATE(img != 0, _SG_VALIDATE_ABND_VS_IMG_EXISTS); +                if (img && img->slot.state == SG_RESOURCESTATE_VALID) { +                    SOKOL_VALIDATE(img->cmn.type == stage->images[i].image_type, _SG_VALIDATE_ABND_VS_IMG_TYPES); +                } +            } +            else { +                SOKOL_VALIDATE(i >= stage->num_images, _SG_VALIDATE_ABND_VS_IMGS); +            } +        } + +        /* has expected fragment shader images */ +        for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { +            _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_FS]; +            if (bindings->fs_images[i].id != SG_INVALID_ID) { +                SOKOL_VALIDATE(i < stage->num_images, _SG_VALIDATE_ABND_FS_IMGS); +                const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->fs_images[i].id); +                SOKOL_VALIDATE(img != 0, _SG_VALIDATE_ABND_FS_IMG_EXISTS); +                if (img && img->slot.state == SG_RESOURCESTATE_VALID) { +                    SOKOL_VALIDATE(img->cmn.type == stage->images[i].image_type, _SG_VALIDATE_ABND_FS_IMG_TYPES); +                } +            } +            else { +                SOKOL_VALIDATE(i >= stage->num_images, _SG_VALIDATE_ABND_FS_IMGS); +            } +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(stage_index); +        _SOKOL_UNUSED(ub_index); +        _SOKOL_UNUSED(data); +        return true; +    #else +        SOKOL_ASSERT((stage_index == SG_SHADERSTAGE_VS) || (stage_index == SG_SHADERSTAGE_FS)); +        SOKOL_ASSERT((ub_index >= 0) && (ub_index < SG_MAX_SHADERSTAGE_UBS)); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, _SG_VALIDATE_AUB_NO_PIPELINE); +        const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); +        SOKOL_ASSERT(pip && (pip->slot.id == _sg.cur_pipeline.id)); +        SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); + +        /* check that there is a uniform block at 'stage' and 'ub_index' */ +        const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[stage_index]; +        SOKOL_VALIDATE(ub_index < stage->num_uniform_blocks, _SG_VALIDATE_AUB_NO_UB_AT_SLOT); + +        /* check that the provided data size doesn't exceed the uniform block size */ +        SOKOL_VALIDATE(data->size <= stage->uniform_blocks[ub_index].size, _SG_VALIDATE_AUB_SIZE); + +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_update_buffer(const _sg_buffer_t* buf, const sg_range* data) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(buf); +        _SOKOL_UNUSED(data); +        return true; +    #else +        SOKOL_ASSERT(buf && data && data->ptr); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_UPDATEBUF_USAGE); +        SOKOL_VALIDATE(buf->cmn.size >= (int)data->size, _SG_VALIDATE_UPDATEBUF_SIZE); +        SOKOL_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, _SG_VALIDATE_UPDATEBUF_ONCE); +        SOKOL_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, _SG_VALIDATE_UPDATEBUF_APPEND); +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_append_buffer(const _sg_buffer_t* buf, const sg_range* data) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(buf); +        _SOKOL_UNUSED(data); +        return true; +    #else +        SOKOL_ASSERT(buf && data && data->ptr); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_APPENDBUF_USAGE); +        SOKOL_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), _SG_VALIDATE_APPENDBUF_SIZE); +        SOKOL_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, _SG_VALIDATE_APPENDBUF_UPDATE); +        return SOKOL_VALIDATE_END(); +    #endif +} + +_SOKOL_PRIVATE bool _sg_validate_update_image(const _sg_image_t* img, const sg_image_data* data) { +    #if !defined(SOKOL_DEBUG) +        _SOKOL_UNUSED(img); +        _SOKOL_UNUSED(data); +        return true; +    #else +        SOKOL_ASSERT(img && data); +        SOKOL_VALIDATE_BEGIN(); +        SOKOL_VALIDATE(img->cmn.usage != SG_USAGE_IMMUTABLE, _SG_VALIDATE_UPDIMG_USAGE); +        SOKOL_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, _SG_VALIDATE_UPDIMG_ONCE); +        SOKOL_VALIDATE(!_sg_is_compressed_pixel_format(img->cmn.pixel_format), _SG_VALIDATE_UPDIMG_COMPRESSED); +        const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1; +        const int num_mips = img->cmn.num_mipmaps; +        for (int face_index = 0; face_index < num_faces; face_index++) { +            for (int mip_index = 0; mip_index < num_mips; mip_index++) { +                SOKOL_VALIDATE(0 != data->subimage[face_index][mip_index].ptr, _SG_VALIDATE_UPDIMG_NOTENOUGHDATA); +                const int mip_width = _sg_max(img->cmn.width >> mip_index, 1); +                const int mip_height = _sg_max(img->cmn.height >> mip_index, 1); +                const int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); +                const int expected_size = bytes_per_slice * img->cmn.num_slices; +                SOKOL_VALIDATE(data->subimage[face_index][mip_index].size <= (size_t)expected_size, _SG_VALIDATE_UPDIMG_SIZE); +            } +        } +        return SOKOL_VALIDATE_END(); +    #endif +} + +/*== fill in desc default values =============================================*/ +_SOKOL_PRIVATE sg_buffer_desc _sg_buffer_desc_defaults(const sg_buffer_desc* desc) { +    sg_buffer_desc def = *desc; +    def.type = _sg_def(def.type, SG_BUFFERTYPE_VERTEXBUFFER); +    def.usage = _sg_def(def.usage, SG_USAGE_IMMUTABLE); +    if (def.size == 0) { +        def.size = def.data.size; +    } +    else if (def.data.size == 0) { +        def.data.size = def.size; +    } +    return def; +} + +_SOKOL_PRIVATE sg_image_desc _sg_image_desc_defaults(const sg_image_desc* desc) { +    sg_image_desc def = *desc; +    def.type = _sg_def(def.type, SG_IMAGETYPE_2D); +    def.num_slices = _sg_def(def.num_slices, 1); +    def.num_mipmaps = _sg_def(def.num_mipmaps, 1); +    def.usage = _sg_def(def.usage, SG_USAGE_IMMUTABLE); +    if (desc->render_target) { +        def.pixel_format = _sg_def(def.pixel_format, _sg.desc.context.color_format); +        def.sample_count = _sg_def(def.sample_count, _sg.desc.context.sample_count); +    } +    else { +        def.pixel_format = _sg_def(def.pixel_format, SG_PIXELFORMAT_RGBA8); +        def.sample_count = _sg_def(def.sample_count, 1); +    } +    def.min_filter = _sg_def(def.min_filter, SG_FILTER_NEAREST); +    def.mag_filter = _sg_def(def.mag_filter, SG_FILTER_NEAREST); +    def.wrap_u = _sg_def(def.wrap_u, SG_WRAP_REPEAT); +    def.wrap_v = _sg_def(def.wrap_v, SG_WRAP_REPEAT); +    def.wrap_w = _sg_def(def.wrap_w, SG_WRAP_REPEAT); +    def.border_color = _sg_def(def.border_color, SG_BORDERCOLOR_OPAQUE_BLACK); +    def.max_anisotropy = _sg_def(def.max_anisotropy, 1); +    def.max_lod = _sg_def_flt(def.max_lod, FLT_MAX); +    return def; +} + +_SOKOL_PRIVATE sg_shader_desc _sg_shader_desc_defaults(const sg_shader_desc* desc) { +    sg_shader_desc def = *desc; +    #if defined(SOKOL_METAL) +        def.vs.entry = _sg_def(def.vs.entry, "_main"); +        def.fs.entry = _sg_def(def.fs.entry, "_main"); +    #else +        def.vs.entry = _sg_def(def.vs.entry, "main"); +        def.fs.entry = _sg_def(def.fs.entry, "main"); +    #endif +    #if defined(SOKOL_D3D11) +        if (def.vs.source) { +            def.vs.d3d11_target = _sg_def(def.vs.d3d11_target, "vs_4_0"); +        } +        if (def.fs.source) { +            def.fs.d3d11_target = _sg_def(def.fs.d3d11_target, "ps_4_0"); +        } +    #endif +    for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { +        sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS)? &def.vs : &def.fs; +        for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { +            sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; +            if (0 == ub_desc->size) { +                break; +            } +            for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { +                sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; +                if (u_desc->type == SG_UNIFORMTYPE_INVALID) { +                    break; +                } +                u_desc->array_count = _sg_def(u_desc->array_count, 1); +            } +        } +        for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { +            sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; +            if (img_desc->image_type == _SG_IMAGETYPE_DEFAULT) { +                break; +            } +            img_desc->sampler_type = _sg_def(img_desc->sampler_type, SG_SAMPLERTYPE_FLOAT); +        } +    } +    return def; +} + +_SOKOL_PRIVATE sg_pipeline_desc _sg_pipeline_desc_defaults(const sg_pipeline_desc* desc) { +    sg_pipeline_desc def = *desc; + +    def.primitive_type = _sg_def(def.primitive_type, SG_PRIMITIVETYPE_TRIANGLES); +    def.index_type = _sg_def(def.index_type, SG_INDEXTYPE_NONE); +    def.cull_mode = _sg_def(def.cull_mode, SG_CULLMODE_NONE); +    def.face_winding = _sg_def(def.face_winding, SG_FACEWINDING_CW); +    def.sample_count = _sg_def(def.sample_count, _sg.desc.context.sample_count); + +    def.stencil.front.compare = _sg_def(def.stencil.front.compare, SG_COMPAREFUNC_ALWAYS); +    def.stencil.front.fail_op = _sg_def(def.stencil.front.fail_op, SG_STENCILOP_KEEP); +    def.stencil.front.depth_fail_op = _sg_def(def.stencil.front.depth_fail_op, SG_STENCILOP_KEEP); +    def.stencil.front.pass_op = _sg_def(def.stencil.front.pass_op, SG_STENCILOP_KEEP); +    def.stencil.back.compare = _sg_def(def.stencil.back.compare, SG_COMPAREFUNC_ALWAYS); +    def.stencil.back.fail_op = _sg_def(def.stencil.back.fail_op, SG_STENCILOP_KEEP); +    def.stencil.back.depth_fail_op = _sg_def(def.stencil.back.depth_fail_op, SG_STENCILOP_KEEP); +    def.stencil.back.pass_op = _sg_def(def.stencil.back.pass_op, SG_STENCILOP_KEEP); + +    def.depth.compare = _sg_def(def.depth.compare, SG_COMPAREFUNC_ALWAYS); +    def.depth.pixel_format = _sg_def(def.depth.pixel_format, _sg.desc.context.depth_format); +    def.color_count = _sg_def(def.color_count, 1); +    if (def.color_count > SG_MAX_COLOR_ATTACHMENTS) { +        def.color_count = SG_MAX_COLOR_ATTACHMENTS; +    } +    for (int i = 0; i < def.color_count; i++) { +        sg_color_state* cs = &def.colors[i]; +        cs->pixel_format = _sg_def(cs->pixel_format, _sg.desc.context.color_format); +        cs->write_mask = _sg_def(cs->write_mask, SG_COLORMASK_RGBA); +        sg_blend_state* bs = &def.colors[i].blend; +        bs->src_factor_rgb = _sg_def(bs->src_factor_rgb, SG_BLENDFACTOR_ONE); +        bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ZERO); +        bs->op_rgb = _sg_def(bs->op_rgb, SG_BLENDOP_ADD); +        bs->src_factor_alpha = _sg_def(bs->src_factor_alpha, SG_BLENDFACTOR_ONE); +        bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ZERO); +        bs->op_alpha = _sg_def(bs->op_alpha, SG_BLENDOP_ADD); +    } + +    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        sg_vertex_attr_desc* a_desc = &def.layout.attrs[attr_index]; +        if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +            break; +        } +        SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +        sg_buffer_layout_desc* b_desc = &def.layout.buffers[a_desc->buffer_index]; +        b_desc->step_func = _sg_def(b_desc->step_func, SG_VERTEXSTEP_PER_VERTEX); +        b_desc->step_rate = _sg_def(b_desc->step_rate, 1); +    } + +    /* resolve vertex layout strides and offsets */ +    int auto_offset[SG_MAX_SHADERSTAGE_BUFFERS]; +    memset(auto_offset, 0, sizeof(auto_offset)); +    bool use_auto_offset = true; +    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        /* to use computed offsets, *all* attr offsets must be 0 */ +        if (def.layout.attrs[attr_index].offset != 0) { +            use_auto_offset = false; +        } +    } +    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { +        sg_vertex_attr_desc* a_desc = &def.layout.attrs[attr_index]; +        if (a_desc->format == SG_VERTEXFORMAT_INVALID) { +            break; +        } +        SOKOL_ASSERT(a_desc->buffer_index < SG_MAX_SHADERSTAGE_BUFFERS); +        if (use_auto_offset) { +            a_desc->offset = auto_offset[a_desc->buffer_index]; +        } +        auto_offset[a_desc->buffer_index] += _sg_vertexformat_bytesize(a_desc->format); +    } +    /* compute vertex strides if needed */ +    for (int buf_index = 0; buf_index < SG_MAX_SHADERSTAGE_BUFFERS; buf_index++) { +        sg_buffer_layout_desc* l_desc = &def.layout.buffers[buf_index]; +        if (l_desc->stride == 0) { +            l_desc->stride = auto_offset[buf_index]; +        } +    } + +    return def; +} + +_SOKOL_PRIVATE sg_pass_desc _sg_pass_desc_defaults(const sg_pass_desc* desc) { +    /* FIXME: no values to replace in sg_pass_desc? */ +    sg_pass_desc def = *desc; +    return def; +} + +/*== allocate/initialize resource private functions ==========================*/ +_SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { +    sg_buffer res; +    int slot_index = _sg_pool_alloc_index(&_sg.pools.buffer_pool); +    if (_SG_INVALID_SLOT_INDEX != slot_index) { +        res.id = _sg_slot_alloc(&_sg.pools.buffer_pool, &_sg.pools.buffers[slot_index].slot, slot_index); +    } +    else { +        /* pool is exhausted */ +        res.id = SG_INVALID_ID; +    } +    return res; +} + +_SOKOL_PRIVATE sg_image _sg_alloc_image(void) { +    sg_image res; +    int slot_index = _sg_pool_alloc_index(&_sg.pools.image_pool); +    if (_SG_INVALID_SLOT_INDEX != slot_index) { +        res.id = _sg_slot_alloc(&_sg.pools.image_pool, &_sg.pools.images[slot_index].slot, slot_index); +    } +    else { +        /* pool is exhausted */ +        res.id = SG_INVALID_ID; +    } +    return res; +} + +_SOKOL_PRIVATE sg_shader _sg_alloc_shader(void) { +    sg_shader res; +    int slot_index = _sg_pool_alloc_index(&_sg.pools.shader_pool); +    if (_SG_INVALID_SLOT_INDEX != slot_index) { +        res.id = _sg_slot_alloc(&_sg.pools.shader_pool, &_sg.pools.shaders[slot_index].slot, slot_index); +    } +    else { +        /* pool is exhausted */ +        res.id = SG_INVALID_ID; +    } +    return res; +} + +_SOKOL_PRIVATE sg_pipeline _sg_alloc_pipeline(void) { +    sg_pipeline res; +    int slot_index = _sg_pool_alloc_index(&_sg.pools.pipeline_pool); +    if (_SG_INVALID_SLOT_INDEX != slot_index) { +        res.id =_sg_slot_alloc(&_sg.pools.pipeline_pool, &_sg.pools.pipelines[slot_index].slot, slot_index); +    } +    else { +        /* pool is exhausted */ +        res.id = SG_INVALID_ID; +    } +    return res; +} + +_SOKOL_PRIVATE sg_pass _sg_alloc_pass(void) { +    sg_pass res; +    int slot_index = _sg_pool_alloc_index(&_sg.pools.pass_pool); +    if (_SG_INVALID_SLOT_INDEX != slot_index) { +        res.id = _sg_slot_alloc(&_sg.pools.pass_pool, &_sg.pools.passes[slot_index].slot, slot_index); +    } +    else { +        /* pool is exhausted */ +        res.id = SG_INVALID_ID; +    } +    return res; +} + +_SOKOL_PRIVATE void _sg_dealloc_buffer(sg_buffer buf_id) { +    SOKOL_ASSERT(buf_id.id != SG_INVALID_ID); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); +    _sg_reset_slot(&buf->slot); +    _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf_id.id)); +} + +_SOKOL_PRIVATE void _sg_dealloc_image(sg_image img_id) { +    SOKOL_ASSERT(img_id.id != SG_INVALID_ID); +    _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); +    _sg_reset_slot(&img->slot); +    _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img_id.id)); +} + +_SOKOL_PRIVATE void _sg_dealloc_shader(sg_shader shd_id) { +    SOKOL_ASSERT(shd_id.id != SG_INVALID_ID); +    _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +    SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); +    _sg_reset_slot(&shd->slot); +    _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd_id.id)); +} + +_SOKOL_PRIVATE void _sg_dealloc_pipeline(sg_pipeline pip_id) { +    SOKOL_ASSERT(pip_id.id != SG_INVALID_ID); +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); +    _sg_reset_slot(&pip->slot); +    _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip_id.id)); +} + +_SOKOL_PRIVATE void _sg_dealloc_pass(sg_pass pass_id) { +    SOKOL_ASSERT(pass_id.id != SG_INVALID_ID); +    _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); +    _sg_reset_slot(&pass->slot); +    _sg_pool_free_index(&_sg.pools.pass_pool, _sg_slot_index(pass_id.id)); +} + +_SOKOL_PRIVATE void _sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { +    SOKOL_ASSERT(buf_id.id != SG_INVALID_ID && desc); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); +    buf->slot.ctx_id = _sg.active_context.id; +    if (_sg_validate_buffer_desc(desc)) { +        buf->slot.state = _sg_create_buffer(buf, desc); +    } +    else { +        buf->slot.state = SG_RESOURCESTATE_FAILED; +    } +    SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID)||(buf->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_image(sg_image img_id, const sg_image_desc* desc) { +    SOKOL_ASSERT(img_id.id != SG_INVALID_ID && desc); +    _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); +    img->slot.ctx_id = _sg.active_context.id; +    if (_sg_validate_image_desc(desc)) { +        img->slot.state = _sg_create_image(img, desc); +    } +    else { +        img->slot.state = SG_RESOURCESTATE_FAILED; +    } +    SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID)||(img->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { +    SOKOL_ASSERT(shd_id.id != SG_INVALID_ID && desc); +    _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +    SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); +    shd->slot.ctx_id = _sg.active_context.id; +    if (_sg_validate_shader_desc(desc)) { +        shd->slot.state = _sg_create_shader(shd, desc); +    } +    else { +        shd->slot.state = SG_RESOURCESTATE_FAILED; +    } +    SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(pip_id.id != SG_INVALID_ID && desc); +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); +    pip->slot.ctx_id = _sg.active_context.id; +    if (_sg_validate_pipeline_desc(desc)) { +        _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); +        if (shd && (shd->slot.state == SG_RESOURCESTATE_VALID)) { +            pip->slot.state = _sg_create_pipeline(pip, shd, desc); +        } +        else { +            pip->slot.state = SG_RESOURCESTATE_FAILED; +        } +    } +    else { +        pip->slot.state = SG_RESOURCESTATE_FAILED; +    } +    SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID)||(pip->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { +    SOKOL_ASSERT(pass_id.id != SG_INVALID_ID && desc); +    _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); +    pass->slot.ctx_id = _sg.active_context.id; +    if (_sg_validate_pass_desc(desc)) { +        /* lookup pass attachment image pointers */ +        _sg_image_t* att_imgs[SG_MAX_COLOR_ATTACHMENTS + 1]; +        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { +            if (desc->color_attachments[i].image.id) { +                att_imgs[i] = _sg_lookup_image(&_sg.pools, desc->color_attachments[i].image.id); +                /* FIXME: this shouldn't be an assertion, but result in a SG_RESOURCESTATE_FAILED pass */ +                SOKOL_ASSERT(att_imgs[i] && att_imgs[i]->slot.state == SG_RESOURCESTATE_VALID); +            } +            else { +                att_imgs[i] = 0; +            } +        } +        const int ds_att_index = SG_MAX_COLOR_ATTACHMENTS; +        if (desc->depth_stencil_attachment.image.id) { +            att_imgs[ds_att_index] = _sg_lookup_image(&_sg.pools, desc->depth_stencil_attachment.image.id); +            /* FIXME: this shouldn't be an assertion, but result in a SG_RESOURCESTATE_FAILED pass */ +            SOKOL_ASSERT(att_imgs[ds_att_index] && att_imgs[ds_att_index]->slot.state == SG_RESOURCESTATE_VALID); +        } +        else { +            att_imgs[ds_att_index] = 0; +        } +        pass->slot.state = _sg_create_pass(pass, att_imgs, desc); +    } +    else { +        pass->slot.state = SG_RESOURCESTATE_FAILED; +    } +    SOKOL_ASSERT((pass->slot.state == SG_RESOURCESTATE_VALID)||(pass->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE bool _sg_uninit_buffer(sg_buffer buf_id) { +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    if (buf) { +        if (buf->slot.ctx_id == _sg.active_context.id) { +            _sg_destroy_buffer(buf); +            _sg_reset_buffer(buf); +            return true; +        } +        else { +            SOKOL_LOG("_sg_uninit_buffer: active context mismatch (must be same as for creation)"); +            _SG_TRACE_NOARGS(err_context_mismatch); +        } +    } +    return false; +} + +_SOKOL_PRIVATE bool _sg_uninit_image(sg_image img_id) { +    _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    if (img) { +        if (img->slot.ctx_id == _sg.active_context.id) { +            _sg_destroy_image(img); +            _sg_reset_image(img); +            return true; +        } +        else { +            SOKOL_LOG("_sg_uninit_image: active context mismatch (must be same as for creation)"); +            _SG_TRACE_NOARGS(err_context_mismatch); +        } +    } +    return false; +} + +_SOKOL_PRIVATE bool _sg_uninit_shader(sg_shader shd_id) { +    _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +    if (shd) { +        if (shd->slot.ctx_id == _sg.active_context.id) { +            _sg_destroy_shader(shd); +            _sg_reset_shader(shd); +            return true; +        } +        else { +            SOKOL_LOG("_sg_uninit_shader: active context mismatch (must be same as for creation)"); +            _SG_TRACE_NOARGS(err_context_mismatch); +        } +    } +    return false; +} + +_SOKOL_PRIVATE bool _sg_uninit_pipeline(sg_pipeline pip_id) { +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    if (pip) { +        if (pip->slot.ctx_id == _sg.active_context.id) { +            _sg_destroy_pipeline(pip); +            _sg_reset_pipeline(pip); +            return true; +        } +        else { +            SOKOL_LOG("_sg_uninit_pipeline: active context mismatch (must be same as for creation)"); +            _SG_TRACE_NOARGS(err_context_mismatch); +        } +    } +    return false; +} + +_SOKOL_PRIVATE bool _sg_uninit_pass(sg_pass pass_id) { +    _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    if (pass) { +        if (pass->slot.ctx_id == _sg.active_context.id) { +            _sg_destroy_pass(pass); +            _sg_reset_pass(pass); +            return true; +        } +        else { +            SOKOL_LOG("_sg_uninit_pass: active context mismatch (must be same as for creation)"); +            _SG_TRACE_NOARGS(err_context_mismatch); +        } +    } +    return false; +} + +/*== PUBLIC API FUNCTIONS ====================================================*/ + +#if defined(SOKOL_METAL) +    // this is ARC compatible +    #if defined(__cplusplus) +        #define _SG_CLEAR(type, item) { item = (type) { }; } +    #else +        #define _SG_CLEAR(type, item) { item = (type) { 0 }; } +    #endif +#else +    #define _SG_CLEAR(type, item) { memset(&item, 0, sizeof(item)); } +#endif + +SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { +    SOKOL_ASSERT(desc); +    SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); +    _SG_CLEAR(_sg_state_t, _sg); +    _sg.desc = *desc; + +    /* replace zero-init items with their default values +        NOTE: on WebGPU, the default color pixel format MUST be provided, +        cannot be a default compile-time constant. +    */ +    #if defined(SOKOL_WGPU) +        SOKOL_ASSERT(SG_PIXELFORMAT_NONE != _sg.desc.context.color_format); +    #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) +        _sg.desc.context.color_format = _sg_def(_sg.desc.context.color_format, SG_PIXELFORMAT_BGRA8); +    #else +        _sg.desc.context.color_format = _sg_def(_sg.desc.context.color_format, SG_PIXELFORMAT_RGBA8); +    #endif +    _sg.desc.context.depth_format = _sg_def(_sg.desc.context.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); +    _sg.desc.context.sample_count = _sg_def(_sg.desc.context.sample_count, 1); +    _sg.desc.buffer_pool_size = _sg_def(_sg.desc.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); +    _sg.desc.image_pool_size = _sg_def(_sg.desc.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); +    _sg.desc.shader_pool_size = _sg_def(_sg.desc.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); +    _sg.desc.pipeline_pool_size = _sg_def(_sg.desc.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); +    _sg.desc.pass_pool_size = _sg_def(_sg.desc.pass_pool_size, _SG_DEFAULT_PASS_POOL_SIZE); +    _sg.desc.context_pool_size = _sg_def(_sg.desc.context_pool_size, _SG_DEFAULT_CONTEXT_POOL_SIZE); +    _sg.desc.uniform_buffer_size = _sg_def(_sg.desc.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); +    _sg.desc.staging_buffer_size = _sg_def(_sg.desc.staging_buffer_size, _SG_DEFAULT_STAGING_SIZE); +    _sg.desc.sampler_cache_size = _sg_def(_sg.desc.sampler_cache_size, _SG_DEFAULT_SAMPLER_CACHE_CAPACITY); + +    _sg_setup_pools(&_sg.pools, &_sg.desc); +    _sg.frame_index = 1; +    _sg_setup_backend(&_sg.desc); +    _sg.valid = true; +    sg_setup_context(); +} + +SOKOL_API_IMPL void sg_shutdown(void) { +    /* can only delete resources for the currently set context here, if multiple +    contexts are used, the app code must take care of properly releasing them +    (since only the app code can switch between 3D-API contexts) +    */ +    if (_sg.active_context.id != SG_INVALID_ID) { +        _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, _sg.active_context.id); +        if (ctx) { +            _sg_destroy_all_resources(&_sg.pools, _sg.active_context.id); +            _sg_destroy_context(ctx); +        } +    } +    _sg_discard_backend(); +    _sg_discard_pools(&_sg.pools); +    _sg.valid = false; +} + +SOKOL_API_IMPL bool sg_isvalid(void) { +    return _sg.valid; +} + +SOKOL_API_IMPL sg_desc sg_query_desc(void) { +    SOKOL_ASSERT(_sg.valid); +    return _sg.desc; +} + +SOKOL_API_IMPL sg_backend sg_query_backend(void) { +    SOKOL_ASSERT(_sg.valid); +    return _sg.backend; +} + +SOKOL_API_IMPL sg_features sg_query_features(void) { +    SOKOL_ASSERT(_sg.valid); +    return _sg.features; +} + +SOKOL_API_IMPL sg_limits sg_query_limits(void) { +    SOKOL_ASSERT(_sg.valid); +    return _sg.limits; +} + +SOKOL_API_IMPL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) { +    SOKOL_ASSERT(_sg.valid); +    int fmt_index = (int) fmt; +    SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM)); +    return _sg.formats[fmt_index]; +} + +SOKOL_API_IMPL sg_context sg_setup_context(void) { +    SOKOL_ASSERT(_sg.valid); +    sg_context res; +    int slot_index = _sg_pool_alloc_index(&_sg.pools.context_pool); +    if (_SG_INVALID_SLOT_INDEX != slot_index) { +        res.id = _sg_slot_alloc(&_sg.pools.context_pool, &_sg.pools.contexts[slot_index].slot, slot_index); +        _sg_context_t* ctx = _sg_context_at(&_sg.pools, res.id); +        ctx->slot.state = _sg_create_context(ctx); +        SOKOL_ASSERT(ctx->slot.state == SG_RESOURCESTATE_VALID); +        _sg_activate_context(ctx); +    } +    else { +        /* pool is exhausted */ +        res.id = SG_INVALID_ID; +    } +    _sg.active_context = res; +    return res; +} + +SOKOL_API_IMPL void sg_discard_context(sg_context ctx_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_destroy_all_resources(&_sg.pools, ctx_id.id); +    _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, ctx_id.id); +    if (ctx) { +        _sg_destroy_context(ctx); +        _sg_reset_context(ctx); +        _sg_reset_slot(&ctx->slot); +        _sg_pool_free_index(&_sg.pools.context_pool, _sg_slot_index(ctx_id.id)); +    } +    _sg.active_context.id = SG_INVALID_ID; +    _sg_activate_context(0); +} + +SOKOL_API_IMPL void sg_activate_context(sg_context ctx_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg.active_context = ctx_id; +    _sg_context_t* ctx = _sg_lookup_context(&_sg.pools, ctx_id.id); +    /* NOTE: ctx can be 0 here if the context is no longer valid */ +    _sg_activate_context(ctx); +} + +SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(trace_hooks); +    _SOKOL_UNUSED(trace_hooks); +    #if defined(SOKOL_TRACE_HOOKS) +        sg_trace_hooks old_hooks = _sg.hooks; +        _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!"); +    #endif +    return old_hooks; +} + +SOKOL_API_IMPL sg_buffer sg_alloc_buffer(void) { +    SOKOL_ASSERT(_sg.valid); +    sg_buffer res = _sg_alloc_buffer(); +    _SG_TRACE_ARGS(alloc_buffer, res); +    return res; +} + +SOKOL_API_IMPL sg_image sg_alloc_image(void) { +    SOKOL_ASSERT(_sg.valid); +    sg_image res = _sg_alloc_image(); +    _SG_TRACE_ARGS(alloc_image, res); +    return res; +} + +SOKOL_API_IMPL sg_shader sg_alloc_shader(void) { +    SOKOL_ASSERT(_sg.valid); +    sg_shader res = _sg_alloc_shader(); +    _SG_TRACE_ARGS(alloc_shader, res); +    return res; +} + +SOKOL_API_IMPL sg_pipeline sg_alloc_pipeline(void) { +    SOKOL_ASSERT(_sg.valid); +    sg_pipeline res = _sg_alloc_pipeline(); +    _SG_TRACE_ARGS(alloc_pipeline, res); +    return res; +} + +SOKOL_API_IMPL sg_pass sg_alloc_pass(void) { +    SOKOL_ASSERT(_sg.valid); +    sg_pass res = _sg_alloc_pass(); +    _SG_TRACE_ARGS(alloc_pass, res); +    return res; +} + +SOKOL_API_IMPL void sg_dealloc_buffer(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_dealloc_buffer(buf_id); +    _SG_TRACE_ARGS(dealloc_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_dealloc_image(sg_image img_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_dealloc_image(img_id); +    _SG_TRACE_ARGS(dealloc_image, img_id); +} + +SOKOL_API_IMPL void sg_dealloc_shader(sg_shader shd_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_dealloc_shader(shd_id); +    _SG_TRACE_ARGS(dealloc_shader, shd_id); +} + +SOKOL_API_IMPL void sg_dealloc_pipeline(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_dealloc_pipeline(pip_id); +    _SG_TRACE_ARGS(dealloc_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_dealloc_pass(sg_pass pass_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_dealloc_pass(pass_id); +    _SG_TRACE_ARGS(dealloc_pass, pass_id); +} + +SOKOL_API_IMPL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); +    _sg_init_buffer(buf_id, &desc_def); +    _SG_TRACE_ARGS(init_buffer, buf_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_image(sg_image img_id, const sg_image_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    sg_image_desc desc_def = _sg_image_desc_defaults(desc); +    _sg_init_image(img_id, &desc_def); +    _SG_TRACE_ARGS(init_image, img_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); +    _sg_init_shader(shd_id, &desc_def); +    _SG_TRACE_ARGS(init_shader, shd_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); +    _sg_init_pipeline(pip_id, &desc_def); +    _SG_TRACE_ARGS(init_pipeline, pip_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_pass(sg_pass pass_id, const sg_pass_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    sg_pass_desc desc_def = _sg_pass_desc_defaults(desc); +    _sg_init_pass(pass_id, &desc_def); +    _SG_TRACE_ARGS(init_pass, pass_id, &desc_def); +} + +SOKOL_API_IMPL bool sg_uninit_buffer(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    bool res = _sg_uninit_buffer(buf_id); +    _SG_TRACE_ARGS(uninit_buffer, buf_id); +    return res; +} + +SOKOL_API_IMPL bool sg_uninit_image(sg_image img_id) { +    SOKOL_ASSERT(_sg.valid); +    bool res = _sg_uninit_image(img_id); +    _SG_TRACE_ARGS(uninit_image, img_id); +    return res; +} + +SOKOL_API_IMPL bool sg_uninit_shader(sg_shader shd_id) { +    SOKOL_ASSERT(_sg.valid); +    bool res = _sg_uninit_shader(shd_id); +    _SG_TRACE_ARGS(uninit_shader, shd_id); +    return res; +} + +SOKOL_API_IMPL bool sg_uninit_pipeline(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    bool res = _sg_uninit_pipeline(pip_id); +    _SG_TRACE_ARGS(uninit_pipeline, pip_id); +    return res; +} + +SOKOL_API_IMPL bool sg_uninit_pass(sg_pass pass_id) { +    SOKOL_ASSERT(_sg.valid); +    bool res = _sg_uninit_pass(pass_id); +    _SG_TRACE_ARGS(uninit_pass, pass_id); +    return res; +} + +/*-- set allocated resource to failed state ----------------------------------*/ +SOKOL_API_IMPL void sg_fail_buffer(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(buf_id.id != SG_INVALID_ID); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    SOKOL_ASSERT(buf && buf->slot.state == SG_RESOURCESTATE_ALLOC); +    buf->slot.ctx_id = _sg.active_context.id; +    buf->slot.state = SG_RESOURCESTATE_FAILED; +    _SG_TRACE_ARGS(fail_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_fail_image(sg_image img_id) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(img_id.id != SG_INVALID_ID); +    _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    SOKOL_ASSERT(img && img->slot.state == SG_RESOURCESTATE_ALLOC); +    img->slot.ctx_id = _sg.active_context.id; +    img->slot.state = SG_RESOURCESTATE_FAILED; +    _SG_TRACE_ARGS(fail_image, img_id); +} + +SOKOL_API_IMPL void sg_fail_shader(sg_shader shd_id) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(shd_id.id != SG_INVALID_ID); +    _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +    SOKOL_ASSERT(shd && shd->slot.state == SG_RESOURCESTATE_ALLOC); +    shd->slot.ctx_id = _sg.active_context.id; +    shd->slot.state = SG_RESOURCESTATE_FAILED; +    _SG_TRACE_ARGS(fail_shader, shd_id); +} + +SOKOL_API_IMPL void sg_fail_pipeline(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(pip_id.id != SG_INVALID_ID); +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    SOKOL_ASSERT(pip && pip->slot.state == SG_RESOURCESTATE_ALLOC); +    pip->slot.ctx_id = _sg.active_context.id; +    pip->slot.state = SG_RESOURCESTATE_FAILED; +    _SG_TRACE_ARGS(fail_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_fail_pass(sg_pass pass_id) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(pass_id.id != SG_INVALID_ID); +    _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    SOKOL_ASSERT(pass && pass->slot.state == SG_RESOURCESTATE_ALLOC); +    pass->slot.ctx_id = _sg.active_context.id; +    pass->slot.state = SG_RESOURCESTATE_FAILED; +    _SG_TRACE_ARGS(fail_pass, pass_id); +} + +/*-- get resource state */ +SOKOL_API_IMPL sg_resource_state sg_query_buffer_state(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    sg_resource_state res = buf ? buf->slot.state : SG_RESOURCESTATE_INVALID; +    return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_image_state(sg_image img_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    sg_resource_state res = img ? img->slot.state : SG_RESOURCESTATE_INVALID; +    return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_shader_state(sg_shader shd_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +    sg_resource_state res = shd ? shd->slot.state : SG_RESOURCESTATE_INVALID; +    return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_pipeline_state(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    sg_resource_state res = pip ? pip->slot.state : SG_RESOURCESTATE_INVALID; +    return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_pass_state(sg_pass pass_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    sg_resource_state res = pass ? pass->slot.state : SG_RESOURCESTATE_INVALID; +    return res; +} + +/*-- allocate and initialize resource ----------------------------------------*/ +SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(desc); +    sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); +    sg_buffer buf_id = _sg_alloc_buffer(); +    if (buf_id.id != SG_INVALID_ID) { +        _sg_init_buffer(buf_id, &desc_def); +    } +    else { +        SOKOL_LOG("buffer pool exhausted!"); +        _SG_TRACE_NOARGS(err_buffer_pool_exhausted); +    } +    _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id); +    return buf_id; +} + +SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(desc); +    sg_image_desc desc_def = _sg_image_desc_defaults(desc); +    sg_image img_id = _sg_alloc_image(); +    if (img_id.id != SG_INVALID_ID) { +        _sg_init_image(img_id, &desc_def); +    } +    else { +        SOKOL_LOG("image pool exhausted!"); +        _SG_TRACE_NOARGS(err_image_pool_exhausted); +    } +    _SG_TRACE_ARGS(make_image, &desc_def, img_id); +    return img_id; +} + +SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(desc); +    sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); +    sg_shader shd_id = _sg_alloc_shader(); +    if (shd_id.id != SG_INVALID_ID) { +        _sg_init_shader(shd_id, &desc_def); +    } +    else { +        SOKOL_LOG("shader pool exhausted!"); +        _SG_TRACE_NOARGS(err_shader_pool_exhausted); +    } +    _SG_TRACE_ARGS(make_shader, &desc_def, shd_id); +    return shd_id; +} + +SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(desc); +    sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); +    sg_pipeline pip_id = _sg_alloc_pipeline(); +    if (pip_id.id != SG_INVALID_ID) { +        _sg_init_pipeline(pip_id, &desc_def); +    } +    else { +        SOKOL_LOG("pipeline pool exhausted!"); +        _SG_TRACE_NOARGS(err_pipeline_pool_exhausted); +    } +    _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id); +    return pip_id; +} + +SOKOL_API_IMPL sg_pass sg_make_pass(const sg_pass_desc* desc) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(desc); +    sg_pass_desc desc_def = _sg_pass_desc_defaults(desc); +    sg_pass pass_id = _sg_alloc_pass(); +    if (pass_id.id != SG_INVALID_ID) { +        _sg_init_pass(pass_id, &desc_def); +    } +    else { +        SOKOL_LOG("pass pool exhausted!"); +        _SG_TRACE_NOARGS(err_pass_pool_exhausted); +    } +    _SG_TRACE_ARGS(make_pass, &desc_def, pass_id); +    return pass_id; +} + +/*-- destroy resource --------------------------------------------------------*/ +SOKOL_API_IMPL void sg_destroy_buffer(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    _SG_TRACE_ARGS(destroy_buffer, buf_id); +    if (_sg_uninit_buffer(buf_id)) { +        _sg_dealloc_buffer(buf_id); +    } +} + +SOKOL_API_IMPL void sg_destroy_image(sg_image img_id) { +    SOKOL_ASSERT(_sg.valid); +    _SG_TRACE_ARGS(destroy_image, img_id); +    if (_sg_uninit_image(img_id)) { +        _sg_dealloc_image(img_id); +    } +} + +SOKOL_API_IMPL void sg_destroy_shader(sg_shader shd_id) { +    SOKOL_ASSERT(_sg.valid); +    _SG_TRACE_ARGS(destroy_shader, shd_id); +    if (_sg_uninit_shader(shd_id)) { +        _sg_dealloc_shader(shd_id); +    } +} + +SOKOL_API_IMPL void sg_destroy_pipeline(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    _SG_TRACE_ARGS(destroy_pipeline, pip_id); +    if (_sg_uninit_pipeline(pip_id)) { +        _sg_dealloc_pipeline(pip_id); +    } +} + +SOKOL_API_IMPL void sg_destroy_pass(sg_pass pass_id) { +    SOKOL_ASSERT(_sg.valid); +    _SG_TRACE_ARGS(destroy_pass, pass_id); +    if (_sg_uninit_pass(pass_id)) { +        _sg_dealloc_pass(pass_id); +    } +} + +SOKOL_API_IMPL void sg_begin_default_pass(const sg_pass_action* pass_action, int width, int height) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(pass_action); +    SOKOL_ASSERT((pass_action->_start_canary == 0) && (pass_action->_end_canary == 0)); +    sg_pass_action pa; +    _sg_resolve_default_pass_action(pass_action, &pa); +    _sg.cur_pass.id = SG_INVALID_ID; +    _sg.pass_valid = true; +    _sg_begin_pass(0, &pa, width, height); +    _SG_TRACE_ARGS(begin_default_pass, pass_action, width, height); +} + +SOKOL_API_IMPL void sg_begin_default_passf(const sg_pass_action* pass_action, float width, float height) { +    sg_begin_default_pass(pass_action, (int)width, (int)height); +} + +SOKOL_API_IMPL void sg_begin_pass(sg_pass pass_id, const sg_pass_action* pass_action) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(pass_action); +    SOKOL_ASSERT((pass_action->_start_canary == 0) && (pass_action->_end_canary == 0)); +    _sg.cur_pass = pass_id; +    _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    if (pass && _sg_validate_begin_pass(pass)) { +        _sg.pass_valid = true; +        sg_pass_action pa; +        _sg_resolve_default_pass_action(pass_action, &pa); +        const _sg_image_t* img = _sg_pass_color_image(pass, 0); +        SOKOL_ASSERT(img); +        const int w = img->cmn.width; +        const int h = img->cmn.height; +        _sg_begin_pass(pass, &pa, w, h); +        _SG_TRACE_ARGS(begin_pass, pass_id, pass_action); +    } +    else { +        _sg.pass_valid = false; +        _SG_TRACE_NOARGS(err_pass_invalid); +    } +} + +SOKOL_API_IMPL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) { +    SOKOL_ASSERT(_sg.valid); +    if (!_sg.pass_valid) { +        _SG_TRACE_NOARGS(err_pass_invalid); +        return; +    } +    _sg_apply_viewport(x, y, width, height, origin_top_left); +    _SG_TRACE_ARGS(apply_viewport, x, y, width, height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) { +    sg_apply_viewport((int)x, (int)y, (int)width, (int)height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) { +    SOKOL_ASSERT(_sg.valid); +    if (!_sg.pass_valid) { +        _SG_TRACE_NOARGS(err_pass_invalid); +        return; +    } +    _sg_apply_scissor_rect(x, y, width, height, origin_top_left); +    _SG_TRACE_ARGS(apply_scissor_rect, x, y, width, height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) { +    sg_apply_scissor_rect((int)x, (int)y, (int)width, (int)height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_pipeline(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg.bindings_valid = false; +    if (!_sg_validate_apply_pipeline(pip_id)) { +        _sg.next_draw_valid = false; +        _SG_TRACE_NOARGS(err_draw_invalid); +        return; +    } +    if (!_sg.pass_valid) { +        _SG_TRACE_NOARGS(err_pass_invalid); +        return; +    } +    _sg.cur_pipeline = pip_id; +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    SOKOL_ASSERT(pip); +    _sg.next_draw_valid = (SG_RESOURCESTATE_VALID == pip->slot.state); +    SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); +    _sg_apply_pipeline(pip); +    _SG_TRACE_ARGS(apply_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_apply_bindings(const sg_bindings* bindings) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(bindings); +    SOKOL_ASSERT((bindings->_start_canary == 0) && (bindings->_end_canary==0)); +    if (!_sg_validate_apply_bindings(bindings)) { +        _sg.next_draw_valid = false; +        _SG_TRACE_NOARGS(err_draw_invalid); +        return; +    } +    _sg.bindings_valid = true; + +    _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); +    SOKOL_ASSERT(pip); + +    _sg_buffer_t* vbs[SG_MAX_SHADERSTAGE_BUFFERS] = { 0 }; +    int num_vbs = 0; +    for (int i = 0; i < SG_MAX_SHADERSTAGE_BUFFERS; i++, num_vbs++) { +        if (bindings->vertex_buffers[i].id) { +            vbs[i] = _sg_lookup_buffer(&_sg.pools, bindings->vertex_buffers[i].id); +            SOKOL_ASSERT(vbs[i]); +            _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == vbs[i]->slot.state); +            _sg.next_draw_valid &= !vbs[i]->cmn.append_overflow; +        } +        else { +            break; +        } +    } + +    _sg_buffer_t* ib = 0; +    if (bindings->index_buffer.id) { +        ib = _sg_lookup_buffer(&_sg.pools, bindings->index_buffer.id); +        SOKOL_ASSERT(ib); +        _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == ib->slot.state); +        _sg.next_draw_valid &= !ib->cmn.append_overflow; +    } + +    _sg_image_t* vs_imgs[SG_MAX_SHADERSTAGE_IMAGES] = { 0 }; +    int num_vs_imgs = 0; +    for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++, num_vs_imgs++) { +        if (bindings->vs_images[i].id) { +            vs_imgs[i] = _sg_lookup_image(&_sg.pools, bindings->vs_images[i].id); +            SOKOL_ASSERT(vs_imgs[i]); +            _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == vs_imgs[i]->slot.state); +        } +        else { +            break; +        } +    } + +    _sg_image_t* fs_imgs[SG_MAX_SHADERSTAGE_IMAGES] = { 0 }; +    int num_fs_imgs = 0; +    for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++, num_fs_imgs++) { +        if (bindings->fs_images[i].id) { +            fs_imgs[i] = _sg_lookup_image(&_sg.pools, bindings->fs_images[i].id); +            SOKOL_ASSERT(fs_imgs[i]); +            _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == fs_imgs[i]->slot.state); +        } +        else { +            break; +        } +    } +    if (_sg.next_draw_valid) { +        const int* vb_offsets = bindings->vertex_buffer_offsets; +        int ib_offset = bindings->index_buffer_offset; +        _sg_apply_bindings(pip, vbs, vb_offsets, num_vbs, ib, ib_offset, vs_imgs, num_vs_imgs, fs_imgs, num_fs_imgs); +        _SG_TRACE_ARGS(apply_bindings, bindings); +    } +    else { +        _SG_TRACE_NOARGS(err_draw_invalid); +    } +} + +SOKOL_API_IMPL void sg_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range* data) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT((stage == SG_SHADERSTAGE_VS) || (stage == SG_SHADERSTAGE_FS)); +    SOKOL_ASSERT((ub_index >= 0) && (ub_index < SG_MAX_SHADERSTAGE_UBS)); +    SOKOL_ASSERT(data && data->ptr && (data->size > 0)); +    if (!_sg_validate_apply_uniforms(stage, ub_index, data)) { +        _sg.next_draw_valid = false; +        _SG_TRACE_NOARGS(err_draw_invalid); +        return; +    } +    if (!_sg.pass_valid) { +        _SG_TRACE_NOARGS(err_pass_invalid); +        return; +    } +    if (!_sg.next_draw_valid) { +        _SG_TRACE_NOARGS(err_draw_invalid); +    } +    _sg_apply_uniforms(stage, ub_index, data); +    _SG_TRACE_ARGS(apply_uniforms, stage, ub_index, data); +} + +SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instances) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(base_element >= 0); +    SOKOL_ASSERT(num_elements >= 0); +    SOKOL_ASSERT(num_instances >= 0); +    #if defined(SOKOL_DEBUG) +        if (!_sg.bindings_valid) { +            SOKOL_LOG("attempting to draw without resource bindings"); +        } +    #endif +    if (!_sg.pass_valid) { +        _SG_TRACE_NOARGS(err_pass_invalid); +        return; +    } +    if (!_sg.next_draw_valid) { +        _SG_TRACE_NOARGS(err_draw_invalid); +        return; +    } +    if (!_sg.bindings_valid) { +        _SG_TRACE_NOARGS(err_bindings_invalid); +        return; +    } +    /* attempting to draw with zero elements or instances is not technically an +       error, but might be handled as an error in the backend API (e.g. on Metal) +    */ +    if ((0 == num_elements) || (0 == num_instances)) { +        _SG_TRACE_NOARGS(err_draw_invalid); +        return; +    } +    _sg_draw(base_element, num_elements, num_instances); +    _SG_TRACE_ARGS(draw, base_element, num_elements, num_instances); +} + +SOKOL_API_IMPL void sg_end_pass(void) { +    SOKOL_ASSERT(_sg.valid); +    if (!_sg.pass_valid) { +        _SG_TRACE_NOARGS(err_pass_invalid); +        return; +    } +    _sg_end_pass(); +    _sg.cur_pass.id = SG_INVALID_ID; +    _sg.cur_pipeline.id = SG_INVALID_ID; +    _sg.pass_valid = false; +    _SG_TRACE_NOARGS(end_pass); +} + +SOKOL_API_IMPL void sg_commit(void) { +    SOKOL_ASSERT(_sg.valid); +    _sg_commit(); +    _SG_TRACE_NOARGS(commit); +    _sg.frame_index++; +} + +SOKOL_API_IMPL void sg_reset_state_cache(void) { +    SOKOL_ASSERT(_sg.valid); +    _sg_reset_state_cache(); +    _SG_TRACE_NOARGS(reset_state_cache); +} + +SOKOL_API_IMPL void sg_update_buffer(sg_buffer buf_id, const sg_range* data) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(data && data->ptr && (data->size > 0)); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    if ((data->size > 0) && buf && (buf->slot.state == SG_RESOURCESTATE_VALID)) { +        if (_sg_validate_update_buffer(buf, data)) { +            SOKOL_ASSERT(data->size <= (size_t)buf->cmn.size); +            /* only one update allowed per buffer and frame */ +            SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index); +            /* update and append on same buffer in same frame not allowed */ +            SOKOL_ASSERT(buf->cmn.append_frame_index != _sg.frame_index); +            _sg_update_buffer(buf, data); +            buf->cmn.update_frame_index = _sg.frame_index; +        } +    } +    _SG_TRACE_ARGS(update_buffer, buf_id, data); +} + +SOKOL_API_IMPL int sg_append_buffer(sg_buffer buf_id, const sg_range* data) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(data && data->ptr); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    int result; +    if (buf) { +        /* rewind append cursor in a new frame */ +        if (buf->cmn.append_frame_index != _sg.frame_index) { +            buf->cmn.append_pos = 0; +            buf->cmn.append_overflow = false; +        } +        if ((buf->cmn.append_pos + _sg_roundup((int)data->size, 4)) > buf->cmn.size) { +            buf->cmn.append_overflow = true; +        } +        const int start_pos = buf->cmn.append_pos; +        if (buf->slot.state == SG_RESOURCESTATE_VALID) { +            if (_sg_validate_append_buffer(buf, data)) { +                if (!buf->cmn.append_overflow && (data->size > 0)) { +                    /* update and append on same buffer in same frame not allowed */ +                    SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index); +                    int copied_num_bytes = _sg_append_buffer(buf, data, buf->cmn.append_frame_index != _sg.frame_index); +                    buf->cmn.append_pos += copied_num_bytes; +                    buf->cmn.append_frame_index = _sg.frame_index; +                } +            } +        } +        result = start_pos; +    } +    else { +        /* FIXME: should we return -1 here? */ +        result = 0; +    } +    _SG_TRACE_ARGS(append_buffer, buf_id, data, result); +    return result; +} + +SOKOL_API_IMPL bool sg_query_buffer_overflow(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    bool result = buf ? buf->cmn.append_overflow : false; +    return result; +} + +SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data) { +    SOKOL_ASSERT(_sg.valid); +    _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    if (img && img->slot.state == SG_RESOURCESTATE_VALID) { +        if (_sg_validate_update_image(img, data)) { +            SOKOL_ASSERT(img->cmn.upd_frame_index != _sg.frame_index); +            _sg_update_image(img, data); +            img->cmn.upd_frame_index = _sg.frame_index; +        } +    } +    _SG_TRACE_ARGS(update_image, img_id, data); +} + +SOKOL_API_IMPL void sg_push_debug_group(const char* name) { +    SOKOL_ASSERT(_sg.valid); +    SOKOL_ASSERT(name); +    _SOKOL_UNUSED(name); +    _SG_TRACE_ARGS(push_debug_group, name); +} + +SOKOL_API_IMPL void sg_pop_debug_group(void) { +    SOKOL_ASSERT(_sg.valid); +    _SG_TRACE_NOARGS(pop_debug_group); +} + +SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { +    SOKOL_ASSERT(_sg.valid); +    sg_buffer_info info; +    memset(&info, 0, sizeof(info)); +    const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); +    if (buf) { +        info.slot.state = buf->slot.state; +        info.slot.res_id = buf->slot.id; +        info.slot.ctx_id = buf->slot.ctx_id; +        info.update_frame_index = buf->cmn.update_frame_index; +        info.append_frame_index = buf->cmn.append_frame_index; +        info.append_pos = buf->cmn.append_pos; +        info.append_overflow = buf->cmn.append_overflow; +        #if defined(SOKOL_D3D11) +        info.num_slots = 1; +        info.active_slot = 0; +        #else +        info.num_slots = buf->cmn.num_slots; +        info.active_slot = buf->cmn.active_slot; +        #endif +    } +    return info; +} + +SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { +    SOKOL_ASSERT(_sg.valid); +    sg_image_info info; +    memset(&info, 0, sizeof(info)); +    const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); +    if (img) { +        info.slot.state = img->slot.state; +        info.slot.res_id = img->slot.id; +        info.slot.ctx_id = img->slot.ctx_id; +        #if defined(SOKOL_D3D11) +        info.num_slots = 1; +        info.active_slot = 0; +        #else +        info.num_slots = img->cmn.num_slots; +        info.active_slot = img->cmn.active_slot; +        #endif +        info.width = img->cmn.width; +        info.height = img->cmn.height; +    } +    return info; +} + +SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { +    SOKOL_ASSERT(_sg.valid); +    sg_shader_info info; +    memset(&info, 0, sizeof(info)); +    const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); +    if (shd) { +        info.slot.state = shd->slot.state; +        info.slot.res_id = shd->slot.id; +        info.slot.ctx_id = shd->slot.ctx_id; +    } +    return info; +} + +SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { +    SOKOL_ASSERT(_sg.valid); +    sg_pipeline_info info; +    memset(&info, 0, sizeof(info)); +    const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); +    if (pip) { +        info.slot.state = pip->slot.state; +        info.slot.res_id = pip->slot.id; +        info.slot.ctx_id = pip->slot.ctx_id; +    } +    return info; +} + +SOKOL_API_IMPL sg_pass_info sg_query_pass_info(sg_pass pass_id) { +    SOKOL_ASSERT(_sg.valid); +    sg_pass_info info; +    memset(&info, 0, sizeof(info)); +    const _sg_pass_t* pass = _sg_lookup_pass(&_sg.pools, pass_id.id); +    if (pass) { +        info.slot.state = pass->slot.state; +        info.slot.res_id = pass->slot.id; +        info.slot.ctx_id = pass->slot.ctx_id; +    } +    return info; +} + +SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) { +    SOKOL_ASSERT(_sg.valid && desc); +    return _sg_buffer_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) { +    SOKOL_ASSERT(_sg.valid && desc); +    return _sg_image_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) { +    SOKOL_ASSERT(_sg.valid && desc); +    return _sg_shader_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(_sg.valid && desc); +    return _sg_pipeline_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_pass_desc sg_query_pass_defaults(const sg_pass_desc* desc) { +    SOKOL_ASSERT(_sg.valid && desc); +    return _sg_pass_desc_defaults(desc); +} + +SOKOL_API_IMPL const void* sg_d3d11_device(void) { +#if defined(SOKOL_D3D11) +    return (const void*) _sg.d3d11.dev; +#else +    return 0; +#endif +} + +SOKOL_API_IMPL const void* sg_mtl_device(void) { +#if defined(SOKOL_METAL) +    if (nil != _sg.mtl.device) { +        return (__bridge const void*) _sg.mtl.device; +    } +    else { +        return 0; +    } +#else +    return 0; +#endif +} + +SOKOL_API_IMPL const void* sg_mtl_render_command_encoder(void) { +    #if defined(SOKOL_METAL) +        if (nil != _sg.mtl.cmd_encoder) { +            return (__bridge const void*) _sg.mtl.cmd_encoder; +        } +        else { +            return 0; +        } +    #else +        return 0; +    #endif +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif /* SOKOL_GFX_IMPL */ diff --git a/v_windows/v/old/thirdparty/sokol/sokol_v.h b/v_windows/v/old/thirdparty/sokol/sokol_v.h new file mode 100644 index 0000000..cc08678 --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/sokol_v.h @@ -0,0 +1,20 @@ +#if defined(__ANDROID__) +	// Adapted from https://stackoverflow.com/a/196018/1904615 +	#define V_ANDROID_LOG_STR_VALUE(arg) #arg +	#define V_ANDROID_LOG_NAME(tag_name) V_ANDROID_LOG_STR_VALUE(tag_name) + +	#ifndef V_ANDROID_LOG_TAG +		#if defined(APPNAME) +			#define V_ANDROID_LOG_TAG APPNAME +		#else +			#define V_ANDROID_LOG_TAG "V_ANDROID" +		#endif +	#endif + +	#define V_ANDROID_LOG_TAG_NAME V_ANDROID_LOG_NAME(V_ANDROID_LOG_TAG) + +	#include <android/log.h> +	#define printf(...) __android_log_print(ANDROID_LOG_INFO, V_ANDROID_LOG_TAG_NAME, __VA_ARGS__) +	#define fprintf(a, ...) __android_log_print(ANDROID_LOG_ERROR, V_ANDROID_LOG_TAG_NAME, __VA_ARGS__) +#endif + diff --git a/v_windows/v/old/thirdparty/sokol/util/sokol_fontstash.h b/v_windows/v/old/thirdparty/sokol/util/sokol_fontstash.h new file mode 100644 index 0000000..9bb915f --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/util/sokol_fontstash.h @@ -0,0 +1,1785 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_IMPL +#endif +#ifndef SOKOL_FONTSTASH_INCLUDED +/* +    sokol_fontstash.h -- renderer for https://github.com/memononen/fontstash +                         on top of sokol_gl.h + +    Project URL: https://github.com/floooh/sokol + +    Do this: +        #define SOKOL_IMPL or +        #define SOKOL_FONTSTASH_IMPL + +    before you include this file in *one* C or C++ file to create the +    implementation. + +    The following defines are used by the implementation to select the +    platform-specific embedded shader code (these are the same defines as +    used by sokol_gfx.h and sokol_app.h): + +    SOKOL_GLCORE33 +    SOKOL_GLES2 +    SOKOL_GLES3 +    SOKOL_D3D11 +    SOKOL_METAL + +    ...optionally provide the following macros 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_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: + +        sokol_gfx.h + +    Additionally include the following headers for including the sokol_fontstash.h +    implementation: + +        sokol_gl.h + +    HOW TO +    ====== +    --- First initialize sokol-gfx and sokol-gl as usual: + +            sg_setup(&(sg_desc){...}); +            sgl_setup(&(sgl_desc){...}); + +    --- Create at least one fontstash context with sfons_create() (this replaces +        glfonsCreate() from fontstash.h's example GL renderer: + +            FONScontext* ctx = sfons_create(atlas_width, atlas_height, FONS_ZERO_TOPLEFT); + +        Each FONScontext manages one font atlas texture which can hold rasterized +        glyphs for multiple fonts. + +    --- From here on, use fontstash.h's functions "as usual" to add TTF +        font data and draw text. Note that (just like with sokol-gl), text +        rendering can happen anywhere in the frame, not only inside +        a sokol-gfx rendering pass. + +    --- You can use the helper function + +            uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + +        To convert a 0..255 RGBA color into a packed uint32_t color value +        expected by fontstash.h. + +    --- Once per frame before calling sgl_draw(), call: + +            sfons_flush(FONScontext* ctx) + +        ...this will update the dynamic sokol-gfx texture with the latest font +        atlas content. + +    --- To actually render the text (and any other sokol-gl draw commands), +        call sgl_draw() inside a sokol-gfx frame. + +    --- NOTE that you can mix fontstash.h calls with sokol-gl calls to mix +        text rendering with sokol-gl rendering. You can also use +        sokol-gl's matrix stack to position fontstash.h text in 3D. + +    --- finally on application shutdown, call: + +            sfons_shutdown() + +        before sgl_shutdown() and sg_shutdown() + + +    WHAT HAPPENS UNDER THE HOOD: +    ============================ + +    sfons_create(): +        - creates a sokol-gfx shader compatible with sokol-gl +        - creates an sgl_pipeline object with alpha-blending using +          this shader +        - creates a 1-byte-per-pixel font atlas texture via sokol-gfx +          (pixel format SG_PIXELFORMAT_R8) + +    fonsDrawText(): +        - this will call the following sequence of sokol-gl functions: + +            sgl_enable_texture(); +            sgl_texture(...); +            sgl_push_pipeline(); +            sgl_load_pipeline(...); +            sgl_begin_triangles(); +            for each vertex: +                sg_v2f_t2f_c1i(...); +            sgl_end(); +            sgl_pop_pipeline(); +            sgl_disable_texture(); + +        - note that sokol-gl will merge several sgl_*_begin/sgl_end pairs +          into a single draw call if no relevant state has changed, typically +          all calls to fonsDrawText() will be merged into a single draw call +          as long as all calls use the same FONScontext + +    sfons_flush(): +        - this will call sg_update_image() on the font atlas texture +          if fontstash.h has added any rasterized glyphs since the last +          frame + +    sfons_shutdown(): +        - destroy the font atlas texture, sgl_pipeline and sg_shader objects + +    LICENSE +    ======= +    zlib/libpng license + +    Copyright (c) 2018 Andre Weissflog + +    This software is provided 'as-is', without any express or implied warranty. +    In no event will the authors be held liable for any damages arising from the +    use of this software. + +    Permission is granted to anyone to use this software for any purpose, +    including commercial applications, and to alter it and redistribute it +    freely, subject to the following restrictions: + +        1. The origin of this software must not be misrepresented; you must not +        claim that you wrote the original software. If you use this software in a +        product, an acknowledgment in the product documentation would be +        appreciated but is not required. + +        2. Altered source versions must be plainly marked as such, and must not +        be misrepresented as being the original software. + +        3. This notice may not be removed or altered from any source +        distribution. +*/ +#define SOKOL_FONTSTASH_INCLUDED (1) +#include <stdint.h> +#include <stdlib.h> + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_fontstash.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_FONTSTASH_API_DECL) +#define SOKOL_FONTSTASH_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_FONTSTASH_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllimport) +#else +#define SOKOL_FONTSTASH_API_DECL extern +#endif +#endif +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_FONTSTASH_API_DECL FONScontext* sfons_create(int width, int height, int flags); +SOKOL_FONTSTASH_API_DECL void sfons_destroy(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL void sfons_flush(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_FONTSTASH_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_FONTSTASH_IMPL +#define SOKOL_FONTSTASH_IMPL_INCLUDED (1) +#include <string.h>     /* memset, memcpy */ + +#if !defined(SOKOL_GL_INCLUDED) +#error "Please include sokol_gl.h before sokol_fontstash.h" +#endif +#if !defined(FONS_H) +#error "Please include fontstash.h before sokol_fontstash.h" +#endif + +#ifndef SOKOL_API_IMPL +    #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG +    #ifndef NDEBUG +        #define SOKOL_DEBUG (1) +    #endif +#endif +#ifndef SOKOL_ASSERT +    #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_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 +#ifndef _SOKOL_UNUSED +    #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(SOKOL_GLCORE33) +/* +    Embedded source code compiled with: + +    sokol-shdc -i sfons.glsl -o sfons.h -l glsl330:glsl100:hlsl4:metal_macos:metal_ios:metal_sim:wgpu -b + +    (not that for Metal and D3D11 byte code, sokol-shdc must be run +    on macOS and Windows) + +    @vs vs +    uniform vs_params { +        uniform mat4 mvp; +        uniform mat4 tm; +    }; +    in vec4 position; +    in vec2 texcoord0; +    in vec4 color0; +    out vec4 uv; +    out vec4 color; +    void main() { +        gl_Position = mvp * position; +        uv = tm * vec4(texcoord0, 0.0, 1.0); +        color = color0; +    } +    @end + +    @fs fs +    uniform sampler2D tex; +    in vec4 uv; +    in vec4 color; +    out vec4 frag_color; +    void main() { +        frag_color = vec4(1.0, 1.0, 1.0, texture(tex, uv.xy).r) * color; +    } +    @end + +    @program sfontstash vs fs +*/ +static const char _sfons_vs_source_glsl330[415] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, +    0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, +    0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, +    0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, +    0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, +    0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79, +    0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31, +    0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f, +    0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f, +    0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, +    0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, +    0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20, +    0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f, +    0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28, +    0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, +    0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, +    0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74, +    0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20, +    0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34, +    0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c, +    0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72, +    0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sfons_fs_source_glsl330[195] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, +    0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, +    0x74,0x65,0x78,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63, +    0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76, +    0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, +    0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x69,0x6e,0x20,0x76, +    0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64, +    0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72, +    0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28, +    0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x74, +    0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x75,0x76,0x2e,0x78, +    0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d, +    0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +static const char _sfons_vs_source_glsl100[381] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x30,0x30,0x0a,0x0a,0x75,0x6e, +    0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, +    0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x61,0x74,0x74,0x72,0x69,0x62,0x75, +    0x74,0x65,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, +    0x3b,0x0a,0x76,0x61,0x72,0x79,0x69,0x6e,0x67,0x20,0x76,0x65,0x63,0x34,0x20,0x75, +    0x76,0x3b,0x0a,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x20,0x76,0x65,0x63, +    0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x76,0x61,0x72, +    0x79,0x69,0x6e,0x67,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b, +    0x0a,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x20,0x76,0x65,0x63,0x34,0x20, +    0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, +    0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f, +    0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, +    0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, +    0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, +    0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28, +    0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, +    0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x74, +    0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, +    0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, +    0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sfons_fs_source_glsl100[233] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x30,0x30,0x0a,0x70,0x72,0x65, +    0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d,0x70,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20, +    0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66, +    0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65, +    0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x79,0x69,0x6e, +    0x67,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b, +    0x0a,0x76,0x61,0x72,0x79,0x69,0x6e,0x67,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76, +    0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64, +    0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c, +    0x5f,0x46,0x72,0x61,0x67,0x44,0x61,0x74,0x61,0x5b,0x30,0x5d,0x20,0x3d,0x20,0x76, +    0x65,0x63,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e, +    0x30,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x28,0x74,0x65,0x78, +    0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f, +    0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +static const uint8_t _sfons_vs_bytecode_metal_macos[3360] = { +    0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x20,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x10,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x54,0xec,0xec,0xa8,0x23,0x83,0x8d, +    0xc7,0xcf,0x6e,0x67,0x94,0x95,0x54,0x94,0xeb,0x98,0x15,0x2a,0x2c,0x54,0xa0,0x4b, +    0x24,0x6f,0x5e,0xed,0x6e,0x76,0x45,0xb7,0x5e,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x37,0x00,0x00, +    0x00,0x56,0x41,0x54,0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, +    0x6e,0x00,0x00,0x80,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80, +    0x63,0x6f,0x6c,0x6f,0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03, +    0x00,0x06,0x04,0x06,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54, +    0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xf0,0x0b,0x00,0x00, +    0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xf9,0x02,0x00,0x00, +    0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91, +    0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19, +    0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14, +    0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80, +    0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0, +    0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x81,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8, +    0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87, +    0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72, +    0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88, +    0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41, +    0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c, +    0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87, +    0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72, +    0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,0x1e, +    0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4,0x21,0x1c,0xda,0xa1,0x1c,0xda, +    0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41,0x1e,0xda,0xa0,0x1c,0xd8,0x21, +    0x1d,0xda,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06,0x77,0x78,0x87,0x36,0x30, +    0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87, +    0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x68,0x03,0x77,0x78,0x07,0x77, +    0x68,0x03,0x76,0x28,0x87,0x70,0x30,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x74,0x70, +    0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x40,0x1d,0xea,0xa1, +    0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08, +    0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,0x87, +    0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, +    0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe6,0x21,0x1d,0xce,0xc1,0x1d, +    0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,0x61,0x1e,0xda,0xc0,0x1c,0xe0, +    0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80, +    0x70,0x87,0x77,0x68,0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38, +    0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00, +    0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1, +    0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c, +    0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87, +    0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a, +    0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d, +    0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00, +    0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x18,0x02,0x01,0x2c,0x40,0x05,0x00, +    0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00, +    0x23,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4,0x84, +    0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4,0x4c, +    0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45,0x94,0x30,0xf9,0x0c,0x80, +    0x34,0xf4,0xef,0x50,0x13,0x1a,0x42,0x08,0xc3,0x08,0x02,0x90,0x04,0x61,0x26,0x6a, +    0x1e,0xe8,0x41,0x1e,0xea,0x61,0x1c,0xe8,0xc1,0x0d,0xda,0xa1,0x1c,0xe8,0x21,0x1c, +    0xd8,0x41,0x0f,0xf4,0xa0,0x1d,0xc2,0x81,0x1e,0xe4,0x21,0x1d,0xf0,0x01,0x05,0xe4, +    0x20,0x69,0x8a,0x28,0x61,0xf2,0x2b,0xe9,0x7f,0x80,0x08,0x60,0x24,0x24,0x94,0x32, +    0x88,0x60,0x08,0xa5,0x10,0x61,0x84,0x43,0x68,0x20,0x60,0x8e,0x00,0x0c,0x72,0x60, +    0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb,0x08,0x00,0x00,0x00,0x00, +    0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60, +    0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03, +    0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07, +    0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71, +    0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80, +    0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, +    0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, +    0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60, +    0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e, +    0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71, +    0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40, +    0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07, +    0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a, +    0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60, +    0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07, +    0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78, +    0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10, +    0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07, +    0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72, +    0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20, +    0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07, +    0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d, +    0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x49,0x00, +    0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8,0x02,0x01,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a, +    0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50,0x80,0x02,0x85,0x50,0x10,0x65,0x50,0x20, +    0xd4,0x46,0x00,0x88,0x8d,0x35,0x28,0x0f,0x01,0x00,0x00,0x00,0x79,0x18,0x00,0x00, +    0x19,0x01,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9, +    0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93, +    0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x06,0xd9,0x20,0x08,0x0e, +    0x8e,0xad,0x0c,0x84,0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d, +    0x24,0x07,0x46,0xc6,0x25,0x86,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac, +    0x25,0x07,0x46,0xc6,0x25,0x86,0xc6,0x25,0x27,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25, +    0x58,0x8e,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82, +    0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56, +    0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50, +    0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61, +    0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04, +    0x65,0x61,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98, +    0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1, +    0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x61,0x10,0x96,0x26, +    0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6, +    0x45,0xf6,0x56,0x27,0xc6,0x56,0xf6,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44, +    0x50,0x1e,0x92,0x41,0x58,0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b, +    0x5b,0x18,0x5d,0x9a,0x5d,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9, +    0x17,0x59,0xda,0x5c,0x98,0x18,0x5b,0xd9,0x10,0x41,0x89,0x78,0x06,0x61,0x69,0x72, +    0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f, +    0x77,0x69,0x64,0x65,0x5f,0x76,0x65,0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73, +    0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x62,0x14,0x96,0x26,0xe7,0x62,0x57,0x26,0x47, +    0x57,0x86,0xf7,0xf5,0x56,0x47,0x07,0x57,0x47,0xc7,0xa5,0x6e,0xae,0x4c,0x0e,0x85, +    0xed,0x6d,0xcc,0x0d,0x26,0x85,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d, +    0x1e,0x5c,0xd9,0x97,0x5b,0x58,0x5b,0x19,0x0d,0x33,0xb6,0xb7,0x30,0x3a,0x1a,0x32, +    0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x6e,0x61,0x6d,0x65,0x14,0xea,0xec,0x86, +    0x30,0x4a,0xa5,0x58,0xca,0xa5,0x60,0x4a,0xa6,0x68,0x5c,0xea,0xe6,0xca,0xe4,0x50, +    0xd8,0xde,0xc6,0xdc,0x62,0x52,0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x94,0x4a, +    0xe1,0x94,0x4b,0xc1,0x94,0x4c,0xe9,0xc8,0x84,0xa5,0xc9,0xb9,0xc0,0xbd,0xcd,0xa5, +    0xd1,0xa5,0xbd,0xb9,0x71,0x39,0x63,0xfb,0x82,0x7a,0x9b,0x4b,0xa3,0x4b,0x7b,0x73, +    0x1b,0xa2,0x28,0x9f,0x72,0x29,0x98,0x92,0x29,0x60,0x30,0xc4,0x50,0x36,0xc5,0x53, +    0xc2,0x80,0x50,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b, +    0x5c,0x1d,0x1d,0xa5,0xb0,0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7, +    0xaf,0x34,0x37,0xb2,0x32,0x3c,0x66,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64, +    0x28,0x38,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x44,0x76,0x34,0x5f,0x66,0x29, +    0x44,0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x8c,0x81,0x42, +    0x06,0x8b,0xb0,0x04,0x4a,0x19,0x28,0x97,0x82,0x29,0x99,0x62,0x06,0xd4,0xce,0xca, +    0xdc,0xca,0xe4,0xc2,0xe8,0xca,0xc8,0x50,0x72,0xe8,0xca,0xf0,0xc6,0xde,0xde,0xe4, +    0xc8,0x60,0x88,0xec,0x64,0xbe,0xcc,0x52,0x68,0x98,0xb1,0xbd,0x85,0xd1,0xc9,0x30, +    0xa1,0x2b,0xc3,0x1b,0x7b,0x7b,0x93,0x23,0x83,0x19,0x42,0x2d,0x81,0x32,0x06,0x0a, +    0x19,0x2c,0xc1,0x12,0x28,0x68,0xa0,0x5c,0x4a,0x1a,0x28,0x99,0xa2,0x06,0xbc,0xce, +    0xca,0xdc,0xca,0xe4,0xc2,0xe8,0xca,0xc8,0x50,0x6c,0xc6,0xde,0xd8,0xde,0xe4,0x60, +    0x88,0xec,0x68,0xbe,0xcc,0x52,0x68,0x8c,0xbd,0xb1,0xbd,0xc9,0xc1,0x0c,0xa1,0x96, +    0x41,0x19,0x03,0x85,0x0c,0x96,0x61,0x09,0x14,0x36,0x50,0x2e,0x05,0x53,0x32,0xa5, +    0x0d,0xa8,0x84,0xa5,0xc9,0xb9,0x88,0xd5,0x99,0x99,0x95,0xc9,0xf1,0x09,0x4b,0x93, +    0x73,0x11,0xab,0x33,0x33,0x2b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x23,0x12,0x96,0x26, +    0xe7,0x22,0x57,0x16,0x46,0x46,0x2a,0x2c,0x4d,0xce,0x65,0x8e,0x4e,0xae,0x6e,0x8c, +    0xee,0x8b,0x2e,0x0f,0xae,0xec,0x2b,0xcd,0xcd,0xec,0x8d,0x88,0x19,0xdb,0x5b,0x18, +    0x1d,0x0d,0x1e,0x0d,0x87,0x36,0x3b,0x38,0x0a,0x74,0x6d,0x43,0xa8,0x45,0x58,0x88, +    0x45,0x50,0xe6,0x40,0xa1,0x83,0x85,0x58,0x88,0x45,0x50,0xe6,0x40,0xa9,0x03,0x46, +    0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x73,0x69,0x7a, +    0x65,0xbc,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xc2, +    0xd8,0xd2,0xce,0xdc,0xbe,0xe6,0xd2,0xf4,0xca,0x98,0xd8,0xcd,0x7d,0xc1,0x85,0xc9, +    0x85,0xb5,0xcd,0x71,0xf8,0x92,0x81,0x19,0x42,0x06,0x0b,0xa2,0xbc,0x81,0x02,0x07, +    0x4b,0xa1,0x90,0xc1,0x22,0x2c,0x81,0x12,0x07,0x8a,0x1c,0x28,0x76,0xa0,0xdc,0xc1, +    0x52,0x28,0x78,0xb0,0x24,0xca,0xa5,0xe4,0x81,0x92,0x29,0x7a,0x30,0x04,0x51,0xce, +    0x40,0x59,0x03,0xc5,0x0d,0x94,0x3d,0x18,0x62,0x24,0x80,0x22,0x06,0x0a,0x1f,0xf0, +    0x79,0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3, +    0x33,0x95,0xd6,0x06,0xc7,0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34, +    0x44,0x50,0xfe,0x60,0x88,0xa1,0xf8,0x81,0x02,0x0a,0x8d,0x32,0xc4,0x50,0x42,0x41, +    0x09,0x85,0x46,0x19,0x11,0xb1,0x03,0x3b,0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03, +    0x39,0xd4,0x03,0x3b,0x94,0x83,0x1b,0x98,0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14, +    0x21,0x18,0x46,0x28,0xec,0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e, +    0xee,0x40,0x0f,0x53,0x82,0x62,0xc4,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5, +    0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e,0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20, +    0x0f,0x6e,0xc0,0x0e,0xe1,0xe0,0x0e,0xe7,0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b, +    0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1, +    0x90,0x0e,0xf2,0xe0,0x06,0xe3,0xf0,0x0e,0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0, +    0x0b,0xef,0x00,0x0f,0xf4,0x90,0x0e,0xef,0xe0,0x0e,0xf3,0x30,0xc5,0x50,0x18,0x07, +    0x92,0xa8,0x11,0x4a,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x3d,0x94,0x83,0x3c,0xd0,0x43, +    0x39,0xe0,0xc3,0x94,0xa0,0x0f,0x00,0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3, +    0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10, +    0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30, +    0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03, +    0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07, +    0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e, +    0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d, +    0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b, +    0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76, +    0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90, +    0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87, +    0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e, +    0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c, +    0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca, +    0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8, +    0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82, +    0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83, +    0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f, +    0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec, +    0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc, +    0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0, +    0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60, +    0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e, +    0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1, +    0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43, +    0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c, +    0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72, +    0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00, +    0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x3d,0x00,0x00,0x00,0x13,0x04,0x41,0x2c, +    0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6,0x22, +    0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0xa8,0x95,0x40,0x19,0x14,0x01,0xbd,0x11, +    0x00,0x1a,0x33,0x00,0x24,0x66,0x00,0x28,0xcc,0x00,0x00,0x00,0xe3,0x15,0x0b,0x84, +    0x61,0x10,0x05,0x65,0x90,0x01,0x1a,0x0c,0x13,0x02,0xf9,0x8c,0x57,0x3c,0x14,0xc7, +    0x2d,0x14,0x94,0x41,0x06,0xea,0x70,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a, +    0x2a,0x0b,0x83,0x30,0x70,0x28,0x28,0x83,0x0c,0x19,0x43,0x99,0x10,0xc8,0xc7,0x8a, +    0x00,0x3e,0xe3,0x15,0x99,0x67,0x06,0x66,0x40,0x51,0x50,0x06,0x19,0xbc,0x48,0x33, +    0x21,0x90,0x8f,0x15,0x01,0x7c,0xc6,0x2b,0xbc,0x31,0x60,0x83,0x35,0x18,0x03,0x0a, +    0xca,0x20,0x83,0x18,0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b,0xc4,0xe0,0x0c,0xe0,0xe0, +    0x0d,0x3c,0x0a,0xca,0x20,0x83,0x19,0x70,0x61,0x60,0x42,0x20,0x1f,0x0b,0x0a,0xf8, +    0x8c,0x57,0x9c,0x01,0x1b,0xd4,0x01,0x1d,0x88,0x01,0x05,0xc5,0x86,0x00,0x3e,0xb3, +    0x0d,0x61,0x10,0x00,0xb3,0x0d,0x01,0x1b,0x04,0xb3,0x0d,0xc1,0x23,0x64,0x10,0x10, +    0x03,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x5b,0x86,0x20,0x10,0x85,0x2d,0x43,0x11, +    0x88,0xc2,0x96,0x41,0x09,0x44,0x61,0xcb,0xf0,0x04,0xa2,0xb0,0x65,0xa0,0x02,0x51, +    0xd8,0x32,0x60,0x81,0x28,0x6c,0x19,0xba,0x40,0x14,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sfons_fs_bytecode_metal_macos[2941] = { +    0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x7d,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd5,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0xa0,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xfd,0x3a,0x06,0x83,0x09,0xe9,0xe7, +    0xd2,0xb5,0xf9,0x1e,0x86,0x99,0xb0,0x0c,0x8c,0xbe,0xe0,0xd3,0x2d,0x9b,0x8c,0xe9, +    0x2a,0x92,0xa3,0xeb,0x0b,0xf7,0x98,0x18,0x30,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, +    0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17, +    0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x84,0x0a,0x00,0x00,0xff,0xff,0xff, +    0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x9e,0x02,0x00,0x00,0x0b,0x82,0x20, +    0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04, +    0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b, +    0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18, +    0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21, +    0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00, +    0x00,0x51,0x18,0x00,0x00,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff, +    0xff,0x01,0x60,0x00,0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07, +    0x7a,0x60,0x87,0x7c,0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72, +    0x68,0x03,0x72,0x48,0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90, +    0x07,0x7a,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc, +    0x21,0x1c,0xd8,0x61,0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81, +    0x1d,0xca,0xa1,0x0d,0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d, +    0xd8,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87, +    0x79,0x98,0x87,0x36,0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36, +    0x30,0x07,0x78,0x68,0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c, +    0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0, +    0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1, +    0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60,0x70,0x87,0x77,0x68,0x03,0x73,0x90, +    0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78,0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07, +    0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36, +    0x60,0x87,0x72,0x08,0x07,0x73,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30, +    0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, +    0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xd4,0xa1,0x1e,0xda,0x01, +    0x1e,0xda,0x80,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60, +    0x87,0x79,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,0x77,0x98,0x87, +    0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1, +    0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c, +    0xc8,0xa1,0x0d,0xf4,0xa1,0x1c,0xe4,0xe1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda, +    0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77, +    0x78,0x87,0x36,0xa0,0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68, +    0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6, +    0x81,0x1e,0xc2,0x61,0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61, +    0x1c,0xe8,0xe1,0x1d,0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e, +    0xda,0x60,0x1e,0xd2,0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87, +    0x70,0x30,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74, +    0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e, +    0xde,0xc1,0x1c,0xe8,0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87, +    0x70,0x60,0x87,0x79,0x28,0x07,0x60,0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81, +    0x00,0x16,0xa0,0xda,0x80,0x10,0xff,0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5, +    0x06,0xa3,0x08,0x80,0x05,0xa8,0x36,0x18,0x86,0x00,0x2c,0x40,0x05,0x49,0x18,0x00, +    0x00,0x03,0x00,0x00,0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,0x00,0x00,0x00, +    0x00,0x89,0x20,0x00,0x00,0x21,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85, +    0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a, +    0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45, +    0x94,0x30,0xf9,0x0c,0x80,0x34,0xf4,0xef,0x50,0x13,0x0a,0xc2,0x51,0xd2,0x14,0x51, +    0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54,0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20, +    0xc2,0x10,0x5c,0x24,0x4d,0x11,0x25,0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f, +    0x8d,0x11,0x00,0x83,0x08,0x85,0x50,0x0a,0x11,0x02,0x31,0x74,0x86,0x11,0x04,0x60, +    0x8e,0x20,0x98,0x23,0x00,0x83,0x61,0x04,0x61,0x29,0x48,0x20,0x26,0x29,0xa6,0x00, +    0xb5,0x81,0x80,0x1c,0x58,0xc3,0x08,0xc4,0x32,0x02,0x00,0x00,0x00,0x13,0xb2,0x70, +    0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, +    0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83, +    0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07, +    0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78, +    0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90, +    0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e, +    0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76, +    0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20, +    0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07, +    0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a, +    0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30, +    0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07, +    0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74, +    0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, +    0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07, +    0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71, +    0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20, +    0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f, +    0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76, +    0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50, +    0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, +    0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78, +    0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00, +    0x00,0x00,0x00,0x00,0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64, +    0x81,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, +    0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50, +    0x10,0x65,0x40,0x70,0xac,0x41,0x79,0x08,0x00,0x79,0x18,0x00,0x00,0xd7,0x00,0x00, +    0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, +    0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, +    0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c,0x05,0xd9,0x20,0x08,0x0e,0x8e,0xad,0x0c, +    0x84,0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x24,0x07,0x46, +    0xc6,0x25,0x86,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x25,0x07,0x46, +    0xc6,0x25,0x86,0xc6,0x25,0x27,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65, +    0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16, +    0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26, +    0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61, +    0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f, +    0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x61,0x19, +    0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5, +    0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5, +    0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x61,0x10,0x96,0x26,0xe7,0x32,0xf6, +    0xd6,0x06,0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6,0x45,0xf6,0x56, +    0x27,0xc6,0x56,0xf6,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x1e,0x92, +    0x41,0x58,0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b,0x5b,0x18,0x5d, +    0x9a,0x5d,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0x59,0xda, +    0x5c,0x98,0x18,0x5b,0xd9,0x10,0xe1,0x89,0x78,0x06,0x61,0x69,0x72,0x2e,0x63,0x6f, +    0x6d,0x70,0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f,0x77,0x69,0x64, +    0x65,0x5f,0x76,0x65,0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73,0x61,0x62,0x6c, +    0x65,0x43,0x84,0x67,0x62,0x14,0x96,0x26,0xe7,0x22,0x57,0xe6,0x46,0x56,0x26,0xf7, +    0x45,0x17,0x26,0x77,0x56,0x46,0xc7,0x28,0x2c,0x4d,0xce,0x25,0x4c,0xee,0xec,0x8b, +    0x2e,0x0f,0xae,0xec,0xcb,0x2d,0xac,0xad,0x8c,0x86,0x19,0xdb,0x5b,0x18,0x1d,0x0d, +    0x99,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xb7,0xb0,0xb6,0x32,0x2a,0x66,0x72, +    0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x43,0x98,0xa7,0x5a,0x86,0xc7,0x7a,0xae, +    0x07,0x7b,0xb2,0x21,0xc2,0xa3,0x51,0x0a,0x4b,0x93,0x73,0x31,0x93,0x0b,0x3b,0x6b, +    0x2b,0x73,0xa3,0xfb,0x4a,0x73,0x83,0xab,0xa3,0xe3,0x52,0x37,0x57,0x26,0x87,0xc2, +    0xf6,0x36,0xe6,0x06,0x93,0x42,0x25,0x2c,0x4d,0xce,0x65,0xac,0xcc,0x8d,0xae,0x4c, +    0x8e,0x4f,0x58,0x9a,0x9c,0x0b,0x5c,0x99,0xdc,0x1c,0x5c,0xd9,0x18,0x5d,0x9a,0x5d, +    0x19,0x85,0x3a,0xbb,0x21,0xd2,0x32,0x3c,0xdc,0xd3,0x3d,0xde,0xf3,0x3d,0xd6,0x73, +    0x3d,0xd8,0x03,0x06,0x5c,0xea,0xe6,0xca,0xe4,0x50,0xd8,0xde,0xc6,0xdc,0x62,0x52, +    0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x91,0x16,0xe1,0xe1,0x1e,0x31,0x78,0xbc,0xe7, +    0x7b,0xac,0xe7,0x7a,0xb0,0x67,0x0c,0xb8,0x84,0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1, +    0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73, +    0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7, +    0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d, +    0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d,0x19,0xde,0x10, +    0x6a,0x21,0x9e,0x32,0x78,0xcc,0x60,0x19,0x16,0xe1,0x39,0x83,0xc7,0x7a,0xd0,0xe0, +    0xc1,0x9e,0x34,0xe0,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x26,0xc7, +    0x63,0x2e,0xac,0x0d,0x8e,0xad,0x4c,0x8e,0x08,0x5d,0x19,0xde,0x54,0x1b,0x1c,0x9b, +    0xdc,0x10,0x69,0x39,0x9e,0x35,0x78,0xcc,0x60,0x19,0x16,0xe1,0xb1,0x1e,0x36,0x78, +    0xb0,0xa7,0x0d,0x86,0x20,0x4f,0x18,0x3c,0x64,0xf0,0xa8,0xc1,0xe3,0x06,0x43,0x0c, +    0x04,0x78,0xb6,0xe7,0x0d,0x46,0x44,0xec,0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e, +    0xef,0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0,0x06,0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3, +    0x30,0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39, +    0x94,0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18,0xb1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8, +    0x43,0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b,0xb8,0xc3,0x94,0xc0,0x18,0x41,0x85,0x43, +    0x3a,0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8,0xc3,0x39,0xd4,0x43,0x38,0x9c,0x43,0x39, +    0xfc,0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43,0x3a,0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11, +    0x53,0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38,0xbc,0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43, +    0x39,0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x3c,0x4c,0x31,0x14, +    0xc6,0x81,0x24,0x6a,0x04,0x13,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0e,0xf2,0x10,0x0e, +    0xe7,0xd0,0x0e,0xe5,0xe0,0x0e,0xf4,0x30,0x25,0x80,0x03,0x00,0x00,0x79,0x18,0x00, +    0x00,0x6d,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d, +    0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c, +    0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d, +    0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d, +    0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79, +    0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc, +    0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50, +    0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30, +    0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03, +    0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07, +    0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76, +    0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98, +    0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8, +    0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21, +    0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43, +    0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f, +    0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70, +    0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0, +    0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40, +    0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41, +    0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e, +    0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07, +    0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f, +    0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d, +    0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38, +    0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88, +    0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08, +    0x07,0x7a,0x28,0x07,0x72,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00, +    0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45, +    0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00, +    0x00,0x61,0x20,0x00,0x00,0x16,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00, +    0x00,0x0b,0x00,0x00,0x00,0x14,0xc7,0x22,0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25, +    0x00,0x01,0xa9,0x11,0x80,0x1a,0x20,0x31,0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a, +    0x06,0x80,0xc0,0x0c,0xc0,0x08,0xc0,0x18,0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00, +    0x00,0x83,0x0c,0x0f,0x91,0x8c,0x18,0x28,0x42,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30, +    0xc8,0x70,0x04,0x8d,0x05,0x91,0x7c,0x66,0x1b,0x94,0x00,0xc8,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sfons_vs_bytecode_metal_ios[3344] = { +    0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x10,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x58,0xff,0x12,0x36,0x80,0x88,0xb5, +    0xf7,0x1c,0xc3,0xd5,0x31,0x57,0x18,0x26,0x26,0xf0,0x8c,0xd4,0x15,0xa2,0x55,0x8b, +    0xd5,0x3c,0x6b,0x11,0xbd,0x17,0x49,0x4a,0x02,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x37,0x00,0x00, +    0x00,0x56,0x41,0x54,0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, +    0x6e,0x00,0x00,0x80,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80, +    0x63,0x6f,0x6c,0x6f,0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03, +    0x00,0x06,0x04,0x06,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54, +    0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xec,0x0b,0x00,0x00, +    0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xf8,0x02,0x00,0x00, +    0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91, +    0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19, +    0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14, +    0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80, +    0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0, +    0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8, +    0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87, +    0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72, +    0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88, +    0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41, +    0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c, +    0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87, +    0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72, +    0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,0x1e, +    0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4,0x21,0x1c,0xda,0xa1,0x1c,0xda, +    0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41,0x1e,0xda,0xa0,0x1c,0xd8,0x21, +    0x1d,0xda,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06,0x77,0x78,0x87,0x36,0x30, +    0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87, +    0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x68,0x03,0x77,0x78,0x07,0x77, +    0x68,0x03,0x76,0x28,0x87,0x70,0x30,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x74,0x70, +    0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x40,0x1d,0xea,0xa1, +    0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08, +    0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,0x87, +    0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, +    0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe6,0x21,0x1d,0xce,0xc1,0x1d, +    0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,0x61,0x1e,0xda,0xc0,0x1c,0xe0, +    0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80, +    0x70,0x87,0x77,0x68,0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38, +    0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00, +    0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1, +    0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c, +    0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87, +    0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a, +    0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d, +    0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00, +    0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x20,0x02,0x01,0x24,0xc0,0x02,0x54, +    0x00,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00, +    0x89,0x20,0x00,0x00,0x23,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04, +    0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c, +    0x0b,0x84,0xa4,0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45,0x94, +    0x30,0xf9,0x0c,0x80,0x34,0xf4,0xef,0x50,0x13,0x1a,0x42,0x08,0xc3,0x08,0x02,0x90, +    0x04,0x61,0x26,0x6a,0x1e,0xe8,0x41,0x1e,0xea,0x61,0x1c,0xe8,0xc1,0x0d,0xda,0xa1, +    0x1c,0xe8,0x21,0x1c,0xd8,0x41,0x0f,0xf4,0xa0,0x1d,0xc2,0x81,0x1e,0xe4,0x21,0x1d, +    0xf0,0x01,0x05,0xe4,0x20,0x69,0x8a,0x28,0x61,0xf2,0x2b,0xe9,0x7f,0x80,0x08,0x60, +    0x24,0x24,0x94,0x32,0x88,0x60,0x08,0xa5,0x10,0x61,0x84,0x43,0x68,0x20,0x60,0x8e, +    0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb,0x08, +    0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70, +    0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80, +    0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0, +    0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07, +    0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9, +    0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30, +    0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07, +    0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72, +    0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0, +    0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07, +    0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73, +    0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20, +    0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07, +    0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, +    0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0, +    0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07, +    0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72, +    0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20, +    0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07, +    0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75, +    0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, +    0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07, +    0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00,0x00, +    0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98,0x10, +    0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50, +    0x04,0x05,0x18,0x50,0x80,0x02,0x85,0x50,0x10,0x65,0x50,0x20,0xd4,0x46,0x00,0x88, +    0x8d,0x25,0x34,0x05,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x19,0x01,0x00,0x00, +    0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7, +    0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3, +    0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x06,0xd9,0x20,0x08,0x0e,0x8e,0xad,0x0c,0x84, +    0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x07,0x46, +    0xc6,0xc5,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x07, +    0x46,0xc6,0xc5,0xe6,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x8e,0x45,0x60, +    0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x84,0x45,0xe0,0x16,0x96, +    0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7, +    0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69, +    0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d, +    0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x21,0x19,0x84, +    0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95, +    0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85, +    0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x61,0x10,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06, +    0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6,0x45,0xf6,0x56,0x27,0xc6, +    0x56,0xf6,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x1e,0x92,0x41,0x58, +    0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b,0x5b,0x18,0x5d,0x9a,0x5d, +    0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0x59,0xda,0x5c,0x98, +    0x18,0x5b,0xd9,0x10,0x41,0x89,0x78,0x06,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70, +    0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f,0x77,0x69,0x64,0x65,0x5f, +    0x76,0x65,0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,0x43, +    0x04,0x65,0x62,0x14,0x96,0x26,0xe7,0x62,0x57,0x26,0x47,0x57,0x86,0xf7,0xf5,0x56, +    0x47,0x07,0x57,0x47,0xc7,0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x0d,0x26, +    0x85,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e,0x5c,0xd9,0x97,0x5b, +    0x58,0x5b,0x19,0x0d,0x33,0xb6,0xb7,0x30,0x3a,0x1a,0x32,0x61,0x69,0x72,0x2e,0x61, +    0x72,0x67,0x5f,0x6e,0x61,0x6d,0x65,0x14,0xea,0xec,0x86,0x30,0x4a,0xa5,0x58,0xca, +    0xa5,0x60,0x4a,0xa6,0x68,0x5c,0xea,0xe6,0xca,0xe4,0x50,0xd8,0xde,0xc6,0xdc,0x62, +    0x52,0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x94,0x4a,0xe1,0x94,0x4b,0xc1,0x94, +    0x4c,0xe9,0xc8,0x84,0xa5,0xc9,0xb9,0xc0,0xbd,0xcd,0xa5,0xd1,0xa5,0xbd,0xb9,0x71, +    0x39,0x63,0xfb,0x82,0x7a,0x9b,0x4b,0xa3,0x4b,0x7b,0x73,0x1b,0xa2,0x28,0x9f,0x72, +    0x29,0x98,0x92,0x29,0x60,0x30,0xc4,0x50,0x36,0xc5,0x53,0xc2,0x80,0x50,0x58,0x9a, +    0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b,0x5c,0x1d,0x1d,0xa5,0xb0, +    0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7,0xaf,0x34,0x37,0xb2,0x32, +    0x3c,0x66,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,0x28,0x38,0x70,0x6f,0x73, +    0x69,0x74,0x69,0x6f,0x6e,0x44,0x76,0x34,0x5f,0x66,0x29,0x44,0xe0,0xde,0xe6,0xd2, +    0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x8c,0x81,0x42,0x06,0x8b,0xb0,0x04,0x4a, +    0x19,0x28,0x97,0x82,0x29,0x99,0x62,0x06,0xd4,0xce,0xca,0xdc,0xca,0xe4,0xc2,0xe8, +    0xca,0xc8,0x50,0x72,0xe8,0xca,0xf0,0xc6,0xde,0xde,0xe4,0xc8,0x60,0x88,0xec,0x64, +    0xbe,0xcc,0x52,0x68,0x98,0xb1,0xbd,0x85,0xd1,0xc9,0x30,0xa1,0x2b,0xc3,0x1b,0x7b, +    0x7b,0x93,0x23,0x83,0x19,0x42,0x2d,0x81,0x32,0x06,0x0a,0x19,0x2c,0xc1,0x12,0x28, +    0x68,0xa0,0x5c,0x4a,0x1a,0x28,0x99,0xa2,0x06,0xbc,0xce,0xca,0xdc,0xca,0xe4,0xc2, +    0xe8,0xca,0xc8,0x50,0x6c,0xc6,0xde,0xd8,0xde,0xe4,0x60,0x88,0xec,0x68,0xbe,0xcc, +    0x52,0x68,0x8c,0xbd,0xb1,0xbd,0xc9,0xc1,0x0c,0xa1,0x96,0x41,0x19,0x03,0x85,0x0c, +    0x96,0x61,0x09,0x14,0x36,0x50,0x2e,0x05,0x53,0x32,0xa5,0x0d,0xa8,0x84,0xa5,0xc9, +    0xb9,0x88,0xd5,0x99,0x99,0x95,0xc9,0xf1,0x09,0x4b,0x93,0x73,0x11,0xab,0x33,0x33, +    0x2b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x23,0x12,0x96,0x26,0xe7,0x22,0x57,0x16,0x46, +    0x46,0x2a,0x2c,0x4d,0xce,0x65,0x8e,0x4e,0xae,0x6e,0x8c,0xee,0x8b,0x2e,0x0f,0xae, +    0xec,0x2b,0xcd,0xcd,0xec,0x8d,0x88,0x19,0xdb,0x5b,0x18,0x1d,0x0d,0x1e,0x0d,0x87, +    0x36,0x3b,0x38,0x0a,0x74,0x6d,0x43,0xa8,0x45,0x58,0x88,0x45,0x50,0xe6,0x40,0xa1, +    0x83,0x85,0x58,0x88,0x45,0x50,0xe6,0x40,0xa9,0x03,0x46,0x61,0x69,0x72,0x2e,0x61, +    0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x73,0x69,0x7a,0x65,0xbc,0xc2,0xd2,0xe4, +    0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xc2,0xd8,0xd2,0xce,0xdc,0xbe, +    0xe6,0xd2,0xf4,0xca,0x98,0xd8,0xcd,0x7d,0xc1,0x85,0xc9,0x85,0xb5,0xcd,0x71,0xf8, +    0x92,0x81,0x19,0x42,0x06,0x0b,0xa2,0xbc,0x81,0x02,0x07,0x4b,0xa1,0x90,0xc1,0x22, +    0x2c,0x81,0x12,0x07,0x8a,0x1c,0x28,0x76,0xa0,0xdc,0xc1,0x52,0x28,0x78,0xb0,0x24, +    0xca,0xa5,0xe4,0x81,0x92,0x29,0x7a,0x30,0x04,0x51,0xce,0x40,0x59,0x03,0xc5,0x0d, +    0x94,0x3d,0x18,0x62,0x24,0x80,0x22,0x06,0x0a,0x1f,0xf0,0x79,0x6b,0x73,0x4b,0x83, +    0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33,0x95,0xd6,0x06,0xc7, +    0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44,0x50,0xfe,0x60,0x88, +    0xa1,0xf8,0x81,0x02,0x0a,0x8d,0x32,0xc4,0x50,0x42,0x41,0x09,0x85,0x46,0x19,0x11, +    0xb1,0x03,0x3b,0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03,0x39,0xd4,0x03,0x3b,0x94, +    0x83,0x1b,0x98,0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14,0x21,0x18,0x46,0x28,0xec, +    0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e,0xee,0x40,0x0f,0x53,0x82, +    0x62,0xc4,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e, +    0xef,0xe0,0x0e,0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20,0x0f,0x6e,0xc0,0x0e,0xe1, +    0xe0,0x0e,0xe7,0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b,0xf6,0x50,0x0e,0xf2,0x30, +    0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1,0x90,0x0e,0xf2,0xe0,0x06, +    0xe3,0xf0,0x0e,0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0,0x0b,0xef,0x00,0x0f,0xf4, +    0x90,0x0e,0xef,0xe0,0x0e,0xf3,0x30,0xc5,0x50,0x18,0x07,0x92,0xa8,0x11,0x4a,0x38, +    0xa4,0x83,0x3c,0xb8,0x81,0x3d,0x94,0x83,0x3c,0xd0,0x43,0x39,0xe0,0xc3,0x94,0xa0, +    0x0f,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00,0x00,0x33,0x08,0x80,0x1c, +    0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07, +    0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e, +    0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43, +    0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c, +    0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76, +    0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e, +    0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8, +    0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4, +    0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68, +    0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07, +    0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71, +    0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5, +    0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4, +    0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90, +    0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43, +    0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b, +    0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78, +    0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2, +    0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20, +    0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0, +    0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83, +    0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07, +    0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61, +    0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d, +    0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39, +    0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79, +    0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x00,0x00,0x00,0x00, +    0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00, +    0x61,0x20,0x00,0x00,0x3d,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00, +    0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6,0x22,0x04,0x41,0x10,0xc6, +    0x22,0x82,0x20,0x08,0xa8,0x95,0x40,0x19,0x14,0x01,0xbd,0x11,0x00,0x1a,0x33,0x00, +    0x24,0x66,0x00,0x28,0xcc,0x00,0x00,0x00,0xe3,0x15,0x0b,0x84,0x61,0x10,0x05,0x65, +    0x90,0x01,0x1a,0x0c,0x13,0x02,0xf9,0x8c,0x57,0x3c,0x14,0xc7,0x2d,0x14,0x94,0x41, +    0x06,0xea,0x70,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a,0x2a,0x0b,0x83,0x30, +    0x70,0x28,0x28,0x83,0x0c,0x19,0x43,0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15, +    0x99,0x67,0x06,0x66,0x40,0x51,0x50,0x06,0x19,0xbc,0x48,0x33,0x21,0x90,0x8f,0x15, +    0x01,0x7c,0xc6,0x2b,0xbc,0x31,0x60,0x83,0x35,0x18,0x03,0x0a,0xca,0x20,0x83,0x18, +    0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b,0xc4,0xe0,0x0c,0xe0,0xe0,0x0d,0x3c,0x0a,0xca, +    0x20,0x83,0x19,0x70,0x61,0x60,0x42,0x20,0x1f,0x0b,0x0a,0xf8,0x8c,0x57,0x9c,0x01, +    0x1b,0xd4,0x01,0x1d,0x88,0x01,0x05,0xc5,0x86,0x00,0x3e,0xb3,0x0d,0x61,0x10,0x00, +    0xb3,0x0d,0x01,0x1b,0x04,0xb3,0x0d,0xc1,0x23,0x64,0x10,0x10,0x03,0x00,0x00,0x00, +    0x09,0x00,0x00,0x00,0x5b,0x86,0x20,0x10,0x85,0x2d,0x43,0x11,0x88,0xc2,0x96,0x41, +    0x09,0x44,0x61,0xcb,0xf0,0x04,0xa2,0xb0,0x65,0xa0,0x02,0x51,0xd8,0x32,0x60,0x81, +    0x28,0x6c,0x19,0xba,0x40,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sfons_fs_bytecode_metal_ios[2925] = { +    0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd5,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x90,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x1b,0x2c,0xa9,0x59,0x2d,0x7d,0xde, +    0xcd,0x5c,0x34,0x76,0x1e,0x63,0x4d,0x0c,0xdc,0xfc,0x74,0x70,0x26,0x76,0xe8,0xde, +    0xed,0xf7,0xc3,0xf7,0xe9,0x62,0x28,0xfa,0xd6,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, +    0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17, +    0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x7c,0x0a,0x00,0x00,0xff,0xff,0xff, +    0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x9c,0x02,0x00,0x00,0x0b,0x82,0x20, +    0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04, +    0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b, +    0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18, +    0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21, +    0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00, +    0x00,0x51,0x18,0x00,0x00,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff, +    0xff,0x01,0x60,0x00,0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07, +    0x7a,0x60,0x87,0x7c,0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72, +    0x68,0x03,0x72,0x48,0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90, +    0x07,0x7a,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc, +    0x21,0x1c,0xd8,0x61,0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81, +    0x1d,0xca,0xa1,0x0d,0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d, +    0xd8,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87, +    0x79,0x98,0x87,0x36,0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36, +    0x30,0x07,0x78,0x68,0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c, +    0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0, +    0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1, +    0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60,0x70,0x87,0x77,0x68,0x03,0x73,0x90, +    0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78,0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07, +    0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36, +    0x60,0x87,0x72,0x08,0x07,0x73,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30, +    0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, +    0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xd4,0xa1,0x1e,0xda,0x01, +    0x1e,0xda,0x80,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60, +    0x87,0x79,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,0x77,0x98,0x87, +    0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1, +    0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c, +    0xc8,0xa1,0x0d,0xf4,0xa1,0x1c,0xe4,0xe1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda, +    0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77, +    0x78,0x87,0x36,0xa0,0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68, +    0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6, +    0x81,0x1e,0xc2,0x61,0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61, +    0x1c,0xe8,0xe1,0x1d,0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e, +    0xda,0x60,0x1e,0xd2,0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87, +    0x70,0x30,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74, +    0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e, +    0xde,0xc1,0x1c,0xe8,0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87, +    0x70,0x60,0x87,0x79,0x28,0x07,0x60,0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81, +    0x00,0x16,0xa0,0xda,0x80,0x10,0xff,0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5, +    0x06,0xa3,0x08,0x80,0x05,0xa8,0x36,0x18,0x86,0x00,0x2c,0x40,0x05,0x49,0x18,0x00, +    0x00,0x03,0x00,0x00,0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,0x00,0x00,0x00, +    0x00,0x89,0x20,0x00,0x00,0x21,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85, +    0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a, +    0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45, +    0x94,0x30,0xf9,0x0c,0x80,0x34,0xf4,0xef,0x50,0x13,0x0a,0xc2,0x51,0xd2,0x14,0x51, +    0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54,0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20, +    0xc2,0x10,0x5c,0x24,0x4d,0x11,0x25,0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f, +    0x8d,0x11,0x00,0x83,0x08,0x85,0x50,0x0a,0x11,0x02,0x31,0x74,0x86,0x11,0x04,0x60, +    0x8e,0x20,0x98,0x23,0x00,0x83,0x61,0x04,0x61,0x29,0x48,0x20,0x26,0x29,0xa6,0x00, +    0xb5,0x81,0x80,0x14,0x58,0xc3,0x08,0xc4,0x32,0x02,0x00,0x00,0x00,0x13,0xa8,0x70, +    0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, +    0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51, +    0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, +    0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78, +    0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60, +    0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07, +    0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, +    0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30, +    0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, +    0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, +    0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0, +    0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, +    0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, +    0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90, +    0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06, +    0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, +    0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0, +    0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07, +    0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72, +    0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10, +    0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, +    0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73, +    0x20,0x07,0x43,0x18,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03, +    0x04,0x80,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00, +    0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, +    0x5a,0x23,0x00,0x25,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0xa1,0x29,0x00, +    0x00,0x79,0x18,0x00,0x00,0xd7,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, +    0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50, +    0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc3,0x23,0x2c, +    0x05,0xd9,0x20,0x08,0x0e,0x8e,0xad,0x0c,0x84,0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c, +    0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x07,0x46,0xc6,0xc5,0xe6,0x06,0x04,0xa5,0xad, +    0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x07,0x46,0xc6,0xc5,0xe6,0x26,0x65,0x88, +    0xf0,0x10,0x43,0x8c,0x65,0x58,0x8c,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04, +    0x79,0x8e,0x65,0x58,0x84,0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97, +    0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26, +    0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69, +    0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61, +    0x62,0x6c,0x65,0x43,0x84,0x67,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1, +    0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d, +    0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86, +    0x61,0x10,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46, +    0x97,0x66,0x57,0xf6,0x45,0xf6,0x56,0x27,0xc6,0x56,0xf6,0x45,0x96,0x36,0x17,0x26, +    0xc6,0x56,0x36,0x44,0x78,0x1e,0x92,0x41,0x58,0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c, +    0x1a,0x5b,0x99,0x8b,0x5b,0x18,0x5d,0x9a,0x5d,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17, +    0xdb,0x9b,0xdb,0xd9,0x17,0x59,0xda,0x5c,0x98,0x18,0x5b,0xd9,0x10,0xe1,0x89,0x78, +    0x06,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74, +    0x69,0x76,0x65,0x5f,0x77,0x69,0x64,0x65,0x5f,0x76,0x65,0x63,0x74,0x6f,0x72,0x73, +    0x5f,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x62,0x14,0x96,0x26,0xe7, +    0x22,0x57,0xe6,0x46,0x56,0x26,0xf7,0x45,0x17,0x26,0x77,0x56,0x46,0xc7,0x28,0x2c, +    0x4d,0xce,0x25,0x4c,0xee,0xec,0x8b,0x2e,0x0f,0xae,0xec,0xcb,0x2d,0xac,0xad,0x8c, +    0x86,0x19,0xdb,0x5b,0x18,0x1d,0x0d,0x99,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, +    0xb7,0xb0,0xb6,0x32,0x2a,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x43, +    0x98,0xa7,0x5a,0x84,0xc7,0x7a,0xae,0x07,0x7b,0xb2,0x21,0xc2,0xa3,0x51,0x0a,0x4b, +    0x93,0x73,0x31,0x93,0x0b,0x3b,0x6b,0x2b,0x73,0xa3,0xfb,0x4a,0x73,0x83,0xab,0xa3, +    0xe3,0x52,0x37,0x57,0x26,0x87,0xc2,0xf6,0x36,0xe6,0x06,0x93,0x42,0x25,0x2c,0x4d, +    0xce,0x65,0xac,0xcc,0x8d,0xae,0x4c,0x8e,0x4f,0x58,0x9a,0x9c,0x0b,0x5c,0x99,0xdc, +    0x1c,0x5c,0xd9,0x18,0x5d,0x9a,0x5d,0x19,0x85,0x3a,0xbb,0x21,0xd2,0x22,0x3c,0xdc, +    0xd3,0x3d,0xde,0xf3,0x3d,0xd6,0x73,0x3d,0xd8,0x03,0x06,0x5c,0xea,0xe6,0xca,0xe4, +    0x50,0xd8,0xde,0xc6,0xdc,0x62,0x52,0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x91,0x96, +    0xe1,0xe1,0x1e,0x31,0x78,0xbc,0xe7,0x7b,0xac,0xe7,0x7a,0xb0,0x67,0x0c,0xb8,0x84, +    0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61, +    0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96, +    0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae, +    0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad, +    0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21,0x9e,0x32,0x78,0xcc,0x60,0x11,0x96, +    0xe1,0x39,0x83,0xc7,0x7a,0xd0,0xe0,0xc1,0x9e,0x34,0xe0,0x12,0x96,0x26,0xe7,0x32, +    0x17,0xd6,0x06,0xc7,0x56,0x26,0xc7,0x63,0x2e,0xac,0x0d,0x8e,0xad,0x4c,0x8e,0x08, +    0x5d,0x19,0xde,0x54,0x1b,0x1c,0x9b,0xdc,0x10,0x69,0x39,0x9e,0x35,0x78,0xcc,0x60, +    0x11,0x96,0xe1,0xb1,0x1e,0x36,0x78,0xb0,0xa7,0x0d,0x86,0x20,0x4f,0x18,0x3c,0x64, +    0xf0,0xa8,0xc1,0xe3,0x06,0x43,0x0c,0x04,0x78,0xb6,0xe7,0x0d,0x46,0x44,0xec,0xc0, +    0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e,0xef,0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0,0x06, +    0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3,0x30,0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0,0x83, +    0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39,0x94,0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18,0xb1, +    0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8,0x43,0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b,0xb8, +    0xc3,0x94,0xc0,0x18,0x41,0x85,0x43,0x3a,0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8,0xc3, +    0x39,0xd4,0x43,0x38,0x9c,0x43,0x39,0xfc,0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43,0x3a, +    0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11,0x53,0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38,0xbc, +    0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43,0x39,0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4,0xc3, +    0x3b,0xb8,0xc3,0x3c,0x4c,0x31,0x14,0xc6,0x81,0x24,0x6a,0x04,0x13,0x0e,0xe9,0x20, +    0x0f,0x6e,0x60,0x0e,0xf2,0x10,0x0e,0xe7,0xd0,0x0e,0xe5,0xe0,0x0e,0xf4,0x30,0x25, +    0x80,0x03,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00,0x00,0x33,0x08,0x80, +    0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80, +    0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80, +    0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88, +    0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78, +    0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03, +    0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f, +    0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c, +    0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39, +    0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b, +    0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60, +    0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87, +    0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e, +    0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c, +    0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6, +    0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94, +    0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03, +    0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07, +    0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f, +    0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33, +    0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc, +    0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c, +    0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78, +    0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca, +    0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21, +    0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43, +    0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87, +    0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x00,0x00,0x00, +    0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00, +    0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d, +    0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x16,0x00,0x00, +    0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x14,0xc7,0x22, +    0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25,0x00,0x01,0xa9,0x11,0x80,0x1a,0x20,0x31, +    0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a,0x06,0x80,0xc0,0x0c,0xc0,0x08,0xc0,0x18, +    0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00,0x00,0x83,0x0c,0x0f,0x91,0x8c,0x18,0x28, +    0x42,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30,0xc8,0x70,0x04,0x8d,0x05,0x91,0x7c,0x66, +    0x1b,0x94,0x00,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _sfons_vs_source_metal_sim[698] = { +    0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, +    0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, +    0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, +    0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, +    0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, +    0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, +    0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, +    0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, +    0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, +    0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, +    0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, +    0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, +    0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, +    0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, +    0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73, +    0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b, +    0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69, +    0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65, +    0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, +    0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x5b,0x5b,0x61,0x74, +    0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, +    0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20, +    0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x32,0x29,0x5d,0x5d, +    0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x36,0x20,0x22, +    0x22,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, +    0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69, +    0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d, +    0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x32,0x30,0x20,0x5b,0x5b,0x62,0x75,0x66, +    0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, +    0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20, +    0x7b,0x7d,0x3b,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x36,0x20,0x22,0x22,0x0a, +    0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, +    0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x32,0x30,0x2e,0x6d,0x76,0x70,0x20,0x2a,0x20, +    0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x23,0x6c,0x69, +    0x6e,0x65,0x20,0x31,0x37,0x20,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74, +    0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x32,0x30,0x2e,0x74,0x6d,0x20,0x2a,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, +    0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x23, +    0x6c,0x69,0x6e,0x65,0x20,0x31,0x38,0x20,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f, +    0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f, +    0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e, +    0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sfons_fs_source_metal_sim[498] = { +    0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, +    0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, +    0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, +    0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, +    0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, +    0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, +    0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, +    0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, +    0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, +    0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, +    0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, +    0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, +    0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20, +    0x31,0x31,0x20,0x22,0x22,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d, +    0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d, +    0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61, +    0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, +    0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65,0x78,0x20,0x5b,0x5b, +    0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61, +    0x6d,0x70,0x6c,0x65,0x72,0x20,0x74,0x65,0x78,0x53,0x6d,0x70,0x6c,0x72,0x20,0x5b, +    0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b, +    0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f, +    0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31, +    0x31,0x20,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61, +    0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, +    0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20, +    0x74,0x65,0x78,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x53,0x6d, +    0x70,0x6c,0x72,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x2e,0x78,0x79,0x29,0x2e,0x78, +    0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20, +    0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a, +    0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +static const uint8_t _sfons_vs_bytecode_hlsl4[1008] = { +    0x44,0x58,0x42,0x43,0xf3,0xd9,0xf4,0x58,0x44,0xc2,0xb9,0x96,0x56,0x7c,0xb3,0x71, +    0x84,0xc5,0xde,0x61,0x01,0x00,0x00,0x00,0xf0,0x03,0x00,0x00,0x05,0x00,0x00,0x00, +    0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x78,0x01,0x00,0x00,0xe8,0x01,0x00,0x00, +    0x74,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, +    0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, +    0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, +    0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x32,0x30,0x5f, +    0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x5f,0x32,0x30,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, +    0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, +    0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, +    0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x50,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x03,0x03,0x00,0x00,0x50,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43, +    0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, +    0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, +    0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69, +    0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab,0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00, +    0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00,0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, +    0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00, +    0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03, +    0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00, +    0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08, +    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00, +    0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a, +    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00, +    0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, +    0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, +    0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00, +    0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00, +    0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, +    0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, +    0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00, +    0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00, +    0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, +    0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +}; +static const uint8_t _sfons_fs_bytecode_hlsl4[640] = { +    0x44,0x58,0x42,0x43,0x5e,0xbc,0x77,0xd9,0xc9,0xdf,0x7d,0x8e,0x0c,0x24,0x18,0x66, +    0x36,0xd3,0xf4,0x78,0x01,0x00,0x00,0x00,0x80,0x02,0x00,0x00,0x05,0x00,0x00,0x00, +    0x34,0x00,0x00,0x00,0xd4,0x00,0x00,0x00,0x20,0x01,0x00,0x00,0x54,0x01,0x00,0x00, +    0x04,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, +    0x10,0x81,0x00,0x00,0x6d,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x69,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x5f,0x74,0x65,0x78,0x5f,0x73,0x61,0x6d, +    0x70,0x6c,0x65,0x72,0x00,0x74,0x65,0x78,0x00,0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f, +    0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68,0x61,0x64, +    0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30,0x2e,0x31, +    0x00,0xab,0xab,0xab,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00,0x38,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, +    0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65, +    0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00, +    0x2a,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00, +    0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00,0x55,0x55,0x00,0x00, +    0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x62,0x10,0x00,0x03, +    0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00, +    0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x45,0x00,0x00,0x09, +    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00, +    0x96,0x73,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00, +    0x36,0x00,0x00,0x05,0x12,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x01,0x40,0x00,0x00, +    0x00,0x00,0x80,0x3f,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, +    0x06,0x0c,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00, +    0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x04,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +}; +#elif defined(SOKOL_WGPU) +/* +    WebGPU shader blobs: + +    Vertex shader source: + +    #version 450 + +    layout(set = 0, binding = 0, std140) uniform vs_params +    { +        mat4 mvp; +        mat4 tm; +    } _20; + +    layout(location = 0) in vec4 position; +    layout(location = 0) out vec4 uv; +    layout(location = 1) in vec2 texcoord0; +    layout(location = 1) out vec4 color; +    layout(location = 2) in vec4 color0; + +    void main() +    { +        gl_Position = _20.mvp * position; +        uv = _20.tm * vec4(texcoord0, 0.0, 1.0); +        color = color0; +    } + +    Fragment shader source: + +    #version 450 + +    layout(location = 0, set = 2, binding = 0) uniform sampler2D tex; + +    layout(location = 0) out vec4 frag_color; +    layout(location = 0) in vec4 uv; +    layout(location = 1) in vec4 color; + +    void main() +    { +        frag_color = vec4(1.0, 1.0, 1.0, texture(tex, uv.xy).x) * color; +    } +*/ +static const uint8_t _sfons_vs_bytecode_wgpu[1932] = { +    0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x08,0x00,0x08,0x00,0x32,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x06,0x00, +    0x02,0x00,0x00,0x00,0x47,0x4c,0x53,0x4c,0x2e,0x73,0x74,0x64,0x2e,0x34,0x35,0x30, +    0x00,0x00,0x00,0x00,0x0e,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x0f,0x00,0x0d,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e, +    0x00,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x24,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x30,0x00,0x00,0x00, +    0x31,0x00,0x00,0x00,0x07,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x37,0x00,0x02,0x00,0x00,0x00,0xc2,0x01,0x00,0x00,0x01,0x00,0x00,0x00, +    0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65, +    0x73,0x73,0x65,0x64,0x20,0x63,0x6c,0x69,0x65,0x6e,0x74,0x20,0x76,0x75,0x6c,0x6b, +    0x61,0x6e,0x31,0x30,0x30,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c, +    0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x63,0x6c,0x69,0x65,0x6e, +    0x74,0x20,0x6f,0x70,0x65,0x6e,0x67,0x6c,0x31,0x30,0x30,0x0a,0x2f,0x2f,0x20,0x4f, +    0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64, +    0x20,0x74,0x61,0x72,0x67,0x65,0x74,0x2d,0x65,0x6e,0x76,0x20,0x76,0x75,0x6c,0x6b, +    0x61,0x6e,0x31,0x2e,0x30,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c, +    0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x74,0x61,0x72,0x67,0x65, +    0x74,0x2d,0x65,0x6e,0x76,0x20,0x6f,0x70,0x65,0x6e,0x67,0x6c,0x0a,0x2f,0x2f,0x20, +    0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65, +    0x64,0x20,0x65,0x6e,0x74,0x72,0x79,0x2d,0x70,0x6f,0x69,0x6e,0x74,0x20,0x6d,0x61, +    0x69,0x6e,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x0a,0x00,0x05,0x00,0x04,0x00, +    0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00, +    0x0c,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x65,0x72,0x56,0x65,0x72,0x74,0x65,0x78, +    0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x06,0x00,0x07,0x00, +    0x0c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74, +    0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x0c,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x67,0x6c,0x5f,0x43,0x6c,0x69,0x70,0x44,0x69,0x73,0x74,0x61, +    0x6e,0x63,0x65,0x00,0x06,0x00,0x07,0x00,0x0c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x67,0x6c,0x5f,0x43,0x75,0x6c,0x6c,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x00, +    0x05,0x00,0x03,0x00,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00, +    0x12,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x00,0x00,0x00, +    0x06,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x76,0x70,0x00, +    0x06,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x74,0x6d,0x00,0x00, +    0x05,0x00,0x03,0x00,0x14,0x00,0x00,0x00,0x5f,0x32,0x30,0x00,0x05,0x00,0x05,0x00, +    0x19,0x00,0x00,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x00,0x00, +    0x05,0x00,0x03,0x00,0x1e,0x00,0x00,0x00,0x75,0x76,0x00,0x00,0x05,0x00,0x05,0x00, +    0x24,0x00,0x00,0x00,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x00,0x00, +    0x05,0x00,0x04,0x00,0x2c,0x00,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x00, +    0x05,0x00,0x04,0x00,0x2d,0x00,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x00,0x00, +    0x05,0x00,0x05,0x00,0x30,0x00,0x00,0x00,0x67,0x6c,0x5f,0x56,0x65,0x72,0x74,0x65, +    0x78,0x49,0x44,0x00,0x05,0x00,0x06,0x00,0x31,0x00,0x00,0x00,0x67,0x6c,0x5f,0x49, +    0x6e,0x73,0x74,0x61,0x6e,0x63,0x65,0x49,0x44,0x00,0x00,0x00,0x48,0x00,0x05,0x00, +    0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x48,0x00,0x05,0x00,0x0c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x0b,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0c,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x47,0x00,0x03,0x00, +    0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x48,0x00,0x04,0x00,0x12,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x12,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00, +    0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x10,0x00,0x00,0x00, +    0x48,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00, +    0x48,0x00,0x05,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x23,0x00,0x00,0x00, +    0x40,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x07,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x12,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x22,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x21,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x19,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x1e,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x24,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x2c,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x2d,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x30,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x05,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x31,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x06,0x00,0x00,0x00,0x13,0x00,0x02,0x00,0x03,0x00,0x00,0x00,0x21,0x00,0x03,0x00, +    0x04,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x07,0x00,0x00,0x00, +    0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x07,0x00,0x00,0x00, +    0x04,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x20,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x0a,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x1c,0x00,0x04,0x00,0x0b,0x00,0x00,0x00,0x07,0x00,0x00,0x00, +    0x0a,0x00,0x00,0x00,0x1e,0x00,0x06,0x00,0x0c,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x07,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x0d,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x0d,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x15,0x00,0x04,0x00, +    0x0f,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x0f,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x04,0x00, +    0x11,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x1e,0x00,0x04,0x00, +    0x12,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x13,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x13,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x15,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x18,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x18,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x1c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x1c,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x0f,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x17,0x00,0x04,0x00, +    0x22,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x23,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x23,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x07,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x07,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,0x3b,0x00,0x04,0x00, +    0x1c,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x18,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x2f,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x2f,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x2f,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x36,0x00,0x05,0x00, +    0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00, +    0xf8,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0x01,0x00,0x00,0x00, +    0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x15,0x00,0x00,0x00, +    0x16,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x11,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x08,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x91,0x00,0x05,0x00, +    0x08,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x1a,0x00,0x00,0x00, +    0x41,0x00,0x05,0x00,0x1c,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x0e,0x00,0x00,0x00, +    0x10,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x1d,0x00,0x00,0x00,0x1b,0x00,0x00,0x00, +    0x08,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x41,0x00,0x05,0x00,0x15,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x14,0x00,0x00,0x00, +    0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x21,0x00,0x00,0x00, +    0x20,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x22,0x00,0x00,0x00,0x25,0x00,0x00,0x00, +    0x24,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x28,0x00,0x00,0x00, +    0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x07,0x00,0x00,0x00, +    0x29,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x50,0x00,0x07,0x00, +    0x08,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x29,0x00,0x00,0x00, +    0x26,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x91,0x00,0x05,0x00,0x08,0x00,0x00,0x00, +    0x2b,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x3e,0x00,0x03,0x00, +    0x1e,0x00,0x00,0x00,0x2b,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0x01,0x00,0x00,0x00, +    0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x00,0x00,0x00, +    0x2e,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x2c,0x00,0x00,0x00, +    0x2e,0x00,0x00,0x00,0xfd,0x00,0x01,0x00,0x38,0x00,0x01,0x00, +}; +static const uint8_t fs_bytecode_wgpu[980] = { +    0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x08,0x00,0x08,0x00,0x1e,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x06,0x00, +    0x02,0x00,0x00,0x00,0x47,0x4c,0x53,0x4c,0x2e,0x73,0x74,0x64,0x2e,0x34,0x35,0x30, +    0x00,0x00,0x00,0x00,0x0e,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x0f,0x00,0x08,0x00,0x04,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e, +    0x00,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x1b,0x00,0x00,0x00, +    0x10,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x07,0x00,0x03,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x37,0x00,0x02,0x00,0x00,0x00, +    0xc2,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64, +    0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x63,0x6c,0x69, +    0x65,0x6e,0x74,0x20,0x76,0x75,0x6c,0x6b,0x61,0x6e,0x31,0x30,0x30,0x0a,0x2f,0x2f, +    0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73, +    0x65,0x64,0x20,0x63,0x6c,0x69,0x65,0x6e,0x74,0x20,0x6f,0x70,0x65,0x6e,0x67,0x6c, +    0x31,0x30,0x30,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50, +    0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x74,0x61,0x72,0x67,0x65,0x74,0x2d, +    0x65,0x6e,0x76,0x20,0x76,0x75,0x6c,0x6b,0x61,0x6e,0x31,0x2e,0x30,0x0a,0x2f,0x2f, +    0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73, +    0x65,0x64,0x20,0x74,0x61,0x72,0x67,0x65,0x74,0x2d,0x65,0x6e,0x76,0x20,0x6f,0x70, +    0x65,0x6e,0x67,0x6c,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65, +    0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x65,0x6e,0x74,0x72,0x79,0x2d, +    0x70,0x6f,0x69,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x0a,0x23,0x6c,0x69,0x6e,0x65, +    0x20,0x31,0x0a,0x00,0x05,0x00,0x04,0x00,0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e, +    0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x0a,0x00,0x00,0x00,0x66,0x72,0x61,0x67, +    0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x05,0x00,0x03,0x00,0x0f,0x00,0x00,0x00, +    0x74,0x65,0x78,0x00,0x05,0x00,0x03,0x00,0x12,0x00,0x00,0x00,0x75,0x76,0x00,0x00, +    0x05,0x00,0x04,0x00,0x1b,0x00,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0a,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0f,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0f,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0f,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x1b,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x13,0x00,0x02,0x00,0x03,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x04,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x00,0x00, +    0x17,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x04,0x00,0x00,0x00, +    0x20,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x2b,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x80,0x3f, +    0x19,0x00,0x09,0x00,0x0c,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x1b,0x00,0x03,0x00,0x0d,0x00,0x00,0x00,0x0c,0x00,0x00,0x00, +    0x20,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x20,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x17,0x00,0x04,0x00,0x13,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x36,0x00,0x05,0x00,0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x04,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x08,0x00,0x04,0x00, +    0x01,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x0d,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x08,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x4f,0x00,0x07,0x00, +    0x13,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x14,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x57,0x00,0x05,0x00,0x08,0x00,0x00,0x00, +    0x16,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x51,0x00,0x05,0x00, +    0x07,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x50,0x00,0x07,0x00,0x08,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x0b,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x08,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x85,0x00,0x05,0x00, +    0x08,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x1c,0x00,0x00,0x00, +    0x3e,0x00,0x03,0x00,0x0a,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0xfd,0x00,0x01,0x00, +    0x38,0x00,0x01,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sfons_vs_source_dummy = ""; +static const char* _sfons_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +typedef struct _sfons_t { +    sg_shader shd; +    sgl_pipeline pip; +    sg_image img; +    int width, height; +    bool img_dirty; +} _sfons_t; + +static int _sfons_render_create(void* user_ptr, int width, int height) { +    SOKOL_ASSERT(user_ptr && (width > 8) && (height > 8)); +    _sfons_t* sfons = (_sfons_t*) user_ptr; + +    /* sokol-gl compatible shader which treats RED channel as alpha */ +    if (sfons->shd.id == SG_INVALID_ID) { +        sg_shader_desc shd_desc; +        memset(&shd_desc, 0, sizeof(shd_desc)); +        shd_desc.attrs[0].name = "position"; +        shd_desc.attrs[1].name = "texcoord0"; +        shd_desc.attrs[2].name = "color0"; +        shd_desc.attrs[0].sem_name = "TEXCOORD"; +        shd_desc.attrs[0].sem_index = 0; +        shd_desc.attrs[1].sem_name = "TEXCOORD"; +        shd_desc.attrs[1].sem_index = 1; +        shd_desc.attrs[2].sem_name = "TEXCOORD"; +        shd_desc.attrs[2].sem_index = 2; +        sg_shader_uniform_block_desc* ub = &shd_desc.vs.uniform_blocks[0]; +        ub->size = 128; +        ub->uniforms[0].name = "vs_params"; +        ub->uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; +        ub->uniforms[0].array_count = 8; +        shd_desc.fs.images[0].name = "tex"; +        shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; +        shd_desc.fs.images[0].sampler_type = SG_SAMPLERTYPE_FLOAT; +        shd_desc.label = "sokol-fontstash-shader"; +        #if defined(SOKOL_GLCORE33) +            shd_desc.vs.source = _sfons_vs_source_glsl330; +            shd_desc.fs.source = _sfons_fs_source_glsl330; +        #elif defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +            shd_desc.vs.source = _sfons_vs_source_glsl100; +            shd_desc.fs.source = _sfons_fs_source_glsl100; +        #elif defined(SOKOL_METAL) +            shd_desc.vs.entry = "main0"; +            shd_desc.fs.entry = "main0"; +            switch (sg_query_backend()) { +                case SG_BACKEND_METAL_MACOS: +                    shd_desc.vs.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_macos); +                    shd_desc.fs.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_macos); +                    break; +                case SG_BACKEND_METAL_IOS: +                    shd_desc.vs.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_ios); +                    shd_desc.fs.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_ios); +                    break; +                default: +                    shd_desc.vs.source = _sfons_vs_source_metal_sim; +                    shd_desc.fs.source = _sfons_fs_source_metal_sim; +                    break; +            } +        #elif defined(SOKOL_D3D11) +            shd_desc.vs.bytecode = SG_RANGE(_sfons_vs_bytecode_hlsl4); +            shd_desc.fs.bytecode = SG_RANGE(_sfons_fs_bytecode_hlsl4); +        #elif defined(SOKOL_WGPU) +            shd_desc.vs.byte_code = _sfons_vs_bytecode_wgpu; +            shd_desc.vs.byte_code_size = sizeof(_sfons_vs_bytecode_wgpu); +            shd_desc.fs.byte_code = _sfons_fs_bytecode_wgpu; +            shd_desc.fs.byte_code_size = sizeof(_sfons_fs_bytecode_wgpu); +        #else +            shd_desc.vs.source = _sfons_vs_src_dummy; +            shd_desc.fs.source = _sfons_fs_src_dummy; +        #endif +        shd_desc.label = "sfons-shader"; +        sfons->shd = sg_make_shader(&shd_desc); +    } + +    /* sokol-gl pipeline object */ +    if (sfons->pip.id == SG_INVALID_ID) { +        sg_pipeline_desc pip_desc; +        memset(&pip_desc, 0, sizeof(pip_desc)); +        pip_desc.shader = sfons->shd; +        pip_desc.colors[0].blend.enabled = true; +        pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; +        pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; +        sfons->pip = sgl_make_pipeline(&pip_desc); +    } + +    /* create or re-create font atlas texture */ +    if (sfons->img.id != SG_INVALID_ID) { +        sg_destroy_image(sfons->img); +        sfons->img.id = SG_INVALID_ID; +    } +    sfons->width = width; +    sfons->height = height; + +    SOKOL_ASSERT(sfons->img.id == SG_INVALID_ID); +    sg_image_desc img_desc; +    memset(&img_desc, 0, sizeof(img_desc)); +    img_desc.width = sfons->width; +    img_desc.height = sfons->height; +    img_desc.min_filter = SG_FILTER_LINEAR; +    img_desc.mag_filter = SG_FILTER_LINEAR; +    img_desc.usage = SG_USAGE_DYNAMIC; +    img_desc.pixel_format = SG_PIXELFORMAT_R8; +    sfons->img = sg_make_image(&img_desc); +    return 1; +} + +static int _sfons_render_resize(void* user_ptr, int width, int height) { +    return _sfons_render_create(user_ptr, width, height); +} + +static void _sfons_render_update(void* user_ptr, int* rect, const unsigned char* data) { +    SOKOL_ASSERT(user_ptr && rect && data); +    _SOKOL_UNUSED(rect); +    _SOKOL_UNUSED(data); +    _sfons_t* sfons = (_sfons_t*) user_ptr; +    sfons->img_dirty = true; +} + +static void _sfons_render_draw(void* user_ptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) { +    SOKOL_ASSERT(user_ptr && verts && tcoords && colors && (nverts > 0)); +    _sfons_t* sfons = (_sfons_t*) user_ptr; +    sgl_enable_texture(); +    sgl_texture(sfons->img); +    sgl_push_pipeline(); +    sgl_load_pipeline(sfons->pip); +    sgl_begin_triangles(); +    for (int i = 0; i < nverts; i++) { +        sgl_v2f_t2f_c1i(verts[2*i+0], verts[2*i+1], tcoords[2*i+0], tcoords[2*i+1], colors[i]); +    } +    sgl_end(); +    sgl_pop_pipeline(); +    sgl_disable_texture(); +} + +static void _sfons_render_delete(void* user_ptr) { +    SOKOL_ASSERT(user_ptr); +    _sfons_t* sfons = (_sfons_t*) user_ptr; +    if (sfons->img.id != SG_INVALID_ID) { +        sg_destroy_image(sfons->img); +        sfons->img.id = SG_INVALID_ID; +    } +    if (sfons->pip.id != SG_INVALID_ID) { +        sgl_destroy_pipeline(sfons->pip); +        sfons->pip.id = SG_INVALID_ID; +    } +    if (sfons->shd.id != SG_INVALID_ID) { +        sg_destroy_shader(sfons->shd); +        sfons->shd.id = SG_INVALID_ID; +    } +    SOKOL_FREE(sfons); +} + +// NOTE clang analyzer will report a potential memory leak for the call +// to SOKOL_MALLOC in the sfons_create() function, this is a false positive +// (the freeing happens in _sfons_render_delete()). The following macro +// silences the false positive when compilation happens with the analyzer active +#if __clang_analyzer__ +#define _SFONS_CLANG_ANALYZER_SILENCE_POTENTIAL_LEAK_FALSE_POSITIVE(x) SOKOL_FREE(x) +#else +#define _SFONS_CLANG_ANALYZER_SILENCE_POTENTIAL_LEAK_FALSE_POSITIVE(x) +#endif + +SOKOL_API_IMPL FONScontext* sfons_create(int width, int height, int flags) { +    SOKOL_ASSERT((width > 0) && (height > 0)); +    FONSparams params; +    _sfons_t* sfons = (_sfons_t*) SOKOL_MALLOC(sizeof(_sfons_t)); +    memset(sfons, 0, sizeof(_sfons_t)); +    memset(¶ms, 0, sizeof(params)); +    params.width = width; +    params.height = height; +    params.flags = (unsigned char) flags; +    params.renderCreate = _sfons_render_create; +    params.renderResize = _sfons_render_resize; +    params.renderUpdate = _sfons_render_update; +    params.renderDraw = _sfons_render_draw; +    params.renderDelete = _sfons_render_delete; +    params.userPtr = sfons; +    FONScontext* ctx = fonsCreateInternal(¶ms); +    _SFONS_CLANG_ANALYZER_SILENCE_POTENTIAL_LEAK_FALSE_POSITIVE(sfons); +    return ctx; +} + +SOKOL_API_IMPL void sfons_destroy(FONScontext* ctx) { +    SOKOL_ASSERT(ctx); +    fonsDeleteInternal(ctx); +} + +SOKOL_API_IMPL void sfons_flush(FONScontext* ctx) { +    SOKOL_ASSERT(ctx && ctx->params.userPtr); +    _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; +    if (sfons->img_dirty) { +        sfons->img_dirty = false; +        sg_image_data data; +        memset(&data, 0, sizeof(data)); +        data.subimage[0][0].ptr = ctx->texData; +        data.subimage[0][0].size = (size_t) (sfons->width * sfons->height); +        sg_update_image(sfons->img, &data); +    } +} + +SOKOL_API_IMPL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    return ((uint32_t)r) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); +} + +#endif /* SOKOL_FONTSTASH_IMPL */ diff --git a/v_windows/v/old/thirdparty/sokol/util/sokol_gl.h b/v_windows/v/old/thirdparty/sokol/util/sokol_gl.h new file mode 100644 index 0000000..e41fc06 --- /dev/null +++ b/v_windows/v/old/thirdparty/sokol/util/sokol_gl.h @@ -0,0 +1,3297 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GL_IMPL) +#define SOKOL_GL_IMPL +#endif +#ifndef SOKOL_GL_INCLUDED +/* +    sokol_gl.h -- OpenGL 1.x style rendering on top of sokol_gfx.h + +    Project URL: https://github.com/floooh/sokol + +    Do this: +        #define SOKOL_IMPL or +        #define SOKOL_GL_IMPL +    before you include this file in *one* C or C++ file to create the +    implementation. + +    The following defines are used by the implementation to select the +    platform-specific embedded shader code (these are the same defines as +    used by sokol_gfx.h and sokol_app.h): + +    SOKOL_GLCORE33 +    SOKOL_GLES2 +    SOKOL_GLES3 +    SOKOL_D3D11 +    SOKOL_METAL +    SOKOL_WGPU + +    ...optionally provide the following macros 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_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 +    including the declaration or implementation: + +    SOKOL_DLL + +    On Windows, SOKOL_DLL will define SOKOL_GL_API_DECL as __declspec(dllexport) +    or __declspec(dllimport) as needed. + +    Include the following headers before including sokol_gl.h: + +        sokol_gfx.h + +    Matrix functions have been taken from MESA and Regal. + +    FEATURE OVERVIEW: +    ================= +    sokol_gl.h implements a subset of the OpenGLES 1.x feature set useful for +    when you just want to quickly render a bunch of colored triangles or +    lines without having to mess with buffers and +    shaders. + +    The current feature set is mostly useful for debug visualizations +    and simple UI-style 2D rendering: + +    What's implemented: +        - vertex components: +            - position (x, y, z) +            - 2D texture coords (u, v) +            - color (r, g, b, a) +        - primitive types: +            - triangle list and strip +            - line list and strip +            - quad list (TODO: quad strips) +            - point list (TODO: point size) +        - one texture layer (no multi-texturing) +        - viewport and scissor-rect with selectable origin (top-left or bottom-left) +        - all GL 1.x matrix stack functions, and additionally equivalent +          functions for gluPerspective and gluLookat + +    Notable GLES 1.x features that are *NOT* implemented: +        - vertex lighting (this is the most likely GL feature that might be added later) +        - vertex arrays (although providing whole chunks of vertex data at once +          might be a useful feature for a later version) +        - texture coordinate generation +        - point size and line width +        - all pixel store functions +        - no ALPHA_TEST +        - no clear functions (clearing is handled by the sokol-gfx render pass) +        - fog + +    Notable differences to GL: +        - No "enum soup" for render states etc, instead there's a +          'pipeline stack', this is similar to GL's matrix stack, +          but for pipeline-state-objects. The pipeline object at +          the top of the pipeline stack defines the active set of render states +        - All angles are in radians, not degrees (note the sgl_rad() and +          sgl_deg() conversion functions) +        - No enable/disable state for scissor test, this is always enabled + +    STEP BY STEP: +    ============= +    --- To initialize sokol-gl, call: + +            sgl_setup(const sgl_desc_t* desc) + +        NOTE that sgl_setup() must be called *after* initializing sokol-gfx +        (via sg_setup). This is because sgl_setup() needs to create +        sokol-gfx resource objects. + +        sgl_setup() needs to know the attributes of the sokol-gfx render pass +        where sokol-gl rendering will happen through the passed-in sgl_desc_t +        struct: + +            sg_pixel_format color_format    - color pixel format of render pass +            sg_pixel_format depth_format    - depth pixel format of render pass +            int sample_count                - MSAA sample count of render pass + +        These values have the same defaults as sokol_gfx.h and sokol_app.h, +        to use the default values, leave them zero-initialized. + +        You can adjust the maximum number of vertices and drawing commands +        per frame through the members: + +            int max_vertices    - default is 65536 +            int max_commands    - default is 16384 + +        You can adjust the size of the internal pipeline state object pool +        with: + +            int pipeline_pool_size  - default is 64 + +        Finally you can change the face winding for front-facing triangles +        and quads: + +            sg_face_winding face_winding    - default is SG_FACEWINDING_CCW + +        The default winding for front faces is counter-clock-wise. This is +        the same as OpenGL's default, but different from sokol-gfx. + +    --- Optionally create pipeline-state-objects if you need render state +        that differs from sokol-gl's default state: + +            sgl_pipeline pip = sgl_make_pipeline(const sg_pipeline_desc* desc) + +        The similarity with sokol_gfx.h's sg_pipeline type and sg_make_pipeline() +        function is intended. sgl_make_pipeline() also takes a standard +        sokol-gfx sg_pipeline_desc object to describe the render state, but +        without: +            - shader +            - vertex layout +            - color- and depth-pixel-formats +            - primitive type (lines, triangles, ...) +            - MSAA sample count +        Those will be filled in by sgl_make_pipeline(). Note that each +        call to sgl_make_pipeline() needs to create several sokol-gfx +        pipeline objects (one for each primitive type). + +    --- if you need to destroy sgl_pipeline objects before sgl_shutdown(): + +            sgl_destroy_pipeline(sgl_pipeline pip) + +    --- After sgl_setup() you can call any of the sokol-gl functions anywhere +        in a frame, *except* sgl_draw(). The 'vanilla' functions +        will only change internal sokol-gl state, and not call any sokol-gfx +        functions. + +    --- Unlike OpenGL, sokol-gl has a function to reset internal state to +        a known default. This is useful at the start of a sequence of +        rendering operations: + +            void sgl_defaults(void) + +        This will set the following default state: + +            - current texture coordinate to u=0.0f, v=0.0f +            - current color to white (rgba all 1.0f) +            - unbind the current texture and texturing will be disabled +            - *all* matrices will be set to identity (also the projection matrix) +            - the default render state will be set by loading the 'default pipeline' +              into the top of the pipeline stack + +        The current matrix- and pipeline-stack-depths will not be changed by +        sgl_defaults(). + +    --- change the currently active renderstate through the +        pipeline-stack functions, this works similar to the +        traditional GL matrix stack: + +            ...load the default pipeline state on the top of the pipeline stack: + +                sgl_default_pipeline() + +            ...load a specific pipeline on the top of the pipeline stack: + +                sgl_load_pipeline(sgl_pipeline pip) + +            ...push and pop the pipeline stack: +                sgl_push_pipeline() +                sgl_pop_pipeline() + +    --- control texturing with: + +            sgl_enable_texture() +            sgl_disable_texture() +            sgl_texture(sg_image img) + +    --- set the current viewport and scissor rect with: + +            sgl_viewport(int x, int y, int w, int h, bool origin_top_left) +            sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) + +        ...or call these alternatives which take float arguments (this might allow +        to avoid casting between float and integer in more strongly typed languages +        when floating point pixel coordinates are used): + +            sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) +            sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) + +        ...these calls add a new command to the internal command queue, so +        that the viewport or scissor rect are set at the right time relative +        to other sokol-gl calls. + +    --- adjust the transform matrices, matrix manipulation works just like +        the OpenGL matrix stack: + +        ...set the current matrix mode: + +            sgl_matrix_mode_modelview() +            sgl_matrix_mode_projection() +            sgl_matrix_mode_texture() + +        ...load the identity matrix into the current matrix: + +            sgl_load_identity() + +        ...translate, rotate and scale the current matrix: + +            sgl_translate(float x, float y, float z) +            sgl_rotate(float angle_rad, float x, float y, float z) +            sgl_scale(float x, float y, float z) + +        NOTE that all angles in sokol-gl are in radians, not in degree. +        Convert between radians and degree with the helper functions: + +            float sgl_rad(float deg)        - degrees to radians +            float sgl_deg(float rad)        - radians to degrees + +        ...directly load the current matrix from a float[16] array: + +            sgl_load_matrix(const float m[16]) +            sgl_load_transpose_matrix(const float m[16]) + +        ...directly multiply the current matrix from a float[16] array: + +            sgl_mult_matrix(const float m[16]) +            sgl_mult_transpose_matrix(const float m[16]) + +        The memory layout of those float[16] arrays is the same as in OpenGL. + +        ...more matrix functions: + +            sgl_frustum(float left, float right, float bottom, float top, float near, float far) +            sgl_ortho(float left, float right, float bottom, float top, float near, float far) +            sgl_perspective(float fov_y, float aspect, float near, float far) +            sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) + +        These functions work the same as glFrustum(), glOrtho(), gluPerspective() +        and gluLookAt(). + +        ...and finally to push / pop the current matrix stack: + +            sgl_push_matrix(void) +            sgl_pop_matrix(void) + +        Again, these work the same as glPushMatrix() and glPopMatrix(). + +    --- perform primitive rendering: + +        ...set the current texture coordinate and color 'registers' with: + +            sgl_t2f(float u, float v)   - set current texture coordinate +            sgl_c*(...)                 - set current color + +        There are several functions for setting the color (as float values, +        unsigned byte values, packed as unsigned 32-bit integer, with +        and without alpha). + +        NOTE that these are the only functions that can be called both inside +        sgl_begin_*() / sgl_end() and outside. + +        ...start a primitive vertex sequence with: + +            sgl_begin_points() +            sgl_begin_lines() +            sgl_begin_line_strip() +            sgl_begin_triangles() +            sgl_begin_triangle_strip() +            sgl_begin_quads() + +        ...after sgl_begin_*() specify vertices: + +            sgl_v*(...) +            sgl_v*_t*(...) +            sgl_v*_c*(...) +            sgl_v*_t*_c*(...) + +        These functions write a new vertex to sokol-gl's internal vertex buffer, +        optionally with texture-coords and color. If the texture coordinate +        and/or color is missing, it will be taken from the current texture-coord +        and color 'register'. + +        ...finally, after specifying vertices, call: + +            sgl_end() + +        This will record a new draw command in sokol-gl's internal command +        list, or it will extend the previous draw command if no relevant +        state has changed since the last sgl_begin/end pair. + +    --- inside a sokol-gfx rendering pass, call: + +            sgl_draw() + +        This will render everything that has been recorded since the last +        call to sgl_draw() through sokol-gfx, and will 'rewind' the internal +        vertex-, uniform- and command-buffers. + +    --- sokol-gl tracks a single internal error code which can be +        queried with + +            sgl_error_t sgl_error(void) + +        ...which can return the following error codes: + +        SGL_NO_ERROR                - all OK, no error occurred since last sgl_draw() +        SGL_ERROR_VERTICES_FULL     - internal vertex buffer is full (checked in sgl_end()) +        SGL_ERROR_UNIFORMS_FULL     - the internal uniforms buffer is full (checked in sgl_end()) +        SGL_ERROR_COMMANDS_FULL     - the internal command buffer is full (checked in sgl_end()) +        SGL_ERROR_STACK_OVERFLOW    - matrix- or pipeline-stack overflow +        SGL_ERROR_STACK_UNDERFLOW   - matrix- or pipeline-stack underflow + +        ...if sokol-gl is in an error-state, sgl_draw() will skip any rendering, +        and reset the error code to SGL_NO_ERROR. + +    UNDER THE HOOD: +    =============== +    sokol_gl.h works by recording vertex data and rendering commands into +    memory buffers, and then drawing the recorded commands via sokol_gfx.h + +    The only functions which call into sokol_gfx.h are: +        - sgl_setup() +        - sgl_shutdown() +        - sgl_draw() + +    sgl_setup() must be called after initializing sokol-gfx. +    sgl_shutdown() must be called before shutting down sokol-gfx. +    sgl_draw() must be called once per frame inside a sokol-gfx render pass. + +    All other sokol-gl function can be called anywhere in a frame, since +    they just record data into memory buffers owned by sokol-gl. + +    What happens in: + +        sgl_setup(): +            - 3 memory buffers are allocated, one for vertex data, +              one for uniform data, and one for commands +            - sokol-gfx resources are created: a (dynamic) vertex buffer, +              a shader object (using embedded shader source or byte code), +              and an 8x8 all-white default texture + +            One vertex is 24 bytes: +                - float3 position +                - float2 texture coords +                - uint32_t color + +            One uniform block is 128 bytes: +                - mat4 model-view-projection matrix +                - mat4 texture matrix + +            One draw command is ca. 24 bytes for the actual +            command code plus command arguments. + +            Each sgl_end() consumes one command, and one uniform block +            (only when the matrices have changed). +            The required size for one sgl_begin/end pair is (at most): + +                (152 + 24 * num_verts) bytes + +        sgl_shutdown(): +            - all sokol-gfx resources (buffer, shader, default-texture and +              all pipeline objects) are destroyed +            - the 3 memory buffers are freed + +        sgl_draw(): +            - copy all recorded vertex data into the dynamic sokol-gfx buffer +              via a call to sg_update_buffer() +            - for each recorded command: +                - if it's a viewport command, call sg_apply_viewport() +                - if it's a scissor-rect command, call sg_apply_scissor_rect() +                - if it's a draw command: +                    - depending on what has changed since the last draw command, +                      call sg_apply_pipeline(), sg_apply_bindings() and +                      sg_apply_uniforms() +                    - finally call sg_draw() + +    All other functions only modify the internally tracked state, add +    data to the vertex, uniform and command buffers, or manipulate +    the matrix stack. + +    ON DRAW COMMAND MERGING +    ======================= +    Not every call to sgl_end() will automatically record a new draw command. +    If possible, the previous draw command will simply be extended, +    resulting in fewer actual draw calls later in sgl_draw(). + +    A draw command will be merged with the previous command if "no relevant +    state has changed" since the last sgl_end(), meaning: + +    - no calls to sgl_apply_viewport() and sgl_apply_scissor_rect() +    - the primitive type hasn't changed +    - the primitive type isn't a 'strip type' (no line or triangle strip) +    - the pipeline state object hasn't changed +    - none of the matrices has changed +    - none of the texture state has changed + +    Merging a draw command simply means that the number of vertices +    to render in the previous draw command will be incremented by the +    number of vertices in the new draw command. + +    LICENSE +    ======= +    zlib/libpng license + +    Copyright (c) 2018 Andre Weissflog + +    This software is provided 'as-is', without any express or implied warranty. +    In no event will the authors be held liable for any damages arising from the +    use of this software. + +    Permission is granted to anyone to use this software for any purpose, +    including commercial applications, and to alter it and redistribute it +    freely, subject to the following restrictions: + +        1. The origin of this software must not be misrepresented; you must not +        claim that you wrote the original software. If you use this software in a +        product, an acknowledgment in the product documentation would be +        appreciated but is not required. + +        2. Altered source versions must be plainly marked as such, and must not +        be misrepresented as being the original software. + +        3. This notice may not be removed or altered from any source +        distribution. +*/ +#define SOKOL_GL_INCLUDED (1) +#include <stdint.h> +#include <stdbool.h> + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_gl.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GL_API_DECL) +#define SOKOL_GL_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GL_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GL_IMPL) +#define SOKOL_GL_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GL_API_DECL __declspec(dllimport) +#else +#define SOKOL_GL_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* sokol_gl pipeline handle (created with sgl_make_pipeline()) */ +typedef struct sgl_pipeline { uint32_t id; } sgl_pipeline; + +/* +    sgl_error_t + +    Errors are reset each frame after calling sgl_draw(), +    get the last error code with sgl_error() +*/ +typedef enum sgl_error_t { +    SGL_NO_ERROR = 0, +    SGL_ERROR_VERTICES_FULL, +    SGL_ERROR_UNIFORMS_FULL, +    SGL_ERROR_COMMANDS_FULL, +    SGL_ERROR_STACK_OVERFLOW, +    SGL_ERROR_STACK_UNDERFLOW, +} sgl_error_t; + +typedef struct sgl_desc_t { +    int max_vertices;       /* size for vertex buffer */ +    int max_commands;       /* size of uniform- and command-buffers */ +    int pipeline_pool_size; /* size of the internal pipeline pool, default is 64 */ +    sg_pixel_format color_format; +    sg_pixel_format depth_format; +    int sample_count; +    sg_face_winding face_winding; /* default front face winding is CCW */ +} sgl_desc_t; + +/* setup/shutdown/misc */ +SOKOL_GL_API_DECL void sgl_setup(const sgl_desc_t* desc); +SOKOL_GL_API_DECL void sgl_shutdown(void); +SOKOL_GL_API_DECL sgl_error_t sgl_error(void); +SOKOL_GL_API_DECL void sgl_defaults(void); +SOKOL_GL_API_DECL float sgl_rad(float deg); +SOKOL_GL_API_DECL float sgl_deg(float rad); + +/* create and destroy pipeline objects */ +SOKOL_GL_API_DECL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GL_API_DECL void sgl_destroy_pipeline(sgl_pipeline pip); + +/* render state functions */ +SOKOL_GL_API_DECL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_enable_texture(void); +SOKOL_GL_API_DECL void sgl_disable_texture(void); +SOKOL_GL_API_DECL void sgl_texture(sg_image img); + +/* pipeline stack functions */ +SOKOL_GL_API_DECL void sgl_default_pipeline(void); +SOKOL_GL_API_DECL void sgl_load_pipeline(sgl_pipeline pip); +SOKOL_GL_API_DECL void sgl_push_pipeline(void); +SOKOL_GL_API_DECL void sgl_pop_pipeline(void); + +/* matrix stack functions */ +SOKOL_GL_API_DECL void sgl_matrix_mode_modelview(void); +SOKOL_GL_API_DECL void sgl_matrix_mode_projection(void); +SOKOL_GL_API_DECL void sgl_matrix_mode_texture(void); +SOKOL_GL_API_DECL void sgl_load_identity(void); +SOKOL_GL_API_DECL void sgl_load_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_load_transpose_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_mult_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_mult_transpose_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_rotate(float angle_rad, float x, float y, float z); +SOKOL_GL_API_DECL void sgl_scale(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_translate(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_frustum(float l, float r, float b, float t, float n, float f); +SOKOL_GL_API_DECL void sgl_ortho(float l, float r, float b, float t, float n, float f); +SOKOL_GL_API_DECL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far); +SOKOL_GL_API_DECL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z); +SOKOL_GL_API_DECL void sgl_push_matrix(void); +SOKOL_GL_API_DECL void sgl_pop_matrix(void); + +/* these functions only set the internal 'current texcoord / color' (valid inside or outside begin/end) */ +SOKOL_GL_API_DECL void sgl_t2f(float u, float v); +SOKOL_GL_API_DECL void sgl_c3f(float r, float g, float b); +SOKOL_GL_API_DECL void sgl_c4f(float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_c1i(uint32_t rgba); + +/* define primitives, each begin/end is one draw command */ +SOKOL_GL_API_DECL void sgl_begin_points(void); +SOKOL_GL_API_DECL void sgl_begin_lines(void); +SOKOL_GL_API_DECL void sgl_begin_line_strip(void); +SOKOL_GL_API_DECL void sgl_begin_triangles(void); +SOKOL_GL_API_DECL void sgl_begin_triangle_strip(void); +SOKOL_GL_API_DECL void sgl_begin_quads(void); +SOKOL_GL_API_DECL void sgl_v2f(float x, float y); +SOKOL_GL_API_DECL void sgl_v3f(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_v2f_t2f(float x, float y, float u, float v); +SOKOL_GL_API_DECL void sgl_v3f_t2f(float x, float y, float z, float u, float v); +SOKOL_GL_API_DECL void sgl_v2f_c3f(float x, float y, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v2f_c1i(float x, float y, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_end(void); + +/* render everything */ +SOKOL_GL_API_DECL void sgl_draw(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sgl_setup(const sgl_desc_t& desc) { return sgl_setup(&desc); } +inline sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc& desc) { return sgl_make_pipeline(&desc); } +#endif +#endif /* SOKOL_GL_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_GL_IMPL +#define SOKOL_GL_IMPL_INCLUDED (1) + +#include <stddef.h> /* offsetof */ +#include <string.h> /* memset */ +#include <math.h> /* M_PI, sqrtf, sinf, cosf */ + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + +#ifndef SOKOL_API_IMPL +    #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG +    #ifndef NDEBUG +        #define SOKOL_DEBUG (1) +    #endif +#endif +#ifndef SOKOL_ASSERT +    #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_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 + +#define _sgl_def(val, def) (((val) == 0) ? (def) : (val)) +#define _SGL_INIT_COOKIE (0xABCDABCD) + +/* +    Embedded source code compiled with: + +    sokol-shdc -i sgl.glsl -o sgl.h -l glsl330:glsl100:hlsl4:metal_macos:metal_ios:metal_sim:wgpu -b + +    (not that for Metal and D3D11 byte code, sokol-shdc must be run +    on macOS and Windows) + +    @vs vs +    uniform vs_params { +        mat4 mvp; +        mat4 tm; +    }; +    in vec4 position; +    in vec2 texcoord0; +    in vec4 color0; +    out vec4 uv; +    out vec4 color; +    void main() { +        gl_Position = mvp * position; +        uv = tm * vec4(texcoord0, 0.0, 1.0); +        color = color0; +    } +    @end + +    @fs fs +    uniform sampler2D tex; +    in vec4 uv; +    in vec4 color; +    out vec4 frag_color; +    void main() { +        frag_color = texture(tex, uv.xy) * color; +    } +    @end + +    @program sgl vs fs +*/ + +#if defined(SOKOL_GLCORE33) +static const char _sgl_vs_source_glsl330[415] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, +    0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, +    0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, +    0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, +    0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, +    0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79, +    0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31, +    0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f, +    0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f, +    0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, +    0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, +    0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20, +    0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f, +    0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28, +    0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, +    0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, +    0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74, +    0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20, +    0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34, +    0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c, +    0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72, +    0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sgl_fs_source_glsl330[172] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, +    0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, +    0x74,0x65,0x78,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63, +    0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76, +    0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, +    0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x69,0x6e,0x20,0x76, +    0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64, +    0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72, +    0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75, +    0x72,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29,0x20,0x2a, +    0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +static const char _sgl_vs_source_glsl100[381] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x30,0x30,0x0a,0x0a,0x75,0x6e, +    0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, +    0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x61,0x74,0x74,0x72,0x69,0x62,0x75, +    0x74,0x65,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, +    0x3b,0x0a,0x76,0x61,0x72,0x79,0x69,0x6e,0x67,0x20,0x76,0x65,0x63,0x34,0x20,0x75, +    0x76,0x3b,0x0a,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x20,0x76,0x65,0x63, +    0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x76,0x61,0x72, +    0x79,0x69,0x6e,0x67,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b, +    0x0a,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x20,0x76,0x65,0x63,0x34,0x20, +    0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, +    0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f, +    0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, +    0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, +    0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, +    0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28, +    0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20,0x76,0x73, +    0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, +    0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x74, +    0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, +    0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, +    0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sgl_fs_source_glsl100[210] = { +    0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x31,0x30,0x30,0x0a,0x70,0x72,0x65, +    0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d,0x70,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20, +    0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75,0x6e,0x69,0x66, +    0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65, +    0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x79,0x69,0x6e, +    0x67,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b, +    0x0a,0x76,0x61,0x72,0x79,0x69,0x6e,0x67,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76, +    0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64, +    0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c, +    0x5f,0x46,0x72,0x61,0x67,0x44,0x61,0x74,0x61,0x5b,0x30,0x5d,0x20,0x3d,0x20,0x74, +    0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x28,0x74,0x65,0x78,0x2c,0x20,0x75,0x76, +    0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a, +    0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +static const uint8_t _sgl_vs_bytecode_metal_macos[3360] = { +    0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x20,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x10,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x54,0xec,0xec,0xa8,0x23,0x83,0x8d, +    0xc7,0xcf,0x6e,0x67,0x94,0x95,0x54,0x94,0xeb,0x98,0x15,0x2a,0x2c,0x54,0xa0,0x4b, +    0x24,0x6f,0x5e,0xed,0x6e,0x76,0x45,0xb7,0x5e,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x37,0x00,0x00, +    0x00,0x56,0x41,0x54,0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, +    0x6e,0x00,0x00,0x80,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80, +    0x63,0x6f,0x6c,0x6f,0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03, +    0x00,0x06,0x04,0x06,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54, +    0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xf0,0x0b,0x00,0x00, +    0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xf9,0x02,0x00,0x00, +    0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91, +    0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19, +    0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14, +    0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80, +    0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0, +    0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x81,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8, +    0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87, +    0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72, +    0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88, +    0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41, +    0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c, +    0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87, +    0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72, +    0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,0x1e, +    0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4,0x21,0x1c,0xda,0xa1,0x1c,0xda, +    0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41,0x1e,0xda,0xa0,0x1c,0xd8,0x21, +    0x1d,0xda,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06,0x77,0x78,0x87,0x36,0x30, +    0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87, +    0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x68,0x03,0x77,0x78,0x07,0x77, +    0x68,0x03,0x76,0x28,0x87,0x70,0x30,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x74,0x70, +    0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x40,0x1d,0xea,0xa1, +    0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08, +    0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,0x87, +    0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, +    0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe6,0x21,0x1d,0xce,0xc1,0x1d, +    0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,0x61,0x1e,0xda,0xc0,0x1c,0xe0, +    0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80, +    0x70,0x87,0x77,0x68,0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38, +    0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00, +    0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1, +    0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c, +    0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87, +    0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a, +    0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d, +    0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00, +    0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x18,0x02,0x01,0x2c,0x40,0x05,0x00, +    0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00, +    0x23,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4,0x84, +    0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4,0x4c, +    0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45,0x94,0x30,0xf9,0x0c,0x80, +    0x34,0xf4,0xef,0x50,0x13,0x1a,0x42,0x08,0xc3,0x08,0x02,0x90,0x04,0x61,0x26,0x6a, +    0x1e,0xe8,0x41,0x1e,0xea,0x61,0x1c,0xe8,0xc1,0x0d,0xda,0xa1,0x1c,0xe8,0x21,0x1c, +    0xd8,0x41,0x0f,0xf4,0xa0,0x1d,0xc2,0x81,0x1e,0xe4,0x21,0x1d,0xf0,0x01,0x05,0xe4, +    0x20,0x69,0x8a,0x28,0x61,0xf2,0x2b,0xe9,0x7f,0x80,0x08,0x60,0x24,0x24,0x94,0x32, +    0x88,0x60,0x08,0xa5,0x10,0x61,0x84,0x43,0x68,0x20,0x60,0x8e,0x00,0x0c,0x72,0x60, +    0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb,0x08,0x00,0x00,0x00,0x00, +    0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60, +    0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03, +    0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07, +    0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71, +    0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80, +    0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, +    0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, +    0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60, +    0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e, +    0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71, +    0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40, +    0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07, +    0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a, +    0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60, +    0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07, +    0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78, +    0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10, +    0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07, +    0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72, +    0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20, +    0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07, +    0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d, +    0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x49,0x00, +    0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8,0x02,0x01,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a, +    0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50,0x80,0x02,0x85,0x50,0x10,0x65,0x50,0x20, +    0xd4,0x46,0x00,0x88,0x8d,0x35,0x28,0x0f,0x01,0x00,0x00,0x00,0x79,0x18,0x00,0x00, +    0x19,0x01,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9, +    0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93, +    0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x06,0xd9,0x20,0x08,0x0e, +    0x8e,0xad,0x0c,0x84,0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d, +    0x24,0x07,0x46,0xc6,0x25,0x86,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac, +    0x25,0x07,0x46,0xc6,0x25,0x86,0xc6,0x25,0x27,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25, +    0x58,0x8e,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82, +    0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56, +    0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50, +    0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61, +    0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04, +    0x65,0x61,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98, +    0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1, +    0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x61,0x10,0x96,0x26, +    0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6, +    0x45,0xf6,0x56,0x27,0xc6,0x56,0xf6,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44, +    0x50,0x1e,0x92,0x41,0x58,0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b, +    0x5b,0x18,0x5d,0x9a,0x5d,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9, +    0x17,0x59,0xda,0x5c,0x98,0x18,0x5b,0xd9,0x10,0x41,0x89,0x78,0x06,0x61,0x69,0x72, +    0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f, +    0x77,0x69,0x64,0x65,0x5f,0x76,0x65,0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73, +    0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x62,0x14,0x96,0x26,0xe7,0x62,0x57,0x26,0x47, +    0x57,0x86,0xf7,0xf5,0x56,0x47,0x07,0x57,0x47,0xc7,0xa5,0x6e,0xae,0x4c,0x0e,0x85, +    0xed,0x6d,0xcc,0x0d,0x26,0x85,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d, +    0x1e,0x5c,0xd9,0x97,0x5b,0x58,0x5b,0x19,0x0d,0x33,0xb6,0xb7,0x30,0x3a,0x1a,0x32, +    0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x6e,0x61,0x6d,0x65,0x14,0xea,0xec,0x86, +    0x30,0x4a,0xa5,0x58,0xca,0xa5,0x60,0x4a,0xa6,0x68,0x5c,0xea,0xe6,0xca,0xe4,0x50, +    0xd8,0xde,0xc6,0xdc,0x62,0x52,0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x94,0x4a, +    0xe1,0x94,0x4b,0xc1,0x94,0x4c,0xe9,0xc8,0x84,0xa5,0xc9,0xb9,0xc0,0xbd,0xcd,0xa5, +    0xd1,0xa5,0xbd,0xb9,0x71,0x39,0x63,0xfb,0x82,0x7a,0x9b,0x4b,0xa3,0x4b,0x7b,0x73, +    0x1b,0xa2,0x28,0x9f,0x72,0x29,0x98,0x92,0x29,0x60,0x30,0xc4,0x50,0x36,0xc5,0x53, +    0xc2,0x80,0x50,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b, +    0x5c,0x1d,0x1d,0xa5,0xb0,0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7, +    0xaf,0x34,0x37,0xb2,0x32,0x3c,0x66,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64, +    0x28,0x38,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x44,0x76,0x34,0x5f,0x66,0x29, +    0x44,0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x8c,0x81,0x42, +    0x06,0x8b,0xb0,0x04,0x4a,0x19,0x28,0x97,0x82,0x29,0x99,0x62,0x06,0xd4,0xce,0xca, +    0xdc,0xca,0xe4,0xc2,0xe8,0xca,0xc8,0x50,0x72,0xe8,0xca,0xf0,0xc6,0xde,0xde,0xe4, +    0xc8,0x60,0x88,0xec,0x64,0xbe,0xcc,0x52,0x68,0x98,0xb1,0xbd,0x85,0xd1,0xc9,0x30, +    0xa1,0x2b,0xc3,0x1b,0x7b,0x7b,0x93,0x23,0x83,0x19,0x42,0x2d,0x81,0x32,0x06,0x0a, +    0x19,0x2c,0xc1,0x12,0x28,0x68,0xa0,0x5c,0x4a,0x1a,0x28,0x99,0xa2,0x06,0xbc,0xce, +    0xca,0xdc,0xca,0xe4,0xc2,0xe8,0xca,0xc8,0x50,0x6c,0xc6,0xde,0xd8,0xde,0xe4,0x60, +    0x88,0xec,0x68,0xbe,0xcc,0x52,0x68,0x8c,0xbd,0xb1,0xbd,0xc9,0xc1,0x0c,0xa1,0x96, +    0x41,0x19,0x03,0x85,0x0c,0x96,0x61,0x09,0x14,0x36,0x50,0x2e,0x05,0x53,0x32,0xa5, +    0x0d,0xa8,0x84,0xa5,0xc9,0xb9,0x88,0xd5,0x99,0x99,0x95,0xc9,0xf1,0x09,0x4b,0x93, +    0x73,0x11,0xab,0x33,0x33,0x2b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x23,0x12,0x96,0x26, +    0xe7,0x22,0x57,0x16,0x46,0x46,0x2a,0x2c,0x4d,0xce,0x65,0x8e,0x4e,0xae,0x6e,0x8c, +    0xee,0x8b,0x2e,0x0f,0xae,0xec,0x2b,0xcd,0xcd,0xec,0x8d,0x88,0x19,0xdb,0x5b,0x18, +    0x1d,0x0d,0x1e,0x0d,0x87,0x36,0x3b,0x38,0x0a,0x74,0x6d,0x43,0xa8,0x45,0x58,0x88, +    0x45,0x50,0xe6,0x40,0xa1,0x83,0x85,0x58,0x88,0x45,0x50,0xe6,0x40,0xa9,0x03,0x46, +    0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x73,0x69,0x7a, +    0x65,0xbc,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xc2, +    0xd8,0xd2,0xce,0xdc,0xbe,0xe6,0xd2,0xf4,0xca,0x98,0xd8,0xcd,0x7d,0xc1,0x85,0xc9, +    0x85,0xb5,0xcd,0x71,0xf8,0x92,0x81,0x19,0x42,0x06,0x0b,0xa2,0xbc,0x81,0x02,0x07, +    0x4b,0xa1,0x90,0xc1,0x22,0x2c,0x81,0x12,0x07,0x8a,0x1c,0x28,0x76,0xa0,0xdc,0xc1, +    0x52,0x28,0x78,0xb0,0x24,0xca,0xa5,0xe4,0x81,0x92,0x29,0x7a,0x30,0x04,0x51,0xce, +    0x40,0x59,0x03,0xc5,0x0d,0x94,0x3d,0x18,0x62,0x24,0x80,0x22,0x06,0x0a,0x1f,0xf0, +    0x79,0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3, +    0x33,0x95,0xd6,0x06,0xc7,0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34, +    0x44,0x50,0xfe,0x60,0x88,0xa1,0xf8,0x81,0x02,0x0a,0x8d,0x32,0xc4,0x50,0x42,0x41, +    0x09,0x85,0x46,0x19,0x11,0xb1,0x03,0x3b,0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03, +    0x39,0xd4,0x03,0x3b,0x94,0x83,0x1b,0x98,0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14, +    0x21,0x18,0x46,0x28,0xec,0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e, +    0xee,0x40,0x0f,0x53,0x82,0x62,0xc4,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5, +    0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e,0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20, +    0x0f,0x6e,0xc0,0x0e,0xe1,0xe0,0x0e,0xe7,0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b, +    0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1, +    0x90,0x0e,0xf2,0xe0,0x06,0xe3,0xf0,0x0e,0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0, +    0x0b,0xef,0x00,0x0f,0xf4,0x90,0x0e,0xef,0xe0,0x0e,0xf3,0x30,0xc5,0x50,0x18,0x07, +    0x92,0xa8,0x11,0x4a,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x3d,0x94,0x83,0x3c,0xd0,0x43, +    0x39,0xe0,0xc3,0x94,0xa0,0x0f,0x00,0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3, +    0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10, +    0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30, +    0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03, +    0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07, +    0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e, +    0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d, +    0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b, +    0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76, +    0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90, +    0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87, +    0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e, +    0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c, +    0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca, +    0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8, +    0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82, +    0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83, +    0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f, +    0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec, +    0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc, +    0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0, +    0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60, +    0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e, +    0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1, +    0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43, +    0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c, +    0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72, +    0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00, +    0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x3d,0x00,0x00,0x00,0x13,0x04,0x41,0x2c, +    0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6,0x22, +    0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0xa8,0x95,0x40,0x19,0x14,0x01,0xbd,0x11, +    0x00,0x1a,0x33,0x00,0x24,0x66,0x00,0x28,0xcc,0x00,0x00,0x00,0xe3,0x15,0x0b,0x84, +    0x61,0x10,0x05,0x65,0x90,0x01,0x1a,0x0c,0x13,0x02,0xf9,0x8c,0x57,0x3c,0x14,0xc7, +    0x2d,0x14,0x94,0x41,0x06,0xea,0x70,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a, +    0x2a,0x0b,0x83,0x30,0x70,0x28,0x28,0x83,0x0c,0x19,0x43,0x99,0x10,0xc8,0xc7,0x8a, +    0x00,0x3e,0xe3,0x15,0x99,0x67,0x06,0x66,0x40,0x51,0x50,0x06,0x19,0xbc,0x48,0x33, +    0x21,0x90,0x8f,0x15,0x01,0x7c,0xc6,0x2b,0xbc,0x31,0x60,0x83,0x35,0x18,0x03,0x0a, +    0xca,0x20,0x83,0x18,0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b,0xc4,0xe0,0x0c,0xe0,0xe0, +    0x0d,0x3c,0x0a,0xca,0x20,0x83,0x19,0x70,0x61,0x60,0x42,0x20,0x1f,0x0b,0x0a,0xf8, +    0x8c,0x57,0x9c,0x01,0x1b,0xd4,0x01,0x1d,0x88,0x01,0x05,0xc5,0x86,0x00,0x3e,0xb3, +    0x0d,0x61,0x10,0x00,0xb3,0x0d,0x01,0x1b,0x04,0xb3,0x0d,0xc1,0x23,0x64,0x10,0x10, +    0x03,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x5b,0x86,0x20,0x10,0x85,0x2d,0x43,0x11, +    0x88,0xc2,0x96,0x41,0x09,0x44,0x61,0xcb,0xf0,0x04,0xa2,0xb0,0x65,0xa0,0x02,0x51, +    0xd8,0x32,0x60,0x81,0x28,0x6c,0x19,0xba,0x40,0x14,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sgl_fs_bytecode_metal_macos[2909] = { +    0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x5d,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd5,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x80,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xc3,0xf2,0x7e,0x47,0xcc,0xf2,0x87, +    0xc2,0x27,0x51,0x05,0xdb,0xac,0xc5,0x2d,0x03,0xfc,0xe6,0x91,0x1c,0x38,0xc9,0xd8, +    0x34,0x4c,0xa7,0xd1,0x68,0xc2,0x62,0x41,0xf6,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, +    0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17, +    0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x64,0x0a,0x00,0x00,0xff,0xff,0xff, +    0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x96,0x02,0x00,0x00,0x0b,0x82,0x20, +    0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04, +    0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b, +    0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18, +    0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21, +    0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00, +    0x00,0x51,0x18,0x00,0x00,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff, +    0xff,0x01,0x60,0x00,0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07, +    0x7a,0x60,0x87,0x7c,0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72, +    0x68,0x03,0x72,0x48,0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90, +    0x07,0x7a,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc, +    0x21,0x1c,0xd8,0x61,0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81, +    0x1d,0xca,0xa1,0x0d,0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d, +    0xd8,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87, +    0x79,0x98,0x87,0x36,0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36, +    0x30,0x07,0x78,0x68,0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c, +    0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0, +    0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1, +    0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60,0x70,0x87,0x77,0x68,0x03,0x73,0x90, +    0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78,0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07, +    0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36, +    0x60,0x87,0x72,0x08,0x07,0x73,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30, +    0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, +    0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xd4,0xa1,0x1e,0xda,0x01, +    0x1e,0xda,0x80,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60, +    0x87,0x79,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,0x77,0x98,0x87, +    0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1, +    0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c, +    0xc8,0xa1,0x0d,0xf4,0xa1,0x1c,0xe4,0xe1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda, +    0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77, +    0x78,0x87,0x36,0xa0,0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68, +    0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6, +    0x81,0x1e,0xc2,0x61,0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61, +    0x1c,0xe8,0xe1,0x1d,0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e, +    0xda,0x60,0x1e,0xd2,0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87, +    0x70,0x30,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74, +    0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e, +    0xde,0xc1,0x1c,0xe8,0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87, +    0x70,0x60,0x87,0x79,0x28,0x07,0x60,0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81, +    0x00,0x16,0xa0,0xda,0x80,0x10,0xff,0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5, +    0x06,0xa3,0x08,0x80,0x05,0xa8,0x36,0x18,0x86,0x00,0x2c,0x40,0x05,0x49,0x18,0x00, +    0x00,0x03,0x00,0x00,0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,0x00,0x00,0x00, +    0x00,0x89,0x20,0x00,0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85, +    0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a, +    0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x48,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45, +    0x94,0x30,0xf9,0x0c,0x80,0x34,0xf4,0xef,0x50,0x13,0x0a,0xc2,0x51,0xd2,0x14,0x51, +    0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54,0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20, +    0xc2,0x10,0x5c,0x24,0x4d,0x11,0x25,0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f, +    0x8d,0x11,0x00,0x83,0x08,0x85,0x50,0x0a,0x11,0x02,0x31,0x74,0x86,0x11,0x04,0x60, +    0x8e,0x20,0x98,0x23,0x00,0x83,0x61,0x04,0x61,0x29,0x48,0x20,0x26,0x29,0xa6,0x00, +    0xb5,0x81,0x80,0x1c,0x58,0x23,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0, +    0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87, +    0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38, +    0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, +    0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0, +    0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07, +    0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a, +    0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60, +    0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07, +    0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, +    0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0, +    0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07, +    0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, +    0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, +    0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, +    0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, +    0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0, +    0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07, +    0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76, +    0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50, +    0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07, +    0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74, +    0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60, +    0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00, +    0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64,0x81,0x00,0x00,0x00, +    0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, +    0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70, +    0xac,0x41,0x79,0x08,0x00,0x79,0x18,0x00,0x00,0xd7,0x00,0x00,0x00,0x1a,0x03,0x4c, +    0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42, +    0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62, +    0x2c,0xc2,0x23,0x2c,0x05,0xd9,0x20,0x08,0x0e,0x8e,0xad,0x0c,0x84,0x89,0xc9,0xaa, +    0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x24,0x07,0x46,0xc6,0x25,0x86,0x06, +    0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x25,0x07,0x46,0xc6,0x25,0x86,0xc6, +    0x25,0x27,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54,0x46, +    0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7,0x32, +    0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45, +    0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63, +    0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68, +    0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x61,0x19,0x84,0xa5,0xc9,0xb9, +    0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99, +    0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1, +    0x95,0x0d,0x11,0x9e,0x86,0x61,0x10,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6, +    0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6,0x45,0xf6,0x56,0x27,0xc6,0x56,0xf6, +    0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x1e,0x92,0x41,0x58,0x9a,0x9c, +    0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b,0x5b,0x18,0x5d,0x9a,0x5d,0xd9,0x17, +    0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0x59,0xda,0x5c,0x98,0x18,0x5b, +    0xd9,0x10,0xe1,0x89,0x78,0x06,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c, +    0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f,0x77,0x69,0x64,0x65,0x5f,0x76,0x65, +    0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,0x43,0x84,0x67, +    0x62,0x14,0x96,0x26,0xe7,0x22,0x57,0xe6,0x46,0x56,0x26,0xf7,0x45,0x17,0x26,0x77, +    0x56,0x46,0xc7,0x28,0x2c,0x4d,0xce,0x25,0x4c,0xee,0xec,0x8b,0x2e,0x0f,0xae,0xec, +    0xcb,0x2d,0xac,0xad,0x8c,0x86,0x19,0xdb,0x5b,0x18,0x1d,0x0d,0x99,0xb0,0x34,0x39, +    0x97,0x30,0xb9,0xb3,0x2f,0xb7,0xb0,0xb6,0x32,0x2a,0x66,0x72,0x61,0x67,0x5f,0x63, +    0x6f,0x6c,0x6f,0x72,0x43,0x98,0xa7,0x5a,0x86,0xc7,0x7a,0xae,0x07,0x7b,0xb2,0x21, +    0xc2,0xa3,0x51,0x0a,0x4b,0x93,0x73,0x31,0x93,0x0b,0x3b,0x6b,0x2b,0x73,0xa3,0xfb, +    0x4a,0x73,0x83,0xab,0xa3,0xe3,0x52,0x37,0x57,0x26,0x87,0xc2,0xf6,0x36,0xe6,0x06, +    0x93,0x42,0x25,0x2c,0x4d,0xce,0x65,0xac,0xcc,0x8d,0xae,0x4c,0x8e,0x4f,0x58,0x9a, +    0x9c,0x0b,0x5c,0x99,0xdc,0x1c,0x5c,0xd9,0x18,0x5d,0x9a,0x5d,0x19,0x85,0x3a,0xbb, +    0x21,0xd2,0x32,0x3c,0xdc,0xd3,0x3d,0xde,0xf3,0x3d,0xd6,0x73,0x3d,0xd8,0x03,0x06, +    0x5c,0xea,0xe6,0xca,0xe4,0x50,0xd8,0xde,0xc6,0xdc,0x62,0x52,0x58,0x8c,0xbd,0xb1, +    0xbd,0xc9,0x0d,0x91,0x16,0xe1,0xe1,0x1e,0x31,0x78,0xbc,0xe7,0x7b,0xac,0xe7,0x7a, +    0xb0,0x67,0x0c,0xb8,0x84,0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51, +    0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23, +    0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae, +    0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64, +    0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21,0x9e,0x32, +    0x78,0xcc,0x60,0x19,0x16,0xe1,0x39,0x83,0xc7,0x7a,0xd0,0xe0,0xc1,0x9e,0x34,0xe0, +    0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x26,0xc7,0x63,0x2e,0xac,0x0d, +    0x8e,0xad,0x4c,0x8e,0x08,0x5d,0x19,0xde,0x54,0x1b,0x1c,0x9b,0xdc,0x10,0x69,0x39, +    0x9e,0x35,0x78,0xcc,0x60,0x19,0x16,0xe1,0xb1,0x1e,0x36,0x78,0xb0,0xa7,0x0d,0x86, +    0x20,0x4f,0x18,0x3c,0x64,0xf0,0xa8,0xc1,0xe3,0x06,0x43,0x0c,0x04,0x78,0xb6,0xe7, +    0x0d,0x46,0x44,0xec,0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e,0xef,0x40,0x0e,0xf5, +    0xc0,0x0e,0xe5,0xe0,0x06,0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3,0x30,0x45,0x08,0x86, +    0x11,0x0a,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39,0x94,0x83,0x3b,0xd0, +    0xc3,0x94,0xa0,0x18,0xb1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8,0x43,0x39,0xc8,0xc3, +    0x3c,0xa4,0xc3,0x3b,0xb8,0xc3,0x94,0xc0,0x18,0x41,0x85,0x43,0x3a,0xc8,0x83,0x1b, +    0xb0,0x43,0x38,0xb8,0xc3,0x39,0xd4,0x43,0x38,0x9c,0x43,0x39,0xfc,0x82,0x3d,0x94, +    0x83,0x3c,0xcc,0x43,0x3a,0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11,0x53,0x38,0xa4,0x83, +    0x3c,0xb8,0xc1,0x38,0xbc,0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43,0x39,0xfc,0xc2,0x3b, +    0xc0,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x3c,0x4c,0x31,0x14,0xc6,0x81,0x24,0x6a, +    0x04,0x13,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0e,0xf2,0x10,0x0e,0xe7,0xd0,0x0e,0xe5, +    0xe0,0x0e,0xf4,0x30,0x25,0x80,0x03,0x00,0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00, +    0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, +    0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, +    0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, +    0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, +    0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, +    0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, +    0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, +    0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, +    0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, +    0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, +    0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, +    0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, +    0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, +    0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, +    0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, +    0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, +    0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, +    0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, +    0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, +    0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, +    0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, +    0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, +    0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, +    0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, +    0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, +    0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, +    0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, +    0x72,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01, +    0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e, +    0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00, +    0x00,0x0f,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00, +    0x00,0xc4,0x46,0x00,0xc6,0x12,0x80,0x80,0xd4,0x08,0x40,0x0d,0x90,0x98,0x01,0xa0, +    0x30,0x03,0x40,0x60,0x04,0x00,0x00,0x00,0x00,0x83,0x0c,0x8b,0x60,0x8c,0x18,0x28, +    0x42,0x40,0x29,0x49,0x50,0x20,0x86,0x60,0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sgl_vs_bytecode_metal_ios[3344] = { +    0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x10,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x58,0xff,0x12,0x36,0x80,0x88,0xb5, +    0xf7,0x1c,0xc3,0xd5,0x31,0x57,0x18,0x26,0x26,0xf0,0x8c,0xd4,0x15,0xa2,0x55,0x8b, +    0xd5,0x3c,0x6b,0x11,0xbd,0x17,0x49,0x4a,0x02,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x37,0x00,0x00, +    0x00,0x56,0x41,0x54,0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, +    0x6e,0x00,0x00,0x80,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80, +    0x63,0x6f,0x6c,0x6f,0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03, +    0x00,0x06,0x04,0x06,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54, +    0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xec,0x0b,0x00,0x00, +    0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xf8,0x02,0x00,0x00, +    0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91, +    0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19, +    0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14, +    0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80, +    0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0, +    0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8, +    0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87, +    0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72, +    0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88, +    0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41, +    0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c, +    0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87, +    0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72, +    0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,0x1e, +    0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4,0x21,0x1c,0xda,0xa1,0x1c,0xda, +    0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41,0x1e,0xda,0xa0,0x1c,0xd8,0x21, +    0x1d,0xda,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06,0x77,0x78,0x87,0x36,0x30, +    0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87, +    0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x68,0x03,0x77,0x78,0x07,0x77, +    0x68,0x03,0x76,0x28,0x87,0x70,0x30,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x74,0x70, +    0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, +    0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x40,0x1d,0xea,0xa1, +    0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08, +    0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,0x87, +    0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, +    0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe6,0x21,0x1d,0xce,0xc1,0x1d, +    0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,0x61,0x1e,0xda,0xc0,0x1c,0xe0, +    0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80, +    0x70,0x87,0x77,0x68,0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38, +    0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00, +    0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1, +    0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c, +    0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87, +    0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a, +    0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d, +    0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00, +    0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x20,0x02,0x01,0x24,0xc0,0x02,0x54, +    0x00,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00, +    0x89,0x20,0x00,0x00,0x23,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04, +    0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c, +    0x0b,0x84,0xa4,0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45,0x94, +    0x30,0xf9,0x0c,0x80,0x34,0xf4,0xef,0x50,0x13,0x1a,0x42,0x08,0xc3,0x08,0x02,0x90, +    0x04,0x61,0x26,0x6a,0x1e,0xe8,0x41,0x1e,0xea,0x61,0x1c,0xe8,0xc1,0x0d,0xda,0xa1, +    0x1c,0xe8,0x21,0x1c,0xd8,0x41,0x0f,0xf4,0xa0,0x1d,0xc2,0x81,0x1e,0xe4,0x21,0x1d, +    0xf0,0x01,0x05,0xe4,0x20,0x69,0x8a,0x28,0x61,0xf2,0x2b,0xe9,0x7f,0x80,0x08,0x60, +    0x24,0x24,0x94,0x32,0x88,0x60,0x08,0xa5,0x10,0x61,0x84,0x43,0x68,0x20,0x60,0x8e, +    0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb,0x08, +    0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70, +    0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80, +    0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0, +    0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07, +    0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9, +    0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30, +    0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07, +    0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72, +    0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0, +    0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07, +    0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73, +    0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20, +    0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07, +    0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, +    0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0, +    0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07, +    0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72, +    0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20, +    0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07, +    0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75, +    0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, +    0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07, +    0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00,0x00, +    0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98,0x10, +    0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50, +    0x04,0x05,0x18,0x50,0x80,0x02,0x85,0x50,0x10,0x65,0x50,0x20,0xd4,0x46,0x00,0x88, +    0x8d,0x25,0x34,0x05,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x19,0x01,0x00,0x00, +    0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7, +    0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3, +    0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x06,0xd9,0x20,0x08,0x0e,0x8e,0xad,0x0c,0x84, +    0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x07,0x46, +    0xc6,0xc5,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x07, +    0x46,0xc6,0xc5,0xe6,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x8e,0x45,0x60, +    0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x84,0x45,0xe0,0x16,0x96, +    0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7, +    0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69, +    0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d, +    0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x21,0x19,0x84, +    0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95, +    0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85, +    0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x61,0x10,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06, +    0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6,0x45,0xf6,0x56,0x27,0xc6, +    0x56,0xf6,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x1e,0x92,0x41,0x58, +    0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b,0x5b,0x18,0x5d,0x9a,0x5d, +    0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0x59,0xda,0x5c,0x98, +    0x18,0x5b,0xd9,0x10,0x41,0x89,0x78,0x06,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70, +    0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f,0x77,0x69,0x64,0x65,0x5f, +    0x76,0x65,0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,0x43, +    0x04,0x65,0x62,0x14,0x96,0x26,0xe7,0x62,0x57,0x26,0x47,0x57,0x86,0xf7,0xf5,0x56, +    0x47,0x07,0x57,0x47,0xc7,0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x0d,0x26, +    0x85,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e,0x5c,0xd9,0x97,0x5b, +    0x58,0x5b,0x19,0x0d,0x33,0xb6,0xb7,0x30,0x3a,0x1a,0x32,0x61,0x69,0x72,0x2e,0x61, +    0x72,0x67,0x5f,0x6e,0x61,0x6d,0x65,0x14,0xea,0xec,0x86,0x30,0x4a,0xa5,0x58,0xca, +    0xa5,0x60,0x4a,0xa6,0x68,0x5c,0xea,0xe6,0xca,0xe4,0x50,0xd8,0xde,0xc6,0xdc,0x62, +    0x52,0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x94,0x4a,0xe1,0x94,0x4b,0xc1,0x94, +    0x4c,0xe9,0xc8,0x84,0xa5,0xc9,0xb9,0xc0,0xbd,0xcd,0xa5,0xd1,0xa5,0xbd,0xb9,0x71, +    0x39,0x63,0xfb,0x82,0x7a,0x9b,0x4b,0xa3,0x4b,0x7b,0x73,0x1b,0xa2,0x28,0x9f,0x72, +    0x29,0x98,0x92,0x29,0x60,0x30,0xc4,0x50,0x36,0xc5,0x53,0xc2,0x80,0x50,0x58,0x9a, +    0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b,0x5c,0x1d,0x1d,0xa5,0xb0, +    0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7,0xaf,0x34,0x37,0xb2,0x32, +    0x3c,0x66,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,0x28,0x38,0x70,0x6f,0x73, +    0x69,0x74,0x69,0x6f,0x6e,0x44,0x76,0x34,0x5f,0x66,0x29,0x44,0xe0,0xde,0xe6,0xd2, +    0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x8c,0x81,0x42,0x06,0x8b,0xb0,0x04,0x4a, +    0x19,0x28,0x97,0x82,0x29,0x99,0x62,0x06,0xd4,0xce,0xca,0xdc,0xca,0xe4,0xc2,0xe8, +    0xca,0xc8,0x50,0x72,0xe8,0xca,0xf0,0xc6,0xde,0xde,0xe4,0xc8,0x60,0x88,0xec,0x64, +    0xbe,0xcc,0x52,0x68,0x98,0xb1,0xbd,0x85,0xd1,0xc9,0x30,0xa1,0x2b,0xc3,0x1b,0x7b, +    0x7b,0x93,0x23,0x83,0x19,0x42,0x2d,0x81,0x32,0x06,0x0a,0x19,0x2c,0xc1,0x12,0x28, +    0x68,0xa0,0x5c,0x4a,0x1a,0x28,0x99,0xa2,0x06,0xbc,0xce,0xca,0xdc,0xca,0xe4,0xc2, +    0xe8,0xca,0xc8,0x50,0x6c,0xc6,0xde,0xd8,0xde,0xe4,0x60,0x88,0xec,0x68,0xbe,0xcc, +    0x52,0x68,0x8c,0xbd,0xb1,0xbd,0xc9,0xc1,0x0c,0xa1,0x96,0x41,0x19,0x03,0x85,0x0c, +    0x96,0x61,0x09,0x14,0x36,0x50,0x2e,0x05,0x53,0x32,0xa5,0x0d,0xa8,0x84,0xa5,0xc9, +    0xb9,0x88,0xd5,0x99,0x99,0x95,0xc9,0xf1,0x09,0x4b,0x93,0x73,0x11,0xab,0x33,0x33, +    0x2b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x23,0x12,0x96,0x26,0xe7,0x22,0x57,0x16,0x46, +    0x46,0x2a,0x2c,0x4d,0xce,0x65,0x8e,0x4e,0xae,0x6e,0x8c,0xee,0x8b,0x2e,0x0f,0xae, +    0xec,0x2b,0xcd,0xcd,0xec,0x8d,0x88,0x19,0xdb,0x5b,0x18,0x1d,0x0d,0x1e,0x0d,0x87, +    0x36,0x3b,0x38,0x0a,0x74,0x6d,0x43,0xa8,0x45,0x58,0x88,0x45,0x50,0xe6,0x40,0xa1, +    0x83,0x85,0x58,0x88,0x45,0x50,0xe6,0x40,0xa9,0x03,0x46,0x61,0x69,0x72,0x2e,0x61, +    0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x73,0x69,0x7a,0x65,0xbc,0xc2,0xd2,0xe4, +    0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xc2,0xd8,0xd2,0xce,0xdc,0xbe, +    0xe6,0xd2,0xf4,0xca,0x98,0xd8,0xcd,0x7d,0xc1,0x85,0xc9,0x85,0xb5,0xcd,0x71,0xf8, +    0x92,0x81,0x19,0x42,0x06,0x0b,0xa2,0xbc,0x81,0x02,0x07,0x4b,0xa1,0x90,0xc1,0x22, +    0x2c,0x81,0x12,0x07,0x8a,0x1c,0x28,0x76,0xa0,0xdc,0xc1,0x52,0x28,0x78,0xb0,0x24, +    0xca,0xa5,0xe4,0x81,0x92,0x29,0x7a,0x30,0x04,0x51,0xce,0x40,0x59,0x03,0xc5,0x0d, +    0x94,0x3d,0x18,0x62,0x24,0x80,0x22,0x06,0x0a,0x1f,0xf0,0x79,0x6b,0x73,0x4b,0x83, +    0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33,0x95,0xd6,0x06,0xc7, +    0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44,0x50,0xfe,0x60,0x88, +    0xa1,0xf8,0x81,0x02,0x0a,0x8d,0x32,0xc4,0x50,0x42,0x41,0x09,0x85,0x46,0x19,0x11, +    0xb1,0x03,0x3b,0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03,0x39,0xd4,0x03,0x3b,0x94, +    0x83,0x1b,0x98,0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14,0x21,0x18,0x46,0x28,0xec, +    0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e,0xee,0x40,0x0f,0x53,0x82, +    0x62,0xc4,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e, +    0xef,0xe0,0x0e,0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20,0x0f,0x6e,0xc0,0x0e,0xe1, +    0xe0,0x0e,0xe7,0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b,0xf6,0x50,0x0e,0xf2,0x30, +    0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1,0x90,0x0e,0xf2,0xe0,0x06, +    0xe3,0xf0,0x0e,0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0,0x0b,0xef,0x00,0x0f,0xf4, +    0x90,0x0e,0xef,0xe0,0x0e,0xf3,0x30,0xc5,0x50,0x18,0x07,0x92,0xa8,0x11,0x4a,0x38, +    0xa4,0x83,0x3c,0xb8,0x81,0x3d,0x94,0x83,0x3c,0xd0,0x43,0x39,0xe0,0xc3,0x94,0xa0, +    0x0f,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00,0x00,0x33,0x08,0x80,0x1c, +    0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07, +    0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e, +    0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43, +    0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c, +    0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76, +    0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e, +    0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8, +    0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4, +    0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68, +    0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07, +    0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71, +    0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5, +    0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4, +    0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90, +    0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43, +    0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b, +    0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78, +    0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2, +    0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20, +    0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0, +    0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83, +    0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07, +    0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61, +    0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d, +    0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39, +    0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79, +    0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x00,0x00,0x00,0x00, +    0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00, +    0x61,0x20,0x00,0x00,0x3d,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00, +    0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6,0x22,0x04,0x41,0x10,0xc6, +    0x22,0x82,0x20,0x08,0xa8,0x95,0x40,0x19,0x14,0x01,0xbd,0x11,0x00,0x1a,0x33,0x00, +    0x24,0x66,0x00,0x28,0xcc,0x00,0x00,0x00,0xe3,0x15,0x0b,0x84,0x61,0x10,0x05,0x65, +    0x90,0x01,0x1a,0x0c,0x13,0x02,0xf9,0x8c,0x57,0x3c,0x14,0xc7,0x2d,0x14,0x94,0x41, +    0x06,0xea,0x70,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a,0x2a,0x0b,0x83,0x30, +    0x70,0x28,0x28,0x83,0x0c,0x19,0x43,0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15, +    0x99,0x67,0x06,0x66,0x40,0x51,0x50,0x06,0x19,0xbc,0x48,0x33,0x21,0x90,0x8f,0x15, +    0x01,0x7c,0xc6,0x2b,0xbc,0x31,0x60,0x83,0x35,0x18,0x03,0x0a,0xca,0x20,0x83,0x18, +    0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b,0xc4,0xe0,0x0c,0xe0,0xe0,0x0d,0x3c,0x0a,0xca, +    0x20,0x83,0x19,0x70,0x61,0x60,0x42,0x20,0x1f,0x0b,0x0a,0xf8,0x8c,0x57,0x9c,0x01, +    0x1b,0xd4,0x01,0x1d,0x88,0x01,0x05,0xc5,0x86,0x00,0x3e,0xb3,0x0d,0x61,0x10,0x00, +    0xb3,0x0d,0x01,0x1b,0x04,0xb3,0x0d,0xc1,0x23,0x64,0x10,0x10,0x03,0x00,0x00,0x00, +    0x09,0x00,0x00,0x00,0x5b,0x86,0x20,0x10,0x85,0x2d,0x43,0x11,0x88,0xc2,0x96,0x41, +    0x09,0x44,0x61,0xcb,0xf0,0x04,0xa2,0xb0,0x65,0xa0,0x02,0x51,0xd8,0x32,0x60,0x81, +    0x28,0x6c,0x19,0xba,0x40,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sgl_fs_bytecode_metal_ios[2893] = { +    0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x4d,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd5,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdd,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x70,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, +    0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, +    0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xa8,0xe6,0x5c,0x09,0x1c,0x98,0x6d, +    0xf9,0x59,0x27,0x6f,0xf0,0xd6,0x41,0x4d,0xf9,0x93,0x2f,0x51,0x99,0xc5,0xc8,0xc7, +    0xad,0x1e,0x89,0xca,0xfe,0x25,0x89,0x6a,0x2a,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, +    0x00,0x01,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, +    0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17, +    0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x5c,0x0a,0x00,0x00,0xff,0xff,0xff, +    0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x94,0x02,0x00,0x00,0x0b,0x82,0x20, +    0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04, +    0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b, +    0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18, +    0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21, +    0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00, +    0x00,0x51,0x18,0x00,0x00,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff, +    0xff,0x01,0x60,0x00,0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07, +    0x7a,0x60,0x87,0x7c,0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72, +    0x68,0x03,0x72,0x48,0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90, +    0x07,0x7a,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc, +    0x21,0x1c,0xd8,0x61,0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81, +    0x1d,0xca,0xa1,0x0d,0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d, +    0xd8,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87, +    0x79,0x98,0x87,0x36,0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36, +    0x30,0x07,0x78,0x68,0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c, +    0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0, +    0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1, +    0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60,0x70,0x87,0x77,0x68,0x03,0x73,0x90, +    0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78,0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07, +    0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36, +    0x60,0x87,0x72,0x08,0x07,0x73,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30, +    0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, +    0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xd4,0xa1,0x1e,0xda,0x01, +    0x1e,0xda,0x80,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60, +    0x87,0x79,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,0x77,0x98,0x87, +    0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1, +    0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c, +    0xc8,0xa1,0x0d,0xf4,0xa1,0x1c,0xe4,0xe1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda, +    0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77, +    0x78,0x87,0x36,0xa0,0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68, +    0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6, +    0x81,0x1e,0xc2,0x61,0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61, +    0x1c,0xe8,0xe1,0x1d,0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e, +    0xda,0x60,0x1e,0xd2,0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87, +    0x70,0x30,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74, +    0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e, +    0xde,0xc1,0x1c,0xe8,0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87, +    0x70,0x60,0x87,0x79,0x28,0x07,0x60,0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81, +    0x00,0x16,0xa0,0xda,0x80,0x10,0xff,0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5, +    0x06,0xa3,0x08,0x80,0x05,0xa8,0x36,0x18,0x86,0x00,0x2c,0x40,0x05,0x49,0x18,0x00, +    0x00,0x03,0x00,0x00,0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,0x00,0x00,0x00, +    0x00,0x89,0x20,0x00,0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85, +    0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a, +    0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x48,0x33,0x00,0xc3,0x08,0x04,0x70,0x90,0x34,0x45, +    0x94,0x30,0xf9,0x0c,0x80,0x34,0xf4,0xef,0x50,0x13,0x0a,0xc2,0x51,0xd2,0x14,0x51, +    0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54,0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20, +    0xc2,0x10,0x5c,0x24,0x4d,0x11,0x25,0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f, +    0x8d,0x11,0x00,0x83,0x08,0x85,0x50,0x0a,0x11,0x02,0x31,0x74,0x86,0x11,0x04,0x60, +    0x8e,0x20,0x98,0x23,0x00,0x83,0x61,0x04,0x61,0x29,0x48,0x20,0x26,0x29,0xa6,0x00, +    0xb5,0x81,0x80,0x14,0x58,0x23,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0, +    0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87, +    0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f, +    0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9, +    0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0, +    0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07, +    0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72, +    0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0, +    0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, +    0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, +    0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20, +    0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07, +    0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78, +    0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0, +    0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07, +    0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72, +    0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, +    0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07, +    0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, +    0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10, +    0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07, +    0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74, +    0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x18, +    0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03,0x04,0x80,0x00,0x00, +    0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98, +    0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x23,0x00,0x25, +    0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0xa1,0x29,0x00,0x00,0x79,0x18,0x00, +    0x00,0xd7,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32, +    0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b, +    0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc3,0x23,0x2c,0x05,0xd9,0x20,0x08, +    0x0e,0x8e,0xad,0x0c,0x84,0x89,0xc9,0xaa,0x09,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd, +    0x0d,0x64,0x26,0x07,0x46,0xc6,0xc5,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd, +    0xac,0xac,0x65,0x26,0x07,0x46,0xc6,0xc5,0xe6,0x26,0x65,0x88,0xf0,0x10,0x43,0x8c, +    0x65,0x58,0x8c,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x65,0x58, +    0x84,0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42, +    0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44, +    0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66, +    0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43, +    0x84,0x67,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9, +    0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d, +    0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x61,0x10,0x96,0x26, +    0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0xe2,0x16,0x46,0x97,0x66,0x57,0xf6, +    0x45,0xf6,0x56,0x27,0xc6,0x56,0xf6,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44, +    0x78,0x1e,0x92,0x41,0x58,0x9a,0x9c,0xcb,0xd8,0x5b,0x1b,0x5c,0x1a,0x5b,0x99,0x8b, +    0x5b,0x18,0x5d,0x9a,0x5d,0xd9,0x17,0xdb,0x9b,0xdb,0xd9,0x17,0xdb,0x9b,0xdb,0xd9, +    0x17,0x59,0xda,0x5c,0x98,0x18,0x5b,0xd9,0x10,0xe1,0x89,0x78,0x06,0x61,0x69,0x72, +    0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x6e,0x61,0x74,0x69,0x76,0x65,0x5f, +    0x77,0x69,0x64,0x65,0x5f,0x76,0x65,0x63,0x74,0x6f,0x72,0x73,0x5f,0x64,0x69,0x73, +    0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x62,0x14,0x96,0x26,0xe7,0x22,0x57,0xe6,0x46, +    0x56,0x26,0xf7,0x45,0x17,0x26,0x77,0x56,0x46,0xc7,0x28,0x2c,0x4d,0xce,0x25,0x4c, +    0xee,0xec,0x8b,0x2e,0x0f,0xae,0xec,0xcb,0x2d,0xac,0xad,0x8c,0x86,0x19,0xdb,0x5b, +    0x18,0x1d,0x0d,0x99,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xb7,0xb0,0xb6,0x32, +    0x2a,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x43,0x98,0xa7,0x5a,0x84, +    0xc7,0x7a,0xae,0x07,0x7b,0xb2,0x21,0xc2,0xa3,0x51,0x0a,0x4b,0x93,0x73,0x31,0x93, +    0x0b,0x3b,0x6b,0x2b,0x73,0xa3,0xfb,0x4a,0x73,0x83,0xab,0xa3,0xe3,0x52,0x37,0x57, +    0x26,0x87,0xc2,0xf6,0x36,0xe6,0x06,0x93,0x42,0x25,0x2c,0x4d,0xce,0x65,0xac,0xcc, +    0x8d,0xae,0x4c,0x8e,0x4f,0x58,0x9a,0x9c,0x0b,0x5c,0x99,0xdc,0x1c,0x5c,0xd9,0x18, +    0x5d,0x9a,0x5d,0x19,0x85,0x3a,0xbb,0x21,0xd2,0x22,0x3c,0xdc,0xd3,0x3d,0xde,0xf3, +    0x3d,0xd6,0x73,0x3d,0xd8,0x03,0x06,0x5c,0xea,0xe6,0xca,0xe4,0x50,0xd8,0xde,0xc6, +    0xdc,0x62,0x52,0x58,0x8c,0xbd,0xb1,0xbd,0xc9,0x0d,0x91,0x96,0xe1,0xe1,0x1e,0x31, +    0x78,0xbc,0xe7,0x7b,0xac,0xe7,0x7a,0xb0,0x67,0x0c,0xb8,0x84,0xa5,0xc9,0xb9,0xd0, +    0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3, +    0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17, +    0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7, +    0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d, +    0x19,0xde,0x10,0x6a,0x21,0x9e,0x32,0x78,0xcc,0x60,0x11,0x96,0xe1,0x39,0x83,0xc7, +    0x7a,0xd0,0xe0,0xc1,0x9e,0x34,0xe0,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7, +    0x56,0x26,0xc7,0x63,0x2e,0xac,0x0d,0x8e,0xad,0x4c,0x8e,0x08,0x5d,0x19,0xde,0x54, +    0x1b,0x1c,0x9b,0xdc,0x10,0x69,0x39,0x9e,0x35,0x78,0xcc,0x60,0x11,0x96,0xe1,0xb1, +    0x1e,0x36,0x78,0xb0,0xa7,0x0d,0x86,0x20,0x4f,0x18,0x3c,0x64,0xf0,0xa8,0xc1,0xe3, +    0x06,0x43,0x0c,0x04,0x78,0xb6,0xe7,0x0d,0x46,0x44,0xec,0xc0,0x0e,0xf6,0xd0,0x0e, +    0x6e,0xd0,0x0e,0xef,0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0,0x06,0xe6,0xc0,0x0e,0xe1, +    0x70,0x0e,0xf3,0x30,0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b, +    0xa4,0x03,0x39,0x94,0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18,0xb1,0x84,0x43,0x3a,0xc8, +    0x83,0x1b,0xd8,0x43,0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b,0xb8,0xc3,0x94,0xc0,0x18, +    0x41,0x85,0x43,0x3a,0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8,0xc3,0x39,0xd4,0x43,0x38, +    0x9c,0x43,0x39,0xfc,0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43,0x3a,0xbc,0x83,0x3b,0x4c, +    0x09,0x90,0x11,0x53,0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38,0xbc,0x43,0x3b,0xc0,0x43, +    0x3a,0xb0,0x43,0x39,0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x3c, +    0x4c,0x31,0x14,0xc6,0x81,0x24,0x6a,0x04,0x13,0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0e, +    0xf2,0x10,0x0e,0xe7,0xd0,0x0e,0xe5,0xe0,0x0e,0xf4,0x30,0x25,0x80,0x03,0x00,0x00, +    0x00,0x79,0x18,0x00,0x00,0x6d,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c, +    0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07, +    0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42, +    0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83, +    0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07, +    0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70, +    0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3, +    0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2, +    0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc, +    0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68, +    0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07, +    0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72, +    0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec, +    0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc, +    0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8, +    0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03, +    0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c, +    0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74, +    0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4, +    0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc, +    0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1, +    0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50, +    0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51, +    0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21, +    0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06, +    0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c, +    0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77, +    0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x00,0x00,0x00,0x00,0x71,0x20,0x00, +    0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f, +    0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20, +    0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x0f,0x00,0x00,0x00,0x13,0x04,0x41, +    0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xc4,0x46,0x00,0xc6,0x12,0x80,0x80, +    0xd4,0x08,0x40,0x0d,0x90,0x98,0x01,0xa0,0x30,0x03,0x40,0x60,0x04,0x00,0x00,0x00, +    0x00,0x83,0x0c,0x8b,0x60,0x8c,0x18,0x28,0x42,0x40,0x29,0x49,0x50,0x20,0x86,0x60, +    0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x00,0x00,0x00,0x00,0x00, +}; +static const char _sgl_vs_source_metal_sim[698] = { +    0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, +    0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, +    0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, +    0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, +    0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, +    0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, +    0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, +    0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, +    0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, +    0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, +    0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, +    0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, +    0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, +    0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, +    0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73, +    0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b, +    0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69, +    0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65, +    0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, +    0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x5b,0x5b,0x61,0x74, +    0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, +    0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20, +    0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x32,0x29,0x5d,0x5d, +    0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x36,0x20,0x22, +    0x22,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, +    0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69, +    0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d, +    0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70, +    0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x32,0x30,0x20,0x5b,0x5b,0x62,0x75,0x66, +    0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, +    0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20, +    0x7b,0x7d,0x3b,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x36,0x20,0x22,0x22,0x0a, +    0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, +    0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x32,0x30,0x2e,0x6d,0x76,0x70,0x20,0x2a,0x20, +    0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x23,0x6c,0x69, +    0x6e,0x65,0x20,0x31,0x37,0x20,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74, +    0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x32,0x30,0x2e,0x74,0x6d,0x20,0x2a,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, +    0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x23, +    0x6c,0x69,0x6e,0x65,0x20,0x31,0x38,0x20,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f, +    0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f, +    0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e, +    0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sgl_fs_source_metal_sim[473] = { +    0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, +    0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, +    0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, +    0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, +    0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, +    0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, +    0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, +    0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, +    0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, +    0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, +    0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, +    0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, +    0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, +    0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20, +    0x31,0x31,0x20,0x22,0x22,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d, +    0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d, +    0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61, +    0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, +    0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65,0x78,0x20,0x5b,0x5b, +    0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61, +    0x6d,0x70,0x6c,0x65,0x72,0x20,0x74,0x65,0x78,0x53,0x6d,0x70,0x6c,0x72,0x20,0x5b, +    0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b, +    0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f, +    0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31, +    0x31,0x20,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61, +    0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x2e,0x73,0x61, +    0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x53,0x6d,0x70,0x6c,0x72,0x2c,0x20,0x69, +    0x6e,0x2e,0x75,0x76,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f, +    0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, +    0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +static const uint8_t _sgl_vs_bytecode_hlsl4[1008] = { +    0x44,0x58,0x42,0x43,0xf3,0xd9,0xf4,0x58,0x44,0xc2,0xb9,0x96,0x56,0x7c,0xb3,0x71, +    0x84,0xc5,0xde,0x61,0x01,0x00,0x00,0x00,0xf0,0x03,0x00,0x00,0x05,0x00,0x00,0x00, +    0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x78,0x01,0x00,0x00,0xe8,0x01,0x00,0x00, +    0x74,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, +    0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, +    0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, +    0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x32,0x30,0x5f, +    0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x5f,0x32,0x30,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, +    0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, +    0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, +    0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x50,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x03,0x03,0x00,0x00,0x50,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43, +    0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, +    0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, +    0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69, +    0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab,0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00, +    0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00,0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, +    0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00, +    0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03, +    0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00, +    0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08, +    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00, +    0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a, +    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00, +    0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, +    0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, +    0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00, +    0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00, +    0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, +    0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, +    0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00, +    0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00, +    0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, +    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, +    0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +}; +static const uint8_t _sgl_fs_bytecode_hlsl4[620] = { +    0x44,0x58,0x42,0x43,0x4f,0x7a,0xe1,0xcf,0x0c,0x89,0xc0,0x09,0x50,0x5a,0xca,0xe9, +    0x75,0x77,0xd1,0x26,0x01,0x00,0x00,0x00,0x6c,0x02,0x00,0x00,0x05,0x00,0x00,0x00, +    0x34,0x00,0x00,0x00,0xd4,0x00,0x00,0x00,0x20,0x01,0x00,0x00,0x54,0x01,0x00,0x00, +    0xf0,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, +    0x10,0x81,0x00,0x00,0x6d,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x69,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x5f,0x74,0x65,0x78,0x5f,0x73,0x61,0x6d, +    0x70,0x6c,0x65,0x72,0x00,0x74,0x65,0x78,0x00,0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f, +    0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68,0x61,0x64, +    0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30,0x2e,0x31, +    0x00,0xab,0xab,0xab,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00,0x38,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, +    0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65, +    0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0x94,0x00,0x00,0x00,0x40,0x00,0x00,0x00, +    0x25,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00, +    0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00,0x55,0x55,0x00,0x00, +    0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x62,0x10,0x00,0x03, +    0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00, +    0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x45,0x00,0x00,0x09, +    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00, +    0x46,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00, +    0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00, +    0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00,0x3e,0x00,0x00,0x01, +    0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +static const uint8_t _sgl_vs_bytecode_wgpu[1932] = { +    0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x08,0x00,0x08,0x00,0x32,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x06,0x00, +    0x02,0x00,0x00,0x00,0x47,0x4c,0x53,0x4c,0x2e,0x73,0x74,0x64,0x2e,0x34,0x35,0x30, +    0x00,0x00,0x00,0x00,0x0e,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x0f,0x00,0x0d,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e, +    0x00,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x24,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x30,0x00,0x00,0x00, +    0x31,0x00,0x00,0x00,0x07,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x03,0x00,0x37,0x00,0x02,0x00,0x00,0x00,0xc2,0x01,0x00,0x00,0x01,0x00,0x00,0x00, +    0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65, +    0x73,0x73,0x65,0x64,0x20,0x63,0x6c,0x69,0x65,0x6e,0x74,0x20,0x76,0x75,0x6c,0x6b, +    0x61,0x6e,0x31,0x30,0x30,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c, +    0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x63,0x6c,0x69,0x65,0x6e, +    0x74,0x20,0x6f,0x70,0x65,0x6e,0x67,0x6c,0x31,0x30,0x30,0x0a,0x2f,0x2f,0x20,0x4f, +    0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64, +    0x20,0x74,0x61,0x72,0x67,0x65,0x74,0x2d,0x65,0x6e,0x76,0x20,0x76,0x75,0x6c,0x6b, +    0x61,0x6e,0x31,0x2e,0x30,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c, +    0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x74,0x61,0x72,0x67,0x65, +    0x74,0x2d,0x65,0x6e,0x76,0x20,0x6f,0x70,0x65,0x6e,0x67,0x6c,0x0a,0x2f,0x2f,0x20, +    0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65, +    0x64,0x20,0x65,0x6e,0x74,0x72,0x79,0x2d,0x70,0x6f,0x69,0x6e,0x74,0x20,0x6d,0x61, +    0x69,0x6e,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x0a,0x00,0x05,0x00,0x04,0x00, +    0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00, +    0x0c,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x65,0x72,0x56,0x65,0x72,0x74,0x65,0x78, +    0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x06,0x00,0x07,0x00, +    0x0c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74, +    0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x0c,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x67,0x6c,0x5f,0x43,0x6c,0x69,0x70,0x44,0x69,0x73,0x74,0x61, +    0x6e,0x63,0x65,0x00,0x06,0x00,0x07,0x00,0x0c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x67,0x6c,0x5f,0x43,0x75,0x6c,0x6c,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x00, +    0x05,0x00,0x03,0x00,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00, +    0x12,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x00,0x00,0x00, +    0x06,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x76,0x70,0x00, +    0x06,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x74,0x6d,0x00,0x00, +    0x05,0x00,0x03,0x00,0x14,0x00,0x00,0x00,0x5f,0x32,0x30,0x00,0x05,0x00,0x05,0x00, +    0x19,0x00,0x00,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x00,0x00, +    0x05,0x00,0x03,0x00,0x1e,0x00,0x00,0x00,0x75,0x76,0x00,0x00,0x05,0x00,0x05,0x00, +    0x24,0x00,0x00,0x00,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x00,0x00, +    0x05,0x00,0x04,0x00,0x2c,0x00,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x00, +    0x05,0x00,0x04,0x00,0x2d,0x00,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x00,0x00, +    0x05,0x00,0x05,0x00,0x30,0x00,0x00,0x00,0x67,0x6c,0x5f,0x56,0x65,0x72,0x74,0x65, +    0x78,0x49,0x44,0x00,0x05,0x00,0x06,0x00,0x31,0x00,0x00,0x00,0x67,0x6c,0x5f,0x49, +    0x6e,0x73,0x74,0x61,0x6e,0x63,0x65,0x49,0x44,0x00,0x00,0x00,0x48,0x00,0x05,0x00, +    0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x48,0x00,0x05,0x00,0x0c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x0b,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0c,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x47,0x00,0x03,0x00, +    0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x48,0x00,0x04,0x00,0x12,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x12,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00, +    0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x10,0x00,0x00,0x00, +    0x48,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00, +    0x48,0x00,0x05,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x23,0x00,0x00,0x00, +    0x40,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x07,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x12,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x22,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x21,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x19,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x1e,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x24,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x2c,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x2d,0x00,0x00,0x00,0x1e,0x00,0x00,0x00, +    0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x30,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x05,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x31,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x06,0x00,0x00,0x00,0x13,0x00,0x02,0x00,0x03,0x00,0x00,0x00,0x21,0x00,0x03,0x00, +    0x04,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x07,0x00,0x00,0x00, +    0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x07,0x00,0x00,0x00, +    0x04,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x20,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x0a,0x00,0x00,0x00, +    0x01,0x00,0x00,0x00,0x1c,0x00,0x04,0x00,0x0b,0x00,0x00,0x00,0x07,0x00,0x00,0x00, +    0x0a,0x00,0x00,0x00,0x1e,0x00,0x06,0x00,0x0c,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x07,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x0d,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x0d,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x15,0x00,0x04,0x00, +    0x0f,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x0f,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x04,0x00, +    0x11,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x1e,0x00,0x04,0x00, +    0x12,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x13,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x13,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x15,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x18,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x18,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x1c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x1c,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x0f,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x17,0x00,0x04,0x00, +    0x22,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x23,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x23,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x07,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00,0x04,0x00, +    0x07,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,0x3b,0x00,0x04,0x00, +    0x1c,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x18,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0x00, +    0x2f,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x2f,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x3b,0x00,0x04,0x00, +    0x2f,0x00,0x00,0x00,0x31,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x36,0x00,0x05,0x00, +    0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00, +    0xf8,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0x01,0x00,0x00,0x00, +    0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x15,0x00,0x00,0x00, +    0x16,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x11,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x08,0x00,0x00,0x00,0x1a,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x91,0x00,0x05,0x00, +    0x08,0x00,0x00,0x00,0x1b,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x1a,0x00,0x00,0x00, +    0x41,0x00,0x05,0x00,0x1c,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0x0e,0x00,0x00,0x00, +    0x10,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x1d,0x00,0x00,0x00,0x1b,0x00,0x00,0x00, +    0x08,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x41,0x00,0x05,0x00,0x15,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x14,0x00,0x00,0x00, +    0x1f,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x21,0x00,0x00,0x00, +    0x20,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x22,0x00,0x00,0x00,0x25,0x00,0x00,0x00, +    0x24,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x28,0x00,0x00,0x00, +    0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x07,0x00,0x00,0x00, +    0x29,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x50,0x00,0x07,0x00, +    0x08,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x29,0x00,0x00,0x00, +    0x26,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x91,0x00,0x05,0x00,0x08,0x00,0x00,0x00, +    0x2b,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x3e,0x00,0x03,0x00, +    0x1e,0x00,0x00,0x00,0x2b,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0x01,0x00,0x00,0x00, +    0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3d,0x00,0x04,0x00,0x08,0x00,0x00,0x00, +    0x2e,0x00,0x00,0x00,0x2d,0x00,0x00,0x00,0x3e,0x00,0x03,0x00,0x2c,0x00,0x00,0x00, +    0x2e,0x00,0x00,0x00,0xfd,0x00,0x01,0x00,0x38,0x00,0x01,0x00, +}; +static const uint8_t _sgl_fs_bytecode_wgpu[916] = { +    0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x08,0x00,0x08,0x00,0x19,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0b,0x00,0x06,0x00, +    0x02,0x00,0x00,0x00,0x47,0x4c,0x53,0x4c,0x2e,0x73,0x74,0x64,0x2e,0x34,0x35,0x30, +    0x00,0x00,0x00,0x00,0x0e,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x0f,0x00,0x08,0x00,0x04,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e, +    0x00,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x16,0x00,0x00,0x00, +    0x10,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x07,0x00,0x03,0x00, +    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x37,0x00,0x02,0x00,0x00,0x00, +    0xc2,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64, +    0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x63,0x6c,0x69, +    0x65,0x6e,0x74,0x20,0x76,0x75,0x6c,0x6b,0x61,0x6e,0x31,0x30,0x30,0x0a,0x2f,0x2f, +    0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73, +    0x65,0x64,0x20,0x63,0x6c,0x69,0x65,0x6e,0x74,0x20,0x6f,0x70,0x65,0x6e,0x67,0x6c, +    0x31,0x30,0x30,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50, +    0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x74,0x61,0x72,0x67,0x65,0x74,0x2d, +    0x65,0x6e,0x76,0x20,0x76,0x75,0x6c,0x6b,0x61,0x6e,0x31,0x2e,0x30,0x0a,0x2f,0x2f, +    0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65,0x50,0x72,0x6f,0x63,0x65,0x73,0x73, +    0x65,0x64,0x20,0x74,0x61,0x72,0x67,0x65,0x74,0x2d,0x65,0x6e,0x76,0x20,0x6f,0x70, +    0x65,0x6e,0x67,0x6c,0x0a,0x2f,0x2f,0x20,0x4f,0x70,0x4d,0x6f,0x64,0x75,0x6c,0x65, +    0x50,0x72,0x6f,0x63,0x65,0x73,0x73,0x65,0x64,0x20,0x65,0x6e,0x74,0x72,0x79,0x2d, +    0x70,0x6f,0x69,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x0a,0x23,0x6c,0x69,0x6e,0x65, +    0x20,0x31,0x0a,0x00,0x05,0x00,0x04,0x00,0x05,0x00,0x00,0x00,0x6d,0x61,0x69,0x6e, +    0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x0a,0x00,0x00,0x00,0x66,0x72,0x61,0x67, +    0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x05,0x00,0x03,0x00,0x0e,0x00,0x00,0x00, +    0x74,0x65,0x78,0x00,0x05,0x00,0x03,0x00,0x11,0x00,0x00,0x00,0x75,0x76,0x00,0x00, +    0x05,0x00,0x04,0x00,0x16,0x00,0x00,0x00,0x63,0x6f,0x6c,0x6f,0x72,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0a,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x0e,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x47,0x00,0x04,0x00,0x16,0x00,0x00,0x00,0x1e,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x13,0x00,0x02,0x00,0x03,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x04,0x00,0x00,0x00, +    0x03,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x00,0x00, +    0x17,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x04,0x00,0x00,0x00, +    0x20,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x03,0x00,0x00,0x00, +    0x19,0x00,0x09,0x00,0x0b,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x1b,0x00,0x03,0x00,0x0c,0x00,0x00,0x00,0x0b,0x00,0x00,0x00, +    0x20,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x0d,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x20,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x17,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +    0x3b,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +    0x36,0x00,0x05,0x00,0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +    0x04,0x00,0x00,0x00,0xf8,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x08,0x00,0x04,0x00, +    0x01,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x0c,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x08,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x4f,0x00,0x07,0x00, +    0x12,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x13,0x00,0x00,0x00, +    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x57,0x00,0x05,0x00,0x08,0x00,0x00,0x00, +    0x15,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x3d,0x00,0x04,0x00, +    0x08,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x85,0x00,0x05,0x00, +    0x08,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x17,0x00,0x00,0x00, +    0x3e,0x00,0x03,0x00,0x0a,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0xfd,0x00,0x01,0x00, +    0x38,0x00,0x01,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sgl_vs_source_dummy = ""; +static const char* _sgl_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE33, SOKOL_GLES2, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +typedef enum { +    SGL_PRIMITIVETYPE_POINTS = 0, +    SGL_PRIMITIVETYPE_LINES, +    SGL_PRIMITIVETYPE_LINE_STRIP, +    SGL_PRIMITIVETYPE_TRIANGLES, +    SGL_PRIMITIVETYPE_TRIANGLE_STRIP, +    SGL_PRIMITIVETYPE_QUADS, +    SGL_NUM_PRIMITIVE_TYPES, +} _sgl_primitive_type_t; + +typedef struct { +    uint32_t id; +    sg_resource_state state; +} _sgl_slot_t; + +typedef struct { +    int size; +    int queue_top; +    uint32_t* gen_ctrs; +    int* free_queue; +} _sgl_pool_t; + +typedef struct { +    _sgl_slot_t slot; +    sg_pipeline pip[SGL_NUM_PRIMITIVE_TYPES]; +} _sgl_pipeline_t; + +typedef struct { +    _sgl_pool_t pool; +    _sgl_pipeline_t* pips; +} _sgl_pipeline_pool_t; + +typedef enum { +    SGL_MATRIXMODE_MODELVIEW, +    SGL_MATRIXMODE_PROJECTION, +    SGL_MATRIXMODE_TEXTURE, +    SGL_NUM_MATRIXMODES +} _sgl_matrix_mode_t; + +typedef struct { +    float pos[3]; +    float uv[2]; +    uint32_t rgba; +} _sgl_vertex_t; + +typedef struct { +    float v[4][4]; +} _sgl_matrix_t; + +typedef struct { +    _sgl_matrix_t mvp;  /* model-view-projection matrix */ +    _sgl_matrix_t tm;   /* texture matrix */ +} _sgl_uniform_t; + +typedef enum { +    SGL_COMMAND_DRAW, +    SGL_COMMAND_VIEWPORT, +    SGL_COMMAND_SCISSOR_RECT, +} _sgl_command_type_t; + +typedef struct { +    sg_pipeline pip; +    sg_image img; +    int base_vertex; +    int num_vertices; +    int uniform_index; +} _sgl_draw_args_t; + +typedef struct { +    int x, y, w, h; +    bool origin_top_left; +} _sgl_viewport_args_t; + +typedef struct { +    int x, y, w, h; +    bool origin_top_left; +} _sgl_scissor_rect_args_t; + +typedef union { +    _sgl_draw_args_t draw; +    _sgl_viewport_args_t viewport; +    _sgl_scissor_rect_args_t scissor_rect; +} _sgl_args_t; + +typedef struct { +    _sgl_command_type_t cmd; +    _sgl_args_t args; +} _sgl_command_t; + +#define _SGL_INVALID_SLOT_INDEX (0) +#define _SGL_MAX_STACK_DEPTH (64) +#define _SGL_DEFAULT_PIPELINE_POOL_SIZE (64) +#define _SGL_DEFAULT_MAX_VERTICES (1<<16) +#define _SGL_DEFAULT_MAX_COMMANDS (1<<14) +#define _SGL_SLOT_SHIFT (16) +#define _SGL_MAX_POOL_SIZE (1<<_SGL_SLOT_SHIFT) +#define _SGL_SLOT_MASK (_SGL_MAX_POOL_SIZE-1) + +typedef struct { +    uint32_t init_cookie; +    sgl_desc_t desc; + +    int num_vertices; +    int num_uniforms; +    int num_commands; +    int cur_vertex; +    int cur_uniform; +    int cur_command; +    _sgl_vertex_t* vertices; +    _sgl_uniform_t* uniforms; +    _sgl_command_t* commands; + +    /* state tracking */ +    int base_vertex; +    int vtx_count;          /* number of times vtx function has been called, used for non-triangle primitives */ +    sgl_error_t error; +    bool in_begin; +    float u, v; +    uint32_t rgba; +    _sgl_primitive_type_t cur_prim_type; +    sg_image cur_img; +    bool texturing_enabled; +    bool matrix_dirty;      /* reset in sgl_end(), set in any of the matrix stack functions */ + +    /* sokol-gfx resources */ +    sg_buffer vbuf; +    sg_image def_img;   /* a default white texture */ +    sg_shader shd; +    sg_bindings bind; +    sgl_pipeline def_pip; +    _sgl_pipeline_pool_t pip_pool; + +    /* pipeline stack */ +    int pip_tos; +    sgl_pipeline pip_stack[_SGL_MAX_STACK_DEPTH]; + +    /* matrix stacks */ +    _sgl_matrix_mode_t cur_matrix_mode; +    int matrix_tos[SGL_NUM_MATRIXMODES]; +    _sgl_matrix_t matrix_stack[SGL_NUM_MATRIXMODES][_SGL_MAX_STACK_DEPTH]; +} _sgl_t; +static _sgl_t _sgl; + +/*== PRIVATE FUNCTIONS =======================================================*/ + +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 */ +    pool->size = num + 1; +    pool->queue_top = 0; +    /* generation counters indexable by pool slot index, slot 0 is reserved */ +    size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; +    pool->gen_ctrs = (uint32_t*) SOKOL_MALLOC(gen_ctrs_size); +    SOKOL_ASSERT(pool->gen_ctrs); +    memset(pool->gen_ctrs, 0, gen_ctrs_size); +    /* it's not a bug to only reserve 'num' here */ +    pool->free_queue = (int*) SOKOL_MALLOC(sizeof(int) * (size_t)num); +    SOKOL_ASSERT(pool->free_queue); +    /* never allocate the zero-th pool item since the invalid id is 0 */ +    for (int i = pool->size-1; i >= 1; i--) { +        pool->free_queue[pool->queue_top++] = i; +    } +} + +static void _sgl_discard_pool(_sgl_pool_t* pool) { +    SOKOL_ASSERT(pool); +    SOKOL_ASSERT(pool->free_queue); +    SOKOL_FREE(pool->free_queue); +    pool->free_queue = 0; +    SOKOL_ASSERT(pool->gen_ctrs); +    SOKOL_FREE(pool->gen_ctrs); +    pool->gen_ctrs = 0; +    pool->size = 0; +    pool->queue_top = 0; +} + +static int _sgl_pool_alloc_index(_sgl_pool_t* pool) { +    SOKOL_ASSERT(pool); +    SOKOL_ASSERT(pool->free_queue); +    if (pool->queue_top > 0) { +        int slot_index = pool->free_queue[--pool->queue_top]; +        SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); +        return slot_index; +    } +    else { +        /* pool exhausted */ +        return _SGL_INVALID_SLOT_INDEX; +    } +} + +static void _sgl_pool_free_index(_sgl_pool_t* pool, int slot_index) { +    SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < pool->size)); +    SOKOL_ASSERT(pool); +    SOKOL_ASSERT(pool->free_queue); +    SOKOL_ASSERT(pool->queue_top < pool->size); +    #ifdef SOKOL_DEBUG +    /* debug check against double-free */ +    for (int i = 0; i < pool->queue_top; i++) { +        SOKOL_ASSERT(pool->free_queue[i] != slot_index); +    } +    #endif +    pool->free_queue[pool->queue_top++] = slot_index; +    SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +static void _sgl_reset_pipeline(_sgl_pipeline_t* pip) { +    SOKOL_ASSERT(pip); +    memset(pip, 0, sizeof(_sgl_pipeline_t)); +} + +static void _sgl_setup_pipeline_pool(const sgl_desc_t* desc) { +    SOKOL_ASSERT(desc); +    /* note: the pools here will have an additional item, since slot 0 is reserved */ +    SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SGL_MAX_POOL_SIZE)); +    _sgl_init_pool(&_sgl.pip_pool.pool, desc->pipeline_pool_size); +    size_t pool_byte_size = sizeof(_sgl_pipeline_t) * (size_t)_sgl.pip_pool.pool.size; +    _sgl.pip_pool.pips = (_sgl_pipeline_t*) SOKOL_MALLOC(pool_byte_size); +    SOKOL_ASSERT(_sgl.pip_pool.pips); +    memset(_sgl.pip_pool.pips, 0, pool_byte_size); +} + +static void _sgl_discard_pipeline_pool(void) { +    SOKOL_FREE(_sgl.pip_pool.pips); _sgl.pip_pool.pips = 0; +    _sgl_discard_pool(&_sgl.pip_pool.pool); +} + +/* allocate the slot at slot_index: +    - bump the slot's generation counter +    - create a resource id from the generation counter and slot index +    - set the slot's id to this id +    - set the slot's state to ALLOC +    - return the resource id +*/ +static uint32_t _sgl_slot_alloc(_sgl_pool_t* pool, _sgl_slot_t* slot, int slot_index) { +    /* FIXME: add handling for an overflowing generation counter, +       for now, just overflow (another option is to disable +       the slot) +    */ +    SOKOL_ASSERT(pool && pool->gen_ctrs); +    SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < pool->size)); +    SOKOL_ASSERT((slot->state == SG_RESOURCESTATE_INITIAL) && (slot->id == SG_INVALID_ID)); +    uint32_t ctr = ++pool->gen_ctrs[slot_index]; +    slot->id = (ctr<<_SGL_SLOT_SHIFT)|(slot_index & _SGL_SLOT_MASK); +    slot->state = SG_RESOURCESTATE_ALLOC; +    return slot->id; +} + +/* extract slot index from id */ +static int _sgl_slot_index(uint32_t id) { +    int slot_index = (int) (id & _SGL_SLOT_MASK); +    SOKOL_ASSERT(_SGL_INVALID_SLOT_INDEX != slot_index); +    return slot_index; +} + +/* get pipeline pointer without id-check */ +static _sgl_pipeline_t* _sgl_pipeline_at(uint32_t pip_id) { +    SOKOL_ASSERT(SG_INVALID_ID != pip_id); +    int slot_index = _sgl_slot_index(pip_id); +    SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < _sgl.pip_pool.pool.size)); +    return &_sgl.pip_pool.pips[slot_index]; +} + +/* get pipeline pointer with id-check, returns 0 if no match */ +static _sgl_pipeline_t* _sgl_lookup_pipeline(uint32_t pip_id) { +    if (SG_INVALID_ID != pip_id) { +        _sgl_pipeline_t* pip = _sgl_pipeline_at(pip_id); +        if (pip->slot.id == pip_id) { +            return pip; +        } +    } +    return 0; +} + +/* make pipeline id from uint32_t id */ +static sgl_pipeline _sgl_make_pip_id(uint32_t pip_id) { +    sgl_pipeline pip; +    pip.id = pip_id; +    return pip; +} + +static sgl_pipeline _sgl_alloc_pipeline(void) { +    sgl_pipeline res; +    int slot_index = _sgl_pool_alloc_index(&_sgl.pip_pool.pool); +    if (_SGL_INVALID_SLOT_INDEX != slot_index) { +        res = _sgl_make_pip_id(_sgl_slot_alloc(&_sgl.pip_pool.pool, &_sgl.pip_pool.pips[slot_index].slot, slot_index)); +    } +    else { +        /* pool is exhausted */ +        res = _sgl_make_pip_id(SG_INVALID_ID); +    } +    return res; +} + +static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_desc) { +    SOKOL_ASSERT((pip_id.id != SG_INVALID_ID) && in_desc); + +    /* create a new desc with 'patched' shader and pixel format state */ +    sg_pipeline_desc desc = *in_desc; +    desc.layout.buffers[0].stride = sizeof(_sgl_vertex_t); +    { +        sg_vertex_attr_desc* pos = &desc.layout.attrs[0]; +        pos->offset = offsetof(_sgl_vertex_t, pos); +        pos->format = SG_VERTEXFORMAT_FLOAT3; +    } +    { +        sg_vertex_attr_desc* uv = &desc.layout.attrs[1]; +        uv->offset = offsetof(_sgl_vertex_t, uv); +        uv->format = SG_VERTEXFORMAT_FLOAT2; +    } +    { +        sg_vertex_attr_desc* rgba = &desc.layout.attrs[2]; +        rgba->offset = offsetof(_sgl_vertex_t, rgba); +        rgba->format = SG_VERTEXFORMAT_UBYTE4N; +    } +    if (in_desc->shader.id == SG_INVALID_ID) { +        desc.shader = _sgl.shd; +    } +    desc.index_type = SG_INDEXTYPE_NONE; +    desc.sample_count = _sgl.desc.sample_count; +    if (desc.face_winding == _SG_FACEWINDING_DEFAULT) { +        desc.face_winding = _sgl.desc.face_winding; +    } +    desc.depth.pixel_format = _sgl.desc.depth_format; +    desc.colors[0].pixel_format = _sgl.desc.color_format; +    if (desc.colors[0].write_mask == _SG_COLORMASK_DEFAULT) { +        desc.colors[0].write_mask = SG_COLORMASK_RGB; +    } + +    _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); +    SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); +    pip->slot.state = SG_RESOURCESTATE_VALID; +    for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { +        switch (i) { +            case SGL_PRIMITIVETYPE_POINTS: +                desc.primitive_type = SG_PRIMITIVETYPE_POINTS; +                break; +            case SGL_PRIMITIVETYPE_LINES: +                desc.primitive_type = SG_PRIMITIVETYPE_LINES; +                break; +            case SGL_PRIMITIVETYPE_LINE_STRIP: +                desc.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP; +                break; +            case SGL_PRIMITIVETYPE_TRIANGLES: +                desc.primitive_type = SG_PRIMITIVETYPE_TRIANGLES; +                break; +            case SGL_PRIMITIVETYPE_TRIANGLE_STRIP: +            case SGL_PRIMITIVETYPE_QUADS: +                desc.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP; +                break; +        } +        if (SGL_PRIMITIVETYPE_QUADS == i) { +            /* quads are emulated via triangles, use the same pipeline object */ +            pip->pip[i] = pip->pip[SGL_PRIMITIVETYPE_TRIANGLES]; +        } +        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"); +                pip->slot.state = SG_RESOURCESTATE_FAILED; +            } +        } +    } +} + +static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(desc); +    sgl_pipeline pip_id = _sgl_alloc_pipeline(); +    if (pip_id.id != SG_INVALID_ID) { +        _sgl_init_pipeline(pip_id, desc); +    } +    else { +        SOKOL_LOG("sokol_gl.h: pipeline pool exhausted!"); +    } +    return pip_id; +} + +static void _sgl_destroy_pipeline(sgl_pipeline pip_id) { +    _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); +    if (pip) { +        for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { +            if (i != SGL_PRIMITIVETYPE_QUADS) { +                sg_destroy_pipeline(pip->pip[i]); +            } +        } +        _sgl_reset_pipeline(pip); +        _sgl_pool_free_index(&_sgl.pip_pool.pool, _sgl_slot_index(pip_id.id)); +    } +} + +static sg_pipeline _sgl_get_pipeline(sgl_pipeline pip_id, _sgl_primitive_type_t prim_type) { +    _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); +    if (pip) { +        return pip->pip[prim_type]; +    } +    else { +        sg_pipeline dummy_id = { SG_INVALID_ID }; +        return dummy_id; +    } +} + +static inline void _sgl_begin(_sgl_primitive_type_t mode) { +    _sgl.in_begin = true; +    _sgl.base_vertex = _sgl.cur_vertex; +    _sgl.vtx_count = 0; +    _sgl.cur_prim_type = mode; +} + +static void _sgl_rewind(void) { +    _sgl.base_vertex = 0; +    _sgl.cur_vertex = 0; +    _sgl.cur_uniform = 0; +    _sgl.cur_command = 0; +    _sgl.error = SGL_NO_ERROR; +    _sgl.matrix_dirty = true; +} + +static inline _sgl_vertex_t* _sgl_next_vertex(void) { +    if (_sgl.cur_vertex < _sgl.num_vertices) { +        return &_sgl.vertices[_sgl.cur_vertex++]; +    } +    else { +        _sgl.error = SGL_ERROR_VERTICES_FULL; +        return 0; +    } +} + +static inline _sgl_uniform_t* _sgl_next_uniform(void) { +    if (_sgl.cur_uniform < _sgl.num_uniforms) { +        return &_sgl.uniforms[_sgl.cur_uniform++]; +    } +    else { +        _sgl.error = SGL_ERROR_UNIFORMS_FULL; +        return 0; +    } +} + +static inline _sgl_command_t* _sgl_prev_command(void) { +    if (_sgl.cur_command > 0) { +        return &_sgl.commands[_sgl.cur_command - 1]; +    } +    else { +        return 0; +    } +} + +static inline _sgl_command_t* _sgl_next_command(void) { +    if (_sgl.cur_command < _sgl.num_commands) { +        return &_sgl.commands[_sgl.cur_command++]; +    } +    else { +        _sgl.error = SGL_ERROR_COMMANDS_FULL; +        return 0; +    } +} + +static inline uint32_t _sgl_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    return (uint32_t)(((uint32_t)a<<24)|((uint32_t)b<<16)|((uint32_t)g<<8)|r); +} + +static inline float _sgl_clamp(float v, float lo, float hi) { +    if (v < lo) return lo; +    else if (v > hi) return hi; +    else return v; +} + +static inline uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) { +    uint8_t r_u8 = (uint8_t) (_sgl_clamp(r, 0.0f, 1.0f) * 255.0f); +    uint8_t g_u8 = (uint8_t) (_sgl_clamp(g, 0.0f, 1.0f) * 255.0f); +    uint8_t b_u8 = (uint8_t) (_sgl_clamp(b, 0.0f, 1.0f) * 255.0f); +    uint8_t a_u8 = (uint8_t) (_sgl_clamp(a, 0.0f, 1.0f) * 255.0f); +    return _sgl_pack_rgbab(r_u8, g_u8, b_u8, a_u8); +} + +static inline void _sgl_vtx(float x, float y, float z, float u, float v, uint32_t rgba) { +    SOKOL_ASSERT(_sgl.in_begin); +    _sgl_vertex_t* vtx; +    /* handle non-native primitive types */ +    if ((_sgl.cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((_sgl.vtx_count & 3) == 3)) { +        /* for quads, before writing the last quad vertex, reuse +           the first and third vertex to start the second triangle in the quad +        */ +        vtx = _sgl_next_vertex(); +        if (vtx) { *vtx = *(vtx - 3); } +        vtx = _sgl_next_vertex(); +        if (vtx) { *vtx = *(vtx - 2); } +    } +    vtx = _sgl_next_vertex(); +    if (vtx) { +        vtx->pos[0] = x; vtx->pos[1] = y; vtx->pos[2] = z; +        vtx->uv[0] = u; vtx->uv[1] = v; +        vtx->rgba = rgba; +    } +    _sgl.vtx_count++; +} + +static void _sgl_identity(_sgl_matrix_t* m) { +    for (int c = 0; c < 4; c++) { +        for (int r = 0; r < 4; r++) { +            m->v[c][r] = (r == c) ? 1.0f : 0.0f; +        } +    } +} + +static void _sgl_transpose(_sgl_matrix_t* dst, const _sgl_matrix_t* m) { +    SOKOL_ASSERT(dst != m); +    for (int c = 0; c < 4; c++) { +        for (int r = 0; r < 4; r++) { +            dst->v[r][c] = m->v[c][r]; +        } +    } +} + +/* _sgl_rotate, _sgl_frustum, _sgl_ortho from MESA m_matric.c */ +static void _sgl_matmul4(_sgl_matrix_t* p, const _sgl_matrix_t* a, const _sgl_matrix_t* b) { +    for (int r = 0; r < 4; r++) { +        float ai0=a->v[0][r], ai1=a->v[1][r], ai2=a->v[2][r], ai3=a->v[3][r]; +        p->v[0][r] = ai0*b->v[0][0] + ai1*b->v[0][1] + ai2*b->v[0][2] + ai3*b->v[0][3]; +        p->v[1][r] = ai0*b->v[1][0] + ai1*b->v[1][1] + ai2*b->v[1][2] + ai3*b->v[1][3]; +        p->v[2][r] = ai0*b->v[2][0] + ai1*b->v[2][1] + ai2*b->v[2][2] + ai3*b->v[2][3]; +        p->v[3][r] = ai0*b->v[3][0] + ai1*b->v[3][1] + ai2*b->v[3][2] + ai3*b->v[3][3]; +    } +} + +static void _sgl_mul(_sgl_matrix_t* dst, const _sgl_matrix_t* m) { +    _sgl_matmul4(dst, dst, m); +} + +static void _sgl_rotate(_sgl_matrix_t* dst, float a, float x, float y, float z) { + +    float s = sinf(a); +    float c = cosf(a); + +    float mag = sqrtf(x*x + y*y + z*z); +    if (mag < 1.0e-4F) { +        return; +    } +    x /= mag; +    y /= mag; +    z /= mag; +    float xx = x * x; +    float yy = y * y; +    float zz = z * z; +    float xy = x * y; +    float yz = y * z; +    float zx = z * x; +    float xs = x * s; +    float ys = y * s; +    float zs = z * s; +    float one_c = 1.0f - c; + +    _sgl_matrix_t m; +    m.v[0][0] = (one_c * xx) + c; +    m.v[1][0] = (one_c * xy) - zs; +    m.v[2][0] = (one_c * zx) + ys; +    m.v[3][0] = 0.0f; +    m.v[0][1] = (one_c * xy) + zs; +    m.v[1][1] = (one_c * yy) + c; +    m.v[2][1] = (one_c * yz) - xs; +    m.v[3][1] = 0.0f; +    m.v[0][2] = (one_c * zx) - ys; +    m.v[1][2] = (one_c * yz) + xs; +    m.v[2][2] = (one_c * zz) + c; +    m.v[3][2] = 0.0f; +    m.v[0][3] = 0.0f; +    m.v[1][3] = 0.0f; +    m.v[2][3] = 0.0f; +    m.v[3][3] = 1.0f; +    _sgl_mul(dst, &m); +} + +static void _sgl_scale(_sgl_matrix_t* dst, float x, float y, float z) { +    for (int r = 0; r < 4; r++) { +        dst->v[0][r] *= x; +        dst->v[1][r] *= y; +        dst->v[2][r] *= z; +    } +} + +static void _sgl_translate(_sgl_matrix_t* dst, float x, float y, float z) { +    for (int r = 0; r < 4; r++) { +        dst->v[3][r] = dst->v[0][r]*x + dst->v[1][r]*y + dst->v[2][r]*z + dst->v[3][r]; +    } +} + +static void _sgl_frustum(_sgl_matrix_t* dst, float left, float right, float bottom, float top, float znear, float zfar) { +    float x = (2.0f * znear) / (right - left); +    float y = (2.0f * znear) / (top - bottom); +    float a = (right + left) / (right - left); +    float b = (top + bottom) / (top - bottom); +    float c = -(zfar + znear) / (zfar - znear); +    float d = -(2.0f * zfar * znear) / (zfar - znear); +    _sgl_matrix_t m; +    m.v[0][0] = x;    m.v[0][1] = 0.0f; m.v[0][2] = 0.0f; m.v[0][3] = 0.0f; +    m.v[1][0] = 0.0f; m.v[1][1] = y;    m.v[1][2] = 0.0f; m.v[1][3] = 0.0f; +    m.v[2][0] = a;    m.v[2][1] = b;    m.v[2][2] = c;    m.v[2][3] = -1.0f; +    m.v[3][0] = 0.0f; m.v[3][1] = 0.0f; m.v[3][2] = d;    m.v[3][3] = 0.0f; +    _sgl_mul(dst, &m); +} + +static void _sgl_ortho(_sgl_matrix_t* dst, float left, float right, float bottom, float top, float znear, float zfar) { +    _sgl_matrix_t m; +    m.v[0][0] = 2.0f / (right - left); +    m.v[1][0] = 0.0f; +    m.v[2][0] = 0.0f; +    m.v[3][0] = -(right + left) / (right - left); +    m.v[0][1] = 0.0f; +    m.v[1][1] = 2.0f / (top - bottom); +    m.v[2][1] = 0.0f; +    m.v[3][1] = -(top + bottom) / (top - bottom); +    m.v[0][2] = 0.0f; +    m.v[1][2] = 0.0f; +    m.v[2][2] = -2.0f / (zfar - znear); +    m.v[3][2] = -(zfar + znear) / (zfar - znear); +    m.v[0][3] = 0.0f; +    m.v[1][3] = 0.0f; +    m.v[2][3] = 0.0f; +    m.v[3][3] = 1.0f; + +    _sgl_mul(dst, &m); +} + +/* _sgl_perspective, _sgl_lookat from Regal project.c */ +static void _sgl_perspective(_sgl_matrix_t* dst, float fovy, float aspect, float znear, float zfar) { +    float sine = sinf(fovy / 2.0f); +    float delta_z = zfar - znear; +    if ((delta_z == 0.0f) || (sine == 0.0f) || (aspect == 0.0f)) { +        return; +    } +    float cotan = cosf(fovy / 2.0f) / sine; +    _sgl_matrix_t m; +    _sgl_identity(&m); +    m.v[0][0] = cotan / aspect; +    m.v[1][1] = cotan; +    m.v[2][2] = -(zfar + znear) / delta_z; +    m.v[2][3] = -1.0f; +    m.v[3][2] = -2.0f * znear * zfar / delta_z; +    m.v[3][3] = 0.0f; +    _sgl_mul(dst, &m); +} + +static void _sgl_normalize(float v[3]) { +    float r = sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); +    if (r == 0.0f) { +        return; +    } +    v[0] /= r; +    v[1] /= r; +    v[2] /= r; +} + +static void _sgl_cross(float v1[3], float v2[3], float res[3]) { +    res[0] = v1[1]*v2[2] - v1[2]*v2[1]; +    res[1] = v1[2]*v2[0] - v1[0]*v2[2]; +    res[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +static void _sgl_lookat(_sgl_matrix_t* dst, +                        float eye_x, float eye_y, float eye_z, +                        float center_x, float center_y, float center_z, +                        float up_x, float up_y, float up_z) +{ +    float fwd[3], side[3], up[3]; + +    fwd[0] = center_x - eye_x; fwd[1] = center_y - eye_y; fwd[2] = center_z - eye_z; +    up[0] = up_x; up[1] = up_y; up[2] = up_z; +    _sgl_normalize(fwd); +    _sgl_cross(fwd, up, side); +    _sgl_normalize(side); +    _sgl_cross(side, fwd, up); + +    _sgl_matrix_t m; +    _sgl_identity(&m); +    m.v[0][0] = side[0]; +    m.v[1][0] = side[1]; +    m.v[2][0] = side[2]; +    m.v[0][1] = up[0]; +    m.v[1][1] = up[1]; +    m.v[2][1] = up[2]; +    m.v[0][2] = -fwd[0]; +    m.v[1][2] = -fwd[1]; +    m.v[2][2] = -fwd[2]; +    _sgl_mul(dst, &m); +    _sgl_translate(dst, -eye_x, -eye_y, -eye_z); +} + +/* current top-of-stack projection matrix */ +static inline _sgl_matrix_t* _sgl_matrix_projection(void) { +    return &_sgl.matrix_stack[SGL_MATRIXMODE_PROJECTION][_sgl.matrix_tos[SGL_MATRIXMODE_PROJECTION]]; +} + +/* get top-of-stack modelview matrix */ +static inline _sgl_matrix_t* _sgl_matrix_modelview(void) { +    return &_sgl.matrix_stack[SGL_MATRIXMODE_MODELVIEW][_sgl.matrix_tos[SGL_MATRIXMODE_MODELVIEW]]; +} + +/* get top-of-stack texture matrix */ +static inline _sgl_matrix_t* _sgl_matrix_texture(void) { +    return &_sgl.matrix_stack[SGL_MATRIXMODE_TEXTURE][_sgl.matrix_tos[SGL_MATRIXMODE_TEXTURE]]; +} + +/* get pointer to current top-of-stack of current matrix mode */ +static inline _sgl_matrix_t* _sgl_matrix(void) { +    return &_sgl.matrix_stack[_sgl.cur_matrix_mode][_sgl.matrix_tos[_sgl.cur_matrix_mode]]; +} + +/*== PUBLIC FUNCTIONS ========================================================*/ +SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { +    SOKOL_ASSERT(desc); +    memset(&_sgl, 0, sizeof(_sgl)); +    _sgl.init_cookie = _SGL_INIT_COOKIE; +    _sgl.desc = *desc; +    _sgl.desc.pipeline_pool_size = _sgl_def(_sgl.desc.pipeline_pool_size, _SGL_DEFAULT_PIPELINE_POOL_SIZE); +    _sgl.desc.max_vertices = _sgl_def(_sgl.desc.max_vertices, _SGL_DEFAULT_MAX_VERTICES); +    _sgl.desc.max_commands = _sgl_def(_sgl.desc.max_commands, _SGL_DEFAULT_MAX_COMMANDS); +    _sgl.desc.face_winding = _sgl_def(_sgl.desc.face_winding, SG_FACEWINDING_CCW); + +    /* allocate buffers and pools */ +    _sgl.num_vertices = _sgl.desc.max_vertices; +    _sgl.num_uniforms = _sgl.desc.max_commands; +    _sgl.num_commands = _sgl.num_uniforms; +    _sgl.vertices = (_sgl_vertex_t*) SOKOL_MALLOC((size_t)_sgl.num_vertices * sizeof(_sgl_vertex_t)); +    SOKOL_ASSERT(_sgl.vertices); +    _sgl.uniforms = (_sgl_uniform_t*) SOKOL_MALLOC((size_t)_sgl.num_uniforms * sizeof(_sgl_uniform_t)); +    SOKOL_ASSERT(_sgl.uniforms); +    _sgl.commands = (_sgl_command_t*) SOKOL_MALLOC((size_t)_sgl.num_commands * sizeof(_sgl_command_t)); +    SOKOL_ASSERT(_sgl.commands); +    _sgl_setup_pipeline_pool(&_sgl.desc); + +    /* create sokol-gfx resource objects */ +    sg_push_debug_group("sokol-gl"); + +    sg_buffer_desc vbuf_desc; +    memset(&vbuf_desc, 0, sizeof(vbuf_desc)); +    vbuf_desc.size = (size_t)_sgl.num_vertices * sizeof(_sgl_vertex_t); +    vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; +    vbuf_desc.usage = SG_USAGE_STREAM; +    vbuf_desc.label = "sgl-vertex-buffer"; +    _sgl.vbuf = sg_make_buffer(&vbuf_desc); +    SOKOL_ASSERT(SG_INVALID_ID != _sgl.vbuf.id); + +    uint32_t pixels[64]; +    for (int i = 0; i < 64; i++) { +        pixels[i] = 0xFFFFFFFF; +    } +    sg_image_desc img_desc; +    memset(&img_desc, 0, sizeof(img_desc)); +    img_desc.type = SG_IMAGETYPE_2D; +    img_desc.width = 8; +    img_desc.height = 8; +    img_desc.num_mipmaps = 1; +    img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; +    img_desc.min_filter = SG_FILTER_NEAREST; +    img_desc.mag_filter = SG_FILTER_NEAREST; +    img_desc.data.subimage[0][0] = SG_RANGE(pixels); +    img_desc.label = "sgl-default-texture"; +    _sgl.def_img = sg_make_image(&img_desc); +    SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_img.id); +    _sgl.cur_img = _sgl.def_img; + +    sg_shader_desc shd_desc; +    memset(&shd_desc, 0, sizeof(shd_desc)); +    shd_desc.attrs[0].name = "position"; +    shd_desc.attrs[1].name = "texcoord0"; +    shd_desc.attrs[2].name = "color0"; +    shd_desc.attrs[0].sem_name = "TEXCOORD"; +    shd_desc.attrs[0].sem_index = 0; +    shd_desc.attrs[1].sem_name = "TEXCOORD"; +    shd_desc.attrs[1].sem_index = 1; +    shd_desc.attrs[2].sem_name = "TEXCOORD"; +    shd_desc.attrs[2].sem_index = 2; +    sg_shader_uniform_block_desc* ub = &shd_desc.vs.uniform_blocks[0]; +    ub->size = sizeof(_sgl_uniform_t); +    ub->uniforms[0].name = "vs_params"; +    ub->uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; +    ub->uniforms[0].array_count = 8; +    shd_desc.fs.images[0].name = "tex"; +    shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; +    shd_desc.fs.images[0].sampler_type = SG_SAMPLERTYPE_FLOAT; +    shd_desc.label = "sgl-shader"; +    #if defined(SOKOL_GLCORE33) +        shd_desc.vs.source = _sgl_vs_source_glsl330; +        shd_desc.fs.source = _sgl_fs_source_glsl330; +    #elif defined(SOKOL_GLES2) || defined(SOKOL_GLES3) +        shd_desc.vs.source = _sgl_vs_source_glsl100; +        shd_desc.fs.source = _sgl_fs_source_glsl100; +    #elif defined(SOKOL_METAL) +        shd_desc.vs.entry = "main0"; +        shd_desc.fs.entry = "main0"; +        switch (sg_query_backend()) { +            case SG_BACKEND_METAL_MACOS: +                shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_macos); +                shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_macos); +                break; +            case SG_BACKEND_METAL_IOS: +                shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_ios); +                shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_ios); +                break; +            default: +                shd_desc.vs.source = _sgl_vs_source_metal_sim; +                shd_desc.fs.source = _sgl_fs_source_metal_sim; +                break; +        } +    #elif defined(SOKOL_D3D11) +        shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_hlsl4); +        shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_hlsl4); +    #elif defined(SOKOL_WGPU) +        shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_wgpu); +        shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_wgpu); +    #else +        shd_desc.vs.source = _sgl_vs_src_dummy; +        shd_desc.fs.source = _sgl_fs_src_dummy; +    #endif +    _sgl.shd = sg_make_shader(&shd_desc); +    SOKOL_ASSERT(SG_INVALID_ID != _sgl.shd.id); + +    /* create default pipeline object */ +    sg_pipeline_desc def_pip_desc; +    memset(&def_pip_desc, 0, sizeof(def_pip_desc)); +    def_pip_desc.depth.write_enabled = true; +    _sgl.def_pip = _sgl_make_pipeline(&def_pip_desc); +    sg_pop_debug_group(); + +    /* default state */ +    _sgl.rgba = 0xFFFFFFFF; +    for (int i = 0; i < SGL_NUM_MATRIXMODES; i++) { +        _sgl_identity(&_sgl.matrix_stack[i][0]); +    } +    _sgl.pip_stack[0] = _sgl.def_pip; +    _sgl.matrix_dirty = true; +} + +SOKOL_API_IMPL void sgl_shutdown(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_FREE(_sgl.vertices); _sgl.vertices = 0; +    SOKOL_FREE(_sgl.uniforms); _sgl.uniforms = 0; +    SOKOL_FREE(_sgl.commands); _sgl.commands = 0; +    sg_push_debug_group("sokol-gl"); +    sg_destroy_buffer(_sgl.vbuf); +    sg_destroy_image(_sgl.def_img); +    sg_destroy_shader(_sgl.shd); +    for (int i = 0; i < _sgl.pip_pool.pool.size; i++) { +        _sgl_pipeline_t* pip = &_sgl.pip_pool.pips[i]; +        _sgl_destroy_pipeline(_sgl_make_pip_id(pip->slot.id)); +    } +    sg_pop_debug_group(); +    _sgl_discard_pipeline_pool(); +    _sgl.init_cookie = 0; +} + +SOKOL_API_IMPL sgl_error_t sgl_error(void) { +    return _sgl.error; +} + +SOKOL_API_IMPL float sgl_rad(float deg) { +    return (deg * (float)M_PI) / 180.0f; +} + +SOKOL_API_IMPL float sgl_deg(float rad) { +    return (rad * 180.0f) / (float)M_PI; +} + +SOKOL_API_IMPL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    return _sgl_make_pipeline(desc); +} + +SOKOL_API_IMPL void sgl_destroy_pipeline(sgl_pipeline pip_id) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl_destroy_pipeline(pip_id); +} + +SOKOL_API_IMPL void sgl_load_pipeline(sgl_pipeline pip_id) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT((_sgl.pip_tos >= 0) && (_sgl.pip_tos < _SGL_MAX_STACK_DEPTH)); +    _sgl.pip_stack[_sgl.pip_tos] = pip_id; +} + +SOKOL_API_IMPL void sgl_default_pipeline(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT((_sgl.pip_tos >= 0) && (_sgl.pip_tos < _SGL_MAX_STACK_DEPTH)); +    _sgl.pip_stack[_sgl.pip_tos] = _sgl.def_pip; +} + +SOKOL_API_IMPL void sgl_push_pipeline(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    if (_sgl.pip_tos < (_SGL_MAX_STACK_DEPTH - 1)) { +        _sgl.pip_tos++; +        _sgl.pip_stack[_sgl.pip_tos] = _sgl.pip_stack[_sgl.pip_tos-1]; +    } +    else { +        _sgl.error = SGL_ERROR_STACK_OVERFLOW; +    } +} + +SOKOL_API_IMPL void sgl_pop_pipeline(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    if (_sgl.pip_tos > 0) { +        _sgl.pip_tos--; +    } +    else { +        _sgl.error = SGL_ERROR_STACK_UNDERFLOW; +    } +} + +SOKOL_API_IMPL void sgl_defaults(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl.u = 0.0f; _sgl.v = 0.0f; +    _sgl.rgba = 0xFFFFFFFF; +    _sgl.texturing_enabled = false; +    _sgl.cur_img = _sgl.def_img; +    sgl_default_pipeline(); +    _sgl_identity(_sgl_matrix_texture()); +    _sgl_identity(_sgl_matrix_modelview()); +    _sgl_identity(_sgl_matrix_projection()); +    _sgl.cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; +    _sgl.matrix_dirty = true; +} + +SOKOL_API_IMPL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_command_t* cmd = _sgl_next_command(); +    if (cmd) { +        cmd->cmd = SGL_COMMAND_VIEWPORT; +        cmd->args.viewport.x = x; +        cmd->args.viewport.y = y; +        cmd->args.viewport.w = w; +        cmd->args.viewport.h = h; +        cmd->args.viewport.origin_top_left = origin_top_left; +    } +} + +SOKOL_API_IMPL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) { +    sgl_viewport((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_command_t* cmd = _sgl_next_command(); +    if (cmd) { +        cmd->cmd = SGL_COMMAND_SCISSOR_RECT; +        cmd->args.scissor_rect.x = x; +        cmd->args.scissor_rect.y = y; +        cmd->args.scissor_rect.w = w; +        cmd->args.scissor_rect.h = h; +        cmd->args.scissor_rect.origin_top_left = origin_top_left; +    } +} + +SOKOL_API_IMPL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) { +    sgl_scissor_rect((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_enable_texture(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl.texturing_enabled = true; +} + +SOKOL_API_IMPL void sgl_disable_texture(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl.texturing_enabled = false; +} + +SOKOL_API_IMPL void sgl_texture(sg_image img) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    if (SG_INVALID_ID != img.id) { +        _sgl.cur_img = img; +    } +    else { +        _sgl.cur_img = _sgl.def_img; +    } +} + +SOKOL_API_IMPL void sgl_begin_points(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_begin(SGL_PRIMITIVETYPE_POINTS); +} + +SOKOL_API_IMPL void sgl_begin_lines(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_begin(SGL_PRIMITIVETYPE_LINES); +} + +SOKOL_API_IMPL void sgl_begin_line_strip(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_begin(SGL_PRIMITIVETYPE_LINE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_triangles(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_begin(SGL_PRIMITIVETYPE_TRIANGLES); +} + +SOKOL_API_IMPL void sgl_begin_triangle_strip(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_begin(SGL_PRIMITIVETYPE_TRIANGLE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_quads(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(!_sgl.in_begin); +    _sgl_begin(SGL_PRIMITIVETYPE_QUADS); +} + +SOKOL_API_IMPL void sgl_end(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT(_sgl.in_begin); +    SOKOL_ASSERT(_sgl.cur_vertex >= _sgl.base_vertex); +    _sgl.in_begin = false; +    bool matrix_dirty = _sgl.matrix_dirty; +    if (matrix_dirty) { +        _sgl.matrix_dirty = false; +        _sgl_uniform_t* uni = _sgl_next_uniform(); +        if (uni) { +            _sgl_matmul4(&uni->mvp, _sgl_matrix_projection(), _sgl_matrix_modelview()); +            uni->tm = *_sgl_matrix_texture(); +        } +    } +    /* check if command can be merged with previous command */ +    sg_pipeline pip = _sgl_get_pipeline(_sgl.pip_stack[_sgl.pip_tos], _sgl.cur_prim_type); +    sg_image img = _sgl.texturing_enabled ? _sgl.cur_img : _sgl.def_img; +    _sgl_command_t* prev_cmd = _sgl_prev_command(); +    bool merge_cmd = false; +    if (prev_cmd) { +        if ((prev_cmd->cmd == SGL_COMMAND_DRAW) && +            (_sgl.cur_prim_type != SGL_PRIMITIVETYPE_LINE_STRIP) && +            (_sgl.cur_prim_type != SGL_PRIMITIVETYPE_TRIANGLE_STRIP) && +            !matrix_dirty && +            (prev_cmd->args.draw.img.id == img.id) && +            (prev_cmd->args.draw.pip.id == pip.id)) +        { +            merge_cmd = true; +        } +    } +    if (merge_cmd) { +        /* draw command can be merged with the previous command */ +        prev_cmd->args.draw.num_vertices += _sgl.cur_vertex - _sgl.base_vertex; +    } +    else { +        /* append a new draw command */ +        _sgl_command_t* cmd = _sgl_next_command(); +        if (cmd) { +            SOKOL_ASSERT(_sgl.cur_uniform > 0); +            cmd->cmd = SGL_COMMAND_DRAW; +            cmd->args.draw.img = img; +            cmd->args.draw.pip = _sgl_get_pipeline(_sgl.pip_stack[_sgl.pip_tos], _sgl.cur_prim_type); +            cmd->args.draw.base_vertex = _sgl.base_vertex; +            cmd->args.draw.num_vertices = _sgl.cur_vertex - _sgl.base_vertex; +            cmd->args.draw.uniform_index = _sgl.cur_uniform - 1; +        } +    } +} + +SOKOL_API_IMPL void sgl_t2f(float u, float v) { +    _sgl.u = u; _sgl.v = v; +} + +SOKOL_API_IMPL void sgl_c3f(float r, float g, float b) { +    _sgl.rgba = _sgl_pack_rgbaf(r, g, b, 1.0f); +} + +SOKOL_API_IMPL void sgl_c4f(float r, float g, float b, float a) { +    _sgl.rgba = _sgl_pack_rgbaf(r, g, b, a); +} + +SOKOL_API_IMPL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b) { +    _sgl.rgba = _sgl_pack_rgbab(r, g, b, 255); +} + +SOKOL_API_IMPL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    _sgl.rgba = _sgl_pack_rgbab(r, g, b, a); +} + +SOKOL_API_IMPL void sgl_c1i(uint32_t rgba) { +    _sgl.rgba = rgba; +} + +SOKOL_API_IMPL void sgl_v2f(float x, float y) { +    _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl.rgba); +} + +SOKOL_API_IMPL void sgl_v3f(float x, float y, float z) { +    _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl.rgba); +} + +SOKOL_API_IMPL void sgl_v2f_t2f(float x, float y, float u, float v) { +    _sgl_vtx(x, y, 0.0f, u, v, _sgl.rgba); +} + +SOKOL_API_IMPL void sgl_v3f_t2f(float x, float y, float z, float u, float v) { +    _sgl_vtx(x, y, z, u, v, _sgl.rgba); +} + +SOKOL_API_IMPL void sgl_v2f_c3f(float x, float y, float r, float g, float b) { +    _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, 1.0f)); +} + +SOKOL_API_IMPL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b) { +    _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, 255)); +} + +SOKOL_API_IMPL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a) { +    _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v2f_c1i(float x, float y, uint32_t rgba) { +    _sgl_vtx(x, y, 0.0f, _sgl.u, _sgl.v, rgba); +} + +SOKOL_API_IMPL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b) { +    _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, 1.0f)); +} + +SOKOL_API_IMPL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b) { +    _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, 255)); +} + +SOKOL_API_IMPL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a) { +    _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbaf(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    _sgl_vtx(x, y, z, _sgl.u, _sgl.v, _sgl_pack_rgbab(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba) { +    _sgl_vtx(x, y, z, _sgl.u, _sgl.v, rgba); +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b) { +    _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b) { +    _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, 255)); +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a) { +    _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    _sgl_vtx(x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba) { +    _sgl_vtx(x, y, 0.0f, u, v, rgba); +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b) { +    _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b) { +    _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbab(r, g, b, 255)); +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a) { +    _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +    _sgl_vtx(x, y, z, u, v, _sgl_pack_rgbab(r, g, b, a)); +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba) { +    _sgl_vtx(x, y, z, u, v, rgba); +} + +SOKOL_API_IMPL void sgl_matrix_mode_modelview(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; +} + +SOKOL_API_IMPL void sgl_matrix_mode_projection(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.cur_matrix_mode = SGL_MATRIXMODE_PROJECTION; +} + +SOKOL_API_IMPL void sgl_matrix_mode_texture(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.cur_matrix_mode = SGL_MATRIXMODE_TEXTURE; +} + +SOKOL_API_IMPL void sgl_load_identity(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_identity(_sgl_matrix()); +} + +SOKOL_API_IMPL void sgl_load_matrix(const float m[16]) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    memcpy(&_sgl_matrix()->v[0][0], &m[0], 64); +} + +SOKOL_API_IMPL void sgl_load_transpose_matrix(const float m[16]) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_transpose(_sgl_matrix(), (const _sgl_matrix_t*) &m[0]); +} + +SOKOL_API_IMPL void sgl_mult_matrix(const float m[16]) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    const _sgl_matrix_t* m0  = (const _sgl_matrix_t*) &m[0]; +    _sgl_mul(_sgl_matrix(), m0); +} + +SOKOL_API_IMPL void sgl_mult_transpose_matrix(const float m[16]) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_matrix_t m0; +    _sgl_transpose(&m0, (const _sgl_matrix_t*) &m[0]); +    _sgl_mul(_sgl_matrix(), &m0); +} + +SOKOL_API_IMPL void sgl_rotate(float angle_rad, float x, float y, float z) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_rotate(_sgl_matrix(), angle_rad, x, y, z); +} + +SOKOL_API_IMPL void sgl_scale(float x, float y, float z) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_scale(_sgl_matrix(), x, y, z); +} + +SOKOL_API_IMPL void sgl_translate(float x, float y, float z) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_translate(_sgl_matrix(), x, y, z); +} + +SOKOL_API_IMPL void sgl_frustum(float l, float r, float b, float t, float n, float f) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_frustum(_sgl_matrix(), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_ortho(float l, float r, float b, float t, float n, float f) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_ortho(_sgl_matrix(), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_perspective(_sgl_matrix(), fov_y, aspect, z_near, z_far); +} + +SOKOL_API_IMPL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    _sgl.matrix_dirty = true; +    _sgl_lookat(_sgl_matrix(), eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z); +} + +SOKOL_GL_API_DECL void sgl_push_matrix(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT((_sgl.cur_matrix_mode >= 0) && (_sgl.cur_matrix_mode < SGL_NUM_MATRIXMODES)); +    _sgl.matrix_dirty = true; +    if (_sgl.matrix_tos[_sgl.cur_matrix_mode] < (_SGL_MAX_STACK_DEPTH - 1)) { +        const _sgl_matrix_t* src = _sgl_matrix(); +        _sgl.matrix_tos[_sgl.cur_matrix_mode]++; +        _sgl_matrix_t* dst = _sgl_matrix(); +        *dst = *src; +    } +    else { +        _sgl.error = SGL_ERROR_STACK_OVERFLOW; +    } +} + +SOKOL_GL_API_DECL void sgl_pop_matrix(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    SOKOL_ASSERT((_sgl.cur_matrix_mode >= 0) && (_sgl.cur_matrix_mode < SGL_NUM_MATRIXMODES)); +    _sgl.matrix_dirty = true; +    if (_sgl.matrix_tos[_sgl.cur_matrix_mode] > 0) { +        _sgl.matrix_tos[_sgl.cur_matrix_mode]--; +    } +    else { +        _sgl.error = SGL_ERROR_STACK_UNDERFLOW; +    } +} + +/* this renders the accumulated draw commands via sokol-gfx */ +SOKOL_API_IMPL void sgl_draw(void) { +    SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); +    if ((_sgl.error == SGL_NO_ERROR) && (_sgl.cur_vertex > 0) && (_sgl.cur_command > 0)) { +        uint32_t cur_pip_id = SG_INVALID_ID; +        uint32_t cur_img_id = SG_INVALID_ID; +        int cur_uniform_index = -1; +        sg_push_debug_group("sokol-gl"); +        const sg_range range = { _sgl.vertices, (size_t)_sgl.cur_vertex * sizeof(_sgl_vertex_t) }; +        sg_update_buffer(_sgl.vbuf, &range); +        _sgl.bind.vertex_buffers[0] = _sgl.vbuf; +        for (int i = 0; i < _sgl.cur_command; i++) { +            const _sgl_command_t* cmd = &_sgl.commands[i]; +            switch (cmd->cmd) { +                case SGL_COMMAND_VIEWPORT: +                    { +                        const _sgl_viewport_args_t* args = &cmd->args.viewport; +                        sg_apply_viewport(args->x, args->y, args->w, args->h, args->origin_top_left); +                    } +                    break; +                case SGL_COMMAND_SCISSOR_RECT: +                    { +                        const _sgl_scissor_rect_args_t* args = &cmd->args.scissor_rect; +                        sg_apply_scissor_rect(args->x, args->y, args->w, args->h, args->origin_top_left); +                    } +                    break; +                case SGL_COMMAND_DRAW: +                    { +                        const _sgl_draw_args_t* args = &cmd->args.draw; +                        if (args->pip.id != cur_pip_id) { +                            sg_apply_pipeline(args->pip); +                            cur_pip_id = args->pip.id; +                            /* when pipeline changes, also need to re-apply uniforms and bindings */ +                            cur_img_id = SG_INVALID_ID; +                            cur_uniform_index = -1; +                        } +                        if (cur_img_id != args->img.id) { +                            _sgl.bind.fs_images[0] = args->img; +                            sg_apply_bindings(&_sgl.bind); +                            cur_img_id = args->img.id; +                        } +                        if (cur_uniform_index != args->uniform_index) { +                            const sg_range ub_range = { &_sgl.uniforms[args->uniform_index], sizeof(_sgl_uniform_t) }; +                            sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &ub_range); +                            cur_uniform_index = args->uniform_index; +                        } +                        /* FIXME: what if number of vertices doesn't match the primitive type? */ +                        if (args->num_vertices > 0) { +                            sg_draw(args->base_vertex, args->num_vertices, 1); +                        } +                    } +                    break; +            } +        } +        sg_pop_debug_group(); +    } +    _sgl_rewind(); +} +#endif /* SOKOL_GL_IMPL */  | 
