commit 883b714

uint  ·  2026-07-21 14:36:34 +0000 UTC
parent 9f17975
vendor sokol_gfx
1 files changed,  +26836, -0
+26836, -0
    1@@ -0,0 +1,26836 @@
    2+#if defined(SOKOL_IMPL) && !defined(SOKOL_GFX_IMPL)
    3+#define SOKOL_GFX_IMPL
    4+#endif
    5+#ifndef SOKOL_GFX_INCLUDED
    6+/*
    7+    sokol_gfx.h -- simple 3D API wrapper
    8+
    9+    Project URL: https://github.com/floooh/sokol
   10+
   11+    Example code: https://github.com/floooh/sokol-samples
   12+
   13+    Do this:
   14+        #define SOKOL_IMPL or
   15+        #define SOKOL_GFX_IMPL
   16+    before you include this file in *one* C or C++ file to create the
   17+    implementation.
   18+
   19+    In the same place define one of the following to select the rendering
   20+    backend:
   21+        #define SOKOL_GLCORE
   22+        #define SOKOL_GLES3
   23+        #define SOKOL_D3D11
   24+        #define SOKOL_METAL
   25+        #define SOKOL_WGPU
   26+        #define SOKOL_VULKAN
   27+        #define SOKOL_DUMMY_BACKEND
   28+
   29+    I.e. for the desktop GL it should look like this:
   30+
   31+    #include ...
   32+    #include ...
   33+    #define SOKOL_IMPL
   34+    #define SOKOL_GLCORE
   35+    #include "sokol_gfx.h"
   36+
   37+    The dummy backend replaces the platform-specific backend code with empty
   38+    stub functions. This is useful for writing tests that need to run on the
   39+    command line.
   40+
   41+    Optionally provide the following defines with your own implementations:
   42+
   43+    SOKOL_ASSERT(c)             - your own assert macro (default: assert(c))
   44+    SOKOL_UNREACHABLE()         - a guard macro for unreachable code (default: assert(false))
   45+    SOKOL_GFX_API_DECL          - public function declaration prefix (default: extern)
   46+    SOKOL_API_DECL              - same as SOKOL_GFX_API_DECL
   47+    SOKOL_API_IMPL              - public function implementation prefix (default: -)
   48+    SOKOL_TRACE_HOOKS           - enable trace hook callbacks (search below for TRACE HOOKS)
   49+    SOKOL_EXTERNAL_GL_LOADER    - indicates that you're using your own GL loader, in this case
   50+                                  sokol_gfx.h will not include any platform GL headers and disable
   51+                                  the integrated Win32 GL loader
   52+
   53+    If sokol_gfx.h is compiled as a DLL, define the following before
   54+    including the declaration or implementation:
   55+
   56+    SOKOL_DLL
   57+
   58+    On Windows, SOKOL_DLL will define SOKOL_GFX_API_DECL as __declspec(dllexport)
   59+    or __declspec(dllimport) as needed.
   60+
   61+    Optionally define the following to force debug checks and validations
   62+    even in release mode:
   63+
   64+    SOKOL_DEBUG - by default this is defined if NDEBUG is not defined
   65+
   66+    Link with the following system libraries (note that sokol_app.h has
   67+    additional linker requirements):
   68+
   69+    - on macOS/iOS with Metal: Metal
   70+    - on macOS with GL: OpenGL
   71+    - on iOS with GL: OpenGLES
   72+    - on Linux with EGL: GL or GLESv2
   73+    - on Linux with GLX: GL
   74+    - on Linux with Vulkan: vulkan
   75+    - on Android: GLESv3, log, android
   76+    - on Windows:
   77+        - with Vulkan: link with vulkan-1 (this is explicit in case you want to
   78+          use your own Vulkan loader library)
   79+        - with D3D11:
   80+            - on MSVC or Clang: no action needed, libs are defined in-source via pragma-comment-lib
   81+            - on MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined and link with -ld3d11
   82+        - with GL: no linking needed since sokol_gfx.h comes with its own GL loader on Windows
   83+
   84+    On macOS and iOS, the implementation must be compiled as Objective-C.
   85+
   86+    For Linux+Vulkan install the following packages (or equivalents):
   87+        - libvulkan-dev
   88+        - vulkan-validationlayers
   89+        - vulkan-tools
   90+
   91+    For Windows+Vulkan install the Vulkan SDK and in your build system:
   92+        - add a header search path to $ENV{VULKAN_SDK}/Include
   93+        - add a link search path to $ENV{VULKAN_SDK}/Env
   94+
   95+    On Emscripten:
   96+        - for WebGL2: add the linker option `-s USE_WEBGL2=1`
   97+        - for WebGPU: compile and link with `--use-port=emdawnwebgpu`
   98+          (for more exotic situations, read: https://dawn.googlesource.com/dawn/+/refs/heads/main/src/emdawnwebgpu/pkg/README.md)
   99+
  100+    sokol_gfx DOES NOT:
  101+    ===================
  102+    - create a window, swapchain or the 3D-API context/device, you must do this
  103+      before sokol_gfx is initialized, and pass any required information
  104+      (like 3D device pointers) to the sokol_gfx initialization call
  105+
  106+    - present the rendered frame, how this is done exactly usually depends
  107+      on how the window and 3D-API context/device was created
  108+
  109+    - provide a unified shader language, instead 3D-API-specific shader
  110+      source-code or shader-bytecode must be provided (for the "official"
  111+      offline shader cross-compiler / code-generator, see here:
  112+      https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md)
  113+
  114+
  115+    STEP BY STEP
  116+    ============
  117+    --- to initialize sokol_gfx, after creating a window and a 3D-API
  118+        context/device, call:
  119+
  120+            sg_setup(const sg_desc*)
  121+
  122+        Depending on the selected 3D backend, sokol-gfx requires some
  123+        information about its runtime environment, like a GPU device pointer,
  124+        default swapchain pixel formats and so on. If you are using sokol_app.h
  125+        for the window system glue, you can use a helper function provided in
  126+        the sokol_glue.h header:
  127+
  128+            #include "sokol_gfx.h"
  129+            #include "sokol_app.h"
  130+            #include "sokol_glue.h"
  131+            //...
  132+            sg_setup(&(sg_desc){
  133+                .environment = sglue_environment(),
  134+            });
  135+
  136+        To get any logging output for errors and from the validation layer, you
  137+        need to provide a logging callback. Easiest way is through sokol_log.h:
  138+
  139+            #include "sokol_log.h"
  140+            //...
  141+            sg_setup(&(sg_desc){
  142+                //...
  143+                .logger.func = slog_func,
  144+            });
  145+
  146+    --- create resource objects (buffers, images, views, samplers, shaders
  147+        and pipeline objects)
  148+
  149+            sg_buffer sg_make_buffer(const sg_buffer_desc*)
  150+            sg_image sg_make_image(const sg_image_desc*)
  151+            sg_view sg_make_view(const sg_view_desc*)
  152+            sg_sampler sg_make_sampler(const sg_sampler_desc*)
  153+            sg_shader sg_make_shader(const sg_shader_desc*)
  154+            sg_pipeline sg_make_pipeline(const sg_pipeline_desc*)
  155+
  156+    --- start a render- or compute-pass:
  157+
  158+            sg_begin_pass(const sg_pass* pass);
  159+
  160+        Typically, render passes render into an externally provided swapchain which
  161+        presents the rendering result on the display. Such a 'swapchain pass'
  162+        is started like this:
  163+
  164+            sg_begin_pass(&(sg_pass){ .action = { ... }, .swapchain = sglue_swapchain() })
  165+
  166+        ...where .action is an sg_pass_action struct containing actions to be performed
  167+        at the start and end of a render pass (such as clearing the render surfaces to
  168+        a specific color), and .swapchain is an sg_swapchain struct with all the required
  169+        information to render into the swapchain's surfaces.
  170+
  171+        To start an 'offscreen render pass' into sokol-gfx image objects, populate
  172+        the sg_pass.attachments nested struct with attachment view objects
  173+        (1..4 color-attachment-views for to render into, a depth-stencil-attachment-view
  174+        to provide the depth-stencil-buffer, and optionally 1..4 resolve-attachment-views
  175+        for an MSAA-resolve operation:
  176+
  177+            sg_begin_pass(&(sg_pass){
  178+                .action = { ... },
  179+                .attachments = {
  180+                    .colors[0] = color_attachment_view,
  181+                    .resolves[0] = optional_resolve_attachment_view,
  182+                    .depth_stencil = depth_stencil_attachment_view,
  183+                },
  184+            });
  185+
  186+        To start a compute-pass, just set the .compute item to true:
  187+
  188+            sg_begin_pass(&(sg_pass){ .compute = true });
  189+
  190+    --- set the pipeline state for the next draw call with:
  191+
  192+            sg_apply_pipeline(sg_pipeline pip)
  193+
  194+    --- fill an sg_bindings struct with the resource bindings for the next
  195+        draw- or dispatch-call (0..N vertex buffers, 0 or 1 index buffer, 0..N views,
  196+        0..N samplers), and call
  197+
  198+            sg_apply_bindings(const sg_bindings* bindings)
  199+
  200+        ...to update the resource bindings. Note that in a compute pass, no vertex-
  201+        or index-buffer bindings can be used, and in render passes, no storage-image bindings
  202+        are allowed. Those restrictions will be checked by the sokol-gfx validation layer.
  203+
  204+    --- optionally update shader uniform data with:
  205+
  206+            sg_apply_uniforms(int ub_slot, const sg_range* data)
  207+
  208+        Read the section 'UNIFORM DATA LAYOUT' to learn about the expected memory layout
  209+        of the uniform data passed into sg_apply_uniforms().
  210+
  211+    --- kick off a draw call with:
  212+
  213+            sg_draw(int base_element, int num_elements, int num_instances)
  214+
  215+        The sg_draw() function unifies all the different ways to render primitives
  216+        in a single call (indexed vs non-indexed rendering, and instanced vs non-instanced
  217+        rendering). In case of indexed rendering, base_element and num_element specify
  218+        indices in the currently bound index buffer. In case of non-indexed rendering
  219+        base_element and num_elements specify vertices in the currently bound
  220+        vertex-buffer(s). To perform instanced rendering, the rendering pipeline
  221+        must be setup for instancing (see sg_pipeline_desc below), a separate vertex buffer
  222+        containing per-instance data must be bound, and the num_instances parameter
  223+        must be > 1.
  224+
  225+        Alternatively, call:
  226+
  227+            sg_draw_ex(...)
  228+
  229+        to provide a base-vertex and/or base-instance which allows to render
  230+        from different sections of a vertex buffer without rebinding the
  231+        vertex buffer with a different offset. Note that the `sg_draw_ex()`
  232+        only has limited portability on OpenGL, check the sg_limits struct
  233+        members .draw_base_vertex and .draw_base_instance for runtime support,
  234+        those are generally true on non-GL-backends, and on GL the feature
  235+        flags are set according to the GL version:
  236+
  237+            - on GL base_instance != 0 is only supported since GL 4.2
  238+            - on GLES3.x, base_instance != 0 is not supported
  239+            - on GLES3.x, base_vertex is only supported since GLES3.2
  240+              (e.g. not supported on WebGL2)
  241+
  242+    --- ...or kick of a dispatch call to invoke a compute shader workload:
  243+
  244+            sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z)
  245+
  246+        The dispatch args define the number of 'compute workgroups' processed
  247+        by the currently applied compute shader.
  248+
  249+    --- finish the current pass with:
  250+
  251+            sg_end_pass()
  252+
  253+    --- when done with the current frame, call
  254+
  255+            sg_commit()
  256+
  257+    --- at the end of your program, shutdown sokol_gfx with:
  258+
  259+            sg_shutdown()
  260+
  261+    --- if you need to destroy resources before sg_shutdown(), call:
  262+
  263+            sg_destroy_buffer(sg_buffer buf)
  264+            sg_destroy_image(sg_image img)
  265+            sg_destroy_sampler(sg_sampler smp)
  266+            sg_destroy_shader(sg_shader shd)
  267+            sg_destroy_pipeline(sg_pipeline pip)
  268+            sg_destroy_view(sg_view view)
  269+
  270+    --- to set a new viewport rectangle, call:
  271+
  272+            sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left)
  273+
  274+        ...or if you want to specify the viewport rectangle with float values:
  275+
  276+            sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left)
  277+
  278+    --- to set a new scissor rect, call:
  279+
  280+            sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left)
  281+
  282+        ...or with float values:
  283+
  284+            sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left)
  285+
  286+        Both sg_apply_viewport() and sg_apply_scissor_rect() must be called
  287+        inside a rendering pass (e.g. not in a compute pass, or outside a pass)
  288+
  289+        Note that sg_begin_pass() will reset both the viewport and scissor
  290+        rectangles to cover the entire framebuffer.
  291+
  292+    --- to update (overwrite) the content of buffer and image resources, call:
  293+
  294+            sg_update_buffer(sg_buffer buf, const sg_range* data)
  295+            sg_update_image(sg_image img, const sg_image_data* data)
  296+
  297+        Buffers and images to be updated must have been created with
  298+        sg_buffer_desc.usage.dynamic_update or .stream_update.
  299+
  300+        Only one update per frame is allowed for buffer and image resources when
  301+        using the sg_update_*() functions. The rationale is to have a simple
  302+        protection from the CPU scribbling over data the GPU is currently
  303+        using, or the CPU having to wait for the GPU
  304+
  305+        Buffer and image updates can be partial, as long as a rendering
  306+        operation only references the valid (updated) data in the
  307+        buffer or image.
  308+
  309+    --- to append a chunk of data to a buffer resource, call:
  310+
  311+            int sg_append_buffer(sg_buffer buf, const sg_range* data)
  312+
  313+        The difference to sg_update_buffer() is that sg_append_buffer()
  314+        can be called multiple times per frame to append new data to the
  315+        buffer piece by piece, optionally interleaved with draw calls referencing
  316+        the previously written data.
  317+
  318+        sg_append_buffer() returns a byte offset to the start of the
  319+        written data, this offset can be assigned to
  320+        sg_bindings.vertex_buffer_offsets[n] or
  321+        sg_bindings.index_buffer_offset
  322+
  323+        Code example:
  324+
  325+        for (...) {
  326+            const void* data = ...;
  327+            const int num_bytes = ...;
  328+            int offset = sg_append_buffer(buf, &(sg_range) { .ptr=data, .size=num_bytes });
  329+            bindings.vertex_buffer_offsets[0] = offset;
  330+            sg_apply_pipeline(pip);
  331+            sg_apply_bindings(&bindings);
  332+            sg_apply_uniforms(...);
  333+            sg_draw(...);
  334+        }
  335+
  336+        A buffer to be used with sg_append_buffer() must have been created
  337+        with sg_buffer_desc.usage.dynamic_update or .stream_update.
  338+
  339+        If the application appends more data to the buffer then fits into
  340+        the buffer, the buffer will go into the "overflow" state for the
  341+        rest of the frame.
  342+
  343+        Any draw calls attempting to render an overflown buffer will be
  344+        silently dropped (in debug mode this will also result in a
  345+        validation error).
  346+
  347+        You can also check manually if a buffer is in overflow-state by calling
  348+
  349+            bool sg_query_buffer_overflow(sg_buffer buf)
  350+
  351+        You can manually check to see if an overflow would occur before adding
  352+        any data to a buffer by calling
  353+
  354+            bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size)
  355+
  356+        NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of
  357+        data will be 4-byte aligned in the destination buffer. This means
  358+        that there will be gaps in index buffers containing 16-bit indices
  359+        when the number of indices in a call to sg_append_buffer() is
  360+        odd. This isn't a problem when each call to sg_append_buffer()
  361+        is associated with one draw call, but will be problematic when
  362+        a single indexed draw call spans several appended chunks of indices.
  363+
  364+    --- to check at runtime for optional features, limits and pixelformat support,
  365+        call:
  366+
  367+            sg_features sg_query_features()
  368+            sg_limits sg_query_limits()
  369+            sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt)
  370+
  371+    --- if you need to call into the underlying 3D-API directly, you must call:
  372+
  373+            sg_reset_state_cache()
  374+
  375+        ...before calling sokol_gfx functions again
  376+
  377+    --- you can inspect the original sg_desc structure handed to sg_setup()
  378+        by calling sg_query_desc(). This will return an sg_desc struct with
  379+        the default values patched in instead of any zero-initialized values
  380+
  381+    --- you can get a desc struct matching the creation attributes of a
  382+        specific resource object via:
  383+
  384+            sg_buffer_desc sg_query_buffer_desc(sg_buffer buf)
  385+            sg_image_desc sg_query_image_desc(sg_image img)
  386+            sg_sampler_desc sg_query_sampler_desc(sg_sampler smp)
  387+            sg_shader_desc sq_query_shader_desc(sg_shader shd)
  388+            sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip)
  389+            sg_view_desc sg_query_view_desc(sg_view view)
  390+
  391+        ...but NOTE that the returned desc structs may be incomplete, only
  392+        creation attributes that are kept around internally after resource
  393+        creation will be filled in, and in some cases (like shaders) that's
  394+        very little. Any missing attributes will be set to zero. The returned
  395+        desc structs might still be useful as partial blueprint for creating
  396+        similar resources if filled up with the missing attributes.
  397+
  398+        Calling the query-desc functions on an invalid resource will return
  399+        completely zeroed structs (it makes sense to check  the resource state
  400+        with sg_query_*_state() first)
  401+
  402+    --- you can query the default resource creation parameters through the functions
  403+
  404+            sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc)
  405+            sg_image_desc sg_query_image_defaults(const sg_image_desc* desc)
  406+            sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc)
  407+            sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc)
  408+            sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc)
  409+            sg_view_desc sg_query_view_defaults(const sg_view_desc* desc)
  410+
  411+        These functions take a pointer to a desc structure which may contain
  412+        zero-initialized items for default values. These zero-init values
  413+        will be replaced with their concrete values in the returned desc
  414+        struct.
  415+
  416+    --- you can inspect various internal resource runtime values via:
  417+
  418+            sg_buffer_info sg_query_buffer_info(sg_buffer buf)
  419+            sg_image_info sg_query_image_info(sg_image img)
  420+            sg_sampler_info sg_query_sampler_info(sg_sampler smp)
  421+            sg_shader_info sg_query_shader_info(sg_shader shd)
  422+            sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip)
  423+            sg_view_info sg_query_view_info(sg_view view)
  424+
  425+        ...please note that the returned info-structs are tied quite closely
  426+        to sokol_gfx.h internals, and may change more often than other
  427+        public API functions and structs.
  428+
  429+    -- you can query the type/flavour and parent resource of a view:
  430+
  431+            sg_view_type sg_query_view_type(sg_view view)
  432+            sg_image sg_query_view_image(sg_view view)
  433+            sg_buffer sg_query_view_buffer(sg_view view)
  434+
  435+    --- you can query stats and control stats collection via:
  436+
  437+            sg_query_stats()
  438+            sg_enable_stats()
  439+            sg_disable_stats()
  440+            sg_stats_enabled()
  441+
  442+    --- you can ask at runtime what backend sokol_gfx.h has been compiled for:
  443+
  444+            sg_backend sg_query_backend(void)
  445+
  446+    --- call the following helper functions to compute the number of
  447+        bytes in a texture row or surface for a specific pixel format.
  448+        These functions might be helpful when preparing image data for consumption
  449+        by sg_make_image() or sg_update_image():
  450+
  451+            int sg_query_row_pitch(sg_pixel_format fmt, int width, int int row_align_bytes);
  452+            int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes);
  453+
  454+        Width and height are generally in number pixels, but note that 'row' has different meaning
  455+        for uncompressed vs compressed pixel formats: for uncompressed formats, a row is identical
  456+        with a single line if pixels, while in compressed formats, one row is a line of *compression blocks*.
  457+
  458+        This is why calling sg_query_surface_pitch() for a compressed pixel format and height
  459+        N, N+1, N+2, ... may return the same result.
  460+
  461+        The row_align_bytes parameter is for added flexibility. For image data that goes into
  462+        the sg_make_image() or sg_update_image() this should generally be 1, because these
  463+        functions take tightly packed image data as input no matter what alignment restrictions
  464+        exist in the backend 3D APIs.
  465+
  466+    ON INITIALIZATION:
  467+    ==================
  468+    When calling sg_setup(), a pointer to an sg_desc struct must be provided
  469+    which contains initialization options. These options provide two types
  470+    of information to sokol-gfx:
  471+
  472+        (1) upper bounds and limits needed to allocate various internal
  473+            data structures:
  474+                - the max number of resources of each type that can
  475+                  be alive at the same time, this is used for allocating
  476+                  internal pools
  477+                - the max overall size of uniform data that can be
  478+                  updated per frame, including a worst-case alignment
  479+                  per uniform update (this worst-case alignment is 256 bytes)
  480+                - the max size of all dynamic resource updates (sg_update_buffer,
  481+                  sg_append_buffer and sg_update_image) per frame
  482+                - the max number of compute-dispatch calls in a compute pass
  483+            Not all of those limit values are used by all backends, but it is
  484+            good practice to provide them none-the-less.
  485+
  486+        (2) 3D backend "environment information" in a nested sg_environment struct:
  487+            - pointers to backend-specific context- or device-objects (for instance
  488+              the D3D11, WebGPU or Metal device objects)
  489+            - defaults for external swapchain pixel formats and sample counts,
  490+              these will be used as default values in image and pipeline objects,
  491+              and the sg_swapchain struct passed into sg_begin_pass()
  492+            Usually you provide a complete sg_environment struct through
  493+            a helper function, as an example look at the sglue_environment()
  494+            function in the sokol_glue.h header.
  495+
  496+    See the documentation block of the sg_desc struct below for more information.
  497+
  498+
  499+    ON RENDER PASSES
  500+    ================
  501+    Relevant samples:
  502+        - https://floooh.github.io/sokol-html5/offscreen-sapp.html
  503+        - https://floooh.github.io/sokol-html5/offscreen-msaa-sapp.html
  504+        - https://floooh.github.io/sokol-html5/mrt-sapp.html
  505+        - https://floooh.github.io/sokol-html5/mrt-pixelformats-sapp.html
  506+
  507+    A render pass groups rendering commands into a set of render target images
  508+    (called 'render pass attachments'). Render target images can be used in subsequent
  509+    passes as textures (it is invalid to use the same image both as render target
  510+    and as texture in the same pass).
  511+
  512+    The following sokol-gfx functions must only be called inside a render-pass:
  513+
  514+        sg_apply_viewport[f]
  515+        sg_apply_scissor_rect[f]
  516+        sg_draw
  517+
  518+    The following function may be called inside a render- or compute-pass, but
  519+    not outside a pass:
  520+
  521+        sg_apply_pipeline
  522+        sg_apply_bindings
  523+        sg_apply_uniforms
  524+
  525+    A frame must have at least one 'swapchain render pass' which renders into an
  526+    externally provided swapchain provided as an sg_swapchain struct to the
  527+    sg_begin_pass() function. If you use sokol_gfx.h together with sokol_app.h,
  528+    just call the sglue_swapchain() helper function in sokol_glue.h to
  529+    provide the swapchain information. Otherwise the following information
  530+    must be provided:
  531+
  532+        - the color pixel-format of the swapchain's render surface
  533+        - an optional depth/stencil pixel format if the swapchain
  534+          has a depth/stencil buffer
  535+        - an optional sample-count for MSAA rendering
  536+        - NOTE: the above three values can be zero-initialized, in that
  537+          case the defaults from the sg_environment struct will be used that
  538+          had been passed to the sg_setup() function.
  539+        - a number of backend specific objects:
  540+            - GL/GLES3: just a GL framebuffer handle
  541+            - D3D11:
  542+                - an ID3D11RenderTargetView for the rendering surface
  543+                - if MSAA is used, an ID3D11RenderTargetView as
  544+                  MSAA resolve-target
  545+                - an optional ID3D11DepthStencilView for the
  546+                  depth/stencil buffer
  547+            - WebGPU
  548+                - a WGPUTextureView object for the rendering surface
  549+                - if MSAA is used, a WGPUTextureView object as MSAA resolve target
  550+                - an optional WGPUTextureView for the
  551+            - Metal (NOTE that the roles of provided surfaces is slightly
  552+              different in Metal than in D3D11 or WebGPU, notably, the
  553+              CAMetalDrawable is either rendered to directly, or serves
  554+              as MSAA resolve target):
  555+                - a CAMetalDrawable object which is either rendered
  556+                  into directly, or in case of MSAA rendering, serves
  557+                  as MSAA-resolve-target
  558+                - if MSAA is used, an multisampled MTLTexture where
  559+                  rendering goes into
  560+                - an optional MTLTexture for the depth/stencil buffer
  561+
  562+    A sg_swapchain struct provided to sg_begin_pass() can indicate that the
  563+    swapchain is in an 'invalid state' via the boolean `sg_swapchain.invalid`.
  564+    When this flag is set, all other sg_swapchain members must be zeroed.
  565+    An invalid swapchain will cause all rendering operations in that pass
  566+    to be silently skipped.
  567+
  568+    It's recommended that you create a helper function which returns an
  569+    initialized sg_swapchain struct by value. This can then be directly plugged
  570+    into the sg_begin_pass function like this:
  571+
  572+        sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() });
  573+
  574+    As an example for such a helper function check out the function sglue_swapchain()
  575+    in the sokol_glue.h header.
  576+
  577+    For offscreen render passes, the render target images used in a render pass
  578+    must be provided as sg_view objects specialized for the specific pass-attachment
  579+    types:
  580+
  581+        - color-attachment-views for color-rendering
  582+        - depth-stencil-attachment-views for the depth-stencil-buffer surface
  583+        - resolve-attachment-views for MSAA-resolve operations
  584+
  585+    For a simple offscreen scenario with one color-, one depth-stencil-render
  586+    target and without multisampling, setting up the required image-
  587+    and view-objects looks like this:
  588+
  589+    First create two render target images, one with a color pixel format,
  590+    and one with the depth- or depth-stencil pixel format. Both images
  591+    must have the same dimensions. Also not the usage flags:
  592+
  593+        const sg_image color_img = sg_make_image(&(sg_image_desc){
  594+            .usage.color_attachment = true,
  595+            .width = 256,
  596+            .height = 256,
  597+            .pixel_format = SG_PIXELFORMAT_RGBA8,
  598+            .sample_count = 1,
  599+        });
  600+        const sg_image depth_img = sg_make_image(&(sg_image_desc){
  601+            .usage.depth_stencil_attachment = true,
  602+            .width = 256,
  603+            .height = 256,
  604+            .pixel_format = SG_PIXELFORMAT_DEPTH,
  605+            .sample_count = 1,
  606+        });
  607+
  608+    NOTE: when creating render target images, have in mind that some default values
  609+    are aligned with the default environment attributes in the sg_environment struct
  610+    that was passed into the sg_setup() call:
  611+
  612+        - the default value for sg_image_desc.pixel_format is taken from
  613+          sg_environment.defaults.color_format
  614+        - the default value for sg_image_desc.sample_count is taken from
  615+          sg_environment.defaults.sample_count
  616+        - the default value for sg_image_desc.num_mipmaps is always 1
  617+
  618+    Next, create two view objects, one color-attachment-view and one
  619+    depth-stencil-attachment view:
  620+
  621+        const sg_view color_att_view = sg_make_view(&(sg_view_desc){
  622+            .color_attachment.image = color_img,
  623+        });
  624+        const sg_view depth_att_view = sg_make_view(&(sg_view_desc){
  625+            .depth_stencil_attachment.image = depth_img,
  626+        });
  627+
  628+    You'll typically also want to create a texture-view on the color image
  629+    to sample the color attachment image as texture in a later pass:
  630+
  631+        const sg_view tex_view = sg_make_view(&(sg_view_desc){
  632+            .texture.image = color_img,
  633+        });
  634+
  635+    The attachment-view objects are then passed into the sg_begin_pass function in
  636+    place of the nested swapchain struct:
  637+
  638+        sg_begin_pass(&(sg_pass){
  639+            .attachments = {
  640+                .colors[0] = color_att_view,
  641+                .depth_stencil = depth_att_view,
  642+            },
  643+        });
  644+
  645+    ...in a later pass when you want to sample the color attachment image as
  646+    texture, use the texture view in the sg_apply_bindings() call:
  647+
  648+        sg_apply_bindings(&(sg_bindings){
  649+            .vertex_buffers[0] = ...,
  650+            .index_buffer = ...,
  651+            .views[VIEW_tex] = tex_view,
  652+            .samplers[SMP_smp] = smp,
  653+        });
  654+
  655+    Swapchain and offscreen passes form dependency trees with a swapchain
  656+    pass at the root, offscreen passes as nodes, and attachment images as
  657+    dependencies between passes.
  658+
  659+    sg_pass_action structs are used to define actions that should happen at the
  660+    start and end of render passes (such as clearing pass attachments to a
  661+    specific color or depth-value, or performing an MSAA resolve operation at
  662+    the end of a pass).
  663+
  664+    A typical sg_pass_action object which clears the color attachment to black
  665+    might look like this:
  666+
  667+        const sg_pass_action = {
  668+            .colors[0] = {
  669+                .load_action = SG_LOADACTION_CLEAR,
  670+                .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }
  671+            }
  672+        };
  673+
  674+    This omits the defaults for the color attachment store action, and
  675+    the depth-stencil-attachments actions. The same pass action with the
  676+    defaults explicitly filled in would look like this:
  677+
  678+        const sg_pass_action pass_action = {
  679+            .colors[0] = {
  680+                .load_action = SG_LOADACTION_CLEAR,
  681+                .store_action = SG_STOREACTION_STORE,
  682+                .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }
  683+            },
  684+            .depth = = {
  685+                .load_action = SG_LOADACTION_CLEAR,
  686+                .store_action = SG_STOREACTION_DONTCARE,
  687+                .clear_value = 1.0f,
  688+            },
  689+            .stencil = {
  690+                .load_action = SG_LOADACTION_CLEAR,
  691+                .store_action = SG_STOREACTION_DONTCARE,
  692+                .clear_value = 0
  693+            }
  694+        };
  695+
  696+    With the sg_pass object and sg_pass_action struct in place everything
  697+    is ready now for the actual render pass:
  698+
  699+    Using such this prepared sg_pass_action in a swapchain pass looks like
  700+    this:
  701+
  702+        sg_begin_pass(&(sg_pass){
  703+            .action = pass_action,
  704+            .swapchain = sglue_swapchain()
  705+        });
  706+        ...
  707+        sg_end_pass();
  708+
  709+    ...of alternatively in one offscreen pass:
  710+
  711+        sg_begin_pass(&(sg_pass){
  712+            .action = pass_action,
  713+            .attachments = {
  714+                .colors[0] = color_att_view,
  715+                .depth_stencil = ds_att_view,
  716+            },
  717+        });
  718+        ...
  719+        sg_end_pass();
  720+
  721+    Offscreen rendering can also go into a mipmap, or a slice/face of
  722+    a cube-, array- or 3d-image (which some restrictions, for instance
  723+    it's not possible to create a 3D image with a depth/stencil pixel format,
  724+    these exceptions are generally caught by the sokol-gfx validation layer).
  725+
  726+    The mipmap/slice selection is baked into the attachment-view objects, for
  727+    instance to create a color-attachment-view for rendering into mip-level
  728+    2 and slice 3 of an array texture:
  729+
  730+        const sg_view color_att_view = sg_make_view(&(sg_view_desc){
  731+            .color_attachment = {
  732+                .image = color_img,
  733+                .mip_level = 2,
  734+                .slice = 3,
  735+            },
  736+        });
  737+
  738+    If MSAA offscreen rendering is desired, the multi-sample rendering result
  739+    must be 'resolved' into a separate 'resolve image', before that image can
  740+    be used as texture.
  741+
  742+    Setting up MSAA offscreen 3D rendering requires three image objects
  743+    (one color-attachment image with a sample count > 1), a resolve-attachment
  744+    image with a sample count of 1, and a depth-stencil-attachment image
  745+    with the same sample count as the color-attachment image:
  746+
  747+        const sg_image color_img = sg_make_image(&(sg_image_desc){
  748+            .usage.color_attachment = true,
  749+            .width = 256,
  750+            .height = 256,
  751+            .pixel_format = SG_PIXELFORMAT_RGBA8,
  752+            .sample_count = 4,
  753+        });
  754+        const sg_image resolve_img = sg_make_image(&(sg_image_desc){
  755+            .usage.resolve_attachment = true,
  756+            .width = 256,
  757+            .height = 256,
  758+            .pixel_format = SG_PIXELFORMAT_RGBA8,
  759+            .sample_count = 1,
  760+        });
  761+        const sg_image depth_img = sg_make_image(&(sg_image_desc){
  762+            .usage.depth_stencil_attachment = true,
  763+            .width = 256,
  764+            .height = 256,
  765+            .pixel_format = SG_PIXELFORMAT_DEPTH,
  766+            .sample_count = 4,
  767+        });
  768+
  769+    Next you'll need the corresponding attachment-view objects:
  770+
  771+        const sg_view color_att_view = sg_make_view(&(sg_view_desc){
  772+            .color_attachment.image = color_img,
  773+        });
  774+        const sg_view resolve_att_view = sg_make_view(&(sg_view_desc){
  775+            .resolve_attachment.image = resolve_img,
  776+        });
  777+        const sg_view depth_att_view = sg_make_view(&(sg_view_desc){
  778+            .depth_stencil_attachment.image = depth_img,
  779+        });
  780+
  781+    To sample the rendered image as a texture in a later pass you'll also
  782+    need a texture-view on the resolve-attachment-image (not the color-attachment-image!):
  783+
  784+        const sg_view tex_view = sg_make_view(&(sg_view_desc){
  785+            .texture.image = resolve_img,
  786+        });
  787+
  788+    Next start the render pass with all attachment-views, as soon as a
  789+    resolve-attachment-view is provided, an MSAA resolve operation will happen
  790+    at the end of the pass. Also note that the content of the MSAA color-attachment-image
  791+    doesn't need to be preserved, since it's only needed until the MSAA-resolve
  792+    at the end of the pass, so the .store_action should be set to "don't care":
  793+
  794+        sg_begin_pass(&(sg_pass){
  795+            .attachments = {
  796+                .colors[0] = color_att_view,
  797+                .resolves[0] = resolve_att_view,
  798+                .depth_stencil = depth_att_view,
  799+            },
  800+            .action = {
  801+                .colors[0] = {
  802+                    .load_action = SG_LOADACTION_CLEAR,
  803+                    .store_action = SG_STOREACTION_DONTCARE,
  804+                    .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f },
  805+                }
  806+            },
  807+        });
  808+
  809+    ...in a later pass, use the texture-view that had been created on the
  810+    resolve-image to use the rendering result as texture:
  811+
  812+        sg_apply_bindings(&(sg_bindings){
  813+            .vertex_buffers[0] = ...,
  814+            .index_buffer = ...,
  815+            .views[VIEW_tex] = tex_view,
  816+            .samplers[SMP_smp] = smp,
  817+        });
  818+
  819+    ON COMPUTE PASSES
  820+    =================
  821+    Compute passes are used to update the content of storage buffers and
  822+    storage images by running compute shader code on
  823+    the GPU. Updating storage resources with a compute shader will almost always
  824+    be more efficient than computing the same data on the CPU and then uploading
  825+    it via `sg_update_buffer()` or `sg_update_image()`.
  826+
  827+    NOTE: compute passes are only supported on the following platforms and
  828+    backends:
  829+
  830+        - macOS and iOS with Metal
  831+        - Windows with D3D11 and OpenGL
  832+        - Linux with OpenGL or GLES3.1+
  833+        - Web with WebGPU
  834+        - Android with GLES3.1+
  835+
  836+    ...this means compute shaders can't be used on the following platform/backend
  837+    combos (the same restrictions apply to using storage buffers without compute
  838+    shaders):
  839+
  840+        - macOS with GL
  841+        - iOS with GLES3
  842+        - Web with WebGL2
  843+
  844+    A compute pass is started with:
  845+
  846+        sg_begin_pass(&(sg_pass){ .compute = true });
  847+
  848+    ...and finished with a regular:
  849+
  850+        sg_end_pass();
  851+
  852+    Typically the following functions will be called inside a compute pass:
  853+
  854+        sg_apply_pipeline()
  855+        sg_apply_bindings()
  856+        sg_apply_uniforms()
  857+        sg_dispatch()
  858+
  859+    The following functions are disallowed inside a compute pass
  860+    and will cause validation layer errors:
  861+
  862+        sg_apply_viewport[f]()
  863+        sg_apply_scissor_rect[f]()
  864+        sg_draw()
  865+
  866+    Only special 'compute shaders' and 'compute pipelines' can be used in
  867+    compute passes. A compute shader only has a compute-function instead
  868+    of a vertex- and fragment-function pair, and it doesn't accept vertex-
  869+    and index-buffers as bindings, only storage-buffer-views (readable
  870+    and writable), storage-image-views (read/write or writeonly) and
  871+    texture-views (read-only).
  872+
  873+    A compute pipeline is created by providing a compute shader object,
  874+    setting the .compute creation parameter to true and not defining any
  875+    'render state':
  876+
  877+        sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){
  878+            .compute = true,
  879+            .shader = compute_shader,
  880+        });
  881+
  882+    The sg_apply_bindings and sg_apply_uniforms calls are the same as in
  883+    render passes, with the exception that no vertex- and index-buffers
  884+    can be bound in the sg_apply_bindings call.
  885+
  886+    Finally to kick off a compute workload, call sg_dispatch with the
  887+    number of workgroups in the x, y and z-dimension:
  888+
  889+        sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z)
  890+
  891+    Also see the following compute-shader samples:
  892+
  893+        - https://floooh.github.io/sokol-webgpu/instancing-compute-sapp.html
  894+        - https://floooh.github.io/sokol-webgpu/computeboids-sapp.html
  895+        - https://floooh.github.io/sokol-webgpu/imageblur-sapp.html
  896+
  897+
  898+    ON SHADER CREATION
  899+    ==================
  900+    sokol-gfx doesn't come with an integrated shader cross-compiler, instead
  901+    backend-specific shader sources or binary blobs need to be provided when
  902+    creating a shader object, along with reflection information about the
  903+    shader resource binding interface needed to bind sokol-gfx resources to the
  904+    proper shader inputs.
  905+
  906+    The easiest way to provide all this shader creation data is to use the
  907+    sokol-shdc shader compiler tool to compile shaders from a common
  908+    GLSL syntax into backend-specific sources or binary blobs, along with
  909+    shader interface information and uniform blocks and storage buffer array items
  910+    mapped to C structs.
  911+
  912+    To create a shader using a C header which has been code-generated by sokol-shdc:
  913+
  914+        // include the C header code-generated by sokol-shdc:
  915+        #include "myshader.glsl.h"
  916+        ...
  917+
  918+        // create shader using a code-generated helper function from the C header:
  919+        sg_shader shd = sg_make_shader(myshader_shader_desc(sg_query_backend()));
  920+
  921+    The samples in the 'sapp' subdirectory of the sokol-samples project
  922+    also use the sokol-shdc approach:
  923+
  924+        https://github.com/floooh/sokol-samples/tree/master/sapp
  925+
  926+    If you're planning to use sokol-shdc, you can stop reading here, instead
  927+    continue with the sokol-shdc documentation:
  928+
  929+        https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md
  930+
  931+    To create shaders with backend-specific shader code or binary blobs,
  932+    the sg_make_shader() function requires the following information:
  933+
  934+    - Shader code or shader binary blobs for the vertex- and fragment-, or the
  935+      compute-shader-stage:
  936+        - for the desktop GL backend, source code can be provided in '#version 410' or
  937+          '#version 430', version 430 is required when using storage buffers and
  938+          compute shaders, but note that this is not available on macOS
  939+        - for the GLES3 backend, source code must be provided in '#version 300 es' or
  940+          '#version 310 es' syntax (version 310 is required for storage buffer and
  941+          compute shader support, but note that this is not supported on WebGL2)
  942+        - for the D3D11 backend, shaders can be provided as source or binary
  943+          blobs, the source code should be in HLSL4.0 (for compatibility with old
  944+          low-end GPUs) or preferably in HLSL5.0 syntax, note that when
  945+          shader source code is provided for the D3D11 backend, sokol-gfx will
  946+          dynamically load 'd3dcompiler_47.dll'
  947+        - for the Metal backends, shaders can be provided as source or binary blobs, the
  948+          MSL version should be in 'metal-1.1' (other versions may work but are not tested)
  949+        - for the WebGPU backend, shaders must be provided as WGSL source code
  950+        - optionally the following shader-code related attributes can be provided:
  951+            - an entry function name (only on D3D11 or Metal, but not OpenGL)
  952+            - on D3D11 only, a compilation target (default is "vs_4_0" and "ps_4_0")
  953+
  954+    - Information about the input vertex attributes used by the vertex shader,
  955+      most of that backend-specific:
  956+        - An optional 'base type' (float, signed-/unsigned-int) for each vertex
  957+          attribute. When provided, this is used by the validation layer to check
  958+          that the CPU-side input vertex format is compatible with the input
  959+          vertex declaration of the vertex shader.
  960+        - Metal: no location information needed since vertex attributes are always bound
  961+          by their attribute location defined in the shader via '[[attribute(N)]]'
  962+        - WebGPU: no location information needed since vertex attributes are always
  963+          bound by their attribute location defined in the shader via `@location(N)`
  964+        - GLSL: vertex attribute names can be optionally provided, in that case their
  965+          location will be looked up by name, otherwise, the vertex attribute location
  966+          can be defined with 'layout(location = N)'
  967+        - D3D11: a 'semantic name' and 'semantic index' must be provided for each vertex
  968+          attribute, e.g. if the vertex attribute is defined as 'TEXCOORD1' in the shader,
  969+          the semantic name would be 'TEXCOORD', and the semantic index would be '1'
  970+
  971+      NOTE that vertex attributes currently must not have gaps. This requirement
  972+      may be relaxed in the future.
  973+
  974+    - Specifically for Metal compute shaders, the 'number of threads per threadgroup'
  975+      must be provided. Normally this is extracted by sokol-shdc from the GLSL
  976+      shader source code. For instance the following statement in the input
  977+      GLSL:
  978+
  979+        layout(local_size_x=64, local_size_y=1, local_size_z=1) in;
  980+
  981+      ...will be communicated to the sokol-gfx Metal backend in the
  982+      code-generated sg_shader_desc struct:
  983+
  984+        (sg_shader_desc){
  985+            .mtl_threads_per_threadgroup = { .x = 64, .y = 1, .z = 1 },
  986+        }
  987+
  988+    - Information about each uniform block binding used in the shader:
  989+        - the shader stage of the uniform block (vertex, fragment or compute)
  990+        - the size of the uniform block in number of bytes
  991+        - a memory layout hint (currently 'native' or 'std140') where 'native' defines a
  992+          backend-specific memory layout which shouldn't be used for cross-platform code.
  993+          Only std140 guarantees a backend-agnostic memory layout.
  994+        - a backend-specific bind slot:
  995+            - D3D11/HLSL: the buffer register N (`register(bN)`) where N is 0..7
  996+            - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 0..7
  997+            - WebGPU: the binding N in `@group(0) @binding(N)` where N is 0..15
  998+        - For GLSL only: a description of the internal uniform block layout, which maps
  999+          member types and their offsets on the CPU side to uniform variable names
 1000+          in the GLSL shader
 1001+        - please also NOTE the documentation sections about UNIFORM DATA LAYOUT
 1002+          and CROSS-BACKEND COMMON UNIFORM DATA LAYOUT below!
 1003+
 1004+    - A description of each resource binding (texture-, storage-buffer-
 1005+      and storage-image-bindings) which directly map to the sg_bindings.view[]
 1006+      array slots.
 1007+
 1008+      Each resource binding slot comes in three flavours:
 1009+
 1010+        1. Texture bindings with the following properties:
 1011+            - the shader stage of the texture (vertex, fragment or compute)
 1012+            - the expected image type:
 1013+                - SG_IMAGETYPE_2D
 1014+                - SG_IMAGETYPE_CUBE
 1015+                - SG_IMAGETYPE_3D
 1016+                - SG_IMAGETYPE_ARRAY
 1017+            - the expected 'image sample type':
 1018+                - SG_IMAGESAMPLETYPE_FLOAT
 1019+                - SG_IMAGESAMPLETYPE_DEPTH
 1020+                - SG_IMAGESAMPLETYPE_SINT
 1021+                - SG_IMAGESAMPLETYPE_UINT
 1022+                - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT
 1023+            - a flag whether the texture is expected to be multisampled
 1024+            - a backend-specific bind slot:
 1025+                - D3D11/HLSL: the texture register N (`register(tN)`) where N is 0..31
 1026+                (in HLSL, readonly storage buffers and texture share the same bind space)
 1027+                - Metal/MSL: the texture bind slot N (`[[texture(N)]]`) where N is 0..31
 1028+                (the bind slot must not collide with storage image bindings on the same stage)
 1029+                - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127
 1030+
 1031+        2. Storage buffer bindings with the following properties:
 1032+            - the shader stage of the storage buffer
 1033+            - a boolean 'readonly' flag, this is used for validation and hazard
 1034+            tracking in some 3D backends. Note that in render passes, only
 1035+            readonly storage buffer bindings are allowed. In compute passes, any
 1036+            read/write storage buffer binding is assumed to be written to by the
 1037+            compute shader.
 1038+            - a backend-specific bind slot:
 1039+                - D3D11/HLSL:
 1040+                    - for readonly storage buffer bindings: the texture register N
 1041+                    (`register(tN)`) where N is 0..31 (in HLSL, readonly storage
 1042+                    buffers and textures share the same bind space for
 1043+                    'shader resource views')
 1044+                    - for read/write storage buffer buffer bindings: the UAV register N
 1045+                    (`register(uN)`) where N is 0..31 (in HLSL, readwrite storage
 1046+                    buffers use their own bind space for 'unordered access views')
 1047+                - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 8..23
 1048+                - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127
 1049+                - GL/GLSL: the buffer binding N in `layout(binding=N)`
 1050+                  where N is 0..sg_limits.max_storage_buffer_bindings_per_stage
 1051+            - note that storage buffer bindings are not supported on all backends
 1052+            and platforms
 1053+
 1054+        3. Storage image bindings with the following properties:
 1055+            - the shader stage (*must* be compute)
 1056+            - the expected image type:
 1057+                - SG_IMAGETYPE_2D
 1058+                - SG_IMAGETYPE_CUBE
 1059+                - SG_IMAGETYPE_3D
 1060+                - SG_IMAGETYPE_ARRAY
 1061+            - the 'access pixel format', this is currently limited to:
 1062+                - SG_PIXELFORMAT_RGBA8
 1063+                - SG_PIXELFORMAT_RGBA8SN/UI/SI
 1064+                - SG_PIXELFORMAT_RGBA16UI/SI/F
 1065+                - SG_PIXELFORMAT_R32UIUI/SI/F
 1066+                - SG_PIXELFORMAT_RG32UI/SI/F
 1067+                - SG_PIXELFORMAT_RGBA32UI/SI/F
 1068+            - the access type (readwrite or writeonly)
 1069+            - a backend-specific bind slot:
 1070+                - D3D11/HLSL: the UAV register N (`register(uN)` where N is 0..31, the
 1071+                bind slot must not collide with UAV storage buffer bindings
 1072+                - Metal/MSL: the texture bind slot N (`[[texture(N)]])` where N is 0..31,
 1073+                the bind slot must not collide with other texture bindings on the same
 1074+                stage
 1075+                - WebGPU/WGSL: the binding N in `@group(1) @binding(N)` where N is 0..127
 1076+                - GL/GLSL: the buffer binding N in `layout(binding=N)`
 1077+                  where N is 0.._sg.max_storage_image_bindings_per_stage
 1078+            - note that storage image bindings are not supported on all backends and platforms
 1079+
 1080+    - A description of each sampler used in the shader:
 1081+        - the shader stage of the sampler (vertex, fragment or compute)
 1082+        - the expected sampler type:
 1083+            - SG_SAMPLERTYPE_FILTERING,
 1084+            - SG_SAMPLERTYPE_NONFILTERING,
 1085+            - SG_SAMPLERTYPE_COMPARISON,
 1086+        - a backend-specific bind slot:
 1087+            - D3D11/HLSL: the sampler register N (`register(sN)`) where N is 0..SG_MAX_SAMPLER_BINDINGS
 1088+            - Metal/MSL: the sampler bind slot N (`[[sampler(N)]]`) where N is 0..SG_MAX_SAMPLER_BINDINGS
 1089+            - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127
 1090+
 1091+    - An array of 'texture-sampler-pairs' used by the shader to sample textures,
 1092+      for D3D11, Metal and WebGPU this is used for validation purposes to check
 1093+      whether the texture and sampler are compatible with each other (especially
 1094+      WebGPU is very picky about combining the correct
 1095+      texture-sample-type with the correct sampler-type). For GLSL an
 1096+      additional 'combined-image-sampler name' must be provided because 'OpenGL
 1097+      style GLSL' cannot handle separate texture and sampler objects, but still
 1098+      groups them into a traditional GLSL 'sampler object'.
 1099+
 1100+    Compatibility rules for image-sample-type vs sampler-type are as follows:
 1101+
 1102+        - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING)
 1103+        - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING
 1104+        - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING
 1105+        - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING
 1106+        - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON
 1107+
 1108+    Backend-specific bindslot ranges (not relevant when using sokol-shdc):
 1109+
 1110+        - D3D11/HLSL:
 1111+            - separate bindslot space per shader stage
 1112+            - uniform block bindings (as cbuffer): `register(b0..b7)`
 1113+            - texture- and readonly storage buffer bindings: `register(t0..t31)`
 1114+            - read/write storage buffer and storage image bindings: `register(u0..u31)`
 1115+            - samplers: `register(s0..s11)`
 1116+        - Metal/MSL:
 1117+            - separate bindslot space per shader stage
 1118+            - uniform blocks: `[[buffer(0..7)]]`
 1119+            - storage buffers: `[[buffer(8..23)]]`
 1120+            - textures and storage image bindings: `[[texture(0..31)]]`
 1121+            - samplers: `[[sampler(0..11)]]`
 1122+        - WebGPU/WGSL:
 1123+            - common bindslot space across shader stages
 1124+            - uniform blocks: `@group(0) @binding(0..15)`
 1125+            - textures, storage-images, storage-buffers and sampler: `@group(1) @binding(0..127)`
 1126+        - GL/GLSL:
 1127+            - uniforms and image-samplers are bound by name
 1128+            - storage buffer bindings: `layout(std430, binding=0..sg_limits.max_storage_buffer_bindings_per_stage` (common
 1129+              bindslot space across shader stages)
 1130+            - storage image bindings: `layout(binding=0..sg_limits.max_storage_image_bindings_per_stage, [access_format])`
 1131+
 1132+    For example code of how to create backend-specific shader objects,
 1133+    please refer to the following samples:
 1134+
 1135+        - for D3D11:    https://github.com/floooh/sokol-samples/tree/master/d3d11
 1136+        - for Metal:    https://github.com/floooh/sokol-samples/tree/master/metal
 1137+        - for OpenGL:   https://github.com/floooh/sokol-samples/tree/master/glfw
 1138+        - for GLES3:    https://github.com/floooh/sokol-samples/tree/master/html5
 1139+        - for WebGPU:   https://github.com/floooh/sokol-samples/tree/master/wgpu
 1140+
 1141+
 1142+    ON SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT AND SG_SAMPLERTYPE_NONFILTERING
 1143+    ========================================================================
 1144+    The WebGPU backend introduces the concept of 'unfilterable-float' textures,
 1145+    which can only be combined with 'nonfiltering' samplers (this is a restriction
 1146+    specific to WebGPU, but since the same sokol-gfx code should work across
 1147+    all backend, the sokol-gfx validation layer also enforces this restriction
 1148+    - the alternative would be undefined behaviour in some backend APIs on
 1149+    some devices).
 1150+
 1151+    The background is that some mobile devices (most notably iOS devices) can
 1152+    not perform linear filtering when sampling textures with certain pixel
 1153+    formats, most notable the 32F formats:
 1154+
 1155+        - SG_PIXELFORMAT_R32F
 1156+        - SG_PIXELFORMAT_RG32F
 1157+        - SG_PIXELFORMAT_RGBA32F
 1158+
 1159+    The information of whether a shader is going to be used with such an
 1160+    unfilterable-float texture must already be provided in the sg_shader_desc
 1161+    struct when creating the shader (see the above section "ON SHADER CREATION").
 1162+
 1163+    If you are using the sokol-shdc shader compiler, the information whether a
 1164+    texture/sampler binding expects an 'unfilterable-float/nonfiltering'
 1165+    texture/sampler combination cannot be inferred from the shader source
 1166+    alone, you'll need to provide this hint via annotation-tags. For instance
 1167+    here is an example from the ozz-skin-sapp.c sample shader which samples an
 1168+    RGBA32F texture with skinning matrices in the vertex shader:
 1169+
 1170+    ```glsl
 1171+    @image_sample_type joint_tex unfilterable_float
 1172+    uniform texture2D joint_tex;
 1173+    @sampler_type smp nonfiltering
 1174+    uniform sampler smp;
 1175+    ```
 1176+
 1177+    This will result in SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT and
 1178+    SG_SAMPLERTYPE_NONFILTERING being written to the code-generated
 1179+    sg_shader_desc struct.
 1180+
 1181+
 1182+    ON VERTEX FORMATS
 1183+    =================
 1184+    Sokol-gfx implements the same strict mapping rules from CPU-side
 1185+    vertex component formats to GPU-side vertex input data types:
 1186+
 1187+    - float and packed normalized CPU-side formats must be used as
 1188+      floating point base type in the vertex shader
 1189+    - packed signed-integer CPU-side formats must be used as signed
 1190+      integer base type in the vertex shader
 1191+    - packed unsigned-integer CPU-side formats must be used as unsigned
 1192+      integer base type in the vertex shader
 1193+
 1194+    These mapping rules are enforced by the sokol-gfx validation layer,
 1195+    but only when sufficient reflection information is provided in
 1196+    `sg_shader_desc.attrs[].base_type`. This is the case when sokol-shdc
 1197+    is used, otherwise the default base_type will be SG_SHADERATTRBASETYPE_UNDEFINED
 1198+    which causes the sokol-gfx validation check to be skipped (of course you
 1199+    can also provide the per-attribute base type information manually when
 1200+    not using sokol-shdc).
 1201+
 1202+    The detailed mapping rules from SG_VERTEXFORMAT_* to GLSL data types
 1203+    are as follows:
 1204+
 1205+    - FLOAT[*] => float, vec*
 1206+    - BYTE4N => vec* (scaled to -1.0 .. +1.0)
 1207+    - UBYTE4N => vec* (scaled to 0.0 .. +1.0)
 1208+    - SHORT[*]N => vec* (scaled to -1.0 .. +1.0)
 1209+    - USHORT[*]N => vec* (scaled to 0.0 .. +1.0)
 1210+    - INT[*] => int, ivec*
 1211+    - UINT[*] => uint, uvec*
 1212+    - BYTE4 => int*
 1213+    - UBYTE4 => uint*
 1214+    - SHORT[*] => int*
 1215+    - USHORT[*] => uint*
 1216+
 1217+    NOTE that sokol-gfx only provides vertex formats with sizes of a multiple
 1218+    of 4 (e.g. BYTE4N but not BYTE2N). This is because vertex components must
 1219+    be 4-byte aligned anyway.
 1220+
 1221+
 1222+    UNIFORM DATA LAYOUT:
 1223+    ====================
 1224+    NOTE: if you use the sokol-shdc shader compiler tool, you don't need to worry
 1225+    about the following details.
 1226+
 1227+    The data that's passed into the sg_apply_uniforms() function must adhere to
 1228+    specific layout rules so that the GPU shader finds the uniform block
 1229+    items at the right offset.
 1230+
 1231+    For the D3D11 and Metal backends, sokol-gfx only cares about the size of uniform
 1232+    blocks, but not about the internal layout. The data will just be copied into
 1233+    a uniform/constant buffer in a single operation and it's up you to arrange the
 1234+    CPU-side layout so that it matches the GPU side layout. This also means that with
 1235+    the D3D11 and Metal backends you are not limited to a 'cross-platform' subset
 1236+    of uniform variable types.
 1237+
 1238+    If you ever only use one of the D3D11, Metal *or* WebGPU backend, you can stop reading here.
 1239+
 1240+    For the GL backends, the internal layout of uniform blocks matters though,
 1241+    and you are limited to a small number of uniform variable types. This is
 1242+    because sokol-gfx must be able to locate the uniform block members in order
 1243+    to upload them to the GPU with glUniformXXX() calls.
 1244+
 1245+    To describe the uniform block layout to sokol-gfx, the following information
 1246+    must be passed to the sg_make_shader() call in the sg_shader_desc struct:
 1247+
 1248+        - a hint about the used packing rule (either SG_UNIFORMLAYOUT_NATIVE or
 1249+          SG_UNIFORMLAYOUT_STD140)
 1250+        - a list of the uniform block members types in the correct order they
 1251+          appear on the CPU side
 1252+
 1253+    For example if the GLSL shader has the following uniform declarations:
 1254+
 1255+        uniform mat4 mvp;
 1256+        uniform vec2 offset0;
 1257+        uniform vec2 offset1;
 1258+        uniform vec2 offset2;
 1259+
 1260+    ...and on the CPU side, there's a similar C struct:
 1261+
 1262+        typedef struct {
 1263+            float mvp[16];
 1264+            float offset0[2];
 1265+            float offset1[2];
 1266+            float offset2[2];
 1267+        } params_t;
 1268+
 1269+    ...the uniform block description in the sg_shader_desc must look like this:
 1270+
 1271+        sg_shader_desc desc = {
 1272+            .vs.uniform_blocks[0] = {
 1273+                .size = sizeof(params_t),
 1274+                .layout = SG_UNIFORMLAYOUT_NATIVE,  // this is the default and can be omitted
 1275+                .uniforms = {
 1276+                    // order must be the same as in 'params_t':
 1277+                    [0] = { .name = "mvp", .type = SG_UNIFORMTYPE_MAT4 },
 1278+                    [1] = { .name = "offset0", .type = SG_UNIFORMTYPE_VEC2 },
 1279+                    [2] = { .name = "offset1", .type = SG_UNIFORMTYPE_VEC2 },
 1280+                    [3] = { .name = "offset2", .type = SG_UNIFORMTYPE_VEC2 },
 1281+                }
 1282+            }
 1283+        };
 1284+
 1285+    With this information sokol-gfx can now compute the correct offsets of the data items
 1286+    within the uniform block struct.
 1287+
 1288+    The SG_UNIFORMLAYOUT_NATIVE packing rule works fine if only the GL backends are used,
 1289+    but for proper D3D11/Metal/GL a subset of the std140 layout must be used which is
 1290+    described in the next section:
 1291+
 1292+
 1293+    CROSS-BACKEND COMMON UNIFORM DATA LAYOUT
 1294+    ========================================
 1295+    For cross-platform / cross-3D-backend code it is important that the same uniform block
 1296+    layout on the CPU side can be used for all sokol-gfx backends. To achieve this,
 1297+    a common subset of the std140 layout must be used:
 1298+
 1299+    - The uniform block layout hint in sg_shader_desc must be explicitly set to
 1300+      SG_UNIFORMLAYOUT_STD140.
 1301+    - Only the following GLSL uniform types can be used (with their associated sokol-gfx enums):
 1302+        - float => SG_UNIFORMTYPE_FLOAT
 1303+        - vec2  => SG_UNIFORMTYPE_FLOAT2
 1304+        - vec3  => SG_UNIFORMTYPE_FLOAT3
 1305+        - vec4  => SG_UNIFORMTYPE_FLOAT4
 1306+        - int   => SG_UNIFORMTYPE_INT
 1307+        - ivec2 => SG_UNIFORMTYPE_INT2
 1308+        - ivec3 => SG_UNIFORMTYPE_INT3
 1309+        - ivec4 => SG_UNIFORMTYPE_INT4
 1310+        - mat4  => SG_UNIFORMTYPE_MAT4
 1311+    - Alignment for those types must be as follows (in bytes):
 1312+        - float => 4
 1313+        - vec2  => 8
 1314+        - vec3  => 16
 1315+        - vec4  => 16
 1316+        - int   => 4
 1317+        - ivec2 => 8
 1318+        - ivec3 => 16
 1319+        - ivec4 => 16
 1320+        - mat4  => 16
 1321+    - Arrays are only allowed for the following types: vec4, int4, mat4.
 1322+
 1323+    Note that the HLSL cbuffer layout rules are slightly different from the
 1324+    std140 layout rules, this means that the cbuffer declarations in HLSL code
 1325+    must be tweaked so that the layout is compatible with std140.
 1326+
 1327+    The by far easiest way to tackle the common uniform block layout problem is
 1328+    to use the sokol-shdc shader cross-compiler tool!
 1329+
 1330+
 1331+    ON STORAGE BUFFERS
 1332+    ==================
 1333+    The two main purpose of storage buffers are:
 1334+
 1335+        - to be populated by compute shaders with dynamically generated data
 1336+        - for providing random-access data to all shader stages
 1337+
 1338+    Storage buffers can be used to pass large amounts of random access structured
 1339+    data from the CPU side to the shaders. They are similar to data textures, but are
 1340+    more convenient to use both on the CPU and shader side since they can be accessed
 1341+    in shaders as as a 1-dimensional array of struct items.
 1342+
 1343+    Storage buffers are *NOT* supported on the following platform/backend combos:
 1344+
 1345+    - macOS+GL (because storage buffers require GL 4.3, while macOS only goes up to GL 4.1)
 1346+    - platforms which only support a GLES3.0 context (WebGL2 and iOS)
 1347+
 1348+    To use storage buffers, the following steps are required:
 1349+
 1350+        - write a shader which uses storage buffers (vertex- and fragment-shaders
 1351+          can only read from storage buffers, while compute-shaders can both read
 1352+          and write storage buffers)
 1353+        - create one or more storage buffers via sg_make_buffer() with the
 1354+          `.usage.storage_buffer = true`
 1355+        - when creating a shader via sg_make_shader(), populate the sg_shader_desc
 1356+          struct with binding info (when using sokol-shdc, this step will be taken care
 1357+          of automatically)
 1358+            - which storage buffer bind slots on the vertex-, fragment- or compute-stage
 1359+              are occupied
 1360+            - whether the storage buffer on that bind slot is readonly (readonly
 1361+              bindings are required for vertex- and fragment-shaders, and in compute
 1362+              shaders the readonly flag is used to control hazard tracking in some
 1363+              3D backends)
 1364+
 1365+        - when calling sg_apply_bindings(), apply the matching bind slots with the previously
 1366+          created storage buffers
 1367+        - ...and that's it.
 1368+
 1369+    For more details, see the following backend-agnostic sokol samples:
 1370+
 1371+    - simple vertex pulling from a storage buffer:
 1372+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.c
 1373+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.glsl
 1374+    - instanced rendering via storage buffers (vertex- and instance-pulling):
 1375+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.c
 1376+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.glsl
 1377+    - storage buffers both on the vertex- and fragment-stage:
 1378+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.c
 1379+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.glsl
 1380+    - the Ozz animation sample rewritten to pull all rendering data from storage buffers:
 1381+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.cc
 1382+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.glsl
 1383+    - the instancing sample modified to use compute shaders:
 1384+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.c
 1385+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.glsl
 1386+    - the Compute Boids sample ported to sokol-gfx:
 1387+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.c
 1388+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.glsl
 1389+
 1390+    ...also see the following backend-specific vertex pulling samples (those also don't use sokol-shdc):
 1391+
 1392+    - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/vertexpulling-d3d11.c
 1393+    - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/vertexpulling-glfw.c
 1394+    - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/vertexpulling-metal.c
 1395+    - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/vertexpulling-wgpu.c
 1396+
 1397+    ...and the backend specific compute shader samples:
 1398+
 1399+    - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/instancing-compute-d3d11.c
 1400+    - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/instancing-compute-glfw.c
 1401+    - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/instancing-compute-metal.c
 1402+    - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/instancing-compute-wgpu.c
 1403+
 1404+    Storage buffer shader authoring caveats when using sokol-shdc:
 1405+
 1406+        - declare a read-only storage buffer interface block with `layout(binding=N) readonly buffer [name] { ... }`
 1407+          (where 'N' is the index in `sg_bindings.storage_buffers[N]`)
 1408+        - ...or a read/write storage buffer interface block with `layout(binding=N) buffer [name] { ... }`
 1409+        - declare a struct which describes a single array item in the storage buffer interface block
 1410+        - only put a single flexible array member into the storage buffer interface block
 1411+
 1412+    E.g. a complete example in 'sokol-shdc GLSL':
 1413+
 1414+        ```glsl
 1415+        @vs
 1416+        // declare a struct:
 1417+        struct sb_vertex {
 1418+            vec3 pos;
 1419+            vec4 color;
 1420+        }
 1421+        // declare a buffer interface block with a single flexible struct array:
 1422+        layout(binding=0) readonly buffer vertices {
 1423+            sb_vertex vtx[];
 1424+        }
 1425+        // in the shader function, access the storage buffer like this:
 1426+        void main() {
 1427+            vec3 pos = vtx[gl_VertexIndex].pos;
 1428+            ...
 1429+        }
 1430+        @end
 1431+        ```
 1432+
 1433+    In a compute shader you can read and write the same item in the same
 1434+    storage buffer (but you'll have to be careful for random access since
 1435+    many threads of the same compute function run in parallel):
 1436+
 1437+        @cs
 1438+        struct sb_item {
 1439+            vec3 pos;
 1440+            vec3 vel;
 1441+        }
 1442+        layout(binding=0) buffer items_ssbo {
 1443+            sb_item items[];
 1444+        }
 1445+        layout(local_size_x=64, local_size_y=1, local_size_z=1) in;
 1446+        void main() {
 1447+            uint idx = gl_GlobalInvocationID.x;
 1448+            vec3 pos = items[idx].pos;
 1449+            ...
 1450+            items[idx].pos = pos;
 1451+        }
 1452+        @end
 1453+
 1454+    Backend-specific storage-buffer caveats (not relevant when using sokol-shdc):
 1455+
 1456+        D3D11:
 1457+            - storage buffers are created as 'raw' Byte Address Buffers
 1458+              (https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-intro#raw-views-of-buffers)
 1459+            - in HLSL, use a ByteAddressBuffer for readonly access of the buffer content:
 1460+              (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-byteaddressbuffer)
 1461+            - ...or RWByteAddressBuffer for read/write access:
 1462+              (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwbyteaddressbuffer)
 1463+            - readonly-storage buffers and textures are both bound as 'shader-resource-view' and
 1464+              share the same bind slots (declared as `register(tN)` in HLSL), where N must be in the range 0..23)
 1465+            - read/write storage buffers and storage images are bound as 'unordered-access-view'
 1466+              (declared as `register(uN)` in HLSL where N is in the range 0..11)
 1467+
 1468+        Metal:
 1469+            - in Metal there is no internal difference between vertex-, uniform- and
 1470+              storage-buffers, all are bound to the same 'buffer bind slots' with the
 1471+              following reserved ranges:
 1472+                - vertex shader stage:
 1473+                    - uniform buffers: slots 0..7
 1474+                    - storage buffers: slots 8..15
 1475+                    - vertex buffers: slots 15..23
 1476+                - fragment shader stage:
 1477+                    - uniform buffers: slots 0..7
 1478+                    - storage buffers: slots 8..15
 1479+            - this means in MSL, storage buffer bindings start at [[buffer(8)]] both in
 1480+              the vertex and fragment stage
 1481+
 1482+        GL:
 1483+            - the GL backend doesn't use name-lookup to find storage buffer bindings, this
 1484+              means you must annotate buffers with `layout(std430, binding=N)` in GLSL
 1485+            - ...where N is 0..sg_limits.max_storage_buffer_bindings_per_stage.
 1486+
 1487+        WebGPU:
 1488+            - in WGSL, textures, samplers and storage buffers all use a shared
 1489+              bindspace across all shader stages on bindgroup 1:
 1490+
 1491+              `@group(1) @binding(0..127)
 1492+
 1493+    ON STORAGE IMAGES:
 1494+    ==================
 1495+    To write pixel data to texture objects in compute shaders, first an image
 1496+    object must be created with `storage_image usage`:
 1497+
 1498+        sg_image storage_image = sg_make_image(&(sg_image_desc){
 1499+            .usage.storage_image = true,
 1500+            },
 1501+            .width = ...,
 1502+            .height = ...,
 1503+            .pixel_format = ...,
 1504+        });
 1505+
 1506+    Next a storage-image-view object is required which also allows to pick
 1507+    a specific mip-level or slice for the compute-shader to access:
 1508+
 1509+        sg_view simg_view = sg_make_view(&(sg_view_desc){
 1510+            .storage_image = {
 1511+                .image = storage_image,
 1512+                .mip_level = ...,
 1513+                .slice = ...
 1514+            },
 1515+        });
 1516+
 1517+    Finally 'bind' the storage-image-view via a regular sg_apply_bindings() call
 1518+    inside a compute pass:
 1519+
 1520+        sg_begin_pass(&(sg_pass){ .compute = true });
 1521+        sg_apply_pipeline(...);
 1522+        sg_apply_bindings(&(sg_bindings){
 1523+            .views[VIEW_simg] = simg_view,
 1524+        });
 1525+        sg_dispatch(...);
 1526+        sg_end_pass();
 1527+
 1528+    Currently, storage images can only be used with `readwrite` or `writeonly` access in
 1529+    shaders. For readonly access use a regular texture binding instead.
 1530+
 1531+    For an example of using storage images in compute shaders see imageblur-sapp:
 1532+
 1533+        - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/imageblur-sapp.c
 1534+        - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/imageblur-sapp.glsl
 1535+
 1536+    TRACE HOOKS:
 1537+    ============
 1538+    sokol_gfx.h optionally allows to install "trace hook" callbacks for
 1539+    each public API functions. When a public API function is called, and
 1540+    a trace hook callback has been installed for this function, the
 1541+    callback will be invoked with the parameters and result of the function.
 1542+    This is useful for things like debugging- and profiling-tools, or
 1543+    keeping track of resource creation and destruction.
 1544+
 1545+    To use the trace hook feature:
 1546+
 1547+    --- Define SOKOL_TRACE_HOOKS before including the implementation.
 1548+
 1549+    --- Setup an sg_trace_hooks structure with your callback function
 1550+        pointers (keep all function pointers you're not interested
 1551+        in zero-initialized), optionally set the user_data member
 1552+        in the sg_trace_hooks struct.
 1553+
 1554+    --- Install the trace hooks by calling sg_install_trace_hooks(),
 1555+        the return value of this function is another sg_trace_hooks
 1556+        struct which contains the previously set of trace hooks.
 1557+        You should keep this struct around, and call those previous
 1558+        functions pointers from your own trace callbacks for proper
 1559+        chaining.
 1560+
 1561+    As an example of how trace hooks are used, have a look at the
 1562+    imgui/sokol_gfx_imgui.h header which implements a realtime
 1563+    debugging UI for sokol_gfx.h on top of Dear ImGui.
 1564+
 1565+
 1566+    MEMORY ALLOCATION OVERRIDE
 1567+    ==========================
 1568+    You can override the memory allocation functions at initialization time
 1569+    like this:
 1570+
 1571+        void* my_alloc(size_t size, void* user_data) {
 1572+            return malloc(size);
 1573+        }
 1574+
 1575+        void my_free(void* ptr, void* user_data) {
 1576+            free(ptr);
 1577+        }
 1578+
 1579+        ...
 1580+            sg_setup(&(sg_desc){
 1581+                // ...
 1582+                .allocator = {
 1583+                    .alloc_fn = my_alloc,
 1584+                    .free_fn = my_free,
 1585+                    .user_data = ...,
 1586+                }
 1587+            });
 1588+        ...
 1589+
 1590+    If no overrides are provided, malloc and free will be used.
 1591+
 1592+    This only affects memory allocation calls done by sokol_gfx.h
 1593+    itself though, not any allocations in OS libraries.
 1594+
 1595+
 1596+    ERROR REPORTING AND LOGGING
 1597+    ===========================
 1598+    To get any logging information at all you need to provide a logging callback in the setup call
 1599+    the easiest way is to use sokol_log.h:
 1600+
 1601+        #include "sokol_log.h"
 1602+
 1603+        sg_setup(&(sg_desc){ .logger.func = slog_func });
 1604+
 1605+    To override logging with your own callback, first write a logging function like this:
 1606+
 1607+        void my_log(const char* tag,                // e.g. 'sg'
 1608+                    uint32_t log_level,             // 0=panic, 1=error, 2=warn, 3=info
 1609+                    uint32_t log_item_id,           // SG_LOGITEM_*
 1610+                    const char* message_or_null,    // a message string, may be nullptr in release mode
 1611+                    uint32_t line_nr,               // line number in sokol_gfx.h
 1612+                    const char* filename_or_null,   // source filename, may be nullptr in release mode
 1613+                    void* user_data)
 1614+        {
 1615+            ...
 1616+        }
 1617+
 1618+    ...and then setup sokol-gfx like this:
 1619+
 1620+        sg_setup(&(sg_desc){
 1621+            .logger = {
 1622+                .func = my_log,
 1623+                .user_data = my_user_data,
 1624+            }
 1625+        });
 1626+
 1627+    The provided logging function must be reentrant (e.g. be callable from
 1628+    different threads).
 1629+
 1630+    If you don't want to provide your own custom logger it is highly recommended to use
 1631+    the standard logger in sokol_log.h instead, otherwise you won't see any warnings or
 1632+    errors.
 1633+
 1634+
 1635+    COMMIT LISTENERS
 1636+    ================
 1637+    It's possible to hook callback functions into sokol-gfx which are called from
 1638+    inside sg_commit() in unspecified order. This is mainly useful for libraries
 1639+    that build on top of sokol_gfx.h to be notified about the end/start of a frame.
 1640+
 1641+    To add a commit listener, call:
 1642+
 1643+        static void my_commit_listener(void* user_data) {
 1644+            ...
 1645+        }
 1646+
 1647+        bool success = sg_add_commit_listener((sg_commit_listener){
 1648+            .func = my_commit_listener,
 1649+            .user_data = ...,
 1650+        });
 1651+
 1652+    The function returns false if the internal array of commit listeners is full,
 1653+    or the same commit listener had already been added.
 1654+
 1655+    If the function returns true, my_commit_listener() will be called each frame
 1656+    from inside sg_commit().
 1657+
 1658+    By default, 1024 distinct commit listeners can be added, but this number
 1659+    can be tweaked in the sg_setup() call:
 1660+
 1661+        sg_setup(&(sg_desc){
 1662+            .max_commit_listeners = 2048,
 1663+        });
 1664+
 1665+    An sg_commit_listener item is equal to another if both the function
 1666+    pointer and user_data field are equal.
 1667+
 1668+    To remove a commit listener:
 1669+
 1670+        bool success = sg_remove_commit_listener((sg_commit_listener){
 1671+            .func = my_commit_listener,
 1672+            .user_data = ...,
 1673+        });
 1674+
 1675+    ...where the .func and .user_data field are equal to a previous
 1676+    sg_add_commit_listener() call. The function returns true if the commit
 1677+    listener item was found and removed, and false otherwise.
 1678+
 1679+
 1680+    RESOURCE CREATION AND DESTRUCTION IN DETAIL
 1681+    ===========================================
 1682+    The 'vanilla' way to create resource objects is with the 'make functions':
 1683+
 1684+        sg_buffer sg_make_buffer(const sg_buffer_desc* desc)
 1685+        sg_image sg_make_image(const sg_image_desc* desc)
 1686+        sg_sampler sg_make_sampler(const sg_sampler_desc* desc)
 1687+        sg_shader sg_make_shader(const sg_shader_desc* desc)
 1688+        sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc)
 1689+        sg_view sg_make_view(const sg_view_desc* desc)
 1690+
 1691+    This will result in one of three cases:
 1692+
 1693+        1. The returned handle is invalid. This happens when there are no more
 1694+           free slots in the resource pool for this resource type. An invalid
 1695+           handle is associated with the INVALID resource state, for instance:
 1696+
 1697+                sg_buffer buf = sg_make_buffer(...)
 1698+                if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID) {
 1699+                    // buffer pool is exhausted
 1700+                }
 1701+
 1702+        2. The returned handle is valid, but creating the underlying resource
 1703+           has failed for some reason. This results in a resource object in the
 1704+           FAILED state. The reason *why* resource creation has failed differ
 1705+           by resource type. Look for log messages with more details. A failed
 1706+           resource state can be checked with:
 1707+
 1708+                sg_buffer buf = sg_make_buffer(...)
 1709+                if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED) {
 1710+                    // creating the resource has failed
 1711+                }
 1712+
 1713+        3. And finally, if everything goes right, the returned resource is
 1714+           in resource state VALID and ready to use. This can be checked
 1715+           with:
 1716+
 1717+                sg_buffer buf = sg_make_buffer(...)
 1718+                if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID) {
 1719+                    // creating the resource has succeeded
 1720+                }
 1721+
 1722+    When calling the 'make functions', the created resource goes through a number
 1723+    of states:
 1724+
 1725+        - INITIAL: the resource slot associated with the new resource is currently
 1726+          free (technically, there is no resource yet, just an empty pool slot)
 1727+        - ALLOC: a handle for the new resource has been allocated, this just means
 1728+          a pool slot has been reserved.
 1729+        - VALID or FAILED: in VALID state any 3D API backend resource objects have
 1730+          been successfully created, otherwise if anything went wrong, the resource
 1731+          will be in FAILED state.
 1732+
 1733+    Sometimes it makes sense to first grab a handle, but initialize the
 1734+    underlying resource at a later time. For instance when loading data
 1735+    asynchronously from a slow data source, you may know what buffers and
 1736+    textures are needed at an early stage of the loading process, but actually
 1737+    loading the buffer or texture content can only be completed at a later time.
 1738+
 1739+    For such situations, sokol-gfx resource objects can be created in two steps.
 1740+    You can allocate a handle upfront with one of the 'alloc functions':
 1741+
 1742+        sg_buffer sg_alloc_buffer(void)
 1743+        sg_image sg_alloc_image(void)
 1744+        sg_sampler sg_alloc_sampler(void)
 1745+        sg_shader sg_alloc_shader(void)
 1746+        sg_pipeline sg_alloc_pipeline(void)
 1747+        sg_view sg_alloc_view(void)
 1748+
 1749+    This will return a handle with the underlying resource object in the
 1750+    ALLOC state:
 1751+
 1752+        sg_image img = sg_alloc_image();
 1753+        if (sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC) {
 1754+            // allocating an image handle has succeeded, otherwise
 1755+            // the image pool is full
 1756+        }
 1757+
 1758+    Such an 'incomplete' handle can be used in most sokol-gfx rendering functions
 1759+    without doing any harm, sokol-gfx will simply skip any rendering operation
 1760+    that involve resources which are not in VALID state.
 1761+
 1762+    At a later time (for instance once the texture has completed loading
 1763+    asynchronously), the resource creation can be completed by calling one of
 1764+    the 'init functions', those functions take an existing resource handle and
 1765+    'desc struct':
 1766+
 1767+        void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc)
 1768+        void sg_init_image(sg_image img, const sg_image_desc* desc)
 1769+        void sg_init_sampler(sg_sampler smp, const sg_sampler_desc* desc)
 1770+        void sg_init_shader(sg_shader shd, const sg_shader_desc* desc)
 1771+        void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc)
 1772+        void sg_init_view(sg_view view, const sg_view_desc* desc)
 1773+
 1774+    The init functions expect a resource in ALLOC state, and after the function
 1775+    returns, the resource will be either in VALID or FAILED state. Calling
 1776+    an 'alloc function' followed by the matching 'init function' is fully
 1777+    equivalent with calling the 'make function' alone.
 1778+
 1779+    Destruction can also happen as a two-step process. The 'uninit functions'
 1780+    will put a resource object from the VALID or FAILED state back into the
 1781+    ALLOC state:
 1782+
 1783+        void sg_uninit_buffer(sg_buffer buf)
 1784+        void sg_uninit_image(sg_image img)
 1785+        void sg_uninit_sampler(sg_sampler smp)
 1786+        void sg_uninit_shader(sg_shader shd)
 1787+        void sg_uninit_pipeline(sg_pipeline pip)
 1788+        void sg_uninit_view(sg_view view)
 1789+
 1790+    Calling the 'uninit functions' with a resource that is not in the VALID or
 1791+    FAILED state is a no-op.
 1792+
 1793+    To finally free the pool slot for recycling call the 'dealloc functions':
 1794+
 1795+        void sg_dealloc_buffer(sg_buffer buf)
 1796+        void sg_dealloc_image(sg_image img)
 1797+        void sg_dealloc_sampler(sg_sampler smp)
 1798+        void sg_dealloc_shader(sg_shader shd)
 1799+        void sg_dealloc_pipeline(sg_pipeline pip)
 1800+        void sg_dealloc_view(sg_view view)
 1801+
 1802+    Calling the 'dealloc functions' on a resource that's not in ALLOC state is
 1803+    a no-op, but will generate a warning log message.
 1804+
 1805+    Calling an 'uninit function' and 'dealloc function' in sequence is equivalent
 1806+    with calling the associated 'destroy function':
 1807+
 1808+        void sg_destroy_buffer(sg_buffer buf)
 1809+        void sg_destroy_image(sg_image img)
 1810+        void sg_destroy_sampler(sg_sampler smp)
 1811+        void sg_destroy_shader(sg_shader shd)
 1812+        void sg_destroy_pipeline(sg_pipeline pip)
 1813+        void sg_destroy_view(sg_view view)
 1814+
 1815+    The 'destroy functions' can be called on resources in any state and generally
 1816+    do the right thing (for instance if the resource is in ALLOC state, the destroy
 1817+    function will be equivalent to the 'dealloc function' and skip the 'uninit part').
 1818+
 1819+    And finally to close the circle, the 'fail functions' can be called to manually
 1820+    put a resource in ALLOC state into the FAILED state:
 1821+
 1822+        sg_fail_buffer(sg_buffer buf)
 1823+        sg_fail_image(sg_image img)
 1824+        sg_fail_sampler(sg_sampler smp)
 1825+        sg_fail_shader(sg_shader shd)
 1826+        sg_fail_pipeline(sg_pipeline pip)
 1827+        sg_fail_view(sg_view view)
 1828+
 1829+    This is recommended if anything went wrong outside of sokol-gfx during asynchronous
 1830+    resource setup (for instance a file loading operation failed). In this case,
 1831+    the 'fail function' should be called instead of the 'init function'.
 1832+
 1833+    Calling a 'fail function' on a resource that's not in ALLOC state is a no-op,
 1834+    but will generate a warning log message.
 1835+
 1836+    NOTE: that two-step resource creation usually only makes sense for buffers,
 1837+    images and views, but not for samplers, shaders or pipelines. Most notably, trying
 1838+    to create a pipeline object with a shader that's not in VALID state will
 1839+    trigger a validation layer error, or if the validation layer is disabled,
 1840+    result in a pipeline object in FAILED state.
 1841+
 1842+
 1843+    WEBGPU CAVEATS
 1844+    ==============
 1845+    For a general overview and design notes of the WebGPU backend see:
 1846+
 1847+        https://floooh.github.io/2023/10/16/sokol-webgpu.html
 1848+
 1849+    In general, don't expect an automatic speedup when switching from the WebGL2
 1850+    backend to the WebGPU backend. Some WebGPU functions currently actually
 1851+    have a higher CPU overhead than similar WebGL2 functions, leading to the
 1852+    paradoxical situation that some WebGPU code may be slower than similar WebGL2
 1853+    code.
 1854+
 1855+    - when writing WGSL shader code by hand, a specific bind-slot convention
 1856+      must be used:
 1857+
 1858+      All uniform block structs must use `@group(0)` and bindings in the
 1859+      range 0..15
 1860+
 1861+        @group(0) @binding(0..15)
 1862+
 1863+      All textures, samplers, storage-buffers and storage-images must use `@group(1)`
 1864+      and bindings must be in the range 0..127:
 1865+
 1866+        @group(1) @binding(0..127)
 1867+
 1868+      Note that the number of texture, sampler, storage-buffer storage-image bindings
 1869+      is still limited despite the large bind range:
 1870+
 1871+        - up to 16 textures and sampler across all shader stages
 1872+        - up to 8 storage buffers across all shader stages
 1873+        - up to 4 storage images on the compute shader stage
 1874+
 1875+      If you use sokol-shdc to generate WGSL shader code, you don't need to worry
 1876+      about the above binding conventions since sokol-shdc will allocate
 1877+      the WGSL bindslots).
 1878+
 1879+    - The sokol-gfx WebGPU backend uses the sg_desc.uniform_buffer_size item
 1880+      to allocate a single per-frame uniform buffer which must be big enough
 1881+      to hold all data written by sg_apply_uniforms() during a single frame,
 1882+      including a worst-case 256-byte alignment (e.g. each sg_apply_uniform
 1883+      call will cost at least 256 bytes of uniform buffer size). The default size
 1884+      is 4 MB, which is enough for 16384 sg_apply_uniform() calls per
 1885+      frame (assuming the uniform data 'payload' is less than 256 bytes
 1886+      per call). These rules are the same as for the Metal backend, so if
 1887+      you are already using the Metal backend you'll be fine.
 1888+
 1889+    - sg_apply_bindings(): the sokol-gfx WebGPU backend implements a bindgroup
 1890+      cache to prevent excessive creation and destruction of BindGroup objects
 1891+      when calling sg_apply_bindings(). The number of slots in the bindgroups
 1892+      cache is defined in sg_desc.wgpu.bindgroups_cache_size when calling
 1893+      sg_setup. The cache size must be a power-of-2 number, with the default being
 1894+      1024. The bindgroups cache behaviour can be observed by calling the new
 1895+      function sg_query_stats(), where the following struct items are
 1896+      of interest:
 1897+
 1898+        .wgpu.num_bindgroup_cache_hits
 1899+        .wgpu.num_bindgroup_cache_misses
 1900+        .wgpu.num_bindgroup_cache_collisions
 1901+        .wgpu_num_bindgroup_cache_invalidates
 1902+        .wgpu.num_bindgroup_cache_vs_hash_key_mismatch
 1903+
 1904+      The value to pay attention to is `.wgpu.num_bindgroup_cache_collisions`,
 1905+      if this number is consistently higher than a few percent of the
 1906+      .wgpu.num_set_bindgroup value, it might be a good idea to bump the
 1907+      bindgroups cache size to the next power-of-2.
 1908+
 1909+    - sg_apply_viewport(): WebGPU currently has a unique restriction that viewport
 1910+      rectangles must be contained entirely within the framebuffer. As a shitty
 1911+      workaround sokol_gfx.h will clip incoming viewport rectangles against
 1912+      the framebuffer, but this will distort the clipspace-to-screenspace mapping.
 1913+      There's no proper way to handle this inside sokol_gfx.h, this must be fixed
 1914+      in a future WebGPU update (see: https://github.com/gpuweb/gpuweb/issues/373
 1915+      and https://github.com/gpuweb/gpuweb/pull/5025)
 1916+
 1917+    - The sokol shader compiler generally adds `diagnostic(off, derivative_uniformity);`
 1918+      into the WGSL output. Currently only the Chrome WebGPU implementation seems
 1919+      to recognize this.
 1920+
 1921+    - Likewise, the following sokol-gfx pixel formats are not supported in WebGPU:
 1922+      R16, R16SN, RG16, RG16SN, RGBA16, RGBA16SN.
 1923+      Unlike unsupported vertex formats, unsupported pixel formats can be queried
 1924+      in cross-backend code via sg_query_pixelformat() though.
 1925+
 1926+    - The Emscripten WebGPU shim currently doesn't support the Closure minification
 1927+      post-link-step (e.g. currently the emcc argument '--closure 1' or '--closure 2'
 1928+      will generate broken Javascript code.
 1929+
 1930+    - sokol-gfx requires the WebGPU device feature `depth32float-stencil8` to be enabled
 1931+      (this should be widely supported)
 1932+
 1933+    - sokol-gfx expects that the WebGPU device feature `float32-filterable` to *not* be
 1934+      enabled (since this would exclude all iOS devices)
 1935+
 1936+
 1937+    LICENSE
 1938+    =======
 1939+    zlib/libpng license
 1940+
 1941+    Copyright (c) 2018 Andre Weissflog
 1942+
 1943+    This software is provided 'as-is', without any express or implied warranty.
 1944+    In no event will the authors be held liable for any damages arising from the
 1945+    use of this software.
 1946+
 1947+    Permission is granted to anyone to use this software for any purpose,
 1948+    including commercial applications, and to alter it and redistribute it
 1949+    freely, subject to the following restrictions:
 1950+
 1951+        1. The origin of this software must not be misrepresented; you must not
 1952+        claim that you wrote the original software. If you use this software in a
 1953+        product, an acknowledgment in the product documentation would be
 1954+        appreciated but is not required.
 1955+
 1956+        2. Altered source versions must be plainly marked as such, and must not
 1957+        be misrepresented as being the original software.
 1958+
 1959+        3. This notice may not be removed or altered from any source
 1960+        distribution.
 1961+*/
 1962+#define SOKOL_GFX_INCLUDED (1)
 1963+#include <stddef.h>     // size_t
 1964+#include <stdint.h>
 1965+#include <stdbool.h>
 1966+
 1967+#if defined(SOKOL_API_DECL) && !defined(SOKOL_GFX_API_DECL)
 1968+#define SOKOL_GFX_API_DECL SOKOL_API_DECL
 1969+#endif
 1970+#ifndef SOKOL_GFX_API_DECL
 1971+#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GFX_IMPL)
 1972+#define SOKOL_GFX_API_DECL __declspec(dllexport)
 1973+#elif defined(_WIN32) && defined(SOKOL_DLL)
 1974+#define SOKOL_GFX_API_DECL __declspec(dllimport)
 1975+#else
 1976+#define SOKOL_GFX_API_DECL extern
 1977+#endif
 1978+#endif
 1979+
 1980+#ifdef __cplusplus
 1981+extern "C" {
 1982+#endif
 1983+
 1984+/*
 1985+    Resource id typedefs:
 1986+
 1987+    sg_buffer:      vertex- and index-buffers
 1988+    sg_image:       images used as textures and render-pass attachments
 1989+    sg_sampler      sampler objects describing how a texture is sampled in a shader
 1990+    sg_shader:      vertex- and fragment-shaders and shader interface information
 1991+    sg_pipeline:    associated shader and vertex-layouts, and render states
 1992+    sg_view:        a resource view object used for bindings and render-pass attachments
 1993+
 1994+    Instead of pointers, resource creation functions return a 32-bit
 1995+    handle which uniquely identifies the resource object.
 1996+
 1997+    The 32-bit resource id is split into a 16-bit pool index in the lower bits,
 1998+    and a 16-bit 'generation counter' in the upper bits. The index allows fast
 1999+    pool lookups, and combined with the generation-counter it allows to detect
 2000+    'dangling accesses' (trying to use an object which no longer exists, and
 2001+    its pool slot has been reused for a new object)
 2002+
 2003+    The resource ids are wrapped into a strongly-typed struct so that
 2004+    trying to pass an incompatible resource id is a compile error.
 2005+*/
 2006+typedef struct sg_buffer        { uint32_t id; } sg_buffer;
 2007+typedef struct sg_image         { uint32_t id; } sg_image;
 2008+typedef struct sg_sampler       { uint32_t id; } sg_sampler;
 2009+typedef struct sg_shader        { uint32_t id; } sg_shader;
 2010+typedef struct sg_pipeline      { uint32_t id; } sg_pipeline;
 2011+typedef struct sg_view          { uint32_t id; } sg_view;
 2012+
 2013+/*
 2014+    sg_range is a pointer-size-pair struct used to pass memory blobs into
 2015+    sokol-gfx. When initialized from a value type (array or struct), you can
 2016+    use the SG_RANGE() macro to build an sg_range struct. For functions which
 2017+    take either a sg_range pointer, or a (C++) sg_range reference, use the
 2018+    SG_RANGE_REF macro as a solution which compiles both in C and C++.
 2019+*/
 2020+typedef struct sg_range {
 2021+    const void* ptr;
 2022+    size_t size;
 2023+} sg_range;
 2024+
 2025+// disabling this for every includer isn't great, but the warnings are also quite pointless
 2026+#if defined(_MSC_VER)
 2027+#pragma warning(disable:4221)   // /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y'
 2028+#pragma warning(disable:4204)   // VS2015: nonstandard extension used: non-constant aggregate initializer
 2029+#endif
 2030+#if defined(__cplusplus)
 2031+#define SG_RANGE(x) sg_range{ &x, sizeof(x) }
 2032+#define SG_RANGE_REF(x) sg_range{ &x, sizeof(x) }
 2033+#else
 2034+#define SG_RANGE(x) (sg_range){ &x, sizeof(x) }
 2035+#define SG_RANGE_REF(x) &(sg_range){ &x, sizeof(x) }
 2036+#endif
 2037+
 2038+// various compile-time constants in the public API
 2039+enum {
 2040+    SG_INVALID_ID = 0,
 2041+    SG_NUM_INFLIGHT_FRAMES = 2,
 2042+    SG_MAX_COLOR_ATTACHMENTS = 8,
 2043+    SG_MAX_UNIFORMBLOCK_MEMBERS = 16,
 2044+    SG_MAX_VERTEX_ATTRIBUTES = 16,
 2045+    SG_MAX_MIPMAPS = 16,
 2046+    SG_MAX_VERTEXBUFFER_BINDSLOTS = 8,
 2047+    SG_MAX_UNIFORMBLOCK_BINDSLOTS = 8,
 2048+    SG_MAX_VIEW_BINDSLOTS = 32,
 2049+    SG_MAX_SAMPLER_BINDSLOTS = 12,
 2050+    SG_MAX_TEXTURE_SAMPLER_PAIRS = 32,  // same as SG_MAX_VIEW_BINDSLOTS
 2051+    SG_MAX_PORTABLE_COLOR_ATTACHMENTS = 4,
 2052+    SG_MAX_PORTABLE_TEXTURE_BINDINGS_PER_STAGE = 16,
 2053+    SG_MAX_PORTABLE_STORAGEBUFFER_BINDINGS_PER_STAGE = 8,   // assuming sg_features.compute = true
 2054+    SG_MAX_PORTABLE_STORAGEIMAGE_BINDINGS_PER_STAGE = 4,    // assuming sg_features.compute = true
 2055+};
 2056+
 2057+/*
 2058+    sg_color
 2059+
 2060+    An RGBA color value.
 2061+*/
 2062+typedef struct sg_color { float r, g, b, a; } sg_color;
 2063+
 2064+/*
 2065+    sg_backend
 2066+
 2067+    The active 3D-API backend, use the function sg_query_backend()
 2068+    to get the currently active backend.
 2069+*/
 2070+typedef enum sg_backend {
 2071+    SG_BACKEND_GLCORE,
 2072+    SG_BACKEND_GLES3,
 2073+    SG_BACKEND_D3D11,
 2074+    SG_BACKEND_METAL_IOS,
 2075+    SG_BACKEND_METAL_MACOS,
 2076+    SG_BACKEND_METAL_SIMULATOR,
 2077+    SG_BACKEND_WGPU,
 2078+    SG_BACKEND_VULKAN,
 2079+    SG_BACKEND_DUMMY,
 2080+} sg_backend;
 2081+
 2082+/*
 2083+    sg_pixel_format
 2084+
 2085+    sokol_gfx.h basically uses the same pixel formats as WebGPU, since these
 2086+    are supported on most newer GPUs.
 2087+
 2088+    A pixelformat name consist of three parts:
 2089+
 2090+        - components (R, RG, RGB or RGBA)
 2091+        - bit width per component (8, 16 or 32)
 2092+        - component data type:
 2093+            - unsigned normalized (no postfix)
 2094+            - signed normalized (SN postfix)
 2095+            - unsigned integer (UI postfix)
 2096+            - signed integer (SI postfix)
 2097+            - float (F postfix)
 2098+
 2099+    Not all pixel formats can be used for everything, call sg_query_pixelformat()
 2100+    to inspect the capabilities of a given pixelformat. The function returns
 2101+    an sg_pixelformat_info struct with the following members:
 2102+
 2103+        - sample: the pixelformat can be sampled as texture at least with
 2104+                  nearest filtering
 2105+        - filter: the pixelformat can be sampled as texture with linear
 2106+                  filtering
 2107+        - render: the pixelformat can be used as render-pass attachment
 2108+        - blend:  blending is supported when used as render-pass attachment
 2109+        - msaa:   multisample-antialiasing is supported when used
 2110+                  as render-pass attachment
 2111+        - depth:  the pixelformat can be used for depth-stencil attachments
 2112+        - compressed: this is a block-compressed format
 2113+        - bytes_per_pixel: the numbers of bytes in a pixel (0 for compressed formats)
 2114+
 2115+    The default pixel format for texture images is SG_PIXELFORMAT_RGBA8.
 2116+
 2117+    The default pixel format for render target images is platform-dependent
 2118+    and taken from the sg_environment struct passed into sg_setup(). Typically
 2119+    the default formats are:
 2120+
 2121+        - for the Metal, D3D11 and WebGPU backends: SG_PIXELFORMAT_BGRA8
 2122+        - for GL backends: SG_PIXELFORMAT_RGBA8
 2123+*/
 2124+typedef enum sg_pixel_format {
 2125+    _SG_PIXELFORMAT_DEFAULT,    // value 0 reserved for default-init
 2126+    SG_PIXELFORMAT_NONE,
 2127+
 2128+    SG_PIXELFORMAT_R8,
 2129+    SG_PIXELFORMAT_R8SN,
 2130+    SG_PIXELFORMAT_R8UI,
 2131+    SG_PIXELFORMAT_R8SI,
 2132+
 2133+    SG_PIXELFORMAT_R16,
 2134+    SG_PIXELFORMAT_R16SN,
 2135+    SG_PIXELFORMAT_R16UI,
 2136+    SG_PIXELFORMAT_R16SI,
 2137+    SG_PIXELFORMAT_R16F,
 2138+    SG_PIXELFORMAT_RG8,
 2139+    SG_PIXELFORMAT_RG8SN,
 2140+    SG_PIXELFORMAT_RG8UI,
 2141+    SG_PIXELFORMAT_RG8SI,
 2142+
 2143+    SG_PIXELFORMAT_R32UI,
 2144+    SG_PIXELFORMAT_R32SI,
 2145+    SG_PIXELFORMAT_R32F,
 2146+    SG_PIXELFORMAT_RG16,
 2147+    SG_PIXELFORMAT_RG16SN,
 2148+    SG_PIXELFORMAT_RG16UI,
 2149+    SG_PIXELFORMAT_RG16SI,
 2150+    SG_PIXELFORMAT_RG16F,
 2151+    SG_PIXELFORMAT_RGBA8,
 2152+    SG_PIXELFORMAT_SRGB8A8,
 2153+    SG_PIXELFORMAT_RGBA8SN,
 2154+    SG_PIXELFORMAT_RGBA8UI,
 2155+    SG_PIXELFORMAT_RGBA8SI,
 2156+    SG_PIXELFORMAT_BGRA8,
 2157+    SG_PIXELFORMAT_SBGR8A8,
 2158+    SG_PIXELFORMAT_RGB10A2,
 2159+    SG_PIXELFORMAT_RG11B10F,
 2160+    SG_PIXELFORMAT_RGB9E5,
 2161+
 2162+    SG_PIXELFORMAT_RG32UI,
 2163+    SG_PIXELFORMAT_RG32SI,
 2164+    SG_PIXELFORMAT_RG32F,
 2165+    SG_PIXELFORMAT_RGBA16,
 2166+    SG_PIXELFORMAT_RGBA16SN,
 2167+    SG_PIXELFORMAT_RGBA16UI,
 2168+    SG_PIXELFORMAT_RGBA16SI,
 2169+    SG_PIXELFORMAT_RGBA16F,
 2170+
 2171+    SG_PIXELFORMAT_RGBA32UI,
 2172+    SG_PIXELFORMAT_RGBA32SI,
 2173+    SG_PIXELFORMAT_RGBA32F,
 2174+
 2175+    SG_PIXELFORMAT_DEPTH,
 2176+    SG_PIXELFORMAT_DEPTH_STENCIL,
 2177+
 2178+    // NOTE: don't put any new compressed format in front of here
 2179+    SG_PIXELFORMAT_BC1_RGBA,
 2180+    SG_PIXELFORMAT_BC2_RGBA,
 2181+    SG_PIXELFORMAT_BC3_RGBA,
 2182+    SG_PIXELFORMAT_BC3_SRGBA,
 2183+    SG_PIXELFORMAT_BC4_R,
 2184+    SG_PIXELFORMAT_BC4_RSN,
 2185+    SG_PIXELFORMAT_BC5_RG,
 2186+    SG_PIXELFORMAT_BC5_RGSN,
 2187+    SG_PIXELFORMAT_BC6H_RGBF,
 2188+    SG_PIXELFORMAT_BC6H_RGBUF,
 2189+    SG_PIXELFORMAT_BC7_RGBA,
 2190+    SG_PIXELFORMAT_BC7_SRGBA,
 2191+    SG_PIXELFORMAT_ETC2_RGB8,
 2192+    SG_PIXELFORMAT_ETC2_SRGB8,
 2193+    SG_PIXELFORMAT_ETC2_RGB8A1,
 2194+    SG_PIXELFORMAT_ETC2_RGBA8,
 2195+    SG_PIXELFORMAT_ETC2_SRGB8A8,
 2196+    SG_PIXELFORMAT_EAC_R11,
 2197+    SG_PIXELFORMAT_EAC_R11SN,
 2198+    SG_PIXELFORMAT_EAC_RG11,
 2199+    SG_PIXELFORMAT_EAC_RG11SN,
 2200+
 2201+    SG_PIXELFORMAT_ASTC_4x4_RGBA,
 2202+    SG_PIXELFORMAT_ASTC_4x4_SRGBA,
 2203+
 2204+    _SG_PIXELFORMAT_NUM,
 2205+    _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF
 2206+} sg_pixel_format;
 2207+
 2208+/*
 2209+    Runtime information about a pixel format, returned by sg_query_pixelformat().
 2210+*/
 2211+typedef struct sg_pixelformat_info {
 2212+    bool sample;            // pixel format can be sampled in shaders at least with nearest filtering
 2213+    bool filter;            // pixel format can be sampled with linear filtering
 2214+    bool render;            // pixel format can be used as render-pass attachment
 2215+    bool blend;             // pixel format supports alpha-blending when used as render-pass attachment
 2216+    bool msaa;              // pixel format supports MSAA when used as render-pass attachment
 2217+    bool depth;             // pixel format is a depth format
 2218+    bool compressed;        // true if this is a hardware-compressed format
 2219+    bool read;              // true if format supports compute shader read access
 2220+    bool write;             // true if format supports compute shader write access
 2221+    int bytes_per_pixel;    // NOTE: this is 0 for compressed formats, use sg_query_row_pitch() / sg_query_surface_pitch() as alternative
 2222+} sg_pixelformat_info;
 2223+
 2224+/*
 2225+    Runtime information about available optional features, returned by sg_query_features()
 2226+*/
 2227+typedef struct sg_features {
 2228+    bool origin_top_left;               // framebuffer- and texture-origin is in top left corner
 2229+    bool image_clamp_to_border;         // border color and clamp-to-border uv-wrap mode is supported
 2230+    bool mrt_independent_blend_state;   // multiple-render-target rendering can use per-render-target blend state
 2231+    bool mrt_independent_write_mask;    // multiple-render-target rendering can use per-render-target color write masks
 2232+    bool compute;                       // storage buffers and compute shaders are supported
 2233+    bool msaa_texture_bindings;         // if true, multisampled images can be bound as textures
 2234+    bool separate_buffer_types;         // cannot use the same buffer for vertex and indices (only WebGL2)
 2235+    bool draw_base_vertex;              // draw with (base vertex > 0) && (base_instance == 0) supported
 2236+    bool draw_base_instance;            // draw with (base instance > 0) supported
 2237+    bool dual_source_blending;          // dual-source-blending supported
 2238+    bool vertexformat_int10_n2;         // SG_VERTEXFORMAT_INT10_N2 is supported
 2239+    bool gl_texture_views;              // supports 'proper' texture views (GL 4.3+)
 2240+} sg_features;
 2241+
 2242+/*
 2243+    Runtime information about resource limits, returned by sg_query_limit()
 2244+*/
 2245+typedef struct sg_limits {
 2246+    int max_image_size_2d;          // max width/height of SG_IMAGETYPE_2D images
 2247+    int max_image_size_cube;        // max width/height of SG_IMAGETYPE_CUBE images
 2248+    int max_image_size_3d;          // max width/height/depth of SG_IMAGETYPE_3D images
 2249+    int max_image_size_array;       // max width/height of SG_IMAGETYPE_ARRAY images
 2250+    int max_image_array_layers;     // max number of layers in SG_IMAGETYPE_ARRAY images
 2251+    int max_vertex_attrs;           // max number of vertex attributes, clamped to SG_MAX_VERTEX_ATTRIBUTES
 2252+    int max_color_attachments;      // max number of render pass color attachments, clamped to SG_MAX_COLOR_ATTACHMENTS
 2253+    int max_texture_bindings_per_stage; // max number of texture bindings per shader stage, clamped to SG_MAX_VIEW_BINDSLOTS
 2254+    int max_storage_buffer_bindings_per_stage;  // max number of storage buffer bindings per shader stage, clamped to SG_MAX_VIEW_BINDSLOTS
 2255+    int max_storage_image_bindings_per_stage;   // max number of storage image bindings per shader stage, clamped to SG_MAX_VIEW_BINDSLOTS
 2256+    int gl_max_vertex_uniform_components;       // GL_MAX_VERTEX_UNIFORM_COMPONENTS (only on GL backends)
 2257+    int gl_max_combined_texture_image_units;    // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (only on GL backends)
 2258+    int d3d11_max_unordered_access_views;       // 8 on feature level 11.0, otherwise 32 (clamped to SG_MAX_VIEW_BINDSLOTS)
 2259+    int vk_min_uniform_buffer_offset_alignment;
 2260+} sg_limits;
 2261+
 2262+/*
 2263+    sg_resource_state
 2264+
 2265+    The current state of a resource in its resource pool.
 2266+    Resources start in the INITIAL state, which means the
 2267+    pool slot is unoccupied and can be allocated. When a resource is
 2268+    created, first an id is allocated, and the resource pool slot
 2269+    is set to state ALLOC. After allocation, the resource is
 2270+    initialized, which may result in the VALID or FAILED state. The
 2271+    reason why allocation and initialization are separate is because
 2272+    some resource types (e.g. buffers and images) might be asynchronously
 2273+    initialized by the user application. If a resource which is not
 2274+    in the VALID state is attempted to be used for rendering, rendering
 2275+    operations will silently be dropped.
 2276+
 2277+    The special INVALID state is returned in sg_query_xxx_state() if no
 2278+    resource object exists for the provided resource id.
 2279+*/
 2280+typedef enum sg_resource_state {
 2281+    SG_RESOURCESTATE_INITIAL,
 2282+    SG_RESOURCESTATE_ALLOC,
 2283+    SG_RESOURCESTATE_VALID,
 2284+    SG_RESOURCESTATE_FAILED,
 2285+    SG_RESOURCESTATE_INVALID,
 2286+    _SG_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF
 2287+} sg_resource_state;
 2288+
 2289+/*
 2290+    sg_index_type
 2291+
 2292+    Indicates whether indexed rendering (fetching vertex-indices from an
 2293+    index buffer) is used, and if yes, the index data type (16- or 32-bits).
 2294+
 2295+    This is used in the sg_pipeline_desc.index_type member when creating a
 2296+    pipeline object.
 2297+
 2298+    The default index type is SG_INDEXTYPE_NONE.
 2299+*/
 2300+typedef enum sg_index_type {
 2301+    _SG_INDEXTYPE_DEFAULT,   // value 0 reserved for default-init
 2302+    SG_INDEXTYPE_NONE,
 2303+    SG_INDEXTYPE_UINT16,
 2304+    SG_INDEXTYPE_UINT32,
 2305+    _SG_INDEXTYPE_NUM,
 2306+    _SG_INDEXTYPE_FORCE_U32 = 0x7FFFFFFF
 2307+} sg_index_type;
 2308+
 2309+/*
 2310+    sg_image_type
 2311+
 2312+    Indicates the basic type of an image object (2D-texture, cubemap,
 2313+    3D-texture or 2D-array-texture). Used in the sg_image_desc.type member when
 2314+    creating an image, and in sg_shader_image_desc to describe a sampled texture
 2315+    in the shader (both must match and will be checked in the validation layer
 2316+    when calling sg_apply_bindings).
 2317+
 2318+    The default image type when creating an image is SG_IMAGETYPE_2D.
 2319+*/
 2320+typedef enum sg_image_type {
 2321+    _SG_IMAGETYPE_DEFAULT,  // value 0 reserved for default-init
 2322+    SG_IMAGETYPE_2D,
 2323+    SG_IMAGETYPE_CUBE,
 2324+    SG_IMAGETYPE_3D,
 2325+    SG_IMAGETYPE_ARRAY,
 2326+    _SG_IMAGETYPE_NUM,
 2327+    _SG_IMAGETYPE_FORCE_U32 = 0x7FFFFFFF
 2328+} sg_image_type;
 2329+
 2330+/*
 2331+    sg_image_sample_type
 2332+
 2333+    The basic data type of a texture sample as expected by a shader.
 2334+    Must be provided in sg_shader_image and used by the validation
 2335+    layer in sg_apply_bindings() to check if the provided image object
 2336+    is compatible with what the shader expects. Apart from the sokol-gfx
 2337+    validation layer, WebGPU is the only backend API which actually requires
 2338+    matching texture and sampler type to be provided upfront for validation
 2339+    (other 3D APIs treat texture/sampler type mismatches as undefined behaviour).
 2340+
 2341+    NOTE that the following texture pixel formats require the use
 2342+    of SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT, combined with a sampler
 2343+    of type SG_SAMPLERTYPE_NONFILTERING:
 2344+
 2345+    - SG_PIXELFORMAT_R32F
 2346+    - SG_PIXELFORMAT_RG32F
 2347+    - SG_PIXELFORMAT_RGBA32F
 2348+
 2349+    (when using sokol-shdc, also check out the meta tags `@image_sample_type`
 2350+    and `@sampler_type`)
 2351+*/
 2352+typedef enum sg_image_sample_type {
 2353+    _SG_IMAGESAMPLETYPE_DEFAULT,  // value 0 reserved for default-init
 2354+    SG_IMAGESAMPLETYPE_FLOAT,
 2355+    SG_IMAGESAMPLETYPE_DEPTH,
 2356+    SG_IMAGESAMPLETYPE_SINT,
 2357+    SG_IMAGESAMPLETYPE_UINT,
 2358+    SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT,
 2359+    _SG_IMAGESAMPLETYPE_NUM,
 2360+    _SG_IMAGESAMPLETYPE_FORCE_U32 = 0x7FFFFFFF
 2361+} sg_image_sample_type;
 2362+
 2363+/*
 2364+    sg_sampler_type
 2365+
 2366+    The basic type of a texture sampler (sampling vs comparison) as
 2367+    defined in a shader. Must be provided in sg_shader_sampler_desc.
 2368+
 2369+    sg_image_sample_type and sg_sampler_type for a texture/sampler
 2370+    pair must be compatible with each other, specifically only
 2371+    the following pairs are allowed:
 2372+
 2373+    - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING)
 2374+    - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING
 2375+    - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING
 2376+    - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING
 2377+    - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON
 2378+*/
 2379+typedef enum sg_sampler_type {
 2380+    _SG_SAMPLERTYPE_DEFAULT,
 2381+    SG_SAMPLERTYPE_FILTERING,
 2382+    SG_SAMPLERTYPE_NONFILTERING,
 2383+    SG_SAMPLERTYPE_COMPARISON,
 2384+    _SG_SAMPLERTYPE_NUM,
 2385+    _SG_SAMPLERTYPE_FORCE_U32,
 2386+} sg_sampler_type;
 2387+
 2388+/*
 2389+    sg_primitive_type
 2390+
 2391+    This is the common subset of 3D primitive types supported across all 3D
 2392+    APIs. This is used in the sg_pipeline_desc.primitive_type member when
 2393+    creating a pipeline object.
 2394+
 2395+    The default primitive type is SG_PRIMITIVETYPE_TRIANGLES.
 2396+*/
 2397+typedef enum sg_primitive_type {
 2398+    _SG_PRIMITIVETYPE_DEFAULT,  // value 0 reserved for default-init
 2399+    SG_PRIMITIVETYPE_POINTS,
 2400+    SG_PRIMITIVETYPE_LINES,
 2401+    SG_PRIMITIVETYPE_LINE_STRIP,
 2402+    SG_PRIMITIVETYPE_TRIANGLES,
 2403+    SG_PRIMITIVETYPE_TRIANGLE_STRIP,
 2404+    _SG_PRIMITIVETYPE_NUM,
 2405+    _SG_PRIMITIVETYPE_FORCE_U32 = 0x7FFFFFFF
 2406+} sg_primitive_type;
 2407+
 2408+/*
 2409+    sg_filter
 2410+
 2411+    The filtering mode when sampling a texture image. This is
 2412+    used in the sg_sampler_desc.min_filter, sg_sampler_desc.mag_filter
 2413+    and sg_sampler_desc.mipmap_filter members when creating a sampler object.
 2414+
 2415+    For the default is SG_FILTER_NEAREST.
 2416+*/
 2417+typedef enum sg_filter {
 2418+    _SG_FILTER_DEFAULT, // value 0 reserved for default-init
 2419+    SG_FILTER_NEAREST,
 2420+    SG_FILTER_LINEAR,
 2421+    _SG_FILTER_NUM,
 2422+    _SG_FILTER_FORCE_U32 = 0x7FFFFFFF
 2423+} sg_filter;
 2424+
 2425+/*
 2426+    sg_wrap
 2427+
 2428+    The texture coordinates wrapping mode when sampling a texture
 2429+    image. This is used in the sg_image_desc.wrap_u, .wrap_v
 2430+    and .wrap_w members when creating an image.
 2431+
 2432+    The default wrap mode is SG_WRAP_REPEAT.
 2433+
 2434+    NOTE: SG_WRAP_CLAMP_TO_BORDER is not supported on all backends
 2435+    and platforms. To check for support, call sg_query_features()
 2436+    and check the "clamp_to_border" boolean in the returned
 2437+    sg_features struct.
 2438+
 2439+    Platforms which don't support SG_WRAP_CLAMP_TO_BORDER will silently fall back
 2440+    to SG_WRAP_CLAMP_TO_EDGE without a validation error.
 2441+*/
 2442+typedef enum sg_wrap {
 2443+    _SG_WRAP_DEFAULT,   // value 0 reserved for default-init
 2444+    SG_WRAP_REPEAT,
 2445+    SG_WRAP_CLAMP_TO_EDGE,
 2446+    SG_WRAP_CLAMP_TO_BORDER,
 2447+    SG_WRAP_MIRRORED_REPEAT,
 2448+    _SG_WRAP_NUM,
 2449+    _SG_WRAP_FORCE_U32 = 0x7FFFFFFF
 2450+} sg_wrap;
 2451+
 2452+/*
 2453+    sg_border_color
 2454+
 2455+    The border color to use when sampling a texture, and the UV wrap
 2456+    mode is SG_WRAP_CLAMP_TO_BORDER.
 2457+
 2458+    The default border color is SG_BORDERCOLOR_OPAQUE_BLACK
 2459+*/
 2460+typedef enum sg_border_color {
 2461+    _SG_BORDERCOLOR_DEFAULT,    // value 0 reserved for default-init
 2462+    SG_BORDERCOLOR_TRANSPARENT_BLACK,
 2463+    SG_BORDERCOLOR_OPAQUE_BLACK,
 2464+    SG_BORDERCOLOR_OPAQUE_WHITE,
 2465+    _SG_BORDERCOLOR_NUM,
 2466+    _SG_BORDERCOLOR_FORCE_U32 = 0x7FFFFFFF
 2467+} sg_border_color;
 2468+
 2469+/*
 2470+    sg_vertex_format
 2471+
 2472+    The data type of a vertex component. This is used to describe
 2473+    the layout of input vertex data when creating a pipeline object.
 2474+
 2475+    NOTE that specific mapping rules exist from the CPU-side vertex
 2476+    formats to the vertex attribute base type in the vertex shader code
 2477+    (see doc header section 'ON VERTEX FORMATS').
 2478+*/
 2479+typedef enum sg_vertex_format {
 2480+    SG_VERTEXFORMAT_INVALID,
 2481+    SG_VERTEXFORMAT_FLOAT,
 2482+    SG_VERTEXFORMAT_FLOAT2,
 2483+    SG_VERTEXFORMAT_FLOAT3,
 2484+    SG_VERTEXFORMAT_FLOAT4,
 2485+    SG_VERTEXFORMAT_INT,
 2486+    SG_VERTEXFORMAT_INT2,
 2487+    SG_VERTEXFORMAT_INT3,
 2488+    SG_VERTEXFORMAT_INT4,
 2489+    SG_VERTEXFORMAT_UINT,
 2490+    SG_VERTEXFORMAT_UINT2,
 2491+    SG_VERTEXFORMAT_UINT3,
 2492+    SG_VERTEXFORMAT_UINT4,
 2493+    SG_VERTEXFORMAT_BYTE4,
 2494+    SG_VERTEXFORMAT_BYTE4N,
 2495+    SG_VERTEXFORMAT_UBYTE4,
 2496+    SG_VERTEXFORMAT_UBYTE4N,
 2497+    SG_VERTEXFORMAT_SHORT2,
 2498+    SG_VERTEXFORMAT_SHORT2N,
 2499+    SG_VERTEXFORMAT_USHORT2,
 2500+    SG_VERTEXFORMAT_USHORT2N,
 2501+    SG_VERTEXFORMAT_SHORT4,
 2502+    SG_VERTEXFORMAT_SHORT4N,
 2503+    SG_VERTEXFORMAT_USHORT4,
 2504+    SG_VERTEXFORMAT_USHORT4N,
 2505+    SG_VERTEXFORMAT_INT10_N2,
 2506+    SG_VERTEXFORMAT_UINT10_N2,
 2507+    SG_VERTEXFORMAT_HALF2,
 2508+    SG_VERTEXFORMAT_HALF4,
 2509+    _SG_VERTEXFORMAT_NUM,
 2510+    _SG_VERTEXFORMAT_FORCE_U32 = 0x7FFFFFFF
 2511+} sg_vertex_format;
 2512+
 2513+/*
 2514+    sg_vertex_step
 2515+
 2516+    Defines whether the input pointer of a vertex input stream is advanced
 2517+    'per vertex' or 'per instance'. The default step-func is
 2518+    SG_VERTEXSTEP_PER_VERTEX. SG_VERTEXSTEP_PER_INSTANCE is used with
 2519+    instanced-rendering.
 2520+
 2521+    The vertex-step is part of the vertex-layout definition
 2522+    when creating pipeline objects.
 2523+*/
 2524+typedef enum sg_vertex_step {
 2525+    _SG_VERTEXSTEP_DEFAULT,     // value 0 reserved for default-init
 2526+    SG_VERTEXSTEP_PER_VERTEX,
 2527+    SG_VERTEXSTEP_PER_INSTANCE,
 2528+    _SG_VERTEXSTEP_NUM,
 2529+    _SG_VERTEXSTEP_FORCE_U32 = 0x7FFFFFFF
 2530+} sg_vertex_step;
 2531+
 2532+/*
 2533+    sg_uniform_type
 2534+
 2535+    The data type of a uniform block member. This is used to
 2536+    describe the internal layout of uniform blocks when creating
 2537+    a shader object. This is only required for the GL backend, all
 2538+    other backends will ignore the interior layout of uniform blocks.
 2539+*/
 2540+typedef enum sg_uniform_type {
 2541+    SG_UNIFORMTYPE_INVALID,
 2542+    SG_UNIFORMTYPE_FLOAT,
 2543+    SG_UNIFORMTYPE_FLOAT2,
 2544+    SG_UNIFORMTYPE_FLOAT3,
 2545+    SG_UNIFORMTYPE_FLOAT4,
 2546+    SG_UNIFORMTYPE_INT,
 2547+    SG_UNIFORMTYPE_INT2,
 2548+    SG_UNIFORMTYPE_INT3,
 2549+    SG_UNIFORMTYPE_INT4,
 2550+    SG_UNIFORMTYPE_MAT4,
 2551+    _SG_UNIFORMTYPE_NUM,
 2552+    _SG_UNIFORMTYPE_FORCE_U32 = 0x7FFFFFFF
 2553+} sg_uniform_type;
 2554+
 2555+/*
 2556+    sg_uniform_layout
 2557+
 2558+    A hint for the interior memory layout of uniform blocks. This is
 2559+    only relevant for the GL backend where the internal layout
 2560+    of uniform blocks must be known to sokol-gfx. For all other backends the
 2561+    internal memory layout of uniform blocks doesn't matter, sokol-gfx
 2562+    will just pass uniform data as an opaque memory blob to the
 2563+    3D backend.
 2564+
 2565+    SG_UNIFORMLAYOUT_NATIVE (default)
 2566+        Native layout means that a 'backend-native' memory layout
 2567+        is used. For the GL backend this means that uniforms
 2568+        are packed tightly in memory (e.g. there are no padding
 2569+        bytes).
 2570+
 2571+    SG_UNIFORMLAYOUT_STD140
 2572+        The memory layout is a subset of std140. Arrays are only
 2573+        allowed for the FLOAT4, INT4 and MAT4. Alignment is as
 2574+        is as follows:
 2575+
 2576+            FLOAT, INT:         4 byte alignment
 2577+            FLOAT2, INT2:       8 byte alignment
 2578+            FLOAT3, INT3:       16 byte alignment(!)
 2579+            FLOAT4, INT4:       16 byte alignment
 2580+            MAT4:               16 byte alignment
 2581+            FLOAT4[], INT4[]:   16 byte alignment
 2582+
 2583+        The overall size of the uniform block must be a multiple
 2584+        of 16.
 2585+
 2586+    For more information search for 'UNIFORM DATA LAYOUT' in the documentation block
 2587+    at the start of the header.
 2588+*/
 2589+typedef enum sg_uniform_layout {
 2590+    _SG_UNIFORMLAYOUT_DEFAULT,     // value 0 reserved for default-init
 2591+    SG_UNIFORMLAYOUT_NATIVE,       // default: layout depends on currently active backend
 2592+    SG_UNIFORMLAYOUT_STD140,       // std140: memory layout according to std140
 2593+    _SG_UNIFORMLAYOUT_NUM,
 2594+    _SG_UNIFORMLAYOUT_FORCE_U32 = 0x7FFFFFFF
 2595+} sg_uniform_layout;
 2596+
 2597+/*
 2598+    sg_cull_mode
 2599+
 2600+    The face-culling mode, this is used in the
 2601+    sg_pipeline_desc.cull_mode member when creating a
 2602+    pipeline object.
 2603+
 2604+    The default cull mode is SG_CULLMODE_NONE
 2605+*/
 2606+typedef enum sg_cull_mode {
 2607+    _SG_CULLMODE_DEFAULT,   // value 0 reserved for default-init
 2608+    SG_CULLMODE_NONE,
 2609+    SG_CULLMODE_FRONT,
 2610+    SG_CULLMODE_BACK,
 2611+    _SG_CULLMODE_NUM,
 2612+    _SG_CULLMODE_FORCE_U32 = 0x7FFFFFFF
 2613+} sg_cull_mode;
 2614+
 2615+/*
 2616+    sg_face_winding
 2617+
 2618+    The vertex-winding rule that determines a front-facing primitive. This
 2619+    is used in the member sg_pipeline_desc.face_winding
 2620+    when creating a pipeline object.
 2621+
 2622+    The default winding is SG_FACEWINDING_CW (clockwise)
 2623+*/
 2624+typedef enum sg_face_winding {
 2625+    _SG_FACEWINDING_DEFAULT,    // value 0 reserved for default-init
 2626+    SG_FACEWINDING_CCW,
 2627+    SG_FACEWINDING_CW,
 2628+    _SG_FACEWINDING_NUM,
 2629+    _SG_FACEWINDING_FORCE_U32 = 0x7FFFFFFF
 2630+} sg_face_winding;
 2631+
 2632+/*
 2633+    sg_compare_func
 2634+
 2635+    The compare-function for configuring depth- and stencil-ref tests
 2636+    in pipeline objects, and for texture samplers which perform a comparison
 2637+    instead of regular sampling operation.
 2638+
 2639+    Used in the following structs:
 2640+
 2641+    sg_pipeline_desc
 2642+        .depth
 2643+            .compare
 2644+        .stencil
 2645+            .front.compare
 2646+            .back.compare
 2647+
 2648+    sg_sampler_desc
 2649+        .compare
 2650+
 2651+    The default compare func for depth- and stencil-tests is
 2652+    SG_COMPAREFUNC_ALWAYS.
 2653+
 2654+    The default compare func for samplers is SG_COMPAREFUNC_NEVER.
 2655+*/
 2656+typedef enum sg_compare_func {
 2657+    _SG_COMPAREFUNC_DEFAULT,    // value 0 reserved for default-init
 2658+    SG_COMPAREFUNC_NEVER,
 2659+    SG_COMPAREFUNC_LESS,
 2660+    SG_COMPAREFUNC_EQUAL,
 2661+    SG_COMPAREFUNC_LESS_EQUAL,
 2662+    SG_COMPAREFUNC_GREATER,
 2663+    SG_COMPAREFUNC_NOT_EQUAL,
 2664+    SG_COMPAREFUNC_GREATER_EQUAL,
 2665+    SG_COMPAREFUNC_ALWAYS,
 2666+    _SG_COMPAREFUNC_NUM,
 2667+    _SG_COMPAREFUNC_FORCE_U32 = 0x7FFFFFFF
 2668+} sg_compare_func;
 2669+
 2670+/*
 2671+    sg_stencil_op
 2672+
 2673+    The operation performed on a currently stored stencil-value when a
 2674+    comparison test passes or fails. This is used when creating a pipeline
 2675+    object in the following sg_pipeline_desc struct items:
 2676+
 2677+    sg_pipeline_desc
 2678+        .stencil
 2679+            .front
 2680+                .fail_op
 2681+                .depth_fail_op
 2682+                .pass_op
 2683+            .back
 2684+                .fail_op
 2685+                .depth_fail_op
 2686+                .pass_op
 2687+
 2688+    The default value is SG_STENCILOP_KEEP.
 2689+*/
 2690+typedef enum sg_stencil_op {
 2691+    _SG_STENCILOP_DEFAULT,      // value 0 reserved for default-init
 2692+    SG_STENCILOP_KEEP,
 2693+    SG_STENCILOP_ZERO,
 2694+    SG_STENCILOP_REPLACE,
 2695+    SG_STENCILOP_INCR_CLAMP,
 2696+    SG_STENCILOP_DECR_CLAMP,
 2697+    SG_STENCILOP_INVERT,
 2698+    SG_STENCILOP_INCR_WRAP,
 2699+    SG_STENCILOP_DECR_WRAP,
 2700+    _SG_STENCILOP_NUM,
 2701+    _SG_STENCILOP_FORCE_U32 = 0x7FFFFFFF
 2702+} sg_stencil_op;
 2703+
 2704+/*
 2705+    sg_blend_factor
 2706+
 2707+    The source and destination factors in blending operations.
 2708+    This is used in the following members when creating a pipeline object:
 2709+
 2710+    sg_pipeline_desc
 2711+        .colors[i]
 2712+            .blend
 2713+                .src_factor_rgb
 2714+                .dst_factor_rgb
 2715+                .src_factor_alpha
 2716+                .dst_factor_alpha
 2717+
 2718+    The default value is SG_BLENDFACTOR_ONE for source
 2719+    factors, and for the destination SG_BLENDFACTOR_ZERO if the associated
 2720+    blend-op is ADD, SUBTRACT or REVERSE_SUBTRACT or SG_BLENDFACTOR_ONE
 2721+    if the associated blend-op is MIN or MAX.
 2722+*/
 2723+typedef enum sg_blend_factor {
 2724+    _SG_BLENDFACTOR_DEFAULT,    // value 0 reserved for default-init
 2725+    SG_BLENDFACTOR_ZERO,
 2726+    SG_BLENDFACTOR_ONE,
 2727+    SG_BLENDFACTOR_SRC_COLOR,
 2728+    SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
 2729+    SG_BLENDFACTOR_SRC_ALPHA,
 2730+    SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
 2731+    SG_BLENDFACTOR_DST_COLOR,
 2732+    SG_BLENDFACTOR_ONE_MINUS_DST_COLOR,
 2733+    SG_BLENDFACTOR_DST_ALPHA,
 2734+    SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA,
 2735+    SG_BLENDFACTOR_SRC_ALPHA_SATURATED,
 2736+    SG_BLENDFACTOR_BLEND_COLOR,
 2737+    SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR,
 2738+    SG_BLENDFACTOR_BLEND_ALPHA,
 2739+    SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA,
 2740+    SG_BLENDFACTOR_SRC1_COLOR,
 2741+    SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR,
 2742+    SG_BLENDFACTOR_SRC1_ALPHA,
 2743+    SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA,
 2744+    _SG_BLENDFACTOR_NUM,
 2745+    _SG_BLENDFACTOR_FORCE_U32 = 0x7FFFFFFF
 2746+} sg_blend_factor;
 2747+
 2748+/*
 2749+    sg_blend_op
 2750+
 2751+    Describes how the source and destination values are combined in the
 2752+    fragment blending operation. It is used in the following struct items
 2753+    when creating a pipeline object:
 2754+
 2755+    sg_pipeline_desc
 2756+        .colors[i]
 2757+            .blend
 2758+                .op_rgb
 2759+                .op_alpha
 2760+
 2761+    The default value is SG_BLENDOP_ADD.
 2762+*/
 2763+typedef enum sg_blend_op {
 2764+    _SG_BLENDOP_DEFAULT,    // value 0 reserved for default-init
 2765+    SG_BLENDOP_ADD,
 2766+    SG_BLENDOP_SUBTRACT,
 2767+    SG_BLENDOP_REVERSE_SUBTRACT,
 2768+    SG_BLENDOP_MIN,
 2769+    SG_BLENDOP_MAX,
 2770+    _SG_BLENDOP_NUM,
 2771+    _SG_BLENDOP_FORCE_U32 = 0x7FFFFFFF
 2772+} sg_blend_op;
 2773+
 2774+/*
 2775+    sg_color_mask
 2776+
 2777+    Selects the active color channels when writing a fragment color to the
 2778+    framebuffer. This is used in the members
 2779+    sg_pipeline_desc.colors[i].write_mask when creating a pipeline object.
 2780+
 2781+    The default colormask is SG_COLORMASK_RGBA (write all colors channels)
 2782+
 2783+    NOTE: since the color mask value 0 is reserved for the default value
 2784+    (SG_COLORMASK_RGBA), use SG_COLORMASK_NONE if all color channels
 2785+    should be disabled.
 2786+*/
 2787+typedef enum sg_color_mask {
 2788+    _SG_COLORMASK_DEFAULT = 0,    // value 0 reserved for default-init
 2789+    SG_COLORMASK_NONE   = 0x10,   // special value for 'all channels disabled
 2790+    SG_COLORMASK_R      = 0x1,
 2791+    SG_COLORMASK_G      = 0x2,
 2792+    SG_COLORMASK_RG     = 0x3,
 2793+    SG_COLORMASK_B      = 0x4,
 2794+    SG_COLORMASK_RB     = 0x5,
 2795+    SG_COLORMASK_GB     = 0x6,
 2796+    SG_COLORMASK_RGB    = 0x7,
 2797+    SG_COLORMASK_A      = 0x8,
 2798+    SG_COLORMASK_RA     = 0x9,
 2799+    SG_COLORMASK_GA     = 0xA,
 2800+    SG_COLORMASK_RGA    = 0xB,
 2801+    SG_COLORMASK_BA     = 0xC,
 2802+    SG_COLORMASK_RBA    = 0xD,
 2803+    SG_COLORMASK_GBA    = 0xE,
 2804+    SG_COLORMASK_RGBA   = 0xF,
 2805+    _SG_COLORMASK_FORCE_U32 = 0x7FFFFFFF
 2806+} sg_color_mask;
 2807+
 2808+/*
 2809+    sg_load_action
 2810+
 2811+    Defines the load action that should be performed at the start of a render pass:
 2812+
 2813+    SG_LOADACTION_CLEAR:        clear the render target
 2814+    SG_LOADACTION_LOAD:         load the previous content of the render target
 2815+    SG_LOADACTION_DONTCARE:     leave the render target in an undefined state
 2816+
 2817+    This is used in the sg_pass_action structure.
 2818+
 2819+    The default load action for all pass attachments is SG_LOADACTION_CLEAR,
 2820+    with the values rgba = { 0.5f, 0.5f, 0.5f, 1.0f }, depth=1.0f and stencil=0.
 2821+
 2822+    If you want to override the default behaviour, it is important to not
 2823+    only set the clear color, but the 'action' field as well (as long as this
 2824+    is _SG_LOADACTION_DEFAULT, the value fields will be ignored).
 2825+*/
 2826+typedef enum sg_load_action {
 2827+    _SG_LOADACTION_DEFAULT,
 2828+    SG_LOADACTION_CLEAR,
 2829+    SG_LOADACTION_LOAD,
 2830+    SG_LOADACTION_DONTCARE,
 2831+    _SG_LOADACTION_FORCE_U32 = 0x7FFFFFFF
 2832+} sg_load_action;
 2833+
 2834+/*
 2835+    sg_store_action
 2836+
 2837+    Defines the store action that should be performed at the end of a render pass:
 2838+
 2839+    SG_STOREACTION_STORE:       store the rendered content to the color attachment image
 2840+    SG_STOREACTION_DONTCARE:    allows the GPU to discard the rendered content
 2841+*/
 2842+typedef enum sg_store_action {
 2843+    _SG_STOREACTION_DEFAULT,
 2844+    SG_STOREACTION_STORE,
 2845+    SG_STOREACTION_DONTCARE,
 2846+    _SG_STOREACTION_FORCE_U32 = 0x7FFFFFFF
 2847+} sg_store_action;
 2848+
 2849+
 2850+/*
 2851+    sg_pass_action
 2852+
 2853+    The sg_pass_action struct defines the actions to be performed
 2854+    at the start and end of a render pass.
 2855+
 2856+    - at the start of the pass: whether the render attachments should be cleared,
 2857+      loaded with their previous content, or start in an undefined state
 2858+    - for clear operations: the clear value (color, depth, or stencil values)
 2859+    - at the end of the pass: whether the rendering result should be
 2860+      stored back into the render attachment or discarded
 2861+*/
 2862+typedef struct sg_color_attachment_action {
 2863+    sg_load_action load_action;         // default: SG_LOADACTION_CLEAR
 2864+    sg_store_action store_action;       // default: SG_STOREACTION_STORE
 2865+    sg_color clear_value;               // default: { 0.5f, 0.5f, 0.5f, 1.0f }
 2866+} sg_color_attachment_action;
 2867+
 2868+typedef struct sg_depth_attachment_action {
 2869+    sg_load_action load_action;         // default: SG_LOADACTION_CLEAR
 2870+    sg_store_action store_action;       // default: SG_STOREACTION_DONTCARE
 2871+    float clear_value;                  // default: 1.0
 2872+} sg_depth_attachment_action;
 2873+
 2874+typedef struct sg_stencil_attachment_action {
 2875+    sg_load_action load_action;         // default: SG_LOADACTION_CLEAR
 2876+    sg_store_action store_action;       // default: SG_STOREACTION_DONTCARE
 2877+    uint8_t clear_value;                // default: 0
 2878+} sg_stencil_attachment_action;
 2879+
 2880+typedef struct sg_pass_action {
 2881+    sg_color_attachment_action colors[SG_MAX_COLOR_ATTACHMENTS];
 2882+    sg_depth_attachment_action depth;
 2883+    sg_stencil_attachment_action stencil;
 2884+} sg_pass_action;
 2885+
 2886+/*
 2887+    sg_swapchain
 2888+
 2889+    Used in sg_begin_pass() to provide details about an external swapchain
 2890+    (pixel formats, sample count and backend-API specific render surface objects).
 2891+
 2892+    The following information must be provided:
 2893+
 2894+    - the width and height of the swapchain surfaces in number of pixels,
 2895+    - the pixel format of the render- and optional msaa-resolve-surface
 2896+    - the pixel format of the optional depth- or depth-stencil-surface
 2897+    - the MSAA sample count for the render and depth-stencil surface
 2898+
 2899+    If the pixel formats and MSAA sample counts are left zero-initialized,
 2900+    their defaults are taken from the sg_environment struct provided in the
 2901+    sg_setup() call.
 2902+
 2903+    The width and height *must* be > 0.
 2904+
 2905+    The boolean `sg_swapchain.invalid` is used to communicate an invalid
 2906+    swapchain state to sokol-gfx (for instance the swapchain code outside of
 2907+    sokol-gfx not being able to create swapchain surfaces). When the .invalid
 2908+    boolean is set to true, all other sg_swapchain struct items must be zeroed
 2909+    (checked in the validation layer), and all rendering in this swapchain-pass
 2910+    will be silently skipped.
 2911+
 2912+    For valid swapchains, the following backend API specific objects must be passed in
 2913+    as 'type erased' void pointers:
 2914+
 2915+    GL:
 2916+        - on all GL backends, a GL framebuffer object must be provided. This
 2917+          can be zero for the default framebuffer.
 2918+
 2919+    D3D11:
 2920+        - an ID3D11RenderTargetView for the rendering surface, without
 2921+          MSAA rendering this surface will also be displayed
 2922+        - an optional ID3D11DepthStencilView for the depth- or depth/stencil
 2923+          buffer surface
 2924+        - when MSAA rendering is used, another ID3D11RenderTargetView
 2925+          which serves as MSAA resolve target and will be displayed
 2926+
 2927+    WebGPU (same as D3D11, except different types)
 2928+        - a WGPUTextureView for the rendering surface, without
 2929+          MSAA rendering this surface will also be displayed
 2930+        - an optional WGPUTextureView for the depth- or depth/stencil
 2931+          buffer surface
 2932+        - when MSAA rendering is used, another WGPUTextureView
 2933+          which serves as MSAA resolve target and will be displayed
 2934+
 2935+    Metal (NOTE that the roles of provided surfaces is slightly different
 2936+    than on D3D11 or WebGPU in case of MSAA vs non-MSAA rendering):
 2937+
 2938+        - A current CAMetalDrawable (NOT an MTLDrawable!) which will be presented.
 2939+          This will either be rendered to directly (if no MSAA is used), or serve
 2940+          as MSAA-resolve target.
 2941+        - an optional MTLTexture for the depth- or depth-stencil buffer
 2942+        - an optional multisampled MTLTexture which serves as intermediate
 2943+          rendering surface which will then be resolved into the
 2944+          CAMetalDrawable.
 2945+
 2946+    NOTE that for Metal you must use an ObjC __bridge cast to
 2947+    properly tunnel the ObjC object id through a C void*, e.g.:
 2948+
 2949+        swapchain.metal.current_drawable = (__bridge const void*) [mtkView currentDrawable];
 2950+
 2951+    On all other backends you shouldn't need to mess with the reference count.
 2952+
 2953+    It's a good practice to write a helper function which returns an initialized
 2954+    sg_swapchain struct, which can then be plugged directly into
 2955+    sg_pass.swapchain. Look at the function sglue_swapchain() in the sokol_glue.h
 2956+    as an example.
 2957+*/
 2958+typedef struct sg_metal_swapchain {
 2959+    const void* current_drawable;       // CAMetalDrawable (NOT MTLDrawable!!!)
 2960+    const void* depth_stencil_texture;  // MTLTexture
 2961+    const void* msaa_color_texture;     // MTLTexture
 2962+} sg_metal_swapchain;
 2963+
 2964+typedef struct sg_d3d11_swapchain {
 2965+    const void* render_view;            // ID3D11RenderTargetView
 2966+    const void* resolve_view;           // ID3D11RenderTargetView
 2967+    const void* depth_stencil_view;     // ID3D11DepthStencilView
 2968+} sg_d3d11_swapchain;
 2969+
 2970+typedef struct sg_wgpu_swapchain {
 2971+    const void* render_view;            // WGPUTextureView
 2972+    const void* resolve_view;           // WGPUTextureView
 2973+    const void* depth_stencil_view;     // WGPUTextureView
 2974+} sg_wgpu_swapchain;
 2975+
 2976+typedef struct sg_vulkan_swapchain {
 2977+    const void* render_image;           // vkImage
 2978+    const void* render_view;            // vkImageView
 2979+    const void* resolve_image;          // vkImage
 2980+    const void* resolve_view;           // vkImageView
 2981+    const void* depth_stencil_image;    // vkImage
 2982+    const void* depth_stencil_view;     // vkImageView
 2983+    const void* render_finished_semaphore;  // vkSemaphore
 2984+    const void* present_complete_semaphore; // vkSemaphore
 2985+} sg_vulkan_swapchain;
 2986+
 2987+typedef struct sg_gl_swapchain {
 2988+    uint32_t framebuffer;               // GL framebuffer object
 2989+} sg_gl_swapchain;
 2990+
 2991+typedef struct sg_swapchain {
 2992+    bool invalid;
 2993+    int width;
 2994+    int height;
 2995+    int sample_count;
 2996+    sg_pixel_format color_format;
 2997+    sg_pixel_format depth_format;
 2998+    sg_metal_swapchain metal;
 2999+    sg_d3d11_swapchain d3d11;
 3000+    sg_wgpu_swapchain wgpu;
 3001+    sg_vulkan_swapchain vulkan;
 3002+    sg_gl_swapchain gl;
 3003+} sg_swapchain;
 3004+
 3005+/*
 3006+    sg_attachments
 3007+
 3008+    Used in sg_pass to provide render pass attachment views. Each
 3009+    type of pass attachment has it corresponding view type:
 3010+
 3011+    sg_attachments.colors[]:
 3012+        populate with color-attachment views, e.g.:
 3013+
 3014+        sg_make_view(&(sg_view_desc){
 3015+            .color_attachment = { ... },
 3016+        });
 3017+
 3018+    sg_attachments.resolves[]:
 3019+        populate with resolve-attachment views, e.g.:
 3020+
 3021+        sg_make_view(&(sg_view_desc){
 3022+            .resolve_attachment = { ... },
 3023+        });
 3024+
 3025+    sg_attachments.depth_stencil:
 3026+        populate with depth-stencil-attachment views, e.g.:
 3027+
 3028+        sg_make_view(&(sg_view_desc){
 3029+            .depth_stencil_attachment = { ... },
 3030+        });
 3031+*/
 3032+typedef struct sg_attachments {
 3033+    sg_view colors[SG_MAX_COLOR_ATTACHMENTS];
 3034+    sg_view resolves[SG_MAX_COLOR_ATTACHMENTS];
 3035+    sg_view depth_stencil;
 3036+} sg_attachments;
 3037+
 3038+/*
 3039+    sg_pass
 3040+
 3041+    The sg_pass structure is passed as argument into the sg_begin_pass()
 3042+    function.
 3043+
 3044+    For a swapchain render pass, provide an sg_pass_action and sg_swapchain
 3045+    struct (for instance via the sglue_swapchain() helper function from
 3046+    sokol_glue.h):
 3047+
 3048+        sg_begin_pass(&(sg_pass){
 3049+            .action = { ... },
 3050+            .swapchain = sglue_swapchain(),
 3051+        });
 3052+
 3053+    For an offscreen render pass, provide an sg_pass_action struct with
 3054+    attachment view objects:
 3055+
 3056+        sg_begin_pass(&(sg_pass){
 3057+            .action = { ... },
 3058+            .attachments = {
 3059+                .colors = { ... },
 3060+                .resolves = { ... },
 3061+                .depth_stencil = ...,
 3062+            },
 3063+        });
 3064+
 3065+    You can also omit the .action object to get default pass action behaviour
 3066+    (clear to color=grey, depth=1 and stencil=0).
 3067+
 3068+    For a compute pass, just set the sg_pass.compute boolean to true:
 3069+
 3070+        sg_begin_pass(&(sg_pass){ .compute = true });
 3071+*/
 3072+typedef struct sg_pass {
 3073+    uint32_t _start_canary;
 3074+    bool compute;
 3075+    sg_pass_action action;
 3076+    sg_attachments attachments;
 3077+    sg_swapchain swapchain;
 3078+    const char* label;
 3079+    uint32_t _end_canary;
 3080+} sg_pass;
 3081+
 3082+/*
 3083+    sg_bindings
 3084+
 3085+    The sg_bindings structure defines the resource bindings for
 3086+    the next draw call.
 3087+
 3088+    To update the resource bindings, call sg_apply_bindings() with
 3089+    a pointer to a populated sg_bindings struct. Note that
 3090+    sg_apply_bindings() must be called after sg_apply_pipeline()
 3091+    and that bindings are not preserved across sg_apply_pipeline()
 3092+    calls, even when the new pipeline uses the same 'bindings layout'.
 3093+
 3094+    A resource binding struct contains:
 3095+
 3096+    - 1..N vertex buffers
 3097+    - 1..N vertex buffer offsets
 3098+    - 0..1 index buffer
 3099+    - 0..1 index buffer offset
 3100+    - 0..N resource views (texture-, storage-image, storage-buffer-views)
 3101+    - 0..N samplers
 3102+
 3103+    Where 'N' is defined in the following constants:
 3104+
 3105+    - SG_MAX_VERTEXBUFFER_BINDSLOTS
 3106+    - SG_MAX_VIEW_BINDSLOTS
 3107+    - SG_MAX_SAMPLER_BINDSLOTS
 3108+
 3109+    Note that inside compute passes vertex- and index-buffer-bindings are
 3110+    disallowed.
 3111+
 3112+    When using sokol-shdc for shader authoring, the `layout(binding=N)`
 3113+    for texture-, storage-image- and storage-buffer-bindings directly
 3114+    maps to the views-array index, for instance the following vertex-
 3115+    and fragment-shader interface for sokol-shdc:
 3116+
 3117+        @vs vs
 3118+        layout(binding=0) uniform vs_params { ... };
 3119+        layout(binding=0) readonly buffer ssbo { ... };
 3120+        layout(binding=1) uniform texture2D vs_tex;
 3121+        layout(binding=0) uniform sampler vs_smp;
 3122+        ...
 3123+        @end
 3124+
 3125+        @fs fs
 3126+        layout(binding=1) uniform fs_params { ... };
 3127+        layout(binding=2) uniform texture2D fs_tex;
 3128+        layout(binding=1) uniform sampler fs_smp;
 3129+        ...
 3130+        @end
 3131+
 3132+    ...would map to the following sg_bindings struct:
 3133+
 3134+        const sg_bindings bnd = {
 3135+            .vertex_buffers[0] = ...,
 3136+            .views[0] = ssbo_view,
 3137+            .views[1] = vs_tex_view,
 3138+            .views[2] = fs_tex_view,
 3139+            .samplers[0] = vs_smp,
 3140+            .samplers[1] = fs_smp,
 3141+        };
 3142+
 3143+    ...alternatively you can use code-generated slot indices:
 3144+
 3145+        const sg_bindings bnd = {
 3146+            .vertex_buffers[0] = ...,
 3147+            .views[VIEW_ssbo] = ssbo_view,
 3148+            .views[VIEW_vs_tex] = vs_tex_view,
 3149+            .views[VIEW_fs_tex] = fs_tex_view,
 3150+            .samplers[SMP_vs_smp] = vs_smp,
 3151+            .samplers[SMP_fs_smp] = fs_smp,
 3152+        };
 3153+
 3154+    Resource bindslots for a specific shader/pipeline may have gaps, and an
 3155+    sg_bindings struct may have populated bind slots which are not used by a
 3156+    specific shader. This allows to use the same sg_bindings struct across
 3157+    different shader variants.
 3158+
 3159+    When not using sokol-shdc, the bindslot indices in the sg_bindings
 3160+    struct need to match the per-binding reflection info slot indices
 3161+    in the sg_shader_desc struct (for details about that see the
 3162+    sg_shader_desc struct documentation).
 3163+
 3164+    The optional buffer offsets can be used to put different unrelated
 3165+    chunks of vertex- and/or index-data into the same buffer objects.
 3166+*/
 3167+typedef struct sg_bindings {
 3168+    uint32_t _start_canary;
 3169+    sg_buffer vertex_buffers[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 3170+    int vertex_buffer_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 3171+    sg_buffer index_buffer;
 3172+    int index_buffer_offset;
 3173+    sg_view views[SG_MAX_VIEW_BINDSLOTS];
 3174+    sg_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS];
 3175+    uint32_t _end_canary;
 3176+} sg_bindings;
 3177+
 3178+/*
 3179+    sg_buffer_usage
 3180+
 3181+    Describes how a buffer object is going to be used:
 3182+
 3183+    .vertex_buffer (default: true)
 3184+        the buffer will be bound as vertex buffer via sg_bindings.vertex_buffers[]
 3185+    .index_buffer (default: false)
 3186+        the buffer will be bound as index buffer via sg_bindings.index_buffer
 3187+    .storage_buffer (default: false)
 3188+        the buffer will be bound as storage buffer via storage-buffer-view
 3189+        in sg_bindings.views[]
 3190+    .immutable (default: true)
 3191+        the buffer content will never be updated from the CPU side (but
 3192+        may be written to by a compute shader)
 3193+    .dynamic_update (default: false)
 3194+        the buffer content will be infrequently updated from the CPU side
 3195+    .stream_upate (default: false)
 3196+        the buffer content will be updated each frame from the CPU side
 3197+*/
 3198+typedef struct sg_buffer_usage {
 3199+    bool vertex_buffer;
 3200+    bool index_buffer;
 3201+    bool storage_buffer;
 3202+    bool immutable;
 3203+    bool dynamic_update;
 3204+    bool stream_update;
 3205+} sg_buffer_usage;
 3206+
 3207+/*
 3208+    sg_buffer_desc
 3209+
 3210+    Creation parameters for sg_buffer objects, used in the sg_make_buffer() call.
 3211+
 3212+    The default configuration is:
 3213+
 3214+    .size:      0       (*must* be >0 for buffers without data)
 3215+    .usage      { .vertex_buffer = true, .immutable = true }
 3216+    .data.ptr   0       (*must* be valid for immutable buffers without storage buffer usage)
 3217+    .data.size  0       (*must* be > 0 for immutable buffers without storage buffer usage)
 3218+    .label      0       (optional string label)
 3219+
 3220+    For immutable buffers which are initialized with initial data,
 3221+    keep the .size item zero-initialized, and set the size together with the
 3222+    pointer to the initial data in the .data item.
 3223+
 3224+    For immutable or mutable buffers without initial data, keep the .data item
 3225+    zero-initialized, and set the buffer size in the .size item instead.
 3226+
 3227+    You can also set both size values, but currently both size values must
 3228+    be identical (this may change in the future when the dynamic resource
 3229+    management may become more flexible).
 3230+
 3231+    NOTE: Immutable buffers without storage-buffer-usage *must* be created
 3232+    with initial content, this restriction doesn't apply to storage buffer usage,
 3233+    because storage buffers may also get their initial content by running
 3234+    a compute shader on them.
 3235+
 3236+    NOTE: Buffers without initial data will have undefined content, e.g.
 3237+    do *not* expect the buffer to be zero-initialized!
 3238+
 3239+    ADVANCED TOPIC: Injecting native 3D-API buffers:
 3240+
 3241+    The following struct members allow to inject your own GL, Metal
 3242+    or D3D11 buffers into sokol_gfx:
 3243+
 3244+    .gl_buffers[SG_NUM_INFLIGHT_FRAMES]
 3245+    .mtl_buffers[SG_NUM_INFLIGHT_FRAMES]
 3246+    .d3d11_buffer
 3247+
 3248+    You must still provide all other struct items except the .data item, and
 3249+    these must match the creation parameters of the native buffers you provide.
 3250+    For sg_buffer_desc.usage.immutable buffers, only provide a single native
 3251+    3D-API buffer, otherwise you need to provide SG_NUM_INFLIGHT_FRAMES buffers
 3252+    (only for GL and Metal, not D3D11). Providing multiple buffers for GL and
 3253+    Metal is necessary because sokol_gfx will rotate through them when calling
 3254+    sg_update_buffer() to prevent lock-stalls.
 3255+
 3256+    Note that it is expected that immutable injected buffer have already been
 3257+    initialized with content, and the .content member must be 0!
 3258+
 3259+    Also you need to call sg_reset_state_cache() after calling native 3D-API
 3260+    functions, and before calling any sokol_gfx function.
 3261+*/
 3262+typedef struct sg_buffer_desc {
 3263+    uint32_t _start_canary;
 3264+    size_t size;
 3265+    sg_buffer_usage usage;
 3266+    sg_range data;
 3267+    const char* label;
 3268+    // optionally inject backend-specific resources
 3269+    uint32_t gl_buffers[SG_NUM_INFLIGHT_FRAMES];
 3270+    const void* mtl_buffers[SG_NUM_INFLIGHT_FRAMES];
 3271+    const void* d3d11_buffer;
 3272+    const void* wgpu_buffer;
 3273+    uint32_t _end_canary;
 3274+} sg_buffer_desc;
 3275+
 3276+/*
 3277+    sg_image_usage
 3278+
 3279+    Describes the intended usage of an image object:
 3280+
 3281+    .storage_image (default: false)
 3282+        the image can be used as parent resource of a storage-image-view,
 3283+        which allows compute shaders to write to the image in a compute
 3284+        pass (for read-only access in compute shaders bind the image
 3285+        via a texture view instead
 3286+    .color_attachment (default: false)
 3287+        the image can be used as parent resource of a color-attachment-view,
 3288+        which is then passed into sg_begin_pass via sg_pass.attachments.colors[]
 3289+        so that fragment shaders can render into the image
 3290+    .resolve_attachment (default: false)
 3291+        the image can be used as parent resource of a resolve-attachment-view,
 3292+        which is then passed into sg_begin_pass via sg_pass.attachments.resolves[]
 3293+        as target for an MSAA-resolve operation in sg_end_pass()
 3294+    .depth_stencil_attachment (default: false)
 3295+        the image can be used as parent resource of a depth-stencil-attachmnet-view
 3296+        which is then passes into sg_begin_pass via sg_pass.attachments.depth_stencil
 3297+        as depth-stencil-buffer
 3298+    .immutable (default: true)
 3299+        the image content cannot be updated from the CPU side
 3300+        (but may be updated by the GPU in a render- or compute-pass)
 3301+    .dynamic_update (default: false)
 3302+        the image content is updated infrequently by the CPU via sg_update_image()
 3303+    .stream_update (default: false)
 3304+        the image content is updated each frame by the CPU via sg_update_image()
 3305+
 3306+    Note that creating a texture view from the image to be used for
 3307+    texture-sampling in vertex-, fragment- or compute-shaders
 3308+    is always implicitly allowed.
 3309+*/
 3310+typedef struct sg_image_usage {
 3311+    bool storage_image;
 3312+    bool color_attachment;
 3313+    bool resolve_attachment;
 3314+    bool depth_stencil_attachment;
 3315+    bool immutable;
 3316+    bool dynamic_update;
 3317+    bool stream_update;
 3318+} sg_image_usage;
 3319+
 3320+/*
 3321+    sg_view_type
 3322+
 3323+    Allows to query the type of a view object via the function sg_query_view_type()
 3324+*/
 3325+typedef enum sg_view_type {
 3326+    SG_VIEWTYPE_INVALID,
 3327+    SG_VIEWTYPE_STORAGEBUFFER,
 3328+    SG_VIEWTYPE_STORAGEIMAGE,
 3329+    SG_VIEWTYPE_TEXTURE,
 3330+    SG_VIEWTYPE_COLORATTACHMENT,
 3331+    SG_VIEWTYPE_RESOLVEATTACHMENT,
 3332+    SG_VIEWTYPE_DEPTHSTENCILATTACHMENT,
 3333+    _SG_VIEWTYPE_FORCE_U32 = 0x7FFFFFFF
 3334+} sg_view_type;
 3335+
 3336+/*
 3337+    sg_image_data
 3338+
 3339+    Defines the content of an image through an array of sg_range structs, each
 3340+    range pointing to the pixel data for one mip-level. For array-, cubemap- and
 3341+    3D-images each mip-level contains all slice-surfaces for that mip-level in a
 3342+    single tightly packed memory block.
 3343+
 3344+    The size of a single surface in a mip-level for a regular 2D texture
 3345+    can be computed via:
 3346+
 3347+        sg_query_surface_pitch(pixel_format, mip_width, mip_height, 1);
 3348+
 3349+    For array- and 3d-images the size of a single miplevel is:
 3350+
 3351+        num_slices * sg_query_surface_pitch(pixel_format, mip_width, mip_height, 1);
 3352+
 3353+    For cubemap-images the size of a single mip-level is:
 3354+
 3355+        6 * sg_query_surface_pitch(pixel_format, mip_width, mip_height, 1);
 3356+
 3357+    The order of cubemap-faces is in a mip-level data chunk is:
 3358+
 3359+        [0] => +X
 3360+        [1] => -X
 3361+        [2] => +Y
 3362+        [3] => -Y
 3363+        [4] => +Z
 3364+        [5] => -Z
 3365+*/
 3366+typedef struct sg_image_data {
 3367+    sg_range mip_levels[SG_MAX_MIPMAPS];
 3368+} sg_image_data;
 3369+
 3370+/*
 3371+    sg_image_desc
 3372+
 3373+    Creation parameters for sg_image objects, used in the sg_make_image() call.
 3374+
 3375+    The default configuration is:
 3376+
 3377+    .type               SG_IMAGETYPE_2D
 3378+    .usage              .immutable = true
 3379+    .width              0 (must be set to >0)
 3380+    .height             0 (must be set to >0)
 3381+    .num_slices         1 (3D textures: depth; array textures: number of layers)
 3382+    .num_mipmaps        1
 3383+    .pixel_format       SG_PIXELFORMAT_RGBA8 for textures, or sg_desc.environment.defaults.color_format for render targets
 3384+    .sample_count       1 for textures, or sg_desc.environment.defaults.sample_count for render targets
 3385+    .data               an sg_image_data struct to define the initial content
 3386+    .label              0 (optional string label for trace hooks)
 3387+
 3388+    Q: Why is the default sample_count for render targets identical with the
 3389+    "default sample count" from sg_desc.environment.defaults.sample_count?
 3390+
 3391+    A: So that it matches the default sample count in pipeline objects. Even
 3392+    though it is a bit strange/confusing that offscreen render targets by default
 3393+    get the same sample count as 'default swapchains', but it's better that
 3394+    an offscreen render target created with default parameters matches
 3395+    a pipeline object created with default parameters.
 3396+
 3397+    NOTE:
 3398+
 3399+    Regular images used as texture binding with usage.immutable must be fully
 3400+    initialized by providing a valid .data member which points to initialization
 3401+    data.
 3402+
 3403+    Images with usage.*_attachment or usage.storage_image must
 3404+    *not* be created with initial content. Be aware that the initial
 3405+    content of pass attachment and storage images is undefined
 3406+    (not guaranteed to be zeroed).
 3407+
 3408+    ADVANCED TOPIC: Injecting native 3D-API textures:
 3409+
 3410+    The following struct members allow to inject your own GL, Metal or D3D11
 3411+    textures into sokol_gfx:
 3412+
 3413+    .gl_textures[SG_NUM_INFLIGHT_FRAMES]
 3414+    .mtl_textures[SG_NUM_INFLIGHT_FRAMES]
 3415+    .d3d11_texture
 3416+    .wgpu_texture
 3417+
 3418+    For GL, you can also specify the texture target or leave it empty to use
 3419+    the default texture target for the image type (GL_TEXTURE_2D for
 3420+    SG_IMAGETYPE_2D etc)
 3421+
 3422+    The same rules apply as for injecting native buffers (see sg_buffer_desc
 3423+    documentation for more details).
 3424+*/
 3425+typedef struct sg_image_desc {
 3426+    uint32_t _start_canary;
 3427+    sg_image_type type;
 3428+    sg_image_usage usage;
 3429+    int width;
 3430+    int height;
 3431+    int num_slices;
 3432+    int num_mipmaps;
 3433+    sg_pixel_format pixel_format;
 3434+    int sample_count;
 3435+    sg_image_data data;
 3436+    const char* label;
 3437+    // optionally inject backend-specific resources
 3438+    uint32_t gl_textures[SG_NUM_INFLIGHT_FRAMES];
 3439+    uint32_t gl_texture_target;
 3440+    const void* mtl_textures[SG_NUM_INFLIGHT_FRAMES];
 3441+    const void* d3d11_texture;
 3442+    const void* wgpu_texture;
 3443+    uint32_t _end_canary;
 3444+} sg_image_desc;
 3445+
 3446+/*
 3447+    sg_sampler_desc
 3448+
 3449+    Creation parameters for sg_sampler objects, used in the sg_make_sampler() call
 3450+
 3451+    .min_filter:        SG_FILTER_NEAREST
 3452+    .mag_filter:        SG_FILTER_NEAREST
 3453+    .mipmap_filter      SG_FILTER_NEAREST
 3454+    .wrap_u:            SG_WRAP_REPEAT
 3455+    .wrap_v:            SG_WRAP_REPEAT
 3456+    .wrap_w:            SG_WRAP_REPEAT (only SG_IMAGETYPE_3D)
 3457+    .min_lod            0.0f
 3458+    .max_lod            FLT_MAX
 3459+    .border_color       SG_BORDERCOLOR_OPAQUE_BLACK
 3460+    .compare            SG_COMPAREFUNC_NEVER
 3461+    .max_anisotropy     1 (must be 1..16)
 3462+*/
 3463+typedef struct sg_sampler_desc {
 3464+    uint32_t _start_canary;
 3465+    sg_filter min_filter;
 3466+    sg_filter mag_filter;
 3467+    sg_filter mipmap_filter;
 3468+    sg_wrap wrap_u;
 3469+    sg_wrap wrap_v;
 3470+    sg_wrap wrap_w;
 3471+    float min_lod;
 3472+    float max_lod;
 3473+    sg_border_color border_color;
 3474+    sg_compare_func compare;
 3475+    uint32_t max_anisotropy;
 3476+    const char* label;
 3477+    // optionally inject backend-specific resources
 3478+    uint32_t gl_sampler;
 3479+    const void* mtl_sampler;
 3480+    const void* d3d11_sampler;
 3481+    const void* wgpu_sampler;
 3482+    uint32_t _end_canary;
 3483+} sg_sampler_desc;
 3484+
 3485+/*
 3486+    sg_shader_desc
 3487+
 3488+    Used as parameter of sg_make_shader() to create a shader object which
 3489+    communicates shader source or bytecode and shader interface
 3490+    reflection information to sokol-gfx.
 3491+
 3492+    If you use sokol-shdc you can ignore the following information since
 3493+    the sg_shader_desc struct will be code-generated.
 3494+
 3495+    Otherwise you need to provide the following information to the
 3496+    sg_make_shader() call:
 3497+
 3498+    - a vertex- and fragment-shader function:
 3499+        - the shader source or bytecode
 3500+        - an optional entry point name
 3501+        - for D3D11: an optional compile target when source code is provided
 3502+          (the defaults are "vs_4_0" and "ps_4_0")
 3503+
 3504+    - ...or alternatively, a compute function:
 3505+        - the shader source or bytecode
 3506+        - an optional entry point name
 3507+        - for D3D11: an optional compile target when source code is provided
 3508+          (the default is "cs_5_0")
 3509+
 3510+    - vertex attributes required by some backends (not for compute shaders):
 3511+        - the vertex attribute base type (undefined, float, signed int, unsigned int),
 3512+          this information is only used in the validation layer to check that the
 3513+          pipeline object vertex formats are compatible with the input vertex attribute
 3514+          type used in the vertex shader. NOTE that the default base type
 3515+          'undefined' skips the validation layer check.
 3516+        - for the GL backend: optional vertex attribute names used for name lookup
 3517+        - for the D3D11 backend: semantic names and indices
 3518+
 3519+    - only for compute shaders on the Metal backend:
 3520+        - the workgroup size aka 'threads per thread-group'
 3521+
 3522+          In other 3D APIs this is declared in the shader code:
 3523+            - GLSL: `layout(local_size_x=x, local_size_y=y, local_size_y=z) in;`
 3524+            - HLSL: `[numthreads(x, y, z)]`
 3525+            - WGSL: `@workgroup_size(x, y, z)`
 3526+          ...but in Metal the workgroup size is declared on the CPU side
 3527+
 3528+    - reflection information for each uniform block binding used by the shader:
 3529+        - the shader stage the uniform block appears in (SG_SHADERSTAGE_*)
 3530+        - the size in bytes of the uniform block
 3531+        - backend-specific bindslots:
 3532+            - HLSL: the constant buffer register `register(b0..7)`
 3533+            - MSL: the buffer attribute `[[buffer(0..7)]]`
 3534+            - WGSL: the binding in `@group(0) @binding(0..15)`
 3535+        - GLSL only: a description of the uniform block interior
 3536+            - the memory layout standard (SG_UNIFORMLAYOUT_*)
 3537+            - for each member in the uniform block:
 3538+                - the member type (SG_UNIFORM_*)
 3539+                - if the member is an array, the array count
 3540+                - the member name
 3541+
 3542+    - reflection information for each texture-, storage-buffer and
 3543+      storage-image bindings by the shader, each with an associated
 3544+      view type:
 3545+        - texture bindings => texture views
 3546+        - storage-buffer bindings => storage-buffer views
 3547+        - storage-image bindings => storage-image views
 3548+
 3549+    - texture bindings must provide the following information:
 3550+        - the shader stage the texture binding appears in (SG_SHADERSTAGE_*)
 3551+        - the image type (SG_IMAGETYPE_*)
 3552+        - the image-sample type (SG_IMAGESAMPLETYPE_*)
 3553+        - whether the texture is multisampled
 3554+        - backend specific bindslots:
 3555+            - HLSL: the texture register `register(t0..31)`
 3556+            - MSL: the texture attribute `[[texture(0..31)]]`
 3557+            - WGSL: the binding in `@group(1) @binding(0..127)`
 3558+
 3559+    - storage-buffer bindings must provide the following information:
 3560+        - the shader stage the storage buffer appears in (SG_SHADERSTAGE_*)
 3561+        - whether the storage buffer is readonly
 3562+        - backend specific bindslots:
 3563+            - HLSL:
 3564+                - for storage buffer bindings: `register(t0..31)`
 3565+                - for read/write storage buffer bindings: `register(u0..31)`
 3566+            - MSL: the buffer attribute `[[buffer(8..23)]]`
 3567+            - WGSL: the binding in `@group(1) @binding(0..127)`
 3568+            - GL: the binding in `layout(binding=0..sg_limits.max_storage_buffer_bindings_per_stage)`
 3569+
 3570+    - storage-image bindings must provide the following information:
 3571+        - the shader stage (*must* be SG_SHADERSTAGE_COMPUTE)
 3572+        - whether the storage image is writeonly or readwrite (for readonly
 3573+          access use a regular texture binding instead)
 3574+        - the image type expected by the shader (SG_IMAGETYPE_*)
 3575+        - the access pixel format expected by the shader (SG_PIXELFORMAT_*),
 3576+          note that only a subset of pixel formats is allowed for storage image
 3577+          bindings
 3578+        - backend specific bindslots:
 3579+            - HLSL: the UAV register `register(u0..31)`
 3580+            - MSL: the texture attribute `[[texture(0..31)]]`
 3581+            - WGSL: the binding in `@group(1) @binding(0..127)`
 3582+            - GLSL: the binding in `layout(binding=0..sg_imits.max_storage_buffer_bindings_per_stage, [access_format])`
 3583+
 3584+    - reflection information for each sampler used by the shader:
 3585+        - the shader stage the sampler appears in (SG_SHADERSTAGE_*)
 3586+        - the sampler type (SG_SAMPLERTYPE_*)
 3587+        - backend specific bindslots:
 3588+            - HLSL: the sampler register `register(s0..11)`
 3589+            - MSL: the sampler attribute `[[sampler(0..11)]]`
 3590+            - WGSL: the binding in `@group(0) @binding(0..127)`
 3591+
 3592+    - reflection information for each texture-sampler pair used by
 3593+      the shader:
 3594+        - the shader stage (SG_SHADERSTAGE_*)
 3595+        - the texture's array index in the sg_shader_desc.views[] array
 3596+        - the sampler's array index in the sg_shader_desc.samplers[] array
 3597+        - GLSL only: the name of the combined image-sampler object
 3598+
 3599+    The number and order of items in the sg_shader_desc.attrs[]
 3600+    array corresponds to the items in sg_pipeline_desc.layout.attrs.
 3601+
 3602+        - sg_shader_desc.attrs[N] => sg_pipeline_desc.layout.attrs[N]
 3603+
 3604+    NOTE that vertex attribute indices currently cannot have gaps.
 3605+
 3606+    The items index in the sg_shader_desc.uniform_blocks[] array corresponds
 3607+    to the ub_slot arg in sg_apply_uniforms():
 3608+
 3609+        - sg_shader_desc.uniform_blocks[N] => sg_apply_uniforms(N, ...)
 3610+
 3611+    The items in the sg_shader_desc.views[] array directly map to
 3612+    the views in the sg_bindings.views[] array!
 3613+
 3614+    For all GL backends, shader source-code must be provided. For D3D11 and Metal,
 3615+    either shader source-code or byte-code can be provided.
 3616+
 3617+    NOTE that the uniform-block, view and sampler arrays may have gaps. This
 3618+    allows to use the same sg_bindings struct for different but related
 3619+    shader variations.
 3620+
 3621+    For D3D11, if source code is provided, the d3dcompiler_47.dll will be loaded
 3622+    on demand. If this fails, shader creation will fail. When compiling HLSL
 3623+    source code, you can provide an optional target string via
 3624+    sg_shader_stage_desc.d3d11_target, the default target is "vs_4_0" for the
 3625+    vertex shader stage and "ps_4_0" for the pixel shader stage.
 3626+    You may optionally provide the file path to enable the default #include handler
 3627+    behavior when compiling source code.
 3628+*/
 3629+typedef enum sg_shader_stage {
 3630+    SG_SHADERSTAGE_NONE,
 3631+    SG_SHADERSTAGE_VERTEX,
 3632+    SG_SHADERSTAGE_FRAGMENT,
 3633+    SG_SHADERSTAGE_COMPUTE,
 3634+    _SG_SHADERSTAGE_FORCE_U32 = 0x7FFFFFFF,
 3635+} sg_shader_stage;
 3636+
 3637+typedef struct sg_shader_function {
 3638+    const char* source;
 3639+    sg_range bytecode;
 3640+    const char* entry;
 3641+    const char* d3d11_target;   // default: "vs_4_0" or "ps_4_0"
 3642+    const char* d3d11_filepath;
 3643+} sg_shader_function;
 3644+
 3645+typedef enum sg_shader_attr_base_type {
 3646+    SG_SHADERATTRBASETYPE_UNDEFINED,
 3647+    SG_SHADERATTRBASETYPE_FLOAT,
 3648+    SG_SHADERATTRBASETYPE_SINT,
 3649+    SG_SHADERATTRBASETYPE_UINT,
 3650+    _SG_SHADERATTRBASETYPE_FORCE_U32 = 0x7FFFFFFF,
 3651+} sg_shader_attr_base_type;
 3652+
 3653+typedef struct sg_shader_vertex_attr {
 3654+    sg_shader_attr_base_type base_type;  // default: UNDEFINED (disables validation)
 3655+    const char* glsl_name;      // [optional] GLSL attribute name
 3656+    const char* hlsl_sem_name;  // HLSL semantic name
 3657+    uint8_t hlsl_sem_index;     // HLSL semantic index
 3658+} sg_shader_vertex_attr;
 3659+
 3660+typedef struct sg_glsl_shader_uniform {
 3661+    sg_uniform_type type;
 3662+    uint16_t array_count;       // 0 or 1 for scalars, >1 for arrays
 3663+    const char* glsl_name;      // glsl name binding is required on GL 4.1 and WebGL2
 3664+} sg_glsl_shader_uniform;
 3665+
 3666+typedef struct sg_shader_uniform_block {
 3667+    sg_shader_stage stage;
 3668+    uint32_t size;
 3669+    uint8_t hlsl_register_b_n;  // HLSL register(bn)
 3670+    uint8_t msl_buffer_n;       // MSL [[buffer(n)]]
 3671+    uint8_t wgsl_group0_binding_n;  // WGSL @group(0) @binding(n)
 3672+    uint8_t spirv_set0_binding_n;   // Vulkan GLSL layout(set=0, binding=n)
 3673+    sg_uniform_layout layout;
 3674+    sg_glsl_shader_uniform glsl_uniforms[SG_MAX_UNIFORMBLOCK_MEMBERS];
 3675+} sg_shader_uniform_block;
 3676+
 3677+typedef struct sg_shader_texture_view {
 3678+    sg_shader_stage stage;
 3679+    sg_image_type image_type;
 3680+    sg_image_sample_type sample_type;
 3681+    bool multisampled;
 3682+    uint8_t hlsl_register_t_n;      // HLSL register(tn) bind slot
 3683+    uint8_t msl_texture_n;          // MSL [[texture(n)]] bind slot
 3684+    uint8_t wgsl_group1_binding_n;  // WGSL @group(1) @binding(n) bind slot
 3685+    uint8_t spirv_set1_binding_n;   // Vulkan GLSL layout(set=1, binding=0)
 3686+} sg_shader_texture_view;
 3687+
 3688+typedef struct sg_shader_storage_buffer_view {
 3689+    sg_shader_stage stage;
 3690+    bool readonly;
 3691+    uint8_t hlsl_register_t_n;      // HLSL register(tn) bind slot (for readonly access)
 3692+    uint8_t hlsl_register_u_n;      // HLSL register(un) bind slot (for read/write access)
 3693+    uint8_t msl_buffer_n;           // MSL [[buffer(n)]] bind slot
 3694+    uint8_t wgsl_group1_binding_n;  // WGSL @group(1) @binding(n) bind slot
 3695+    uint8_t spirv_set1_binding_n;   // Vulkan GLSL layout(set=1, binding=0)
 3696+    uint8_t glsl_binding_n;         // GLSL layout(binding=n)
 3697+} sg_shader_storage_buffer_view;
 3698+
 3699+typedef struct sg_shader_storage_image_view {
 3700+    sg_shader_stage stage;
 3701+    sg_image_type image_type;
 3702+    sg_pixel_format access_format;  // shader-access pixel format
 3703+    bool writeonly;                 // false means read/write access
 3704+    uint8_t hlsl_register_u_n;      // HLSL register(un) bind slot
 3705+    uint8_t msl_texture_n;          // MSL [[texture(n)]] bind slot
 3706+    uint8_t wgsl_group1_binding_n;  // WGSL @group(2) @binding(n) bind slot
 3707+    uint8_t spirv_set1_binding_n;   // Vulkan GLSL layout(set=1, binding=0)
 3708+    uint8_t glsl_binding_n;         // GLSL layout(binding=n)
 3709+} sg_shader_storage_image_view;
 3710+
 3711+typedef struct sg_shader_view {
 3712+    sg_shader_texture_view texture;
 3713+    sg_shader_storage_buffer_view storage_buffer;
 3714+    sg_shader_storage_image_view storage_image;
 3715+} sg_shader_view;
 3716+
 3717+typedef struct sg_shader_sampler {
 3718+    sg_shader_stage stage;
 3719+    sg_sampler_type sampler_type;
 3720+    uint8_t hlsl_register_s_n;      // HLSL register(sn) bind slot
 3721+    uint8_t msl_sampler_n;          // MSL [[sampler(n)]] bind slot
 3722+    uint8_t wgsl_group1_binding_n;  // WGSL @group(1) @binding(n) bind slot
 3723+    uint8_t spirv_set1_binding_n;   // Vulkan GLSL layout(set=1, binding=0)
 3724+} sg_shader_sampler;
 3725+
 3726+typedef struct sg_shader_texture_sampler_pair {
 3727+    sg_shader_stage stage;
 3728+    uint8_t view_slot;              // must be SG_VIEWTYPE_TEXTURE
 3729+    uint8_t sampler_slot;
 3730+    const char* glsl_name;          // glsl name binding required because of GL 4.1 and WebGL2
 3731+} sg_shader_texture_sampler_pair;
 3732+
 3733+typedef struct sg_mtl_shader_threads_per_threadgroup {
 3734+    int x, y, z;
 3735+} sg_mtl_shader_threads_per_threadgroup;
 3736+
 3737+typedef struct sg_shader_desc {
 3738+    uint32_t _start_canary;
 3739+    sg_shader_function vertex_func;
 3740+    sg_shader_function fragment_func;
 3741+    sg_shader_function compute_func;
 3742+    sg_shader_vertex_attr attrs[SG_MAX_VERTEX_ATTRIBUTES];
 3743+    sg_shader_uniform_block uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 3744+    sg_shader_view views[SG_MAX_VIEW_BINDSLOTS];
 3745+    sg_shader_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS];
 3746+    sg_shader_texture_sampler_pair texture_sampler_pairs[SG_MAX_TEXTURE_SAMPLER_PAIRS];
 3747+    sg_mtl_shader_threads_per_threadgroup mtl_threads_per_threadgroup;
 3748+    const char* label;
 3749+    uint32_t _end_canary;
 3750+} sg_shader_desc;
 3751+
 3752+/*
 3753+    sg_pipeline_desc
 3754+
 3755+    The sg_pipeline_desc struct defines all creation parameters for an
 3756+    sg_pipeline object, used as argument to the sg_make_pipeline() function:
 3757+
 3758+    Pipeline objects come in two flavours:
 3759+
 3760+    - render pipelines for use in render passes
 3761+    - compute pipelines for use in compute passes
 3762+
 3763+    A compute pipeline only requires a compute shader object but no
 3764+    'render state', while a render pipeline requires a vertex/fragment shader
 3765+    object and additional render state declarations:
 3766+
 3767+    - the vertex layout for all input vertex buffers
 3768+    - a shader object
 3769+    - the 3D primitive type (points, lines, triangles, ...)
 3770+    - the index type (none, 16- or 32-bit)
 3771+    - all the fixed-function-pipeline state (depth-, stencil-, blend-state, etc...)
 3772+
 3773+    If the vertex data has no gaps between vertex components, you can omit
 3774+    the .layout.buffers[].stride and layout.attrs[].offset items (leave them
 3775+    default-initialized to 0), sokol-gfx will then compute the offsets and
 3776+    strides from the vertex component formats (.layout.attrs[].format).
 3777+    Please note that ALL vertex attribute offsets must be 0 in order for the
 3778+    automatic offset computation to kick in.
 3779+
 3780+    Note that if you use vertex-pulling from storage buffers instead of
 3781+    fixed-function vertex input you can simply omit the entire nested .layout
 3782+    struct.
 3783+
 3784+    The default configuration is as follows:
 3785+
 3786+    .compute:               false (must be set to true for a compute pipeline)
 3787+    .shader:                0 (must be initialized with a valid sg_shader id!)
 3788+    .layout:
 3789+        .buffers[]:         vertex buffer layouts
 3790+            .stride:        0 (if no stride is given it will be computed)
 3791+            .step_func      SG_VERTEXSTEP_PER_VERTEX
 3792+            .step_rate      1
 3793+        .attrs[]:           vertex attribute declarations
 3794+            .buffer_index   0 the vertex buffer bind slot
 3795+            .offset         0 (offsets can be omitted if the vertex layout has no gaps)
 3796+            .format         SG_VERTEXFORMAT_INVALID (must be initialized!)
 3797+    .depth:
 3798+        .pixel_format:      sg_desc.context.depth_format
 3799+        .compare:           SG_COMPAREFUNC_ALWAYS
 3800+        .write_enabled:     false
 3801+        .bias:              0.0f
 3802+        .bias_slope_scale:  0.0f
 3803+        .bias_clamp:        0.0f
 3804+    .stencil:
 3805+        .enabled:           false
 3806+        .front/back:
 3807+            .compare:       SG_COMPAREFUNC_ALWAYS
 3808+            .fail_op:       SG_STENCILOP_KEEP
 3809+            .depth_fail_op: SG_STENCILOP_KEEP
 3810+            .pass_op:       SG_STENCILOP_KEEP
 3811+        .read_mask:         0
 3812+        .write_mask:        0
 3813+        .ref:               0
 3814+    .color_count            1
 3815+    .colors[0..color_count]
 3816+        .pixel_format       sg_desc.context.color_format
 3817+        .write_mask:        SG_COLORMASK_RGBA
 3818+        .blend:
 3819+            .enabled:           false
 3820+            .src_factor_rgb:    SG_BLENDFACTOR_ONE
 3821+            .dst_factor_rgb:    SG_BLENDFACTOR_ZERO
 3822+            .op_rgb:            SG_BLENDOP_ADD
 3823+            .src_factor_alpha:  SG_BLENDFACTOR_ONE
 3824+            .dst_factor_alpha:  SG_BLENDFACTOR_ZERO
 3825+            .op_alpha:          SG_BLENDOP_ADD
 3826+    .primitive_type:            SG_PRIMITIVETYPE_TRIANGLES
 3827+    .index_type:                SG_INDEXTYPE_NONE
 3828+    .cull_mode:                 SG_CULLMODE_NONE
 3829+    .face_winding:              SG_FACEWINDING_CW
 3830+    .sample_count:              sg_desc.context.sample_count
 3831+    .blend_color:               (sg_color) { 0.0f, 0.0f, 0.0f, 0.0f }
 3832+    .alpha_to_coverage_enabled: false
 3833+    .label  0       (optional string label for trace hooks)
 3834+*/
 3835+typedef struct sg_vertex_buffer_layout_state {
 3836+    int stride;
 3837+    sg_vertex_step step_func;
 3838+    int step_rate;
 3839+} sg_vertex_buffer_layout_state;
 3840+
 3841+typedef struct sg_vertex_attr_state {
 3842+    int buffer_index;
 3843+    int offset;
 3844+    sg_vertex_format format;
 3845+} sg_vertex_attr_state;
 3846+
 3847+typedef struct sg_vertex_layout_state {
 3848+    sg_vertex_buffer_layout_state buffers[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 3849+    sg_vertex_attr_state attrs[SG_MAX_VERTEX_ATTRIBUTES];
 3850+} sg_vertex_layout_state;
 3851+
 3852+typedef struct sg_stencil_face_state {
 3853+    sg_compare_func compare;
 3854+    sg_stencil_op fail_op;
 3855+    sg_stencil_op depth_fail_op;
 3856+    sg_stencil_op pass_op;
 3857+} sg_stencil_face_state;
 3858+
 3859+typedef struct sg_stencil_state {
 3860+    bool enabled;
 3861+    sg_stencil_face_state front;
 3862+    sg_stencil_face_state back;
 3863+    uint8_t read_mask;
 3864+    uint8_t write_mask;
 3865+    uint8_t ref;
 3866+} sg_stencil_state;
 3867+
 3868+typedef struct sg_depth_state {
 3869+    sg_pixel_format pixel_format;
 3870+    sg_compare_func compare;
 3871+    bool write_enabled;
 3872+    float bias;
 3873+    float bias_slope_scale;
 3874+    float bias_clamp;
 3875+} sg_depth_state;
 3876+
 3877+typedef struct sg_blend_state {
 3878+    bool enabled;
 3879+    sg_blend_factor src_factor_rgb;
 3880+    sg_blend_factor dst_factor_rgb;
 3881+    sg_blend_op op_rgb;
 3882+    sg_blend_factor src_factor_alpha;
 3883+    sg_blend_factor dst_factor_alpha;
 3884+    sg_blend_op op_alpha;
 3885+} sg_blend_state;
 3886+
 3887+typedef struct sg_color_target_state {
 3888+    sg_pixel_format pixel_format;
 3889+    sg_color_mask write_mask;
 3890+    sg_blend_state blend;
 3891+} sg_color_target_state;
 3892+
 3893+typedef struct sg_pipeline_desc {
 3894+    uint32_t _start_canary;
 3895+    bool compute;
 3896+    sg_shader shader;
 3897+    sg_vertex_layout_state layout;
 3898+    sg_depth_state depth;
 3899+    sg_stencil_state stencil;
 3900+    int color_count;
 3901+    sg_color_target_state colors[SG_MAX_COLOR_ATTACHMENTS];
 3902+    sg_primitive_type primitive_type;
 3903+    sg_index_type index_type;
 3904+    sg_cull_mode cull_mode;
 3905+    sg_face_winding face_winding;
 3906+    int sample_count;
 3907+    sg_color blend_color;
 3908+    bool alpha_to_coverage_enabled;
 3909+    const char* label;
 3910+    uint32_t _end_canary;
 3911+} sg_pipeline_desc;
 3912+
 3913+/*
 3914+    sg_view_desc
 3915+
 3916+    Creation params for sg_view objects, passed into sg_make_view() calls.
 3917+
 3918+    View objects are passed into sg_apply_bindings() (for texture-, storage-buffer-
 3919+    and storage-image views), and sg_begin_pass() (for color-, resolve-
 3920+    and depth-stencil-attachment views).
 3921+
 3922+    The view type is determined by initializing one of the sub-structs of
 3923+    sg_view_desc:
 3924+
 3925+    .texture            a texture-view object will be created
 3926+        .image          the sg_image parent resource
 3927+        .mip_levels     optional mip-level range, keep zero-initialized for the
 3928+                        entire mipmap chain
 3929+            .base       the first mip level
 3930+            .count      number of mip levels, keeping this zero-initialized means
 3931+                        'all remaining mip levels'
 3932+        .slices         optional slice range, keep zero-initialized to include
 3933+                        all slices
 3934+            .base       the first slice
 3935+            .count      number of slices, keeping this zero-initializied means 'all remaining slices'
 3936+
 3937+    .storage_buffer     a storage-buffer-view object will be created
 3938+        .buffer         the sg_buffer parent resource, must have been created
 3939+                        with `sg_buffer_desc.usage.storage_buffer = true`
 3940+        .offset         optional 256-byte aligned byte-offset into the buffer
 3941+
 3942+    .storage_image      a storage-image-view object will be created
 3943+        .image          the sg_image parent resource, must have been created
 3944+                        with `sg_image_desc.usage.storage_image = true`
 3945+        .mip_level      selects the mip-level for the compute shader to write
 3946+        .slice          selects the slice for the compute shader to write
 3947+
 3948+    .color_attachment   a color-attachment-view object will be created
 3949+        .image          the sg_image parent resource, must have been created
 3950+                        with `sg_image_desc.usage.color_attachment = true`
 3951+        .mip_level      selects the mip-level to render into
 3952+        .slice          selects the slice to render into
 3953+
 3954+    .resolve_attachment a resolve-attachment-view object will be created
 3955+        .image          the sg_image parent resource, must have been created
 3956+                        with `sg_image_desc.usage.resolve_attachment = true`
 3957+        .mip_level      selects the mip-level to msaa-resolve into
 3958+        .slice          selects the slice to msaa-resolve into
 3959+
 3960+    .depth_stencil_attachment   a depth-stencil-attachment-view object will be created
 3961+        .image          the sg_image parent resource, must have been created
 3962+                        with `sg_image_desc.usage.depth_stencil_attachment = true`
 3963+        .mip_level      selects the mip-level to render into
 3964+        .slice          selects the slice to render into
 3965+*/
 3966+typedef struct sg_buffer_view_desc {
 3967+    sg_buffer buffer;
 3968+    int offset;
 3969+} sg_buffer_view_desc;
 3970+
 3971+typedef struct sg_image_view_desc {
 3972+    sg_image image;
 3973+    int mip_level;
 3974+    int slice;      // cube texture: face; array texture: layer; 3D texture: depth-slice
 3975+} sg_image_view_desc;
 3976+
 3977+typedef struct sg_texture_view_range {
 3978+    int base;
 3979+    int count;
 3980+} sg_texture_view_range;
 3981+
 3982+typedef struct sg_texture_view_desc {
 3983+    sg_image image;
 3984+    sg_texture_view_range mip_levels;
 3985+    sg_texture_view_range slices;   // cube texture: face; array texture: layer; 3D texture: depth-slice
 3986+} sg_texture_view_desc;
 3987+
 3988+typedef struct sg_view_desc {
 3989+    uint32_t _start_canary;
 3990+    sg_texture_view_desc texture;
 3991+    sg_buffer_view_desc storage_buffer;
 3992+    sg_image_view_desc storage_image;
 3993+    sg_image_view_desc color_attachment;
 3994+    sg_image_view_desc resolve_attachment;
 3995+    sg_image_view_desc depth_stencil_attachment;
 3996+    const char* label;
 3997+    uint32_t _end_canary;
 3998+} sg_view_desc;
 3999+
 4000+/*
 4001+    sg_trace_hooks
 4002+
 4003+    Installable callback functions to keep track of the sokol-gfx calls,
 4004+    this is useful for debugging, or keeping track of resource creation
 4005+    and destruction.
 4006+
 4007+    Trace hooks are installed with sg_install_trace_hooks(), this returns
 4008+    another sg_trace_hooks struct with the previous set of
 4009+    trace hook function pointers. These should be invoked by the
 4010+    new trace hooks to form a proper call chain.
 4011+*/
 4012+typedef struct sg_trace_hooks {
 4013+    void* user_data;
 4014+    void (*reset_state_cache)(void* user_data);
 4015+    void (*make_buffer)(const sg_buffer_desc* desc, sg_buffer result, void* user_data);
 4016+    void (*make_image)(const sg_image_desc* desc, sg_image result, void* user_data);
 4017+    void (*make_sampler)(const sg_sampler_desc* desc, sg_sampler result, void* user_data);
 4018+    void (*make_shader)(const sg_shader_desc* desc, sg_shader result, void* user_data);
 4019+    void (*make_pipeline)(const sg_pipeline_desc* desc, sg_pipeline result, void* user_data);
 4020+    void (*make_view)(const sg_view_desc* desc, sg_view result, void* user_data);
 4021+    void (*destroy_buffer)(sg_buffer buf, void* user_data);
 4022+    void (*destroy_image)(sg_image img, void* user_data);
 4023+    void (*destroy_sampler)(sg_sampler smp, void* user_data);
 4024+    void (*destroy_shader)(sg_shader shd, void* user_data);
 4025+    void (*destroy_pipeline)(sg_pipeline pip, void* user_data);
 4026+    void (*destroy_view)(sg_view view, void* user_data);
 4027+    void (*update_buffer)(sg_buffer buf, const sg_range* data, void* user_data);
 4028+    void (*update_image)(sg_image img, const sg_image_data* data, void* user_data);
 4029+    void (*append_buffer)(sg_buffer buf, const sg_range* data, int result, void* user_data);
 4030+    void (*begin_pass)(const sg_pass* pass, void* user_data);
 4031+    void (*apply_viewport)(int x, int y, int width, int height, bool origin_top_left, void* user_data);
 4032+    void (*apply_scissor_rect)(int x, int y, int width, int height, bool origin_top_left, void* user_data);
 4033+    void (*apply_pipeline)(sg_pipeline pip, void* user_data);
 4034+    void (*apply_bindings)(const sg_bindings* bindings, void* user_data);
 4035+    void (*apply_uniforms)(int ub_index, const sg_range* data, void* user_data);
 4036+    void (*draw)(int base_element, int num_elements, int num_instances, void* user_data);
 4037+    void (*draw_ex)(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance, void* user_data);
 4038+    void (*dispatch)(int num_groups_x, int num_groups_y, int num_groups_z, void* user_data);
 4039+    void (*end_pass)(void* user_data);
 4040+    void (*commit)(void* user_data);
 4041+    void (*alloc_buffer)(sg_buffer result, void* user_data);
 4042+    void (*alloc_image)(sg_image result, void* user_data);
 4043+    void (*alloc_sampler)(sg_sampler result, void* user_data);
 4044+    void (*alloc_shader)(sg_shader result, void* user_data);
 4045+    void (*alloc_pipeline)(sg_pipeline result, void* user_data);
 4046+    void (*alloc_view)(sg_view result, void* user_data);
 4047+    void (*dealloc_buffer)(sg_buffer buf_id, void* user_data);
 4048+    void (*dealloc_image)(sg_image img_id, void* user_data);
 4049+    void (*dealloc_sampler)(sg_sampler smp_id, void* user_data);
 4050+    void (*dealloc_shader)(sg_shader shd_id, void* user_data);
 4051+    void (*dealloc_pipeline)(sg_pipeline pip_id, void* user_data);
 4052+    void (*dealloc_view)(sg_view view_id, void* user_data);
 4053+    void (*init_buffer)(sg_buffer buf_id, const sg_buffer_desc* desc, void* user_data);
 4054+    void (*init_image)(sg_image img_id, const sg_image_desc* desc, void* user_data);
 4055+    void (*init_sampler)(sg_sampler smp_id, const sg_sampler_desc* desc, void* user_data);
 4056+    void (*init_shader)(sg_shader shd_id, const sg_shader_desc* desc, void* user_data);
 4057+    void (*init_pipeline)(sg_pipeline pip_id, const sg_pipeline_desc* desc, void* user_data);
 4058+    void (*init_view)(sg_view view_id, const sg_view_desc* desc, void* user_data);
 4059+    void (*uninit_buffer)(sg_buffer buf_id, void* user_data);
 4060+    void (*uninit_image)(sg_image img_id, void* user_data);
 4061+    void (*uninit_sampler)(sg_sampler smp_id, void* user_data);
 4062+    void (*uninit_shader)(sg_shader shd_id, void* user_data);
 4063+    void (*uninit_pipeline)(sg_pipeline pip_id, void* user_data);
 4064+    void (*uninit_view)(sg_view view_id, void* user_data);
 4065+    void (*fail_buffer)(sg_buffer buf_id, void* user_data);
 4066+    void (*fail_image)(sg_image img_id, void* user_data);
 4067+    void (*fail_sampler)(sg_sampler smp_id, void* user_data);
 4068+    void (*fail_shader)(sg_shader shd_id, void* user_data);
 4069+    void (*fail_pipeline)(sg_pipeline pip_id, void* user_data);
 4070+    void (*fail_view)(sg_view view_id, void* user_data);
 4071+    void (*push_debug_group)(const char* name, void* user_data);
 4072+    void (*pop_debug_group)(void* user_data);
 4073+} sg_trace_hooks;
 4074+
 4075+/*
 4076+    sg_buffer_info
 4077+    sg_image_info
 4078+    sg_sampler_info
 4079+    sg_shader_info
 4080+    sg_pipeline_info
 4081+    sg_view_info
 4082+
 4083+    These structs contain various internal resource attributes which
 4084+    might be useful for debug-inspection. Please don't rely on the
 4085+    actual content of those structs too much, as they are quite closely
 4086+    tied to sokol_gfx.h internals and may change more frequently than
 4087+    the other public API elements.
 4088+
 4089+    The *_info structs are used as the return values of the following functions:
 4090+
 4091+    sg_query_buffer_info()
 4092+    sg_query_image_info()
 4093+    sg_query_sampler_info()
 4094+    sg_query_shader_info()
 4095+    sg_query_pipeline_info()
 4096+    sg_query_view_info()
 4097+*/
 4098+typedef struct sg_slot_info {
 4099+    sg_resource_state state;    // the current state of this resource slot
 4100+    uint32_t res_id;            // type-neutral resource if (e.g. sg_buffer.id)
 4101+    uint32_t uninit_count;
 4102+} sg_slot_info;
 4103+
 4104+typedef struct sg_buffer_info {
 4105+    sg_slot_info slot;              // resource pool slot info
 4106+    uint32_t update_frame_index;    // frame index of last sg_update_buffer()
 4107+    uint32_t append_frame_index;    // frame index of last sg_append_buffer()
 4108+    int append_pos;                 // current position in buffer for sg_append_buffer()
 4109+    bool append_overflow;           // is buffer in overflow state (due to sg_append_buffer)
 4110+    int num_slots;                  // number of renaming-slots for dynamically updated buffers
 4111+    int active_slot;                // currently active write-slot for dynamically updated buffers
 4112+} sg_buffer_info;
 4113+
 4114+typedef struct sg_image_info {
 4115+    sg_slot_info slot;              // resource pool slot info
 4116+    uint32_t upd_frame_index;       // frame index of last sg_update_image()
 4117+    int num_slots;                  // number of renaming-slots for dynamically updated images
 4118+    int active_slot;                // currently active write-slot for dynamically updated images
 4119+} sg_image_info;
 4120+
 4121+typedef struct sg_sampler_info {
 4122+    sg_slot_info slot;              // resource pool slot info
 4123+} sg_sampler_info;
 4124+
 4125+typedef struct sg_shader_info {
 4126+    sg_slot_info slot;              // resource pool slot info
 4127+} sg_shader_info;
 4128+
 4129+typedef struct sg_pipeline_info {
 4130+    sg_slot_info slot;              // resource pool slot info
 4131+} sg_pipeline_info;
 4132+
 4133+typedef struct sg_view_info {
 4134+    sg_slot_info slot;              // resource pool slot info
 4135+} sg_view_info;
 4136+
 4137+/*
 4138+    sg_stats
 4139+
 4140+    Allows to track generic and backend-specific rendering stats,
 4141+    obtained via sg_query_stats().
 4142+*/
 4143+typedef struct sg_frame_stats_gl {
 4144+    uint32_t num_bind_buffer;
 4145+    uint32_t num_active_texture;
 4146+    uint32_t num_bind_texture;
 4147+    uint32_t num_bind_sampler;
 4148+    uint32_t num_bind_image_texture;
 4149+    uint32_t num_use_program;
 4150+    uint32_t num_render_state;
 4151+    uint32_t num_vertex_attrib_pointer;
 4152+    uint32_t num_vertex_attrib_divisor;
 4153+    uint32_t num_enable_vertex_attrib_array;
 4154+    uint32_t num_disable_vertex_attrib_array;
 4155+    uint32_t num_uniform;
 4156+    uint32_t num_memory_barriers;
 4157+} sg_frame_stats_gl;
 4158+
 4159+typedef struct sg_frame_stats_d3d11_pass {
 4160+    uint32_t num_om_set_render_targets;
 4161+    uint32_t num_clear_render_target_view;
 4162+    uint32_t num_clear_depth_stencil_view;
 4163+    uint32_t num_resolve_subresource;
 4164+} sg_frame_stats_d3d11_pass;
 4165+
 4166+typedef struct sg_frame_stats_d3d11_pipeline {
 4167+    uint32_t num_rs_set_state;
 4168+    uint32_t num_om_set_depth_stencil_state;
 4169+    uint32_t num_om_set_blend_state;
 4170+    uint32_t num_ia_set_primitive_topology;
 4171+    uint32_t num_ia_set_input_layout;
 4172+    uint32_t num_vs_set_shader;
 4173+    uint32_t num_vs_set_constant_buffers;
 4174+    uint32_t num_ps_set_shader;
 4175+    uint32_t num_ps_set_constant_buffers;
 4176+    uint32_t num_cs_set_shader;
 4177+    uint32_t num_cs_set_constant_buffers;
 4178+} sg_frame_stats_d3d11_pipeline;
 4179+
 4180+typedef struct sg_frame_stats_d3d11_bindings {
 4181+    uint32_t num_ia_set_vertex_buffers;
 4182+    uint32_t num_ia_set_index_buffer;
 4183+    uint32_t num_vs_set_shader_resources;
 4184+    uint32_t num_vs_set_samplers;
 4185+    uint32_t num_ps_set_shader_resources;
 4186+    uint32_t num_ps_set_samplers;
 4187+    uint32_t num_cs_set_shader_resources;
 4188+    uint32_t num_cs_set_samplers;
 4189+    uint32_t num_cs_set_unordered_access_views;
 4190+} sg_frame_stats_d3d11_bindings;
 4191+
 4192+typedef struct sg_frame_stats_d3d11_uniforms {
 4193+    uint32_t num_update_subresource;
 4194+} sg_frame_stats_d3d11_uniforms;
 4195+
 4196+typedef struct sg_frame_stats_d3d11_draw {
 4197+    uint32_t num_draw_indexed_instanced;
 4198+    uint32_t num_draw_indexed;
 4199+    uint32_t num_draw_instanced;
 4200+    uint32_t num_draw;
 4201+} sg_frame_stats_d3d11_draw;
 4202+
 4203+typedef struct sg_frame_stats_d3d11 {
 4204+    sg_frame_stats_d3d11_pass pass;
 4205+    sg_frame_stats_d3d11_pipeline pipeline;
 4206+    sg_frame_stats_d3d11_bindings bindings;
 4207+    sg_frame_stats_d3d11_uniforms uniforms;
 4208+    sg_frame_stats_d3d11_draw draw;
 4209+    uint32_t num_map;
 4210+    uint32_t num_unmap;
 4211+} sg_frame_stats_d3d11;
 4212+
 4213+typedef struct sg_frame_stats_metal_idpool {
 4214+    uint32_t num_added;
 4215+    uint32_t num_released;
 4216+    uint32_t num_garbage_collected;
 4217+} sg_frame_stats_metal_idpool;
 4218+
 4219+typedef struct sg_frame_stats_metal_pipeline {
 4220+    uint32_t num_set_blend_color;
 4221+    uint32_t num_set_cull_mode;
 4222+    uint32_t num_set_front_facing_winding;
 4223+    uint32_t num_set_stencil_reference_value;
 4224+    uint32_t num_set_depth_bias;
 4225+    uint32_t num_set_render_pipeline_state;
 4226+    uint32_t num_set_depth_stencil_state;
 4227+} sg_frame_stats_metal_pipeline;
 4228+
 4229+typedef struct sg_frame_stats_metal_bindings {
 4230+    uint32_t num_set_vertex_buffer;
 4231+    uint32_t num_set_vertex_buffer_offset;
 4232+    uint32_t num_skip_redundant_vertex_buffer;
 4233+    uint32_t num_set_vertex_texture;
 4234+    uint32_t num_skip_redundant_vertex_texture;
 4235+    uint32_t num_set_vertex_sampler_state;
 4236+    uint32_t num_skip_redundant_vertex_sampler_state;
 4237+    uint32_t num_set_fragment_buffer;
 4238+    uint32_t num_set_fragment_buffer_offset;
 4239+    uint32_t num_skip_redundant_fragment_buffer;
 4240+    uint32_t num_set_fragment_texture;
 4241+    uint32_t num_skip_redundant_fragment_texture;
 4242+    uint32_t num_set_fragment_sampler_state;
 4243+    uint32_t num_skip_redundant_fragment_sampler_state;
 4244+    uint32_t num_set_compute_buffer;
 4245+    uint32_t num_set_compute_buffer_offset;
 4246+    uint32_t num_skip_redundant_compute_buffer;
 4247+    uint32_t num_set_compute_texture;
 4248+    uint32_t num_skip_redundant_compute_texture;
 4249+    uint32_t num_set_compute_sampler_state;
 4250+    uint32_t num_skip_redundant_compute_sampler_state;
 4251+} sg_frame_stats_metal_bindings;
 4252+
 4253+typedef struct sg_frame_stats_metal_uniforms {
 4254+    uint32_t num_set_vertex_buffer_offset;
 4255+    uint32_t num_set_fragment_buffer_offset;
 4256+    uint32_t num_set_compute_buffer_offset;
 4257+} sg_frame_stats_metal_uniforms;
 4258+
 4259+typedef struct sg_frame_stats_metal {
 4260+    sg_frame_stats_metal_idpool idpool;
 4261+    sg_frame_stats_metal_pipeline pipeline;
 4262+    sg_frame_stats_metal_bindings bindings;
 4263+    sg_frame_stats_metal_uniforms uniforms;
 4264+} sg_frame_stats_metal;
 4265+
 4266+typedef struct sg_frame_stats_wgpu_uniforms {
 4267+    uint32_t num_set_bindgroup;
 4268+    uint32_t size_write_buffer;
 4269+} sg_frame_stats_wgpu_uniforms;
 4270+
 4271+typedef struct sg_frame_stats_wgpu_bindings {
 4272+    uint32_t num_set_vertex_buffer;
 4273+    uint32_t num_skip_redundant_vertex_buffer;
 4274+    uint32_t num_set_index_buffer;
 4275+    uint32_t num_skip_redundant_index_buffer;
 4276+    uint32_t num_create_bindgroup;
 4277+    uint32_t num_discard_bindgroup;
 4278+    uint32_t num_set_bindgroup;
 4279+    uint32_t num_skip_redundant_bindgroup;
 4280+    uint32_t num_bindgroup_cache_hits;
 4281+    uint32_t num_bindgroup_cache_misses;
 4282+    uint32_t num_bindgroup_cache_collisions;
 4283+    uint32_t num_bindgroup_cache_invalidates;
 4284+    uint32_t num_bindgroup_cache_hash_vs_key_mismatch;
 4285+} sg_frame_stats_wgpu_bindings;
 4286+
 4287+typedef struct sg_frame_stats_wgpu {
 4288+    sg_frame_stats_wgpu_uniforms uniforms;
 4289+    sg_frame_stats_wgpu_bindings bindings;
 4290+} sg_frame_stats_wgpu;
 4291+
 4292+typedef struct sg_frame_stats_vk {
 4293+    uint32_t num_cmd_pipeline_barrier;
 4294+    uint32_t num_allocate_memory;
 4295+    uint32_t num_free_memory;
 4296+    uint32_t size_allocate_memory;
 4297+    uint32_t num_delete_queue_added;
 4298+    uint32_t num_delete_queue_collected;
 4299+    uint32_t num_cmd_copy_buffer;
 4300+    uint32_t num_cmd_copy_buffer_to_image;
 4301+    uint32_t num_cmd_set_descriptor_buffer_offsets;
 4302+    uint32_t size_descriptor_buffer_writes;
 4303+} sg_frame_stats_vk;
 4304+
 4305+typedef struct sg_frame_resource_stats {
 4306+    uint32_t allocated;     // number of allocated objects in current frame
 4307+    uint32_t deallocated;   // number of deallocated object in current frame
 4308+    uint32_t inited;        // number of initialized objects in current frame
 4309+    uint32_t uninited;      // number of deinitialized objects in current frame
 4310+} sg_frame_resource_stats;
 4311+
 4312+typedef struct sg_total_resource_stats {
 4313+    uint32_t alive;     // number of live objects in pool
 4314+    uint32_t free;      // number of free objects in pool
 4315+    uint32_t allocated;     // total number of object allocations
 4316+    uint32_t deallocated;   // total number of object deallocations
 4317+    uint32_t inited;        // total number of object initializations
 4318+    uint32_t uninited;      // total number of object deinitializations
 4319+} sg_total_resource_stats;
 4320+
 4321+typedef struct sg_total_stats {
 4322+    sg_total_resource_stats buffers;
 4323+    sg_total_resource_stats images;
 4324+    sg_total_resource_stats samplers;
 4325+    sg_total_resource_stats views;
 4326+    sg_total_resource_stats shaders;
 4327+    sg_total_resource_stats pipelines;
 4328+} sg_total_stats;
 4329+
 4330+typedef struct sg_frame_stats {
 4331+    uint32_t frame_index;   // current frame counter, starts at 0
 4332+
 4333+    uint32_t num_passes;
 4334+    uint32_t num_apply_viewport;
 4335+    uint32_t num_apply_scissor_rect;
 4336+    uint32_t num_apply_pipeline;
 4337+    uint32_t num_apply_bindings;
 4338+    uint32_t num_apply_uniforms;
 4339+    uint32_t num_draw;
 4340+    uint32_t num_draw_ex;
 4341+    uint32_t num_dispatch;
 4342+    uint32_t num_update_buffer;
 4343+    uint32_t num_append_buffer;
 4344+    uint32_t num_update_image;
 4345+
 4346+    uint32_t size_apply_uniforms;
 4347+    uint32_t size_update_buffer;
 4348+    uint32_t size_append_buffer;
 4349+    uint32_t size_update_image;
 4350+
 4351+    sg_frame_resource_stats buffers;
 4352+    sg_frame_resource_stats images;
 4353+    sg_frame_resource_stats samplers;
 4354+    sg_frame_resource_stats views;
 4355+    sg_frame_resource_stats shaders;
 4356+    sg_frame_resource_stats pipelines;
 4357+
 4358+    sg_frame_stats_gl gl;
 4359+    sg_frame_stats_d3d11 d3d11;
 4360+    sg_frame_stats_metal metal;
 4361+    sg_frame_stats_wgpu wgpu;
 4362+    sg_frame_stats_vk vk;
 4363+} sg_frame_stats;
 4364+
 4365+typedef struct sg_stats {
 4366+    sg_frame_stats prev_frame;
 4367+    sg_frame_stats cur_frame;
 4368+    sg_total_stats total;
 4369+} sg_stats;
 4370+
 4371+/*
 4372+    sg_log_item
 4373+
 4374+    An enum with a unique item for each log message, warning, error
 4375+    and validation layer message. Note that these messages are only
 4376+    visible when a logger function is installed in the sg_setup() call.
 4377+*/
 4378+#define _SG_LOG_ITEMS \
 4379+    _SG_LOGITEM_XMACRO(OK, "Ok") \
 4380+    _SG_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \
 4381+    _SG_LOGITEM_XMACRO(GL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (gl)") \
 4382+    _SG_LOGITEM_XMACRO(GL_3D_TEXTURES_NOT_SUPPORTED, "3d textures not supported (gl)") \
 4383+    _SG_LOGITEM_XMACRO(GL_ARRAY_TEXTURES_NOT_SUPPORTED, "array textures not supported (gl)") \
 4384+    _SG_LOGITEM_XMACRO(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE, "GLSL storage buffer bindslot is out of range (sg_limits.max_storage_buffer_bindings_per_stage) (gl)") \
 4385+    _SG_LOGITEM_XMACRO(GL_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE, "GLSL storage image bindslot is out of range (sg.limits.max_storage_image_bindings_per_stage) (gl)") \
 4386+    _SG_LOGITEM_XMACRO(GL_SHADER_COMPILATION_FAILED, "shader compilation failed (gl)") \
 4387+    _SG_LOGITEM_XMACRO(GL_SHADER_LINKING_FAILED, "shader linking failed (gl)") \
 4388+    _SG_LOGITEM_XMACRO(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, "vertex attribute not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \
 4389+    _SG_LOGITEM_XMACRO(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER, "uniform block name not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \
 4390+    _SG_LOGITEM_XMACRO(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, "image-sampler name not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \
 4391+    _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNDEFINED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNDEFINED (gl)") \
 4392+    _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (gl)") \
 4393+    _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (gl)") \
 4394+    _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNSUPPORTED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNSUPPORTED (gl)") \
 4395+    _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE (gl)") \
 4396+    _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNKNOWN, "framebuffer completeness check failed (unknown reason) (gl)") \
 4397+    _SG_LOGITEM_XMACRO(D3D11_FEATURE_LEVEL_0_DETECTED, "D3D11 Feature Level 0 device detected, this restricts the number of UAV slots to 8! (d3d11)") \
 4398+    _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_FAILED, "CreateBuffer() failed (d3d11)") \
 4399+    _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_SRV_FAILED, "CreateShaderResourceView() failed for storage buffer (d3d11)") \
 4400+    _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_UAV_FAILED, "CreateUnorderedAccessView() failed for storage buffer (d3d11)") \
 4401+    _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for depth-stencil texture (d3d11)") \
 4402+    _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_FAILED, "CreateTexture2D() failed for depth-stencil texture (d3d11)") \
 4403+    _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 2d-, cube- or array-texture (d3d11)") \
 4404+    _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_FAILED, "CreateTexture2D() failed for 2d-, cube- or array-texture (d3d11)") \
 4405+    _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_SRV_FAILED, "CreateShaderResourceView() failed for 2d-, cube- or array-texture (d3d11)") \
 4406+    _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 3D texture (d3d11)") \
 4407+    _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_FAILED, "CreateTexture3D() failed (d3d11)") \
 4408+    _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_SRV_FAILED, "CreateShaderResourceView() failed for 3d texture (d3d11)") \
 4409+    _SG_LOGITEM_XMACRO(D3D11_CREATE_MSAA_TEXTURE_FAILED, "CreateTexture2D() failed for MSAA render target texture (d3d11)") \
 4410+    _SG_LOGITEM_XMACRO(D3D11_CREATE_SAMPLER_STATE_FAILED, "CreateSamplerState() failed (d3d11)") \
 4411+    _SG_LOGITEM_XMACRO(D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE, "sg_shader_desc.uniform_blocks[].hlsl_register_b_n is out of range (must be 0..7)") \
 4412+    _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE, "sg_shader_desc.views[].storage_buffer.hlsl_register_t_n is out of range (must be 0..31)") \
 4413+    _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE, "sg_shader_desc.views[].storage_buffer.hlsl_register_u_n is out of range (must be 0..31)") \
 4414+    _SG_LOGITEM_XMACRO(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE, "sg_shader_desc.views[].texture.hlsl_register_t_n is out of range (must be 0..31)") \
 4415+    _SG_LOGITEM_XMACRO(D3D11_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE, "sg_shader_desc.views[].storage_image.hlsl_register_u_n is out of range (must be 0..31)") \
 4416+    _SG_LOGITEM_XMACRO(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE, "sampler 'hlsl_register_s_n' is out of rang (must be 0..11)") \
 4417+    _SG_LOGITEM_XMACRO(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED, "loading d3dcompiler_47.dll failed (d3d11)") \
 4418+    _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_FAILED, "shader compilation failed (d3d11)") \
 4419+    _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_OUTPUT, "") \
 4420+    _SG_LOGITEM_XMACRO(D3D11_CREATE_CONSTANT_BUFFER_FAILED, "CreateBuffer() failed for uniform constant buffer (d3d11)") \
 4421+    _SG_LOGITEM_XMACRO(D3D11_CREATE_INPUT_LAYOUT_FAILED, "CreateInputLayout() failed (d3d11)") \
 4422+    _SG_LOGITEM_XMACRO(D3D11_CREATE_RASTERIZER_STATE_FAILED, "CreateRasterizerState() failed (d3d11)") \
 4423+    _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED, "CreateDepthStencilState() failed (d3d11)") \
 4424+    _SG_LOGITEM_XMACRO(D3D11_CREATE_BLEND_STATE_FAILED, "CreateBlendState() failed (d3d11)") \
 4425+    _SG_LOGITEM_XMACRO(D3D11_CREATE_RTV_FAILED, "CreateRenderTargetView() failed (d3d11)") \
 4426+    _SG_LOGITEM_XMACRO(D3D11_CREATE_DSV_FAILED, "CreateDepthStencilView() failed (d3d11)") \
 4427+    _SG_LOGITEM_XMACRO(D3D11_CREATE_UAV_FAILED, "CreateUnorderedAccessView() failed (d3d11)") \
 4428+    _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED, "Map() failed when updating buffer (d3d11)") \
 4429+    _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_APPEND_BUFFER_FAILED, "Map() failed when appending to buffer (d3d11)") \
 4430+    _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED, "Map() failed when updating image (d3d11)") \
 4431+    _SG_LOGITEM_XMACRO(METAL_CREATE_BUFFER_FAILED, "failed to create buffer object (metal)") \
 4432+    _SG_LOGITEM_XMACRO(METAL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (metal)") \
 4433+    _SG_LOGITEM_XMACRO(METAL_CREATE_TEXTURE_FAILED, "failed to create texture object (metal)") \
 4434+    _SG_LOGITEM_XMACRO(METAL_CREATE_SAMPLER_FAILED, "failed to create sampler object (metal)") \
 4435+    _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_FAILED, "shader compilation failed (metal)") \
 4436+    _SG_LOGITEM_XMACRO(METAL_SHADER_CREATION_FAILED, "shader creation failed (metal)") \
 4437+    _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_OUTPUT, "") \
 4438+    _SG_LOGITEM_XMACRO(METAL_SHADER_ENTRY_NOT_FOUND, "shader entry function not found (metal)") \
 4439+    _SG_LOGITEM_XMACRO(METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE, "uniform block 'msl_buffer_n' is out of range (must be 0..7)") \
 4440+    _SG_LOGITEM_XMACRO(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE, "storage buffer 'msl_buffer_n' is out of range (must be 8..23)") \
 4441+    _SG_LOGITEM_XMACRO(METAL_STORAGEIMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "storage image 'msl_texture_n' is out of range (must be 0..31)") \
 4442+    _SG_LOGITEM_XMACRO(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "image 'msl_texture_n' is out of range (must be 0..31)") \
 4443+    _SG_LOGITEM_XMACRO(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE, "sampler 'msl_sampler_n' is out of range (must be 0..11)") \
 4444+    _SG_LOGITEM_XMACRO(METAL_CREATE_CPS_FAILED, "failed to create compute pipeline state (metal)") \
 4445+    _SG_LOGITEM_XMACRO(METAL_CREATE_CPS_OUTPUT, "") \
 4446+    _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_FAILED, "failed to create render pipeline state (metal)") \
 4447+    _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_OUTPUT, "") \
 4448+    _SG_LOGITEM_XMACRO(METAL_CREATE_DSS_FAILED, "failed to create depth stencil state (metal)") \
 4449+    _SG_LOGITEM_XMACRO(WGPU_BINDGROUPS_POOL_EXHAUSTED, "bindgroups pool exhausted (increase sg_desc.bindgroups_cache_size) (wgpu)") \
 4450+    _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE, "sg_desc.wgpu.bindgroups_cache_size must be > 1 (wgpu)") \
 4451+    _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_POW2, "sg_desc.wgpu.bindgroups_cache_size must be a power of 2 (wgpu)") \
 4452+    _SG_LOGITEM_XMACRO(WGPU_CREATEBINDGROUP_FAILED, "wgpuDeviceCreateBindGroup failed") \
 4453+    _SG_LOGITEM_XMACRO(WGPU_CREATE_BUFFER_FAILED, "wgpuDeviceCreateBuffer() failed") \
 4454+    _SG_LOGITEM_XMACRO(WGPU_CREATE_TEXTURE_FAILED, "wgpuDeviceCreateTexture() failed") \
 4455+    _SG_LOGITEM_XMACRO(WGPU_CREATE_TEXTURE_VIEW_FAILED, "wgpuTextureCreateView() failed") \
 4456+    _SG_LOGITEM_XMACRO(WGPU_CREATE_SAMPLER_FAILED, "wgpuDeviceCreateSampler() failed") \
 4457+    _SG_LOGITEM_XMACRO(WGPU_CREATE_SHADER_MODULE_FAILED, "wgpuDeviceCreateShaderModule() failed") \
 4458+    _SG_LOGITEM_XMACRO(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED, "wgpuDeviceCreateBindGroupLayout() for shader stage failed") \
 4459+    _SG_LOGITEM_XMACRO(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE, "uniform block 'wgsl_group0_binding_n' is out of range (must be 0..15)") \
 4460+    _SG_LOGITEM_XMACRO(WGPU_TEXTURE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "texture 'wgsl_group1_binding_n' is out of range (must be 0..127)") \
 4461+    _SG_LOGITEM_XMACRO(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage buffer 'wgsl_group1_binding_n' is out of range (must be 0..127)") \
 4462+    _SG_LOGITEM_XMACRO(WGPU_STORAGEIMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage image 'wgsl_group1_binding_n' is out of range (must be 0..127)") \
 4463+    _SG_LOGITEM_XMACRO(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "sampler 'wgsl_group1_binding_n' is out of range (must be 0..127)") \
 4464+    _SG_LOGITEM_XMACRO(WGPU_CREATE_PIPELINE_LAYOUT_FAILED, "wgpuDeviceCreatePipelineLayout() failed") \
 4465+    _SG_LOGITEM_XMACRO(WGPU_CREATE_RENDER_PIPELINE_FAILED, "wgpuDeviceCreateRenderPipeline() failed") \
 4466+    _SG_LOGITEM_XMACRO(WGPU_CREATE_COMPUTE_PIPELINE_FAILED, "wgpuDeviceCreateComputePipeline() failed") \
 4467+    _SG_LOGITEM_XMACRO(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING, "vulkan: could not look up a required extension function pointer") \
 4468+    _SG_LOGITEM_XMACRO(VULKAN_ALLOC_DEVICE_MEMORY_NO_SUITABLE_MEMORY_TYPE, "vulkan: could not find suitable memory type") \
 4469+    _SG_LOGITEM_XMACRO(VULKAN_ALLOCATE_MEMORY_FAILED, "vulkan: vkAllocateMemory() failed!") \
 4470+    _SG_LOGITEM_XMACRO(VULKAN_ALLOC_BUFFER_DEVICE_MEMORY_FAILED, "vulkan: allocating buffer device memory failed") \
 4471+    _SG_LOGITEM_XMACRO(VULKAN_ALLOC_IMAGE_DEVICE_MEMORY_FAILED, "vulkan: allocating image device memory failed") \
 4472+    _SG_LOGITEM_XMACRO(VULKAN_DELETE_QUEUE_EXHAUSTED, "vulkan: internal delete queue exhausted (too many objects destroyed per frame)") \
 4473+    _SG_LOGITEM_XMACRO(VULKAN_STAGING_CREATE_BUFFER_FAILED, "vulkan: vkCreateBuffer() failed for staging buffer") \
 4474+    _SG_LOGITEM_XMACRO(VULKAN_STAGING_ALLOCATE_MEMORY_FAILED, "vulkan: allocating device memory for staging buffer failed") \
 4475+    _SG_LOGITEM_XMACRO(VULKAN_STAGING_BIND_BUFFER_MEMORY_FAILED, "vulkan: vkBindBufferMemory() failed for staging buffer") \
 4476+    _SG_LOGITEM_XMACRO(VULKAN_STAGING_STREAM_BUFFER_OVERFLOW, "vulkan: per-frame stream staging buffer has overflown (sg_desc.vulkan.stream_staging_buffer_size)") \
 4477+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_SHARED_BUFFER_FAILED, "vulkan: vkCreateBuffer() failed for cpu/gpu-shared buffer") \
 4478+    _SG_LOGITEM_XMACRO(VULKAN_ALLOCATE_SHARED_BUFFER_MEMORY_FAILED, "vulkan: allocating device memory for cpu/gpu-shared buffer failed") \
 4479+    _SG_LOGITEM_XMACRO(VULKAN_BIND_SHARED_BUFFER_MEMORY_FAILED, "vulkan: vkBindBufferMemory() failed for cpu/gpu-shared buffer") \
 4480+    _SG_LOGITEM_XMACRO(VULKAN_MAP_SHARED_BUFFER_MEMORY_FAILED, "vulkan: vkMapMemory() failed on cpu/gpu-shared buffer") \
 4481+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_BUFFER_FAILED, "vulkan: vkCreateBuffer() failed!") \
 4482+    _SG_LOGITEM_XMACRO(VULKAN_BIND_BUFFER_MEMORY_FAILED, "vulkan: vkBindBufferMemory() failed!") \
 4483+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_IMAGE_FAILED, "vulkan: vkCreateImage() failed!") \
 4484+    _SG_LOGITEM_XMACRO(VULKAN_BIND_IMAGE_MEMORY_FAILED, "vulkan: vkBindImageMemory() failed!") \
 4485+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_SHADER_MODULE_FAILED, "vukan: vkCreateShaderModule() failed!") \
 4486+    _SG_LOGITEM_XMACRO(VULKAN_UNIFORMBLOCK_SPIRV_SET0_BINDING_OUT_OF_RANGE, "vulkan: uniform block 'spirv_set0_binding_n' is out of range (must be 0..15)") \
 4487+    _SG_LOGITEM_XMACRO(VULKAN_TEXTURE_SPIRV_SET1_BINDING_OUT_OF_RANGE, "vulkan: texture 'spirv_set1_binding_n' is out of range (must be 0..127)") \
 4488+    _SG_LOGITEM_XMACRO(VULKAN_STORAGEBUFFER_SPIRV_SET1_BINDING_OUT_OF_RANGE, "vulkan: storage buffer 'spirv_set1_binding_n' is out of range (must be 0..127)") \
 4489+    _SG_LOGITEM_XMACRO(VULKAN_STORAGEIMAGE_SPIRV_SET1_BINDING_OUT_OF_RANGE, "vulkan: storage image 'spirv_set1_binding_n' is out of range (must be 0..127)") \
 4490+    _SG_LOGITEM_XMACRO(VULKAN_SAMPLER_SPIRV_SET1_BINDING_OUT_OF_RANGE, "vulkan: sampler 'spirv_set1_binding_n' is out of range (must be 0..127)") \
 4491+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_DESCRIPTOR_SET_LAYOUT_FAILED, "vulkan: vkCreateDescriptorSetLayout() failed!") \
 4492+    _SG_LOGITEM_XMACRO(VULKAN_SHADER_UNIFORM_DESCRIPTOR_SET_SIZE_VS_CACHE_SIZE, "vulkan: shader uniform descriptor set is too big for the descriptor set cache (please write a Github issue)") \
 4493+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_PIPELINE_LAYOUT_FAILED, "vulkan: vkCreatePipelineLayout() failed!") \
 4494+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_GRAPHICS_PIPELINE_FAILED, "vulkan: vkCreateGraphicsPipelines() failed!") \
 4495+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_COMPUTE_PIPELINE_FAILED, "vulkan: vkCreateComputePipelines() failed!") \
 4496+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_IMAGE_VIEW_FAILED, "vulkan: vkCreateImageView() failed!") \
 4497+    _SG_LOGITEM_XMACRO(VULKAN_VIEW_MAX_DESCRIPTOR_SIZE, "vulkan: required view descriptor size is greater than _SG_VK_MAX_DESCRIPTOR_DATA_SIZE") \
 4498+    _SG_LOGITEM_XMACRO(VULKAN_CREATE_SAMPLER_FAILED, "vulkan: vkCreateSampler() failed!") \
 4499+    _SG_LOGITEM_XMACRO(VULKAN_SAMPLER_MAX_DESCRIPTOR_SIZE, "vulkan: required sampler descriptor size is greater than _SG_VK_MAX_DESCRIPTOR_DATA_SIZE") \
 4500+    _SG_LOGITEM_XMACRO(VULKAN_WAIT_FOR_FENCE_FAILED, "vulkan: vkWaitForFence() failed!") \
 4501+    _SG_LOGITEM_XMACRO(VULKAN_UNIFORM_BUFFER_OVERFLOW, "vulkan: uniform buffer has overflown (increase sg_desc.uniform_buffer_size)") \
 4502+    _SG_LOGITEM_XMACRO(VULKAN_DESCRIPTOR_BUFFER_OVERFLOW, "vulkan: desccriptor buffer has overflown (increase sg_desc.vulkan.descriptor_buffer_size)") \
 4503+    _SG_LOGITEM_XMACRO(IDENTICAL_COMMIT_LISTENER, "attempting to add identical commit listener") \
 4504+    _SG_LOGITEM_XMACRO(COMMIT_LISTENER_ARRAY_FULL, "commit listener array full") \
 4505+    _SG_LOGITEM_XMACRO(TRACE_HOOKS_NOT_ENABLED, "sg_install_trace_hooks() called, but SOKOL_TRACE_HOOKS is not defined") \
 4506+    _SG_LOGITEM_XMACRO(DEALLOC_BUFFER_INVALID_STATE, "sg_dealloc_buffer(): buffer must be in ALLOC state") \
 4507+    _SG_LOGITEM_XMACRO(DEALLOC_IMAGE_INVALID_STATE, "sg_dealloc_image(): image must be in alloc state") \
 4508+    _SG_LOGITEM_XMACRO(DEALLOC_SAMPLER_INVALID_STATE, "sg_dealloc_sampler(): sampler must be in alloc state") \
 4509+    _SG_LOGITEM_XMACRO(DEALLOC_SHADER_INVALID_STATE, "sg_dealloc_shader(): shader must be in ALLOC state") \
 4510+    _SG_LOGITEM_XMACRO(DEALLOC_PIPELINE_INVALID_STATE, "sg_dealloc_pipeline(): pipeline must be in ALLOC state") \
 4511+    _SG_LOGITEM_XMACRO(DEALLOC_VIEW_INVALID_STATE, "sg_dealloc_view(): view must be in ALLOC state") \
 4512+    _SG_LOGITEM_XMACRO(INIT_BUFFER_INVALID_STATE, "sg_init_buffer(): buffer must be in ALLOC state") \
 4513+    _SG_LOGITEM_XMACRO(INIT_IMAGE_INVALID_STATE, "sg_init_image(): image must be in ALLOC state") \
 4514+    _SG_LOGITEM_XMACRO(INIT_SAMPLER_INVALID_STATE, "sg_init_sampler(): sampler must be in ALLOC state") \
 4515+    _SG_LOGITEM_XMACRO(INIT_SHADER_INVALID_STATE, "sg_init_shader(): shader must be in ALLOC state") \
 4516+    _SG_LOGITEM_XMACRO(INIT_PIPELINE_INVALID_STATE, "sg_init_pipeline(): pipeline must be in ALLOC state") \
 4517+    _SG_LOGITEM_XMACRO(INIT_VIEW_INVALID_STATE, "sg_init_view(): view must be in ALLOC state") \
 4518+    _SG_LOGITEM_XMACRO(UNINIT_BUFFER_INVALID_STATE, "sg_uninit_buffer(): buffer must be in VALID, FAILED or ALLOC state") \
 4519+    _SG_LOGITEM_XMACRO(UNINIT_IMAGE_INVALID_STATE, "sg_uninit_image(): image must be in VALID, FAILED or ALLOC state") \
 4520+    _SG_LOGITEM_XMACRO(UNINIT_SAMPLER_INVALID_STATE, "sg_uninit_sampler(): sampler must be in VALID, FAILED or ALLOC state") \
 4521+    _SG_LOGITEM_XMACRO(UNINIT_SHADER_INVALID_STATE, "sg_uninit_shader(): shader must be in VALID, FAILED or ALLOC state") \
 4522+    _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_INVALID_STATE, "sg_uninit_pipeline(): pipeline must be in VALID, FAILED or ALLOC state") \
 4523+    _SG_LOGITEM_XMACRO(UNINIT_VIEW_INVALID_STATE, "sg_uninit_view(): view must be in VALID, FAILED or ALLOC state") \
 4524+    _SG_LOGITEM_XMACRO(FAIL_BUFFER_INVALID_STATE, "sg_fail_buffer(): buffer must be in ALLOC state") \
 4525+    _SG_LOGITEM_XMACRO(FAIL_IMAGE_INVALID_STATE, "sg_fail_image(): image must be in ALLOC state") \
 4526+    _SG_LOGITEM_XMACRO(FAIL_SAMPLER_INVALID_STATE, "sg_fail_sampler(): sampler must be in ALLOC state") \
 4527+    _SG_LOGITEM_XMACRO(FAIL_SHADER_INVALID_STATE, "sg_fail_shader(): shader must be in ALLOC state") \
 4528+    _SG_LOGITEM_XMACRO(FAIL_PIPELINE_INVALID_STATE, "sg_fail_pipeline(): pipeline must be in ALLOC state") \
 4529+    _SG_LOGITEM_XMACRO(FAIL_VIEW_INVALID_STATE, "sg_fail_view(): view must be in ALLOC state") \
 4530+    _SG_LOGITEM_XMACRO(BUFFER_POOL_EXHAUSTED, "buffer pool exhausted") \
 4531+    _SG_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \
 4532+    _SG_LOGITEM_XMACRO(SAMPLER_POOL_EXHAUSTED, "sampler pool exhausted") \
 4533+    _SG_LOGITEM_XMACRO(SHADER_POOL_EXHAUSTED, "shader pool exhausted") \
 4534+    _SG_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted") \
 4535+    _SG_LOGITEM_XMACRO(VIEW_POOL_EXHAUSTED, "view pool exhausted") \
 4536+    _SG_LOGITEM_XMACRO(BEGINPASS_TOO_MANY_COLOR_ATTACHMENTS, "sg_begin_pass: too many color attachments (sg_limits.max_color_attachments)") \
 4537+    _SG_LOGITEM_XMACRO(BEGINPASS_TOO_MANY_RESOLVE_ATTACHMENTS, "sg_begin_pass: too many resolve attachments (sg_limits.max_color_attachments)") \
 4538+    _SG_LOGITEM_XMACRO(BEGINPASS_ATTACHMENTS_ALIVE, "sg_begin_pass: an attachment was provided that no longer exists") \
 4539+    _SG_LOGITEM_XMACRO(DRAW_WITHOUT_BINDINGS, "attempting to draw without resource bindings") \
 4540+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURES, "sg_shader_desc: too many texture bindings on vertex shader stage (sg_limits.max_texture_bindings_per_stage)") \
 4541+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURES, "sg_shader_desc: too many texture bindings on fragment shader stage (sg_limits.max_texture_bindings_per_stage)") \
 4542+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURES, "sg_shader_desc: too many texture bindings on compute shader stage (sg_limits.max_texture_bindings_per_stage)") \
 4543+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEBUFFERS, "sg_shader_desc: too many storage buffer bindings on vertex shader stage (sg_limits.max_storage_buffer_bindings_per_stage)") \
 4544+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEBUFFERS, "sg_shader_desc: too many storage buffer bindings on fragment shader stage (sg_limits.max_storage_buffer_bindings_per_stage)") \
 4545+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEBUFFERS, "sg_shader_desc: too many storage buffer bindings on compute shader stage (sg_limits.max_storage_buffer_bindings_per_stage)") \
 4546+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEIMAGES, "sg_shader_desc: too many storage image bindings on vertex shader stage (sg_limits.max_storage_image_bindings_per_stage)") \
 4547+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEIMAGES, "sg_shader_desc: too many storage image bindings on fragment shader stage (sg_limits.max_storage_image_bindings_per_stage)") \
 4548+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEIMAGES, "sg_shader_desc: too many storage image bindings on compute shader stage (sg_limits.max_storage_image_bindings_per_stage)") \
 4549+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURESAMPLERPAIRS, "sg_shader_desc: too many texture-sampler-pairs on vertex shader stage (sg_limits.max_texture_bindings_per_stage)") \
 4550+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURESAMPLERPAIRS, "sg_shader_desc: too many texture-sampler-pairs on fragment shader stage (sg_limits.max_texture_bindings_per_stage)") \
 4551+    _SG_LOGITEM_XMACRO(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURESAMPLERPAIRS, "sg_shader_desc: too many texture-sampler-pairs on compute shader stage (sg_limits.max_texture_bindings_per_stage)") \
 4552+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_CANARY, "sg_buffer_desc not initialized") \
 4553+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_IMMUTABLE_DYNAMIC_STREAM, "sg_buffer_desc.usage: only one of .immutable, .dynamic_update, .stream_update can be true") \
 4554+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_SEPARATE_BUFFER_TYPES, "sg_buffer_desc.usage: on WebGL2, only one of .vertex_buffer or .index_buffer can be true (check sg_features.separate_buffer_types)") \
 4555+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE, "sg_buffer_desc.size must be greater zero") \
 4556+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE, "sg_buffer_desc.size and .data.size must be equal") \
 4557+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE, "sg_buffer_desc.data.size expected to be zero") \
 4558+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_NO_DATA, "sg_buffer_desc.data.ptr must be null for dynamic/stream buffers") \
 4559+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_DATA, "sg_buffer_desc: initial content data must be provided for immutable buffers without storage buffer usage") \
 4560+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED, "storage buffers not supported by the backend 3D API (requires OpenGL >= 4.3)") \
 4561+    _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4, "size of storage buffers must be a multiple of 4") \
 4562+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_NODATA, "sg_image_data: no data (.ptr and/or .size is zero)") \
 4563+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_DATA_SIZE, "sg_image_data: data size doesn't match expected surface size") \
 4564+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_CANARY, "sg_image_desc not initialized") \
 4565+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMMUTABLE_DYNAMIC_STREAM, "sg_image_desc.usage: only one of .immutable, .dynamic_update, .stream_update can be true") \
 4566+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_COLOR_DEPTH_STENCIL, "sg_image_desc.usage: only one of .color_attachment and .depth_stencil_attachment can be true") \
 4567+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_2D_NUMSLICES, "sg_image_desc.num_slices must be exactly 1 for SG_IMAGETYPE_2D") \
 4568+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_CUBE_NUMSLICES, "sg_image_desc.num_slices must be exactly 6 for SG_IMAGETYPE_CUBE") \
 4569+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_ARRAY_NUMSLICES, "sg_image_desc.num_slices must be ((>= 1) && (<= sg_limits.max_image_array_layers)) for SG_IMAGETYPE_ARRAY") \
 4570+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_IMAGETYPE_3D_NUMSLICES, "sg_image_desc.num_slices must be ((>= 1) && (<= sg_limits.max_image_size_3d)) for SG_IMAGETYPE_ARRAY") \
 4571+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NUMSLICES, "sg_image_desc.num_slices must be > 0") \
 4572+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_WIDTH, "sg_image_desc.width must be > 0") \
 4573+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_HEIGHT, "sg_image_desc.height must be > 0") \
 4574+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, "invalid pixel format for non-render-target image") \
 4575+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_BUT_NO_ATTACHMENT, "non-attachment images cannot be multisampled") \
 4576+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE, "3D images cannot have a depth/stencil image format") \
 4577+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_IMMUTABLE, "attachment and storage images must be sg_image_usage.immutable") \
 4578+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_NO_DATA, "render/storage attachment images cannot be initialized with data") \
 4579+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_PIXELFORMAT, "invalid pixel format for render attachment image") \
 4580+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_RESOLVE_EXPECT_NO_MSAA, "resolve attachment images cannot be multisampled") \
 4581+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_NO_MSAA_SUPPORT, "multisampling not supported for this pixel format") \
 4582+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_NUM_MIPMAPS, "multisample images must have num_mipmaps == 1") \
 4583+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_3D_IMAGE, "3D images cannot have a sample_count > 1") \
 4584+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_CUBE_IMAGE, "cube images cannot have sample_count > 1") \
 4585+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_ARRAY_IMAGE, "array images cannot have sample_count > 1") \
 4586+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_STORAGEIMAGE_PIXELFORMAT, "invalid pixel format for storage image") \
 4587+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_STORAGEIMAGE_EXPECT_NO_MSAA, "storage images cannot be multisampled") \
 4588+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_INJECTED_NO_DATA, "images with injected textures cannot be initialized with data") \
 4589+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA, "dynamic/stream-update images cannot be initialized with data") \
 4590+    _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE, "compressed images must be immutable") \
 4591+    _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_CANARY, "sg_sampler_desc not initialized") \
 4592+    _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING, "sg_sampler_desc.max_anisotropy > 1 requires min/mag/mipmap_filter to be SG_FILTER_LINEAR") \
 4593+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_CANARY, "sg_shader_desc not initialized") \
 4594+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VERTEX_SOURCE, "vertex shader source code expected") \
 4595+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_FRAGMENT_SOURCE, "fragment shader source code expected") \
 4596+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPUTE_SOURCE, "compute shader source code expected") \
 4597+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE, "vertex shader source or byte code expected") \
 4598+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE, "fragment shader source or byte code expected") \
 4599+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE, "compute shader source or byte code expected") \
 4600+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_INVALID_SHADER_COMBO, "cannot combine compute shaders with vertex or fragment shaders") \
 4601+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, "shader byte code length (in bytes) required") \
 4602+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_INITIALIZED, "sg_shader_desc.mtl_threads_per_threadgroup must be initialized for compute shaders (metal)") \
 4603+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_MULTIPLE_32, "sg_shader_desc.mtl_threads_per_threadgroup (x * y * z) must be a multiple of 32 (metal)") \
 4604+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS, "sg_shader_desc.uniform_blocks[].glsl_uniforms[]: items must occupy continuous slots") \
 4605+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO, "sg_shader_desc.uniform_blocks[].size cannot be zero") \
 4606+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION, "sg_shader_desc.uniform_blocks[].msl_buffer_n must be unique across uniform blocks and storage buffers in same shader stage") \
 4607+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION, "sg_shader_desc.uniform_blocks[].hlsl_register_b_n must be unique across uniform blocks in same shader stage") \
 4608+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION, "sg_shader_desc.uniform_blocks[].wgsl_group0_binding_n must be unique across all uniform blocks") \
 4609+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SPIRV_SET0_BINDING_COLLISION, "sg_shader_desc.unifrom_blocks[].spirv_set0_binding_n must be unique across all uniform blocks") \
 4610+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS, "sg_shader_desc.uniform_blocks[].glsl_uniforms[]: GL backend requires uniform block member declarations") \
 4611+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME, "sg_shader_desc.uniform_blocks[].glsl_uniforms[].glsl_name missing") \
 4612+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH, "sg_shader_desc.uniform_blocks[].glsl_uniforms[]: size of uniform block members doesn't match uniform block size") \
 4613+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT, "sg_shader_desc.uniform_blocks[].glsl_uniforms[].array_count must be >= 1") \
 4614+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE, "sg_shader_desc.uniform_blocks[].glsl_uniforms[].type: uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \
 4615+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION, "sg_shader_desc.views[].storage_buffer.storagemsl_buffer_n must be unique across uniform blocks and storage buffer in same shader stage") \
 4616+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION, "sg_shader_desc.views[].storage_buffer.hlsl_register_t_n must be unique across read-only storage buffers and images in same shader stage") \
 4617+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION, "sg_shader_desc.views[].storage_buffer.hlsl_register_u_n must be unique across read/write storage buffers and storage images in same shader stage") \
 4618+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_GLSL_BINDING_COLLISION, "sg_shader_desc.views[].storage_buffer.glsl_binding_n must be unique across shader stages") \
 4619+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.views[].storage_buffer.wgsl_group1_binding_n must be unique across all view and sampler bindings") \
 4620+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_SPIRV_SET1_BINDING_COLLISION, "sg_shader_desc.views[].storage_buffer.spirv_set1_binding_n must be unique across all view and sampler bindings") \
 4621+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_EXPECT_COMPUTE_STAGE, "sg_shader_desc.views[].storage_image: storage images are allowed on the compute stage") \
 4622+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_METAL_TEXTURE_SLOT_COLLISION, "sg_shader_desc.views[].storage_image.msl_texture_n must be unique across images and storage images in same shader stage") \
 4623+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_HLSL_REGISTER_U_COLLISION, "sg_shader_desc.views[].storage_image.hlsl_register_u_n must be unique across storage images and read/write storage buffers in same shader stage") \
 4624+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_GLSL_BINDING_COLLISION, "sg_shader_desc.views[].storage_image.glsl_binding_n must be unique across shader stages") \
 4625+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.views[].storage_image.wgsl_group1_binding_n must be unique across all view and sampler bindings") \
 4626+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_SPIRV_SET1_BINDING_COLLISION, "sg_shader_desc.views[].storage_image.spirv_set1_binding_n must be unique across all view and sampler bindings") \
 4627+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_METAL_TEXTURE_SLOT_COLLISION, "sg_shader_desc.views[].texture.msl_texture_n must be unique across textures and storage images in same shader stage") \
 4628+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_HLSL_REGISTER_T_COLLISION, "sg_shader_desc.views[].texture.hlsl_register_t_n must be unique across textures and storage buffers in same shader stage") \
 4629+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.views[].texture.wgsl_group1_binding_n must be unique across all view and sampler bindings") \
 4630+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VIEW_TEXTURE_SPIRV_SET1_BINDING_COLLISION, "sg_shader_desc.views[].texture.spirv_set1_binding_n must be unique across all view and sampler bindings") \
 4631+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION, "sg_shader_desc.samplers[].msl_sampler_n must be unique in same shader stage") \
 4632+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION, "sg_shader_desc.samplers[].hlsl_register_s_n must be unique in same shader stage") \
 4633+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION, "sg_shader_desc.samplers[].wgsl_group1_binding_n must be unique across all view and sampler bindings") \
 4634+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_SPIRV_SET1_BINDING_COLLISION, "sg_shader_desc.samplers[].spirv_set1_binding_n must be unique across all view and sampler bindings") \
 4635+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_VIEW_SLOT_OUT_OF_RANGE, "texture-sampler-pair view slot index is out of range (sg_shader_desc.texture_sampler_pairs[].view_slot)") \
 4636+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE, "texture-sampler-pair sampler slot index is out of range (sg_shader_desc.texture_sampler_pairs[].sampler_slot)") \
 4637+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_TEXTURE_STAGE_MISMATCH, "texture-sampler-pair stage doesn't match referenced texture stage") \
 4638+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_EXPECT_TEXTURE_VIEW, "texture-sampler-pair view must be a texture view (sg_shader_desc.texture_sampler_pairs[].view_slot => sg_shaders_desc.views[i].texture)") \
 4639+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH, "texture-sampler-pair stage doesn't match referenced sampler stage") \
 4640+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_GLSL_NAME, "texture-sampler-pair 'glsl_name' missing") \
 4641+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED, "image sample type UNFILTERABLE_FLOAT, UINT, SINT can only be used with NONFILTERING sampler") \
 4642+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED, "image sample type DEPTH can only be used with COMPARISON sampler") \
 4643+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_TEXVIEW_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS, "one or more texture views are not referenced by by texture-sampler-pairs (sg_shader_desc.texture_sampler_pairs[].view_slot)") \
 4644+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS, "one or more samplers are not referenced by texture-sampler-pairs (sg_shader_desc.texture_sampler_pairs[].sampler_slot)") \
 4645+    _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, "vertex attribute name/semantic string too long (max len 16)") \
 4646+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_CANARY, "sg_pipeline_desc not initialized") \
 4647+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER, "sg_pipeline_desc.shader missing or invalid") \
 4648+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED, "sg_pipeline_desc.shader must be a compute shader") \
 4649+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED, "sg_pipeline_desc.compute is false, but shader is a compute shader") \
 4650+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_CONT_ATTRS, "sg_pipeline_desc.layout.attrs is not continuous") \
 4651+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "sg_pipeline_desc.layout.attrs[].format is incompatible with sg_shader_desc.attrs[].base_type") \
 4652+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_VERTEXFORMAT_INT10_N2_NOT_SUPPORTED, "sg_pipeline_desc.layout.attrs[].format: SG_VERTEXFORMAT_INT10_N2 not supported on this platform") \
 4653+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4") \
 4654+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, "D3D11 missing vertex attribute semantics in shader") \
 4655+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS, "sg_pipeline_desc.shader: only readonly storage buffer bindings allowed in render pipelines") \
 4656+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE, "SG_BLENDOP_MIN/MAX requires all blend factors to be SG_BLENDFACTOR_ONE") \
 4657+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_DUAL_SOURCE_BLENDING_NOT_SUPPORTED, "dual source blending not supported (sg_features.dual_source_blending)") \
 4658+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_DEPTH_FORMAT_NONE_BUT_DEPTH_WRITE_ENABLED, "sg_pipeline_desc.depth.write_enabled cannot be true when sg_pipeline_desc.depth.pixel_format is SG_PIXELFORMAT_NONE") \
 4659+    _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_DEPTH_FORMAT_NONE_COMPARE_FUNC_MISMATCH, "sg_pipeline_desc.depth.compare must be SG_COMPAREFUNC_ALWAYS or SG_COMPAREFUNC_NEVER when sg_pipeline_desc.pixel_format is SG_PIXELFORMAT_NONE") \
 4660+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_CANARY, "sg_view_desc not initialized") \
 4661+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE, "sg_view_desc: only one view type can be active") \
 4662+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_ANY_VIEWTYPE, "sg_view_desc: exactly one view type must be active") \
 4663+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOURCE_ALIVE, "sg_view_desc: resource object is no longer alive (.buffer or .image)") \
 4664+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOURCE_FAILED, "sg_view_desc: resource object cannot be in FAILED state (.buffer or .image)") \
 4665+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_VS_BUFFER_SIZE, "sg_view_desc.storage_buffer.offset is >= buffer size") \
 4666+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_MULTIPLE_256, "sg_view_desc.storage_buffer.offset must be a multiple of 256") \
 4667+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEBUFFER_USAGE, "sg_view_desc.storage_buffer.buffer must have been created with sg_buffer_desc.usage.storage_buffer = true") \
 4668+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEIMAGE_USAGE, "sg_view_desc.storage_image.image must have been created with sg_image_desc.usage.storage_image = true") \
 4669+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_COLORATTACHMENT_USAGE, "sg_view_desc.color_attachment.image must have been created with sg_image_desc.usage.color_attachment = true") \
 4670+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOLVEATTACHMENT_USAGE, "sg_view_desc.resolve_attachment.image must have been created with sg_image_desc.usage.resolve_attachment = true") \
 4671+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_USAGE, "sg_view_desc.depth_stencil_attachment.image must have been created with sg_image_desc.usage.depth_stencil_attachment = true") \
 4672+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_MIPLEVEL, "sg_view_desc: image/attachment view mip level is out of range (must be >=0 and <image.num_miplevels)") \
 4673+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_2D_SLICE, "sg_view_desc: image/attachment view slice is out of range for 2D image (must be 0)") \
 4674+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_CUBEMAP_SLICE, "sg_view_desc: image/attachment view slice is out of range for cubemap image (must be >=0 and <6)") \
 4675+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_ARRAY_SLICE, "sg_view_desc: image/attachment view slice is out of range for 2D array image (must be >=0 and <image.num_slices") \
 4676+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_IMAGE_3D_SLICE, "sg_view_desc: image/attachment view slice is out of range for 3D image (must be 0)") \
 4677+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_TEXTURE_EXPECT_NO_MSAA, "sg_view_desc: MSAA texture bindings not allowed on this backend (sg_features.msaa_texture_bindings)") \
 4678+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_TEXTURE_MIPLEVELS, "sg_view_desc: texture view mip levels are out of range (must be >=0 and <image.num_miplevels)") \
 4679+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_TEXTURE_2D_SLICES, "sg_view_desc: texture view slices are out of range for 2D image (must be 0)") \
 4680+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_TEXTURE_CUBEMAP_SLICES, "sg_view_desc: texture view slices are out of range for cubemap image (must be 0)") \
 4681+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_TEXTURE_ARRAY_SLICES, "sg_view_desc: texture view slices are out of range for 2D array image (must be >=0 and <image.num_slices") \
 4682+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_TEXTURE_3D_SLICES, "sg_view_desc: texture view slices are out of range for 3D image (must be 0)") \
 4683+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_STORAGEIMAGE_PIXELFORMAT, "sg_view_desc.storage_image_binding: image pixel format must be GPU readable or writable (sg_pixelformat_info.read/write)") \
 4684+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_COLORATTACHMENT_PIXELFORMAT, "sg_view_desc.color_attachment: pixel format of image must be renderable (sg_pixelformat_info.render)") \
 4685+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_PIXELFORMAT, "sg_view_desc.depth_stencil_attachment: pixel format of image must be a depth or depth-stencil format (sg_pixelformat_info.depth)") \
 4686+    _SG_LOGITEM_XMACRO(VALIDATE_VIEWDESC_RESOLVEATTACHMENT_SAMPLECOUNT, "sg_view_desc.resolve_attachment: image cannot be multisampled") \
 4687+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_CANARY, "sg_begin_pass: pass struct not initialized") \
 4688+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COMPUTEPASS_EXPECT_NO_ATTACHMENTS, "sg_begin_pass: compute passes cannot have attachments") \
 4689+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH, "sg_begin_pass: expected pass.swapchain.width > 0") \
 4690+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET, "sg_begin_pass: expected pass.swapchain.width == 0") \
 4691+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT, "sg_begin_pass: expected pass.swapchain.height > 0") \
 4692+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET, "sg_begin_pass: expected pass.swapchain.height == 0") \
 4693+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT, "sg_begin_pass: expected pass.swapchain.sample_count > 0") \
 4694+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET, "sg_begin_pass: expected pass.swapchain.sample_count == 0") \
 4695+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT, "sg_begin_pass: expected pass.swapchain.color_format to be valid") \
 4696+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET, "sg_begin_pass: expected pass.swapchain.color_format to be unset") \
 4697+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET, "sg_begin_pass: expected pass.swapchain.depth_format to be unset") \
 4698+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE, "sg_begin_pass: expected pass.swapchain.metal.current_drawable != 0") \
 4699+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.current_drawable == 0") \
 4700+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE, "sg_begin_pass: expected pass.swapchain.metal.depth_stencil_texture != 0") \
 4701+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.depth_stencil_texture == 0") \
 4702+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE, "sg_begin_pass: expected pass.swapchain.metal.msaa_color_texture != 0") \
 4703+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.msaa_color_texture == 0") \
 4704+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.render_view != 0") \
 4705+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.render_view == 0") \
 4706+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.resolve_view != 0") \
 4707+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.resolve_view == 0") \
 4708+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.depth_stencil_view != 0") \
 4709+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.depth_stencil_view == 0") \
 4710+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.render_view != 0") \
 4711+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.render_view == 0") \
 4712+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.resolve_view != 0") \
 4713+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.resolve_view == 0") \
 4714+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.depth_stencil_view != 0") \
 4715+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.depth_stencil_view == 0") \
 4716+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET, "sg_begin_pass: expected pass.swapchain.gl.framebuffer == 0") \
 4717+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERIMAGE, "sg_begin_pass: expected pass.swapchain.vk.render_image != 0") \
 4718+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERIMAGE_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.render_image == 0") \
 4719+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.vk.render_view != 0") \
 4720+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.render_view == 0") \
 4721+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILIMAGE, "sg_begin_pass: expected pass.swapchain.vk.depth_stencil_image != 0") \
 4722+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILIMAGE_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.depth_stencil_image == 0") \
 4723+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.vk.depth_stencil_view != 0") \
 4724+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.depth_stencil_view == 0") \
 4725+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEIMAGE, "sg_begin_pass: expected pass.swapchain.vk.resolve_image != 0") \
 4726+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEIMAGE_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.resolve_image == 0") \
 4727+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.vk.resolve_view != 0") \
 4728+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.resolve_view == 0") \
 4729+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERFINISHEDSEMAPHORE, "sg_begin_pass: expected pass.swapchain.vk.render_finished_semaphore != 0") \
 4730+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERFINISHEDSEMAPHORE_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.render_finished_semaphore == 0") \
 4731+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_PRESENTCOMPLETESEMAPHORE, "sg_begin_pass: expected pass.swapchain.vk.present_complete_semaphore != 0") \
 4732+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_PRESENTCOMPLETESEMAPHORE_NOTSET, "sg_begin_pass: expected pass.swapchain.vk.present_complete_semaphore == 0") \
 4733+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEWS_CONTINUOUS, "sg_begin_pass: color attachment view array must be continuous") \
 4734+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_ALIVE, "sg_begin_pass: color attachment view no longer alive") \
 4735+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_VALID, "sg_begin_pass: color attachment view not in valid state (SG_RESOURCESTATE_VALID)") \
 4736+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_TYPE, "sg_begin_pass: color attachment view has wrong type (must be sg_view_desc.color_attachment)") \
 4737+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_ALIVE, "sg_begin_pass: color attachment view's image object is uninitialized or no longer alive") \
 4738+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_VALID, "sg_begin_pass: color attachment view's image is not in valid state (SG_RESOURCESTATE_VALID)") \
 4739+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SIZES, "sg_begin_pass: all color attachments must have the same width and height") \
 4740+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNT, "sg_begin_pass: when resolve attachments are provided, the color attachment sample count must be > 1") \
 4741+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNTS_EQUAL, "sg_begin_pass: all color attachments must have the same sample count") \
 4742+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_NO_COLORATTACHMENTVIEW, "sg_begin_pass: a resolve attachment view must have an associated color attachment view at the same index") \
 4743+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_ALIVE, "sg_begin_pass: resolve attachment view no longer alive") \
 4744+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_VALID, "sg_begin_pass: resolve attachment view not in valid state (SG_RESOURCESTATE_VALID)") \
 4745+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_TYPE, "sg_begin_pass: resolve attachment view has wrong type (must be sg_view_desc.resolve_attachment)") \
 4746+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_ALIVE, "sg_begin_pass: resolve attachment view's image object is uninitialized or no longer alive") \
 4747+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_VALID, "sg_begin_pass: resolve attachment view's image is not in valid state (SG_RESOURCESTATE_VALID)") \
 4748+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_SIZES, "sg_begin_pass: all attachments must have the same width and height") \
 4749+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEWS_CONTINUOUS, "sg_begin_pass: color attachment view array must be continuous") \
 4750+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_ALIVE, "sg_begin_pass: depth-stencil attachment view no longer alive") \
 4751+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_VALID, "sg_begin_pass: depth-stencil attachment view not in valid state (SG_RESOURCESTATE_VALID)") \
 4752+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_TYPE, "sg_begin_pass: depth-stencil attachment view has wrong type (must be sg_view_desc.depth_stencil_attachment)") \
 4753+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_ALIVE, "sg_begin_pass: depth-stencil attachment view's image object is uninitialized or no longer alive") \
 4754+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_VALID, "sg_begin_pass: depth-stencil attachment view's image is not in valid state (SG_RESOURCESTATE_VALID)") \
 4755+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SIZES, "sg_begin_pass: attachments must have the same width and height") \
 4756+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SAMPLECOUNT, "sg_begin_pass: all color attachments must have the same sample count") \
 4757+    _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_ATTACHMENTS_EXPECTED, "sg_begin_pass: offscreen render passes must have at least one color- or depth-stencil attachment") \
 4758+    _SG_LOGITEM_XMACRO(VALIDATE_AVP_RENDERPASS_EXPECTED, "sg_apply_viewport: must be called in a render pass") \
 4759+    _SG_LOGITEM_XMACRO(VALIDATE_ASR_RENDERPASS_EXPECTED, "sg_apply_scissor_rect: must be called in a render pass") \
 4760+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID_ID, "sg_apply_pipeline: invalid pipeline id provided") \
 4761+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_EXISTS, "sg_apply_pipeline: pipeline object no longer alive") \
 4762+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID, "sg_apply_pipeline: pipeline object not in valid state (SG_RESOURCESTATE_VALID)") \
 4763+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_PASS_EXPECTED, "sg_apply_pipeline: must be called in a pass") \
 4764+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_SHADER_ALIVE, "sg_apply_pipeline: shader object associated with pipeline no longer alive") \
 4765+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_SHADER_VALID, "sg_apply_pipeline: shader object associated with pipeline not in valid state") \
 4766+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_COMPUTEPASS_EXPECTED, "sg_apply_pipeline: trying to apply compute pipeline in render pass") \
 4767+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_RENDERPASS_EXPECTED, "sg_apply_pipeline: trying to apply render pipeline in compute pass") \
 4768+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_COLOR_COUNT, "sg_apply_pipeline: the pipeline .color_count must be 1 in swapchain render passes") \
 4769+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_COLOR_FORMAT, "sg_apply_pipeline: the pipeline .colors[0].pixel_format doesn't match the sg_pass.swapchain.color_format") \
 4770+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_DEPTH_FORMAT, "sg_apply_pipeline: the pipeline .depth.pixel_format doesn't match the sg_pass.swapchain.depth_format") \
 4771+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_SWAPCHAIN_SAMPLE_COUNT, "sg_apply_pipeline: the pipeline .sample_count doesn't match the sg_pass.swapchain.sample_count") \
 4772+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATTACHMENTS_ALIVE, "sg_apply_pipeline: at least one pass attachment view or base image object is no longer alive") \
 4773+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_COUNT, "sg_apply_pipeline: the pipeline .color_count doesn't match the number of render pass color attachments") \
 4774+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_VIEW_VALID, "sg_apply_pipeline: a pass color attachment view is not in valid state (SG_RESOURCESTATE_VALID)") \
 4775+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_IMAGE_VALID, "sg_apply_pipeline: a pass color attachment view's image object is not in valid state (SG_RESOURCESTATE_VALID)") \
 4776+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLORATTACHMENTS_FORMAT, "sg_apply_pipeline: a pipeline .colors[n].pixel_format doesn't match sg_pass.attachments.colors[n] image pixel format") \
 4777+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCILATTACHMENT_VIEW_VALID, "sg_apply_pipeline: the pass depth-stencil attachment view is not in valid state (SG_RESOURCESTATE_VALID)") \
 4778+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCILATTACHMENT_IMAGE_VALID, "sg_apply_pipeline: the pass depth-stencil attachment view's image object is not in valid state (SG_RESOURCESTATE_VALID)") \
 4779+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTHSTENCILATTACHMENT_FORMAT, "sg_apply_pipeline: pipeline .depth.pixel_format doesn't match sg_pass.attachments.depth_stencil image pixel format") \
 4780+    _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATTACHMENT_SAMPLE_COUNT, "sg_apply_pipeline: pipeline MSAA sample count doesn't match pass attachment sample count") \
 4781+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_PASS_EXPECTED, "sg_apply_bindings: must be called in a pass") \
 4782+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EMPTY_BINDINGS, "sg_apply_bindings: the provided sg_bindings struct is empty") \
 4783+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_NO_PIPELINE, "sg_apply_bindings: must be called after sg_apply_pipeline") \
 4784+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_ALIVE, "sg_apply_bindings: currently applied pipeline object no longer alive") \
 4785+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_VALID, "sg_apply_bindings: currently applied pipeline object not in valid state") \
 4786+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_SHADER_ALIVE, "sg_apply_bindings: shader associated with currently applied pipeline is no longer alive") \
 4787+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_SHADER_VALID, "sg_apply_bindings: shader associated with currently applied pipeline is not in valid state") \
 4788+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBUFS, "sg_apply_bindings: vertex buffer bindings not allowed in a compute pass") \
 4789+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_IBUF, "sg_apply_bindings: index buffer binding not allowed in compute pass") \
 4790+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_VBUF, "sg_apply_bindings: vertex buffer binding is missing or buffer handle is invalid") \
 4791+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBUF_ALIVE, "sg_apply_bindings: vertex buffer no longer alive") \
 4792+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBUF_USAGE, "sg_apply_bindings: buffer in vertex buffer bind slot must have usage.vertex_buffer") \
 4793+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_VBUF_OVERFLOW, "sg_apply_bindings: buffer in vertex buffer bind slot is overflown") \
 4794+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_NO_IBUF, "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer binding provided") \
 4795+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_IBUF, "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer binding provided") \
 4796+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_IBUF_ALIVE, "sg_apply_bindings: index buffer no longer alive") \
 4797+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_IBUF_USAGE, "sg_apply_bindings: buffer in index buffer bind slot must have usage.index_buffer") \
 4798+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_IBUF_OVERFLOW, "sg_apply_bindings: buffer in index buffer slot is overflown") \
 4799+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_VIEW_BINDING, "sg_apply_bindings: view binding is missing or the view handle is invalid") \
 4800+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_VIEW_ALIVE, "sg_apply_bindings: view no longer alive") \
 4801+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECT_TEXVIEW, "sg_apply_bindings: view type mismatch in bindslot (shader expects a texture view)") \
 4802+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECT_SBVIEW, "sg_apply_bindings: view type mismatch in bindslot (shader expects a storage buffer view)") \
 4803+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECT_SIMGVIEW, "sg_apply_bindings: view type mismatch in bindslot (shader expects a storage image view)") \
 4804+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_IMAGETYPE_MISMATCH, "sg_apply_bindings: image type of bound texture doesn't match shader desc") \
 4805+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_MULTISAMPLED_IMAGE, "sg_apply_bindings: texture bindings expects image with sample_count > 1") \
 4806+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_NON_MULTISAMPLED_IMAGE, "sg_apply_bindings: texture bindings expects image with sample_count == 1") \
 4807+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_FILTERABLE_IMAGE, "sg_apply_bindings: filterable image expected") \
 4808+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXVIEW_EXPECTED_DEPTH_IMAGE, "sg_apply_bindings: depth image expected") \
 4809+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_SBVIEW_READWRITE_IMMUTABLE, "sg_apply_bindings: storage buffers bound as read/write must have usage immutable") \
 4810+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_SIMGVIEW_COMPUTE_PASS_EXPECTED, "sg_apply_bindings: storage image bindings can only appear on compute passes") \
 4811+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_SIMGVIEW_IMAGETYPE_MISMATCH, "sg_apply_bindings: image type of bound storage image doesn't match shader desc") \
 4812+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_SIMGVIEW_ACCESSFORMAT, "sg_apply_bindings: pixel format of storage image view doesn't match access format in shader desc") \
 4813+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_SAMPLER_BINDING, "sg_apply_bindings: sampler binding is missing or the sampler handle is invalid") \
 4814+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_COMPARISON but sampler has SG_COMPAREFUNC_NEVER") \
 4815+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING but sampler doesn't have SG_COMPAREFUNC_NEVER") \
 4816+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING, but sampler has SG_FILTER_LINEAR filters") \
 4817+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_SAMPLER_ALIVE, "sg_apply_bindings: bound sampler no longer alive") \
 4818+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_SAMPLER_VALID, "sg_apply_bindings: bound sampler not in valid state") \
 4819+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_BINDING_VS_DEPTHSTENCIL_ATTACHMENT, "sg_apply_bindings: cannot bind texture in the same pass it is used as depth-stencil attachment") \
 4820+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_BINDING_VS_COLOR_ATTACHMENT, "sg_apply_bindings: cannot bind texture in the same pass it is used as color attachment") \
 4821+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_BINDING_VS_RESOLVE_ATTACHMENT, "sg_apply_bindings: cannot bind texture in the same pass it is used as resolve attachment") \
 4822+    _SG_LOGITEM_XMACRO(VALIDATE_ABND_TEXTURE_VS_STORAGEIMAGE_BINDING, "sg_apply_bindings: an image cannot be bound as a texture and storage image at the same time") \
 4823+    _SG_LOGITEM_XMACRO(VALIDATE_AU_PASS_EXPECTED, "sg_apply_uniforms: must be called in a pass") \
 4824+    _SG_LOGITEM_XMACRO(VALIDATE_AU_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \
 4825+    _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_ALIVE, "sg_apply_uniforms: currently applied pipeline object no longer alive") \
 4826+    _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_VALID, "sg_apply_uniforms: currently applied pipeline object not in valid state") \
 4827+    _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_SHADER_ALIVE, "sg_apply_uniforms: shader associated with currently applied pipeline is no longer alive") \
 4828+    _SG_LOGITEM_XMACRO(VALIDATE_AU_PIPELINE_SHADER_VALID, "sg_apply_uniforms: shader associated with currently applied pipeline is not in valid state") \
 4829+    _SG_LOGITEM_XMACRO(VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \
 4830+    _SG_LOGITEM_XMACRO(VALIDATE_AU_SIZE, "sg_apply_uniforms: data size doesn't match declared uniform block size") \
 4831+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_RENDERPASS_EXPECTED, "sg_draw: must be called in a render pass") \
 4832+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_BASEELEMENT_GE_ZERO, "sg_draw: base_element cannot be < 0") \
 4833+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMELEMENTS_GE_ZERO, "sg_draw: num_elements cannot be < 0") \
 4834+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMINSTANCES_GE_ZERO, "sg_draw: num_instances cannot be < 0") \
 4835+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_RENDERPASS_EXPECTED, "sg_draw: must be called in a render pass") \
 4836+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEELEMENT_GE_ZERO, "sg_draw_ex: base_element cannot be < 0") \
 4837+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_NUMELEMENTS_GE_ZERO, "sg_draw_ex: num_elements cannot be < 0") \
 4838+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_NUMINSTANCES_GE_ZERO, "sg_draw_ex: num_instances cannot be < 0") \
 4839+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEINSTANCE_GE_ZERO, "sg_draw_ex: base_instance cannot be < 0") \
 4840+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEVERTEX_VS_INDEXED, "sg_draw_ex(): base_vertex must be == 0 for non-indexed rendering") \
 4841+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEINSTANCE_VS_INSTANCED, "sg_draw_ex(): base_instance must be == 0 for non-instanced rendering") \
 4842+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEVERTEX_NOT_SUPPORTED, "sg_draw_ex(): base_vertex != 0 not supported on this backend (sg_features.draw_base_vertex)") \
 4843+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_EX_BASEINSTANCE_NOT_SUPPORTED, "sg_draw_ex(): base_instance > 0 not supported on this backend (sg_features.draw_base_instance)") \
 4844+    _SG_LOGITEM_XMACRO(VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING, "sg_draw: call to sg_apply_bindings() and/or sg_apply_uniforms() missing after sg_apply_pipeline()") \
 4845+    _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED, "sg_dispatch: must be called in a compute pass") \
 4846+    _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSX, "sg_dispatch: num_groups_x must be >=0 and <65536") \
 4847+    _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSY, "sg_dispatch: num_groups_y must be >=0 and <65536") \
 4848+    _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSZ, "sg_dispatch: num_groups_z must be >=0 and <65536") \
 4849+    _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING, "sg_dispatch: call to sg_apply_bindings() and/or sg_apply_uniforms() missing after sg_apply_pipeline()") \
 4850+    _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_USAGE, "sg_update_buffer: cannot update immutable buffer") \
 4851+    _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_SIZE, "sg_update_buffer: update size is bigger than buffer size") \
 4852+    _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_ONCE, "sg_update_buffer: only one update allowed per buffer and frame") \
 4853+    _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_APPEND, "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame") \
 4854+    _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_USAGE, "sg_append_buffer: cannot append to immutable buffer") \
 4855+    _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_SIZE, "sg_append_buffer: overall appended size is bigger than buffer size") \
 4856+    _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_UPDATE, "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame") \
 4857+    _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_USAGE, "sg_update_image: cannot update immutable image") \
 4858+    _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_ONCE, "sg_update_image: only one update allowed per image and frame") \
 4859+    _SG_LOGITEM_XMACRO(VALIDATION_FAILED, "validation layer checks failed") \
 4860+
 4861+#define _SG_LOGITEM_XMACRO(item,msg) SG_LOGITEM_##item,
 4862+typedef enum sg_log_item {
 4863+    _SG_LOG_ITEMS
 4864+} sg_log_item;
 4865+#undef _SG_LOGITEM_XMACRO
 4866+
 4867+/*
 4868+    sg_desc
 4869+
 4870+    The sg_desc struct contains configuration values for sokol_gfx,
 4871+    it is used as parameter to the sg_setup() call.
 4872+
 4873+    The default configuration is:
 4874+
 4875+    .buffer_pool_size                   128
 4876+    .image_pool_size                    128
 4877+    .sampler_pool_size                  64
 4878+    .shader_pool_size                   32
 4879+    .pipeline_pool_size                 64
 4880+    .view_pool_size                     256
 4881+    .uniform_buffer_size                4 MB (4*1024*1024)
 4882+    .max_commit_listeners               1024
 4883+    .disable_validation                 false
 4884+    .metal.force_managed_storage_mode   false
 4885+    .metal.use_command_buffer_with_retained_references  false
 4886+    .wgpu.disable_bindgroups_cache      false
 4887+    .wgpu.bindgroups_cache_size         1024
 4888+    .vulkan.copy_staging_buffer_size    4 MB
 4889+    .vulkan.stream_staging_buffer_size  16 MB
 4890+    .vulkan.descriptor_buffer_size      16 MB
 4891+
 4892+    .allocator.alloc_fn     0 (in this case, malloc() will be called)
 4893+    .allocator.free_fn      0 (in this case, free() will be called)
 4894+    .allocator.user_data    0
 4895+
 4896+    .environment.defaults.color_format: default value depends on selected backend:
 4897+        all GL backends:    SG_PIXELFORMAT_RGBA8
 4898+        Metal and D3D11:    SG_PIXELFORMAT_BGRA8
 4899+        WebGPU:             *no default* (must be queried from WebGPU swapchain object)
 4900+    .environment.defaults.depth_format: SG_PIXELFORMAT_DEPTH_STENCIL
 4901+    .environment.defaults.sample_count: 1
 4902+
 4903+    Metal specific:
 4904+        (NOTE: All Objective-C object references are transferred through
 4905+        a bridged cast (__bridge const void*) to sokol_gfx, which will use an
 4906+        unretained bridged cast (__bridge id<xxx>) to retrieve the Objective-C
 4907+        references back. Since the bridge cast is unretained, the caller
 4908+        must hold a strong reference to the Objective-C object until sg_setup()
 4909+        returns.
 4910+
 4911+        .metal.force_managed_storage_mode
 4912+            when enabled, Metal buffers and texture resources are created in managed storage
 4913+            mode, otherwise sokol-gfx will decide whether to create buffers and
 4914+            textures in managed or shared storage mode (this is mainly a debugging option)
 4915+        .metal.use_command_buffer_with_retained_references
 4916+            when true, the sokol-gfx Metal backend will use Metal command buffers which
 4917+            bump the reference count of resource objects as long as they are inflight,
 4918+            this is slower than the default command-buffer-with-unretained-references
 4919+            method, this may be a workaround when confronted with lifetime validation
 4920+            errors from the Metal validation layer until a proper fix has been implemented
 4921+        .environment.metal.device
 4922+            a pointer to the MTLDevice object
 4923+
 4924+    D3D11 specific:
 4925+        .environment.d3d11.device
 4926+            a pointer to the ID3D11Device object, this must have been created
 4927+            before sg_setup() is called
 4928+        .environment.d3d11.device_context
 4929+            a pointer to the ID3D11DeviceContext object
 4930+        .d3d11.shader_debugging
 4931+            set this to true to compile shaders which are provided as HLSL source
 4932+            code with debug information and without optimization, this allows
 4933+            shader debugging in tools like RenderDoc, to output source code
 4934+            instead of byte code from sokol-shdc, omit the `--binary` cmdline
 4935+            option
 4936+
 4937+    WebGPU specific:
 4938+        .wgpu.disable_bindgroups_cache
 4939+            When this is true, the WebGPU backend will create and immediately
 4940+            release a BindGroup object in the sg_apply_bindings() call, only
 4941+            use this for debugging purposes.
 4942+        .wgpu.bindgroups_cache_size
 4943+            The size of the bindgroups cache for re-using BindGroup objects
 4944+            between sg_apply_bindings() calls. The smaller the cache size,
 4945+            the more likely are cache slot collisions which will cause
 4946+            a BindGroups object to be destroyed and a new one created.
 4947+            Use the information returned by sg_query_stats() to check
 4948+            if this is a frequent occurrence, and increase the cache size as
 4949+            needed (the default is 1024).
 4950+            NOTE: wgpu_bindgroups_cache_size must be a power-of-2 number!
 4951+        .environment.wgpu.device
 4952+            a WGPUDevice handle
 4953+
 4954+    Vulkan specific:
 4955+        .vulkan.copy_staging_buffer_size
 4956+            Size of the staging buffer in bytes for uploading the initial
 4957+            content of buffers and images, and for updating
 4958+            .usage.dynamic_update resources. The default is 4 MB,
 4959+            bigger resource updates are split into multiple chunks
 4960+            of the staging buffer size
 4961+        .vulkan.stream_staging_buffer_size
 4962+            Size of the staging buffer in bytes for updating .usage.stream_update
 4963+            resources. The default is 16 MB. The size must be big enough
 4964+            to accomodate all update into .usage.stream_update resources.
 4965+            Any additional data will cause an error log message and
 4966+            incomplete rendering. Note that the actually allocated size
 4967+            will be twice as much because the stream-staging-buffer is
 4968+            double-buffered.
 4969+        .vulkan.descriptor_buffer_size
 4970+            Size of the descriptor-upload buffer in bytes. The default
 4971+            size is 16 bytes. The size must be big enough to accomodate
 4972+            all unifrom-block, view- and sampler-bindings in a single
 4973+            frame (assume a worst-case of 256 bytes per binding). Note
 4974+            that the actually allocated size will be twice as much
 4975+            because the descriptor-buffer is double-buffered.
 4976+
 4977+    When using sokol_gfx.h and sokol_app.h together, consider using the
 4978+    helper function sglue_environment() in the sokol_glue.h header to
 4979+    initialize the sg_desc.environment nested struct. sglue_environment() returns
 4980+    a completely initialized sg_environment struct with information
 4981+    provided by sokol_app.h.
 4982+*/
 4983+typedef struct sg_environment_defaults {
 4984+    sg_pixel_format color_format;
 4985+    sg_pixel_format depth_format;
 4986+    int sample_count;
 4987+} sg_environment_defaults;
 4988+
 4989+typedef struct sg_metal_environment {
 4990+    const void* device;
 4991+} sg_metal_environment;
 4992+
 4993+typedef struct sg_d3d11_environment {
 4994+    const void* device;
 4995+    const void* device_context;
 4996+} sg_d3d11_environment;
 4997+
 4998+typedef struct sg_wgpu_environment {
 4999+    const void* device;
 5000+} sg_wgpu_environment;
 5001+
 5002+typedef struct sg_vulkan_environment {
 5003+    const void* instance;
 5004+    const void* physical_device;
 5005+    const void* device;
 5006+    const void* queue;
 5007+    uint32_t queue_family_index;
 5008+} sg_vulkan_environment;
 5009+
 5010+typedef struct sg_environment {
 5011+    sg_environment_defaults defaults;
 5012+    sg_metal_environment metal;
 5013+    sg_d3d11_environment d3d11;
 5014+    sg_wgpu_environment wgpu;
 5015+    sg_vulkan_environment vulkan;
 5016+} sg_environment;
 5017+
 5018+/*
 5019+    sg_commit_listener
 5020+
 5021+    Used with function sg_add_commit_listener() to add a callback
 5022+    which will be called in sg_commit(). This is useful for libraries
 5023+    building on top of sokol-gfx to be notified about when a frame
 5024+    ends (instead of having to guess, or add a manual 'new-frame'
 5025+    function.
 5026+*/
 5027+typedef struct sg_commit_listener {
 5028+    void (*func)(void* user_data);
 5029+    void* user_data;
 5030+} sg_commit_listener;
 5031+
 5032+/*
 5033+    sg_allocator
 5034+
 5035+    Used in sg_desc to provide custom memory-alloc and -free functions
 5036+    to sokol_gfx.h. If memory management should be overridden, both the
 5037+    alloc_fn and free_fn function must be provided (e.g. it's not valid to
 5038+    override one function but not the other).
 5039+*/
 5040+typedef struct sg_allocator {
 5041+    void* (*alloc_fn)(size_t size, void* user_data);
 5042+    void (*free_fn)(void* ptr, void* user_data);
 5043+    void* user_data;
 5044+} sg_allocator;
 5045+
 5046+/*
 5047+    sg_logger
 5048+
 5049+    Used in sg_desc to provide a logging function. Please be aware
 5050+    that without logging function, sokol-gfx will be completely
 5051+    silent, e.g. it will not report errors, warnings and
 5052+    validation layer messages. For maximum error verbosity,
 5053+    compile in debug mode (e.g. NDEBUG *not* defined) and provide a
 5054+    compatible logger function in the sg_setup() call
 5055+    (for instance the standard logging function from sokol_log.h).
 5056+*/
 5057+typedef struct sg_logger {
 5058+    void (*func)(
 5059+        const char* tag,                // always "sg"
 5060+        uint32_t log_level,             // 0=panic, 1=error, 2=warning, 3=info
 5061+        uint32_t log_item_id,           // SG_LOGITEM_*
 5062+        const char* message_or_null,    // a message string, may be nullptr in release mode
 5063+        uint32_t line_nr,               // line number in sokol_gfx.h
 5064+        const char* filename_or_null,   // source filename, may be nullptr in release mode
 5065+        void* user_data);
 5066+    void* user_data;
 5067+} sg_logger;
 5068+
 5069+typedef struct sg_d3d11_desc {
 5070+    bool shader_debugging;  // if true, HLSL shaders are compiled with D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION
 5071+} sg_d3d11_desc;
 5072+
 5073+typedef struct sg_metal_desc {
 5074+    bool force_managed_storage_mode; // for debugging: use Metal managed storage mode for resources even with UMA
 5075+    bool use_command_buffer_with_retained_references;    // Metal: use a managed MTLCommandBuffer which ref-counts used resources
 5076+} sg_metal_desc;
 5077+
 5078+typedef struct sg_wgpu_desc {
 5079+    bool disable_bindgroups_cache; // set to true to disable the WebGPU backend BindGroup cache
 5080+    int bindgroups_cache_size;     // number of slots in the WebGPU bindgroup cache (must be 2^N)
 5081+} sg_wgpu_desc;
 5082+
 5083+typedef struct sg_vulkan_desc {
 5084+    int copy_staging_buffer_size;    // size of staging buffer for immutable and dynamic resources (default: 4 MB)
 5085+    int stream_staging_buffer_size;  // size of per-frame staging buffer for updating streaming resources (default: 16 MB)
 5086+    int descriptor_buffer_size;      // size of per-frame descriptor buffer for updating resource bindings (default: 16 MB)
 5087+} sg_vulkan_desc;
 5088+
 5089+typedef struct sg_desc {
 5090+    uint32_t _start_canary;
 5091+    int buffer_pool_size;
 5092+    int image_pool_size;
 5093+    int sampler_pool_size;
 5094+    int shader_pool_size;
 5095+    int pipeline_pool_size;
 5096+    int view_pool_size;
 5097+    int uniform_buffer_size;        // max size of all sg_apply_uniform() calls per frame, with worst-case 256 byte alignment
 5098+    int max_commit_listeners;       // max number of commit listener hook functions
 5099+    bool disable_validation;        // disable validation layer even in debug mode, useful for tests
 5100+    bool enforce_portable_limits;   // if true, enforce portable resource binding limits (SG_MAX_PORTABLE_*)
 5101+    sg_d3d11_desc d3d11;            // d3d11-specific setup parameters
 5102+    sg_metal_desc metal;            // metal-specific setup parameters
 5103+    sg_wgpu_desc wgpu;              // webgpu-specific setup parameters
 5104+    sg_vulkan_desc vulkan;          // vulkan-specific setup parameters
 5105+    sg_allocator allocator;         // optional memory allocation hooks
 5106+    sg_logger logger;               // optional log function override
 5107+    sg_environment environment;     // required externally provided runtime objects and defaults
 5108+    uint32_t _end_canary;
 5109+} sg_desc;
 5110+
 5111+// setup and misc functions
 5112+SOKOL_GFX_API_DECL void sg_setup(const sg_desc* desc);
 5113+SOKOL_GFX_API_DECL void sg_shutdown(void);
 5114+SOKOL_GFX_API_DECL bool sg_isvalid(void);
 5115+SOKOL_GFX_API_DECL void sg_reset_state_cache(void);
 5116+SOKOL_GFX_API_DECL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks);
 5117+SOKOL_GFX_API_DECL void sg_push_debug_group(const char* name);
 5118+SOKOL_GFX_API_DECL void sg_pop_debug_group(void);
 5119+SOKOL_GFX_API_DECL bool sg_add_commit_listener(sg_commit_listener listener);
 5120+SOKOL_GFX_API_DECL bool sg_remove_commit_listener(sg_commit_listener listener);
 5121+
 5122+// resource creation, destruction and updating
 5123+SOKOL_GFX_API_DECL sg_buffer sg_make_buffer(const sg_buffer_desc* desc);
 5124+SOKOL_GFX_API_DECL sg_image sg_make_image(const sg_image_desc* desc);
 5125+SOKOL_GFX_API_DECL sg_sampler sg_make_sampler(const sg_sampler_desc* desc);
 5126+SOKOL_GFX_API_DECL sg_shader sg_make_shader(const sg_shader_desc* desc);
 5127+SOKOL_GFX_API_DECL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc);
 5128+SOKOL_GFX_API_DECL sg_view sg_make_view(const sg_view_desc* desc);
 5129+SOKOL_GFX_API_DECL void sg_destroy_buffer(sg_buffer buf);
 5130+SOKOL_GFX_API_DECL void sg_destroy_image(sg_image img);
 5131+SOKOL_GFX_API_DECL void sg_destroy_sampler(sg_sampler smp);
 5132+SOKOL_GFX_API_DECL void sg_destroy_shader(sg_shader shd);
 5133+SOKOL_GFX_API_DECL void sg_destroy_pipeline(sg_pipeline pip);
 5134+SOKOL_GFX_API_DECL void sg_destroy_view(sg_view view);
 5135+SOKOL_GFX_API_DECL void sg_update_buffer(sg_buffer buf, const sg_range* data);
 5136+SOKOL_GFX_API_DECL void sg_update_image(sg_image img, const sg_image_data* data);
 5137+SOKOL_GFX_API_DECL int sg_append_buffer(sg_buffer buf, const sg_range* data);
 5138+SOKOL_GFX_API_DECL bool sg_query_buffer_overflow(sg_buffer buf);
 5139+SOKOL_GFX_API_DECL bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size);
 5140+
 5141+// render and compute functions
 5142+SOKOL_GFX_API_DECL void sg_begin_pass(const sg_pass* pass);
 5143+SOKOL_GFX_API_DECL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left);
 5144+SOKOL_GFX_API_DECL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left);
 5145+SOKOL_GFX_API_DECL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left);
 5146+SOKOL_GFX_API_DECL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left);
 5147+SOKOL_GFX_API_DECL void sg_apply_pipeline(sg_pipeline pip);
 5148+SOKOL_GFX_API_DECL void sg_apply_bindings(const sg_bindings* bindings);
 5149+SOKOL_GFX_API_DECL void sg_apply_uniforms(int ub_slot, const sg_range* data);
 5150+SOKOL_GFX_API_DECL void sg_draw(int base_element, int num_elements, int num_instances);
 5151+SOKOL_GFX_API_DECL void sg_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance);
 5152+SOKOL_GFX_API_DECL void sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z);
 5153+SOKOL_GFX_API_DECL void sg_end_pass(void);
 5154+SOKOL_GFX_API_DECL void sg_commit(void);
 5155+
 5156+// getting information
 5157+SOKOL_GFX_API_DECL sg_desc sg_query_desc(void);
 5158+SOKOL_GFX_API_DECL sg_backend sg_query_backend(void);
 5159+SOKOL_GFX_API_DECL sg_features sg_query_features(void);
 5160+SOKOL_GFX_API_DECL sg_limits sg_query_limits(void);
 5161+SOKOL_GFX_API_DECL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt);
 5162+SOKOL_GFX_API_DECL int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes);
 5163+SOKOL_GFX_API_DECL int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes);
 5164+// get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID)
 5165+SOKOL_GFX_API_DECL sg_resource_state sg_query_buffer_state(sg_buffer buf);
 5166+SOKOL_GFX_API_DECL sg_resource_state sg_query_image_state(sg_image img);
 5167+SOKOL_GFX_API_DECL sg_resource_state sg_query_sampler_state(sg_sampler smp);
 5168+SOKOL_GFX_API_DECL sg_resource_state sg_query_shader_state(sg_shader shd);
 5169+SOKOL_GFX_API_DECL sg_resource_state sg_query_pipeline_state(sg_pipeline pip);
 5170+SOKOL_GFX_API_DECL sg_resource_state sg_query_view_state(sg_view view);
 5171+// get runtime information about a resource
 5172+SOKOL_GFX_API_DECL sg_buffer_info sg_query_buffer_info(sg_buffer buf);
 5173+SOKOL_GFX_API_DECL sg_image_info sg_query_image_info(sg_image img);
 5174+SOKOL_GFX_API_DECL sg_sampler_info sg_query_sampler_info(sg_sampler smp);
 5175+SOKOL_GFX_API_DECL sg_shader_info sg_query_shader_info(sg_shader shd);
 5176+SOKOL_GFX_API_DECL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip);
 5177+SOKOL_GFX_API_DECL sg_view_info sg_query_view_info(sg_view view);
 5178+// get desc structs matching a specific resource (NOTE that not all creation attributes may be provided)
 5179+SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf);
 5180+SOKOL_GFX_API_DECL sg_image_desc sg_query_image_desc(sg_image img);
 5181+SOKOL_GFX_API_DECL sg_sampler_desc sg_query_sampler_desc(sg_sampler smp);
 5182+SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_desc(sg_shader shd);
 5183+SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip);
 5184+SOKOL_GFX_API_DECL sg_view_desc sg_query_view_desc(sg_view view);
 5185+// get resource creation desc struct with their default values replaced
 5186+SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc);
 5187+SOKOL_GFX_API_DECL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc);
 5188+SOKOL_GFX_API_DECL sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc);
 5189+SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc);
 5190+SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc);
 5191+SOKOL_GFX_API_DECL sg_view_desc sg_query_view_defaults(const sg_view_desc* desc);
 5192+// assorted query functions
 5193+SOKOL_GFX_API_DECL size_t sg_query_buffer_size(sg_buffer buf);
 5194+SOKOL_GFX_API_DECL sg_buffer_usage sg_query_buffer_usage(sg_buffer buf);
 5195+SOKOL_GFX_API_DECL sg_image_type sg_query_image_type(sg_image img);
 5196+SOKOL_GFX_API_DECL int sg_query_image_width(sg_image img);
 5197+SOKOL_GFX_API_DECL int sg_query_image_height(sg_image img);
 5198+SOKOL_GFX_API_DECL int sg_query_image_num_slices(sg_image img);
 5199+SOKOL_GFX_API_DECL int sg_query_image_num_mipmaps(sg_image img);
 5200+SOKOL_GFX_API_DECL sg_pixel_format sg_query_image_pixelformat(sg_image img);
 5201+SOKOL_GFX_API_DECL sg_image_usage sg_query_image_usage(sg_image img);
 5202+SOKOL_GFX_API_DECL int sg_query_image_sample_count(sg_image img);
 5203+SOKOL_GFX_API_DECL sg_view_type sg_query_view_type(sg_view view);
 5204+SOKOL_GFX_API_DECL sg_image sg_query_view_image(sg_view view);
 5205+SOKOL_GFX_API_DECL sg_buffer sg_query_view_buffer(sg_view view);
 5206+
 5207+// separate resource allocation and initialization (for async setup)
 5208+SOKOL_GFX_API_DECL sg_buffer sg_alloc_buffer(void);
 5209+SOKOL_GFX_API_DECL sg_image sg_alloc_image(void);
 5210+SOKOL_GFX_API_DECL sg_sampler sg_alloc_sampler(void);
 5211+SOKOL_GFX_API_DECL sg_shader sg_alloc_shader(void);
 5212+SOKOL_GFX_API_DECL sg_pipeline sg_alloc_pipeline(void);
 5213+SOKOL_GFX_API_DECL sg_view sg_alloc_view(void);
 5214+SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf);
 5215+SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img);
 5216+SOKOL_GFX_API_DECL void sg_dealloc_sampler(sg_sampler smp);
 5217+SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd);
 5218+SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip);
 5219+SOKOL_GFX_API_DECL void sg_dealloc_view(sg_view view);
 5220+SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc);
 5221+SOKOL_GFX_API_DECL void sg_init_image(sg_image img, const sg_image_desc* desc);
 5222+SOKOL_GFX_API_DECL void sg_init_sampler(sg_sampler smg, const sg_sampler_desc* desc);
 5223+SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd, const sg_shader_desc* desc);
 5224+SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc);
 5225+SOKOL_GFX_API_DECL void sg_init_view(sg_view view, const sg_view_desc* desc);
 5226+SOKOL_GFX_API_DECL void sg_uninit_buffer(sg_buffer buf);
 5227+SOKOL_GFX_API_DECL void sg_uninit_image(sg_image img);
 5228+SOKOL_GFX_API_DECL void sg_uninit_sampler(sg_sampler smp);
 5229+SOKOL_GFX_API_DECL void sg_uninit_shader(sg_shader shd);
 5230+SOKOL_GFX_API_DECL void sg_uninit_pipeline(sg_pipeline pip);
 5231+SOKOL_GFX_API_DECL void sg_uninit_view(sg_view view);
 5232+SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf);
 5233+SOKOL_GFX_API_DECL void sg_fail_image(sg_image img);
 5234+SOKOL_GFX_API_DECL void sg_fail_sampler(sg_sampler smp);
 5235+SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd);
 5236+SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip);
 5237+SOKOL_GFX_API_DECL void sg_fail_view(sg_view view);
 5238+
 5239+// frame and total stats
 5240+SOKOL_GFX_API_DECL void sg_enable_stats(void);
 5241+SOKOL_GFX_API_DECL void sg_disable_stats(void);
 5242+SOKOL_GFX_API_DECL bool sg_stats_enabled(void);
 5243+SOKOL_GFX_API_DECL sg_stats sg_query_stats(void);
 5244+
 5245+/* Backend-specific structs and functions, these may come in handy for mixing
 5246+   sokol-gfx rendering with 'native backend' rendering functions.
 5247+
 5248+   This group of functions will be expanded as needed.
 5249+*/
 5250+
 5251+typedef struct sg_d3d11_buffer_info {
 5252+    const void* buf;      // ID3D11Buffer*
 5253+} sg_d3d11_buffer_info;
 5254+
 5255+typedef struct sg_d3d11_image_info {
 5256+    const void* tex2d;    // ID3D11Texture2D*
 5257+    const void* tex3d;    // ID3D11Texture3D*
 5258+    const void* res;      // ID3D11Resource* (either tex2d or tex3d)
 5259+} sg_d3d11_image_info;
 5260+
 5261+typedef struct sg_d3d11_sampler_info {
 5262+    const void* smp;      // ID3D11SamplerState*
 5263+} sg_d3d11_sampler_info;
 5264+
 5265+typedef struct sg_d3d11_shader_info {
 5266+    const void* cbufs[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; // ID3D11Buffer* (constant buffers by bind slot)
 5267+    const void* vs;   // ID3D11VertexShader*
 5268+    const void* fs;   // ID3D11PixelShader*
 5269+} sg_d3d11_shader_info;
 5270+
 5271+typedef struct sg_d3d11_pipeline_info {
 5272+    const void* il;   // ID3D11InputLayout*
 5273+    const void* rs;   // ID3D11RasterizerState*
 5274+    const void* dss;  // ID3D11DepthStencilState*
 5275+    const void* bs;   // ID3D11BlendState*
 5276+} sg_d3d11_pipeline_info;
 5277+
 5278+typedef struct sg_d3d11_view_info {
 5279+    const void* srv;    // ID3D11ShaderResourceView
 5280+    const void* uav;    // ID3D11UnorderedAccessView
 5281+    const void* rtv;    // ID3D11RenderTargetView
 5282+    const void* dsv;    // ID3D11DepthStencilView
 5283+} sg_d3d11_view_info;
 5284+
 5285+typedef struct sg_mtl_buffer_info {
 5286+    const void* buf[SG_NUM_INFLIGHT_FRAMES];  // id<MTLBuffer>
 5287+    int active_slot;
 5288+} sg_mtl_buffer_info;
 5289+
 5290+typedef struct sg_mtl_image_info {
 5291+    const void* tex[SG_NUM_INFLIGHT_FRAMES]; // id<MTLTexture>
 5292+    int active_slot;
 5293+} sg_mtl_image_info;
 5294+
 5295+typedef struct sg_mtl_sampler_info {
 5296+    const void* smp;  // id<MTLSamplerState>
 5297+} sg_mtl_sampler_info;
 5298+
 5299+typedef struct sg_mtl_shader_info {
 5300+    const void* vertex_lib;     // id<MTLLibrary>
 5301+    const void* fragment_lib;   // id<MTLLibrary>
 5302+    const void* vertex_func;    // id<MTLFunction>
 5303+    const void* fragment_func;  // id<MTLFunction>
 5304+} sg_mtl_shader_info;
 5305+
 5306+typedef struct sg_mtl_pipeline_info {
 5307+    const void* rps;      // id<MTLRenderPipelineState>
 5308+    const void* dss;      // id<MTLDepthStencilState>
 5309+} sg_mtl_pipeline_info;
 5310+
 5311+typedef struct sg_wgpu_buffer_info {
 5312+    const void* buf;  // WGPUBuffer
 5313+} sg_wgpu_buffer_info;
 5314+
 5315+typedef struct sg_wgpu_image_info {
 5316+    const void* tex;  // WGPUTexture
 5317+} sg_wgpu_image_info;
 5318+
 5319+typedef struct sg_wgpu_sampler_info {
 5320+    const void* smp;  // WGPUSampler
 5321+} sg_wgpu_sampler_info;
 5322+
 5323+typedef struct sg_wgpu_shader_info {
 5324+    const void* vs_mod;   // WGPUShaderModule
 5325+    const void* fs_mod;   // WGPUShaderModule
 5326+    const void* bgl;      // WGPUBindGroupLayout;
 5327+} sg_wgpu_shader_info;
 5328+
 5329+typedef struct sg_wgpu_pipeline_info {
 5330+    const void* render_pipeline;   // WGPURenderPipeline
 5331+    const void* compute_pipeline;  // WGPUComputePipeline
 5332+} sg_wgpu_pipeline_info;
 5333+
 5334+typedef struct sg_wgpu_view_info {
 5335+    const void* view; // WGPUTextureView
 5336+} sg_wgpu_view_info;
 5337+
 5338+typedef struct sg_gl_buffer_info {
 5339+    uint32_t buf[SG_NUM_INFLIGHT_FRAMES];
 5340+    int active_slot;
 5341+} sg_gl_buffer_info;
 5342+
 5343+typedef struct sg_gl_image_info {
 5344+    uint32_t tex[SG_NUM_INFLIGHT_FRAMES];
 5345+    uint32_t tex_target;
 5346+    int active_slot;
 5347+} sg_gl_image_info;
 5348+
 5349+typedef struct sg_gl_sampler_info {
 5350+    uint32_t smp;
 5351+} sg_gl_sampler_info;
 5352+
 5353+typedef struct sg_gl_shader_info {
 5354+    uint32_t prog;
 5355+} sg_gl_shader_info;
 5356+
 5357+typedef struct sg_gl_view_info {
 5358+    uint32_t tex_view[SG_NUM_INFLIGHT_FRAMES];
 5359+    uint32_t msaa_render_buffer;
 5360+    uint32_t msaa_resolve_frame_buffer;
 5361+} sg_gl_view_info;
 5362+
 5363+// D3D11: return ID3D11Device
 5364+SOKOL_GFX_API_DECL const void* sg_d3d11_device(void);
 5365+// D3D11: return ID3D11DeviceContext
 5366+SOKOL_GFX_API_DECL const void* sg_d3d11_device_context(void);
 5367+// D3D11: get internal buffer resource objects
 5368+SOKOL_GFX_API_DECL sg_d3d11_buffer_info sg_d3d11_query_buffer_info(sg_buffer buf);
 5369+// D3D11: get internal image resource objects
 5370+SOKOL_GFX_API_DECL sg_d3d11_image_info sg_d3d11_query_image_info(sg_image img);
 5371+// D3D11: get internal sampler resource objects
 5372+SOKOL_GFX_API_DECL sg_d3d11_sampler_info sg_d3d11_query_sampler_info(sg_sampler smp);
 5373+// D3D11: get internal shader resource objects
 5374+SOKOL_GFX_API_DECL sg_d3d11_shader_info sg_d3d11_query_shader_info(sg_shader shd);
 5375+// D3D11: get internal pipeline resource objects
 5376+SOKOL_GFX_API_DECL sg_d3d11_pipeline_info sg_d3d11_query_pipeline_info(sg_pipeline pip);
 5377+// D3D11: get internal view resource objects
 5378+SOKOL_GFX_API_DECL sg_d3d11_view_info sg_d3d11_query_view_info(sg_view view);
 5379+
 5380+// Metal: return __bridge-casted MTLDevice
 5381+SOKOL_GFX_API_DECL const void* sg_mtl_device(void);
 5382+// Metal: return __bridge-casted MTLRenderCommandEncoder when inside render pass (otherwise zero)
 5383+SOKOL_GFX_API_DECL const void* sg_mtl_render_command_encoder(void);
 5384+// Metal: return __bridge-casted MTLComputeCommandEncoder when inside compute pass (otherwise zero)
 5385+SOKOL_GFX_API_DECL const void* sg_mtl_compute_command_encoder(void);
 5386+// Metal: return __bridge-casted MTLCommandQueue
 5387+SOKOL_GFX_API_DECL const void* sg_mtl_command_queue(void);
 5388+// Metal: get internal __bridge-casted buffer resource objects
 5389+SOKOL_GFX_API_DECL sg_mtl_buffer_info sg_mtl_query_buffer_info(sg_buffer buf);
 5390+// Metal: get internal __bridge-casted image resource objects
 5391+SOKOL_GFX_API_DECL sg_mtl_image_info sg_mtl_query_image_info(sg_image img);
 5392+// Metal: get internal __bridge-casted sampler resource objects
 5393+SOKOL_GFX_API_DECL sg_mtl_sampler_info sg_mtl_query_sampler_info(sg_sampler smp);
 5394+// Metal: get internal __bridge-casted shader resource objects
 5395+SOKOL_GFX_API_DECL sg_mtl_shader_info sg_mtl_query_shader_info(sg_shader shd);
 5396+// Metal: get internal __bridge-casted pipeline resource objects
 5397+SOKOL_GFX_API_DECL sg_mtl_pipeline_info sg_mtl_query_pipeline_info(sg_pipeline pip);
 5398+
 5399+// WebGPU: return WGPUDevice object
 5400+SOKOL_GFX_API_DECL const void* sg_wgpu_device(void);
 5401+// WebGPU: return WGPUQueue object
 5402+SOKOL_GFX_API_DECL const void* sg_wgpu_queue(void);
 5403+// WebGPU: return this frame's WGPUCommandEncoder
 5404+SOKOL_GFX_API_DECL const void* sg_wgpu_command_encoder(void);
 5405+// WebGPU: return WGPURenderPassEncoder of current pass (returns 0 when outside pass or in a compute pass)
 5406+SOKOL_GFX_API_DECL const void* sg_wgpu_render_pass_encoder(void);
 5407+// WebGPU: return WGPUComputePassEncoder of current pass (returns 0 when outside pass or in a render pass)
 5408+SOKOL_GFX_API_DECL const void* sg_wgpu_compute_pass_encoder(void);
 5409+// WebGPU: get internal buffer resource objects
 5410+SOKOL_GFX_API_DECL sg_wgpu_buffer_info sg_wgpu_query_buffer_info(sg_buffer buf);
 5411+// WebGPU: get internal image resource objects
 5412+SOKOL_GFX_API_DECL sg_wgpu_image_info sg_wgpu_query_image_info(sg_image img);
 5413+// WebGPU: get internal sampler resource objects
 5414+SOKOL_GFX_API_DECL sg_wgpu_sampler_info sg_wgpu_query_sampler_info(sg_sampler smp);
 5415+// WebGPU: get internal shader resource objects
 5416+SOKOL_GFX_API_DECL sg_wgpu_shader_info sg_wgpu_query_shader_info(sg_shader shd);
 5417+// WebGPU: get internal pipeline resource objects
 5418+SOKOL_GFX_API_DECL sg_wgpu_pipeline_info sg_wgpu_query_pipeline_info(sg_pipeline pip);
 5419+// WebGPU: get internal view resource objects
 5420+SOKOL_GFX_API_DECL sg_wgpu_view_info sg_wgpu_query_view_info(sg_view view);
 5421+
 5422+// GL: get internal buffer resource objects
 5423+SOKOL_GFX_API_DECL sg_gl_buffer_info sg_gl_query_buffer_info(sg_buffer buf);
 5424+// GL: get internal image resource objects
 5425+SOKOL_GFX_API_DECL sg_gl_image_info sg_gl_query_image_info(sg_image img);
 5426+// GL: get internal sampler resource objects
 5427+SOKOL_GFX_API_DECL sg_gl_sampler_info sg_gl_query_sampler_info(sg_sampler smp);
 5428+// GL: get internal shader resource objects
 5429+SOKOL_GFX_API_DECL sg_gl_shader_info sg_gl_query_shader_info(sg_shader shd);
 5430+// GL: get internal view resource objects
 5431+SOKOL_GFX_API_DECL sg_gl_view_info sg_gl_query_view_info(sg_view view);
 5432+
 5433+#ifdef __cplusplus
 5434+} // extern "C"
 5435+
 5436+// reference-based equivalents for c++
 5437+inline void sg_setup(const sg_desc& desc) { return sg_setup(&desc); }
 5438+
 5439+inline sg_buffer sg_make_buffer(const sg_buffer_desc& desc) { return sg_make_buffer(&desc); }
 5440+inline sg_image sg_make_image(const sg_image_desc& desc) { return sg_make_image(&desc); }
 5441+inline sg_sampler sg_make_sampler(const sg_sampler_desc& desc) { return sg_make_sampler(&desc); }
 5442+inline sg_shader sg_make_shader(const sg_shader_desc& desc) { return sg_make_shader(&desc); }
 5443+inline sg_pipeline sg_make_pipeline(const sg_pipeline_desc& desc) { return sg_make_pipeline(&desc); }
 5444+inline sg_view sg_make_view(const sg_view_desc& desc) { return sg_make_view(&desc); }
 5445+inline void sg_update_image(sg_image img, const sg_image_data& data) { return sg_update_image(img, &data); }
 5446+
 5447+inline void sg_begin_pass(const sg_pass& pass) { return sg_begin_pass(&pass); }
 5448+inline void sg_apply_bindings(const sg_bindings& bindings) { return sg_apply_bindings(&bindings); }
 5449+inline void sg_apply_uniforms(int ub_slot, const sg_range& data) { return sg_apply_uniforms(ub_slot, &data); }
 5450+
 5451+inline sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc& desc) { return sg_query_buffer_defaults(&desc); }
 5452+inline sg_image_desc sg_query_image_defaults(const sg_image_desc& desc) { return sg_query_image_defaults(&desc); }
 5453+inline sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc& desc) { return sg_query_sampler_defaults(&desc); }
 5454+inline sg_shader_desc sg_query_shader_defaults(const sg_shader_desc& desc) { return sg_query_shader_defaults(&desc); }
 5455+inline sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc& desc) { return sg_query_pipeline_defaults(&desc); }
 5456+inline sg_view_desc sg_query_view_defaults(const sg_view_desc& desc) { return sg_query_view_defaults(&desc); }
 5457+
 5458+inline void sg_init_buffer(sg_buffer buf, const sg_buffer_desc& desc) { return sg_init_buffer(buf, &desc); }
 5459+inline void sg_init_image(sg_image img, const sg_image_desc& desc) { return sg_init_image(img, &desc); }
 5460+inline void sg_init_sampler(sg_sampler smp, const sg_sampler_desc& desc) { return sg_init_sampler(smp, &desc); }
 5461+inline void sg_init_shader(sg_shader shd, const sg_shader_desc& desc) { return sg_init_shader(shd, &desc); }
 5462+inline void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc& desc) { return sg_init_pipeline(pip, &desc); }
 5463+inline void sg_init_view(sg_view view, const sg_view_desc& desc) { return sg_init_view(view, &desc); }
 5464+
 5465+inline void sg_update_buffer(sg_buffer buf_id, const sg_range& data) { return sg_update_buffer(buf_id, &data); }
 5466+inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_append_buffer(buf_id, &data); }
 5467+#endif
 5468+#endif // SOKOL_GFX_INCLUDED
 5469+
 5470+// ██ ███    ███ ██████  ██      ███████ ███    ███ ███████ ███    ██ ████████  █████  ████████ ██  ██████  ███    ██
 5471+// ██ ████  ████ ██   ██ ██      ██      ████  ████ ██      ████   ██    ██    ██   ██    ██    ██ ██    ██ ████   ██
 5472+// ██ ██ ████ ██ ██████  ██      █████   ██ ████ ██ █████   ██ ██  ██    ██    ███████    ██    ██ ██    ██ ██ ██  ██
 5473+// ██ ██  ██  ██ ██      ██      ██      ██  ██  ██ ██      ██  ██ ██    ██    ██   ██    ██    ██ ██    ██ ██  ██ ██
 5474+// ██ ██      ██ ██      ███████ ███████ ██      ██ ███████ ██   ████    ██    ██   ██    ██    ██  ██████  ██   ████
 5475+//
 5476+// >>implementation
 5477+#ifdef SOKOL_GFX_IMPL
 5478+#define SOKOL_GFX_IMPL_INCLUDED (1)
 5479+
 5480+#if !(defined(SOKOL_GLCORE)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_VULKAN)||defined(SOKOL_DUMMY_BACKEND))
 5481+#error "Please select a backend with SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU, SOKOL_VULKAN or SOKOL_DUMMY_BACKEND"
 5482+#endif
 5483+#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE)
 5484+#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sg_desc.allocator to override memory allocation functions"
 5485+#endif
 5486+
 5487+#include <stdlib.h> // malloc, free, qsort
 5488+#include <string.h> // memset
 5489+#include <float.h> // FLT_MAX
 5490+
 5491+#ifndef SOKOL_API_IMPL
 5492+    #define SOKOL_API_IMPL
 5493+#endif
 5494+#ifndef SOKOL_DEBUG
 5495+    #ifndef NDEBUG
 5496+        #define SOKOL_DEBUG
 5497+    #endif
 5498+#endif
 5499+#ifndef SOKOL_ASSERT
 5500+    #include <assert.h>
 5501+    #define SOKOL_ASSERT(c) assert(c)
 5502+#endif
 5503+#ifndef SOKOL_UNREACHABLE
 5504+    #define SOKOL_UNREACHABLE SOKOL_ASSERT(false)
 5505+#endif
 5506+
 5507+#ifndef _SOKOL_PRIVATE
 5508+    #if defined(__GNUC__) || defined(__clang__)
 5509+        #define _SOKOL_PRIVATE __attribute__((unused)) static
 5510+    #else
 5511+        #define _SOKOL_PRIVATE static
 5512+    #endif
 5513+#endif
 5514+
 5515+#ifndef _SOKOL_UNUSED
 5516+    #define _SOKOL_UNUSED(x) (void)(x)
 5517+#endif
 5518+
 5519+#if defined(SOKOL_TRACE_HOOKS)
 5520+#define _SG_TRACE_ARGS(fn, ...) if (_sg.hooks.fn) { _sg.hooks.fn(__VA_ARGS__, _sg.hooks.user_data); }
 5521+#define _SG_TRACE_NOARGS(fn) if (_sg.hooks.fn) { _sg.hooks.fn(_sg.hooks.user_data); }
 5522+#else
 5523+#define _SG_TRACE_ARGS(fn, ...)
 5524+#define _SG_TRACE_NOARGS(fn)
 5525+#endif
 5526+
 5527+#ifdef __cplusplus
 5528+#define _SG_STRUCT(TYPE, NAME) TYPE NAME = {}
 5529+#else
 5530+#define _SG_STRUCT(TYPE, NAME) TYPE NAME = {0}
 5531+#endif
 5532+
 5533+// default clear values
 5534+#ifndef SG_DEFAULT_CLEAR_RED
 5535+#define SG_DEFAULT_CLEAR_RED (0.5f)
 5536+#endif
 5537+#ifndef SG_DEFAULT_CLEAR_GREEN
 5538+#define SG_DEFAULT_CLEAR_GREEN (0.5f)
 5539+#endif
 5540+#ifndef SG_DEFAULT_CLEAR_BLUE
 5541+#define SG_DEFAULT_CLEAR_BLUE (0.5f)
 5542+#endif
 5543+#ifndef SG_DEFAULT_CLEAR_ALPHA
 5544+#define SG_DEFAULT_CLEAR_ALPHA (1.0f)
 5545+#endif
 5546+#ifndef SG_DEFAULT_CLEAR_DEPTH
 5547+#define SG_DEFAULT_CLEAR_DEPTH (1.0f)
 5548+#endif
 5549+#ifndef SG_DEFAULT_CLEAR_STENCIL
 5550+#define SG_DEFAULT_CLEAR_STENCIL (0)
 5551+#endif
 5552+
 5553+#ifdef _MSC_VER
 5554+#pragma warning(push)
 5555+#pragma warning(disable:4115)   // named type definition in parentheses
 5556+#pragma warning(disable:4505)   // unreferenced local function has been removed
 5557+#pragma warning(disable:4201)   // nonstandard extension used: nameless struct/union (needed by d3d11.h)
 5558+#pragma warning(disable:4054)   // 'type cast': from function pointer
 5559+#pragma warning(disable:4055)   // 'type cast': from data pointer
 5560+#endif
 5561+
 5562+#if defined(SOKOL_D3D11)
 5563+    #if defined(__GNUC__)
 5564+        #pragma GCC diagnostic push
 5565+        #pragma GCC diagnostic ignored "-Wunknown-pragmas"
 5566+    #endif
 5567+    #ifndef D3D11_NO_HELPERS
 5568+    #define D3D11_NO_HELPERS
 5569+    #endif
 5570+    #ifndef WIN32_LEAN_AND_MEAN
 5571+    #define WIN32_LEAN_AND_MEAN
 5572+    #endif
 5573+    #ifndef NOMINMAX
 5574+    #define NOMINMAX
 5575+    #endif
 5576+    #include <d3d11.h>
 5577+    #include <d3dcompiler.h>
 5578+    #pragma comment (lib, "kernel32")
 5579+    #pragma comment (lib, "user32")
 5580+    #pragma comment (lib, "dxgi")
 5581+    #pragma comment (lib, "d3d11")
 5582+    #if defined(__GNUC__)
 5583+        #pragma GCC diagnostic pop
 5584+    #endif
 5585+#elif defined(SOKOL_METAL)
 5586+    // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting
 5587+    #if !defined(__cplusplus)
 5588+        #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
 5589+            #error "sokol_gfx.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
 5590+        #endif
 5591+    #endif
 5592+    #include <TargetConditionals.h>
 5593+    #include <AvailabilityMacros.h>
 5594+    #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
 5595+        #define _SG_TARGET_MACOS (1)
 5596+    #else
 5597+        #define _SG_TARGET_IOS (1)
 5598+        #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
 5599+            #define _SG_TARGET_IOS_SIMULATOR (1)
 5600+        #endif
 5601+    #endif
 5602+    #import <Metal/Metal.h>
 5603+    #import <QuartzCore/CoreAnimation.h> // needed for CAMetalDrawable
 5604+#elif defined(SOKOL_WGPU)
 5605+    #include <webgpu/webgpu.h>
 5606+    #if defined(__EMSCRIPTEN__)
 5607+        #include <emscripten/emscripten.h>
 5608+    #endif
 5609+#elif defined(SOKOL_VULKAN)
 5610+    #include <vulkan/vulkan.h>
 5611+#elif defined(SOKOL_GLCORE) || defined(SOKOL_GLES3)
 5612+    #define _SOKOL_ANY_GL (1)
 5613+
 5614+    // include platform specific GL headers (or on Win32: use an embedded GL loader)
 5615+    #if !defined(SOKOL_EXTERNAL_GL_LOADER)
 5616+        #if defined(_WIN32)
 5617+            #if defined(SOKOL_GLCORE)
 5618+                #define _SOKOL_USE_WIN32_GL_LOADER (1)
 5619+                #ifndef WIN32_LEAN_AND_MEAN
 5620+                #define WIN32_LEAN_AND_MEAN
 5621+                #endif
 5622+                #ifndef NOMINMAX
 5623+                #define NOMINMAX
 5624+                #endif
 5625+                #include <windows.h>
 5626+                #pragma comment (lib, "kernel32")   // GetProcAddress()
 5627+            #endif
 5628+        #elif defined(__APPLE__)
 5629+            #include <TargetConditionals.h>
 5630+            #ifndef GL_SILENCE_DEPRECATION
 5631+                #define GL_SILENCE_DEPRECATION
 5632+            #endif
 5633+            #ifndef GLES_SILENCE_DEPRECATION
 5634+                #define GLES_SILENCE_DEPRECATION
 5635+            #endif
 5636+            #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
 5637+                #if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
 5638+                    #include <OpenGL/gl3.h>
 5639+                #else
 5640+                    #include <OpenGLES/ES3/gl.h>
 5641+                    #include <OpenGLES/ES3/glext.h>
 5642+                #endif
 5643+            #else
 5644+                #include <OpenGL/gl3.h>
 5645+            #endif
 5646+        #elif defined(__EMSCRIPTEN__)
 5647+            #if defined(SOKOL_GLES3)
 5648+                #include <GLES3/gl3.h>
 5649+            #endif
 5650+        #elif defined(__ANDROID__)
 5651+            #if __ANDROID_API__ >= 24
 5652+                #include <GLES3/gl32.h>
 5653+            #else
 5654+                #include <GLES3/gl31.h>
 5655+            #endif
 5656+        #elif defined(__linux__) || defined(__unix__)
 5657+            #if defined(SOKOL_GLCORE)
 5658+                #define GL_GLEXT_PROTOTYPES
 5659+                #include <GL/gl.h>
 5660+            #else
 5661+                #include <GLES3/gl32.h>
 5662+                #include <GLES3/gl3ext.h>
 5663+            #endif
 5664+        #endif
 5665+    #endif
 5666+
 5667+    // broad GL feature availability defines (DON'T merge this into the above ifdef-block!)
 5668+    #if defined(_WIN32)
 5669+        #define _SOKOL_GL_HAS_COLORMASKI (1)
 5670+        #if defined(GL_VERSION_4_3) || defined(_SOKOL_USE_WIN32_GL_LOADER)
 5671+            #define _SOKOL_GL_HAS_COMPUTE (1)
 5672+            #define _SOKOL_GL_HAS_TEXVIEWS (1)
 5673+        #endif
 5674+        #if defined(GL_VERSION_4_2) || defined(_SOKOL_USE_WIN32_GL_LOADER)
 5675+            #define _SOKOL_GL_HAS_TEXSTORAGE (1)
 5676+            #define _SOKOL_GL_HAS_BASEINSTANCE (1)
 5677+        #endif
 5678+        #if defined(GL_VERSION_3_3) || defined(_SOKOL_USE_WIN32_GL_LOADER)
 5679+            #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1)
 5680+        #endif
 5681+        #if defined(GL_VERSION_3_2) || defined(_SOKOL_USE_WIN32_GL_LOADER)
 5682+            #define _SOKOL_GL_HAS_BASEVERTEX (1)
 5683+        #endif
 5684+    #elif defined(__APPLE__)
 5685+        #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
 5686+            #if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
 5687+                #define _SOKOL_GL_HAS_COLORMASKI (1)
 5688+                #define _SOKOL_GL_HAS_BASEVERTEX (1)
 5689+                #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1)
 5690+            #else
 5691+                #define _SOKOL_GL_HAS_TEXSTORAGE (1)
 5692+            #endif
 5693+        #else
 5694+            #define _SOKOL_GL_HAS_COLORMASKI (1)
 5695+            #define _SOKOL_GL_HAS_BASEVERTEX (1)
 5696+            #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1)
 5697+        #endif
 5698+    #elif defined(__EMSCRIPTEN__)
 5699+        #define _SOKOL_GL_HAS_TEXSTORAGE (1)
 5700+    #elif defined(__ANDROID__)
 5701+        #define _SOKOL_GL_HAS_COMPUTE (1)
 5702+        #define _SOKOL_GL_HAS_TEXSTORAGE (1)
 5703+        #if defined(GL_ES_VERSION_3_2)
 5704+            #define _SOKOL_GL_HAS_COLORMASKI (1)
 5705+        #endif
 5706+    #elif defined(__linux__) || defined(__unix__)
 5707+        #define _SOKOL_GL_HAS_COLORMASKI (1)
 5708+        #if defined(SOKOL_GLCORE)
 5709+            #if defined(GL_VERSION_4_3)
 5710+                #define _SOKOL_GL_HAS_COMPUTE (1)
 5711+                #define _SOKOL_GL_HAS_TEXVIEWS (1)
 5712+            #endif
 5713+            #if defined(GL_VERSION_4_2)
 5714+                #define _SOKOL_GL_HAS_TEXSTORAGE (1)
 5715+                #define _SOKOL_GL_HAS_BASEINSTANCE (1)
 5716+            #endif
 5717+            #if defined(GL_VERSION_3_3)
 5718+                #define _SOKOL_GL_HAS_DUALSOURCEBLENDING (1)
 5719+            #endif
 5720+            #if defined(GL_VERSION_3_2)
 5721+                #define _SOKOL_GL_HAS_BASEVERTEX (1)
 5722+            #endif
 5723+        #else
 5724+            #define _SOKOL_GL_HAS_COMPUTE (1)
 5725+            #define _SOKOL_GL_HAS_TEXSTORAGE (1)
 5726+            #define _SOKOL_GL_HAS_BASEVERTEX (1)
 5727+        #endif
 5728+    #endif
 5729+
 5730+    // optional GL loader definitions (only on Win32)
 5731+    #if defined(_SOKOL_USE_WIN32_GL_LOADER)
 5732+        #define __gl_h_ 1
 5733+        #define __gl32_h_ 1
 5734+        #define __gl31_h_ 1
 5735+        #define __GL_H__ 1
 5736+        #define __glext_h_ 1
 5737+        #define __GLEXT_H_ 1
 5738+        #define __gltypes_h_ 1
 5739+        #define __glcorearb_h_ 1
 5740+        #define __gl_glcorearb_h_ 1
 5741+        #define GL_APIENTRY APIENTRY
 5742+
 5743+        typedef unsigned int  GLenum;
 5744+        typedef unsigned int  GLuint;
 5745+        typedef int  GLsizei;
 5746+        typedef char  GLchar;
 5747+        typedef ptrdiff_t  GLintptr;
 5748+        typedef ptrdiff_t  GLsizeiptr;
 5749+        typedef double  GLclampd;
 5750+        typedef unsigned short  GLushort;
 5751+        typedef unsigned char  GLubyte;
 5752+        typedef unsigned char  GLboolean;
 5753+        typedef uint64_t  GLuint64;
 5754+        typedef double  GLdouble;
 5755+        typedef unsigned short  GLhalf;
 5756+        typedef float  GLclampf;
 5757+        typedef unsigned int  GLbitfield;
 5758+        typedef signed char  GLbyte;
 5759+        typedef short  GLshort;
 5760+        typedef void  GLvoid;
 5761+        typedef int64_t  GLint64;
 5762+        typedef float  GLfloat;
 5763+        typedef int  GLint;
 5764+        #define GL_INT_2_10_10_10_REV 0x8D9F
 5765+        #define GL_R32F 0x822E
 5766+        #define GL_PROGRAM_POINT_SIZE 0x8642
 5767+        #define GL_DEPTH_ATTACHMENT 0x8D00
 5768+        #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
 5769+        #define GL_COLOR_ATTACHMENT0 0x8CE0
 5770+        #define GL_R16F 0x822D
 5771+        #define GL_DRAW_FRAMEBUFFER 0x8CA9
 5772+        #define GL_FRAMEBUFFER_COMPLETE 0x8CD5
 5773+        #define GL_NUM_EXTENSIONS 0x821D
 5774+        #define GL_INFO_LOG_LENGTH 0x8B84
 5775+        #define GL_VERTEX_SHADER 0x8B31
 5776+        #define GL_INCR 0x1E02
 5777+        #define GL_DYNAMIC_DRAW 0x88E8
 5778+        #define GL_STATIC_DRAW 0x88E4
 5779+        #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
 5780+        #define GL_TEXTURE_CUBE_MAP 0x8513
 5781+        #define GL_FUNC_SUBTRACT 0x800A
 5782+        #define GL_FUNC_REVERSE_SUBTRACT 0x800B
 5783+        #define GL_CONSTANT_COLOR 0x8001
 5784+        #define GL_DECR_WRAP 0x8508
 5785+        #define GL_R8 0x8229
 5786+        #define GL_LINEAR_MIPMAP_LINEAR 0x2703
 5787+        #define GL_ELEMENT_ARRAY_BUFFER 0x8893
 5788+        #define GL_SHORT 0x1402
 5789+        #define GL_DEPTH_TEST 0x0B71
 5790+        #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
 5791+        #define GL_LINK_STATUS 0x8B82
 5792+        #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
 5793+        #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E
 5794+        #define GL_RGBA16F 0x881A
 5795+        #define GL_CONSTANT_ALPHA 0x8003
 5796+        #define GL_READ_FRAMEBUFFER 0x8CA8
 5797+        #define GL_TEXTURE0 0x84C0
 5798+        #define GL_TEXTURE_MIN_LOD 0x813A
 5799+        #define GL_CLAMP_TO_EDGE 0x812F
 5800+        #define GL_UNSIGNED_SHORT_5_6_5 0x8363
 5801+        #define GL_TEXTURE_WRAP_R 0x8072
 5802+        #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
 5803+        #define GL_NEAREST_MIPMAP_NEAREST 0x2700
 5804+        #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
 5805+        #define GL_SRC_ALPHA_SATURATE 0x0308
 5806+        #define GL_STREAM_DRAW 0x88E0
 5807+        #define GL_ONE 1
 5808+        #define GL_NEAREST_MIPMAP_LINEAR 0x2702
 5809+        #define GL_RGB10_A2 0x8059
 5810+        #define GL_RGBA8 0x8058
 5811+        #define GL_SRGB8_ALPHA8 0x8C43
 5812+        #define GL_RGBA4 0x8056
 5813+        #define GL_RGB8 0x8051
 5814+        #define GL_ARRAY_BUFFER 0x8892
 5815+        #define GL_STENCIL 0x1802
 5816+        #define GL_TEXTURE_2D 0x0DE1
 5817+        #define GL_DEPTH 0x1801
 5818+        #define GL_FRONT 0x0404
 5819+        #define GL_STENCIL_BUFFER_BIT 0x00000400
 5820+        #define GL_REPEAT 0x2901
 5821+        #define GL_RGBA 0x1908
 5822+        #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
 5823+        #define GL_DECR 0x1E03
 5824+        #define GL_FRAGMENT_SHADER 0x8B30
 5825+        #define GL_COMPUTE_SHADER 0x91B9
 5826+        #define GL_FLOAT 0x1406
 5827+        #define GL_TEXTURE_MAX_LOD 0x813B
 5828+        #define GL_DEPTH_COMPONENT 0x1902
 5829+        #define GL_ONE_MINUS_DST_ALPHA 0x0305
 5830+        #define GL_COLOR 0x1800
 5831+        #define GL_TEXTURE_2D_ARRAY 0x8C1A
 5832+        #define GL_TRIANGLES 0x0004
 5833+        #define GL_UNSIGNED_BYTE 0x1401
 5834+        #define GL_TEXTURE_MAG_FILTER 0x2800
 5835+        #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
 5836+        #define GL_NONE 0
 5837+        #define GL_SRC_COLOR 0x0300
 5838+        #define GL_SRC1_ALPHA 0x8589
 5839+        #define GL_SRC1_COLOR 0x88F9
 5840+        #define GL_ONE_MINUS_SRC1_ALPHA 0x88FB
 5841+        #define GL_ONE_MINUS_SRC1_COLOR 0x88FA
 5842+        #define GL_BYTE 0x1400
 5843+        #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
 5844+        #define GL_LINE_STRIP 0x0003
 5845+        #define GL_TEXTURE_3D 0x806F
 5846+        #define GL_CW 0x0900
 5847+        #define GL_LINEAR 0x2601
 5848+        #define GL_RENDERBUFFER 0x8D41
 5849+        #define GL_GEQUAL 0x0206
 5850+        #define GL_COLOR_BUFFER_BIT 0x00004000
 5851+        #define GL_RGBA32F 0x8814
 5852+        #define GL_BLEND 0x0BE2
 5853+        #define GL_ONE_MINUS_SRC_ALPHA 0x0303
 5854+        #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
 5855+        #define GL_TEXTURE_WRAP_T 0x2803
 5856+        #define GL_TEXTURE_WRAP_S 0x2802
 5857+        #define GL_TEXTURE_MIN_FILTER 0x2801
 5858+        #define GL_LINEAR_MIPMAP_NEAREST 0x2701
 5859+        #define GL_EXTENSIONS 0x1F03
 5860+        #define GL_NO_ERROR 0
 5861+        #define GL_REPLACE 0x1E01
 5862+        #define GL_KEEP 0x1E00
 5863+        #define GL_CCW 0x0901
 5864+        #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
 5865+        #define GL_RGB 0x1907
 5866+        #define GL_TRIANGLE_STRIP 0x0005
 5867+        #define GL_FALSE 0
 5868+        #define GL_ZERO 0
 5869+        #define GL_CULL_FACE 0x0B44
 5870+        #define GL_INVERT 0x150A
 5871+        #define GL_INT 0x1404
 5872+        #define GL_UNSIGNED_INT 0x1405
 5873+        #define GL_UNSIGNED_SHORT 0x1403
 5874+        #define GL_NEAREST 0x2600
 5875+        #define GL_SCISSOR_TEST 0x0C11
 5876+        #define GL_LEQUAL 0x0203
 5877+        #define GL_STENCIL_TEST 0x0B90
 5878+        #define GL_DITHER 0x0BD0
 5879+        #define GL_DEPTH_COMPONENT32F 0x8CAC
 5880+        #define GL_EQUAL 0x0202
 5881+        #define GL_FRAMEBUFFER 0x8D40
 5882+        #define GL_RGB5 0x8050
 5883+        #define GL_LINES 0x0001
 5884+        #define GL_DEPTH_BUFFER_BIT 0x00000100
 5885+        #define GL_SRC_ALPHA 0x0302
 5886+        #define GL_INCR_WRAP 0x8507
 5887+        #define GL_LESS 0x0201
 5888+        #define GL_MULTISAMPLE 0x809D
 5889+        #define GL_FRAMEBUFFER_BINDING 0x8CA6
 5890+        #define GL_BACK 0x0405
 5891+        #define GL_ALWAYS 0x0207
 5892+        #define GL_FUNC_ADD 0x8006
 5893+        #define GL_ONE_MINUS_DST_COLOR 0x0307
 5894+        #define GL_NOTEQUAL 0x0205
 5895+        #define GL_DST_COLOR 0x0306
 5896+        #define GL_COMPILE_STATUS 0x8B81
 5897+        #define GL_RED 0x1903
 5898+        #define GL_DST_ALPHA 0x0304
 5899+        #define GL_RGB5_A1 0x8057
 5900+        #define GL_GREATER 0x0204
 5901+        #define GL_POLYGON_OFFSET_FILL 0x8037
 5902+        #define GL_TRUE 1
 5903+        #define GL_NEVER 0x0200
 5904+        #define GL_POINTS 0x0000
 5905+        #define GL_ONE_MINUS_SRC_COLOR 0x0301
 5906+        #define GL_MIRRORED_REPEAT 0x8370
 5907+        #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D
 5908+        #define GL_R11F_G11F_B10F 0x8C3A
 5909+        #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
 5910+        #define GL_RGB9_E5 0x8C3D
 5911+        #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
 5912+        #define GL_RGBA32UI 0x8D70
 5913+        #define GL_RGB32UI 0x8D71
 5914+        #define GL_RGBA16UI 0x8D76
 5915+        #define GL_RGB16UI 0x8D77
 5916+        #define GL_RGBA8UI 0x8D7C
 5917+        #define GL_RGB8UI 0x8D7D
 5918+        #define GL_RGBA32I 0x8D82
 5919+        #define GL_RGB32I 0x8D83
 5920+        #define GL_RGBA16I 0x8D88
 5921+        #define GL_RGB16I 0x8D89
 5922+        #define GL_RGBA8I 0x8D8E
 5923+        #define GL_RGB8I 0x8D8F
 5924+        #define GL_RED_INTEGER 0x8D94
 5925+        #define GL_RG 0x8227
 5926+        #define GL_RG_INTEGER 0x8228
 5927+        #define GL_R8 0x8229
 5928+        #define GL_R16 0x822A
 5929+        #define GL_RG8 0x822B
 5930+        #define GL_RG16 0x822C
 5931+        #define GL_R16F 0x822D
 5932+        #define GL_R32F 0x822E
 5933+        #define GL_RG16F 0x822F
 5934+        #define GL_RG32F 0x8230
 5935+        #define GL_R8I 0x8231
 5936+        #define GL_R8UI 0x8232
 5937+        #define GL_R16I 0x8233
 5938+        #define GL_R16UI 0x8234
 5939+        #define GL_R32I 0x8235
 5940+        #define GL_R32UI 0x8236
 5941+        #define GL_RG8I 0x8237
 5942+        #define GL_RG8UI 0x8238
 5943+        #define GL_RG16I 0x8239
 5944+        #define GL_RG16UI 0x823A
 5945+        #define GL_RG32I 0x823B
 5946+        #define GL_RG32UI 0x823C
 5947+        #define GL_RGBA_INTEGER 0x8D99
 5948+        #define GL_R8_SNORM 0x8F94
 5949+        #define GL_RG8_SNORM 0x8F95
 5950+        #define GL_RGB8_SNORM 0x8F96
 5951+        #define GL_RGBA8_SNORM 0x8F97
 5952+        #define GL_R16_SNORM 0x8F98
 5953+        #define GL_RG16_SNORM 0x8F99
 5954+        #define GL_RGB16_SNORM 0x8F9A
 5955+        #define GL_RGBA16_SNORM 0x8F9B
 5956+        #define GL_RGBA16 0x805B
 5957+        #define GL_MAX_TEXTURE_SIZE 0x0D33
 5958+        #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C
 5959+        #define GL_MAX_3D_TEXTURE_SIZE 0x8073
 5960+        #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
 5961+        #define GL_MAX_VERTEX_ATTRIBS 0x8869
 5962+        #define GL_CLAMP_TO_BORDER 0x812D
 5963+        #define GL_TEXTURE_BORDER_COLOR 0x1004
 5964+        #define GL_CURRENT_PROGRAM 0x8B8D
 5965+        #define GL_MAX_VERTEX_UNIFORM_COMPONENTS  0x8B4A
 5966+        #define GL_UNPACK_ALIGNMENT 0x0CF5
 5967+        #define GL_FRAMEBUFFER_SRGB 0x8DB9
 5968+        #define GL_TEXTURE_COMPARE_MODE 0x884C
 5969+        #define GL_TEXTURE_COMPARE_FUNC 0x884D
 5970+        #define GL_COMPARE_REF_TO_TEXTURE 0x884E
 5971+        #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F
 5972+        #define GL_TEXTURE_MAX_LEVEL 0x813D
 5973+        #define GL_FRAMEBUFFER_UNDEFINED 0x8219
 5974+        #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
 5975+        #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
 5976+        #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
 5977+        #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
 5978+        #define GL_MAJOR_VERSION 0x821B
 5979+        #define GL_MINOR_VERSION 0x821C
 5980+        #define GL_TEXTURE_2D_MULTISAMPLE 0x9100
 5981+        #define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102
 5982+        #define GL_SHADER_STORAGE_BARRIER_BIT 0x2000
 5983+        #define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001
 5984+        #define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002
 5985+        #define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008
 5986+        #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020
 5987+        #define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400
 5988+        #define GL_MIN 0x8007
 5989+        #define GL_MAX 0x8008
 5990+        #define GL_WRITE_ONLY 0x88B9
 5991+        #define GL_READ_WRITE 0x88BA
 5992+        #define GL_MAX_DRAW_BUFFERS 0x8824
 5993+        #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
 5994+        #define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD
 5995+        #define GL_MAX_IMAGE_UNITS 0x8F38
 5996+        #define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD
 5997+        #define GL_DEPTH32F_STENCIL8 0x8CAD
 5998+    #endif
 5999+
 6000+    #ifndef GL_UNSIGNED_INT_2_10_10_10_REV
 6001+    #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
 6002+    #endif
 6003+    #ifndef GL_UNSIGNED_INT_24_8
 6004+    #define GL_UNSIGNED_INT_24_8 0x84FA
 6005+    #endif
 6006+    #ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
 6007+    #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
 6008+    #endif
 6009+    #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
 6010+    #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
 6011+    #endif
 6012+    #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
 6013+    #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
 6014+    #endif
 6015+    #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
 6016+    #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
 6017+    #endif
 6018+    #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
 6019+    #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
 6020+    #endif
 6021+    #ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
 6022+    #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F
 6023+    #endif
 6024+    #ifndef GL_COMPRESSED_RED_RGTC1
 6025+    #define GL_COMPRESSED_RED_RGTC1 0x8DBB
 6026+    #endif
 6027+    #ifndef GL_COMPRESSED_SIGNED_RED_RGTC1
 6028+    #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC
 6029+    #endif
 6030+    #ifndef GL_COMPRESSED_RED_GREEN_RGTC2
 6031+    #define GL_COMPRESSED_RED_GREEN_RGTC2 0x8DBD
 6032+    #endif
 6033+    #ifndef GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2
 6034+    #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 0x8DBE
 6035+    #endif
 6036+    #ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB
 6037+    #define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C
 6038+    #endif
 6039+    #ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB
 6040+    #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D
 6041+    #endif
 6042+    #ifndef GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB
 6043+    #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E
 6044+    #endif
 6045+    #ifndef GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB
 6046+    #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F
 6047+    #endif
 6048+    #ifndef GL_COMPRESSED_RGB8_ETC2
 6049+    #define GL_COMPRESSED_RGB8_ETC2 0x9274
 6050+    #endif
 6051+    #ifndef GL_COMPRESSED_SRGB8_ETC2
 6052+    #define GL_COMPRESSED_SRGB8_ETC2 0x9275
 6053+    #endif
 6054+    #ifndef GL_COMPRESSED_RGBA8_ETC2_EAC
 6055+    #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
 6056+    #endif
 6057+    #ifndef GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
 6058+    #define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279
 6059+    #endif
 6060+    #ifndef GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2
 6061+    #define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276
 6062+    #endif
 6063+    #ifndef GL_COMPRESSED_R11_EAC
 6064+    #define GL_COMPRESSED_R11_EAC 0x9270
 6065+    #endif
 6066+    #ifndef GL_COMPRESSED_SIGNED_R11_EAC
 6067+    #define GL_COMPRESSED_SIGNED_R11_EAC 0x9271
 6068+    #endif
 6069+    #ifndef GL_COMPRESSED_RG11_EAC
 6070+    #define GL_COMPRESSED_RG11_EAC 0x9272
 6071+    #endif
 6072+    #ifndef GL_COMPRESSED_SIGNED_RG11_EAC
 6073+    #define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273
 6074+    #endif
 6075+    #ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR
 6076+    #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0
 6077+    #endif
 6078+    #ifndef GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR
 6079+    #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0
 6080+    #endif
 6081+    #ifndef GL_DEPTH24_STENCIL8
 6082+    #define GL_DEPTH24_STENCIL8 0x88F0
 6083+    #endif
 6084+    #ifndef GL_HALF_FLOAT
 6085+    #define GL_HALF_FLOAT 0x140B
 6086+    #endif
 6087+    #ifndef GL_DEPTH_STENCIL
 6088+    #define GL_DEPTH_STENCIL 0x84F9
 6089+    #endif
 6090+    #ifndef GL_LUMINANCE
 6091+    #define GL_LUMINANCE 0x1909
 6092+    #endif
 6093+    #ifndef GL_COMPUTE_SHADER
 6094+    #define GL_COMPUTE_SHADER 0x91B9
 6095+    #endif
 6096+    #ifndef _SG_GL_CHECK_ERROR
 6097+        #if defined(__EMSCRIPTEN__)
 6098+            // generally turn off glGetError() on WASM, it's a too big performance hit
 6099+            // and WebGL provides much better diagnostics anyway
 6100+            #define _SG_GL_CHECK_ERROR()
 6101+        #elif defined(SOKOL_DEBUG)
 6102+            // make sure that glGetError() is only called in debug mode
 6103+            #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); }
 6104+        #else
 6105+            #define _SG_GL_CHECK_ERROR()
 6106+        #endif
 6107+    #endif
 6108+    // make some GL constants generally available to simplify compilation,
 6109+    // use of those constants will be filtered by runtime flags
 6110+    #ifndef GL_SHADER_STORAGE_BUFFER
 6111+    #define GL_SHADER_STORAGE_BUFFER 0x90D2
 6112+    #endif
 6113+#endif
 6114+
 6115+#if defined(SOKOL_GLES3)
 6116+    // on WebGL2, GL_FRAMEBUFFER_UNDEFINED technically doesn't exist (it is defined
 6117+    // in the Emscripten headers, but may not exist in other WebGL2 shims)
 6118+    // see: https://github.com/floooh/sokol/pull/933
 6119+    #ifndef GL_FRAMEBUFFER_UNDEFINED
 6120+    #define GL_FRAMEBUFFER_UNDEFINED 0x8219
 6121+    #endif
 6122+#endif
 6123+
 6124+// ███████ ████████ ██████  ██    ██  ██████ ████████ ███████
 6125+// ██         ██    ██   ██ ██    ██ ██         ██    ██
 6126+// ███████    ██    ██████  ██    ██ ██         ██    ███████
 6127+//      ██    ██    ██   ██ ██    ██ ██         ██         ██
 6128+// ███████    ██    ██   ██  ██████   ██████    ██    ███████
 6129+//
 6130+// >>structs
 6131+
 6132+typedef struct { int x, y, w, h; } _sg_recti_t;
 6133+typedef struct { int width, height; } _sg_dimi_t;
 6134+
 6135+// resource pool slots
 6136+typedef struct {
 6137+    uint32_t id;
 6138+    uint32_t uninit_count;
 6139+    sg_resource_state state;
 6140+} _sg_slot_t;
 6141+
 6142+// resource pool housekeeping struct
 6143+typedef struct {
 6144+    int size;
 6145+    int queue_top;
 6146+    uint32_t* gen_ctrs;
 6147+    int* free_queue;
 6148+} _sg_pool_t;
 6149+
 6150+// resource hazard tracking struct
 6151+typedef struct {
 6152+    int num_slots;
 6153+    int cur_slot;
 6154+    uint32_t* slots;        // tracked unique resource ids
 6155+    uint32_t occupy_num_bytes;  // size of occupy_bits array in bytes
 6156+    uint8_t* occupy_bits;   // one set bit for each unique resource (idx = (id & 0xFFFF) >> 3)
 6157+} _sg_track_t;
 6158+
 6159+// resource func forward decls
 6160+struct _sg_buffer_s;
 6161+struct _sg_image_s;
 6162+struct _sg_sampler_s;
 6163+struct _sg_shader_s;
 6164+struct _sg_pipeline_s;
 6165+struct _sg_view_s;
 6166+
 6167+// a general resource slot reference useful for caches
 6168+typedef struct _sg_sref_s {
 6169+    uint32_t id;
 6170+    uint32_t uninit_count;
 6171+} _sg_sref_t;
 6172+
 6173+// safe (in debug mode) internal resource references
 6174+typedef struct _sg_buffer_ref_s {
 6175+    struct _sg_buffer_s* ptr;
 6176+    _sg_sref_t sref;
 6177+} _sg_buffer_ref_t;
 6178+
 6179+typedef struct _sg_image_ref_s {
 6180+    struct _sg_image_s* ptr;
 6181+    _sg_sref_t sref;
 6182+} _sg_image_ref_t;
 6183+
 6184+typedef struct _sg_sampler_ref_t {
 6185+    struct _sg_sampler_s* ptr;
 6186+    _sg_sref_t sref;
 6187+} _sg_sampler_ref_t;
 6188+
 6189+typedef struct _sg_shader_ref_s {
 6190+    struct _sg_shader_s* ptr;
 6191+    _sg_sref_t sref;
 6192+} _sg_shader_ref_t;
 6193+
 6194+typedef struct _sg_pipeline_ref_s {
 6195+    struct _sg_pipeline_s* ptr;
 6196+    _sg_sref_t sref;
 6197+} _sg_pipeline_ref_t;
 6198+
 6199+typedef struct _sg_view_ref_s {
 6200+    struct _sg_view_s* ptr;
 6201+    _sg_sref_t sref;
 6202+} _sg_view_ref_t;
 6203+
 6204+// constants
 6205+enum {
 6206+    _SG_STRING_SIZE = 32,
 6207+    _SG_SLOT_SHIFT = 16,
 6208+    _SG_SLOT_MASK = (1<<_SG_SLOT_SHIFT)-1,
 6209+    _SG_MAX_POOL_SIZE = (1<<_SG_SLOT_SHIFT),
 6210+    _SG_DEFAULT_BUFFER_POOL_SIZE = 128,
 6211+    _SG_DEFAULT_IMAGE_POOL_SIZE = 128,
 6212+    _SG_DEFAULT_SAMPLER_POOL_SIZE = 64,
 6213+    _SG_DEFAULT_SHADER_POOL_SIZE = 32,
 6214+    _SG_DEFAULT_PIPELINE_POOL_SIZE = 64,
 6215+    _SG_DEFAULT_VIEW_POOL_SIZE = 256,
 6216+    _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024,
 6217+    _SG_DEFAULT_MAX_COMMIT_LISTENERS = 1024,
 6218+    _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE = 1024,
 6219+    _SG_DEFAULT_VK_COPY_STAGING_SIZE = (4 * 1024 * 1024),
 6220+    _SG_DEFAULT_VK_STREAM_STAGING_SIZE = (16 * 1024 * 1024),
 6221+    _SG_DEFAULT_VK_DESCRIPTOR_BUFFER_SIZE = (16 * 1024 * 1024),
 6222+    _SG_MAX_STORAGEBUFFER_BINDINGS_PER_STAGE = SG_MAX_VIEW_BINDSLOTS,
 6223+    _SG_MAX_STORAGEIMAGE_BINDINGS_PER_STAGE = SG_MAX_VIEW_BINDSLOTS,
 6224+    _SG_MAX_TEXTURE_BINDINGS_PER_STAGE = SG_MAX_VIEW_BINDSLOTS,
 6225+    _SG_MAX_UNIFORMBLOCK_BINDINGS_PER_STAGE = 8,
 6226+};
 6227+
 6228+// fixed-size string
 6229+typedef struct {
 6230+    char buf[_SG_STRING_SIZE];
 6231+} _sg_str_t;
 6232+
 6233+typedef struct {
 6234+    int size;
 6235+    int append_pos;
 6236+    bool append_overflow;
 6237+    uint32_t update_frame_index;
 6238+    uint32_t append_frame_index;
 6239+    int num_slots;
 6240+    int active_slot;
 6241+    sg_buffer_usage usage;
 6242+} _sg_buffer_common_t;
 6243+
 6244+typedef struct {
 6245+    uint32_t upd_frame_index;
 6246+    int num_slots;
 6247+    int active_slot;
 6248+    sg_image_type type;
 6249+    int width;
 6250+    int height;
 6251+    int num_slices;
 6252+    int num_mipmaps;
 6253+    sg_image_usage usage;
 6254+    sg_pixel_format pixel_format;
 6255+    int sample_count;
 6256+} _sg_image_common_t;
 6257+
 6258+typedef struct {
 6259+    sg_filter min_filter;
 6260+    sg_filter mag_filter;
 6261+    sg_filter mipmap_filter;
 6262+    sg_wrap wrap_u;
 6263+    sg_wrap wrap_v;
 6264+    sg_wrap wrap_w;
 6265+    float min_lod;
 6266+    float max_lod;
 6267+    sg_border_color border_color;
 6268+    sg_compare_func compare;
 6269+    uint32_t max_anisotropy;
 6270+} _sg_sampler_common_t;
 6271+
 6272+typedef struct {
 6273+    sg_shader_attr_base_type base_type;
 6274+} _sg_shader_attr_t;
 6275+
 6276+typedef struct {
 6277+    sg_shader_stage stage;
 6278+    uint32_t size;
 6279+} _sg_shader_uniform_block_t;
 6280+
 6281+typedef struct {
 6282+    sg_shader_stage stage;
 6283+    sg_view_type view_type;
 6284+    sg_image_type image_type;
 6285+    sg_pixel_format access_format;
 6286+    sg_image_sample_type sample_type;
 6287+    bool sbuf_readonly;
 6288+    bool simg_writeonly;
 6289+    bool multisampled;
 6290+} _sg_shader_view_t;
 6291+
 6292+typedef struct {
 6293+    sg_shader_stage stage;
 6294+    sg_sampler_type sampler_type;
 6295+} _sg_shader_sampler_t;
 6296+
 6297+typedef struct {
 6298+    sg_shader_stage stage;
 6299+    uint8_t view_slot;
 6300+    uint8_t sampler_slot;
 6301+} _sg_shader_texture_sampler_t;
 6302+
 6303+typedef struct {
 6304+    uint32_t required_bindings_and_uniforms;
 6305+    bool is_compute;
 6306+    _sg_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES];
 6307+    _sg_shader_uniform_block_t uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6308+    _sg_shader_view_t views[SG_MAX_VIEW_BINDSLOTS];
 6309+    _sg_shader_sampler_t samplers[SG_MAX_SAMPLER_BINDSLOTS];
 6310+    _sg_shader_texture_sampler_t texture_samplers[SG_MAX_TEXTURE_SAMPLER_PAIRS];
 6311+} _sg_shader_common_t;
 6312+
 6313+typedef struct {
 6314+    bool vertex_buffer_layout_active[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 6315+    bool use_instanced_draw;
 6316+    bool is_compute;
 6317+    uint32_t required_bindings_and_uniforms;
 6318+    _sg_shader_ref_t shader;
 6319+    sg_vertex_layout_state layout;
 6320+    sg_depth_state depth;
 6321+    sg_stencil_state stencil;
 6322+    int color_count;
 6323+    sg_color_target_state colors[SG_MAX_COLOR_ATTACHMENTS];
 6324+    sg_primitive_type primitive_type;
 6325+    sg_index_type index_type;
 6326+    sg_cull_mode cull_mode;
 6327+    sg_face_winding face_winding;
 6328+    int sample_count;
 6329+    sg_color blend_color;
 6330+    bool alpha_to_coverage_enabled;
 6331+} _sg_pipeline_common_t;
 6332+
 6333+typedef struct {
 6334+    _sg_buffer_ref_t ref;
 6335+    int offset;
 6336+} _sg_buffer_view_common_t;
 6337+
 6338+typedef struct {
 6339+    _sg_image_ref_t ref;
 6340+    int mip_level;
 6341+    int slice;
 6342+    int mip_level_count;
 6343+    int slice_count;
 6344+} _sg_image_view_common_t;
 6345+
 6346+typedef struct {
 6347+    sg_view_type type;
 6348+    _sg_buffer_view_common_t buf;
 6349+    _sg_image_view_common_t img;
 6350+} _sg_view_common_t;
 6351+
 6352+#if defined(SOKOL_DUMMY_BACKEND)
 6353+typedef struct _sg_buffer_s {
 6354+    _sg_slot_t slot;
 6355+    _sg_buffer_common_t cmn;
 6356+} _sg_dummy_buffer_t;
 6357+typedef _sg_dummy_buffer_t _sg_buffer_t;
 6358+
 6359+typedef struct _sg_image_s {
 6360+    _sg_slot_t slot;
 6361+    _sg_image_common_t cmn;
 6362+} _sg_dummy_image_t;
 6363+typedef _sg_dummy_image_t _sg_image_t;
 6364+
 6365+typedef struct _sg_sampler_s {
 6366+    _sg_slot_t slot;
 6367+    _sg_sampler_common_t cmn;
 6368+} _sg_dummy_sampler_t;
 6369+typedef _sg_dummy_sampler_t _sg_sampler_t;
 6370+
 6371+typedef struct _sg_shader_s {
 6372+    _sg_slot_t slot;
 6373+    _sg_shader_common_t cmn;
 6374+} _sg_dummy_shader_t;
 6375+typedef _sg_dummy_shader_t _sg_shader_t;
 6376+
 6377+typedef struct _sg_pipeline_s {
 6378+    _sg_slot_t slot;
 6379+    _sg_pipeline_common_t cmn;
 6380+} _sg_dummy_pipeline_t;
 6381+typedef _sg_dummy_pipeline_t _sg_pipeline_t;
 6382+
 6383+typedef struct _sg_view_s {
 6384+    _sg_slot_t slot;
 6385+    _sg_view_common_t cmn;
 6386+} _sg_dummy_view_t;
 6387+typedef _sg_dummy_view_t _sg_view_t;
 6388+
 6389+#elif defined(_SOKOL_ANY_GL)
 6390+
 6391+typedef enum {
 6392+    _SG_GL_GPUDIRTY_VERTEXBUFFER = (1<<0),
 6393+    _SG_GL_GPUDIRTY_INDEXBUFFER = (1<<1),
 6394+    _SG_GL_GPUDIRTY_STORAGEBUFFER = (1<<2),
 6395+    _SG_GL_GPUDIRTY_TEXTURE = (1<<3),
 6396+    _SG_GL_GPUDIRTY_STORAGEIMAGE = (1<<4),
 6397+    _SG_GL_GPUDIRTY_ATTACHMENT = (1<<5),
 6398+    _SG_GL_GPUDIRTY_BUFFER_ALL = _SG_GL_GPUDIRTY_VERTEXBUFFER | _SG_GL_GPUDIRTY_INDEXBUFFER | _SG_GL_GPUDIRTY_STORAGEBUFFER,
 6399+    _SG_GL_GPUDIRTY_IMAGE_ALL = _SG_GL_GPUDIRTY_TEXTURE | _SG_GL_GPUDIRTY_STORAGEIMAGE | _SG_GL_GPUDIRTY_ATTACHMENT,
 6400+} _sg_gl_gpudirty_t;
 6401+
 6402+typedef struct _sg_buffer_s {
 6403+    _sg_slot_t slot;
 6404+    _sg_buffer_common_t cmn;
 6405+    struct {
 6406+        GLuint buf[SG_NUM_INFLIGHT_FRAMES];
 6407+        uint8_t gpu_dirty_flags; // combination of _sg_gl_gpudirty_t flags
 6408+        bool injected;  // if true, external buffers were injected with sg_buffer_desc.gl_buffers
 6409+    } gl;
 6410+} _sg_gl_buffer_t;
 6411+typedef _sg_gl_buffer_t _sg_buffer_t;
 6412+
 6413+typedef struct _sg_image_s {
 6414+    _sg_slot_t slot;
 6415+    _sg_image_common_t cmn;
 6416+    struct {
 6417+        GLenum target;
 6418+        GLuint tex[SG_NUM_INFLIGHT_FRAMES];
 6419+        uint8_t gpu_dirty_flags; // combination of _sg_gl_gpudirty_flags
 6420+        bool injected;  // if true, external textures were injected with sg_image_desc.gl_textures
 6421+    } gl;
 6422+} _sg_gl_image_t;
 6423+typedef _sg_gl_image_t _sg_image_t;
 6424+
 6425+typedef struct _sg_sampler_s {
 6426+    _sg_slot_t slot;
 6427+    _sg_sampler_common_t cmn;
 6428+    struct {
 6429+        GLuint smp;
 6430+        bool injected;  // true if external sampler was injects in sg_sampler_desc.gl_sampler
 6431+    } gl;
 6432+} _sg_gl_sampler_t;
 6433+typedef _sg_gl_sampler_t _sg_sampler_t;
 6434+
 6435+typedef struct {
 6436+    GLint gl_loc;
 6437+    sg_uniform_type type;
 6438+    uint16_t count;
 6439+    uint16_t offset;
 6440+} _sg_gl_uniform_t;
 6441+
 6442+typedef struct {
 6443+    int num_uniforms;
 6444+    _sg_gl_uniform_t uniforms[SG_MAX_UNIFORMBLOCK_MEMBERS];
 6445+} _sg_gl_uniform_block_t;
 6446+
 6447+typedef struct {
 6448+    _sg_str_t name;
 6449+} _sg_gl_shader_attr_t;
 6450+
 6451+typedef struct _sg_shader_s {
 6452+    _sg_slot_t slot;
 6453+    _sg_shader_common_t cmn;
 6454+    struct {
 6455+        GLuint prog;
 6456+        _sg_gl_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES];
 6457+        _sg_gl_uniform_block_t uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6458+        uint8_t sbuf_binding[SG_MAX_VIEW_BINDSLOTS];
 6459+        uint8_t simg_binding[SG_MAX_VIEW_BINDSLOTS];
 6460+        int8_t tex_slot[SG_MAX_TEXTURE_SAMPLER_PAIRS]; // GL texture unit index
 6461+    } gl;
 6462+} _sg_gl_shader_t;
 6463+typedef _sg_gl_shader_t _sg_shader_t;
 6464+
 6465+typedef struct {
 6466+    int8_t vb_index;        // -1 if attr is not enabled
 6467+    int8_t divisor;         // -1 if not initialized
 6468+    uint8_t stride;
 6469+    uint8_t size;
 6470+    uint8_t normalized;
 6471+    int offset;
 6472+    GLenum type;
 6473+    sg_shader_attr_base_type base_type;
 6474+} _sg_gl_attr_t;
 6475+
 6476+typedef struct _sg_pipeline_s {
 6477+    _sg_slot_t slot;
 6478+    _sg_pipeline_common_t cmn;
 6479+    struct {
 6480+        _sg_gl_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES];
 6481+        sg_depth_state depth;
 6482+        sg_stencil_state stencil;
 6483+        sg_primitive_type primitive_type;
 6484+        sg_blend_state blend;
 6485+        sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS];
 6486+        sg_cull_mode cull_mode;
 6487+        sg_face_winding face_winding;
 6488+        int sample_count;
 6489+        bool alpha_to_coverage_enabled;
 6490+    } gl;
 6491+} _sg_gl_pipeline_t;
 6492+typedef _sg_gl_pipeline_t _sg_pipeline_t;
 6493+
 6494+typedef struct _sg_view_s {
 6495+    _sg_slot_t slot;
 6496+    _sg_view_common_t cmn;
 6497+    struct {
 6498+        GLuint tex_view[SG_NUM_INFLIGHT_FRAMES];    // only if sg_features.gl_texture_views
 6499+        GLuint msaa_render_buffer;                  // only if !msaa_texture_bindings
 6500+        GLuint msaa_resolve_frame_buffer;
 6501+    } gl;
 6502+} _sg_gl_view_t;
 6503+typedef _sg_gl_view_t _sg_view_t;
 6504+
 6505+typedef struct {
 6506+    _sg_gl_attr_t gl_attr;
 6507+    GLuint gl_vbuf;
 6508+} _sg_gl_cache_attr_t;
 6509+
 6510+typedef struct {
 6511+    GLenum target;
 6512+    GLuint texture;
 6513+    GLuint sampler;
 6514+} _sg_gl_cache_texture_sampler_bind_slot;
 6515+
 6516+#define _SG_GL_MAX_SBUF_BINDINGS (_SG_MAX_STORAGEBUFFER_BINDINGS_PER_STAGE)
 6517+#define _SG_GL_MAX_SIMG_BINDINGS (_SG_MAX_STORAGEIMAGE_BINDINGS_PER_STAGE)
 6518+#define _SG_GL_MAX_TEX_SMP_BINDINGS (SG_MAX_TEXTURE_SAMPLER_PAIRS)
 6519+typedef struct {
 6520+    sg_depth_state depth;
 6521+    sg_stencil_state stencil;
 6522+    sg_blend_state blend;
 6523+    sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS];
 6524+    sg_cull_mode cull_mode;
 6525+    sg_face_winding face_winding;
 6526+    bool polygon_offset_enabled;
 6527+    int sample_count;
 6528+    sg_color blend_color;
 6529+    bool alpha_to_coverage_enabled;
 6530+    _sg_gl_cache_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES];
 6531+    GLuint vertex_buffer;
 6532+    GLuint index_buffer;
 6533+    GLuint storage_buffer;  // general bind point
 6534+    GLuint storage_buffers[_SG_GL_MAX_SBUF_BINDINGS];
 6535+    int storage_buffer_offsets[_SG_GL_MAX_SBUF_BINDINGS];
 6536+    GLuint stored_vertex_buffer;
 6537+    GLuint stored_index_buffer;
 6538+    GLuint stored_storage_buffer;
 6539+    GLuint prog;
 6540+    _sg_gl_cache_texture_sampler_bind_slot texture_samplers[_SG_GL_MAX_TEX_SMP_BINDINGS];
 6541+    _sg_gl_cache_texture_sampler_bind_slot stored_texture_sampler;
 6542+    int cur_ib_offset;
 6543+    GLenum cur_primitive_type;
 6544+    GLenum cur_index_type;
 6545+    GLenum cur_active_texture;
 6546+    _sg_sref_t cur_pip;
 6547+} _sg_gl_cache_t;
 6548+
 6549+typedef struct {
 6550+    bool valid;
 6551+    GLuint vao;     // global mutated vertex-array-object
 6552+    GLuint fb;      // global mutated framebuffer
 6553+    _sg_gl_cache_t cache;
 6554+    bool ext_anisotropic;
 6555+    GLint max_anisotropy;
 6556+    sg_store_action color_store_actions[SG_MAX_COLOR_ATTACHMENTS];
 6557+    sg_store_action depth_store_action;
 6558+    sg_store_action stencil_store_action;
 6559+    #if _SOKOL_USE_WIN32_GL_LOADER
 6560+    HINSTANCE opengl32_dll;
 6561+    #endif
 6562+} _sg_gl_backend_t;
 6563+
 6564+#elif defined(SOKOL_D3D11)
 6565+
 6566+typedef struct _sg_buffer_s {
 6567+    _sg_slot_t slot;
 6568+    _sg_buffer_common_t cmn;
 6569+    struct {
 6570+        ID3D11Buffer* buf;
 6571+    } d3d11;
 6572+} _sg_d3d11_buffer_t;
 6573+typedef _sg_d3d11_buffer_t _sg_buffer_t;
 6574+
 6575+typedef struct _sg_image_s {
 6576+    _sg_slot_t slot;
 6577+    _sg_image_common_t cmn;
 6578+    struct {
 6579+        DXGI_FORMAT format;
 6580+        ID3D11Texture2D* tex2d;
 6581+        ID3D11Texture3D* tex3d;
 6582+        ID3D11Resource* res;    // either tex2d or tex3d
 6583+    } d3d11;
 6584+} _sg_d3d11_image_t;
 6585+typedef _sg_d3d11_image_t _sg_image_t;
 6586+
 6587+typedef struct _sg_sampler_s {
 6588+    _sg_slot_t slot;
 6589+    _sg_sampler_common_t cmn;
 6590+    struct {
 6591+        ID3D11SamplerState* smp;
 6592+    } d3d11;
 6593+} _sg_d3d11_sampler_t;
 6594+typedef _sg_d3d11_sampler_t _sg_sampler_t;
 6595+
 6596+typedef struct {
 6597+    _sg_str_t sem_name;
 6598+    int sem_index;
 6599+} _sg_d3d11_shader_attr_t;
 6600+
 6601+#define _SG_D3D11_MAX_TEXTUREARRAY_LAYERS (2048)
 6602+#define _SG_D3D11_MAX_TEXTURE_SUBRESOURCES (SG_MAX_MIPMAPS * _SG_D3D11_MAX_TEXTUREARRAY_LAYERS)
 6603+#define _SG_D3D11_MAX_STAGE_UB_BINDINGS (_SG_MAX_UNIFORMBLOCK_BINDINGS_PER_STAGE)
 6604+#define _SG_D3D11_MAX_STAGE_SRV_BINDINGS (SG_MAX_VIEW_BINDSLOTS)
 6605+#define _SG_D3D11_MAX_STAGE_UAV_BINDINGS (SG_MAX_VIEW_BINDSLOTS)
 6606+#define _SG_D3D11_MAX_STAGE_SMP_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS)
 6607+
 6608+typedef struct _sg_shader_s {
 6609+    _sg_slot_t slot;
 6610+    _sg_shader_common_t cmn;
 6611+    struct {
 6612+        _sg_d3d11_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES];
 6613+        ID3D11VertexShader* vs;
 6614+        ID3D11PixelShader* fs;
 6615+        ID3D11ComputeShader* cs;
 6616+        void* vs_blob;
 6617+        size_t vs_blob_length;
 6618+        uint8_t ub_register_b_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6619+        uint8_t view_register_t_n[SG_MAX_VIEW_BINDSLOTS];
 6620+        uint8_t view_register_u_n[SG_MAX_VIEW_BINDSLOTS];
 6621+        uint8_t smp_register_s_n[SG_MAX_SAMPLER_BINDSLOTS];
 6622+        ID3D11Buffer* all_cbufs[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6623+        ID3D11Buffer* vs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS];
 6624+        ID3D11Buffer* fs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS];
 6625+        ID3D11Buffer* cs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS];
 6626+    } d3d11;
 6627+} _sg_d3d11_shader_t;
 6628+typedef _sg_d3d11_shader_t _sg_shader_t;
 6629+
 6630+typedef struct _sg_pipeline_s {
 6631+    _sg_slot_t slot;
 6632+    _sg_pipeline_common_t cmn;
 6633+    struct {
 6634+        UINT stencil_ref;
 6635+        UINT vb_strides[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 6636+        D3D_PRIMITIVE_TOPOLOGY topology;
 6637+        DXGI_FORMAT index_format;
 6638+        ID3D11InputLayout* il;
 6639+        ID3D11RasterizerState* rs;
 6640+        ID3D11DepthStencilState* dss;
 6641+        ID3D11BlendState* bs;
 6642+    } d3d11;
 6643+} _sg_d3d11_pipeline_t;
 6644+typedef _sg_d3d11_pipeline_t _sg_pipeline_t;
 6645+
 6646+typedef struct _sg_view_s {
 6647+    _sg_slot_t slot;
 6648+    _sg_view_common_t cmn;
 6649+    struct {
 6650+        ID3D11ShaderResourceView* srv;
 6651+        ID3D11UnorderedAccessView* uav;
 6652+        ID3D11RenderTargetView* rtv;
 6653+        ID3D11DepthStencilView* dsv;
 6654+    } d3d11;
 6655+} _sg_d3d11_view_t;
 6656+typedef _sg_d3d11_view_t _sg_view_t;
 6657+
 6658+typedef struct {
 6659+    bool valid;
 6660+    ID3D11Device* dev;
 6661+    ID3D11DeviceContext* ctx;
 6662+    struct {
 6663+        ID3D11RenderTargetView* render_view;
 6664+        ID3D11RenderTargetView* resolve_view;
 6665+    } cur_swapchain;
 6666+    // on-demand loaded d3dcompiler_47.dll handles
 6667+    HINSTANCE d3dcompiler_dll;
 6668+    bool d3dcompiler_dll_load_failed;
 6669+    pD3DCompile D3DCompile_func;
 6670+    // static bindings arrays
 6671+    struct {
 6672+        ID3D11Buffer* vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 6673+        UINT vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 6674+        ID3D11ShaderResourceView* vs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS];
 6675+        ID3D11ShaderResourceView* fs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS];
 6676+        ID3D11ShaderResourceView* cs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS];
 6677+        ID3D11UnorderedAccessView* cs_uavs[_SG_D3D11_MAX_STAGE_UAV_BINDINGS];
 6678+        ID3D11SamplerState* vs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS];
 6679+        ID3D11SamplerState* fs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS];
 6680+        ID3D11SamplerState* cs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS];
 6681+    } bnd;
 6682+    // global subresourcedata array for texture updates
 6683+    D3D11_SUBRESOURCE_DATA subres_data[_SG_D3D11_MAX_TEXTURE_SUBRESOURCES];
 6684+} _sg_d3d11_backend_t;
 6685+
 6686+#elif defined(SOKOL_METAL)
 6687+
 6688+#if defined(_SG_TARGET_MACOS) || defined(_SG_TARGET_IOS_SIMULATOR)
 6689+#define _SG_MTL_UB_ALIGN (256)
 6690+#else
 6691+#define _SG_MTL_UB_ALIGN (16)
 6692+#endif
 6693+#define _SG_MTL_INVALID_SLOT_INDEX (0)
 6694+
 6695+typedef struct {
 6696+    uint32_t frame_index;   // frame index at which it is safe to release this resource
 6697+    int slot_index;
 6698+} _sg_mtl_release_item_t;
 6699+
 6700+typedef struct {
 6701+    NSMutableArray* pool;
 6702+    int num_slots;
 6703+    int free_queue_top;
 6704+    int* free_queue;
 6705+    int release_queue_front;
 6706+    int release_queue_back;
 6707+    _sg_mtl_release_item_t* release_queue;
 6708+} _sg_mtl_idpool_t;
 6709+
 6710+typedef struct _sg_buffer_s {
 6711+    _sg_slot_t slot;
 6712+    _sg_buffer_common_t cmn;
 6713+    struct {
 6714+        int buf[SG_NUM_INFLIGHT_FRAMES];  // index into _sg_mtl_pool
 6715+    } mtl;
 6716+} _sg_mtl_buffer_t;
 6717+typedef _sg_mtl_buffer_t _sg_buffer_t;
 6718+
 6719+typedef struct _sg_image_s {
 6720+    _sg_slot_t slot;
 6721+    _sg_image_common_t cmn;
 6722+    struct {
 6723+        int tex[SG_NUM_INFLIGHT_FRAMES];
 6724+    } mtl;
 6725+} _sg_mtl_image_t;
 6726+typedef _sg_mtl_image_t _sg_image_t;
 6727+
 6728+typedef struct _sg_sampler_s {
 6729+    _sg_slot_t slot;
 6730+    _sg_sampler_common_t cmn;
 6731+    struct {
 6732+        int sampler_state;
 6733+    } mtl;
 6734+} _sg_mtl_sampler_t;
 6735+typedef _sg_mtl_sampler_t _sg_sampler_t;
 6736+
 6737+typedef struct {
 6738+    int mtl_lib;
 6739+    int mtl_func;
 6740+} _sg_mtl_shader_func_t;
 6741+
 6742+typedef struct _sg_shader_s {
 6743+    _sg_slot_t slot;
 6744+    _sg_shader_common_t cmn;
 6745+    struct {
 6746+        _sg_mtl_shader_func_t vertex_func;
 6747+        _sg_mtl_shader_func_t fragment_func;
 6748+        _sg_mtl_shader_func_t compute_func;
 6749+        MTLSize threads_per_threadgroup;
 6750+        uint8_t ub_buffer_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6751+        uint8_t view_buffer_texture_n[SG_MAX_VIEW_BINDSLOTS];
 6752+        uint8_t smp_sampler_n[SG_MAX_SAMPLER_BINDSLOTS];
 6753+    } mtl;
 6754+} _sg_mtl_shader_t;
 6755+typedef _sg_mtl_shader_t _sg_shader_t;
 6756+
 6757+typedef struct _sg_pipeline_s {
 6758+    _sg_slot_t slot;
 6759+    _sg_pipeline_common_t cmn;
 6760+    struct {
 6761+        MTLPrimitiveType prim_type;
 6762+        int index_size;
 6763+        MTLIndexType index_type;
 6764+        MTLCullMode cull_mode;
 6765+        MTLWinding winding;
 6766+        uint32_t stencil_ref;
 6767+        MTLSize threads_per_threadgroup;
 6768+        int cps;    // MTLComputePipelineState
 6769+        int rps;    // MTLRenderPipelineState
 6770+        int dss;    // MTLDepthStencilState
 6771+    } mtl;
 6772+} _sg_mtl_pipeline_t;
 6773+typedef _sg_mtl_pipeline_t _sg_pipeline_t;
 6774+
 6775+typedef struct _sg_view_s {
 6776+    _sg_slot_t slot;
 6777+    _sg_view_common_t cmn;
 6778+    struct {
 6779+        int tex_view[SG_NUM_INFLIGHT_FRAMES];
 6780+    } mtl;
 6781+} _sg_mtl_view_t;
 6782+typedef _sg_mtl_view_t _sg_view_t;
 6783+
 6784+// resource binding state cache
 6785+//
 6786+//  NOTE: reserved buffer bindslot ranges:
 6787+//      - 0..<=7: uniform buffer bindings
 6788+//      - 8..<=22: storage buffer bindings
 6789+//      - 23..<=30: vertex buffer bindings
 6790+//
 6791+#define _SG_MTL_MAX_STAGE_BUFFER_BINDINGS (31)  // see: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 6792+#define _SG_MTL_MAX_STAGE_UB_BINDINGS (_SG_MAX_UNIFORMBLOCK_BINDINGS_PER_STAGE)
 6793+#define _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS (_SG_MTL_MAX_STAGE_BUFFER_BINDINGS - SG_MAX_VERTEXBUFFER_BINDSLOTS)
 6794+#define _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS (SG_MAX_VIEW_BINDSLOTS)
 6795+#define _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS)
 6796+
 6797+typedef struct {
 6798+    _sg_sref_t sref;
 6799+    int active_slot;
 6800+    int offset;
 6801+} _sg_mtl_cache_buf_t;
 6802+
 6803+typedef struct {
 6804+    _sg_sref_t sref;
 6805+    int active_slot;
 6806+} _sg_mtl_cache_tex_t;
 6807+
 6808+typedef enum {
 6809+    _SG_MTL_CACHE_CMP_EQUAL = 0,
 6810+    _SG_MTL_CACHE_CMP_SREF = (1<<1),
 6811+    _SG_MTL_CACHE_CMP_OFFSET = (1<<2),
 6812+    _SG_MTL_CACHE_CMP_ACTIVESLOT = (1<<3),
 6813+} _sg_mtl_cache_cmp_result_t;
 6814+
 6815+typedef struct {
 6816+    _sg_sref_t cur_pip;
 6817+    _sg_buffer_ref_t cur_ibuf;
 6818+    int cur_ibuf_offset;
 6819+    _sg_mtl_cache_buf_t cur_vsbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS];
 6820+    _sg_mtl_cache_buf_t cur_fsbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS];
 6821+    _sg_mtl_cache_buf_t cur_csbufs[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS];
 6822+    _sg_mtl_cache_tex_t cur_vstexs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS];
 6823+    _sg_mtl_cache_tex_t cur_fstexs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS];
 6824+    _sg_mtl_cache_tex_t cur_cstexs[_SG_MTL_MAX_STAGE_TEXTURE_BINDINGS];
 6825+    _sg_sref_t cur_vssmps[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS];
 6826+    _sg_sref_t cur_fssmps[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS];
 6827+    _sg_sref_t cur_cssmps[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS];
 6828+} _sg_mtl_cache_t;
 6829+
 6830+typedef struct {
 6831+    bool valid;
 6832+    bool use_shared_storage_mode;
 6833+    uint32_t cur_frame_rotate_index;
 6834+    int ub_size;
 6835+    int cur_ub_offset;
 6836+    uint8_t* cur_ub_base_ptr;
 6837+    _sg_mtl_cache_t cache;
 6838+    _sg_mtl_idpool_t idpool;
 6839+    dispatch_semaphore_t sem;
 6840+    id<MTLDevice> device;
 6841+    id<MTLCommandQueue> cmd_queue;
 6842+    id<MTLCommandBuffer> cmd_buffer;
 6843+    id<MTLRenderCommandEncoder> render_cmd_encoder;
 6844+    id<MTLComputeCommandEncoder> compute_cmd_encoder;
 6845+    id<CAMetalDrawable> cur_drawable;
 6846+    id<MTLBuffer> uniform_buffers[SG_NUM_INFLIGHT_FRAMES];
 6847+} _sg_mtl_backend_t;
 6848+
 6849+#elif defined(SOKOL_WGPU)
 6850+
 6851+#define _SG_WGPU_ROWPITCH_ALIGN (256)
 6852+#define _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE (1<<16) // also see WGPULimits.maxUniformBufferBindingSize
 6853+#define _SG_WGPU_MAX_BINDGROUPS (2) // 0: uniforms, 1: images, samplers, storage buffers, storage images
 6854+#define _SG_WGPU_UB_BINDGROUP_INDEX (0)
 6855+#define _SG_WGPU_VIEW_SMP_BINDGROUP_INDEX (1)
 6856+#define _SG_WGPU_MAX_UB_BINDGROUP_ENTRIES (SG_MAX_UNIFORMBLOCK_BINDSLOTS)
 6857+#define _SG_WGPU_MAX_UB_BINDGROUP_WGSL_SLOTS (2 * SG_MAX_UNIFORMBLOCK_BINDSLOTS)
 6858+#define _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES (SG_MAX_VIEW_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS)
 6859+#define _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS (128)
 6860+
 6861+typedef struct _sg_buffer_s {
 6862+    _sg_slot_t slot;
 6863+    _sg_buffer_common_t cmn;
 6864+    struct {
 6865+        WGPUBuffer buf;
 6866+    } wgpu;
 6867+} _sg_wgpu_buffer_t;
 6868+typedef _sg_wgpu_buffer_t _sg_buffer_t;
 6869+
 6870+typedef struct _sg_image_s {
 6871+    _sg_slot_t slot;
 6872+    _sg_image_common_t cmn;
 6873+    struct {
 6874+        WGPUTexture tex;
 6875+    } wgpu;
 6876+} _sg_wgpu_image_t;
 6877+typedef _sg_wgpu_image_t _sg_image_t;
 6878+
 6879+typedef struct _sg_sampler_s {
 6880+    _sg_slot_t slot;
 6881+    _sg_sampler_common_t cmn;
 6882+    struct {
 6883+        WGPUSampler smp;
 6884+    } wgpu;
 6885+} _sg_wgpu_sampler_t;
 6886+typedef _sg_wgpu_sampler_t _sg_sampler_t;
 6887+
 6888+typedef struct {
 6889+    WGPUShaderModule module;
 6890+    _sg_str_t entry;
 6891+} _sg_wgpu_shader_func_t;
 6892+
 6893+typedef struct _sg_shader_s {
 6894+    _sg_slot_t slot;
 6895+    _sg_shader_common_t cmn;
 6896+    struct {
 6897+        _sg_wgpu_shader_func_t vertex_func;
 6898+        _sg_wgpu_shader_func_t fragment_func;
 6899+        _sg_wgpu_shader_func_t compute_func;
 6900+        WGPUBindGroupLayout bgl_ub;
 6901+        WGPUBindGroup bg_ub;
 6902+        WGPUBindGroupLayout bgl_view_smp;
 6903+        // a mapping of sokol-gfx bind slots to setBindGroup dynamic-offset-array indices
 6904+        uint8_t ub_num_dynoffsets;
 6905+        uint8_t ub_dynoffsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6906+        // indexed by sokol-gfx bind slot:
 6907+        uint8_t ub_grp0_bnd_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 6908+        uint8_t view_grp1_bnd_n[SG_MAX_VIEW_BINDSLOTS];
 6909+        uint8_t smp_grp1_bnd_n[SG_MAX_SAMPLER_BINDSLOTS];
 6910+    } wgpu;
 6911+} _sg_wgpu_shader_t;
 6912+typedef _sg_wgpu_shader_t _sg_shader_t;
 6913+
 6914+typedef struct _sg_pipeline_s {
 6915+    _sg_slot_t slot;
 6916+    _sg_pipeline_common_t cmn;
 6917+    struct {
 6918+        WGPURenderPipeline rpip;
 6919+        WGPUComputePipeline cpip;
 6920+        WGPUColor blend_color;
 6921+    } wgpu;
 6922+} _sg_wgpu_pipeline_t;
 6923+typedef _sg_wgpu_pipeline_t _sg_pipeline_t;
 6924+
 6925+typedef struct _sg_view_s {
 6926+    _sg_slot_t slot;
 6927+    _sg_view_common_t cmn;
 6928+    struct {
 6929+        WGPUTextureView view;
 6930+    } wgpu;
 6931+} _sg_wgpu_view_t;
 6932+typedef _sg_wgpu_view_t _sg_view_t;
 6933+
 6934+// a pool of per-frame uniform buffers
 6935+typedef struct {
 6936+    uint32_t num_bytes;
 6937+    uint32_t offset;    // current offset into buf
 6938+    uint8_t* staging;   // intermediate buffer for uniform data updates
 6939+    WGPUBuffer buf;     // the GPU-side uniform buffer
 6940+    bool dirty;
 6941+    uint32_t bind_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS];   // NOTE: index is sokol-gfx ub slot index!
 6942+} _sg_wgpu_uniform_system_t;
 6943+
 6944+typedef struct {
 6945+    uint32_t id;
 6946+} _sg_wgpu_bindgroup_handle_t;
 6947+
 6948+typedef enum {
 6949+    _SG_WGPU_BINDGROUPSCACHEITEMTYPE_NONE     = 0,
 6950+    _SG_WGPU_BINDGROUPSCACHEITEMTYPE_VIEW     = 1,
 6951+    _SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER  = 2,
 6952+    _SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE = 3,
 6953+} _sg_wgpu_bindgroups_cache_item_type_t;
 6954+
 6955+#define _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS (1 + _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES)
 6956+typedef struct {
 6957+    uint64_t hash;
 6958+    // the format of cache key items is BBTCCCCCIIIIIIII
 6959+    // where
 6960+    //  - BB: 8 bits WGPU binding
 6961+    //  - T: 2 bits _sg_wgpu_bindgroups_cache_item_type_t
 6962+    //  - CCCCC: 22 bits slot.uninit_count
 6963+    //  - IIIIIIII: 32 bits slot.id
 6964+    //
 6965+    // where the item type is a per-resource-type bit pattern
 6966+    uint64_t items[_SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS];
 6967+} _sg_wgpu_bindgroups_cache_key_t;
 6968+
 6969+typedef struct {
 6970+    uint32_t num;           // must be 2^n
 6971+    uint32_t index_mask;    // mask to turn hash into valid index
 6972+    _sg_wgpu_bindgroup_handle_t* items;
 6973+} _sg_wgpu_bindgroups_cache_t;
 6974+
 6975+typedef struct {
 6976+    _sg_slot_t slot;
 6977+    WGPUBindGroup bindgroup;
 6978+    _sg_wgpu_bindgroups_cache_key_t key;
 6979+} _sg_wgpu_bindgroup_t;
 6980+
 6981+typedef struct {
 6982+    _sg_pool_t pool;
 6983+    _sg_wgpu_bindgroup_t* bindgroups;
 6984+} _sg_wgpu_bindgroups_pool_t;
 6985+
 6986+typedef struct {
 6987+    struct {
 6988+        sg_buffer buffer;
 6989+        uint64_t offset;
 6990+    } vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 6991+    struct {
 6992+        sg_buffer buffer;
 6993+        uint64_t offset;
 6994+    } ib;
 6995+    _sg_wgpu_bindgroup_handle_t bg;
 6996+} _sg_wgpu_bindings_cache_t;
 6997+
 6998+// the WGPU backend state
 6999+typedef struct {
 7000+    bool valid;
 7001+    WGPUDevice dev;
 7002+    WGPULimits limits;
 7003+    WGPUQueue queue;
 7004+    WGPUCommandEncoder cmd_enc;
 7005+    WGPURenderPassEncoder rpass_enc;
 7006+    WGPUComputePassEncoder cpass_enc;
 7007+    _sg_wgpu_uniform_system_t uniform;
 7008+    _sg_wgpu_bindings_cache_t bindings_cache;
 7009+    _sg_wgpu_bindgroups_cache_t bindgroups_cache;
 7010+    _sg_wgpu_bindgroups_pool_t bindgroups_pool;
 7011+} _sg_wgpu_backend_t;
 7012+
 7013+#elif defined(SOKOL_VULKAN)
 7014+
 7015+#define _SG_VK_MAX_UNIFORM_UPDATE_SIZE (1<<16)
 7016+#define _SG_VK_NUM_DESCRIPTORSETS (2) // 0: uniforms, 1: images, samplers, storage buffers, storage images
 7017+#define _SG_VK_UB_DESCRIPTORSET_INDEX (0)
 7018+#define _SG_VK_VIEW_SMP_DESCRIPTORSET_INDEX (1)
 7019+#define _SG_VK_MAX_UB_DESCRIPTORSET_ENTRIES (SG_MAX_UNIFORMBLOCK_BINDSLOTS)
 7020+#define _SG_VK_MAX_UB_DESCRIPTORSET_SLOTS (2 * SG_MAX_UNIFORMBLOCK_BINDSLOTS)
 7021+#define _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_ENTRIES (SG_MAX_VIEW_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS)
 7022+#define _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_SLOTS (128)
 7023+#define _SG_VK_MAX_DESCRIPTOR_DATA_SIZE (256) // FIXME: llvmpipe needs 280 bytes, do we need to care about that?
 7024+
 7025+typedef enum {
 7026+    _SG_VK_MEMTYPE_STORAGE_BUFFER,
 7027+    _SG_VK_MEMTYPE_GENERIC_BUFFER,
 7028+    _SG_VK_MEMTYPE_IMAGE,
 7029+    _SG_VK_MEMTYPE_STAGING_COPY,
 7030+    _SG_VK_MEMTYPE_STAGING_STREAM,
 7031+    _SG_VK_MEMTYPE_UNIFORMS,
 7032+    _SG_VK_MEMTYPE_DESCRIPTORS,
 7033+} _sg_vk_memtype_t;
 7034+
 7035+typedef void (*_sg_vk_delete_queue_destructor_t)(void* obj);
 7036+
 7037+typedef struct {
 7038+    _sg_vk_delete_queue_destructor_t destructor;
 7039+    void* obj;
 7040+} _sg_vk_delete_queue_item_t;
 7041+
 7042+typedef struct {
 7043+    uint32_t index;
 7044+    uint32_t num;
 7045+    _sg_vk_delete_queue_item_t* items;
 7046+} _sg_vk_delete_queue_t;
 7047+
 7048+typedef enum {
 7049+    _SG_VK_ACCESS_NONE = (0), // initial state for new resources
 7050+    _SG_VK_ACCESS_STAGING = (1<<0),
 7051+    _SG_VK_ACCESS_VERTEXBUFFER = (1<<1),
 7052+    _SG_VK_ACCESS_INDEXBUFFER = (1<<2),
 7053+    _SG_VK_ACCESS_STORAGEBUFFER_RO = (1<<3),
 7054+    _SG_VK_ACCESS_STORAGEBUFFER_RW = (1<<4),
 7055+    _SG_VK_ACCESS_TEXTURE = (1<<5),
 7056+    _SG_VK_ACCESS_STORAGEIMAGE = (1<<6),
 7057+    _SG_VK_ACCESS_COLOR_ATTACHMENT = (1<<7),
 7058+    _SG_VK_ACCESS_RESOLVE_ATTACHMENT = (1<<8),
 7059+    _SG_VK_ACCESS_DEPTH_ATTACHMENT = (1<<9),
 7060+    _SG_VK_ACCESS_STENCIL_ATTACHMENT = (1<<10),
 7061+    _SG_VK_ACCESS_DISCARD = (1<<11),    // in combination with attachments
 7062+    _SG_VK_ACCESS_PRESENT = (1<<12),
 7063+} _sg_vk_access_bits_t;
 7064+typedef int _sg_vk_access_t;
 7065+
 7066+typedef struct _sg_buffer_s {
 7067+    _sg_slot_t slot;
 7068+    _sg_buffer_common_t cmn;
 7069+    struct {
 7070+        VkBuffer buf;
 7071+        VkDeviceMemory mem;
 7072+        VkDeviceAddress dev_addr;   // only valid for storage buffers
 7073+        _sg_vk_access_t cur_access;
 7074+    } vk;
 7075+} _sg_vk_buffer_t;
 7076+typedef _sg_vk_buffer_t _sg_buffer_t;
 7077+
 7078+typedef struct _sg_image_s {
 7079+    _sg_slot_t slot;
 7080+    _sg_image_common_t cmn;
 7081+    struct {
 7082+        VkImage img;
 7083+        VkDeviceMemory mem;
 7084+        _sg_vk_access_t cur_access;
 7085+    } vk;
 7086+} _sg_vk_image_t;
 7087+typedef _sg_vk_image_t _sg_image_t;
 7088+
 7089+typedef struct _sg_sampler_s {
 7090+    _sg_slot_t slot;
 7091+    _sg_sampler_common_t cmn;
 7092+    struct {
 7093+        VkSampler smp;
 7094+        size_t descriptor_size;
 7095+        uint8_t descriptor_data[_SG_VK_MAX_DESCRIPTOR_DATA_SIZE];
 7096+    } vk;
 7097+} _sg_vk_sampler_t;
 7098+typedef _sg_vk_sampler_t _sg_sampler_t;
 7099+
 7100+typedef struct {
 7101+    VkShaderModule module;
 7102+    _sg_str_t entry;
 7103+} _sg_vk_shader_func_t;
 7104+
 7105+typedef struct _sg_shader_s {
 7106+    _sg_slot_t slot;
 7107+    _sg_shader_common_t cmn;
 7108+    struct {
 7109+        _sg_vk_shader_func_t vertex_func;
 7110+        _sg_vk_shader_func_t fragment_func;
 7111+        _sg_vk_shader_func_t compute_func;
 7112+        VkDescriptorSetLayout ub_dsl;
 7113+        VkDeviceSize ub_dset_size;
 7114+        VkDescriptorSetLayout view_smp_dsl;
 7115+        VkDeviceSize view_smp_dset_size;
 7116+        VkPipelineLayout pip_layout;
 7117+        // indexed by sokol-gfx bind-slot
 7118+        uint8_t ub_set0_bnd_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 7119+        uint8_t view_set1_bnd_n[SG_MAX_VIEW_BINDSLOTS];
 7120+        uint8_t smp_set1_bnd_n[SG_MAX_SAMPLER_BINDSLOTS];
 7121+        // relative descriptor offsets to start of descriptor set in descriptor buffer
 7122+        uint16_t ub_dset_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 7123+        uint16_t view_dset_offsets[SG_MAX_VIEW_BINDSLOTS];
 7124+        uint16_t smp_dset_offsets[SG_MAX_SAMPLER_BINDSLOTS];
 7125+    } vk;
 7126+} _sg_vk_shader_t;
 7127+typedef _sg_vk_shader_t _sg_shader_t;
 7128+
 7129+typedef struct _sg_pipeline_s {
 7130+    _sg_slot_t slot;
 7131+    _sg_pipeline_common_t cmn;
 7132+    struct {
 7133+        VkPipeline pip;
 7134+    } vk;
 7135+} _sg_vk_pipeline_t;
 7136+typedef _sg_vk_pipeline_t _sg_pipeline_t;
 7137+
 7138+typedef struct _sg_view_s {
 7139+    _sg_slot_t slot;
 7140+    _sg_view_common_t cmn;
 7141+    struct {
 7142+        VkImageView img_view;
 7143+        size_t descriptor_size;
 7144+        uint8_t descriptor_data[_SG_VK_MAX_DESCRIPTOR_DATA_SIZE];
 7145+    } vk;
 7146+} _sg_vk_view_t;
 7147+typedef _sg_vk_view_t _sg_view_t;
 7148+
 7149+// a double-buffer cpu-write / gpu-read buffer
 7150+#define _SG_VK_SHARED_BUFFER_OVERFLOW_RESULT (0xFFFFFFFF)
 7151+typedef struct {
 7152+    uint32_t size;          // buffer size
 7153+    uint32_t align;         // required buffer offset alignemnt
 7154+    uint32_t offset;        // current offset into buffer
 7155+    VkBuffer cur_buf;       // currently mapped buffer
 7156+    void* cur_mem_ptr;      // current pointer into currently mapped buffer
 7157+    VkDeviceAddress cur_dev_addr;   // current buffer device address (only valid for some buffer types)
 7158+    bool overflown;         // true when in overflown state
 7159+    struct {
 7160+        VkBuffer buf;
 7161+        VkDeviceMemory mem;
 7162+        VkDeviceAddress dev_addr;   // only valid for some buffer types!
 7163+        void* mem_ptr;
 7164+    } slots[SG_NUM_INFLIGHT_FRAMES];
 7165+} _sg_vk_shared_buffer_t;
 7166+
 7167+typedef struct {
 7168+    bool valid;
 7169+    VkInstance instance;
 7170+    VkPhysicalDevice phys_dev;
 7171+    VkDevice dev;
 7172+    VkQueue queue;
 7173+    uint32_t queue_family_index;
 7174+    sg_vulkan_swapchain swapchain;
 7175+    VkSemaphore present_complete_sem;
 7176+    VkSemaphore render_finished_sem;
 7177+
 7178+    // extension function pointers
 7179+    struct {
 7180+        PFN_vkSetDebugUtilsObjectNameEXT set_debug_utils_object_name_ext;
 7181+        PFN_vkGetDescriptorSetLayoutSizeEXT get_descriptor_set_layout_size;
 7182+        PFN_vkGetDescriptorSetLayoutBindingOffsetEXT get_descriptor_set_layout_binding_offset;
 7183+        PFN_vkGetDescriptorEXT get_descriptor;
 7184+        PFN_vkCmdBindDescriptorBuffersEXT cmd_bind_descriptor_buffers;
 7185+        PFN_vkCmdSetDescriptorBufferOffsetsEXT cmd_set_descriptor_buffer_offsets;
 7186+    } ext;
 7187+
 7188+    uint32_t frame_slot;
 7189+    struct {
 7190+        VkCommandPool cmd_pool;
 7191+        VkCommandBuffer cmd_buf;
 7192+        VkCommandBuffer stream_cmd_buf;
 7193+        struct {
 7194+            VkFence fence;
 7195+            VkCommandBuffer command_buffer;
 7196+            VkCommandBuffer stream_command_buffer;
 7197+            _sg_vk_delete_queue_t delete_queue;
 7198+        } slot[SG_NUM_INFLIGHT_FRAMES];
 7199+    } frame;
 7200+    // staging system
 7201+    struct {
 7202+        // staging system for immutable and dynamic resources, generally causes a stall
 7203+        struct {
 7204+            VkCommandPool cmd_pool;
 7205+            VkCommandBuffer cmd_buf;
 7206+            uint32_t size;
 7207+            VkBuffer buf;
 7208+            VkDeviceMemory mem;
 7209+        } copy;
 7210+        // staging buffer for per-frame streaming updates
 7211+        _sg_vk_shared_buffer_t stream;
 7212+    } stage;
 7213+    // uniform update system
 7214+    struct {
 7215+        bool dirty;
 7216+        _sg_vk_shared_buffer_t dbuf;    // descriptor buffer
 7217+        VkDescriptorAddressInfoEXT addr_info[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 7218+        VkDescriptorGetInfoEXT get_info[SG_MAX_UNIFORMBLOCK_BINDSLOTS];
 7219+        size_t dset_cache_size;
 7220+        uint8_t* dset_cache;
 7221+    } uniforms;
 7222+    // resource binding system (using descriptor buffers)
 7223+    _sg_vk_shared_buffer_t bind;
 7224+    // hazard tracking system for buffers and images
 7225+    struct {
 7226+        _sg_track_t buffers;
 7227+        _sg_track_t images;
 7228+    } track;
 7229+    // device properties and features (initialized at startup)
 7230+    VkPhysicalDeviceProperties2 dev_props;
 7231+    VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer_props;
 7232+    VkPhysicalDeviceFeatures2 dev_features;
 7233+} _sg_vk_backend_t;
 7234+
 7235+#endif // SOKOL_VULKAN
 7236+
 7237+// this *MUST* remain 0
 7238+#define _SG_INVALID_SLOT_INDEX (0)
 7239+
 7240+typedef struct _sg_pools_s {
 7241+    _sg_pool_t buffer_pool;
 7242+    _sg_pool_t image_pool;
 7243+    _sg_pool_t sampler_pool;
 7244+    _sg_pool_t shader_pool;
 7245+    _sg_pool_t pipeline_pool;
 7246+    _sg_pool_t view_pool;
 7247+    _sg_buffer_t* buffers;
 7248+    _sg_image_t* images;
 7249+    _sg_sampler_t* samplers;
 7250+    _sg_shader_t* shaders;
 7251+    _sg_pipeline_t* pipelines;
 7252+    _sg_view_t* views;
 7253+} _sg_pools_t;
 7254+
 7255+typedef struct {
 7256+    int num;        // number of allocated commit listener items
 7257+    int upper;      // the current upper index (no valid items past this point)
 7258+    sg_commit_listener* items;
 7259+} _sg_commit_listeners_t;
 7260+
 7261+// resolved pass attachments struct
 7262+typedef struct {
 7263+    bool empty;
 7264+    int num_color_views;
 7265+    _sg_view_t* color_views[SG_MAX_COLOR_ATTACHMENTS];
 7266+    _sg_view_t* resolve_views[SG_MAX_COLOR_ATTACHMENTS];
 7267+    _sg_view_t* ds_view;
 7268+} _sg_attachments_ptrs_t;
 7269+
 7270+// resolved resource bindings struct
 7271+typedef struct {
 7272+    _sg_pipeline_t* pip;
 7273+    int vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 7274+    int ib_offset;
 7275+    _sg_buffer_t* vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS];
 7276+    _sg_buffer_t* ib;
 7277+    _sg_view_t* views[SG_MAX_VIEW_BINDSLOTS];
 7278+    _sg_sampler_t* smps[SG_MAX_SAMPLER_BINDSLOTS];
 7279+} _sg_bindings_ptrs_t;
 7280+
 7281+typedef struct {
 7282+    bool sample;
 7283+    bool filter;
 7284+    bool render;
 7285+    bool blend;
 7286+    bool msaa;
 7287+    bool depth;
 7288+    bool read;
 7289+    bool write;
 7290+} _sg_pixelformat_info_t;
 7291+
 7292+typedef struct {
 7293+    bool valid;
 7294+    sg_desc desc;       // original desc with default values patched in
 7295+    uint32_t frame_index;
 7296+    struct {
 7297+        bool valid;
 7298+        bool in_pass;
 7299+        bool is_compute;
 7300+        _sg_dimi_t dim;
 7301+        sg_attachments atts;
 7302+        sg_pass_action action;
 7303+        struct {
 7304+            bool invalid;
 7305+            sg_pixel_format color_fmt;
 7306+            sg_pixel_format depth_fmt;
 7307+            int sample_count;
 7308+        } swapchain;
 7309+    } cur_pass;
 7310+    _sg_pipeline_ref_t cur_pip;
 7311+    bool next_draw_valid;
 7312+    bool use_indexed_draw;
 7313+    bool use_instanced_draw;
 7314+    uint32_t required_bindings_and_uniforms;    // used to check that bindings and uniforms are applied after applying pipeline
 7315+    uint32_t applied_bindings_and_uniforms;     // bits 0..7: uniform blocks, bit 8: bindings
 7316+    #if defined(SOKOL_DEBUG)
 7317+    sg_log_item validate_error;
 7318+    #endif
 7319+    _sg_pools_t pools;
 7320+    sg_backend backend;
 7321+    sg_features features;
 7322+    sg_limits limits;
 7323+    _sg_pixelformat_info_t formats[_SG_PIXELFORMAT_NUM];
 7324+    bool stats_enabled;
 7325+    sg_stats stats;
 7326+    #if defined(_SOKOL_ANY_GL)
 7327+    _sg_gl_backend_t gl;
 7328+    #elif defined(SOKOL_METAL)
 7329+    _sg_mtl_backend_t mtl;
 7330+    #elif defined(SOKOL_D3D11)
 7331+    _sg_d3d11_backend_t d3d11;
 7332+    #elif defined(SOKOL_WGPU)
 7333+    _sg_wgpu_backend_t wgpu;
 7334+    #elif defined(SOKOL_VULKAN)
 7335+    _sg_vk_backend_t vk;
 7336+    #endif
 7337+    #if defined(SOKOL_TRACE_HOOKS)
 7338+    sg_trace_hooks hooks;
 7339+    #endif
 7340+    _sg_commit_listeners_t commit_listeners;
 7341+} _sg_state_t;
 7342+static _sg_state_t _sg;
 7343+
 7344+// ██       ██████   ██████   ██████  ██ ███    ██  ██████
 7345+// ██      ██    ██ ██       ██       ██ ████   ██ ██
 7346+// ██      ██    ██ ██   ███ ██   ███ ██ ██ ██  ██ ██   ███
 7347+// ██      ██    ██ ██    ██ ██    ██ ██ ██  ██ ██ ██    ██
 7348+// ███████  ██████   ██████   ██████  ██ ██   ████  ██████
 7349+//
 7350+// >>logging
 7351+#if defined(SOKOL_DEBUG)
 7352+#define _SG_LOGITEM_XMACRO(item,msg) #item ": " msg,
 7353+static const char* _sg_log_messages[] = {
 7354+    _SG_LOG_ITEMS
 7355+};
 7356+#undef _SG_LOGITEM_XMACRO
 7357+#endif // SOKOL_DEBUG
 7358+
 7359+#define _SG_PANIC(code) _sg_log(SG_LOGITEM_ ##code, 0, 0, __LINE__)
 7360+#define _SG_ERROR(code) _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__)
 7361+#define _SG_WARN(code) _sg_log(SG_LOGITEM_ ##code, 2, 0, __LINE__)
 7362+#define _SG_INFO(code) _sg_log(SG_LOGITEM_ ##code, 3, 0, __LINE__)
 7363+#define _SG_LOGMSG(code,msg) _sg_log(SG_LOGITEM_ ##code, 3, msg, __LINE__)
 7364+#define _SG_VALIDATE(cond,code) if (!(cond)){ _sg.validate_error = SG_LOGITEM_ ##code; _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__); }
 7365+
 7366+static void _sg_log(sg_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) {
 7367+    if (_sg.desc.logger.func) {
 7368+        const char* filename = 0;
 7369+        #if defined(SOKOL_DEBUG)
 7370+            filename = __FILE__;
 7371+            if (0 == msg) {
 7372+                msg = _sg_log_messages[log_item];
 7373+            }
 7374+        #endif
 7375+        _sg.desc.logger.func("sg", log_level, (uint32_t)log_item, msg, line_nr, filename, _sg.desc.logger.user_data);
 7376+    } else {
 7377+        // for log level PANIC it would be 'undefined behaviour' to continue
 7378+        if (log_level == 0) {
 7379+            abort();
 7380+        }
 7381+    }
 7382+}
 7383+
 7384+// ███    ███ ███████ ███    ███  ██████  ██████  ██    ██
 7385+// ████  ████ ██      ████  ████ ██    ██ ██   ██  ██  ██
 7386+// ██ ████ ██ █████   ██ ████ ██ ██    ██ ██████    ████
 7387+// ██  ██  ██ ██      ██  ██  ██ ██    ██ ██   ██    ██
 7388+// ██      ██ ███████ ██      ██  ██████  ██   ██    ██
 7389+//
 7390+// >>memory
 7391+
 7392+_SOKOL_PRIVATE int _sg_roundup(int val, int round_to) {
 7393+    return (val+(round_to-1)) & ~(round_to-1);
 7394+}
 7395+
 7396+_SOKOL_PRIVATE uint32_t _sg_roundup_u32(uint32_t val, uint32_t round_to) {
 7397+    return (val+(round_to-1)) & ~(round_to-1);
 7398+}
 7399+
 7400+_SOKOL_PRIVATE uint64_t _sg_roundup_u64(uint64_t val, uint64_t round_to) {
 7401+    return (val+(round_to-1)) & ~(round_to-1);
 7402+}
 7403+
 7404+_SOKOL_PRIVATE bool _sg_multiple_u64(uint64_t val, uint64_t of) {
 7405+    return (val & (of-1)) == 0;
 7406+}
 7407+
 7408+// a helper macro to clear a struct with potentially ARC'ed ObjC references
 7409+#if defined(SOKOL_METAL)
 7410+    #if defined(__cplusplus)
 7411+        #define _SG_CLEAR_ARC_STRUCT(type, item) { item = type(); }
 7412+    #else
 7413+        #define _SG_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; }
 7414+    #endif
 7415+#else
 7416+    #define _SG_CLEAR_ARC_STRUCT(type, item) { _sg_clear(&item, sizeof(item)); }
 7417+#endif
 7418+
 7419+_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size) {
 7420+    SOKOL_ASSERT(ptr && (size > 0));
 7421+    memset(ptr, 0, size);
 7422+}
 7423+
 7424+_SOKOL_PRIVATE void* _sg_malloc(size_t size) {
 7425+    SOKOL_ASSERT(size > 0);
 7426+    void* ptr;
 7427+    if (_sg.desc.allocator.alloc_fn) {
 7428+        ptr = _sg.desc.allocator.alloc_fn(size, _sg.desc.allocator.user_data);
 7429+    } else {
 7430+        ptr = malloc(size);
 7431+    }
 7432+    if (0 == ptr) {
 7433+        _SG_PANIC(MALLOC_FAILED);
 7434+    }
 7435+    return ptr;
 7436+}
 7437+
 7438+_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size) {
 7439+    void* ptr = _sg_malloc(size);
 7440+    _sg_clear(ptr, size);
 7441+    return ptr;
 7442+}
 7443+
 7444+_SOKOL_PRIVATE void _sg_free(void* ptr) {
 7445+    if (_sg.desc.allocator.free_fn) {
 7446+        _sg.desc.allocator.free_fn(ptr, _sg.desc.allocator.user_data);
 7447+    } else {
 7448+        free(ptr);
 7449+    }
 7450+}
 7451+
 7452+_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) {
 7453+    return 0 == str->buf[0];
 7454+}
 7455+
 7456+_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) {
 7457+    return &str->buf[0];
 7458+}
 7459+
 7460+_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) {
 7461+    SOKOL_ASSERT(dst);
 7462+    if (src) {
 7463+        #if defined(_MSC_VER)
 7464+        strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1));
 7465+        #else
 7466+        strncpy(dst->buf, src, _SG_STRING_SIZE);
 7467+        #endif
 7468+        dst->buf[_SG_STRING_SIZE-1] = 0;
 7469+    } else {
 7470+        _sg_clear(dst->buf, _SG_STRING_SIZE);
 7471+    }
 7472+}
 7473+
 7474+// ██████   ██████   ██████  ██
 7475+// ██   ██ ██    ██ ██    ██ ██
 7476+// ██████  ██    ██ ██    ██ ██
 7477+// ██      ██    ██ ██    ██ ██
 7478+// ██       ██████   ██████  ███████
 7479+//
 7480+// >>pool
 7481+_SOKOL_PRIVATE void _sg_pool_init(_sg_pool_t* pool, int num) {
 7482+    SOKOL_ASSERT(pool && (num >= 1));
 7483+    // slot 0 is reserved for the 'invalid id', so bump the pool size by 1
 7484+    pool->size = num + 1;
 7485+    pool->queue_top = 0;
 7486+    // generation counters indexable by pool slot index, slot 0 is reserved
 7487+    size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size;
 7488+    pool->gen_ctrs = (uint32_t*)_sg_malloc_clear(gen_ctrs_size);
 7489+    // it's not a bug to only reserve 'num' here
 7490+    pool->free_queue = (int*) _sg_malloc_clear(sizeof(int) * (size_t)num);
 7491+    // never allocate the zero-th pool item since the invalid id is 0
 7492+    for (int i = pool->size-1; i >= 1; i--) {
 7493+        pool->free_queue[pool->queue_top++] = i;
 7494+    }
 7495+}
 7496+
 7497+_SOKOL_PRIVATE void _sg_pool_discard(_sg_pool_t* pool) {
 7498+    SOKOL_ASSERT(pool);
 7499+    SOKOL_ASSERT(pool->free_queue);
 7500+    _sg_free(pool->free_queue);
 7501+    pool->free_queue = 0;
 7502+    SOKOL_ASSERT(pool->gen_ctrs);
 7503+    _sg_free(pool->gen_ctrs);
 7504+    pool->gen_ctrs = 0;
 7505+    pool->size = 0;
 7506+    pool->queue_top = 0;
 7507+}
 7508+
 7509+_SOKOL_PRIVATE int _sg_pool_alloc_index(_sg_pool_t* pool) {
 7510+    SOKOL_ASSERT(pool);
 7511+    SOKOL_ASSERT(pool->free_queue);
 7512+    if (pool->queue_top > 0) {
 7513+        int slot_index = pool->free_queue[--pool->queue_top];
 7514+        SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size));
 7515+        return slot_index;
 7516+    } else {
 7517+        // pool exhausted
 7518+        return _SG_INVALID_SLOT_INDEX;
 7519+    }
 7520+}
 7521+
 7522+_SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index) {
 7523+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size));
 7524+    SOKOL_ASSERT(pool);
 7525+    SOKOL_ASSERT(pool->free_queue);
 7526+    SOKOL_ASSERT(pool->queue_top < pool->size);
 7527+    #ifdef SOKOL_DEBUG
 7528+    // debug check against double-free
 7529+    for (int i = 0; i < pool->queue_top; i++) {
 7530+        SOKOL_ASSERT(pool->free_queue[i] != slot_index);
 7531+    }
 7532+    #endif
 7533+    pool->free_queue[pool->queue_top++] = slot_index;
 7534+    SOKOL_ASSERT(pool->queue_top <= (pool->size-1));
 7535+}
 7536+
 7537+_SOKOL_PRIVATE void _sg_slot_reset(_sg_slot_t* slot) {
 7538+    SOKOL_ASSERT(slot);
 7539+    _sg_clear(slot, sizeof(_sg_slot_t));
 7540+}
 7541+
 7542+_SOKOL_PRIVATE void _sg_reset_buffer_to_alloc_state(_sg_buffer_t* buf) {
 7543+    SOKOL_ASSERT(buf);
 7544+    _sg_slot_t slot = buf->slot;
 7545+    _sg_clear(buf, sizeof(*buf));
 7546+    buf->slot = slot;
 7547+    buf->slot.uninit_count += 1;
 7548+    buf->slot.state = SG_RESOURCESTATE_ALLOC;
 7549+}
 7550+
 7551+_SOKOL_PRIVATE void _sg_reset_image_to_alloc_state(_sg_image_t* img) {
 7552+    SOKOL_ASSERT(img);
 7553+    _sg_slot_t slot = img->slot;
 7554+    _sg_clear(img, sizeof(*img));
 7555+    img->slot = slot;
 7556+    img->slot.uninit_count += 1;
 7557+    img->slot.state = SG_RESOURCESTATE_ALLOC;
 7558+}
 7559+
 7560+_SOKOL_PRIVATE void _sg_reset_sampler_to_alloc_state(_sg_sampler_t* smp) {
 7561+    SOKOL_ASSERT(smp);
 7562+    _sg_slot_t slot = smp->slot;
 7563+    _sg_clear(smp, sizeof(*smp));
 7564+    smp->slot = slot;
 7565+    smp->slot.uninit_count += 1;
 7566+    smp->slot.state = SG_RESOURCESTATE_ALLOC;
 7567+}
 7568+
 7569+_SOKOL_PRIVATE void _sg_reset_shader_to_alloc_state(_sg_shader_t* shd) {
 7570+    SOKOL_ASSERT(shd);
 7571+    _sg_slot_t slot = shd->slot;
 7572+    _sg_clear(shd, sizeof(*shd));
 7573+    shd->slot = slot;
 7574+    shd->slot.uninit_count += 1;
 7575+    shd->slot.state = SG_RESOURCESTATE_ALLOC;
 7576+}
 7577+
 7578+_SOKOL_PRIVATE void _sg_reset_pipeline_to_alloc_state(_sg_pipeline_t* pip) {
 7579+    SOKOL_ASSERT(pip);
 7580+    _sg_slot_t slot = pip->slot;
 7581+    _sg_clear(pip, sizeof(*pip));
 7582+    pip->slot = slot;
 7583+    pip->slot.uninit_count += 1;
 7584+    pip->slot.state = SG_RESOURCESTATE_ALLOC;
 7585+}
 7586+
 7587+_SOKOL_PRIVATE void _sg_reset_view_to_alloc_state(_sg_view_t* view) {
 7588+    SOKOL_ASSERT(view);
 7589+    _sg_slot_t slot = view->slot;
 7590+    _sg_clear(view, sizeof(*view));
 7591+    view->slot = slot;
 7592+    view->slot.uninit_count += 1;
 7593+    view->slot.state = SG_RESOURCESTATE_ALLOC;
 7594+}
 7595+
 7596+_SOKOL_PRIVATE void _sg_setup_pools(_sg_pools_t* p, const sg_desc* desc) {
 7597+    SOKOL_ASSERT(p);
 7598+    SOKOL_ASSERT(desc);
 7599+    // note: the pools here will have an additional item, since slot 0 is reserved
 7600+    SOKOL_ASSERT((desc->buffer_pool_size > 0) && (desc->buffer_pool_size < _SG_MAX_POOL_SIZE));
 7601+    _sg_pool_init(&p->buffer_pool, desc->buffer_pool_size);
 7602+    size_t buffer_pool_byte_size = sizeof(_sg_buffer_t) * (size_t)p->buffer_pool.size;
 7603+    p->buffers = (_sg_buffer_t*) _sg_malloc_clear(buffer_pool_byte_size);
 7604+
 7605+    SOKOL_ASSERT((desc->image_pool_size > 0) && (desc->image_pool_size < _SG_MAX_POOL_SIZE));
 7606+    _sg_pool_init(&p->image_pool, desc->image_pool_size);
 7607+    size_t image_pool_byte_size = sizeof(_sg_image_t) * (size_t)p->image_pool.size;
 7608+    p->images = (_sg_image_t*) _sg_malloc_clear(image_pool_byte_size);
 7609+
 7610+    SOKOL_ASSERT((desc->sampler_pool_size > 0) && (desc->sampler_pool_size < _SG_MAX_POOL_SIZE));
 7611+    _sg_pool_init(&p->sampler_pool, desc->sampler_pool_size);
 7612+    size_t sampler_pool_byte_size = sizeof(_sg_sampler_t) * (size_t)p->sampler_pool.size;
 7613+    p->samplers = (_sg_sampler_t*) _sg_malloc_clear(sampler_pool_byte_size);
 7614+
 7615+    SOKOL_ASSERT((desc->shader_pool_size > 0) && (desc->shader_pool_size < _SG_MAX_POOL_SIZE));
 7616+    _sg_pool_init(&p->shader_pool, desc->shader_pool_size);
 7617+    size_t shader_pool_byte_size = sizeof(_sg_shader_t) * (size_t)p->shader_pool.size;
 7618+    p->shaders = (_sg_shader_t*) _sg_malloc_clear(shader_pool_byte_size);
 7619+
 7620+    SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SG_MAX_POOL_SIZE));
 7621+    _sg_pool_init(&p->pipeline_pool, desc->pipeline_pool_size);
 7622+    size_t pipeline_pool_byte_size = sizeof(_sg_pipeline_t) * (size_t)p->pipeline_pool.size;
 7623+    p->pipelines = (_sg_pipeline_t*) _sg_malloc_clear(pipeline_pool_byte_size);
 7624+
 7625+    SOKOL_ASSERT((desc->view_pool_size > 0) && (desc->view_pool_size < _SG_MAX_POOL_SIZE));
 7626+    _sg_pool_init(&p->view_pool, desc->view_pool_size);
 7627+    size_t view_pool_byte_size = sizeof(_sg_view_t) * (size_t)p->view_pool.size;
 7628+    p->views = (_sg_view_t*) _sg_malloc_clear(view_pool_byte_size);
 7629+}
 7630+
 7631+_SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) {
 7632+    SOKOL_ASSERT(p);
 7633+    _sg_free(p->views);     p->views = 0;
 7634+    _sg_free(p->pipelines); p->pipelines = 0;
 7635+    _sg_free(p->shaders);   p->shaders = 0;
 7636+    _sg_free(p->samplers);  p->samplers = 0;
 7637+    _sg_free(p->images);    p->images = 0;
 7638+    _sg_free(p->buffers);   p->buffers = 0;
 7639+    _sg_pool_discard(&p->view_pool);
 7640+    _sg_pool_discard(&p->pipeline_pool);
 7641+    _sg_pool_discard(&p->shader_pool);
 7642+    _sg_pool_discard(&p->sampler_pool);
 7643+    _sg_pool_discard(&p->image_pool);
 7644+    _sg_pool_discard(&p->buffer_pool);
 7645+}
 7646+
 7647+/* allocate the slot at slot_index:
 7648+    - bump the slot's generation counter
 7649+    - create a resource id from the generation counter and slot index
 7650+    - set the slot's id to this id
 7651+    - set the slot's state to ALLOC
 7652+    - return the resource id
 7653+*/
 7654+_SOKOL_PRIVATE uint32_t _sg_slot_alloc(_sg_pool_t* pool, _sg_slot_t* slot, int slot_index) {
 7655+    /* FIXME: add handling for an overflowing generation counter,
 7656+       for now, just overflow (another option is to disable
 7657+       the slot)
 7658+    */
 7659+    SOKOL_ASSERT(pool && pool->gen_ctrs);
 7660+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size));
 7661+    SOKOL_ASSERT(slot->id == SG_INVALID_ID);
 7662+    SOKOL_ASSERT(slot->state == SG_RESOURCESTATE_INITIAL);
 7663+    uint32_t ctr = ++pool->gen_ctrs[slot_index];
 7664+    slot->id = (ctr<<_SG_SLOT_SHIFT)|(slot_index & _SG_SLOT_MASK);
 7665+    slot->state = SG_RESOURCESTATE_ALLOC;
 7666+    return slot->id;
 7667+}
 7668+
 7669+// extract slot index from id
 7670+_SOKOL_PRIVATE int _sg_slot_index(uint32_t id) {
 7671+    int slot_index = (int) (id & _SG_SLOT_MASK);
 7672+    SOKOL_ASSERT(_SG_INVALID_SLOT_INDEX != slot_index);
 7673+    return slot_index;
 7674+}
 7675+
 7676+// returns pointer to resource by id without matching id check
 7677+_SOKOL_PRIVATE _sg_buffer_t* _sg_buffer_at(uint32_t buf_id) {
 7678+    SOKOL_ASSERT(SG_INVALID_ID != buf_id);
 7679+    int slot_index = _sg_slot_index(buf_id);
 7680+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.buffer_pool.size));
 7681+    return &_sg.pools.buffers[slot_index];
 7682+}
 7683+
 7684+_SOKOL_PRIVATE _sg_image_t* _sg_image_at(uint32_t img_id) {
 7685+    SOKOL_ASSERT(SG_INVALID_ID != img_id);
 7686+    int slot_index = _sg_slot_index(img_id);
 7687+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.image_pool.size));
 7688+    return &_sg.pools.images[slot_index];
 7689+}
 7690+
 7691+_SOKOL_PRIVATE _sg_sampler_t* _sg_sampler_at(uint32_t smp_id) {
 7692+    SOKOL_ASSERT(SG_INVALID_ID != smp_id);
 7693+    int slot_index = _sg_slot_index(smp_id);
 7694+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.sampler_pool.size));
 7695+    return &_sg.pools.samplers[slot_index];
 7696+}
 7697+
 7698+_SOKOL_PRIVATE _sg_shader_t* _sg_shader_at(uint32_t shd_id) {
 7699+    SOKOL_ASSERT(SG_INVALID_ID != shd_id);
 7700+    int slot_index = _sg_slot_index(shd_id);
 7701+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.shader_pool.size));
 7702+    return &_sg.pools.shaders[slot_index];
 7703+}
 7704+
 7705+_SOKOL_PRIVATE _sg_pipeline_t* _sg_pipeline_at(uint32_t pip_id) {
 7706+    SOKOL_ASSERT(SG_INVALID_ID != pip_id);
 7707+    int slot_index = _sg_slot_index(pip_id);
 7708+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.pipeline_pool.size));
 7709+    return &_sg.pools.pipelines[slot_index];
 7710+}
 7711+
 7712+_SOKOL_PRIVATE _sg_view_t* _sg_view_at(uint32_t view_id) {
 7713+    SOKOL_ASSERT(SG_INVALID_ID != view_id);
 7714+    int slot_index = _sg_slot_index(view_id);
 7715+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < _sg.pools.view_pool.size));
 7716+    return &_sg.pools.views[slot_index];
 7717+}
 7718+
 7719+// returns pointer to resource with matching id check, may return 0
 7720+_SOKOL_PRIVATE _sg_buffer_t* _sg_lookup_buffer(uint32_t buf_id) {
 7721+    if (SG_INVALID_ID != buf_id) {
 7722+        _sg_buffer_t* buf = _sg_buffer_at(buf_id);
 7723+        if (buf->slot.id == buf_id) {
 7724+            return buf;
 7725+        }
 7726+    }
 7727+    return 0;
 7728+}
 7729+
 7730+_SOKOL_PRIVATE _sg_image_t* _sg_lookup_image(uint32_t img_id) {
 7731+    if (SG_INVALID_ID != img_id) {
 7732+        _sg_image_t* img = _sg_image_at(img_id);
 7733+        if (img->slot.id == img_id) {
 7734+            return img;
 7735+        }
 7736+    }
 7737+    return 0;
 7738+}
 7739+
 7740+_SOKOL_PRIVATE _sg_sampler_t* _sg_lookup_sampler(uint32_t smp_id) {
 7741+    if (SG_INVALID_ID != smp_id) {
 7742+        _sg_sampler_t* smp = _sg_sampler_at(smp_id);
 7743+        if (smp->slot.id == smp_id) {
 7744+            return smp;
 7745+        }
 7746+    }
 7747+    return 0;
 7748+}
 7749+
 7750+_SOKOL_PRIVATE _sg_shader_t* _sg_lookup_shader(uint32_t shd_id) {
 7751+    if (SG_INVALID_ID != shd_id) {
 7752+        _sg_shader_t* shd = _sg_shader_at(shd_id);
 7753+        if (shd->slot.id == shd_id) {
 7754+            return shd;
 7755+        }
 7756+    }
 7757+    return 0;
 7758+}
 7759+
 7760+_SOKOL_PRIVATE _sg_pipeline_t* _sg_lookup_pipeline(uint32_t pip_id) {
 7761+    if (SG_INVALID_ID != pip_id) {
 7762+        _sg_pipeline_t* pip = _sg_pipeline_at(pip_id);
 7763+        if (pip->slot.id == pip_id) {
 7764+            return pip;
 7765+        }
 7766+    }
 7767+    return 0;
 7768+}
 7769+
 7770+_SOKOL_PRIVATE _sg_view_t* _sg_lookup_view(uint32_t view_id) {
 7771+    if (SG_INVALID_ID != view_id) {
 7772+        _sg_view_t* view = _sg_view_at(view_id);
 7773+        if (view->slot.id == view_id) {
 7774+            return view;
 7775+        }
 7776+    }
 7777+    return 0;
 7778+}
 7779+
 7780+// ████████ ██████   █████   ██████ ██   ██
 7781+//    ██    ██   ██ ██   ██ ██      ██  ██
 7782+//    ██    ██████  ███████ ██      █████
 7783+//    ██    ██   ██ ██   ██ ██      ██  ██
 7784+//    ██    ██   ██ ██   ██  ██████ ██   ██
 7785+//
 7786+// >>track
 7787+_SOKOL_PRIVATE void _sg_track_init(_sg_track_t* track, int num_slots) {
 7788+    SOKOL_ASSERT(track && (num_slots > 0));
 7789+    _sg_clear(track, sizeof(_sg_track_t));
 7790+    track->num_slots = num_slots;
 7791+    track->slots = (uint32_t*)_sg_malloc_clear((size_t)num_slots * sizeof(uint32_t));
 7792+    track->occupy_num_bytes = _sg_roundup_u32((uint32_t)num_slots, 8) >> 3;
 7793+    track->occupy_bits = (uint8_t*)_sg_malloc_clear(track->occupy_num_bytes);
 7794+}
 7795+
 7796+_SOKOL_PRIVATE void _sg_track_discard(_sg_track_t* track) {
 7797+    SOKOL_ASSERT(track);
 7798+    if (track->slots) {
 7799+        _sg_free(track->slots);
 7800+        track->slots = 0;
 7801+    }
 7802+    if (track->occupy_bits) {
 7803+        _sg_free(track->occupy_bits);
 7804+        track->occupy_num_bytes = 0;
 7805+        track->occupy_bits = 0;
 7806+    }
 7807+    track->num_slots = 0;
 7808+    track->cur_slot = 0;
 7809+}
 7810+
 7811+_SOKOL_PRIVATE void _sg_track_reset(_sg_track_t* track) {
 7812+    SOKOL_ASSERT(track && track->slots && track->occupy_bits);
 7813+    track->cur_slot = 0;
 7814+    _sg_clear(track->occupy_bits, track->occupy_num_bytes);
 7815+}
 7816+
 7817+_SOKOL_PRIVATE int _sg_track_occupy_index(int slot_index) {
 7818+    const int occupy_index = slot_index >> 3;
 7819+    return occupy_index;
 7820+}
 7821+
 7822+_SOKOL_PRIVATE uint8_t _sg_track_occupy_mask(int slot_index) {
 7823+    return (uint8_t)(1 << (slot_index & 7));
 7824+}
 7825+
 7826+_SOKOL_PRIVATE void _sg_track_add(_sg_track_t* track, uint32_t id) {
 7827+    SOKOL_ASSERT(track && track->slots && track->occupy_bits);
 7828+    SOKOL_ASSERT(id != SG_INVALID_ID);
 7829+    const int slot_index = _sg_slot_index(id);
 7830+    const int occupy_index = _sg_track_occupy_index(slot_index);
 7831+    SOKOL_ASSERT((uint32_t)occupy_index < track->occupy_num_bytes);
 7832+    const uint8_t occupy_mask = _sg_track_occupy_mask(slot_index);
 7833+    // don't record the same resource twice
 7834+    if (0 == (track->occupy_bits[occupy_index] & occupy_mask)) {
 7835+        SOKOL_ASSERT(track->cur_slot < track->num_slots);
 7836+        track->slots[track->cur_slot++] = id;
 7837+        track->occupy_bits[occupy_index] |= occupy_mask;
 7838+    }
 7839+}
 7840+
 7841+_SOKOL_PRIVATE void _sg_track_remove(_sg_track_t* track, uint32_t id) {
 7842+    SOKOL_ASSERT(track && track->slots && track->occupy_bits);
 7843+    SOKOL_ASSERT(id != SG_INVALID_ID);
 7844+    const int slot_index = _sg_slot_index(id);
 7845+    const int occupy_index = _sg_track_occupy_index(slot_index);
 7846+    const uint8_t occupy_mask = _sg_track_occupy_mask(slot_index);
 7847+    if (track->occupy_bits[occupy_index] & occupy_mask) {
 7848+        track->occupy_bits[occupy_index] &= ~occupy_mask;
 7849+        // remove tracked id from the slots array
 7850+        for (int i = 0; i < track->cur_slot; i++) {
 7851+            if (id == track->slots[i]) {
 7852+                SOKOL_ASSERT(track->cur_slot > 0);
 7853+                track->slots[i] = track->slots[--track->cur_slot];
 7854+                break;
 7855+            }
 7856+        }
 7857+    }
 7858+}
 7859+
 7860+// ██████  ███████ ███████ ███████
 7861+// ██   ██ ██      ██      ██
 7862+// ██████  █████   █████   ███████
 7863+// ██   ██ ██      ██           ██
 7864+// ██   ██ ███████ ██      ███████
 7865+//
 7866+// >>refs
 7867+_SOKOL_PRIVATE _sg_sref_t _sg_sref(const _sg_slot_t* slot) {
 7868+    _SG_STRUCT(_sg_sref_t, sref);
 7869+    if (slot) {
 7870+        sref.id = slot->id;
 7871+        sref.uninit_count = slot->uninit_count;
 7872+    }
 7873+    return sref;
 7874+}
 7875+
 7876+_SOKOL_PRIVATE bool _sg_sref_slot_eql(const _sg_sref_t* sref, const _sg_slot_t* slot) {
 7877+    SOKOL_ASSERT(sref && slot);
 7878+    return (sref->id == slot->id) && (sref->uninit_count == slot->uninit_count);
 7879+}
 7880+
 7881+_SOKOL_PRIVATE bool _sg_sref_sref_eql(const _sg_sref_t* sref0, const _sg_sref_t* sref1) {
 7882+    SOKOL_ASSERT(sref0 && sref1);
 7883+    return (sref0->id == sref1->id) && (sref0->uninit_count == sref1->uninit_count);
 7884+}
 7885+
 7886+_SOKOL_PRIVATE _sg_buffer_ref_t _sg_buffer_ref(_sg_buffer_t* buf_or_null) {
 7887+    _SG_STRUCT(_sg_buffer_ref_t, ref);
 7888+    if (buf_or_null) {
 7889+        _sg_buffer_t* buf = buf_or_null;
 7890+        SOKOL_ASSERT(buf->slot.id != SG_INVALID_ID);
 7891+        ref.ptr = buf;
 7892+        ref.sref = _sg_sref(&buf->slot);
 7893+    }
 7894+    return ref;
 7895+}
 7896+
 7897+_SOKOL_PRIVATE _sg_image_ref_t _sg_image_ref(_sg_image_t* img_or_null) {
 7898+    _SG_STRUCT(_sg_image_ref_t, ref);
 7899+    if (img_or_null) {
 7900+        _sg_image_t* img = img_or_null;
 7901+        SOKOL_ASSERT(img->slot.id != SG_INVALID_ID);
 7902+        ref.ptr = img;
 7903+        ref.sref = _sg_sref(&img->slot);
 7904+    }
 7905+    return ref;
 7906+}
 7907+
 7908+_SOKOL_PRIVATE _sg_sampler_ref_t _sg_sampler_ref(_sg_sampler_t* smp_or_null) {
 7909+    _SG_STRUCT(_sg_sampler_ref_t, ref);
 7910+    if (smp_or_null) {
 7911+        _sg_sampler_t* smp = smp_or_null;
 7912+        SOKOL_ASSERT(smp->slot.id != SG_INVALID_ID);
 7913+        ref.ptr = smp;
 7914+        ref.sref = _sg_sref(&smp->slot);
 7915+    }
 7916+    return ref;
 7917+}
 7918+
 7919+_SOKOL_PRIVATE _sg_shader_ref_t _sg_shader_ref(_sg_shader_t* shd_or_null) {
 7920+    _SG_STRUCT(_sg_shader_ref_t, ref);
 7921+    if (shd_or_null) {
 7922+        _sg_shader_t* shd = shd_or_null;
 7923+        SOKOL_ASSERT(shd->slot.id != SG_INVALID_ID);
 7924+        ref.ptr = shd;
 7925+        ref.sref = _sg_sref(&shd->slot);
 7926+    }
 7927+    return ref;
 7928+}
 7929+
 7930+_SOKOL_PRIVATE _sg_pipeline_ref_t _sg_pipeline_ref(_sg_pipeline_t* pip_or_null) {
 7931+    _SG_STRUCT(_sg_pipeline_ref_t, ref);
 7932+    if (pip_or_null) {
 7933+        _sg_pipeline_t* pip = pip_or_null;
 7934+        SOKOL_ASSERT(pip->slot.id != SG_INVALID_ID);
 7935+        ref.ptr = pip;
 7936+        ref.sref = _sg_sref(&pip->slot);
 7937+    }
 7938+    return ref;
 7939+}
 7940+
 7941+_SOKOL_PRIVATE _sg_view_ref_t _sg_view_ref(_sg_view_t* view_or_null) {
 7942+    _SG_STRUCT(_sg_view_ref_t, ref);
 7943+    if (view_or_null) {
 7944+        _sg_view_t* view = view_or_null;
 7945+        SOKOL_ASSERT(view->slot.id != SG_INVALID_ID);
 7946+        ref.ptr = view;
 7947+        ref.sref = _sg_sref(&view->slot);
 7948+    }
 7949+    return ref;
 7950+}
 7951+
 7952+#define _SG_IMPL_RES_EQL(NAME,REF,RES) _SOKOL_PRIVATE bool NAME(const REF* ref, const RES* res) { SOKOL_ASSERT(ref && res); return _sg_sref_slot_eql(&ref->sref, &res->slot); }
 7953+_SG_IMPL_RES_EQL(_sg_buffer_ref_eql, _sg_buffer_ref_t, _sg_buffer_t)
 7954+_SG_IMPL_RES_EQL(_sg_image_ref_eql, _sg_image_ref_t, _sg_image_t)
 7955+_SG_IMPL_RES_EQL(_sg_sampler_ref_eql, _sg_sampler_ref_t, _sg_sampler_t)
 7956+_SG_IMPL_RES_EQL(_sg_shader_ref_eql, _sg_shader_ref_t, _sg_shader_t)
 7957+_SG_IMPL_RES_EQL(_sg_pipeline_ref_eql, _sg_pipeline_ref_t, _sg_pipeline_t)
 7958+_SG_IMPL_RES_EQL(_sg_view_ref_eql, _sg_view_ref_t, _sg_view_t)
 7959+
 7960+#define _SG_IMPL_RES_NULL(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return SG_INVALID_ID == ref->sref.id; }
 7961+_SG_IMPL_RES_NULL(_sg_buffer_ref_null, _sg_buffer_ref_t)
 7962+_SG_IMPL_RES_NULL(_sg_image_ref_null, _sg_image_ref_t)
 7963+_SG_IMPL_RES_NULL(_sg_sampler_ref_null, _sg_sampler_ref_t)
 7964+_SG_IMPL_RES_NULL(_sg_shader_ref_null, _sg_shader_ref_t)
 7965+_SG_IMPL_RES_NULL(_sg_pipeline_ref_null, _sg_pipeline_ref_t)
 7966+_SG_IMPL_RES_NULL(_sg_view_ref_null, _sg_view_ref_t)
 7967+
 7968+#define _SG_IMPL_RES_ALIVE(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return ref->ptr && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot); }
 7969+_SG_IMPL_RES_ALIVE(_sg_buffer_ref_alive, _sg_buffer_ref_t)
 7970+_SG_IMPL_RES_ALIVE(_sg_image_ref_alive, _sg_image_ref_t)
 7971+_SG_IMPL_RES_ALIVE(_sg_sampler_ref_alive, _sg_sampler_ref_t)
 7972+_SG_IMPL_RES_ALIVE(_sg_shader_ref_alive, _sg_shader_ref_t)
 7973+_SG_IMPL_RES_ALIVE(_sg_pipeline_ref_alive, _sg_pipeline_ref_t)
 7974+_SG_IMPL_RES_ALIVE(_sg_view_ref_alive, _sg_view_ref_t)
 7975+
 7976+#define _SG_IMPL_RES_VALID(NAME,REF) _SOKOL_PRIVATE bool NAME(const REF* ref) { SOKOL_ASSERT(ref); return ref->ptr && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot) && (ref->ptr->slot.state == SG_RESOURCESTATE_VALID); }
 7977+_SG_IMPL_RES_VALID(_sg_buffer_ref_valid, _sg_buffer_ref_t)
 7978+_SG_IMPL_RES_VALID(_sg_image_ref_valid, _sg_image_ref_t)
 7979+_SG_IMPL_RES_VALID(_sg_sampler_ref_valid, _sg_sampler_ref_t)
 7980+_SG_IMPL_RES_VALID(_sg_shader_ref_valid, _sg_shader_ref_t)
 7981+_SG_IMPL_RES_VALID(_sg_pipeline_ref_valid, _sg_pipeline_ref_t)
 7982+_SG_IMPL_RES_VALID(_sg_view_ref_valid, _sg_view_ref_t)
 7983+
 7984+#define _SG_IMPL_RES_PTR(NAME,REF,RES) _SOKOL_PRIVATE RES* NAME(const REF* ref) { SOKOL_ASSERT(ref && ref->ptr && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot)); return ref->ptr; }
 7985+_SG_IMPL_RES_PTR(_sg_buffer_ref_ptr, _sg_buffer_ref_t, _sg_buffer_t)
 7986+_SG_IMPL_RES_PTR(_sg_image_ref_ptr, _sg_image_ref_t, _sg_image_t)
 7987+_SG_IMPL_RES_PTR(_sg_sampler_ref_ptr, _sg_sampler_ref_t, _sg_sampler_t)
 7988+_SG_IMPL_RES_PTR(_sg_shader_ref_ptr, _sg_shader_ref_t, _sg_shader_t)
 7989+_SG_IMPL_RES_PTR(_sg_pipeline_ref_ptr, _sg_pipeline_ref_t, _sg_pipeline_t)
 7990+_SG_IMPL_RES_PTR(_sg_view_ref_ptr, _sg_view_ref_t, _sg_view_t)
 7991+
 7992+#define _SG_IMPL_RES_PTR_OR_NULL(NAME,REF,RES) _SOKOL_PRIVATE RES* NAME(const REF* ref) { SOKOL_ASSERT(ref); if ((SG_INVALID_ID != ref->sref.id) && _sg_sref_slot_eql(&ref->sref, &ref->ptr->slot)) { return ref->ptr; } else { return 0; } }
 7993+_SG_IMPL_RES_PTR_OR_NULL(_sg_buffer_ref_ptr_or_null, _sg_buffer_ref_t, _sg_buffer_t)
 7994+_SG_IMPL_RES_PTR_OR_NULL(_sg_image_ref_ptr_or_null, _sg_image_ref_t, _sg_image_t)
 7995+_SG_IMPL_RES_PTR_OR_NULL(_sg_sampler_ref_ptr_or_null, _sg_sampler_ref_t, _sg_sampler_t)
 7996+_SG_IMPL_RES_PTR_OR_NULL(_sg_shader_ref_ptr_or_null, _sg_shader_ref_t, _sg_shader_t)
 7997+_SG_IMPL_RES_PTR_OR_NULL(_sg_pipeline_ref_ptr_or_null, _sg_pipeline_ref_t, _sg_pipeline_t)
 7998+_SG_IMPL_RES_PTR_OR_NULL(_sg_view_ref_ptr_or_null, _sg_view_ref_t, _sg_view_t)
 7999+
 8000+// ██   ██ ███████ ██      ██████  ███████ ██████  ███████
 8001+// ██   ██ ██      ██      ██   ██ ██      ██   ██ ██
 8002+// ███████ █████   ██      ██████  █████   ██████  ███████
 8003+// ██   ██ ██      ██      ██      ██      ██   ██      ██
 8004+// ██   ██ ███████ ███████ ██      ███████ ██   ██ ███████
 8005+//
 8006+// >>helpers
 8007+
 8008+// helper macros
 8009+#define _sg_def(val, def) (((val) == 0) ? (def) : (val))
 8010+#define _sg_def_flt(val, def) (((val) == 0.0f) ? (def) : (val))
 8011+#define _sg_min(a,b) (((a)<(b))?(a):(b))
 8012+#define _sg_max(a,b) (((a)>(b))?(a):(b))
 8013+#define _sg_clamp(v,v0,v1) (((v)<(v0))?(v0):(((v)>(v1))?(v1):(v)))
 8014+#define _sg_fequal(val,cmp,delta) ((((val)-(cmp))> -(delta))&&(((val)-(cmp))<(delta)))
 8015+#define _sg_ispow2(val) ((val&(val-1))==0)
 8016+#define _sg_stats_add(key,val) {if(_sg.stats_enabled){ _sg.stats.cur_frame.key+=val;}}
 8017+#define _sg_stats_inc(key) {if(_sg.stats_enabled){ _sg.stats.cur_frame.key++;}}
 8018+#define _sg_resource_stats_inc(key) {if(_sg.stats_enabled){ _sg.stats.cur_frame.key++; _sg.stats.total.key++;}}
 8019+
 8020+_SOKOL_PRIVATE void _sg_update_alive_free_resource_stats(sg_total_resource_stats* stats, const _sg_pool_t* pool) {
 8021+    SOKOL_ASSERT(stats && pool);
 8022+    stats->alive = (uint32_t) ((pool->size - 1) - pool->queue_top);
 8023+    stats->free = (uint32_t) pool->queue_top;
 8024+}
 8025+
 8026+_SOKOL_PRIVATE void _sg_update_stats(void) {
 8027+    _sg.stats.cur_frame.frame_index = _sg.frame_index;
 8028+    _sg.stats.prev_frame = _sg.stats.cur_frame;
 8029+    _sg_clear(&_sg.stats.cur_frame, sizeof(_sg.stats.cur_frame));
 8030+}
 8031+
 8032+_SOKOL_PRIVATE uint32_t _sg_align_u32(uint32_t val, uint32_t align) {
 8033+    SOKOL_ASSERT((align > 0) && ((align & (align - 1)) == 0));
 8034+    return (val + (align - 1)) & ~(align - 1);
 8035+}
 8036+
 8037+_SOKOL_PRIVATE _sg_recti_t _sg_clipi(int x, int y, int w, int h, int clip_width, int clip_height) {
 8038+    x = _sg_min(_sg_max(0, x), clip_width-1);
 8039+    y = _sg_min(_sg_max(0, y), clip_height-1);
 8040+    if ((x + w) > clip_width) {
 8041+        w = clip_width - x;
 8042+    }
 8043+    if ((y + h) > clip_height) {
 8044+        h = clip_height - y;
 8045+    }
 8046+    w = _sg_max(w, 1);
 8047+    h = _sg_max(h, 1);
 8048+    const _sg_recti_t res = { x, y, w, h };
 8049+    return res;
 8050+}
 8051+
 8052+// return size of a mipmap level
 8053+_SOKOL_PRIVATE int _sg_miplevel_dim(int base_dim, int mip_level) {
 8054+    return _sg_max(base_dim >> mip_level, 1);
 8055+}
 8056+
 8057+_SOKOL_PRIVATE bool _sg_image_view_alive(const _sg_view_t* view) {
 8058+    return view && _sg_image_ref_alive(&view->cmn.img.ref);
 8059+}
 8060+
 8061+_SOKOL_PRIVATE _sg_dimi_t _sg_image_view_dim(const _sg_view_t* view) {
 8062+    SOKOL_ASSERT(view);
 8063+    const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
 8064+    SOKOL_ASSERT((img->cmn.width > 0) && (img->cmn.height > 0));
 8065+    _SG_STRUCT(_sg_dimi_t, res);
 8066+    res.width = _sg_miplevel_dim(img->cmn.width, view->cmn.img.mip_level);
 8067+    res.height = _sg_miplevel_dim(img->cmn.height, view->cmn.img.mip_level);
 8068+    return res;
 8069+}
 8070+
 8071+_SOKOL_PRIVATE bool _sg_attachments_empty(const sg_attachments* atts) {
 8072+    SOKOL_ASSERT(atts);
 8073+    for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
 8074+        if (atts->colors[i].id != SG_INVALID_ID) {
 8075+            return false;
 8076+        }
 8077+        if (atts->resolves[i].id != SG_INVALID_ID) {
 8078+            return false;
 8079+        }
 8080+    }
 8081+    if (atts->depth_stencil.id != SG_INVALID_ID) {
 8082+        return false;
 8083+    }
 8084+    return true;
 8085+}
 8086+
 8087+_SOKOL_PRIVATE _sg_attachments_ptrs_t _sg_attachments_ptrs(const sg_attachments* atts) {
 8088+    SOKOL_ASSERT(atts);
 8089+    _SG_STRUCT(_sg_attachments_ptrs_t, res);
 8090+    res.empty = true;
 8091+    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
 8092+        if (atts->colors[i].id != SG_INVALID_ID) {
 8093+            res.empty = false;
 8094+            res.num_color_views += 1;
 8095+            res.color_views[i] = _sg_lookup_view(atts->colors[i].id);
 8096+        }
 8097+        if (atts->resolves[i].id != SG_INVALID_ID) {
 8098+            SOKOL_ASSERT(atts->colors[i].id != SG_INVALID_ID);
 8099+            res.empty = false;
 8100+            res.resolve_views[i] = _sg_lookup_view(atts->resolves[i].id);
 8101+        }
 8102+    }
 8103+    if (atts->depth_stencil.id != SG_INVALID_ID) {
 8104+        res.empty = false;
 8105+        res.ds_view = _sg_lookup_view(atts->depth_stencil.id);
 8106+    }
 8107+    return res;
 8108+}
 8109+
 8110+_SOKOL_PRIVATE _sg_dimi_t _sg_attachments_dim(const _sg_attachments_ptrs_t* atts_ptrs) {
 8111+    if (atts_ptrs->ds_view) {
 8112+        return _sg_image_view_dim(atts_ptrs->ds_view);
 8113+    } else {
 8114+        SOKOL_ASSERT(atts_ptrs->color_views[0]);
 8115+        return _sg_image_view_dim(atts_ptrs->color_views[0]);
 8116+    }
 8117+}
 8118+
 8119+_SOKOL_PRIVATE bool _sg_attachments_alive(const _sg_attachments_ptrs_t* atts_ptrs) {
 8120+    for (int i = 0; i < atts_ptrs->num_color_views; i++) {
 8121+        if (!_sg_image_view_alive(atts_ptrs->color_views[i])) {
 8122+            return false;
 8123+        }
 8124+        if (atts_ptrs->resolve_views[i] && !_sg_image_view_alive(atts_ptrs->resolve_views[i])) {
 8125+            return false;
 8126+        }
 8127+    }
 8128+    if (atts_ptrs->ds_view && !_sg_image_view_alive(atts_ptrs->ds_view)) {
 8129+        return false;
 8130+    }
 8131+    return true;
 8132+}
 8133+
 8134+_SOKOL_PRIVATE bool _sg_is_dualsource_blendfactor(sg_blend_factor f) {
 8135+    switch (f) {
 8136+        case SG_BLENDFACTOR_SRC1_COLOR:
 8137+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR:
 8138+        case SG_BLENDFACTOR_SRC1_ALPHA:
 8139+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA:
 8140+            return true;
 8141+        default:
 8142+            return false;
 8143+    }
 8144+}
 8145+
 8146+_SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) {
 8147+    cmn->size = (int)desc->size;
 8148+    cmn->append_pos = 0;
 8149+    cmn->append_overflow = false;
 8150+    cmn->update_frame_index = 0;
 8151+    cmn->append_frame_index = 0;
 8152+    cmn->num_slots = desc->usage.immutable ? 1 : SG_NUM_INFLIGHT_FRAMES;
 8153+    cmn->active_slot = 0;
 8154+    cmn->usage = desc->usage;
 8155+}
 8156+
 8157+_SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_image_desc* desc) {
 8158+    cmn->upd_frame_index = 0;
 8159+    cmn->num_slots = desc->usage.immutable ? 1 : SG_NUM_INFLIGHT_FRAMES;
 8160+    cmn->active_slot = 0;
 8161+    cmn->type = desc->type;
 8162+    cmn->width = desc->width;
 8163+    cmn->height = desc->height;
 8164+    cmn->num_slices = desc->num_slices;
 8165+    cmn->num_mipmaps = desc->num_mipmaps;
 8166+    cmn->usage = desc->usage;
 8167+    cmn->pixel_format = desc->pixel_format;
 8168+    cmn->sample_count = desc->sample_count;
 8169+}
 8170+
 8171+_SOKOL_PRIVATE void _sg_sampler_common_init(_sg_sampler_common_t* cmn, const sg_sampler_desc* desc) {
 8172+    cmn->min_filter = desc->min_filter;
 8173+    cmn->mag_filter = desc->mag_filter;
 8174+    cmn->mipmap_filter = desc->mipmap_filter;
 8175+    cmn->wrap_u = desc->wrap_u;
 8176+    cmn->wrap_v = desc->wrap_v;
 8177+    cmn->wrap_w = desc->wrap_w;
 8178+    cmn->min_lod = desc->min_lod;
 8179+    cmn->max_lod = desc->max_lod;
 8180+    cmn->border_color = desc->border_color;
 8181+    cmn->compare = desc->compare;
 8182+    cmn->max_anisotropy = desc->max_anisotropy;
 8183+}
 8184+
 8185+_SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_shader_desc* desc) {
 8186+    cmn->is_compute = desc->compute_func.source || desc->compute_func.bytecode.ptr;
 8187+    for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) {
 8188+        cmn->attrs[i].base_type = desc->attrs[i].base_type;
 8189+    }
 8190+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
 8191+        const sg_shader_uniform_block* src = &desc->uniform_blocks[i];
 8192+        _sg_shader_uniform_block_t* dst = &cmn->uniform_blocks[i];
 8193+        if (src->stage != SG_SHADERSTAGE_NONE) {
 8194+            cmn->required_bindings_and_uniforms |= (1 << i);
 8195+            dst->stage = src->stage;
 8196+            dst->size = src->size;
 8197+        }
 8198+    }
 8199+    const uint32_t required_bindings_flag = (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS);
 8200+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
 8201+        _sg_shader_view_t* dst = &cmn->views[i];
 8202+        if (desc->views[i].texture.stage != SG_SHADERSTAGE_NONE) {
 8203+            const sg_shader_texture_view* src = &desc->views[i].texture;
 8204+            dst->stage = src->stage;
 8205+            dst->view_type = SG_VIEWTYPE_TEXTURE;
 8206+            dst->image_type = src->image_type;
 8207+            dst->sample_type = src->sample_type;
 8208+            dst->multisampled = src->multisampled;
 8209+        } else if (desc->views[i].storage_buffer.stage != SG_SHADERSTAGE_NONE) {
 8210+            const sg_shader_storage_buffer_view* src = &desc->views[i].storage_buffer;
 8211+            cmn->required_bindings_and_uniforms |= required_bindings_flag;
 8212+            dst->stage = src->stage;
 8213+            dst->view_type = SG_VIEWTYPE_STORAGEBUFFER;
 8214+            dst->sbuf_readonly = src->readonly;
 8215+        } else if (desc->views[i].storage_image.stage != SG_SHADERSTAGE_NONE) {
 8216+            const sg_shader_storage_image_view* src = &desc->views[i].storage_image;
 8217+            cmn->required_bindings_and_uniforms |= required_bindings_flag;
 8218+            dst->stage = src->stage;
 8219+            dst->view_type = SG_VIEWTYPE_STORAGEIMAGE;
 8220+            dst->image_type = src->image_type;
 8221+            dst->access_format = src->access_format;
 8222+            dst->simg_writeonly = src->writeonly;
 8223+        }
 8224+    }
 8225+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
 8226+        const sg_shader_sampler* src = &desc->samplers[i];
 8227+        _sg_shader_sampler_t* dst = &cmn->samplers[i];
 8228+        if (src->stage != SG_SHADERSTAGE_NONE) {
 8229+            cmn->required_bindings_and_uniforms |= required_bindings_flag;
 8230+            dst->stage = src->stage;
 8231+            dst->sampler_type = src->sampler_type;
 8232+        }
 8233+    }
 8234+    for (size_t i = 0; i < SG_MAX_TEXTURE_SAMPLER_PAIRS; i++) {
 8235+        const sg_shader_texture_sampler_pair* src = &desc->texture_sampler_pairs[i];
 8236+        _sg_shader_texture_sampler_t* dst = &cmn->texture_samplers[i];
 8237+        if (src->stage != SG_SHADERSTAGE_NONE) {
 8238+            dst->stage = src->stage;
 8239+            SOKOL_ASSERT((src->view_slot >= 0) && (src->view_slot < SG_MAX_VIEW_BINDSLOTS));
 8240+            SOKOL_ASSERT(cmn->views[src->view_slot].view_type == SG_VIEWTYPE_TEXTURE);
 8241+            SOKOL_ASSERT(cmn->views[src->view_slot].stage == src->stage);
 8242+            dst->view_slot = src->view_slot;
 8243+            SOKOL_ASSERT((src->sampler_slot >= 0) && (src->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS));
 8244+            SOKOL_ASSERT(desc->samplers[src->sampler_slot].stage == src->stage);
 8245+            dst->sampler_slot = src->sampler_slot;
 8246+        }
 8247+    }
 8248+}
 8249+
 8250+_SOKOL_PRIVATE void _sg_pipeline_common_init(_sg_pipeline_common_t* cmn, const sg_pipeline_desc* desc, _sg_shader_t* shd) {
 8251+    SOKOL_ASSERT((desc->color_count >= 0) && (desc->color_count <= SG_MAX_COLOR_ATTACHMENTS));
 8252+
 8253+    // FIXME: most of this isn't needed for compute pipelines
 8254+
 8255+    const uint32_t required_bindings_flag = (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS);
 8256+    for (size_t attr_idx = 0; attr_idx < SG_MAX_VERTEX_ATTRIBUTES; attr_idx++) {
 8257+        const sg_vertex_attr_state* attr_state = &desc->layout.attrs[attr_idx];
 8258+        if (attr_state->format != SG_VERTEXFORMAT_INVALID) {
 8259+            SOKOL_ASSERT((attr_state->buffer_index >= 0) && (attr_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS));
 8260+            cmn->vertex_buffer_layout_active[attr_state->buffer_index] = true;
 8261+            cmn->required_bindings_and_uniforms |= required_bindings_flag;
 8262+        }
 8263+    }
 8264+    cmn->use_instanced_draw = false;
 8265+    for (size_t vbuf_idx = 0; vbuf_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS; vbuf_idx++) {
 8266+        const sg_vertex_buffer_layout_state* vbuf_state = &desc->layout.buffers[vbuf_idx];
 8267+        if (vbuf_state->step_func == SG_VERTEXSTEP_PER_INSTANCE) {
 8268+            cmn->use_instanced_draw = true;
 8269+        }
 8270+    }
 8271+    cmn->is_compute = desc->compute;
 8272+    cmn->shader = _sg_shader_ref(shd);
 8273+    cmn->layout = desc->layout;
 8274+    cmn->depth = desc->depth;
 8275+    cmn->stencil = desc->stencil;
 8276+    cmn->color_count = desc->color_count;
 8277+    for (int i = 0; i < desc->color_count; i++) {
 8278+        cmn->colors[i] = desc->colors[i];
 8279+    }
 8280+    cmn->primitive_type = desc->primitive_type;
 8281+    cmn->index_type = desc->index_type;
 8282+    if (cmn->index_type != SG_INDEXTYPE_NONE) {
 8283+        cmn->required_bindings_and_uniforms |= required_bindings_flag;
 8284+    }
 8285+    cmn->cull_mode = desc->cull_mode;
 8286+    cmn->face_winding = desc->face_winding;
 8287+    cmn->sample_count = desc->sample_count;
 8288+    cmn->blend_color = desc->blend_color;
 8289+    cmn->alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled;
 8290+}
 8291+
 8292+_SOKOL_PRIVATE void _sg_buffer_view_common_init(_sg_buffer_view_common_t* cmn, const sg_buffer_view_desc* desc, _sg_buffer_t* buf) {
 8293+    SOKOL_ASSERT(SG_RESOURCESTATE_VALID == buf->slot.state);
 8294+    cmn->ref = _sg_buffer_ref(buf);
 8295+    cmn->offset = desc->offset;
 8296+}
 8297+
 8298+_SOKOL_PRIVATE void _sg_texture_view_common_init(_sg_image_view_common_t* cmn, const sg_texture_view_desc* desc, _sg_image_t* img) {
 8299+    SOKOL_ASSERT(SG_RESOURCESTATE_VALID == img->slot.state);
 8300+    cmn->ref = _sg_image_ref(img);
 8301+    cmn->mip_level = desc->mip_levels.base;
 8302+    cmn->mip_level_count = _sg_def(desc->mip_levels.count, img->cmn.num_mipmaps - cmn->mip_level);
 8303+    cmn->slice = desc->slices.base;
 8304+    switch (img->cmn.type) {
 8305+        case SG_IMAGETYPE_2D:
 8306+            cmn->slice_count = 1;
 8307+            break;
 8308+        case SG_IMAGETYPE_CUBE:
 8309+            cmn->slice_count = 6;
 8310+            break;
 8311+        case SG_IMAGETYPE_3D:
 8312+            cmn->slice_count = 1;
 8313+            break;
 8314+        case SG_IMAGETYPE_ARRAY:
 8315+            cmn->slice_count = _sg_def(desc->slices.count, img->cmn.num_slices - cmn->slice);
 8316+            break;
 8317+        default:
 8318+            SOKOL_UNREACHABLE;
 8319+    }
 8320+}
 8321+
 8322+_SOKOL_PRIVATE void _sg_image_view_common_init(_sg_image_view_common_t* cmn, const sg_image_view_desc* desc, _sg_image_t* img) {
 8323+    SOKOL_ASSERT(SG_RESOURCESTATE_VALID == img->slot.state);
 8324+    cmn->ref = _sg_image_ref(img);
 8325+    cmn->mip_level = desc->mip_level;
 8326+    cmn->mip_level_count = 1;
 8327+    cmn->slice = desc->slice;
 8328+    cmn->slice_count = 1;
 8329+}
 8330+
 8331+_SOKOL_PRIVATE void _sg_view_common_init(_sg_view_common_t* cmn, const sg_view_desc* desc, _sg_buffer_t* buf, _sg_image_t* img) {
 8332+    if (desc->texture.image.id != SG_INVALID_ID) {
 8333+        SOKOL_ASSERT(img);
 8334+        cmn->type = SG_VIEWTYPE_TEXTURE;
 8335+        _sg_texture_view_common_init(&cmn->img, &desc->texture, img);
 8336+    } else if (desc->storage_buffer.buffer.id != SG_INVALID_ID) {
 8337+        SOKOL_ASSERT(buf);
 8338+        cmn->type = SG_VIEWTYPE_STORAGEBUFFER;
 8339+        _sg_buffer_view_common_init(&cmn->buf, &desc->storage_buffer, buf);
 8340+    } else if (desc->storage_image.image.id != SG_INVALID_ID) {
 8341+        SOKOL_ASSERT(img);
 8342+        cmn->type = SG_VIEWTYPE_STORAGEIMAGE;
 8343+        _sg_image_view_common_init(&cmn->img, &desc->storage_image, img);
 8344+    } else if (desc->color_attachment.image.id != SG_INVALID_ID) {
 8345+        SOKOL_ASSERT(img);
 8346+        cmn->type = SG_VIEWTYPE_COLORATTACHMENT;
 8347+        _sg_image_view_common_init(&cmn->img, &desc->color_attachment, img);
 8348+    } else if (desc->resolve_attachment.image.id != SG_INVALID_ID) {
 8349+        SOKOL_ASSERT(img);
 8350+        cmn->type = SG_VIEWTYPE_RESOLVEATTACHMENT;
 8351+        _sg_image_view_common_init(&cmn->img, &desc->resolve_attachment, img);
 8352+    } else if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) {
 8353+        SOKOL_ASSERT(img);
 8354+        cmn->type = SG_VIEWTYPE_DEPTHSTENCILATTACHMENT;
 8355+        _sg_image_view_common_init(&cmn->img, &desc->depth_stencil_attachment, img);
 8356+    } else {
 8357+        SOKOL_UNREACHABLE;
 8358+    }
 8359+}
 8360+
 8361+_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) {
 8362+    switch (fmt) {
 8363+        case SG_VERTEXFORMAT_FLOAT:     return 4;
 8364+        case SG_VERTEXFORMAT_FLOAT2:    return 8;
 8365+        case SG_VERTEXFORMAT_FLOAT3:    return 12;
 8366+        case SG_VERTEXFORMAT_FLOAT4:    return 16;
 8367+        case SG_VERTEXFORMAT_INT:       return 4;
 8368+        case SG_VERTEXFORMAT_INT2:      return 8;
 8369+        case SG_VERTEXFORMAT_INT3:      return 12;
 8370+        case SG_VERTEXFORMAT_INT4:      return 16;
 8371+        case SG_VERTEXFORMAT_UINT:      return 4;
 8372+        case SG_VERTEXFORMAT_UINT2:     return 8;
 8373+        case SG_VERTEXFORMAT_UINT3:     return 12;
 8374+        case SG_VERTEXFORMAT_UINT4:     return 16;
 8375+        case SG_VERTEXFORMAT_BYTE4:     return 4;
 8376+        case SG_VERTEXFORMAT_BYTE4N:    return 4;
 8377+        case SG_VERTEXFORMAT_UBYTE4:    return 4;
 8378+        case SG_VERTEXFORMAT_UBYTE4N:   return 4;
 8379+        case SG_VERTEXFORMAT_SHORT2:    return 4;
 8380+        case SG_VERTEXFORMAT_SHORT2N:   return 4;
 8381+        case SG_VERTEXFORMAT_USHORT2:   return 4;
 8382+        case SG_VERTEXFORMAT_USHORT2N:  return 4;
 8383+        case SG_VERTEXFORMAT_SHORT4:    return 8;
 8384+        case SG_VERTEXFORMAT_SHORT4N:   return 8;
 8385+        case SG_VERTEXFORMAT_USHORT4:   return 8;
 8386+        case SG_VERTEXFORMAT_USHORT4N:  return 8;
 8387+        case SG_VERTEXFORMAT_INT10_N2:  return 4;
 8388+        case SG_VERTEXFORMAT_UINT10_N2: return 4;
 8389+        case SG_VERTEXFORMAT_HALF2:     return 4;
 8390+        case SG_VERTEXFORMAT_HALF4:     return 8;
 8391+        case SG_VERTEXFORMAT_INVALID:   return 0;
 8392+        default:
 8393+            SOKOL_UNREACHABLE;
 8394+            return -1;
 8395+    }
 8396+}
 8397+
 8398+_SOKOL_PRIVATE const char* _sg_vertexformat_to_string(sg_vertex_format fmt) {
 8399+    switch (fmt) {
 8400+        case SG_VERTEXFORMAT_FLOAT:     return "FLOAT";
 8401+        case SG_VERTEXFORMAT_FLOAT2:    return "FLOAT2";
 8402+        case SG_VERTEXFORMAT_FLOAT3:    return "FLOAT3";
 8403+        case SG_VERTEXFORMAT_FLOAT4:    return "FLOAT4";
 8404+        case SG_VERTEXFORMAT_INT:       return "INT";
 8405+        case SG_VERTEXFORMAT_INT2:      return "INT2";
 8406+        case SG_VERTEXFORMAT_INT3:      return "INT3";
 8407+        case SG_VERTEXFORMAT_INT4:      return "INT4";
 8408+        case SG_VERTEXFORMAT_UINT:      return "UINT";
 8409+        case SG_VERTEXFORMAT_UINT2:     return "UINT2";
 8410+        case SG_VERTEXFORMAT_UINT3:     return "UINT3";
 8411+        case SG_VERTEXFORMAT_UINT4:     return "UINT4";
 8412+        case SG_VERTEXFORMAT_BYTE4:     return "BYTE4";
 8413+        case SG_VERTEXFORMAT_BYTE4N:    return "BYTE4N";
 8414+        case SG_VERTEXFORMAT_UBYTE4:    return "UBYTE4";
 8415+        case SG_VERTEXFORMAT_UBYTE4N:   return "UBYTE4N";
 8416+        case SG_VERTEXFORMAT_SHORT2:    return "SHORT2";
 8417+        case SG_VERTEXFORMAT_SHORT2N:   return "SHORT2N";
 8418+        case SG_VERTEXFORMAT_USHORT2:   return "USHORT2";
 8419+        case SG_VERTEXFORMAT_USHORT2N:  return "USHORT2N";
 8420+        case SG_VERTEXFORMAT_SHORT4:    return "SHORT4";
 8421+        case SG_VERTEXFORMAT_SHORT4N:   return "SHORT4N";
 8422+        case SG_VERTEXFORMAT_USHORT4:   return "USHORT4";
 8423+        case SG_VERTEXFORMAT_USHORT4N:  return "USHORT4N";
 8424+        case SG_VERTEXFORMAT_INT10_N2:  return "INT10_N2";
 8425+        case SG_VERTEXFORMAT_UINT10_N2: return "UINT10_N2";
 8426+        case SG_VERTEXFORMAT_HALF2:     return "HALF2";
 8427+        case SG_VERTEXFORMAT_HALF4:     return "HALF4";
 8428+        default:
 8429+            SOKOL_UNREACHABLE;
 8430+            return "INVALID";
 8431+    }
 8432+}
 8433+
 8434+_SOKOL_PRIVATE const char* _sg_shaderattrbasetype_to_string(sg_shader_attr_base_type b) {
 8435+    switch (b) {
 8436+        case SG_SHADERATTRBASETYPE_UNDEFINED:   return "UNDEFINED";
 8437+        case SG_SHADERATTRBASETYPE_FLOAT:       return "FLOAT";
 8438+        case SG_SHADERATTRBASETYPE_SINT:        return "SINT";
 8439+        case SG_SHADERATTRBASETYPE_UINT:        return "UINT";
 8440+        default:
 8441+            SOKOL_UNREACHABLE;
 8442+            return "INVALID";
 8443+    }
 8444+}
 8445+
 8446+_SOKOL_PRIVATE sg_shader_attr_base_type _sg_vertexformat_basetype(sg_vertex_format fmt) {
 8447+    switch (fmt) {
 8448+        case SG_VERTEXFORMAT_FLOAT:
 8449+        case SG_VERTEXFORMAT_FLOAT2:
 8450+        case SG_VERTEXFORMAT_FLOAT3:
 8451+        case SG_VERTEXFORMAT_FLOAT4:
 8452+        case SG_VERTEXFORMAT_HALF2:
 8453+        case SG_VERTEXFORMAT_HALF4:
 8454+        case SG_VERTEXFORMAT_BYTE4N:
 8455+        case SG_VERTEXFORMAT_UBYTE4N:
 8456+        case SG_VERTEXFORMAT_SHORT2N:
 8457+        case SG_VERTEXFORMAT_USHORT2N:
 8458+        case SG_VERTEXFORMAT_SHORT4N:
 8459+        case SG_VERTEXFORMAT_USHORT4N:
 8460+        case SG_VERTEXFORMAT_INT10_N2:
 8461+        case SG_VERTEXFORMAT_UINT10_N2:
 8462+            return SG_SHADERATTRBASETYPE_FLOAT;
 8463+        case SG_VERTEXFORMAT_INT:
 8464+        case SG_VERTEXFORMAT_INT2:
 8465+        case SG_VERTEXFORMAT_INT3:
 8466+        case SG_VERTEXFORMAT_INT4:
 8467+        case SG_VERTEXFORMAT_BYTE4:
 8468+        case SG_VERTEXFORMAT_SHORT2:
 8469+        case SG_VERTEXFORMAT_SHORT4:
 8470+            return SG_SHADERATTRBASETYPE_SINT;
 8471+        case SG_VERTEXFORMAT_UINT:
 8472+        case SG_VERTEXFORMAT_UINT2:
 8473+        case SG_VERTEXFORMAT_UINT3:
 8474+        case SG_VERTEXFORMAT_UINT4:
 8475+        case SG_VERTEXFORMAT_UBYTE4:
 8476+        case SG_VERTEXFORMAT_USHORT2:
 8477+        case SG_VERTEXFORMAT_USHORT4:
 8478+            return SG_SHADERATTRBASETYPE_UINT;
 8479+        default:
 8480+            SOKOL_UNREACHABLE;
 8481+            return SG_SHADERATTRBASETYPE_UNDEFINED;
 8482+    }
 8483+}
 8484+
 8485+_SOKOL_PRIVATE uint32_t _sg_uniform_alignment(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) {
 8486+    if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) {
 8487+        return 1;
 8488+    } else {
 8489+        SOKOL_ASSERT(array_count > 0);
 8490+        if (array_count == 1) {
 8491+            switch (type) {
 8492+                case SG_UNIFORMTYPE_FLOAT:
 8493+                case SG_UNIFORMTYPE_INT:
 8494+                    return 4;
 8495+                case SG_UNIFORMTYPE_FLOAT2:
 8496+                case SG_UNIFORMTYPE_INT2:
 8497+                    return 8;
 8498+                case SG_UNIFORMTYPE_FLOAT3:
 8499+                case SG_UNIFORMTYPE_FLOAT4:
 8500+                case SG_UNIFORMTYPE_INT3:
 8501+                case SG_UNIFORMTYPE_INT4:
 8502+                    return 16;
 8503+                case SG_UNIFORMTYPE_MAT4:
 8504+                    return 16;
 8505+                default:
 8506+                    SOKOL_UNREACHABLE;
 8507+                    return 1;
 8508+            }
 8509+        } else {
 8510+            return 16;
 8511+        }
 8512+    }
 8513+}
 8514+
 8515+_SOKOL_PRIVATE uint32_t _sg_uniform_size(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) {
 8516+    SOKOL_ASSERT(array_count > 0);
 8517+    if (array_count == 1) {
 8518+        switch (type) {
 8519+            case SG_UNIFORMTYPE_FLOAT:
 8520+            case SG_UNIFORMTYPE_INT:
 8521+                return 4;
 8522+            case SG_UNIFORMTYPE_FLOAT2:
 8523+            case SG_UNIFORMTYPE_INT2:
 8524+                return 8;
 8525+            case SG_UNIFORMTYPE_FLOAT3:
 8526+            case SG_UNIFORMTYPE_INT3:
 8527+                return 12;
 8528+            case SG_UNIFORMTYPE_FLOAT4:
 8529+            case SG_UNIFORMTYPE_INT4:
 8530+                return 16;
 8531+            case SG_UNIFORMTYPE_MAT4:
 8532+                return 64;
 8533+            default:
 8534+                SOKOL_UNREACHABLE;
 8535+                return 0;
 8536+        }
 8537+    } else {
 8538+        if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) {
 8539+            switch (type) {
 8540+                case SG_UNIFORMTYPE_FLOAT:
 8541+                case SG_UNIFORMTYPE_INT:
 8542+                    return 4 * (uint32_t)array_count;
 8543+                case SG_UNIFORMTYPE_FLOAT2:
 8544+                case SG_UNIFORMTYPE_INT2:
 8545+                    return 8 * (uint32_t)array_count;
 8546+                case SG_UNIFORMTYPE_FLOAT3:
 8547+                case SG_UNIFORMTYPE_INT3:
 8548+                    return 12 * (uint32_t)array_count;
 8549+                case SG_UNIFORMTYPE_FLOAT4:
 8550+                case SG_UNIFORMTYPE_INT4:
 8551+                    return 16 * (uint32_t)array_count;
 8552+                case SG_UNIFORMTYPE_MAT4:
 8553+                    return 64 * (uint32_t)array_count;
 8554+                default:
 8555+                    SOKOL_UNREACHABLE;
 8556+                    return 0;
 8557+            }
 8558+        } else {
 8559+            switch (type) {
 8560+                case SG_UNIFORMTYPE_FLOAT:
 8561+                case SG_UNIFORMTYPE_FLOAT2:
 8562+                case SG_UNIFORMTYPE_FLOAT3:
 8563+                case SG_UNIFORMTYPE_FLOAT4:
 8564+                case SG_UNIFORMTYPE_INT:
 8565+                case SG_UNIFORMTYPE_INT2:
 8566+                case SG_UNIFORMTYPE_INT3:
 8567+                case SG_UNIFORMTYPE_INT4:
 8568+                    return 16 * (uint32_t)array_count;
 8569+                case SG_UNIFORMTYPE_MAT4:
 8570+                    return 64 * (uint32_t)array_count;
 8571+                default:
 8572+                    SOKOL_UNREACHABLE;
 8573+                    return 0;
 8574+            }
 8575+        }
 8576+    }
 8577+}
 8578+
 8579+_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) {
 8580+    switch (fmt) {
 8581+        case SG_PIXELFORMAT_BC1_RGBA:
 8582+        case SG_PIXELFORMAT_BC2_RGBA:
 8583+        case SG_PIXELFORMAT_BC3_RGBA:
 8584+        case SG_PIXELFORMAT_BC3_SRGBA:
 8585+        case SG_PIXELFORMAT_BC4_R:
 8586+        case SG_PIXELFORMAT_BC4_RSN:
 8587+        case SG_PIXELFORMAT_BC5_RG:
 8588+        case SG_PIXELFORMAT_BC5_RGSN:
 8589+        case SG_PIXELFORMAT_BC6H_RGBF:
 8590+        case SG_PIXELFORMAT_BC6H_RGBUF:
 8591+        case SG_PIXELFORMAT_BC7_RGBA:
 8592+        case SG_PIXELFORMAT_BC7_SRGBA:
 8593+        case SG_PIXELFORMAT_ETC2_RGB8:
 8594+        case SG_PIXELFORMAT_ETC2_SRGB8:
 8595+        case SG_PIXELFORMAT_ETC2_RGB8A1:
 8596+        case SG_PIXELFORMAT_ETC2_RGBA8:
 8597+        case SG_PIXELFORMAT_ETC2_SRGB8A8:
 8598+        case SG_PIXELFORMAT_EAC_R11:
 8599+        case SG_PIXELFORMAT_EAC_R11SN:
 8600+        case SG_PIXELFORMAT_EAC_RG11:
 8601+        case SG_PIXELFORMAT_EAC_RG11SN:
 8602+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:
 8603+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA:
 8604+            return true;
 8605+        default:
 8606+            return false;
 8607+    }
 8608+}
 8609+
 8610+_SOKOL_PRIVATE bool _sg_is_valid_attachment_color_format(sg_pixel_format fmt) {
 8611+    const int fmt_index = (int) fmt;
 8612+    SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM));
 8613+    return _sg.formats[fmt_index].render && !_sg.formats[fmt_index].depth;
 8614+}
 8615+
 8616+_SOKOL_PRIVATE bool _sg_is_valid_attachment_depth_format(sg_pixel_format fmt) {
 8617+    const int fmt_index = (int) fmt;
 8618+    SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM));
 8619+    return _sg.formats[fmt_index].render && _sg.formats[fmt_index].depth;
 8620+}
 8621+
 8622+_SOKOL_PRIVATE bool _sg_is_valid_storage_image_format(sg_pixel_format fmt) {
 8623+    const int fmt_index = (int) fmt;
 8624+    SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM));
 8625+    return _sg.formats[fmt_index].read || _sg.formats[fmt_index].write;
 8626+}
 8627+
 8628+_SOKOL_PRIVATE bool _sg_is_depth_or_depth_stencil_format(sg_pixel_format fmt) {
 8629+    return (SG_PIXELFORMAT_DEPTH == fmt) || (SG_PIXELFORMAT_DEPTH_STENCIL == fmt);
 8630+}
 8631+
 8632+_SOKOL_PRIVATE bool _sg_is_depth_stencil_format(sg_pixel_format fmt) {
 8633+    return (SG_PIXELFORMAT_DEPTH_STENCIL == fmt);
 8634+}
 8635+
 8636+_SOKOL_PRIVATE bool _sg_is_srgb_format(sg_pixel_format fmt) {
 8637+    switch (fmt) {
 8638+        case SG_PIXELFORMAT_SRGB8A8:
 8639+        case SG_PIXELFORMAT_SBGR8A8:
 8640+            return true;
 8641+        default:
 8642+            return false;
 8643+    }
 8644+}
 8645+
 8646+_SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) {
 8647+    switch (fmt) {
 8648+        case SG_PIXELFORMAT_R8:
 8649+        case SG_PIXELFORMAT_R8SN:
 8650+        case SG_PIXELFORMAT_R8UI:
 8651+        case SG_PIXELFORMAT_R8SI:
 8652+            return 1;
 8653+        case SG_PIXELFORMAT_R16:
 8654+        case SG_PIXELFORMAT_R16SN:
 8655+        case SG_PIXELFORMAT_R16UI:
 8656+        case SG_PIXELFORMAT_R16SI:
 8657+        case SG_PIXELFORMAT_R16F:
 8658+        case SG_PIXELFORMAT_RG8:
 8659+        case SG_PIXELFORMAT_RG8SN:
 8660+        case SG_PIXELFORMAT_RG8UI:
 8661+        case SG_PIXELFORMAT_RG8SI:
 8662+            return 2;
 8663+        case SG_PIXELFORMAT_R32UI:
 8664+        case SG_PIXELFORMAT_R32SI:
 8665+        case SG_PIXELFORMAT_R32F:
 8666+        case SG_PIXELFORMAT_RG16:
 8667+        case SG_PIXELFORMAT_RG16SN:
 8668+        case SG_PIXELFORMAT_RG16UI:
 8669+        case SG_PIXELFORMAT_RG16SI:
 8670+        case SG_PIXELFORMAT_RG16F:
 8671+        case SG_PIXELFORMAT_RGBA8:
 8672+        case SG_PIXELFORMAT_SRGB8A8:
 8673+        case SG_PIXELFORMAT_RGBA8SN:
 8674+        case SG_PIXELFORMAT_RGBA8UI:
 8675+        case SG_PIXELFORMAT_RGBA8SI:
 8676+        case SG_PIXELFORMAT_BGRA8:
 8677+        case SG_PIXELFORMAT_SBGR8A8:
 8678+        case SG_PIXELFORMAT_RGB10A2:
 8679+        case SG_PIXELFORMAT_RG11B10F:
 8680+        case SG_PIXELFORMAT_RGB9E5:
 8681+            return 4;
 8682+        case SG_PIXELFORMAT_RG32UI:
 8683+        case SG_PIXELFORMAT_RG32SI:
 8684+        case SG_PIXELFORMAT_RG32F:
 8685+        case SG_PIXELFORMAT_RGBA16:
 8686+        case SG_PIXELFORMAT_RGBA16SN:
 8687+        case SG_PIXELFORMAT_RGBA16UI:
 8688+        case SG_PIXELFORMAT_RGBA16SI:
 8689+        case SG_PIXELFORMAT_RGBA16F:
 8690+            return 8;
 8691+        case SG_PIXELFORMAT_RGBA32UI:
 8692+        case SG_PIXELFORMAT_RGBA32SI:
 8693+        case SG_PIXELFORMAT_RGBA32F:
 8694+            return 16;
 8695+        case SG_PIXELFORMAT_DEPTH:
 8696+        case SG_PIXELFORMAT_DEPTH_STENCIL:
 8697+            return 4;
 8698+        default:
 8699+            SOKOL_UNREACHABLE;
 8700+            return 0;
 8701+    }
 8702+}
 8703+
 8704+// return the texture block width/height of an image format
 8705+_SOKOL_PRIVATE int _sg_block_dim(sg_pixel_format fmt) {
 8706+    if (_sg_is_compressed_pixel_format(fmt)) {
 8707+        return 4;
 8708+    } else {
 8709+        return 1;
 8710+    }
 8711+}
 8712+
 8713+// return texture block size in bytes
 8714+_SOKOL_PRIVATE int _sg_block_bytesize(sg_pixel_format fmt) {
 8715+    switch (fmt) {
 8716+        case SG_PIXELFORMAT_BC1_RGBA:
 8717+        case SG_PIXELFORMAT_BC4_R:
 8718+        case SG_PIXELFORMAT_BC4_RSN:
 8719+        case SG_PIXELFORMAT_ETC2_RGB8:
 8720+        case SG_PIXELFORMAT_ETC2_SRGB8:
 8721+        case SG_PIXELFORMAT_ETC2_RGB8A1:
 8722+        case SG_PIXELFORMAT_EAC_R11:
 8723+        case SG_PIXELFORMAT_EAC_R11SN:
 8724+            return 8;
 8725+        case SG_PIXELFORMAT_BC2_RGBA:
 8726+        case SG_PIXELFORMAT_BC3_RGBA:
 8727+        case SG_PIXELFORMAT_BC3_SRGBA:
 8728+        case SG_PIXELFORMAT_BC5_RG:
 8729+        case SG_PIXELFORMAT_BC5_RGSN:
 8730+        case SG_PIXELFORMAT_BC6H_RGBF:
 8731+        case SG_PIXELFORMAT_BC6H_RGBUF:
 8732+        case SG_PIXELFORMAT_BC7_RGBA:
 8733+        case SG_PIXELFORMAT_BC7_SRGBA:
 8734+        case SG_PIXELFORMAT_ETC2_RGBA8:
 8735+        case SG_PIXELFORMAT_ETC2_SRGB8A8:
 8736+        case SG_PIXELFORMAT_EAC_RG11:
 8737+        case SG_PIXELFORMAT_EAC_RG11SN:
 8738+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:
 8739+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA:
 8740+            return 16;
 8741+        default:
 8742+            return _sg_pixelformat_bytesize(fmt);
 8743+    }
 8744+}
 8745+
 8746+/* return row pitch for an image
 8747+
 8748+    see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp
 8749+*/
 8750+_SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) {
 8751+    const int block_dim = _sg_block_dim(fmt);
 8752+    const int num_blocks_in_row = (width + (block_dim-1)) / block_dim;
 8753+    const int block_num_bytes = _sg_block_bytesize(fmt);
 8754+    int pitch = num_blocks_in_row * block_num_bytes;
 8755+    pitch = (pitch < block_num_bytes) ? block_num_bytes : pitch;
 8756+    pitch = _sg_roundup(pitch, row_align);
 8757+    return pitch;
 8758+}
 8759+
 8760+// compute the number of rows in a surface depending on pixel format
 8761+_SOKOL_PRIVATE int _sg_num_rows(sg_pixel_format fmt, int height) {
 8762+    const int block_dim = _sg_block_dim(fmt);
 8763+    int num_rows = (height + (block_dim-1)) / block_dim;
 8764+    if (num_rows < 1) {
 8765+        num_rows = 1;
 8766+    }
 8767+    return num_rows;
 8768+}
 8769+
 8770+/* return pitch of a 2D subimage / texture slice
 8771+    see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp
 8772+*/
 8773+_SOKOL_PRIVATE int _sg_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align) {
 8774+    int num_rows = _sg_num_rows(fmt, height);
 8775+    return num_rows * _sg_row_pitch(fmt, width, row_align);
 8776+}
 8777+
 8778+// capability table pixel format helper functions
 8779+_SOKOL_PRIVATE void _sg_pixelformat_all(_sg_pixelformat_info_t* pfi) {
 8780+    pfi->sample = true;
 8781+    pfi->filter = true;
 8782+    pfi->blend = true;
 8783+    pfi->render = true;
 8784+    pfi->msaa = true;
 8785+}
 8786+
 8787+_SOKOL_PRIVATE void _sg_pixelformat_s(_sg_pixelformat_info_t* pfi) {
 8788+    pfi->sample = true;
 8789+}
 8790+
 8791+_SOKOL_PRIVATE void _sg_pixelformat_sf(_sg_pixelformat_info_t* pfi) {
 8792+    pfi->sample = true;
 8793+    pfi->filter = true;
 8794+}
 8795+
 8796+_SOKOL_PRIVATE void _sg_pixelformat_sr(_sg_pixelformat_info_t* pfi) {
 8797+    pfi->sample = true;
 8798+    pfi->render = true;
 8799+}
 8800+
 8801+_SOKOL_PRIVATE void _sg_pixelformat_sfr(_sg_pixelformat_info_t* pfi) {
 8802+    pfi->sample = true;
 8803+    pfi->filter = true;
 8804+    pfi->render = true;
 8805+}
 8806+
 8807+_SOKOL_PRIVATE void _sg_pixelformat_srmd(_sg_pixelformat_info_t* pfi) {
 8808+    pfi->sample = true;
 8809+    pfi->render = true;
 8810+    pfi->msaa = true;
 8811+    pfi->depth = true;
 8812+}
 8813+
 8814+_SOKOL_PRIVATE void _sg_pixelformat_srm(_sg_pixelformat_info_t* pfi) {
 8815+    pfi->sample = true;
 8816+    pfi->render = true;
 8817+    pfi->msaa = true;
 8818+}
 8819+
 8820+_SOKOL_PRIVATE void _sg_pixelformat_sfrm(_sg_pixelformat_info_t* pfi) {
 8821+    pfi->sample = true;
 8822+    pfi->filter = true;
 8823+    pfi->render = true;
 8824+    pfi->msaa = true;
 8825+}
 8826+_SOKOL_PRIVATE void _sg_pixelformat_sbrm(_sg_pixelformat_info_t* pfi) {
 8827+    pfi->sample = true;
 8828+    pfi->blend = true;
 8829+    pfi->render = true;
 8830+    pfi->msaa = true;
 8831+}
 8832+
 8833+_SOKOL_PRIVATE void _sg_pixelformat_sbr(_sg_pixelformat_info_t* pfi) {
 8834+    pfi->sample = true;
 8835+    pfi->blend = true;
 8836+    pfi->render = true;
 8837+}
 8838+
 8839+_SOKOL_PRIVATE void _sg_pixelformat_sfbr(_sg_pixelformat_info_t* pfi) {
 8840+    pfi->sample = true;
 8841+    pfi->filter = true;
 8842+    pfi->blend = true;
 8843+    pfi->render = true;
 8844+}
 8845+
 8846+_SOKOL_PRIVATE void _sg_pixelformat_compute_all(_sg_pixelformat_info_t* pfi) {
 8847+    pfi->read = true;
 8848+    pfi->write = true;
 8849+}
 8850+
 8851+_SOKOL_PRIVATE void _sg_pixelformat_compute_writeonly(_sg_pixelformat_info_t* pfi) {
 8852+    pfi->read = false;
 8853+    pfi->write = true;
 8854+}
 8855+
 8856+_SOKOL_PRIVATE sg_pass_action _sg_pass_action_defaults(const sg_pass_action* action) {
 8857+    SOKOL_ASSERT(action);
 8858+    sg_pass_action res = *action;
 8859+    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
 8860+        if (res.colors[i].load_action == _SG_LOADACTION_DEFAULT) {
 8861+            res.colors[i].load_action = SG_LOADACTION_CLEAR;
 8862+            res.colors[i].clear_value.r = SG_DEFAULT_CLEAR_RED;
 8863+            res.colors[i].clear_value.g = SG_DEFAULT_CLEAR_GREEN;
 8864+            res.colors[i].clear_value.b = SG_DEFAULT_CLEAR_BLUE;
 8865+            res.colors[i].clear_value.a = SG_DEFAULT_CLEAR_ALPHA;
 8866+        }
 8867+        if (res.colors[i].store_action == _SG_STOREACTION_DEFAULT) {
 8868+            res.colors[i].store_action = SG_STOREACTION_STORE;
 8869+        }
 8870+    }
 8871+    if (res.depth.load_action == _SG_LOADACTION_DEFAULT) {
 8872+        res.depth.load_action = SG_LOADACTION_CLEAR;
 8873+        res.depth.clear_value = SG_DEFAULT_CLEAR_DEPTH;
 8874+    }
 8875+    if (res.depth.store_action == _SG_STOREACTION_DEFAULT) {
 8876+        res.depth.store_action = SG_STOREACTION_DONTCARE;
 8877+    }
 8878+    if (res.stencil.load_action == _SG_LOADACTION_DEFAULT) {
 8879+        res.stencil.load_action = SG_LOADACTION_CLEAR;
 8880+        res.stencil.clear_value = SG_DEFAULT_CLEAR_STENCIL;
 8881+    }
 8882+    if (res.stencil.store_action == _SG_STOREACTION_DEFAULT) {
 8883+        res.stencil.store_action = SG_STOREACTION_DONTCARE;
 8884+    }
 8885+    return res;
 8886+}
 8887+
 8888+// ██████  ██    ██ ███    ███ ███    ███ ██    ██     ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
 8889+// ██   ██ ██    ██ ████  ████ ████  ████  ██  ██      ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
 8890+// ██   ██ ██    ██ ██ ████ ██ ██ ████ ██   ████       ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
 8891+// ██   ██ ██    ██ ██  ██  ██ ██  ██  ██    ██        ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
 8892+// ██████   ██████  ██      ██ ██      ██    ██        ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
 8893+//
 8894+// >>dummy backend
 8895+#if defined(SOKOL_DUMMY_BACKEND)
 8896+
 8897+_SOKOL_PRIVATE void _sg_dummy_setup_backend(const sg_desc* desc) {
 8898+    SOKOL_ASSERT(desc);
 8899+    _SOKOL_UNUSED(desc);
 8900+    _sg.backend = SG_BACKEND_DUMMY;
 8901+    for (int i = SG_PIXELFORMAT_R8; i < SG_PIXELFORMAT_BC1_RGBA; i++) {
 8902+        _sg.formats[i].sample = true;
 8903+        _sg.formats[i].filter = true;
 8904+        _sg.formats[i].render = true;
 8905+        _sg.formats[i].blend = true;
 8906+        _sg.formats[i].msaa = true;
 8907+    }
 8908+    _sg.formats[SG_PIXELFORMAT_DEPTH].depth = true;
 8909+    _sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL].depth = true;
 8910+    _sg.limits.max_image_size_2d = 1024;
 8911+    _sg.limits.max_image_size_cube = 1024;
 8912+    _sg.limits.max_image_size_3d = 1024;
 8913+    _sg.limits.max_image_size_array = 1024;
 8914+    _sg.limits.max_image_array_layers = 1024;
 8915+    _sg.limits.max_vertex_attrs = 16;
 8916+    _sg.limits.max_color_attachments = SG_MAX_PORTABLE_COLOR_ATTACHMENTS;
 8917+    _sg.limits.max_texture_bindings_per_stage = SG_MAX_PORTABLE_TEXTURE_BINDINGS_PER_STAGE;
 8918+    _sg.limits.max_storage_buffer_bindings_per_stage = SG_MAX_PORTABLE_STORAGEBUFFER_BINDINGS_PER_STAGE;
 8919+    _sg.limits.max_storage_image_bindings_per_stage = SG_MAX_PORTABLE_STORAGEIMAGE_BINDINGS_PER_STAGE;
 8920+}
 8921+
 8922+_SOKOL_PRIVATE void _sg_dummy_discard_backend(void) {
 8923+    // empty
 8924+}
 8925+
 8926+_SOKOL_PRIVATE void _sg_dummy_reset_state_cache(void) {
 8927+    // empty
 8928+}
 8929+
 8930+_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
 8931+    SOKOL_ASSERT(buf && desc);
 8932+    _SOKOL_UNUSED(buf);
 8933+    _SOKOL_UNUSED(desc);
 8934+    return SG_RESOURCESTATE_VALID;
 8935+}
 8936+
 8937+_SOKOL_PRIVATE void _sg_dummy_discard_buffer(_sg_buffer_t* buf) {
 8938+    SOKOL_ASSERT(buf);
 8939+    _SOKOL_UNUSED(buf);
 8940+}
 8941+
 8942+_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_image(_sg_image_t* img, const sg_image_desc* desc) {
 8943+    SOKOL_ASSERT(img && desc);
 8944+    _SOKOL_UNUSED(img);
 8945+    _SOKOL_UNUSED(desc);
 8946+    return SG_RESOURCESTATE_VALID;
 8947+}
 8948+
 8949+_SOKOL_PRIVATE void _sg_dummy_discard_image(_sg_image_t* img) {
 8950+    SOKOL_ASSERT(img);
 8951+    _SOKOL_UNUSED(img);
 8952+}
 8953+
 8954+_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
 8955+    SOKOL_ASSERT(smp && desc);
 8956+    _SOKOL_UNUSED(smp);
 8957+    _SOKOL_UNUSED(desc);
 8958+    return SG_RESOURCESTATE_VALID;
 8959+}
 8960+
 8961+_SOKOL_PRIVATE void _sg_dummy_discard_sampler(_sg_sampler_t* smp) {
 8962+    SOKOL_ASSERT(smp);
 8963+    _SOKOL_UNUSED(smp);
 8964+}
 8965+
 8966+_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
 8967+    SOKOL_ASSERT(shd && desc);
 8968+    _SOKOL_UNUSED(shd);
 8969+    _SOKOL_UNUSED(desc);
 8970+    return SG_RESOURCESTATE_VALID;
 8971+}
 8972+
 8973+_SOKOL_PRIVATE void _sg_dummy_discard_shader(_sg_shader_t* shd) {
 8974+    SOKOL_ASSERT(shd);
 8975+    _SOKOL_UNUSED(shd);
 8976+}
 8977+
 8978+_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
 8979+    SOKOL_ASSERT(pip && desc);
 8980+    _SOKOL_UNUSED(pip);
 8981+    _SOKOL_UNUSED(desc);
 8982+    return SG_RESOURCESTATE_VALID;
 8983+}
 8984+
 8985+_SOKOL_PRIVATE void _sg_dummy_discard_pipeline(_sg_pipeline_t* pip) {
 8986+    SOKOL_ASSERT(pip);
 8987+    _SOKOL_UNUSED(pip);
 8988+}
 8989+
 8990+_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_view(_sg_view_t* view, const sg_view_desc* desc) {
 8991+    SOKOL_ASSERT(view && desc);
 8992+    _SOKOL_UNUSED(view);
 8993+    _SOKOL_UNUSED(desc);
 8994+    return SG_RESOURCESTATE_VALID;
 8995+}
 8996+
 8997+_SOKOL_PRIVATE void _sg_dummy_discard_view(_sg_view_t* view) {
 8998+    SOKOL_ASSERT(view);
 8999+    _SOKOL_UNUSED(view);
 9000+}
 9001+
 9002+_SOKOL_PRIVATE void _sg_dummy_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
 9003+    SOKOL_ASSERT(pass && atts);
 9004+    _SOKOL_UNUSED(pass);
 9005+    _SOKOL_UNUSED(atts);
 9006+}
 9007+
 9008+_SOKOL_PRIVATE void _sg_dummy_end_pass(const _sg_attachments_ptrs_t* atts) {
 9009+    SOKOL_ASSERT(atts);
 9010+    _SOKOL_UNUSED(atts);
 9011+}
 9012+
 9013+_SOKOL_PRIVATE void _sg_dummy_commit(void) {
 9014+    // empty
 9015+}
 9016+
 9017+_SOKOL_PRIVATE void _sg_dummy_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
 9018+    _SOKOL_UNUSED(x);
 9019+    _SOKOL_UNUSED(y);
 9020+    _SOKOL_UNUSED(w);
 9021+    _SOKOL_UNUSED(h);
 9022+    _SOKOL_UNUSED(origin_top_left);
 9023+}
 9024+
 9025+_SOKOL_PRIVATE void _sg_dummy_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
 9026+    _SOKOL_UNUSED(x);
 9027+    _SOKOL_UNUSED(y);
 9028+    _SOKOL_UNUSED(w);
 9029+    _SOKOL_UNUSED(h);
 9030+    _SOKOL_UNUSED(origin_top_left);
 9031+}
 9032+
 9033+_SOKOL_PRIVATE void _sg_dummy_apply_pipeline(_sg_pipeline_t* pip) {
 9034+    SOKOL_ASSERT(pip);
 9035+    _SOKOL_UNUSED(pip);
 9036+}
 9037+
 9038+_SOKOL_PRIVATE bool _sg_dummy_apply_bindings(_sg_bindings_ptrs_t* bnd) {
 9039+    SOKOL_ASSERT(bnd);
 9040+    SOKOL_ASSERT(bnd->pip);
 9041+    _SOKOL_UNUSED(bnd);
 9042+    return true;
 9043+}
 9044+
 9045+_SOKOL_PRIVATE void _sg_dummy_apply_uniforms(int ub_slot, const sg_range* data) {
 9046+    _SOKOL_UNUSED(ub_slot);
 9047+    _SOKOL_UNUSED(data);
 9048+}
 9049+
 9050+_SOKOL_PRIVATE void _sg_dummy_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
 9051+    _SOKOL_UNUSED(base_element);
 9052+    _SOKOL_UNUSED(num_elements);
 9053+    _SOKOL_UNUSED(num_instances);
 9054+    _SOKOL_UNUSED(base_vertex);
 9055+    _SOKOL_UNUSED(base_instance);
 9056+}
 9057+
 9058+_SOKOL_PRIVATE void _sg_dummy_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
 9059+    _SOKOL_UNUSED(num_groups_x);
 9060+    _SOKOL_UNUSED(num_groups_y);
 9061+    _SOKOL_UNUSED(num_groups_z);
 9062+}
 9063+
 9064+_SOKOL_PRIVATE void _sg_dummy_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
 9065+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
 9066+    _SOKOL_UNUSED(data);
 9067+    if (++buf->cmn.active_slot >= buf->cmn.num_slots) {
 9068+        buf->cmn.active_slot = 0;
 9069+    }
 9070+}
 9071+
 9072+_SOKOL_PRIVATE bool _sg_dummy_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
 9073+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
 9074+    _SOKOL_UNUSED(data);
 9075+    if (new_frame) {
 9076+        if (++buf->cmn.active_slot >= buf->cmn.num_slots) {
 9077+            buf->cmn.active_slot = 0;
 9078+        }
 9079+    }
 9080+    return true;
 9081+}
 9082+
 9083+_SOKOL_PRIVATE void _sg_dummy_update_image(_sg_image_t* img, const sg_image_data* data) {
 9084+    SOKOL_ASSERT(img && data);
 9085+    _SOKOL_UNUSED(data);
 9086+    if (++img->cmn.active_slot >= img->cmn.num_slots) {
 9087+        img->cmn.active_slot = 0;
 9088+    }
 9089+}
 9090+
 9091+//  ██████  ██████  ███████ ███    ██  ██████  ██          ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
 9092+// ██    ██ ██   ██ ██      ████   ██ ██       ██          ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
 9093+// ██    ██ ██████  █████   ██ ██  ██ ██   ███ ██          ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
 9094+// ██    ██ ██      ██      ██  ██ ██ ██    ██ ██          ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
 9095+//  ██████  ██      ███████ ██   ████  ██████  ███████     ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
 9096+//
 9097+// >>opengl backend
 9098+#elif defined(_SOKOL_ANY_GL)
 9099+
 9100+// optional GL loader for win32
 9101+#if defined(_SOKOL_USE_WIN32_GL_LOADER)
 9102+
 9103+#ifndef SG_GL_FUNCS_EXT
 9104+#define SG_GL_FUNCS_EXT
 9105+#endif
 9106+
 9107+// X Macro list of GL function names and signatures
 9108+#define _SG_GL_FUNCS \
 9109+    SG_GL_FUNCS_EXT \
 9110+    _SG_XMACRO(glBindVertexArray,                 void, (GLuint array)) \
 9111+    _SG_XMACRO(glFramebufferTextureLayer,         void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \
 9112+    _SG_XMACRO(glGenFramebuffers,                 void, (GLsizei n, GLuint * framebuffers)) \
 9113+    _SG_XMACRO(glBindFramebuffer,                 void, (GLenum target, GLuint framebuffer)) \
 9114+    _SG_XMACRO(glBindRenderbuffer,                void, (GLenum target, GLuint renderbuffer)) \
 9115+    _SG_XMACRO(glGetStringi,                      const GLubyte *, (GLenum name, GLuint index)) \
 9116+    _SG_XMACRO(glClearBufferfi,                   void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \
 9117+    _SG_XMACRO(glClearBufferfv,                   void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \
 9118+    _SG_XMACRO(glClearBufferuiv,                  void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \
 9119+    _SG_XMACRO(glClearBufferiv,                   void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \
 9120+    _SG_XMACRO(glDeleteRenderbuffers,             void, (GLsizei n, const GLuint * renderbuffers)) \
 9121+    _SG_XMACRO(glUniform1fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \
 9122+    _SG_XMACRO(glUniform2fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \
 9123+    _SG_XMACRO(glUniform3fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \
 9124+    _SG_XMACRO(glUniform4fv,                      void, (GLint location, GLsizei count, const GLfloat * value)) \
 9125+    _SG_XMACRO(glUniform1iv,                      void, (GLint location, GLsizei count, const GLint * value)) \
 9126+    _SG_XMACRO(glUniform2iv,                      void, (GLint location, GLsizei count, const GLint * value)) \
 9127+    _SG_XMACRO(glUniform3iv,                      void, (GLint location, GLsizei count, const GLint * value)) \
 9128+    _SG_XMACRO(glUniform4iv,                      void, (GLint location, GLsizei count, const GLint * value)) \
 9129+    _SG_XMACRO(glUniformMatrix4fv,                void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \
 9130+    _SG_XMACRO(glUseProgram,                      void, (GLuint program)) \
 9131+    _SG_XMACRO(glShaderSource,                    void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \
 9132+    _SG_XMACRO(glLinkProgram,                     void, (GLuint program)) \
 9133+    _SG_XMACRO(glGetUniformLocation,              GLint, (GLuint program, const GLchar * name)) \
 9134+    _SG_XMACRO(glGetShaderiv,                     void, (GLuint shader, GLenum pname, GLint * params)) \
 9135+    _SG_XMACRO(glGetProgramInfoLog,               void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \
 9136+    _SG_XMACRO(glGetAttribLocation,               GLint, (GLuint program, const GLchar * name)) \
 9137+    _SG_XMACRO(glDisableVertexAttribArray,        void, (GLuint index)) \
 9138+    _SG_XMACRO(glDeleteShader,                    void, (GLuint shader)) \
 9139+    _SG_XMACRO(glDeleteProgram,                   void, (GLuint program)) \
 9140+    _SG_XMACRO(glCompileShader,                   void, (GLuint shader)) \
 9141+    _SG_XMACRO(glStencilFuncSeparate,             void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \
 9142+    _SG_XMACRO(glStencilOpSeparate,               void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \
 9143+    _SG_XMACRO(glRenderbufferStorageMultisample,  void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \
 9144+    _SG_XMACRO(glDrawBuffers,                     void, (GLsizei n, const GLenum * bufs)) \
 9145+    _SG_XMACRO(glVertexAttribDivisor,             void, (GLuint index, GLuint divisor)) \
 9146+    _SG_XMACRO(glBufferSubData,                   void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \
 9147+    _SG_XMACRO(glGenBuffers,                      void, (GLsizei n, GLuint * buffers)) \
 9148+    _SG_XMACRO(glCheckFramebufferStatus,          GLenum, (GLenum target)) \
 9149+    _SG_XMACRO(glFramebufferRenderbuffer,         void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \
 9150+    _SG_XMACRO(glCompressedTexImage2D,            void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \
 9151+    _SG_XMACRO(glCompressedTexImage3D,            void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \
 9152+    _SG_XMACRO(glActiveTexture,                   void, (GLenum texture)) \
 9153+    _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)) \
 9154+    _SG_XMACRO(glRenderbufferStorage,             void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \
 9155+    _SG_XMACRO(glGenTextures,                     void, (GLsizei n, GLuint * textures)) \
 9156+    _SG_XMACRO(glPolygonOffset,                   void, (GLfloat factor, GLfloat units)) \
 9157+    _SG_XMACRO(glDrawElements,                    void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \
 9158+    _SG_XMACRO(glDeleteFramebuffers,              void, (GLsizei n, const GLuint * framebuffers)) \
 9159+    _SG_XMACRO(glBlendEquationSeparate,           void, (GLenum modeRGB, GLenum modeAlpha)) \
 9160+    _SG_XMACRO(glDeleteTextures,                  void, (GLsizei n, const GLuint * textures)) \
 9161+    _SG_XMACRO(glGetProgramiv,                    void, (GLuint program, GLenum pname, GLint * params)) \
 9162+    _SG_XMACRO(glBindTexture,                     void, (GLenum target, GLuint texture)) \
 9163+    _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)) \
 9164+    _SG_XMACRO(glCreateShader,                    GLuint, (GLenum type)) \
 9165+    _SG_XMACRO(glTexSubImage2D,                   void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \
 9166+    _SG_XMACRO(glFramebufferTexture2D,            void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \
 9167+    _SG_XMACRO(glCreateProgram,                   GLuint, (void)) \
 9168+    _SG_XMACRO(glViewport,                        void, (GLint x, GLint y, GLsizei width, GLsizei height)) \
 9169+    _SG_XMACRO(glDeleteBuffers,                   void, (GLsizei n, const GLuint * buffers)) \
 9170+    _SG_XMACRO(glDrawArrays,                      void, (GLenum mode, GLint first, GLsizei count)) \
 9171+    _SG_XMACRO(glDrawElementsInstanced,           void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \
 9172+    _SG_XMACRO(glVertexAttribPointer,             void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \
 9173+    _SG_XMACRO(glVertexAttribIPointer,            void, (GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer)) \
 9174+    _SG_XMACRO(glUniform1i,                       void, (GLint location, GLint v0)) \
 9175+    _SG_XMACRO(glDisable,                         void, (GLenum cap)) \
 9176+    _SG_XMACRO(glColorMask,                       void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \
 9177+    _SG_XMACRO(glColorMaski,                      void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \
 9178+    _SG_XMACRO(glBindBuffer,                      void, (GLenum target, GLuint buffer)) \
 9179+    _SG_XMACRO(glDeleteVertexArrays,              void, (GLsizei n, const GLuint * arrays)) \
 9180+    _SG_XMACRO(glDepthMask,                       void, (GLboolean flag)) \
 9181+    _SG_XMACRO(glDrawArraysInstanced,             void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \
 9182+    _SG_XMACRO(glScissor,                         void, (GLint x, GLint y, GLsizei width, GLsizei height)) \
 9183+    _SG_XMACRO(glGenRenderbuffers,                void, (GLsizei n, GLuint * renderbuffers)) \
 9184+    _SG_XMACRO(glBufferData,                      void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \
 9185+    _SG_XMACRO(glBlendFuncSeparate,               void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \
 9186+    _SG_XMACRO(glTexParameteri,                   void, (GLenum target, GLenum pname, GLint param)) \
 9187+    _SG_XMACRO(glGetIntegerv,                     void, (GLenum pname, GLint * data)) \
 9188+    _SG_XMACRO(glEnable,                          void, (GLenum cap)) \
 9189+    _SG_XMACRO(glBlitFramebuffer,                 void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \
 9190+    _SG_XMACRO(glStencilMask,                     void, (GLuint mask)) \
 9191+    _SG_XMACRO(glAttachShader,                    void, (GLuint program, GLuint shader)) \
 9192+    _SG_XMACRO(glGetError,                        GLenum, (void)) \
 9193+    _SG_XMACRO(glBlendColor,                      void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \
 9194+    _SG_XMACRO(glTexParameterf,                   void, (GLenum target, GLenum pname, GLfloat param)) \
 9195+    _SG_XMACRO(glTexParameterfv,                  void, (GLenum target, GLenum pname, const GLfloat* params)) \
 9196+    _SG_XMACRO(glGetShaderInfoLog,                void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \
 9197+    _SG_XMACRO(glDepthFunc,                       void, (GLenum func)) \
 9198+    _SG_XMACRO(glStencilOp ,                      void, (GLenum fail, GLenum zfail, GLenum zpass)) \
 9199+    _SG_XMACRO(glStencilFunc,                     void, (GLenum func, GLint ref, GLuint mask)) \
 9200+    _SG_XMACRO(glEnableVertexAttribArray,         void, (GLuint index)) \
 9201+    _SG_XMACRO(glBlendFunc,                       void, (GLenum sfactor, GLenum dfactor)) \
 9202+    _SG_XMACRO(glReadBuffer,                      void, (GLenum src)) \
 9203+    _SG_XMACRO(glTexImage2D,                      void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \
 9204+    _SG_XMACRO(glGenVertexArrays,                 void, (GLsizei n, GLuint * arrays)) \
 9205+    _SG_XMACRO(glFrontFace,                       void, (GLenum mode)) \
 9206+    _SG_XMACRO(glCullFace,                        void, (GLenum mode)) \
 9207+    _SG_XMACRO(glPixelStorei,                     void, (GLenum pname, GLint param)) \
 9208+    _SG_XMACRO(glBindSampler,                     void, (GLuint unit, GLuint sampler)) \
 9209+    _SG_XMACRO(glGenSamplers,                     void, (GLsizei n, GLuint* samplers)) \
 9210+    _SG_XMACRO(glSamplerParameteri,               void, (GLuint sampler, GLenum pname, GLint param)) \
 9211+    _SG_XMACRO(glSamplerParameterf,               void, (GLuint sampler, GLenum pname, GLfloat param)) \
 9212+    _SG_XMACRO(glSamplerParameterfv,              void, (GLuint sampler, GLenum pname, const GLfloat* params)) \
 9213+    _SG_XMACRO(glDeleteSamplers,                  void, (GLsizei n, const GLuint* samplers)) \
 9214+    _SG_XMACRO(glBindBufferBase,                  void, (GLenum target, GLuint index, GLuint buffer)) \
 9215+    _SG_XMACRO(glBindBufferRange,                 void, (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)) \
 9216+    _SG_XMACRO(glTexImage2DMultisample,           void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) \
 9217+    _SG_XMACRO(glTexImage3DMultisample,           void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations)) \
 9218+    _SG_XMACRO(glDispatchCompute,                 void, (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z)) \
 9219+    _SG_XMACRO(glMemoryBarrier,                   void, (GLbitfield barriers)) \
 9220+    _SG_XMACRO(glBindImageTexture,                void, (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format)) \
 9221+    _SG_XMACRO(glTexStorage2DMultisample,         void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) \
 9222+    _SG_XMACRO(glTexStorage2D,                    void, (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)) \
 9223+    _SG_XMACRO(glTexStorage3DMultisample,         void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations)) \
 9224+    _SG_XMACRO(glTexStorage3D,                    void, (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)) \
 9225+    _SG_XMACRO(glCompressedTexSubImage2D,         void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data)) \
 9226+    _SG_XMACRO(glCompressedTexSubImage3D,         void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data)) \
 9227+    _SG_XMACRO(glTextureView,                     void, (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)) \
 9228+    _SG_XMACRO(glDrawElementsBaseVertex,          void, (GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex)) \
 9229+    _SG_XMACRO(glDrawElementsInstancedBaseVertex, void, (GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex)) \
 9230+    _SG_XMACRO(glDrawElementsInstancedBaseVertexBaseInstance, void, (GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance)) \
 9231+    _SG_XMACRO(glDrawArraysInstancedBaseInstance, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance))
 9232+
 9233+// generate GL function pointer typedefs
 9234+#define _SG_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args;
 9235+_SG_GL_FUNCS
 9236+#undef _SG_XMACRO
 9237+
 9238+// generate GL function pointers
 9239+#define _SG_XMACRO(name, ret, args) static PFN_ ## name name;
 9240+_SG_GL_FUNCS
 9241+#undef _SG_XMACRO
 9242+
 9243+// helper function to lookup GL functions in GL DLL
 9244+typedef PROC (WINAPI * _sg_wglGetProcAddress)(LPCSTR);
 9245+_SOKOL_PRIVATE void* _sg_gl_getprocaddr(const char* name, _sg_wglGetProcAddress wgl_getprocaddress) {
 9246+    void* proc_addr = (void*) wgl_getprocaddress(name);
 9247+    if (0 == proc_addr) {
 9248+        proc_addr = (void*) GetProcAddress(_sg.gl.opengl32_dll, name);
 9249+    }
 9250+    SOKOL_ASSERT(proc_addr);
 9251+    return proc_addr;
 9252+}
 9253+
 9254+// populate GL function pointers
 9255+_SOKOL_PRIVATE  void _sg_gl_load_opengl(void) {
 9256+    SOKOL_ASSERT(0 == _sg.gl.opengl32_dll);
 9257+    _sg.gl.opengl32_dll = LoadLibraryA("opengl32.dll");
 9258+    SOKOL_ASSERT(_sg.gl.opengl32_dll);
 9259+    _sg_wglGetProcAddress wgl_getprocaddress = (_sg_wglGetProcAddress) GetProcAddress(_sg.gl.opengl32_dll, "wglGetProcAddress");
 9260+    SOKOL_ASSERT(wgl_getprocaddress);
 9261+    #define _SG_XMACRO(name, ret, args) name = (PFN_ ## name) _sg_gl_getprocaddr(#name, wgl_getprocaddress);
 9262+    _SG_GL_FUNCS
 9263+    #undef _SG_XMACRO
 9264+}
 9265+
 9266+_SOKOL_PRIVATE void _sg_gl_unload_opengl(void) {
 9267+    SOKOL_ASSERT(_sg.gl.opengl32_dll);
 9268+    FreeLibrary(_sg.gl.opengl32_dll);
 9269+    _sg.gl.opengl32_dll = 0;
 9270+}
 9271+#endif // _SOKOL_USE_WIN32_GL_LOADER
 9272+
 9273+//-- type translation ----------------------------------------------------------
 9274+_SOKOL_PRIVATE GLenum _sg_gl_buffer_target(const sg_buffer_usage* usg) {
 9275+    // NOTE: the buffer target returned here is only used for the bind point
 9276+    // to copy data into the buffer, expect for WebGL2, the bind point doesn't
 9277+    // need to match the later usage of the buffer (but because of the WebGL2
 9278+    // restriction we cannot simply select a random bind point, because in WebGL2
 9279+    // a buffer cannot 'switch' bind points later.
 9280+    if (usg->vertex_buffer) {
 9281+        return GL_ARRAY_BUFFER;
 9282+    } else if (usg->index_buffer) {
 9283+        return GL_ELEMENT_ARRAY_BUFFER;
 9284+    } else if (usg->storage_buffer) {
 9285+        return GL_SHADER_STORAGE_BUFFER;
 9286+    } else {
 9287+        SOKOL_UNREACHABLE; return 0;
 9288+    }
 9289+}
 9290+
 9291+_SOKOL_PRIVATE GLenum _sg_gl_texture_target(sg_image_type t, int sample_count) {
 9292+    #if defined(SOKOL_GLCORE)
 9293+        const bool msaa = sample_count > 1;
 9294+        if (msaa) {
 9295+            switch (t) {
 9296+                case SG_IMAGETYPE_2D: return GL_TEXTURE_2D_MULTISAMPLE;
 9297+                case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
 9298+                default: SOKOL_UNREACHABLE; return 0;
 9299+            }
 9300+        } else {
 9301+            switch (t) {
 9302+                case SG_IMAGETYPE_2D:   return GL_TEXTURE_2D;
 9303+                case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP;
 9304+                case SG_IMAGETYPE_3D:       return GL_TEXTURE_3D;
 9305+                case SG_IMAGETYPE_ARRAY:    return GL_TEXTURE_2D_ARRAY;
 9306+                default: SOKOL_UNREACHABLE; return 0;
 9307+            }
 9308+        }
 9309+    #else
 9310+        SOKOL_ASSERT(sample_count == 1); _SOKOL_UNUSED(sample_count);
 9311+        switch (t) {
 9312+            case SG_IMAGETYPE_2D:   return GL_TEXTURE_2D;
 9313+            case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP;
 9314+            case SG_IMAGETYPE_3D:       return GL_TEXTURE_3D;
 9315+            case SG_IMAGETYPE_ARRAY:    return GL_TEXTURE_2D_ARRAY;
 9316+            default: SOKOL_UNREACHABLE; return 0;
 9317+        }
 9318+    #endif
 9319+}
 9320+
 9321+_SOKOL_PRIVATE GLenum _sg_gl_buffer_usage(const sg_buffer_usage* usg) {
 9322+    if (usg->immutable) {
 9323+        return GL_STATIC_DRAW;
 9324+    } else if (usg->dynamic_update) {
 9325+        return GL_DYNAMIC_DRAW;
 9326+    } else if (usg->stream_update) {
 9327+        return GL_STREAM_DRAW;
 9328+    } else {
 9329+        SOKOL_UNREACHABLE; return 0;
 9330+    }
 9331+}
 9332+
 9333+_SOKOL_PRIVATE GLenum _sg_gl_shader_stage(sg_shader_stage stage) {
 9334+    switch (stage) {
 9335+        case SG_SHADERSTAGE_VERTEX:   return GL_VERTEX_SHADER;
 9336+        case SG_SHADERSTAGE_FRAGMENT: return GL_FRAGMENT_SHADER;
 9337+        case SG_SHADERSTAGE_COMPUTE:  return GL_COMPUTE_SHADER;
 9338+        default: SOKOL_UNREACHABLE; return 0;
 9339+    }
 9340+}
 9341+
 9342+_SOKOL_PRIVATE GLint _sg_gl_vertexformat_size(sg_vertex_format fmt) {
 9343+    switch (fmt) {
 9344+        case SG_VERTEXFORMAT_FLOAT:     return 1;
 9345+        case SG_VERTEXFORMAT_FLOAT2:    return 2;
 9346+        case SG_VERTEXFORMAT_FLOAT3:    return 3;
 9347+        case SG_VERTEXFORMAT_FLOAT4:    return 4;
 9348+        case SG_VERTEXFORMAT_INT:       return 1;
 9349+        case SG_VERTEXFORMAT_INT2:      return 2;
 9350+        case SG_VERTEXFORMAT_INT3:      return 3;
 9351+        case SG_VERTEXFORMAT_INT4:      return 4;
 9352+        case SG_VERTEXFORMAT_UINT:      return 1;
 9353+        case SG_VERTEXFORMAT_UINT2:     return 2;
 9354+        case SG_VERTEXFORMAT_UINT3:     return 3;
 9355+        case SG_VERTEXFORMAT_UINT4:     return 4;
 9356+        case SG_VERTEXFORMAT_BYTE4:     return 4;
 9357+        case SG_VERTEXFORMAT_BYTE4N:    return 4;
 9358+        case SG_VERTEXFORMAT_UBYTE4:    return 4;
 9359+        case SG_VERTEXFORMAT_UBYTE4N:   return 4;
 9360+        case SG_VERTEXFORMAT_SHORT2:    return 2;
 9361+        case SG_VERTEXFORMAT_SHORT2N:   return 2;
 9362+        case SG_VERTEXFORMAT_USHORT2:   return 2;
 9363+        case SG_VERTEXFORMAT_USHORT2N:  return 2;
 9364+        case SG_VERTEXFORMAT_SHORT4:    return 4;
 9365+        case SG_VERTEXFORMAT_SHORT4N:   return 4;
 9366+        case SG_VERTEXFORMAT_USHORT4:   return 4;
 9367+        case SG_VERTEXFORMAT_USHORT4N:  return 4;
 9368+        case SG_VERTEXFORMAT_INT10_N2:  return 4;
 9369+        case SG_VERTEXFORMAT_UINT10_N2: return 4;
 9370+        case SG_VERTEXFORMAT_HALF2:     return 2;
 9371+        case SG_VERTEXFORMAT_HALF4:     return 4;
 9372+        default: SOKOL_UNREACHABLE; return 0;
 9373+    }
 9374+}
 9375+
 9376+_SOKOL_PRIVATE GLenum _sg_gl_vertexformat_type(sg_vertex_format fmt) {
 9377+    switch (fmt) {
 9378+        case SG_VERTEXFORMAT_FLOAT:
 9379+        case SG_VERTEXFORMAT_FLOAT2:
 9380+        case SG_VERTEXFORMAT_FLOAT3:
 9381+        case SG_VERTEXFORMAT_FLOAT4:
 9382+            return GL_FLOAT;
 9383+        case SG_VERTEXFORMAT_INT:
 9384+        case SG_VERTEXFORMAT_INT2:
 9385+        case SG_VERTEXFORMAT_INT3:
 9386+        case SG_VERTEXFORMAT_INT4:
 9387+            return GL_INT;
 9388+        case SG_VERTEXFORMAT_UINT:
 9389+        case SG_VERTEXFORMAT_UINT2:
 9390+        case SG_VERTEXFORMAT_UINT3:
 9391+        case SG_VERTEXFORMAT_UINT4:
 9392+            return GL_UNSIGNED_INT;
 9393+        case SG_VERTEXFORMAT_BYTE4:
 9394+        case SG_VERTEXFORMAT_BYTE4N:
 9395+            return GL_BYTE;
 9396+        case SG_VERTEXFORMAT_UBYTE4:
 9397+        case SG_VERTEXFORMAT_UBYTE4N:
 9398+            return GL_UNSIGNED_BYTE;
 9399+        case SG_VERTEXFORMAT_SHORT2:
 9400+        case SG_VERTEXFORMAT_SHORT2N:
 9401+        case SG_VERTEXFORMAT_SHORT4:
 9402+        case SG_VERTEXFORMAT_SHORT4N:
 9403+            return GL_SHORT;
 9404+        case SG_VERTEXFORMAT_USHORT2:
 9405+        case SG_VERTEXFORMAT_USHORT2N:
 9406+        case SG_VERTEXFORMAT_USHORT4:
 9407+        case SG_VERTEXFORMAT_USHORT4N:
 9408+            return GL_UNSIGNED_SHORT;
 9409+        case SG_VERTEXFORMAT_INT10_N2:
 9410+            return GL_INT_2_10_10_10_REV;
 9411+        case SG_VERTEXFORMAT_UINT10_N2:
 9412+            return GL_UNSIGNED_INT_2_10_10_10_REV;
 9413+        case SG_VERTEXFORMAT_HALF2:
 9414+        case SG_VERTEXFORMAT_HALF4:
 9415+            return GL_HALF_FLOAT;
 9416+        default:
 9417+            SOKOL_UNREACHABLE; return 0;
 9418+    }
 9419+}
 9420+
 9421+_SOKOL_PRIVATE GLboolean _sg_gl_vertexformat_normalized(sg_vertex_format fmt) {
 9422+    switch (fmt) {
 9423+        case SG_VERTEXFORMAT_BYTE4N:
 9424+        case SG_VERTEXFORMAT_UBYTE4N:
 9425+        case SG_VERTEXFORMAT_SHORT2N:
 9426+        case SG_VERTEXFORMAT_USHORT2N:
 9427+        case SG_VERTEXFORMAT_SHORT4N:
 9428+        case SG_VERTEXFORMAT_USHORT4N:
 9429+        case SG_VERTEXFORMAT_INT10_N2:
 9430+        case SG_VERTEXFORMAT_UINT10_N2:
 9431+            return GL_TRUE;
 9432+        default:
 9433+            return GL_FALSE;
 9434+    }
 9435+}
 9436+
 9437+_SOKOL_PRIVATE GLenum _sg_gl_primitive_type(sg_primitive_type t) {
 9438+    switch (t) {
 9439+        case SG_PRIMITIVETYPE_POINTS:           return GL_POINTS;
 9440+        case SG_PRIMITIVETYPE_LINES:            return GL_LINES;
 9441+        case SG_PRIMITIVETYPE_LINE_STRIP:       return GL_LINE_STRIP;
 9442+        case SG_PRIMITIVETYPE_TRIANGLES:        return GL_TRIANGLES;
 9443+        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return GL_TRIANGLE_STRIP;
 9444+        default: SOKOL_UNREACHABLE; return 0;
 9445+    }
 9446+}
 9447+
 9448+_SOKOL_PRIVATE GLenum _sg_gl_index_type(sg_index_type t) {
 9449+    switch (t) {
 9450+        case SG_INDEXTYPE_NONE:     return 0;
 9451+        case SG_INDEXTYPE_UINT16:   return GL_UNSIGNED_SHORT;
 9452+        case SG_INDEXTYPE_UINT32:   return GL_UNSIGNED_INT;
 9453+        default: SOKOL_UNREACHABLE; return 0;
 9454+    }
 9455+}
 9456+
 9457+_SOKOL_PRIVATE GLenum _sg_gl_compare_func(sg_compare_func cmp) {
 9458+    switch (cmp) {
 9459+        case SG_COMPAREFUNC_NEVER:          return GL_NEVER;
 9460+        case SG_COMPAREFUNC_LESS:           return GL_LESS;
 9461+        case SG_COMPAREFUNC_EQUAL:          return GL_EQUAL;
 9462+        case SG_COMPAREFUNC_LESS_EQUAL:     return GL_LEQUAL;
 9463+        case SG_COMPAREFUNC_GREATER:        return GL_GREATER;
 9464+        case SG_COMPAREFUNC_NOT_EQUAL:      return GL_NOTEQUAL;
 9465+        case SG_COMPAREFUNC_GREATER_EQUAL:  return GL_GEQUAL;
 9466+        case SG_COMPAREFUNC_ALWAYS:         return GL_ALWAYS;
 9467+        default: SOKOL_UNREACHABLE; return 0;
 9468+    }
 9469+}
 9470+
 9471+_SOKOL_PRIVATE GLenum _sg_gl_stencil_op(sg_stencil_op op) {
 9472+    switch (op) {
 9473+        case SG_STENCILOP_KEEP:         return GL_KEEP;
 9474+        case SG_STENCILOP_ZERO:         return GL_ZERO;
 9475+        case SG_STENCILOP_REPLACE:      return GL_REPLACE;
 9476+        case SG_STENCILOP_INCR_CLAMP:   return GL_INCR;
 9477+        case SG_STENCILOP_DECR_CLAMP:   return GL_DECR;
 9478+        case SG_STENCILOP_INVERT:       return GL_INVERT;
 9479+        case SG_STENCILOP_INCR_WRAP:    return GL_INCR_WRAP;
 9480+        case SG_STENCILOP_DECR_WRAP:    return GL_DECR_WRAP;
 9481+        default: SOKOL_UNREACHABLE; return 0;
 9482+    }
 9483+}
 9484+
 9485+_SOKOL_PRIVATE GLenum _sg_gl_blend_factor(sg_blend_factor f) {
 9486+    switch (f) {
 9487+        case SG_BLENDFACTOR_ZERO:                   return GL_ZERO;
 9488+        case SG_BLENDFACTOR_ONE:                    return GL_ONE;
 9489+        case SG_BLENDFACTOR_SRC_COLOR:              return GL_SRC_COLOR;
 9490+        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return GL_ONE_MINUS_SRC_COLOR;
 9491+        case SG_BLENDFACTOR_SRC_ALPHA:              return GL_SRC_ALPHA;
 9492+        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return GL_ONE_MINUS_SRC_ALPHA;
 9493+        case SG_BLENDFACTOR_DST_COLOR:              return GL_DST_COLOR;
 9494+        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return GL_ONE_MINUS_DST_COLOR;
 9495+        case SG_BLENDFACTOR_DST_ALPHA:              return GL_DST_ALPHA;
 9496+        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return GL_ONE_MINUS_DST_ALPHA;
 9497+        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return GL_SRC_ALPHA_SATURATE;
 9498+        case SG_BLENDFACTOR_BLEND_COLOR:            return GL_CONSTANT_COLOR;
 9499+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return GL_ONE_MINUS_CONSTANT_COLOR;
 9500+        case SG_BLENDFACTOR_BLEND_ALPHA:            return GL_CONSTANT_ALPHA;
 9501+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return GL_ONE_MINUS_CONSTANT_ALPHA;
 9502+        #if defined(_SOKOL_GL_HAS_DUALSOURCEBLENDING)
 9503+        case SG_BLENDFACTOR_SRC1_COLOR:             return GL_SRC1_COLOR;
 9504+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR:   return GL_ONE_MINUS_SRC1_COLOR;
 9505+        case SG_BLENDFACTOR_SRC1_ALPHA:             return GL_SRC1_ALPHA;
 9506+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA:   return GL_ONE_MINUS_SRC1_ALPHA;
 9507+        #endif
 9508+        default: SOKOL_UNREACHABLE; return 0;
 9509+    }
 9510+}
 9511+
 9512+_SOKOL_PRIVATE GLenum _sg_gl_blend_op(sg_blend_op op) {
 9513+    switch (op) {
 9514+        case SG_BLENDOP_ADD:                return GL_FUNC_ADD;
 9515+        case SG_BLENDOP_SUBTRACT:           return GL_FUNC_SUBTRACT;
 9516+        case SG_BLENDOP_REVERSE_SUBTRACT:   return GL_FUNC_REVERSE_SUBTRACT;
 9517+        case SG_BLENDOP_MIN:                return GL_MIN;
 9518+        case SG_BLENDOP_MAX:                return GL_MAX;
 9519+        default: SOKOL_UNREACHABLE; return 0;
 9520+    }
 9521+}
 9522+
 9523+_SOKOL_PRIVATE GLenum _sg_gl_min_filter(sg_filter min_f, sg_filter mipmap_f) {
 9524+    if (min_f == SG_FILTER_NEAREST) {
 9525+        switch (mipmap_f) {
 9526+            case SG_FILTER_NEAREST: return GL_NEAREST_MIPMAP_NEAREST;
 9527+            case SG_FILTER_LINEAR:  return GL_NEAREST_MIPMAP_LINEAR;
 9528+            default: SOKOL_UNREACHABLE; return (GLenum)0;
 9529+        }
 9530+    } else if (min_f == SG_FILTER_LINEAR) {
 9531+        switch (mipmap_f) {
 9532+            case SG_FILTER_NEAREST: return GL_LINEAR_MIPMAP_NEAREST;
 9533+            case SG_FILTER_LINEAR:  return GL_LINEAR_MIPMAP_LINEAR;
 9534+            default: SOKOL_UNREACHABLE; return (GLenum)0;
 9535+        }
 9536+    } else {
 9537+        SOKOL_UNREACHABLE; return (GLenum)0;
 9538+    }
 9539+}
 9540+
 9541+_SOKOL_PRIVATE GLenum _sg_gl_mag_filter(sg_filter mag_f) {
 9542+    if (mag_f == SG_FILTER_NEAREST) {
 9543+        return GL_NEAREST;
 9544+    } else {
 9545+        return GL_LINEAR;
 9546+    }
 9547+}
 9548+
 9549+_SOKOL_PRIVATE GLenum _sg_gl_wrap(sg_wrap w) {
 9550+    switch (w) {
 9551+        case SG_WRAP_CLAMP_TO_EDGE:     return GL_CLAMP_TO_EDGE;
 9552+        #if defined(SOKOL_GLCORE)
 9553+        case SG_WRAP_CLAMP_TO_BORDER:   return GL_CLAMP_TO_BORDER;
 9554+        #else
 9555+        case SG_WRAP_CLAMP_TO_BORDER:   return GL_CLAMP_TO_EDGE;
 9556+        #endif
 9557+        case SG_WRAP_REPEAT:            return GL_REPEAT;
 9558+        case SG_WRAP_MIRRORED_REPEAT:   return GL_MIRRORED_REPEAT;
 9559+        default: SOKOL_UNREACHABLE; return 0;
 9560+    }
 9561+}
 9562+
 9563+_SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) {
 9564+    switch (fmt) {
 9565+        case SG_PIXELFORMAT_R8:
 9566+        case SG_PIXELFORMAT_R8UI:
 9567+        case SG_PIXELFORMAT_RG8:
 9568+        case SG_PIXELFORMAT_RG8UI:
 9569+        case SG_PIXELFORMAT_RGBA8:
 9570+        case SG_PIXELFORMAT_SRGB8A8:
 9571+        case SG_PIXELFORMAT_RGBA8UI:
 9572+        case SG_PIXELFORMAT_BGRA8:
 9573+            return GL_UNSIGNED_BYTE;
 9574+        case SG_PIXELFORMAT_R8SN:
 9575+        case SG_PIXELFORMAT_R8SI:
 9576+        case SG_PIXELFORMAT_RG8SN:
 9577+        case SG_PIXELFORMAT_RG8SI:
 9578+        case SG_PIXELFORMAT_RGBA8SN:
 9579+        case SG_PIXELFORMAT_RGBA8SI:
 9580+            return GL_BYTE;
 9581+        case SG_PIXELFORMAT_R16:
 9582+        case SG_PIXELFORMAT_R16UI:
 9583+        case SG_PIXELFORMAT_RG16:
 9584+        case SG_PIXELFORMAT_RG16UI:
 9585+        case SG_PIXELFORMAT_RGBA16:
 9586+        case SG_PIXELFORMAT_RGBA16UI:
 9587+            return GL_UNSIGNED_SHORT;
 9588+        case SG_PIXELFORMAT_R16SN:
 9589+        case SG_PIXELFORMAT_R16SI:
 9590+        case SG_PIXELFORMAT_RG16SN:
 9591+        case SG_PIXELFORMAT_RG16SI:
 9592+        case SG_PIXELFORMAT_RGBA16SN:
 9593+        case SG_PIXELFORMAT_RGBA16SI:
 9594+            return GL_SHORT;
 9595+        case SG_PIXELFORMAT_R16F:
 9596+        case SG_PIXELFORMAT_RG16F:
 9597+        case SG_PIXELFORMAT_RGBA16F:
 9598+            return GL_HALF_FLOAT;
 9599+        case SG_PIXELFORMAT_R32UI:
 9600+        case SG_PIXELFORMAT_RG32UI:
 9601+        case SG_PIXELFORMAT_RGBA32UI:
 9602+            return GL_UNSIGNED_INT;
 9603+        case SG_PIXELFORMAT_R32SI:
 9604+        case SG_PIXELFORMAT_RG32SI:
 9605+        case SG_PIXELFORMAT_RGBA32SI:
 9606+            return GL_INT;
 9607+        case SG_PIXELFORMAT_R32F:
 9608+        case SG_PIXELFORMAT_RG32F:
 9609+        case SG_PIXELFORMAT_RGBA32F:
 9610+            return GL_FLOAT;
 9611+        case SG_PIXELFORMAT_RGB10A2:
 9612+            return GL_UNSIGNED_INT_2_10_10_10_REV;
 9613+        case SG_PIXELFORMAT_RG11B10F:
 9614+            return GL_UNSIGNED_INT_10F_11F_11F_REV;
 9615+        case SG_PIXELFORMAT_RGB9E5:
 9616+            return GL_UNSIGNED_INT_5_9_9_9_REV;
 9617+        case SG_PIXELFORMAT_DEPTH:
 9618+            return GL_FLOAT;
 9619+        case SG_PIXELFORMAT_DEPTH_STENCIL:
 9620+            return GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
 9621+        default:
 9622+            SOKOL_UNREACHABLE; return 0;
 9623+    }
 9624+}
 9625+
 9626+_SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) {
 9627+    switch (fmt) {
 9628+        case SG_PIXELFORMAT_R8:
 9629+        case SG_PIXELFORMAT_R8SN:
 9630+        case SG_PIXELFORMAT_R16:
 9631+        case SG_PIXELFORMAT_R16SN:
 9632+        case SG_PIXELFORMAT_R16F:
 9633+        case SG_PIXELFORMAT_R32F:
 9634+            return GL_RED;
 9635+        case SG_PIXELFORMAT_R8UI:
 9636+        case SG_PIXELFORMAT_R8SI:
 9637+        case SG_PIXELFORMAT_R16UI:
 9638+        case SG_PIXELFORMAT_R16SI:
 9639+        case SG_PIXELFORMAT_R32UI:
 9640+        case SG_PIXELFORMAT_R32SI:
 9641+            return GL_RED_INTEGER;
 9642+        case SG_PIXELFORMAT_RG8:
 9643+        case SG_PIXELFORMAT_RG8SN:
 9644+        case SG_PIXELFORMAT_RG16:
 9645+        case SG_PIXELFORMAT_RG16SN:
 9646+        case SG_PIXELFORMAT_RG16F:
 9647+        case SG_PIXELFORMAT_RG32F:
 9648+            return GL_RG;
 9649+        case SG_PIXELFORMAT_RG8UI:
 9650+        case SG_PIXELFORMAT_RG8SI:
 9651+        case SG_PIXELFORMAT_RG16UI:
 9652+        case SG_PIXELFORMAT_RG16SI:
 9653+        case SG_PIXELFORMAT_RG32UI:
 9654+        case SG_PIXELFORMAT_RG32SI:
 9655+            return GL_RG_INTEGER;
 9656+        case SG_PIXELFORMAT_RGBA8:
 9657+        case SG_PIXELFORMAT_SRGB8A8:
 9658+        case SG_PIXELFORMAT_RGBA8SN:
 9659+        case SG_PIXELFORMAT_RGBA16:
 9660+        case SG_PIXELFORMAT_RGBA16SN:
 9661+        case SG_PIXELFORMAT_RGBA16F:
 9662+        case SG_PIXELFORMAT_RGBA32F:
 9663+        case SG_PIXELFORMAT_RGB10A2:
 9664+            return GL_RGBA;
 9665+        case SG_PIXELFORMAT_RGBA8UI:
 9666+        case SG_PIXELFORMAT_RGBA8SI:
 9667+        case SG_PIXELFORMAT_RGBA16UI:
 9668+        case SG_PIXELFORMAT_RGBA16SI:
 9669+        case SG_PIXELFORMAT_RGBA32UI:
 9670+        case SG_PIXELFORMAT_RGBA32SI:
 9671+            return GL_RGBA_INTEGER;
 9672+        case SG_PIXELFORMAT_RG11B10F:
 9673+        case SG_PIXELFORMAT_RGB9E5:
 9674+            return GL_RGB;
 9675+        case SG_PIXELFORMAT_DEPTH:
 9676+            return GL_DEPTH_COMPONENT;
 9677+        case SG_PIXELFORMAT_DEPTH_STENCIL:
 9678+            return GL_DEPTH_STENCIL;
 9679+        case SG_PIXELFORMAT_BC1_RGBA:
 9680+            return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
 9681+        case SG_PIXELFORMAT_BC2_RGBA:
 9682+            return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
 9683+        case SG_PIXELFORMAT_BC3_RGBA:
 9684+            return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
 9685+        case SG_PIXELFORMAT_BC3_SRGBA:
 9686+            return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
 9687+        case SG_PIXELFORMAT_BC4_R:
 9688+            return GL_COMPRESSED_RED_RGTC1;
 9689+        case SG_PIXELFORMAT_BC4_RSN:
 9690+            return GL_COMPRESSED_SIGNED_RED_RGTC1;
 9691+        case SG_PIXELFORMAT_BC5_RG:
 9692+            return GL_COMPRESSED_RED_GREEN_RGTC2;
 9693+        case SG_PIXELFORMAT_BC5_RGSN:
 9694+            return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2;
 9695+        case SG_PIXELFORMAT_BC6H_RGBF:
 9696+            return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB;
 9697+        case SG_PIXELFORMAT_BC6H_RGBUF:
 9698+            return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB;
 9699+        case SG_PIXELFORMAT_BC7_RGBA:
 9700+            return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
 9701+        case SG_PIXELFORMAT_BC7_SRGBA:
 9702+            return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB;
 9703+        case SG_PIXELFORMAT_ETC2_RGB8:
 9704+            return GL_COMPRESSED_RGB8_ETC2;
 9705+        case SG_PIXELFORMAT_ETC2_SRGB8:
 9706+            return GL_COMPRESSED_SRGB8_ETC2;
 9707+        case SG_PIXELFORMAT_ETC2_RGB8A1:
 9708+            return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
 9709+        case SG_PIXELFORMAT_ETC2_RGBA8:
 9710+            return GL_COMPRESSED_RGBA8_ETC2_EAC;
 9711+        case SG_PIXELFORMAT_ETC2_SRGB8A8:
 9712+            return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
 9713+        case SG_PIXELFORMAT_EAC_R11:
 9714+            return GL_COMPRESSED_R11_EAC;
 9715+        case SG_PIXELFORMAT_EAC_R11SN:
 9716+            return GL_COMPRESSED_SIGNED_R11_EAC;
 9717+        case SG_PIXELFORMAT_EAC_RG11:
 9718+            return GL_COMPRESSED_RG11_EAC;
 9719+        case SG_PIXELFORMAT_EAC_RG11SN:
 9720+            return GL_COMPRESSED_SIGNED_RG11_EAC;
 9721+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:
 9722+            return GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
 9723+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA:
 9724+            return GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR;
 9725+        default:
 9726+            SOKOL_UNREACHABLE; return 0;
 9727+    }
 9728+}
 9729+
 9730+_SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) {
 9731+    switch (fmt) {
 9732+        case SG_PIXELFORMAT_R8:         return GL_R8;
 9733+        case SG_PIXELFORMAT_R8SN:       return GL_R8_SNORM;
 9734+        case SG_PIXELFORMAT_R8UI:       return GL_R8UI;
 9735+        case SG_PIXELFORMAT_R8SI:       return GL_R8I;
 9736+        #if !defined(SOKOL_GLES3)
 9737+            case SG_PIXELFORMAT_R16:        return GL_R16;
 9738+            case SG_PIXELFORMAT_R16SN:      return GL_R16_SNORM;
 9739+        #endif
 9740+        case SG_PIXELFORMAT_R16UI:      return GL_R16UI;
 9741+        case SG_PIXELFORMAT_R16SI:      return GL_R16I;
 9742+        case SG_PIXELFORMAT_R16F:       return GL_R16F;
 9743+        case SG_PIXELFORMAT_RG8:        return GL_RG8;
 9744+        case SG_PIXELFORMAT_RG8SN:      return GL_RG8_SNORM;
 9745+        case SG_PIXELFORMAT_RG8UI:      return GL_RG8UI;
 9746+        case SG_PIXELFORMAT_RG8SI:      return GL_RG8I;
 9747+        case SG_PIXELFORMAT_R32UI:      return GL_R32UI;
 9748+        case SG_PIXELFORMAT_R32SI:      return GL_R32I;
 9749+        case SG_PIXELFORMAT_R32F:       return GL_R32F;
 9750+        #if !defined(SOKOL_GLES3)
 9751+            case SG_PIXELFORMAT_RG16:       return GL_RG16;
 9752+            case SG_PIXELFORMAT_RG16SN:     return GL_RG16_SNORM;
 9753+        #endif
 9754+        case SG_PIXELFORMAT_RG16UI:     return GL_RG16UI;
 9755+        case SG_PIXELFORMAT_RG16SI:     return GL_RG16I;
 9756+        case SG_PIXELFORMAT_RG16F:      return GL_RG16F;
 9757+        case SG_PIXELFORMAT_RGBA8:      return GL_RGBA8;
 9758+        case SG_PIXELFORMAT_SRGB8A8:    return GL_SRGB8_ALPHA8;
 9759+        case SG_PIXELFORMAT_RGBA8SN:    return GL_RGBA8_SNORM;
 9760+        case SG_PIXELFORMAT_RGBA8UI:    return GL_RGBA8UI;
 9761+        case SG_PIXELFORMAT_RGBA8SI:    return GL_RGBA8I;
 9762+        case SG_PIXELFORMAT_RGB10A2:    return GL_RGB10_A2;
 9763+        case SG_PIXELFORMAT_RG11B10F:   return GL_R11F_G11F_B10F;
 9764+        case SG_PIXELFORMAT_RGB9E5:     return GL_RGB9_E5;
 9765+        case SG_PIXELFORMAT_RG32UI:     return GL_RG32UI;
 9766+        case SG_PIXELFORMAT_RG32SI:     return GL_RG32I;
 9767+        case SG_PIXELFORMAT_RG32F:      return GL_RG32F;
 9768+        #if !defined(SOKOL_GLES3)
 9769+            case SG_PIXELFORMAT_RGBA16:     return GL_RGBA16;
 9770+            case SG_PIXELFORMAT_RGBA16SN:   return GL_RGBA16_SNORM;
 9771+        #endif
 9772+        case SG_PIXELFORMAT_RGBA16UI:   return GL_RGBA16UI;
 9773+        case SG_PIXELFORMAT_RGBA16SI:   return GL_RGBA16I;
 9774+        case SG_PIXELFORMAT_RGBA16F:    return GL_RGBA16F;
 9775+        case SG_PIXELFORMAT_RGBA32UI:   return GL_RGBA32UI;
 9776+        case SG_PIXELFORMAT_RGBA32SI:   return GL_RGBA32I;
 9777+        case SG_PIXELFORMAT_RGBA32F:    return GL_RGBA32F;
 9778+        case SG_PIXELFORMAT_DEPTH:      return GL_DEPTH_COMPONENT32F;
 9779+        case SG_PIXELFORMAT_DEPTH_STENCIL:      return GL_DEPTH32F_STENCIL8;
 9780+        case SG_PIXELFORMAT_BC1_RGBA:           return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
 9781+        case SG_PIXELFORMAT_BC2_RGBA:           return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
 9782+        case SG_PIXELFORMAT_BC3_RGBA:           return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
 9783+        case SG_PIXELFORMAT_BC3_SRGBA:          return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
 9784+        case SG_PIXELFORMAT_BC4_R:              return GL_COMPRESSED_RED_RGTC1;
 9785+        case SG_PIXELFORMAT_BC4_RSN:            return GL_COMPRESSED_SIGNED_RED_RGTC1;
 9786+        case SG_PIXELFORMAT_BC5_RG:             return GL_COMPRESSED_RED_GREEN_RGTC2;
 9787+        case SG_PIXELFORMAT_BC5_RGSN:           return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2;
 9788+        case SG_PIXELFORMAT_BC6H_RGBF:          return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB;
 9789+        case SG_PIXELFORMAT_BC6H_RGBUF:         return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB;
 9790+        case SG_PIXELFORMAT_BC7_RGBA:           return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
 9791+        case SG_PIXELFORMAT_BC7_SRGBA:          return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB;
 9792+        case SG_PIXELFORMAT_ETC2_RGB8:          return GL_COMPRESSED_RGB8_ETC2;
 9793+        case SG_PIXELFORMAT_ETC2_SRGB8:         return GL_COMPRESSED_SRGB8_ETC2;
 9794+        case SG_PIXELFORMAT_ETC2_RGB8A1:        return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
 9795+        case SG_PIXELFORMAT_ETC2_RGBA8:         return GL_COMPRESSED_RGBA8_ETC2_EAC;
 9796+        case SG_PIXELFORMAT_ETC2_SRGB8A8:       return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
 9797+        case SG_PIXELFORMAT_EAC_R11:            return GL_COMPRESSED_R11_EAC;
 9798+        case SG_PIXELFORMAT_EAC_R11SN:          return GL_COMPRESSED_SIGNED_R11_EAC;
 9799+        case SG_PIXELFORMAT_EAC_RG11:           return GL_COMPRESSED_RG11_EAC;
 9800+        case SG_PIXELFORMAT_EAC_RG11SN:         return GL_COMPRESSED_SIGNED_RG11_EAC;
 9801+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:      return GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
 9802+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA:     return GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR;
 9803+        default: SOKOL_UNREACHABLE; return 0;
 9804+    }
 9805+}
 9806+
 9807+_SOKOL_PRIVATE GLenum _sg_gl_cubeface_target(int face_index) {
 9808+    switch (face_index) {
 9809+        case 0: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
 9810+        case 1: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
 9811+        case 2: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
 9812+        case 3: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
 9813+        case 4: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
 9814+        case 5: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
 9815+        default: SOKOL_UNREACHABLE; return 0;
 9816+    }
 9817+}
 9818+
 9819+// see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml
 9820+_SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) {
 9821+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]);
 9822+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]);
 9823+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]);
 9824+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]);
 9825+    #if !defined(SOKOL_GLES3)
 9826+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]);
 9827+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]);
 9828+    #endif
 9829+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]);
 9830+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]);
 9831+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]);
 9832+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]);
 9833+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]);
 9834+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]);
 9835+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]);
 9836+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]);
 9837+    #if !defined(SOKOL_GLES3)
 9838+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]);
 9839+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]);
 9840+    #endif
 9841+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]);
 9842+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]);
 9843+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
 9844+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]);
 9845+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
 9846+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
 9847+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
 9848+    if (has_bgra) {
 9849+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]);
 9850+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SBGR8A8]);
 9851+    }
 9852+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
 9853+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
 9854+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
 9855+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
 9856+    #if !defined(SOKOL_GLES3)
 9857+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]);
 9858+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]);
 9859+    #endif
 9860+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
 9861+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
 9862+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
 9863+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
 9864+    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]);
 9865+    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]);
 9866+}
 9867+
 9868+// FIXME: OES_half_float_blend
 9869+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_half_float(bool has_colorbuffer_half_float) {
 9870+    if (has_colorbuffer_half_float) {
 9871+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]);
 9872+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]);
 9873+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
 9874+    } else {
 9875+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R16F]);
 9876+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG16F]);
 9877+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
 9878+    }
 9879+}
 9880+
 9881+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_float(bool has_colorbuffer_float, bool has_texture_float_linear, bool has_float_blend) {
 9882+    if (has_texture_float_linear) {
 9883+        if (has_colorbuffer_float) {
 9884+            if (has_float_blend) {
 9885+                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]);
 9886+                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]);
 9887+                _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
 9888+            } else {
 9889+                _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_R32F]);
 9890+                _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG32F]);
 9891+                _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
 9892+            }
 9893+            _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
 9894+        } else {
 9895+            _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R32F]);
 9896+            _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG32F]);
 9897+            _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
 9898+            _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
 9899+        }
 9900+    } else {
 9901+        if (has_colorbuffer_float) {
 9902+            _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_R32F]);
 9903+            _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RG32F]);
 9904+            _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
 9905+            _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
 9906+        } else {
 9907+            _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R32F]);
 9908+            _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG32F]);
 9909+            _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
 9910+            _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
 9911+        }
 9912+    }
 9913+}
 9914+
 9915+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_s3tc(void) {
 9916+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]);
 9917+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]);
 9918+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]);
 9919+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]);
 9920+}
 9921+
 9922+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_rgtc(void) {
 9923+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]);
 9924+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]);
 9925+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]);
 9926+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]);
 9927+}
 9928+
 9929+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_bptc(void) {
 9930+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]);
 9931+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]);
 9932+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]);
 9933+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]);
 9934+}
 9935+
 9936+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_etc2(void) {
 9937+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]);
 9938+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]);
 9939+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]);
 9940+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]);
 9941+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]);
 9942+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]);
 9943+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]);
 9944+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]);
 9945+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]);
 9946+}
 9947+
 9948+_SOKOL_PRIVATE void _sg_gl_init_pixelformats_astc(void) {
 9949+     _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]);
 9950+     _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]);
 9951+}
 9952+
 9953+_SOKOL_PRIVATE void _sg_gl_init_limits(void) {
 9954+    _SG_GL_CHECK_ERROR();
 9955+
 9956+    GLint gl_int;
 9957+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_int);
 9958+    _SG_GL_CHECK_ERROR();
 9959+
 9960+    _sg.limits.max_image_size_2d = gl_int;
 9961+    _sg.limits.max_image_size_array = gl_int;
 9962+
 9963+    glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &gl_int);
 9964+    _SG_GL_CHECK_ERROR();
 9965+    _sg.limits.max_image_size_cube = gl_int;
 9966+
 9967+    glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gl_int);
 9968+    _SG_GL_CHECK_ERROR();
 9969+    _sg.limits.max_image_size_3d = gl_int;
 9970+
 9971+    glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gl_int);
 9972+    _SG_GL_CHECK_ERROR();
 9973+    _sg.limits.max_image_array_layers = gl_int;
 9974+
 9975+    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_int);
 9976+    _SG_GL_CHECK_ERROR();
 9977+    _sg.limits.max_vertex_attrs = _sg_min(gl_int, SG_MAX_VERTEX_ATTRIBUTES);
 9978+
 9979+    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gl_int);
 9980+    _SG_GL_CHECK_ERROR();
 9981+    _sg.limits.max_color_attachments = _sg_min(gl_int, SG_MAX_COLOR_ATTACHMENTS);
 9982+
 9983+    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gl_int);
 9984+    _SG_GL_CHECK_ERROR();
 9985+    _sg.limits.max_texture_bindings_per_stage = _sg_min(gl_int, SG_MAX_VIEW_BINDSLOTS);
 9986+
 9987+    #if defined(_SOKOL_GL_HAS_COMPUTE)
 9988+    if (_sg.features.compute) {
 9989+        glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &gl_int);
 9990+        _SG_GL_CHECK_ERROR();
 9991+        _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min(gl_int, SG_MAX_VIEW_BINDSLOTS);
 9992+
 9993+        glGetIntegerv(GL_MAX_IMAGE_UNITS, &gl_int);
 9994+        _SG_GL_CHECK_ERROR();
 9995+        _sg.limits.max_storage_image_bindings_per_stage = _sg_min(gl_int, SG_MAX_VIEW_BINDSLOTS);
 9996+    }
 9997+    #endif
 9998+
 9999+    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &gl_int);
10000+    _SG_GL_CHECK_ERROR();
10001+    _sg.limits.gl_max_vertex_uniform_components = gl_int;
10002+
10003+    if (_sg.gl.ext_anisotropic) {
10004+        glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_int);
10005+        _SG_GL_CHECK_ERROR();
10006+        _sg.gl.max_anisotropy = gl_int;
10007+    } else {
10008+        _sg.gl.max_anisotropy = 1;
10009+    }
10010+
10011+    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gl_int);
10012+    _SG_GL_CHECK_ERROR();
10013+    _sg.limits.gl_max_combined_texture_image_units = gl_int;
10014+}
10015+
10016+#if defined(SOKOL_GLCORE)
10017+_SOKOL_PRIVATE void _sg_gl_init_caps_glcore(void) {
10018+    _sg.backend = SG_BACKEND_GLCORE;
10019+
10020+    GLint major_version = 0;
10021+    GLint minor_version = 0;
10022+    glGetIntegerv(GL_MAJOR_VERSION, &major_version);
10023+    glGetIntegerv(GL_MINOR_VERSION, &minor_version);
10024+    const int version = major_version * 100 + minor_version * 10;
10025+    _sg.features.origin_top_left = false;
10026+    _sg.features.image_clamp_to_border = true;
10027+    _sg.features.mrt_independent_blend_state = false;
10028+    _sg.features.mrt_independent_write_mask = true;
10029+    _sg.features.compute = version >= 430;
10030+    _sg.features.gl_texture_views = version >= 430;
10031+    #if defined(__APPLE__)
10032+    _sg.features.msaa_texture_bindings = false;
10033+    #else
10034+    _sg.features.msaa_texture_bindings = true;
10035+    #endif
10036+    _sg.features.draw_base_vertex = version >= 320;
10037+    _sg.features.draw_base_instance = version >= 420;
10038+    _sg.features.dual_source_blending = version >= 330;
10039+    _sg.features.vertexformat_int10_n2 = true;
10040+
10041+    // scan extensions
10042+    bool has_s3tc = false;  // BC1..BC3
10043+    bool has_rgtc = false;  // BC4 and BC5
10044+    bool has_bptc = false;  // BC6H and BC7
10045+    bool has_etc2 = false;
10046+    bool has_astc = false;
10047+    GLint num_ext = 0;
10048+    glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext);
10049+    for (int i = 0; i < num_ext; i++) {
10050+        const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i);
10051+        if (ext) {
10052+            if (strstr(ext, "_texture_compression_s3tc")) {
10053+                has_s3tc = true;
10054+            } else if (strstr(ext, "_texture_compression_rgtc")) {
10055+                has_rgtc = true;
10056+            } else if (strstr(ext, "_texture_compression_bptc")) {
10057+                has_bptc = true;
10058+            } else if (strstr(ext, "_ES3_compatibility")) {
10059+                has_etc2 = true;
10060+            } else if (strstr(ext, "_texture_filter_anisotropic")) {
10061+                _sg.gl.ext_anisotropic = true;
10062+            } else if (strstr(ext, "_texture_compression_astc_ldr")) {
10063+                has_astc = true;
10064+            }
10065+        }
10066+    }
10067+
10068+    // limits
10069+    _sg_gl_init_limits();
10070+
10071+    // pixel formats
10072+    const bool has_bgra = false;    // not a bug
10073+    const bool has_colorbuffer_float = true;
10074+    const bool has_colorbuffer_half_float = true;
10075+    const bool has_texture_float_linear = true; // FIXME???
10076+    const bool has_float_blend = true;
10077+    _sg_gl_init_pixelformats(has_bgra);
10078+    _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend);
10079+    _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float);
10080+    if (has_s3tc) {
10081+        _sg_gl_init_pixelformats_s3tc();
10082+    }
10083+    if (has_rgtc) {
10084+        _sg_gl_init_pixelformats_rgtc();
10085+    }
10086+    if (has_bptc) {
10087+        _sg_gl_init_pixelformats_bptc();
10088+    }
10089+    if (has_etc2) {
10090+        _sg_gl_init_pixelformats_etc2();
10091+    }
10092+    if (has_astc) {
10093+        _sg_gl_init_pixelformats_astc();
10094+    }
10095+    if (_sg.features.compute) {
10096+        // see: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBindImageTexture.xhtml
10097+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
10098+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
10099+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32F]);
10100+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16F]);
10101+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
10102+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
10103+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
10104+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]);
10105+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16F]);
10106+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
10107+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
10108+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16UI]);
10109+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8UI]);
10110+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]);
10111+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16UI]);
10112+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8UI]);
10113+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
10114+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
10115+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
10116+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
10117+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16SI]);
10118+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8SI]);
10119+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]);
10120+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16SI]);
10121+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8SI]);
10122+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]);
10123+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
10124+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
10125+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16]);
10126+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8]);
10127+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16]);
10128+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8]);
10129+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]);
10130+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
10131+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]);
10132+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8SN]);
10133+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16SN]);
10134+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8SN]);
10135+    }
10136+}
10137+#endif
10138+
10139+#if defined(SOKOL_GLES3)
10140+_SOKOL_PRIVATE void _sg_gl_init_caps_gles3(void) {
10141+    _sg.backend = SG_BACKEND_GLES3;
10142+
10143+    GLint major_version = 0;
10144+    GLint minor_version = 0;
10145+    glGetIntegerv(GL_MAJOR_VERSION, &major_version);
10146+    glGetIntegerv(GL_MINOR_VERSION, &minor_version);
10147+    const int version = major_version * 100 + minor_version * 10;
10148+    _sg.features.origin_top_left = false;
10149+    _sg.features.image_clamp_to_border = false;
10150+    _sg.features.mrt_independent_blend_state = false;
10151+    #if defined(_SOKOL_GL_HAS_COLORMASKI)
10152+    _sg.features.mrt_independent_write_mask = version >= 320;
10153+    #else
10154+    _sg.features.mrt_independent_write_mask = false;
10155+    #endif
10156+    _sg.features.compute = version >= 310;
10157+    _sg.features.msaa_texture_bindings = false;
10158+    _sg.features.gl_texture_views = version >= 430;
10159+    #if defined(__EMSCRIPTEN__)
10160+    _sg.features.separate_buffer_types = true;
10161+    #else
10162+    _sg.features.separate_buffer_types = false;
10163+    #endif
10164+    _sg.features.draw_base_vertex = version >= 320;
10165+    _sg.features.draw_base_instance = false;
10166+    _sg.features.dual_source_blending = false;
10167+    _sg.features.vertexformat_int10_n2 = true;
10168+
10169+    bool has_s3tc = false;  // BC1..BC3
10170+    bool has_rgtc = false;  // BC4 and BC5
10171+    bool has_bptc = false;  // BC6H and BC7
10172+    #if defined(__EMSCRIPTEN__)
10173+        bool has_etc2 = false;
10174+    #else
10175+        bool has_etc2 = true;
10176+    #endif
10177+    bool has_astc = false;
10178+    bool has_colorbuffer_float = false;
10179+    bool has_colorbuffer_half_float = false;
10180+    bool has_texture_float_linear = false;
10181+    bool has_float_blend = false;
10182+    GLint num_ext = 0;
10183+    glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext);
10184+    for (int i = 0; i < num_ext; i++) {
10185+        const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i);
10186+        if (ext) {
10187+            if (strstr(ext, "_texture_compression_s3tc")) {
10188+                has_s3tc = true;
10189+            } else if (strstr(ext, "_compressed_texture_s3tc")) {
10190+                has_s3tc = true;
10191+            } else if (strstr(ext, "_texture_compression_rgtc")) {
10192+                has_rgtc = true;
10193+            } else if (strstr(ext, "_texture_compression_bptc")) {
10194+                has_bptc = true;
10195+            } else if (strstr(ext, "_compressed_texture_etc")) {
10196+                has_etc2 = true;
10197+            } else if (strstr(ext, "_compressed_texture_astc")) {
10198+                has_astc = true;
10199+            } else if (strstr(ext, "_color_buffer_float")) {
10200+                has_colorbuffer_float = true;
10201+            } else if (strstr(ext, "_color_buffer_half_float")) {
10202+                has_colorbuffer_half_float = true;
10203+            } else if (strstr(ext, "_texture_float_linear")) {
10204+                has_texture_float_linear = true;
10205+            } else if (strstr(ext, "_float_blend")) {
10206+                has_float_blend = true;
10207+            } else if (strstr(ext, "_texture_filter_anisotropic")) {
10208+                _sg.gl.ext_anisotropic = true;
10209+            }
10210+        }
10211+    }
10212+
10213+    /* on WebGL2, color_buffer_float also includes 16-bit formats
10214+       see: https://developer.mozilla.org/en-US/docs/Web/API/EXT_color_buffer_float
10215+    */
10216+    #if defined(__EMSCRIPTEN__)
10217+    if (!has_colorbuffer_half_float && has_colorbuffer_float) {
10218+        has_colorbuffer_half_float = has_colorbuffer_float;
10219+    }
10220+    #endif
10221+
10222+    // limits
10223+    _sg_gl_init_limits();
10224+
10225+    // pixel formats
10226+    const bool has_bgra = false;    // not a bug
10227+    _sg_gl_init_pixelformats(has_bgra);
10228+    _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend);
10229+    _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float);
10230+    if (has_s3tc) {
10231+        _sg_gl_init_pixelformats_s3tc();
10232+    }
10233+    if (has_rgtc) {
10234+        _sg_gl_init_pixelformats_rgtc();
10235+    }
10236+    if (has_bptc) {
10237+        _sg_gl_init_pixelformats_bptc();
10238+    }
10239+    if (has_etc2) {
10240+        _sg_gl_init_pixelformats_etc2();
10241+    }
10242+    if (has_astc) {
10243+        _sg_gl_init_pixelformats_astc();
10244+    }
10245+    if (_sg.features.compute) {
10246+        // see https://registry.khronos.org/OpenGL-Refpages/es3.1/html/glBindImageTexture.xhtml
10247+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
10248+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
10249+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]);
10250+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
10251+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
10252+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
10253+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]);
10254+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
10255+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
10256+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
10257+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]);
10258+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
10259+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
10260+    }
10261+}
10262+#endif
10263+
10264+//-- state cache implementation ------------------------------------------------
10265+_SOKOL_PRIVATE void _sg_gl_cache_clear_buffer_bindings(bool force) {
10266+    if (force || (_sg.gl.cache.vertex_buffer != 0)) {
10267+        glBindBuffer(GL_ARRAY_BUFFER, 0);
10268+        _sg.gl.cache.vertex_buffer = 0;
10269+        _sg_stats_inc(gl.num_bind_buffer);
10270+    }
10271+    if (force || (_sg.gl.cache.index_buffer != 0)) {
10272+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
10273+        _sg.gl.cache.index_buffer = 0;
10274+        _sg_stats_inc(gl.num_bind_buffer);
10275+    }
10276+    if (force || (_sg.gl.cache.storage_buffer != 0)) {
10277+        if (_sg.features.compute) {
10278+            glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
10279+        }
10280+        _sg.gl.cache.storage_buffer = 0;
10281+        _sg_stats_inc(gl.num_bind_buffer);
10282+    }
10283+    for (int i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) {
10284+        if (force || (_sg.gl.cache.storage_buffers[i] != 0)) {
10285+            if (_sg.features.compute && (i < _sg.limits.max_storage_buffer_bindings_per_stage)) {
10286+                glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, 0);
10287+            }
10288+            _sg.gl.cache.storage_buffers[i] = 0;
10289+            _sg_stats_inc(gl.num_bind_buffer);
10290+        }
10291+    }
10292+}
10293+
10294+_SOKOL_PRIVATE void _sg_gl_cache_bind_buffer(GLenum target, GLuint buffer) {
10295+    SOKOL_ASSERT((GL_ARRAY_BUFFER == target) || (GL_ELEMENT_ARRAY_BUFFER == target) || (GL_SHADER_STORAGE_BUFFER == target));
10296+    if (target == GL_ARRAY_BUFFER) {
10297+        if (_sg.gl.cache.vertex_buffer != buffer) {
10298+            _sg.gl.cache.vertex_buffer = buffer;
10299+            glBindBuffer(target, buffer);
10300+            _sg_stats_inc(gl.num_bind_buffer);
10301+        }
10302+    } else if (target == GL_ELEMENT_ARRAY_BUFFER) {
10303+        if (_sg.gl.cache.index_buffer != buffer) {
10304+            _sg.gl.cache.index_buffer = buffer;
10305+            glBindBuffer(target, buffer);
10306+            _sg_stats_inc(gl.num_bind_buffer);
10307+        }
10308+    } else if (target == GL_SHADER_STORAGE_BUFFER) {
10309+        if (_sg.gl.cache.storage_buffer != buffer) {
10310+            _sg.gl.cache.storage_buffer = buffer;
10311+            if (_sg.features.compute) {
10312+                glBindBuffer(target, buffer);
10313+            }
10314+            _sg_stats_inc(gl.num_bind_buffer);
10315+        }
10316+    } else {
10317+        SOKOL_UNREACHABLE;
10318+    }
10319+}
10320+
10321+_SOKOL_PRIVATE void _sg_gl_cache_bind_storage_buffer(uint8_t glsl_binding_n, GLuint buffer, int offset, int buf_size) {
10322+    SOKOL_ASSERT(glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS);
10323+    SOKOL_ASSERT(offset < buf_size);
10324+    const bool buf_neql = _sg.gl.cache.storage_buffers[glsl_binding_n] != buffer;
10325+    const bool off_neql = _sg.gl.cache.storage_buffer_offsets[glsl_binding_n] != offset;
10326+    if  (buf_neql || off_neql) {
10327+        _sg.gl.cache.storage_buffers[glsl_binding_n] = buffer;
10328+        _sg.gl.cache.storage_buffer_offsets[glsl_binding_n] = offset;
10329+        _sg.gl.cache.storage_buffer = buffer; // not a bug
10330+        if (_sg.features.compute) {
10331+            SOKOL_ASSERT(glsl_binding_n < _sg.limits.max_storage_buffer_bindings_per_stage);
10332+            glBindBufferRange(GL_SHADER_STORAGE_BUFFER, glsl_binding_n, buffer, offset, buf_size - offset);
10333+        }
10334+        _sg_stats_inc(gl.num_bind_buffer);
10335+    }
10336+}
10337+
10338+_SOKOL_PRIVATE void _sg_gl_cache_store_buffer_binding(GLenum target) {
10339+    if (target == GL_ARRAY_BUFFER) {
10340+        _sg.gl.cache.stored_vertex_buffer = _sg.gl.cache.vertex_buffer;
10341+    } else if (target == GL_ELEMENT_ARRAY_BUFFER) {
10342+        _sg.gl.cache.stored_index_buffer = _sg.gl.cache.index_buffer;
10343+    } else if (target == GL_SHADER_STORAGE_BUFFER) {
10344+        _sg.gl.cache.stored_storage_buffer = _sg.gl.cache.storage_buffer;
10345+    } else {
10346+        SOKOL_UNREACHABLE;
10347+    }
10348+}
10349+
10350+_SOKOL_PRIVATE void _sg_gl_cache_restore_buffer_binding(GLenum target) {
10351+    if (target == GL_ARRAY_BUFFER) {
10352+        if (_sg.gl.cache.stored_vertex_buffer != 0) {
10353+            // we only care about restoring valid ids
10354+            _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_vertex_buffer);
10355+            _sg.gl.cache.stored_vertex_buffer = 0;
10356+        }
10357+    } else if (target == GL_ELEMENT_ARRAY_BUFFER) {
10358+        if (_sg.gl.cache.stored_index_buffer != 0) {
10359+            // we only care about restoring valid ids
10360+            _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_index_buffer);
10361+            _sg.gl.cache.stored_index_buffer = 0;
10362+        }
10363+    } else if (target == GL_SHADER_STORAGE_BUFFER) {
10364+        if (_sg.gl.cache.stored_storage_buffer != 0) {
10365+            // we only care about restoring valid ids
10366+            _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_storage_buffer);
10367+            _sg.gl.cache.stored_storage_buffer = 0;
10368+        }
10369+    } else {
10370+        SOKOL_UNREACHABLE;
10371+    }
10372+}
10373+
10374+// called from _sg_gl_discard_buffer()
10375+_SOKOL_PRIVATE void _sg_gl_cache_invalidate_buffer(GLuint buf) {
10376+    if (buf == _sg.gl.cache.vertex_buffer) {
10377+        _sg.gl.cache.vertex_buffer = 0;
10378+        glBindBuffer(GL_ARRAY_BUFFER, 0);
10379+        _sg_stats_inc(gl.num_bind_buffer);
10380+    }
10381+    if (buf == _sg.gl.cache.index_buffer) {
10382+        _sg.gl.cache.index_buffer = 0;
10383+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
10384+        _sg_stats_inc(gl.num_bind_buffer);
10385+    }
10386+    if (buf == _sg.gl.cache.storage_buffer) {
10387+        _sg.gl.cache.storage_buffer = 0;
10388+        glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
10389+        _sg_stats_inc(gl.num_bind_buffer);
10390+    }
10391+    for (int i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) {
10392+        if (buf == _sg.gl.cache.storage_buffers[i]) {
10393+            _sg.gl.cache.storage_buffers[i] = 0;
10394+            _sg.gl.cache.storage_buffer = 0; // not a bug!
10395+            if (_sg.features.compute && (i < _sg.limits.max_storage_buffer_bindings_per_stage)) {
10396+                glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, 0);
10397+            }
10398+            _sg_stats_inc(gl.num_bind_buffer);
10399+        }
10400+    }
10401+    if (buf == _sg.gl.cache.stored_vertex_buffer) {
10402+        _sg.gl.cache.stored_vertex_buffer = 0;
10403+    }
10404+    if (buf == _sg.gl.cache.stored_index_buffer) {
10405+        _sg.gl.cache.stored_index_buffer = 0;
10406+    }
10407+    if (buf == _sg.gl.cache.stored_storage_buffer) {
10408+        _sg.gl.cache.stored_storage_buffer = 0;
10409+    }
10410+    for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) {
10411+        if (buf == _sg.gl.cache.attrs[i].gl_vbuf) {
10412+            _sg.gl.cache.attrs[i].gl_vbuf = 0;
10413+        }
10414+    }
10415+}
10416+
10417+_SOKOL_PRIVATE void _sg_gl_cache_active_texture(GLenum texture) {
10418+    _SG_GL_CHECK_ERROR();
10419+    if (_sg.gl.cache.cur_active_texture != texture) {
10420+        _sg.gl.cache.cur_active_texture = texture;
10421+        glActiveTexture(texture);
10422+        _sg_stats_inc(gl.num_active_texture);
10423+    }
10424+    _SG_GL_CHECK_ERROR();
10425+}
10426+
10427+_SOKOL_PRIVATE void _sg_gl_cache_clear_texture_sampler_bindings(bool force) {
10428+    _SG_GL_CHECK_ERROR();
10429+    for (int i = 0; (i < _SG_GL_MAX_TEX_SMP_BINDINGS) && (i < _sg.limits.gl_max_combined_texture_image_units); i++) {
10430+        if (force || (_sg.gl.cache.texture_samplers[i].texture != 0)) {
10431+            GLenum gl_texture_unit = (GLenum) (GL_TEXTURE0 + i);
10432+            glActiveTexture(gl_texture_unit);
10433+            _sg_stats_inc(gl.num_active_texture);
10434+            glBindTexture(GL_TEXTURE_2D, 0);
10435+            glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
10436+            glBindTexture(GL_TEXTURE_3D, 0);
10437+            glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
10438+            _sg_stats_add(gl.num_bind_texture, 4);
10439+            glBindSampler((GLuint)i, 0);
10440+            _sg_stats_inc(gl.num_bind_sampler);
10441+            _sg.gl.cache.texture_samplers[i].target = 0;
10442+            _sg.gl.cache.texture_samplers[i].texture = 0;
10443+            _sg.gl.cache.texture_samplers[i].sampler = 0;
10444+            _sg.gl.cache.cur_active_texture = gl_texture_unit;
10445+        }
10446+    }
10447+    _SG_GL_CHECK_ERROR();
10448+}
10449+
10450+_SOKOL_PRIVATE void _sg_gl_cache_bind_texture_sampler(int8_t gl_tex_slot, GLenum target, GLuint texture, GLuint sampler) {
10451+    /* it's valid to call this function with target=0 and/or texture=0
10452+       target=0 will unbind the previous binding, texture=0 will clear
10453+       the new binding
10454+    */
10455+    SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_TEX_SMP_BINDINGS));
10456+    if (gl_tex_slot >= _sg.limits.gl_max_combined_texture_image_units) {
10457+        return;
10458+    }
10459+    _SG_GL_CHECK_ERROR();
10460+    _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.texture_samplers[gl_tex_slot];
10461+    if ((slot->target != target) || (slot->texture != texture) || (slot->sampler != sampler)) {
10462+        _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + gl_tex_slot));
10463+        // if the target has changed, clear the previous binding on that target
10464+        if ((target != slot->target) && (slot->target != 0)) {
10465+            glBindTexture(slot->target, 0);
10466+            _SG_GL_CHECK_ERROR();
10467+            _sg_stats_inc(gl.num_bind_texture);
10468+        }
10469+        // apply new binding (can be 0 to unbind)
10470+        if (target != 0) {
10471+            glBindTexture(target, texture);
10472+            _SG_GL_CHECK_ERROR();
10473+            _sg_stats_inc(gl.num_bind_texture);
10474+        }
10475+        // apply new sampler (can be 0 to unbind)
10476+        glBindSampler((GLuint)gl_tex_slot, sampler);
10477+        _SG_GL_CHECK_ERROR();
10478+        _sg_stats_inc(gl.num_bind_sampler);
10479+
10480+        slot->target = target;
10481+        slot->texture = texture;
10482+        slot->sampler = sampler;
10483+    }
10484+}
10485+
10486+_SOKOL_PRIVATE void _sg_gl_cache_store_texture_sampler_binding(int8_t gl_tex_slot) {
10487+    SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_TEX_SMP_BINDINGS));
10488+    _sg.gl.cache.stored_texture_sampler = _sg.gl.cache.texture_samplers[gl_tex_slot];
10489+}
10490+
10491+_SOKOL_PRIVATE void _sg_gl_cache_restore_texture_sampler_binding(int8_t gl_tex_slot) {
10492+    SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_TEX_SMP_BINDINGS));
10493+    _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.stored_texture_sampler;
10494+    if (slot->texture != 0) {
10495+        // we only care about restoring valid ids
10496+        SOKOL_ASSERT(slot->target != 0);
10497+        _sg_gl_cache_bind_texture_sampler(gl_tex_slot, slot->target, slot->texture, slot->sampler);
10498+        slot->target = 0;
10499+        slot->texture = 0;
10500+        slot->sampler = 0;
10501+    }
10502+}
10503+
10504+// called from _sg_gl_discard_texture() and _sg_gl_discard_sampler()
10505+_SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture_sampler(GLuint tex, GLuint smp) {
10506+    _SG_GL_CHECK_ERROR();
10507+    for (size_t i = 0; i < _SG_GL_MAX_TEX_SMP_BINDINGS; i++) {
10508+        _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.texture_samplers[i];
10509+        if ((0 != slot->target) && ((tex == slot->texture) || (smp == slot->sampler))) {
10510+            _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + i));
10511+            glBindTexture(slot->target, 0);
10512+            _SG_GL_CHECK_ERROR();
10513+            _sg_stats_inc(gl.num_bind_texture);
10514+            glBindSampler((GLuint)i, 0);
10515+            _SG_GL_CHECK_ERROR();
10516+            _sg_stats_inc(gl.num_bind_sampler);
10517+            slot->target = 0;
10518+            slot->texture = 0;
10519+            slot->sampler = 0;
10520+        }
10521+    }
10522+    if ((tex == _sg.gl.cache.stored_texture_sampler.texture) || (smp == _sg.gl.cache.stored_texture_sampler.sampler)) {
10523+        _sg.gl.cache.stored_texture_sampler.target = 0;
10524+        _sg.gl.cache.stored_texture_sampler.texture = 0;
10525+        _sg.gl.cache.stored_texture_sampler.sampler = 0;
10526+    }
10527+}
10528+
10529+// called from _sg_gl_discard_shader()
10530+_SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) {
10531+    if (prog == _sg.gl.cache.prog) {
10532+        _sg.gl.cache.prog = 0;
10533+        glUseProgram(0);
10534+        _sg_stats_inc(gl.num_use_program);
10535+    }
10536+}
10537+
10538+// called from _sg_gl_discard_pipeline()
10539+_SOKOL_PRIVATE void _sg_gl_cache_invalidate_pipeline(_sg_pipeline_t* pip) {
10540+    if (_sg_sref_slot_eql(&_sg.gl.cache.cur_pip, &pip->slot)) {
10541+        _sg.gl.cache.cur_pip = _sg_sref(0);
10542+    }
10543+}
10544+
10545+_SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) {
10546+    _SG_GL_CHECK_ERROR();
10547+    glBindVertexArray(_sg.gl.vao);
10548+    _SG_GL_CHECK_ERROR();
10549+    _sg_clear(&_sg.gl.cache, sizeof(_sg.gl.cache));
10550+    _sg_gl_cache_clear_buffer_bindings(true);
10551+    _SG_GL_CHECK_ERROR();
10552+    _sg_gl_cache_clear_texture_sampler_bindings(true);
10553+    _SG_GL_CHECK_ERROR();
10554+    for (int i = 0; i < _sg.limits.max_vertex_attrs; i++) {
10555+        _sg_gl_attr_t* attr = &_sg.gl.cache.attrs[i].gl_attr;
10556+        attr->vb_index = -1;
10557+        attr->divisor = -1;
10558+        glDisableVertexAttribArray((GLuint)i);
10559+        _SG_GL_CHECK_ERROR();
10560+        _sg_stats_inc(gl.num_disable_vertex_attrib_array);
10561+    }
10562+    _sg.gl.cache.cur_primitive_type = GL_TRIANGLES;
10563+
10564+    // shader program
10565+    glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&_sg.gl.cache.prog);
10566+    _SG_GL_CHECK_ERROR();
10567+
10568+    // depth and stencil state
10569+    _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS;
10570+    _sg.gl.cache.stencil.front.compare = SG_COMPAREFUNC_ALWAYS;
10571+    _sg.gl.cache.stencil.front.fail_op = SG_STENCILOP_KEEP;
10572+    _sg.gl.cache.stencil.front.depth_fail_op = SG_STENCILOP_KEEP;
10573+    _sg.gl.cache.stencil.front.pass_op = SG_STENCILOP_KEEP;
10574+    _sg.gl.cache.stencil.back.compare = SG_COMPAREFUNC_ALWAYS;
10575+    _sg.gl.cache.stencil.back.fail_op = SG_STENCILOP_KEEP;
10576+    _sg.gl.cache.stencil.back.depth_fail_op = SG_STENCILOP_KEEP;
10577+    _sg.gl.cache.stencil.back.pass_op = SG_STENCILOP_KEEP;
10578+    glEnable(GL_DEPTH_TEST);
10579+    glDepthFunc(GL_ALWAYS);
10580+    glDepthMask(GL_FALSE);
10581+    glDisable(GL_STENCIL_TEST);
10582+    glStencilFunc(GL_ALWAYS, 0, 0);
10583+    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
10584+    glStencilMask(0);
10585+    _sg_stats_add(gl.num_render_state, 7);
10586+
10587+    // blend state
10588+    _sg.gl.cache.blend.src_factor_rgb = SG_BLENDFACTOR_ONE;
10589+    _sg.gl.cache.blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO;
10590+    _sg.gl.cache.blend.op_rgb = SG_BLENDOP_ADD;
10591+    _sg.gl.cache.blend.src_factor_alpha = SG_BLENDFACTOR_ONE;
10592+    _sg.gl.cache.blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO;
10593+    _sg.gl.cache.blend.op_alpha = SG_BLENDOP_ADD;
10594+    glDisable(GL_BLEND);
10595+    glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
10596+    glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
10597+    glBlendColor(0.0f, 0.0f, 0.0f, 0.0f);
10598+    _sg_stats_add(gl.num_render_state, 4);
10599+
10600+    // standalone state
10601+    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
10602+        _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA;
10603+    }
10604+    _sg.gl.cache.cull_mode = SG_CULLMODE_NONE;
10605+    _sg.gl.cache.face_winding = SG_FACEWINDING_CW;
10606+    _sg.gl.cache.sample_count = 1;
10607+    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
10608+    glPolygonOffset(0.0f, 0.0f);
10609+    glDisable(GL_POLYGON_OFFSET_FILL);
10610+    glDisable(GL_CULL_FACE);
10611+    glFrontFace(GL_CW);
10612+    glCullFace(GL_BACK);
10613+    glEnable(GL_SCISSOR_TEST);
10614+    glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
10615+    glEnable(GL_DITHER);
10616+    glDisable(GL_POLYGON_OFFSET_FILL);
10617+    _sg_stats_add(gl.num_render_state, 10);
10618+    #if defined(SOKOL_GLCORE)
10619+        glEnable(GL_MULTISAMPLE);
10620+        glEnable(GL_PROGRAM_POINT_SIZE);
10621+        _sg_stats_add(gl.num_render_state, 2);
10622+    #endif
10623+}
10624+
10625+_SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) {
10626+    _SOKOL_UNUSED(desc);
10627+
10628+    // assumes that _sg.gl is already zero-initialized
10629+    _sg.gl.valid = true;
10630+
10631+    #if defined(_SOKOL_USE_WIN32_GL_LOADER)
10632+    _sg_gl_load_opengl();
10633+    #endif
10634+
10635+    // clear initial GL error state
10636+    #if defined(SOKOL_DEBUG)
10637+        while (glGetError() != GL_NO_ERROR);
10638+    #endif
10639+    #if defined(SOKOL_GLCORE)
10640+        _sg_gl_init_caps_glcore();
10641+    #elif defined(SOKOL_GLES3)
10642+        _sg_gl_init_caps_gles3();
10643+    #endif
10644+
10645+    // create and bind global vertex array object which will be mutated as needed
10646+    glGenVertexArrays(1, &_sg.gl.vao);
10647+    glBindVertexArray(_sg.gl.vao);
10648+    _SG_GL_CHECK_ERROR();
10649+
10650+    // create global framebuffer object which will be mutated as needed
10651+    glGenFramebuffers(1, &_sg.gl.fb);
10652+    _SG_GL_CHECK_ERROR();
10653+
10654+    // incoming texture data is generally expected to be packed tightly
10655+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
10656+    #if defined(SOKOL_GLCORE)
10657+        // enable seamless cubemap sampling (only desktop GL)
10658+        glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
10659+    #endif
10660+    _sg_gl_reset_state_cache();
10661+}
10662+
10663+_SOKOL_PRIVATE void _sg_gl_discard_backend(void) {
10664+    SOKOL_ASSERT(_sg.gl.valid);
10665+    if (_sg.gl.fb) {
10666+        glDeleteFramebuffers(1, &_sg.gl.fb);
10667+    }
10668+    if (_sg.gl.vao) {
10669+        glDeleteVertexArrays(1, &_sg.gl.vao);
10670+    }
10671+    #if defined(_SOKOL_USE_WIN32_GL_LOADER)
10672+    _sg_gl_unload_opengl();
10673+    #endif
10674+    _sg.gl.valid = false;
10675+}
10676+
10677+//-- GL backend resource creation and destruction ------------------------------
10678+_SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
10679+    SOKOL_ASSERT(buf && desc);
10680+    _SG_GL_CHECK_ERROR();
10681+    buf->gl.injected = (0 != desc->gl_buffers[0]);
10682+    const GLenum gl_target = _sg_gl_buffer_target(&buf->cmn.usage);
10683+    const GLenum gl_usage  = _sg_gl_buffer_usage(&buf->cmn.usage);
10684+    for (int slot = 0; slot < buf->cmn.num_slots; slot++) {
10685+        GLuint gl_buf = 0;
10686+        if (buf->gl.injected) {
10687+            SOKOL_ASSERT(desc->gl_buffers[slot]);
10688+            gl_buf = desc->gl_buffers[slot];
10689+        } else {
10690+            glGenBuffers(1, &gl_buf);
10691+            SOKOL_ASSERT(gl_buf);
10692+            _sg_gl_cache_store_buffer_binding(gl_target);
10693+            _sg_gl_cache_bind_buffer(gl_target, gl_buf);
10694+            glBufferData(gl_target, buf->cmn.size, 0, gl_usage);
10695+            if (desc->data.ptr) {
10696+                glBufferSubData(gl_target, 0, buf->cmn.size, desc->data.ptr);
10697+            }
10698+            _sg_gl_cache_restore_buffer_binding(gl_target);
10699+        }
10700+        buf->gl.buf[slot] = gl_buf;
10701+    }
10702+    _SG_GL_CHECK_ERROR();
10703+    return SG_RESOURCESTATE_VALID;
10704+}
10705+
10706+_SOKOL_PRIVATE void _sg_gl_discard_buffer(_sg_buffer_t* buf) {
10707+    SOKOL_ASSERT(buf);
10708+    _SG_GL_CHECK_ERROR();
10709+    for (int slot = 0; slot < buf->cmn.num_slots; slot++) {
10710+        if (buf->gl.buf[slot]) {
10711+            _sg_gl_cache_invalidate_buffer(buf->gl.buf[slot]);
10712+            if (!buf->gl.injected) {
10713+                glDeleteBuffers(1, &buf->gl.buf[slot]);
10714+            }
10715+        }
10716+    }
10717+    _SG_GL_CHECK_ERROR();
10718+}
10719+
10720+_SOKOL_PRIVATE bool _sg_gl_supported_texture_format(sg_pixel_format fmt) {
10721+    const int fmt_index = (int) fmt;
10722+    SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM));
10723+    return _sg.formats[fmt_index].sample;
10724+}
10725+
10726+_SOKOL_PRIVATE void _sg_gl_texstorage(const _sg_image_t* img) {
10727+    const GLenum tgt = img->gl.target;
10728+    const int num_mips = img->cmn.num_mipmaps;
10729+    #if defined(_SOKOL_GL_HAS_TEXSTORAGE)
10730+        const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format);
10731+        const bool msaa = img->cmn.sample_count > 1;
10732+        const int w = img->cmn.width;
10733+        const int h = img->cmn.height;
10734+        if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) {
10735+            #if defined(SOKOL_GLCORE)
10736+                if (msaa) {
10737+                    glTexStorage2DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, GL_TRUE);
10738+                } else {
10739+                    glTexStorage2D(tgt, num_mips, ifmt, w, h);
10740+                }
10741+            #else
10742+                SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa);
10743+                glTexStorage2D(tgt, num_mips, ifmt, w, h);
10744+            #endif
10745+        } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) {
10746+            const int depth = img->cmn.num_slices;
10747+            #if defined(SOKOL_GLCORE)
10748+                if (msaa) {
10749+                    // NOTE: MSAA works only for array textures, not 3D textures
10750+                    glTexStorage3DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, depth, GL_TRUE);
10751+                } else {
10752+                    glTexStorage3D(tgt, num_mips, ifmt, w, h, depth);
10753+                }
10754+            #else
10755+                SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa);
10756+                glTexStorage3D(tgt, num_mips, ifmt, w, h, depth);
10757+            #endif
10758+        }
10759+    #else
10760+        glTexParameteri(tgt, GL_TEXTURE_MAX_LEVEL, num_mips - 1);
10761+    #endif
10762+    _SG_GL_CHECK_ERROR();
10763+}
10764+
10765+_SOKOL_PRIVATE void _sg_gl_texsubimage(const _sg_image_t* img, GLenum tgt, int mip_index, int w, int h, int depth, const GLvoid* data_ptr, GLsizei data_size) {
10766+    SOKOL_ASSERT(data_ptr && (data_size > 0));
10767+    SOKOL_ASSERT(img->cmn.sample_count == 1);
10768+    const bool compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format);
10769+    if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) {
10770+        if (compressed) {
10771+            const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format);
10772+            glCompressedTexSubImage2D(tgt, mip_index, 0, 0, w, h, ifmt, data_size, data_ptr);
10773+        } else {
10774+            const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format);
10775+            const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format);
10776+            glTexSubImage2D(tgt, mip_index, 0, 0, w, h, fmt, type, data_ptr);
10777+        }
10778+    } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) {
10779+        if (compressed) {
10780+            const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format);
10781+            glCompressedTexSubImage3D(tgt, mip_index, 0, 0, 0, w, h, depth, ifmt, data_size, data_ptr);
10782+        } else {
10783+            const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format);
10784+            const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format);
10785+            glTexSubImage3D(tgt, mip_index, 0, 0, 0, w, h, depth, fmt, type, data_ptr);
10786+        }
10787+    }
10788+}
10789+
10790+_SOKOL_PRIVATE void _sg_gl_teximage(const _sg_image_t* img, GLenum tgt, int mip_index, int w, int h, int depth, const GLvoid* data_ptr, GLsizei data_size) {
10791+    #if defined(_SOKOL_GL_HAS_TEXSTORAGE)
10792+        if (data_ptr == 0) {
10793+            return;
10794+        }
10795+        _sg_gl_texsubimage(img, tgt, mip_index, w, h, depth, data_ptr, data_size);
10796+    #else
10797+        const bool compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format);
10798+        const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format);
10799+        const bool msaa = img->cmn.sample_count > 1;
10800+        if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) {
10801+            if (compressed) {
10802+                SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa);
10803+                glCompressedTexImage2D(tgt, mip_index, ifmt, w, h, 0, data_size, data_ptr);
10804+            } else {
10805+                const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format);
10806+                const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format);
10807+                #if defined(SOKOL_GLCORE) && !defined(__APPLE__)
10808+                    if (msaa) {
10809+                        glTexImage2DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, GL_TRUE);
10810+                    } else {
10811+                        glTexImage2D(tgt, mip_index, (GLint)ifmt, w, h, 0, fmt, type, data_ptr);
10812+                    }
10813+                #else
10814+                    SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa);
10815+                    glTexImage2D(tgt, mip_index, (GLint)ifmt, w, h, 0, fmt, type, data_ptr);
10816+                #endif
10817+            }
10818+        } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) {
10819+            if (compressed) {
10820+                SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa);
10821+                glCompressedTexImage3D(tgt, mip_index, ifmt, w, h, depth, 0, data_size, data_ptr);
10822+            } else {
10823+                const GLenum type = _sg_gl_teximage_type(img->cmn.pixel_format);
10824+                const GLenum fmt = _sg_gl_teximage_format(img->cmn.pixel_format);
10825+                #if defined(SOKOL_GLCORE) && !defined(__APPLE__)
10826+                    if (msaa) {
10827+                        // NOTE: MSAA works only for array textures, not 3D textures
10828+                        glTexImage3DMultisample(tgt, img->cmn.sample_count, ifmt, w, h, depth, GL_TRUE);
10829+                    } else {
10830+                        glTexImage3D(tgt, mip_index, (GLint)ifmt, w, h, depth, 0, fmt, type, data_ptr);
10831+                    }
10832+                #else
10833+                    SOKOL_ASSERT(!msaa); _SOKOL_UNUSED(msaa);
10834+                    glTexImage3D(tgt, mip_index, (GLint)ifmt, w, h, depth, 0, fmt, type, data_ptr);
10835+                #endif
10836+            }
10837+        }
10838+    #endif
10839+    _SG_GL_CHECK_ERROR();
10840+}
10841+
10842+_SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_image_desc* desc) {
10843+    SOKOL_ASSERT(img && desc);
10844+    _SG_GL_CHECK_ERROR();
10845+    img->gl.injected = (0 != desc->gl_textures[0]);
10846+
10847+    // check if texture format is support
10848+    if (!_sg_gl_supported_texture_format(img->cmn.pixel_format)) {
10849+        _SG_ERROR(GL_TEXTURE_FORMAT_NOT_SUPPORTED);
10850+        return SG_RESOURCESTATE_FAILED;
10851+    }
10852+
10853+    if (img->gl.injected) {
10854+        img->gl.target = _sg_gl_texture_target(img->cmn.type, img->cmn.sample_count);
10855+        // inject externally GL textures
10856+        for (int slot = 0; slot < img->cmn.num_slots; slot++) {
10857+            SOKOL_ASSERT(desc->gl_textures[slot]);
10858+            img->gl.tex[slot] = desc->gl_textures[slot];
10859+        }
10860+        if (desc->gl_texture_target) {
10861+            img->gl.target = (GLenum)desc->gl_texture_target;
10862+        }
10863+    } else {
10864+        // on platforms that don't support MSAA texture bindings, no actual GL
10865+        // texture object is created, instead only attachment view object can be built
10866+        const bool msaa = img->cmn.sample_count > 1;
10867+        if (msaa && !_sg.features.msaa_texture_bindings) {
10868+            if (img->cmn.usage.color_attachment || img->cmn.usage.depth_stencil_attachment) {
10869+                return SG_RESOURCESTATE_VALID;
10870+            } else {
10871+                return SG_RESOURCESTATE_FAILED;
10872+            }
10873+        }
10874+        img->gl.target = _sg_gl_texture_target(img->cmn.type, img->cmn.sample_count);
10875+        for (int slot = 0; slot < img->cmn.num_slots; slot++) {
10876+            glGenTextures(1, &img->gl.tex[slot]);
10877+            SOKOL_ASSERT(img->gl.tex[slot]);
10878+            _sg_gl_cache_store_texture_sampler_binding(0);
10879+            _sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[slot], 0);
10880+            _sg_gl_texstorage(img);
10881+            for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) {
10882+                const GLvoid* data_ptr = desc->data.mip_levels[mip_index].ptr;
10883+                const GLsizei data_size = (GLsizei)desc->data.mip_levels[mip_index].size;
10884+                const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
10885+                const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
10886+                const int mip_depth = (SG_IMAGETYPE_3D == img->cmn.type) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices;
10887+                if (SG_IMAGETYPE_CUBE == img->cmn.type) {
10888+                    const int surf_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1);
10889+                    // NOTE: surf_ptr is allowed to be null here
10890+                    const uint8_t* surf_ptr = (const uint8_t*) data_ptr;
10891+                    for (int i = 0; i < 6; i++) {
10892+                        const GLenum gl_img_target = _sg_gl_cubeface_target(i);
10893+                        _sg_gl_teximage(img, gl_img_target, mip_index, mip_width, mip_height, mip_depth, surf_ptr, surf_pitch);
10894+                        if (data_ptr) {
10895+                            SOKOL_ASSERT((6 * surf_pitch) <= data_size);
10896+                            surf_ptr += surf_pitch;
10897+                        }
10898+                    }
10899+                } else {
10900+                    _sg_gl_teximage(img, img->gl.target, mip_index, mip_width, mip_height, mip_depth, data_ptr, data_size);
10901+                }
10902+            }
10903+            _sg_gl_cache_restore_texture_sampler_binding(0);
10904+        }
10905+    }
10906+    _SG_GL_CHECK_ERROR();
10907+    return SG_RESOURCESTATE_VALID;
10908+}
10909+
10910+_SOKOL_PRIVATE void _sg_gl_discard_image(_sg_image_t* img) {
10911+    SOKOL_ASSERT(img);
10912+    _SG_GL_CHECK_ERROR();
10913+    for (int slot = 0; slot < img->cmn.num_slots; slot++) {
10914+        if (img->gl.tex[slot]) {
10915+            _sg_gl_cache_invalidate_texture_sampler(img->gl.tex[slot], 0);
10916+            if (!img->gl.injected) {
10917+                glDeleteTextures(1, &img->gl.tex[slot]);
10918+            }
10919+        }
10920+    }
10921+    _SG_GL_CHECK_ERROR();
10922+}
10923+
10924+_SOKOL_PRIVATE sg_resource_state _sg_gl_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
10925+    SOKOL_ASSERT(smp && desc);
10926+    _SG_GL_CHECK_ERROR();
10927+    smp->gl.injected = (0 != desc->gl_sampler);
10928+    if (smp->gl.injected) {
10929+        smp->gl.smp = (GLuint) desc->gl_sampler;
10930+    } else {
10931+        glGenSamplers(1, &smp->gl.smp);
10932+        SOKOL_ASSERT(smp->gl.smp);
10933+
10934+        const GLenum gl_min_filter = _sg_gl_min_filter(smp->cmn.min_filter, smp->cmn.mipmap_filter);
10935+        const GLenum gl_mag_filter = _sg_gl_mag_filter(smp->cmn.mag_filter);
10936+        glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MIN_FILTER, (GLint)gl_min_filter);
10937+        glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MAG_FILTER, (GLint)gl_mag_filter);
10938+        // GL spec has strange defaults for mipmap min/max lod: -1000 to +1000
10939+        const float min_lod = _sg_clamp(desc->min_lod, 0.0f, 1000.0f);
10940+        const float max_lod = _sg_clamp(desc->max_lod, 0.0f, 1000.0f);
10941+        glSamplerParameterf(smp->gl.smp, GL_TEXTURE_MIN_LOD, min_lod);
10942+        glSamplerParameterf(smp->gl.smp, GL_TEXTURE_MAX_LOD, max_lod);
10943+        glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_S, (GLint)_sg_gl_wrap(smp->cmn.wrap_u));
10944+        glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_T, (GLint)_sg_gl_wrap(smp->cmn.wrap_v));
10945+        glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_R, (GLint)_sg_gl_wrap(smp->cmn.wrap_w));
10946+        #if defined(SOKOL_GLCORE)
10947+        float border[4];
10948+        switch (smp->cmn.border_color) {
10949+            case SG_BORDERCOLOR_TRANSPARENT_BLACK:
10950+                border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 0.0f;
10951+                break;
10952+            case SG_BORDERCOLOR_OPAQUE_WHITE:
10953+                border[0] = 1.0f; border[1] = 1.0f; border[2] = 1.0f; border[3] = 1.0f;
10954+                break;
10955+            default:
10956+                border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 1.0f;
10957+                break;
10958+        }
10959+        glSamplerParameterfv(smp->gl.smp, GL_TEXTURE_BORDER_COLOR, border);
10960+        #endif
10961+        if (smp->cmn.compare != SG_COMPAREFUNC_NEVER) {
10962+            glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
10963+            glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_FUNC, (GLint)_sg_gl_compare_func(smp->cmn.compare));
10964+        } else {
10965+            glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_MODE, GL_NONE);
10966+        }
10967+        if (_sg.gl.ext_anisotropic && (smp->cmn.max_anisotropy > 1)) {
10968+            GLint max_aniso = (GLint) smp->cmn.max_anisotropy;
10969+            if (max_aniso > _sg.gl.max_anisotropy) {
10970+                max_aniso = _sg.gl.max_anisotropy;
10971+            }
10972+            glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_aniso);
10973+        }
10974+    }
10975+    _SG_GL_CHECK_ERROR();
10976+    return SG_RESOURCESTATE_VALID;
10977+}
10978+
10979+_SOKOL_PRIVATE void _sg_gl_discard_sampler(_sg_sampler_t* smp) {
10980+    SOKOL_ASSERT(smp);
10981+    _SG_GL_CHECK_ERROR();
10982+    _sg_gl_cache_invalidate_texture_sampler(0, smp->gl.smp);
10983+    if (!smp->gl.injected) {
10984+        glDeleteSamplers(1, &smp->gl.smp);
10985+    }
10986+    _SG_GL_CHECK_ERROR();
10987+}
10988+
10989+_SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* src) {
10990+    SOKOL_ASSERT(src);
10991+    _SG_GL_CHECK_ERROR();
10992+    GLuint gl_shd = glCreateShader(_sg_gl_shader_stage(stage));
10993+    glShaderSource(gl_shd, 1, &src, 0);
10994+    glCompileShader(gl_shd);
10995+    GLint compile_status = 0;
10996+    glGetShaderiv(gl_shd, GL_COMPILE_STATUS, &compile_status);
10997+    if (!compile_status) {
10998+        // compilation failed, log error and delete shader
10999+        GLint log_len = 0;
11000+        glGetShaderiv(gl_shd, GL_INFO_LOG_LENGTH, &log_len);
11001+        if (log_len > 0) {
11002+            GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len);
11003+            glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf);
11004+            _SG_ERROR(GL_SHADER_COMPILATION_FAILED);
11005+            _SG_LOGMSG(GL_SHADER_COMPILATION_FAILED, log_buf);
11006+            _sg_free(log_buf);
11007+        }
11008+        glDeleteShader(gl_shd);
11009+        gl_shd = 0;
11010+    }
11011+    _SG_GL_CHECK_ERROR();
11012+    return gl_shd;
11013+}
11014+
11015+// NOTE: this is an out-of-range check for GLSL bindslots that's also active in release mode
11016+_SOKOL_PRIVATE bool _sg_gl_ensure_glsl_bindslot_ranges(const sg_shader_desc* desc) {
11017+    SOKOL_ASSERT(desc); _SOKOL_UNUSED(desc);
11018+    #if defined(_SOKOL_GL_HAS_COMPUTE)
11019+        SOKOL_ASSERT(_sg.limits.max_storage_buffer_bindings_per_stage <= _SG_GL_MAX_SBUF_BINDINGS);
11020+        SOKOL_ASSERT(_sg.limits.max_storage_image_bindings_per_stage <= _SG_GL_MAX_SIMG_BINDINGS);
11021+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
11022+            const sg_shader_view* view = &desc->views[i];
11023+            if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
11024+                if (view->storage_buffer.glsl_binding_n >= _sg.limits.max_storage_buffer_bindings_per_stage) {
11025+                    _SG_ERROR(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE);
11026+                    return false;
11027+                }
11028+            }
11029+            if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
11030+                if (view->storage_image.glsl_binding_n >= _sg.limits.max_storage_image_bindings_per_stage) {
11031+                    _SG_ERROR(GL_STORAGEIMAGE_GLSL_BINDING_OUT_OF_RANGE);
11032+                    return false;
11033+                }
11034+            }
11035+        }
11036+    #endif
11037+    return true;
11038+}
11039+
11040+_SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
11041+    SOKOL_ASSERT(shd && desc);
11042+    SOKOL_ASSERT(!shd->gl.prog);
11043+    _SG_GL_CHECK_ERROR();
11044+
11045+    // perform a fatal range-check on GLSL bindslots that's also active
11046+    // in release mode to avoid potential out-of-bounds array accesses
11047+    if (!_sg_gl_ensure_glsl_bindslot_ranges(desc)) {
11048+        return SG_RESOURCESTATE_FAILED;
11049+    }
11050+
11051+    // copy the optional vertex attribute names over
11052+    for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) {
11053+        _sg_strcpy(&shd->gl.attrs[i].name, desc->attrs[i].glsl_name);
11054+    }
11055+
11056+    const bool has_vs = desc->vertex_func.source;
11057+    const bool has_fs = desc->fragment_func.source;
11058+    const bool has_cs = desc->compute_func.source;
11059+    SOKOL_ASSERT((has_vs && has_fs) || has_cs);
11060+    GLuint gl_prog = glCreateProgram();
11061+    if (has_vs && has_fs) {
11062+        GLuint gl_vs = _sg_gl_compile_shader(SG_SHADERSTAGE_VERTEX, desc->vertex_func.source);
11063+        GLuint gl_fs = _sg_gl_compile_shader(SG_SHADERSTAGE_FRAGMENT, desc->fragment_func.source);
11064+        if (!(gl_vs && gl_fs)) {
11065+            glDeleteProgram(gl_prog);
11066+            if (gl_vs) { glDeleteShader(gl_vs); }
11067+            if (gl_fs) { glDeleteShader(gl_fs); }
11068+            return SG_RESOURCESTATE_FAILED;
11069+        }
11070+        glAttachShader(gl_prog, gl_vs);
11071+        glAttachShader(gl_prog, gl_fs);
11072+        glLinkProgram(gl_prog);
11073+        glDeleteShader(gl_vs);
11074+        glDeleteShader(gl_fs);
11075+        _SG_GL_CHECK_ERROR();
11076+    } else if (has_cs) {
11077+        GLuint gl_cs = _sg_gl_compile_shader(SG_SHADERSTAGE_COMPUTE, desc->compute_func.source);
11078+        if (!gl_cs) {
11079+            glDeleteProgram(gl_prog);
11080+            return SG_RESOURCESTATE_FAILED;
11081+        }
11082+        glAttachShader(gl_prog, gl_cs);
11083+        glLinkProgram(gl_prog);
11084+        glDeleteShader(gl_cs);
11085+        _SG_GL_CHECK_ERROR();
11086+    } else {
11087+        SOKOL_UNREACHABLE;
11088+    }
11089+    GLint link_status;
11090+    glGetProgramiv(gl_prog, GL_LINK_STATUS, &link_status);
11091+    if (!link_status) {
11092+        GLint log_len = 0;
11093+        glGetProgramiv(gl_prog, GL_INFO_LOG_LENGTH, &log_len);
11094+        if (log_len > 0) {
11095+            GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len);
11096+            glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf);
11097+            _SG_ERROR(GL_SHADER_LINKING_FAILED);
11098+            _SG_LOGMSG(GL_SHADER_LINKING_FAILED, log_buf);
11099+            _sg_free(log_buf);
11100+        }
11101+        glDeleteProgram(gl_prog);
11102+        return SG_RESOURCESTATE_FAILED;
11103+    }
11104+    shd->gl.prog = gl_prog;
11105+
11106+    // resolve uniforms
11107+    _SG_GL_CHECK_ERROR();
11108+    for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) {
11109+        const sg_shader_uniform_block* ub_desc = &desc->uniform_blocks[ub_index];
11110+        if (ub_desc->stage == SG_SHADERSTAGE_NONE) {
11111+            continue;
11112+        }
11113+        SOKOL_ASSERT(ub_desc->size > 0);
11114+        _sg_gl_uniform_block_t* ub = &shd->gl.uniform_blocks[ub_index];
11115+        SOKOL_ASSERT(ub->num_uniforms == 0);
11116+        uint32_t cur_uniform_offset = 0;
11117+        for (int u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) {
11118+            const sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index];
11119+            if (u_desc->type == SG_UNIFORMTYPE_INVALID) {
11120+                break;
11121+            }
11122+            const uint32_t u_align = _sg_uniform_alignment(u_desc->type, u_desc->array_count, ub_desc->layout);
11123+            const uint32_t u_size = _sg_uniform_size(u_desc->type, u_desc->array_count, ub_desc->layout);
11124+            cur_uniform_offset = _sg_align_u32(cur_uniform_offset, u_align);
11125+            _sg_gl_uniform_t* u = &ub->uniforms[u_index];
11126+            u->type = u_desc->type;
11127+            u->count = (uint16_t) u_desc->array_count;
11128+            u->offset = (uint16_t) cur_uniform_offset;
11129+            SOKOL_ASSERT(u_desc->glsl_name);
11130+            u->gl_loc = glGetUniformLocation(gl_prog, u_desc->glsl_name);
11131+            if (u->gl_loc == -1) {
11132+                _SG_WARN(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER);
11133+                _SG_LOGMSG(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER, u_desc->glsl_name);
11134+            }
11135+            cur_uniform_offset += u_size;
11136+            ub->num_uniforms++;
11137+        }
11138+        if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) {
11139+            cur_uniform_offset = _sg_align_u32(cur_uniform_offset, 16);
11140+        }
11141+        SOKOL_ASSERT(ub_desc->size == (size_t)cur_uniform_offset);
11142+        _SOKOL_UNUSED(cur_uniform_offset);
11143+    }
11144+
11145+    // copy resource bindslot mappings
11146+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
11147+        const sg_shader_view* view = &desc->views[i];
11148+        SOKOL_ASSERT(0 == shd->gl.sbuf_binding[i]);
11149+        SOKOL_ASSERT(0 == shd->gl.simg_binding[i]);
11150+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
11151+            shd->gl.sbuf_binding[i] = view->storage_buffer.glsl_binding_n;
11152+        } else if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
11153+            shd->gl.simg_binding[i] = view->storage_image.glsl_binding_n;
11154+        }
11155+    }
11156+
11157+    // record image sampler location in shader program
11158+    _SG_GL_CHECK_ERROR();
11159+    GLuint cur_prog = 0;
11160+    glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&cur_prog);
11161+    glUseProgram(gl_prog);
11162+    GLint gl_tex_slot = 0;
11163+    for (size_t tex_smp_index = 0; tex_smp_index < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_index++) {
11164+        const sg_shader_texture_sampler_pair* tex_smp_desc = &desc->texture_sampler_pairs[tex_smp_index];
11165+        if (tex_smp_desc->stage == SG_SHADERSTAGE_NONE) {
11166+            continue;
11167+        }
11168+        SOKOL_ASSERT(tex_smp_desc->glsl_name);
11169+        GLint gl_loc = glGetUniformLocation(gl_prog, tex_smp_desc->glsl_name);
11170+        if (gl_loc != -1) {
11171+            glUniform1i(gl_loc, gl_tex_slot);
11172+            shd->gl.tex_slot[tex_smp_index] = (int8_t)gl_tex_slot++;
11173+        } else {
11174+            shd->gl.tex_slot[tex_smp_index] = -1;
11175+            _SG_WARN(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER);
11176+            _SG_LOGMSG(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, tex_smp_desc->glsl_name);
11177+        }
11178+    }
11179+
11180+    // it's legal to call glUseProgram with 0
11181+    glUseProgram(cur_prog);
11182+    _SG_GL_CHECK_ERROR();
11183+    return SG_RESOURCESTATE_VALID;
11184+}
11185+
11186+_SOKOL_PRIVATE void _sg_gl_discard_shader(_sg_shader_t* shd) {
11187+    SOKOL_ASSERT(shd);
11188+    _SG_GL_CHECK_ERROR();
11189+    if (shd->gl.prog) {
11190+        _sg_gl_cache_invalidate_program(shd->gl.prog);
11191+        glDeleteProgram(shd->gl.prog);
11192+    }
11193+    _SG_GL_CHECK_ERROR();
11194+}
11195+
11196+_SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
11197+    SOKOL_ASSERT(pip && desc);
11198+    SOKOL_ASSERT(_sg.limits.max_vertex_attrs <= SG_MAX_VERTEX_ATTRIBUTES);
11199+    if (pip->cmn.is_compute) {
11200+        // shortcut for compute pipelines
11201+        return SG_RESOURCESTATE_VALID;
11202+    }
11203+    pip->gl.primitive_type = desc->primitive_type;
11204+    pip->gl.depth = desc->depth;
11205+    pip->gl.stencil = desc->stencil;
11206+    // FIXME: blend color and write mask per draw-buffer-attachment (requires GL4)
11207+    pip->gl.blend = desc->colors[0].blend;
11208+    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
11209+        pip->gl.color_write_mask[i] = desc->colors[i].write_mask;
11210+    }
11211+    pip->gl.cull_mode = desc->cull_mode;
11212+    pip->gl.face_winding = desc->face_winding;
11213+    pip->gl.sample_count = desc->sample_count;
11214+    pip->gl.alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled;
11215+
11216+    // NOTE: GLSL compilers may remove unused vertex attributes so we can't rely
11217+    // on the 'prepopulated' vertex_buffer_layout_active[] state and need to
11218+    // fill this array from scratch with the actual info after GLSL compilation
11219+    for (int i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
11220+        pip->cmn.vertex_buffer_layout_active[i] = false;
11221+    }
11222+
11223+    // resolve vertex attributes
11224+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
11225+    SOKOL_ASSERT(shd->gl.prog);
11226+    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
11227+        pip->gl.attrs[attr_index].vb_index = -1;
11228+    }
11229+    for (int attr_index = 0; attr_index < _sg.limits.max_vertex_attrs; attr_index++) {
11230+        const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index];
11231+        if (a_state->format == SG_VERTEXFORMAT_INVALID) {
11232+            break;
11233+        }
11234+        SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
11235+        const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[a_state->buffer_index];
11236+        const sg_vertex_step step_func = l_state->step_func;
11237+        const int step_rate = l_state->step_rate;
11238+        GLint attr_loc = attr_index;
11239+        if (!_sg_strempty(&shd->gl.attrs[attr_index].name)) {
11240+            attr_loc = glGetAttribLocation(shd->gl.prog, _sg_strptr(&shd->gl.attrs[attr_index].name));
11241+        }
11242+        if (attr_loc != -1) {
11243+            SOKOL_ASSERT(attr_loc < (GLint)_sg.limits.max_vertex_attrs);
11244+            _sg_gl_attr_t* gl_attr = &pip->gl.attrs[attr_loc];
11245+            SOKOL_ASSERT(gl_attr->vb_index == -1);
11246+            gl_attr->vb_index = (int8_t) a_state->buffer_index;
11247+            if (step_func == SG_VERTEXSTEP_PER_VERTEX) {
11248+                gl_attr->divisor = 0;
11249+            } else {
11250+                gl_attr->divisor = (int8_t) step_rate;
11251+            }
11252+            SOKOL_ASSERT(l_state->stride > 0);
11253+            gl_attr->stride = (uint8_t) l_state->stride;
11254+            gl_attr->offset = a_state->offset;
11255+            gl_attr->size = (uint8_t) _sg_gl_vertexformat_size(a_state->format);
11256+            gl_attr->type = _sg_gl_vertexformat_type(a_state->format);
11257+            gl_attr->normalized = _sg_gl_vertexformat_normalized(a_state->format);
11258+            gl_attr->base_type = _sg_vertexformat_basetype(a_state->format);
11259+            pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true;
11260+        } else {
11261+            _SG_WARN(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER);
11262+            _SG_LOGMSG(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, _sg_strptr(&shd->gl.attrs[attr_index].name));
11263+        }
11264+    }
11265+    return SG_RESOURCESTATE_VALID;
11266+}
11267+
11268+_SOKOL_PRIVATE void _sg_gl_discard_pipeline(_sg_pipeline_t* pip) {
11269+    SOKOL_ASSERT(pip);
11270+    _sg_gl_cache_invalidate_pipeline(pip);
11271+}
11272+
11273+_SOKOL_PRIVATE void _sg_gl_fb_attach_texture(const _sg_view_t* view, GLenum gl_att_type) {
11274+    const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11275+    const GLuint gl_tex = img->gl.tex[0];
11276+    SOKOL_ASSERT(gl_tex);
11277+    const GLuint gl_target = img->gl.target;
11278+    SOKOL_ASSERT(gl_target);
11279+    const int mip_level = view->cmn.img.mip_level;
11280+    const int slice = view->cmn.img.slice;
11281+    switch (img->cmn.type) {
11282+        case SG_IMAGETYPE_2D:
11283+            glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, gl_target, gl_tex, mip_level);
11284+            break;
11285+        case SG_IMAGETYPE_CUBE:
11286+            glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, _sg_gl_cubeface_target(slice), gl_tex, mip_level);
11287+            break;
11288+        default:
11289+            glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_att_type, gl_tex, mip_level, slice);
11290+            break;
11291+    }
11292+}
11293+
11294+_SOKOL_PRIVATE GLenum _sg_gl_depth_stencil_attachment_type(const _sg_image_t* ds_img) {
11295+    if (_sg_is_depth_stencil_format(ds_img->cmn.pixel_format)) {
11296+        return GL_DEPTH_STENCIL_ATTACHMENT;
11297+    } else {
11298+        return GL_DEPTH_ATTACHMENT;
11299+    }
11300+}
11301+
11302+_SOKOL_PRIVATE bool _sg_gl_check_framebuffer_status(void) {
11303+    const GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
11304+    if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
11305+        switch (fb_status) {
11306+            case GL_FRAMEBUFFER_UNDEFINED:
11307+                _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNDEFINED);
11308+                break;
11309+            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
11310+                _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT);
11311+                break;
11312+            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
11313+                _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT);
11314+                break;
11315+            case GL_FRAMEBUFFER_UNSUPPORTED:
11316+                _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNSUPPORTED);
11317+                break;
11318+            case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
11319+                _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE);
11320+                break;
11321+            default:
11322+                _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNKNOWN);
11323+                break;
11324+        }
11325+        return false;
11326+    }
11327+    return true;
11328+}
11329+
11330+_SOKOL_PRIVATE sg_resource_state _sg_gl_create_view(_sg_view_t* view, const sg_view_desc* desc) {
11331+    SOKOL_ASSERT(view && desc);
11332+    _SOKOL_UNUSED(desc);
11333+    _SG_GL_CHECK_ERROR();
11334+    if ((view->cmn.type == SG_VIEWTYPE_TEXTURE) && (_sg.features.gl_texture_views)) {
11335+        #if defined(_SOKOL_GL_HAS_TEXVIEWS)
11336+        if (_sg.features.gl_texture_views) {
11337+            const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11338+            for (int slot = 0; slot < img->cmn.num_slots; slot++) {
11339+                SOKOL_ASSERT(img->gl.tex[slot] != 0);
11340+                const GLuint min_level = (GLuint)view->cmn.img.mip_level;
11341+                const GLuint num_levels = (GLuint)view->cmn.img.mip_level_count;
11342+                const GLuint min_layer = (GLuint)view->cmn.img.slice;
11343+                const GLuint num_layers = (GLuint)view->cmn.img.slice_count;
11344+                const GLenum ifmt = _sg_gl_teximage_internal_format(img->cmn.pixel_format);
11345+                glGenTextures(1, &view->gl.tex_view[slot]);
11346+                glTextureView(view->gl.tex_view[slot], img->gl.target, img->gl.tex[slot], ifmt, min_level, num_levels, min_layer, num_layers);
11347+            }
11348+        }
11349+        #endif
11350+    } else if ((view->cmn.type == SG_VIEWTYPE_COLORATTACHMENT) || (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT)) {
11351+        // create MSAA render buffer if MSAA textures are not supported
11352+        const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11353+        const bool msaa = img->cmn.sample_count > 1;
11354+        if (msaa && !_sg.features.msaa_texture_bindings) {
11355+            const GLenum gl_internal_format = _sg_gl_teximage_internal_format(img->cmn.pixel_format);
11356+            glGenRenderbuffers(1, &view->gl.msaa_render_buffer);
11357+            glBindRenderbuffer(GL_RENDERBUFFER, view->gl.msaa_render_buffer);
11358+            glRenderbufferStorageMultisample(GL_RENDERBUFFER, img->cmn.sample_count, gl_internal_format, img->cmn.width, img->cmn.height);
11359+        }
11360+    } else if (view->cmn.type == SG_VIEWTYPE_RESOLVEATTACHMENT) {
11361+        // store current framebuffer binding (restored at end of block)
11362+        GLuint gl_orig_fb;
11363+        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&gl_orig_fb);
11364+
11365+        // create MSAA resolve framebuffer
11366+        glGenFramebuffers(1, &view->gl.msaa_resolve_frame_buffer);
11367+        glBindFramebuffer(GL_FRAMEBUFFER, view->gl.msaa_resolve_frame_buffer);
11368+        _sg_gl_fb_attach_texture(view, GL_COLOR_ATTACHMENT0);
11369+        if (!_sg_gl_check_framebuffer_status()) {
11370+            return SG_RESOURCESTATE_FAILED;
11371+        }
11372+        // setup color attachments for the framebuffer
11373+        static const GLenum gl_draw_buf = GL_COLOR_ATTACHMENT0;
11374+        glDrawBuffers(1, &gl_draw_buf);
11375+        // bind original framebuffer
11376+        glBindFramebuffer(GL_FRAMEBUFFER, gl_orig_fb);
11377+    }
11378+    _SG_GL_CHECK_ERROR();
11379+    return SG_RESOURCESTATE_VALID;
11380+}
11381+
11382+_SOKOL_PRIVATE void _sg_gl_discard_view(_sg_view_t* view) {
11383+    SOKOL_ASSERT(view);
11384+    _SG_GL_CHECK_ERROR();
11385+    for (size_t slot = 0; slot < SG_NUM_INFLIGHT_FRAMES; slot++) {
11386+        if (0 != view->gl.tex_view[slot]) {
11387+            // NOTE: cache invalidation also works as expected without
11388+            // GL texture view support, in that case the view's texture object
11389+            // will simply remain bound until the sg_image object is discarded
11390+            _sg_gl_cache_invalidate_texture_sampler(view->gl.tex_view[slot], 0);
11391+            glDeleteTextures(1, &view->gl.tex_view[slot]);
11392+        }
11393+    }
11394+    if (view->gl.msaa_render_buffer) {
11395+        glDeleteRenderbuffers(1, &view->gl.msaa_render_buffer);
11396+    }
11397+    if (view->gl.msaa_resolve_frame_buffer) {
11398+        glDeleteFramebuffers(1, &view->gl.msaa_resolve_frame_buffer);
11399+    }
11400+    _SG_GL_CHECK_ERROR();
11401+}
11402+
11403+#if defined(_SOKOL_GL_HAS_COMPUTE)
11404+_SOKOL_PRIVATE void _sg_gl_handle_memory_barriers(const _sg_shader_t* shd, const _sg_bindings_ptrs_t* bnd, const _sg_attachments_ptrs_t* atts) {
11405+    SOKOL_ASSERT((shd && bnd && atts == 0) || (atts && shd == 0 && bnd == 0));
11406+    if (!_sg.features.compute) {
11407+        return;
11408+    }
11409+    GLbitfield gl_barrier_bits = 0;
11410+
11411+    // if vertex-, index- or storage-buffer bindings have been written
11412+    // by a compute shader before, a barrier must be issued
11413+    if (bnd) {
11414+        for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
11415+            _sg_buffer_t* buf = bnd->vbs[i];
11416+            if (!buf) {
11417+                continue;
11418+            }
11419+            if (buf->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_VERTEXBUFFER) {
11420+                gl_barrier_bits |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
11421+                buf->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_VERTEXBUFFER;
11422+            }
11423+        }
11424+        if (bnd->ib) {
11425+            _sg_buffer_t* buf = bnd->ib;
11426+            if (buf->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_INDEXBUFFER) {
11427+                gl_barrier_bits |= GL_ELEMENT_ARRAY_BARRIER_BIT;
11428+                buf->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_INDEXBUFFER;
11429+            }
11430+        }
11431+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
11432+            const _sg_view_t* view = bnd->views[i];
11433+            if (!view) {
11434+                continue;
11435+            }
11436+            if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
11437+                _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
11438+                if (buf->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_STORAGEBUFFER) {
11439+                    gl_barrier_bits |= GL_SHADER_STORAGE_BARRIER_BIT;
11440+                    buf->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_STORAGEBUFFER;
11441+                }
11442+            } else if (view->cmn.type == SG_VIEWTYPE_TEXTURE) {
11443+                _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11444+                if (img->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_TEXTURE) {
11445+                    gl_barrier_bits |= GL_TEXTURE_FETCH_BARRIER_BIT;
11446+                    img->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_TEXTURE;
11447+                }
11448+            } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) {
11449+                _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11450+                if (img->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_STORAGEIMAGE) {
11451+                    gl_barrier_bits |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
11452+                    img->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_STORAGEIMAGE;
11453+                }
11454+            } else {
11455+                SOKOL_UNREACHABLE;
11456+            }
11457+        }
11458+    }
11459+    if (atts) {
11460+        for (int i = 0; i < atts->num_color_views; i++) {
11461+            const _sg_view_t* view = atts->color_views[i];
11462+            SOKOL_ASSERT(view);
11463+            _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11464+            if (img->gl.gpu_dirty_flags & _SG_GL_GPUDIRTY_ATTACHMENT) {
11465+                gl_barrier_bits |= GL_FRAMEBUFFER_BARRIER_BIT;
11466+                img->gl.gpu_dirty_flags &= (uint8_t)~_SG_GL_GPUDIRTY_ATTACHMENT;
11467+            }
11468+        }
11469+    }
11470+    if (0 != gl_barrier_bits) {
11471+        glMemoryBarrier(gl_barrier_bits);
11472+        _sg_stats_inc(gl.num_memory_barriers);
11473+    }
11474+
11475+    // mark resources as dirty which will be written by compute shaders
11476+    // (don't merge this into the above loop, this would mess up the
11477+    // dirty flags if the same resource is bound multiple times)
11478+    if (bnd) {
11479+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
11480+            const _sg_view_t* view = bnd->views[i];
11481+            if (!view) {
11482+                continue;
11483+            }
11484+            if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
11485+                if (!shd->cmn.views[i].sbuf_readonly) {
11486+                    _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
11487+                    buf->gl.gpu_dirty_flags = _SG_GL_GPUDIRTY_BUFFER_ALL;
11488+                }
11489+            } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) {
11490+                // NOTE: storage image bindings are always written, otherwise
11491+                // they would be texture bindings!
11492+                _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11493+                img->gl.gpu_dirty_flags = _SG_GL_GPUDIRTY_IMAGE_ALL;
11494+            }
11495+        }
11496+    }
11497+}
11498+#endif
11499+
11500+_SOKOL_PRIVATE void _sg_gl_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
11501+    SOKOL_ASSERT(pass && atts);
11502+    _SG_GL_CHECK_ERROR();
11503+
11504+    // early out if this a compute pass
11505+    if (pass->compute) {
11506+        return;
11507+    }
11508+
11509+    const sg_swapchain* swapchain = &pass->swapchain;
11510+    const sg_pass_action* action = &pass->action;
11511+    const bool is_swapchain_pass = atts->empty;
11512+    const bool is_offscreen_pass = !atts->empty;
11513+
11514+    // bind the render pass framebuffer
11515+    if (is_offscreen_pass) {
11516+
11517+        // offscreen pass, mutate the global offscreen framebuffer object
11518+        #if defined(SOKOL_GLCORE)
11519+        glEnable(GL_FRAMEBUFFER_SRGB);
11520+        #endif
11521+        glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.fb);
11522+        for (int i = 0; i < atts->num_color_views; i++) {
11523+            const _sg_view_t* view = atts->color_views[i];
11524+            const GLenum gl_att_type = (GLenum)(GL_COLOR_ATTACHMENT0 + i);
11525+            if (view->gl.msaa_render_buffer) {
11526+                glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att_type, GL_RENDERBUFFER, view->gl.msaa_render_buffer);
11527+            } else {
11528+                _sg_gl_fb_attach_texture(view, gl_att_type);
11529+            }
11530+        }
11531+        // explicitly detach unused color attachments
11532+        for (int i = atts->num_color_views; i < _sg.limits.max_color_attachments; i++) {
11533+            const GLenum gl_att_type = (GLenum)(GL_COLOR_ATTACHMENT0 + i);
11534+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att_type, GL_RENDERBUFFER, 0);
11535+            glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, GL_TEXTURE_2D, 0, 0);
11536+        }
11537+        if (atts->ds_view) {
11538+            // When switching between depth-only and depth-stencil attachments,
11539+            // explicitly detach BOTH attachment types first. Some GL drivers
11540+            // fail with GL_FRAMEBUFFER_UNSUPPORTED if both attachment types
11541+            // are bound to the same FBO.
11542+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
11543+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
11544+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
11545+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
11546+            const _sg_view_t* view = atts->ds_view;
11547+            const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
11548+            const GLenum gl_att_type = _sg_gl_depth_stencil_attachment_type(img);
11549+            if (view->gl.msaa_render_buffer) {
11550+                glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att_type, GL_RENDERBUFFER, view->gl.msaa_render_buffer);
11551+            } else {
11552+                _sg_gl_fb_attach_texture(view, gl_att_type);
11553+            }
11554+        } else {
11555+            // explicitly detach depth-stencil attachment if not used in this pass
11556+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
11557+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
11558+        }
11559+        if (!_sg_gl_check_framebuffer_status()) {
11560+            _sg.cur_pass.valid = false;
11561+            return;
11562+        }
11563+        GLenum gl_draw_bufs[SG_MAX_COLOR_ATTACHMENTS];
11564+        SOKOL_ASSERT(_sg.limits.max_color_attachments <= SG_MAX_COLOR_ATTACHMENTS);
11565+        for (int i = 0; i < _sg.limits.max_color_attachments; i++) {
11566+            if (i < atts->num_color_views) {
11567+                gl_draw_bufs[i] = (GLenum)(GL_COLOR_ATTACHMENT0 + i);
11568+            } else {
11569+                gl_draw_bufs[i] = GL_NONE;
11570+            }
11571+        }
11572+        glDrawBuffers(_sg.limits.max_color_attachments, gl_draw_bufs);
11573+
11574+        #if defined(_SOKOL_GL_HAS_COMPUTE)
11575+            _sg_gl_handle_memory_barriers(0, 0, atts);
11576+            _SG_GL_CHECK_ERROR();
11577+        #endif
11578+
11579+    } else {
11580+        // swapchain pass
11581+        #if defined(SOKOL_GLCORE)
11582+        if (_sg_is_srgb_format(swapchain->color_format)) {
11583+            glEnable(GL_FRAMEBUFFER_SRGB);
11584+        } else {
11585+            glDisable(GL_FRAMEBUFFER_SRGB);
11586+        }
11587+        #endif
11588+        // NOTE: on some platforms, the default framebuffer of a context
11589+        // is null, so we can't actually assert here that the
11590+        // framebuffer has been provided
11591+        glBindFramebuffer(GL_FRAMEBUFFER, swapchain->gl.framebuffer);
11592+    }
11593+    glViewport(0, 0, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height);
11594+    glScissor(0, 0, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height);
11595+
11596+    // number of color attachments
11597+    const int num_color_atts = is_offscreen_pass ? atts->num_color_views : 1;
11598+
11599+    // clear color and depth-stencil attachments if needed
11600+    bool clear_any_color = false;
11601+    for (int i = 0; i < num_color_atts; i++) {
11602+        if (SG_LOADACTION_CLEAR == action->colors[i].load_action) {
11603+            clear_any_color = true;
11604+            break;
11605+        }
11606+    }
11607+    const bool clear_depth = (action->depth.load_action == SG_LOADACTION_CLEAR);
11608+    const bool clear_stencil = (action->stencil.load_action == SG_LOADACTION_CLEAR);
11609+
11610+    bool need_pip_cache_flush = false;
11611+    if (clear_any_color) {
11612+        bool need_color_mask_flush = false;
11613+        // NOTE: not a bug to iterate over all possible color attachments
11614+        for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
11615+            if (SG_COLORMASK_RGBA != _sg.gl.cache.color_write_mask[i]) {
11616+                need_pip_cache_flush = true;
11617+                need_color_mask_flush = true;
11618+                _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA;
11619+            }
11620+        }
11621+        if (need_color_mask_flush) {
11622+            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
11623+        }
11624+    }
11625+    if (clear_depth) {
11626+        if (!_sg.gl.cache.depth.write_enabled) {
11627+            need_pip_cache_flush = true;
11628+            _sg.gl.cache.depth.write_enabled = true;
11629+            glDepthMask(GL_TRUE);
11630+        }
11631+        if (_sg.gl.cache.depth.compare != SG_COMPAREFUNC_ALWAYS) {
11632+            need_pip_cache_flush = true;
11633+            _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS;
11634+            glDepthFunc(GL_ALWAYS);
11635+        }
11636+    }
11637+    if (clear_stencil) {
11638+        if (_sg.gl.cache.stencil.write_mask != 0xFF) {
11639+            need_pip_cache_flush = true;
11640+            _sg.gl.cache.stencil.write_mask = 0xFF;
11641+            glStencilMask(0xFF);
11642+        }
11643+    }
11644+    if (need_pip_cache_flush) {
11645+        // we messed with the state cache directly, need to clear cached
11646+        // pipeline to force re-evaluation in next sg_apply_pipeline()
11647+        _sg.gl.cache.cur_pip = _sg_sref(0);
11648+    }
11649+    for (int i = 0; i < num_color_atts; i++) {
11650+        if (action->colors[i].load_action == SG_LOADACTION_CLEAR) {
11651+            glClearBufferfv(GL_COLOR, i, &action->colors[i].clear_value.r);
11652+        }
11653+    }
11654+    if (is_swapchain_pass || atts->ds_view) {
11655+        if (clear_depth && clear_stencil) {
11656+            glClearBufferfi(GL_DEPTH_STENCIL, 0, action->depth.clear_value, action->stencil.clear_value);
11657+        } else if (clear_depth) {
11658+            glClearBufferfv(GL_DEPTH, 0, &action->depth.clear_value);
11659+        } else if (clear_stencil) {
11660+            GLint val = (GLint) action->stencil.clear_value;
11661+            glClearBufferiv(GL_STENCIL, 0, &val);
11662+        }
11663+    }
11664+    // keep store actions for end-pass
11665+    for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) {
11666+        _sg.gl.color_store_actions[i] = action->colors[i].store_action;
11667+    }
11668+    _sg.gl.depth_store_action = action->depth.store_action;
11669+    _sg.gl.stencil_store_action = action->stencil.store_action;
11670+
11671+    _SG_GL_CHECK_ERROR();
11672+}
11673+
11674+_SOKOL_PRIVATE void _sg_gl_end_render_pass(const _sg_attachments_ptrs_t* atts) {
11675+    SOKOL_ASSERT(atts);
11676+    if (!atts->empty) {
11677+        bool fb_read_bound = false;
11678+        bool fb_draw_bound = false;
11679+        const int num_color_atts = atts->num_color_views;
11680+        for (int i = 0; i < num_color_atts; i++) {
11681+            // perform MSAA resolve if needed
11682+            const _sg_view_t* rsv_view = atts->resolve_views[i];
11683+            if (rsv_view && rsv_view->gl.msaa_resolve_frame_buffer) {
11684+                if (!fb_read_bound) {
11685+                    glBindFramebuffer(GL_READ_FRAMEBUFFER, _sg.gl.fb);
11686+                    fb_read_bound = true;
11687+                }
11688+                const _sg_image_t* rsv_img = _sg_image_ref_ptr(&rsv_view->cmn.img.ref);
11689+                const int w = rsv_img->cmn.width;
11690+                const int h = rsv_img->cmn.height;
11691+                glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rsv_view->gl.msaa_resolve_frame_buffer);
11692+                glReadBuffer((GLenum)(GL_COLOR_ATTACHMENT0 + i));
11693+                glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
11694+                fb_draw_bound = true;
11695+            }
11696+        }
11697+
11698+        // invalidate framebuffers
11699+        _SOKOL_UNUSED(fb_draw_bound);
11700+        #if defined(SOKOL_GLES3)
11701+        // need to restore framebuffer binding before invalidate if the MSAA resolve had changed the binding
11702+        if (fb_draw_bound) {
11703+            glBindFramebuffer(GL_FRAMEBUFFER, _sg.gl.fb);
11704+        }
11705+        GLenum invalidate_atts[SG_MAX_COLOR_ATTACHMENTS + 2] = { 0 };
11706+        int att_index = 0;
11707+        for (int i = 0; i < num_color_atts; i++) {
11708+            if (_sg.gl.color_store_actions[i] == SG_STOREACTION_DONTCARE) {
11709+                invalidate_atts[att_index++] = (GLenum)(GL_COLOR_ATTACHMENT0 + i);
11710+            }
11711+        }
11712+        if (!atts->ds_view) {
11713+            if (_sg.gl.depth_store_action == SG_STOREACTION_DONTCARE) {
11714+                invalidate_atts[att_index++] = GL_DEPTH_ATTACHMENT;
11715+            }
11716+            if (_sg.gl.stencil_store_action == SG_STOREACTION_DONTCARE) {
11717+                invalidate_atts[att_index++] = GL_STENCIL_ATTACHMENT;
11718+            }
11719+        }
11720+        if (att_index > 0) {
11721+            glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, att_index, invalidate_atts);
11722+        }
11723+        #endif
11724+    }
11725+}
11726+
11727+_SOKOL_PRIVATE void _sg_gl_end_pass(const _sg_attachments_ptrs_t* atts) {
11728+    _SG_GL_CHECK_ERROR();
11729+    if (!_sg.cur_pass.is_compute) {
11730+        _sg_gl_end_render_pass(atts);
11731+    }
11732+    _SG_GL_CHECK_ERROR();
11733+}
11734+
11735+_SOKOL_PRIVATE void _sg_gl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
11736+    y = origin_top_left ? (_sg.cur_pass.dim.height - (y+h)) : y;
11737+    glViewport(x, y, w, h);
11738+}
11739+
11740+_SOKOL_PRIVATE void _sg_gl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
11741+    y = origin_top_left ? (_sg.cur_pass.dim.height - (y+h)) : y;
11742+    glScissor(x, y, w, h);
11743+}
11744+
11745+_SOKOL_PRIVATE void _sg_gl_apply_render_pipeline_state(_sg_pipeline_t* pip) {
11746+    // update render pipeline state
11747+    _sg.gl.cache.cur_primitive_type = _sg_gl_primitive_type(pip->gl.primitive_type);
11748+    _sg.gl.cache.cur_index_type = _sg_gl_index_type(pip->cmn.index_type);
11749+
11750+    // update depth state
11751+    {
11752+        const sg_depth_state* state_ds = &pip->gl.depth;
11753+        sg_depth_state* cache_ds = &_sg.gl.cache.depth;
11754+        if (state_ds->compare != cache_ds->compare) {
11755+            cache_ds->compare = state_ds->compare;
11756+            glDepthFunc(_sg_gl_compare_func(state_ds->compare));
11757+            _sg_stats_inc(gl.num_render_state);
11758+        }
11759+        if (state_ds->write_enabled != cache_ds->write_enabled) {
11760+            cache_ds->write_enabled = state_ds->write_enabled;
11761+            glDepthMask(state_ds->write_enabled);
11762+            _sg_stats_inc(gl.num_render_state);
11763+        }
11764+        if (!_sg_fequal(state_ds->bias, cache_ds->bias, 0.000001f) ||
11765+            !_sg_fequal(state_ds->bias_slope_scale, cache_ds->bias_slope_scale, 0.000001f))
11766+        {
11767+            /* according to ANGLE's D3D11 backend:
11768+                D3D11 SlopeScaledDepthBias ==> GL polygonOffsetFactor
11769+                D3D11 DepthBias ==> GL polygonOffsetUnits
11770+                DepthBiasClamp has no meaning on GL
11771+            */
11772+            cache_ds->bias = state_ds->bias;
11773+            cache_ds->bias_slope_scale = state_ds->bias_slope_scale;
11774+            glPolygonOffset(state_ds->bias_slope_scale, state_ds->bias);
11775+            _sg_stats_inc(gl.num_render_state);
11776+            bool po_enabled = true;
11777+            if (_sg_fequal(state_ds->bias, 0.0f, 0.000001f) &&
11778+                _sg_fequal(state_ds->bias_slope_scale, 0.0f, 0.000001f))
11779+            {
11780+                po_enabled = false;
11781+            }
11782+            if (po_enabled != _sg.gl.cache.polygon_offset_enabled) {
11783+                _sg.gl.cache.polygon_offset_enabled = po_enabled;
11784+                if (po_enabled) {
11785+                    glEnable(GL_POLYGON_OFFSET_FILL);
11786+                } else {
11787+                    glDisable(GL_POLYGON_OFFSET_FILL);
11788+                }
11789+                _sg_stats_inc(gl.num_render_state);
11790+            }
11791+        }
11792+    }
11793+
11794+    // update stencil state
11795+    {
11796+        const sg_stencil_state* state_ss = &pip->gl.stencil;
11797+        sg_stencil_state* cache_ss = &_sg.gl.cache.stencil;
11798+        if (state_ss->enabled != cache_ss->enabled) {
11799+            cache_ss->enabled = state_ss->enabled;
11800+            if (state_ss->enabled) {
11801+                glEnable(GL_STENCIL_TEST);
11802+            } else {
11803+                glDisable(GL_STENCIL_TEST);
11804+            }
11805+            _sg_stats_inc(gl.num_render_state);
11806+        }
11807+        if (state_ss->write_mask != cache_ss->write_mask) {
11808+            cache_ss->write_mask = state_ss->write_mask;
11809+            glStencilMask(state_ss->write_mask);
11810+            _sg_stats_inc(gl.num_render_state);
11811+        }
11812+        for (int i = 0; i < 2; i++) {
11813+            const sg_stencil_face_state* state_sfs = (i==0)? &state_ss->front : &state_ss->back;
11814+            sg_stencil_face_state* cache_sfs = (i==0)? &cache_ss->front : &cache_ss->back;
11815+            GLenum gl_face = (i==0)? GL_FRONT : GL_BACK;
11816+            if ((state_sfs->compare != cache_sfs->compare) ||
11817+                (state_ss->read_mask != cache_ss->read_mask) ||
11818+                (state_ss->ref != cache_ss->ref))
11819+            {
11820+                cache_sfs->compare = state_sfs->compare;
11821+                glStencilFuncSeparate(gl_face,
11822+                    _sg_gl_compare_func(state_sfs->compare),
11823+                    state_ss->ref,
11824+                    state_ss->read_mask);
11825+                _sg_stats_inc(gl.num_render_state);
11826+            }
11827+            if ((state_sfs->fail_op != cache_sfs->fail_op) ||
11828+                (state_sfs->depth_fail_op != cache_sfs->depth_fail_op) ||
11829+                (state_sfs->pass_op != cache_sfs->pass_op))
11830+            {
11831+                cache_sfs->fail_op = state_sfs->fail_op;
11832+                cache_sfs->depth_fail_op = state_sfs->depth_fail_op;
11833+                cache_sfs->pass_op = state_sfs->pass_op;
11834+                glStencilOpSeparate(gl_face,
11835+                    _sg_gl_stencil_op(state_sfs->fail_op),
11836+                    _sg_gl_stencil_op(state_sfs->depth_fail_op),
11837+                    _sg_gl_stencil_op(state_sfs->pass_op));
11838+                _sg_stats_inc(gl.num_render_state);
11839+            }
11840+        }
11841+        cache_ss->read_mask = state_ss->read_mask;
11842+        cache_ss->ref = state_ss->ref;
11843+    }
11844+
11845+    if (pip->cmn.color_count > 0) {
11846+        // update blend state
11847+        // FIXME: separate blend state per color attachment
11848+        const sg_blend_state* state_bs = &pip->gl.blend;
11849+        sg_blend_state* cache_bs = &_sg.gl.cache.blend;
11850+        if (state_bs->enabled != cache_bs->enabled) {
11851+            cache_bs->enabled = state_bs->enabled;
11852+            if (state_bs->enabled) {
11853+                glEnable(GL_BLEND);
11854+            } else {
11855+                glDisable(GL_BLEND);
11856+            }
11857+            _sg_stats_inc(gl.num_render_state);
11858+        }
11859+        if ((state_bs->src_factor_rgb != cache_bs->src_factor_rgb) ||
11860+            (state_bs->dst_factor_rgb != cache_bs->dst_factor_rgb) ||
11861+            (state_bs->src_factor_alpha != cache_bs->src_factor_alpha) ||
11862+            (state_bs->dst_factor_alpha != cache_bs->dst_factor_alpha))
11863+        {
11864+            cache_bs->src_factor_rgb = state_bs->src_factor_rgb;
11865+            cache_bs->dst_factor_rgb = state_bs->dst_factor_rgb;
11866+            cache_bs->src_factor_alpha = state_bs->src_factor_alpha;
11867+            cache_bs->dst_factor_alpha = state_bs->dst_factor_alpha;
11868+            glBlendFuncSeparate(_sg_gl_blend_factor(state_bs->src_factor_rgb),
11869+                _sg_gl_blend_factor(state_bs->dst_factor_rgb),
11870+                _sg_gl_blend_factor(state_bs->src_factor_alpha),
11871+                _sg_gl_blend_factor(state_bs->dst_factor_alpha));
11872+            _sg_stats_inc(gl.num_render_state);
11873+        }
11874+        if ((state_bs->op_rgb != cache_bs->op_rgb) || (state_bs->op_alpha != cache_bs->op_alpha)) {
11875+            cache_bs->op_rgb = state_bs->op_rgb;
11876+            cache_bs->op_alpha = state_bs->op_alpha;
11877+            glBlendEquationSeparate(_sg_gl_blend_op(state_bs->op_rgb), _sg_gl_blend_op(state_bs->op_alpha));
11878+            _sg_stats_inc(gl.num_render_state);
11879+        }
11880+
11881+        // standalone color target state
11882+        for (GLuint i = 0; i < (GLuint)pip->cmn.color_count; i++) {
11883+            if (pip->gl.color_write_mask[i] != _sg.gl.cache.color_write_mask[i]) {
11884+                const sg_color_mask cm = pip->gl.color_write_mask[i];
11885+                _sg.gl.cache.color_write_mask[i] = cm;
11886+                if (_sg.features.mrt_independent_write_mask) {
11887+                    #if defined(_SOKOL_GL_HAS_COLORMASKI)
11888+                    glColorMaski(i,
11889+                                (cm & SG_COLORMASK_R) != 0,
11890+                                (cm & SG_COLORMASK_G) != 0,
11891+                                (cm & SG_COLORMASK_B) != 0,
11892+                                (cm & SG_COLORMASK_A) != 0);
11893+                    #else
11894+                    // can't happen
11895+                    SOKOL_ASSERT(false);
11896+                    #endif
11897+                } else if (0 == i) {
11898+                    glColorMask((cm & SG_COLORMASK_R) != 0,
11899+                                (cm & SG_COLORMASK_G) != 0,
11900+                                (cm & SG_COLORMASK_B) != 0,
11901+                                (cm & SG_COLORMASK_A) != 0);
11902+                }
11903+                _sg_stats_inc(gl.num_render_state);
11904+            }
11905+        }
11906+
11907+        if (!_sg_fequal(pip->cmn.blend_color.r, _sg.gl.cache.blend_color.r, 0.0001f) ||
11908+            !_sg_fequal(pip->cmn.blend_color.g, _sg.gl.cache.blend_color.g, 0.0001f) ||
11909+            !_sg_fequal(pip->cmn.blend_color.b, _sg.gl.cache.blend_color.b, 0.0001f) ||
11910+            !_sg_fequal(pip->cmn.blend_color.a, _sg.gl.cache.blend_color.a, 0.0001f))
11911+        {
11912+            sg_color c = pip->cmn.blend_color;
11913+            _sg.gl.cache.blend_color = c;
11914+            glBlendColor(c.r, c.g, c.b, c.a);
11915+            _sg_stats_inc(gl.num_render_state);
11916+        }
11917+    } // pip->cmn.color_count > 0
11918+
11919+    if (pip->gl.cull_mode != _sg.gl.cache.cull_mode) {
11920+        _sg.gl.cache.cull_mode = pip->gl.cull_mode;
11921+        if (SG_CULLMODE_NONE == pip->gl.cull_mode) {
11922+            glDisable(GL_CULL_FACE);
11923+            _sg_stats_inc(gl.num_render_state);
11924+        } else {
11925+            glEnable(GL_CULL_FACE);
11926+            GLenum gl_mode = (SG_CULLMODE_FRONT == pip->gl.cull_mode) ? GL_FRONT : GL_BACK;
11927+            glCullFace(gl_mode);
11928+            _sg_stats_add(gl.num_render_state, 2);
11929+        }
11930+    }
11931+    if (pip->gl.face_winding != _sg.gl.cache.face_winding) {
11932+        _sg.gl.cache.face_winding = pip->gl.face_winding;
11933+        GLenum gl_winding = (SG_FACEWINDING_CW == pip->gl.face_winding) ? GL_CW : GL_CCW;
11934+        glFrontFace(gl_winding);
11935+        _sg_stats_inc(gl.num_render_state);
11936+    }
11937+    if (pip->gl.alpha_to_coverage_enabled != _sg.gl.cache.alpha_to_coverage_enabled) {
11938+        _sg.gl.cache.alpha_to_coverage_enabled = pip->gl.alpha_to_coverage_enabled;
11939+        if (pip->gl.alpha_to_coverage_enabled) {
11940+            glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
11941+        } else {
11942+            glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
11943+        }
11944+        _sg_stats_inc(gl.num_render_state);
11945+    }
11946+    #ifdef SOKOL_GLCORE
11947+    if (pip->gl.sample_count != _sg.gl.cache.sample_count) {
11948+        _sg.gl.cache.sample_count = pip->gl.sample_count;
11949+        if (pip->gl.sample_count > 1) {
11950+            glEnable(GL_MULTISAMPLE);
11951+        } else {
11952+            glDisable(GL_MULTISAMPLE);
11953+        }
11954+        _sg_stats_inc(gl.num_render_state);
11955+    }
11956+    #endif
11957+}
11958+
11959+_SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) {
11960+    SOKOL_ASSERT(pip);
11961+    _SG_GL_CHECK_ERROR();
11962+    if (!_sg_sref_slot_eql(&_sg.gl.cache.cur_pip, &pip->slot)) {
11963+        _sg.gl.cache.cur_pip = _sg_sref(&pip->slot);
11964+
11965+        // bind shader program
11966+        const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
11967+        if (shd->gl.prog != _sg.gl.cache.prog) {
11968+            _sg.gl.cache.prog = shd->gl.prog;
11969+            glUseProgram(shd->gl.prog);
11970+            _sg_stats_inc(gl.num_use_program);
11971+        }
11972+
11973+        if (!pip->cmn.is_compute) {
11974+            _sg_gl_apply_render_pipeline_state(pip);
11975+        }
11976+    }
11977+    _SG_GL_CHECK_ERROR();
11978+}
11979+
11980+_SOKOL_PRIVATE bool _sg_gl_apply_bindings(_sg_bindings_ptrs_t* bnd) {
11981+    SOKOL_ASSERT(bnd);
11982+    SOKOL_ASSERT(bnd->pip);
11983+    _SG_GL_CHECK_ERROR();
11984+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
11985+
11986+    // bind combined texture-samplers
11987+    _SG_GL_CHECK_ERROR();
11988+    for (size_t tex_smp_index = 0; tex_smp_index < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_index++) {
11989+        const _sg_shader_texture_sampler_t* tex_smp = &shd->cmn.texture_samplers[tex_smp_index];
11990+        if (tex_smp->stage == SG_SHADERSTAGE_NONE) {
11991+            continue;
11992+        }
11993+        const int8_t gl_tex_slot = (GLint)shd->gl.tex_slot[tex_smp_index];
11994+        if (gl_tex_slot != -1) {
11995+            SOKOL_ASSERT(tex_smp->view_slot < SG_MAX_VIEW_BINDSLOTS);
11996+            SOKOL_ASSERT(tex_smp->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS);
11997+            const _sg_view_t* view = bnd->views[tex_smp->view_slot];
11998+            const _sg_sampler_t* smp = bnd->smps[tex_smp->sampler_slot];
11999+            SOKOL_ASSERT(view);
12000+            SOKOL_ASSERT(smp);
12001+            const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
12002+            const GLenum gl_tgt = img->gl.target;
12003+            const GLuint gl_smp = smp->gl.smp;
12004+            GLuint gl_tex;
12005+            if (_sg.features.gl_texture_views) {
12006+                gl_tex = view->gl.tex_view[img->cmn.active_slot];
12007+            } else {
12008+                gl_tex = img->gl.tex[img->cmn.active_slot];
12009+            }
12010+            _sg_gl_cache_bind_texture_sampler(gl_tex_slot, gl_tgt, gl_tex, gl_smp);
12011+        }
12012+    }
12013+    _SG_GL_CHECK_ERROR();
12014+
12015+    // bind storage buffer and images
12016+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
12017+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
12018+            continue;
12019+        }
12020+        const _sg_view_t* view = bnd->views[i];
12021+        if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
12022+            const _sg_buffer_t* sbuf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
12023+            const uint8_t gl_binding = shd->gl.sbuf_binding[i];
12024+            GLuint gl_sbuf = sbuf->gl.buf[sbuf->cmn.active_slot];
12025+            _sg_gl_cache_bind_storage_buffer(gl_binding, gl_sbuf, view->cmn.buf.offset, sbuf->cmn.size);
12026+        } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) {
12027+            #if defined(_SOKOL_GL_HAS_COMPUTE)
12028+                const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
12029+                const uint8_t gl_unit = shd->gl.simg_binding[i];
12030+                SOKOL_ASSERT((int)gl_unit < _sg.limits.max_storage_image_bindings_per_stage);
12031+                GLuint gl_tex = img->gl.tex[img->cmn.active_slot];
12032+                GLint level = (GLint)view->cmn.img.mip_level;
12033+                GLint layer = (GLint)view->cmn.img.slice;
12034+                // NOTE: when picking a specific layer, the 'layered' flag must be false,
12035+                // this was previously bugged
12036+                GLboolean layered = GL_FALSE;
12037+                GLenum access = shd->cmn.views[i].simg_writeonly ? GL_WRITE_ONLY : GL_READ_WRITE;
12038+                GLenum format = _sg_gl_teximage_internal_format(shd->cmn.views[i].access_format);
12039+                // NOTE: we specifically don't go through the GL cache since storage images
12040+                // are not supported on WebGL2, and on native platforms call caching isn't
12041+                // worth the hassle
12042+                glBindImageTexture(gl_unit, gl_tex, level, layered, layer, access, format);
12043+                _sg_stats_inc(gl.num_bind_image_texture);
12044+            #endif
12045+        }
12046+    }
12047+    _SG_GL_CHECK_ERROR();
12048+
12049+    if (!bnd->pip->cmn.is_compute) {
12050+        // index buffer (can be 0)
12051+        const GLuint gl_ib = bnd->ib ? bnd->ib->gl.buf[bnd->ib->cmn.active_slot] : 0;
12052+        _sg_gl_cache_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, gl_ib);
12053+        _sg.gl.cache.cur_ib_offset = bnd->ib_offset;
12054+
12055+        // vertex attributes
12056+        for (GLuint attr_index = 0; attr_index < (GLuint)_sg.limits.max_vertex_attrs; attr_index++) {
12057+            _sg_gl_attr_t* attr = &bnd->pip->gl.attrs[attr_index];
12058+            _sg_gl_cache_attr_t* cache_attr = &_sg.gl.cache.attrs[attr_index];
12059+            bool cache_attr_dirty = false;
12060+            int vb_offset = 0;
12061+            GLuint gl_vb = 0;
12062+            if (attr->vb_index >= 0) {
12063+                // attribute is enabled
12064+                SOKOL_ASSERT(attr->vb_index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
12065+                _sg_buffer_t* vb = bnd->vbs[attr->vb_index];
12066+                SOKOL_ASSERT(vb);
12067+                gl_vb = vb->gl.buf[vb->cmn.active_slot];
12068+                vb_offset = bnd->vb_offsets[attr->vb_index] + attr->offset;
12069+                if ((gl_vb != cache_attr->gl_vbuf) ||
12070+                    (attr->size != cache_attr->gl_attr.size) ||
12071+                    (attr->type != cache_attr->gl_attr.type) ||
12072+                    (attr->normalized != cache_attr->gl_attr.normalized) ||
12073+                    (attr->base_type != cache_attr->gl_attr.base_type) ||
12074+                    (attr->stride != cache_attr->gl_attr.stride) ||
12075+                    (vb_offset != cache_attr->gl_attr.offset) ||
12076+                    (cache_attr->gl_attr.divisor != attr->divisor))
12077+                {
12078+                    _sg_gl_cache_bind_buffer(GL_ARRAY_BUFFER, gl_vb);
12079+                    if (attr->base_type == SG_SHADERATTRBASETYPE_FLOAT) {
12080+                        glVertexAttribPointer(attr_index, attr->size, attr->type, attr->normalized, attr->stride, (const GLvoid*)(GLintptr)vb_offset);
12081+                    } else {
12082+                        glVertexAttribIPointer(attr_index, attr->size, attr->type, attr->stride, (const GLvoid*)(GLintptr)vb_offset);
12083+                    }
12084+                    _sg_stats_inc(gl.num_vertex_attrib_pointer);
12085+                    glVertexAttribDivisor(attr_index, (GLuint)attr->divisor);
12086+                    _sg_stats_inc(gl.num_vertex_attrib_divisor);
12087+                    cache_attr_dirty = true;
12088+                }
12089+                if (cache_attr->gl_attr.vb_index == -1) {
12090+                    glEnableVertexAttribArray(attr_index);
12091+                    _sg_stats_inc(gl.num_enable_vertex_attrib_array);
12092+                    cache_attr_dirty = true;
12093+                }
12094+            } else {
12095+                // attribute is disabled
12096+                if (cache_attr->gl_attr.vb_index != -1) {
12097+                    glDisableVertexAttribArray(attr_index);
12098+                    _sg_stats_inc(gl.num_disable_vertex_attrib_array);
12099+                    cache_attr_dirty = true;
12100+                }
12101+            }
12102+            if (cache_attr_dirty) {
12103+                cache_attr->gl_attr = *attr;
12104+                cache_attr->gl_attr.offset = vb_offset;
12105+                cache_attr->gl_vbuf = gl_vb;
12106+            }
12107+        }
12108+        _SG_GL_CHECK_ERROR();
12109+    }
12110+
12111+    // take care of storage resource memory barriers (this needs to happen after the bindings are set)
12112+    #if defined(_SOKOL_GL_HAS_COMPUTE)
12113+        _sg_gl_handle_memory_barriers(shd, bnd, 0);
12114+        _SG_GL_CHECK_ERROR();
12115+    #endif
12116+
12117+    return true;
12118+}
12119+
12120+_SOKOL_PRIVATE void _sg_gl_apply_uniforms(int ub_slot, const sg_range* data) {
12121+    SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
12122+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
12123+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
12124+    SOKOL_ASSERT(SG_SHADERSTAGE_NONE != shd->cmn.uniform_blocks[ub_slot].stage);
12125+    SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size);
12126+    const _sg_gl_uniform_block_t* gl_ub = &shd->gl.uniform_blocks[ub_slot];
12127+    for (int u_index = 0; u_index < gl_ub->num_uniforms; u_index++) {
12128+        const _sg_gl_uniform_t* u = &gl_ub->uniforms[u_index];
12129+        SOKOL_ASSERT(u->type != SG_UNIFORMTYPE_INVALID);
12130+        if (u->gl_loc == -1) {
12131+            continue;
12132+        }
12133+        _sg_stats_inc(gl.num_uniform);
12134+        GLfloat* fptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset);
12135+        GLint* iptr = (GLint*) (((uint8_t*)data->ptr) + u->offset);
12136+        switch (u->type) {
12137+            case SG_UNIFORMTYPE_INVALID:
12138+                break;
12139+            case SG_UNIFORMTYPE_FLOAT:
12140+                glUniform1fv(u->gl_loc, u->count, fptr);
12141+                break;
12142+            case SG_UNIFORMTYPE_FLOAT2:
12143+                glUniform2fv(u->gl_loc, u->count, fptr);
12144+                break;
12145+            case SG_UNIFORMTYPE_FLOAT3:
12146+                glUniform3fv(u->gl_loc, u->count, fptr);
12147+                break;
12148+            case SG_UNIFORMTYPE_FLOAT4:
12149+                glUniform4fv(u->gl_loc, u->count, fptr);
12150+                break;
12151+            case SG_UNIFORMTYPE_INT:
12152+                glUniform1iv(u->gl_loc, u->count, iptr);
12153+                break;
12154+            case SG_UNIFORMTYPE_INT2:
12155+                glUniform2iv(u->gl_loc, u->count, iptr);
12156+                break;
12157+            case SG_UNIFORMTYPE_INT3:
12158+                glUniform3iv(u->gl_loc, u->count, iptr);
12159+                break;
12160+            case SG_UNIFORMTYPE_INT4:
12161+                glUniform4iv(u->gl_loc, u->count, iptr);
12162+                break;
12163+            case SG_UNIFORMTYPE_MAT4:
12164+                glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, fptr);
12165+                break;
12166+            default:
12167+                SOKOL_UNREACHABLE;
12168+                break;
12169+        }
12170+    }
12171+}
12172+
12173+_SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
12174+    const GLenum p_type = _sg.gl.cache.cur_primitive_type;
12175+    const bool use_instanced_draw = (num_instances > 1) || _sg.use_instanced_draw;
12176+    if (_sg.use_indexed_draw) {
12177+        // indexed rendering
12178+        const GLenum i_type = _sg.gl.cache.cur_index_type;
12179+        const int i_size = (i_type == GL_UNSIGNED_SHORT) ? 2 : 4;
12180+        const int ib_offset = _sg.gl.cache.cur_ib_offset;
12181+        const GLvoid* indices = (const GLvoid*)(GLintptr)(base_element*i_size+ib_offset);
12182+        if (use_instanced_draw) {
12183+            if ((base_vertex == 0) && (base_instance == 0)) {
12184+                glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances);
12185+            } else if ((base_vertex != 0) && (base_instance == 0) && _sg.features.draw_base_vertex) {
12186+                #if defined(_SOKOL_GL_HAS_BASEVERTEX)
12187+                glDrawElementsInstancedBaseVertex(p_type, num_elements, i_type, indices, num_instances, base_vertex);
12188+                #endif
12189+            } else if ((base_instance != 0) && _sg.features.draw_base_instance) {
12190+                #if defined(_SOKOL_GL_HAS_BASEINSTANCE)
12191+                glDrawElementsInstancedBaseVertexBaseInstance(p_type, num_elements, i_type, indices, num_instances, base_vertex, (GLuint)base_instance);
12192+                #endif
12193+            }
12194+        } else {
12195+            if (base_vertex == 0) {
12196+                glDrawElements(p_type, num_elements, i_type, indices);
12197+            } else if (_sg.features.draw_base_vertex) {
12198+                #if defined(_SOKOL_GL_HAS_BASEVERTEX)
12199+                glDrawElementsBaseVertex(p_type, num_elements, i_type, indices, base_vertex);
12200+                #endif
12201+            }
12202+        }
12203+    } else {
12204+        // non-indexed rendering
12205+        if (use_instanced_draw) {
12206+            if (base_instance == 0) {
12207+                glDrawArraysInstanced(p_type, base_element, num_elements, num_instances);
12208+            } else if (_sg.features.draw_base_instance) {
12209+                #if defined(_SOKOL_GL_HAS_BASEINSTANCE)
12210+                glDrawArraysInstancedBaseInstance(p_type, base_element, num_elements, num_instances, (GLuint)base_instance);
12211+                #endif
12212+            }
12213+        } else {
12214+            glDrawArrays(p_type, base_element, num_elements);
12215+        }
12216+    }
12217+}
12218+
12219+_SOKOL_PRIVATE void _sg_gl_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
12220+    #if defined(_SOKOL_GL_HAS_COMPUTE)
12221+        if (!_sg.features.compute) {
12222+            return;
12223+        }
12224+        glDispatchCompute((GLuint)num_groups_x, (GLuint)num_groups_y, (GLuint)num_groups_z);
12225+    #else
12226+        (void)num_groups_x; (void)num_groups_y; (void)num_groups_z;
12227+    #endif
12228+}
12229+
12230+_SOKOL_PRIVATE void _sg_gl_commit(void) {
12231+    // "soft" clear bindings (only those that are actually bound)
12232+    _sg_gl_cache_clear_buffer_bindings(false);
12233+    _sg_gl_cache_clear_texture_sampler_bindings(false);
12234+}
12235+
12236+_SOKOL_PRIVATE void _sg_gl_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
12237+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
12238+    // only one update per buffer per frame allowed
12239+    if (++buf->cmn.active_slot >= buf->cmn.num_slots) {
12240+        buf->cmn.active_slot = 0;
12241+    }
12242+    GLenum gl_tgt = _sg_gl_buffer_target(&buf->cmn.usage);
12243+    SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES);
12244+    GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot];
12245+    SOKOL_ASSERT(gl_buf);
12246+    _SG_GL_CHECK_ERROR();
12247+    _sg_gl_cache_store_buffer_binding(gl_tgt);
12248+    _sg_gl_cache_bind_buffer(gl_tgt, gl_buf);
12249+    glBufferSubData(gl_tgt, 0, (GLsizeiptr)data->size, data->ptr);
12250+    _sg_gl_cache_restore_buffer_binding(gl_tgt);
12251+    _SG_GL_CHECK_ERROR();
12252+}
12253+
12254+_SOKOL_PRIVATE void _sg_gl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
12255+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
12256+    if (new_frame) {
12257+        if (++buf->cmn.active_slot >= buf->cmn.num_slots) {
12258+            buf->cmn.active_slot = 0;
12259+        }
12260+    }
12261+    GLenum gl_tgt = _sg_gl_buffer_target(&buf->cmn.usage);
12262+    SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES);
12263+    GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot];
12264+    SOKOL_ASSERT(gl_buf);
12265+    _SG_GL_CHECK_ERROR();
12266+    _sg_gl_cache_store_buffer_binding(gl_tgt);
12267+    _sg_gl_cache_bind_buffer(gl_tgt, gl_buf);
12268+    glBufferSubData(gl_tgt, buf->cmn.append_pos, (GLsizeiptr)data->size, data->ptr);
12269+    _sg_gl_cache_restore_buffer_binding(gl_tgt);
12270+    _SG_GL_CHECK_ERROR();
12271+}
12272+
12273+_SOKOL_PRIVATE void _sg_gl_update_image(_sg_image_t* img, const sg_image_data* data) {
12274+    SOKOL_ASSERT(img && data);
12275+    // only one update per image per frame allowed
12276+    if (++img->cmn.active_slot >= img->cmn.num_slots) {
12277+        img->cmn.active_slot = 0;
12278+    }
12279+    SOKOL_ASSERT(img->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES);
12280+    SOKOL_ASSERT(0 != img->gl.tex[img->cmn.active_slot]);
12281+    _sg_gl_cache_store_texture_sampler_binding(0);
12282+    _sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[img->cmn.active_slot], 0);
12283+    const int num_mips = img->cmn.num_mipmaps;
12284+    for (int mip_index = 0; mip_index < num_mips; mip_index++) {
12285+        const GLvoid* data_ptr = data->mip_levels[mip_index].ptr;
12286+        const GLsizei data_size = (GLsizei)data->mip_levels[mip_index].size;
12287+        const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
12288+        const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
12289+        const int mip_depth = (SG_IMAGETYPE_3D == img->cmn.type) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices;
12290+        if (SG_IMAGETYPE_CUBE == img->cmn.type) {
12291+            const int surf_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1);
12292+            SOKOL_ASSERT((6 * surf_pitch) <= data_size);
12293+            const uint8_t* surf_ptr = (const uint8_t*) data_ptr;
12294+            for (int i = 0; i < 6; i++) {
12295+                const GLenum gl_img_target = _sg_gl_cubeface_target(i);
12296+                _sg_gl_texsubimage(img, gl_img_target, mip_index, mip_width, mip_height, mip_depth, surf_ptr, surf_pitch);
12297+                surf_ptr += surf_pitch;
12298+            }
12299+        } else {
12300+            _sg_gl_texsubimage(img, img->gl.target, mip_index, mip_width, mip_height, mip_depth, data_ptr, data_size);
12301+        }
12302+    }
12303+    _sg_gl_cache_restore_texture_sampler_binding(0);
12304+}
12305+
12306+// ██████  ██████  ██████   ██  ██     ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
12307+// ██   ██      ██ ██   ██ ███ ███     ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
12308+// ██   ██  █████  ██   ██  ██  ██     ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
12309+// ██   ██      ██ ██   ██  ██  ██     ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
12310+// ██████  ██████  ██████   ██  ██     ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
12311+//
12312+// >>d3d11 backend
12313+#elif defined(SOKOL_D3D11)
12314+
12315+#if defined(__cplusplus)
12316+#define _sg_d3d11_AddRef(self) (self)->AddRef()
12317+#else
12318+#define _sg_d3d11_AddRef(self) (self)->lpVtbl->AddRef(self)
12319+#endif
12320+
12321+#if defined(__cplusplus)
12322+#define _sg_d3d11_Release(self) (self)->Release()
12323+#else
12324+#define _sg_d3d11_Release(self) (self)->lpVtbl->Release(self)
12325+#endif
12326+
12327+// NOTE: This needs to be a macro since we can't use the polymorphism in C. It's called on many kinds of resources.
12328+// NOTE: Based on microsoft docs, it's fine to call this with pData=NULL if DataSize is also zero.
12329+#if defined(__cplusplus)
12330+#define _sg_d3d11_SetPrivateData(self, guid, DataSize, pData) (self)->SetPrivateData(guid, DataSize, pData)
12331+#else
12332+#define _sg_d3d11_SetPrivateData(self, guid, DataSize, pData) (self)->lpVtbl->SetPrivateData(self, guid, DataSize, pData)
12333+#endif
12334+
12335+#if defined(__cplusplus)
12336+#define _sg_win32_refguid(guid) guid
12337+#else
12338+#define _sg_win32_refguid(guid) &guid
12339+#endif
12340+
12341+static const GUID _sg_d3d11_WKPDID_D3DDebugObjectName = { 0x429b8c22,0x9188,0x4b0c, {0x87,0x42,0xac,0xb0,0xbf,0x85,0xc2,0x00} };
12342+
12343+#if defined(SOKOL_DEBUG)
12344+#define _sg_d3d11_setlabel(self, label) _sg_d3d11_SetPrivateData(self, _sg_win32_refguid(_sg_d3d11_WKPDID_D3DDebugObjectName), label ? (UINT)strlen(label) : 0, label)
12345+#else
12346+#define _sg_d3d11_setlabel(self, label)
12347+#endif
12348+
12349+
12350+//-- D3D11 C/C++ wrappers ------------------------------------------------------
12351+static inline HRESULT _sg_d3d11_CheckFormatSupport(ID3D11Device* self, DXGI_FORMAT Format, UINT* pFormatSupport) {
12352+    #if defined(__cplusplus)
12353+        return self->CheckFormatSupport(Format, pFormatSupport);
12354+    #else
12355+        return self->lpVtbl->CheckFormatSupport(self, Format, pFormatSupport);
12356+    #endif
12357+}
12358+
12359+static inline void _sg_d3d11_OMSetRenderTargets(ID3D11DeviceContext* self, UINT NumViews, ID3D11RenderTargetView* const* ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) {
12360+    #if defined(__cplusplus)
12361+        self->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView);
12362+    #else
12363+        self->lpVtbl->OMSetRenderTargets(self, NumViews, ppRenderTargetViews, pDepthStencilView);
12364+    #endif
12365+}
12366+
12367+static inline void _sg_d3d11_RSSetState(ID3D11DeviceContext* self, ID3D11RasterizerState* pRasterizerState) {
12368+    #if defined(__cplusplus)
12369+        self->RSSetState(pRasterizerState);
12370+    #else
12371+        self->lpVtbl->RSSetState(self, pRasterizerState);
12372+    #endif
12373+}
12374+
12375+static inline void _sg_d3d11_OMSetDepthStencilState(ID3D11DeviceContext* self, ID3D11DepthStencilState* pDepthStencilState, UINT StencilRef) {
12376+    #if defined(__cplusplus)
12377+        self->OMSetDepthStencilState(pDepthStencilState, StencilRef);
12378+    #else
12379+        self->lpVtbl->OMSetDepthStencilState(self, pDepthStencilState, StencilRef);
12380+    #endif
12381+}
12382+
12383+static inline void _sg_d3d11_OMSetBlendState(ID3D11DeviceContext* self, ID3D11BlendState* pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) {
12384+    #if defined(__cplusplus)
12385+        self->OMSetBlendState(pBlendState, BlendFactor, SampleMask);
12386+    #else
12387+        self->lpVtbl->OMSetBlendState(self, pBlendState, BlendFactor, SampleMask);
12388+    #endif
12389+}
12390+
12391+static inline void _sg_d3d11_IASetVertexBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppVertexBuffers, const UINT* pStrides, const UINT* pOffsets) {
12392+    #if defined(__cplusplus)
12393+        self->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets);
12394+    #else
12395+        self->lpVtbl->IASetVertexBuffers(self, StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets);
12396+    #endif
12397+}
12398+
12399+static inline void _sg_d3d11_IASetIndexBuffer(ID3D11DeviceContext* self, ID3D11Buffer* pIndexBuffer, DXGI_FORMAT Format, UINT Offset) {
12400+    #if defined(__cplusplus)
12401+        self->IASetIndexBuffer(pIndexBuffer, Format, Offset);
12402+    #else
12403+        self->lpVtbl->IASetIndexBuffer(self, pIndexBuffer, Format, Offset);
12404+    #endif
12405+}
12406+
12407+static inline void _sg_d3d11_IASetInputLayout(ID3D11DeviceContext* self, ID3D11InputLayout* pInputLayout) {
12408+    #if defined(__cplusplus)
12409+        self->IASetInputLayout(pInputLayout);
12410+    #else
12411+        self->lpVtbl->IASetInputLayout(self, pInputLayout);
12412+    #endif
12413+}
12414+
12415+static inline void _sg_d3d11_VSSetShader(ID3D11DeviceContext* self, ID3D11VertexShader* pVertexShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) {
12416+    #if defined(__cplusplus)
12417+        self->VSSetShader(pVertexShader, ppClassInstances, NumClassInstances);
12418+    #else
12419+        self->lpVtbl->VSSetShader(self, pVertexShader, ppClassInstances, NumClassInstances);
12420+    #endif
12421+}
12422+
12423+static inline void _sg_d3d11_PSSetShader(ID3D11DeviceContext* self, ID3D11PixelShader* pPixelShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) {
12424+    #if defined(__cplusplus)
12425+        self->PSSetShader(pPixelShader, ppClassInstances, NumClassInstances);
12426+    #else
12427+        self->lpVtbl->PSSetShader(self, pPixelShader, ppClassInstances, NumClassInstances);
12428+    #endif
12429+}
12430+
12431+static inline void _sg_d3d11_CSSetShader(ID3D11DeviceContext* self, ID3D11ComputeShader* pComputeShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) {
12432+    #if defined(__cplusplus)
12433+        self->CSSetShader(pComputeShader, ppClassInstances, NumClassInstances);
12434+    #else
12435+        self->lpVtbl->CSSetShader(self, pComputeShader, ppClassInstances, NumClassInstances);
12436+    #endif
12437+}
12438+
12439+static inline void _sg_d3d11_VSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) {
12440+    #if defined(__cplusplus)
12441+        self->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers);
12442+    #else
12443+        self->lpVtbl->VSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers);
12444+    #endif
12445+}
12446+
12447+static inline void _sg_d3d11_PSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) {
12448+    #if defined(__cplusplus)
12449+        self->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers);
12450+    #else
12451+        self->lpVtbl->PSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers);
12452+    #endif
12453+}
12454+
12455+static inline void _sg_d3d11_CSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) {
12456+    #if defined(__cplusplus)
12457+        self->CSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers);
12458+    #else
12459+        self->lpVtbl->CSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers);
12460+    #endif
12461+}
12462+
12463+static inline void _sg_d3d11_VSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) {
12464+    #if defined(__cplusplus)
12465+        self->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews);
12466+    #else
12467+        self->lpVtbl->VSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews);
12468+    #endif
12469+}
12470+
12471+static inline void _sg_d3d11_PSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) {
12472+    #if defined(__cplusplus)
12473+        self->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews);
12474+    #else
12475+        self->lpVtbl->PSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews);
12476+    #endif
12477+}
12478+
12479+static inline void _sg_d3d11_CSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) {
12480+    #if defined(__cplusplus)
12481+        self->CSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews);
12482+    #else
12483+        self->lpVtbl->CSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews);
12484+    #endif
12485+}
12486+
12487+static inline void _sg_d3d11_VSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) {
12488+    #if defined(__cplusplus)
12489+        self->VSSetSamplers(StartSlot, NumSamplers, ppSamplers);
12490+    #else
12491+        self->lpVtbl->VSSetSamplers(self, StartSlot, NumSamplers, ppSamplers);
12492+    #endif
12493+}
12494+
12495+static inline void _sg_d3d11_PSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) {
12496+    #if defined(__cplusplus)
12497+        self->PSSetSamplers(StartSlot, NumSamplers, ppSamplers);
12498+    #else
12499+        self->lpVtbl->PSSetSamplers(self, StartSlot, NumSamplers, ppSamplers);
12500+    #endif
12501+}
12502+
12503+static inline void _sg_d3d11_CSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) {
12504+    #if defined(__cplusplus)
12505+        self->CSSetSamplers(StartSlot, NumSamplers, ppSamplers);
12506+    #else
12507+        self->lpVtbl->CSSetSamplers(self, StartSlot, NumSamplers, ppSamplers);
12508+    #endif
12509+}
12510+
12511+static inline void _sg_d3d11_CSSetUnorderedAccessViews(ID3D11DeviceContext* self, UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView* const* ppUnorderedAccessViews, const UINT* pUAVInitialCounts) {
12512+    #if defined(__cplusplus)
12513+        self->CSSetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts);
12514+    #else
12515+        self->lpVtbl->CSSetUnorderedAccessViews(self, StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts);
12516+    #endif
12517+}
12518+
12519+static inline HRESULT _sg_d3d11_CreateBuffer(ID3D11Device* self, const D3D11_BUFFER_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Buffer** ppBuffer) {
12520+    #if defined(__cplusplus)
12521+        return self->CreateBuffer(pDesc, pInitialData, ppBuffer);
12522+    #else
12523+        return self->lpVtbl->CreateBuffer(self, pDesc, pInitialData, ppBuffer);
12524+    #endif
12525+}
12526+
12527+static inline HRESULT _sg_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) {
12528+    #if defined(__cplusplus)
12529+        return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D);
12530+    #else
12531+        return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D);
12532+    #endif
12533+}
12534+
12535+static inline HRESULT _sg_d3d11_CreateShaderResourceView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC* pDesc, ID3D11ShaderResourceView** ppSRView) {
12536+    #if defined(__cplusplus)
12537+        return self->CreateShaderResourceView(pResource, pDesc, ppSRView);
12538+    #else
12539+        return self->lpVtbl->CreateShaderResourceView(self, pResource, pDesc, ppSRView);
12540+    #endif
12541+}
12542+
12543+static inline HRESULT _sg_d3d11_CreateUnorderedAccessView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC* pDesc, ID3D11UnorderedAccessView** ppUAVView) {
12544+    #if defined(__cplusplus)
12545+        return self->CreateUnorderedAccessView(pResource, pDesc, ppUAVView);
12546+    #else
12547+        return self->lpVtbl->CreateUnorderedAccessView(self, pResource, pDesc, ppUAVView);
12548+    #endif
12549+}
12550+
12551+static inline void _sg_d3d11_GetResource(ID3D11View* self, ID3D11Resource** ppResource) {
12552+    #if defined(__cplusplus)
12553+        self->GetResource(ppResource);
12554+    #else
12555+        self->lpVtbl->GetResource(self, ppResource);
12556+    #endif
12557+}
12558+
12559+static inline HRESULT _sg_d3d11_CreateTexture3D(ID3D11Device* self, const D3D11_TEXTURE3D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture3D** ppTexture3D) {
12560+    #if defined(__cplusplus)
12561+        return self->CreateTexture3D(pDesc, pInitialData, ppTexture3D);
12562+    #else
12563+        return self->lpVtbl->CreateTexture3D(self, pDesc, pInitialData, ppTexture3D);
12564+    #endif
12565+}
12566+
12567+static inline HRESULT _sg_d3d11_CreateSamplerState(ID3D11Device* self, const D3D11_SAMPLER_DESC* pSamplerDesc, ID3D11SamplerState** ppSamplerState) {
12568+    #if defined(__cplusplus)
12569+        return self->CreateSamplerState(pSamplerDesc, ppSamplerState);
12570+    #else
12571+        return self->lpVtbl->CreateSamplerState(self, pSamplerDesc, ppSamplerState);
12572+    #endif
12573+}
12574+
12575+static inline LPVOID _sg_d3d11_GetBufferPointer(ID3D10Blob* self) {
12576+    #if defined(__cplusplus)
12577+        return self->GetBufferPointer();
12578+    #else
12579+        return self->lpVtbl->GetBufferPointer(self);
12580+    #endif
12581+}
12582+
12583+static inline SIZE_T _sg_d3d11_GetBufferSize(ID3D10Blob* self) {
12584+    #if defined(__cplusplus)
12585+        return self->GetBufferSize();
12586+    #else
12587+        return self->lpVtbl->GetBufferSize(self);
12588+    #endif
12589+}
12590+
12591+static inline HRESULT _sg_d3d11_CreateVertexShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11VertexShader** ppVertexShader) {
12592+    #if defined(__cplusplus)
12593+        return self->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader);
12594+    #else
12595+        return self->lpVtbl->CreateVertexShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader);
12596+    #endif
12597+}
12598+
12599+static inline HRESULT _sg_d3d11_CreatePixelShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11PixelShader** ppPixelShader) {
12600+    #if defined(__cplusplus)
12601+        return self->CreatePixelShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader);
12602+    #else
12603+        return self->lpVtbl->CreatePixelShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader);
12604+    #endif
12605+}
12606+
12607+static inline HRESULT _sg_d3d11_CreateComputeShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11ComputeShader** ppComputeShader) {
12608+    #if defined(__cplusplus)
12609+        return self->CreateComputeShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader);
12610+    #else
12611+        return self->lpVtbl->CreateComputeShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader);
12612+    #endif
12613+}
12614+
12615+static inline HRESULT _sg_d3d11_CreateInputLayout(ID3D11Device* self, const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs, UINT NumElements, const void* pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) {
12616+    #if defined(__cplusplus)
12617+        return self->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout);
12618+    #else
12619+        return self->lpVtbl->CreateInputLayout(self, pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout);
12620+    #endif
12621+}
12622+
12623+static inline HRESULT _sg_d3d11_CreateRasterizerState(ID3D11Device* self, const D3D11_RASTERIZER_DESC* pRasterizerDesc, ID3D11RasterizerState** ppRasterizerState) {
12624+    #if defined(__cplusplus)
12625+        return self->CreateRasterizerState(pRasterizerDesc, ppRasterizerState);
12626+    #else
12627+        return self->lpVtbl->CreateRasterizerState(self, pRasterizerDesc, ppRasterizerState);
12628+    #endif
12629+}
12630+
12631+static inline HRESULT _sg_d3d11_CreateDepthStencilState(ID3D11Device* self, const D3D11_DEPTH_STENCIL_DESC* pDepthStencilDesc, ID3D11DepthStencilState** ppDepthStencilState) {
12632+    #if defined(__cplusplus)
12633+        return self->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState);
12634+    #else
12635+        return self->lpVtbl->CreateDepthStencilState(self, pDepthStencilDesc, ppDepthStencilState);
12636+    #endif
12637+}
12638+
12639+static inline HRESULT _sg_d3d11_CreateBlendState(ID3D11Device* self, const D3D11_BLEND_DESC* pBlendStateDesc, ID3D11BlendState** ppBlendState) {
12640+    #if defined(__cplusplus)
12641+        return self->CreateBlendState(pBlendStateDesc, ppBlendState);
12642+    #else
12643+        return self->lpVtbl->CreateBlendState(self, pBlendStateDesc, ppBlendState);
12644+    #endif
12645+}
12646+
12647+static inline HRESULT _sg_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) {
12648+    #if defined(__cplusplus)
12649+        return self->CreateRenderTargetView(pResource, pDesc, ppRTView);
12650+    #else
12651+        return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView);
12652+    #endif
12653+}
12654+
12655+static inline HRESULT _sg_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) {
12656+    #if defined(__cplusplus)
12657+        return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView);
12658+    #else
12659+        return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView);
12660+    #endif
12661+}
12662+
12663+static inline void _sg_d3d11_RSSetViewports(ID3D11DeviceContext* self, UINT NumViewports, const D3D11_VIEWPORT* pViewports) {
12664+    #if defined(__cplusplus)
12665+        self->RSSetViewports(NumViewports, pViewports);
12666+    #else
12667+        self->lpVtbl->RSSetViewports(self, NumViewports, pViewports);
12668+    #endif
12669+}
12670+
12671+static inline void _sg_d3d11_RSSetScissorRects(ID3D11DeviceContext* self, UINT NumRects, const D3D11_RECT* pRects) {
12672+    #if defined(__cplusplus)
12673+        self->RSSetScissorRects(NumRects, pRects);
12674+    #else
12675+        self->lpVtbl->RSSetScissorRects(self, NumRects, pRects);
12676+    #endif
12677+}
12678+
12679+static inline void _sg_d3d11_ClearRenderTargetView(ID3D11DeviceContext* self, ID3D11RenderTargetView* pRenderTargetView, const FLOAT ColorRGBA[4]) {
12680+    #if defined(__cplusplus)
12681+        self->ClearRenderTargetView(pRenderTargetView, ColorRGBA);
12682+    #else
12683+        self->lpVtbl->ClearRenderTargetView(self, pRenderTargetView, ColorRGBA);
12684+    #endif
12685+}
12686+
12687+static inline void _sg_d3d11_ClearDepthStencilView(ID3D11DeviceContext* self, ID3D11DepthStencilView* pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) {
12688+    #if defined(__cplusplus)
12689+        self->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil);
12690+    #else
12691+        self->lpVtbl->ClearDepthStencilView(self, pDepthStencilView, ClearFlags, Depth, Stencil);
12692+    #endif
12693+}
12694+
12695+static inline void _sg_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) {
12696+    #if defined(__cplusplus)
12697+        self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format);
12698+    #else
12699+        self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format);
12700+    #endif
12701+}
12702+
12703+static inline void _sg_d3d11_IASetPrimitiveTopology(ID3D11DeviceContext* self, D3D11_PRIMITIVE_TOPOLOGY Topology) {
12704+    #if defined(__cplusplus)
12705+        self->IASetPrimitiveTopology(Topology);
12706+    #else
12707+        self->lpVtbl->IASetPrimitiveTopology(self, Topology);
12708+    #endif
12709+}
12710+
12711+static inline void _sg_d3d11_UpdateSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, const D3D11_BOX* pDstBox, const void* pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) {
12712+    #if defined(__cplusplus)
12713+        self->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch);
12714+    #else
12715+        self->lpVtbl->UpdateSubresource(self, pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch);
12716+    #endif
12717+}
12718+
12719+static inline void _sg_d3d11_DrawIndexed(ID3D11DeviceContext* self, UINT IndexCount, UINT StartIndexLocation, INT  BaseVertexLocation) {
12720+    #if defined(__cplusplus)
12721+        self->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation);
12722+    #else
12723+        self->lpVtbl->DrawIndexed(self, IndexCount, StartIndexLocation, BaseVertexLocation);
12724+    #endif
12725+}
12726+
12727+static inline void _sg_d3d11_DrawIndexedInstanced(ID3D11DeviceContext* self, UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) {
12728+    #if defined(__cplusplus)
12729+        self->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation);
12730+    #else
12731+        self->lpVtbl->DrawIndexedInstanced(self, IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation);
12732+    #endif
12733+}
12734+
12735+static inline void _sg_d3d11_Draw(ID3D11DeviceContext* self, UINT VertexCount, UINT StartVertexLocation) {
12736+    #if defined(__cplusplus)
12737+        self->Draw(VertexCount, StartVertexLocation);
12738+    #else
12739+        self->lpVtbl->Draw(self, VertexCount, StartVertexLocation);
12740+    #endif
12741+}
12742+
12743+static inline void _sg_d3d11_DrawInstanced(ID3D11DeviceContext* self, UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) {
12744+    #if defined(__cplusplus)
12745+        self->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation);
12746+    #else
12747+        self->lpVtbl->DrawInstanced(self, VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation);
12748+    #endif
12749+}
12750+
12751+static inline void _sg_d3d11_Dispatch(ID3D11DeviceContext* self, UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) {
12752+    #if defined(__cplusplus)
12753+        self->Dispatch(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ);
12754+    #else
12755+        self->lpVtbl->Dispatch(self, ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ);
12756+    #endif
12757+}
12758+
12759+static inline HRESULT _sg_d3d11_Map(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE* pMappedResource) {
12760+    #if defined(__cplusplus)
12761+        return self->Map(pResource, Subresource, MapType, MapFlags, pMappedResource);
12762+    #else
12763+        return self->lpVtbl->Map(self, pResource, Subresource, MapType, MapFlags, pMappedResource);
12764+    #endif
12765+}
12766+
12767+static inline void _sg_d3d11_Unmap(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource) {
12768+    #if defined(__cplusplus)
12769+        self->Unmap(pResource, Subresource);
12770+    #else
12771+        self->lpVtbl->Unmap(self, pResource, Subresource);
12772+    #endif
12773+}
12774+
12775+static inline void _sg_d3d11_ClearState(ID3D11DeviceContext* self) {
12776+    #if defined(__cplusplus)
12777+        self->ClearState();
12778+    #else
12779+        self->lpVtbl->ClearState(self);
12780+    #endif
12781+}
12782+
12783+static inline D3D_FEATURE_LEVEL _sg_d3d11_GetFeatureLevel(ID3D11Device* self) {
12784+    #if defined(__cplusplus)
12785+        return self->GetFeatureLevel();
12786+    #else
12787+        return self->lpVtbl->GetFeatureLevel(self);
12788+    #endif
12789+}
12790+
12791+//-- enum translation functions ------------------------------------------------
12792+_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_image_usage(const sg_image_usage* usg) {
12793+    if (usg->immutable) {
12794+        if (usg->color_attachment ||
12795+            usg->resolve_attachment ||
12796+            usg->depth_stencil_attachment ||
12797+            usg->storage_image)
12798+        {
12799+            return D3D11_USAGE_DEFAULT;
12800+        } else {
12801+            return D3D11_USAGE_IMMUTABLE;
12802+        }
12803+    } else {
12804+        return D3D11_USAGE_DYNAMIC;
12805+    }
12806+}
12807+
12808+_SOKOL_PRIVATE UINT _sg_d3d11_image_bind_flags(const sg_image_usage* usg) {
12809+    UINT res = D3D11_BIND_SHADER_RESOURCE;
12810+    if (usg->color_attachment) {
12811+        res |= D3D11_BIND_RENDER_TARGET;
12812+    }
12813+    if (usg->depth_stencil_attachment) {
12814+        res |= D3D11_BIND_DEPTH_STENCIL;
12815+    }
12816+    if (usg->storage_image) {
12817+        res |= D3D11_BIND_UNORDERED_ACCESS;
12818+    }
12819+    return res;
12820+}
12821+
12822+_SOKOL_PRIVATE UINT _sg_d3d11_image_cpu_access_flags(const sg_image_usage* usg) {
12823+    if (usg->color_attachment ||
12824+        usg->resolve_attachment ||
12825+        usg->depth_stencil_attachment ||
12826+        usg->storage_image ||
12827+        usg->immutable)
12828+    {
12829+        return 0;
12830+    } else {
12831+        return D3D11_CPU_ACCESS_WRITE;
12832+    }
12833+}
12834+
12835+_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_buffer_usage(const sg_buffer_usage* usg) {
12836+    if (usg->immutable) {
12837+        return usg->storage_buffer ? D3D11_USAGE_DEFAULT : D3D11_USAGE_IMMUTABLE;
12838+    } else {
12839+        return D3D11_USAGE_DYNAMIC;
12840+    }
12841+}
12842+
12843+_SOKOL_PRIVATE UINT _sg_d3d11_buffer_bind_flags(const sg_buffer_usage* usg) {
12844+    UINT res = 0;
12845+    if (usg->vertex_buffer) {
12846+        res |= D3D11_BIND_VERTEX_BUFFER;
12847+    }
12848+    if (usg->index_buffer) {
12849+        res |= D3D11_BIND_INDEX_BUFFER;
12850+    }
12851+    if (usg->storage_buffer) {
12852+        res |= D3D11_BIND_SHADER_RESOURCE;
12853+        if (usg->immutable) {
12854+            res |= D3D11_BIND_UNORDERED_ACCESS;
12855+        }
12856+    }
12857+    return res;
12858+}
12859+
12860+_SOKOL_PRIVATE UINT _sg_d3d11_buffer_misc_flags(const sg_buffer_usage* usg) {
12861+    return usg->storage_buffer ? D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS : 0;
12862+}
12863+
12864+_SOKOL_PRIVATE UINT _sg_d3d11_buffer_cpu_access_flags(const sg_buffer_usage* usg) {
12865+    return usg->immutable ? 0 : D3D11_CPU_ACCESS_WRITE;
12866+}
12867+
12868+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_base_pixel_format(sg_pixel_format fmt) {
12869+    switch (fmt) {
12870+        case SG_PIXELFORMAT_R8:             return DXGI_FORMAT_R8_UNORM;
12871+        case SG_PIXELFORMAT_R8SN:           return DXGI_FORMAT_R8_SNORM;
12872+        case SG_PIXELFORMAT_R8UI:           return DXGI_FORMAT_R8_UINT;
12873+        case SG_PIXELFORMAT_R8SI:           return DXGI_FORMAT_R8_SINT;
12874+        case SG_PIXELFORMAT_R16:            return DXGI_FORMAT_R16_UNORM;
12875+        case SG_PIXELFORMAT_R16SN:          return DXGI_FORMAT_R16_SNORM;
12876+        case SG_PIXELFORMAT_R16UI:          return DXGI_FORMAT_R16_UINT;
12877+        case SG_PIXELFORMAT_R16SI:          return DXGI_FORMAT_R16_SINT;
12878+        case SG_PIXELFORMAT_R16F:           return DXGI_FORMAT_R16_FLOAT;
12879+        case SG_PIXELFORMAT_RG8:            return DXGI_FORMAT_R8G8_UNORM;
12880+        case SG_PIXELFORMAT_RG8SN:          return DXGI_FORMAT_R8G8_SNORM;
12881+        case SG_PIXELFORMAT_RG8UI:          return DXGI_FORMAT_R8G8_UINT;
12882+        case SG_PIXELFORMAT_RG8SI:          return DXGI_FORMAT_R8G8_SINT;
12883+        case SG_PIXELFORMAT_R32UI:          return DXGI_FORMAT_R32_UINT;
12884+        case SG_PIXELFORMAT_R32SI:          return DXGI_FORMAT_R32_SINT;
12885+        case SG_PIXELFORMAT_R32F:           return DXGI_FORMAT_R32_FLOAT;
12886+        case SG_PIXELFORMAT_RG16:           return DXGI_FORMAT_R16G16_UNORM;
12887+        case SG_PIXELFORMAT_RG16SN:         return DXGI_FORMAT_R16G16_SNORM;
12888+        case SG_PIXELFORMAT_RG16UI:         return DXGI_FORMAT_R16G16_UINT;
12889+        case SG_PIXELFORMAT_RG16SI:         return DXGI_FORMAT_R16G16_SINT;
12890+        case SG_PIXELFORMAT_RG16F:          return DXGI_FORMAT_R16G16_FLOAT;
12891+        case SG_PIXELFORMAT_RGBA8:          return DXGI_FORMAT_R8G8B8A8_UNORM;
12892+        case SG_PIXELFORMAT_SRGB8A8:        return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
12893+        case SG_PIXELFORMAT_RGBA8SN:        return DXGI_FORMAT_R8G8B8A8_SNORM;
12894+        case SG_PIXELFORMAT_RGBA8UI:        return DXGI_FORMAT_R8G8B8A8_UINT;
12895+        case SG_PIXELFORMAT_RGBA8SI:        return DXGI_FORMAT_R8G8B8A8_SINT;
12896+        case SG_PIXELFORMAT_BGRA8:          return DXGI_FORMAT_B8G8R8A8_UNORM;
12897+        case SG_PIXELFORMAT_SBGR8A8:        return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
12898+        case SG_PIXELFORMAT_RGB10A2:        return DXGI_FORMAT_R10G10B10A2_UNORM;
12899+        case SG_PIXELFORMAT_RG11B10F:       return DXGI_FORMAT_R11G11B10_FLOAT;
12900+        case SG_PIXELFORMAT_RGB9E5:         return DXGI_FORMAT_R9G9B9E5_SHAREDEXP;
12901+        case SG_PIXELFORMAT_RG32UI:         return DXGI_FORMAT_R32G32_UINT;
12902+        case SG_PIXELFORMAT_RG32SI:         return DXGI_FORMAT_R32G32_SINT;
12903+        case SG_PIXELFORMAT_RG32F:          return DXGI_FORMAT_R32G32_FLOAT;
12904+        case SG_PIXELFORMAT_RGBA16:         return DXGI_FORMAT_R16G16B16A16_UNORM;
12905+        case SG_PIXELFORMAT_RGBA16SN:       return DXGI_FORMAT_R16G16B16A16_SNORM;
12906+        case SG_PIXELFORMAT_RGBA16UI:       return DXGI_FORMAT_R16G16B16A16_UINT;
12907+        case SG_PIXELFORMAT_RGBA16SI:       return DXGI_FORMAT_R16G16B16A16_SINT;
12908+        case SG_PIXELFORMAT_RGBA16F:        return DXGI_FORMAT_R16G16B16A16_FLOAT;
12909+        case SG_PIXELFORMAT_RGBA32UI:       return DXGI_FORMAT_R32G32B32A32_UINT;
12910+        case SG_PIXELFORMAT_RGBA32SI:       return DXGI_FORMAT_R32G32B32A32_SINT;
12911+        case SG_PIXELFORMAT_RGBA32F:        return DXGI_FORMAT_R32G32B32A32_FLOAT;
12912+        case SG_PIXELFORMAT_DEPTH:          return DXGI_FORMAT_R32_TYPELESS;
12913+        case SG_PIXELFORMAT_DEPTH_STENCIL:  return DXGI_FORMAT_R32G8X24_TYPELESS;
12914+        case SG_PIXELFORMAT_BC1_RGBA:       return DXGI_FORMAT_BC1_UNORM;
12915+        case SG_PIXELFORMAT_BC2_RGBA:       return DXGI_FORMAT_BC2_UNORM;
12916+        case SG_PIXELFORMAT_BC3_RGBA:       return DXGI_FORMAT_BC3_UNORM;
12917+        case SG_PIXELFORMAT_BC3_SRGBA:      return DXGI_FORMAT_BC3_UNORM_SRGB;
12918+        case SG_PIXELFORMAT_BC4_R:          return DXGI_FORMAT_BC4_UNORM;
12919+        case SG_PIXELFORMAT_BC4_RSN:        return DXGI_FORMAT_BC4_SNORM;
12920+        case SG_PIXELFORMAT_BC5_RG:         return DXGI_FORMAT_BC5_UNORM;
12921+        case SG_PIXELFORMAT_BC5_RGSN:       return DXGI_FORMAT_BC5_SNORM;
12922+        case SG_PIXELFORMAT_BC6H_RGBF:      return DXGI_FORMAT_BC6H_SF16;
12923+        case SG_PIXELFORMAT_BC6H_RGBUF:     return DXGI_FORMAT_BC6H_UF16;
12924+        case SG_PIXELFORMAT_BC7_RGBA:       return DXGI_FORMAT_BC7_UNORM;
12925+        case SG_PIXELFORMAT_BC7_SRGBA:      return DXGI_FORMAT_BC7_UNORM_SRGB;
12926+        default:                            return DXGI_FORMAT_UNKNOWN;
12927+    };
12928+}
12929+
12930+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_texture_pixel_format(sg_pixel_format fmt) {
12931+    if (fmt == SG_PIXELFORMAT_SRGB8A8) {
12932+        return DXGI_FORMAT_R8G8B8A8_TYPELESS;
12933+    } else if (fmt == SG_PIXELFORMAT_SBGR8A8) {
12934+        return DXGI_FORMAT_B8G8R8A8_TYPELESS;
12935+    } else {
12936+        return _sg_d3d11_base_pixel_format(fmt);
12937+    }
12938+}
12939+
12940+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_srv_pixel_format(sg_pixel_format fmt) {
12941+    if (fmt == SG_PIXELFORMAT_DEPTH) {
12942+        return DXGI_FORMAT_R32_FLOAT;
12943+    } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) {
12944+        return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
12945+    } else {
12946+        return _sg_d3d11_base_pixel_format(fmt);
12947+    }
12948+}
12949+
12950+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_dsv_pixel_format(sg_pixel_format fmt) {
12951+    if (fmt == SG_PIXELFORMAT_DEPTH) {
12952+        return DXGI_FORMAT_D32_FLOAT;
12953+    } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) {
12954+        return DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
12955+    } else {
12956+        return _sg_d3d11_base_pixel_format(fmt);
12957+    }
12958+}
12959+
12960+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_rtv_uav_pixel_format(sg_pixel_format fmt) {
12961+    if (fmt == SG_PIXELFORMAT_DEPTH) {
12962+        return DXGI_FORMAT_R32_FLOAT;
12963+    } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) {
12964+        return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
12965+    } else {
12966+        return _sg_d3d11_base_pixel_format(fmt);
12967+    }
12968+}
12969+
12970+_SOKOL_PRIVATE D3D11_PRIMITIVE_TOPOLOGY _sg_d3d11_primitive_topology(sg_primitive_type prim_type) {
12971+    switch (prim_type) {
12972+        case SG_PRIMITIVETYPE_POINTS:           return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST;
12973+        case SG_PRIMITIVETYPE_LINES:            return D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
12974+        case SG_PRIMITIVETYPE_LINE_STRIP:       return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP;
12975+        case SG_PRIMITIVETYPE_TRIANGLES:        return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
12976+        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
12977+        default: SOKOL_UNREACHABLE; return (D3D11_PRIMITIVE_TOPOLOGY) 0;
12978+    }
12979+}
12980+
12981+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_index_format(sg_index_type index_type) {
12982+    switch (index_type) {
12983+        case SG_INDEXTYPE_NONE:     return DXGI_FORMAT_UNKNOWN;
12984+        case SG_INDEXTYPE_UINT16:   return DXGI_FORMAT_R16_UINT;
12985+        case SG_INDEXTYPE_UINT32:   return DXGI_FORMAT_R32_UINT;
12986+        default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0;
12987+    }
12988+}
12989+
12990+_SOKOL_PRIVATE D3D11_FILTER _sg_d3d11_filter(sg_filter min_f, sg_filter mag_f, sg_filter mipmap_f, bool comparison, uint32_t max_anisotropy) {
12991+    uint32_t d3d11_filter = 0;
12992+    if (max_anisotropy > 1) {
12993+        // D3D11_FILTER_ANISOTROPIC = 0x55,
12994+        d3d11_filter |= 0x55;
12995+    } else {
12996+        // D3D11_FILTER_MIN_MAG_MIP_POINT = 0,
12997+        // D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR = 0x1,
12998+        // D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x4,
12999+        // D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR = 0x5,
13000+        // D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT = 0x10,
13001+        // D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x11,
13002+        // D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT = 0x14,
13003+        // D3D11_FILTER_MIN_MAG_MIP_LINEAR = 0x15,
13004+        if (mipmap_f == SG_FILTER_LINEAR) {
13005+            d3d11_filter |= 0x01;
13006+        }
13007+        if (mag_f == SG_FILTER_LINEAR) {
13008+            d3d11_filter |= 0x04;
13009+        }
13010+        if (min_f == SG_FILTER_LINEAR) {
13011+            d3d11_filter |= 0x10;
13012+        }
13013+    }
13014+    // D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT = 0x80,
13015+    // D3D11_FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR = 0x81,
13016+    // D3D11_FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x84,
13017+    // D3D11_FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR = 0x85,
13018+    // D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT = 0x90,
13019+    // D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x91,
13020+    // D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT = 0x94,
13021+    // D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR = 0x95,
13022+    // D3D11_FILTER_COMPARISON_ANISOTROPIC = 0xd5,
13023+    if (comparison) {
13024+        d3d11_filter |= 0x80;
13025+    }
13026+    return (D3D11_FILTER)d3d11_filter;
13027+}
13028+
13029+_SOKOL_PRIVATE D3D11_TEXTURE_ADDRESS_MODE _sg_d3d11_address_mode(sg_wrap m) {
13030+    switch (m) {
13031+        case SG_WRAP_REPEAT:            return D3D11_TEXTURE_ADDRESS_WRAP;
13032+        case SG_WRAP_CLAMP_TO_EDGE:     return D3D11_TEXTURE_ADDRESS_CLAMP;
13033+        case SG_WRAP_CLAMP_TO_BORDER:   return D3D11_TEXTURE_ADDRESS_BORDER;
13034+        case SG_WRAP_MIRRORED_REPEAT:   return D3D11_TEXTURE_ADDRESS_MIRROR;
13035+        default: SOKOL_UNREACHABLE; return (D3D11_TEXTURE_ADDRESS_MODE) 0;
13036+    }
13037+}
13038+
13039+_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_vertex_format(sg_vertex_format fmt) {
13040+    switch (fmt) {
13041+        case SG_VERTEXFORMAT_FLOAT:     return DXGI_FORMAT_R32_FLOAT;
13042+        case SG_VERTEXFORMAT_FLOAT2:    return DXGI_FORMAT_R32G32_FLOAT;
13043+        case SG_VERTEXFORMAT_FLOAT3:    return DXGI_FORMAT_R32G32B32_FLOAT;
13044+        case SG_VERTEXFORMAT_FLOAT4:    return DXGI_FORMAT_R32G32B32A32_FLOAT;
13045+        case SG_VERTEXFORMAT_INT:       return DXGI_FORMAT_R32_SINT;
13046+        case SG_VERTEXFORMAT_INT2:      return DXGI_FORMAT_R32G32_SINT;
13047+        case SG_VERTEXFORMAT_INT3:      return DXGI_FORMAT_R32G32B32_SINT;
13048+        case SG_VERTEXFORMAT_INT4:      return DXGI_FORMAT_R32G32B32A32_SINT;
13049+        case SG_VERTEXFORMAT_UINT:      return DXGI_FORMAT_R32_UINT;
13050+        case SG_VERTEXFORMAT_UINT2:     return DXGI_FORMAT_R32G32_UINT;
13051+        case SG_VERTEXFORMAT_UINT3:     return DXGI_FORMAT_R32G32B32_UINT;
13052+        case SG_VERTEXFORMAT_UINT4:     return DXGI_FORMAT_R32G32B32A32_UINT;
13053+        case SG_VERTEXFORMAT_BYTE4:     return DXGI_FORMAT_R8G8B8A8_SINT;
13054+        case SG_VERTEXFORMAT_BYTE4N:    return DXGI_FORMAT_R8G8B8A8_SNORM;
13055+        case SG_VERTEXFORMAT_UBYTE4:    return DXGI_FORMAT_R8G8B8A8_UINT;
13056+        case SG_VERTEXFORMAT_UBYTE4N:   return DXGI_FORMAT_R8G8B8A8_UNORM;
13057+        case SG_VERTEXFORMAT_SHORT2:    return DXGI_FORMAT_R16G16_SINT;
13058+        case SG_VERTEXFORMAT_SHORT2N:   return DXGI_FORMAT_R16G16_SNORM;
13059+        case SG_VERTEXFORMAT_USHORT2:   return DXGI_FORMAT_R16G16_UINT;
13060+        case SG_VERTEXFORMAT_USHORT2N:  return DXGI_FORMAT_R16G16_UNORM;
13061+        case SG_VERTEXFORMAT_SHORT4:    return DXGI_FORMAT_R16G16B16A16_SINT;
13062+        case SG_VERTEXFORMAT_SHORT4N:   return DXGI_FORMAT_R16G16B16A16_SNORM;
13063+        case SG_VERTEXFORMAT_USHORT4:   return DXGI_FORMAT_R16G16B16A16_UINT;
13064+        case SG_VERTEXFORMAT_USHORT4N:  return DXGI_FORMAT_R16G16B16A16_UNORM;
13065+        case SG_VERTEXFORMAT_UINT10_N2: return DXGI_FORMAT_R10G10B10A2_UNORM;
13066+        case SG_VERTEXFORMAT_HALF2:     return DXGI_FORMAT_R16G16_FLOAT;
13067+        case SG_VERTEXFORMAT_HALF4:     return DXGI_FORMAT_R16G16B16A16_FLOAT;
13068+        default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0;
13069+    }
13070+}
13071+
13072+_SOKOL_PRIVATE D3D11_INPUT_CLASSIFICATION _sg_d3d11_input_classification(sg_vertex_step step) {
13073+    switch (step) {
13074+        case SG_VERTEXSTEP_PER_VERTEX:      return D3D11_INPUT_PER_VERTEX_DATA;
13075+        case SG_VERTEXSTEP_PER_INSTANCE:    return D3D11_INPUT_PER_INSTANCE_DATA;
13076+        default: SOKOL_UNREACHABLE; return (D3D11_INPUT_CLASSIFICATION) 0;
13077+    }
13078+}
13079+
13080+_SOKOL_PRIVATE D3D11_CULL_MODE _sg_d3d11_cull_mode(sg_cull_mode m) {
13081+    switch (m) {
13082+        case SG_CULLMODE_NONE:      return D3D11_CULL_NONE;
13083+        case SG_CULLMODE_FRONT:     return D3D11_CULL_FRONT;
13084+        case SG_CULLMODE_BACK:      return D3D11_CULL_BACK;
13085+        default: SOKOL_UNREACHABLE; return (D3D11_CULL_MODE) 0;
13086+    }
13087+}
13088+
13089+_SOKOL_PRIVATE D3D11_COMPARISON_FUNC _sg_d3d11_compare_func(sg_compare_func f) {
13090+    switch (f) {
13091+        case SG_COMPAREFUNC_NEVER:          return D3D11_COMPARISON_NEVER;
13092+        case SG_COMPAREFUNC_LESS:           return D3D11_COMPARISON_LESS;
13093+        case SG_COMPAREFUNC_EQUAL:          return D3D11_COMPARISON_EQUAL;
13094+        case SG_COMPAREFUNC_LESS_EQUAL:     return D3D11_COMPARISON_LESS_EQUAL;
13095+        case SG_COMPAREFUNC_GREATER:        return D3D11_COMPARISON_GREATER;
13096+        case SG_COMPAREFUNC_NOT_EQUAL:      return D3D11_COMPARISON_NOT_EQUAL;
13097+        case SG_COMPAREFUNC_GREATER_EQUAL:  return D3D11_COMPARISON_GREATER_EQUAL;
13098+        case SG_COMPAREFUNC_ALWAYS:         return D3D11_COMPARISON_ALWAYS;
13099+        default: SOKOL_UNREACHABLE; return (D3D11_COMPARISON_FUNC) 0;
13100+    }
13101+}
13102+
13103+_SOKOL_PRIVATE D3D11_STENCIL_OP _sg_d3d11_stencil_op(sg_stencil_op op) {
13104+    switch (op) {
13105+        case SG_STENCILOP_KEEP:         return D3D11_STENCIL_OP_KEEP;
13106+        case SG_STENCILOP_ZERO:         return D3D11_STENCIL_OP_ZERO;
13107+        case SG_STENCILOP_REPLACE:      return D3D11_STENCIL_OP_REPLACE;
13108+        case SG_STENCILOP_INCR_CLAMP:   return D3D11_STENCIL_OP_INCR_SAT;
13109+        case SG_STENCILOP_DECR_CLAMP:   return D3D11_STENCIL_OP_DECR_SAT;
13110+        case SG_STENCILOP_INVERT:       return D3D11_STENCIL_OP_INVERT;
13111+        case SG_STENCILOP_INCR_WRAP:    return D3D11_STENCIL_OP_INCR;
13112+        case SG_STENCILOP_DECR_WRAP:    return D3D11_STENCIL_OP_DECR;
13113+        default: SOKOL_UNREACHABLE; return (D3D11_STENCIL_OP) 0;
13114+    }
13115+}
13116+
13117+_SOKOL_PRIVATE D3D11_BLEND _sg_d3d11_blend_factor(sg_blend_factor f) {
13118+    switch (f) {
13119+        case SG_BLENDFACTOR_ZERO:                   return D3D11_BLEND_ZERO;
13120+        case SG_BLENDFACTOR_ONE:                    return D3D11_BLEND_ONE;
13121+        case SG_BLENDFACTOR_SRC_COLOR:              return D3D11_BLEND_SRC_COLOR;
13122+        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return D3D11_BLEND_INV_SRC_COLOR;
13123+        case SG_BLENDFACTOR_SRC_ALPHA:              return D3D11_BLEND_SRC_ALPHA;
13124+        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return D3D11_BLEND_INV_SRC_ALPHA;
13125+        case SG_BLENDFACTOR_DST_COLOR:              return D3D11_BLEND_DEST_COLOR;
13126+        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return D3D11_BLEND_INV_DEST_COLOR;
13127+        case SG_BLENDFACTOR_DST_ALPHA:              return D3D11_BLEND_DEST_ALPHA;
13128+        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return D3D11_BLEND_INV_DEST_ALPHA;
13129+        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return D3D11_BLEND_SRC_ALPHA_SAT;
13130+        case SG_BLENDFACTOR_BLEND_COLOR:            return D3D11_BLEND_BLEND_FACTOR;
13131+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return D3D11_BLEND_INV_BLEND_FACTOR;
13132+        case SG_BLENDFACTOR_BLEND_ALPHA:            return D3D11_BLEND_BLEND_FACTOR;
13133+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return D3D11_BLEND_INV_BLEND_FACTOR;
13134+        case SG_BLENDFACTOR_SRC1_COLOR:             return D3D11_BLEND_SRC1_COLOR;
13135+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR:   return D3D11_BLEND_INV_SRC1_COLOR;
13136+        case SG_BLENDFACTOR_SRC1_ALPHA:             return D3D11_BLEND_SRC1_ALPHA;
13137+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA:   return D3D11_BLEND_INV_SRC1_ALPHA;
13138+        default: SOKOL_UNREACHABLE; return (D3D11_BLEND) 0;
13139+    }
13140+}
13141+
13142+_SOKOL_PRIVATE D3D11_BLEND_OP _sg_d3d11_blend_op(sg_blend_op op) {
13143+    switch (op) {
13144+        case SG_BLENDOP_ADD:                return D3D11_BLEND_OP_ADD;
13145+        case SG_BLENDOP_SUBTRACT:           return D3D11_BLEND_OP_SUBTRACT;
13146+        case SG_BLENDOP_REVERSE_SUBTRACT:   return D3D11_BLEND_OP_REV_SUBTRACT;
13147+        case SG_BLENDOP_MIN:                return D3D11_BLEND_OP_MIN;
13148+        case SG_BLENDOP_MAX:                return D3D11_BLEND_OP_MAX;
13149+        default: SOKOL_UNREACHABLE; return (D3D11_BLEND_OP) 0;
13150+    }
13151+}
13152+
13153+_SOKOL_PRIVATE UINT8 _sg_d3d11_color_write_mask(sg_color_mask m) {
13154+    UINT8 res = 0;
13155+    if (m & SG_COLORMASK_R) {
13156+        res |= D3D11_COLOR_WRITE_ENABLE_RED;
13157+    }
13158+    if (m & SG_COLORMASK_G) {
13159+        res |= D3D11_COLOR_WRITE_ENABLE_GREEN;
13160+    }
13161+    if (m & SG_COLORMASK_B) {
13162+        res |= D3D11_COLOR_WRITE_ENABLE_BLUE;
13163+    }
13164+    if (m & SG_COLORMASK_A) {
13165+        res |= D3D11_COLOR_WRITE_ENABLE_ALPHA;
13166+    }
13167+    return res;
13168+}
13169+
13170+_SOKOL_PRIVATE UINT _sg_d3d11_dxgi_fmt_caps(DXGI_FORMAT dxgi_fmt) {
13171+    UINT dxgi_fmt_caps = 0;
13172+    if (dxgi_fmt != DXGI_FORMAT_UNKNOWN) {
13173+        HRESULT hr = _sg_d3d11_CheckFormatSupport(_sg.d3d11.dev, dxgi_fmt, &dxgi_fmt_caps);
13174+        SOKOL_ASSERT(SUCCEEDED(hr) || (E_FAIL == hr));
13175+        if (!SUCCEEDED(hr)) {
13176+            dxgi_fmt_caps = 0;
13177+        }
13178+    }
13179+    return dxgi_fmt_caps;
13180+}
13181+
13182+// see: https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits#resource-limits-for-feature-level-11-hardware
13183+_SOKOL_PRIVATE void _sg_d3d11_init_caps(void) {
13184+    _sg.backend = SG_BACKEND_D3D11;
13185+
13186+    _sg.features.origin_top_left = true;
13187+    _sg.features.image_clamp_to_border = true;
13188+    _sg.features.mrt_independent_blend_state = true;
13189+    _sg.features.mrt_independent_write_mask = true;
13190+    _sg.features.compute = true;
13191+    _sg.features.msaa_texture_bindings = true;
13192+    _sg.features.draw_base_vertex = true;
13193+    _sg.features.draw_base_instance = true;
13194+    _sg.features.dual_source_blending = true;
13195+    _sg.features.vertexformat_int10_n2 = false;
13196+
13197+    _sg.limits.max_image_size_2d = 16 * 1024;
13198+    _sg.limits.max_image_size_cube = 16 * 1024;
13199+    _sg.limits.max_image_size_3d = 2 * 1024;
13200+    _sg.limits.max_image_size_array = 16 * 1024;
13201+    _sg.limits.max_image_array_layers = _SG_D3D11_MAX_TEXTUREARRAY_LAYERS;
13202+    _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES;
13203+    _sg.limits.max_color_attachments = _sg_min(8, SG_MAX_COLOR_ATTACHMENTS);
13204+    _sg.limits.max_texture_bindings_per_stage = _sg_min(128, SG_MAX_VIEW_BINDSLOTS);
13205+    _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min(64, SG_MAX_VIEW_BINDSLOTS);
13206+    if (_sg_d3d11_GetFeatureLevel(_sg.d3d11.dev) >= D3D_FEATURE_LEVEL_11_1) {
13207+        _sg.limits.d3d11_max_unordered_access_views = _sg_min(64, SG_MAX_VIEW_BINDSLOTS);
13208+    } else {
13209+        _sg.limits.d3d11_max_unordered_access_views = _sg_min(8, SG_MAX_VIEW_BINDSLOTS);
13210+    }
13211+    _sg.limits.max_storage_image_bindings_per_stage = _sg.limits.d3d11_max_unordered_access_views;
13212+
13213+    // see: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_format_support
13214+    for (int fmt = (SG_PIXELFORMAT_NONE+1); fmt < _SG_PIXELFORMAT_NUM; fmt++) {
13215+        const UINT srv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_srv_pixel_format((sg_pixel_format)fmt));
13216+        const UINT rtv_uav_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_rtv_uav_pixel_format((sg_pixel_format)fmt));
13217+        const UINT dsv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_dsv_pixel_format((sg_pixel_format)fmt));
13218+        _sg_pixelformat_info_t* info = &_sg.formats[fmt];
13219+        const bool render = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_RENDER_TARGET);
13220+        const bool depth  = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL);
13221+        info->sample = 0 != (srv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_TEXTURE2D);
13222+        info->filter = 0 != (srv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_SHADER_SAMPLE);
13223+        info->render = render || depth;
13224+        if (depth) {
13225+            info->blend = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE);
13226+            info->msaa  = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET);
13227+        } else {
13228+            info->blend = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE);
13229+            info->msaa  = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET);
13230+        }
13231+        info->depth = depth;
13232+        info->read = info->write = 0 != (rtv_uav_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_TYPED_UNORDERED_ACCESS_VIEW);
13233+    }
13234+}
13235+
13236+_SOKOL_PRIVATE void _sg_d3d11_setup_backend(const sg_desc* desc) {
13237+    // assume _sg.d3d11 already is zero-initialized
13238+    SOKOL_ASSERT(desc);
13239+    SOKOL_ASSERT(desc->environment.d3d11.device);
13240+    SOKOL_ASSERT(desc->environment.d3d11.device_context);
13241+    _sg.d3d11.valid = true;
13242+    _sg.d3d11.dev = (ID3D11Device*) desc->environment.d3d11.device;
13243+    _sg.d3d11.ctx = (ID3D11DeviceContext*) desc->environment.d3d11.device_context;
13244+    _sg_d3d11_init_caps();
13245+    if (_sg_d3d11_GetFeatureLevel(_sg.d3d11.dev) == D3D_FEATURE_LEVEL_11_0) {
13246+        _SG_WARN(D3D11_FEATURE_LEVEL_0_DETECTED);
13247+    }
13248+}
13249+
13250+_SOKOL_PRIVATE void _sg_d3d11_discard_backend(void) {
13251+    SOKOL_ASSERT(_sg.d3d11.valid);
13252+    _sg.d3d11.valid = false;
13253+}
13254+
13255+_SOKOL_PRIVATE void _sg_d3d11_clear_state(void) {
13256+    // clear all the device context state, so that resource refs don't keep stuck in the d3d device context
13257+    _sg_d3d11_ClearState(_sg.d3d11.ctx);
13258+}
13259+
13260+_SOKOL_PRIVATE void _sg_d3d11_reset_state_cache(void) {
13261+    // there's currently no state cache in the D3D11 backend, so this is a no-op
13262+}
13263+
13264+_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
13265+    SOKOL_ASSERT(buf && desc);
13266+    SOKOL_ASSERT(!buf->d3d11.buf);
13267+    const bool injected = (0 != desc->d3d11_buffer);
13268+    if (injected) {
13269+        buf->d3d11.buf = (ID3D11Buffer*) desc->d3d11_buffer;
13270+        _sg_d3d11_AddRef(buf->d3d11.buf);
13271+    } else {
13272+        _SG_STRUCT(D3D11_BUFFER_DESC, d3d11_buf_desc);
13273+        d3d11_buf_desc.ByteWidth = (UINT)buf->cmn.size;
13274+        d3d11_buf_desc.Usage = _sg_d3d11_buffer_usage(&buf->cmn.usage);
13275+        d3d11_buf_desc.BindFlags = _sg_d3d11_buffer_bind_flags(&buf->cmn.usage);
13276+        d3d11_buf_desc.CPUAccessFlags = _sg_d3d11_buffer_cpu_access_flags(&buf->cmn.usage);
13277+        d3d11_buf_desc.MiscFlags = _sg_d3d11_buffer_misc_flags(&buf->cmn.usage);
13278+        D3D11_SUBRESOURCE_DATA* init_data_ptr = 0;
13279+        _SG_STRUCT(D3D11_SUBRESOURCE_DATA, init_data);
13280+        if (desc->data.ptr) {
13281+            init_data.pSysMem = desc->data.ptr;
13282+            init_data_ptr = &init_data;
13283+        }
13284+        HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_buf_desc, init_data_ptr, &buf->d3d11.buf);
13285+        if (!(SUCCEEDED(hr) && buf->d3d11.buf)) {
13286+            _SG_ERROR(D3D11_CREATE_BUFFER_FAILED);
13287+            return SG_RESOURCESTATE_FAILED;
13288+        }
13289+        _sg_d3d11_setlabel(buf->d3d11.buf, desc->label);
13290+    }
13291+    return SG_RESOURCESTATE_VALID;
13292+}
13293+
13294+_SOKOL_PRIVATE void _sg_d3d11_discard_buffer(_sg_buffer_t* buf) {
13295+    SOKOL_ASSERT(buf);
13296+    if (buf->d3d11.buf) {
13297+        _sg_d3d11_Release(buf->d3d11.buf);
13298+    }
13299+}
13300+
13301+_SOKOL_PRIVATE void _sg_d3d11_fill_subres_data(const _sg_image_t* img, const sg_image_data* data) {
13302+    const int num_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? 1 : img->cmn.num_slices;
13303+    int subres_index = 0;
13304+    for (int slice_index = 0; slice_index < num_slices; slice_index++) {
13305+        for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) {
13306+            SOKOL_ASSERT(subres_index < _SG_D3D11_MAX_TEXTURE_SUBRESOURCES);
13307+            D3D11_SUBRESOURCE_DATA* subres_data = &_sg.d3d11.subres_data[subres_index];
13308+            const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
13309+            const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
13310+            const sg_range* miplevel_data = &(data->mip_levels[mip_index]);
13311+            const size_t slice_size = miplevel_data->size / (size_t)num_slices;
13312+            const size_t slice_offset = slice_size * (size_t)slice_index;
13313+            const uint8_t* ptr = (const uint8_t*) miplevel_data->ptr;
13314+            subres_data->pSysMem = ptr + slice_offset;
13315+            subres_data->SysMemPitch = (UINT)_sg_row_pitch(img->cmn.pixel_format, mip_width, 1);
13316+            if (img->cmn.type == SG_IMAGETYPE_3D) {
13317+                subres_data->SysMemSlicePitch = (UINT)_sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1);
13318+            } else {
13319+                subres_data->SysMemSlicePitch = 0;
13320+            }
13321+        }
13322+    }
13323+}
13324+
13325+_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const sg_image_desc* desc) {
13326+    SOKOL_ASSERT(img && desc);
13327+    SOKOL_ASSERT((0 == img->d3d11.tex2d) && (0 == img->d3d11.tex3d) && (0 == img->d3d11.res));
13328+    HRESULT hr;
13329+
13330+    const bool injected = (0 != desc->d3d11_texture);
13331+    const bool msaa = (img->cmn.sample_count > 1);
13332+    SOKOL_ASSERT(!(msaa && (img->cmn.type == SG_IMAGETYPE_CUBE)));
13333+    img->d3d11.format = _sg_d3d11_texture_pixel_format(img->cmn.pixel_format);
13334+    if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) {
13335+        _SG_ERROR(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT);
13336+        return SG_RESOURCESTATE_FAILED;
13337+    }
13338+
13339+    // prepare initial content pointers
13340+    D3D11_SUBRESOURCE_DATA* init_data = 0;
13341+    if (!injected && desc->data.mip_levels[0].ptr) {
13342+        _sg_d3d11_fill_subres_data(img, &desc->data);
13343+        init_data = _sg.d3d11.subres_data;
13344+    }
13345+    if (img->cmn.type != SG_IMAGETYPE_3D) {
13346+        // 2D-, cube- or array-texture
13347+        // first check for injected texture and/or resource view
13348+        if (injected) {
13349+            img->d3d11.tex2d = (ID3D11Texture2D*) desc->d3d11_texture;
13350+            _sg_d3d11_AddRef(img->d3d11.tex2d);
13351+        } else {
13352+            // if not injected, create 2D texture
13353+            _SG_STRUCT(D3D11_TEXTURE2D_DESC, d3d11_tex_desc);
13354+            d3d11_tex_desc.Width = (UINT)img->cmn.width;
13355+            d3d11_tex_desc.Height = (UINT)img->cmn.height;
13356+            d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps;
13357+            d3d11_tex_desc.ArraySize = (UINT)img->cmn.num_slices;
13358+            d3d11_tex_desc.Format = img->d3d11.format;
13359+            d3d11_tex_desc.BindFlags = _sg_d3d11_image_bind_flags(&img->cmn.usage);
13360+            d3d11_tex_desc.Usage = _sg_d3d11_image_usage(&img->cmn.usage);
13361+            d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_image_cpu_access_flags(&img->cmn.usage);
13362+            d3d11_tex_desc.SampleDesc.Count = (UINT)img->cmn.sample_count;
13363+            d3d11_tex_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0);
13364+            d3d11_tex_desc.MiscFlags = (img->cmn.type == SG_IMAGETYPE_CUBE) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0;
13365+            hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d);
13366+            if (!(SUCCEEDED(hr) && img->d3d11.tex2d)) {
13367+                _SG_ERROR(D3D11_CREATE_2D_TEXTURE_FAILED);
13368+                return SG_RESOURCESTATE_FAILED;
13369+            }
13370+            _sg_d3d11_setlabel(img->d3d11.tex2d, desc->label);
13371+        }
13372+        SOKOL_ASSERT(img->d3d11.tex2d);
13373+        img->d3d11.res = (ID3D11Resource*)img->d3d11.tex2d;
13374+        _sg_d3d11_AddRef(img->d3d11.res);
13375+    } else {
13376+        // 3D texture - same procedure, first check if injected, than create non-injected
13377+        if (injected) {
13378+            img->d3d11.tex3d = (ID3D11Texture3D*) desc->d3d11_texture;
13379+            _sg_d3d11_AddRef(img->d3d11.tex3d);
13380+        } else {
13381+            // not injected, create 3d texture
13382+            _SG_STRUCT(D3D11_TEXTURE3D_DESC, d3d11_tex_desc);
13383+            d3d11_tex_desc.Width = (UINT)img->cmn.width;
13384+            d3d11_tex_desc.Height = (UINT)img->cmn.height;
13385+            d3d11_tex_desc.Depth = (UINT)img->cmn.num_slices;
13386+            d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps;
13387+            d3d11_tex_desc.Format = img->d3d11.format;
13388+            d3d11_tex_desc.BindFlags = _sg_d3d11_image_bind_flags(&img->cmn.usage);
13389+            d3d11_tex_desc.Usage = _sg_d3d11_image_usage(&img->cmn.usage);
13390+            d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_image_cpu_access_flags(&img->cmn.usage);
13391+            if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) {
13392+                _SG_ERROR(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT);
13393+                return SG_RESOURCESTATE_FAILED;
13394+            }
13395+            hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d);
13396+            if (!(SUCCEEDED(hr) && img->d3d11.tex3d)) {
13397+                _SG_ERROR(D3D11_CREATE_3D_TEXTURE_FAILED);
13398+                return SG_RESOURCESTATE_FAILED;
13399+            }
13400+            _sg_d3d11_setlabel(img->d3d11.tex3d, desc->label);
13401+        }
13402+        SOKOL_ASSERT(img->d3d11.tex3d);
13403+        img->d3d11.res = (ID3D11Resource*)img->d3d11.tex3d;
13404+        _sg_d3d11_AddRef(img->d3d11.res);
13405+    }
13406+    return SG_RESOURCESTATE_VALID;
13407+}
13408+
13409+_SOKOL_PRIVATE void _sg_d3d11_discard_image(_sg_image_t* img) {
13410+    SOKOL_ASSERT(img);
13411+    if (img->d3d11.tex2d) {
13412+        _sg_d3d11_Release(img->d3d11.tex2d);
13413+    }
13414+    if (img->d3d11.tex3d) {
13415+        _sg_d3d11_Release(img->d3d11.tex3d);
13416+    }
13417+    if (img->d3d11.res) {
13418+        _sg_d3d11_Release(img->d3d11.res);
13419+    }
13420+}
13421+
13422+_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
13423+    SOKOL_ASSERT(smp && desc);
13424+    SOKOL_ASSERT(0 == smp->d3d11.smp);
13425+    const bool injected = (0 != desc->d3d11_sampler);
13426+    if (injected) {
13427+        smp->d3d11.smp = (ID3D11SamplerState*)desc->d3d11_sampler;
13428+        _sg_d3d11_AddRef(smp->d3d11.smp);
13429+    } else {
13430+        _SG_STRUCT(D3D11_SAMPLER_DESC, d3d11_smp_desc);
13431+        d3d11_smp_desc.Filter = _sg_d3d11_filter(desc->min_filter, desc->mag_filter, desc->mipmap_filter, desc->compare != SG_COMPAREFUNC_NEVER, desc->max_anisotropy);
13432+        d3d11_smp_desc.AddressU = _sg_d3d11_address_mode(desc->wrap_u);
13433+        d3d11_smp_desc.AddressV = _sg_d3d11_address_mode(desc->wrap_v);
13434+        d3d11_smp_desc.AddressW = _sg_d3d11_address_mode(desc->wrap_w);
13435+        d3d11_smp_desc.MipLODBias = 0.0f; // FIXME?
13436+        switch (desc->border_color) {
13437+            case SG_BORDERCOLOR_TRANSPARENT_BLACK:
13438+                // all 0.0f
13439+                break;
13440+            case SG_BORDERCOLOR_OPAQUE_WHITE:
13441+                for (int i = 0; i < 4; i++) {
13442+                    d3d11_smp_desc.BorderColor[i] = 1.0f;
13443+                }
13444+                break;
13445+            default:
13446+                // opaque black
13447+                d3d11_smp_desc.BorderColor[3] = 1.0f;
13448+                break;
13449+        }
13450+        d3d11_smp_desc.MaxAnisotropy = desc->max_anisotropy;
13451+        d3d11_smp_desc.ComparisonFunc = _sg_d3d11_compare_func(desc->compare);
13452+        d3d11_smp_desc.MinLOD = desc->min_lod;
13453+        d3d11_smp_desc.MaxLOD = desc->max_lod;
13454+        HRESULT hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &smp->d3d11.smp);
13455+        if (!(SUCCEEDED(hr) && smp->d3d11.smp)) {
13456+            _SG_ERROR(D3D11_CREATE_SAMPLER_STATE_FAILED);
13457+            return SG_RESOURCESTATE_FAILED;
13458+        }
13459+        _sg_d3d11_setlabel(smp->d3d11.smp, desc->label);
13460+    }
13461+    return SG_RESOURCESTATE_VALID;
13462+}
13463+
13464+_SOKOL_PRIVATE void _sg_d3d11_discard_sampler(_sg_sampler_t* smp) {
13465+    SOKOL_ASSERT(smp);
13466+    if (smp->d3d11.smp) {
13467+        _sg_d3d11_Release(smp->d3d11.smp);
13468+    }
13469+}
13470+
13471+_SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) {
13472+    if ((0 == _sg.d3d11.d3dcompiler_dll) && !_sg.d3d11.d3dcompiler_dll_load_failed) {
13473+        _sg.d3d11.d3dcompiler_dll = LoadLibraryA("d3dcompiler_47.dll");
13474+        if (0 == _sg.d3d11.d3dcompiler_dll) {
13475+            // don't attempt to load missing DLL in the future
13476+            _SG_ERROR(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED);
13477+            _sg.d3d11.d3dcompiler_dll_load_failed = true;
13478+            return false;
13479+        }
13480+        // look up function pointers
13481+        _sg.d3d11.D3DCompile_func = (pD3DCompile)(void*) GetProcAddress(_sg.d3d11.d3dcompiler_dll, "D3DCompile");
13482+        SOKOL_ASSERT(_sg.d3d11.D3DCompile_func);
13483+    }
13484+    return 0 != _sg.d3d11.d3dcompiler_dll;
13485+}
13486+
13487+_SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_function* shd_func) {
13488+    if (!_sg_d3d11_load_d3dcompiler_dll()) {
13489+        return NULL;
13490+    }
13491+    SOKOL_ASSERT(shd_func->d3d11_target);
13492+    UINT flags1 = D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR;
13493+    if (_sg.desc.d3d11.shader_debugging) {
13494+        flags1 |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
13495+    } else {
13496+        flags1 |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
13497+    }
13498+    ID3DBlob* output = NULL;
13499+    ID3DBlob* errors_or_warnings = NULL;
13500+    HRESULT hr = _sg.d3d11.D3DCompile_func(
13501+        shd_func->source,                  // pSrcData
13502+        strlen(shd_func->source),          // SrcDataSize
13503+        shd_func->d3d11_filepath,          // pSourceName
13504+        NULL,                              // pDefines
13505+        D3D_COMPILE_STANDARD_FILE_INCLUDE, // pInclude
13506+        shd_func->entry ? shd_func->entry : "main", // pEntryPoint
13507+        shd_func->d3d11_target,            // pTarget
13508+        flags1,     // Flags1
13509+        0,          // Flags2
13510+        &output,    // ppCode
13511+        &errors_or_warnings);   // ppErrorMsgs
13512+    if (FAILED(hr)) {
13513+        _SG_ERROR(D3D11_SHADER_COMPILATION_FAILED);
13514+    }
13515+    if (errors_or_warnings) {
13516+        _SG_WARN(D3D11_SHADER_COMPILATION_OUTPUT);
13517+        _SG_LOGMSG(D3D11_SHADER_COMPILATION_OUTPUT, (LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings));
13518+        _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL;
13519+    }
13520+    if (FAILED(hr)) {
13521+        // just in case, usually output is NULL here
13522+        if (output) {
13523+            _sg_d3d11_Release(output);
13524+            output = NULL;
13525+        }
13526+    }
13527+    return output;
13528+}
13529+
13530+// NOTE: this is an out-of-range check for HLSL bindslots that's also active in release mode
13531+_SOKOL_PRIVATE bool _sg_d3d11_ensure_hlsl_bindslot_ranges(const sg_shader_desc* desc) {
13532+    SOKOL_ASSERT(desc);
13533+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
13534+        const sg_shader_uniform_block* ub = &desc->uniform_blocks[i];
13535+        if (ub->stage != SG_SHADERSTAGE_NONE) {
13536+            if (ub->hlsl_register_b_n >= _SG_D3D11_MAX_STAGE_UB_BINDINGS) {
13537+                _SG_ERROR(D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE);
13538+                return false;
13539+            }
13540+        }
13541+    }
13542+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
13543+        const sg_shader_view* view = &desc->views[i];
13544+        if (view->texture.stage != SG_SHADERSTAGE_NONE) {
13545+            if (view->texture.hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) {
13546+                _SG_ERROR(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE);
13547+                return false;
13548+            }
13549+        }
13550+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
13551+            if (view->storage_buffer.hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) {
13552+                _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE);
13553+                return false;
13554+            }
13555+            if (view->storage_buffer.hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) {
13556+                _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE);
13557+                return false;
13558+            }
13559+        }
13560+        if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
13561+            if (view->storage_image.hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) {
13562+                _SG_ERROR(D3D11_STORAGEIMAGE_HLSL_REGISTER_U_OUT_OF_RANGE);
13563+                return false;
13564+            }
13565+        }
13566+    }
13567+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
13568+        const sg_shader_sampler* smp = &desc->samplers[i];
13569+        if (smp->stage != SG_SHADERSTAGE_NONE) {
13570+            if (smp->hlsl_register_s_n >= _SG_D3D11_MAX_STAGE_SMP_BINDINGS) {
13571+                _SG_ERROR(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE);
13572+                return false;
13573+            }
13574+        }
13575+    }
13576+    return true;
13577+}
13578+
13579+_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
13580+    SOKOL_ASSERT(shd && desc);
13581+    SOKOL_ASSERT(!shd->d3d11.vs && !shd->d3d11.fs && !shd->d3d11.cs && !shd->d3d11.vs_blob);
13582+    HRESULT hr;
13583+
13584+    // perform a range-check on HLSL bindslots that's also active in release
13585+    // mode to avoid potential out-of-bounds array accesses
13586+    if (!_sg_d3d11_ensure_hlsl_bindslot_ranges(desc)) {
13587+        return SG_RESOURCESTATE_FAILED;
13588+    }
13589+
13590+    // copy vertex attribute semantic names and indices
13591+    for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) {
13592+        _sg_strcpy(&shd->d3d11.attrs[i].sem_name, desc->attrs[i].hlsl_sem_name);
13593+        shd->d3d11.attrs[i].sem_index = desc->attrs[i].hlsl_sem_index;
13594+    }
13595+
13596+    // copy HLSL bind slots
13597+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
13598+        SOKOL_ASSERT(0 == shd->d3d11.ub_register_b_n[i]);
13599+        shd->d3d11.ub_register_b_n[i] = desc->uniform_blocks[i].hlsl_register_b_n;
13600+    }
13601+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
13602+        const sg_shader_view* view = &desc->views[i];
13603+        SOKOL_ASSERT((0 == shd->d3d11.view_register_t_n[i]) && (0 == shd->d3d11.view_register_u_n[i]));
13604+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
13605+            shd->d3d11.view_register_t_n[i] = view->storage_buffer.hlsl_register_t_n;
13606+            shd->d3d11.view_register_u_n[i] = view->storage_buffer.hlsl_register_u_n;
13607+        } else if (view->texture.stage != SG_SHADERSTAGE_NONE) {
13608+            shd->d3d11.view_register_t_n[i] = view->texture.hlsl_register_t_n;
13609+        } else if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
13610+            shd->d3d11.view_register_u_n[i] = view->storage_image.hlsl_register_u_n;
13611+        }
13612+    }
13613+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
13614+        SOKOL_ASSERT(0 == shd->d3d11.smp_register_s_n[i]);
13615+        shd->d3d11.smp_register_s_n[i] = desc->samplers[i].hlsl_register_s_n;
13616+    }
13617+
13618+    // create a D3D constant buffer for each uniform block
13619+    for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) {
13620+        const sg_shader_stage stage = desc->uniform_blocks[ub_index].stage;
13621+        if (stage == SG_SHADERSTAGE_NONE) {
13622+            continue;
13623+        }
13624+        const _sg_shader_uniform_block_t* ub = &shd->cmn.uniform_blocks[ub_index];
13625+        ID3D11Buffer* cbuf = 0;
13626+        _SG_STRUCT(D3D11_BUFFER_DESC, cb_desc);
13627+        cb_desc.ByteWidth = (UINT)_sg_roundup((int)ub->size, 16);
13628+        cb_desc.Usage = D3D11_USAGE_DEFAULT;
13629+        cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
13630+        hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &cbuf);
13631+        if (!(SUCCEEDED(hr) && cbuf)) {
13632+            _SG_ERROR(D3D11_CREATE_CONSTANT_BUFFER_FAILED);
13633+            return SG_RESOURCESTATE_FAILED;
13634+        }
13635+        _sg_d3d11_setlabel(cbuf, desc->label);
13636+        shd->d3d11.all_cbufs[ub_index] = cbuf;
13637+
13638+        const uint8_t d3d11_slot = shd->d3d11.ub_register_b_n[ub_index];
13639+        SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_UB_BINDINGS);
13640+        if (stage == SG_SHADERSTAGE_VERTEX) {
13641+            SOKOL_ASSERT(0 == shd->d3d11.vs_cbufs[d3d11_slot]);
13642+            shd->d3d11.vs_cbufs[d3d11_slot] = cbuf;
13643+        } else if (stage == SG_SHADERSTAGE_FRAGMENT) {
13644+            SOKOL_ASSERT(0 == shd->d3d11.fs_cbufs[d3d11_slot]);
13645+            shd->d3d11.fs_cbufs[d3d11_slot] = cbuf;
13646+        } else if (stage == SG_SHADERSTAGE_COMPUTE) {
13647+            SOKOL_ASSERT(0 == shd->d3d11.cs_cbufs[d3d11_slot]);
13648+            shd->d3d11.cs_cbufs[d3d11_slot] = cbuf;
13649+        } else {
13650+            SOKOL_UNREACHABLE;
13651+        }
13652+    }
13653+
13654+    // create shader functions
13655+    const bool has_vs = desc->vertex_func.bytecode.ptr || desc->vertex_func.source;
13656+    const bool has_fs = desc->fragment_func.bytecode.ptr || desc->fragment_func.source;
13657+    const bool has_cs = desc->compute_func.bytecode.ptr || desc->compute_func.source;
13658+    bool vs_valid = false; bool fs_valid = false; bool cs_valid = false;
13659+    if (has_vs) {
13660+        const void* vs_ptr = 0; SIZE_T vs_length = 0;
13661+        ID3DBlob* vs_blob = 0;
13662+        if (desc->vertex_func.bytecode.ptr) {
13663+            SOKOL_ASSERT(desc->vertex_func.bytecode.size > 0);
13664+            vs_ptr = desc->vertex_func.bytecode.ptr;
13665+            vs_length = desc->vertex_func.bytecode.size;
13666+        } else {
13667+            SOKOL_ASSERT(desc->vertex_func.source);
13668+            vs_blob = _sg_d3d11_compile_shader(&desc->vertex_func);
13669+            if (vs_blob) {
13670+                vs_ptr = _sg_d3d11_GetBufferPointer(vs_blob);
13671+                vs_length = _sg_d3d11_GetBufferSize(vs_blob);
13672+            }
13673+        }
13674+        if (vs_ptr && (vs_length > 0)) {
13675+            hr = _sg_d3d11_CreateVertexShader(_sg.d3d11.dev, vs_ptr, vs_length, NULL, &shd->d3d11.vs);
13676+            vs_valid = SUCCEEDED(hr) && shd->d3d11.vs;
13677+        }
13678+        // set label, and need to store a copy of the vertex shader blob for the pipeline creation
13679+        if (vs_valid) {
13680+            _sg_d3d11_setlabel(shd->d3d11.vs, desc->label);
13681+            shd->d3d11.vs_blob_length = vs_length;
13682+            shd->d3d11.vs_blob = _sg_malloc((size_t)vs_length);
13683+            SOKOL_ASSERT(shd->d3d11.vs_blob);
13684+            memcpy(shd->d3d11.vs_blob, vs_ptr, vs_length);
13685+        }
13686+        if (vs_blob) {
13687+            _sg_d3d11_Release(vs_blob);
13688+        }
13689+    }
13690+    if (has_fs) {
13691+        const void* fs_ptr = 0; SIZE_T fs_length = 0;
13692+        ID3DBlob* fs_blob = 0;
13693+        if (desc->fragment_func.bytecode.ptr) {
13694+            SOKOL_ASSERT(desc->fragment_func.bytecode.size > 0);
13695+            fs_ptr = desc->fragment_func.bytecode.ptr;
13696+            fs_length = desc->fragment_func.bytecode.size;
13697+        } else {
13698+            SOKOL_ASSERT(desc->fragment_func.source);
13699+            fs_blob = _sg_d3d11_compile_shader(&desc->fragment_func);
13700+            if (fs_blob) {
13701+                fs_ptr = _sg_d3d11_GetBufferPointer(fs_blob);
13702+                fs_length = _sg_d3d11_GetBufferSize(fs_blob);
13703+            }
13704+        }
13705+        if (fs_ptr && (fs_length > 0)) {
13706+            hr = _sg_d3d11_CreatePixelShader(_sg.d3d11.dev, fs_ptr, fs_length, NULL, &shd->d3d11.fs);
13707+            fs_valid = SUCCEEDED(hr) && shd->d3d11.fs;
13708+        }
13709+        if (fs_valid) {
13710+            _sg_d3d11_setlabel(shd->d3d11.fs, desc->label);
13711+        }
13712+        if (fs_blob) {
13713+            _sg_d3d11_Release(fs_blob);
13714+        }
13715+    }
13716+    if (has_cs) {
13717+        const void* cs_ptr = 0; SIZE_T cs_length = 0;
13718+        ID3DBlob* cs_blob = 0;
13719+        if (desc->compute_func.bytecode.ptr) {
13720+            SOKOL_ASSERT(desc->compute_func.bytecode.size > 0);
13721+            cs_ptr = desc->compute_func.bytecode.ptr;
13722+            cs_length = desc->compute_func.bytecode.size;
13723+        } else {
13724+            SOKOL_ASSERT(desc->compute_func.source);
13725+            cs_blob = _sg_d3d11_compile_shader(&desc->compute_func);
13726+            if (cs_blob) {
13727+                cs_ptr = _sg_d3d11_GetBufferPointer(cs_blob);
13728+                cs_length = _sg_d3d11_GetBufferSize(cs_blob);
13729+            }
13730+        }
13731+        if (cs_ptr && (cs_length > 0)) {
13732+            hr = _sg_d3d11_CreateComputeShader(_sg.d3d11.dev, cs_ptr, cs_length, NULL, &shd->d3d11.cs);
13733+            cs_valid = SUCCEEDED(hr) && shd->d3d11.cs;
13734+        }
13735+        if (cs_blob) {
13736+            _sg_d3d11_Release(cs_blob);
13737+        }
13738+    }
13739+    if ((vs_valid && fs_valid) || cs_valid) {
13740+        return SG_RESOURCESTATE_VALID;
13741+    } else {
13742+        return SG_RESOURCESTATE_FAILED;
13743+    }
13744+}
13745+
13746+_SOKOL_PRIVATE void _sg_d3d11_discard_shader(_sg_shader_t* shd) {
13747+    SOKOL_ASSERT(shd);
13748+    if (shd->d3d11.vs) {
13749+        _sg_d3d11_Release(shd->d3d11.vs);
13750+    }
13751+    if (shd->d3d11.fs) {
13752+        _sg_d3d11_Release(shd->d3d11.fs);
13753+    }
13754+    if (shd->d3d11.cs) {
13755+        _sg_d3d11_Release(shd->d3d11.cs);
13756+    }
13757+    if (shd->d3d11.vs_blob) {
13758+        _sg_free(shd->d3d11.vs_blob);
13759+    }
13760+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
13761+        if (shd->d3d11.all_cbufs[i]) {
13762+            _sg_d3d11_Release(shd->d3d11.all_cbufs[i]);
13763+        }
13764+    }
13765+}
13766+
13767+_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
13768+    SOKOL_ASSERT(pip && desc);
13769+    _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
13770+
13771+    // if this is a compute pipeline, we're done here
13772+    if (pip->cmn.is_compute) {
13773+        return SG_RESOURCESTATE_VALID;
13774+    }
13775+
13776+    // a render pipeline...
13777+    SOKOL_ASSERT(shd->d3d11.vs_blob && shd->d3d11.vs_blob_length > 0);
13778+    SOKOL_ASSERT(!pip->d3d11.il && !pip->d3d11.rs && !pip->d3d11.dss && !pip->d3d11.bs);
13779+
13780+    pip->d3d11.index_format = _sg_d3d11_index_format(pip->cmn.index_type);
13781+    pip->d3d11.topology = _sg_d3d11_primitive_topology(desc->primitive_type);
13782+    pip->d3d11.stencil_ref = desc->stencil.ref;
13783+
13784+    // create input layout object
13785+    HRESULT hr;
13786+    _SG_STRUCT(D3D11_INPUT_ELEMENT_DESC, d3d11_comps[SG_MAX_VERTEX_ATTRIBUTES]);
13787+    size_t attr_index = 0;
13788+    for (; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
13789+        const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index];
13790+        if (a_state->format == SG_VERTEXFORMAT_INVALID) {
13791+            break;
13792+        }
13793+        SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
13794+        SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[a_state->buffer_index]);
13795+        const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[a_state->buffer_index];
13796+        const sg_vertex_step step_func = l_state->step_func;
13797+        const int step_rate = l_state->step_rate;
13798+        D3D11_INPUT_ELEMENT_DESC* d3d11_comp = &d3d11_comps[attr_index];
13799+        d3d11_comp->SemanticName = _sg_strptr(&shd->d3d11.attrs[attr_index].sem_name);
13800+        d3d11_comp->SemanticIndex = (UINT)shd->d3d11.attrs[attr_index].sem_index;
13801+        d3d11_comp->Format = _sg_d3d11_vertex_format(a_state->format);
13802+        d3d11_comp->InputSlot = (UINT)a_state->buffer_index;
13803+        d3d11_comp->AlignedByteOffset = (UINT)a_state->offset;
13804+        d3d11_comp->InputSlotClass = _sg_d3d11_input_classification(step_func);
13805+        if (SG_VERTEXSTEP_PER_INSTANCE == step_func) {
13806+            d3d11_comp->InstanceDataStepRate = (UINT)step_rate;
13807+        }
13808+    }
13809+    for (size_t layout_index = 0; layout_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; layout_index++) {
13810+        if (pip->cmn.vertex_buffer_layout_active[layout_index]) {
13811+            const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[layout_index];
13812+            SOKOL_ASSERT(l_state->stride > 0);
13813+            pip->d3d11.vb_strides[layout_index] = (UINT)l_state->stride;
13814+        } else {
13815+            pip->d3d11.vb_strides[layout_index] = 0;
13816+        }
13817+    }
13818+    if (attr_index > 0) {
13819+        hr = _sg_d3d11_CreateInputLayout(_sg.d3d11.dev,
13820+            d3d11_comps,                // pInputElementDesc
13821+            (UINT)attr_index,           // NumElements
13822+            shd->d3d11.vs_blob,         // pShaderByteCodeWithInputSignature
13823+            shd->d3d11.vs_blob_length,  // BytecodeLength
13824+            &pip->d3d11.il);
13825+        if (!(SUCCEEDED(hr) && pip->d3d11.il)) {
13826+            _SG_ERROR(D3D11_CREATE_INPUT_LAYOUT_FAILED);
13827+            return SG_RESOURCESTATE_FAILED;
13828+        }
13829+        _sg_d3d11_setlabel(pip->d3d11.il, desc->label);
13830+    }
13831+
13832+    // create rasterizer state
13833+    _SG_STRUCT(D3D11_RASTERIZER_DESC, rs_desc);
13834+    rs_desc.FillMode = D3D11_FILL_SOLID;
13835+    rs_desc.CullMode = _sg_d3d11_cull_mode(desc->cull_mode);
13836+    rs_desc.FrontCounterClockwise = desc->face_winding == SG_FACEWINDING_CCW;
13837+    rs_desc.DepthBias = (INT) pip->cmn.depth.bias;
13838+    rs_desc.DepthBiasClamp = pip->cmn.depth.bias_clamp;
13839+    rs_desc.SlopeScaledDepthBias = pip->cmn.depth.bias_slope_scale;
13840+    rs_desc.DepthClipEnable = TRUE;
13841+    rs_desc.ScissorEnable = TRUE;
13842+    rs_desc.MultisampleEnable = desc->sample_count > 1;
13843+    rs_desc.AntialiasedLineEnable = FALSE;
13844+    hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs);
13845+    if (!(SUCCEEDED(hr) && pip->d3d11.rs)) {
13846+        _SG_ERROR(D3D11_CREATE_RASTERIZER_STATE_FAILED);
13847+        return SG_RESOURCESTATE_FAILED;
13848+    }
13849+    _sg_d3d11_setlabel(pip->d3d11.rs, desc->label);
13850+
13851+    // create depth-stencil state
13852+    _SG_STRUCT(D3D11_DEPTH_STENCIL_DESC, dss_desc);
13853+    dss_desc.DepthEnable = TRUE;
13854+    dss_desc.DepthWriteMask = desc->depth.write_enabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
13855+    dss_desc.DepthFunc = _sg_d3d11_compare_func(desc->depth.compare);
13856+    dss_desc.StencilEnable = desc->stencil.enabled;
13857+    dss_desc.StencilReadMask = desc->stencil.read_mask;
13858+    dss_desc.StencilWriteMask = desc->stencil.write_mask;
13859+    const sg_stencil_face_state* sf = &desc->stencil.front;
13860+    dss_desc.FrontFace.StencilFailOp = _sg_d3d11_stencil_op(sf->fail_op);
13861+    dss_desc.FrontFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sf->depth_fail_op);
13862+    dss_desc.FrontFace.StencilPassOp = _sg_d3d11_stencil_op(sf->pass_op);
13863+    dss_desc.FrontFace.StencilFunc = _sg_d3d11_compare_func(sf->compare);
13864+    const sg_stencil_face_state* sb = &desc->stencil.back;
13865+    dss_desc.BackFace.StencilFailOp = _sg_d3d11_stencil_op(sb->fail_op);
13866+    dss_desc.BackFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sb->depth_fail_op);
13867+    dss_desc.BackFace.StencilPassOp = _sg_d3d11_stencil_op(sb->pass_op);
13868+    dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare);
13869+    hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss);
13870+    if (!(SUCCEEDED(hr) && pip->d3d11.dss)) {
13871+        _SG_ERROR(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED);
13872+        return SG_RESOURCESTATE_FAILED;
13873+    }
13874+    _sg_d3d11_setlabel(pip->d3d11.dss, desc->label);
13875+
13876+    // create blend state
13877+    _SG_STRUCT(D3D11_BLEND_DESC, bs_desc);
13878+    bs_desc.AlphaToCoverageEnable = desc->alpha_to_coverage_enabled;
13879+    bs_desc.IndependentBlendEnable = TRUE;
13880+    {
13881+        size_t i = 0;
13882+        for (i = 0; i < (size_t)desc->color_count; i++) {
13883+            const sg_blend_state* src = &desc->colors[i].blend;
13884+            D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i];
13885+            dst->BlendEnable = src->enabled;
13886+            dst->SrcBlend = _sg_d3d11_blend_factor(src->src_factor_rgb);
13887+            dst->DestBlend = _sg_d3d11_blend_factor(src->dst_factor_rgb);
13888+            dst->BlendOp = _sg_d3d11_blend_op(src->op_rgb);
13889+            dst->SrcBlendAlpha = _sg_d3d11_blend_factor(src->src_factor_alpha);
13890+            dst->DestBlendAlpha = _sg_d3d11_blend_factor(src->dst_factor_alpha);
13891+            dst->BlendOpAlpha = _sg_d3d11_blend_op(src->op_alpha);
13892+            dst->RenderTargetWriteMask = _sg_d3d11_color_write_mask(desc->colors[i].write_mask);
13893+        }
13894+        for (; i < 8; i++) {
13895+            D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i];
13896+            dst->BlendEnable = FALSE;
13897+            dst->SrcBlend = dst->SrcBlendAlpha = D3D11_BLEND_ONE;
13898+            dst->DestBlend = dst->DestBlendAlpha = D3D11_BLEND_ZERO;
13899+            dst->BlendOp = dst->BlendOpAlpha = D3D11_BLEND_OP_ADD;
13900+            dst->RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
13901+        }
13902+    }
13903+    hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs);
13904+    if (!(SUCCEEDED(hr) && pip->d3d11.bs)) {
13905+        _SG_ERROR(D3D11_CREATE_BLEND_STATE_FAILED);
13906+        return SG_RESOURCESTATE_FAILED;
13907+    }
13908+    _sg_d3d11_setlabel(pip->d3d11.bs, desc->label);
13909+    return SG_RESOURCESTATE_VALID;
13910+}
13911+
13912+_SOKOL_PRIVATE void _sg_d3d11_discard_pipeline(_sg_pipeline_t* pip) {
13913+    SOKOL_ASSERT(pip);
13914+    if (pip->d3d11.il) {
13915+        _sg_d3d11_Release(pip->d3d11.il);
13916+    }
13917+    if (pip->d3d11.rs) {
13918+        _sg_d3d11_Release(pip->d3d11.rs);
13919+    }
13920+    if (pip->d3d11.dss) {
13921+        _sg_d3d11_Release(pip->d3d11.dss);
13922+    }
13923+    if (pip->d3d11.bs) {
13924+        _sg_d3d11_Release(pip->d3d11.bs);
13925+    }
13926+}
13927+
13928+_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_view(_sg_view_t* view, const sg_view_desc* desc) {
13929+    SOKOL_ASSERT(view && desc);
13930+    _SOKOL_UNUSED(desc);
13931+    HRESULT hr;
13932+    if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
13933+        const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
13934+        SOKOL_ASSERT(buf->d3d11.buf);
13935+        const UINT size = (UINT) buf->cmn.size;
13936+        SOKOL_ASSERT(_sg_multiple_u64(size, 4));
13937+        const UINT offset = (UINT) view->cmn.buf.offset;
13938+        SOKOL_ASSERT(_sg_multiple_u64(offset, 4));
13939+        SOKOL_ASSERT(offset < size);
13940+        const UINT first_element = offset / 4;
13941+        const UINT num_elements = (size - offset) / 4;
13942+        _SG_STRUCT(D3D11_SHADER_RESOURCE_VIEW_DESC, d3d11_srv_desc);
13943+        d3d11_srv_desc.Format = DXGI_FORMAT_R32_TYPELESS;
13944+        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
13945+        d3d11_srv_desc.BufferEx.FirstElement = first_element;
13946+        d3d11_srv_desc.BufferEx.NumElements = num_elements;
13947+        d3d11_srv_desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
13948+        SOKOL_ASSERT(!view->d3d11.srv);
13949+        hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_srv_desc, &view->d3d11.srv);
13950+        if (!(SUCCEEDED(hr) && view->d3d11.srv)) {
13951+            _SG_ERROR(D3D11_CREATE_BUFFER_SRV_FAILED);
13952+            return SG_RESOURCESTATE_FAILED;
13953+        }
13954+        _sg_d3d11_setlabel(view->d3d11.srv, desc->label);
13955+        if (buf->cmn.usage.immutable) {
13956+            _SG_STRUCT(D3D11_UNORDERED_ACCESS_VIEW_DESC, d3d11_uav_desc);
13957+            d3d11_uav_desc.Format = DXGI_FORMAT_R32_TYPELESS;
13958+            d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
13959+            d3d11_uav_desc.Buffer.FirstElement = first_element;
13960+            d3d11_uav_desc.Buffer.NumElements = num_elements;
13961+            d3d11_uav_desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
13962+            SOKOL_ASSERT(!view->d3d11.uav);
13963+            hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_uav_desc, &view->d3d11.uav);
13964+            if (!(SUCCEEDED(hr) && view->d3d11.uav)) {
13965+                _SG_ERROR(D3D11_CREATE_BUFFER_UAV_FAILED);
13966+                return SG_RESOURCESTATE_FAILED;
13967+            }
13968+            _sg_d3d11_setlabel(view->d3d11.uav, desc->label);
13969+        }
13970+    } else {
13971+        // it's an image view
13972+        const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
13973+        SOKOL_ASSERT(img->d3d11.res);
13974+        const bool msaa = img->cmn.sample_count > 1;
13975+        SOKOL_ASSERT(view->cmn.img.mip_level_count >= 1);
13976+        SOKOL_ASSERT(view->cmn.img.slice_count >= 1);
13977+        const UINT mip_level = (UINT)view->cmn.img.mip_level;
13978+        const UINT mip_count = (UINT)view->cmn.img.mip_level_count;
13979+        const UINT slice = (UINT)view->cmn.img.slice;
13980+        const UINT slice_count = (UINT)view->cmn.img.slice_count;
13981+
13982+        if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) {
13983+            SOKOL_ASSERT(!msaa);
13984+            _SG_STRUCT(D3D11_UNORDERED_ACCESS_VIEW_DESC, d3d11_uav_desc);
13985+            d3d11_uav_desc.Format = _sg_d3d11_rtv_uav_pixel_format(img->cmn.pixel_format);
13986+            switch (img->cmn.type) {
13987+                case SG_IMAGETYPE_2D:
13988+                    d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
13989+                    d3d11_uav_desc.Texture2D.MipSlice = mip_level;
13990+                    break;
13991+                case SG_IMAGETYPE_CUBE:
13992+                case SG_IMAGETYPE_ARRAY:
13993+                    d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
13994+                    d3d11_uav_desc.Texture2DArray.MipSlice = mip_level;
13995+                    d3d11_uav_desc.Texture2DArray.FirstArraySlice = slice;
13996+                    d3d11_uav_desc.Texture2DArray.ArraySize = 1;
13997+                    break;
13998+                case SG_IMAGETYPE_3D:
13999+                    d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D;
14000+                    d3d11_uav_desc.Texture3D.MipSlice = mip_level;
14001+                    d3d11_uav_desc.Texture3D.FirstWSlice = slice;
14002+                    d3d11_uav_desc.Texture3D.WSize = 1;
14003+                    break;
14004+                default: SOKOL_UNREACHABLE; break;
14005+            }
14006+            hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, img->d3d11.res, &d3d11_uav_desc, &view->d3d11.uav);
14007+            if (!(SUCCEEDED(hr) && view->d3d11.uav)) {
14008+                _SG_ERROR(D3D11_CREATE_UAV_FAILED);
14009+                return SG_RESOURCESTATE_FAILED;
14010+            }
14011+            _sg_d3d11_setlabel(view->d3d11.uav, desc->label);
14012+
14013+        } else if (view->cmn.type == SG_VIEWTYPE_TEXTURE) {
14014+
14015+            _SG_STRUCT(D3D11_SHADER_RESOURCE_VIEW_DESC, d3d11_srv_desc);
14016+            d3d11_srv_desc.Format = _sg_d3d11_srv_pixel_format(img->cmn.pixel_format);
14017+            switch (img->cmn.type) {
14018+                case SG_IMAGETYPE_2D:
14019+                    if (msaa) {
14020+                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
14021+                    } else {
14022+                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
14023+                        d3d11_srv_desc.Texture2D.MostDetailedMip = mip_level;
14024+                        d3d11_srv_desc.Texture2D.MipLevels = mip_count;
14025+                    }
14026+                    break;
14027+                case SG_IMAGETYPE_CUBE:
14028+                    SOKOL_ASSERT(!msaa);
14029+                    d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
14030+                    d3d11_srv_desc.TextureCube.MostDetailedMip = mip_level;
14031+                    d3d11_srv_desc.TextureCube.MipLevels = mip_count;
14032+                    break;
14033+                case SG_IMAGETYPE_ARRAY:
14034+                    if (msaa) {
14035+                        // NOTE: _sg_validate_image_desc() currently disallows MSAA array textures
14036+                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY;
14037+                        d3d11_srv_desc.Texture2DMSArray.FirstArraySlice = slice;
14038+                        d3d11_srv_desc.Texture2DMSArray.ArraySize = slice_count;
14039+                    } else {
14040+                        d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
14041+                        d3d11_srv_desc.Texture2DArray.MostDetailedMip = mip_level;
14042+                        d3d11_srv_desc.Texture2DArray.MipLevels = mip_count;
14043+                        d3d11_srv_desc.Texture2DArray.FirstArraySlice = slice;
14044+                        d3d11_srv_desc.Texture2DArray.ArraySize = slice_count;
14045+                    }
14046+                    break;
14047+                case SG_IMAGETYPE_3D:
14048+                    SOKOL_ASSERT(!msaa);
14049+                    d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
14050+                    d3d11_srv_desc.Texture3D.MostDetailedMip = mip_level;
14051+                    d3d11_srv_desc.Texture3D.MipLevels = mip_count;
14052+                    break;
14053+                default:
14054+                    SOKOL_UNREACHABLE; break;
14055+            }
14056+            hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, img->d3d11.res, &d3d11_srv_desc, &view->d3d11.srv);
14057+            if (!(SUCCEEDED(hr) && view->d3d11.srv)) {
14058+                _SG_ERROR(D3D11_CREATE_2D_SRV_FAILED);
14059+                return SG_RESOURCESTATE_FAILED;
14060+            }
14061+            _sg_d3d11_setlabel(view->d3d11.srv, desc->label);
14062+
14063+        } else if (view->cmn.type == SG_VIEWTYPE_COLORATTACHMENT) {
14064+
14065+            _SG_STRUCT(D3D11_RENDER_TARGET_VIEW_DESC, d3d11_rtv_desc);
14066+            d3d11_rtv_desc.Format = _sg_d3d11_rtv_uav_pixel_format(img->cmn.pixel_format);
14067+            switch (img->cmn.type) {
14068+                case SG_IMAGETYPE_2D:
14069+                    if (msaa) {
14070+                        d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
14071+                    } else {
14072+                        d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
14073+                        d3d11_rtv_desc.Texture2D.MipSlice = mip_level;
14074+                    }
14075+                    break;
14076+                case SG_IMAGETYPE_CUBE:
14077+                case SG_IMAGETYPE_ARRAY:
14078+                    if (msaa) {
14079+                        d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY;
14080+                        d3d11_rtv_desc.Texture2DMSArray.FirstArraySlice = slice;
14081+                        d3d11_rtv_desc.Texture2DMSArray.ArraySize = 1;
14082+                    } else {
14083+                        d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
14084+                        d3d11_rtv_desc.Texture2DArray.MipSlice = mip_level;
14085+                        d3d11_rtv_desc.Texture2DArray.FirstArraySlice = slice;
14086+                        d3d11_rtv_desc.Texture2DArray.ArraySize = 1;
14087+                    }
14088+                    break;
14089+                case SG_IMAGETYPE_3D:
14090+                    SOKOL_ASSERT(!msaa);
14091+                    d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
14092+                    d3d11_rtv_desc.Texture3D.MipSlice = mip_level;
14093+                    d3d11_rtv_desc.Texture3D.FirstWSlice = slice;
14094+                    d3d11_rtv_desc.Texture3D.WSize = 1;
14095+                    break;
14096+                default: SOKOL_UNREACHABLE; break;
14097+            }
14098+            hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, img->d3d11.res, &d3d11_rtv_desc, &view->d3d11.rtv);
14099+            if (!(SUCCEEDED(hr) && view->d3d11.rtv)) {
14100+                _SG_ERROR(D3D11_CREATE_RTV_FAILED);
14101+                return SG_RESOURCESTATE_FAILED;
14102+            }
14103+            _sg_d3d11_setlabel(view->d3d11.rtv, desc->label);
14104+
14105+        } else if (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT) {
14106+
14107+            SOKOL_ASSERT(img->cmn.type != SG_IMAGETYPE_3D);
14108+            _SG_STRUCT(D3D11_DEPTH_STENCIL_VIEW_DESC, d3d11_dsv_desc);
14109+            d3d11_dsv_desc.Format = _sg_d3d11_dsv_pixel_format(img->cmn.pixel_format);
14110+            switch (img->cmn.type) {
14111+                case SG_IMAGETYPE_2D:
14112+                    if (msaa) {
14113+                        d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
14114+                    } else {
14115+                        d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
14116+                        d3d11_dsv_desc.Texture2D.MipSlice = mip_level;
14117+                    }
14118+                    break;
14119+                case SG_IMAGETYPE_CUBE:
14120+                case SG_IMAGETYPE_ARRAY:
14121+                    if (msaa) {
14122+                        d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY;
14123+                        d3d11_dsv_desc.Texture2DMSArray.FirstArraySlice = slice;
14124+                        d3d11_dsv_desc.Texture2DMSArray.ArraySize = 1;
14125+                    } else {
14126+                        d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
14127+                        d3d11_dsv_desc.Texture2DArray.MipSlice = mip_level;
14128+                        d3d11_dsv_desc.Texture2DArray.FirstArraySlice = slice;
14129+                        d3d11_dsv_desc.Texture2DArray.ArraySize = 1;
14130+                    }
14131+                    break;
14132+                default: SOKOL_UNREACHABLE; break;
14133+            }
14134+            hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, img->d3d11.res, &d3d11_dsv_desc, &view->d3d11.dsv);
14135+            if (!(SUCCEEDED(hr) && view->d3d11.dsv)) {
14136+                _SG_ERROR(D3D11_CREATE_DSV_FAILED);
14137+                return SG_RESOURCESTATE_FAILED;
14138+            }
14139+            _sg_d3d11_setlabel(view->d3d11.dsv, desc->label);
14140+        }
14141+    }
14142+    return SG_RESOURCESTATE_VALID;
14143+}
14144+
14145+_SOKOL_PRIVATE void _sg_d3d11_discard_view(_sg_view_t* view) {
14146+    SOKOL_ASSERT(view);
14147+    if (view->d3d11.srv) {
14148+        _sg_d3d11_Release(view->d3d11.srv);
14149+    }
14150+    if (view->d3d11.uav) {
14151+        _sg_d3d11_Release(view->d3d11.uav);
14152+    }
14153+    if (view->d3d11.rtv) {
14154+        _sg_d3d11_Release(view->d3d11.rtv);
14155+    }
14156+    if (view->d3d11.dsv) {
14157+        _sg_d3d11_Release(view->d3d11.dsv);
14158+    }
14159+}
14160+
14161+_SOKOL_PRIVATE void _sg_d3d11_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
14162+    SOKOL_ASSERT(_sg.d3d11.ctx && pass && atts);
14163+    if (_sg.cur_pass.is_compute) {
14164+        // nothing to do in compute passes
14165+        return;
14166+    }
14167+    int num_rtvs = 0;
14168+    ID3D11RenderTargetView* rtvs[SG_MAX_COLOR_ATTACHMENTS] = { 0 };
14169+    ID3D11DepthStencilView* dsv = 0;
14170+    _sg.d3d11.cur_swapchain.render_view = 0;
14171+    _sg.d3d11.cur_swapchain.resolve_view = 0;
14172+    if (!atts->empty) {
14173+        SOKOL_ASSERT(atts->num_color_views <= SG_MAX_COLOR_ATTACHMENTS);
14174+        num_rtvs = atts->num_color_views;
14175+        for (int i = 0; i < num_rtvs; i++) {
14176+            SOKOL_ASSERT(atts->color_views[i]);
14177+            SOKOL_ASSERT(atts->color_views[i]->d3d11.rtv);
14178+            rtvs[i] = atts->color_views[i]->d3d11.rtv;
14179+        }
14180+        if (atts->ds_view) {
14181+            SOKOL_ASSERT(atts->ds_view->d3d11.dsv);
14182+            dsv = atts->ds_view->d3d11.dsv;
14183+        }
14184+    } else {
14185+        // NOTE: swapchain depth-stencil-view is optional
14186+        const sg_swapchain* swapchain = &pass->swapchain;
14187+        SOKOL_ASSERT(swapchain->d3d11.render_view);
14188+        num_rtvs = 1;
14189+        rtvs[0] = (ID3D11RenderTargetView*) swapchain->d3d11.render_view;
14190+        dsv = (ID3D11DepthStencilView*) swapchain->d3d11.depth_stencil_view;
14191+        _sg.d3d11.cur_swapchain.render_view = (ID3D11RenderTargetView*) swapchain->d3d11.render_view;
14192+        _sg.d3d11.cur_swapchain.resolve_view = (ID3D11RenderTargetView*) swapchain->d3d11.resolve_view;
14193+    }
14194+    // apply the render-target- and depth-stencil-views
14195+    _sg_d3d11_OMSetRenderTargets(_sg.d3d11.ctx, SG_MAX_COLOR_ATTACHMENTS, rtvs, dsv);
14196+    _sg_stats_inc(d3d11.pass.num_om_set_render_targets);
14197+
14198+    // set viewport and scissor rect to cover whole screen
14199+    _SG_STRUCT(D3D11_VIEWPORT, vp);
14200+    vp.Width = (FLOAT) _sg.cur_pass.dim.width;
14201+    vp.Height = (FLOAT) _sg.cur_pass.dim.height;
14202+    vp.MaxDepth = 1.0f;
14203+    _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp);
14204+    D3D11_RECT rect;
14205+    rect.left = 0;
14206+    rect.top = 0;
14207+    rect.right = _sg.cur_pass.dim.width;
14208+    rect.bottom = _sg.cur_pass.dim.height;
14209+    _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect);
14210+
14211+    // perform clear action
14212+    const sg_pass_action* action = &pass->action;
14213+    for (size_t i = 0; i < (size_t)num_rtvs; i++) {
14214+        if (action->colors[i].load_action == SG_LOADACTION_CLEAR) {
14215+            _sg_d3d11_ClearRenderTargetView(_sg.d3d11.ctx, rtvs[i], (float*)&action->colors[i].clear_value);
14216+            _sg_stats_inc(d3d11.pass.num_clear_render_target_view);
14217+        }
14218+    }
14219+    UINT ds_flags = 0;
14220+    if (action->depth.load_action == SG_LOADACTION_CLEAR) {
14221+        ds_flags |= D3D11_CLEAR_DEPTH;
14222+    }
14223+    if (action->stencil.load_action == SG_LOADACTION_CLEAR) {
14224+        ds_flags |= D3D11_CLEAR_STENCIL;
14225+    }
14226+    if ((0 != ds_flags) && dsv) {
14227+        _sg_d3d11_ClearDepthStencilView(_sg.d3d11.ctx, dsv, ds_flags, action->depth.clear_value, action->stencil.clear_value);
14228+        _sg_stats_inc(d3d11.pass.num_clear_depth_stencil_view);
14229+    }
14230+}
14231+
14232+// D3D11CalcSubresource only exists for C++
14233+_SOKOL_PRIVATE UINT _sg_d3d11_calcsubresource(UINT mip_slice, UINT array_slice, UINT mip_levels) {
14234+    return mip_slice + array_slice * mip_levels;
14235+}
14236+
14237+_SOKOL_PRIVATE void _sg_d3d11_end_pass(const _sg_attachments_ptrs_t* atts) {
14238+    SOKOL_ASSERT(_sg.d3d11.ctx && atts);
14239+
14240+    if (!_sg.cur_pass.is_compute) {
14241+        // need to resolve MSAA render attachments into texture?
14242+        if (!atts->empty) {
14243+            // ...for offscreen pass...
14244+            for (int i = 0; i < atts->num_color_views; i++) {
14245+                const _sg_view_t* resolve_view = atts->resolve_views[i];
14246+                if (resolve_view) {
14247+                    const _sg_image_t* resolve_img = _sg_image_ref_ptr(&resolve_view->cmn.img.ref);
14248+                    const _sg_view_t* color_view = atts->color_views[i];
14249+                    SOKOL_ASSERT(color_view);
14250+                    const _sg_image_t* color_img = _sg_image_ref_ptr(&color_view->cmn.img.ref);
14251+                    SOKOL_ASSERT(color_img->cmn.sample_count > 1);
14252+                    SOKOL_ASSERT(resolve_img->cmn.sample_count == 1);
14253+                    const UINT src_subres = _sg_d3d11_calcsubresource(
14254+                        (UINT)color_view->cmn.img.mip_level,
14255+                        (UINT)color_view->cmn.img.slice,
14256+                        (UINT)color_img->cmn.num_mipmaps);
14257+                    const UINT dst_subres = _sg_d3d11_calcsubresource(
14258+                        (UINT)resolve_view->cmn.img.mip_level,
14259+                        (UINT)resolve_view->cmn.img.slice,
14260+                        (UINT)resolve_img->cmn.num_mipmaps);
14261+                    _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx,
14262+                        resolve_img->d3d11.res,
14263+                        dst_subres,
14264+                        color_img->d3d11.res,
14265+                        src_subres,
14266+                        _sg_d3d11_rtv_uav_pixel_format(color_img->cmn.pixel_format));
14267+                    _sg_stats_inc(d3d11.pass.num_resolve_subresource);
14268+                }
14269+            }
14270+        } else {
14271+            // ...for swapchain pass...
14272+            if (_sg.d3d11.cur_swapchain.resolve_view) {
14273+                SOKOL_ASSERT(_sg.d3d11.cur_swapchain.render_view);
14274+                SOKOL_ASSERT(_sg.cur_pass.swapchain.sample_count > 1);
14275+                SOKOL_ASSERT(_sg.cur_pass.swapchain.color_fmt > SG_PIXELFORMAT_NONE);
14276+                ID3D11Resource* d3d11_render_res = 0;
14277+                ID3D11Resource* d3d11_resolve_res = 0;
14278+                _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_swapchain.render_view, &d3d11_render_res);
14279+                _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_swapchain.resolve_view, &d3d11_resolve_res);
14280+                SOKOL_ASSERT(d3d11_render_res);
14281+                SOKOL_ASSERT(d3d11_resolve_res);
14282+                const sg_pixel_format color_fmt = _sg.cur_pass.swapchain.color_fmt;
14283+                _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx, d3d11_resolve_res, 0, d3d11_render_res, 0, _sg_d3d11_rtv_uav_pixel_format(color_fmt));
14284+                _sg_d3d11_Release(d3d11_render_res);
14285+                _sg_d3d11_Release(d3d11_resolve_res);
14286+                _sg_stats_inc(d3d11.pass.num_resolve_subresource);
14287+            }
14288+        }
14289+    }
14290+    _sg.d3d11.cur_swapchain.render_view = 0;
14291+    _sg.d3d11.cur_swapchain.resolve_view = 0;
14292+    _sg_d3d11_clear_state();
14293+}
14294+
14295+_SOKOL_PRIVATE void _sg_d3d11_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
14296+    SOKOL_ASSERT(_sg.d3d11.ctx);
14297+    D3D11_VIEWPORT vp;
14298+    vp.TopLeftX = (FLOAT) x;
14299+    vp.TopLeftY = (FLOAT) (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h)));
14300+    vp.Width = (FLOAT) w;
14301+    vp.Height = (FLOAT) h;
14302+    vp.MinDepth = 0.0f;
14303+    vp.MaxDepth = 1.0f;
14304+    _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp);
14305+}
14306+
14307+_SOKOL_PRIVATE void _sg_d3d11_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
14308+    SOKOL_ASSERT(_sg.d3d11.ctx);
14309+    D3D11_RECT rect;
14310+    rect.left = x;
14311+    rect.top = (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h)));
14312+    rect.right = x + w;
14313+    rect.bottom = origin_top_left ? (y + h) : (_sg.cur_pass.dim.height - y);
14314+    _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect);
14315+}
14316+
14317+_SOKOL_PRIVATE void _sg_d3d11_apply_pipeline(_sg_pipeline_t* pip) {
14318+    SOKOL_ASSERT(pip);
14319+    SOKOL_ASSERT(_sg.d3d11.ctx);
14320+
14321+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
14322+    if (pip->cmn.is_compute) {
14323+        // a compute pipeline
14324+        SOKOL_ASSERT(shd->d3d11.cs);
14325+        _sg_d3d11_CSSetShader(_sg.d3d11.ctx, shd->d3d11.cs, NULL, 0);
14326+        _sg_d3d11_CSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, shd->d3d11.cs_cbufs);
14327+        _sg_stats_inc(d3d11.pipeline.num_cs_set_shader);
14328+        _sg_stats_inc(d3d11.pipeline.num_cs_set_constant_buffers);
14329+    } else {
14330+        // a render pipeline
14331+        SOKOL_ASSERT(pip->d3d11.rs && pip->d3d11.bs && pip->d3d11.dss);
14332+        SOKOL_ASSERT(shd->d3d11.vs);
14333+        SOKOL_ASSERT(shd->d3d11.fs);
14334+
14335+        _sg_d3d11_RSSetState(_sg.d3d11.ctx, pip->d3d11.rs);
14336+        _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, pip->d3d11.dss, pip->d3d11.stencil_ref);
14337+        _sg_d3d11_OMSetBlendState(_sg.d3d11.ctx, pip->d3d11.bs, (float*)&pip->cmn.blend_color, 0xFFFFFFFF);
14338+        _sg_d3d11_IASetPrimitiveTopology(_sg.d3d11.ctx, pip->d3d11.topology);
14339+        _sg_d3d11_IASetInputLayout(_sg.d3d11.ctx, pip->d3d11.il);
14340+        _sg_d3d11_VSSetShader(_sg.d3d11.ctx, shd->d3d11.vs, NULL, 0);
14341+        _sg_d3d11_VSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, shd->d3d11.vs_cbufs);
14342+        _sg_d3d11_PSSetShader(_sg.d3d11.ctx, shd->d3d11.fs, NULL, 0);
14343+        _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, shd->d3d11.fs_cbufs);
14344+        _sg_stats_inc(d3d11.pipeline.num_rs_set_state);
14345+        _sg_stats_inc(d3d11.pipeline.num_om_set_depth_stencil_state);
14346+        _sg_stats_inc(d3d11.pipeline.num_om_set_blend_state);
14347+        _sg_stats_inc(d3d11.pipeline.num_ia_set_primitive_topology);
14348+        _sg_stats_inc(d3d11.pipeline.num_ia_set_input_layout);
14349+        _sg_stats_inc(d3d11.pipeline.num_vs_set_shader);
14350+        _sg_stats_inc(d3d11.pipeline.num_vs_set_constant_buffers);
14351+        _sg_stats_inc(d3d11.pipeline.num_ps_set_shader);
14352+        _sg_stats_inc(d3d11.pipeline.num_ps_set_constant_buffers);
14353+    }
14354+}
14355+
14356+_SOKOL_PRIVATE bool _sg_d3d11_apply_bindings(_sg_bindings_ptrs_t* bnd) {
14357+    SOKOL_ASSERT(bnd);
14358+    SOKOL_ASSERT(bnd->pip);
14359+    SOKOL_ASSERT(_sg.d3d11.ctx);
14360+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
14361+    const bool is_compute = bnd->pip->cmn.is_compute;
14362+
14363+    if (is_compute) {
14364+        _sg_clear(&_sg.d3d11.bnd.cs_srvs, sizeof(_sg.d3d11.bnd.cs_srvs));
14365+        _sg_clear(&_sg.d3d11.bnd.cs_uavs, sizeof(_sg.d3d11.bnd.cs_uavs));
14366+        _sg_clear(&_sg.d3d11.bnd.cs_smps, sizeof(_sg.d3d11.bnd.cs_smps));
14367+    } else {
14368+        _sg_clear(&_sg.d3d11.bnd.vbs, sizeof(_sg.d3d11.bnd.vbs));
14369+        _sg_clear(&_sg.d3d11.bnd.vb_offsets, sizeof(_sg.d3d11.bnd.vb_offsets));
14370+        _sg_clear(&_sg.d3d11.bnd.vs_srvs, sizeof(_sg.d3d11.bnd.vs_srvs));
14371+        _sg_clear(&_sg.d3d11.bnd.fs_srvs, sizeof(_sg.d3d11.bnd.fs_srvs));
14372+        _sg_clear(&_sg.d3d11.bnd.vs_smps, sizeof(_sg.d3d11.bnd.vs_smps));
14373+        _sg_clear(&_sg.d3d11.bnd.fs_smps, sizeof(_sg.d3d11.bnd.fs_smps));
14374+    }
14375+
14376+    // gather all the D3D11 resources into arrays
14377+    ID3D11Buffer* d3d11_ib = bnd->ib ? bnd->ib->d3d11.buf : 0;
14378+
14379+    if (is_compute) {
14380+        // on D3D11 we need to break a chicken-egg-situation where a resource
14381+        // may still be set as shader resource view, but is going to be set
14382+        // as unordered-access-view, so first clear all shader resource view bindings
14383+        _sg_d3d11_CSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.cs_srvs);
14384+    } else {
14385+        for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
14386+            const _sg_buffer_t* vb = bnd->vbs[i];
14387+            if (vb == 0) {
14388+                continue;
14389+            }
14390+            SOKOL_ASSERT(vb->d3d11.buf);
14391+            _sg.d3d11.bnd.vbs[i] = vb->d3d11.buf;
14392+            _sg.d3d11.bnd.vb_offsets[i] = (UINT)bnd->vb_offsets[i];
14393+        }
14394+    }
14395+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
14396+        const _sg_view_t* view = bnd->views[i];
14397+        if (0 == view) {
14398+            continue;
14399+        }
14400+        const _sg_shader_view_t* shd_view = &shd->cmn.views[i];
14401+        const sg_shader_stage stage = shd_view->stage;
14402+        SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX)
14403+            || (stage == SG_SHADERSTAGE_FRAGMENT)
14404+            || (stage == SG_SHADERSTAGE_COMPUTE));
14405+        SOKOL_ASSERT((shd_view->view_type == SG_VIEWTYPE_TEXTURE)
14406+            || (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER)
14407+            || (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE));
14408+        if (shd_view->view_type == SG_VIEWTYPE_TEXTURE) {
14409+            const uint8_t d3d11_slot = shd->d3d11.view_register_t_n[i];
14410+            SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS);
14411+            ID3D11ShaderResourceView* d3d11_srv = view->d3d11.srv;
14412+            SOKOL_ASSERT(d3d11_srv);
14413+            switch (stage) {
14414+                case SG_SHADERSTAGE_VERTEX: _sg.d3d11.bnd.vs_srvs[d3d11_slot] = d3d11_srv; break;
14415+                case SG_SHADERSTAGE_FRAGMENT: _sg.d3d11.bnd.fs_srvs[d3d11_slot] = d3d11_srv; break;
14416+                case SG_SHADERSTAGE_COMPUTE: _sg.d3d11.bnd.cs_srvs[d3d11_slot] = d3d11_srv; break;
14417+                default: SOKOL_UNREACHABLE;
14418+            }
14419+        } else if (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER) {
14420+            if (shd->cmn.views[i].sbuf_readonly) {
14421+                const uint8_t d3d11_slot = shd->d3d11.view_register_t_n[i];
14422+                SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS);
14423+                ID3D11ShaderResourceView* d3d11_srv = view->d3d11.srv;
14424+                SOKOL_ASSERT(d3d11_srv);
14425+                switch (stage) {
14426+                    case SG_SHADERSTAGE_VERTEX: _sg.d3d11.bnd.vs_srvs[d3d11_slot] = d3d11_srv; break;
14427+                    case SG_SHADERSTAGE_FRAGMENT: _sg.d3d11.bnd.fs_srvs[d3d11_slot] = d3d11_srv; break;
14428+                    case SG_SHADERSTAGE_COMPUTE: _sg.d3d11.bnd.cs_srvs[d3d11_slot] = d3d11_srv; break;
14429+                    default: SOKOL_UNREACHABLE;
14430+                }
14431+            } else {
14432+                SOKOL_ASSERT(stage == SG_SHADERSTAGE_COMPUTE);
14433+                const uint8_t d3d11_slot = shd->d3d11.view_register_u_n[i];
14434+                SOKOL_ASSERT(d3d11_slot < _sg.limits.d3d11_max_unordered_access_views);
14435+                ID3D11UnorderedAccessView* d3d11_uav = view->d3d11.uav;
14436+                SOKOL_ASSERT(d3d11_uav);
14437+                _sg.d3d11.bnd.cs_uavs[d3d11_slot] = d3d11_uav;
14438+            }
14439+        } else if (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE) {
14440+            SOKOL_ASSERT(stage == SG_SHADERSTAGE_COMPUTE);
14441+            const uint8_t d3d11_slot = shd->d3d11.view_register_u_n[i];
14442+            SOKOL_ASSERT(d3d11_slot < _sg.limits.d3d11_max_unordered_access_views);
14443+            ID3D11UnorderedAccessView* d3d11_uav = view->d3d11.uav;
14444+            SOKOL_ASSERT(d3d11_uav);
14445+            _sg.d3d11.bnd.cs_uavs[d3d11_slot] = d3d11_uav;
14446+        } else SOKOL_UNREACHABLE;
14447+    }
14448+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
14449+        const _sg_sampler_t* smp = bnd->smps[i];
14450+        if (smp == 0) {
14451+            continue;
14452+        }
14453+        const sg_shader_stage stage = shd->cmn.samplers[i].stage;
14454+        SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE);
14455+        const uint8_t d3d11_slot = shd->d3d11.smp_register_s_n[i];
14456+        SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SMP_BINDINGS);
14457+        SOKOL_ASSERT(smp->d3d11.smp);
14458+        ID3D11SamplerState* d3d11_smp = smp->d3d11.smp;
14459+        switch (stage) {
14460+            case SG_SHADERSTAGE_VERTEX: _sg.d3d11.bnd.vs_smps[d3d11_slot] = d3d11_smp; break;
14461+            case SG_SHADERSTAGE_FRAGMENT: _sg.d3d11.bnd.fs_smps[d3d11_slot] = d3d11_smp; break;
14462+            case SG_SHADERSTAGE_COMPUTE: _sg.d3d11.bnd.cs_smps[d3d11_slot] = d3d11_smp; break;
14463+            default: SOKOL_UNREACHABLE;
14464+        }
14465+    }
14466+    if (is_compute) {
14467+        SOKOL_ASSERT(_sg.limits.d3d11_max_unordered_access_views <= _SG_D3D11_MAX_STAGE_UAV_BINDINGS);
14468+        _sg_d3d11_CSSetUnorderedAccessViews(_sg.d3d11.ctx, 0, _sg.limits.d3d11_max_unordered_access_views, _sg.d3d11.bnd.cs_uavs, NULL);
14469+        _sg_d3d11_CSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.cs_srvs);
14470+        _sg_d3d11_CSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, _sg.d3d11.bnd.cs_smps);
14471+        _sg_stats_inc(d3d11.bindings.num_cs_set_shader_resources);
14472+        _sg_stats_inc(d3d11.bindings.num_cs_set_samplers);
14473+        _sg_stats_inc(d3d11.bindings.num_cs_set_unordered_access_views);
14474+    } else {
14475+        _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_VERTEXBUFFER_BINDSLOTS, _sg.d3d11.bnd.vbs, bnd->pip->d3d11.vb_strides, _sg.d3d11.bnd.vb_offsets);
14476+        _sg_d3d11_IASetIndexBuffer(_sg.d3d11.ctx, d3d11_ib, bnd->pip->d3d11.index_format, (UINT)bnd->ib_offset);
14477+        _sg_d3d11_VSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.vs_srvs);
14478+        _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, _sg.d3d11.bnd.fs_srvs);
14479+        _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, _sg.d3d11.bnd.vs_smps);
14480+        _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, _sg.d3d11.bnd.fs_smps);
14481+        _sg_stats_inc(d3d11.bindings.num_ia_set_vertex_buffers);
14482+        _sg_stats_inc(d3d11.bindings.num_ia_set_index_buffer);
14483+        _sg_stats_inc(d3d11.bindings.num_vs_set_shader_resources);
14484+        _sg_stats_inc(d3d11.bindings.num_ps_set_shader_resources);
14485+        _sg_stats_inc(d3d11.bindings.num_vs_set_samplers);
14486+        _sg_stats_inc(d3d11.bindings.num_ps_set_samplers);
14487+    }
14488+    return true;
14489+}
14490+
14491+_SOKOL_PRIVATE void _sg_d3d11_apply_uniforms(int ub_slot, const sg_range* data) {
14492+    SOKOL_ASSERT(_sg.d3d11.ctx);
14493+    SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
14494+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
14495+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
14496+    SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size);
14497+
14498+    ID3D11Buffer* cbuf = shd->d3d11.all_cbufs[ub_slot];
14499+    SOKOL_ASSERT(cbuf);
14500+    _sg_d3d11_UpdateSubresource(_sg.d3d11.ctx, (ID3D11Resource*)cbuf, 0, NULL, data->ptr, 0, 0);
14501+    _sg_stats_inc(d3d11.uniforms.num_update_subresource);
14502+}
14503+
14504+_SOKOL_PRIVATE void _sg_d3d11_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
14505+    const bool use_instanced_draw = (num_instances > 1) || (_sg.use_instanced_draw);
14506+    if (_sg.use_indexed_draw) {
14507+        if (use_instanced_draw) {
14508+            _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx,
14509+                (UINT)num_elements,
14510+                (UINT)num_instances,
14511+                (UINT)base_element,
14512+                base_vertex,
14513+                (UINT)base_instance);
14514+            _sg_stats_inc(d3d11.draw.num_draw_indexed_instanced);
14515+        } else {
14516+            _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, base_vertex);
14517+            _sg_stats_inc(d3d11.draw.num_draw_indexed);
14518+        }
14519+    } else {
14520+        if (use_instanced_draw) {
14521+            _sg_d3d11_DrawInstanced(_sg.d3d11.ctx,
14522+                (UINT)num_elements,
14523+                (UINT)num_instances,
14524+                (UINT)base_element,
14525+                (UINT)base_instance);
14526+            _sg_stats_inc(d3d11.draw.num_draw_instanced);
14527+        } else {
14528+            _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element);
14529+            _sg_stats_inc(d3d11.draw.num_draw);
14530+        }
14531+    }
14532+}
14533+
14534+_SOKOL_PRIVATE void _sg_d3d11_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
14535+    _sg_d3d11_Dispatch(_sg.d3d11.ctx, (UINT)num_groups_x, (UINT)num_groups_y, (UINT)num_groups_z);
14536+}
14537+
14538+_SOKOL_PRIVATE void _sg_d3d11_commit(void) {
14539+    // empty
14540+}
14541+
14542+_SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
14543+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
14544+    SOKOL_ASSERT(_sg.d3d11.ctx);
14545+    SOKOL_ASSERT(buf->d3d11.buf);
14546+    D3D11_MAPPED_SUBRESOURCE d3d11_msr;
14547+    HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr);
14548+    _sg_stats_inc(d3d11.num_map);
14549+    if (SUCCEEDED(hr)) {
14550+        memcpy(d3d11_msr.pData, data->ptr, data->size);
14551+        _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0);
14552+        _sg_stats_inc(d3d11.num_unmap);
14553+    } else {
14554+        _SG_ERROR(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED);
14555+    }
14556+}
14557+
14558+_SOKOL_PRIVATE void _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
14559+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
14560+    SOKOL_ASSERT(_sg.d3d11.ctx);
14561+    SOKOL_ASSERT(buf->d3d11.buf);
14562+    D3D11_MAP map_type = new_frame ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE;
14563+    D3D11_MAPPED_SUBRESOURCE d3d11_msr;
14564+    HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, map_type, 0, &d3d11_msr);
14565+    _sg_stats_inc(d3d11.num_map);
14566+    if (SUCCEEDED(hr)) {
14567+        uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos;
14568+        memcpy(dst_ptr, data->ptr, data->size);
14569+        _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0);
14570+        _sg_stats_inc(d3d11.num_unmap);
14571+    } else {
14572+        _SG_ERROR(D3D11_MAP_FOR_APPEND_BUFFER_FAILED);
14573+    }
14574+}
14575+
14576+// see: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-subresources
14577+// also see: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11calcsubresource
14578+_SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data* data) {
14579+    SOKOL_ASSERT(img && data);
14580+    SOKOL_ASSERT(_sg.d3d11.ctx);
14581+    SOKOL_ASSERT(img->d3d11.res);
14582+    const int num_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? 1 : img->cmn.num_slices;
14583+    const int num_depth_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? img->cmn.num_slices : 1;
14584+    UINT subres_index = 0;
14585+    HRESULT hr;
14586+    D3D11_MAPPED_SUBRESOURCE d3d11_msr;
14587+    for (int slice_index = 0; slice_index < num_slices; slice_index++) {
14588+        for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) {
14589+            SOKOL_ASSERT(subres_index < _SG_D3D11_MAX_TEXTURE_SUBRESOURCES);
14590+            const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
14591+            const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
14592+            const int src_row_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1);
14593+            const int src_depth_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1);
14594+            const sg_range* miplevel_data = &(data->mip_levels[mip_index]);
14595+            const size_t slice_size = miplevel_data->size / (size_t)num_slices;
14596+            SOKOL_ASSERT(slice_size == (size_t)(src_depth_pitch * num_depth_slices));
14597+            const size_t slice_offset = slice_size * (size_t)slice_index;
14598+            const uint8_t* slice_ptr = ((const uint8_t*)miplevel_data->ptr) + slice_offset;
14599+            hr = _sg_d3d11_Map(_sg.d3d11.ctx, img->d3d11.res, subres_index, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr);
14600+            _sg_stats_inc(d3d11.num_map);
14601+            if (SUCCEEDED(hr)) {
14602+                const uint8_t* src_ptr = slice_ptr;
14603+                uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData;
14604+                for (int depth_index = 0; depth_index < num_depth_slices; depth_index++) {
14605+                    if (src_row_pitch == (int)d3d11_msr.RowPitch) {
14606+                        const size_t copy_size = slice_size / (size_t)num_depth_slices;
14607+                        SOKOL_ASSERT((copy_size * (size_t)num_depth_slices) == slice_size);
14608+                        memcpy(dst_ptr, src_ptr, copy_size);
14609+                    } else {
14610+                        SOKOL_ASSERT(src_row_pitch < (int)d3d11_msr.RowPitch);
14611+                        const uint8_t* src_row_ptr = src_ptr;
14612+                        uint8_t* dst_row_ptr = dst_ptr;
14613+                        for (int row_index = 0; row_index < mip_height; row_index++) {
14614+                            memcpy(dst_row_ptr, src_row_ptr, (size_t)src_row_pitch);
14615+                            src_row_ptr += src_row_pitch;
14616+                            dst_row_ptr += d3d11_msr.RowPitch;
14617+                        }
14618+                    }
14619+                    src_ptr += src_depth_pitch;
14620+                    dst_ptr += d3d11_msr.DepthPitch;
14621+                }
14622+                _sg_d3d11_Unmap(_sg.d3d11.ctx, img->d3d11.res, subres_index);
14623+                _sg_stats_inc(d3d11.num_unmap);
14624+            } else {
14625+                _SG_ERROR(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED);
14626+            }
14627+        }
14628+    }
14629+}
14630+
14631+// ███    ███ ███████ ████████  █████  ██          ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
14632+// ████  ████ ██         ██    ██   ██ ██          ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
14633+// ██ ████ ██ █████      ██    ███████ ██          ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
14634+// ██  ██  ██ ██         ██    ██   ██ ██          ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
14635+// ██      ██ ███████    ██    ██   ██ ███████     ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
14636+//
14637+// >>metal backend
14638+#elif defined(SOKOL_METAL)
14639+#pragma clang diagnostic push
14640+// silence Intel Mac deprecations for now
14641+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
14642+
14643+#if __has_feature(objc_arc)
14644+#define _SG_OBJC_RETAIN(obj) { }
14645+#define _SG_OBJC_RELEASE(obj) { obj = nil; }
14646+#else
14647+#define _SG_OBJC_RETAIN(obj) { [obj retain]; }
14648+#define _SG_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
14649+#endif
14650+
14651+//-- enum translation functions ------------------------------------------------
14652+_SOKOL_PRIVATE MTLLoadAction _sg_mtl_load_action(sg_load_action a) {
14653+    switch (a) {
14654+        case SG_LOADACTION_CLEAR:       return MTLLoadActionClear;
14655+        case SG_LOADACTION_LOAD:        return MTLLoadActionLoad;
14656+        case SG_LOADACTION_DONTCARE:    return MTLLoadActionDontCare;
14657+        default: SOKOL_UNREACHABLE;     return (MTLLoadAction)0;
14658+    }
14659+}
14660+
14661+_SOKOL_PRIVATE MTLStoreAction _sg_mtl_store_action(sg_store_action a, bool resolve) {
14662+    switch (a) {
14663+        case SG_STOREACTION_STORE:
14664+            if (resolve) {
14665+                return MTLStoreActionStoreAndMultisampleResolve;
14666+            } else {
14667+                return MTLStoreActionStore;
14668+            }
14669+            break;
14670+        case SG_STOREACTION_DONTCARE:
14671+            if (resolve) {
14672+                return MTLStoreActionMultisampleResolve;
14673+            } else {
14674+                return MTLStoreActionDontCare;
14675+            }
14676+            break;
14677+        default: SOKOL_UNREACHABLE; return (MTLStoreAction)0;
14678+    }
14679+}
14680+
14681+_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_resource_options_storage_mode_managed_or_shared(void) {
14682+    #if defined(_SG_TARGET_MACOS)
14683+    if (_sg.mtl.use_shared_storage_mode) {
14684+        return MTLResourceStorageModeShared;
14685+    } else {
14686+        return MTLResourceStorageModeManaged;
14687+    }
14688+    #else
14689+        // MTLResourceStorageModeManaged is not even defined on iOS SDK
14690+        return MTLResourceStorageModeShared;
14691+    #endif
14692+}
14693+
14694+_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_buffer_resource_options(const sg_buffer_usage* usage) {
14695+    if (usage->immutable) {
14696+        return _sg_mtl_resource_options_storage_mode_managed_or_shared();
14697+    } else {
14698+        return MTLResourceCPUCacheModeWriteCombined | _sg_mtl_resource_options_storage_mode_managed_or_shared();
14699+    }
14700+}
14701+
14702+_SOKOL_PRIVATE MTLVertexStepFunction _sg_mtl_step_function(sg_vertex_step step) {
14703+    switch (step) {
14704+        case SG_VERTEXSTEP_PER_VERTEX:      return MTLVertexStepFunctionPerVertex;
14705+        case SG_VERTEXSTEP_PER_INSTANCE:    return MTLVertexStepFunctionPerInstance;
14706+        default: SOKOL_UNREACHABLE; return (MTLVertexStepFunction)0;
14707+    }
14708+}
14709+
14710+_SOKOL_PRIVATE MTLVertexFormat _sg_mtl_vertex_format(sg_vertex_format fmt) {
14711+    switch (fmt) {
14712+        case SG_VERTEXFORMAT_FLOAT:     return MTLVertexFormatFloat;
14713+        case SG_VERTEXFORMAT_FLOAT2:    return MTLVertexFormatFloat2;
14714+        case SG_VERTEXFORMAT_FLOAT3:    return MTLVertexFormatFloat3;
14715+        case SG_VERTEXFORMAT_FLOAT4:    return MTLVertexFormatFloat4;
14716+        case SG_VERTEXFORMAT_INT:       return MTLVertexFormatInt;
14717+        case SG_VERTEXFORMAT_INT2:      return MTLVertexFormatInt2;
14718+        case SG_VERTEXFORMAT_INT3:      return MTLVertexFormatInt3;
14719+        case SG_VERTEXFORMAT_INT4:      return MTLVertexFormatInt4;
14720+        case SG_VERTEXFORMAT_UINT:      return MTLVertexFormatUInt;
14721+        case SG_VERTEXFORMAT_UINT2:     return MTLVertexFormatUInt2;
14722+        case SG_VERTEXFORMAT_UINT3:     return MTLVertexFormatUInt3;
14723+        case SG_VERTEXFORMAT_UINT4:     return MTLVertexFormatUInt4;
14724+        case SG_VERTEXFORMAT_BYTE4:     return MTLVertexFormatChar4;
14725+        case SG_VERTEXFORMAT_BYTE4N:    return MTLVertexFormatChar4Normalized;
14726+        case SG_VERTEXFORMAT_UBYTE4:    return MTLVertexFormatUChar4;
14727+        case SG_VERTEXFORMAT_UBYTE4N:   return MTLVertexFormatUChar4Normalized;
14728+        case SG_VERTEXFORMAT_SHORT2:    return MTLVertexFormatShort2;
14729+        case SG_VERTEXFORMAT_SHORT2N:   return MTLVertexFormatShort2Normalized;
14730+        case SG_VERTEXFORMAT_USHORT2:   return MTLVertexFormatUShort2;
14731+        case SG_VERTEXFORMAT_USHORT2N:  return MTLVertexFormatUShort2Normalized;
14732+        case SG_VERTEXFORMAT_SHORT4:    return MTLVertexFormatShort4;
14733+        case SG_VERTEXFORMAT_SHORT4N:   return MTLVertexFormatShort4Normalized;
14734+        case SG_VERTEXFORMAT_USHORT4:   return MTLVertexFormatUShort4;
14735+        case SG_VERTEXFORMAT_USHORT4N:  return MTLVertexFormatUShort4Normalized;
14736+        case SG_VERTEXFORMAT_INT10_N2:  return MTLVertexFormatInt1010102Normalized;
14737+        case SG_VERTEXFORMAT_UINT10_N2: return MTLVertexFormatUInt1010102Normalized;
14738+        case SG_VERTEXFORMAT_HALF2:     return MTLVertexFormatHalf2;
14739+        case SG_VERTEXFORMAT_HALF4:     return MTLVertexFormatHalf4;
14740+        default: SOKOL_UNREACHABLE; return (MTLVertexFormat)0;
14741+    }
14742+}
14743+
14744+_SOKOL_PRIVATE MTLPrimitiveType _sg_mtl_primitive_type(sg_primitive_type t) {
14745+    switch (t) {
14746+        case SG_PRIMITIVETYPE_POINTS:           return MTLPrimitiveTypePoint;
14747+        case SG_PRIMITIVETYPE_LINES:            return MTLPrimitiveTypeLine;
14748+        case SG_PRIMITIVETYPE_LINE_STRIP:       return MTLPrimitiveTypeLineStrip;
14749+        case SG_PRIMITIVETYPE_TRIANGLES:        return MTLPrimitiveTypeTriangle;
14750+        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return MTLPrimitiveTypeTriangleStrip;
14751+        default: SOKOL_UNREACHABLE; return (MTLPrimitiveType)0;
14752+    }
14753+}
14754+
14755+_SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) {
14756+    switch (fmt) {
14757+        case SG_PIXELFORMAT_R8:                     return MTLPixelFormatR8Unorm;
14758+        case SG_PIXELFORMAT_R8SN:                   return MTLPixelFormatR8Snorm;
14759+        case SG_PIXELFORMAT_R8UI:                   return MTLPixelFormatR8Uint;
14760+        case SG_PIXELFORMAT_R8SI:                   return MTLPixelFormatR8Sint;
14761+        case SG_PIXELFORMAT_R16:                    return MTLPixelFormatR16Unorm;
14762+        case SG_PIXELFORMAT_R16SN:                  return MTLPixelFormatR16Snorm;
14763+        case SG_PIXELFORMAT_R16UI:                  return MTLPixelFormatR16Uint;
14764+        case SG_PIXELFORMAT_R16SI:                  return MTLPixelFormatR16Sint;
14765+        case SG_PIXELFORMAT_R16F:                   return MTLPixelFormatR16Float;
14766+        case SG_PIXELFORMAT_RG8:                    return MTLPixelFormatRG8Unorm;
14767+        case SG_PIXELFORMAT_RG8SN:                  return MTLPixelFormatRG8Snorm;
14768+        case SG_PIXELFORMAT_RG8UI:                  return MTLPixelFormatRG8Uint;
14769+        case SG_PIXELFORMAT_RG8SI:                  return MTLPixelFormatRG8Sint;
14770+        case SG_PIXELFORMAT_R32UI:                  return MTLPixelFormatR32Uint;
14771+        case SG_PIXELFORMAT_R32SI:                  return MTLPixelFormatR32Sint;
14772+        case SG_PIXELFORMAT_R32F:                   return MTLPixelFormatR32Float;
14773+        case SG_PIXELFORMAT_RG16:                   return MTLPixelFormatRG16Unorm;
14774+        case SG_PIXELFORMAT_RG16SN:                 return MTLPixelFormatRG16Snorm;
14775+        case SG_PIXELFORMAT_RG16UI:                 return MTLPixelFormatRG16Uint;
14776+        case SG_PIXELFORMAT_RG16SI:                 return MTLPixelFormatRG16Sint;
14777+        case SG_PIXELFORMAT_RG16F:                  return MTLPixelFormatRG16Float;
14778+        case SG_PIXELFORMAT_RGBA8:                  return MTLPixelFormatRGBA8Unorm;
14779+        case SG_PIXELFORMAT_SRGB8A8:                return MTLPixelFormatRGBA8Unorm_sRGB;
14780+        case SG_PIXELFORMAT_RGBA8SN:                return MTLPixelFormatRGBA8Snorm;
14781+        case SG_PIXELFORMAT_RGBA8UI:                return MTLPixelFormatRGBA8Uint;
14782+        case SG_PIXELFORMAT_RGBA8SI:                return MTLPixelFormatRGBA8Sint;
14783+        case SG_PIXELFORMAT_BGRA8:                  return MTLPixelFormatBGRA8Unorm;
14784+        case SG_PIXELFORMAT_SBGR8A8:                return MTLPixelFormatBGRA8Unorm_sRGB;
14785+        case SG_PIXELFORMAT_RGB10A2:                return MTLPixelFormatRGB10A2Unorm;
14786+        case SG_PIXELFORMAT_RG11B10F:               return MTLPixelFormatRG11B10Float;
14787+        case SG_PIXELFORMAT_RGB9E5:                 return MTLPixelFormatRGB9E5Float;
14788+        case SG_PIXELFORMAT_RG32UI:                 return MTLPixelFormatRG32Uint;
14789+        case SG_PIXELFORMAT_RG32SI:                 return MTLPixelFormatRG32Sint;
14790+        case SG_PIXELFORMAT_RG32F:                  return MTLPixelFormatRG32Float;
14791+        case SG_PIXELFORMAT_RGBA16:                 return MTLPixelFormatRGBA16Unorm;
14792+        case SG_PIXELFORMAT_RGBA16SN:               return MTLPixelFormatRGBA16Snorm;
14793+        case SG_PIXELFORMAT_RGBA16UI:               return MTLPixelFormatRGBA16Uint;
14794+        case SG_PIXELFORMAT_RGBA16SI:               return MTLPixelFormatRGBA16Sint;
14795+        case SG_PIXELFORMAT_RGBA16F:                return MTLPixelFormatRGBA16Float;
14796+        case SG_PIXELFORMAT_RGBA32UI:               return MTLPixelFormatRGBA32Uint;
14797+        case SG_PIXELFORMAT_RGBA32SI:               return MTLPixelFormatRGBA32Sint;
14798+        case SG_PIXELFORMAT_RGBA32F:                return MTLPixelFormatRGBA32Float;
14799+        case SG_PIXELFORMAT_DEPTH:                  return MTLPixelFormatDepth32Float;
14800+        case SG_PIXELFORMAT_DEPTH_STENCIL:          return MTLPixelFormatDepth32Float_Stencil8;
14801+        #if defined(_SG_TARGET_MACOS)
14802+        case SG_PIXELFORMAT_BC1_RGBA:               return MTLPixelFormatBC1_RGBA;
14803+        case SG_PIXELFORMAT_BC2_RGBA:               return MTLPixelFormatBC2_RGBA;
14804+        case SG_PIXELFORMAT_BC3_RGBA:               return MTLPixelFormatBC3_RGBA;
14805+        case SG_PIXELFORMAT_BC3_SRGBA:              return MTLPixelFormatBC3_RGBA_sRGB;
14806+        case SG_PIXELFORMAT_BC4_R:                  return MTLPixelFormatBC4_RUnorm;
14807+        case SG_PIXELFORMAT_BC4_RSN:                return MTLPixelFormatBC4_RSnorm;
14808+        case SG_PIXELFORMAT_BC5_RG:                 return MTLPixelFormatBC5_RGUnorm;
14809+        case SG_PIXELFORMAT_BC5_RGSN:               return MTLPixelFormatBC5_RGSnorm;
14810+        case SG_PIXELFORMAT_BC6H_RGBF:              return MTLPixelFormatBC6H_RGBFloat;
14811+        case SG_PIXELFORMAT_BC6H_RGBUF:             return MTLPixelFormatBC6H_RGBUfloat;
14812+        case SG_PIXELFORMAT_BC7_RGBA:               return MTLPixelFormatBC7_RGBAUnorm;
14813+        case SG_PIXELFORMAT_BC7_SRGBA:              return MTLPixelFormatBC7_RGBAUnorm_sRGB;
14814+        #else
14815+        case SG_PIXELFORMAT_ETC2_RGB8:              return MTLPixelFormatETC2_RGB8;
14816+        case SG_PIXELFORMAT_ETC2_SRGB8:             return MTLPixelFormatETC2_RGB8_sRGB;
14817+        case SG_PIXELFORMAT_ETC2_RGB8A1:            return MTLPixelFormatETC2_RGB8A1;
14818+        case SG_PIXELFORMAT_ETC2_RGBA8:             return MTLPixelFormatEAC_RGBA8;
14819+        case SG_PIXELFORMAT_ETC2_SRGB8A8:           return MTLPixelFormatEAC_RGBA8_sRGB;
14820+        case SG_PIXELFORMAT_EAC_R11:                return MTLPixelFormatEAC_R11Unorm;
14821+        case SG_PIXELFORMAT_EAC_R11SN:              return MTLPixelFormatEAC_R11Snorm;
14822+        case SG_PIXELFORMAT_EAC_RG11:               return MTLPixelFormatEAC_RG11Unorm;
14823+        case SG_PIXELFORMAT_EAC_RG11SN:             return MTLPixelFormatEAC_RG11Snorm;
14824+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:          return MTLPixelFormatASTC_4x4_LDR;
14825+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA:         return MTLPixelFormatASTC_4x4_sRGB;
14826+        #endif
14827+        default: return MTLPixelFormatInvalid;
14828+    }
14829+}
14830+
14831+_SOKOL_PRIVATE MTLColorWriteMask _sg_mtl_color_write_mask(sg_color_mask m) {
14832+    MTLColorWriteMask mtl_mask = MTLColorWriteMaskNone;
14833+    if (m & SG_COLORMASK_R) {
14834+        mtl_mask |= MTLColorWriteMaskRed;
14835+    }
14836+    if (m & SG_COLORMASK_G) {
14837+        mtl_mask |= MTLColorWriteMaskGreen;
14838+    }
14839+    if (m & SG_COLORMASK_B) {
14840+        mtl_mask |= MTLColorWriteMaskBlue;
14841+    }
14842+    if (m & SG_COLORMASK_A) {
14843+        mtl_mask |= MTLColorWriteMaskAlpha;
14844+    }
14845+    return mtl_mask;
14846+}
14847+
14848+_SOKOL_PRIVATE MTLBlendOperation _sg_mtl_blend_op(sg_blend_op op) {
14849+    switch (op) {
14850+        case SG_BLENDOP_ADD:                return MTLBlendOperationAdd;
14851+        case SG_BLENDOP_SUBTRACT:           return MTLBlendOperationSubtract;
14852+        case SG_BLENDOP_REVERSE_SUBTRACT:   return MTLBlendOperationReverseSubtract;
14853+        case SG_BLENDOP_MIN:                return MTLBlendOperationMin;
14854+        case SG_BLENDOP_MAX:                return MTLBlendOperationMax;
14855+        default: SOKOL_UNREACHABLE; return (MTLBlendOperation)0;
14856+    }
14857+}
14858+
14859+_SOKOL_PRIVATE MTLBlendFactor _sg_mtl_blend_factor(sg_blend_factor f) {
14860+    switch (f) {
14861+        case SG_BLENDFACTOR_ZERO:                   return MTLBlendFactorZero;
14862+        case SG_BLENDFACTOR_ONE:                    return MTLBlendFactorOne;
14863+        case SG_BLENDFACTOR_SRC_COLOR:              return MTLBlendFactorSourceColor;
14864+        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return MTLBlendFactorOneMinusSourceColor;
14865+        case SG_BLENDFACTOR_SRC_ALPHA:              return MTLBlendFactorSourceAlpha;
14866+        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return MTLBlendFactorOneMinusSourceAlpha;
14867+        case SG_BLENDFACTOR_DST_COLOR:              return MTLBlendFactorDestinationColor;
14868+        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return MTLBlendFactorOneMinusDestinationColor;
14869+        case SG_BLENDFACTOR_DST_ALPHA:              return MTLBlendFactorDestinationAlpha;
14870+        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return MTLBlendFactorOneMinusDestinationAlpha;
14871+        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return MTLBlendFactorSourceAlphaSaturated;
14872+        case SG_BLENDFACTOR_BLEND_COLOR:            return MTLBlendFactorBlendColor;
14873+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return MTLBlendFactorOneMinusBlendColor;
14874+        case SG_BLENDFACTOR_BLEND_ALPHA:            return MTLBlendFactorBlendAlpha;
14875+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return MTLBlendFactorOneMinusBlendAlpha;
14876+        case SG_BLENDFACTOR_SRC1_COLOR:             return MTLBlendFactorSource1Color;
14877+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR:   return MTLBlendFactorOneMinusSource1Color;
14878+        case SG_BLENDFACTOR_SRC1_ALPHA:             return MTLBlendFactorSource1Alpha;
14879+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA:   return MTLBlendFactorOneMinusSource1Alpha;
14880+        default: SOKOL_UNREACHABLE; return (MTLBlendFactor)0;
14881+    }
14882+}
14883+
14884+_SOKOL_PRIVATE MTLCompareFunction _sg_mtl_compare_func(sg_compare_func f) {
14885+    switch (f) {
14886+        case SG_COMPAREFUNC_NEVER:          return MTLCompareFunctionNever;
14887+        case SG_COMPAREFUNC_LESS:           return MTLCompareFunctionLess;
14888+        case SG_COMPAREFUNC_EQUAL:          return MTLCompareFunctionEqual;
14889+        case SG_COMPAREFUNC_LESS_EQUAL:     return MTLCompareFunctionLessEqual;
14890+        case SG_COMPAREFUNC_GREATER:        return MTLCompareFunctionGreater;
14891+        case SG_COMPAREFUNC_NOT_EQUAL:      return MTLCompareFunctionNotEqual;
14892+        case SG_COMPAREFUNC_GREATER_EQUAL:  return MTLCompareFunctionGreaterEqual;
14893+        case SG_COMPAREFUNC_ALWAYS:         return MTLCompareFunctionAlways;
14894+        default: SOKOL_UNREACHABLE; return (MTLCompareFunction)0;
14895+    }
14896+}
14897+
14898+_SOKOL_PRIVATE MTLStencilOperation _sg_mtl_stencil_op(sg_stencil_op op) {
14899+    switch (op) {
14900+        case SG_STENCILOP_KEEP:         return MTLStencilOperationKeep;
14901+        case SG_STENCILOP_ZERO:         return MTLStencilOperationZero;
14902+        case SG_STENCILOP_REPLACE:      return MTLStencilOperationReplace;
14903+        case SG_STENCILOP_INCR_CLAMP:   return MTLStencilOperationIncrementClamp;
14904+        case SG_STENCILOP_DECR_CLAMP:   return MTLStencilOperationDecrementClamp;
14905+        case SG_STENCILOP_INVERT:       return MTLStencilOperationInvert;
14906+        case SG_STENCILOP_INCR_WRAP:    return MTLStencilOperationIncrementWrap;
14907+        case SG_STENCILOP_DECR_WRAP:    return MTLStencilOperationDecrementWrap;
14908+        default: SOKOL_UNREACHABLE; return (MTLStencilOperation)0;
14909+    }
14910+}
14911+
14912+_SOKOL_PRIVATE MTLCullMode _sg_mtl_cull_mode(sg_cull_mode m) {
14913+    switch (m) {
14914+        case SG_CULLMODE_NONE:  return MTLCullModeNone;
14915+        case SG_CULLMODE_FRONT: return MTLCullModeFront;
14916+        case SG_CULLMODE_BACK:  return MTLCullModeBack;
14917+        default: SOKOL_UNREACHABLE; return (MTLCullMode)0;
14918+    }
14919+}
14920+
14921+_SOKOL_PRIVATE MTLWinding _sg_mtl_winding(sg_face_winding w) {
14922+    switch (w) {
14923+        case SG_FACEWINDING_CW:     return MTLWindingClockwise;
14924+        case SG_FACEWINDING_CCW:    return MTLWindingCounterClockwise;
14925+        default: SOKOL_UNREACHABLE; return (MTLWinding)0;
14926+    }
14927+}
14928+
14929+_SOKOL_PRIVATE MTLIndexType _sg_mtl_index_type(sg_index_type t) {
14930+    switch (t) {
14931+        case SG_INDEXTYPE_UINT16:   return MTLIndexTypeUInt16;
14932+        case SG_INDEXTYPE_UINT32:   return MTLIndexTypeUInt32;
14933+        default: SOKOL_UNREACHABLE; return (MTLIndexType)0;
14934+    }
14935+}
14936+
14937+_SOKOL_PRIVATE int _sg_mtl_index_size(sg_index_type t) {
14938+    switch (t) {
14939+        case SG_INDEXTYPE_NONE:     return 0;
14940+        case SG_INDEXTYPE_UINT16:   return 2;
14941+        case SG_INDEXTYPE_UINT32:   return 4;
14942+        default: SOKOL_UNREACHABLE; return 0;
14943+    }
14944+}
14945+
14946+_SOKOL_PRIVATE MTLTextureType _sg_mtl_texture_type(sg_image_type t, bool msaa) {
14947+    switch (t) {
14948+        case SG_IMAGETYPE_2D:       return msaa ? MTLTextureType2DMultisample : MTLTextureType2D;
14949+        case SG_IMAGETYPE_CUBE:     return MTLTextureTypeCube;
14950+        case SG_IMAGETYPE_3D:       return MTLTextureType3D;
14951+        // NOTE: MTLTextureType2DMultisampleArray requires macOS 10.14+, iOS 14.0+
14952+        case SG_IMAGETYPE_ARRAY:    return MTLTextureType2DArray;
14953+        default: SOKOL_UNREACHABLE; return (MTLTextureType)0;
14954+    }
14955+}
14956+
14957+_SOKOL_PRIVATE MTLSamplerAddressMode _sg_mtl_address_mode(sg_wrap w) {
14958+    if (_sg.features.image_clamp_to_border) {
14959+        if (@available(macOS 12.0, iOS 14.0, *)) {
14960+            // border color feature available
14961+            switch (w) {
14962+                case SG_WRAP_REPEAT:            return MTLSamplerAddressModeRepeat;
14963+                case SG_WRAP_CLAMP_TO_EDGE:     return MTLSamplerAddressModeClampToEdge;
14964+                case SG_WRAP_CLAMP_TO_BORDER:   return MTLSamplerAddressModeClampToBorderColor;
14965+                case SG_WRAP_MIRRORED_REPEAT:   return MTLSamplerAddressModeMirrorRepeat;
14966+                default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0;
14967+            }
14968+        }
14969+    }
14970+    // fallthrough: clamp to border no supported
14971+    switch (w) {
14972+        case SG_WRAP_REPEAT:            return MTLSamplerAddressModeRepeat;
14973+        case SG_WRAP_CLAMP_TO_EDGE:     return MTLSamplerAddressModeClampToEdge;
14974+        case SG_WRAP_CLAMP_TO_BORDER:   return MTLSamplerAddressModeClampToEdge;
14975+        case SG_WRAP_MIRRORED_REPEAT:   return MTLSamplerAddressModeMirrorRepeat;
14976+        default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0;
14977+    }
14978+}
14979+
14980+_SOKOL_PRIVATE API_AVAILABLE(ios(14.0), macos(12.0)) MTLSamplerBorderColor _sg_mtl_border_color(sg_border_color c) {
14981+    switch (c) {
14982+        case SG_BORDERCOLOR_TRANSPARENT_BLACK: return MTLSamplerBorderColorTransparentBlack;
14983+        case SG_BORDERCOLOR_OPAQUE_BLACK: return MTLSamplerBorderColorOpaqueBlack;
14984+        case SG_BORDERCOLOR_OPAQUE_WHITE: return MTLSamplerBorderColorOpaqueWhite;
14985+        default: SOKOL_UNREACHABLE; return (MTLSamplerBorderColor)0;
14986+    }
14987+}
14988+
14989+_SOKOL_PRIVATE MTLSamplerMinMagFilter _sg_mtl_minmag_filter(sg_filter f) {
14990+    switch (f) {
14991+        case SG_FILTER_NEAREST:
14992+            return MTLSamplerMinMagFilterNearest;
14993+        case SG_FILTER_LINEAR:
14994+            return MTLSamplerMinMagFilterLinear;
14995+        default:
14996+            SOKOL_UNREACHABLE; return (MTLSamplerMinMagFilter)0;
14997+    }
14998+}
14999+
15000+_SOKOL_PRIVATE MTLSamplerMipFilter _sg_mtl_mipmap_filter(sg_filter f) {
15001+    switch (f) {
15002+        case SG_FILTER_NEAREST:
15003+            return MTLSamplerMipFilterNearest;
15004+        case SG_FILTER_LINEAR:
15005+            return MTLSamplerMipFilterLinear;
15006+        default:
15007+            SOKOL_UNREACHABLE; return (MTLSamplerMipFilter)0;
15008+    }
15009+}
15010+
15011+_SOKOL_PRIVATE size_t _sg_mtl_vertexbuffer_bindslot(size_t sokol_bindslot) {
15012+    return sokol_bindslot + _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS;
15013+}
15014+
15015+//-- a pool for all Metal resource objects, with deferred release queue ---------
15016+_SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) {
15017+    _sg.mtl.idpool.num_slots = 2 *
15018+        (
15019+            2 * desc->buffer_pool_size +
15020+            2 * desc->image_pool_size +
15021+            1 * desc->sampler_pool_size +
15022+            6 * desc->shader_pool_size +
15023+            3 * desc->pipeline_pool_size +
15024+            1 * desc->view_pool_size +
15025+            128
15026+        );
15027+    _sg.mtl.idpool.pool = [NSMutableArray arrayWithCapacity:(NSUInteger)_sg.mtl.idpool.num_slots];
15028+    _SG_OBJC_RETAIN(_sg.mtl.idpool.pool);
15029+    NSNull* null = [NSNull null];
15030+    for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) {
15031+        [_sg.mtl.idpool.pool addObject:null];
15032+    }
15033+    SOKOL_ASSERT([_sg.mtl.idpool.pool count] == (NSUInteger)_sg.mtl.idpool.num_slots);
15034+    // a queue of currently free slot indices
15035+    _sg.mtl.idpool.free_queue_top = 0;
15036+    _sg.mtl.idpool.free_queue = (int*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(int));
15037+    // pool slot 0 is reserved!
15038+    for (int i = _sg.mtl.idpool.num_slots-1; i >= 1; i--) {
15039+        _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = i;
15040+    }
15041+    // a circular queue which holds release items (frame index when a resource is to be released, and the resource's pool index
15042+    _sg.mtl.idpool.release_queue_front = 0;
15043+    _sg.mtl.idpool.release_queue_back = 0;
15044+    _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t));
15045+    for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) {
15046+        _sg.mtl.idpool.release_queue[i].frame_index = 0;
15047+        _sg.mtl.idpool.release_queue[i].slot_index = _SG_MTL_INVALID_SLOT_INDEX;
15048+    }
15049+}
15050+
15051+_SOKOL_PRIVATE void _sg_mtl_destroy_pool(void) {
15052+    _sg_free(_sg.mtl.idpool.release_queue);  _sg.mtl.idpool.release_queue = 0;
15053+    _sg_free(_sg.mtl.idpool.free_queue);     _sg.mtl.idpool.free_queue = 0;
15054+    _SG_OBJC_RELEASE(_sg.mtl.idpool.pool);
15055+}
15056+
15057+// get a new free resource pool slot
15058+_SOKOL_PRIVATE int _sg_mtl_alloc_pool_slot(void) {
15059+    SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top > 0);
15060+    const int slot_index = _sg.mtl.idpool.free_queue[--_sg.mtl.idpool.free_queue_top];
15061+    SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots));
15062+    return slot_index;
15063+}
15064+
15065+// put a free resource pool slot back into the free-queue
15066+_SOKOL_PRIVATE void _sg_mtl_free_pool_slot(int slot_index) {
15067+    SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top < _sg.mtl.idpool.num_slots);
15068+    SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots));
15069+    _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = slot_index;
15070+}
15071+
15072+// add an MTLResource to the pool, return pool index or 0 if input was 'nil'
15073+_SOKOL_PRIVATE int _sg_mtl_add_resource(id res) {
15074+    if (nil == res) {
15075+        return _SG_MTL_INVALID_SLOT_INDEX;
15076+    }
15077+    _sg_stats_inc(metal.idpool.num_added);
15078+    const int slot_index = _sg_mtl_alloc_pool_slot();
15079+    // NOTE: the NSMutableArray will take ownership of its items
15080+    SOKOL_ASSERT([NSNull null] == _sg.mtl.idpool.pool[(NSUInteger)slot_index]);
15081+    _sg.mtl.idpool.pool[(NSUInteger)slot_index] = res;
15082+    return slot_index;
15083+}
15084+
15085+/*  mark an MTLResource for release, this will put the resource into the
15086+    deferred-release queue, and the resource will then be released N frames later,
15087+    the special pool index 0 will be ignored (this means that a nil
15088+    value was provided to _sg_mtl_add_resource()
15089+*/
15090+_SOKOL_PRIVATE void _sg_mtl_release_resource(uint32_t frame_index, int slot_index) {
15091+    if (slot_index == _SG_MTL_INVALID_SLOT_INDEX) {
15092+        return;
15093+    }
15094+    _sg_stats_inc(metal.idpool.num_released);
15095+    SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots));
15096+    SOKOL_ASSERT([NSNull null] != _sg.mtl.idpool.pool[(NSUInteger)slot_index]);
15097+    int release_index = _sg.mtl.idpool.release_queue_front++;
15098+    if (_sg.mtl.idpool.release_queue_front >= _sg.mtl.idpool.num_slots) {
15099+        // wrap-around
15100+        _sg.mtl.idpool.release_queue_front = 0;
15101+    }
15102+    // release queue full?
15103+    SOKOL_ASSERT(_sg.mtl.idpool.release_queue_front != _sg.mtl.idpool.release_queue_back);
15104+    SOKOL_ASSERT(0 == _sg.mtl.idpool.release_queue[release_index].frame_index);
15105+    const uint32_t safe_to_release_frame_index = frame_index + SG_NUM_INFLIGHT_FRAMES + 1;
15106+    _sg.mtl.idpool.release_queue[release_index].frame_index = safe_to_release_frame_index;
15107+    _sg.mtl.idpool.release_queue[release_index].slot_index = slot_index;
15108+}
15109+
15110+// run garbage-collection pass on all resources in the release-queue
15111+_SOKOL_PRIVATE void _sg_mtl_garbage_collect(uint32_t frame_index) {
15112+    while (_sg.mtl.idpool.release_queue_back != _sg.mtl.idpool.release_queue_front) {
15113+        if (frame_index < _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index) {
15114+            // don't need to check further, release-items past this are too young
15115+            break;
15116+        }
15117+        _sg_stats_inc(metal.idpool.num_garbage_collected);
15118+        // safe to release this resource
15119+        const int slot_index = _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index;
15120+        SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots));
15121+        // note: the NSMutableArray takes ownership of its items, assigning an NSNull object will
15122+        // release the object, no matter if using ARC or not
15123+        SOKOL_ASSERT(_sg.mtl.idpool.pool[(NSUInteger)slot_index] != [NSNull null]);
15124+        _sg.mtl.idpool.pool[(NSUInteger)slot_index] = [NSNull null];
15125+        // put the now free pool index back on the free queue
15126+        _sg_mtl_free_pool_slot(slot_index);
15127+        // reset the release queue slot and advance the back index
15128+        _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index = 0;
15129+        _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index = _SG_MTL_INVALID_SLOT_INDEX;
15130+        _sg.mtl.idpool.release_queue_back++;
15131+        if (_sg.mtl.idpool.release_queue_back >= _sg.mtl.idpool.num_slots) {
15132+            // wrap-around
15133+            _sg.mtl.idpool.release_queue_back = 0;
15134+        }
15135+    }
15136+}
15137+
15138+_SOKOL_PRIVATE id _sg_mtl_id(int slot_index) {
15139+    return _sg.mtl.idpool.pool[(NSUInteger)slot_index];
15140+}
15141+
15142+_SOKOL_PRIVATE void _sg_mtl_clear_state_cache(void) {
15143+    _sg_clear(&_sg.mtl.cache, sizeof(_sg.mtl.cache));
15144+}
15145+
15146+// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
15147+_SOKOL_PRIVATE void _sg_mtl_init_caps(void) {
15148+    #if defined(_SG_TARGET_MACOS)
15149+        _sg.backend = SG_BACKEND_METAL_MACOS;
15150+    #elif defined(_SG_TARGET_IOS)
15151+        #if defined(_SG_TARGET_IOS_SIMULATOR)
15152+            _sg.backend = SG_BACKEND_METAL_SIMULATOR;
15153+        #else
15154+            _sg.backend = SG_BACKEND_METAL_IOS;
15155+        #endif
15156+    #endif
15157+    _sg.features.origin_top_left = true;
15158+    _sg.features.mrt_independent_blend_state = true;
15159+    _sg.features.mrt_independent_write_mask = true;
15160+    _sg.features.compute = true;
15161+    _sg.features.msaa_texture_bindings = true;
15162+    _sg.features.draw_base_vertex = true;
15163+    _sg.features.draw_base_instance = true;
15164+    _sg.features.dual_source_blending = true;
15165+    _sg.features.vertexformat_int10_n2 = true;
15166+
15167+    _sg.features.image_clamp_to_border = false;
15168+    #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)
15169+    if (@available(macOS 12.0, iOS 14.0, *)) {
15170+        _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple7]
15171+                                             || [_sg.mtl.device supportsFamily:MTLGPUFamilyMac2];
15172+        #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 130000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 160000)
15173+        if (!_sg.features.image_clamp_to_border) {
15174+            if (@available(macOS 13.0, iOS 16.0, *)) {
15175+                _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyMetal3];
15176+            }
15177+        }
15178+        #endif
15179+    }
15180+    #endif
15181+
15182+    #if defined(_SG_TARGET_MACOS)
15183+        _sg.limits.max_image_size_2d = 16 * 1024;
15184+        _sg.limits.max_image_size_cube = 16 * 1024;
15185+        _sg.limits.max_image_size_3d = 2 * 1024;
15186+        _sg.limits.max_image_size_array = 16 * 1024;
15187+        _sg.limits.max_image_array_layers = 2 * 1024;
15188+        _sg.limits.max_texture_bindings_per_stage = _sg_min(128, SG_MAX_VIEW_BINDSLOTS);
15189+    #else
15190+        // FIXME: newer iOS devices support 16k textures
15191+        _sg.limits.max_image_size_2d = 8 * 1024;
15192+        _sg.limits.max_image_size_cube = 8 * 1024;
15193+        _sg.limits.max_image_size_3d = 2 * 1024;
15194+        _sg.limits.max_image_size_array = 8 * 1024;
15195+        _sg.limits.max_image_array_layers = 2 * 1024;
15196+        _sg.limits.max_texture_bindings_per_stage = _sg_min(96, SG_MAX_VIEW_BINDSLOTS); // since iPhone8
15197+    #endif
15198+    _sg.limits.max_storage_image_bindings_per_stage = _sg.limits.max_texture_bindings_per_stage;    // shared with texture bindings
15199+    _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min(_SG_MTL_MAX_STAGE_BUFFER_BINDINGS - (SG_MAX_VERTEXBUFFER_BINDSLOTS + SG_MAX_UNIFORMBLOCK_BINDSLOTS), SG_MAX_VIEW_BINDSLOTS);
15200+    _sg.limits.max_color_attachments = _sg_min(8, SG_MAX_COLOR_ATTACHMENTS);
15201+    _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES;
15202+
15203+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]);
15204+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8SN]);
15205+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]);
15206+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]);
15207+    #if defined(_SG_TARGET_MACOS)
15208+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]);
15209+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]);
15210+    #else
15211+        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16]);
15212+        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16SN]);
15213+    #endif
15214+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]);
15215+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]);
15216+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]);
15217+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]);
15218+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8SN]);
15219+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]);
15220+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]);
15221+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]);
15222+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]);
15223+    #if defined(_SG_TARGET_MACOS)
15224+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]);
15225+    #else
15226+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R32F]);
15227+    #endif
15228+    #if defined(_SG_TARGET_MACOS)
15229+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]);
15230+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]);
15231+    #else
15232+        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16]);
15233+        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16SN]);
15234+    #endif
15235+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]);
15236+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]);
15237+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]);
15238+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
15239+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]);
15240+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
15241+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
15242+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
15243+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]);
15244+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SBGR8A8]);
15245+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
15246+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
15247+    #if defined(_SG_TARGET_MACOS)
15248+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
15249+        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
15250+        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
15251+    #else
15252+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
15253+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
15254+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
15255+    #endif
15256+    #if defined(_SG_TARGET_MACOS)
15257+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]);
15258+    #else
15259+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]);
15260+    #endif
15261+    #if defined(_SG_TARGET_MACOS)
15262+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]);
15263+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]);
15264+    #else
15265+        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16]);
15266+        _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]);
15267+    #endif
15268+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
15269+    _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
15270+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
15271+    #if defined(_SG_TARGET_MACOS)
15272+        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
15273+        _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
15274+        _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
15275+    #else
15276+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
15277+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
15278+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
15279+    #endif
15280+    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]);
15281+    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]);
15282+    #if defined(_SG_TARGET_MACOS)
15283+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]);
15284+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]);
15285+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]);
15286+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]);
15287+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]);
15288+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]);
15289+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]);
15290+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]);
15291+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]);
15292+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]);
15293+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]);
15294+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]);
15295+    #else
15296+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]);
15297+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]);
15298+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]);
15299+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]);
15300+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]);
15301+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]);
15302+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]);
15303+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]);
15304+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]);
15305+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]);
15306+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]);
15307+    #endif
15308+
15309+    // compute shader access
15310+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8]);
15311+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8SN]);
15312+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8UI]);
15313+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8SI]);
15314+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16]);
15315+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16SN]);
15316+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16UI]);
15317+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16SI]);
15318+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16F]);
15319+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8]);
15320+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8SN]);
15321+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8UI]);
15322+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG8SI]);
15323+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]);
15324+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]);
15325+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]);
15326+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16]);
15327+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]);
15328+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16UI]);
15329+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16SI]);
15330+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG16F]);
15331+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
15332+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
15333+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
15334+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
15335+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]);
15336+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
15337+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
15338+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
15339+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
15340+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32F]);
15341+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]);
15342+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]);
15343+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
15344+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
15345+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
15346+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
15347+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
15348+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
15349+}
15350+
15351+//-- main Metal backend state and functions ------------------------------------
15352+_SOKOL_PRIVATE void _sg_mtl_setup_backend(const sg_desc* desc) {
15353+    // assume already zero-initialized
15354+    SOKOL_ASSERT(desc);
15355+    SOKOL_ASSERT(desc->environment.metal.device);
15356+    SOKOL_ASSERT(desc->uniform_buffer_size > 0);
15357+    _sg_mtl_init_pool(desc);
15358+    _sg_mtl_clear_state_cache();
15359+    _sg.mtl.valid = true;
15360+    _sg.mtl.ub_size = desc->uniform_buffer_size;
15361+    _sg.mtl.sem = dispatch_semaphore_create(SG_NUM_INFLIGHT_FRAMES);
15362+    _sg.mtl.device = (__bridge id<MTLDevice>) desc->environment.metal.device;
15363+    _sg.mtl.cmd_queue = [_sg.mtl.device newCommandQueue];
15364+
15365+    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
15366+        _sg.mtl.uniform_buffers[i] = [_sg.mtl.device
15367+            newBufferWithLength:(NSUInteger)_sg.mtl.ub_size
15368+            options:MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeShared
15369+        ];
15370+        #if defined(SOKOL_DEBUG)
15371+            _sg.mtl.uniform_buffers[i].label = [NSString stringWithFormat:@"sg-uniform-buffer.%d", i];
15372+        #endif
15373+    }
15374+
15375+    if (desc->metal.force_managed_storage_mode) {
15376+        _sg.mtl.use_shared_storage_mode = false;
15377+    } else if (@available(macOS 10.15, iOS 13.0, *)) {
15378+        // on Intel Macs, always use managed resources even though the
15379+        // device says it supports unified memory (because of texture restrictions)
15380+        const bool is_apple_gpu = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple1];
15381+        if (!is_apple_gpu) {
15382+            _sg.mtl.use_shared_storage_mode = false;
15383+        } else {
15384+            _sg.mtl.use_shared_storage_mode = true;
15385+        }
15386+    } else {
15387+        #if defined(_SG_TARGET_MACOS)
15388+            _sg.mtl.use_shared_storage_mode = false;
15389+        #else
15390+            _sg.mtl.use_shared_storage_mode = true;
15391+        #endif
15392+    }
15393+    _sg_mtl_init_caps();
15394+}
15395+
15396+_SOKOL_PRIVATE void _sg_mtl_discard_backend(void) {
15397+    SOKOL_ASSERT(_sg.mtl.valid);
15398+    // wait for the last frame to finish
15399+    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
15400+        dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER);
15401+    }
15402+    // semaphore must be "relinquished" before destruction
15403+    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
15404+        dispatch_semaphore_signal(_sg.mtl.sem);
15405+    }
15406+    _sg_mtl_garbage_collect(_sg.frame_index + SG_NUM_INFLIGHT_FRAMES + 2);
15407+    _sg_mtl_destroy_pool();
15408+    _sg.mtl.valid = false;
15409+
15410+    _SG_OBJC_RELEASE(_sg.mtl.sem);
15411+    _SG_OBJC_RELEASE(_sg.mtl.device);
15412+    _SG_OBJC_RELEASE(_sg.mtl.cmd_queue);
15413+    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
15414+        _SG_OBJC_RELEASE(_sg.mtl.uniform_buffers[i]);
15415+    }
15416+    // NOTE: MTLCommandBuffer, MTLRenderCommandEncoder and MTLComputeCommandEncoder are auto-released
15417+    _sg.mtl.cmd_buffer = nil;
15418+    _sg.mtl.render_cmd_encoder = nil;
15419+    _sg.mtl.compute_cmd_encoder = nil;
15420+}
15421+
15422+_SOKOL_PRIVATE void _sg_mtl_reset_state_cache(void) {
15423+    _sg_mtl_clear_state_cache();
15424+}
15425+
15426+_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
15427+    SOKOL_ASSERT(buf && desc);
15428+    SOKOL_ASSERT(buf->cmn.size > 0);
15429+    const bool injected = (0 != desc->mtl_buffers[0]);
15430+    MTLResourceOptions mtl_options = _sg_mtl_buffer_resource_options(&buf->cmn.usage);
15431+    for (int slot = 0; slot < buf->cmn.num_slots; slot++) {
15432+        id<MTLBuffer> mtl_buf;
15433+        if (injected) {
15434+            SOKOL_ASSERT(desc->mtl_buffers[slot]);
15435+            mtl_buf = (__bridge id<MTLBuffer>) desc->mtl_buffers[slot];
15436+        } else {
15437+            if (desc->data.ptr) {
15438+                SOKOL_ASSERT(desc->data.size > 0);
15439+                mtl_buf = [_sg.mtl.device newBufferWithBytes:desc->data.ptr length:(NSUInteger)buf->cmn.size options:mtl_options];
15440+            } else {
15441+                mtl_buf = [_sg.mtl.device newBufferWithLength:(NSUInteger)buf->cmn.size options:mtl_options];
15442+            }
15443+            if (nil == mtl_buf) {
15444+                _SG_ERROR(METAL_CREATE_BUFFER_FAILED);
15445+                return SG_RESOURCESTATE_FAILED;
15446+            }
15447+        }
15448+        #if defined(SOKOL_DEBUG)
15449+            if (desc->label) {
15450+                mtl_buf.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot];
15451+            }
15452+        #endif
15453+        buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf);
15454+        _SG_OBJC_RELEASE(mtl_buf);
15455+    }
15456+    return SG_RESOURCESTATE_VALID;
15457+}
15458+
15459+_SOKOL_PRIVATE void _sg_mtl_discard_buffer(_sg_buffer_t* buf) {
15460+    SOKOL_ASSERT(buf);
15461+    for (int slot = 0; slot < buf->cmn.num_slots; slot++) {
15462+        // it's valid to call release resource with '0'
15463+        _sg_mtl_release_resource(_sg.frame_index, buf->mtl.buf[slot]);
15464+    }
15465+}
15466+
15467+_SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unretained id<MTLTexture> mtl_tex, const sg_image_data* data) {
15468+    const int num_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? 1 : img->cmn.num_slices;
15469+    for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) {
15470+        SOKOL_ASSERT(data->mip_levels[mip_index].ptr);
15471+        SOKOL_ASSERT(data->mip_levels[mip_index].size > 0);
15472+        const uint8_t* data_ptr = (const uint8_t*)data->mip_levels[mip_index].ptr;
15473+        const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
15474+        const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
15475+        int bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1);
15476+        int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1);
15477+        /* bytesPerImage special case: https://developer.apple.com/documentation/metal/mtltexture/1515679-replaceregion
15478+
15479+            "Supply a nonzero value only when you copy data to a MTLTextureType3D type texture"
15480+        */
15481+        MTLRegion region;
15482+        int bytes_per_image;
15483+        if (img->cmn.type == SG_IMAGETYPE_3D) {
15484+            const int mip_depth = _sg_miplevel_dim(img->cmn.num_slices, mip_index);
15485+            region = MTLRegionMake3D(0, 0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height, (NSUInteger)mip_depth);
15486+            bytes_per_image = bytes_per_slice;
15487+            // FIXME: apparently the minimal bytes_per_image size for 3D texture is 4 KByte... somehow need to handle this
15488+        } else {
15489+            region = MTLRegionMake2D(0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height);
15490+            bytes_per_image = 0;
15491+        }
15492+
15493+        for (int slice_index = 0; slice_index < num_slices; slice_index++) {
15494+            const int slice_offset = slice_index * bytes_per_slice;
15495+            SOKOL_ASSERT((slice_offset + bytes_per_slice) <= (int)data->mip_levels[mip_index].size);
15496+            [mtl_tex replaceRegion:region
15497+                mipmapLevel:(NSUInteger)mip_index
15498+                slice:(NSUInteger)slice_index
15499+                withBytes:data_ptr + slice_offset
15500+                bytesPerRow:(NSUInteger)bytes_per_row
15501+                bytesPerImage:(NSUInteger)bytes_per_image];
15502+        }
15503+    }
15504+}
15505+
15506+_SOKOL_PRIVATE bool _sg_mtl_init_texdesc(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) {
15507+    mtl_desc.textureType = _sg_mtl_texture_type(img->cmn.type, img->cmn.sample_count > 1);
15508+    mtl_desc.pixelFormat = _sg_mtl_pixel_format(img->cmn.pixel_format);
15509+    if (MTLPixelFormatInvalid == mtl_desc.pixelFormat) {
15510+        _SG_ERROR(METAL_TEXTURE_FORMAT_NOT_SUPPORTED);
15511+        return false;
15512+    }
15513+    mtl_desc.width = (NSUInteger)img->cmn.width;
15514+    mtl_desc.height = (NSUInteger)img->cmn.height;
15515+    if (SG_IMAGETYPE_3D == img->cmn.type) {
15516+        mtl_desc.depth = (NSUInteger)img->cmn.num_slices;
15517+    } else {
15518+        mtl_desc.depth = 1;
15519+    }
15520+    mtl_desc.mipmapLevelCount = (NSUInteger)img->cmn.num_mipmaps;
15521+    if (SG_IMAGETYPE_ARRAY == img->cmn.type) {
15522+        mtl_desc.arrayLength = (NSUInteger)img->cmn.num_slices;
15523+    } else {
15524+        mtl_desc.arrayLength = 1;
15525+    }
15526+    mtl_desc.sampleCount = (NSUInteger)img->cmn.sample_count;
15527+
15528+    const sg_image_usage* usg = &img->cmn.usage;
15529+    const bool any_attachment = usg->color_attachment || usg->resolve_attachment || usg->depth_stencil_attachment;
15530+    MTLTextureUsage mtl_tex_usage = MTLTextureUsageShaderRead;
15531+    if (any_attachment) {
15532+        mtl_tex_usage |= MTLTextureUsageRenderTarget;
15533+    }
15534+    if (img->cmn.usage.storage_image) {
15535+        mtl_tex_usage |= MTLTextureUsageShaderWrite;
15536+    }
15537+    mtl_desc.usage = mtl_tex_usage;
15538+
15539+    MTLResourceOptions mtl_res_options = 0;
15540+    if (any_attachment || img->cmn.usage.storage_image) {
15541+        mtl_res_options |= MTLResourceStorageModePrivate;
15542+    } else {
15543+        mtl_res_options |= _sg_mtl_resource_options_storage_mode_managed_or_shared();
15544+        if (!img->cmn.usage.immutable) {
15545+            mtl_res_options |= MTLResourceCPUCacheModeWriteCombined;
15546+        }
15547+    }
15548+    mtl_desc.resourceOptions = mtl_res_options;
15549+    return true;
15550+}
15551+
15552+_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg_image_desc* desc) {
15553+    SOKOL_ASSERT(img && desc);
15554+    const bool injected = (0 != desc->mtl_textures[0]);
15555+
15556+    // first initialize all Metal resource pool slots to 'empty'
15557+    for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
15558+        img->mtl.tex[i] = _sg_mtl_add_resource(nil);
15559+    }
15560+
15561+    // initialize a Metal texture descriptor
15562+    MTLTextureDescriptor* mtl_desc = [[MTLTextureDescriptor alloc] init];
15563+    if (!_sg_mtl_init_texdesc(mtl_desc, img)) {
15564+        _SG_OBJC_RELEASE(mtl_desc);
15565+        return SG_RESOURCESTATE_FAILED;
15566+    }
15567+    for (int slot = 0; slot < img->cmn.num_slots; slot++) {
15568+        id<MTLTexture> mtl_tex;
15569+        if (injected) {
15570+            SOKOL_ASSERT(desc->mtl_textures[slot]);
15571+            mtl_tex = (__bridge id<MTLTexture>) desc->mtl_textures[slot];
15572+        } else {
15573+            mtl_tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc];
15574+            if (nil == mtl_tex) {
15575+                _SG_OBJC_RELEASE(mtl_desc);
15576+                _SG_ERROR(METAL_CREATE_TEXTURE_FAILED);
15577+                return SG_RESOURCESTATE_FAILED;
15578+            }
15579+            if (desc->data.mip_levels[0].ptr) {
15580+                _sg_mtl_copy_image_data(img, mtl_tex, &desc->data);
15581+            }
15582+        }
15583+        #if defined(SOKOL_DEBUG)
15584+            if (desc->label) {
15585+                mtl_tex.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot];
15586+            }
15587+        #endif
15588+        img->mtl.tex[slot] = _sg_mtl_add_resource(mtl_tex);
15589+        _SG_OBJC_RELEASE(mtl_tex);
15590+    }
15591+    _SG_OBJC_RELEASE(mtl_desc);
15592+    return SG_RESOURCESTATE_VALID;
15593+}
15594+
15595+_SOKOL_PRIVATE void _sg_mtl_discard_image(_sg_image_t* img) {
15596+    SOKOL_ASSERT(img);
15597+    // it's valid to call release resource with a 'null resource'
15598+    for (int slot = 0; slot < img->cmn.num_slots; slot++) {
15599+        _sg_mtl_release_resource(_sg.frame_index, img->mtl.tex[slot]);
15600+    }
15601+}
15602+
15603+_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
15604+    SOKOL_ASSERT(smp && desc);
15605+    id<MTLSamplerState> mtl_smp;
15606+    const bool injected = (0 != desc->mtl_sampler);
15607+    if (injected) {
15608+        SOKOL_ASSERT(desc->mtl_sampler);
15609+        mtl_smp = (__bridge id<MTLSamplerState>) desc->mtl_sampler;
15610+    } else {
15611+        MTLSamplerDescriptor* mtl_desc = [[MTLSamplerDescriptor alloc] init];
15612+        mtl_desc.sAddressMode = _sg_mtl_address_mode(desc->wrap_u);
15613+        mtl_desc.tAddressMode = _sg_mtl_address_mode(desc->wrap_v);
15614+        mtl_desc.rAddressMode = _sg_mtl_address_mode(desc->wrap_w);
15615+        if (_sg.features.image_clamp_to_border) {
15616+            if (@available(macOS 12.0, iOS 14.0, *)) {
15617+                mtl_desc.borderColor  = _sg_mtl_border_color(desc->border_color);
15618+            }
15619+        }
15620+        mtl_desc.minFilter = _sg_mtl_minmag_filter(desc->min_filter);
15621+        mtl_desc.magFilter = _sg_mtl_minmag_filter(desc->mag_filter);
15622+        mtl_desc.mipFilter = _sg_mtl_mipmap_filter(desc->mipmap_filter);
15623+        mtl_desc.lodMinClamp = desc->min_lod;
15624+        mtl_desc.lodMaxClamp = desc->max_lod;
15625+        // FIXME: lodAverage?
15626+        mtl_desc.maxAnisotropy = desc->max_anisotropy;
15627+        mtl_desc.normalizedCoordinates = YES;
15628+        mtl_desc.compareFunction = _sg_mtl_compare_func(desc->compare);
15629+        #if defined(SOKOL_DEBUG)
15630+            if (desc->label) {
15631+                mtl_desc.label = [NSString stringWithUTF8String:desc->label];
15632+            }
15633+        #endif
15634+        mtl_smp = [_sg.mtl.device newSamplerStateWithDescriptor:mtl_desc];
15635+        _SG_OBJC_RELEASE(mtl_desc);
15636+        if (nil == mtl_smp) {
15637+            _SG_ERROR(METAL_CREATE_SAMPLER_FAILED);
15638+            return SG_RESOURCESTATE_FAILED;
15639+        }
15640+    }
15641+    smp->mtl.sampler_state = _sg_mtl_add_resource(mtl_smp);
15642+    _SG_OBJC_RELEASE(mtl_smp);
15643+    return SG_RESOURCESTATE_VALID;
15644+}
15645+
15646+_SOKOL_PRIVATE void _sg_mtl_discard_sampler(_sg_sampler_t* smp) {
15647+    SOKOL_ASSERT(smp);
15648+    // it's valid to call release resource with a 'null resource'
15649+    _sg_mtl_release_resource(_sg.frame_index, smp->mtl.sampler_state);
15650+}
15651+
15652+_SOKOL_PRIVATE id<MTLLibrary> _sg_mtl_compile_library(const char* src) {
15653+    NSError* err = NULL;
15654+    id<MTLLibrary> lib = [_sg.mtl.device
15655+        newLibraryWithSource:[NSString stringWithUTF8String:src]
15656+        options:nil
15657+        error:&err
15658+    ];
15659+    if (err) {
15660+        _SG_ERROR(METAL_SHADER_COMPILATION_FAILED);
15661+        _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]);
15662+    }
15663+    return lib;
15664+}
15665+
15666+_SOKOL_PRIVATE id<MTLLibrary> _sg_mtl_library_from_bytecode(const void* ptr, size_t num_bytes) {
15667+    NSError* err = NULL;
15668+    dispatch_data_t lib_data = dispatch_data_create(ptr, num_bytes, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
15669+    id<MTLLibrary> lib = [_sg.mtl.device newLibraryWithData:lib_data error:&err];
15670+    if (err) {
15671+        _SG_ERROR(METAL_SHADER_CREATION_FAILED);
15672+        _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]);
15673+    }
15674+    _SG_OBJC_RELEASE(lib_data);
15675+    return lib;
15676+}
15677+
15678+_SOKOL_PRIVATE bool _sg_mtl_create_shader_func(const sg_shader_function* func, const char* label, const char* label_ext, _sg_mtl_shader_func_t* res) {
15679+    SOKOL_ASSERT(res->mtl_lib == _SG_MTL_INVALID_SLOT_INDEX);
15680+    SOKOL_ASSERT(res->mtl_func == _SG_MTL_INVALID_SLOT_INDEX);
15681+    id<MTLLibrary> mtl_lib = nil;
15682+    if (func->bytecode.ptr) {
15683+        SOKOL_ASSERT(func->bytecode.size > 0);
15684+        mtl_lib = _sg_mtl_library_from_bytecode(func->bytecode.ptr, func->bytecode.size);
15685+    } else if (func->source) {
15686+        mtl_lib = _sg_mtl_compile_library(func->source);
15687+    }
15688+    if (mtl_lib == nil) {
15689+        return false;
15690+    }
15691+    #if defined(SOKOL_DEBUG)
15692+    if (label) {
15693+        SOKOL_ASSERT(label_ext);
15694+        mtl_lib.label = [NSString stringWithFormat:@"%s.%s", label, label_ext];
15695+    }
15696+    #else
15697+    _SOKOL_UNUSED(label);
15698+    _SOKOL_UNUSED(label_ext);
15699+    #endif
15700+    SOKOL_ASSERT(func->entry);
15701+    id<MTLFunction> mtl_func = [mtl_lib newFunctionWithName:[NSString stringWithUTF8String:func->entry]];
15702+    if (mtl_func == nil) {
15703+        _SG_ERROR(METAL_SHADER_ENTRY_NOT_FOUND);
15704+        _SG_OBJC_RELEASE(mtl_lib);
15705+        return false;
15706+    }
15707+    res->mtl_lib = _sg_mtl_add_resource(mtl_lib);
15708+    res->mtl_func = _sg_mtl_add_resource(mtl_func);
15709+    _SG_OBJC_RELEASE(mtl_lib);
15710+    _SG_OBJC_RELEASE(mtl_func);
15711+    return true;
15712+}
15713+
15714+_SOKOL_PRIVATE void _sg_mtl_discard_shader_func(const _sg_mtl_shader_func_t* func) {
15715+    // it is valid to call _sg_mtl_release_resource with a 'null resource'
15716+    _sg_mtl_release_resource(_sg.frame_index, func->mtl_func);
15717+    _sg_mtl_release_resource(_sg.frame_index, func->mtl_lib);
15718+}
15719+
15720+// NOTE: this is an out-of-range check for MSL bindslots that's also active in release mode
15721+_SOKOL_PRIVATE bool _sg_mtl_ensure_msl_bindslot_ranges(const sg_shader_desc* desc) {
15722+    SOKOL_ASSERT(desc);
15723+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
15724+        const sg_shader_uniform_block* ub = &desc->uniform_blocks[i];
15725+        if (ub->stage != SG_SHADERSTAGE_NONE) {
15726+            if (ub->msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_BINDINGS) {
15727+                _SG_ERROR(METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE);
15728+                return false;
15729+            }
15730+        }
15731+    }
15732+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
15733+        const sg_shader_view* view = &desc->views[i];
15734+        if (view->texture.stage != SG_SHADERSTAGE_NONE) {
15735+            if (view->texture.msl_texture_n >= _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS) {
15736+                _SG_ERROR(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE);
15737+                return false;
15738+            }
15739+        }
15740+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
15741+            if ((view->storage_buffer.msl_buffer_n < _SG_MTL_MAX_STAGE_UB_BINDINGS) ||
15742+                (view->storage_buffer.msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS))
15743+            {
15744+                _SG_ERROR(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE);
15745+                return false;
15746+            }
15747+        }
15748+        if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
15749+            if (view->storage_image.msl_texture_n >= _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS) {
15750+                _SG_ERROR(METAL_STORAGEIMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE);
15751+                return false;
15752+            }
15753+        }
15754+    }
15755+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
15756+        const sg_shader_sampler* smp = &desc->samplers[i];
15757+        if (smp->stage != SG_SHADERSTAGE_NONE) {
15758+            if (smp->msl_sampler_n >= _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS) {
15759+                _SG_ERROR(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE);
15760+                return false;
15761+            }
15762+        }
15763+    }
15764+    return true;
15765+}
15766+
15767+_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
15768+    SOKOL_ASSERT(shd && desc);
15769+
15770+    // do a MSL bindslot range check also in release mode, and if that fails,
15771+    // also fail shader creation
15772+    if (!_sg_mtl_ensure_msl_bindslot_ranges(desc)) {
15773+        return SG_RESOURCESTATE_FAILED;
15774+    }
15775+
15776+    shd->mtl.threads_per_threadgroup = MTLSizeMake(
15777+        (NSUInteger)desc->mtl_threads_per_threadgroup.x,
15778+        (NSUInteger)desc->mtl_threads_per_threadgroup.y,
15779+        (NSUInteger)desc->mtl_threads_per_threadgroup.z);
15780+
15781+    // copy resource bindslot mappings
15782+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
15783+        shd->mtl.ub_buffer_n[i] = desc->uniform_blocks[i].msl_buffer_n;
15784+    }
15785+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
15786+        const sg_shader_view* view = &desc->views[i];
15787+        SOKOL_ASSERT(0 == shd->mtl.view_buffer_texture_n[i]);
15788+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
15789+            shd->mtl.view_buffer_texture_n[i] = view->storage_buffer.msl_buffer_n;
15790+        } else if (view->texture.stage != SG_SHADERSTAGE_NONE) {
15791+            shd->mtl.view_buffer_texture_n[i] = view->texture.msl_texture_n;
15792+        } else if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
15793+            shd->mtl.view_buffer_texture_n[i] = view->storage_image.msl_texture_n;
15794+        }
15795+    }
15796+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
15797+        shd->mtl.smp_sampler_n[i] = desc->samplers[i].msl_sampler_n;
15798+    }
15799+
15800+    // create metal library and function objects
15801+    bool shd_valid = true;
15802+    if (desc->vertex_func.source || desc->vertex_func.bytecode.ptr) {
15803+        shd_valid &= _sg_mtl_create_shader_func(&desc->vertex_func, desc->label, "vs", &shd->mtl.vertex_func);
15804+    }
15805+    if (desc->fragment_func.source || desc->fragment_func.bytecode.ptr) {
15806+        shd_valid &= _sg_mtl_create_shader_func(&desc->fragment_func, desc->label, "fs", &shd->mtl.fragment_func);
15807+    }
15808+    if (desc->compute_func.source || desc->compute_func.bytecode.ptr) {
15809+        shd_valid &= _sg_mtl_create_shader_func(&desc->compute_func, desc->label, "cs", &shd->mtl.compute_func);
15810+    }
15811+    if (!shd_valid) {
15812+        _sg_mtl_discard_shader_func(&shd->mtl.vertex_func);
15813+        _sg_mtl_discard_shader_func(&shd->mtl.fragment_func);
15814+        _sg_mtl_discard_shader_func(&shd->mtl.compute_func);
15815+    }
15816+    return shd_valid ? SG_RESOURCESTATE_VALID : SG_RESOURCESTATE_FAILED;
15817+}
15818+
15819+_SOKOL_PRIVATE void _sg_mtl_discard_shader(_sg_shader_t* shd) {
15820+    SOKOL_ASSERT(shd);
15821+    _sg_mtl_discard_shader_func(&shd->mtl.vertex_func);
15822+    _sg_mtl_discard_shader_func(&shd->mtl.fragment_func);
15823+    _sg_mtl_discard_shader_func(&shd->mtl.compute_func);
15824+}
15825+
15826+_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
15827+    SOKOL_ASSERT(pip && desc);
15828+    _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
15829+    if (pip->cmn.is_compute) {
15830+        NSError* err = NULL;
15831+        MTLComputePipelineDescriptor* cp_desc = [[MTLComputePipelineDescriptor alloc] init];
15832+        cp_desc.computeFunction = _sg_mtl_id(shd->mtl.compute_func.mtl_func);
15833+        cp_desc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true;
15834+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
15835+            const _sg_shader_view_t* view = &shd->cmn.views[i];
15836+            if (view->view_type != SG_VIEWTYPE_STORAGEBUFFER) {
15837+                continue;
15838+            }
15839+            if (!view->sbuf_readonly) {
15840+                continue;
15841+            }
15842+            SOKOL_ASSERT(view->stage == SG_SHADERSTAGE_COMPUTE);
15843+            const NSUInteger mtl_slot = shd->mtl.view_buffer_texture_n[i];
15844+            SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS);
15845+            cp_desc.buffers[mtl_slot].mutability = MTLMutabilityImmutable;
15846+        }
15847+        #if defined(SOKOL_DEBUG)
15848+            if (desc->label) {
15849+                cp_desc.label = [NSString stringWithFormat:@"%s", desc->label];
15850+            }
15851+        #endif
15852+        id<MTLComputePipelineState> mtl_cps = [_sg.mtl.device
15853+            newComputePipelineStateWithDescriptor:cp_desc
15854+            options:MTLPipelineOptionNone
15855+            reflection:nil
15856+            error:&err];
15857+        _SG_OBJC_RELEASE(cp_desc);
15858+        if (nil == mtl_cps) {
15859+            SOKOL_ASSERT(err);
15860+            _SG_ERROR(METAL_CREATE_CPS_FAILED);
15861+            _SG_LOGMSG(METAL_CREATE_CPS_OUTPUT, [err.localizedDescription UTF8String]);
15862+            return SG_RESOURCESTATE_FAILED;
15863+        }
15864+        pip->mtl.cps = _sg_mtl_add_resource(mtl_cps);
15865+        _SG_OBJC_RELEASE(mtl_cps);
15866+        pip->mtl.threads_per_threadgroup = shd->mtl.threads_per_threadgroup;
15867+    } else {
15868+        sg_primitive_type prim_type = desc->primitive_type;
15869+        pip->mtl.prim_type = _sg_mtl_primitive_type(prim_type);
15870+        pip->mtl.index_size = _sg_mtl_index_size(pip->cmn.index_type);
15871+        if (SG_INDEXTYPE_NONE != pip->cmn.index_type) {
15872+            pip->mtl.index_type = _sg_mtl_index_type(pip->cmn.index_type);
15873+        }
15874+        pip->mtl.cull_mode = _sg_mtl_cull_mode(desc->cull_mode);
15875+        pip->mtl.winding = _sg_mtl_winding(desc->face_winding);
15876+        pip->mtl.stencil_ref = desc->stencil.ref;
15877+
15878+        // create vertex-descriptor
15879+        MTLVertexDescriptor* vtx_desc = [MTLVertexDescriptor vertexDescriptor];
15880+        for (NSUInteger attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
15881+            const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index];
15882+            if (a_state->format == SG_VERTEXFORMAT_INVALID) {
15883+                break;
15884+            }
15885+            SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
15886+            SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[a_state->buffer_index]);
15887+            vtx_desc.attributes[attr_index].format = _sg_mtl_vertex_format(a_state->format);
15888+            vtx_desc.attributes[attr_index].offset = (NSUInteger)a_state->offset;
15889+            vtx_desc.attributes[attr_index].bufferIndex = _sg_mtl_vertexbuffer_bindslot((size_t)a_state->buffer_index);
15890+        }
15891+        for (NSUInteger layout_index = 0; layout_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; layout_index++) {
15892+            if (pip->cmn.vertex_buffer_layout_active[layout_index]) {
15893+                const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[layout_index];
15894+                const NSUInteger mtl_vb_slot = _sg_mtl_vertexbuffer_bindslot(layout_index);
15895+                SOKOL_ASSERT(l_state->stride > 0);
15896+                vtx_desc.layouts[mtl_vb_slot].stride = (NSUInteger)l_state->stride;
15897+                vtx_desc.layouts[mtl_vb_slot].stepFunction = _sg_mtl_step_function(l_state->step_func);
15898+                vtx_desc.layouts[mtl_vb_slot].stepRate = (NSUInteger)l_state->step_rate;
15899+            }
15900+        }
15901+
15902+        // render-pipeline descriptor
15903+        MTLRenderPipelineDescriptor* rp_desc = [[MTLRenderPipelineDescriptor alloc] init];
15904+        rp_desc.vertexDescriptor = vtx_desc;
15905+        SOKOL_ASSERT(shd->mtl.vertex_func.mtl_func != _SG_MTL_INVALID_SLOT_INDEX);
15906+        rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.vertex_func.mtl_func);
15907+        SOKOL_ASSERT(shd->mtl.fragment_func.mtl_func != _SG_MTL_INVALID_SLOT_INDEX);
15908+        rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.fragment_func.mtl_func);
15909+        rp_desc.rasterSampleCount = (NSUInteger)desc->sample_count;
15910+        rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled;
15911+        rp_desc.alphaToOneEnabled = NO;
15912+        rp_desc.rasterizationEnabled = YES;
15913+        rp_desc.depthAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format);
15914+        if (desc->depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL) {
15915+            rp_desc.stencilAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format);
15916+        }
15917+        for (NSUInteger i = 0; i < (NSUInteger)desc->color_count; i++) {
15918+            SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS);
15919+            const sg_color_target_state* cs = &desc->colors[i];
15920+            rp_desc.colorAttachments[i].pixelFormat = _sg_mtl_pixel_format(cs->pixel_format);
15921+            rp_desc.colorAttachments[i].writeMask = _sg_mtl_color_write_mask(cs->write_mask);
15922+            rp_desc.colorAttachments[i].blendingEnabled = cs->blend.enabled;
15923+            rp_desc.colorAttachments[i].alphaBlendOperation = _sg_mtl_blend_op(cs->blend.op_alpha);
15924+            rp_desc.colorAttachments[i].rgbBlendOperation = _sg_mtl_blend_op(cs->blend.op_rgb);
15925+            rp_desc.colorAttachments[i].destinationAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_alpha);
15926+            rp_desc.colorAttachments[i].destinationRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_rgb);
15927+            rp_desc.colorAttachments[i].sourceAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_alpha);
15928+            rp_desc.colorAttachments[i].sourceRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_rgb);
15929+        }
15930+        // Set buffer mutability for all buffers (vertex buffers and storage buffers).
15931+        // For vertex buffer it is guaranteed that neither the GPU nor CPU will update their content
15932+        // as long as it is in flight (since dynamic buffers are double-buffered, and vertex-buffers
15933+        // are not updated by the GPU).
15934+        // For storage buffer the same double-buffering applies, and if they are applied
15935+        // to the vertex- or fragment-stage must be declared as readonly in the shader.
15936+        for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
15937+            if (pip->cmn.vertex_buffer_layout_active[i]) {
15938+                const NSUInteger mtl_slot = _sg_mtl_vertexbuffer_bindslot(i);
15939+                rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable;
15940+            }
15941+        }
15942+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
15943+            const _sg_shader_view_t* view = &shd->cmn.views[i];
15944+            if (view->view_type != SG_VIEWTYPE_STORAGEBUFFER) {
15945+                continue;
15946+            }
15947+            const sg_shader_stage stage = view->stage;
15948+            SOKOL_ASSERT(view->stage != SG_SHADERSTAGE_COMPUTE);
15949+            SOKOL_ASSERT(view->sbuf_readonly);
15950+            const NSUInteger mtl_slot = shd->mtl.view_buffer_texture_n[i];
15951+            SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS);
15952+            if (stage == SG_SHADERSTAGE_VERTEX) {
15953+                rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable;
15954+            } else if (stage == SG_SHADERSTAGE_FRAGMENT) {
15955+                rp_desc.fragmentBuffers[mtl_slot].mutability = MTLMutabilityImmutable;
15956+            }
15957+        }
15958+        #if defined(SOKOL_DEBUG)
15959+            if (desc->label) {
15960+                rp_desc.label = [NSString stringWithFormat:@"%s", desc->label];
15961+            }
15962+        #endif
15963+        NSError* err = NULL;
15964+        id<MTLRenderPipelineState> mtl_rps = [_sg.mtl.device newRenderPipelineStateWithDescriptor:rp_desc error:&err];
15965+        _SG_OBJC_RELEASE(rp_desc);
15966+        if (nil == mtl_rps) {
15967+            SOKOL_ASSERT(err);
15968+            _SG_ERROR(METAL_CREATE_RPS_FAILED);
15969+            _SG_LOGMSG(METAL_CREATE_RPS_OUTPUT, [err.localizedDescription UTF8String]);
15970+            return SG_RESOURCESTATE_FAILED;
15971+        }
15972+        pip->mtl.rps = _sg_mtl_add_resource(mtl_rps);
15973+        _SG_OBJC_RELEASE(mtl_rps);
15974+
15975+        // depth-stencil-state
15976+        MTLDepthStencilDescriptor* ds_desc = [[MTLDepthStencilDescriptor alloc] init];
15977+        ds_desc.depthCompareFunction = _sg_mtl_compare_func(desc->depth.compare);
15978+        ds_desc.depthWriteEnabled = desc->depth.write_enabled;
15979+        if (desc->stencil.enabled) {
15980+            const sg_stencil_face_state* sb = &desc->stencil.back;
15981+            ds_desc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
15982+            ds_desc.backFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sb->fail_op);
15983+            ds_desc.backFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sb->depth_fail_op);
15984+            ds_desc.backFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sb->pass_op);
15985+            ds_desc.backFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sb->compare);
15986+            ds_desc.backFaceStencil.readMask = desc->stencil.read_mask;
15987+            ds_desc.backFaceStencil.writeMask = desc->stencil.write_mask;
15988+            const sg_stencil_face_state* sf = &desc->stencil.front;
15989+            ds_desc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
15990+            ds_desc.frontFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sf->fail_op);
15991+            ds_desc.frontFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sf->depth_fail_op);
15992+            ds_desc.frontFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sf->pass_op);
15993+            ds_desc.frontFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sf->compare);
15994+            ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask;
15995+            ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask;
15996+        }
15997+        #if defined(SOKOL_DEBUG)
15998+            if (desc->label) {
15999+                ds_desc.label = [NSString stringWithFormat:@"%s.dss", desc->label];
16000+            }
16001+        #endif
16002+        id<MTLDepthStencilState> mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc];
16003+        _SG_OBJC_RELEASE(ds_desc);
16004+        if (nil == mtl_dss) {
16005+            _SG_ERROR(METAL_CREATE_DSS_FAILED);
16006+            return SG_RESOURCESTATE_FAILED;
16007+        }
16008+        pip->mtl.dss = _sg_mtl_add_resource(mtl_dss);
16009+        _SG_OBJC_RELEASE(mtl_dss);
16010+    }
16011+    return SG_RESOURCESTATE_VALID;
16012+}
16013+
16014+_SOKOL_PRIVATE void _sg_mtl_discard_pipeline(_sg_pipeline_t* pip) {
16015+    SOKOL_ASSERT(pip);
16016+    // it's valid to call release resource with a 'null resource'
16017+    _sg_mtl_release_resource(_sg.frame_index, pip->mtl.cps);
16018+    _sg_mtl_release_resource(_sg.frame_index, pip->mtl.rps);
16019+    _sg_mtl_release_resource(_sg.frame_index, pip->mtl.dss);
16020+}
16021+
16022+_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_view(_sg_view_t* view, const sg_view_desc* desc) {
16023+    SOKOL_ASSERT(view && desc);
16024+    _SOKOL_UNUSED(desc);
16025+    if ((SG_VIEWTYPE_TEXTURE == view->cmn.type) || (SG_VIEWTYPE_STORAGEIMAGE == view->cmn.type)) {
16026+        const _sg_image_view_common_t* cmn = &view->cmn.img;
16027+        const _sg_image_t* img = _sg_image_ref_ptr(&cmn->ref);
16028+        SOKOL_ASSERT(cmn->mip_level_count >= 1);
16029+        SOKOL_ASSERT(cmn->slice_count >= 1);
16030+        for (int slot = 0; slot < img->cmn.num_slots; slot++) {
16031+            SOKOL_ASSERT(img->mtl.tex[slot] != _SG_MTL_INVALID_SLOT_INDEX);
16032+            id<MTLTexture> mtl_tex_view = [_sg_mtl_id(img->mtl.tex[slot])
16033+                newTextureViewWithPixelFormat: _sg_mtl_pixel_format(img->cmn.pixel_format)
16034+                textureType: _sg_mtl_texture_type(img->cmn.type, img->cmn.sample_count > 1)
16035+                levels: NSMakeRange((NSUInteger)cmn->mip_level, (NSUInteger)cmn->mip_level_count)
16036+                slices: NSMakeRange((NSUInteger)cmn->slice, (NSUInteger)cmn->slice_count)];
16037+            #if defined(SOKOL_DEBUG)
16038+                if (desc->label) {
16039+                    mtl_tex_view.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot];
16040+                }
16041+            #endif
16042+            view->mtl.tex_view[slot] = _sg_mtl_add_resource(mtl_tex_view);
16043+            _SG_OBJC_RELEASE(mtl_tex_view);
16044+        }
16045+    }
16046+    return SG_RESOURCESTATE_VALID;
16047+}
16048+
16049+_SOKOL_PRIVATE void _sg_mtl_discard_view(_sg_view_t* view) {
16050+    SOKOL_ASSERT(view);
16051+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
16052+        // it's valid to call _sg_mtl_release_resource with a null handle
16053+        _sg_mtl_release_resource(_sg.frame_index, view->mtl.tex_view[i]);
16054+    }
16055+}
16056+
16057+_SOKOL_PRIVATE void _sg_mtl_bind_uniform_buffers(void) {
16058+    // In the Metal backend, uniform buffer bindings happen once in sg_begin_pass() and
16059+    // remain valid for the entire pass. Only binding offsets will be updated
16060+    // in sg_apply_uniforms()
16061+    if (_sg.cur_pass.is_compute) {
16062+        SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16063+        for (size_t slot = 0; slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS; slot++) {
16064+            [_sg.mtl.compute_cmd_encoder
16065+                setBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index]
16066+                offset:0
16067+                atIndex:slot];
16068+        }
16069+    } else {
16070+        SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16071+        for (size_t slot = 0; slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS; slot++) {
16072+            [_sg.mtl.render_cmd_encoder
16073+                setVertexBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index]
16074+                offset:0
16075+                atIndex:slot];
16076+            [_sg.mtl.render_cmd_encoder
16077+                setFragmentBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index]
16078+                offset:0
16079+                atIndex:slot];
16080+        }
16081+    }
16082+}
16083+
16084+_SOKOL_PRIVATE void _sg_mtl_begin_compute_pass(const sg_pass* pass) {
16085+    SOKOL_ASSERT(pass); (void)pass;
16086+    SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer);
16087+    SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder);
16088+    SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder);
16089+
16090+    _sg.mtl.compute_cmd_encoder = [_sg.mtl.cmd_buffer computeCommandEncoder];
16091+    if (nil == _sg.mtl.compute_cmd_encoder) {
16092+        _sg.cur_pass.valid = false;
16093+        return;
16094+    }
16095+
16096+    #if defined(SOKOL_DEBUG)
16097+    if (pass->label) {
16098+        _sg.mtl.compute_cmd_encoder.label = [NSString stringWithUTF8String:pass->label];
16099+    }
16100+    #endif
16101+}
16102+
16103+_SOKOL_PRIVATE void _sg_mtl_begin_render_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
16104+    SOKOL_ASSERT(pass && atts);
16105+    SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer);
16106+    SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder);
16107+    SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder);
16108+
16109+    const sg_swapchain* swapchain = &pass->swapchain;
16110+    const sg_pass_action* action = &pass->action;
16111+
16112+    MTLRenderPassDescriptor* pass_desc = [MTLRenderPassDescriptor renderPassDescriptor];
16113+    SOKOL_ASSERT(pass_desc);
16114+    if (!atts->empty) {
16115+        // setup pass descriptor for offscreen rendering
16116+        for (NSUInteger i = 0; i < (NSUInteger)atts->num_color_views; i++) {
16117+            const _sg_view_t* color_view = atts->color_views[i];
16118+            SOKOL_ASSERT(color_view);
16119+            const _sg_view_t* resolve_view = atts->resolve_views[i];
16120+            const _sg_image_t* color_img = _sg_image_ref_ptr(&color_view->cmn.img.ref);
16121+            SOKOL_ASSERT(color_img->slot.state == SG_RESOURCESTATE_VALID);
16122+            SOKOL_ASSERT(color_img->cmn.active_slot == 0);
16123+            SOKOL_ASSERT(color_img->mtl.tex[0] != _SG_MTL_INVALID_SLOT_INDEX);
16124+            pass_desc.colorAttachments[i].loadAction = _sg_mtl_load_action(action->colors[i].load_action);
16125+            pass_desc.colorAttachments[i].storeAction = _sg_mtl_store_action(action->colors[i].store_action, resolve_view != 0);
16126+            sg_color c = action->colors[i].clear_value;
16127+            pass_desc.colorAttachments[i].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a);
16128+            pass_desc.colorAttachments[i].texture = _sg_mtl_id(color_img->mtl.tex[0]);
16129+            pass_desc.colorAttachments[i].level = (NSUInteger)color_view->cmn.img.mip_level;
16130+            switch (color_img->cmn.type) {
16131+                case SG_IMAGETYPE_CUBE:
16132+                case SG_IMAGETYPE_ARRAY:
16133+                    pass_desc.colorAttachments[i].slice = (NSUInteger)color_view->cmn.img.slice;
16134+                    break;
16135+                case SG_IMAGETYPE_3D:
16136+                    pass_desc.colorAttachments[i].depthPlane = (NSUInteger)color_view->cmn.img.slice;
16137+                    break;
16138+                default: break;
16139+            }
16140+            if (resolve_view) {
16141+                const _sg_image_t* resolve_img = _sg_image_ref_ptr(&resolve_view->cmn.img.ref);
16142+                SOKOL_ASSERT(resolve_img->slot.state == SG_RESOURCESTATE_VALID);
16143+                SOKOL_ASSERT(resolve_img->cmn.active_slot == 0);
16144+                SOKOL_ASSERT(resolve_img->mtl.tex[0] != _SG_MTL_INVALID_SLOT_INDEX);
16145+                pass_desc.colorAttachments[i].resolveTexture = _sg_mtl_id(resolve_img->mtl.tex[0]);
16146+                pass_desc.colorAttachments[i].resolveLevel = (NSUInteger)resolve_view->cmn.img.mip_level;
16147+                switch (resolve_img->cmn.type) {
16148+                    case SG_IMAGETYPE_CUBE:
16149+                    case SG_IMAGETYPE_ARRAY:
16150+                        pass_desc.colorAttachments[i].resolveSlice = (NSUInteger)resolve_view->cmn.img.slice;
16151+                        break;
16152+                    case SG_IMAGETYPE_3D:
16153+                        pass_desc.colorAttachments[i].resolveDepthPlane = (NSUInteger)resolve_view->cmn.img.slice;
16154+                        break;
16155+                    default: break;
16156+                }
16157+            }
16158+        }
16159+        if (atts->ds_view) {
16160+            const _sg_view_t* ds_view = atts->ds_view;
16161+            const _sg_image_t* ds_img = _sg_image_ref_ptr(&ds_view->cmn.img.ref);
16162+            SOKOL_ASSERT(ds_img->slot.state == SG_RESOURCESTATE_VALID);
16163+            SOKOL_ASSERT(ds_img->cmn.active_slot == 0);
16164+            SOKOL_ASSERT(ds_img->mtl.tex[0] != _SG_MTL_INVALID_SLOT_INDEX);
16165+            pass_desc.depthAttachment.texture = _sg_mtl_id(ds_img->mtl.tex[0]);
16166+            pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.load_action);
16167+            pass_desc.depthAttachment.storeAction = _sg_mtl_store_action(action->depth.store_action, false);
16168+            pass_desc.depthAttachment.clearDepth = action->depth.clear_value;
16169+            switch (ds_img->cmn.type) {
16170+                case SG_IMAGETYPE_CUBE:
16171+                case SG_IMAGETYPE_ARRAY:
16172+                    pass_desc.depthAttachment.slice = (NSUInteger)ds_view->cmn.img.slice;
16173+                    break;
16174+                case SG_IMAGETYPE_3D:
16175+                    pass_desc.depthAttachment.resolveDepthPlane = (NSUInteger)ds_view->cmn.img.slice;
16176+                    break;
16177+                default: break;
16178+            }
16179+            if (_sg_is_depth_stencil_format(ds_img->cmn.pixel_format)) {
16180+                pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_img->mtl.tex[0]);
16181+                pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.load_action);
16182+                pass_desc.stencilAttachment.storeAction = _sg_mtl_store_action(action->depth.store_action, false);
16183+                pass_desc.stencilAttachment.clearStencil = action->stencil.clear_value;
16184+                switch (ds_img->cmn.type) {
16185+                    case SG_IMAGETYPE_CUBE:
16186+                    case SG_IMAGETYPE_ARRAY:
16187+                        pass_desc.stencilAttachment.slice = (NSUInteger)ds_view->cmn.img.slice;
16188+                        break;
16189+                    case SG_IMAGETYPE_3D:
16190+                        pass_desc.stencilAttachment.resolveDepthPlane = (NSUInteger)ds_view->cmn.img.slice;
16191+                        break;
16192+                    default: break;
16193+                }
16194+            }
16195+        }
16196+    } else {
16197+        // setup pass descriptor for swapchain rendering
16198+        //
16199+        // NOTE: at least in macOS Sonoma this no longer seems to be the case, the
16200+        // current drawable is also valid in a minimized window
16201+        // ===
16202+        // an MTKView current_drawable will not be valid if window is minimized, don't do any rendering in this case
16203+        if (0 == swapchain->metal.current_drawable) {
16204+            _sg.cur_pass.valid = false;
16205+            return;
16206+        }
16207+        // pin the swapchain resources into memory so that they outlive their command buffer
16208+        // (this is necessary because the command buffer doesn't retain references)
16209+        int pass_desc_ref = _sg_mtl_add_resource(pass_desc);
16210+        _sg_mtl_release_resource(_sg.frame_index, pass_desc_ref);
16211+
16212+        _sg.mtl.cur_drawable = (__bridge id<CAMetalDrawable>) swapchain->metal.current_drawable;
16213+        if (swapchain->sample_count > 1) {
16214+            // multi-sampling: render into msaa texture, resolve into drawable texture
16215+            id<MTLTexture> msaa_tex = (__bridge id<MTLTexture>) swapchain->metal.msaa_color_texture;
16216+            SOKOL_ASSERT(msaa_tex != nil);
16217+            pass_desc.colorAttachments[0].texture = msaa_tex;
16218+            pass_desc.colorAttachments[0].resolveTexture = _sg.mtl.cur_drawable.texture;
16219+            pass_desc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve;
16220+        } else {
16221+            // non-msaa: render into current_drawable
16222+            pass_desc.colorAttachments[0].texture = _sg.mtl.cur_drawable.texture;
16223+            pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore;
16224+        }
16225+        pass_desc.colorAttachments[0].loadAction = _sg_mtl_load_action(action->colors[0].load_action);
16226+        const sg_color c = action->colors[0].clear_value;
16227+        pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a);
16228+
16229+        // optional depth-stencil texture
16230+        if (swapchain->metal.depth_stencil_texture) {
16231+            id<MTLTexture> ds_tex = (__bridge id<MTLTexture>) swapchain->metal.depth_stencil_texture;
16232+            SOKOL_ASSERT(ds_tex != nil);
16233+            pass_desc.depthAttachment.texture = ds_tex;
16234+            pass_desc.depthAttachment.storeAction = MTLStoreActionDontCare;
16235+            pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.load_action);
16236+            pass_desc.depthAttachment.clearDepth = action->depth.clear_value;
16237+            if (_sg_is_depth_stencil_format(swapchain->depth_format)) {
16238+                pass_desc.stencilAttachment.texture = ds_tex;
16239+                pass_desc.stencilAttachment.storeAction = MTLStoreActionDontCare;
16240+                pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.load_action);
16241+                pass_desc.stencilAttachment.clearStencil = action->stencil.clear_value;
16242+            }
16243+        }
16244+    }
16245+
16246+    // NOTE: at least in macOS Sonoma, the following is no longer the case, a valid
16247+    // render command encoder is also returned in a minimized window
16248+    // ===
16249+    // create a render command encoder, this might return nil if window is minimized
16250+    _sg.mtl.render_cmd_encoder = [_sg.mtl.cmd_buffer renderCommandEncoderWithDescriptor:pass_desc];
16251+    if (nil == _sg.mtl.render_cmd_encoder) {
16252+        _sg.cur_pass.valid = false;
16253+        return;
16254+    }
16255+
16256+    #if defined(SOKOL_DEBUG)
16257+    if (pass->label) {
16258+        _sg.mtl.render_cmd_encoder.label = [NSString stringWithUTF8String:pass->label];
16259+    }
16260+    #endif
16261+}
16262+
16263+_SOKOL_PRIVATE void _sg_mtl_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
16264+    SOKOL_ASSERT(pass && atts);
16265+    SOKOL_ASSERT(_sg.mtl.cmd_queue);
16266+    SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder);
16267+    SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder);
16268+    SOKOL_ASSERT(nil == _sg.mtl.cur_drawable);
16269+    _sg_mtl_clear_state_cache();
16270+
16271+    // if this is the first pass in the frame, create one command buffer and blit-cmd-encoder for the entire frame
16272+    if (nil == _sg.mtl.cmd_buffer) {
16273+        // block until the oldest frame in flight has finished
16274+        dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER);
16275+        if (_sg.desc.metal.use_command_buffer_with_retained_references) {
16276+            _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBuffer];
16277+        } else {
16278+            _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBufferWithUnretainedReferences];
16279+        }
16280+        [_sg.mtl.cmd_buffer enqueue];
16281+        [_sg.mtl.cmd_buffer addCompletedHandler:^(id<MTLCommandBuffer> cmd_buf) {
16282+            // NOTE: this code is called on a different thread!
16283+            _SOKOL_UNUSED(cmd_buf);
16284+            dispatch_semaphore_signal(_sg.mtl.sem);
16285+        }];
16286+    }
16287+
16288+    // if this is first pass in frame, get uniform buffer base pointer
16289+    if (0 == _sg.mtl.cur_ub_base_ptr) {
16290+        _sg.mtl.cur_ub_base_ptr = (uint8_t*)[_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] contents];
16291+    }
16292+
16293+    if (pass->compute) {
16294+        _sg_mtl_begin_compute_pass(pass);
16295+    } else {
16296+        _sg_mtl_begin_render_pass(pass, atts);
16297+    }
16298+
16299+    // bind uniform buffers, those bindings remain valid for the entire pass
16300+    if (_sg.cur_pass.valid) {
16301+        _sg_mtl_bind_uniform_buffers();
16302+    }
16303+}
16304+
16305+_SOKOL_PRIVATE void _sg_mtl_end_pass(const _sg_attachments_ptrs_t* atts) {
16306+    _SOKOL_UNUSED(atts);
16307+    if (nil != _sg.mtl.render_cmd_encoder) {
16308+        [_sg.mtl.render_cmd_encoder endEncoding];
16309+        // NOTE: MTLRenderCommandEncoder is autoreleased
16310+        _sg.mtl.render_cmd_encoder = nil;
16311+    }
16312+    if (nil != _sg.mtl.compute_cmd_encoder) {
16313+        [_sg.mtl.compute_cmd_encoder endEncoding];
16314+        // NOTE: MTLComputeCommandEncoder is autoreleased
16315+        _sg.mtl.compute_cmd_encoder = nil;
16316+    }
16317+    // if this is a swapchain pass, present the drawable
16318+    if (nil != _sg.mtl.cur_drawable) {
16319+        [_sg.mtl.cmd_buffer presentDrawable:_sg.mtl.cur_drawable];
16320+        _sg.mtl.cur_drawable = nil;
16321+    }
16322+}
16323+
16324+_SOKOL_PRIVATE void _sg_mtl_commit(void) {
16325+    SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder);
16326+    SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder);
16327+
16328+    // commit the frame's command buffer
16329+    if (_sg.mtl.cmd_buffer) {
16330+        [_sg.mtl.cmd_buffer commit];
16331+    }
16332+
16333+    // garbage-collect resources pending for release
16334+    _sg_mtl_garbage_collect(_sg.frame_index);
16335+
16336+    // rotate uniform buffer slot
16337+    if (++_sg.mtl.cur_frame_rotate_index >= SG_NUM_INFLIGHT_FRAMES) {
16338+        _sg.mtl.cur_frame_rotate_index = 0;
16339+    }
16340+    _sg.mtl.cur_ub_offset = 0;
16341+    _sg.mtl.cur_ub_base_ptr = 0;
16342+    // NOTE: MTLCommandBuffer is autoreleased
16343+    _sg.mtl.cmd_buffer = nil;
16344+}
16345+
16346+_SOKOL_PRIVATE void _sg_mtl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
16347+    SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16348+    SOKOL_ASSERT(_sg.cur_pass.dim.height > 0);
16349+    MTLViewport vp;
16350+    vp.originX = (double) x;
16351+    vp.originY = (double) (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h)));
16352+    vp.width   = (double) w;
16353+    vp.height  = (double) h;
16354+    vp.znear   = 0.0;
16355+    vp.zfar    = 1.0;
16356+    [_sg.mtl.render_cmd_encoder setViewport:vp];
16357+}
16358+
16359+_SOKOL_PRIVATE void _sg_mtl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
16360+    SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16361+    SOKOL_ASSERT(_sg.cur_pass.dim.width > 0);
16362+    SOKOL_ASSERT(_sg.cur_pass.dim.height > 0);
16363+    // clip against framebuffer rect
16364+    const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height);
16365+    MTLScissorRect r;
16366+    r.x = (NSUInteger)clip.x;
16367+    r.y = (NSUInteger) (origin_top_left ? clip.y : (_sg.cur_pass.dim.height - (clip.y + clip.h)));
16368+    r.width = (NSUInteger)clip.w;
16369+    r.height = (NSUInteger)clip.h;
16370+    [_sg.mtl.render_cmd_encoder setScissorRect:r];
16371+}
16372+
16373+_SOKOL_PRIVATE void _sg_mtl_apply_pipeline(_sg_pipeline_t* pip) {
16374+    SOKOL_ASSERT(pip);
16375+    if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_pip, &pip->slot)) {
16376+        _sg.mtl.cache.cur_pip = _sg_sref(&pip->slot);
16377+        if (pip->cmn.is_compute) {
16378+            SOKOL_ASSERT(_sg.cur_pass.is_compute);
16379+            SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16380+            SOKOL_ASSERT(pip->mtl.cps != _SG_MTL_INVALID_SLOT_INDEX);
16381+            [_sg.mtl.compute_cmd_encoder setComputePipelineState:_sg_mtl_id(pip->mtl.cps)];
16382+        } else {
16383+            SOKOL_ASSERT(!_sg.cur_pass.is_compute);
16384+            SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16385+            sg_color c = pip->cmn.blend_color;
16386+            [_sg.mtl.render_cmd_encoder setBlendColorRed:c.r green:c.g blue:c.b alpha:c.a];
16387+            _sg_stats_inc(metal.pipeline.num_set_blend_color);
16388+            [_sg.mtl.render_cmd_encoder setCullMode:pip->mtl.cull_mode];
16389+            _sg_stats_inc(metal.pipeline.num_set_cull_mode);
16390+            [_sg.mtl.render_cmd_encoder setFrontFacingWinding:pip->mtl.winding];
16391+            _sg_stats_inc(metal.pipeline.num_set_front_facing_winding);
16392+            [_sg.mtl.render_cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref];
16393+            _sg_stats_inc(metal.pipeline.num_set_stencil_reference_value);
16394+            [_sg.mtl.render_cmd_encoder setDepthBias:pip->cmn.depth.bias slopeScale:pip->cmn.depth.bias_slope_scale clamp:pip->cmn.depth.bias_clamp];
16395+            _sg_stats_inc(metal.pipeline.num_set_depth_bias);
16396+            SOKOL_ASSERT(pip->mtl.rps != _SG_MTL_INVALID_SLOT_INDEX);
16397+            [_sg.mtl.render_cmd_encoder setRenderPipelineState:_sg_mtl_id(pip->mtl.rps)];
16398+            _sg_stats_inc(metal.pipeline.num_set_render_pipeline_state);
16399+            SOKOL_ASSERT(pip->mtl.dss != _SG_MTL_INVALID_SLOT_INDEX);
16400+            [_sg.mtl.render_cmd_encoder setDepthStencilState:_sg_mtl_id(pip->mtl.dss)];
16401+            _sg_stats_inc(metal.pipeline.num_set_depth_stencil_state);
16402+        }
16403+    }
16404+}
16405+
16406+_SOKOL_PRIVATE int _sg_mtl_cache_buf_cmp(const _sg_mtl_cache_buf_t* item, const _sg_slot_t* slot, int active_slot, int offset) {
16407+    int res = _SG_MTL_CACHE_CMP_EQUAL;
16408+    if (!_sg_sref_slot_eql(&item->sref, slot)) {
16409+        res |= _SG_MTL_CACHE_CMP_SREF;
16410+    }
16411+    if (item->active_slot != active_slot) {
16412+        res |= _SG_MTL_CACHE_CMP_ACTIVESLOT;
16413+    }
16414+    if (item->offset != offset) {
16415+        res |= _SG_MTL_CACHE_CMP_OFFSET;
16416+    }
16417+    return res;
16418+}
16419+
16420+_SOKOL_PRIVATE void _sg_mtl_cache_buf_upd(_sg_mtl_cache_buf_t* item, const _sg_slot_t* slot, int active_slot, int offset) {
16421+    item->sref = _sg_sref(slot);
16422+    item->offset = offset;
16423+    item->active_slot = active_slot;
16424+}
16425+
16426+_SOKOL_PRIVATE int _sg_mtl_cache_tex_cmp(const _sg_mtl_cache_tex_t* item, const _sg_slot_t* slot, int active_slot) {
16427+    int res = _SG_MTL_CACHE_CMP_EQUAL;
16428+    if (!_sg_sref_slot_eql(&item->sref, slot)) {
16429+        res |= _SG_MTL_CACHE_CMP_SREF;
16430+    }
16431+    if (item->active_slot != active_slot) {
16432+        res |= _SG_MTL_CACHE_CMP_ACTIVESLOT;
16433+    }
16434+    return res;
16435+}
16436+
16437+_SOKOL_PRIVATE void _sg_mtl_cache_tex_upd(_sg_mtl_cache_tex_t* item, const _sg_slot_t* slot, int active_slot) {
16438+    item->sref = _sg_sref(slot);
16439+    item->active_slot = active_slot;
16440+}
16441+
16442+
16443+_SOKOL_PRIVATE bool _sg_mtl_apply_bindings(_sg_bindings_ptrs_t* bnd) {
16444+    SOKOL_ASSERT(bnd);
16445+    SOKOL_ASSERT(bnd->pip);
16446+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
16447+
16448+    // don't set vertex- and index-buffers in compute passes
16449+    if (!_sg.cur_pass.is_compute) {
16450+        SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16451+        // store index buffer binding, this will be needed later in sg_draw()
16452+        _sg.mtl.cache.cur_ibuf = _sg_buffer_ref(bnd->ib);
16453+        _sg.mtl.cache.cur_ibuf_offset = bnd->ib_offset;
16454+        if (bnd->ib) {
16455+            SOKOL_ASSERT(bnd->pip->cmn.index_type != SG_INDEXTYPE_NONE);
16456+        } else {
16457+            SOKOL_ASSERT(bnd->pip->cmn.index_type == SG_INDEXTYPE_NONE);
16458+        }
16459+        // apply vertex buffers
16460+        for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
16461+            const _sg_buffer_t* vb = bnd->vbs[i];
16462+            if (vb == 0) {
16463+                continue;
16464+            }
16465+            const NSUInteger mtl_slot = _sg_mtl_vertexbuffer_bindslot(i);
16466+            SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS);
16467+            const int active_slot = vb->cmn.active_slot;
16468+            SOKOL_ASSERT(vb->mtl.buf[active_slot] != _SG_MTL_INVALID_SLOT_INDEX);
16469+            const int offset = bnd->vb_offsets[i];
16470+            _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_vsbufs[i];
16471+            const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &vb->slot, active_slot, offset);
16472+            if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16473+                _sg_mtl_cache_buf_upd(cache_item, &vb->slot, active_slot, offset);
16474+                if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) {
16475+                    // only vertex buffer offset has changed
16476+                    [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)offset atIndex:mtl_slot];
16477+                    _sg_stats_inc(metal.bindings.num_set_vertex_buffer_offset);
16478+                } else {
16479+                    [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(vb->mtl.buf[active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot];
16480+                    _sg_stats_inc(metal.bindings.num_set_vertex_buffer);
16481+                }
16482+            } else {
16483+                _sg_stats_inc(metal.bindings.num_skip_redundant_vertex_buffer);
16484+            }
16485+        }
16486+    }
16487+
16488+    // apply view bindings (textures, storage images, storage buffers)
16489+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
16490+        const _sg_view_t* view = bnd->views[i];
16491+        if (0 == view) {
16492+            continue;
16493+        }
16494+        const _sg_shader_view_t* shd_view = &shd->cmn.views[i];
16495+        const sg_shader_stage stage = shd_view->stage;
16496+        SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX)
16497+            || (stage == SG_SHADERSTAGE_FRAGMENT)
16498+            || (stage == SG_SHADERSTAGE_COMPUTE));
16499+        SOKOL_ASSERT((shd_view->view_type == SG_VIEWTYPE_TEXTURE)
16500+            || (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER)
16501+            || (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE));
16502+        const NSUInteger mtl_slot = shd->mtl.view_buffer_texture_n[i];
16503+
16504+        // same handling for textures and storage images
16505+        if ((shd_view->view_type == SG_VIEWTYPE_TEXTURE) || (shd_view->view_type == SG_VIEWTYPE_STORAGEIMAGE)) {
16506+            SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_TEXTURE_BINDINGS);
16507+            const int active_slot = _sg_image_ref_ptr(&view->cmn.img.ref)->cmn.active_slot;
16508+            SOKOL_ASSERT(view->mtl.tex_view[active_slot] != _SG_MTL_INVALID_SLOT_INDEX);
16509+            if (stage == SG_SHADERSTAGE_VERTEX) {
16510+                SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16511+                _sg_mtl_cache_tex_t* cache_item = &_sg.mtl.cache.cur_vstexs[mtl_slot];
16512+                const int cmp = _sg_mtl_cache_tex_cmp(cache_item, &view->slot, active_slot);
16513+                if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16514+                    _sg_mtl_cache_tex_upd(cache_item, &view->slot, active_slot);
16515+                    [_sg.mtl.render_cmd_encoder setVertexTexture:_sg_mtl_id(view->mtl.tex_view[active_slot]) atIndex:mtl_slot];
16516+                    _sg_stats_inc(metal.bindings.num_set_vertex_texture);
16517+                } else {
16518+                    _sg_stats_inc(metal.bindings.num_skip_redundant_vertex_texture);
16519+                }
16520+            } else if (stage == SG_SHADERSTAGE_FRAGMENT) {
16521+                SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16522+                _sg_mtl_cache_tex_t* cache_item = &_sg.mtl.cache.cur_fstexs[mtl_slot];
16523+                const int cmp = _sg_mtl_cache_tex_cmp(cache_item, &view->slot, active_slot);
16524+                if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16525+                    _sg_mtl_cache_tex_upd(cache_item, &view->slot, active_slot);
16526+                    [_sg.mtl.render_cmd_encoder setFragmentTexture:_sg_mtl_id(view->mtl.tex_view[active_slot]) atIndex:mtl_slot];
16527+                    _sg_stats_inc(metal.bindings.num_set_fragment_texture);
16528+                } else {
16529+                    _sg_stats_inc(metal.bindings.num_skip_redundant_fragment_texture);
16530+                }
16531+            } else if (stage == SG_SHADERSTAGE_COMPUTE) {
16532+                SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16533+                _sg_mtl_cache_tex_t* cache_item = &_sg.mtl.cache.cur_cstexs[mtl_slot];
16534+                const int cmp = _sg_mtl_cache_tex_cmp(cache_item, &view->slot, active_slot);
16535+                if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16536+                    _sg_mtl_cache_tex_upd(cache_item, &view->slot, active_slot);
16537+                    [_sg.mtl.compute_cmd_encoder setTexture:_sg_mtl_id(view->mtl.tex_view[active_slot]) atIndex:mtl_slot];
16538+                    _sg_stats_inc(metal.bindings.num_set_compute_texture);
16539+                } else {
16540+                    _sg_stats_inc(metal.bindings.num_skip_redundant_compute_texture);
16541+                }
16542+            } else SOKOL_UNREACHABLE;
16543+        } else if (shd_view->view_type == SG_VIEWTYPE_STORAGEBUFFER) {
16544+            SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS);
16545+            const _sg_buffer_t* sbuf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
16546+            const int active_slot = sbuf->cmn.active_slot;
16547+            SOKOL_ASSERT(sbuf->mtl.buf[sbuf->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX);
16548+            const int offset = view->cmn.buf.offset;
16549+            if (stage == SG_SHADERSTAGE_VERTEX) {
16550+                SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16551+                _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_vsbufs[mtl_slot];
16552+                const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &sbuf->slot, active_slot, offset);
16553+                if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16554+                    _sg_mtl_cache_buf_upd(cache_item, &sbuf->slot, active_slot, offset);
16555+                    if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) {
16556+                        // only offset has changed
16557+                        [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)offset atIndex:mtl_slot];
16558+                        _sg_stats_inc(metal.bindings.num_set_vertex_buffer_offset);
16559+                    } else {
16560+                        [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot];
16561+                        _sg_stats_inc(metal.bindings.num_set_vertex_buffer);
16562+                    }
16563+                } else {
16564+                    _sg_stats_inc(metal.bindings.num_skip_redundant_vertex_buffer);
16565+                }
16566+            } else if (stage == SG_SHADERSTAGE_FRAGMENT) {
16567+                SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16568+                _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_fsbufs[mtl_slot];
16569+                const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &sbuf->slot, active_slot, offset);
16570+                if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16571+                    _sg_mtl_cache_buf_upd(cache_item, &sbuf->slot, active_slot, offset);
16572+                    if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) {
16573+                        // only offset has changed
16574+                        [_sg.mtl.render_cmd_encoder setFragmentBufferOffset:(NSUInteger)offset atIndex:mtl_slot];
16575+                        _sg_stats_inc(metal.bindings.num_set_fragment_buffer_offset);
16576+                    } else {
16577+                        [_sg.mtl.render_cmd_encoder setFragmentBuffer:_sg_mtl_id(sbuf->mtl.buf[active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot];
16578+                        _sg_stats_inc(metal.bindings.num_set_fragment_buffer);
16579+                    }
16580+                } else {
16581+                    _sg_stats_inc(metal.bindings.num_skip_redundant_fragment_buffer);
16582+                }
16583+            } else if (stage == SG_SHADERSTAGE_COMPUTE) {
16584+                SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16585+                _sg_mtl_cache_buf_t* cache_item = &_sg.mtl.cache.cur_csbufs[mtl_slot];
16586+                const int cmp = _sg_mtl_cache_buf_cmp(cache_item, &sbuf->slot, active_slot, offset);
16587+                if (cmp != _SG_MTL_CACHE_CMP_EQUAL) {
16588+                    _sg_mtl_cache_buf_upd(cache_item, &sbuf->slot, active_slot, offset);
16589+                    if (0 == (cmp & ~_SG_MTL_CACHE_CMP_OFFSET)) {
16590+                        // only offset has changed
16591+                        [_sg.mtl.compute_cmd_encoder setBufferOffset:(NSUInteger)offset atIndex:mtl_slot];
16592+                        _sg_stats_inc(metal.bindings.num_set_compute_buffer_offset);
16593+                    } else {
16594+                        [_sg.mtl.compute_cmd_encoder setBuffer:_sg_mtl_id(sbuf->mtl.buf[active_slot]) offset:(NSUInteger)offset atIndex:mtl_slot];
16595+                        _sg_stats_inc(metal.bindings.num_set_compute_buffer);
16596+                    }
16597+                } else {
16598+                    _sg_stats_inc(metal.bindings.num_skip_redundant_compute_buffer);
16599+                }
16600+            }
16601+        } else SOKOL_UNREACHABLE;
16602+    }
16603+
16604+    // apply sampler bindings
16605+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
16606+        const _sg_sampler_t* smp = bnd->smps[i];
16607+        if (smp == 0) {
16608+            continue;
16609+        }
16610+        SOKOL_ASSERT(smp->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX);
16611+        const sg_shader_stage stage = shd->cmn.samplers[i].stage;
16612+        SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE));
16613+        const NSUInteger mtl_slot = shd->mtl.smp_sampler_n[i];
16614+        SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS);
16615+        if (stage == SG_SHADERSTAGE_VERTEX) {
16616+            SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16617+            if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_vssmps[mtl_slot], &smp->slot)) {
16618+                _sg.mtl.cache.cur_vssmps[mtl_slot] = _sg_sref(&smp->slot);
16619+                [_sg.mtl.render_cmd_encoder setVertexSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot];
16620+                _sg_stats_inc(metal.bindings.num_set_vertex_sampler_state);
16621+            } else {
16622+                _sg_stats_inc(metal.bindings.num_skip_redundant_vertex_sampler_state);
16623+            }
16624+        } else if (stage == SG_SHADERSTAGE_FRAGMENT) {
16625+            SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16626+            if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_fssmps[mtl_slot], &smp->slot)) {
16627+                _sg.mtl.cache.cur_fssmps[mtl_slot] = _sg_sref(&smp->slot);
16628+                [_sg.mtl.render_cmd_encoder setFragmentSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot];
16629+                _sg_stats_inc(metal.bindings.num_set_fragment_sampler_state);
16630+            } else {
16631+                _sg_stats_inc(metal.bindings.num_skip_redundant_fragment_sampler_state);
16632+            }
16633+        } else if (stage == SG_SHADERSTAGE_COMPUTE) {
16634+            SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16635+            if (!_sg_sref_slot_eql(&_sg.mtl.cache.cur_cssmps[mtl_slot], &smp->slot)) {
16636+                _sg.mtl.cache.cur_cssmps[mtl_slot] = _sg_sref(&smp->slot);
16637+                [_sg.mtl.compute_cmd_encoder setSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot];
16638+                _sg_stats_inc(metal.bindings.num_set_compute_sampler_state);
16639+            } else {
16640+                _sg_stats_inc(metal.bindings.num_skip_redundant_compute_sampler_state);
16641+            }
16642+        } else SOKOL_UNREACHABLE;
16643+    }
16644+    return true;
16645+}
16646+
16647+_SOKOL_PRIVATE void _sg_mtl_apply_uniforms(int ub_slot, const sg_range* data) {
16648+    SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
16649+    SOKOL_ASSERT(((size_t)_sg.mtl.cur_ub_offset + data->size) <= (size_t)_sg.mtl.ub_size);
16650+    SOKOL_ASSERT((_sg.mtl.cur_ub_offset & (_SG_MTL_UB_ALIGN-1)) == 0);
16651+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
16652+    SOKOL_ASSERT(pip);
16653+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
16654+    SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size);
16655+
16656+    const sg_shader_stage stage = shd->cmn.uniform_blocks[ub_slot].stage;
16657+    const NSUInteger mtl_slot = shd->mtl.ub_buffer_n[ub_slot];
16658+
16659+    // copy to global uniform buffer, record offset into cmd encoder, and advance offset
16660+    uint8_t* dst = &_sg.mtl.cur_ub_base_ptr[_sg.mtl.cur_ub_offset];
16661+    memcpy(dst, data->ptr, data->size);
16662+    if (stage == SG_SHADERSTAGE_VERTEX) {
16663+        SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16664+        [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot];
16665+        _sg_stats_inc(metal.uniforms.num_set_vertex_buffer_offset);
16666+    } else if (stage == SG_SHADERSTAGE_FRAGMENT) {
16667+        SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16668+        [_sg.mtl.render_cmd_encoder setFragmentBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot];
16669+        _sg_stats_inc(metal.uniforms.num_set_fragment_buffer_offset);
16670+    } else if (stage == SG_SHADERSTAGE_COMPUTE) {
16671+        SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16672+        [_sg.mtl.compute_cmd_encoder setBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot];
16673+        _sg_stats_inc(metal.uniforms.num_set_compute_buffer_offset);
16674+    } else {
16675+        SOKOL_UNREACHABLE;
16676+    }
16677+    _sg.mtl.cur_ub_offset = _sg_roundup(_sg.mtl.cur_ub_offset + (int)data->size, _SG_MTL_UB_ALIGN);
16678+}
16679+
16680+_SOKOL_PRIVATE void _sg_mtl_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
16681+    SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder);
16682+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
16683+    SOKOL_ASSERT(pip);
16684+    if (_sg.use_indexed_draw) {
16685+        // indexed rendering
16686+        const _sg_buffer_t* ib = _sg_buffer_ref_ptr(&_sg.mtl.cache.cur_ibuf);
16687+        SOKOL_ASSERT(ib && (ib->mtl.buf[ib->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX));
16688+        const NSUInteger index_buffer_offset = (NSUInteger) (_sg.mtl.cache.cur_ibuf_offset + base_element * pip->mtl.index_size);
16689+        [_sg.mtl.render_cmd_encoder drawIndexedPrimitives:pip->mtl.prim_type
16690+            indexCount:(NSUInteger)num_elements
16691+            indexType:pip->mtl.index_type
16692+            indexBuffer:_sg_mtl_id(ib->mtl.buf[ib->cmn.active_slot])
16693+            indexBufferOffset:index_buffer_offset
16694+            instanceCount:(NSUInteger)num_instances
16695+            baseVertex:base_vertex
16696+            baseInstance:(NSUInteger)base_instance];
16697+    } else {
16698+        // non-indexed rendering
16699+        [_sg.mtl.render_cmd_encoder drawPrimitives:pip->mtl.prim_type
16700+            vertexStart:(NSUInteger)base_element
16701+            vertexCount:(NSUInteger)num_elements
16702+            instanceCount:(NSUInteger)num_instances
16703+            baseInstance:(NSUInteger)base_instance];
16704+    }
16705+}
16706+
16707+_SOKOL_PRIVATE void _sg_mtl_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
16708+    SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder);
16709+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
16710+    SOKOL_ASSERT(pip);
16711+    const MTLSize thread_groups = MTLSizeMake(
16712+        (NSUInteger)num_groups_x,
16713+        (NSUInteger)num_groups_y,
16714+        (NSUInteger)num_groups_z);
16715+    const MTLSize threads_per_threadgroup = pip->mtl.threads_per_threadgroup;
16716+    [_sg.mtl.compute_cmd_encoder dispatchThreadgroups:thread_groups threadsPerThreadgroup:threads_per_threadgroup];
16717+}
16718+
16719+_SOKOL_PRIVATE void _sg_mtl_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
16720+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
16721+    if (++buf->cmn.active_slot >= buf->cmn.num_slots) {
16722+        buf->cmn.active_slot = 0;
16723+    }
16724+    __unsafe_unretained id<MTLBuffer> mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]);
16725+    void* dst_ptr = [mtl_buf contents];
16726+    memcpy(dst_ptr, data->ptr, data->size);
16727+    #if defined(_SG_TARGET_MACOS)
16728+    if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) {
16729+        [mtl_buf didModifyRange:NSMakeRange(0, data->size)];
16730+    }
16731+    #endif
16732+}
16733+
16734+_SOKOL_PRIVATE void _sg_mtl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
16735+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
16736+    if (new_frame) {
16737+        if (++buf->cmn.active_slot >= buf->cmn.num_slots) {
16738+            buf->cmn.active_slot = 0;
16739+        }
16740+    }
16741+    __unsafe_unretained id<MTLBuffer> mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]);
16742+    uint8_t* dst_ptr = (uint8_t*) [mtl_buf contents];
16743+    dst_ptr += buf->cmn.append_pos;
16744+    memcpy(dst_ptr, data->ptr, data->size);
16745+    #if defined(_SG_TARGET_MACOS)
16746+    if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) {
16747+        [mtl_buf didModifyRange:NSMakeRange((NSUInteger)buf->cmn.append_pos, (NSUInteger)data->size)];
16748+    }
16749+    #endif
16750+}
16751+
16752+_SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data* data) {
16753+    SOKOL_ASSERT(img && data);
16754+    if (++img->cmn.active_slot >= img->cmn.num_slots) {
16755+        img->cmn.active_slot = 0;
16756+    }
16757+    __unsafe_unretained id<MTLTexture> mtl_tex = _sg_mtl_id(img->mtl.tex[img->cmn.active_slot]);
16758+    _sg_mtl_copy_image_data(img, mtl_tex, data);
16759+}
16760+
16761+_SOKOL_PRIVATE void _sg_mtl_push_debug_group(const char* name) {
16762+    SOKOL_ASSERT(name);
16763+    if (_sg.mtl.render_cmd_encoder) {
16764+        [_sg.mtl.render_cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]];
16765+    } else if (_sg.mtl.compute_cmd_encoder) {
16766+        [_sg.mtl.compute_cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]];
16767+    }
16768+}
16769+
16770+_SOKOL_PRIVATE void _sg_mtl_pop_debug_group(void) {
16771+    if (_sg.mtl.render_cmd_encoder) {
16772+        [_sg.mtl.render_cmd_encoder popDebugGroup];
16773+    } else if (_sg.mtl.compute_cmd_encoder) {
16774+        [_sg.mtl.compute_cmd_encoder popDebugGroup];
16775+    }
16776+}
16777+
16778+#pragma clang diagnostic pop // Intel Mac deprecations
16779+
16780+// ██     ██ ███████ ██████   ██████  ██████  ██    ██     ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
16781+// ██     ██ ██      ██   ██ ██       ██   ██ ██    ██     ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
16782+// ██  █  ██ █████   ██████  ██   ███ ██████  ██    ██     ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
16783+// ██ ███ ██ ██      ██   ██ ██    ██ ██      ██    ██     ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
16784+//  ███ ███  ███████ ██████   ██████  ██       ██████      ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
16785+//
16786+// >>webgpu
16787+// >>wgpu
16788+#elif defined(SOKOL_WGPU)
16789+
16790+_SOKOL_PRIVATE WGPUStringView _sg_wgpu_stringview(const char* str) {
16791+    WGPUStringView res;
16792+    if (str) {
16793+        res.data = str;
16794+        res.length = strlen(str);
16795+    } else {
16796+        res.data = 0;
16797+        res.length = 0;
16798+    }
16799+    return res;
16800+}
16801+
16802+_SOKOL_PRIVATE WGPUOptionalBool _sg_wgpu_optional_bool(bool b) {
16803+    return b ? WGPUOptionalBool_True : WGPUOptionalBool_False;
16804+}
16805+
16806+_SOKOL_PRIVATE WGPUBufferUsage _sg_wgpu_buffer_usage(const sg_buffer_usage* usg) {
16807+    int res = 0;
16808+    if (usg->vertex_buffer) {
16809+        res |= (int)WGPUBufferUsage_Vertex;
16810+    }
16811+    if (usg->index_buffer) {
16812+        res |= (int)WGPUBufferUsage_Index;
16813+    }
16814+    if (usg->storage_buffer) {
16815+        res |= (int)WGPUBufferUsage_Storage;
16816+    }
16817+    if (!usg->immutable) {
16818+        res |= (int)WGPUBufferUsage_CopyDst;
16819+    }
16820+    return (WGPUBufferUsage)res;
16821+}
16822+
16823+_SOKOL_PRIVATE WGPULoadOp _sg_wgpu_load_op(WGPUTextureView view, sg_load_action a) {
16824+    if (0 == view) {
16825+        return WGPULoadOp_Undefined;
16826+    } else switch (a) {
16827+        case SG_LOADACTION_CLEAR:
16828+        case SG_LOADACTION_DONTCARE:
16829+            return WGPULoadOp_Clear;
16830+        case SG_LOADACTION_LOAD:
16831+            return WGPULoadOp_Load;
16832+        default:
16833+            SOKOL_UNREACHABLE;
16834+            return WGPULoadOp_Force32;
16835+    }
16836+}
16837+
16838+_SOKOL_PRIVATE WGPUStoreOp _sg_wgpu_store_op(WGPUTextureView view, sg_store_action a) {
16839+    if (0 == view) {
16840+        return WGPUStoreOp_Undefined;
16841+    } else switch (a) {
16842+        case SG_STOREACTION_STORE:
16843+            return WGPUStoreOp_Store;
16844+        case SG_STOREACTION_DONTCARE:
16845+            return WGPUStoreOp_Discard;
16846+        default:
16847+            SOKOL_UNREACHABLE;
16848+            return WGPUStoreOp_Force32;
16849+    }
16850+}
16851+
16852+_SOKOL_PRIVATE WGPUTextureViewDimension _sg_wgpu_texture_view_dimension(sg_image_type t) {
16853+    switch (t) {
16854+        case SG_IMAGETYPE_2D:       return WGPUTextureViewDimension_2D;
16855+        case SG_IMAGETYPE_CUBE:     return WGPUTextureViewDimension_Cube;
16856+        case SG_IMAGETYPE_3D:       return WGPUTextureViewDimension_3D;
16857+        case SG_IMAGETYPE_ARRAY:    return WGPUTextureViewDimension_2DArray;
16858+        default: SOKOL_UNREACHABLE; return WGPUTextureViewDimension_Force32;
16859+    }
16860+}
16861+
16862+_SOKOL_PRIVATE WGPUTextureViewDimension _sg_wgpu_attachment_view_dimension(sg_image_type t) {
16863+    switch (t) {
16864+        case SG_IMAGETYPE_2D:       return WGPUTextureViewDimension_2D;
16865+        case SG_IMAGETYPE_CUBE:     return WGPUTextureViewDimension_2DArray; // not a bug
16866+        case SG_IMAGETYPE_3D:       return WGPUTextureViewDimension_2D; // not a bug
16867+        case SG_IMAGETYPE_ARRAY:    return WGPUTextureViewDimension_2DArray;
16868+        default: SOKOL_UNREACHABLE; return WGPUTextureViewDimension_Force32;
16869+    }
16870+}
16871+
16872+_SOKOL_PRIVATE WGPUTextureDimension _sg_wgpu_texture_dimension(sg_image_type t) {
16873+    if (SG_IMAGETYPE_3D == t) {
16874+        return WGPUTextureDimension_3D;
16875+    } else {
16876+        return WGPUTextureDimension_2D;
16877+    }
16878+}
16879+
16880+_SOKOL_PRIVATE WGPUTextureSampleType _sg_wgpu_texture_sample_type(sg_image_sample_type t, bool msaa) {
16881+    switch (t) {
16882+        case SG_IMAGESAMPLETYPE_FLOAT:  return msaa ? WGPUTextureSampleType_UnfilterableFloat : WGPUTextureSampleType_Float;
16883+        case SG_IMAGESAMPLETYPE_DEPTH:  return WGPUTextureSampleType_Depth;
16884+        case SG_IMAGESAMPLETYPE_SINT:   return WGPUTextureSampleType_Sint;
16885+        case SG_IMAGESAMPLETYPE_UINT:   return WGPUTextureSampleType_Uint;
16886+        case SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT: return WGPUTextureSampleType_UnfilterableFloat;
16887+        default: SOKOL_UNREACHABLE;     return WGPUTextureSampleType_Force32;
16888+    }
16889+}
16890+
16891+_SOKOL_PRIVATE WGPUSamplerBindingType _sg_wgpu_sampler_binding_type(sg_sampler_type t) {
16892+    switch (t) {
16893+        case SG_SAMPLERTYPE_FILTERING: return WGPUSamplerBindingType_Filtering;
16894+        case SG_SAMPLERTYPE_COMPARISON: return WGPUSamplerBindingType_Comparison;
16895+        case SG_SAMPLERTYPE_NONFILTERING: return WGPUSamplerBindingType_NonFiltering;
16896+        default: SOKOL_UNREACHABLE; return WGPUSamplerBindingType_Force32;
16897+    }
16898+}
16899+
16900+_SOKOL_PRIVATE WGPUAddressMode _sg_wgpu_sampler_address_mode(sg_wrap m) {
16901+    switch (m) {
16902+        case SG_WRAP_REPEAT:
16903+            return WGPUAddressMode_Repeat;
16904+        case SG_WRAP_CLAMP_TO_EDGE:
16905+        case SG_WRAP_CLAMP_TO_BORDER:
16906+            return WGPUAddressMode_ClampToEdge;
16907+        case SG_WRAP_MIRRORED_REPEAT:
16908+            return WGPUAddressMode_MirrorRepeat;
16909+        default:
16910+            SOKOL_UNREACHABLE;
16911+            return WGPUAddressMode_Force32;
16912+    }
16913+}
16914+
16915+_SOKOL_PRIVATE WGPUFilterMode _sg_wgpu_sampler_minmag_filter(sg_filter f) {
16916+    switch (f) {
16917+        case SG_FILTER_NEAREST:
16918+            return WGPUFilterMode_Nearest;
16919+        case SG_FILTER_LINEAR:
16920+            return WGPUFilterMode_Linear;
16921+        default:
16922+            SOKOL_UNREACHABLE;
16923+            return WGPUFilterMode_Force32;
16924+    }
16925+}
16926+
16927+_SOKOL_PRIVATE WGPUMipmapFilterMode _sg_wgpu_sampler_mipmap_filter(sg_filter f) {
16928+    switch (f) {
16929+        case SG_FILTER_NEAREST:
16930+            return WGPUMipmapFilterMode_Nearest;
16931+        case SG_FILTER_LINEAR:
16932+            return WGPUMipmapFilterMode_Linear;
16933+        default:
16934+            SOKOL_UNREACHABLE;
16935+            return WGPUMipmapFilterMode_Force32;
16936+    }
16937+}
16938+
16939+_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_indexformat(sg_index_type t) {
16940+    // NOTE: there's no WGPUIndexFormat_None
16941+    return (t == SG_INDEXTYPE_UINT16) ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32;
16942+}
16943+
16944+_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_stripindexformat(sg_primitive_type prim_type, sg_index_type idx_type) {
16945+    if (idx_type == SG_INDEXTYPE_NONE) {
16946+        return WGPUIndexFormat_Undefined;
16947+    } else if ((prim_type == SG_PRIMITIVETYPE_LINE_STRIP) || (prim_type == SG_PRIMITIVETYPE_TRIANGLE_STRIP)) {
16948+        return _sg_wgpu_indexformat(idx_type);
16949+    } else {
16950+        return WGPUIndexFormat_Undefined;
16951+    }
16952+}
16953+
16954+_SOKOL_PRIVATE WGPUVertexStepMode _sg_wgpu_stepmode(sg_vertex_step s) {
16955+    return (s == SG_VERTEXSTEP_PER_VERTEX) ? WGPUVertexStepMode_Vertex : WGPUVertexStepMode_Instance;
16956+}
16957+
16958+_SOKOL_PRIVATE WGPUVertexFormat _sg_wgpu_vertexformat(sg_vertex_format f) {
16959+    switch (f) {
16960+        case SG_VERTEXFORMAT_FLOAT:         return WGPUVertexFormat_Float32;
16961+        case SG_VERTEXFORMAT_FLOAT2:        return WGPUVertexFormat_Float32x2;
16962+        case SG_VERTEXFORMAT_FLOAT3:        return WGPUVertexFormat_Float32x3;
16963+        case SG_VERTEXFORMAT_FLOAT4:        return WGPUVertexFormat_Float32x4;
16964+        case SG_VERTEXFORMAT_INT:           return WGPUVertexFormat_Sint32;
16965+        case SG_VERTEXFORMAT_INT2:          return WGPUVertexFormat_Sint32x2;
16966+        case SG_VERTEXFORMAT_INT3:          return WGPUVertexFormat_Sint32x3;
16967+        case SG_VERTEXFORMAT_INT4:          return WGPUVertexFormat_Sint32x4;
16968+        case SG_VERTEXFORMAT_UINT:          return WGPUVertexFormat_Uint32;
16969+        case SG_VERTEXFORMAT_UINT2:         return WGPUVertexFormat_Uint32x2;
16970+        case SG_VERTEXFORMAT_UINT3:         return WGPUVertexFormat_Uint32x3;
16971+        case SG_VERTEXFORMAT_UINT4:         return WGPUVertexFormat_Uint32x4;
16972+        case SG_VERTEXFORMAT_BYTE4:         return WGPUVertexFormat_Sint8x4;
16973+        case SG_VERTEXFORMAT_BYTE4N:        return WGPUVertexFormat_Snorm8x4;
16974+        case SG_VERTEXFORMAT_UBYTE4:        return WGPUVertexFormat_Uint8x4;
16975+        case SG_VERTEXFORMAT_UBYTE4N:       return WGPUVertexFormat_Unorm8x4;
16976+        case SG_VERTEXFORMAT_SHORT2:        return WGPUVertexFormat_Sint16x2;
16977+        case SG_VERTEXFORMAT_SHORT2N:       return WGPUVertexFormat_Snorm16x2;
16978+        case SG_VERTEXFORMAT_USHORT2:       return WGPUVertexFormat_Uint16x2;
16979+        case SG_VERTEXFORMAT_USHORT2N:      return WGPUVertexFormat_Unorm16x2;
16980+        case SG_VERTEXFORMAT_SHORT4:        return WGPUVertexFormat_Sint16x4;
16981+        case SG_VERTEXFORMAT_SHORT4N:       return WGPUVertexFormat_Snorm16x4;
16982+        case SG_VERTEXFORMAT_USHORT4:       return WGPUVertexFormat_Uint16x4;
16983+        case SG_VERTEXFORMAT_USHORT4N:      return WGPUVertexFormat_Unorm16x4;
16984+        case SG_VERTEXFORMAT_UINT10_N2:     return WGPUVertexFormat_Unorm10_10_10_2;
16985+        case SG_VERTEXFORMAT_HALF2:         return WGPUVertexFormat_Float16x2;
16986+        case SG_VERTEXFORMAT_HALF4:         return WGPUVertexFormat_Float16x4;
16987+        default:
16988+            SOKOL_UNREACHABLE;
16989+            return WGPUVertexFormat_Force32;
16990+    }
16991+}
16992+
16993+_SOKOL_PRIVATE WGPUPrimitiveTopology _sg_wgpu_topology(sg_primitive_type t) {
16994+    switch (t) {
16995+        case SG_PRIMITIVETYPE_POINTS:           return WGPUPrimitiveTopology_PointList;
16996+        case SG_PRIMITIVETYPE_LINES:            return WGPUPrimitiveTopology_LineList;
16997+        case SG_PRIMITIVETYPE_LINE_STRIP:       return WGPUPrimitiveTopology_LineStrip;
16998+        case SG_PRIMITIVETYPE_TRIANGLES:        return WGPUPrimitiveTopology_TriangleList;
16999+        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return WGPUPrimitiveTopology_TriangleStrip;
17000+        default:
17001+            SOKOL_UNREACHABLE;
17002+            return WGPUPrimitiveTopology_Force32;
17003+    }
17004+}
17005+
17006+_SOKOL_PRIVATE WGPUFrontFace _sg_wgpu_frontface(sg_face_winding fw) {
17007+    return (fw == SG_FACEWINDING_CCW) ? WGPUFrontFace_CCW : WGPUFrontFace_CW;
17008+}
17009+
17010+_SOKOL_PRIVATE WGPUCullMode _sg_wgpu_cullmode(sg_cull_mode cm) {
17011+    switch (cm) {
17012+        case SG_CULLMODE_NONE:      return WGPUCullMode_None;
17013+        case SG_CULLMODE_FRONT:     return WGPUCullMode_Front;
17014+        case SG_CULLMODE_BACK:      return WGPUCullMode_Back;
17015+        default:
17016+            SOKOL_UNREACHABLE;
17017+            return WGPUCullMode_Force32;
17018+    }
17019+}
17020+
17021+_SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) {
17022+    switch (p) {
17023+        case SG_PIXELFORMAT_NONE:           return WGPUTextureFormat_Undefined;
17024+        case SG_PIXELFORMAT_R8:             return WGPUTextureFormat_R8Unorm;
17025+        case SG_PIXELFORMAT_R8SN:           return WGPUTextureFormat_R8Snorm;
17026+        case SG_PIXELFORMAT_R8UI:           return WGPUTextureFormat_R8Uint;
17027+        case SG_PIXELFORMAT_R8SI:           return WGPUTextureFormat_R8Sint;
17028+        case SG_PIXELFORMAT_R16:            return WGPUTextureFormat_R16Unorm;
17029+        case SG_PIXELFORMAT_R16SN:          return WGPUTextureFormat_R16Snorm;
17030+        case SG_PIXELFORMAT_R16UI:          return WGPUTextureFormat_R16Uint;
17031+        case SG_PIXELFORMAT_R16SI:          return WGPUTextureFormat_R16Sint;
17032+        case SG_PIXELFORMAT_R16F:           return WGPUTextureFormat_R16Float;
17033+        case SG_PIXELFORMAT_RG8:            return WGPUTextureFormat_RG8Unorm;
17034+        case SG_PIXELFORMAT_RG8SN:          return WGPUTextureFormat_RG8Snorm;
17035+        case SG_PIXELFORMAT_RG8UI:          return WGPUTextureFormat_RG8Uint;
17036+        case SG_PIXELFORMAT_RG8SI:          return WGPUTextureFormat_RG8Sint;
17037+        case SG_PIXELFORMAT_R32UI:          return WGPUTextureFormat_R32Uint;
17038+        case SG_PIXELFORMAT_R32SI:          return WGPUTextureFormat_R32Sint;
17039+        case SG_PIXELFORMAT_R32F:           return WGPUTextureFormat_R32Float;
17040+        case SG_PIXELFORMAT_RG16:           return WGPUTextureFormat_RG16Unorm;
17041+        case SG_PIXELFORMAT_RG16SN:         return WGPUTextureFormat_RG16Snorm;
17042+        case SG_PIXELFORMAT_RG16UI:         return WGPUTextureFormat_RG16Uint;
17043+        case SG_PIXELFORMAT_RG16SI:         return WGPUTextureFormat_RG16Sint;
17044+        case SG_PIXELFORMAT_RG16F:          return WGPUTextureFormat_RG16Float;
17045+        case SG_PIXELFORMAT_RGBA8:          return WGPUTextureFormat_RGBA8Unorm;
17046+        case SG_PIXELFORMAT_SRGB8A8:        return WGPUTextureFormat_RGBA8UnormSrgb;
17047+        case SG_PIXELFORMAT_RGBA8SN:        return WGPUTextureFormat_RGBA8Snorm;
17048+        case SG_PIXELFORMAT_RGBA8UI:        return WGPUTextureFormat_RGBA8Uint;
17049+        case SG_PIXELFORMAT_RGBA8SI:        return WGPUTextureFormat_RGBA8Sint;
17050+        case SG_PIXELFORMAT_BGRA8:          return WGPUTextureFormat_BGRA8Unorm;
17051+        case SG_PIXELFORMAT_SBGR8A8:        return WGPUTextureFormat_BGRA8UnormSrgb;
17052+        case SG_PIXELFORMAT_RGB10A2:        return WGPUTextureFormat_RGB10A2Unorm;
17053+        case SG_PIXELFORMAT_RG11B10F:       return WGPUTextureFormat_RG11B10Ufloat;
17054+        case SG_PIXELFORMAT_RGB9E5:         return WGPUTextureFormat_RGB9E5Ufloat;
17055+        case SG_PIXELFORMAT_RG32UI:         return WGPUTextureFormat_RG32Uint;
17056+        case SG_PIXELFORMAT_RG32SI:         return WGPUTextureFormat_RG32Sint;
17057+        case SG_PIXELFORMAT_RG32F:          return WGPUTextureFormat_RG32Float;
17058+        case SG_PIXELFORMAT_RGBA16:         return WGPUTextureFormat_RGBA16Unorm;
17059+        case SG_PIXELFORMAT_RGBA16SN:       return WGPUTextureFormat_RGBA16Snorm;
17060+        case SG_PIXELFORMAT_RGBA16UI:       return WGPUTextureFormat_RGBA16Uint;
17061+        case SG_PIXELFORMAT_RGBA16SI:       return WGPUTextureFormat_RGBA16Sint;
17062+        case SG_PIXELFORMAT_RGBA16F:        return WGPUTextureFormat_RGBA16Float;
17063+        case SG_PIXELFORMAT_RGBA32UI:       return WGPUTextureFormat_RGBA32Uint;
17064+        case SG_PIXELFORMAT_RGBA32SI:       return WGPUTextureFormat_RGBA32Sint;
17065+        case SG_PIXELFORMAT_RGBA32F:        return WGPUTextureFormat_RGBA32Float;
17066+        case SG_PIXELFORMAT_DEPTH:          return WGPUTextureFormat_Depth32Float;
17067+        case SG_PIXELFORMAT_DEPTH_STENCIL:  return WGPUTextureFormat_Depth32FloatStencil8;
17068+        case SG_PIXELFORMAT_BC1_RGBA:       return WGPUTextureFormat_BC1RGBAUnorm;
17069+        case SG_PIXELFORMAT_BC2_RGBA:       return WGPUTextureFormat_BC2RGBAUnorm;
17070+        case SG_PIXELFORMAT_BC3_RGBA:       return WGPUTextureFormat_BC3RGBAUnorm;
17071+        case SG_PIXELFORMAT_BC3_SRGBA:      return WGPUTextureFormat_BC3RGBAUnormSrgb;
17072+        case SG_PIXELFORMAT_BC4_R:          return WGPUTextureFormat_BC4RUnorm;
17073+        case SG_PIXELFORMAT_BC4_RSN:        return WGPUTextureFormat_BC4RSnorm;
17074+        case SG_PIXELFORMAT_BC5_RG:         return WGPUTextureFormat_BC5RGUnorm;
17075+        case SG_PIXELFORMAT_BC5_RGSN:       return WGPUTextureFormat_BC5RGSnorm;
17076+        case SG_PIXELFORMAT_BC6H_RGBF:      return WGPUTextureFormat_BC6HRGBFloat;
17077+        case SG_PIXELFORMAT_BC6H_RGBUF:     return WGPUTextureFormat_BC6HRGBUfloat;
17078+        case SG_PIXELFORMAT_BC7_RGBA:       return WGPUTextureFormat_BC7RGBAUnorm;
17079+        case SG_PIXELFORMAT_BC7_SRGBA:      return WGPUTextureFormat_BC7RGBAUnormSrgb;
17080+        case SG_PIXELFORMAT_ETC2_RGB8:      return WGPUTextureFormat_ETC2RGB8Unorm;
17081+        case SG_PIXELFORMAT_ETC2_RGB8A1:    return WGPUTextureFormat_ETC2RGB8A1Unorm;
17082+        case SG_PIXELFORMAT_ETC2_RGBA8:     return WGPUTextureFormat_ETC2RGBA8Unorm;
17083+        case SG_PIXELFORMAT_ETC2_SRGB8:     return WGPUTextureFormat_ETC2RGB8UnormSrgb;
17084+        case SG_PIXELFORMAT_ETC2_SRGB8A8:   return WGPUTextureFormat_ETC2RGBA8UnormSrgb;
17085+        case SG_PIXELFORMAT_EAC_R11:        return WGPUTextureFormat_EACR11Unorm;
17086+        case SG_PIXELFORMAT_EAC_R11SN:      return WGPUTextureFormat_EACR11Snorm;
17087+        case SG_PIXELFORMAT_EAC_RG11:       return WGPUTextureFormat_EACRG11Unorm;
17088+        case SG_PIXELFORMAT_EAC_RG11SN:     return WGPUTextureFormat_EACRG11Snorm;
17089+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:  return WGPUTextureFormat_ASTC4x4Unorm;
17090+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return WGPUTextureFormat_ASTC4x4UnormSrgb;
17091+        default:
17092+            SOKOL_UNREACHABLE;
17093+            return WGPUTextureFormat_Force32;
17094+    }
17095+}
17096+
17097+_SOKOL_PRIVATE WGPUCompareFunction _sg_wgpu_comparefunc(sg_compare_func f) {
17098+    switch (f) {
17099+        case SG_COMPAREFUNC_NEVER:          return WGPUCompareFunction_Never;
17100+        case SG_COMPAREFUNC_LESS:           return WGPUCompareFunction_Less;
17101+        case SG_COMPAREFUNC_EQUAL:          return WGPUCompareFunction_Equal;
17102+        case SG_COMPAREFUNC_LESS_EQUAL:     return WGPUCompareFunction_LessEqual;
17103+        case SG_COMPAREFUNC_GREATER:        return WGPUCompareFunction_Greater;
17104+        case SG_COMPAREFUNC_NOT_EQUAL:      return WGPUCompareFunction_NotEqual;
17105+        case SG_COMPAREFUNC_GREATER_EQUAL:  return WGPUCompareFunction_GreaterEqual;
17106+        case SG_COMPAREFUNC_ALWAYS:         return WGPUCompareFunction_Always;
17107+        default:
17108+            SOKOL_UNREACHABLE;
17109+            return WGPUCompareFunction_Force32;
17110+    }
17111+}
17112+
17113+_SOKOL_PRIVATE WGPUStencilOperation _sg_wgpu_stencilop(sg_stencil_op op) {
17114+    switch (op) {
17115+        case SG_STENCILOP_KEEP:         return WGPUStencilOperation_Keep;
17116+        case SG_STENCILOP_ZERO:         return WGPUStencilOperation_Zero;
17117+        case SG_STENCILOP_REPLACE:      return WGPUStencilOperation_Replace;
17118+        case SG_STENCILOP_INCR_CLAMP:   return WGPUStencilOperation_IncrementClamp;
17119+        case SG_STENCILOP_DECR_CLAMP:   return WGPUStencilOperation_DecrementClamp;
17120+        case SG_STENCILOP_INVERT:       return WGPUStencilOperation_Invert;
17121+        case SG_STENCILOP_INCR_WRAP:    return WGPUStencilOperation_IncrementWrap;
17122+        case SG_STENCILOP_DECR_WRAP:    return WGPUStencilOperation_DecrementWrap;
17123+        default:
17124+            SOKOL_UNREACHABLE;
17125+            return WGPUStencilOperation_Force32;
17126+    }
17127+}
17128+
17129+_SOKOL_PRIVATE WGPUBlendOperation _sg_wgpu_blendop(sg_blend_op op) {
17130+    switch (op) {
17131+        case SG_BLENDOP_ADD:                return WGPUBlendOperation_Add;
17132+        case SG_BLENDOP_SUBTRACT:           return WGPUBlendOperation_Subtract;
17133+        case SG_BLENDOP_REVERSE_SUBTRACT:   return WGPUBlendOperation_ReverseSubtract;
17134+        case SG_BLENDOP_MIN:                return WGPUBlendOperation_Min;
17135+        case SG_BLENDOP_MAX:                return WGPUBlendOperation_Max;
17136+        default:
17137+            SOKOL_UNREACHABLE;
17138+            return WGPUBlendOperation_Force32;
17139+    }
17140+}
17141+
17142+_SOKOL_PRIVATE WGPUBlendFactor _sg_wgpu_blendfactor(sg_blend_factor f) {
17143+    switch (f) {
17144+        case SG_BLENDFACTOR_ZERO:                   return WGPUBlendFactor_Zero;
17145+        case SG_BLENDFACTOR_ONE:                    return WGPUBlendFactor_One;
17146+        case SG_BLENDFACTOR_SRC_COLOR:              return WGPUBlendFactor_Src;
17147+        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return WGPUBlendFactor_OneMinusSrc;
17148+        case SG_BLENDFACTOR_SRC_ALPHA:              return WGPUBlendFactor_SrcAlpha;
17149+        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return WGPUBlendFactor_OneMinusSrcAlpha;
17150+        case SG_BLENDFACTOR_DST_COLOR:              return WGPUBlendFactor_Dst;
17151+        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return WGPUBlendFactor_OneMinusDst;
17152+        case SG_BLENDFACTOR_DST_ALPHA:              return WGPUBlendFactor_DstAlpha;
17153+        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return WGPUBlendFactor_OneMinusDstAlpha;
17154+        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return WGPUBlendFactor_SrcAlphaSaturated;
17155+        case SG_BLENDFACTOR_BLEND_COLOR:            return WGPUBlendFactor_Constant;
17156+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return WGPUBlendFactor_OneMinusConstant;
17157+        // FIXME: separate blend alpha value not supported?
17158+        case SG_BLENDFACTOR_BLEND_ALPHA:            return WGPUBlendFactor_Constant;
17159+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return WGPUBlendFactor_OneMinusConstant;
17160+        case SG_BLENDFACTOR_SRC1_COLOR:             return WGPUBlendFactor_Src1 ;
17161+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR:   return WGPUBlendFactor_OneMinusSrc1;
17162+        case SG_BLENDFACTOR_SRC1_ALPHA:             return WGPUBlendFactor_Src1Alpha;
17163+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA:   return WGPUBlendFactor_OneMinusSrc1Alpha;
17164+        default:
17165+            SOKOL_UNREACHABLE;
17166+            return WGPUBlendFactor_Force32;
17167+    }
17168+}
17169+
17170+_SOKOL_PRIVATE WGPUColorWriteMask _sg_wgpu_colorwritemask(sg_color_mask m) {
17171+    int res = 0;
17172+    if (0 != (m & SG_COLORMASK_R)) {
17173+        res |= (int)WGPUColorWriteMask_Red;
17174+    }
17175+    if (0 != (m & SG_COLORMASK_G)) {
17176+        res |= (int)WGPUColorWriteMask_Green;
17177+    }
17178+    if (0 != (m & SG_COLORMASK_B)) {
17179+        res |= (int)WGPUColorWriteMask_Blue;
17180+    }
17181+    if (0 != (m & SG_COLORMASK_A)) {
17182+        res |= (int)WGPUColorWriteMask_Alpha;
17183+    }
17184+    return (WGPUColorWriteMask)res;
17185+}
17186+
17187+_SOKOL_PRIVATE WGPUShaderStage _sg_wgpu_shader_stage(sg_shader_stage stage) {
17188+    switch (stage) {
17189+        case SG_SHADERSTAGE_VERTEX: return WGPUShaderStage_Vertex;
17190+        case SG_SHADERSTAGE_FRAGMENT: return WGPUShaderStage_Fragment;
17191+        case SG_SHADERSTAGE_COMPUTE: return WGPUShaderStage_Compute;
17192+        default: SOKOL_UNREACHABLE; return WGPUShaderStage_None;
17193+    }
17194+}
17195+
17196+_SOKOL_PRIVATE void _sg_wgpu_init_caps(void) {
17197+    _sg.backend = SG_BACKEND_WGPU;
17198+    _sg.features.origin_top_left = true;
17199+    _sg.features.image_clamp_to_border = false;
17200+    _sg.features.mrt_independent_blend_state = true;
17201+    _sg.features.mrt_independent_write_mask = true;
17202+    _sg.features.compute = true;
17203+    _sg.features.msaa_texture_bindings = true;
17204+    _sg.features.draw_base_vertex = true;
17205+    _sg.features.draw_base_instance = true;
17206+    _sg.features.dual_source_blending = wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_DualSourceBlending);
17207+    _sg.features.vertexformat_int10_n2 = false;
17208+
17209+    wgpuDeviceGetLimits(_sg.wgpu.dev, &_sg.wgpu.limits);
17210+
17211+    const WGPULimits* l = &_sg.wgpu.limits;
17212+    _sg.limits.max_image_size_2d = (int) l->maxTextureDimension2D;
17213+    _sg.limits.max_image_size_cube = (int) l->maxTextureDimension2D; // not a bug, see: https://github.com/gpuweb/gpuweb/issues/1327
17214+    _sg.limits.max_image_size_3d = (int) l->maxTextureDimension3D;
17215+    _sg.limits.max_image_size_array = (int) l->maxTextureDimension2D;
17216+    _sg.limits.max_image_array_layers = (int) l->maxTextureArrayLayers;
17217+    _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES;
17218+    _sg.limits.max_color_attachments = _sg_min((int)l->maxColorAttachments, SG_MAX_COLOR_ATTACHMENTS);
17219+    _sg.limits.max_texture_bindings_per_stage = _sg_min((int)l->maxSampledTexturesPerShaderStage, SG_MAX_VIEW_BINDSLOTS);
17220+    _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min((int)l->maxStorageBuffersPerShaderStage, SG_MAX_VIEW_BINDSLOTS);
17221+    _sg.limits.max_storage_image_bindings_per_stage = _sg_min((int)l->maxStorageTexturesPerShaderStage, SG_MAX_VIEW_BINDSLOTS);
17222+
17223+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]);
17224+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]);
17225+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
17226+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]);
17227+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]);
17228+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]);
17229+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]);
17230+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
17231+    _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]);
17232+
17233+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]);
17234+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]);
17235+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
17236+
17237+    // FIXME: can be made renderable via extension
17238+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]);
17239+
17240+    // NOTE: msaa rendering is possible in WebGPU, but no resolve
17241+    // which is a combination that's not currently supported in sokol-gfx
17242+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R8UI]);
17243+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R8SI]);
17244+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG8UI]);
17245+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG8SI]);
17246+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
17247+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
17248+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R16UI]);
17249+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R16SI]);
17250+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG16UI]);
17251+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG16SI]);
17252+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
17253+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
17254+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]);
17255+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]);
17256+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
17257+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
17258+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
17259+    _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
17260+
17261+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_Float32Filterable)) {
17262+        _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_R32F]);
17263+        _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_RG32F]);
17264+        _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
17265+    } else {
17266+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32F]);
17267+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32F]);
17268+        _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
17269+    }
17270+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_Float32Blendable)) {
17271+        _sg.formats[SG_PIXELFORMAT_R32F].blend = true;
17272+        _sg.formats[SG_PIXELFORMAT_RG32F].blend = true;
17273+        _sg.formats[SG_PIXELFORMAT_RGBA32F].blend = true;
17274+    }
17275+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureFormatsTier1)) {
17276+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R16]);
17277+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R16SN]);
17278+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG16]);
17279+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG16SN]);
17280+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RGBA16]);
17281+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]);
17282+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R8SN]);
17283+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG8SN]);
17284+        _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
17285+    }
17286+
17287+    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]);
17288+    _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]);
17289+
17290+    _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]);
17291+
17292+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionBC)) {
17293+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]);
17294+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]);
17295+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]);
17296+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]);
17297+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]);
17298+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]);
17299+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]);
17300+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]);
17301+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]);
17302+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]);
17303+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]);
17304+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]);
17305+    }
17306+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionETC2)) {
17307+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]);
17308+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]);
17309+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]);
17310+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]);
17311+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]);
17312+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]);
17313+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]);
17314+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]);
17315+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]);
17316+    }
17317+
17318+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionASTC)) {
17319+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]);
17320+        _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]);
17321+    }
17322+
17323+    // see: https://github.com/gpuweb/gpuweb/issues/513
17324+    // NOTE: can't express read-only/write-only vs read-write in sokol-gfx
17325+    // e.g. some of the below formats are only read-write with texture-tier-2
17326+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]);
17327+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]);
17328+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]);
17329+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]);
17330+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]);
17331+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]);
17332+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]);
17333+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32UI]);
17334+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32SI]);
17335+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R32F]);
17336+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32UI]);
17337+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32SI]);
17338+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RG32F]);
17339+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]);
17340+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]);
17341+    _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]);
17342+    if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureFormatsTier2)) {
17343+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8]);
17344+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8UI]);
17345+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R8SI]);
17346+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16UI]);
17347+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16SI]);
17348+        _sg_pixelformat_compute_all(&_sg.formats[SG_PIXELFORMAT_R16F]);
17349+    }
17350+}
17351+
17352+_SOKOL_PRIVATE void _sg_wgpu_uniform_system_init(const sg_desc* desc) {
17353+    SOKOL_ASSERT(0 == _sg.wgpu.uniform.staging);
17354+    SOKOL_ASSERT(0 == _sg.wgpu.uniform.buf);
17355+
17356+    // Add the max-uniform-update size (64 KB) to the requested buffer size,
17357+    // this is to prevent validation errors in the WebGPU implementation
17358+    // if the entire buffer size is used per frame. 64 KB is the allowed
17359+    // max uniform update size on NVIDIA
17360+    //
17361+    // FIXME: is this still needed?
17362+    _sg.wgpu.uniform.num_bytes = (uint32_t)(desc->uniform_buffer_size + _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE);
17363+    _sg.wgpu.uniform.staging = (uint8_t*)_sg_malloc(_sg.wgpu.uniform.num_bytes);
17364+
17365+    _SG_STRUCT(WGPUBufferDescriptor, ub_desc);
17366+    ub_desc.size = _sg.wgpu.uniform.num_bytes;
17367+    ub_desc.usage = WGPUBufferUsage_Uniform|WGPUBufferUsage_CopyDst;
17368+    _sg.wgpu.uniform.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &ub_desc);
17369+    SOKOL_ASSERT(_sg.wgpu.uniform.buf);
17370+}
17371+
17372+_SOKOL_PRIVATE void _sg_wgpu_uniform_system_discard(void) {
17373+    if (_sg.wgpu.uniform.buf) {
17374+        wgpuBufferRelease(_sg.wgpu.uniform.buf);
17375+        _sg.wgpu.uniform.buf = 0;
17376+    }
17377+    if (_sg.wgpu.uniform.staging) {
17378+        _sg_free(_sg.wgpu.uniform.staging);
17379+        _sg.wgpu.uniform.staging = 0;
17380+    }
17381+}
17382+
17383+_SOKOL_PRIVATE void _sg_wgpu_uniform_system_set_bindgroup(void) {
17384+    SOKOL_ASSERT(_sg.wgpu.uniform.dirty);
17385+    _sg.wgpu.uniform.dirty = false;
17386+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
17387+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
17388+    // NOTE: dynamic offsets must be in binding order, not in BindGroupEntry order
17389+    SOKOL_ASSERT(shd->wgpu.ub_num_dynoffsets < SG_MAX_UNIFORMBLOCK_BINDSLOTS);
17390+    _SG_STRUCT(uint32_t, dyn_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]);
17391+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
17392+        if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) {
17393+            continue;
17394+        }
17395+        uint8_t dynoffset_index = shd->wgpu.ub_dynoffsets[i];
17396+        SOKOL_ASSERT(dynoffset_index < shd->wgpu.ub_num_dynoffsets);
17397+        dyn_offsets[dynoffset_index] = _sg.wgpu.uniform.bind_offsets[i];
17398+    }
17399+    if (_sg.cur_pass.is_compute) {
17400+        SOKOL_ASSERT(_sg.wgpu.cpass_enc);
17401+        wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc,
17402+            _SG_WGPU_UB_BINDGROUP_INDEX,
17403+            shd->wgpu.bg_ub,
17404+            shd->wgpu.ub_num_dynoffsets,
17405+            dyn_offsets);
17406+    } else {
17407+        SOKOL_ASSERT(_sg.wgpu.rpass_enc);
17408+        wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc,
17409+            _SG_WGPU_UB_BINDGROUP_INDEX,
17410+            shd->wgpu.bg_ub,
17411+            shd->wgpu.ub_num_dynoffsets,
17412+            dyn_offsets);
17413+    }
17414+}
17415+
17416+_SOKOL_PRIVATE void _sg_wgpu_uniform_system_on_apply_pipeline(void) {
17417+    _sg.wgpu.uniform.dirty = false;
17418+}
17419+
17420+_SOKOL_PRIVATE void _sg_wgpu_uniform_system_on_commit(void) {
17421+    wgpuQueueWriteBuffer(_sg.wgpu.queue, _sg.wgpu.uniform.buf, 0, _sg.wgpu.uniform.staging, _sg.wgpu.uniform.offset);
17422+    _sg_stats_add(wgpu.uniforms.size_write_buffer, _sg.wgpu.uniform.offset);
17423+    _sg.wgpu.uniform.offset = 0;
17424+    _sg_clear(_sg.wgpu.uniform.bind_offsets, sizeof(_sg.wgpu.uniform.bind_offsets));
17425+}
17426+
17427+_SOKOL_PRIVATE void _sg_wgpu_bindgroups_pool_init(const sg_desc* desc) {
17428+    SOKOL_ASSERT((desc->wgpu.bindgroups_cache_size > 0) && (desc->wgpu.bindgroups_cache_size < _SG_MAX_POOL_SIZE));
17429+    _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool;
17430+    SOKOL_ASSERT(0 == p->bindgroups);
17431+    const int pool_size = desc->wgpu.bindgroups_cache_size;
17432+    _sg_pool_init(&p->pool, pool_size);
17433+    size_t pool_byte_size = sizeof(_sg_wgpu_bindgroup_t) * (size_t)p->pool.size;
17434+    p->bindgroups = (_sg_wgpu_bindgroup_t*) _sg_malloc_clear(pool_byte_size);
17435+}
17436+
17437+_SOKOL_PRIVATE void _sg_wgpu_bindgroups_pool_discard(void) {
17438+    _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool;
17439+    SOKOL_ASSERT(p->bindgroups);
17440+    _sg_free(p->bindgroups); p->bindgroups = 0;
17441+    _sg_pool_discard(&p->pool);
17442+}
17443+
17444+_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_bindgroup_at(uint32_t bg_id) {
17445+    SOKOL_ASSERT(SG_INVALID_ID != bg_id);
17446+    _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool;
17447+    int slot_index = _sg_slot_index(bg_id);
17448+    SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->pool.size));
17449+    return &p->bindgroups[slot_index];
17450+}
17451+
17452+_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_lookup_bindgroup(uint32_t bg_id) {
17453+    if (SG_INVALID_ID != bg_id) {
17454+        _sg_wgpu_bindgroup_t* bg = _sg_wgpu_bindgroup_at(bg_id);
17455+        if (bg->slot.id == bg_id) {
17456+            return bg;
17457+        }
17458+    }
17459+    return 0;
17460+}
17461+
17462+_SOKOL_PRIVATE _sg_wgpu_bindgroup_handle_t _sg_wgpu_alloc_bindgroup(void) {
17463+    _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool;
17464+    _sg_wgpu_bindgroup_handle_t res;
17465+    int slot_index = _sg_pool_alloc_index(&p->pool);
17466+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
17467+        res.id = _sg_slot_alloc(&p->pool, &p->bindgroups[slot_index].slot, slot_index);
17468+    } else {
17469+        res.id = SG_INVALID_ID;
17470+        _SG_ERROR(WGPU_BINDGROUPS_POOL_EXHAUSTED);
17471+    }
17472+    return res;
17473+}
17474+
17475+_SOKOL_PRIVATE void _sg_wgpu_dealloc_bindgroup(_sg_wgpu_bindgroup_t* bg) {
17476+    SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_ALLOC) && (bg->slot.id != SG_INVALID_ID));
17477+    _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool;
17478+    _sg_pool_free_index(&p->pool, _sg_slot_index(bg->slot.id));
17479+    _sg_slot_reset(&bg->slot);
17480+}
17481+
17482+_SOKOL_PRIVATE void _sg_wgpu_reset_bindgroup_to_alloc_state(_sg_wgpu_bindgroup_t* bg) {
17483+    SOKOL_ASSERT(bg);
17484+    _sg_slot_t slot = bg->slot;
17485+    _sg_clear(bg, sizeof(_sg_wgpu_bindgroup_t));
17486+    bg->slot = slot;
17487+    bg->slot.state = SG_RESOURCESTATE_ALLOC;
17488+}
17489+
17490+// MurmurHash64B (see: https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash2.cpp#L142)
17491+_SOKOL_PRIVATE uint64_t _sg_wgpu_hash(const void* key, int len, uint64_t seed) {
17492+    const uint32_t m = 0x5bd1e995;
17493+    const int r = 24;
17494+    uint32_t h1 = (uint32_t)seed ^ (uint32_t)len;
17495+    uint32_t h2 = (uint32_t)(seed >> 32);
17496+    const uint32_t * data = (const uint32_t *)key;
17497+    while (len >= 8) {
17498+        uint32_t k1 = *data++;
17499+        k1 *= m; k1 ^= k1 >> r; k1 *= m;
17500+        h1 *= m; h1 ^= k1;
17501+        len -= 4;
17502+        uint32_t k2 = *data++;
17503+        k2 *= m; k2 ^= k2 >> r; k2 *= m;
17504+        h2 *= m; h2 ^= k2;
17505+        len -= 4;
17506+    }
17507+    if (len >= 4) {
17508+        uint32_t k1 = *data++;
17509+        k1 *= m; k1 ^= k1 >> r; k1 *= m;
17510+        h1 *= m; h1 ^= k1;
17511+        len -= 4;
17512+    }
17513+    switch(len) {
17514+        case 3: h2 ^= (uint32_t)(((unsigned char*)data)[2] << 16);
17515+        // fall through
17516+        case 2: h2 ^= (uint32_t)(((unsigned char*)data)[1] << 8);
17517+        // fall through
17518+        case 1: h2 ^= ((unsigned char*)data)[0];
17519+        // fall through
17520+        h2 *= m;
17521+    };
17522+    h1 ^= h2 >> 18; h1 *= m;
17523+    h2 ^= h1 >> 22; h2 *= m;
17524+    h1 ^= h2 >> 17; h1 *= m;
17525+    h2 ^= h1 >> 19; h2 *= m;
17526+    uint64_t h = h1;
17527+    h = (h << 32) | h2;
17528+    return h;
17529+}
17530+
17531+_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_item(_sg_wgpu_bindgroups_cache_item_type_t type, uint8_t wgpu_binding, uint32_t id, uint32_t uninit_count) {
17532+    const uint64_t bb = wgpu_binding;
17533+    const uint64_t t = type & 3;
17534+    const uint64_t ccccc = uninit_count & ((1 << 22) - 1);
17535+    const uint64_t iiiiiiii = id;
17536+    return (bb << 56) | (t << 54) | (ccccc << 32) | iiiiiiii;
17537+}
17538+
17539+_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_pip_item(const _sg_slot_t* slot) {
17540+    return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE, 0xFF, slot->id, slot->uninit_count);
17541+}
17542+
17543+_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_view_item(uint8_t wgpu_binding, const _sg_slot_t* slot) {
17544+    return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_VIEW, wgpu_binding, slot->id, slot->uninit_count);
17545+}
17546+
17547+_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_sampler_item(uint8_t wgpu_binding, const _sg_slot_t* slot) {
17548+    return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER, wgpu_binding, slot->id, slot->uninit_count);
17549+}
17550+
17551+_SOKOL_PRIVATE void _sg_wgpu_init_bindgroups_cache_key(_sg_wgpu_bindgroups_cache_key_t* key, const _sg_bindings_ptrs_t* bnd) {
17552+    SOKOL_ASSERT(bnd);
17553+    SOKOL_ASSERT(bnd->pip);
17554+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
17555+
17556+    _sg_clear(key->items, sizeof(key->items));
17557+    key->items[0] = _sg_wgpu_bindgroups_cache_pip_item(&bnd->pip->slot);
17558+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
17559+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
17560+            continue;
17561+        }
17562+        SOKOL_ASSERT(bnd->views[i]);
17563+        const size_t item_idx = i + 1;
17564+        SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS);
17565+        SOKOL_ASSERT(0 == key->items[item_idx]);
17566+        const uint8_t wgpu_binding = shd->wgpu.view_grp1_bnd_n[i];
17567+        key->items[item_idx] = _sg_wgpu_bindgroups_cache_view_item(wgpu_binding, &bnd->views[i]->slot);
17568+    }
17569+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
17570+        if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) {
17571+            continue;
17572+        }
17573+        SOKOL_ASSERT(bnd->smps[i]);
17574+        const size_t item_idx = i + 1 + SG_MAX_VIEW_BINDSLOTS;
17575+        SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS);
17576+        SOKOL_ASSERT(0 == key->items[item_idx]);
17577+        const uint8_t wgpu_binding = shd->wgpu.smp_grp1_bnd_n[i];
17578+        key->items[item_idx] = _sg_wgpu_bindgroups_cache_sampler_item(wgpu_binding, &bnd->smps[i]->slot);
17579+    }
17580+    key->hash = _sg_wgpu_hash(&key->items, (int)sizeof(key->items), 0x1234567887654321);
17581+}
17582+
17583+_SOKOL_PRIVATE bool _sg_wgpu_compare_bindgroups_cache_key(_sg_wgpu_bindgroups_cache_key_t* k0, _sg_wgpu_bindgroups_cache_key_t* k1) {
17584+    SOKOL_ASSERT(k0 && k1);
17585+    if (k0->hash != k1->hash) {
17586+        return false;
17587+    }
17588+    if (memcmp(&k0->items, &k1->items, sizeof(k0->items)) != 0) {
17589+        _sg_stats_inc(wgpu.bindings.num_bindgroup_cache_hash_vs_key_mismatch);
17590+        return false;
17591+    }
17592+    return true;
17593+}
17594+
17595+_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_create_bindgroup(_sg_bindings_ptrs_t* bnd) {
17596+    SOKOL_ASSERT(_sg.wgpu.dev);
17597+    SOKOL_ASSERT(bnd->pip);
17598+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
17599+    _sg_stats_inc(wgpu.bindings.num_create_bindgroup);
17600+    _sg_wgpu_bindgroup_handle_t bg_id = _sg_wgpu_alloc_bindgroup();
17601+    if (bg_id.id == SG_INVALID_ID) {
17602+        return 0;
17603+    }
17604+    _sg_wgpu_bindgroup_t* bg = _sg_wgpu_bindgroup_at(bg_id.id);
17605+    SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_ALLOC));
17606+
17607+    // create wgpu bindgroup object (also see _sg_wgpu_create_shader())
17608+    WGPUBindGroupLayout bgl = shd->wgpu.bgl_view_smp;
17609+    SOKOL_ASSERT(bgl);
17610+    _SG_STRUCT(WGPUBindGroupEntry, bg_entries[_SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES]);
17611+    size_t bgl_index = 0;
17612+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
17613+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
17614+            continue;
17615+        }
17616+        const _sg_view_t* view = bnd->views[i];
17617+        SOKOL_ASSERT(view);
17618+        SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES);
17619+        WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index];
17620+        bg_entry->binding = shd->wgpu.view_grp1_bnd_n[i];
17621+        if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
17622+            const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
17623+            SOKOL_ASSERT(buf->wgpu.buf);
17624+            SOKOL_ASSERT(view->cmn.buf.offset < buf->cmn.size);
17625+            bg_entry->buffer = buf->wgpu.buf;
17626+            bg_entry->offset = (uint64_t)view->cmn.buf.offset;
17627+            bg_entry->size = (uint64_t)(buf->cmn.size - view->cmn.buf.offset);
17628+        } else {
17629+            SOKOL_ASSERT(view->wgpu.view);
17630+            bg_entry->textureView = view->wgpu.view;
17631+        }
17632+        bgl_index += 1;
17633+    }
17634+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
17635+        if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) {
17636+            continue;
17637+        }
17638+        SOKOL_ASSERT(bnd->smps[i]);
17639+        SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES);
17640+        WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index];
17641+        bg_entry->binding = shd->wgpu.smp_grp1_bnd_n[i];
17642+        bg_entry->sampler = bnd->smps[i]->wgpu.smp;
17643+        bgl_index += 1;
17644+    }
17645+    _SG_STRUCT(WGPUBindGroupDescriptor, bg_desc);
17646+    bg_desc.layout = bgl;
17647+    bg_desc.entryCount = bgl_index;
17648+    bg_desc.entries = bg_entries;
17649+    bg->bindgroup = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc);
17650+    if (bg->bindgroup == 0) {
17651+        _SG_ERROR(WGPU_CREATEBINDGROUP_FAILED);
17652+        bg->slot.state = SG_RESOURCESTATE_FAILED;
17653+        return bg;
17654+    }
17655+    _sg_wgpu_init_bindgroups_cache_key(&bg->key, bnd);
17656+    bg->slot.state = SG_RESOURCESTATE_VALID;
17657+    return bg;
17658+}
17659+
17660+_SOKOL_PRIVATE void _sg_wgpu_discard_bindgroup(_sg_wgpu_bindgroup_t* bg) {
17661+    SOKOL_ASSERT(bg);
17662+    _sg_stats_inc(wgpu.bindings.num_discard_bindgroup);
17663+    if (bg->slot.state == SG_RESOURCESTATE_VALID) {
17664+        if (bg->bindgroup) {
17665+            wgpuBindGroupRelease(bg->bindgroup);
17666+            bg->bindgroup = 0;
17667+        }
17668+        _sg_wgpu_reset_bindgroup_to_alloc_state(bg);
17669+        SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_ALLOC);
17670+    }
17671+    if (bg->slot.state == SG_RESOURCESTATE_ALLOC) {
17672+        _sg_wgpu_dealloc_bindgroup(bg);
17673+        SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_INITIAL);
17674+    }
17675+}
17676+
17677+_SOKOL_PRIVATE void _sg_wgpu_discard_all_bindgroups(void) {
17678+    _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool;
17679+    for (int i = 0; i < p->pool.size; i++) {
17680+        sg_resource_state state = p->bindgroups[i].slot.state;
17681+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
17682+            _sg_wgpu_discard_bindgroup(&p->bindgroups[i]);
17683+        }
17684+    }
17685+}
17686+
17687+_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_init(const sg_desc* desc) {
17688+    SOKOL_ASSERT(desc);
17689+    SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.num == 0);
17690+    SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.index_mask == 0);
17691+    SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items == 0);
17692+    const int num = desc->wgpu.bindgroups_cache_size;
17693+    if (num <= 1) {
17694+        _SG_PANIC(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE);
17695+    }
17696+    if (!_sg_ispow2(num)) {
17697+        _SG_PANIC(WGPU_BINDGROUPSCACHE_SIZE_POW2);
17698+    }
17699+    _sg.wgpu.bindgroups_cache.num = (uint32_t)desc->wgpu.bindgroups_cache_size;
17700+    _sg.wgpu.bindgroups_cache.index_mask = _sg.wgpu.bindgroups_cache.num - 1;
17701+    size_t size_in_bytes = sizeof(_sg_wgpu_bindgroup_handle_t) * (size_t)num;
17702+    _sg.wgpu.bindgroups_cache.items = (_sg_wgpu_bindgroup_handle_t*)_sg_malloc_clear(size_in_bytes);
17703+}
17704+
17705+_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_discard(void) {
17706+    if (_sg.wgpu.bindgroups_cache.items) {
17707+        _sg_free(_sg.wgpu.bindgroups_cache.items);
17708+        _sg.wgpu.bindgroups_cache.items = 0;
17709+    }
17710+    _sg.wgpu.bindgroups_cache.num = 0;
17711+    _sg.wgpu.bindgroups_cache.index_mask = 0;
17712+}
17713+
17714+_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_set(uint64_t hash, uint32_t bg_id) {
17715+    uint32_t index = hash & _sg.wgpu.bindgroups_cache.index_mask;
17716+    SOKOL_ASSERT(index < _sg.wgpu.bindgroups_cache.num);
17717+    SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items);
17718+    _sg.wgpu.bindgroups_cache.items[index].id = bg_id;
17719+}
17720+
17721+_SOKOL_PRIVATE uint32_t _sg_wgpu_bindgroups_cache_get(uint64_t hash) {
17722+    uint32_t index = hash & _sg.wgpu.bindgroups_cache.index_mask;
17723+    SOKOL_ASSERT(index < _sg.wgpu.bindgroups_cache.num);
17724+    SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items);
17725+    return _sg.wgpu.bindgroups_cache.items[index].id;
17726+}
17727+
17728+// called from wgpu resource destroy functions to also invalidate any
17729+// bindgroups cache slot and bindgroup referencing that resource
17730+_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_invalidate(_sg_wgpu_bindgroups_cache_item_type_t type, const _sg_slot_t* slot) {
17731+    const uint64_t key_mask = _sg_wgpu_bindgroups_cache_item(type, 0xFF, 0xFFFFFFFF, 0xFFFFFFFF);
17732+    const uint64_t key_item = _sg_wgpu_bindgroups_cache_item(type, 0, slot->id, slot->uninit_count) & key_mask;
17733+    SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items);
17734+    for (uint32_t cache_item_idx = 0; cache_item_idx < _sg.wgpu.bindgroups_cache.num; cache_item_idx++) {
17735+        const uint32_t bg_id = _sg.wgpu.bindgroups_cache.items[cache_item_idx].id;
17736+        if (bg_id != SG_INVALID_ID) {
17737+            _sg_wgpu_bindgroup_t* bg = _sg_wgpu_lookup_bindgroup(bg_id);
17738+            SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_VALID));
17739+            // check if resource is in bindgroup, if yes discard bindgroup and invalidate cache slot
17740+            bool invalidate_cache_item = false;
17741+            for (int key_item_idx = 0; key_item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS; key_item_idx++) {
17742+                if ((bg->key.items[key_item_idx] & key_mask) == key_item) {
17743+                    invalidate_cache_item = true;
17744+                    break;
17745+                }
17746+            }
17747+            if (invalidate_cache_item) {
17748+                _sg_wgpu_discard_bindgroup(bg); bg = 0;
17749+                _sg_wgpu_bindgroups_cache_set(cache_item_idx, SG_INVALID_ID);
17750+                _sg_stats_inc(wgpu.bindings.num_bindgroup_cache_invalidates);
17751+            }
17752+        }
17753+    }
17754+}
17755+
17756+_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_clear(void) {
17757+    memset(&_sg.wgpu.bindings_cache, 0, sizeof(_sg.wgpu.bindings_cache));
17758+}
17759+
17760+_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_vb_dirty(size_t index, const _sg_buffer_t* vb, uint64_t offset) {
17761+    SOKOL_ASSERT(index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
17762+    if (vb) {
17763+        return (_sg.wgpu.bindings_cache.vbs[index].buffer.id != vb->slot.id)
17764+            || (_sg.wgpu.bindings_cache.vbs[index].offset != offset);
17765+    } else {
17766+        return _sg.wgpu.bindings_cache.vbs[index].buffer.id != SG_INVALID_ID;
17767+    }
17768+}
17769+
17770+_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_vb_update(size_t index, const _sg_buffer_t* vb, uint64_t offset) {
17771+    SOKOL_ASSERT(index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
17772+    if (vb) {
17773+        _sg.wgpu.bindings_cache.vbs[index].buffer.id = vb->slot.id;
17774+        _sg.wgpu.bindings_cache.vbs[index].offset = offset;
17775+    } else {
17776+        _sg.wgpu.bindings_cache.vbs[index].buffer.id = SG_INVALID_ID;
17777+        _sg.wgpu.bindings_cache.vbs[index].offset = 0;
17778+    }
17779+}
17780+
17781+_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_ib_dirty(const _sg_buffer_t* ib, uint64_t offset) {
17782+    if (ib) {
17783+        return (_sg.wgpu.bindings_cache.ib.buffer.id != ib->slot.id)
17784+            || (_sg.wgpu.bindings_cache.ib.offset != offset);
17785+    } else {
17786+        return _sg.wgpu.bindings_cache.ib.buffer.id != SG_INVALID_ID;
17787+    }
17788+}
17789+
17790+_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_ib_update(const _sg_buffer_t* ib, uint64_t offset) {
17791+    if (ib) {
17792+        _sg.wgpu.bindings_cache.ib.buffer.id = ib->slot.id;
17793+        _sg.wgpu.bindings_cache.ib.offset = offset;
17794+    } else {
17795+        _sg.wgpu.bindings_cache.ib.buffer.id = SG_INVALID_ID;
17796+        _sg.wgpu.bindings_cache.ib.offset = 0;
17797+    }
17798+}
17799+
17800+_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_bg_dirty(const _sg_wgpu_bindgroup_t* bg) {
17801+    if (bg) {
17802+        return _sg.wgpu.bindings_cache.bg.id != bg->slot.id;
17803+    } else {
17804+        return _sg.wgpu.bindings_cache.bg.id != SG_INVALID_ID;
17805+    }
17806+}
17807+
17808+_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_bg_update(const _sg_wgpu_bindgroup_t* bg) {
17809+    if (bg) {
17810+        _sg.wgpu.bindings_cache.bg.id = bg->slot.id;
17811+    } else {
17812+        _sg.wgpu.bindings_cache.bg.id = SG_INVALID_ID;
17813+    }
17814+}
17815+
17816+_SOKOL_PRIVATE void _sg_wgpu_set_bindgroup(uint32_t bg_idx, _sg_wgpu_bindgroup_t* bg) {
17817+    if (_sg_wgpu_bindings_cache_bg_dirty(bg)) {
17818+        _sg_wgpu_bindings_cache_bg_update(bg);
17819+        _sg_stats_inc(wgpu.bindings.num_set_bindgroup);
17820+        if (_sg.cur_pass.is_compute) {
17821+            SOKOL_ASSERT(_sg.wgpu.cpass_enc);
17822+            if (bg) {
17823+                SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID);
17824+                SOKOL_ASSERT(bg->bindgroup);
17825+                wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, bg_idx, bg->bindgroup, 0, 0);
17826+            } else {
17827+                wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, bg_idx, 0, 0, 0);
17828+            }
17829+        } else {
17830+            SOKOL_ASSERT(_sg.wgpu.rpass_enc);
17831+            if (bg) {
17832+                SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID);
17833+                SOKOL_ASSERT(bg->bindgroup);
17834+                wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, bg_idx, bg->bindgroup, 0, 0);
17835+            } else {
17836+                wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, bg_idx, 0, 0, 0);
17837+            }
17838+        }
17839+    } else {
17840+        _sg_stats_inc(wgpu.bindings.num_skip_redundant_bindgroup);
17841+    }
17842+}
17843+
17844+_SOKOL_PRIVATE bool _sg_wgpu_apply_bindings_bindgroup(_sg_bindings_ptrs_t* bnd) {
17845+    if (!_sg.desc.wgpu.disable_bindgroups_cache) {
17846+        _sg_wgpu_bindgroup_t* bg = 0;
17847+        _sg_wgpu_bindgroups_cache_key_t key;
17848+        _sg_wgpu_init_bindgroups_cache_key(&key, bnd);
17849+        uint32_t bg_id = _sg_wgpu_bindgroups_cache_get(key.hash);
17850+        if (bg_id != SG_INVALID_ID) {
17851+            // potential cache hit
17852+            bg = _sg_wgpu_lookup_bindgroup(bg_id);
17853+            SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_VALID));
17854+            if (!_sg_wgpu_compare_bindgroups_cache_key(&key, &bg->key)) {
17855+                // cache collision, need to delete cached bindgroup
17856+                _sg_stats_inc(wgpu.bindings.num_bindgroup_cache_collisions);
17857+                _sg_wgpu_discard_bindgroup(bg);
17858+                _sg_wgpu_bindgroups_cache_set(key.hash, SG_INVALID_ID);
17859+                bg = 0;
17860+            } else {
17861+                _sg_stats_inc(wgpu.bindings.num_bindgroup_cache_hits);
17862+            }
17863+        } else {
17864+            _sg_stats_inc(wgpu.bindings.num_bindgroup_cache_misses);
17865+        }
17866+        if (bg == 0) {
17867+            // either no cache entry yet, or cache collision, create new bindgroup and store in cache
17868+            bg = _sg_wgpu_create_bindgroup(bnd);
17869+            _sg_wgpu_bindgroups_cache_set(key.hash, bg->slot.id);
17870+        }
17871+        if (bg && bg->slot.state == SG_RESOURCESTATE_VALID) {
17872+            _sg_wgpu_set_bindgroup(_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, bg);
17873+        } else {
17874+            return false;
17875+        }
17876+    } else {
17877+        // bindgroups cache disabled, create and destroy bindgroup on the fly (expensive!)
17878+        _sg_wgpu_bindgroup_t* bg = _sg_wgpu_create_bindgroup(bnd);
17879+        if (bg) {
17880+            if (bg->slot.state == SG_RESOURCESTATE_VALID) {
17881+                _sg_wgpu_set_bindgroup(_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, bg);
17882+            }
17883+            _sg_wgpu_discard_bindgroup(bg);
17884+        } else {
17885+            return false;
17886+        }
17887+    }
17888+    return true;
17889+}
17890+
17891+_SOKOL_PRIVATE bool _sg_wgpu_apply_index_buffer(_sg_bindings_ptrs_t* bnd) {
17892+    SOKOL_ASSERT(_sg.wgpu.rpass_enc);
17893+    const _sg_buffer_t* ib = bnd->ib;
17894+    uint64_t offset = (uint64_t)bnd->ib_offset;
17895+    if (_sg_wgpu_bindings_cache_ib_dirty(ib, offset)) {
17896+        _sg_wgpu_bindings_cache_ib_update(ib, offset);
17897+        if (ib) {
17898+            const WGPUIndexFormat format = _sg_wgpu_indexformat(bnd->pip->cmn.index_type);
17899+            const uint64_t buf_size = (uint64_t)ib->cmn.size;
17900+            SOKOL_ASSERT(buf_size > offset);
17901+            const uint64_t max_bytes = buf_size - offset;
17902+            wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.rpass_enc, ib->wgpu.buf, format, offset, max_bytes);
17903+        /*
17904+            NOTE: as per webgpu spec setIndexBuffer does not accept a null pointer
17905+        } else {
17906+            wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.rpass_enc, 0, WGPUIndexFormat_Undefined, 0, 0);
17907+        */
17908+        }
17909+        _sg_stats_inc(wgpu.bindings.num_set_index_buffer);
17910+    } else {
17911+        _sg_stats_inc(wgpu.bindings.num_skip_redundant_index_buffer);
17912+    }
17913+    return true;
17914+}
17915+
17916+_SOKOL_PRIVATE bool _sg_wgpu_apply_vertex_buffers(_sg_bindings_ptrs_t* bnd) {
17917+    SOKOL_ASSERT(_sg.wgpu.rpass_enc);
17918+    for (uint32_t slot = 0; slot < SG_MAX_VERTEXBUFFER_BINDSLOTS; slot++) {
17919+        const _sg_buffer_t* vb = bnd->vbs[slot];
17920+        const uint64_t offset = (uint64_t)bnd->vb_offsets[slot];
17921+        if (_sg_wgpu_bindings_cache_vb_dirty(slot, vb, offset)) {
17922+            _sg_wgpu_bindings_cache_vb_update(slot, vb, offset);
17923+            if (vb) {
17924+                const uint64_t buf_size = (uint64_t)vb->cmn.size;
17925+                SOKOL_ASSERT(buf_size > offset);
17926+                const uint64_t max_bytes = buf_size - offset;
17927+                wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.rpass_enc, slot, vb->wgpu.buf, offset, max_bytes);
17928+            } else {
17929+                wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.rpass_enc, slot, 0, 0, 0);
17930+            }
17931+            _sg_stats_inc(wgpu.bindings.num_set_vertex_buffer);
17932+        } else {
17933+            _sg_stats_inc(wgpu.bindings.num_skip_redundant_vertex_buffer);
17934+        }
17935+    }
17936+    return true;
17937+}
17938+
17939+_SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) {
17940+    SOKOL_ASSERT(desc);
17941+    SOKOL_ASSERT(desc->environment.wgpu.device);
17942+    SOKOL_ASSERT(desc->uniform_buffer_size > 0);
17943+    _sg.wgpu.valid = true;
17944+    _sg.wgpu.dev = (WGPUDevice) desc->environment.wgpu.device;
17945+    _sg.wgpu.queue = wgpuDeviceGetQueue(_sg.wgpu.dev);
17946+    SOKOL_ASSERT(_sg.wgpu.queue);
17947+
17948+    _sg_wgpu_init_caps();
17949+    _sg_wgpu_uniform_system_init(desc);
17950+    _sg_wgpu_bindgroups_pool_init(desc);
17951+    _sg_wgpu_bindgroups_cache_init(desc);
17952+    _sg_wgpu_bindings_cache_clear();
17953+}
17954+
17955+_SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) {
17956+    SOKOL_ASSERT(_sg.wgpu.valid);
17957+    _sg.wgpu.valid = false;
17958+    _sg_wgpu_discard_all_bindgroups();
17959+    _sg_wgpu_bindgroups_cache_discard();
17960+    _sg_wgpu_bindgroups_pool_discard();
17961+    _sg_wgpu_uniform_system_discard();
17962+    // the command encoder is usually released in sg_commit()
17963+    if (_sg.wgpu.cmd_enc) {
17964+        wgpuCommandEncoderRelease(_sg.wgpu.cmd_enc); _sg.wgpu.cmd_enc = 0;
17965+    }
17966+    wgpuQueueRelease(_sg.wgpu.queue); _sg.wgpu.queue = 0;
17967+}
17968+
17969+_SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) {
17970+    _sg_wgpu_bindings_cache_clear();
17971+}
17972+
17973+_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
17974+    SOKOL_ASSERT(buf && desc);
17975+    SOKOL_ASSERT(buf->cmn.size > 0);
17976+    const bool injected = (0 != desc->wgpu_buffer);
17977+    if (injected) {
17978+        buf->wgpu.buf = (WGPUBuffer) desc->wgpu_buffer;
17979+        wgpuBufferAddRef(buf->wgpu.buf);
17980+    } else {
17981+        // buffer mapping size must be multiple of 4, so round up buffer size (only a problem
17982+        // with index buffers containing odd number of indices)
17983+        const uint64_t wgpu_buf_size = _sg_roundup_u64((uint64_t)buf->cmn.size, 4);
17984+        const bool map_at_creation = buf->cmn.usage.immutable && (desc->data.ptr);
17985+
17986+        _SG_STRUCT(WGPUBufferDescriptor, wgpu_buf_desc);
17987+        wgpu_buf_desc.usage = _sg_wgpu_buffer_usage(&buf->cmn.usage);
17988+        wgpu_buf_desc.size = wgpu_buf_size;
17989+        wgpu_buf_desc.mappedAtCreation = map_at_creation;
17990+        wgpu_buf_desc.label = _sg_wgpu_stringview(desc->label);
17991+        buf->wgpu.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &wgpu_buf_desc);
17992+        if (0 == buf->wgpu.buf) {
17993+            _SG_ERROR(WGPU_CREATE_BUFFER_FAILED);
17994+            return SG_RESOURCESTATE_FAILED;
17995+        }
17996+        if (map_at_creation) {
17997+            SOKOL_ASSERT(desc->data.ptr && (desc->data.size > 0));
17998+            SOKOL_ASSERT(desc->data.size <= (size_t)buf->cmn.size);
17999+            // FIXME: inefficient on WASM
18000+            void* ptr = wgpuBufferGetMappedRange(buf->wgpu.buf, 0, wgpu_buf_size);
18001+            SOKOL_ASSERT(ptr);
18002+            memcpy(ptr, desc->data.ptr, desc->data.size);
18003+            wgpuBufferUnmap(buf->wgpu.buf);
18004+        }
18005+    }
18006+    return SG_RESOURCESTATE_VALID;
18007+}
18008+
18009+_SOKOL_PRIVATE void _sg_wgpu_discard_buffer(_sg_buffer_t* buf) {
18010+    SOKOL_ASSERT(buf);
18011+    if (buf->wgpu.buf) {
18012+        wgpuBufferRelease(buf->wgpu.buf);
18013+    }
18014+}
18015+
18016+_SOKOL_PRIVATE void _sg_wgpu_copy_buffer_data(const _sg_buffer_t* buf, uint64_t offset, const sg_range* data) {
18017+    SOKOL_ASSERT((offset + data->size) <= (size_t)buf->cmn.size);
18018+    // WebGPU's write-buffer requires the size to be a multiple of four, so we may need to split the copy
18019+    // operation into two writeBuffer calls
18020+    uint64_t clamped_size = data->size & ~3UL;
18021+    uint64_t extra_size = data->size & 3UL;
18022+    SOKOL_ASSERT(extra_size < 4);
18023+    wgpuQueueWriteBuffer(_sg.wgpu.queue, buf->wgpu.buf, offset, data->ptr, clamped_size);
18024+    if (extra_size > 0) {
18025+        const uint64_t extra_src_offset = clamped_size;
18026+        const uint64_t extra_dst_offset = offset + clamped_size;
18027+        uint8_t extra_data[4] = { 0 };
18028+        const uint8_t* extra_src_ptr = ((uint8_t*)data->ptr) + extra_src_offset;
18029+        for (size_t i = 0; i < extra_size; i++) {
18030+            extra_data[i] = extra_src_ptr[i];
18031+        }
18032+        wgpuQueueWriteBuffer(_sg.wgpu.queue, buf->wgpu.buf, extra_dst_offset, extra_data, 4);
18033+    }
18034+}
18035+
18036+_SOKOL_PRIVATE void _sg_wgpu_copy_image_data(const _sg_image_t* img, const sg_image_data* data) {
18037+    _SG_STRUCT(WGPUTexelCopyBufferLayout, wgpu_layout);
18038+    _SG_STRUCT(WGPUTexelCopyTextureInfo, wgpu_copy_tex);
18039+    wgpu_copy_tex.texture = img->wgpu.tex;
18040+    wgpu_copy_tex.aspect = WGPUTextureAspect_All;
18041+    _SG_STRUCT(WGPUExtent3D, wgpu_extent);
18042+    for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) {
18043+        wgpu_copy_tex.mipLevel = (uint32_t)mip_index;
18044+        int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
18045+        int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
18046+        int mip_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices;
18047+        const int row_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1);
18048+        const int num_rows = _sg_num_rows(img->cmn.pixel_format, mip_height);
18049+        if (_sg_is_compressed_pixel_format(img->cmn.pixel_format)) {
18050+            mip_width = _sg_roundup(mip_width, 4);
18051+            mip_height = _sg_roundup(mip_height, 4);
18052+        }
18053+        wgpu_layout.bytesPerRow = (uint32_t)row_pitch;
18054+        wgpu_layout.rowsPerImage = (uint32_t)num_rows;
18055+        wgpu_extent.width = (uint32_t)mip_width;
18056+        wgpu_extent.height = (uint32_t)mip_height;
18057+        wgpu_extent.depthOrArrayLayers = (uint32_t)mip_slices;
18058+        const sg_range* mip_data = &data->mip_levels[mip_index];
18059+        wgpuQueueWriteTexture(_sg.wgpu.queue, &wgpu_copy_tex, mip_data->ptr, mip_data->size, &wgpu_layout, &wgpu_extent);
18060+    }
18061+}
18062+
18063+_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const sg_image_desc* desc) {
18064+    SOKOL_ASSERT(img && desc);
18065+    const bool injected = (0 != desc->wgpu_texture);
18066+    if (injected) {
18067+        img->wgpu.tex = (WGPUTexture)desc->wgpu_texture;
18068+        wgpuTextureAddRef(img->wgpu.tex);
18069+    } else {
18070+        _SG_STRUCT(WGPUTextureDescriptor, wgpu_tex_desc);
18071+        wgpu_tex_desc.label = _sg_wgpu_stringview(desc->label);
18072+        wgpu_tex_desc.usage = WGPUTextureUsage_TextureBinding|WGPUTextureUsage_CopyDst;
18073+        if (desc->usage.color_attachment || desc->usage.resolve_attachment || desc->usage.depth_stencil_attachment) {
18074+            wgpu_tex_desc.usage |= WGPUTextureUsage_RenderAttachment;
18075+        }
18076+        if (desc->usage.storage_image) {
18077+            wgpu_tex_desc.usage |= WGPUTextureUsage_StorageBinding;
18078+        }
18079+        wgpu_tex_desc.dimension = _sg_wgpu_texture_dimension(img->cmn.type);
18080+        wgpu_tex_desc.size.width = (uint32_t) img->cmn.width;
18081+        wgpu_tex_desc.size.height = (uint32_t) img->cmn.height;
18082+        wgpu_tex_desc.size.depthOrArrayLayers = (uint32_t) img->cmn.num_slices;
18083+        wgpu_tex_desc.format = _sg_wgpu_textureformat(img->cmn.pixel_format);
18084+        wgpu_tex_desc.mipLevelCount = (uint32_t) img->cmn.num_mipmaps;
18085+        wgpu_tex_desc.sampleCount = (uint32_t) img->cmn.sample_count;
18086+        img->wgpu.tex = wgpuDeviceCreateTexture(_sg.wgpu.dev, &wgpu_tex_desc);
18087+        if (0 == img->wgpu.tex) {
18088+            _SG_ERROR(WGPU_CREATE_TEXTURE_FAILED);
18089+            return SG_RESOURCESTATE_FAILED;
18090+        }
18091+        if (desc->data.mip_levels[0].ptr) {
18092+            _sg_wgpu_copy_image_data(img, &desc->data);
18093+        }
18094+    }
18095+    return SG_RESOURCESTATE_VALID;
18096+}
18097+
18098+_SOKOL_PRIVATE void _sg_wgpu_discard_image(_sg_image_t* img) {
18099+    SOKOL_ASSERT(img);
18100+    if (img->wgpu.tex) {
18101+        wgpuTextureRelease(img->wgpu.tex);
18102+        img->wgpu.tex = 0;
18103+    }
18104+}
18105+
18106+_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
18107+    SOKOL_ASSERT(smp && desc);
18108+    SOKOL_ASSERT(_sg.wgpu.dev);
18109+    const bool injected = (0 != desc->wgpu_sampler);
18110+    if (injected) {
18111+        smp->wgpu.smp = (WGPUSampler) desc->wgpu_sampler;
18112+        wgpuSamplerAddRef(smp->wgpu.smp);
18113+    } else {
18114+        _SG_STRUCT(WGPUSamplerDescriptor, wgpu_desc);
18115+        wgpu_desc.label = _sg_wgpu_stringview(desc->label);
18116+        wgpu_desc.addressModeU = _sg_wgpu_sampler_address_mode(desc->wrap_u);
18117+        wgpu_desc.addressModeV = _sg_wgpu_sampler_address_mode(desc->wrap_v);
18118+        wgpu_desc.addressModeW = _sg_wgpu_sampler_address_mode(desc->wrap_w);
18119+        wgpu_desc.magFilter = _sg_wgpu_sampler_minmag_filter(desc->mag_filter);
18120+        wgpu_desc.minFilter = _sg_wgpu_sampler_minmag_filter(desc->min_filter);
18121+        wgpu_desc.mipmapFilter = _sg_wgpu_sampler_mipmap_filter(desc->mipmap_filter);
18122+        wgpu_desc.lodMinClamp = desc->min_lod;
18123+        wgpu_desc.lodMaxClamp = desc->max_lod;
18124+        wgpu_desc.compare = _sg_wgpu_comparefunc(desc->compare);
18125+        if (wgpu_desc.compare == WGPUCompareFunction_Never) {
18126+            wgpu_desc.compare = WGPUCompareFunction_Undefined;
18127+        }
18128+        wgpu_desc.maxAnisotropy = (uint16_t)desc->max_anisotropy;
18129+        smp->wgpu.smp = wgpuDeviceCreateSampler(_sg.wgpu.dev, &wgpu_desc);
18130+        if (0 == smp->wgpu.smp) {
18131+            _SG_ERROR(WGPU_CREATE_SAMPLER_FAILED);
18132+            return SG_RESOURCESTATE_FAILED;
18133+        }
18134+    }
18135+    return SG_RESOURCESTATE_VALID;
18136+}
18137+
18138+_SOKOL_PRIVATE void _sg_wgpu_discard_sampler(_sg_sampler_t* smp) {
18139+    SOKOL_ASSERT(smp);
18140+    _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER, &smp->slot);
18141+    if (smp->wgpu.smp) {
18142+        wgpuSamplerRelease(smp->wgpu.smp);
18143+        smp->wgpu.smp = 0;
18144+    }
18145+}
18146+
18147+_SOKOL_PRIVATE _sg_wgpu_shader_func_t _sg_wgpu_create_shader_func(const sg_shader_function* func, const char* label) {
18148+    SOKOL_ASSERT(func);
18149+    SOKOL_ASSERT(func->source);
18150+    SOKOL_ASSERT(func->entry);
18151+
18152+    _SG_STRUCT(_sg_wgpu_shader_func_t, res);
18153+    _sg_strcpy(&res.entry, func->entry);
18154+
18155+    _SG_STRUCT(WGPUShaderSourceWGSL, wgpu_shdsrc_wgsl);
18156+    wgpu_shdsrc_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL;
18157+    wgpu_shdsrc_wgsl.code = _sg_wgpu_stringview(func->source);
18158+
18159+    _SG_STRUCT(WGPUShaderModuleDescriptor, wgpu_shdmod_desc);
18160+    wgpu_shdmod_desc.nextInChain = &wgpu_shdsrc_wgsl.chain;
18161+    wgpu_shdmod_desc.label = _sg_wgpu_stringview(label);
18162+
18163+    // NOTE: if compilation fails we won't actually find out in this call since
18164+    // it always returns a valid module handle, and the GetCompilationInfo() call
18165+    // is asynchronous
18166+    res.module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc);
18167+    if (0 == res.module) {
18168+        _SG_ERROR(WGPU_CREATE_SHADER_MODULE_FAILED);
18169+    }
18170+    return res;
18171+}
18172+
18173+_SOKOL_PRIVATE void _sg_wgpu_discard_shader_func(_sg_wgpu_shader_func_t* func) {
18174+    if (func->module) {
18175+        wgpuShaderModuleRelease(func->module);
18176+        func->module = 0;
18177+    }
18178+}
18179+
18180+typedef struct { uint8_t sokol_slot, wgpu_slot; } _sg_wgpu_dynoffset_mapping_t;
18181+
18182+_SOKOL_PRIVATE int _sg_wgpu_dynoffset_cmp(const void* a, const void* b) {
18183+    const _sg_wgpu_dynoffset_mapping_t* aa = (const _sg_wgpu_dynoffset_mapping_t*)a;
18184+    const _sg_wgpu_dynoffset_mapping_t* bb = (const _sg_wgpu_dynoffset_mapping_t*)b;
18185+    if (aa->wgpu_slot < bb->wgpu_slot) return -1;
18186+    else if (aa->wgpu_slot > bb->wgpu_slot) return 1;
18187+    return 0;
18188+}
18189+
18190+// NOTE: this is an out-of-range check for WGSL bindslots that's also active in release mode
18191+_SOKOL_PRIVATE bool _sg_wgpu_ensure_wgsl_bindslot_ranges(const sg_shader_desc* desc) {
18192+    SOKOL_ASSERT(desc);
18193+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
18194+        const sg_shader_uniform_block* ub = &desc->uniform_blocks[i];
18195+        if (ub->stage != SG_SHADERSTAGE_NONE) {
18196+            if (ub->wgsl_group0_binding_n >= _SG_WGPU_MAX_UB_BINDGROUP_WGSL_SLOTS) {
18197+                _SG_ERROR(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE);
18198+                return false;
18199+            }
18200+        }
18201+    }
18202+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
18203+        const sg_shader_view* view = &desc->views[i];
18204+        if (view->texture.stage != SG_SHADERSTAGE_NONE) {
18205+            if (view->texture.wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) {
18206+                _SG_ERROR(WGPU_TEXTURE_WGSL_GROUP1_BINDING_OUT_OF_RANGE);
18207+                return false;
18208+            }
18209+        }
18210+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
18211+            if (view->storage_buffer.wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) {
18212+                _SG_ERROR(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE);
18213+                return false;
18214+            }
18215+        }
18216+        if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
18217+            if (view->storage_image.wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) {
18218+                _SG_ERROR(WGPU_STORAGEIMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE);
18219+                return false;
18220+            }
18221+        }
18222+    }
18223+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
18224+        const sg_shader_sampler* smp = &desc->samplers[i];
18225+        if (smp->stage != SG_SHADERSTAGE_NONE) {
18226+            if (smp->wgsl_group1_binding_n >= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_WGSL_SLOTS) {
18227+                _SG_ERROR(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE);
18228+                return false;
18229+            }
18230+        }
18231+    }
18232+    return true;
18233+}
18234+
18235+_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
18236+    SOKOL_ASSERT(shd && desc);
18237+    SOKOL_ASSERT(shd->wgpu.vertex_func.module == 0);
18238+    SOKOL_ASSERT(shd->wgpu.fragment_func.module == 0);
18239+    SOKOL_ASSERT(shd->wgpu.compute_func.module == 0);
18240+    SOKOL_ASSERT(shd->wgpu.bgl_ub == 0);
18241+    SOKOL_ASSERT(shd->wgpu.bg_ub == 0);
18242+    SOKOL_ASSERT(shd->wgpu.bgl_view_smp == 0);
18243+
18244+    // do a release-mode bounds-check on wgsl bindslots, even though out-of-range
18245+    // bindslots can't cause out-of-bounds accesses in the wgpu backend, this
18246+    // is done to be consistent with the other backends
18247+    if (!_sg_wgpu_ensure_wgsl_bindslot_ranges(desc)) {
18248+        return SG_RESOURCESTATE_FAILED;
18249+    }
18250+
18251+    // build shader modules
18252+    bool shd_valid = true;
18253+    if (desc->vertex_func.source) {
18254+        shd->wgpu.vertex_func = _sg_wgpu_create_shader_func(&desc->vertex_func, desc->label);
18255+        shd_valid &= shd->wgpu.vertex_func.module != 0;
18256+    }
18257+    if (desc->fragment_func.source) {
18258+        shd->wgpu.fragment_func = _sg_wgpu_create_shader_func(&desc->fragment_func, desc->label);
18259+        shd_valid &= shd->wgpu.fragment_func.module != 0;
18260+    }
18261+    if (desc->compute_func.source) {
18262+        shd->wgpu.compute_func = _sg_wgpu_create_shader_func(&desc->compute_func, desc->label);
18263+        shd_valid &= shd->wgpu.compute_func.module != 0;
18264+    }
18265+    if (!shd_valid) {
18266+        _sg_wgpu_discard_shader_func(&shd->wgpu.vertex_func);
18267+        _sg_wgpu_discard_shader_func(&shd->wgpu.fragment_func);
18268+        _sg_wgpu_discard_shader_func(&shd->wgpu.compute_func);
18269+        return SG_RESOURCESTATE_FAILED;
18270+    }
18271+
18272+    // create bind group layout and bind group for uniform blocks
18273+    // NOTE also need to create a mapping of sokol ub bind slots to array indices
18274+    // for the dynamic offsets array in the setBindGroup call
18275+    SOKOL_ASSERT(_SG_WGPU_MAX_UB_BINDGROUP_ENTRIES <= _SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES);
18276+    _SG_STRUCT(WGPUBindGroupLayoutEntry, bgl_entries[_SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES]);
18277+    _SG_STRUCT(WGPUBindGroupLayoutDescriptor, bgl_desc);
18278+    _SG_STRUCT(WGPUBindGroupEntry, bg_entries[_SG_WGPU_MAX_VIEW_SMP_BINDGROUP_ENTRIES]);
18279+    _SG_STRUCT(WGPUBindGroupDescriptor, bg_desc);
18280+    _SG_STRUCT(_sg_wgpu_dynoffset_mapping_t, dynoffset_map[SG_MAX_UNIFORMBLOCK_BINDSLOTS]);
18281+    size_t bgl_index = 0;
18282+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
18283+        if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) {
18284+            continue;
18285+        }
18286+        shd->wgpu.ub_grp0_bnd_n[i] = desc->uniform_blocks[i].wgsl_group0_binding_n;
18287+        WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index];
18288+        WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index];
18289+        bgl_entry->binding = shd->wgpu.ub_grp0_bnd_n[i];
18290+        bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.uniform_blocks[i].stage);
18291+        bgl_entry->buffer.type = WGPUBufferBindingType_Uniform;
18292+        bgl_entry->buffer.hasDynamicOffset = true;
18293+        bg_entry->binding = bgl_entry->binding;
18294+        bg_entry->buffer = _sg.wgpu.uniform.buf;
18295+        bg_entry->size = _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE;
18296+        dynoffset_map[i].sokol_slot = (uint8_t)i;
18297+        dynoffset_map[i].wgpu_slot = (uint8_t)bgl_entry->binding;
18298+        bgl_index += 1;
18299+    }
18300+    bgl_desc.entryCount = bgl_index;
18301+    bgl_desc.entries = bgl_entries;
18302+    shd->wgpu.bgl_ub = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc);
18303+    SOKOL_ASSERT(shd->wgpu.bgl_ub);
18304+    bg_desc.layout = shd->wgpu.bgl_ub;
18305+    bg_desc.entryCount = bgl_index;
18306+    bg_desc.entries = bg_entries;
18307+    shd->wgpu.bg_ub = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc);
18308+    SOKOL_ASSERT(shd->wgpu.bg_ub);
18309+
18310+    // sort the dynoffset_map by wgpu bindings, this is because the
18311+    // dynamic offsets of the WebGPU setBindGroup call must be in
18312+    // 'binding order', not 'bindgroup entry order'
18313+    qsort(dynoffset_map, bgl_index, sizeof(_sg_wgpu_dynoffset_mapping_t), _sg_wgpu_dynoffset_cmp);
18314+    shd->wgpu.ub_num_dynoffsets = (uint8_t)bgl_index;
18315+    for (uint8_t i = 0; i < bgl_index; i++) {
18316+        const uint8_t sokol_slot = dynoffset_map[i].sokol_slot;
18317+        shd->wgpu.ub_dynoffsets[sokol_slot] = i;
18318+    }
18319+
18320+    // create bind group layout for textures, storage buffers/images and samplers
18321+    _sg_clear(bgl_entries, sizeof(bgl_entries));
18322+    _sg_clear(&bgl_desc, sizeof(bgl_desc));
18323+    bgl_index = 0;
18324+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
18325+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
18326+            continue;
18327+        }
18328+        WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index];
18329+        bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.views[i].stage);
18330+        if (shd->cmn.views[i].view_type == SG_VIEWTYPE_TEXTURE) {
18331+            shd->wgpu.view_grp1_bnd_n[i] = desc->views[i].texture.wgsl_group1_binding_n;
18332+            const bool msaa = shd->cmn.views[i].multisampled;
18333+            bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.views[i].image_type);
18334+            bgl_entry->texture.sampleType = _sg_wgpu_texture_sample_type(shd->cmn.views[i].sample_type, msaa);
18335+            bgl_entry->texture.multisampled = msaa;
18336+        } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) {
18337+            shd->wgpu.view_grp1_bnd_n[i] = desc->views[i].storage_buffer.wgsl_group1_binding_n;
18338+            if (shd->cmn.views[i].sbuf_readonly) {
18339+                bgl_entry->buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
18340+            } else {
18341+                bgl_entry->buffer.type = WGPUBufferBindingType_Storage;
18342+            }
18343+        } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEIMAGE) {
18344+            shd->wgpu.view_grp1_bnd_n[i] = desc->views[i].storage_image.wgsl_group1_binding_n;
18345+            if (shd->cmn.views[i].simg_writeonly) {
18346+                bgl_entry->storageTexture.access = WGPUStorageTextureAccess_WriteOnly;
18347+            } else {
18348+                bgl_entry->storageTexture.access = WGPUStorageTextureAccess_ReadWrite;
18349+            }
18350+            bgl_entry->storageTexture.format = _sg_wgpu_textureformat(shd->cmn.views[i].access_format);
18351+            bgl_entry->storageTexture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.views[i].image_type);
18352+        } else {
18353+            SOKOL_UNREACHABLE;
18354+        }
18355+        bgl_entry->binding = shd->wgpu.view_grp1_bnd_n[i];
18356+        bgl_index += 1;
18357+    }
18358+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
18359+        if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) {
18360+            continue;
18361+        }
18362+        shd->wgpu.smp_grp1_bnd_n[i] = desc->samplers[i].wgsl_group1_binding_n;
18363+        WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index];
18364+        bgl_entry->binding = shd->wgpu.smp_grp1_bnd_n[i];
18365+        bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.samplers[i].stage);
18366+        bgl_entry->sampler.type = _sg_wgpu_sampler_binding_type(shd->cmn.samplers[i].sampler_type);
18367+        bgl_index += 1;
18368+    }
18369+    bgl_desc.entryCount = bgl_index;
18370+    bgl_desc.entries = bgl_entries;
18371+    shd->wgpu.bgl_view_smp = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc);
18372+    if (shd->wgpu.bgl_view_smp == 0) {
18373+        _SG_ERROR(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED);
18374+        return SG_RESOURCESTATE_FAILED;
18375+    }
18376+    return SG_RESOURCESTATE_VALID;
18377+}
18378+
18379+_SOKOL_PRIVATE void _sg_wgpu_discard_shader(_sg_shader_t* shd) {
18380+    SOKOL_ASSERT(shd);
18381+    _sg_wgpu_discard_shader_func(&shd->wgpu.vertex_func);
18382+    _sg_wgpu_discard_shader_func(&shd->wgpu.fragment_func);
18383+    _sg_wgpu_discard_shader_func(&shd->wgpu.compute_func);
18384+    if (shd->wgpu.bgl_ub) {
18385+        wgpuBindGroupLayoutRelease(shd->wgpu.bgl_ub);
18386+        shd->wgpu.bgl_ub = 0;
18387+    }
18388+    if (shd->wgpu.bg_ub) {
18389+        wgpuBindGroupRelease(shd->wgpu.bg_ub);
18390+        shd->wgpu.bg_ub = 0;
18391+    }
18392+    if (shd->wgpu.bgl_view_smp) {
18393+        wgpuBindGroupLayoutRelease(shd->wgpu.bgl_view_smp);
18394+        shd->wgpu.bgl_view_smp = 0;
18395+    }
18396+}
18397+
18398+_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
18399+    SOKOL_ASSERT(pip && desc);
18400+
18401+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
18402+    SOKOL_ASSERT(shd->wgpu.bgl_ub);
18403+    SOKOL_ASSERT(shd->wgpu.bgl_view_smp);
18404+
18405+    pip->wgpu.blend_color.r = (double) desc->blend_color.r;
18406+    pip->wgpu.blend_color.g = (double) desc->blend_color.g;
18407+    pip->wgpu.blend_color.b = (double) desc->blend_color.b;
18408+    pip->wgpu.blend_color.a = (double) desc->blend_color.a;
18409+
18410+    // - @group(0) for uniform blocks
18411+    // - @group(1) for all image, sampler and storagebuffer resources
18412+    size_t num_bgls = 2;
18413+    _SG_STRUCT(WGPUBindGroupLayout, wgpu_bgl[_SG_WGPU_MAX_BINDGROUPS]);
18414+    wgpu_bgl[_SG_WGPU_UB_BINDGROUP_INDEX ] = shd->wgpu.bgl_ub;
18415+    wgpu_bgl[_SG_WGPU_VIEW_SMP_BINDGROUP_INDEX] = shd->wgpu.bgl_view_smp;
18416+    _SG_STRUCT(WGPUPipelineLayoutDescriptor, wgpu_pl_desc);
18417+    wgpu_pl_desc.bindGroupLayoutCount = num_bgls;
18418+    wgpu_pl_desc.bindGroupLayouts = &wgpu_bgl[0];
18419+    const WGPUPipelineLayout wgpu_pip_layout = wgpuDeviceCreatePipelineLayout(_sg.wgpu.dev, &wgpu_pl_desc);
18420+    if (0 == wgpu_pip_layout) {
18421+        _SG_ERROR(WGPU_CREATE_PIPELINE_LAYOUT_FAILED);
18422+        return SG_RESOURCESTATE_FAILED;
18423+    }
18424+    SOKOL_ASSERT(wgpu_pip_layout);
18425+
18426+    if (pip->cmn.is_compute) {
18427+        _SG_STRUCT(WGPUComputePipelineDescriptor, wgpu_pip_desc);
18428+        wgpu_pip_desc.label = _sg_wgpu_stringview(desc->label);
18429+        wgpu_pip_desc.layout = wgpu_pip_layout;
18430+        wgpu_pip_desc.compute.module = shd->wgpu.compute_func.module;
18431+        wgpu_pip_desc.compute.entryPoint = _sg_wgpu_stringview(shd->wgpu.compute_func.entry.buf);
18432+        pip->wgpu.cpip = wgpuDeviceCreateComputePipeline(_sg.wgpu.dev, &wgpu_pip_desc);
18433+        wgpuPipelineLayoutRelease(wgpu_pip_layout);
18434+        if (0 == pip->wgpu.cpip) {
18435+            _SG_ERROR(WGPU_CREATE_COMPUTE_PIPELINE_FAILED);
18436+            return SG_RESOURCESTATE_FAILED;
18437+        }
18438+    } else {
18439+        _SG_STRUCT(WGPUVertexBufferLayout, wgpu_vb_layouts[SG_MAX_VERTEXBUFFER_BINDSLOTS]);
18440+        _SG_STRUCT(WGPUVertexAttribute, wgpu_vtx_attrs[SG_MAX_VERTEXBUFFER_BINDSLOTS][SG_MAX_VERTEX_ATTRIBUTES]);
18441+        int wgpu_vb_num = 0;
18442+        for (int vb_idx = 0; vb_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS; vb_idx++, wgpu_vb_num++) {
18443+            const sg_vertex_buffer_layout_state* vbl_state = &desc->layout.buffers[vb_idx];
18444+            if (0 == vbl_state->stride) {
18445+                break;
18446+            }
18447+            wgpu_vb_layouts[vb_idx].arrayStride = (uint64_t)vbl_state->stride;
18448+            wgpu_vb_layouts[vb_idx].stepMode = _sg_wgpu_stepmode(vbl_state->step_func);
18449+            wgpu_vb_layouts[vb_idx].attributes = &wgpu_vtx_attrs[vb_idx][0];
18450+        }
18451+        for (int va_idx = 0; va_idx < SG_MAX_VERTEX_ATTRIBUTES; va_idx++) {
18452+            const sg_vertex_attr_state* va_state = &desc->layout.attrs[va_idx];
18453+            if (SG_VERTEXFORMAT_INVALID == va_state->format) {
18454+                break;
18455+            }
18456+            const int vb_idx = va_state->buffer_index;
18457+            SOKOL_ASSERT(vb_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS);
18458+            SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[vb_idx]);
18459+            const size_t wgpu_attr_idx = wgpu_vb_layouts[vb_idx].attributeCount;
18460+            wgpu_vb_layouts[vb_idx].attributeCount += 1;
18461+            wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].format = _sg_wgpu_vertexformat(va_state->format);
18462+            wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].offset = (uint64_t)va_state->offset;
18463+            wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].shaderLocation = (uint32_t)va_idx;
18464+        }
18465+
18466+        _SG_STRUCT(WGPURenderPipelineDescriptor, wgpu_pip_desc);
18467+        _SG_STRUCT(WGPUDepthStencilState, wgpu_ds_state);
18468+        _SG_STRUCT(WGPUFragmentState, wgpu_frag_state);
18469+        _SG_STRUCT(WGPUColorTargetState, wgpu_ctgt_state[SG_MAX_COLOR_ATTACHMENTS]);
18470+        _SG_STRUCT(WGPUBlendState, wgpu_blend_state[SG_MAX_COLOR_ATTACHMENTS]);
18471+        wgpu_pip_desc.label = _sg_wgpu_stringview(desc->label);
18472+        wgpu_pip_desc.layout = wgpu_pip_layout;
18473+        wgpu_pip_desc.vertex.module = shd->wgpu.vertex_func.module;
18474+        wgpu_pip_desc.vertex.entryPoint = _sg_wgpu_stringview(shd->wgpu.vertex_func.entry.buf);
18475+        wgpu_pip_desc.vertex.bufferCount = (size_t)wgpu_vb_num;
18476+        wgpu_pip_desc.vertex.buffers = &wgpu_vb_layouts[0];
18477+        wgpu_pip_desc.primitive.topology = _sg_wgpu_topology(desc->primitive_type);
18478+        wgpu_pip_desc.primitive.stripIndexFormat = _sg_wgpu_stripindexformat(desc->primitive_type, desc->index_type);
18479+        wgpu_pip_desc.primitive.frontFace = _sg_wgpu_frontface(desc->face_winding);
18480+        wgpu_pip_desc.primitive.cullMode = _sg_wgpu_cullmode(desc->cull_mode);
18481+        if (SG_PIXELFORMAT_NONE != desc->depth.pixel_format) {
18482+            wgpu_ds_state.format = _sg_wgpu_textureformat(desc->depth.pixel_format);
18483+            wgpu_ds_state.depthWriteEnabled = _sg_wgpu_optional_bool(desc->depth.write_enabled);
18484+            wgpu_ds_state.depthCompare = _sg_wgpu_comparefunc(desc->depth.compare);
18485+            wgpu_ds_state.stencilFront.compare = _sg_wgpu_comparefunc(desc->stencil.front.compare);
18486+            wgpu_ds_state.stencilFront.failOp = _sg_wgpu_stencilop(desc->stencil.front.fail_op);
18487+            wgpu_ds_state.stencilFront.depthFailOp = _sg_wgpu_stencilop(desc->stencil.front.depth_fail_op);
18488+            wgpu_ds_state.stencilFront.passOp = _sg_wgpu_stencilop(desc->stencil.front.pass_op);
18489+            wgpu_ds_state.stencilBack.compare = _sg_wgpu_comparefunc(desc->stencil.back.compare);
18490+            wgpu_ds_state.stencilBack.failOp = _sg_wgpu_stencilop(desc->stencil.back.fail_op);
18491+            wgpu_ds_state.stencilBack.depthFailOp = _sg_wgpu_stencilop(desc->stencil.back.depth_fail_op);
18492+            wgpu_ds_state.stencilBack.passOp = _sg_wgpu_stencilop(desc->stencil.back.pass_op);
18493+            wgpu_ds_state.stencilReadMask = desc->stencil.read_mask;
18494+            wgpu_ds_state.stencilWriteMask = desc->stencil.write_mask;
18495+            wgpu_ds_state.depthBias = (int32_t)desc->depth.bias;
18496+            wgpu_ds_state.depthBiasSlopeScale = desc->depth.bias_slope_scale;
18497+            wgpu_ds_state.depthBiasClamp = desc->depth.bias_clamp;
18498+            wgpu_pip_desc.depthStencil = &wgpu_ds_state;
18499+        }
18500+        wgpu_pip_desc.multisample.count = (uint32_t)desc->sample_count;
18501+        wgpu_pip_desc.multisample.mask = 0xFFFFFFFF;
18502+        wgpu_pip_desc.multisample.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled;
18503+        if (desc->color_count > 0) {
18504+            wgpu_frag_state.module = shd->wgpu.fragment_func.module;
18505+            wgpu_frag_state.entryPoint = _sg_wgpu_stringview(shd->wgpu.fragment_func.entry.buf);
18506+            wgpu_frag_state.targetCount = (size_t)desc->color_count;
18507+            wgpu_frag_state.targets = &wgpu_ctgt_state[0];
18508+            for (int i = 0; i < desc->color_count; i++) {
18509+                SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS);
18510+                wgpu_ctgt_state[i].format = _sg_wgpu_textureformat(desc->colors[i].pixel_format);
18511+                wgpu_ctgt_state[i].writeMask = _sg_wgpu_colorwritemask(desc->colors[i].write_mask);
18512+                if (desc->colors[i].blend.enabled) {
18513+                    wgpu_ctgt_state[i].blend = &wgpu_blend_state[i];
18514+                    wgpu_blend_state[i].color.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_rgb);
18515+                    wgpu_blend_state[i].color.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_rgb);
18516+                    wgpu_blend_state[i].color.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_rgb);
18517+                    wgpu_blend_state[i].alpha.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_alpha);
18518+                    wgpu_blend_state[i].alpha.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_alpha);
18519+                    wgpu_blend_state[i].alpha.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_alpha);
18520+                }
18521+            }
18522+            wgpu_pip_desc.fragment = &wgpu_frag_state;
18523+        }
18524+        pip->wgpu.rpip = wgpuDeviceCreateRenderPipeline(_sg.wgpu.dev, &wgpu_pip_desc);
18525+        wgpuPipelineLayoutRelease(wgpu_pip_layout);
18526+        if (0 == pip->wgpu.rpip) {
18527+            _SG_ERROR(WGPU_CREATE_RENDER_PIPELINE_FAILED);
18528+            return SG_RESOURCESTATE_FAILED;
18529+        }
18530+    }
18531+    return SG_RESOURCESTATE_VALID;
18532+}
18533+
18534+_SOKOL_PRIVATE void _sg_wgpu_discard_pipeline(_sg_pipeline_t* pip) {
18535+    SOKOL_ASSERT(pip);
18536+    _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE, &pip->slot);
18537+    if (pip->wgpu.rpip) {
18538+        wgpuRenderPipelineRelease(pip->wgpu.rpip);
18539+        pip->wgpu.rpip = 0;
18540+    }
18541+    if (pip->wgpu.cpip) {
18542+        wgpuComputePipelineRelease(pip->wgpu.cpip);
18543+        pip->wgpu.cpip = 0;
18544+    }
18545+}
18546+
18547+_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_view(_sg_view_t* view, const sg_view_desc* desc) {
18548+    SOKOL_ASSERT(view && desc);
18549+    if (view->cmn.type != SG_VIEWTYPE_STORAGEBUFFER) {
18550+        const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
18551+        SOKOL_ASSERT(img->wgpu.tex);
18552+        SOKOL_ASSERT(view->cmn.img.mip_level_count >= 1);
18553+        SOKOL_ASSERT(view->cmn.img.slice_count >= 1);
18554+        _SG_STRUCT(WGPUTextureViewDescriptor, wgpu_texview_desc);
18555+        wgpu_texview_desc.label = _sg_wgpu_stringview(desc->label);
18556+        wgpu_texview_desc.baseMipLevel = (uint32_t)view->cmn.img.mip_level;
18557+        wgpu_texview_desc.mipLevelCount = (uint32_t)view->cmn.img.mip_level_count;
18558+        wgpu_texview_desc.baseArrayLayer = (uint32_t)view->cmn.img.slice;
18559+        wgpu_texview_desc.arrayLayerCount = (uint32_t)view->cmn.img.slice_count;
18560+        if (view->cmn.type == SG_VIEWTYPE_TEXTURE) {
18561+            wgpu_texview_desc.dimension = _sg_wgpu_texture_view_dimension(img->cmn.type);
18562+        } else {
18563+            wgpu_texview_desc.dimension = _sg_wgpu_attachment_view_dimension(img->cmn.type);
18564+        }
18565+        if (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT) {
18566+            wgpu_texview_desc.aspect = WGPUTextureAspect_All;
18567+        } else if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) {
18568+                wgpu_texview_desc.aspect = WGPUTextureAspect_DepthOnly;
18569+        } else {
18570+            wgpu_texview_desc.aspect = WGPUTextureAspect_All;
18571+        }
18572+        view->wgpu.view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_texview_desc);
18573+        if (0 == view->wgpu.view) {
18574+            _SG_ERROR(WGPU_CREATE_TEXTURE_VIEW_FAILED);
18575+            return SG_RESOURCESTATE_FAILED;
18576+        }
18577+    }
18578+    return SG_RESOURCESTATE_VALID;
18579+}
18580+
18581+_SOKOL_PRIVATE void _sg_wgpu_discard_view(_sg_view_t* view) {
18582+    SOKOL_ASSERT(view);
18583+    _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_VIEW, &view->slot);
18584+    if (view->wgpu.view) {
18585+        wgpuTextureViewRelease(view->wgpu.view);
18586+        view->wgpu.view = 0;
18587+    }
18588+}
18589+
18590+_SOKOL_PRIVATE void _sg_wgpu_init_color_att(WGPURenderPassColorAttachment* wgpu_att, const sg_color_attachment_action* action, WGPUTextureView color_view, WGPUTextureView resolve_view) {
18591+    wgpu_att->depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
18592+    wgpu_att->view = color_view;
18593+    wgpu_att->resolveTarget = resolve_view;
18594+    wgpu_att->loadOp = _sg_wgpu_load_op(color_view, action->load_action);
18595+    wgpu_att->storeOp = _sg_wgpu_store_op(color_view, action->store_action);
18596+    wgpu_att->clearValue.r = action->clear_value.r;
18597+    wgpu_att->clearValue.g = action->clear_value.g;
18598+    wgpu_att->clearValue.b = action->clear_value.b;
18599+    wgpu_att->clearValue.a = action->clear_value.a;
18600+}
18601+
18602+_SOKOL_PRIVATE void _sg_wgpu_init_ds_att(WGPURenderPassDepthStencilAttachment* wgpu_att, const sg_pass_action* action, sg_pixel_format fmt, WGPUTextureView view) {
18603+    wgpu_att->view = view;
18604+    wgpu_att->depthLoadOp = _sg_wgpu_load_op(view, action->depth.load_action);
18605+    wgpu_att->depthStoreOp = _sg_wgpu_store_op(view, action->depth.store_action);
18606+    wgpu_att->depthClearValue = action->depth.clear_value;
18607+    wgpu_att->depthReadOnly = false;
18608+    if (_sg_is_depth_stencil_format(fmt)) {
18609+        wgpu_att->stencilLoadOp = _sg_wgpu_load_op(view, action->stencil.load_action);
18610+        wgpu_att->stencilStoreOp = _sg_wgpu_store_op(view, action->stencil.store_action);
18611+    } else {
18612+        wgpu_att->stencilLoadOp = WGPULoadOp_Undefined;
18613+        wgpu_att->stencilStoreOp = WGPUStoreOp_Undefined;
18614+    }
18615+    wgpu_att->stencilClearValue = action->stencil.clear_value;
18616+    wgpu_att->stencilReadOnly = false;
18617+}
18618+
18619+_SOKOL_PRIVATE void _sg_wgpu_begin_compute_pass(const sg_pass* pass) {
18620+    _SG_STRUCT(WGPUComputePassDescriptor, wgpu_pass_desc);
18621+    wgpu_pass_desc.label = _sg_wgpu_stringview(pass->label);
18622+    _sg.wgpu.cpass_enc = wgpuCommandEncoderBeginComputePass(_sg.wgpu.cmd_enc, &wgpu_pass_desc);
18623+    SOKOL_ASSERT(_sg.wgpu.cpass_enc);
18624+    // clear initial bindings
18625+    wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_UB_BINDGROUP_INDEX, 0, 0, 0);
18626+    wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, 0, 0, 0);
18627+    _sg_stats_inc(wgpu.bindings.num_set_bindgroup);
18628+}
18629+
18630+_SOKOL_PRIVATE void _sg_wgpu_begin_render_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
18631+    const sg_swapchain* swapchain = &pass->swapchain;
18632+    const sg_pass_action* action = &pass->action;
18633+
18634+    _SG_STRUCT(WGPURenderPassDescriptor, wgpu_pass_desc);
18635+    _SG_STRUCT(WGPURenderPassColorAttachment, wgpu_color_att[SG_MAX_COLOR_ATTACHMENTS]);
18636+    _SG_STRUCT(WGPURenderPassDepthStencilAttachment, wgpu_ds_att);
18637+    wgpu_pass_desc.label = _sg_wgpu_stringview(pass->label);
18638+    if (!atts->empty) {
18639+        SOKOL_ASSERT(atts->num_color_views <= SG_MAX_COLOR_ATTACHMENTS);
18640+        for (int i = 0; i < atts->num_color_views; i++) {
18641+            SOKOL_ASSERT(atts->color_views[i]);
18642+            WGPUTextureView wgpu_color_view = atts->color_views[i]->wgpu.view;
18643+            WGPUTextureView wgpu_resolve_view = 0;
18644+            if (atts->resolve_views[i]) {
18645+                wgpu_resolve_view = atts->resolve_views[i]->wgpu.view;
18646+            }
18647+            _sg_wgpu_init_color_att(&wgpu_color_att[i], &action->colors[i], wgpu_color_view, wgpu_resolve_view);
18648+        }
18649+        wgpu_pass_desc.colorAttachmentCount = (size_t)atts->num_color_views;
18650+        wgpu_pass_desc.colorAttachments = &wgpu_color_att[0];
18651+        if (atts->ds_view) {
18652+            const _sg_image_t* img = _sg_image_ref_ptr(&atts->ds_view->cmn.img.ref);
18653+            WGPUTextureView wgpu_ds_view = atts->ds_view->wgpu.view;
18654+            SOKOL_ASSERT(wgpu_ds_view);
18655+            _sg_wgpu_init_ds_att(&wgpu_ds_att, action, img->cmn.pixel_format, wgpu_ds_view);
18656+            wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att;
18657+        }
18658+    } else {
18659+        WGPUTextureView wgpu_color_view = (WGPUTextureView) swapchain->wgpu.render_view;
18660+        WGPUTextureView wgpu_resolve_view = (WGPUTextureView) swapchain->wgpu.resolve_view;
18661+        WGPUTextureView wgpu_depth_stencil_view = (WGPUTextureView) swapchain->wgpu.depth_stencil_view;
18662+        _sg_wgpu_init_color_att(&wgpu_color_att[0], &action->colors[0], wgpu_color_view, wgpu_resolve_view);
18663+        wgpu_pass_desc.colorAttachmentCount = 1;
18664+        wgpu_pass_desc.colorAttachments = &wgpu_color_att[0];
18665+        if (wgpu_depth_stencil_view) {
18666+            SOKOL_ASSERT(swapchain->depth_format > SG_PIXELFORMAT_NONE);
18667+            _sg_wgpu_init_ds_att(&wgpu_ds_att, action, swapchain->depth_format, wgpu_depth_stencil_view);
18668+            wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att;
18669+        }
18670+    }
18671+    _sg.wgpu.rpass_enc = wgpuCommandEncoderBeginRenderPass(_sg.wgpu.cmd_enc, &wgpu_pass_desc);
18672+    SOKOL_ASSERT(_sg.wgpu.rpass_enc);
18673+
18674+    wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_UB_BINDGROUP_INDEX, 0, 0, 0);
18675+    wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_VIEW_SMP_BINDGROUP_INDEX, 0, 0, 0);
18676+    _sg_stats_inc(wgpu.bindings.num_set_bindgroup);
18677+}
18678+
18679+_SOKOL_PRIVATE void _sg_wgpu_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
18680+    SOKOL_ASSERT(pass && atts);
18681+    SOKOL_ASSERT(_sg.wgpu.dev);
18682+    SOKOL_ASSERT(0 == _sg.wgpu.rpass_enc);
18683+    SOKOL_ASSERT(0 == _sg.wgpu.cpass_enc);
18684+
18685+    // first pass in the frame? create command encoder
18686+    if (0 == _sg.wgpu.cmd_enc) {
18687+        _SG_STRUCT(WGPUCommandEncoderDescriptor, cmd_enc_desc);
18688+        _sg.wgpu.cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc);
18689+        SOKOL_ASSERT(_sg.wgpu.cmd_enc);
18690+    }
18691+
18692+    _sg_wgpu_bindings_cache_clear();
18693+    if (pass->compute) {
18694+        _sg_wgpu_begin_compute_pass(pass);
18695+    } else {
18696+        _sg_wgpu_begin_render_pass(pass, atts);
18697+    }
18698+}
18699+
18700+_SOKOL_PRIVATE void _sg_wgpu_end_pass(const _sg_attachments_ptrs_t* atts) {
18701+    _SOKOL_UNUSED(atts);
18702+    if (_sg.wgpu.rpass_enc) {
18703+        wgpuRenderPassEncoderEnd(_sg.wgpu.rpass_enc);
18704+        wgpuRenderPassEncoderRelease(_sg.wgpu.rpass_enc);
18705+        _sg.wgpu.rpass_enc = 0;
18706+    }
18707+    if (_sg.wgpu.cpass_enc) {
18708+        wgpuComputePassEncoderEnd(_sg.wgpu.cpass_enc);
18709+        wgpuComputePassEncoderRelease(_sg.wgpu.cpass_enc);
18710+        _sg.wgpu.cpass_enc = 0;
18711+    }
18712+}
18713+
18714+_SOKOL_PRIVATE void _sg_wgpu_commit(void) {
18715+    if (!_sg.wgpu.cmd_enc) {
18716+        // no valid pass in this frame
18717+        return;
18718+    }
18719+    _sg_wgpu_uniform_system_on_commit();
18720+    _SG_STRUCT(WGPUCommandBufferDescriptor, cmd_buf_desc);
18721+    WGPUCommandBuffer wgpu_cmd_buf = wgpuCommandEncoderFinish(_sg.wgpu.cmd_enc, &cmd_buf_desc);
18722+    SOKOL_ASSERT(wgpu_cmd_buf);
18723+    wgpuCommandEncoderRelease(_sg.wgpu.cmd_enc);
18724+    _sg.wgpu.cmd_enc = 0;
18725+    wgpuQueueSubmit(_sg.wgpu.queue, 1, &wgpu_cmd_buf);
18726+    wgpuCommandBufferRelease(wgpu_cmd_buf);
18727+}
18728+
18729+_SOKOL_PRIVATE void _sg_wgpu_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
18730+    SOKOL_ASSERT(_sg.wgpu.rpass_enc);
18731+    float xf = (float) x;
18732+    float yf = (float) (origin_top_left ? y : (_sg.cur_pass.dim.height - (y + h)));
18733+    float wf = (float) w;
18734+    float hf = (float) h;
18735+    wgpuRenderPassEncoderSetViewport(_sg.wgpu.rpass_enc, xf, yf, wf, hf, 0.0f, 1.0f);
18736+}
18737+
18738+_SOKOL_PRIVATE void _sg_wgpu_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
18739+    SOKOL_ASSERT(_sg.wgpu.rpass_enc);
18740+    const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height);
18741+    uint32_t sx = (uint32_t) clip.x;
18742+    uint32_t sy = (uint32_t) (origin_top_left ? clip.y : (_sg.cur_pass.dim.height - (clip.y + clip.h)));
18743+    uint32_t sw = (uint32_t) clip.w;
18744+    uint32_t sh = (uint32_t) clip.h;
18745+    wgpuRenderPassEncoderSetScissorRect(_sg.wgpu.rpass_enc, sx, sy, sw, sh);
18746+}
18747+
18748+_SOKOL_PRIVATE void _sg_wgpu_apply_pipeline(_sg_pipeline_t* pip) {
18749+    SOKOL_ASSERT(pip);
18750+    _sg_wgpu_uniform_system_on_apply_pipeline();
18751+    if (pip->cmn.is_compute) {
18752+        SOKOL_ASSERT(_sg.cur_pass.is_compute);
18753+        SOKOL_ASSERT(pip->wgpu.cpip);
18754+        SOKOL_ASSERT(_sg.wgpu.cpass_enc);
18755+        wgpuComputePassEncoderSetPipeline(_sg.wgpu.cpass_enc, pip->wgpu.cpip);
18756+    } else {
18757+        SOKOL_ASSERT(!_sg.cur_pass.is_compute);
18758+        SOKOL_ASSERT(pip->wgpu.rpip);
18759+        SOKOL_ASSERT(_sg.wgpu.rpass_enc);
18760+        wgpuRenderPassEncoderSetPipeline(_sg.wgpu.rpass_enc, pip->wgpu.rpip);
18761+        wgpuRenderPassEncoderSetBlendConstant(_sg.wgpu.rpass_enc, &pip->wgpu.blend_color);
18762+        wgpuRenderPassEncoderSetStencilReference(_sg.wgpu.rpass_enc, pip->cmn.stencil.ref);
18763+    }
18764+}
18765+
18766+_SOKOL_PRIVATE bool _sg_wgpu_apply_bindings(_sg_bindings_ptrs_t* bnd) {
18767+    SOKOL_ASSERT(bnd);
18768+    bool retval = true;
18769+    if (!_sg.cur_pass.is_compute) {
18770+        retval &= _sg_wgpu_apply_index_buffer(bnd);
18771+        retval &= _sg_wgpu_apply_vertex_buffers(bnd);
18772+    }
18773+    retval &= _sg_wgpu_apply_bindings_bindgroup(bnd);
18774+    return retval;
18775+}
18776+
18777+_SOKOL_PRIVATE void _sg_wgpu_apply_uniforms(int ub_slot, const sg_range* data) {
18778+    const uint32_t alignment = _sg.wgpu.limits.minUniformBufferOffsetAlignment;
18779+    SOKOL_ASSERT(_sg.wgpu.uniform.staging);
18780+    SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
18781+    SOKOL_ASSERT((_sg.wgpu.uniform.offset + data->size) <= _sg.wgpu.uniform.num_bytes);
18782+    SOKOL_ASSERT((_sg.wgpu.uniform.offset & (alignment - 1)) == 0);
18783+    SOKOL_ASSERT(data->size <= _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE);
18784+
18785+    _sg_stats_inc(wgpu.uniforms.num_set_bindgroup);
18786+    memcpy(_sg.wgpu.uniform.staging + _sg.wgpu.uniform.offset, data->ptr, data->size);
18787+    _sg.wgpu.uniform.bind_offsets[ub_slot] = _sg.wgpu.uniform.offset;
18788+    _sg.wgpu.uniform.offset = _sg_roundup_u32(_sg.wgpu.uniform.offset + (uint32_t)data->size, alignment);
18789+    _sg.wgpu.uniform.dirty = true;
18790+}
18791+
18792+_SOKOL_PRIVATE void _sg_wgpu_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
18793+    SOKOL_ASSERT(_sg.wgpu.rpass_enc);
18794+    if (_sg.wgpu.uniform.dirty) {
18795+        _sg_wgpu_uniform_system_set_bindgroup();
18796+    }
18797+    if (_sg.use_indexed_draw) {
18798+        wgpuRenderPassEncoderDrawIndexed(_sg.wgpu.rpass_enc,
18799+            (uint32_t)num_elements,
18800+            (uint32_t)num_instances,
18801+            (uint32_t)base_element,
18802+            base_vertex,
18803+            (uint32_t)base_instance);
18804+    } else {
18805+        wgpuRenderPassEncoderDraw(_sg.wgpu.rpass_enc,
18806+            (uint32_t)num_elements,
18807+            (uint32_t)num_instances,
18808+            (uint32_t)base_element,
18809+            (uint32_t)base_instance);
18810+    }
18811+}
18812+
18813+_SOKOL_PRIVATE void _sg_wgpu_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
18814+    SOKOL_ASSERT(_sg.wgpu.cpass_enc);
18815+    if (_sg.wgpu.uniform.dirty) {
18816+        _sg_wgpu_uniform_system_set_bindgroup();
18817+    }
18818+    wgpuComputePassEncoderDispatchWorkgroups(_sg.wgpu.cpass_enc,
18819+        (uint32_t)num_groups_x,
18820+        (uint32_t)num_groups_y,
18821+        (uint32_t)num_groups_z);
18822+}
18823+
18824+_SOKOL_PRIVATE void _sg_wgpu_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
18825+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
18826+    _sg_wgpu_copy_buffer_data(buf, 0, data);
18827+}
18828+
18829+_SOKOL_PRIVATE void _sg_wgpu_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
18830+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
18831+    _SOKOL_UNUSED(new_frame);
18832+    _sg_wgpu_copy_buffer_data(buf, (uint64_t)buf->cmn.append_pos, data);
18833+}
18834+
18835+_SOKOL_PRIVATE void _sg_wgpu_update_image(_sg_image_t* img, const sg_image_data* data) {
18836+    SOKOL_ASSERT(img && data);
18837+    _sg_wgpu_copy_image_data(img, data);
18838+}
18839+
18840+// ██    ██ ██    ██ ██      ██   ██  █████  ███    ██     ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
18841+// ██    ██ ██    ██ ██      ██  ██  ██   ██ ████   ██     ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
18842+// ██    ██ ██    ██ ██      █████   ███████ ██ ██  ██     ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
18843+//  ██  ██  ██    ██ ██      ██  ██  ██   ██ ██  ██ ██     ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
18844+//   ████    ██████  ███████ ██   ██ ██   ██ ██   ████     ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
18845+//
18846+// >>vulkan
18847+// >>vk
18848+#elif defined(SOKOL_VULKAN)
18849+
18850+_SOKOL_PRIVATE void _sg_vk_set_object_label(VkObjectType obj_type, uint64_t obj_handle, const char* label) {
18851+    #if defined(SOKOL_DEBUG)
18852+        SOKOL_ASSERT(_sg.vk.dev);
18853+        SOKOL_ASSERT(_sg.vk.ext.set_debug_utils_object_name_ext);
18854+        SOKOL_ASSERT(obj_handle != 0);
18855+        if (label) {
18856+            _SG_STRUCT(VkDebugUtilsObjectNameInfoEXT, name_info);
18857+            name_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
18858+            name_info.objectType = obj_type;
18859+            name_info.objectHandle = obj_handle,
18860+            name_info.pObjectName = label;
18861+            VkResult res = _sg.vk.ext.set_debug_utils_object_name_ext(_sg.vk.dev, &name_info);
18862+            SOKOL_ASSERT(res == VK_SUCCESS);
18863+        }
18864+    #else
18865+        _SOKOL_UNUSED(obj_type);
18866+        _SOKOL_UNUSED(obj_handle);
18867+        _SOKOL_UNUSED(label);
18868+    #endif
18869+}
18870+
18871+_SOKOL_PRIVATE bool _sg_vk_is_read_access(_sg_vk_access_t access) {
18872+    _sg_vk_access_t read_bits =
18873+        _SG_VK_ACCESS_VERTEXBUFFER |
18874+        _SG_VK_ACCESS_INDEXBUFFER |
18875+        _SG_VK_ACCESS_STORAGEBUFFER_RO |
18876+        _SG_VK_ACCESS_TEXTURE |
18877+        _SG_VK_ACCESS_PRESENT;
18878+    return 0 == (access & ~read_bits);
18879+}
18880+
18881+_SOKOL_PRIVATE VkPipelineStageFlags2 _sg_vk_stage_mask(_sg_vk_access_t access, bool is_dst_access) {
18882+    access &= ~_SG_VK_ACCESS_DISCARD;
18883+    if (is_dst_access) {
18884+        SOKOL_ASSERT(access != _SG_VK_ACCESS_NONE);
18885+    }
18886+    VkPipelineStageFlags2 f = 0;
18887+    if (access == _SG_VK_ACCESS_NONE) {
18888+        return VK_PIPELINE_STAGE_2_NONE;
18889+    }
18890+    if (access & _SG_VK_ACCESS_PRESENT) {
18891+        return VK_PIPELINE_STAGE_2_NONE;
18892+    }
18893+    if (access & _SG_VK_ACCESS_STAGING) {
18894+        f |= VK_PIPELINE_STAGE_2_COPY_BIT;
18895+    }
18896+    if (access & _SG_VK_ACCESS_VERTEXBUFFER) {
18897+        f |= VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT;
18898+    }
18899+    if (access & _SG_VK_ACCESS_INDEXBUFFER) {
18900+        f |= VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT;
18901+    }
18902+    if (access & (_SG_VK_ACCESS_STORAGEBUFFER_RO|_SG_VK_ACCESS_TEXTURE)) {
18903+        f |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT |
18904+             VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT |
18905+             VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
18906+    }
18907+    if (access & _SG_VK_ACCESS_STORAGEBUFFER_RW) {
18908+        f |= VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
18909+    }
18910+    if (access & _SG_VK_ACCESS_STORAGEIMAGE) {
18911+        f |= VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
18912+    }
18913+    if (access & _SG_VK_ACCESS_COLOR_ATTACHMENT) {
18914+        f |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
18915+    }
18916+    if (access & _SG_VK_ACCESS_RESOLVE_ATTACHMENT) {
18917+        f |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
18918+    }
18919+    if (access & (_SG_VK_ACCESS_DEPTH_ATTACHMENT|_SG_VK_ACCESS_STENCIL_ATTACHMENT)) {
18920+        f |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT|VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
18921+    }
18922+    SOKOL_ASSERT(f != 0);
18923+    return f;
18924+}
18925+
18926+// return pipeline stages on 'before' side of a barrier
18927+_SOKOL_PRIVATE VkPipelineStageFlags2 _sg_vk_src_stage_mask(_sg_vk_access_t access) {
18928+    return _sg_vk_stage_mask(access, false);
18929+}
18930+
18931+// return pipeline stage on 'after side' of a barrier
18932+_SOKOL_PRIVATE VkPipelineStageFlags2 _sg_vk_dst_stage_mask(_sg_vk_access_t access) {
18933+    return _sg_vk_stage_mask(access, true);
18934+}
18935+
18936+_SOKOL_PRIVATE VkAccessFlags2 _sg_vk_access_mask(_sg_vk_access_t access, bool is_dst_access) {
18937+    access &= ~_SG_VK_ACCESS_DISCARD;
18938+    if (access == _SG_VK_ACCESS_NONE) {
18939+        return VK_ACCESS_2_NONE;
18940+    }
18941+    if (access & _SG_VK_ACCESS_PRESENT) {
18942+        return VK_ACCESS_2_NONE;
18943+    }
18944+    VkAccessFlags2 f = VK_ACCESS_2_NONE;
18945+    if (is_dst_access) {
18946+        // NOTE: read bits don't make sense for src-mask
18947+        if (access & _SG_VK_ACCESS_VERTEXBUFFER) {
18948+            f |= VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT;
18949+        }
18950+        if (access & _SG_VK_ACCESS_INDEXBUFFER) {
18951+            f |= VK_ACCESS_2_INDEX_READ_BIT;
18952+        }
18953+        if (access & _SG_VK_ACCESS_STORAGEBUFFER_RO) {
18954+            f |= VK_ACCESS_2_SHADER_STORAGE_READ_BIT;
18955+        }
18956+        if (access & _SG_VK_ACCESS_TEXTURE) {
18957+            f |= VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
18958+        }
18959+    }
18960+    if (access & _SG_VK_ACCESS_STAGING) {
18961+        f |= VK_ACCESS_2_TRANSFER_WRITE_BIT;
18962+    }
18963+    if (access & _SG_VK_ACCESS_STORAGEBUFFER_RW) {
18964+        f |= VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT;
18965+    }
18966+    if (access & _SG_VK_ACCESS_STORAGEIMAGE) {
18967+        f |= VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT;
18968+    }
18969+    if (access & _SG_VK_ACCESS_COLOR_ATTACHMENT) {
18970+        f |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
18971+    }
18972+    if (access & _SG_VK_ACCESS_RESOLVE_ATTACHMENT) {
18973+        f |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
18974+    }
18975+    if (access & (_SG_VK_ACCESS_DEPTH_ATTACHMENT | _SG_VK_ACCESS_STENCIL_ATTACHMENT)) {
18976+        f |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
18977+        if (is_dst_access) {
18978+            f |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
18979+        }
18980+    }
18981+    return f;
18982+}
18983+
18984+_SOKOL_PRIVATE VkAccessFlags2 _sg_vk_src_access_mask(_sg_vk_access_t access) {
18985+    return _sg_vk_access_mask(access, false);
18986+}
18987+
18988+_SOKOL_PRIVATE VkAccessFlags2 _sg_vk_dst_access_mask(_sg_vk_access_t access) {
18989+    return _sg_vk_access_mask(access, true);
18990+}
18991+
18992+_SOKOL_PRIVATE VkImageLayout _sg_vk_image_layout(_sg_vk_access_t access) {
18993+    // NOTE: "image layout transitions with VK_IMAGE_LAYOUT_UNDEFINED allow
18994+    // the implementation to discard the image subresource range"
18995+    if (access & _SG_VK_ACCESS_DISCARD) {
18996+        return VK_IMAGE_LAYOUT_UNDEFINED;
18997+    }
18998+    switch (access) {
18999+        case _SG_VK_ACCESS_NONE:
19000+            return VK_IMAGE_LAYOUT_UNDEFINED;
19001+        case _SG_VK_ACCESS_STAGING:
19002+            return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
19003+        case _SG_VK_ACCESS_TEXTURE:
19004+            return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
19005+        case _SG_VK_ACCESS_STORAGEIMAGE:
19006+            return VK_IMAGE_LAYOUT_GENERAL;
19007+        case _SG_VK_ACCESS_COLOR_ATTACHMENT:
19008+        case _SG_VK_ACCESS_RESOLVE_ATTACHMENT:
19009+        case _SG_VK_ACCESS_DEPTH_ATTACHMENT:
19010+        case _SG_VK_ACCESS_DEPTH_ATTACHMENT|_SG_VK_ACCESS_STENCIL_ATTACHMENT:
19011+            return VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
19012+        case _SG_VK_ACCESS_PRESENT:
19013+            return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
19014+        default:
19015+            SOKOL_UNREACHABLE;
19016+            return VK_IMAGE_LAYOUT_UNDEFINED;
19017+    }
19018+}
19019+
19020+_SOKOL_PRIVATE void _sg_vk_swapchain_beginpass_barrier(VkCommandBuffer cmd_buf, VkImage vkimg, _sg_vk_access_t pass_access) {
19021+    SOKOL_ASSERT(cmd_buf);
19022+    _SG_STRUCT(VkImageMemoryBarrier2, barrier);
19023+    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
19024+    barrier.srcStageMask = _sg_vk_src_stage_mask(pass_access);
19025+    barrier.srcAccessMask = _sg_vk_src_access_mask(pass_access);
19026+    barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
19027+    barrier.dstStageMask = _sg_vk_dst_stage_mask(pass_access);
19028+    barrier.dstAccessMask = _sg_vk_dst_access_mask(pass_access);
19029+    barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
19030+    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19031+    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19032+    barrier.image = vkimg;
19033+    if (0 != (pass_access & (_SG_VK_ACCESS_DEPTH_ATTACHMENT|_SG_VK_ACCESS_STENCIL_ATTACHMENT))) {
19034+        barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
19035+        if (0 != (pass_access & _SG_VK_ACCESS_STENCIL_ATTACHMENT)) {
19036+            barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
19037+        }
19038+    } else {
19039+        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
19040+    }
19041+    barrier.subresourceRange.levelCount = 1;
19042+    barrier.subresourceRange.layerCount = 1;
19043+    _SG_STRUCT(VkDependencyInfo, dep_info);
19044+    dep_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
19045+    dep_info.imageMemoryBarrierCount = 1;
19046+    dep_info.pImageMemoryBarriers = &barrier;
19047+    vkCmdPipelineBarrier2(cmd_buf, &dep_info);
19048+    _sg_stats_inc(vk.num_cmd_pipeline_barrier);
19049+}
19050+
19051+_SOKOL_PRIVATE void _sg_vk_swapchain_endpass_barrier(VkCommandBuffer cmd_buf, VkImage vkimg, _sg_vk_access_t pass_access, bool present) {
19052+    SOKOL_ASSERT(cmd_buf);
19053+    _SG_STRUCT(VkImageMemoryBarrier2, barrier);
19054+    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
19055+    barrier.srcStageMask = _sg_vk_src_stage_mask(pass_access);
19056+    barrier.srcAccessMask = _sg_vk_src_access_mask(pass_access);
19057+    barrier.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
19058+    barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
19059+    barrier.dstAccessMask = VK_ACCESS_2_NONE;
19060+    if (present) {
19061+        barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
19062+    } else {
19063+        barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
19064+    }
19065+    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19066+    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19067+    barrier.image = vkimg;
19068+    if (0 != (pass_access & (_SG_VK_ACCESS_DEPTH_ATTACHMENT|_SG_VK_ACCESS_STENCIL_ATTACHMENT))) {
19069+        barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
19070+        if (0 != (pass_access & _SG_VK_ACCESS_STENCIL_ATTACHMENT)) {
19071+            barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
19072+        }
19073+    } else {
19074+        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
19075+    }
19076+    barrier.subresourceRange.levelCount = 1;
19077+    barrier.subresourceRange.layerCount = 1;
19078+    _SG_STRUCT(VkDependencyInfo, dep_info);
19079+    dep_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
19080+    dep_info.imageMemoryBarrierCount = 1;
19081+    dep_info.pImageMemoryBarriers = &barrier;
19082+    vkCmdPipelineBarrier2(cmd_buf, &dep_info);
19083+    _sg_stats_inc(vk.num_cmd_pipeline_barrier);
19084+}
19085+
19086+_SOKOL_PRIVATE void _sg_vk_image_barrier(VkCommandBuffer cmd_buf, _sg_image_t* img, _sg_vk_access_t new_access) {
19087+    SOKOL_ASSERT(cmd_buf && img && img->vk.img);
19088+    if (_sg_vk_is_read_access(img->vk.cur_access) && _sg_vk_is_read_access(new_access)) {
19089+        return;
19090+    }
19091+    _SG_STRUCT(VkImageMemoryBarrier2, barrier);
19092+    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
19093+    barrier.srcStageMask = _sg_vk_src_stage_mask(img->vk.cur_access);
19094+    barrier.srcAccessMask = _sg_vk_src_access_mask(img->vk.cur_access);
19095+    barrier.oldLayout = _sg_vk_image_layout(img->vk.cur_access);
19096+    barrier.dstStageMask = _sg_vk_dst_stage_mask(new_access);
19097+    barrier.dstAccessMask = _sg_vk_dst_access_mask(new_access);
19098+    barrier.newLayout = _sg_vk_image_layout(new_access);
19099+    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19100+    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19101+    barrier.image = img->vk.img;
19102+    if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) {
19103+        barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
19104+        if (_sg_is_depth_stencil_format(img->cmn.pixel_format)) {
19105+            barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
19106+        }
19107+    } else {
19108+        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
19109+    }
19110+    barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
19111+    barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
19112+    _SG_STRUCT(VkDependencyInfo, dep_info);
19113+    dep_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
19114+    dep_info.imageMemoryBarrierCount = 1;
19115+    dep_info.pImageMemoryBarriers = &barrier;
19116+    vkCmdPipelineBarrier2(cmd_buf, &dep_info);
19117+    _sg_stats_inc(vk.num_cmd_pipeline_barrier);
19118+    img->vk.cur_access = new_access;
19119+}
19120+
19121+_SOKOL_PRIVATE void _sg_vk_buffer_barrier(VkCommandBuffer cmd_buf, _sg_buffer_t* buf, _sg_vk_access_t new_access) {
19122+    SOKOL_ASSERT(cmd_buf && buf && buf->vk.buf);
19123+    if (_sg_vk_is_read_access(buf->vk.cur_access) && _sg_vk_is_read_access(new_access)) {
19124+        return;
19125+    }
19126+    _SG_STRUCT(VkBufferMemoryBarrier2, barrier);
19127+    barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2;
19128+    barrier.srcStageMask = _sg_vk_src_stage_mask(buf->vk.cur_access);
19129+    barrier.srcAccessMask = _sg_vk_src_access_mask(buf->vk.cur_access);
19130+    barrier.dstStageMask = _sg_vk_dst_stage_mask(new_access);
19131+    barrier.dstAccessMask = _sg_vk_dst_access_mask(new_access);
19132+    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19133+    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
19134+    barrier.buffer = buf->vk.buf;
19135+    barrier.offset = 0;
19136+    barrier.size = VK_WHOLE_SIZE;
19137+    _SG_STRUCT(VkDependencyInfo, dep_info);
19138+    dep_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
19139+    dep_info.bufferMemoryBarrierCount = 1;
19140+    dep_info.pBufferMemoryBarriers = &barrier;
19141+    vkCmdPipelineBarrier2(cmd_buf, &dep_info);
19142+    _sg_stats_inc(vk.num_cmd_pipeline_barrier);
19143+    buf->vk.cur_access = new_access;
19144+}
19145+
19146+_SOKOL_PRIVATE void _sg_vk_barrier_on_begin_pass(VkCommandBuffer cmd_buf, const sg_pass* pass, const _sg_attachments_ptrs_t* atts, bool is_compute_pass) {
19147+    SOKOL_ASSERT(cmd_buf);
19148+    if (is_compute_pass) {
19149+        SOKOL_ASSERT(0 == _sg.vk.track.buffers.cur_slot);
19150+        SOKOL_ASSERT(0 == _sg.vk.track.images.cur_slot);
19151+    } else {
19152+        const bool is_swapchain_pass = atts->empty;
19153+        if (is_swapchain_pass) {
19154+            const sg_vulkan_swapchain* vk_swapchain = &pass->swapchain.vulkan;
19155+            SOKOL_ASSERT(vk_swapchain->render_image);
19156+            VkImage vk_color_image = (VkImage)vk_swapchain->render_image;
19157+            _sg_vk_swapchain_beginpass_barrier(cmd_buf, vk_color_image, _SG_VK_ACCESS_COLOR_ATTACHMENT);
19158+            if (_sg.cur_pass.swapchain.sample_count > 1) {
19159+                VkImage vk_resolve_image = (VkImage)vk_swapchain->resolve_image;
19160+                SOKOL_ASSERT(vk_resolve_image);
19161+                _sg_vk_swapchain_beginpass_barrier(cmd_buf, vk_resolve_image, _SG_VK_ACCESS_RESOLVE_ATTACHMENT);
19162+            }
19163+            if (vk_swapchain->depth_stencil_image) {
19164+                VkImage vk_ds_image = (VkImage)vk_swapchain->depth_stencil_image;
19165+                const bool has_stencil = _sg_is_depth_stencil_format(_sg.cur_pass.swapchain.depth_fmt);
19166+                _sg_vk_access_t access = _SG_VK_ACCESS_DEPTH_ATTACHMENT;
19167+                if (has_stencil) {
19168+                    access |= _SG_VK_ACCESS_STENCIL_ATTACHMENT;
19169+                }
19170+                _sg_vk_swapchain_beginpass_barrier(cmd_buf, vk_ds_image, access);
19171+            }
19172+        } else {
19173+            SOKOL_ASSERT(atts->num_color_views <= SG_MAX_COLOR_ATTACHMENTS);
19174+            for (int i = 0; i < atts->num_color_views; i++) {
19175+                SOKOL_ASSERT(atts->color_views[i]);
19176+                _sg_image_t* color_image = _sg_image_ref_ptr(&atts->color_views[i]->cmn.img.ref);
19177+                if (pass->action.colors[i].load_action != SG_LOADACTION_LOAD) {
19178+                    // don't need to preserve image content for clear and dontcare
19179+                    color_image->vk.cur_access |= _SG_VK_ACCESS_DISCARD;
19180+                }
19181+                _sg_vk_image_barrier(cmd_buf, color_image, _SG_VK_ACCESS_COLOR_ATTACHMENT);
19182+                if (atts->resolve_views[i]) {
19183+                    _sg_image_t* resolve_image = _sg_image_ref_ptr(&atts->resolve_views[i]->cmn.img.ref);
19184+                    // never need to preserve content for resolve image
19185+                    resolve_image->vk.cur_access |= _SG_VK_ACCESS_DISCARD;
19186+                    _sg_vk_image_barrier(cmd_buf, resolve_image, _SG_VK_ACCESS_RESOLVE_ATTACHMENT);
19187+                }
19188+            }
19189+            if (atts->ds_view) {
19190+                _sg_image_t* ds_image = _sg_image_ref_ptr(&atts->ds_view->cmn.img.ref);
19191+                const bool has_stencil = _sg_is_depth_stencil_format(ds_image->cmn.pixel_format);
19192+                if ((pass->action.depth.load_action != SG_LOADACTION_LOAD) &&
19193+                    (pass->action.stencil.load_action != SG_LOADACTION_LOAD))
19194+                {
19195+                    // don't need to preserve image content for clear and dontcare
19196+                    ds_image->vk.cur_access |= _SG_VK_ACCESS_DISCARD;
19197+                }
19198+                _sg_vk_access_t dst_access = _SG_VK_ACCESS_DEPTH_ATTACHMENT;
19199+                if (has_stencil) {
19200+                    dst_access |= _SG_VK_ACCESS_STENCIL_ATTACHMENT;
19201+                }
19202+                _sg_vk_image_barrier(cmd_buf, ds_image, dst_access);
19203+            }
19204+        }
19205+    }
19206+}
19207+
19208+_SOKOL_PRIVATE void _sg_vk_barrier_on_apply_bindings(VkCommandBuffer cmd_buf, const _sg_bindings_ptrs_t* bnd, bool is_compute_pass) {
19209+    SOKOL_ASSERT(bnd);
19210+    if (is_compute_pass) {
19211+        SOKOL_ASSERT(bnd->pip);
19212+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
19213+            const _sg_view_t* view = bnd->views[i];
19214+            if (0 == view) {
19215+                continue;
19216+            } else if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
19217+                const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
19218+                _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
19219+                _sg_vk_access_t new_access = shd->cmn.views[i].sbuf_readonly
19220+                    ? _SG_VK_ACCESS_STORAGEBUFFER_RO
19221+                    : _SG_VK_ACCESS_STORAGEBUFFER_RW;
19222+                _sg_vk_buffer_barrier(cmd_buf, buf, new_access);
19223+                _sg_track_add(&_sg.vk.track.buffers, buf->slot.id);
19224+            } else if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) {
19225+                _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
19226+                _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_STORAGEIMAGE);
19227+                _sg_track_add(&_sg.vk.track.images, img->slot.id);
19228+            } else if (view->cmn.type == SG_VIEWTYPE_TEXTURE) {
19229+                _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
19230+                _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19231+                _sg_track_add(&_sg.vk.track.images, img->slot.id);
19232+            } else {
19233+                SOKOL_UNREACHABLE;
19234+            }
19235+        }
19236+    } else {
19237+        // no transitions allowed in render passes, but check if resources are in
19238+        // correct access state
19239+        for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
19240+            if (bnd->vbs[i]) {
19241+                SOKOL_ASSERT(0 != (bnd->vbs[i]->vk.cur_access & _SG_VK_ACCESS_VERTEXBUFFER));
19242+            }
19243+        }
19244+        if (bnd->ib) {
19245+            SOKOL_ASSERT(0 != (bnd->ib->vk.cur_access & _SG_VK_ACCESS_INDEXBUFFER));
19246+        }
19247+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
19248+            const _sg_view_t* view = bnd->views[i];
19249+            if (0 == view) {
19250+                continue;
19251+            }
19252+            else if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
19253+                const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
19254+                _SOKOL_UNUSED(buf);
19255+                SOKOL_ASSERT(0 != (buf->vk.cur_access & _SG_VK_ACCESS_STORAGEBUFFER_RO));
19256+            } else if (view->cmn.type == SG_VIEWTYPE_TEXTURE) {
19257+                const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
19258+                _SOKOL_UNUSED(img);
19259+                SOKOL_ASSERT(0 != (img->vk.cur_access & _SG_VK_ACCESS_TEXTURE));
19260+            } else {
19261+                SOKOL_UNREACHABLE;
19262+            }
19263+        }
19264+    }
19265+}
19266+
19267+_SOKOL_PRIVATE void _sg_vk_barrier_on_end_pass(VkCommandBuffer cmd_buf, const _sg_attachments_ptrs_t* atts, bool is_compute_pass) {
19268+    SOKOL_ASSERT(cmd_buf);
19269+    if (is_compute_pass) {
19270+        // transition all tracked buffers into vertex+index+sbuf-ro access
19271+        const _sg_vk_access_t new_buf_access = _SG_VK_ACCESS_VERTEXBUFFER|_SG_VK_ACCESS_INDEXBUFFER|_SG_VK_ACCESS_STORAGEBUFFER_RO;
19272+        for (int i = 0; i < _sg.vk.track.buffers.cur_slot; i++) {
19273+            const uint32_t buf_id = _sg.vk.track.buffers.slots[i];
19274+            _sg_buffer_t* buf = _sg_lookup_buffer(buf_id);
19275+            if (buf) {
19276+                _sg_vk_buffer_barrier(cmd_buf, buf, new_buf_access);
19277+            }
19278+        }
19279+        _sg_track_reset(&_sg.vk.track.buffers);
19280+
19281+        // transition all tracked images into texture access
19282+        const _sg_vk_access_t new_img_access = _SG_VK_ACCESS_TEXTURE;
19283+        for (int i = 0; i < _sg.vk.track.images.cur_slot; i++) {
19284+            const uint32_t img_id = _sg.vk.track.images.slots[i];
19285+            _sg_image_t* img = _sg_lookup_image(img_id);
19286+            if (img) {
19287+                _sg_vk_image_barrier(cmd_buf, img, new_img_access);
19288+            }
19289+        }
19290+        _sg_track_reset(&_sg.vk.track.images);
19291+    } else {
19292+        const bool is_swapchain_pass = atts->empty;
19293+        if (is_swapchain_pass) {
19294+            SOKOL_ASSERT(_sg.vk.swapchain.render_image);
19295+            VkImage present_image = _sg.vk.swapchain.resolve_image
19296+                ? (VkImage)_sg.vk.swapchain.resolve_image
19297+                : (VkImage)_sg.vk.swapchain.render_image;
19298+            _sg_vk_swapchain_endpass_barrier(cmd_buf, present_image, _SG_VK_ACCESS_COLOR_ATTACHMENT, true);
19299+        } else {
19300+            for (int i = 0; i < atts->num_color_views; i++) {
19301+                if (_sg.cur_pass.action.colors[i].store_action == SG_STOREACTION_STORE) {
19302+                    SOKOL_ASSERT(atts->color_views[i]);
19303+                    _sg_image_t* img = _sg_image_ref_ptr(&atts->color_views[i]->cmn.img.ref);
19304+                    _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19305+                }
19306+                if (atts->resolve_views[i]) {
19307+                    _sg_image_t* img = _sg_image_ref_ptr(&atts->resolve_views[i]->cmn.img.ref);
19308+                    _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19309+                }
19310+            }
19311+            if (atts->ds_view) {
19312+                _sg_image_t* img = _sg_image_ref_ptr(&atts->ds_view->cmn.img.ref);
19313+                if (_sg.cur_pass.action.depth.store_action == SG_STOREACTION_STORE) {
19314+                    _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19315+                }
19316+            }
19317+        }
19318+    }
19319+}
19320+
19321+_SOKOL_PRIVATE int _sg_vk_mem_find_memory_type_index(uint32_t type_filter, VkMemoryPropertyFlags props) {
19322+    SOKOL_ASSERT(_sg.vk.phys_dev);
19323+    _SG_STRUCT(VkPhysicalDeviceMemoryProperties, mem_props);
19324+    vkGetPhysicalDeviceMemoryProperties(_sg.vk.phys_dev, &mem_props);
19325+    for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) {
19326+        if ((type_filter & (1 << i)) && ((mem_props.memoryTypes[i].propertyFlags & props) == props)) {
19327+            return (int)i;
19328+        }
19329+    }
19330+    return -1;
19331+}
19332+
19333+_SOKOL_PRIVATE VkDeviceMemory _sg_vk_mem_alloc_device_memory(_sg_vk_memtype_t mem_type, const VkMemoryRequirements* mem_reqs) {
19334+    SOKOL_ASSERT(_sg.vk.dev);
19335+    SOKOL_ASSERT(mem_reqs);
19336+
19337+    VkMemoryPropertyFlags mem_prop_flags = 0;
19338+    VkMemoryAllocateFlags mem_alloc_flags = 0;
19339+    switch (mem_type) {
19340+        case _SG_VK_MEMTYPE_GENERIC_BUFFER:
19341+            mem_prop_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
19342+            break;
19343+        case _SG_VK_MEMTYPE_STORAGE_BUFFER:
19344+            mem_prop_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
19345+            mem_alloc_flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
19346+            break;
19347+        case _SG_VK_MEMTYPE_IMAGE:
19348+            mem_prop_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
19349+            break;
19350+        case _SG_VK_MEMTYPE_STAGING_COPY:
19351+            mem_prop_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
19352+            break;
19353+        case _SG_VK_MEMTYPE_STAGING_STREAM:
19354+            mem_prop_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
19355+            break;
19356+        case _SG_VK_MEMTYPE_UNIFORMS:
19357+            mem_prop_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
19358+            mem_alloc_flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
19359+            break;
19360+        case _SG_VK_MEMTYPE_DESCRIPTORS:
19361+            mem_prop_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
19362+            mem_alloc_flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
19363+            break;
19364+        default:
19365+            SOKOL_UNREACHABLE;
19366+            break;
19367+    }
19368+
19369+    int mem_type_index = _sg_vk_mem_find_memory_type_index(mem_reqs->memoryTypeBits, mem_prop_flags);
19370+    if (-1 == mem_type_index) {
19371+        _SG_ERROR(VULKAN_ALLOC_DEVICE_MEMORY_NO_SUITABLE_MEMORY_TYPE);
19372+        return 0;
19373+    }
19374+    _SG_STRUCT(VkMemoryAllocateFlagsInfo, flags_info);
19375+    flags_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
19376+    flags_info.flags = mem_alloc_flags;
19377+    _SG_STRUCT(VkMemoryAllocateInfo, alloc_info);
19378+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
19379+    alloc_info.pNext = &flags_info;
19380+    alloc_info.allocationSize = mem_reqs->size;
19381+    alloc_info.memoryTypeIndex = (uint32_t) mem_type_index;
19382+    VkDeviceMemory vk_dev_mem = 0;
19383+    VkResult res = vkAllocateMemory(_sg.vk.dev, &alloc_info, 0, &vk_dev_mem);
19384+    _sg_stats_inc(vk.num_allocate_memory);
19385+    _sg_stats_add(vk.size_allocate_memory, (uint32_t)mem_reqs->size);
19386+    if (res != VK_SUCCESS) {
19387+        _SG_ERROR(VULKAN_ALLOCATE_MEMORY_FAILED);
19388+        return 0;
19389+    }
19390+    SOKOL_ASSERT(vk_dev_mem);
19391+    return vk_dev_mem;
19392+}
19393+
19394+_SOKOL_PRIVATE void _sg_vk_mem_free_device_memory(VkDeviceMemory vk_dev_mem) {
19395+    SOKOL_ASSERT(_sg.vk.dev);
19396+    SOKOL_ASSERT(vk_dev_mem);
19397+    vkFreeMemory(_sg.vk.dev, vk_dev_mem, 0);
19398+    _sg_stats_inc(vk.num_free_memory);
19399+}
19400+
19401+_SOKOL_PRIVATE bool _sg_vk_mem_alloc_buffer_device_memory(_sg_buffer_t* buf) {
19402+    SOKOL_ASSERT(_sg.vk.dev);
19403+    SOKOL_ASSERT(buf);
19404+    SOKOL_ASSERT(buf->vk.buf);
19405+    SOKOL_ASSERT(0 == buf->vk.mem);
19406+    _SG_STRUCT(VkMemoryRequirements, mem_reqs);
19407+    vkGetBufferMemoryRequirements(_sg.vk.dev, buf->vk.buf, &mem_reqs);
19408+    _sg_vk_memtype_t mem_type = buf->cmn.usage.storage_buffer
19409+        ? _SG_VK_MEMTYPE_STORAGE_BUFFER
19410+        : _SG_VK_MEMTYPE_GENERIC_BUFFER;
19411+    buf->vk.mem = _sg_vk_mem_alloc_device_memory(mem_type, &mem_reqs);
19412+    if (0 == buf->vk.mem) {
19413+        _SG_ERROR(VULKAN_ALLOC_BUFFER_DEVICE_MEMORY_FAILED);
19414+        return false;
19415+    }
19416+    return true;
19417+}
19418+
19419+_SOKOL_PRIVATE bool _sg_vk_mem_alloc_image_device_memory(_sg_image_t* img) {
19420+    SOKOL_ASSERT(_sg.vk.dev);
19421+    SOKOL_ASSERT(img);
19422+    SOKOL_ASSERT(img->vk.img);
19423+    SOKOL_ASSERT(0 == img->vk.mem);
19424+    _SG_STRUCT(VkMemoryRequirements, mem_reqs);
19425+    vkGetImageMemoryRequirements(_sg.vk.dev, img->vk.img, &mem_reqs);
19426+    img->vk.mem = _sg_vk_mem_alloc_device_memory(_SG_VK_MEMTYPE_IMAGE, &mem_reqs);
19427+    if (0 == img->vk.mem) {
19428+        _SG_ERROR(VULKAN_ALLOC_IMAGE_DEVICE_MEMORY_FAILED);
19429+        return false;
19430+    }
19431+    return true;
19432+}
19433+
19434+_SOKOL_PRIVATE void _sg_vk_create_delete_queues(void) {
19435+    const uint32_t num_items = (uint32_t)
19436+        (2 * _sg.desc.buffer_pool_size +
19437+        2 * _sg.desc.image_pool_size +
19438+        1 * _sg.desc.sampler_pool_size +
19439+        5 * _sg.desc.shader_pool_size +
19440+        2 * _sg.desc.pipeline_pool_size +
19441+        1 * _sg.desc.view_pool_size +
19442+        256);
19443+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
19444+        _sg_vk_delete_queue_t* queue = &_sg.vk.frame.slot[i].delete_queue;
19445+        SOKOL_ASSERT(0 == queue->items);
19446+        SOKOL_ASSERT(0 == queue->index);
19447+        queue->num = num_items;
19448+        const size_t pool_size = num_items * sizeof(_sg_vk_delete_queue_item_t);
19449+        queue->items = (_sg_vk_delete_queue_item_t*)_sg_malloc(pool_size);
19450+    }
19451+}
19452+
19453+_SOKOL_PRIVATE void _sg_vk_delete_queue_collect_items(_sg_vk_delete_queue_t* queue) {
19454+    SOKOL_ASSERT(queue && queue->items);
19455+    for (uint32_t i = 0; i < queue->index; i++) {
19456+        _sg_vk_delete_queue_item_t* item = &queue->items[i];
19457+        SOKOL_ASSERT(item->destructor && item->obj);
19458+        item->destructor(item->obj);
19459+        item->destructor = 0;
19460+        item->obj = 0;
19461+    }
19462+    _sg_stats_add(vk.num_delete_queue_collected, queue->index);
19463+    queue->index = 0;
19464+}
19465+
19466+_SOKOL_PRIVATE void _sg_vk_destroy_delete_queues(void) {
19467+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
19468+        _sg_vk_delete_queue_t* queue = &_sg.vk.frame.slot[i].delete_queue;
19469+        SOKOL_ASSERT(queue->items);
19470+        _sg_vk_delete_queue_collect_items(queue);
19471+        _sg_free(queue->items);
19472+        SOKOL_ASSERT(queue->index == 0);
19473+        queue->items = 0;
19474+        queue->num = 0;
19475+    }
19476+}
19477+
19478+_SOKOL_PRIVATE _sg_vk_delete_queue_t* _sg_vk_cur_delete_queue(void) {
19479+    return &_sg.vk.frame.slot[_sg.vk.frame_slot].delete_queue;
19480+}
19481+
19482+_SOKOL_PRIVATE void _sg_vk_delete_queue_collect(void) {
19483+    _sg_vk_delete_queue_t* queue = _sg_vk_cur_delete_queue();
19484+    _sg_vk_delete_queue_collect_items(queue);
19485+}
19486+
19487+_SOKOL_PRIVATE void _sg_vk_delete_queue_add(_sg_vk_delete_queue_destructor_t destructor, void* obj) {
19488+    SOKOL_ASSERT(destructor && obj);
19489+    _sg_vk_delete_queue_t* queue = _sg_vk_cur_delete_queue();
19490+    SOKOL_ASSERT(queue->items);
19491+    if (queue->index >= queue->num) {
19492+        _SG_PANIC(VULKAN_DELETE_QUEUE_EXHAUSTED);
19493+    }
19494+    queue->items[queue->index].destructor = destructor;
19495+    queue->items[queue->index].obj = obj;
19496+    queue->index += 1;
19497+    _sg_stats_inc(vk.num_delete_queue_added);
19498+}
19499+
19500+// double-buffer system for any non-blocking CPU => GPU data
19501+_SOKOL_PRIVATE void _sg_vk_shared_buffer_init(_sg_vk_shared_buffer_t* shbuf, uint32_t size, uint32_t align, _sg_vk_memtype_t mem_type, const char* label) {
19502+    SOKOL_ASSERT(_sg.vk.dev);
19503+    SOKOL_ASSERT(shbuf && (size > 0) && (align > 0));
19504+    SOKOL_ASSERT(0 == shbuf->size);
19505+    SOKOL_ASSERT(0 == shbuf->offset);
19506+    SOKOL_ASSERT(0 == shbuf->cur_buf);
19507+    SOKOL_ASSERT(false == shbuf->overflown);
19508+    VkResult res;
19509+    VkBufferUsageFlags vk_usage = 0;
19510+    bool want_device_address = false;
19511+    switch (mem_type) {
19512+        case _SG_VK_MEMTYPE_STAGING_STREAM:
19513+            vk_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
19514+            break;
19515+        case _SG_VK_MEMTYPE_UNIFORMS:
19516+            vk_usage  = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
19517+            vk_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
19518+            want_device_address = true;
19519+            break;
19520+        case _SG_VK_MEMTYPE_DESCRIPTORS:
19521+            vk_usage  = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
19522+            vk_usage |= VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT;
19523+            vk_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
19524+            want_device_address = true;
19525+            break;
19526+        default:
19527+            SOKOL_UNREACHABLE;
19528+            break;
19529+    }
19530+
19531+    shbuf->size = _sg_roundup_u32(size, align);
19532+    shbuf->align = align;
19533+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
19534+        SOKOL_ASSERT(0 == shbuf->slots[i].buf);
19535+        SOKOL_ASSERT(0 == shbuf->slots[i].mem);
19536+        SOKOL_ASSERT(0 == shbuf->slots[i].mem_ptr);
19537+        _SG_STRUCT(VkBufferCreateInfo, buf_create_info);
19538+        buf_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
19539+        buf_create_info.size = shbuf->size;
19540+        buf_create_info.usage = vk_usage;
19541+        buf_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
19542+        res = vkCreateBuffer(_sg.vk.dev, &buf_create_info, 0, &shbuf->slots[i].buf);
19543+        if (res != VK_SUCCESS) {
19544+            _SG_PANIC(VULKAN_CREATE_SHARED_BUFFER_FAILED);
19545+        }
19546+        SOKOL_ASSERT(shbuf->slots[i].buf);
19547+        _sg_vk_set_object_label(VK_OBJECT_TYPE_BUFFER, (uint64_t)shbuf->slots[i].buf, label);
19548+
19549+        _SG_STRUCT(VkMemoryRequirements, mem_reqs);
19550+        vkGetBufferMemoryRequirements(_sg.vk.dev, shbuf->slots[i].buf, &mem_reqs);
19551+        shbuf->slots[i].mem = _sg_vk_mem_alloc_device_memory(mem_type, &mem_reqs);
19552+        if (0 == shbuf->slots[i].mem) {
19553+            _SG_PANIC(VULKAN_ALLOCATE_SHARED_BUFFER_MEMORY_FAILED);
19554+        }
19555+        res = vkBindBufferMemory(_sg.vk.dev, shbuf->slots[i].buf, shbuf->slots[i].mem, 0);
19556+        if (res != VK_SUCCESS) {
19557+            _SG_PANIC(VULKAN_BIND_SHARED_BUFFER_MEMORY_FAILED);
19558+        }
19559+        if (want_device_address) {
19560+            _SG_STRUCT(VkBufferDeviceAddressInfo, addr_info);
19561+            addr_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
19562+            addr_info.buffer = shbuf->slots[i].buf;
19563+            shbuf->slots[i].dev_addr = vkGetBufferDeviceAddress(_sg.vk.dev, &addr_info);
19564+            SOKOL_ASSERT(shbuf->slots[i].dev_addr);
19565+        }
19566+        res = vkMapMemory(_sg.vk.dev, shbuf->slots[i].mem, 0, VK_WHOLE_SIZE, 0, &shbuf->slots[i].mem_ptr);
19567+        if (res != VK_SUCCESS) {
19568+            _SG_PANIC(VULKAN_MAP_SHARED_BUFFER_MEMORY_FAILED);
19569+        }
19570+        SOKOL_ASSERT(shbuf->slots[i].mem_ptr);
19571+    }
19572+}
19573+
19574+_SOKOL_PRIVATE void _sg_vk_shared_buffer_discard(_sg_vk_shared_buffer_t* shbuf) {
19575+    SOKOL_ASSERT(_sg.vk.dev);
19576+    SOKOL_ASSERT(shbuf);
19577+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
19578+        SOKOL_ASSERT(shbuf->slots[i].buf);
19579+        SOKOL_ASSERT(shbuf->slots[i].mem);
19580+        SOKOL_ASSERT(shbuf->slots[i].mem_ptr);
19581+        vkUnmapMemory(_sg.vk.dev, shbuf->slots[i].mem);
19582+        shbuf->slots[i].mem_ptr = 0;
19583+        _sg_vk_mem_free_device_memory(shbuf->slots[i].mem);
19584+        shbuf->slots[i].mem = 0;
19585+        vkDestroyBuffer(_sg.vk.dev, shbuf->slots[i].buf, 0);
19586+        shbuf->slots[i].buf = 0;
19587+        shbuf->slots[i].dev_addr = 0;
19588+    }
19589+    shbuf->size = 0;
19590+    shbuf->offset = 0;
19591+    shbuf->cur_buf = 0;
19592+    shbuf->cur_dev_addr = 0;
19593+    shbuf->overflown = false;
19594+}
19595+
19596+_SOKOL_PRIVATE void _sg_vk_shared_buffer_after_acquire(_sg_vk_shared_buffer_t* shbuf) {
19597+    SOKOL_ASSERT(_sg.vk.dev);
19598+    SOKOL_ASSERT(0 == shbuf->cur_buf);
19599+    SOKOL_ASSERT(0 == shbuf->cur_mem_ptr);
19600+    SOKOL_ASSERT(0 == shbuf->cur_dev_addr);
19601+    const uint32_t frame_slot = _sg.vk.frame_slot;
19602+    shbuf->offset = 0;
19603+    shbuf->cur_buf = shbuf->slots[frame_slot].buf;
19604+    shbuf->cur_mem_ptr = shbuf->slots[frame_slot].mem_ptr;
19605+    shbuf->cur_dev_addr = shbuf->slots[frame_slot].dev_addr; // NOTE: may be 0
19606+    shbuf->overflown = false;
19607+    SOKOL_ASSERT(shbuf->cur_buf);
19608+    SOKOL_ASSERT(shbuf->cur_mem_ptr);
19609+}
19610+
19611+_SOKOL_PRIVATE void _sg_vk_shared_buffer_before_submit(_sg_vk_shared_buffer_t* shbuf) {
19612+    SOKOL_ASSERT(shbuf->cur_buf);
19613+    SOKOL_ASSERT(shbuf->cur_mem_ptr);
19614+    // NOTE: if the buffer wouldn't be cache-coherent, this would be the place to do a flush
19615+    shbuf->cur_buf = 0;
19616+    shbuf->cur_mem_ptr = 0;
19617+    shbuf->cur_dev_addr = 0;
19618+}
19619+
19620+_SOKOL_PRIVATE VkDeviceSize _sg_vk_shared_buffer_alloc(_sg_vk_shared_buffer_t* shbuf, uint32_t num_bytes) {
19621+    SOKOL_ASSERT(shbuf && (num_bytes > 0));
19622+    if (shbuf->overflown) {
19623+        return _SG_VK_SHARED_BUFFER_OVERFLOW_RESULT;
19624+    }
19625+    if ((shbuf->offset + num_bytes) > shbuf->size) {
19626+        shbuf->overflown = true;
19627+        return _SG_VK_SHARED_BUFFER_OVERFLOW_RESULT;
19628+    }
19629+    SOKOL_ASSERT((shbuf->offset & (shbuf->align - 1)) == 0);
19630+    VkDeviceSize offset = shbuf->offset;
19631+    shbuf->offset = _sg_roundup_u32(shbuf->offset + num_bytes, shbuf->align);
19632+    return offset;
19633+}
19634+
19635+_SOKOL_PRIVATE uint8_t* _sg_vk_shared_buffer_ptr(_sg_vk_shared_buffer_t* shbuf, VkDeviceSize offset) {
19636+    SOKOL_ASSERT(shbuf && shbuf->cur_mem_ptr);
19637+    SOKOL_ASSERT(!shbuf->overflown);
19638+    SOKOL_ASSERT(offset < shbuf->size);
19639+    return ((uint8_t*)shbuf->cur_mem_ptr) + offset;
19640+}
19641+
19642+_SOKOL_PRIVATE VkDeviceSize _sg_vk_shared_buffer_memcpy(_sg_vk_shared_buffer_t* shbuf, const void* src_ptr, uint32_t num_bytes) {
19643+    SOKOL_ASSERT(shbuf && src_ptr && (num_bytes > 0));
19644+    const VkDeviceSize offset = _sg_vk_shared_buffer_alloc(shbuf, num_bytes);
19645+    if (offset != _SG_VK_SHARED_BUFFER_OVERFLOW_RESULT) {
19646+        memcpy(_sg_vk_shared_buffer_ptr(shbuf, offset), src_ptr, num_bytes);
19647+    }
19648+    return offset;
19649+}
19650+
19651+// staging system for blocking immutable and dynamic updates, can deal arbitrarily sized data
19652+_SOKOL_PRIVATE void _sg_vk_staging_copy_init(void) {
19653+    SOKOL_ASSERT(_sg.vk.dev);
19654+    VkResult res;
19655+
19656+    SOKOL_ASSERT(0 == _sg.vk.stage.copy.cmd_pool);
19657+    SOKOL_ASSERT(0 == _sg.vk.stage.copy.cmd_buf);
19658+    SOKOL_ASSERT(0 == _sg.vk.stage.copy.size);
19659+    SOKOL_ASSERT(0 == _sg.vk.stage.copy.buf);
19660+    SOKOL_ASSERT(0 == _sg.vk.stage.copy.mem);
19661+    SOKOL_ASSERT(_sg.desc.vulkan.copy_staging_buffer_size > 0);
19662+
19663+    _SG_STRUCT(VkCommandPoolCreateInfo, pool_create_info);
19664+    pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
19665+    pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
19666+    pool_create_info.queueFamilyIndex = _sg.vk.queue_family_index;
19667+    res = vkCreateCommandPool(_sg.vk.dev, &pool_create_info, 0, &_sg.vk.stage.copy.cmd_pool);
19668+    SOKOL_ASSERT((res == VK_SUCCESS && _sg.vk.stage.copy.cmd_pool));
19669+    _sg_vk_set_object_label(VK_OBJECT_TYPE_COMMAND_POOL, (uint64_t)_sg.vk.stage.copy.cmd_pool, "copy-staging cmd pool");
19670+
19671+    _SG_STRUCT(VkCommandBufferAllocateInfo, cmdbuf_alloc_info);
19672+    cmdbuf_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
19673+    cmdbuf_alloc_info.commandPool = _sg.vk.stage.copy.cmd_pool;
19674+    cmdbuf_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
19675+    cmdbuf_alloc_info.commandBufferCount = 1;
19676+    res = vkAllocateCommandBuffers(_sg.vk.dev, &cmdbuf_alloc_info, &_sg.vk.stage.copy.cmd_buf);
19677+    SOKOL_ASSERT((res == VK_SUCCESS) && _sg.vk.stage.copy.cmd_buf);
19678+    _sg_vk_set_object_label(VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)_sg.vk.stage.copy.cmd_buf, "copy-staging cmd buffer");
19679+
19680+    _sg.vk.stage.copy.size = (uint32_t) _sg.desc.vulkan.copy_staging_buffer_size;
19681+    _SG_STRUCT(VkBufferCreateInfo, buf_create_info);
19682+    buf_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
19683+    buf_create_info.size = _sg.vk.stage.copy.size;
19684+    buf_create_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
19685+    buf_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
19686+    res = vkCreateBuffer(_sg.vk.dev, &buf_create_info, 0, &_sg.vk.stage.copy.buf);
19687+    if (res != VK_SUCCESS) {
19688+        _SG_PANIC(VULKAN_STAGING_CREATE_BUFFER_FAILED);
19689+    }
19690+    SOKOL_ASSERT(_sg.vk.stage.copy.buf);
19691+    _sg_vk_set_object_label(VK_OBJECT_TYPE_BUFFER, (uint64_t)_sg.vk.stage.copy.buf, "copy-staging staging buffer");
19692+
19693+    _SG_STRUCT(VkMemoryRequirements, mem_reqs);
19694+    vkGetBufferMemoryRequirements(_sg.vk.dev, _sg.vk.stage.copy.buf, &mem_reqs);
19695+    _sg.vk.stage.copy.mem = _sg_vk_mem_alloc_device_memory(_SG_VK_MEMTYPE_STAGING_COPY, &mem_reqs);
19696+    if (0 == _sg.vk.stage.copy.mem) {
19697+        _SG_PANIC(VULKAN_STAGING_ALLOCATE_MEMORY_FAILED);
19698+    }
19699+    res = vkBindBufferMemory(_sg.vk.dev, _sg.vk.stage.copy.buf, _sg.vk.stage.copy.mem, 0);
19700+    if (res != VK_SUCCESS) {
19701+        _SG_PANIC(VULKAN_STAGING_BIND_BUFFER_MEMORY_FAILED);
19702+    }
19703+}
19704+
19705+_SOKOL_PRIVATE void _sg_vk_staging_copy_discard(void) {
19706+    SOKOL_ASSERT(_sg.vk.dev);
19707+    SOKOL_ASSERT(_sg.vk.stage.copy.cmd_pool);
19708+    SOKOL_ASSERT(_sg.vk.stage.copy.cmd_buf);
19709+    SOKOL_ASSERT(_sg.vk.stage.copy.size);
19710+    SOKOL_ASSERT(_sg.vk.stage.copy.buf);
19711+    SOKOL_ASSERT(_sg.vk.stage.copy.mem);
19712+
19713+    _sg_vk_mem_free_device_memory(_sg.vk.stage.copy.mem);
19714+    _sg.vk.stage.copy.mem = 0;
19715+    vkDestroyBuffer(_sg.vk.dev, _sg.vk.stage.copy.buf, 0);
19716+    _sg.vk.stage.copy.buf = 0;
19717+    vkDestroyCommandPool(_sg.vk.dev, _sg.vk.stage.copy.cmd_pool, 0);
19718+    _sg.vk.stage.copy.cmd_pool = 0;
19719+    _sg.vk.stage.copy.cmd_buf = 0;
19720+    _sg.vk.stage.copy.size = 0;
19721+}
19722+
19723+_SOKOL_PRIVATE VkCommandBuffer _sg_vk_staging_copy_begin(void) {
19724+    VkCommandBuffer cmd_buf = _sg.vk.stage.copy.cmd_buf;
19725+    _SG_STRUCT(VkCommandBufferBeginInfo, cmdbuf_begin_info);
19726+    cmdbuf_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
19727+    cmdbuf_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
19728+    VkResult res = vkBeginCommandBuffer(cmd_buf, &cmdbuf_begin_info);
19729+    SOKOL_ASSERT(res == VK_SUCCESS); _SOKOL_UNUSED(res);
19730+    return cmd_buf;
19731+}
19732+
19733+_SOKOL_PRIVATE void _sg_vk_staging_copy_end(VkCommandBuffer cmd_buf, VkQueue queue) {
19734+    SOKOL_ASSERT(cmd_buf && queue);
19735+    VkResult res;
19736+    _SOKOL_UNUSED(res);
19737+    vkEndCommandBuffer(cmd_buf);
19738+    _SG_STRUCT(VkSubmitInfo, submit_info);
19739+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
19740+    submit_info.commandBufferCount = 1;
19741+    submit_info.pCommandBuffers = &cmd_buf;
19742+    res = vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE);
19743+    SOKOL_ASSERT(res == VK_SUCCESS);
19744+    res = vkQueueWaitIdle(queue);
19745+    SOKOL_ASSERT(res == VK_SUCCESS);
19746+    res = vkResetCommandBuffer(cmd_buf, 0);
19747+    SOKOL_ASSERT(res == VK_SUCCESS);
19748+}
19749+
19750+_SOKOL_PRIVATE void _sg_vk_staging_map_memcpy_unmap(VkDeviceMemory mem, const void* ptr, uint32_t num_bytes) {
19751+    SOKOL_ASSERT(_sg.vk.dev);
19752+    SOKOL_ASSERT(mem);
19753+    SOKOL_ASSERT(ptr);
19754+    SOKOL_ASSERT(num_bytes > 0);
19755+    void* dst_ptr = 0;
19756+    VkResult res = vkMapMemory(_sg.vk.dev, mem, 0, VK_WHOLE_SIZE, 0, &dst_ptr);
19757+    SOKOL_ASSERT((res == VK_SUCCESS) && dst_ptr); _SOKOL_UNUSED(res);
19758+    memcpy(dst_ptr, ptr, num_bytes);
19759+    vkUnmapMemory(_sg.vk.dev, mem);
19760+}
19761+
19762+_SOKOL_PRIVATE void _sg_vk_staging_copy_buffer_data(_sg_buffer_t* buf, const sg_range* src_data, size_t dst_offset, bool initial_wait) {
19763+    SOKOL_ASSERT(_sg.vk.dev);
19764+    SOKOL_ASSERT(_sg.vk.queue);
19765+    SOKOL_ASSERT(_sg.vk.stage.copy.mem);
19766+    SOKOL_ASSERT(_sg.vk.stage.copy.buf);
19767+    SOKOL_ASSERT(buf && buf->vk.buf);
19768+    SOKOL_ASSERT(src_data && src_data->ptr && (src_data->size > 0));
19769+    SOKOL_ASSERT((dst_offset + src_data->size) <= (size_t)buf->cmn.size);
19770+
19771+    // an inital wait is only needed for updating existing resources but not when populating a new resource
19772+    if (initial_wait) {
19773+        VkResult res = vkQueueWaitIdle(_sg.vk.queue);
19774+        SOKOL_ASSERT(res == VK_SUCCESS); _SOKOL_UNUSED(res);
19775+    }
19776+
19777+    VkDeviceMemory dst_mem = _sg.vk.stage.copy.mem;
19778+    VkBuffer src_buf = _sg.vk.stage.copy.buf;
19779+    VkBuffer dst_buf = buf->vk.buf;
19780+    const uint8_t* src_ptr = (const uint8_t*)src_data->ptr;
19781+    uint32_t dst_size = _sg.vk.stage.copy.size;
19782+    uint32_t bytes_remaining = (uint32_t)src_data->size;
19783+    _SG_STRUCT(VkBufferCopy, region);
19784+    region.dstOffset = dst_offset;
19785+    while (bytes_remaining > 0) {
19786+        uint64_t bytes_to_copy = bytes_remaining;
19787+        if (bytes_remaining > dst_size) {
19788+            bytes_to_copy = dst_size;
19789+            bytes_remaining -= dst_size;
19790+        } else {
19791+            bytes_to_copy = bytes_remaining;
19792+            bytes_remaining = 0;
19793+        }
19794+        region.size = bytes_to_copy;
19795+        _sg_vk_staging_map_memcpy_unmap(dst_mem, src_ptr, (uint32_t)bytes_to_copy);
19796+        VkCommandBuffer cmd_buf = _sg_vk_staging_copy_begin();
19797+        vkCmdCopyBuffer(cmd_buf, src_buf, dst_buf, 1, &region);
19798+        _sg_stats_inc(vk.num_cmd_copy_buffer);
19799+        _sg_vk_staging_copy_end(cmd_buf, _sg.vk.queue);
19800+        src_ptr += bytes_to_copy;
19801+        region.dstOffset += bytes_to_copy;
19802+    }
19803+    buf->vk.cur_access = _SG_VK_ACCESS_VERTEXBUFFER | _SG_VK_ACCESS_INDEXBUFFER | _SG_VK_ACCESS_STORAGEBUFFER_RO;
19804+}
19805+
19806+_SOKOL_PRIVATE void _sg_vk_init_vk_image_staging_structs(const _sg_image_t* img, VkBuffer vk_buf, VkBufferImageCopy2* region, VkCopyBufferToImageInfo2* copy_info) {
19807+    SOKOL_ASSERT(img && region && copy_info);
19808+
19809+    region->sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
19810+    if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) {
19811+        region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
19812+        if (_sg_is_depth_stencil_format(img->cmn.pixel_format)) {
19813+            region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
19814+        }
19815+    } else {
19816+        region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
19817+    }
19818+    region->imageSubresource.layerCount = 1;
19819+    region->imageExtent.depth = 1;
19820+
19821+    copy_info->sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
19822+    copy_info->srcBuffer = vk_buf;
19823+    copy_info->dstImage = img->vk.img;
19824+    copy_info->dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
19825+    copy_info->regionCount = 1;
19826+    copy_info->pRegions = region;
19827+}
19828+
19829+_SOKOL_PRIVATE void _sg_vk_staging_copy_image_data(_sg_image_t* img, const sg_image_data* src_data, bool initial_wait) {
19830+    SOKOL_ASSERT(_sg.vk.dev);
19831+    SOKOL_ASSERT(_sg.vk.queue);
19832+    SOKOL_ASSERT(_sg.vk.stage.copy.mem);
19833+    SOKOL_ASSERT(_sg.vk.stage.copy.buf);
19834+    SOKOL_ASSERT(img && img->vk.img);
19835+    const uint32_t block_dim = (uint32_t)_sg_block_dim(img->cmn.pixel_format);
19836+
19837+    // an inital wait is only needed for updating existing resources but not when populating a new resource
19838+    if (initial_wait) {
19839+        VkResult res = vkQueueWaitIdle(_sg.vk.queue);
19840+        SOKOL_ASSERT(res == VK_SUCCESS); _SOKOL_UNUSED(res);
19841+    }
19842+
19843+    VkDeviceMemory mem = _sg.vk.stage.copy.mem;
19844+    _SG_STRUCT(VkBufferImageCopy2, region);
19845+    _SG_STRUCT(VkCopyBufferToImageInfo2, copy_info);
19846+    _sg_vk_init_vk_image_staging_structs(img, _sg.vk.stage.copy.buf, &region, &copy_info);
19847+    for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) {
19848+        const uint8_t* src_ptr = (uint8_t*)src_data->mip_levels[mip_index].ptr;
19849+        int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
19850+        int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
19851+        int mip_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices;
19852+        const uint32_t row_pitch = (uint32_t) _sg_row_pitch(img->cmn.pixel_format, mip_width, 1);
19853+        const uint32_t num_rows = (uint32_t) _sg_num_rows(img->cmn.pixel_format, mip_height);
19854+        region.imageSubresource.mipLevel = (uint32_t)mip_index;
19855+        region.imageExtent.width = (uint32_t)mip_width;
19856+
19857+        const uint32_t max_rows = _sg.vk.stage.copy.size / row_pitch;
19858+        for (int slice_index = 0; slice_index < mip_slices; slice_index++) {
19859+            if (img->cmn.type == SG_IMAGETYPE_3D) {
19860+                region.imageOffset.z = slice_index;
19861+            } else {
19862+                region.imageSubresource.baseArrayLayer = (uint32_t)slice_index;
19863+            }
19864+            uint32_t rows_remaining = num_rows;
19865+            uint32_t cur_row = 0;
19866+            while (rows_remaining > 0) {
19867+                uint32_t rows_to_copy = rows_remaining;
19868+                if (rows_remaining > max_rows) {
19869+                    rows_to_copy = max_rows;
19870+                    rows_remaining -= max_rows;
19871+                } else {
19872+                    rows_to_copy = rows_remaining;
19873+                    rows_remaining = 0;
19874+                }
19875+                const uint32_t bytes_to_copy = rows_to_copy * row_pitch;
19876+                SOKOL_ASSERT(bytes_to_copy <= _sg.vk.stage.copy.size);
19877+                _sg_vk_staging_map_memcpy_unmap(mem, src_ptr, bytes_to_copy);
19878+                src_ptr += bytes_to_copy;
19879+                VkCommandBuffer cmd_buf = _sg_vk_staging_copy_begin();
19880+                _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_STAGING);
19881+                region.imageOffset.y = (int32_t)(cur_row * block_dim);
19882+                region.imageExtent.height = _sg_min((uint32_t)mip_height, rows_to_copy * block_dim);
19883+                vkCmdCopyBufferToImage2(cmd_buf, &copy_info);
19884+                _sg_stats_inc(vk.num_cmd_copy_buffer_to_image);
19885+                _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19886+
19887+                _sg_vk_staging_copy_end(cmd_buf, _sg.vk.queue);
19888+                cur_row += rows_to_copy;
19889+            }
19890+        }
19891+    }
19892+}
19893+
19894+// staging system for non-blocking streaming updates with a max per-frame data limit
19895+_SOKOL_PRIVATE void _sg_vk_staging_stream_init(void) {
19896+    SOKOL_ASSERT(_sg.desc.vulkan.stream_staging_buffer_size > 0);
19897+    _sg_vk_shared_buffer_init(&_sg.vk.stage.stream,
19898+        (uint32_t)_sg.desc.vulkan.stream_staging_buffer_size,
19899+        16, // NOTE: arbitrary alignment (FIXME?)
19900+        _SG_VK_MEMTYPE_STAGING_STREAM,
19901+        "shared-stream-buffer");
19902+}
19903+
19904+_SOKOL_PRIVATE void _sg_vk_staging_stream_discard(void) {
19905+    _sg_vk_shared_buffer_discard(&_sg.vk.stage.stream);
19906+}
19907+
19908+_SOKOL_PRIVATE void _sg_vk_staging_stream_after_acquire(void) {
19909+    _sg_vk_shared_buffer_after_acquire(&_sg.vk.stage.stream);
19910+}
19911+
19912+_SOKOL_PRIVATE void _sg_vk_staging_stream_before_submit(void) {
19913+    _sg_vk_shared_buffer_before_submit(&_sg.vk.stage.stream);
19914+}
19915+
19916+_SOKOL_PRIVATE void _sg_vk_staging_stream_buffer_data(_sg_buffer_t* buf, const sg_range* src_data, size_t dst_offset) {
19917+    SOKOL_ASSERT(_sg.vk.dev);
19918+    SOKOL_ASSERT(_sg.vk.frame.stream_cmd_buf);
19919+    SOKOL_ASSERT(_sg.vk.stage.stream.cur_buf);
19920+    SOKOL_ASSERT(buf && buf->vk.buf);
19921+    SOKOL_ASSERT(src_data && src_data->ptr && (src_data->size > 0));
19922+    SOKOL_ASSERT((src_data->size + dst_offset) <= (size_t)buf->cmn.size);
19923+
19924+    const uint32_t src_offset = (uint32_t)_sg_vk_shared_buffer_memcpy(&_sg.vk.stage.stream, src_data->ptr, (uint32_t)src_data->size);
19925+    if (src_offset == _SG_VK_SHARED_BUFFER_OVERFLOW_RESULT) {
19926+        _SG_ERROR(VULKAN_STAGING_STREAM_BUFFER_OVERFLOW);
19927+        return;
19928+    }
19929+    VkCommandBuffer cmd_buf = _sg.vk.frame.stream_cmd_buf;
19930+    VkBuffer vk_src_buf = _sg.vk.stage.stream.cur_buf;
19931+    VkBuffer vk_dst_buf = buf->vk.buf;
19932+    _SG_STRUCT(VkBufferCopy, region);
19933+    region.srcOffset = src_offset;
19934+    region.dstOffset = dst_offset;
19935+    region.size = src_data->size;
19936+    _sg_vk_buffer_barrier(cmd_buf, buf, _SG_VK_ACCESS_STAGING);
19937+    vkCmdCopyBuffer(cmd_buf, vk_src_buf, vk_dst_buf, 1, &region);
19938+    _sg_stats_inc(vk.num_cmd_copy_buffer);
19939+    // FIXME: not great to issue a barrier right here,
19940+    // rethink buffer barrier strategy? => a single memory barrier
19941+    // at the end of the stream command buffer should be sufficient?
19942+    _sg_vk_buffer_barrier(cmd_buf, buf, _SG_VK_ACCESS_VERTEXBUFFER|_SG_VK_ACCESS_INDEXBUFFER|_SG_VK_ACCESS_STORAGEBUFFER_RO);
19943+}
19944+
19945+_SOKOL_PRIVATE void _sg_vk_staging_stream_image_data(_sg_image_t* img, const sg_image_data* src_data) {
19946+    SOKOL_ASSERT(_sg.vk.dev);
19947+    SOKOL_ASSERT(_sg.vk.frame.stream_cmd_buf);
19948+    SOKOL_ASSERT(img && img->vk.img);
19949+    SOKOL_ASSERT(src_data);
19950+    VkCommandBuffer cmd_buf = _sg.vk.frame.stream_cmd_buf;
19951+    _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_STAGING);
19952+    _SG_STRUCT(VkBufferImageCopy2, region);
19953+    _SG_STRUCT(VkCopyBufferToImageInfo2, copy_info);
19954+    _sg_vk_init_vk_image_staging_structs(img, _sg.vk.stage.stream.cur_buf, &region, &copy_info);
19955+    for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) {
19956+        const sg_range* src_mip = &src_data->mip_levels[mip_index];
19957+        SOKOL_ASSERT(src_mip->ptr);
19958+        SOKOL_ASSERT(src_mip->size > 0);
19959+        const uint32_t src_offset = (uint32_t)_sg_vk_shared_buffer_memcpy(&_sg.vk.stage.stream, src_mip->ptr, (uint32_t)src_mip->size);
19960+        if (src_offset == _SG_VK_SHARED_BUFFER_OVERFLOW_RESULT) {
19961+            _SG_ERROR(VULKAN_STAGING_STREAM_BUFFER_OVERFLOW);
19962+            _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19963+            return;
19964+        }
19965+        region.bufferOffset = src_offset;
19966+        int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index);
19967+        int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index);
19968+        int mip_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? _sg_miplevel_dim(img->cmn.num_slices, mip_index) : img->cmn.num_slices;
19969+        region.imageExtent.width = (uint32_t)mip_width;
19970+        region.imageExtent.height = (uint32_t)mip_height;
19971+        region.imageSubresource.mipLevel = (uint32_t)mip_index;
19972+        if (img->cmn.type == SG_IMAGETYPE_3D) {
19973+            region.imageExtent.depth = (uint32_t)mip_slices;
19974+            region.imageSubresource.layerCount = 1;
19975+        } else {
19976+            region.imageExtent.depth = 1;
19977+            region.imageSubresource.layerCount = (uint32_t)mip_slices;
19978+        }
19979+        vkCmdCopyBufferToImage2(cmd_buf, &copy_info);
19980+        _sg_stats_inc(vk.num_cmd_copy_buffer_to_image);
19981+    }
19982+    _sg_vk_image_barrier(cmd_buf, img, _SG_VK_ACCESS_TEXTURE);
19983+}
19984+
19985+// uniform data system
19986+_SOKOL_PRIVATE void _sg_vk_uniform_init(void) {
19987+    SOKOL_ASSERT(_sg.desc.uniform_buffer_size > 0);
19988+    SOKOL_ASSERT(0 == _sg.vk.uniforms.dset_cache);
19989+
19990+    _sg_vk_shared_buffer_init(&_sg.vk.uniforms.dbuf,
19991+        (uint32_t)_sg.desc.uniform_buffer_size,
19992+        (uint32_t)_sg.vk.dev_props.properties.limits.minUniformBufferOffsetAlignment,
19993+        _SG_VK_MEMTYPE_UNIFORMS,
19994+        "shared-uniform-buffer");
19995+
19996+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
19997+        _sg.vk.uniforms.addr_info[i].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT;
19998+        _sg.vk.uniforms.get_info[i].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT;
19999+        _sg.vk.uniforms.get_info[i].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
20000+        _sg.vk.uniforms.get_info[i].data.pUniformBuffer = &_sg.vk.uniforms.addr_info[i];
20001+    }
20002+
20003+    // NOTE: we assume here that the max alignment for uniform buffer
20004+    // descriptors in the descriptor buffer is the same as the assumed max
20005+    // descriptor size (e.g. 256 bytes)
20006+    _sg.vk.uniforms.dset_cache_size = SG_MAX_UNIFORMBLOCK_BINDSLOTS * _SG_VK_MAX_DESCRIPTOR_DATA_SIZE;
20007+    _sg.vk.uniforms.dset_cache = (uint8_t*)_sg_malloc_clear(_sg.vk.uniforms.dset_cache_size);
20008+}
20009+
20010+_SOKOL_PRIVATE void _sg_vk_uniform_discard(void) {
20011+    SOKOL_ASSERT(_sg.vk.uniforms.dset_cache);
20012+    _sg_free(_sg.vk.uniforms.dset_cache); _sg.vk.uniforms.dset_cache = 0;
20013+    _sg_vk_shared_buffer_discard(&_sg.vk.uniforms.dbuf);
20014+}
20015+
20016+// called from _sg_vk_acquire_frame_command_buffer()
20017+_SOKOL_PRIVATE void _sg_vk_uniform_after_acquire(void) {
20018+    _sg_vk_shared_buffer_after_acquire(&_sg.vk.uniforms.dbuf);
20019+    // reset uniform tracking data
20020+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
20021+        _sg.vk.uniforms.addr_info[i].address = 0;
20022+        _sg.vk.uniforms.addr_info[i].range = 0;
20023+    }
20024+}
20025+
20026+// called from _sg_vk_submit_frame_command_buffer()
20027+_SOKOL_PRIVATE void _sg_vk_uniform_before_submit(void) {
20028+    _sg_vk_shared_buffer_before_submit(&_sg.vk.uniforms.dbuf);
20029+}
20030+
20031+// called form _sg_vk_apply_uniforms, returns offset of data snippet into uniform buffer
20032+_SOKOL_PRIVATE uint32_t _sg_vk_uniform_copy(const sg_range* data) {
20033+    SOKOL_ASSERT(data && data->ptr && (data->size > 0));
20034+    return (uint32_t)_sg_vk_shared_buffer_memcpy(&_sg.vk.uniforms.dbuf, data->ptr, (uint32_t)data->size);
20035+}
20036+
20037+// resource binding system
20038+_SOKOL_PRIVATE void _sg_vk_bind_init(void) {
20039+    SOKOL_ASSERT(_sg.desc.vulkan.descriptor_buffer_size > 0);
20040+    _sg_vk_shared_buffer_init(&_sg.vk.bind,
20041+        (uint32_t)_sg.desc.vulkan.descriptor_buffer_size,
20042+        (uint32_t)_sg.vk.descriptor_buffer_props.descriptorBufferOffsetAlignment,
20043+        _SG_VK_MEMTYPE_DESCRIPTORS,
20044+        "shared-descriptor-buffer");
20045+}
20046+
20047+_SOKOL_PRIVATE void _sg_vk_bind_discard(void) {
20048+    _sg_vk_shared_buffer_discard(&_sg.vk.bind);
20049+}
20050+
20051+// called from _sg_vk_acquire_frame_command_buffer()
20052+_SOKOL_PRIVATE void _sg_vk_bind_after_acquire(void) {
20053+    _sg_vk_shared_buffer_after_acquire(&_sg.vk.bind);
20054+
20055+    // bind the current frame's descriptor buffer
20056+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
20057+    SOKOL_ASSERT(_sg.vk.bind.cur_buf);
20058+    SOKOL_ASSERT(_sg.vk.bind.cur_dev_addr);
20059+    _SG_STRUCT(VkDescriptorBufferBindingInfoEXT, bind_info);
20060+    bind_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT;
20061+    bind_info.address = _sg.vk.bind.cur_dev_addr;
20062+    bind_info.usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT |
20063+                      VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT;
20064+    _sg.vk.ext.cmd_bind_descriptor_buffers(_sg.vk.frame.cmd_buf, 1, &bind_info);
20065+}
20066+
20067+// called from _sg_vk_submit_frame_command_buffer()
20068+_SOKOL_PRIVATE void _sg_vk_bind_before_submit(void) {
20069+    _sg_vk_shared_buffer_before_submit(&_sg.vk.bind);
20070+}
20071+
20072+_SOKOL_PRIVATE bool _sg_vk_bind_view_smp_descriptor_set(VkCommandBuffer cmd_buf, const _sg_bindings_ptrs_t* bnd, VkPipelineBindPoint vk_bind_point) {
20073+    SOKOL_ASSERT(_sg.vk.dev);
20074+    SOKOL_ASSERT(cmd_buf);
20075+    SOKOL_ASSERT(bnd && bnd->pip);
20076+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd->pip->cmn.shader);
20077+
20078+    // get next pointer in descriptor buffer
20079+    const VkDeviceSize dset_size = shd->vk.view_smp_dset_size;
20080+    if (dset_size == 0) {
20081+        // nothing to bind
20082+        return true;
20083+    }
20084+    const VkDeviceSize dbuf_offset = _sg_vk_shared_buffer_alloc(&_sg.vk.bind, (uint32_t)dset_size);
20085+    if (_sg.vk.bind.overflown) {
20086+        _SG_ERROR(VULKAN_DESCRIPTOR_BUFFER_OVERFLOW);
20087+        return false;
20088+    }
20089+    _sg_stats_add(vk.size_descriptor_buffer_writes, (uint32_t)dset_size);
20090+    uint8_t* dbuf_ptr = _sg_vk_shared_buffer_ptr(&_sg.vk.bind, dbuf_offset);
20091+
20092+    // copy pre-recorded descriptor data into descriptor buffer
20093+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
20094+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
20095+            continue;
20096+        }
20097+        const _sg_view_t* view = bnd->views[i];
20098+        SOKOL_ASSERT(view && (view->vk.descriptor_size > 0));
20099+        const void* src_ptr = view->vk.descriptor_data;
20100+        size_t size = view->vk.descriptor_size;
20101+        void* dst_ptr = dbuf_ptr + shd->vk.view_dset_offsets[i];
20102+        memcpy(dst_ptr, src_ptr, size);
20103+    }
20104+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
20105+        if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) {
20106+            continue;
20107+        }
20108+        const _sg_sampler_t* smp = bnd->smps[i];
20109+        SOKOL_ASSERT(smp && (smp->vk.descriptor_size > 0));
20110+        const void* src_ptr = smp->vk.descriptor_data;
20111+        size_t size = smp->vk.descriptor_size;
20112+        void* dst_ptr = dbuf_ptr + shd->vk.smp_dset_offsets[i];
20113+        memcpy(dst_ptr, src_ptr, size);
20114+    }
20115+
20116+    // record the new descriptor buffer offset
20117+    const uint32_t dbuf_index = 0;
20118+    SOKOL_ASSERT(shd->vk.pip_layout);
20119+    _sg.vk.ext.cmd_set_descriptor_buffer_offsets(
20120+        cmd_buf,
20121+        vk_bind_point,
20122+        shd->vk.pip_layout,
20123+        _SG_VK_VIEW_SMP_DESCRIPTORSET_INDEX, // firstSet
20124+        1,  // setCount
20125+        &dbuf_index,
20126+        &dbuf_offset);
20127+    _sg_stats_inc(vk.num_cmd_set_descriptor_buffer_offsets);
20128+    return true;
20129+}
20130+
20131+_SOKOL_PRIVATE bool _sg_vk_bind_uniform_descriptor_set(VkCommandBuffer cmd_buf) {
20132+    SOKOL_ASSERT(cmd_buf);
20133+    SOKOL_ASSERT(_sg.vk.uniforms.dirty);
20134+    _sg.vk.uniforms.dirty = false;
20135+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
20136+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
20137+
20138+    // get next pointer in descriptor buffer
20139+    const VkDeviceSize dbuf_offset = _sg_vk_shared_buffer_alloc(&_sg.vk.bind, (uint32_t)shd->vk.ub_dset_size);
20140+    if (_sg.vk.bind.overflown) {
20141+        _SG_ERROR(VULKAN_DESCRIPTOR_BUFFER_OVERFLOW);
20142+        return false;
20143+    }
20144+    _sg_stats_add(vk.size_descriptor_buffer_writes, (uint32_t)shd->vk.ub_dset_size);
20145+    uint8_t* dbuf_ptr = _sg_vk_shared_buffer_ptr(&_sg.vk.bind, dbuf_offset);
20146+
20147+    // update descriptor buffer
20148+    SOKOL_ASSERT(shd->vk.ub_dset_size <= _sg.vk.uniforms.dset_cache_size);
20149+    memcpy(dbuf_ptr, _sg.vk.uniforms.dset_cache, shd->vk.ub_dset_size);
20150+
20151+    // record the descriptor buffer offset
20152+    const VkPipelineBindPoint vk_bind_point = _sg.cur_pass.is_compute
20153+        ? VK_PIPELINE_BIND_POINT_COMPUTE
20154+        : VK_PIPELINE_BIND_POINT_GRAPHICS;
20155+    const uint32_t dbuf_index = 0;
20156+    SOKOL_ASSERT(shd->vk.pip_layout);
20157+    _sg.vk.ext.cmd_set_descriptor_buffer_offsets(
20158+        cmd_buf,
20159+        vk_bind_point,
20160+        shd->vk.pip_layout,
20161+        _SG_VK_UB_DESCRIPTORSET_INDEX, // firstIndex
20162+        1, // setCount
20163+        &dbuf_index,
20164+        &dbuf_offset);
20165+    _sg_stats_inc(vk.num_cmd_set_descriptor_buffer_offsets);
20166+    return true;
20167+}
20168+
20169+_SOKOL_PRIVATE void _sg_vk_memory_destructor(void* obj) {
20170+    SOKOL_ASSERT(_sg.vk.dev && obj);
20171+    _sg_vk_mem_free_device_memory((VkDeviceMemory)obj);
20172+}
20173+
20174+_SOKOL_PRIVATE void _sg_vk_buffer_destructor(void* obj) {
20175+    SOKOL_ASSERT(_sg.vk.dev && obj);
20176+    vkDestroyBuffer(_sg.vk.dev, (VkBuffer)obj, 0);
20177+}
20178+
20179+_SOKOL_PRIVATE void _sg_vk_image_destructor(void* obj) {
20180+    SOKOL_ASSERT(_sg.vk.dev && obj);
20181+    vkDestroyImage(_sg.vk.dev, (VkImage)obj, 0);
20182+}
20183+
20184+_SOKOL_PRIVATE void _sg_vk_image_view_destructor(void* obj) {
20185+    SOKOL_ASSERT(_sg.vk.dev && obj);
20186+    vkDestroyImageView(_sg.vk.dev, (VkImageView)obj, 0);
20187+}
20188+
20189+_SOKOL_PRIVATE void _sg_vk_sampler_destructor(void* obj) {
20190+    SOKOL_ASSERT(_sg.vk.dev && obj);
20191+    vkDestroySampler(_sg.vk.dev, (VkSampler)obj, 0);
20192+}
20193+
20194+_SOKOL_PRIVATE void _sg_vk_shader_module_destructor(void* obj) {
20195+    SOKOL_ASSERT(_sg.vk.dev && obj);
20196+    vkDestroyShaderModule(_sg.vk.dev, (VkShaderModule)obj, 0);
20197+}
20198+
20199+_SOKOL_PRIVATE void _sg_vk_pipelinelayout_destructor(void* obj) {
20200+    SOKOL_ASSERT(_sg.vk.dev && obj);
20201+    vkDestroyPipelineLayout(_sg.vk.dev, (VkPipelineLayout)obj, 0);
20202+}
20203+
20204+_SOKOL_PRIVATE void _sg_vk_descriptorsetlayout_destructor(void* obj) {
20205+    SOKOL_ASSERT(_sg.vk.dev && obj);
20206+    vkDestroyDescriptorSetLayout(_sg.vk.dev, (VkDescriptorSetLayout)obj, 0);
20207+}
20208+
20209+_SOKOL_PRIVATE void _sg_vk_pipeline_destructor(void* obj) {
20210+    SOKOL_ASSERT(_sg.vk.dev && obj);
20211+    vkDestroyPipeline(_sg.vk.dev, (VkPipeline)obj, 0);
20212+}
20213+
20214+_SOKOL_PRIVATE VkBufferUsageFlags _sg_vk_buffer_usage(const sg_buffer_usage* usg) {
20215+    VkBufferUsageFlags res = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
20216+    if (usg->vertex_buffer) {
20217+        res |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
20218+    }
20219+    if (usg->index_buffer) {
20220+        res |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
20221+    }
20222+    if (usg->storage_buffer) {
20223+        res |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
20224+    }
20225+    return res;
20226+}
20227+
20228+_SOKOL_PRIVATE VkVertexInputRate _sg_vk_vertex_input_rate(sg_vertex_step s) {
20229+    return (s == SG_VERTEXSTEP_PER_VERTEX) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
20230+}
20231+
20232+_SOKOL_PRIVATE VkFormat _sg_vk_vertex_format(sg_vertex_format f) {
20233+    switch (f) {
20234+        case SG_VERTEXFORMAT_FLOAT:         return VK_FORMAT_R32_SFLOAT;
20235+        case SG_VERTEXFORMAT_FLOAT2:        return VK_FORMAT_R32G32_SFLOAT;
20236+        case SG_VERTEXFORMAT_FLOAT3:        return VK_FORMAT_R32G32B32_SFLOAT;
20237+        case SG_VERTEXFORMAT_FLOAT4:        return VK_FORMAT_R32G32B32A32_SFLOAT;
20238+        case SG_VERTEXFORMAT_INT:           return VK_FORMAT_R32_SINT;
20239+        case SG_VERTEXFORMAT_INT2:          return VK_FORMAT_R32G32_SINT;
20240+        case SG_VERTEXFORMAT_INT3:          return VK_FORMAT_R32G32B32_SINT;
20241+        case SG_VERTEXFORMAT_INT4:          return VK_FORMAT_R32G32B32A32_SINT;
20242+        case SG_VERTEXFORMAT_UINT:          return VK_FORMAT_R32_UINT;
20243+        case SG_VERTEXFORMAT_UINT2:         return VK_FORMAT_R32G32_UINT;
20244+        case SG_VERTEXFORMAT_UINT3:         return VK_FORMAT_R32G32B32_UINT;
20245+        case SG_VERTEXFORMAT_UINT4:         return VK_FORMAT_R32G32B32A32_UINT;
20246+        case SG_VERTEXFORMAT_BYTE4:         return VK_FORMAT_R8G8B8A8_SINT;
20247+        case SG_VERTEXFORMAT_BYTE4N:        return VK_FORMAT_R8G8B8A8_SNORM;
20248+        case SG_VERTEXFORMAT_UBYTE4:        return VK_FORMAT_R8G8B8A8_UINT;
20249+        case SG_VERTEXFORMAT_UBYTE4N:       return VK_FORMAT_R8G8B8A8_UNORM;
20250+        case SG_VERTEXFORMAT_SHORT2:        return VK_FORMAT_R16G16_SINT;
20251+        case SG_VERTEXFORMAT_SHORT2N:       return VK_FORMAT_R16G16_SNORM;
20252+        case SG_VERTEXFORMAT_USHORT2:       return VK_FORMAT_R16G16_UINT;
20253+        case SG_VERTEXFORMAT_USHORT2N:      return VK_FORMAT_R16G16_UNORM;
20254+        case SG_VERTEXFORMAT_SHORT4:        return VK_FORMAT_R16G16B16A16_SINT;
20255+        case SG_VERTEXFORMAT_SHORT4N:       return VK_FORMAT_R16G16B16A16_SNORM;
20256+        case SG_VERTEXFORMAT_USHORT4:       return VK_FORMAT_R16G16B16A16_UINT;
20257+        case SG_VERTEXFORMAT_USHORT4N:      return VK_FORMAT_R16G16B16A16_UNORM;
20258+        case SG_VERTEXFORMAT_INT10_N2:      return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
20259+        case SG_VERTEXFORMAT_UINT10_N2:     return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
20260+        case SG_VERTEXFORMAT_HALF2:         return VK_FORMAT_R16G16_SFLOAT;
20261+        case SG_VERTEXFORMAT_HALF4:         return VK_FORMAT_R16G16B16A16_SFLOAT;
20262+        default:
20263+            SOKOL_UNREACHABLE;
20264+            return VK_FORMAT_UNDEFINED;
20265+    }
20266+}
20267+
20268+_SOKOL_PRIVATE VkImageCreateFlags _sg_vk_image_create_flags(sg_image_type t) {
20269+    switch (t) {
20270+        case SG_IMAGETYPE_2D: return 0;
20271+        case SG_IMAGETYPE_CUBE: return VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
20272+        // FIXME: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT needed for render to slice?
20273+        case SG_IMAGETYPE_3D: return 0;
20274+        case SG_IMAGETYPE_ARRAY: return 0;
20275+        default:
20276+            SOKOL_UNREACHABLE;
20277+            return 0;
20278+    }
20279+}
20280+
20281+_SOKOL_PRIVATE VkImageType _sg_vk_image_type(sg_image_type t) {
20282+    return (SG_IMAGETYPE_3D == t) ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D;
20283+}
20284+
20285+_SOKOL_PRIVATE VkImageUsageFlags _sg_vk_image_usage(const sg_image_usage* usg) {
20286+    VkImageUsageFlags res = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
20287+    res |= VK_IMAGE_USAGE_SAMPLED_BIT;
20288+    if (usg->storage_image) {
20289+        res |= VK_IMAGE_USAGE_STORAGE_BIT;
20290+    }
20291+    if (usg->color_attachment || usg->resolve_attachment) {
20292+        res |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
20293+    }
20294+    if (usg->depth_stencil_attachment) {
20295+        res |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
20296+    }
20297+    return res;
20298+}
20299+
20300+_SOKOL_PRIVATE VkFormat _sg_vk_format(sg_pixel_format fmt) {
20301+    switch (fmt) {
20302+        case SG_PIXELFORMAT_NONE:           return VK_FORMAT_UNDEFINED;
20303+        case SG_PIXELFORMAT_R8:             return VK_FORMAT_R8_UNORM;
20304+        case SG_PIXELFORMAT_R8SN:           return VK_FORMAT_R8_SNORM;
20305+        case SG_PIXELFORMAT_R8UI:           return VK_FORMAT_R8_UINT;
20306+        case SG_PIXELFORMAT_R8SI:           return VK_FORMAT_R8_SINT;
20307+        case SG_PIXELFORMAT_R16:            return VK_FORMAT_R16_UNORM;
20308+        case SG_PIXELFORMAT_R16SN:          return VK_FORMAT_R16_SNORM;
20309+        case SG_PIXELFORMAT_R16UI:          return VK_FORMAT_R16_UINT;
20310+        case SG_PIXELFORMAT_R16SI:          return VK_FORMAT_R16_SINT;
20311+        case SG_PIXELFORMAT_R16F:           return VK_FORMAT_R16_SFLOAT;
20312+        case SG_PIXELFORMAT_RG8:            return VK_FORMAT_R8G8_UNORM;
20313+        case SG_PIXELFORMAT_RG8SN:          return VK_FORMAT_R8G8_SNORM;
20314+        case SG_PIXELFORMAT_RG8UI:          return VK_FORMAT_R8G8_UINT;
20315+        case SG_PIXELFORMAT_RG8SI:          return VK_FORMAT_R8G8_SINT;
20316+        case SG_PIXELFORMAT_R32UI:          return VK_FORMAT_R32_UINT;
20317+        case SG_PIXELFORMAT_R32SI:          return VK_FORMAT_R32_SINT;
20318+        case SG_PIXELFORMAT_R32F:           return VK_FORMAT_R32_SFLOAT;
20319+        case SG_PIXELFORMAT_RG16:           return VK_FORMAT_R16G16_UNORM;
20320+        case SG_PIXELFORMAT_RG16SN:         return VK_FORMAT_R16G16_SNORM;
20321+        case SG_PIXELFORMAT_RG16UI:         return VK_FORMAT_R16G16_UINT;
20322+        case SG_PIXELFORMAT_RG16SI:         return VK_FORMAT_R16G16_SINT;
20323+        case SG_PIXELFORMAT_RG16F:          return VK_FORMAT_R16G16_SFLOAT;
20324+        case SG_PIXELFORMAT_RGBA8:          return VK_FORMAT_R8G8B8A8_UNORM;
20325+        case SG_PIXELFORMAT_SRGB8A8:        return VK_FORMAT_R8G8B8A8_SRGB;
20326+        case SG_PIXELFORMAT_RGBA8SN:        return VK_FORMAT_R8G8B8A8_SNORM;
20327+        case SG_PIXELFORMAT_RGBA8UI:        return VK_FORMAT_R8G8B8A8_UINT;
20328+        case SG_PIXELFORMAT_RGBA8SI:        return VK_FORMAT_R8G8B8A8_SINT;
20329+        case SG_PIXELFORMAT_BGRA8:          return VK_FORMAT_B8G8R8A8_UNORM;
20330+        case SG_PIXELFORMAT_SBGR8A8:        return VK_FORMAT_B8G8R8A8_SRGB;
20331+        case SG_PIXELFORMAT_RGB10A2:        return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
20332+        case SG_PIXELFORMAT_RG11B10F:       return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
20333+        case SG_PIXELFORMAT_RGB9E5:         return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
20334+        case SG_PIXELFORMAT_RG32UI:         return VK_FORMAT_R32G32_UINT;
20335+        case SG_PIXELFORMAT_RG32SI:         return VK_FORMAT_R32G32_SINT;
20336+        case SG_PIXELFORMAT_RG32F:          return VK_FORMAT_R32G32_SFLOAT;
20337+        case SG_PIXELFORMAT_RGBA16:         return VK_FORMAT_R16G16B16A16_UNORM;
20338+        case SG_PIXELFORMAT_RGBA16SN:       return VK_FORMAT_R16G16B16A16_SNORM;
20339+        case SG_PIXELFORMAT_RGBA16UI:       return VK_FORMAT_R16G16B16A16_UINT;
20340+        case SG_PIXELFORMAT_RGBA16SI:       return VK_FORMAT_R16G16B16A16_SINT;
20341+        case SG_PIXELFORMAT_RGBA16F:        return VK_FORMAT_R16G16B16A16_SFLOAT;
20342+        case SG_PIXELFORMAT_RGBA32UI:       return VK_FORMAT_R32G32B32A32_UINT;
20343+        case SG_PIXELFORMAT_RGBA32SI:       return VK_FORMAT_R32G32B32A32_SINT;
20344+        case SG_PIXELFORMAT_RGBA32F:        return VK_FORMAT_R32G32B32A32_SFLOAT;
20345+        case SG_PIXELFORMAT_DEPTH:          return VK_FORMAT_D32_SFLOAT;
20346+        case SG_PIXELFORMAT_DEPTH_STENCIL:  return VK_FORMAT_D32_SFLOAT_S8_UINT;
20347+        case SG_PIXELFORMAT_BC1_RGBA:       return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
20348+        case SG_PIXELFORMAT_BC2_RGBA:       return VK_FORMAT_BC2_UNORM_BLOCK;
20349+        case SG_PIXELFORMAT_BC3_RGBA:       return VK_FORMAT_BC3_UNORM_BLOCK;
20350+        case SG_PIXELFORMAT_BC3_SRGBA:      return VK_FORMAT_BC3_SRGB_BLOCK;
20351+        case SG_PIXELFORMAT_BC4_R:          return VK_FORMAT_BC4_UNORM_BLOCK;
20352+        case SG_PIXELFORMAT_BC4_RSN:        return VK_FORMAT_BC4_SNORM_BLOCK;
20353+        case SG_PIXELFORMAT_BC5_RG:         return VK_FORMAT_BC5_UNORM_BLOCK;
20354+        case SG_PIXELFORMAT_BC5_RGSN:       return VK_FORMAT_BC5_SNORM_BLOCK;
20355+        case SG_PIXELFORMAT_BC6H_RGBF:      return VK_FORMAT_BC6H_SFLOAT_BLOCK;
20356+        case SG_PIXELFORMAT_BC6H_RGBUF:     return VK_FORMAT_BC6H_UFLOAT_BLOCK;
20357+        case SG_PIXELFORMAT_BC7_RGBA:       return VK_FORMAT_BC7_UNORM_BLOCK;
20358+        case SG_PIXELFORMAT_BC7_SRGBA:      return VK_FORMAT_BC7_SRGB_BLOCK;
20359+        case SG_PIXELFORMAT_ETC2_RGB8:      return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
20360+        case SG_PIXELFORMAT_ETC2_RGB8A1:    return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
20361+        case SG_PIXELFORMAT_ETC2_RGBA8:     return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
20362+        case SG_PIXELFORMAT_ETC2_SRGB8:     return VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK;
20363+        case SG_PIXELFORMAT_ETC2_SRGB8A8:   return VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK;
20364+        case SG_PIXELFORMAT_EAC_R11:        return VK_FORMAT_EAC_R11_UNORM_BLOCK;
20365+        case SG_PIXELFORMAT_EAC_R11SN:      return VK_FORMAT_EAC_R11_SNORM_BLOCK;
20366+        case SG_PIXELFORMAT_EAC_RG11:       return VK_FORMAT_EAC_R11G11_UNORM_BLOCK;
20367+        case SG_PIXELFORMAT_EAC_RG11SN:     return VK_FORMAT_EAC_R11G11_SNORM_BLOCK;
20368+        case SG_PIXELFORMAT_ASTC_4x4_RGBA:  return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
20369+        case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return VK_FORMAT_ASTC_4x4_SRGB_BLOCK;
20370+        default:                            return VK_FORMAT_UNDEFINED;
20371+    };
20372+}
20373+
20374+_SOKOL_PRIVATE VkPrimitiveTopology _sg_vk_primitive_topology(sg_primitive_type t) {
20375+    switch (t) {
20376+        case SG_PRIMITIVETYPE_POINTS:           return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
20377+        case SG_PRIMITIVETYPE_LINES:            return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
20378+        case SG_PRIMITIVETYPE_LINE_STRIP:       return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
20379+        case SG_PRIMITIVETYPE_TRIANGLES:        return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
20380+        case SG_PRIMITIVETYPE_TRIANGLE_STRIP:   return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
20381+        default:
20382+            SOKOL_UNREACHABLE;
20383+            return VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
20384+    }
20385+}
20386+
20387+_SOKOL_PRIVATE VkCullModeFlags _sg_vk_cullmode(sg_cull_mode cm) {
20388+    switch (cm) {
20389+        case SG_CULLMODE_NONE:      return VK_CULL_MODE_NONE;
20390+        case SG_CULLMODE_FRONT:     return VK_CULL_MODE_FRONT_BIT;
20391+        case SG_CULLMODE_BACK:      return VK_CULL_MODE_BACK_BIT;
20392+        default:
20393+            SOKOL_UNREACHABLE;
20394+            return VK_CULL_MODE_NONE;
20395+    }
20396+}
20397+
20398+_SOKOL_PRIVATE VkFrontFace _sg_vk_frontface(sg_face_winding fw) {
20399+    return (fw == SG_FACEWINDING_CCW) ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE;
20400+}
20401+
20402+_SOKOL_PRIVATE VkCompareOp _sg_vk_compare_op(sg_compare_func f) {
20403+    switch (f) {
20404+        case SG_COMPAREFUNC_NEVER:          return VK_COMPARE_OP_NEVER;
20405+        case SG_COMPAREFUNC_LESS:           return VK_COMPARE_OP_LESS;
20406+        case SG_COMPAREFUNC_EQUAL:          return VK_COMPARE_OP_EQUAL;
20407+        case SG_COMPAREFUNC_LESS_EQUAL:     return VK_COMPARE_OP_LESS_OR_EQUAL;
20408+        case SG_COMPAREFUNC_GREATER:        return VK_COMPARE_OP_GREATER;
20409+        case SG_COMPAREFUNC_NOT_EQUAL:      return VK_COMPARE_OP_NOT_EQUAL;
20410+        case SG_COMPAREFUNC_GREATER_EQUAL:  return VK_COMPARE_OP_GREATER_OR_EQUAL;
20411+        case SG_COMPAREFUNC_ALWAYS:         return VK_COMPARE_OP_ALWAYS;
20412+        default:
20413+            SOKOL_UNREACHABLE;
20414+            return VK_COMPARE_OP_ALWAYS;
20415+    }
20416+}
20417+
20418+_SOKOL_PRIVATE VkStencilOp _sg_vk_stencil_op(sg_stencil_op op) {
20419+    switch (op) {
20420+        case SG_STENCILOP_KEEP:         return VK_STENCIL_OP_KEEP;
20421+        case SG_STENCILOP_ZERO:         return VK_STENCIL_OP_ZERO;
20422+        case SG_STENCILOP_REPLACE:      return VK_STENCIL_OP_REPLACE;
20423+        case SG_STENCILOP_INCR_CLAMP:   return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
20424+        case SG_STENCILOP_DECR_CLAMP:   return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
20425+        case SG_STENCILOP_INVERT:       return VK_STENCIL_OP_INVERT;
20426+        case SG_STENCILOP_INCR_WRAP:    return VK_STENCIL_OP_INCREMENT_AND_WRAP;
20427+        case SG_STENCILOP_DECR_WRAP:    return VK_STENCIL_OP_DECREMENT_AND_WRAP;
20428+        default:
20429+            SOKOL_UNREACHABLE;
20430+            return VK_STENCIL_OP_KEEP;
20431+    }
20432+}
20433+
20434+_SOKOL_PRIVATE VkBlendOp _sg_vk_blend_op(sg_blend_op op) {
20435+    switch (op) {
20436+        case SG_BLENDOP_ADD:                return VK_BLEND_OP_ADD;
20437+        case SG_BLENDOP_SUBTRACT:           return VK_BLEND_OP_SUBTRACT;
20438+        case SG_BLENDOP_REVERSE_SUBTRACT:   return VK_BLEND_OP_REVERSE_SUBTRACT;
20439+        case SG_BLENDOP_MIN:                return VK_BLEND_OP_MIN;
20440+        case SG_BLENDOP_MAX:                return VK_BLEND_OP_MAX;
20441+        default:
20442+            SOKOL_UNREACHABLE;
20443+            return VK_BLEND_OP_ADD;
20444+    }
20445+}
20446+
20447+_SOKOL_PRIVATE VkBlendFactor _sg_vk_blend_factor(sg_blend_factor f) {
20448+    switch (f) {
20449+        case SG_BLENDFACTOR_ZERO:                   return VK_BLEND_FACTOR_ZERO;
20450+        case SG_BLENDFACTOR_ONE:                    return VK_BLEND_FACTOR_ONE;
20451+        case SG_BLENDFACTOR_SRC_COLOR:              return VK_BLEND_FACTOR_SRC_COLOR;
20452+        case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR:    return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
20453+        case SG_BLENDFACTOR_SRC_ALPHA:              return VK_BLEND_FACTOR_SRC_ALPHA;
20454+        case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:    return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
20455+        case SG_BLENDFACTOR_DST_COLOR:              return VK_BLEND_FACTOR_DST_COLOR;
20456+        case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR:    return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
20457+        case SG_BLENDFACTOR_DST_ALPHA:              return VK_BLEND_FACTOR_DST_ALPHA;
20458+        case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA:    return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
20459+        case SG_BLENDFACTOR_SRC_ALPHA_SATURATED:    return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
20460+        case SG_BLENDFACTOR_BLEND_COLOR:            return VK_BLEND_FACTOR_CONSTANT_COLOR;
20461+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR:  return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
20462+        case SG_BLENDFACTOR_BLEND_ALPHA:            return VK_BLEND_FACTOR_CONSTANT_ALPHA;
20463+        case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA:  return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
20464+        case SG_BLENDFACTOR_SRC1_COLOR:             return VK_BLEND_FACTOR_SRC1_COLOR ;
20465+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_COLOR:   return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
20466+        case SG_BLENDFACTOR_SRC1_ALPHA:             return VK_BLEND_FACTOR_SRC1_ALPHA;
20467+        case SG_BLENDFACTOR_ONE_MINUS_SRC1_ALPHA:   return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
20468+        default:
20469+            SOKOL_UNREACHABLE;
20470+            return VK_BLEND_FACTOR_ONE;
20471+    }
20472+}
20473+
20474+_SOKOL_PRIVATE VkColorComponentFlags _sg_vk_color_write_mask(sg_color_mask m) {
20475+    int res = 0;
20476+    if (0 != (m & SG_COLORMASK_R)) {
20477+        res |= (int)VK_COLOR_COMPONENT_R_BIT;
20478+    }
20479+    if (0 != (m & SG_COLORMASK_G)) {
20480+        res |= (int)VK_COLOR_COMPONENT_G_BIT;
20481+    }
20482+    if (0 != (m & SG_COLORMASK_B)) {
20483+        res |= (int)VK_COLOR_COMPONENT_B_BIT;
20484+    }
20485+    if (0 != (m & SG_COLORMASK_A)) {
20486+        res |= (int)VK_COLOR_COMPONENT_A_BIT;
20487+    }
20488+    return (VkColorComponentFlags)res;
20489+}
20490+
20491+_SOKOL_PRIVATE VkShaderStageFlags _sg_vk_shader_stage(sg_shader_stage s) {
20492+    switch (s) {
20493+        case SG_SHADERSTAGE_VERTEX: return VK_SHADER_STAGE_VERTEX_BIT;
20494+        case SG_SHADERSTAGE_FRAGMENT: return VK_SHADER_STAGE_FRAGMENT_BIT;
20495+        case SG_SHADERSTAGE_COMPUTE: return VK_SHADER_STAGE_COMPUTE_BIT;
20496+        default: SOKOL_UNREACHABLE; return 0;
20497+    }
20498+}
20499+
20500+_SOKOL_PRIVATE VkAttachmentLoadOp _sg_vk_load_op(sg_load_action a) {
20501+    switch (a) {
20502+        case SG_LOADACTION_CLEAR:
20503+            return VK_ATTACHMENT_LOAD_OP_CLEAR;
20504+        case SG_LOADACTION_DONTCARE:
20505+            return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
20506+        default:
20507+            return VK_ATTACHMENT_LOAD_OP_LOAD;
20508+    }
20509+}
20510+
20511+_SOKOL_PRIVATE VkAttachmentStoreOp _sg_vk_store_op(sg_store_action a) {
20512+    switch (a) {
20513+        case SG_STOREACTION_STORE:
20514+            return VK_ATTACHMENT_STORE_OP_STORE;
20515+        default:
20516+            return VK_ATTACHMENT_STORE_OP_DONT_CARE;
20517+    }
20518+}
20519+
20520+_SOKOL_PRIVATE VkIndexType _sg_vk_index_type(sg_index_type t) {
20521+    return (t == SG_INDEXTYPE_UINT16) ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32;
20522+}
20523+
20524+_SOKOL_PRIVATE VkImageViewType _sg_vk_texture_image_view_type(sg_image_type t) {
20525+    switch (t) {
20526+        case SG_IMAGETYPE_2D: return VK_IMAGE_VIEW_TYPE_2D;
20527+        case SG_IMAGETYPE_CUBE: return VK_IMAGE_VIEW_TYPE_CUBE;
20528+        case SG_IMAGETYPE_3D: return VK_IMAGE_VIEW_TYPE_3D;
20529+        case SG_IMAGETYPE_ARRAY: return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
20530+        default: SOKOL_UNREACHABLE; return VK_IMAGE_VIEW_TYPE_2D;
20531+    }
20532+}
20533+
20534+_SOKOL_PRIVATE VkImageViewType _sg_vk_attachment_image_view_type(sg_image_type t) {
20535+    switch (t) {
20536+        case SG_IMAGETYPE_2D: return VK_IMAGE_VIEW_TYPE_2D;
20537+        case SG_IMAGETYPE_CUBE: return VK_IMAGE_VIEW_TYPE_2D_ARRAY; // not a bug
20538+        case SG_IMAGETYPE_3D: return VK_IMAGE_VIEW_TYPE_2D; // not a bug
20539+        case SG_IMAGETYPE_ARRAY: return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
20540+        default: SOKOL_UNREACHABLE; return VK_IMAGE_VIEW_TYPE_2D;
20541+    }
20542+}
20543+
20544+_SOKOL_PRIVATE VkFilter _sg_vk_sampler_minmag_filter(sg_filter f) {
20545+    switch (f) {
20546+        case SG_FILTER_NEAREST: return VK_FILTER_NEAREST;
20547+        case SG_FILTER_LINEAR: return VK_FILTER_LINEAR;
20548+        default: SOKOL_UNREACHABLE; return VK_FILTER_NEAREST;
20549+    }
20550+}
20551+
20552+_SOKOL_PRIVATE VkSamplerMipmapMode _sg_vk_sampler_mipmap_mode(sg_filter f) {
20553+    switch (f) {
20554+        case SG_FILTER_NEAREST: return VK_SAMPLER_MIPMAP_MODE_NEAREST;
20555+        case SG_FILTER_LINEAR: return VK_SAMPLER_MIPMAP_MODE_LINEAR;
20556+        default: SOKOL_UNREACHABLE; return VK_SAMPLER_MIPMAP_MODE_NEAREST;
20557+    }
20558+}
20559+
20560+_SOKOL_PRIVATE VkSamplerAddressMode _sg_vk_sampler_address_mode(sg_wrap w) {
20561+    switch (w) {
20562+        case SG_WRAP_REPEAT: return VK_SAMPLER_ADDRESS_MODE_REPEAT;
20563+        case SG_WRAP_CLAMP_TO_EDGE: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
20564+        case SG_WRAP_CLAMP_TO_BORDER: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
20565+        case SG_WRAP_MIRRORED_REPEAT: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
20566+        default: SOKOL_UNREACHABLE; return VK_SAMPLER_ADDRESS_MODE_REPEAT;
20567+    }
20568+}
20569+
20570+_SOKOL_PRIVATE VkBorderColor _sg_vk_sampler_border_color(sg_border_color c) {
20571+    switch (c) {
20572+        case SG_BORDERCOLOR_TRANSPARENT_BLACK: return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
20573+        case SG_BORDERCOLOR_OPAQUE_BLACK: return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
20574+        case SG_BORDERCOLOR_OPAQUE_WHITE: return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
20575+        default: SOKOL_UNREACHABLE; return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
20576+    }
20577+}
20578+
20579+_SOKOL_PRIVATE void _sg_vk_load_ext_funcs(void) {
20580+    SOKOL_ASSERT(_sg.vk.dev);
20581+    #if defined(SOKOL_DEBUG)
20582+        _sg.vk.ext.set_debug_utils_object_name_ext = (PFN_vkSetDebugUtilsObjectNameEXT)vkGetInstanceProcAddr(_sg.vk.instance, "vkSetDebugUtilsObjectNameEXT");
20583+        if (0 == _sg.vk.ext.set_debug_utils_object_name_ext) {
20584+            _SG_PANIC(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING);
20585+        }
20586+    #endif
20587+    _sg.vk.ext.get_descriptor_set_layout_size = (PFN_vkGetDescriptorSetLayoutSizeEXT)vkGetDeviceProcAddr(_sg.vk.dev, "vkGetDescriptorSetLayoutSizeEXT");
20588+    if (0 == _sg.vk.ext.get_descriptor_set_layout_size) {
20589+        _SG_PANIC(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING);
20590+    }
20591+    _sg.vk.ext.get_descriptor_set_layout_binding_offset = (PFN_vkGetDescriptorSetLayoutBindingOffsetEXT)vkGetDeviceProcAddr(_sg.vk.dev, "vkGetDescriptorSetLayoutBindingOffsetEXT");
20592+    if (0 == _sg.vk.ext.get_descriptor_set_layout_binding_offset) {
20593+        _SG_PANIC(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING);
20594+    }
20595+    _sg.vk.ext.get_descriptor = (PFN_vkGetDescriptorEXT)vkGetDeviceProcAddr(_sg.vk.dev, "vkGetDescriptorEXT");
20596+    if (0 == _sg.vk.ext.get_descriptor) {
20597+        _SG_PANIC(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING);
20598+    }
20599+    _sg.vk.ext.cmd_bind_descriptor_buffers = (PFN_vkCmdBindDescriptorBuffersEXT)vkGetDeviceProcAddr(_sg.vk.dev, "vkCmdBindDescriptorBuffersEXT");
20600+    if (0 == _sg.vk.ext.cmd_bind_descriptor_buffers) {
20601+        _SG_PANIC(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING);
20602+    }
20603+    _sg.vk.ext.cmd_set_descriptor_buffer_offsets = (PFN_vkCmdSetDescriptorBufferOffsetsEXT)vkGetDeviceProcAddr(_sg.vk.dev, "vkCmdSetDescriptorBufferOffsetsEXT");
20604+    if (0 == _sg.vk.ext.cmd_set_descriptor_buffer_offsets) {
20605+        _SG_PANIC(VULKAN_REQUIRED_EXTENSION_FUNCTION_MISSING);
20606+    }
20607+}
20608+
20609+_SOKOL_PRIVATE void _sg_vk_init_caps(void) {
20610+    _sg.backend = SG_BACKEND_VULKAN;
20611+    _sg.features.origin_top_left = true;
20612+    _sg.features.image_clamp_to_border = false; // FIXME?
20613+    _sg.features.mrt_independent_blend_state = true;
20614+    _sg.features.mrt_independent_write_mask = true;
20615+    _sg.features.compute = true;
20616+    _sg.features.msaa_texture_bindings = true;
20617+    _sg.features.draw_base_vertex = true;
20618+    _sg.features.draw_base_instance = true;
20619+    _sg.features.dual_source_blending = true;
20620+    _sg.features.vertexformat_int10_n2 = true;
20621+
20622+    SOKOL_ASSERT(_sg.vk.phys_dev);
20623+    _sg.vk.descriptor_buffer_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
20624+    _sg.vk.dev_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
20625+    _sg.vk.dev_props.pNext = &_sg.vk.descriptor_buffer_props;
20626+    vkGetPhysicalDeviceProperties2(_sg.vk.phys_dev, &_sg.vk.dev_props);
20627+    _sg.vk.dev_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
20628+    vkGetPhysicalDeviceFeatures2(_sg.vk.phys_dev, &_sg.vk.dev_features);
20629+
20630+    const VkPhysicalDeviceLimits* l = &_sg.vk.dev_props.properties.limits;
20631+    _sg.limits.max_image_size_2d = (int)l->maxImageDimension2D;
20632+    _sg.limits.max_image_size_cube = (int)l->maxImageDimensionCube;
20633+    _sg.limits.max_image_size_3d = (int)l->maxImageDimension3D;
20634+    _sg.limits.max_image_size_array = _sg.limits.max_image_size_2d;
20635+    _sg.limits.max_image_array_layers = (int)l->maxImageArrayLayers;
20636+    _sg.limits.max_vertex_attrs = _sg_min((int)l->maxVertexInputAttributes, SG_MAX_VERTEX_ATTRIBUTES);
20637+    _sg.limits.max_color_attachments = _sg_min((int)l->maxFragmentOutputAttachments, SG_MAX_COLOR_ATTACHMENTS);
20638+    _sg.limits.max_texture_bindings_per_stage = _sg_min((int)l->maxPerStageDescriptorSampledImages, SG_MAX_VIEW_BINDSLOTS);
20639+    _sg.limits.max_storage_buffer_bindings_per_stage = _sg_min((int)l->maxPerStageDescriptorStorageBuffers, SG_MAX_VIEW_BINDSLOTS);
20640+    _sg.limits.max_storage_image_bindings_per_stage = _sg_min((int)l->maxPerStageDescriptorStorageImages, SG_MAX_VIEW_BINDSLOTS);
20641+    _sg.limits.vk_min_uniform_buffer_offset_alignment = (int)l->minUniformBufferOffsetAlignment;
20642+
20643+    _SG_STRUCT(VkPhysicalDeviceImageFormatInfo2, fmt_info);
20644+    fmt_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
20645+    fmt_info.type = VK_IMAGE_TYPE_2D;
20646+    fmt_info.tiling = VK_IMAGE_TILING_OPTIMAL;
20647+    _SG_STRUCT(VkImageFormatProperties2, props2);
20648+    props2.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
20649+    for (int fmt = (SG_PIXELFORMAT_NONE+1); fmt < _SG_PIXELFORMAT_NUM; fmt++) {
20650+        _SG_STRUCT(VkFormatProperties, props);
20651+        VkFormat vkfmt = _sg_vk_format((sg_pixel_format)fmt);
20652+        vkGetPhysicalDeviceFormatProperties(_sg.vk.phys_dev, vkfmt, &props);
20653+        const VkFormatFeatureFlags f = props.optimalTilingFeatures;
20654+        _sg_pixelformat_info_t* info = &_sg.formats[fmt];
20655+        info->sample = 0 != (f & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
20656+        info->filter = 0 != (f & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
20657+        info->render = 0 != (f & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT);
20658+        info->blend = 0 != (f & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT);
20659+        info->depth = 0 != (f & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
20660+        info->read = info->write = 0 != (f & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
20661+        if (info->depth) {
20662+            info->render = true;
20663+        }
20664+        if (info->render || info->depth) {
20665+            // query msaa support
20666+            fmt_info.format = vkfmt;
20667+            fmt_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
20668+            if (info->depth) {
20669+                fmt_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
20670+            } else {
20671+                fmt_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
20672+            }
20673+            VkResult res = vkGetPhysicalDeviceImageFormatProperties2(_sg.vk.phys_dev, &fmt_info, &props2);
20674+            if (res == VK_SUCCESS) {
20675+                info->msaa = props2.imageFormatProperties.sampleCounts > VK_SAMPLE_COUNT_1_BIT;
20676+            }
20677+        }
20678+    }
20679+}
20680+
20681+_SOKOL_PRIVATE void _sg_vk_create_fences(void) {
20682+    SOKOL_ASSERT(_sg.vk.dev);
20683+    _SG_STRUCT(VkFenceCreateInfo, create_info);
20684+    create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
20685+    create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
20686+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
20687+        SOKOL_ASSERT(0 == _sg.vk.frame.slot[i].fence);
20688+        VkResult res = vkCreateFence(_sg.vk.dev, &create_info, 0, &_sg.vk.frame.slot[i].fence);
20689+        SOKOL_ASSERT((res == VK_SUCCESS) && _sg.vk.frame.slot[i].fence); _SOKOL_UNUSED(res);
20690+    }
20691+}
20692+
20693+_SOKOL_PRIVATE void _sg_vk_destroy_fences(void) {
20694+    SOKOL_ASSERT(_sg.vk.dev);
20695+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
20696+        SOKOL_ASSERT(_sg.vk.frame.slot[i].fence);
20697+        vkDestroyFence(_sg.vk.dev, _sg.vk.frame.slot[i].fence, 0);
20698+        _sg.vk.frame.slot[i].fence = 0;
20699+    }
20700+}
20701+
20702+_SOKOL_PRIVATE void _sg_vk_create_frame_command_pool_and_buffers(void) {
20703+    SOKOL_ASSERT(_sg.vk.dev);
20704+    SOKOL_ASSERT(0 == _sg.vk.frame.cmd_pool);
20705+    _SG_STRUCT(VkCommandPoolCreateInfo, pool_create_info);
20706+    pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
20707+    // FIXME: transient bit when the cmd buffers are reset each frame?
20708+    pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
20709+    pool_create_info.queueFamilyIndex = _sg.vk.queue_family_index;
20710+    VkResult res = vkCreateCommandPool(_sg.vk.dev, &pool_create_info, 0, &_sg.vk.frame.cmd_pool);
20711+    SOKOL_ASSERT((res == VK_SUCCESS) && _sg.vk.frame.cmd_pool); _SOKOL_UNUSED(res);
20712+
20713+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
20714+        _SG_STRUCT(VkCommandBufferAllocateInfo, cmdbuf_alloc_info);
20715+        cmdbuf_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
20716+        cmdbuf_alloc_info.commandPool = _sg.vk.frame.cmd_pool;
20717+        cmdbuf_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
20718+        cmdbuf_alloc_info.commandBufferCount = 1;
20719+        res = vkAllocateCommandBuffers(_sg.vk.dev, &cmdbuf_alloc_info, &_sg.vk.frame.slot[i].command_buffer);
20720+        SOKOL_ASSERT((res == VK_SUCCESS) && _sg.vk.frame.slot[i].command_buffer);
20721+        res = vkAllocateCommandBuffers(_sg.vk.dev, &cmdbuf_alloc_info, &_sg.vk.frame.slot[i].stream_command_buffer);
20722+        SOKOL_ASSERT((res == VK_SUCCESS) && _sg.vk.frame.slot[i].stream_command_buffer);
20723+    }
20724+}
20725+
20726+_SOKOL_PRIVATE void _sg_vk_destroy_frame_command_pool(void) {
20727+    SOKOL_ASSERT(_sg.vk.dev);
20728+    SOKOL_ASSERT(_sg.vk.frame.cmd_pool);
20729+    SOKOL_ASSERT(0 == _sg.vk.frame.cmd_buf);
20730+    SOKOL_ASSERT(0 == _sg.vk.frame.stream_cmd_buf);
20731+    // NOTE: command buffers owned by the pool will be automatically destroyed
20732+    vkDestroyCommandPool(_sg.vk.dev, _sg.vk.frame.cmd_pool, 0);
20733+    _sg.vk.frame.cmd_pool = 0;
20734+    for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
20735+        SOKOL_ASSERT(_sg.vk.frame.slot[i].command_buffer);
20736+        _sg.vk.frame.slot[i].command_buffer = 0;
20737+        _sg.vk.frame.slot[i].stream_command_buffer = 0;
20738+    }
20739+}
20740+
20741+_SOKOL_PRIVATE void _sg_vk_acquire_frame_command_buffers(void) {
20742+    SOKOL_ASSERT(_sg.vk.dev);
20743+    VkResult res;
20744+    if (0 == _sg.vk.frame.cmd_buf) {
20745+        SOKOL_ASSERT(0 == _sg.vk.frame.stream_cmd_buf);
20746+        _sg.vk.frame_slot = (_sg.vk.frame_slot + 1) % SG_NUM_INFLIGHT_FRAMES;
20747+        // block until oldest inflight-frame has finished
20748+        do {
20749+            res = vkWaitForFences(_sg.vk.dev,
20750+                1,
20751+                &_sg.vk.frame.slot[_sg.vk.frame_slot].fence,
20752+                VK_TRUE,
20753+                UINT64_MAX);
20754+        } while (res == VK_TIMEOUT);
20755+        if (res != VK_SUCCESS) {
20756+            _SG_WARN(VULKAN_WAIT_FOR_FENCE_FAILED);
20757+            _sg.cur_pass.valid = false;
20758+            return;
20759+        }
20760+        res = vkResetFences(_sg.vk.dev, 1, &_sg.vk.frame.slot[_sg.vk.frame_slot].fence);
20761+        SOKOL_ASSERT(res == VK_SUCCESS); _SOKOL_UNUSED(res);
20762+
20763+        _sg_vk_delete_queue_collect();
20764+
20765+        _sg.vk.frame.cmd_buf = _sg.vk.frame.slot[_sg.vk.frame_slot].command_buffer;
20766+        res = vkResetCommandBuffer(_sg.vk.frame.cmd_buf, 0);
20767+        SOKOL_ASSERT(res == VK_SUCCESS);
20768+        _sg.vk.frame.stream_cmd_buf = _sg.vk.frame.slot[_sg.vk.frame_slot].stream_command_buffer;
20769+        res = vkResetCommandBuffer(_sg.vk.frame.stream_cmd_buf, 0);
20770+        SOKOL_ASSERT(res == VK_SUCCESS);
20771+
20772+        _SG_STRUCT(VkCommandBufferBeginInfo, cmdbuf_begin_info);
20773+        cmdbuf_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
20774+        cmdbuf_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
20775+        res = vkBeginCommandBuffer(_sg.vk.frame.cmd_buf, &cmdbuf_begin_info);
20776+        SOKOL_ASSERT(res == VK_SUCCESS);
20777+        res = vkBeginCommandBuffer(_sg.vk.frame.stream_cmd_buf, &cmdbuf_begin_info);
20778+        SOKOL_ASSERT(res == VK_SUCCESS);
20779+
20780+        _sg_vk_uniform_after_acquire();
20781+        _sg_vk_bind_after_acquire();
20782+        _sg_vk_staging_stream_after_acquire();
20783+    }
20784+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
20785+}
20786+
20787+_SOKOL_PRIVATE void _sg_vk_submit_frame_command_buffers(void) {
20788+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
20789+    SOKOL_ASSERT(_sg.vk.frame.stream_cmd_buf);
20790+    VkResult res;
20791+    _SOKOL_UNUSED(res);
20792+
20793+    _sg_vk_staging_stream_before_submit();
20794+    _sg_vk_bind_before_submit();
20795+    _sg_vk_uniform_before_submit();
20796+
20797+    res = vkEndCommandBuffer(_sg.vk.frame.stream_cmd_buf);
20798+    SOKOL_ASSERT(res == VK_SUCCESS);
20799+    res = vkEndCommandBuffer(_sg.vk.frame.cmd_buf);
20800+    SOKOL_ASSERT(res == VK_SUCCESS);
20801+
20802+    _SG_STRUCT(VkSubmitInfo, submit_infos[2]);
20803+    // streaming-update command buffer
20804+    submit_infos[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
20805+    submit_infos[0].commandBufferCount = 1;
20806+    submit_infos[0].pCommandBuffers = &_sg.vk.frame.stream_cmd_buf;
20807+    // render command buffer
20808+    const VkPipelineStageFlags present_wait_dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
20809+    submit_infos[1].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
20810+    // NOTE: the render_finished_sem and present_complete_sem are only
20811+    // provided in swapchain passes, but rendering without a swapchain
20812+    // pass should be a valid scenario (for instance for 'headless rendering')
20813+    //
20814+    // see: https://github.com/floooh/sokol/issues/1459
20815+    if (_sg.vk.present_complete_sem && _sg.vk.render_finished_sem) {
20816+        submit_infos[1].waitSemaphoreCount = 1;
20817+        submit_infos[1].pWaitSemaphores = &_sg.vk.present_complete_sem;
20818+        submit_infos[1].pWaitDstStageMask = &present_wait_dst_stage_mask;
20819+        submit_infos[1].signalSemaphoreCount = 1;
20820+        submit_infos[1].pSignalSemaphores = &_sg.vk.render_finished_sem;
20821+    }
20822+    submit_infos[1].commandBufferCount = 1;
20823+    submit_infos[1].pCommandBuffers = &_sg.vk.frame.cmd_buf;
20824+    res = vkQueueSubmit(_sg.vk.queue, 2, submit_infos, _sg.vk.frame.slot[_sg.vk.frame_slot].fence);
20825+    SOKOL_ASSERT(res == VK_SUCCESS);
20826+
20827+    _sg.vk.frame.cmd_buf = 0;
20828+    _sg.vk.frame.stream_cmd_buf = 0;
20829+
20830+    // NOTE: it's valid to register resource objects for destruction in the
20831+    // delete queue past this point (between _sg_vk_submit_frame_command_buffer()
20832+    // and the next _sg_vk_acquire_frame_command_buffer()) since resources which are
20833+    // destroyed in this 'gap' can at most have been used by the command
20834+    // buffer that was just submitted
20835+}
20836+
20837+_SOKOL_PRIVATE void _sg_vk_setup_backend(const sg_desc* desc) {
20838+    SOKOL_ASSERT(desc);
20839+    SOKOL_ASSERT(desc->environment.vulkan.instance);
20840+    SOKOL_ASSERT(desc->environment.vulkan.physical_device);
20841+    SOKOL_ASSERT(desc->environment.vulkan.device);
20842+    SOKOL_ASSERT(desc->environment.vulkan.queue);
20843+    SOKOL_ASSERT(desc->uniform_buffer_size > 0);
20844+    _sg.vk.valid = true;
20845+    _sg.vk.instance = (VkInstance) desc->environment.vulkan.instance;
20846+    _sg.vk.phys_dev = (VkPhysicalDevice) desc->environment.vulkan.physical_device;
20847+    _sg.vk.dev = (VkDevice) desc->environment.vulkan.device;
20848+    _sg.vk.queue = (VkQueue) desc->environment.vulkan.queue;
20849+    _sg.vk.queue_family_index = desc->environment.vulkan.queue_family_index;
20850+
20851+    _sg_track_init(&_sg.vk.track.buffers, _sg.pools.buffer_pool.size);
20852+    _sg_track_init(&_sg.vk.track.images, _sg.pools.image_pool.size);
20853+    _sg_vk_load_ext_funcs();
20854+    _sg_vk_init_caps();
20855+    _sg_vk_create_fences();
20856+    _sg_vk_create_frame_command_pool_and_buffers();
20857+    _sg_vk_staging_copy_init();
20858+    _sg_vk_staging_stream_init();
20859+    _sg_vk_uniform_init();
20860+    _sg_vk_bind_init();
20861+    _sg_vk_create_delete_queues();
20862+}
20863+
20864+_SOKOL_PRIVATE void _sg_vk_discard_backend(void) {
20865+    SOKOL_ASSERT(_sg.vk.valid);
20866+    SOKOL_ASSERT(_sg.vk.dev);
20867+    vkDeviceWaitIdle(_sg.vk.dev);
20868+    _sg_vk_destroy_delete_queues();
20869+    _sg_vk_bind_discard();
20870+    _sg_vk_uniform_discard();
20871+    _sg_vk_staging_stream_discard();
20872+    _sg_vk_staging_copy_discard();
20873+    _sg_vk_destroy_frame_command_pool();
20874+    _sg_vk_destroy_fences();
20875+    _sg_track_discard(&_sg.vk.track.images);
20876+    _sg_track_discard(&_sg.vk.track.buffers);
20877+    _sg.vk.valid = false;
20878+}
20879+
20880+_SOKOL_PRIVATE void _sg_vk_reset_state_cache(void) {
20881+    // nothing to do here
20882+}
20883+
20884+_SOKOL_PRIVATE sg_resource_state _sg_vk_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
20885+    SOKOL_ASSERT(_sg.vk.dev);
20886+    SOKOL_ASSERT(buf && desc);
20887+    SOKOL_ASSERT(buf->cmn.size > 0);
20888+    SOKOL_ASSERT(0 == buf->vk.buf);
20889+    SOKOL_ASSERT(0 == buf->vk.mem);
20890+    SOKOL_ASSERT(0 == buf->vk.dev_addr);
20891+    VkResult res;
20892+    // FIXME: inject external buffer
20893+
20894+    buf->vk.cur_access = _SG_VK_ACCESS_NONE;
20895+
20896+    _SG_STRUCT(VkBufferCreateInfo, create_info);
20897+    create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
20898+    create_info.size = (VkDeviceSize)buf->cmn.size;
20899+    create_info.usage = _sg_vk_buffer_usage(&buf->cmn.usage);
20900+    create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
20901+    res = vkCreateBuffer(_sg.vk.dev, &create_info, 0, &buf->vk.buf);
20902+    if (res != VK_SUCCESS) {
20903+        _SG_ERROR(VULKAN_CREATE_BUFFER_FAILED);
20904+        return SG_RESOURCESTATE_FAILED;
20905+    }
20906+    SOKOL_ASSERT(buf->vk.buf);
20907+    _sg_vk_set_object_label(VK_OBJECT_TYPE_BUFFER, (uint64_t)buf->vk.buf, desc->label);
20908+
20909+    if (!_sg_vk_mem_alloc_buffer_device_memory(buf)) {
20910+        return SG_RESOURCESTATE_FAILED;
20911+    }
20912+    SOKOL_ASSERT(buf->vk.mem);
20913+    res = vkBindBufferMemory(_sg.vk.dev, buf->vk.buf, buf->vk.mem, 0);
20914+    if (res != VK_SUCCESS) {
20915+        _SG_ERROR(VULKAN_BIND_BUFFER_MEMORY_FAILED);
20916+        return SG_RESOURCESTATE_FAILED;
20917+    }
20918+    if (buf->cmn.usage.storage_buffer) {
20919+        _SG_STRUCT(VkBufferDeviceAddressInfo, addr_info);
20920+        addr_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
20921+        addr_info.buffer = buf->vk.buf;
20922+        buf->vk.dev_addr = vkGetBufferDeviceAddress(_sg.vk.dev, &addr_info);
20923+        SOKOL_ASSERT(buf->vk.dev_addr);
20924+    }
20925+    if (buf->cmn.usage.immutable && desc->data.ptr) {
20926+        _sg_vk_staging_copy_buffer_data(buf, &desc->data, 0, false);
20927+    }
20928+    return SG_RESOURCESTATE_VALID;
20929+}
20930+
20931+_SOKOL_PRIVATE void _sg_vk_discard_buffer(_sg_buffer_t* buf) {
20932+    SOKOL_ASSERT(buf);
20933+    _sg_track_remove(&_sg.vk.track.buffers, buf->slot.id);
20934+    if (buf->vk.buf) {
20935+        _sg_vk_delete_queue_add(_sg_vk_buffer_destructor, (void*)buf->vk.buf);
20936+        buf->vk.buf = 0;
20937+    }
20938+    if (buf->vk.mem) {
20939+        _sg_vk_delete_queue_add(_sg_vk_memory_destructor, (void*)buf->vk.mem);
20940+        buf->vk.mem = 0;
20941+    }
20942+}
20943+
20944+_SOKOL_PRIVATE sg_resource_state _sg_vk_create_image(_sg_image_t* img, const sg_image_desc* desc) {
20945+    SOKOL_ASSERT(img && desc);
20946+    VkResult res;
20947+    // FIXME: injected images
20948+
20949+    img->vk.cur_access = _SG_VK_ACCESS_NONE;
20950+
20951+    _SG_STRUCT(VkImageCreateInfo, create_info);
20952+    create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
20953+    create_info.flags = _sg_vk_image_create_flags(img->cmn.type);
20954+    create_info.imageType = _sg_vk_image_type(img->cmn.type);
20955+    create_info.format = _sg_vk_format(desc->pixel_format);
20956+    create_info.extent.width = (uint32_t)img->cmn.width;
20957+    create_info.extent.height = (uint32_t)img->cmn.height;
20958+    if (desc->type == SG_IMAGETYPE_3D) {
20959+        create_info.extent.depth = (uint32_t)img->cmn.num_slices;
20960+        create_info.arrayLayers = 1;
20961+    } else {
20962+        create_info.extent.depth = 1;
20963+        create_info.arrayLayers = (uint32_t)img->cmn.num_slices;
20964+    }
20965+    create_info.mipLevels = (uint32_t)img->cmn.num_mipmaps;
20966+    create_info.samples = (VkSampleCountFlagBits)desc->sample_count;
20967+    create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
20968+    create_info.usage = _sg_vk_image_usage(&img->cmn.usage);
20969+    create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
20970+    create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
20971+    res = vkCreateImage(_sg.vk.dev, &create_info, 0, &img->vk.img);
20972+    if (res != VK_SUCCESS) {
20973+        _SG_ERROR(VULKAN_CREATE_IMAGE_FAILED);
20974+        return SG_RESOURCESTATE_FAILED;
20975+    }
20976+    SOKOL_ASSERT(img->vk.img);
20977+    _sg_vk_set_object_label(VK_OBJECT_TYPE_IMAGE, (uint64_t)img->vk.img, desc->label);
20978+
20979+    if (!_sg_vk_mem_alloc_image_device_memory(img)) {
20980+        return SG_RESOURCESTATE_FAILED;
20981+    }
20982+    SOKOL_ASSERT(img->vk.mem);
20983+    res = vkBindImageMemory(_sg.vk.dev, img->vk.img, img->vk.mem, 0);
20984+    if (res != VK_SUCCESS) {
20985+        _SG_ERROR(VULKAN_BIND_IMAGE_MEMORY_FAILED);
20986+        return SG_RESOURCESTATE_FAILED;
20987+    }
20988+    if (img->cmn.usage.immutable && desc->data.mip_levels[0].ptr) {
20989+        _sg_vk_staging_copy_image_data(img, &desc->data, false);
20990+    }
20991+    return SG_RESOURCESTATE_VALID;
20992+}
20993+
20994+_SOKOL_PRIVATE void _sg_vk_discard_image(_sg_image_t* img) {
20995+    SOKOL_ASSERT(img);
20996+    _sg_track_remove(&_sg.vk.track.images, img->slot.id);
20997+    if (img->vk.img) {
20998+        _sg_vk_delete_queue_add(_sg_vk_image_destructor, (void*)img->vk.img);
20999+        img->vk.img = 0;
21000+    }
21001+    if (img->vk.mem) {
21002+        _sg_vk_delete_queue_add(_sg_vk_memory_destructor, (void*)img->vk.mem);
21003+        img->vk.mem = 0;
21004+    }
21005+}
21006+
21007+_SOKOL_PRIVATE sg_resource_state _sg_vk_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
21008+    SOKOL_ASSERT(smp && desc);
21009+    SOKOL_ASSERT(_sg.vk.dev);
21010+    SOKOL_ASSERT(0 == smp->vk.smp);
21011+    // FIXME: injection
21012+
21013+    // create sampler object
21014+    _SG_STRUCT(VkSamplerCreateInfo, create_info);
21015+    create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
21016+    create_info.magFilter = _sg_vk_sampler_minmag_filter(desc->mag_filter);
21017+    create_info.minFilter = _sg_vk_sampler_minmag_filter(desc->min_filter);
21018+    create_info.mipmapMode = _sg_vk_sampler_mipmap_mode(desc->mipmap_filter);
21019+    create_info.addressModeU = _sg_vk_sampler_address_mode(desc->wrap_u);
21020+    create_info.addressModeV = _sg_vk_sampler_address_mode(desc->wrap_v);
21021+    create_info.addressModeW = _sg_vk_sampler_address_mode(desc->wrap_w);
21022+    create_info.mipLodBias = 0.0f;
21023+    if (desc->max_anisotropy > 1) {
21024+        create_info.anisotropyEnable = VK_TRUE;
21025+        create_info.maxAnisotropy = (float)desc->max_anisotropy;
21026+    }
21027+    if (desc->compare != SG_COMPAREFUNC_NEVER) {
21028+        create_info.compareEnable = VK_TRUE;
21029+        create_info.compareOp = _sg_vk_compare_op(desc->compare);
21030+    }
21031+    create_info.minLod = desc->min_lod;
21032+    create_info.maxLod = desc->max_lod;
21033+    create_info.borderColor = _sg_vk_sampler_border_color(desc->border_color);
21034+    VkResult res = vkCreateSampler(_sg.vk.dev, &create_info, 0, &smp->vk.smp);
21035+    if (res != VK_SUCCESS) {
21036+        _SG_ERROR(VULKAN_CREATE_SAMPLER_FAILED);
21037+        return SG_RESOURCESTATE_FAILED;
21038+    }
21039+    SOKOL_ASSERT(smp->vk.smp);
21040+    _sg_vk_set_object_label(VK_OBJECT_TYPE_SAMPLER, (uint64_t)smp->vk.smp, desc->label);
21041+
21042+    // record sampler descriptor data
21043+    smp->vk.descriptor_size = _sg.vk.descriptor_buffer_props.samplerDescriptorSize;
21044+    if (_SG_VK_MAX_DESCRIPTOR_DATA_SIZE < smp->vk.descriptor_size) {
21045+        _SG_ERROR(VULKAN_SAMPLER_MAX_DESCRIPTOR_SIZE);
21046+        return SG_RESOURCESTATE_FAILED;
21047+    }
21048+    _SG_STRUCT(VkDescriptorGetInfoEXT, get_info);
21049+    get_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT;
21050+    get_info.type = VK_DESCRIPTOR_TYPE_SAMPLER;
21051+    get_info.data.pSampler = &smp->vk.smp;
21052+    _sg.vk.ext.get_descriptor(_sg.vk.dev, &get_info, smp->vk.descriptor_size, &smp->vk.descriptor_data);
21053+
21054+    return SG_RESOURCESTATE_VALID;
21055+}
21056+
21057+_SOKOL_PRIVATE void _sg_vk_discard_sampler(_sg_sampler_t* smp) {
21058+    SOKOL_ASSERT(smp);
21059+    if (smp->vk.smp) {
21060+        _sg_vk_delete_queue_add(_sg_vk_sampler_destructor, (void*)smp->vk.smp);
21061+        smp->vk.smp = 0;
21062+    }
21063+}
21064+
21065+_SOKOL_PRIVATE _sg_vk_shader_func_t _sg_vk_create_shader_func(const sg_shader_function* func, const char* label) {
21066+    SOKOL_ASSERT(_sg.vk.dev);
21067+    SOKOL_ASSERT(func);
21068+    SOKOL_ASSERT(func->bytecode.ptr && (func->bytecode.size > 0));
21069+    SOKOL_ASSERT(func->entry);
21070+
21071+    _SG_STRUCT(_sg_vk_shader_func_t, vk_func);
21072+    _sg_strcpy(&vk_func.entry, func->entry);
21073+
21074+    _SG_STRUCT(VkShaderModuleCreateInfo, create_info);
21075+    create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
21076+    create_info.codeSize = func->bytecode.size;
21077+    create_info.pCode = (uint32_t*)func->bytecode.ptr;
21078+    VkResult res = vkCreateShaderModule(_sg.vk.dev, &create_info, 0, &vk_func.module);
21079+    if (VK_SUCCESS != res) {
21080+        _SG_ERROR(VULKAN_CREATE_SHADER_MODULE_FAILED);
21081+    } else {
21082+        SOKOL_ASSERT(vk_func.module);
21083+        _sg_vk_set_object_label(VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)vk_func.module, label);
21084+    }
21085+    return vk_func;
21086+}
21087+
21088+_SOKOL_PRIVATE void _sg_vk_discard_shader_func(_sg_vk_shader_func_t* func) {
21089+    SOKOL_ASSERT(_sg.vk.dev);
21090+    SOKOL_ASSERT(func);
21091+    if (func->module) {
21092+        _sg_vk_delete_queue_add(_sg_vk_shader_module_destructor, (void*)func->module);
21093+        func->module = 0;
21094+    }
21095+}
21096+
21097+_SOKOL_PRIVATE bool _sg_vk_ensure_spirv_bindslot_ranges(const sg_shader_desc* desc) {
21098+    SOKOL_ASSERT(desc);
21099+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
21100+        const sg_shader_uniform_block* ub = &desc->uniform_blocks[i];
21101+        if (ub->stage != SG_SHADERSTAGE_NONE) {
21102+            if (ub->spirv_set0_binding_n >= _SG_VK_MAX_UB_DESCRIPTORSET_SLOTS) {
21103+                _SG_ERROR(VULKAN_UNIFORMBLOCK_SPIRV_SET0_BINDING_OUT_OF_RANGE);
21104+                return false;
21105+            }
21106+        }
21107+    }
21108+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
21109+        const sg_shader_view* view = &desc->views[i];
21110+        if (view->texture.stage != SG_SHADERSTAGE_NONE) {
21111+            if (view->texture.spirv_set1_binding_n >= _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_SLOTS) {
21112+                _SG_ERROR(VULKAN_TEXTURE_SPIRV_SET1_BINDING_OUT_OF_RANGE);
21113+                return false;
21114+            }
21115+        }
21116+        if (view->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
21117+            if (view->storage_buffer.spirv_set1_binding_n >= _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_SLOTS) {
21118+                _SG_ERROR(VULKAN_STORAGEBUFFER_SPIRV_SET1_BINDING_OUT_OF_RANGE);
21119+                return false;
21120+            }
21121+        }
21122+        if (view->storage_image.stage != SG_SHADERSTAGE_NONE) {
21123+            if (view->storage_image.spirv_set1_binding_n >= _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_SLOTS) {
21124+                _SG_ERROR(VULKAN_STORAGEIMAGE_SPIRV_SET1_BINDING_OUT_OF_RANGE);
21125+                return false;
21126+            }
21127+        }
21128+    }
21129+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
21130+        const sg_shader_sampler* smp = &desc->samplers[i];
21131+        if (smp->stage != SG_SHADERSTAGE_NONE) {
21132+            if (smp->spirv_set1_binding_n >= _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_SLOTS) {
21133+                _SG_ERROR(VULKAN_SAMPLER_SPIRV_SET1_BINDING_OUT_OF_RANGE);
21134+                return false;
21135+            }
21136+        }
21137+    }
21138+    return true;
21139+}
21140+
21141+_SOKOL_PRIVATE sg_resource_state _sg_vk_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
21142+    SOKOL_ASSERT(shd && desc);
21143+    SOKOL_ASSERT(_sg.vk.dev);
21144+    SOKOL_ASSERT(shd->vk.vertex_func.module == 0);
21145+    SOKOL_ASSERT(shd->vk.fragment_func.module == 0);
21146+    SOKOL_ASSERT(shd->vk.compute_func.module == 0);
21147+    SOKOL_ASSERT(shd->vk.ub_dsl == 0);
21148+    SOKOL_ASSERT(shd->vk.view_smp_dsl == 0);
21149+
21150+    if (!_sg_vk_ensure_spirv_bindslot_ranges(desc)) {
21151+        return SG_RESOURCESTATE_FAILED;
21152+    }
21153+
21154+    // build shader modules
21155+    bool shd_valid = true;
21156+    if (desc->vertex_func.bytecode.ptr) {
21157+        shd->vk.vertex_func = _sg_vk_create_shader_func(&desc->vertex_func, desc->label);
21158+        shd_valid &= shd->vk.vertex_func.module != 0;
21159+    }
21160+    if (desc->fragment_func.bytecode.ptr) {
21161+        shd->vk.fragment_func = _sg_vk_create_shader_func(&desc->fragment_func, desc->label);
21162+        shd_valid &= shd->vk.fragment_func.module != 0;
21163+    }
21164+    if (desc->compute_func.bytecode.ptr) {
21165+        shd->vk.compute_func = _sg_vk_create_shader_func(&desc->compute_func, desc->label);
21166+        shd_valid &= shd->vk.compute_func.module != 0;
21167+    }
21168+    if (!shd_valid) {
21169+        _sg_vk_discard_shader_func(&shd->vk.vertex_func);
21170+        _sg_vk_discard_shader_func(&shd->vk.fragment_func);
21171+        _sg_vk_discard_shader_func(&shd->vk.compute_func);
21172+        return SG_RESOURCESTATE_FAILED;
21173+    }
21174+
21175+    // descriptor set layouts and pipeline layout
21176+    VkResult res;
21177+    _SG_STRUCT(VkDescriptorSetLayoutBinding, dsl_entries[_SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_ENTRIES]);
21178+    _SG_STRUCT(VkDescriptorSetLayoutCreateInfo, dsl_create_info);
21179+    uint32_t dsl_index = 0;
21180+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
21181+        if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) {
21182+            continue;
21183+        }
21184+        shd->vk.ub_set0_bnd_n[i] = desc->uniform_blocks[i].spirv_set0_binding_n;
21185+        VkDescriptorSetLayoutBinding* dsl_entry = &dsl_entries[dsl_index];
21186+        dsl_entry->binding = shd->vk.ub_set0_bnd_n[i];
21187+        dsl_entry->descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
21188+        dsl_entry->descriptorCount = 1;
21189+        dsl_entry->stageFlags = _sg_vk_shader_stage(shd->cmn.uniform_blocks[i].stage);
21190+        dsl_index += 1;
21191+    }
21192+    dsl_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
21193+    dsl_create_info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
21194+    dsl_create_info.bindingCount = dsl_index;
21195+    dsl_create_info.pBindings = dsl_entries;
21196+    res = vkCreateDescriptorSetLayout(_sg.vk.dev, &dsl_create_info, 0, &shd->vk.ub_dsl);
21197+    if (res != VK_SUCCESS) {
21198+        _SG_ERROR(VULKAN_CREATE_DESCRIPTOR_SET_LAYOUT_FAILED);
21199+        return SG_RESOURCESTATE_FAILED;
21200+    }
21201+
21202+    // store uniform descriptor set size and descriptor offsets
21203+    _sg.vk.ext.get_descriptor_set_layout_size(_sg.vk.dev, shd->vk.ub_dsl, &shd->vk.ub_dset_size);
21204+    if (shd->vk.ub_dset_size > _sg.vk.uniforms.dset_cache_size) {
21205+        _SG_ERROR(VULKAN_SHADER_UNIFORM_DESCRIPTOR_SET_SIZE_VS_CACHE_SIZE);
21206+        return SG_RESOURCESTATE_FAILED;
21207+    }
21208+    for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
21209+        if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) {
21210+            continue;
21211+        }
21212+        const uint8_t vk_bnd = shd->vk.ub_set0_bnd_n[i];
21213+        VkDeviceSize dset_offset = 0;
21214+        _sg.vk.ext.get_descriptor_set_layout_binding_offset(_sg.vk.dev, shd->vk.ub_dsl, vk_bnd, &dset_offset);
21215+        shd->vk.ub_dset_offsets[i] = (uint16_t)dset_offset;
21216+    }
21217+
21218+    _sg_clear(dsl_entries, sizeof(dsl_entries));
21219+    _sg_clear(&dsl_create_info, sizeof(dsl_create_info));
21220+    dsl_index = 0;
21221+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
21222+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
21223+            continue;
21224+        }
21225+        SOKOL_ASSERT(dsl_index < _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_ENTRIES);
21226+        VkDescriptorSetLayoutBinding* dsl_entry = &dsl_entries[dsl_index];
21227+        dsl_entry->stageFlags = _sg_vk_shader_stage(shd->cmn.views[i].stage);
21228+        if (shd->cmn.views[i].view_type == SG_VIEWTYPE_TEXTURE) {
21229+            shd->vk.view_set1_bnd_n[i] = desc->views[i].texture.spirv_set1_binding_n;
21230+            dsl_entry->descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
21231+        } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) {
21232+            shd->vk.view_set1_bnd_n[i] = desc->views[i].storage_buffer.spirv_set1_binding_n;
21233+            dsl_entry->descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
21234+        } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEIMAGE) {
21235+            shd->vk.view_set1_bnd_n[i] = desc->views[i].storage_image.spirv_set1_binding_n;
21236+            dsl_entry->descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
21237+        } else {
21238+            SOKOL_UNREACHABLE;
21239+        }
21240+        dsl_entry->binding = shd->vk.view_set1_bnd_n[i];
21241+        dsl_entry->descriptorCount = 1;
21242+        dsl_index += 1;
21243+    }
21244+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
21245+        if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) {
21246+            continue;
21247+        }
21248+        shd->vk.smp_set1_bnd_n[i] = desc->samplers[i].spirv_set1_binding_n;
21249+        SOKOL_ASSERT(dsl_index < _SG_VK_MAX_VIEW_SMP_DESCRIPTORSET_ENTRIES);
21250+        VkDescriptorSetLayoutBinding* dsl_entry = &dsl_entries[dsl_index];
21251+        dsl_entry->binding = shd->vk.smp_set1_bnd_n[i];
21252+        dsl_entry->descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
21253+        dsl_entry->descriptorCount = 1;
21254+        dsl_entry->stageFlags = _sg_vk_shader_stage(shd->cmn.samplers[i].stage);
21255+        dsl_index += 1;
21256+    }
21257+    dsl_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
21258+    dsl_create_info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
21259+    dsl_create_info.bindingCount = dsl_index;
21260+    dsl_create_info.pBindings = dsl_entries;
21261+    res = vkCreateDescriptorSetLayout(_sg.vk.dev, &dsl_create_info, 0, &shd->vk.view_smp_dsl);
21262+    if (res != VK_SUCCESS) {
21263+        _SG_ERROR(VULKAN_CREATE_DESCRIPTOR_SET_LAYOUT_FAILED);
21264+        return SG_RESOURCESTATE_FAILED;
21265+    }
21266+
21267+    // store view/smp descriptor set size and descriptor offsets
21268+    _sg.vk.ext.get_descriptor_set_layout_size(_sg.vk.dev, shd->vk.view_smp_dsl, &shd->vk.view_smp_dset_size);
21269+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
21270+        if (shd->cmn.views[i].stage == SG_SHADERSTAGE_NONE) {
21271+            continue;
21272+        }
21273+        const uint8_t vk_bnd = shd->vk.view_set1_bnd_n[i];
21274+        VkDeviceSize dset_offset = 0;
21275+        _sg.vk.ext.get_descriptor_set_layout_binding_offset(_sg.vk.dev, shd->vk.view_smp_dsl, vk_bnd, &dset_offset);
21276+        shd->vk.view_dset_offsets[i] = (uint16_t)dset_offset;
21277+    }
21278+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
21279+        if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) {
21280+            continue;
21281+        }
21282+        const uint8_t vk_bnd = shd->vk.smp_set1_bnd_n[i];
21283+        VkDeviceSize dset_offset = 0;
21284+        _sg.vk.ext.get_descriptor_set_layout_binding_offset(_sg.vk.dev, shd->vk.view_smp_dsl, vk_bnd, &dset_offset);
21285+        shd->vk.smp_dset_offsets[i] = (uint16_t)dset_offset;
21286+    }
21287+
21288+    VkDescriptorSetLayout set_layouts[_SG_VK_NUM_DESCRIPTORSETS] = {
21289+        shd->vk.ub_dsl,
21290+        shd->vk.view_smp_dsl,
21291+    };
21292+    _SG_STRUCT(VkPipelineLayoutCreateInfo, pl_create_info);
21293+    pl_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
21294+    pl_create_info.setLayoutCount = _SG_VK_NUM_DESCRIPTORSETS;
21295+    pl_create_info.pSetLayouts = set_layouts;
21296+    res = vkCreatePipelineLayout(_sg.vk.dev, &pl_create_info, 0, &shd->vk.pip_layout);
21297+    if (res != VK_SUCCESS) {
21298+        _SG_ERROR(VULKAN_CREATE_PIPELINE_LAYOUT_FAILED);
21299+        return SG_RESOURCESTATE_FAILED;
21300+    }
21301+    return SG_RESOURCESTATE_VALID;
21302+}
21303+
21304+_SOKOL_PRIVATE void _sg_vk_discard_shader(_sg_shader_t* shd) {
21305+    SOKOL_ASSERT(shd);
21306+    SOKOL_ASSERT(_sg.vk.dev);
21307+    _sg_vk_discard_shader_func(&shd->vk.vertex_func);
21308+    _sg_vk_discard_shader_func(&shd->vk.fragment_func);
21309+    _sg_vk_discard_shader_func(&shd->vk.compute_func);
21310+    if (shd->vk.pip_layout) {
21311+        _sg_vk_delete_queue_add(_sg_vk_pipelinelayout_destructor, (void*)shd->vk.pip_layout);
21312+        shd->vk.pip_layout = 0;
21313+    }
21314+    if (shd->vk.ub_dsl) {
21315+        _sg_vk_delete_queue_add(_sg_vk_descriptorsetlayout_destructor, (void*)shd->vk.ub_dsl);
21316+        shd->vk.ub_dsl = 0;
21317+    }
21318+    if (shd->vk.view_smp_dsl) {
21319+        _sg_vk_delete_queue_add(_sg_vk_descriptorsetlayout_destructor, (void*)shd->vk.view_smp_dsl);
21320+        shd->vk.view_smp_dsl = 0;
21321+    }
21322+}
21323+
21324+_SOKOL_PRIVATE sg_resource_state _sg_vk_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
21325+    SOKOL_ASSERT(pip && desc);
21326+    SOKOL_ASSERT(_sg.vk.dev);
21327+    VkResult res;
21328+
21329+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
21330+    SOKOL_ASSERT(shd->vk.pip_layout);
21331+    if (pip->cmn.is_compute) {
21332+        SOKOL_ASSERT(shd->vk.compute_func.module);
21333+        _SG_STRUCT(VkComputePipelineCreateInfo, pip_create_info);
21334+        pip_create_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
21335+        pip_create_info.flags = VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
21336+        pip_create_info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
21337+        pip_create_info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
21338+        pip_create_info.stage.module = shd->vk.compute_func.module;
21339+        pip_create_info.stage.pName = shd->vk.compute_func.entry.buf;
21340+        pip_create_info.layout = shd->vk.pip_layout;
21341+        res = vkCreateComputePipelines(_sg.vk.dev, VK_NULL_HANDLE, 1, &pip_create_info, 0, &pip->vk.pip);
21342+        if (res != VK_SUCCESS) {
21343+            _SG_ERROR(VULKAN_CREATE_COMPUTE_PIPELINE_FAILED);
21344+            return SG_RESOURCESTATE_FAILED;
21345+        }
21346+    } else {
21347+        uint32_t num_stages = 0;
21348+        _SG_STRUCT(VkPipelineShaderStageCreateInfo, stages[2]);
21349+        if (shd->vk.vertex_func.module) {
21350+            stages[num_stages].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
21351+            stages[num_stages].stage = VK_SHADER_STAGE_VERTEX_BIT;
21352+            stages[num_stages].module = shd->vk.vertex_func.module;
21353+            stages[num_stages].pName = shd->vk.vertex_func.entry.buf;
21354+            num_stages += 1;
21355+        }
21356+        if (shd->vk.fragment_func.module) {
21357+            stages[num_stages].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
21358+            stages[num_stages].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
21359+            stages[num_stages].module = shd->vk.fragment_func.module;
21360+            stages[num_stages].pName = shd->vk.fragment_func.entry.buf;
21361+            num_stages += 1;
21362+        }
21363+
21364+        uint32_t num_vtx_bnds = 0;
21365+        _SG_STRUCT(VkVertexInputBindingDescription, vtx_bnds[SG_MAX_VERTEXBUFFER_BINDSLOTS]);
21366+        for (uint32_t vbl_idx = 0; vbl_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS; vbl_idx++, num_vtx_bnds++) {
21367+            const sg_vertex_buffer_layout_state* vbl_state = &desc->layout.buffers[vbl_idx];
21368+            if (0 == vbl_state->stride) {
21369+                break;
21370+            }
21371+            vtx_bnds[vbl_idx].binding = vbl_idx;
21372+            vtx_bnds[vbl_idx].stride = (uint32_t)vbl_state->stride;
21373+            vtx_bnds[vbl_idx].inputRate = _sg_vk_vertex_input_rate(vbl_state->step_func);
21374+        }
21375+
21376+        uint32_t num_vtx_attrs = 0;
21377+        _SG_STRUCT(VkVertexInputAttributeDescription, vtx_attrs[SG_MAX_VERTEX_ATTRIBUTES]);
21378+        for (uint32_t va_idx = 0; va_idx < SG_MAX_VERTEX_ATTRIBUTES; va_idx++, num_vtx_attrs++) {
21379+            const sg_vertex_attr_state* va_state = &desc->layout.attrs[va_idx];
21380+            if (SG_VERTEXFORMAT_INVALID == va_state->format) {
21381+                break;
21382+            }
21383+            const uint32_t vbl_idx = (uint32_t)va_state->buffer_index;
21384+            SOKOL_ASSERT(vbl_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS);
21385+            SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[vbl_idx]);
21386+            vtx_attrs[va_idx].location = va_idx;
21387+            vtx_attrs[va_idx].binding = vbl_idx;
21388+            vtx_attrs[va_idx].format = _sg_vk_vertex_format(va_state->format);
21389+            vtx_attrs[va_idx].offset = (uint32_t)va_state->offset;
21390+        }
21391+
21392+        _SG_STRUCT(VkPipelineVertexInputStateCreateInfo, vi_state);
21393+        vi_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
21394+        vi_state.vertexBindingDescriptionCount = num_vtx_bnds;
21395+        vi_state.pVertexBindingDescriptions = vtx_bnds;
21396+        vi_state.vertexAttributeDescriptionCount = num_vtx_attrs;
21397+        vi_state.pVertexAttributeDescriptions = vtx_attrs;
21398+
21399+        _SG_STRUCT(VkPipelineInputAssemblyStateCreateInfo, ia_state);
21400+        ia_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
21401+        ia_state.topology = _sg_vk_primitive_topology(desc->primitive_type);
21402+        ia_state.primitiveRestartEnable = VK_FALSE; // FIXME: needs 'primitiveTopologyRestart feature enabled'
21403+
21404+        _SG_STRUCT(VkPipelineViewportStateCreateInfo, vp_state);
21405+        vp_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
21406+        vp_state.viewportCount = 1;
21407+        vp_state.scissorCount = 1;
21408+
21409+        _SG_STRUCT(VkPipelineRasterizationStateCreateInfo, rs_state);
21410+        rs_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
21411+        rs_state.depthClampEnable = false;
21412+        rs_state.rasterizerDiscardEnable = false;
21413+        rs_state.polygonMode = VK_POLYGON_MODE_FILL;
21414+        rs_state.cullMode = _sg_vk_cullmode(desc->cull_mode);
21415+        rs_state.frontFace = _sg_vk_frontface(desc->face_winding);
21416+        rs_state.depthBiasEnable = ((int32_t)desc->depth.bias) != 0;
21417+        rs_state.depthBiasConstantFactor = desc->depth.bias;
21418+        rs_state.depthBiasClamp = desc->depth.bias_clamp;
21419+        rs_state.depthBiasSlopeFactor = desc->depth.bias_slope_scale;
21420+        rs_state.lineWidth = 1.0f;
21421+
21422+        _SG_STRUCT(VkPipelineMultisampleStateCreateInfo, ms_state);
21423+        ms_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
21424+        ms_state.rasterizationSamples = (VkSampleCountFlagBits)desc->sample_count;
21425+        ms_state.alphaToCoverageEnable = desc->alpha_to_coverage_enabled;
21426+
21427+        _SG_STRUCT(VkPipelineDepthStencilStateCreateInfo, ds_state);
21428+        ds_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
21429+        ds_state.depthTestEnable = desc->depth.compare != SG_COMPAREFUNC_ALWAYS;
21430+        ds_state.depthWriteEnable = desc->depth.write_enabled;
21431+        ds_state.depthCompareOp = _sg_vk_compare_op(desc->depth.compare);
21432+        ds_state.depthBoundsTestEnable = false;
21433+        ds_state.stencilTestEnable = desc->stencil.enabled;
21434+        ds_state.front.failOp = _sg_vk_stencil_op(desc->stencil.front.fail_op);
21435+        ds_state.front.passOp = _sg_vk_stencil_op(desc->stencil.front.pass_op);
21436+        ds_state.front.depthFailOp = _sg_vk_stencil_op(desc->stencil.front.depth_fail_op);
21437+        ds_state.front.compareOp = _sg_vk_compare_op(desc->stencil.front.compare);
21438+        ds_state.front.compareMask = desc->stencil.read_mask;
21439+        ds_state.front.writeMask = desc->stencil.write_mask;
21440+        ds_state.front.reference = desc->stencil.ref;
21441+        ds_state.back.failOp = _sg_vk_stencil_op(desc->stencil.back.fail_op);
21442+        ds_state.back.passOp = _sg_vk_stencil_op(desc->stencil.back.pass_op);
21443+        ds_state.back.depthFailOp = _sg_vk_stencil_op(desc->stencil.back.depth_fail_op);
21444+        ds_state.back.compareOp = _sg_vk_compare_op(desc->stencil.back.compare);
21445+        ds_state.back.compareMask = desc->stencil.read_mask;
21446+        ds_state.back.writeMask = desc->stencil.write_mask;
21447+        ds_state.back.reference = desc->stencil.ref;
21448+
21449+        _SG_STRUCT(VkPipelineColorBlendAttachmentState, att_states[SG_MAX_COLOR_ATTACHMENTS]);
21450+        SOKOL_ASSERT(desc->color_count < SG_MAX_COLOR_ATTACHMENTS);
21451+        for (int i = 0; i < desc->color_count; i++) {
21452+            att_states[i].blendEnable = desc->colors[i].blend.enabled;
21453+            att_states[i].srcColorBlendFactor = _sg_vk_blend_factor(desc->colors[i].blend.src_factor_rgb);
21454+            att_states[i].dstColorBlendFactor = _sg_vk_blend_factor(desc->colors[i].blend.dst_factor_rgb);
21455+            att_states[i].colorBlendOp = _sg_vk_blend_op(desc->colors[i].blend.op_rgb);
21456+            att_states[i].srcAlphaBlendFactor = _sg_vk_blend_factor(desc->colors[i].blend.src_factor_alpha);
21457+            att_states[i].dstAlphaBlendFactor = _sg_vk_blend_factor(desc->colors[i].blend.dst_factor_alpha);
21458+            att_states[i].alphaBlendOp = _sg_vk_blend_op(desc->colors[i].blend.op_alpha);
21459+            att_states[i].colorWriteMask = _sg_vk_color_write_mask(desc->colors[i].write_mask);
21460+        }
21461+
21462+        _SG_STRUCT(VkPipelineColorBlendStateCreateInfo, cb_state);
21463+        cb_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
21464+        cb_state.logicOpEnable = false;
21465+        cb_state.attachmentCount = (uint32_t)desc->color_count;
21466+        cb_state.pAttachments = att_states;
21467+        cb_state.blendConstants[0] = desc->blend_color.r;
21468+        cb_state.blendConstants[1] = desc->blend_color.g;
21469+        cb_state.blendConstants[2] = desc->blend_color.b;
21470+        cb_state.blendConstants[3] = desc->blend_color.a;
21471+
21472+        _SG_STRUCT(VkFormat, color_formats[SG_MAX_COLOR_ATTACHMENTS]);
21473+        SOKOL_ASSERT(desc->color_count <= SG_MAX_COLOR_ATTACHMENTS);
21474+        for (int i = 0; i < desc->color_count; i++) {
21475+            color_formats[i] = _sg_vk_format(desc->colors[i].pixel_format);
21476+        }
21477+        _SG_STRUCT(VkPipelineRenderingCreateInfo, rnd_state);
21478+        rnd_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
21479+        rnd_state.colorAttachmentCount = (uint32_t)desc->color_count;
21480+        rnd_state.pColorAttachmentFormats = color_formats;
21481+        rnd_state.depthAttachmentFormat = _sg_vk_format(desc->depth.pixel_format);
21482+        if (_sg_is_depth_stencil_format(desc->depth.pixel_format)) {
21483+            rnd_state.stencilAttachmentFormat = _sg_vk_format(desc->depth.pixel_format);
21484+        } else {
21485+            rnd_state.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
21486+        }
21487+        VkDynamicState dyn_states[2] = {
21488+            VK_DYNAMIC_STATE_VIEWPORT,
21489+            VK_DYNAMIC_STATE_SCISSOR,
21490+        };
21491+        _SG_STRUCT(VkPipelineDynamicStateCreateInfo, dyn_state);
21492+        dyn_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
21493+        dyn_state.dynamicStateCount = 2;
21494+        dyn_state.pDynamicStates = dyn_states;
21495+
21496+        _SG_STRUCT(VkGraphicsPipelineCreateInfo, pip_create_info);
21497+        pip_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
21498+        pip_create_info.pNext = &rnd_state;
21499+        pip_create_info.flags = VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
21500+        pip_create_info.stageCount = num_stages;
21501+        pip_create_info.pStages = stages;
21502+        pip_create_info.pVertexInputState = &vi_state;
21503+        pip_create_info.pInputAssemblyState = &ia_state;
21504+        pip_create_info.pViewportState = &vp_state;
21505+        pip_create_info.pRasterizationState = &rs_state;
21506+        pip_create_info.pMultisampleState = &ms_state;
21507+        pip_create_info.pDepthStencilState = &ds_state;
21508+        pip_create_info.pColorBlendState = &cb_state;
21509+        pip_create_info.pDynamicState = &dyn_state;
21510+        pip_create_info.layout = shd->vk.pip_layout;
21511+
21512+        res = vkCreateGraphicsPipelines(_sg.vk.dev, VK_NULL_HANDLE, 1, &pip_create_info, 0, &pip->vk.pip);
21513+        if (res != VK_SUCCESS) {
21514+            _SG_ERROR(VULKAN_CREATE_GRAPHICS_PIPELINE_FAILED);
21515+            return SG_RESOURCESTATE_FAILED;
21516+        }
21517+    }
21518+    SOKOL_ASSERT(pip->vk.pip);
21519+    _sg_vk_set_object_label(VK_OBJECT_TYPE_PIPELINE, (uint64_t)pip->vk.pip, desc->label);
21520+    return SG_RESOURCESTATE_VALID;
21521+}
21522+
21523+_SOKOL_PRIVATE void _sg_vk_discard_pipeline(_sg_pipeline_t* pip) {
21524+    SOKOL_ASSERT(pip);
21525+    if (pip->vk.pip) {
21526+        _sg_vk_delete_queue_add(_sg_vk_pipeline_destructor, (void*)pip->vk.pip);
21527+        pip->vk.pip = 0;
21528+    }
21529+}
21530+
21531+_SOKOL_PRIVATE sg_resource_state _sg_vk_create_view(_sg_view_t* view, const sg_view_desc* desc) {
21532+    SOKOL_ASSERT(view && desc);
21533+    SOKOL_ASSERT(_sg.vk.dev);
21534+    SOKOL_ASSERT(0 == view->vk.img_view);
21535+    VkResult res;
21536+    _SG_STRUCT(VkDescriptorGetInfoEXT, get_info);
21537+    get_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT;
21538+    if (view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
21539+        // record descriptor data for storage buffer
21540+        view->vk.descriptor_size = _sg.vk.descriptor_buffer_props.storageBufferDescriptorSize;
21541+        if (_SG_VK_MAX_DESCRIPTOR_DATA_SIZE < view->vk.descriptor_size) {
21542+            _SG_ERROR(VULKAN_VIEW_MAX_DESCRIPTOR_SIZE);
21543+            return SG_RESOURCESTATE_FAILED;
21544+        }
21545+        const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
21546+        SOKOL_ASSERT(buf->vk.dev_addr);
21547+        _SG_STRUCT(VkDescriptorAddressInfoEXT, addr_info);
21548+        addr_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT;
21549+        addr_info.address = buf->vk.dev_addr + (VkDeviceSize)view->cmn.buf.offset;
21550+        addr_info.range = (VkDeviceSize)(buf->cmn.size - view->cmn.buf.offset);
21551+        get_info.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
21552+        get_info.data.pStorageBuffer = &addr_info;
21553+        _sg.vk.ext.get_descriptor(_sg.vk.dev, &get_info, view->vk.descriptor_size, &view->vk.descriptor_data);
21554+    } else {
21555+        // create image view object
21556+        const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
21557+        SOKOL_ASSERT(img->vk.img);
21558+        SOKOL_ASSERT(view->cmn.img.mip_level_count >= 1);
21559+        SOKOL_ASSERT(view->cmn.img.slice_count >= 1);
21560+        _SG_STRUCT(VkImageViewCreateInfo, create_info);
21561+        create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
21562+        create_info.image = img->vk.img;
21563+        if (view->cmn.type == SG_VIEWTYPE_TEXTURE) {
21564+            create_info.viewType = _sg_vk_texture_image_view_type(img->cmn.type);
21565+        } else {
21566+            create_info.viewType = _sg_vk_attachment_image_view_type(img->cmn.type);
21567+        }
21568+        create_info.format = _sg_vk_format(img->cmn.pixel_format);
21569+        if (view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT) {
21570+            create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
21571+            if (_sg_is_depth_stencil_format(img->cmn.pixel_format)) {
21572+                create_info.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
21573+            }
21574+        } else if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) {
21575+            create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
21576+        } else {
21577+            create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
21578+        }
21579+        create_info.subresourceRange.baseMipLevel = (uint32_t)view->cmn.img.mip_level;
21580+        create_info.subresourceRange.levelCount = (uint32_t)view->cmn.img.mip_level_count;
21581+        create_info.subresourceRange.baseArrayLayer = (uint32_t)view->cmn.img.slice;
21582+        create_info.subresourceRange.layerCount = (uint32_t)view->cmn.img.slice_count;
21583+        res = vkCreateImageView(_sg.vk.dev, &create_info, 0, &view->vk.img_view);
21584+        if (res != VK_SUCCESS) {
21585+            _SG_ERROR(VULKAN_CREATE_IMAGE_VIEW_FAILED);
21586+            return SG_RESOURCESTATE_FAILED;
21587+        }
21588+        SOKOL_ASSERT(view->vk.img_view);
21589+        _sg_vk_set_object_label(VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)view->vk.img_view, desc->label);
21590+
21591+        // record descriptor data for storage images and textures
21592+        if ((view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) || (view->cmn.type == SG_VIEWTYPE_TEXTURE)) {
21593+            _SG_STRUCT(VkDescriptorImageInfo, img_info);
21594+            img_info.imageView = view->vk.img_view;
21595+            if (view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE) {
21596+                view->vk.descriptor_size = _sg.vk.descriptor_buffer_props.storageImageDescriptorSize;
21597+                img_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
21598+                get_info.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
21599+                get_info.data.pStorageImage = &img_info;
21600+            } else {
21601+                view->vk.descriptor_size = _sg.vk.descriptor_buffer_props.sampledImageDescriptorSize;
21602+                img_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
21603+                get_info.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
21604+                get_info.data.pSampledImage = &img_info;
21605+            }
21606+            if (_SG_VK_MAX_DESCRIPTOR_DATA_SIZE < view->vk.descriptor_size) {
21607+                _SG_ERROR(VULKAN_VIEW_MAX_DESCRIPTOR_SIZE);
21608+                return SG_RESOURCESTATE_FAILED;
21609+            }
21610+            _sg.vk.ext.get_descriptor(_sg.vk.dev, &get_info, view->vk.descriptor_size, &view->vk.descriptor_data);
21611+        }
21612+    }
21613+    return SG_RESOURCESTATE_VALID;
21614+}
21615+
21616+_SOKOL_PRIVATE void _sg_vk_discard_view(_sg_view_t* view) {
21617+    SOKOL_ASSERT(view);
21618+    if (view->vk.img_view) {
21619+        _sg_vk_delete_queue_add(_sg_vk_image_view_destructor, (void*)view->vk.img_view);
21620+        view->vk.img_view = 0;
21621+    }
21622+}
21623+
21624+_SOKOL_PRIVATE void _sg_vk_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
21625+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21626+    _SG_STRUCT(VkViewport, vp);
21627+    vp.x = (float) x;
21628+    vp.width = (float) w;
21629+    vp.height = (float) -h;
21630+    vp.maxDepth = 1.0f;
21631+    if (origin_top_left) {
21632+        vp.y = (float)(y + h);
21633+    } else {
21634+        vp.y = (float)(_sg.cur_pass.dim.height - y);
21635+    }
21636+    vkCmdSetViewport(_sg.vk.frame.cmd_buf, 0, 1, &vp);
21637+}
21638+
21639+_SOKOL_PRIVATE void _sg_vk_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
21640+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21641+    const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.dim.width, _sg.cur_pass.dim.height);
21642+    _SG_STRUCT(VkRect2D, rect);
21643+    rect.offset.x = clip.x;
21644+    rect.offset.y = (origin_top_left ? clip.y : (_sg.cur_pass.dim.height - (clip.y + clip.h)));
21645+    rect.extent.width = (uint32_t) clip.w;
21646+    rect.extent.height = (uint32_t) clip.h;
21647+    vkCmdSetScissor(_sg.vk.frame.cmd_buf, 0, 1, &rect);
21648+}
21649+
21650+_SOKOL_PRIVATE void _sg_vk_init_color_attachment_info(VkRenderingAttachmentInfo* info, const sg_color_attachment_action* action, VkImageView color_view, VkImageView resolve_view) {
21651+    info->sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
21652+    info->imageView = color_view;
21653+    info->imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
21654+    if (resolve_view) {
21655+        info->resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT;
21656+        info->resolveImageView = resolve_view;
21657+        info->resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
21658+    } else {
21659+        info->resolveMode = VK_RESOLVE_MODE_NONE;
21660+        info->resolveImageView = 0;
21661+        info->resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
21662+    }
21663+    info->loadOp = _sg_vk_load_op(action->load_action);
21664+    info->storeOp = _sg_vk_store_op(action->store_action);
21665+    info->clearValue.color.float32[0] = action->clear_value.r;
21666+    info->clearValue.color.float32[1] = action->clear_value.g;
21667+    info->clearValue.color.float32[2] = action->clear_value.b;
21668+    info->clearValue.color.float32[3] = action->clear_value.a;
21669+}
21670+
21671+_SOKOL_PRIVATE void _sg_vk_init_depth_attachment_info(VkRenderingAttachmentInfo* info, const sg_depth_attachment_action* action, VkImageView ds_view) {
21672+    info->sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
21673+    info->imageView = ds_view;
21674+    info->imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
21675+    info->resolveMode = VK_RESOLVE_MODE_NONE;
21676+    info->loadOp = _sg_vk_load_op(action->load_action);
21677+    info->storeOp = _sg_vk_store_op(action->store_action);
21678+    info->clearValue.depthStencil.depth = action->clear_value;
21679+}
21680+
21681+_SOKOL_PRIVATE void _sg_vk_init_stencil_attachment_info(VkRenderingAttachmentInfo* info, const sg_stencil_attachment_action* action, VkImageView ds_view) {
21682+    info->sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
21683+    info->imageView = ds_view;
21684+    info->imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
21685+    info->resolveMode = VK_RESOLVE_MODE_NONE;
21686+    info->loadOp = _sg_vk_load_op(action->load_action);
21687+    info->storeOp = _sg_vk_store_op(action->store_action);
21688+    info->clearValue.depthStencil.stencil = action->clear_value;
21689+}
21690+
21691+_SOKOL_PRIVATE void _sg_vk_begin_compute_pass(VkCommandBuffer cmd_buf, const sg_pass* pass) {
21692+    // FIXME: nothing to do here?
21693+    _SOKOL_UNUSED(cmd_buf && pass);
21694+}
21695+
21696+_SOKOL_PRIVATE void _sg_vk_begin_render_pass(VkCommandBuffer cmd_buf, const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
21697+    const sg_pass_action* action = &pass->action;
21698+    const bool is_swapchain_pass = atts->empty;
21699+
21700+    _SG_STRUCT(VkRenderingAttachmentInfo, color_att_infos[SG_MAX_COLOR_ATTACHMENTS]);
21701+    _SG_STRUCT(VkRenderingAttachmentInfo, depth_att_info);
21702+    _SG_STRUCT(VkRenderingAttachmentInfo, stencil_att_info);
21703+    _SG_STRUCT(VkRenderingInfo, render_info);
21704+    render_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
21705+    render_info.renderArea.extent.width = (uint32_t)_sg.cur_pass.dim.width;
21706+    render_info.renderArea.extent.height = (uint32_t)_sg.cur_pass.dim.height;
21707+    render_info.layerCount = 1;
21708+
21709+    if (is_swapchain_pass) {
21710+        _sg.vk.swapchain = pass->swapchain.vulkan;
21711+        SOKOL_ASSERT(_sg.vk.swapchain.render_view);
21712+        if (pass->swapchain.sample_count > 1) {
21713+            SOKOL_ASSERT(_sg.vk.swapchain.resolve_view);
21714+        }
21715+        SOKOL_ASSERT(_sg.vk.swapchain.present_complete_semaphore);
21716+        SOKOL_ASSERT(_sg.vk.swapchain.render_finished_semaphore);
21717+        // FIXME: need to support multiple present_complete_semaphores
21718+        SOKOL_ASSERT(0 == _sg.vk.present_complete_sem);
21719+        _sg.vk.present_complete_sem = (VkSemaphore)_sg.vk.swapchain.present_complete_semaphore;
21720+        if (0 == _sg.vk.render_finished_sem) {
21721+            _sg.vk.render_finished_sem = (VkSemaphore)_sg.vk.swapchain.render_finished_semaphore;
21722+        } else {
21723+            SOKOL_ASSERT(_sg.vk.render_finished_sem == _sg.vk.swapchain.render_finished_semaphore);
21724+        }
21725+        VkImageView vk_color_view = (VkImageView)_sg.vk.swapchain.render_view;
21726+        VkImageView vk_resolve_view = (VkImageView)_sg.vk.swapchain.resolve_view;
21727+        _sg_vk_init_color_attachment_info(&color_att_infos[0], &action->colors[0], vk_color_view, vk_resolve_view);
21728+        render_info.colorAttachmentCount = 1;
21729+        render_info.pColorAttachments = color_att_infos;
21730+        if (_sg.vk.swapchain.depth_stencil_view) {
21731+            VkImageView vk_ds_view = (VkImageView)_sg.vk.swapchain.depth_stencil_view;
21732+            const bool has_stencil = _sg_is_depth_stencil_format(pass->swapchain.depth_format);
21733+            _sg_vk_init_depth_attachment_info(&depth_att_info, &action->depth, vk_ds_view);
21734+            render_info.pDepthAttachment = &depth_att_info;
21735+            if (has_stencil) {
21736+                _sg_vk_init_stencil_attachment_info(&stencil_att_info, &action->stencil, vk_ds_view);
21737+                render_info.pStencilAttachment = &stencil_att_info;
21738+            }
21739+        }
21740+    } else {
21741+        SOKOL_ASSERT(atts->num_color_views <= SG_MAX_COLOR_ATTACHMENTS);
21742+        for (int i = 0; i < atts->num_color_views; i++) {
21743+            SOKOL_ASSERT(atts->color_views[i]);
21744+            const _sg_view_t* color_view = atts->color_views[i];
21745+            VkImageView vk_color_view = color_view->vk.img_view;
21746+            const _sg_view_t* resolve_view = atts->resolve_views[i];
21747+            VkImageView vk_resolve_view = 0;
21748+            if (resolve_view) {
21749+                vk_resolve_view = resolve_view->vk.img_view;
21750+            }
21751+            _sg_vk_init_color_attachment_info(&color_att_infos[i], &action->colors[i], vk_color_view, vk_resolve_view);
21752+        }
21753+        if (atts->num_color_views > 0) {
21754+            render_info.colorAttachmentCount = (uint32_t)atts->num_color_views;
21755+            render_info.pColorAttachments = color_att_infos;
21756+        }
21757+        if (atts->ds_view) {
21758+            const _sg_view_t* ds_view = atts->ds_view;
21759+            const _sg_image_t* ds_image = _sg_image_ref_ptr(&ds_view->cmn.img.ref);
21760+            const bool has_stencil = _sg_is_depth_stencil_format(ds_image->cmn.pixel_format);
21761+            VkImageView vk_ds_view = ds_view->vk.img_view;
21762+            _sg_vk_init_depth_attachment_info(&depth_att_info, &action->depth, vk_ds_view);
21763+            render_info.pDepthAttachment = &depth_att_info;
21764+            if (has_stencil) {
21765+                _sg_vk_init_stencil_attachment_info(&stencil_att_info, &action->stencil, vk_ds_view);
21766+                render_info.pStencilAttachment = &stencil_att_info;
21767+            }
21768+        }
21769+    }
21770+    vkCmdBeginRendering(cmd_buf, &render_info);
21771+
21772+    _SG_STRUCT(VkViewport, vp);
21773+    vp.y = (float)_sg.cur_pass.dim.height;
21774+    vp.width = (float)_sg.cur_pass.dim.width;
21775+    vp.height = (float)-_sg.cur_pass.dim.height;
21776+    vp.maxDepth = 1.0f;
21777+    vkCmdSetViewport(_sg.vk.frame.cmd_buf, 0, 1, &vp);
21778+
21779+    _SG_STRUCT(VkRect2D, rect);
21780+    rect.extent.width = (uint32_t)_sg.cur_pass.dim.width;
21781+    rect.extent.height = (uint32_t)_sg.cur_pass.dim.height;
21782+    vkCmdSetScissor(_sg.vk.frame.cmd_buf, 0, 1, &rect);
21783+}
21784+
21785+_SOKOL_PRIVATE void _sg_vk_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
21786+    SOKOL_ASSERT(pass && atts);
21787+    _sg_vk_acquire_frame_command_buffers();
21788+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21789+    _sg_vk_barrier_on_begin_pass(_sg.vk.frame.cmd_buf, pass, atts, _sg.cur_pass.is_compute);
21790+    if (_sg.cur_pass.is_compute) {
21791+        _sg_vk_begin_compute_pass(_sg.vk.frame.cmd_buf, pass);
21792+    } else {
21793+        _sg_vk_begin_render_pass(_sg.vk.frame.cmd_buf, pass, atts);
21794+    }
21795+}
21796+
21797+_SOKOL_PRIVATE void _sg_vk_end_pass(const _sg_attachments_ptrs_t* atts) {
21798+    SOKOL_ASSERT(atts);
21799+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21800+    if (!_sg.cur_pass.is_compute) {
21801+        vkCmdEndRendering(_sg.vk.frame.cmd_buf);
21802+    }
21803+    _sg_vk_barrier_on_end_pass(_sg.vk.frame.cmd_buf, atts, _sg.cur_pass.is_compute);
21804+    _sg_clear(&_sg.vk.swapchain, sizeof(_sg.vk.swapchain));
21805+}
21806+
21807+_SOKOL_PRIVATE void _sg_vk_commit(void) {
21808+    SOKOL_ASSERT(_sg.vk.queue);
21809+    if (_sg.vk.frame.cmd_buf) {
21810+        _sg_vk_submit_frame_command_buffers();
21811+    }
21812+    _sg.vk.present_complete_sem = 0;
21813+    _sg.vk.render_finished_sem = 0;
21814+}
21815+
21816+_SOKOL_PRIVATE void _sg_vk_apply_pipeline(_sg_pipeline_t* pip) {
21817+    SOKOL_ASSERT(pip);
21818+    SOKOL_ASSERT(pip->vk.pip);
21819+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21820+    _sg.vk.uniforms.dirty = false;
21821+    VkPipelineBindPoint bindpoint = pip->cmn.is_compute
21822+        ? VK_PIPELINE_BIND_POINT_COMPUTE
21823+        : VK_PIPELINE_BIND_POINT_GRAPHICS;
21824+    vkCmdBindPipeline(_sg.vk.frame.cmd_buf, bindpoint, pip->vk.pip);
21825+}
21826+
21827+_SOKOL_PRIVATE bool _sg_vk_apply_bindings(_sg_bindings_ptrs_t* bnd) {
21828+    SOKOL_ASSERT(bnd && bnd->pip);
21829+    SOKOL_ASSERT(_sg.vk.dev);
21830+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21831+    VkCommandBuffer cmd_buf = _sg.vk.frame.cmd_buf;
21832+
21833+    // track or insert pipeline barriers
21834+    _sg_vk_barrier_on_apply_bindings(cmd_buf, bnd, _sg.cur_pass.is_compute);
21835+
21836+    if (!_sg.cur_pass.is_compute) {
21837+        // bind vertex buffers
21838+        // FIXME: could do this in a single call if buffer bindings are guaranteed
21839+        // to be continuous (currently that's not checked anywhere), or alternative
21840+        // via nullDescriptor robustness feature (which apparently may have performance downsides)
21841+        for (uint32_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
21842+            if (bnd->vbs[i]) {
21843+                VkBuffer vk_buf = bnd->vbs[i]->vk.buf;
21844+                VkDeviceSize vk_offset = (VkDeviceSize)bnd->vb_offsets[i];
21845+                vkCmdBindVertexBuffers(cmd_buf, i, 1, &vk_buf, &vk_offset);
21846+            }
21847+        }
21848+        if (bnd->ib) {
21849+            VkBuffer vk_buf = bnd->ib->vk.buf;
21850+            VkDeviceSize vk_offset = (VkDeviceSize)bnd->ib_offset;
21851+            VkIndexType vk_index_type = _sg_vk_index_type(bnd->pip->cmn.index_type);
21852+            vkCmdBindIndexBuffer(cmd_buf, vk_buf, vk_offset, vk_index_type);
21853+        }
21854+    }
21855+
21856+    // bind views and samplers
21857+    const VkPipelineBindPoint pip_bind_point = _sg.cur_pass.is_compute
21858+        ? VK_PIPELINE_BIND_POINT_COMPUTE
21859+        : VK_PIPELINE_BIND_POINT_GRAPHICS;
21860+    return _sg_vk_bind_view_smp_descriptor_set(cmd_buf, bnd, pip_bind_point);
21861+}
21862+
21863+_SOKOL_PRIVATE void _sg_vk_apply_uniforms(int ub_slot, const sg_range* data) {
21864+    SOKOL_ASSERT(_sg.vk.uniforms.dbuf.cur_dev_addr);
21865+    SOKOL_ASSERT(data && data->ptr && (data->size > 0));
21866+    SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
21867+    const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
21868+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
21869+    SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size);
21870+
21871+    // copy data into uniform buffer and keep track of uniform bind infos
21872+    const VkDeviceSize ubuf_offset = _sg_vk_uniform_copy(data);
21873+    if (_sg.vk.uniforms.dbuf.overflown) {
21874+        _SG_ERROR(VULKAN_UNIFORM_BUFFER_OVERFLOW);
21875+        _sg.next_draw_valid = false;
21876+        return;
21877+    }
21878+    _sg.vk.uniforms.addr_info[ub_slot].range = data->size;
21879+    _sg.vk.uniforms.addr_info[ub_slot].address = _sg.vk.uniforms.dbuf.cur_dev_addr + ubuf_offset;
21880+
21881+    // copy uniform buffer descriptor data into intermediate sysmem buffer
21882+    // NOTE: letting vkGetDescriptorEXT write directly into the descriptor
21883+    // buffer has catastrophic performance on some Vulkan drivers, notably
21884+    // Intel's Windows driver
21885+    const size_t dsize = _sg.vk.descriptor_buffer_props.uniformBufferDescriptorSize;
21886+    SOKOL_ASSERT((shd->vk.ub_dset_offsets[ub_slot] + dsize) <= _sg.vk.uniforms.dset_cache_size);
21887+    uint8_t* dst_ptr = _sg.vk.uniforms.dset_cache + shd->vk.ub_dset_offsets[ub_slot];
21888+    _sg.vk.ext.get_descriptor(_sg.vk.dev, &_sg.vk.uniforms.get_info[ub_slot], dsize, dst_ptr);
21889+
21890+    // set uniforms dirty, applying the descriptor buffer offset is happens in draw/dispatch
21891+    _sg.vk.uniforms.dirty = true;
21892+}
21893+
21894+_SOKOL_PRIVATE void _sg_vk_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
21895+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21896+    VkCommandBuffer cmd_buf = _sg.vk.frame.cmd_buf;
21897+    if (_sg.vk.uniforms.dirty) {
21898+        if (!_sg_vk_bind_uniform_descriptor_set(cmd_buf)) {
21899+            return;
21900+        }
21901+    }
21902+    if (_sg.use_indexed_draw) {
21903+        vkCmdDrawIndexed(cmd_buf,
21904+            (uint32_t)num_elements,
21905+            (uint32_t)num_instances,
21906+            (uint32_t)base_element,
21907+            base_vertex,
21908+            (uint32_t)base_instance);
21909+    } else {
21910+        vkCmdDraw(cmd_buf,
21911+            (uint32_t)num_elements,
21912+            (uint32_t)num_instances,
21913+            (uint32_t)base_element,
21914+            (uint32_t)base_instance);
21915+    }
21916+}
21917+
21918+_SOKOL_PRIVATE void _sg_vk_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
21919+    SOKOL_ASSERT(_sg.vk.frame.cmd_buf);
21920+    VkCommandBuffer cmd_buf = _sg.vk.frame.cmd_buf;
21921+    if (_sg.vk.uniforms.dirty) {
21922+        if (!_sg_vk_bind_uniform_descriptor_set(cmd_buf)) {
21923+            return;
21924+        }
21925+    }
21926+    vkCmdDispatch(cmd_buf, (uint32_t)num_groups_x, (uint32_t)num_groups_y, (uint32_t)num_groups_z);
21927+}
21928+
21929+_SOKOL_PRIVATE void _sg_vk_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
21930+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
21931+    if (buf->cmn.usage.stream_update) {
21932+        _sg_vk_acquire_frame_command_buffers();
21933+        _sg_vk_staging_stream_buffer_data(buf, data, 0);
21934+    } else {
21935+        _sg_vk_staging_copy_buffer_data(buf, data, 0, true);
21936+    }
21937+}
21938+
21939+_SOKOL_PRIVATE void _sg_vk_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
21940+    SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0));
21941+    _SOKOL_UNUSED(new_frame);
21942+    if (buf->cmn.usage.stream_update) {
21943+        _sg_vk_acquire_frame_command_buffers();
21944+        _sg_vk_staging_stream_buffer_data(buf, data, (size_t)buf->cmn.append_pos);
21945+    } else {
21946+        _sg_vk_staging_copy_buffer_data(buf, data, (size_t)buf->cmn.append_pos, true);
21947+    }
21948+}
21949+
21950+_SOKOL_PRIVATE void _sg_vk_update_image(_sg_image_t* img, const sg_image_data* data) {
21951+    SOKOL_ASSERT(img && data);
21952+    if (img->cmn.usage.stream_update) {
21953+        _sg_vk_acquire_frame_command_buffers();
21954+        _sg_vk_staging_stream_image_data(img, data);
21955+    } else {
21956+        _sg_vk_staging_copy_image_data(img, data, true);
21957+    }
21958+}
21959+
21960+#endif
21961+
21962+//  ██████  ███████ ███    ██ ███████ ██████  ██  ██████     ██████   █████   ██████ ██   ██ ███████ ███    ██ ██████
21963+// ██       ██      ████   ██ ██      ██   ██ ██ ██          ██   ██ ██   ██ ██      ██  ██  ██      ████   ██ ██   ██
21964+// ██   ███ █████   ██ ██  ██ █████   ██████  ██ ██          ██████  ███████ ██      █████   █████   ██ ██  ██ ██   ██
21965+// ██    ██ ██      ██  ██ ██ ██      ██   ██ ██ ██          ██   ██ ██   ██ ██      ██  ██  ██      ██  ██ ██ ██   ██
21966+//  ██████  ███████ ██   ████ ███████ ██   ██ ██  ██████     ██████  ██   ██  ██████ ██   ██ ███████ ██   ████ ██████
21967+//
21968+// >>generic backend
21969+static inline void _sg_setup_backend(const sg_desc* desc) {
21970+    #if defined(_SOKOL_ANY_GL)
21971+    _sg_gl_setup_backend(desc);
21972+    #elif defined(SOKOL_METAL)
21973+    _sg_mtl_setup_backend(desc);
21974+    #elif defined(SOKOL_D3D11)
21975+    _sg_d3d11_setup_backend(desc);
21976+    #elif defined(SOKOL_WGPU)
21977+    _sg_wgpu_setup_backend(desc);
21978+    #elif defined(SOKOL_VULKAN)
21979+    _sg_vk_setup_backend(desc);
21980+    #elif defined(SOKOL_DUMMY_BACKEND)
21981+    _sg_dummy_setup_backend(desc);
21982+    #else
21983+    #error("INVALID BACKEND");
21984+    #endif
21985+}
21986+
21987+static inline void _sg_discard_backend(void) {
21988+    #if defined(_SOKOL_ANY_GL)
21989+    _sg_gl_discard_backend();
21990+    #elif defined(SOKOL_METAL)
21991+    _sg_mtl_discard_backend();
21992+    #elif defined(SOKOL_D3D11)
21993+    _sg_d3d11_discard_backend();
21994+    #elif defined(SOKOL_WGPU)
21995+    _sg_wgpu_discard_backend();
21996+    #elif defined(SOKOL_VULKAN)
21997+    _sg_vk_discard_backend();
21998+    #elif defined(SOKOL_DUMMY_BACKEND)
21999+    _sg_dummy_discard_backend();
22000+    #else
22001+    #error("INVALID BACKEND");
22002+    #endif
22003+}
22004+
22005+static inline void _sg_reset_state_cache(void) {
22006+    #if defined(_SOKOL_ANY_GL)
22007+    _sg_gl_reset_state_cache();
22008+    #elif defined(SOKOL_METAL)
22009+    _sg_mtl_reset_state_cache();
22010+    #elif defined(SOKOL_D3D11)
22011+    _sg_d3d11_reset_state_cache();
22012+    #elif defined(SOKOL_WGPU)
22013+    _sg_wgpu_reset_state_cache();
22014+    #elif defined(SOKOL_VULKAN)
22015+    _sg_vk_reset_state_cache();
22016+    #elif defined(SOKOL_DUMMY_BACKEND)
22017+    _sg_dummy_reset_state_cache();
22018+    #else
22019+    #error("INVALID BACKEND");
22020+    #endif
22021+}
22022+
22023+static inline sg_resource_state _sg_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
22024+    #if defined(_SOKOL_ANY_GL)
22025+    return _sg_gl_create_buffer(buf, desc);
22026+    #elif defined(SOKOL_METAL)
22027+    return _sg_mtl_create_buffer(buf, desc);
22028+    #elif defined(SOKOL_D3D11)
22029+    return _sg_d3d11_create_buffer(buf, desc);
22030+    #elif defined(SOKOL_WGPU)
22031+    return _sg_wgpu_create_buffer(buf, desc);
22032+    #elif defined(SOKOL_VULKAN)
22033+    return _sg_vk_create_buffer(buf, desc);
22034+    #elif defined(SOKOL_DUMMY_BACKEND)
22035+    return _sg_dummy_create_buffer(buf, desc);
22036+    #else
22037+    #error("INVALID BACKEND");
22038+    #endif
22039+}
22040+
22041+static inline void _sg_discard_buffer(_sg_buffer_t* buf) {
22042+    #if defined(_SOKOL_ANY_GL)
22043+    _sg_gl_discard_buffer(buf);
22044+    #elif defined(SOKOL_METAL)
22045+    _sg_mtl_discard_buffer(buf);
22046+    #elif defined(SOKOL_D3D11)
22047+    _sg_d3d11_discard_buffer(buf);
22048+    #elif defined(SOKOL_WGPU)
22049+    _sg_wgpu_discard_buffer(buf);
22050+    #elif defined(SOKOL_VULKAN)
22051+    _sg_vk_discard_buffer(buf);
22052+    #elif defined(SOKOL_DUMMY_BACKEND)
22053+    _sg_dummy_discard_buffer(buf);
22054+    #else
22055+    #error("INVALID BACKEND");
22056+    #endif
22057+}
22058+
22059+static inline sg_resource_state _sg_create_image(_sg_image_t* img, const sg_image_desc* desc) {
22060+    #if defined(_SOKOL_ANY_GL)
22061+    return _sg_gl_create_image(img, desc);
22062+    #elif defined(SOKOL_METAL)
22063+    return _sg_mtl_create_image(img, desc);
22064+    #elif defined(SOKOL_D3D11)
22065+    return _sg_d3d11_create_image(img, desc);
22066+    #elif defined(SOKOL_WGPU)
22067+    return _sg_wgpu_create_image(img, desc);
22068+    #elif defined(SOKOL_VULKAN)
22069+    return _sg_vk_create_image(img, desc);
22070+    #elif defined(SOKOL_DUMMY_BACKEND)
22071+    return _sg_dummy_create_image(img, desc);
22072+    #else
22073+    #error("INVALID BACKEND");
22074+    #endif
22075+}
22076+
22077+static inline void _sg_discard_image(_sg_image_t* img) {
22078+    #if defined(_SOKOL_ANY_GL)
22079+    _sg_gl_discard_image(img);
22080+    #elif defined(SOKOL_METAL)
22081+    _sg_mtl_discard_image(img);
22082+    #elif defined(SOKOL_D3D11)
22083+    _sg_d3d11_discard_image(img);
22084+    #elif defined(SOKOL_WGPU)
22085+    _sg_wgpu_discard_image(img);
22086+    #elif defined(SOKOL_VULKAN)
22087+    _sg_vk_discard_image(img);
22088+    #elif defined(SOKOL_DUMMY_BACKEND)
22089+    _sg_dummy_discard_image(img);
22090+    #else
22091+    #error("INVALID BACKEND");
22092+    #endif
22093+}
22094+
22095+static inline sg_resource_state _sg_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
22096+    #if defined(_SOKOL_ANY_GL)
22097+    return _sg_gl_create_sampler(smp, desc);
22098+    #elif defined(SOKOL_METAL)
22099+    return _sg_mtl_create_sampler(smp, desc);
22100+    #elif defined(SOKOL_D3D11)
22101+    return _sg_d3d11_create_sampler(smp, desc);
22102+    #elif defined(SOKOL_WGPU)
22103+    return _sg_wgpu_create_sampler(smp, desc);
22104+    #elif defined(SOKOL_VULKAN)
22105+    return _sg_vk_create_sampler(smp, desc);
22106+    #elif defined(SOKOL_DUMMY_BACKEND)
22107+    return _sg_dummy_create_sampler(smp, desc);
22108+    #else
22109+    #error("INVALID BACKEND");
22110+    #endif
22111+}
22112+
22113+static inline void _sg_discard_sampler(_sg_sampler_t* smp) {
22114+    #if defined(_SOKOL_ANY_GL)
22115+    _sg_gl_discard_sampler(smp);
22116+    #elif defined(SOKOL_METAL)
22117+    _sg_mtl_discard_sampler(smp);
22118+    #elif defined(SOKOL_D3D11)
22119+    _sg_d3d11_discard_sampler(smp);
22120+    #elif defined(SOKOL_WGPU)
22121+    _sg_wgpu_discard_sampler(smp);
22122+    #elif defined(SOKOL_VULKAN)
22123+    _sg_vk_discard_sampler(smp);
22124+    #elif defined(SOKOL_DUMMY_BACKEND)
22125+    _sg_dummy_discard_sampler(smp);
22126+    #else
22127+    #error("INVALID BACKEND");
22128+    #endif
22129+}
22130+
22131+static inline sg_resource_state _sg_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
22132+    #if defined(_SOKOL_ANY_GL)
22133+    return _sg_gl_create_shader(shd, desc);
22134+    #elif defined(SOKOL_METAL)
22135+    return _sg_mtl_create_shader(shd, desc);
22136+    #elif defined(SOKOL_D3D11)
22137+    return _sg_d3d11_create_shader(shd, desc);
22138+    #elif defined(SOKOL_WGPU)
22139+    return _sg_wgpu_create_shader(shd, desc);
22140+    #elif defined(SOKOL_VULKAN)
22141+    return _sg_vk_create_shader(shd, desc);
22142+    #elif defined(SOKOL_DUMMY_BACKEND)
22143+    return _sg_dummy_create_shader(shd, desc);
22144+    #else
22145+    #error("INVALID BACKEND");
22146+    #endif
22147+}
22148+
22149+static inline void _sg_discard_shader(_sg_shader_t* shd) {
22150+    #if defined(_SOKOL_ANY_GL)
22151+    _sg_gl_discard_shader(shd);
22152+    #elif defined(SOKOL_METAL)
22153+    _sg_mtl_discard_shader(shd);
22154+    #elif defined(SOKOL_D3D11)
22155+    _sg_d3d11_discard_shader(shd);
22156+    #elif defined(SOKOL_WGPU)
22157+    _sg_wgpu_discard_shader(shd);
22158+    #elif defined(SOKOL_VULKAN)
22159+    _sg_vk_discard_shader(shd);
22160+    #elif defined(SOKOL_DUMMY_BACKEND)
22161+    _sg_dummy_discard_shader(shd);
22162+    #else
22163+    #error("INVALID BACKEND");
22164+    #endif
22165+}
22166+
22167+static inline sg_resource_state _sg_create_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
22168+    #if defined(_SOKOL_ANY_GL)
22169+    return _sg_gl_create_pipeline(pip, desc);
22170+    #elif defined(SOKOL_METAL)
22171+    return _sg_mtl_create_pipeline(pip, desc);
22172+    #elif defined(SOKOL_D3D11)
22173+    return _sg_d3d11_create_pipeline(pip, desc);
22174+    #elif defined(SOKOL_WGPU)
22175+    return _sg_wgpu_create_pipeline(pip, desc);
22176+    #elif defined(SOKOL_VULKAN)
22177+    return _sg_vk_create_pipeline(pip, desc);
22178+    #elif defined(SOKOL_DUMMY_BACKEND)
22179+    return _sg_dummy_create_pipeline(pip, desc);
22180+    #else
22181+    #error("INVALID BACKEND");
22182+    #endif
22183+}
22184+
22185+static inline void _sg_discard_pipeline(_sg_pipeline_t* pip) {
22186+    #if defined(_SOKOL_ANY_GL)
22187+    _sg_gl_discard_pipeline(pip);
22188+    #elif defined(SOKOL_METAL)
22189+    _sg_mtl_discard_pipeline(pip);
22190+    #elif defined(SOKOL_D3D11)
22191+    _sg_d3d11_discard_pipeline(pip);
22192+    #elif defined(SOKOL_WGPU)
22193+    _sg_wgpu_discard_pipeline(pip);
22194+    #elif defined(SOKOL_VULKAN)
22195+    _sg_vk_discard_pipeline(pip);
22196+    #elif defined(SOKOL_DUMMY_BACKEND)
22197+    _sg_dummy_discard_pipeline(pip);
22198+    #else
22199+    #error("INVALID BACKEND");
22200+    #endif
22201+}
22202+
22203+static inline sg_resource_state _sg_create_view(_sg_view_t* view, const sg_view_desc* desc) {
22204+    #if defined(_SOKOL_ANY_GL)
22205+    return _sg_gl_create_view(view, desc);
22206+    #elif defined(SOKOL_METAL)
22207+    return _sg_mtl_create_view(view, desc);
22208+    #elif defined(SOKOL_D3D11)
22209+    return _sg_d3d11_create_view(view, desc);
22210+    #elif defined(SOKOL_WGPU)
22211+    return _sg_wgpu_create_view(view, desc);
22212+    #elif defined(SOKOL_VULKAN)
22213+    return _sg_vk_create_view(view, desc);
22214+    #elif defined(SOKOL_DUMMY_BACKEND)
22215+    return _sg_dummy_create_view(view, desc);
22216+    #else
22217+    #error("INVALID BACKEND");
22218+    #endif
22219+}
22220+
22221+static inline void _sg_discard_view(_sg_view_t* view) {
22222+    #if defined(_SOKOL_ANY_GL)
22223+    _sg_gl_discard_view(view);
22224+    #elif defined(SOKOL_METAL)
22225+    _sg_mtl_discard_view(view);
22226+    #elif defined(SOKOL_D3D11)
22227+    _sg_d3d11_discard_view(view);
22228+    #elif defined(SOKOL_WGPU)
22229+    _sg_wgpu_discard_view(view);
22230+    #elif defined(SOKOL_VULKAN)
22231+    _sg_vk_discard_view(view);
22232+    #elif defined(SOKOL_DUMMY_BACKEND)
22233+    _sg_dummy_discard_view(view);
22234+    #else
22235+    #error("INVALID BACKEND");
22236+    #endif
22237+}
22238+
22239+static inline void _sg_begin_pass(const sg_pass* pass, const _sg_attachments_ptrs_t* atts) {
22240+    #if defined(_SOKOL_ANY_GL)
22241+    _sg_gl_begin_pass(pass, atts);
22242+    #elif defined(SOKOL_METAL)
22243+    _sg_mtl_begin_pass(pass, atts);
22244+    #elif defined(SOKOL_D3D11)
22245+    _sg_d3d11_begin_pass(pass, atts);
22246+    #elif defined(SOKOL_WGPU)
22247+    _sg_wgpu_begin_pass(pass, atts);
22248+    #elif defined(SOKOL_VULKAN)
22249+    _sg_vk_begin_pass(pass, atts);
22250+    #elif defined(SOKOL_DUMMY_BACKEND)
22251+    _sg_dummy_begin_pass(pass, atts);
22252+    #else
22253+    #error("INVALID BACKEND");
22254+    #endif
22255+}
22256+
22257+static inline void _sg_end_pass(const _sg_attachments_ptrs_t* atts) {
22258+    #if defined(_SOKOL_ANY_GL)
22259+    _sg_gl_end_pass(atts);
22260+    #elif defined(SOKOL_METAL)
22261+    _sg_mtl_end_pass(atts);
22262+    #elif defined(SOKOL_D3D11)
22263+    _sg_d3d11_end_pass(atts);
22264+    #elif defined(SOKOL_WGPU)
22265+    _sg_wgpu_end_pass(atts);
22266+    #elif defined(SOKOL_VULKAN)
22267+    _sg_vk_end_pass(atts);
22268+    #elif defined(SOKOL_DUMMY_BACKEND)
22269+    _sg_dummy_end_pass(atts);
22270+    #else
22271+    #error("INVALID BACKEND");
22272+    #endif
22273+}
22274+
22275+static inline void _sg_apply_viewport(int x, int y, int w, int h, bool origin_top_left) {
22276+    #if defined(_SOKOL_ANY_GL)
22277+    _sg_gl_apply_viewport(x, y, w, h, origin_top_left);
22278+    #elif defined(SOKOL_METAL)
22279+    _sg_mtl_apply_viewport(x, y, w, h, origin_top_left);
22280+    #elif defined(SOKOL_D3D11)
22281+    _sg_d3d11_apply_viewport(x, y, w, h, origin_top_left);
22282+    #elif defined(SOKOL_WGPU)
22283+    _sg_wgpu_apply_viewport(x, y, w, h, origin_top_left);
22284+    #elif defined(SOKOL_VULKAN)
22285+    _sg_vk_apply_viewport(x, y, w, h, origin_top_left);
22286+    #elif defined(SOKOL_DUMMY_BACKEND)
22287+    _sg_dummy_apply_viewport(x, y, w, h, origin_top_left);
22288+    #else
22289+    #error("INVALID BACKEND");
22290+    #endif
22291+}
22292+
22293+static inline void _sg_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) {
22294+    #if defined(_SOKOL_ANY_GL)
22295+    _sg_gl_apply_scissor_rect(x, y, w, h, origin_top_left);
22296+    #elif defined(SOKOL_METAL)
22297+    _sg_mtl_apply_scissor_rect(x, y, w, h, origin_top_left);
22298+    #elif defined(SOKOL_D3D11)
22299+    _sg_d3d11_apply_scissor_rect(x, y, w, h, origin_top_left);
22300+    #elif defined(SOKOL_WGPU)
22301+    _sg_wgpu_apply_scissor_rect(x, y, w, h, origin_top_left);
22302+    #elif defined(SOKOL_VULKAN)
22303+    _sg_vk_apply_scissor_rect(x, y, w, h, origin_top_left);
22304+    #elif defined(SOKOL_DUMMY_BACKEND)
22305+    _sg_dummy_apply_scissor_rect(x, y, w, h, origin_top_left);
22306+    #else
22307+    #error("INVALID BACKEND");
22308+    #endif
22309+}
22310+
22311+static inline void _sg_apply_pipeline(_sg_pipeline_t* pip) {
22312+    #if defined(_SOKOL_ANY_GL)
22313+    _sg_gl_apply_pipeline(pip);
22314+    #elif defined(SOKOL_METAL)
22315+    _sg_mtl_apply_pipeline(pip);
22316+    #elif defined(SOKOL_D3D11)
22317+    _sg_d3d11_apply_pipeline(pip);
22318+    #elif defined(SOKOL_WGPU)
22319+    _sg_wgpu_apply_pipeline(pip);
22320+    #elif defined(SOKOL_VULKAN)
22321+    _sg_vk_apply_pipeline(pip);
22322+    #elif defined(SOKOL_DUMMY_BACKEND)
22323+    _sg_dummy_apply_pipeline(pip);
22324+    #else
22325+    #error("INVALID BACKEND");
22326+    #endif
22327+}
22328+
22329+static inline bool _sg_apply_bindings(_sg_bindings_ptrs_t* bnd) {
22330+    #if defined(_SOKOL_ANY_GL)
22331+    return _sg_gl_apply_bindings(bnd);
22332+    #elif defined(SOKOL_METAL)
22333+    return _sg_mtl_apply_bindings(bnd);
22334+    #elif defined(SOKOL_D3D11)
22335+    return _sg_d3d11_apply_bindings(bnd);
22336+    #elif defined(SOKOL_WGPU)
22337+    return _sg_wgpu_apply_bindings(bnd);
22338+    #elif defined(SOKOL_VULKAN)
22339+    return _sg_vk_apply_bindings(bnd);
22340+    #elif defined(SOKOL_DUMMY_BACKEND)
22341+    return _sg_dummy_apply_bindings(bnd);
22342+    #else
22343+    #error("INVALID BACKEND");
22344+    #endif
22345+}
22346+
22347+static inline void _sg_apply_uniforms(int ub_slot, const sg_range* data) {
22348+    #if defined(_SOKOL_ANY_GL)
22349+    _sg_gl_apply_uniforms(ub_slot, data);
22350+    #elif defined(SOKOL_METAL)
22351+    _sg_mtl_apply_uniforms(ub_slot, data);
22352+    #elif defined(SOKOL_D3D11)
22353+    _sg_d3d11_apply_uniforms(ub_slot, data);
22354+    #elif defined(SOKOL_WGPU)
22355+    _sg_wgpu_apply_uniforms(ub_slot, data);
22356+    #elif defined(SOKOL_VULKAN)
22357+    _sg_vk_apply_uniforms(ub_slot, data);
22358+    #elif defined(SOKOL_DUMMY_BACKEND)
22359+    _sg_dummy_apply_uniforms(ub_slot, data);
22360+    #else
22361+    #error("INVALID BACKEND");
22362+    #endif
22363+}
22364+
22365+static inline void _sg_draw(int base_element, int num_elements, int num_instances, int base_vertex, int base_index) {
22366+    #if defined(_SOKOL_ANY_GL)
22367+    _sg_gl_draw(base_element, num_elements, num_instances, base_vertex, base_index);
22368+    #elif defined(SOKOL_METAL)
22369+    _sg_mtl_draw(base_element, num_elements, num_instances, base_vertex, base_index);
22370+    #elif defined(SOKOL_D3D11)
22371+    _sg_d3d11_draw(base_element, num_elements, num_instances, base_vertex, base_index);
22372+    #elif defined(SOKOL_WGPU)
22373+    _sg_wgpu_draw(base_element, num_elements, num_instances, base_vertex, base_index);
22374+    #elif defined(SOKOL_VULKAN)
22375+    _sg_vk_draw(base_element, num_elements, num_instances, base_vertex, base_index);
22376+    #elif defined(SOKOL_DUMMY_BACKEND)
22377+    _sg_dummy_draw(base_element, num_elements, num_instances, base_vertex, base_index);
22378+    #else
22379+    #error("INVALID BACKEND");
22380+    #endif
22381+}
22382+
22383+static inline void _sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
22384+    #if defined(_SOKOL_ANY_GL)
22385+    _sg_gl_dispatch(num_groups_x, num_groups_y, num_groups_z);
22386+    #elif defined(SOKOL_METAL)
22387+    _sg_mtl_dispatch(num_groups_x, num_groups_y, num_groups_z);
22388+    #elif defined(SOKOL_D3D11)
22389+    _sg_d3d11_dispatch(num_groups_x, num_groups_y, num_groups_z);
22390+    #elif defined(SOKOL_WGPU)
22391+    _sg_wgpu_dispatch(num_groups_x, num_groups_y, num_groups_z);
22392+    #elif defined(SOKOL_VULKAN)
22393+    _sg_vk_dispatch(num_groups_x, num_groups_y, num_groups_z);
22394+    #elif defined(SOKOL_DUMMY_BACKEND)
22395+    _sg_dummy_dispatch(num_groups_x, num_groups_y, num_groups_z);
22396+    #else
22397+    #error("INVALID BACKEND");
22398+    #endif
22399+}
22400+
22401+static inline void _sg_commit(void) {
22402+    #if defined(_SOKOL_ANY_GL)
22403+    _sg_gl_commit();
22404+    #elif defined(SOKOL_METAL)
22405+    _sg_mtl_commit();
22406+    #elif defined(SOKOL_D3D11)
22407+    _sg_d3d11_commit();
22408+    #elif defined(SOKOL_WGPU)
22409+    _sg_wgpu_commit();
22410+    #elif defined(SOKOL_VULKAN)
22411+    _sg_vk_commit();
22412+    #elif defined(SOKOL_DUMMY_BACKEND)
22413+    _sg_dummy_commit();
22414+    #else
22415+    #error("INVALID BACKEND");
22416+    #endif
22417+}
22418+
22419+static inline void _sg_update_buffer(_sg_buffer_t* buf, const sg_range* data) {
22420+    #if defined(_SOKOL_ANY_GL)
22421+    _sg_gl_update_buffer(buf, data);
22422+    #elif defined(SOKOL_METAL)
22423+    _sg_mtl_update_buffer(buf, data);
22424+    #elif defined(SOKOL_D3D11)
22425+    _sg_d3d11_update_buffer(buf, data);
22426+    #elif defined(SOKOL_WGPU)
22427+    _sg_wgpu_update_buffer(buf, data);
22428+    #elif defined(SOKOL_VULKAN)
22429+    _sg_vk_update_buffer(buf, data);
22430+    #elif defined(SOKOL_DUMMY_BACKEND)
22431+    _sg_dummy_update_buffer(buf, data);
22432+    #else
22433+    #error("INVALID BACKEND");
22434+    #endif
22435+}
22436+
22437+static inline void _sg_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) {
22438+    #if defined(_SOKOL_ANY_GL)
22439+    _sg_gl_append_buffer(buf, data, new_frame);
22440+    #elif defined(SOKOL_METAL)
22441+    _sg_mtl_append_buffer(buf, data, new_frame);
22442+    #elif defined(SOKOL_D3D11)
22443+    _sg_d3d11_append_buffer(buf, data, new_frame);
22444+    #elif defined(SOKOL_WGPU)
22445+    _sg_wgpu_append_buffer(buf, data, new_frame);
22446+    #elif defined(SOKOL_VULKAN)
22447+    _sg_vk_append_buffer(buf, data, new_frame);
22448+    #elif defined(SOKOL_DUMMY_BACKEND)
22449+    _sg_dummy_append_buffer(buf, data, new_frame);
22450+    #else
22451+    #error("INVALID BACKEND");
22452+    #endif
22453+}
22454+
22455+static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data) {
22456+    #if defined(_SOKOL_ANY_GL)
22457+    _sg_gl_update_image(img, data);
22458+    #elif defined(SOKOL_METAL)
22459+    _sg_mtl_update_image(img, data);
22460+    #elif defined(SOKOL_D3D11)
22461+    _sg_d3d11_update_image(img, data);
22462+    #elif defined(SOKOL_WGPU)
22463+    _sg_wgpu_update_image(img, data);
22464+    #elif defined(SOKOL_VULKAN)
22465+    _sg_vk_update_image(img, data);
22466+    #elif defined(SOKOL_DUMMY_BACKEND)
22467+    _sg_dummy_update_image(img, data);
22468+    #else
22469+    #error("INVALID BACKEND");
22470+    #endif
22471+}
22472+
22473+static inline void _sg_push_debug_group(const char* name) {
22474+    #if defined(SOKOL_METAL)
22475+    _sg_mtl_push_debug_group(name);
22476+    #else
22477+    _SOKOL_UNUSED(name);
22478+    #endif
22479+}
22480+
22481+static inline void _sg_pop_debug_group(void) {
22482+    #if defined(SOKOL_METAL)
22483+    _sg_mtl_pop_debug_group();
22484+    #endif
22485+}
22486+
22487+// ██    ██  █████  ██      ██ ██████   █████  ████████ ██  ██████  ███    ██
22488+// ██    ██ ██   ██ ██      ██ ██   ██ ██   ██    ██    ██ ██    ██ ████   ██
22489+// ██    ██ ███████ ██      ██ ██   ██ ███████    ██    ██ ██    ██ ██ ██  ██
22490+//  ██  ██  ██   ██ ██      ██ ██   ██ ██   ██    ██    ██ ██    ██ ██  ██ ██
22491+//   ████   ██   ██ ███████ ██ ██████  ██   ██    ██    ██  ██████  ██   ████
22492+//
22493+// >>validation
22494+#if defined(SOKOL_DEBUG)
22495+_SOKOL_PRIVATE void _sg_validate_begin(void) {
22496+    _sg.validate_error = SG_LOGITEM_OK;
22497+}
22498+
22499+_SOKOL_PRIVATE bool _sg_validate_end(void) {
22500+    if (_sg.validate_error != SG_LOGITEM_OK) {
22501+        #if !defined(SOKOL_VALIDATE_NON_FATAL)
22502+            _SG_PANIC(VALIDATION_FAILED);
22503+            return false;
22504+        #else
22505+            return false;
22506+        #endif
22507+    } else {
22508+        return true;
22509+    }
22510+}
22511+#endif
22512+
22513+_SOKOL_PRIVATE bool _sg_one(bool b0, bool b1, bool b2) {
22514+    return (b0 && !b1 && !b2) || (!b0 && b1 && !b2) || (!b0 && !b1 && b2);
22515+}
22516+
22517+_SOKOL_PRIVATE bool _sg_validate_buffer_desc(const sg_buffer_desc* desc) {
22518+    #if !defined(SOKOL_DEBUG)
22519+        _SOKOL_UNUSED(desc);
22520+        return true;
22521+    #else
22522+        if (_sg.desc.disable_validation) {
22523+            return true;
22524+        }
22525+        SOKOL_ASSERT(desc);
22526+        _sg_validate_begin();
22527+        _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_BUFFERDESC_CANARY);
22528+        _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_BUFFERDESC_CANARY);
22529+        _SG_VALIDATE(desc->size > 0, VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE);
22530+        _SG_VALIDATE(_sg_one(desc->usage.immutable, desc->usage.dynamic_update, desc->usage.stream_update), VALIDATE_BUFFERDESC_IMMUTABLE_DYNAMIC_STREAM);
22531+        if (_sg.features.separate_buffer_types) {
22532+            _SG_VALIDATE(_sg_one(desc->usage.vertex_buffer, desc->usage.index_buffer, desc->usage.storage_buffer), VALIDATE_BUFFERDESC_SEPARATE_BUFFER_TYPES);
22533+        }
22534+        bool injected = (0 != desc->gl_buffers[0]) ||
22535+                        (0 != desc->mtl_buffers[0]) ||
22536+                        (0 != desc->d3d11_buffer) ||
22537+                        (0 != desc->wgpu_buffer);
22538+        if (!injected && desc->usage.immutable) {
22539+            if (desc->data.ptr) {
22540+                _SG_VALIDATE(desc->size == desc->data.size, VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE);
22541+            } else {
22542+                _SG_VALIDATE(desc->usage.storage_buffer, VALIDATE_BUFFERDESC_EXPECT_DATA);
22543+                _SG_VALIDATE(desc->data.size == 0, VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE);
22544+            }
22545+        } else {
22546+            _SG_VALIDATE(0 == desc->data.ptr, VALIDATE_BUFFERDESC_EXPECT_NO_DATA);
22547+            _SG_VALIDATE(desc->data.size == 0, VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE);
22548+        }
22549+        if (desc->usage.storage_buffer) {
22550+            _SG_VALIDATE(_sg.features.compute, VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED);
22551+            _SG_VALIDATE(_sg_multiple_u64(desc->size, 4), VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4);
22552+        }
22553+        return _sg_validate_end();
22554+    #endif
22555+}
22556+
22557+_SOKOL_PRIVATE void _sg_validate_image_data(const sg_image_data* data, sg_pixel_format fmt, int width, int height, int num_mips, int num_slices) {
22558+    #if !defined(SOKOL_DEBUG)
22559+        _SOKOL_UNUSED(data);
22560+        _SOKOL_UNUSED(fmt);
22561+        _SOKOL_UNUSED(width);
22562+        _SOKOL_UNUSED(height);
22563+        _SOKOL_UNUSED(num_mips);
22564+        _SOKOL_UNUSED(num_slices);
22565+    #else
22566+        for (int mip_index = 0; mip_index < num_mips; mip_index++) {
22567+            const bool has_data = data->mip_levels[mip_index].ptr != 0;
22568+            const bool has_size = data->mip_levels[mip_index].size > 0;
22569+            _SG_VALIDATE(has_data && has_size, VALIDATE_IMAGEDATA_NODATA);
22570+            const int mip_width = _sg_miplevel_dim(width, mip_index);
22571+            const int mip_height = _sg_miplevel_dim(height, mip_index);
22572+            const int bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, 1);
22573+            const int expected_size = bytes_per_slice * num_slices;
22574+            _SG_VALIDATE(expected_size == (int)data->mip_levels[mip_index].size, VALIDATE_IMAGEDATA_DATA_SIZE);
22575+        }
22576+    #endif
22577+}
22578+
22579+_SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) {
22580+    #if !defined(SOKOL_DEBUG)
22581+        _SOKOL_UNUSED(desc);
22582+        return true;
22583+    #else
22584+        if (_sg.desc.disable_validation) {
22585+            return true;
22586+        }
22587+        SOKOL_ASSERT(desc);
22588+        const sg_image_usage* usg = &desc->usage;
22589+        const bool any_attachment = usg->color_attachment || usg->resolve_attachment || usg->depth_stencil_attachment;
22590+        _sg_validate_begin();
22591+        _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_IMAGEDESC_CANARY);
22592+        _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_IMAGEDESC_CANARY);
22593+        _SG_VALIDATE(_sg_one(usg->immutable, usg->dynamic_update, usg->stream_update), VALIDATE_IMAGEDESC_IMMUTABLE_DYNAMIC_STREAM);
22594+        _SG_VALIDATE(!(usg->color_attachment && usg->depth_stencil_attachment), VALIDATE_IMAGEDESC_ATTACHMENT_COLOR_DEPTH_STENCIL);
22595+        switch (desc->type) {
22596+            case SG_IMAGETYPE_2D:
22597+                _SG_VALIDATE(desc->num_slices == 1, VALIDATE_IMAGEDESC_IMAGETYPE_2D_NUMSLICES);
22598+                break;
22599+            case SG_IMAGETYPE_CUBE:
22600+                _SG_VALIDATE(desc->num_slices == 6, VALIDATE_IMAGEDESC_IMAGETYPE_CUBE_NUMSLICES);
22601+                break;
22602+            case SG_IMAGETYPE_ARRAY:
22603+                _SG_VALIDATE((desc->num_slices >= 1) && (desc->num_slices <= _sg.limits.max_image_array_layers), VALIDATE_IMAGEDESC_IMAGETYPE_ARRAY_NUMSLICES);
22604+                break;
22605+            case SG_IMAGETYPE_3D:
22606+                _SG_VALIDATE((desc->num_slices >= 1) && (desc->num_slices <= _sg.limits.max_image_size_3d), VALIDATE_IMAGEDESC_IMAGETYPE_3D_NUMSLICES);
22607+                break;
22608+            default:
22609+                SOKOL_UNREACHABLE;
22610+                break;
22611+        }
22612+        _SG_VALIDATE(desc->width > 0, VALIDATE_IMAGEDESC_WIDTH);
22613+        _SG_VALIDATE(desc->height > 0, VALIDATE_IMAGEDESC_HEIGHT);
22614+        const sg_pixel_format fmt = desc->pixel_format;
22615+        const bool injected = (0 != desc->gl_textures[0]) ||
22616+                              (0 != desc->mtl_textures[0]) ||
22617+                              (0 != desc->d3d11_texture) ||
22618+                              (0 != desc->wgpu_texture);
22619+        if (_sg_is_depth_or_depth_stencil_format(fmt)) {
22620+            _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE);
22621+        }
22622+        if (any_attachment || usg->storage_image) {
22623+            SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM));
22624+            _SG_VALIDATE(usg->immutable, VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_IMMUTABLE);
22625+            _SG_VALIDATE(desc->data.mip_levels[0].ptr==0, VALIDATE_IMAGEDESC_ATTACHMENT_EXPECT_NO_DATA);
22626+            if (any_attachment) {
22627+                _SG_VALIDATE(_sg.formats[fmt].render, VALIDATE_IMAGEDESC_ATTACHMENT_PIXELFORMAT);
22628+                if (usg->resolve_attachment) {
22629+                    _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_ATTACHMENT_RESOLVE_EXPECT_NO_MSAA);
22630+                }
22631+                if (desc->sample_count > 1) {
22632+                    _SG_VALIDATE(_sg.formats[fmt].msaa, VALIDATE_IMAGEDESC_ATTACHMENT_NO_MSAA_SUPPORT);
22633+                    _SG_VALIDATE(desc->num_mipmaps == 1, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_NUM_MIPMAPS);
22634+                    _SG_VALIDATE(desc->type != SG_IMAGETYPE_ARRAY, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_ARRAY_IMAGE);
22635+                    _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_3D_IMAGE);
22636+                    _SG_VALIDATE(desc->type != SG_IMAGETYPE_CUBE, VALIDATE_IMAGEDESC_ATTACHMENT_MSAA_CUBE_IMAGE);
22637+                }
22638+            } else if (usg->storage_image) {
22639+                _SG_VALIDATE(_sg_is_valid_storage_image_format(fmt), VALIDATE_IMAGEDESC_STORAGEIMAGE_PIXELFORMAT);
22640+                // D3D11 doesn't allow multisampled UAVs (see: https://github.com/gpuweb/gpuweb/issues/513)
22641+                _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_STORAGEIMAGE_EXPECT_NO_MSAA);
22642+            }
22643+        } else {
22644+            _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_MSAA_BUT_NO_ATTACHMENT);
22645+            const bool valid_nonrt_fmt = !_sg_is_valid_attachment_depth_format(fmt);
22646+            _SG_VALIDATE(valid_nonrt_fmt, VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT);
22647+            const bool is_compressed = _sg_is_compressed_pixel_format(desc->pixel_format);
22648+            if (is_compressed) {
22649+                _SG_VALIDATE(usg->immutable, VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE);
22650+            }
22651+            if (!injected && usg->immutable) {
22652+                // image desc must have valid data
22653+                _sg_validate_image_data(&desc->data,
22654+                    desc->pixel_format,
22655+                    desc->width,
22656+                    desc->height,
22657+                    desc->num_mipmaps,
22658+                    desc->num_slices);
22659+            } else {
22660+                // image desc must not have data
22661+                for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) {
22662+                    const bool no_data = 0 == desc->data.mip_levels[mip_index].ptr;
22663+                    const bool no_size = 0 == desc->data.mip_levels[mip_index].size;
22664+                    if (injected) {
22665+                        _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_INJECTED_NO_DATA);
22666+                    }
22667+                    if (!usg->immutable) {
22668+                        _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA);
22669+                    }
22670+                }
22671+            }
22672+        }
22673+        return _sg_validate_end();
22674+    #endif
22675+}
22676+
22677+_SOKOL_PRIVATE bool _sg_validate_sampler_desc(const sg_sampler_desc* desc) {
22678+    #if !defined(SOKOL_DEBUG)
22679+        _SOKOL_UNUSED(desc);
22680+        return true;
22681+    #else
22682+        if (_sg.desc.disable_validation) {
22683+            return true;
22684+        }
22685+        SOKOL_ASSERT(desc);
22686+        _sg_validate_begin();
22687+        _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SAMPLERDESC_CANARY);
22688+        _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SAMPLERDESC_CANARY);
22689+        // restriction from WebGPU: when anisotropy > 1, all filters must be linear
22690+        if (desc->max_anisotropy > 1) {
22691+            _SG_VALIDATE((desc->min_filter == SG_FILTER_LINEAR)
22692+                      && (desc->mag_filter == SG_FILTER_LINEAR)
22693+                      && (desc->mipmap_filter == SG_FILTER_LINEAR),
22694+                      VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING);
22695+        }
22696+        return _sg_validate_end();
22697+    #endif
22698+}
22699+
22700+typedef struct {
22701+    uint64_t lo, hi;
22702+} _sg_u128_t;
22703+
22704+_SOKOL_PRIVATE _sg_u128_t _sg_u128(void) {
22705+    _SG_STRUCT(_sg_u128_t, res);
22706+    return res;
22707+}
22708+
22709+_SOKOL_PRIVATE _sg_u128_t _sg_validate_set_slot_bit(_sg_u128_t bits, sg_shader_stage stage, uint8_t slot) {
22710+    switch (stage) {
22711+        case SG_SHADERSTAGE_NONE:
22712+            SOKOL_ASSERT(slot < 128);
22713+            if (slot < 64) {
22714+                bits.lo |= 1ULL << slot;
22715+            } else {
22716+                bits.hi |= 1ULL << (slot - 64);
22717+            }
22718+            break;
22719+        case SG_SHADERSTAGE_VERTEX:
22720+            SOKOL_ASSERT(slot < 64);
22721+            bits.lo |= 1ULL << slot;
22722+            break;
22723+        case SG_SHADERSTAGE_FRAGMENT:
22724+            SOKOL_ASSERT(slot < 64);
22725+            bits.hi |= 1ULL << slot;
22726+            break;
22727+        case SG_SHADERSTAGE_COMPUTE:
22728+            SOKOL_ASSERT(slot < 64);
22729+            bits.lo |= 1ULL << slot;
22730+            break;
22731+        default:
22732+            SOKOL_UNREACHABLE;
22733+            break;
22734+    }
22735+    return bits;
22736+}
22737+
22738+_SOKOL_PRIVATE bool _sg_validate_slot_bits(_sg_u128_t bits, sg_shader_stage stage, uint8_t slot) {
22739+    _sg_u128_t mask = _sg_u128();
22740+    switch (stage) {
22741+        case SG_SHADERSTAGE_NONE:
22742+            SOKOL_ASSERT(slot < 128);
22743+            if (slot < 64) {
22744+                mask.lo = 1ULL << slot;
22745+            } else {
22746+                mask.hi = 1ULL << (slot - 64);
22747+            }
22748+            break;
22749+        case SG_SHADERSTAGE_VERTEX:
22750+            SOKOL_ASSERT(slot < 64);
22751+            mask.lo = 1ULL << slot;
22752+            break;
22753+        case SG_SHADERSTAGE_FRAGMENT:
22754+            SOKOL_ASSERT(slot < 64);
22755+            mask.hi = 1ULL << slot;
22756+            break;
22757+        case SG_SHADERSTAGE_COMPUTE:
22758+            SOKOL_ASSERT(slot < 64);
22759+            mask.lo = 1ULL << slot;
22760+            break;
22761+        default:
22762+            SOKOL_UNREACHABLE;
22763+            break;
22764+    }
22765+    return ((bits.lo & mask.lo) == 0) && ((bits.hi & mask.hi) == 0);
22766+}
22767+
22768+_SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) {
22769+    #if !defined(SOKOL_DEBUG)
22770+        _SOKOL_UNUSED(desc);
22771+        return true;
22772+    #else
22773+        if (_sg.desc.disable_validation) {
22774+            return true;
22775+        }
22776+        SOKOL_ASSERT(desc);
22777+        bool is_compute_shader = (desc->compute_func.source != 0) || (desc->compute_func.bytecode.ptr != 0);
22778+        _sg_validate_begin();
22779+        _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SHADERDESC_CANARY);
22780+        _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SHADERDESC_CANARY);
22781+        #if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) || defined(SOKOL_WGPU)
22782+            // on GL or WebGPU, must provide shader source code
22783+            if (is_compute_shader) {
22784+                _SG_VALIDATE(0 != desc->compute_func.source, VALIDATE_SHADERDESC_COMPUTE_SOURCE);
22785+            } else {
22786+                _SG_VALIDATE(0 != desc->vertex_func.source, VALIDATE_SHADERDESC_VERTEX_SOURCE);
22787+                _SG_VALIDATE(0 != desc->fragment_func.source, VALIDATE_SHADERDESC_FRAGMENT_SOURCE);
22788+            }
22789+        #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11)
22790+            // on Metal or D3D11, must provide shader source code or byte code
22791+            if (is_compute_shader) {
22792+                _SG_VALIDATE((0 != desc->compute_func.source) || (0 != desc->compute_func.bytecode.ptr), VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE);
22793+            } else {
22794+                _SG_VALIDATE((0 != desc->vertex_func.source)|| (0 != desc->vertex_func.bytecode.ptr), VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE);
22795+                _SG_VALIDATE((0 != desc->fragment_func.source) || (0 != desc->fragment_func.bytecode.ptr), VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE);
22796+            }
22797+        #else
22798+            // Dummy Backend, don't require source or bytecode
22799+        #endif
22800+        if (is_compute_shader) {
22801+            _SG_VALIDATE((0 == desc->vertex_func.source) && (0 == desc->vertex_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO);
22802+            _SG_VALIDATE((0 == desc->fragment_func.source) && (0 == desc->fragment_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO);
22803+        } else {
22804+            _SG_VALIDATE((0 == desc->compute_func.source) && (0 == desc->compute_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO);
22805+        }
22806+        #if defined(SOKOL_METAL)
22807+        if (is_compute_shader) {
22808+            int x = desc->mtl_threads_per_threadgroup.x;
22809+            int y = desc->mtl_threads_per_threadgroup.y;
22810+            int z = desc->mtl_threads_per_threadgroup.z;
22811+            _SG_VALIDATE((x > 0) && (y > 0) && (z > 0), VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_INITIALIZED);
22812+            _SG_VALIDATE(((x * y * z) & 31) == 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP_MULTIPLE_32);
22813+        }
22814+        #endif
22815+        for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) {
22816+            if (desc->attrs[i].glsl_name) {
22817+                _SG_VALIDATE(strlen(desc->attrs[i].glsl_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG);
22818+            }
22819+            if (desc->attrs[i].hlsl_sem_name) {
22820+                _SG_VALIDATE(strlen(desc->attrs[i].hlsl_sem_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG);
22821+            }
22822+        }
22823+        // if shader byte code, the size must also be provided
22824+        if (0 != desc->vertex_func.bytecode.ptr) {
22825+            _SG_VALIDATE(desc->vertex_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE);
22826+        }
22827+        if (0 != desc->fragment_func.bytecode.ptr) {
22828+            _SG_VALIDATE(desc->fragment_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE);
22829+        }
22830+        if (0 != desc->compute_func.bytecode.ptr) {
22831+            _SG_VALIDATE(desc->compute_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE);
22832+        }
22833+
22834+        #if defined(SOKOL_METAL)
22835+        _sg_u128_t msl_buf_bits = _sg_u128();
22836+        _sg_u128_t msl_tex_bits = _sg_u128();
22837+        _sg_u128_t msl_smp_bits = _sg_u128();
22838+        #elif defined(SOKOL_D3D11)
22839+        _sg_u128_t hlsl_buf_bits = _sg_u128();
22840+        _sg_u128_t hlsl_srv_bits = _sg_u128();
22841+        _sg_u128_t hlsl_uav_bits = _sg_u128();
22842+        _sg_u128_t hlsl_smp_bits = _sg_u128();
22843+        #elif defined(_SOKOL_ANY_GL)
22844+        _sg_u128_t glsl_sbuf_bnd_bits = _sg_u128();
22845+        _sg_u128_t glsl_simg_bnd_bits = _sg_u128();
22846+        #elif defined(SOKOL_WGPU)
22847+        _sg_u128_t wgsl_group0_bits = _sg_u128();
22848+        _sg_u128_t wgsl_group1_bits = _sg_u128();
22849+        #elif defined(SOKOL_VULKAN)
22850+        _sg_u128_t spirv_set0_bits = _sg_u128();
22851+        _sg_u128_t spirv_set1_bits = _sg_u128();
22852+        #endif
22853+        for (size_t ub_idx = 0; ub_idx < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_idx++) {
22854+            const sg_shader_uniform_block* ub_desc = &desc->uniform_blocks[ub_idx];
22855+            if (ub_desc->stage == SG_SHADERSTAGE_NONE) {
22856+                continue;
22857+            }
22858+            _SG_VALIDATE(ub_desc->size > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO);
22859+            #if defined(SOKOL_METAL)
22860+            _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, ub_desc->stage, ub_desc->msl_buffer_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION);
22861+            msl_buf_bits = _sg_validate_set_slot_bit(msl_buf_bits, ub_desc->stage, ub_desc->msl_buffer_n);
22862+            #elif defined(SOKOL_D3D11)
22863+            _SG_VALIDATE(_sg_validate_slot_bits(hlsl_buf_bits, ub_desc->stage, ub_desc->hlsl_register_b_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION);
22864+            hlsl_buf_bits = _sg_validate_set_slot_bit(hlsl_buf_bits, ub_desc->stage, ub_desc->hlsl_register_b_n);
22865+            #elif defined(SOKOL_WGPU)
22866+            _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group0_bits, SG_SHADERSTAGE_NONE, ub_desc->wgsl_group0_binding_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION);
22867+            wgsl_group0_bits = _sg_validate_set_slot_bit(wgsl_group0_bits, SG_SHADERSTAGE_NONE, ub_desc->wgsl_group0_binding_n);
22868+            #elif defined(SOKOL_VULKAN)
22869+            _SG_VALIDATE(_sg_validate_slot_bits(spirv_set0_bits, SG_SHADERSTAGE_NONE, ub_desc->spirv_set0_binding_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_SPIRV_SET0_BINDING_COLLISION);
22870+            spirv_set0_bits = _sg_validate_set_slot_bit(spirv_set0_bits, SG_SHADERSTAGE_NONE, ub_desc->spirv_set0_binding_n);
22871+            #endif
22872+            #if defined(_SOKOL_ANY_GL)
22873+            bool uniforms_continuous = true;
22874+            uint32_t uniform_offset = 0;
22875+            int num_uniforms = 0;
22876+            for (size_t u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) {
22877+                const sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index];
22878+                if (u_desc->type != SG_UNIFORMTYPE_INVALID) {
22879+                    _SG_VALIDATE(uniforms_continuous, VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS);
22880+                    _SG_VALIDATE(u_desc->glsl_name, VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME);
22881+                    const int array_count = u_desc->array_count;
22882+                    _SG_VALIDATE(array_count > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT);
22883+                    const uint32_t u_align = _sg_uniform_alignment(u_desc->type, array_count, ub_desc->layout);
22884+                    const uint32_t u_size  = _sg_uniform_size(u_desc->type, array_count, ub_desc->layout);
22885+                    uniform_offset = _sg_align_u32(uniform_offset, u_align);
22886+                    uniform_offset += u_size;
22887+                    num_uniforms++;
22888+                    // with std140, arrays are only allowed for FLOAT4, INT4, MAT4
22889+                    if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) {
22890+                        if (array_count > 1) {
22891+                            _SG_VALIDATE((u_desc->type == SG_UNIFORMTYPE_FLOAT4) || (u_desc->type == SG_UNIFORMTYPE_INT4) || (u_desc->type == SG_UNIFORMTYPE_MAT4), VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE);
22892+                        }
22893+                    }
22894+                } else {
22895+                    uniforms_continuous = false;
22896+                }
22897+            }
22898+            if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) {
22899+                uniform_offset = _sg_align_u32(uniform_offset, 16);
22900+            }
22901+            _SG_VALIDATE((size_t)uniform_offset == ub_desc->size, VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH);
22902+            _SG_VALIDATE(num_uniforms > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS);
22903+            #endif
22904+        }
22905+
22906+        uint32_t texview_slot_mask = 0;
22907+        for (size_t view_idx = 0; view_idx < SG_MAX_VIEW_BINDSLOTS; view_idx++) {
22908+            const sg_shader_view* view_desc = &desc->views[view_idx];
22909+            if (view_desc->texture.stage != SG_SHADERSTAGE_NONE) {
22910+                const sg_shader_texture_view* tex_desc = &view_desc->texture;
22911+                texview_slot_mask |= (1 << view_idx);
22912+                #if defined(SOKOL_METAL)
22913+                _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, tex_desc->stage, tex_desc->msl_texture_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_METAL_TEXTURE_SLOT_COLLISION);
22914+                msl_tex_bits = _sg_validate_set_slot_bit(msl_tex_bits, tex_desc->stage, tex_desc->msl_texture_n);
22915+                #elif defined(SOKOL_D3D11)
22916+                _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, tex_desc->stage, tex_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_HLSL_REGISTER_T_COLLISION);
22917+                hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, tex_desc->stage, tex_desc->hlsl_register_t_n);
22918+                #elif defined(SOKOL_WGPU)
22919+                _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, tex_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_WGSL_GROUP1_BINDING_COLLISION);
22920+                wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, tex_desc->wgsl_group1_binding_n);
22921+                #elif defined(SOKOL_VULKAN)
22922+                _SG_VALIDATE(_sg_validate_slot_bits(spirv_set1_bits, SG_SHADERSTAGE_NONE, tex_desc->spirv_set1_binding_n), VALIDATE_SHADERDESC_VIEW_TEXTURE_SPIRV_SET1_BINDING_COLLISION);
22923+                spirv_set1_bits = _sg_validate_set_slot_bit(spirv_set1_bits, SG_SHADERSTAGE_NONE, tex_desc->spirv_set1_binding_n);
22924+                #elif defined(SOKOL_DUMMY_BACKEND) || defined(_SOKOL_ANY_GL)
22925+                _SOKOL_UNUSED(tex_desc);
22926+                #endif
22927+            } else if (view_desc->storage_buffer.stage != SG_SHADERSTAGE_NONE) {
22928+                const sg_shader_storage_buffer_view* sbuf_desc = &view_desc->storage_buffer;
22929+                #if defined(SOKOL_METAL)
22930+                _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION);
22931+                msl_buf_bits = _sg_validate_set_slot_bit(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n);
22932+                #elif defined(SOKOL_D3D11)
22933+                if (sbuf_desc->readonly) {
22934+                    _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION);
22935+                    hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n);
22936+                } else {
22937+                    _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION);
22938+                    hlsl_uav_bits = _sg_validate_set_slot_bit(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n);
22939+                }
22940+                #elif defined(_SOKOL_ANY_GL)
22941+                _SG_VALIDATE(_sg_validate_slot_bits(glsl_sbuf_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_GLSL_BINDING_COLLISION);
22942+                glsl_sbuf_bnd_bits = _sg_validate_set_slot_bit(glsl_sbuf_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n);
22943+                #elif defined(SOKOL_WGPU)
22944+                _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION);
22945+                wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n);
22946+                #elif defined(SOKOL_VULKAN)
22947+                _SG_VALIDATE(_sg_validate_slot_bits(spirv_set1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->spirv_set1_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEBUFFER_SPIRV_SET1_BINDING_COLLISION);
22948+                spirv_set1_bits = _sg_validate_set_slot_bit(spirv_set1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->spirv_set1_binding_n);
22949+                #elif defined(SOKOL_DUMMY_BACKEND)
22950+                _SOKOL_UNUSED(sbuf_desc);
22951+                #endif
22952+            } else if (view_desc->storage_image.stage != SG_SHADERSTAGE_NONE) {
22953+                const sg_shader_storage_image_view* simg_desc = &view_desc->storage_image;
22954+                _SG_VALIDATE(simg_desc->stage == SG_SHADERSTAGE_COMPUTE, VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_EXPECT_COMPUTE_STAGE);
22955+                #if defined(SOKOL_METAL)
22956+                _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, simg_desc->stage, simg_desc->msl_texture_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_METAL_TEXTURE_SLOT_COLLISION);
22957+                msl_tex_bits = _sg_validate_set_slot_bit(msl_tex_bits, simg_desc->stage, simg_desc->msl_texture_n);
22958+                #elif defined(SOKOL_D3D11)
22959+                _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, simg_desc->stage, simg_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_HLSL_REGISTER_U_COLLISION);
22960+                hlsl_uav_bits = _sg_validate_set_slot_bit(hlsl_uav_bits, simg_desc->stage, simg_desc->hlsl_register_u_n);
22961+                #elif defined(_SOKOL_ANY_GL)
22962+                _SG_VALIDATE(_sg_validate_slot_bits(glsl_simg_bnd_bits, SG_SHADERSTAGE_NONE, simg_desc->glsl_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_GLSL_BINDING_COLLISION);
22963+                glsl_simg_bnd_bits = _sg_validate_set_slot_bit(glsl_simg_bnd_bits, SG_SHADERSTAGE_NONE, simg_desc->glsl_binding_n);
22964+                #elif defined(SOKOL_WGPU)
22965+                _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, simg_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_WGSL_GROUP1_BINDING_COLLISION);
22966+                wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, simg_desc->wgsl_group1_binding_n);
22967+                #elif defined(SOKOL_VULKAN)
22968+                _SG_VALIDATE(_sg_validate_slot_bits(spirv_set1_bits, SG_SHADERSTAGE_NONE, simg_desc->spirv_set1_binding_n), VALIDATE_SHADERDESC_VIEW_STORAGEIMAGE_SPIRV_SET1_BINDING_COLLISION);
22969+                spirv_set1_bits = _sg_validate_set_slot_bit(spirv_set1_bits, SG_SHADERSTAGE_NONE, simg_desc->spirv_set1_binding_n);
22970+                #endif
22971+            }
22972+        }
22973+
22974+        uint32_t smp_slot_mask = 0;
22975+        for (size_t smp_idx = 0; smp_idx < SG_MAX_SAMPLER_BINDSLOTS; smp_idx++) {
22976+            const sg_shader_sampler* smp_desc = &desc->samplers[smp_idx];
22977+            if (smp_desc->stage == SG_SHADERSTAGE_NONE) {
22978+                continue;
22979+            }
22980+            smp_slot_mask |= (1 << smp_idx);
22981+            #if defined(SOKOL_METAL)
22982+            _SG_VALIDATE(_sg_validate_slot_bits(msl_smp_bits, smp_desc->stage, smp_desc->msl_sampler_n), VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION);
22983+            msl_smp_bits = _sg_validate_set_slot_bit(msl_smp_bits, smp_desc->stage, smp_desc->msl_sampler_n);
22984+            #elif defined(SOKOL_D3D11)
22985+            _SG_VALIDATE(_sg_validate_slot_bits(hlsl_smp_bits, smp_desc->stage, smp_desc->hlsl_register_s_n), VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION);
22986+            hlsl_smp_bits = _sg_validate_set_slot_bit(hlsl_smp_bits, smp_desc->stage, smp_desc->hlsl_register_s_n);
22987+            #elif defined(SOKOL_WGPU)
22988+            _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, smp_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION);
22989+            wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, smp_desc->wgsl_group1_binding_n);
22990+            #elif defined(SOKOL_VULKAN)
22991+            _SG_VALIDATE(_sg_validate_slot_bits(spirv_set1_bits, SG_SHADERSTAGE_NONE, smp_desc->spirv_set1_binding_n), VALIDATE_SHADERDESC_SAMPLER_SPIRV_SET1_BINDING_COLLISION);
22992+            spirv_set1_bits = _sg_validate_set_slot_bit(spirv_set1_bits, SG_SHADERSTAGE_NONE, smp_desc->spirv_set1_binding_n);
22993+            #endif
22994+        }
22995+
22996+        uint32_t ref_texview_slot_mask = 0;
22997+        uint32_t ref_smp_slot_mask = 0;
22998+        for (size_t tex_smp_idx = 0; tex_smp_idx < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_idx++) {
22999+            const sg_shader_texture_sampler_pair* tex_smp_desc = &desc->texture_sampler_pairs[tex_smp_idx];
23000+            if (tex_smp_desc->stage == SG_SHADERSTAGE_NONE) {
23001+                continue;
23002+            }
23003+            #if defined(_SOKOL_ANY_GL)
23004+            _SG_VALIDATE(tex_smp_desc->glsl_name != 0, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_GLSL_NAME);
23005+            #endif
23006+            const bool view_slot_in_range = tex_smp_desc->view_slot < SG_MAX_VIEW_BINDSLOTS;
23007+            const bool smp_slot_in_range = tex_smp_desc->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS;
23008+            _SG_VALIDATE(view_slot_in_range, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_VIEW_SLOT_OUT_OF_RANGE);
23009+            _SG_VALIDATE(smp_slot_in_range, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE);
23010+            if (view_slot_in_range && smp_slot_in_range) {
23011+                ref_texview_slot_mask |= 1 << tex_smp_desc->view_slot;
23012+                ref_smp_slot_mask |= 1 << tex_smp_desc->sampler_slot;
23013+                const sg_shader_view* view_desc = &desc->views[tex_smp_desc->view_slot];
23014+                const sg_shader_sampler* smp_desc = &desc->samplers[tex_smp_desc->sampler_slot];
23015+                _SG_VALIDATE(view_desc->texture.stage != SG_SHADERSTAGE_NONE, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_EXPECT_TEXTURE_VIEW);
23016+                _SG_VALIDATE(view_desc->texture.stage == tex_smp_desc->stage, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_TEXTURE_STAGE_MISMATCH);
23017+                _SG_VALIDATE(smp_desc->stage == tex_smp_desc->stage, VALIDATE_SHADERDESC_TEXTURE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH);
23018+                const bool needs_nonfiltering = (view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_UINT)
23019+                                             || (view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_SINT)
23020+                                             || (view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT);
23021+                const bool needs_comparison = view_desc->texture.sample_type == SG_IMAGESAMPLETYPE_DEPTH;
23022+                if (needs_nonfiltering) {
23023+                    _SG_VALIDATE(needs_nonfiltering && (smp_desc->sampler_type == SG_SAMPLERTYPE_NONFILTERING), VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED);
23024+                }
23025+                if (needs_comparison) {
23026+                    _SG_VALIDATE(needs_comparison && (smp_desc->sampler_type == SG_SAMPLERTYPE_COMPARISON), VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED);
23027+                }
23028+            }
23029+        }
23030+        // each image and sampler must be referenced by an image sampler
23031+        _SG_VALIDATE(texview_slot_mask == ref_texview_slot_mask, VALIDATE_SHADERDESC_TEXVIEW_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS);
23032+        _SG_VALIDATE(smp_slot_mask == ref_smp_slot_mask, VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_TEXTURE_SAMPLER_PAIRS);
23033+
23034+        return _sg_validate_end();
23035+    #endif
23036+}
23037+
23038+_SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) {
23039+    #if !defined(SOKOL_DEBUG)
23040+        _SOKOL_UNUSED(desc);
23041+        return true;
23042+    #else
23043+        if (_sg.desc.disable_validation) {
23044+            return true;
23045+        }
23046+        SOKOL_ASSERT(desc);
23047+        _sg_validate_begin();
23048+        _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PIPELINEDESC_CANARY);
23049+        _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PIPELINEDESC_CANARY);
23050+        _SG_VALIDATE(desc->shader.id != SG_INVALID_ID, VALIDATE_PIPELINEDESC_SHADER);
23051+        const _sg_shader_t* shd = _sg_lookup_shader(desc->shader.id);
23052+        _SG_VALIDATE(0 != shd, VALIDATE_PIPELINEDESC_SHADER);
23053+        if (shd) {
23054+            _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PIPELINEDESC_SHADER);
23055+            if (desc->compute) {
23056+                _SG_VALIDATE(shd->cmn.is_compute, VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED);
23057+            } else {
23058+                _SG_VALIDATE(!shd->cmn.is_compute, VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED);
23059+                bool attrs_cont = true;
23060+                for (size_t attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
23061+                    const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index];
23062+                    if (a_state->format == SG_VERTEXFORMAT_INVALID) {
23063+                        attrs_cont = false;
23064+                        continue;
23065+                    }
23066+                    _SG_VALIDATE(attrs_cont, VALIDATE_PIPELINEDESC_NO_CONT_ATTRS);
23067+                    SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS);
23068+                    // vertex format must match expected shader attribute base type (if provided)
23069+                    if (shd->cmn.attrs[attr_index].base_type != SG_SHADERATTRBASETYPE_UNDEFINED) {
23070+                        if (_sg_vertexformat_basetype(a_state->format) != shd->cmn.attrs[attr_index].base_type) {
23071+                            _SG_VALIDATE(false, VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH);
23072+                            _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "attr format:");
23073+                            _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, _sg_vertexformat_to_string(a_state->format));
23074+                            _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "shader attr base type:");
23075+                            _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, _sg_shaderattrbasetype_to_string(shd->cmn.attrs[attr_index].base_type));
23076+                        }
23077+                    }
23078+                    if (a_state->format == SG_VERTEXFORMAT_INT10_N2) {
23079+                        _SG_VALIDATE(_sg.features.vertexformat_int10_n2, VALIDATE_PIPELINEDESC_ATTR_VERTEXFORMAT_INT10_N2_NOT_SUPPORTED);
23080+                    }
23081+                    #if defined(SOKOL_D3D11)
23082+                    // on D3D11, semantic names (and semantic indices) must be provided
23083+                    _SG_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), VALIDATE_PIPELINEDESC_ATTR_SEMANTICS);
23084+                    #endif
23085+                }
23086+                // must only use readonly storage buffer bindings in render pipelines
23087+                for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
23088+                    if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) {
23089+                        _SG_VALIDATE(shd->cmn.views[i].sbuf_readonly, VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS);
23090+                    }
23091+                }
23092+                for (int buf_index = 0; buf_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; buf_index++) {
23093+                    const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[buf_index];
23094+                    if (l_state->stride == 0) {
23095+                        continue;
23096+                    }
23097+                    _SG_VALIDATE(_sg_multiple_u64((uint64_t)l_state->stride, 4), VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4);
23098+                }
23099+            }
23100+        }
23101+        if (desc->depth.pixel_format == SG_PIXELFORMAT_NONE) {
23102+            _SG_VALIDATE(desc->depth.write_enabled == false, VALIDATE_PIPELINEDESC_DEPTH_FORMAT_NONE_BUT_DEPTH_WRITE_ENABLED);
23103+            _SG_VALIDATE((desc->depth.compare == SG_COMPAREFUNC_ALWAYS) || (desc->depth.compare == SG_COMPAREFUNC_NEVER), VALIDATE_PIPELINEDESC_DEPTH_FORMAT_NONE_COMPARE_FUNC_MISMATCH);
23104+        }
23105+        for (size_t color_index = 0; color_index < (size_t)desc->color_count; color_index++) {
23106+            SOKOL_ASSERT(color_index < SG_MAX_COLOR_ATTACHMENTS);
23107+            const sg_blend_state* bs = &desc->colors[color_index].blend;
23108+            if ((bs->op_rgb == SG_BLENDOP_MIN) || (bs->op_rgb == SG_BLENDOP_MAX)) {
23109+                _SG_VALIDATE((bs->src_factor_rgb == SG_BLENDFACTOR_ONE) && (bs->dst_factor_rgb == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE);
23110+            }
23111+            if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) {
23112+                _SG_VALIDATE((bs->src_factor_alpha == SG_BLENDFACTOR_ONE) && (bs->dst_factor_alpha == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE);
23113+            }
23114+            const bool needs_dualsource_blending =
23115+                _sg_is_dualsource_blendfactor(bs->src_factor_rgb) ||
23116+                _sg_is_dualsource_blendfactor(bs->dst_factor_rgb) ||
23117+                _sg_is_dualsource_blendfactor(bs->src_factor_alpha) ||
23118+                _sg_is_dualsource_blendfactor(bs->dst_factor_alpha);
23119+            if (needs_dualsource_blending) {
23120+                _SG_VALIDATE(_sg.features.dual_source_blending, VALIDATE_PIPELINEDESC_DUAL_SOURCE_BLENDING_NOT_SUPPORTED);
23121+            }
23122+        }
23123+        return _sg_validate_end();
23124+    #endif
23125+}
23126+
23127+_SOKOL_PRIVATE bool _sg_validate_view_desc(const sg_view_desc* desc) {
23128+    #if !defined(SOKOL_DEBUG)
23129+        _SOKOL_UNUSED(desc);
23130+        return true;
23131+    #else
23132+        if (_sg.desc.disable_validation) {
23133+            return true;
23134+        }
23135+        SOKOL_ASSERT(desc);
23136+        _sg_validate_begin();
23137+        _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_VIEWDESC_CANARY);
23138+        _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_VIEWDESC_CANARY);
23139+
23140+        // only one view type can be define
23141+        sg_view_type view_type = SG_VIEWTYPE_INVALID;
23142+        const sg_image_view_desc* img_desc = 0;
23143+        const sg_texture_view_desc* tex_desc = 0;
23144+        const sg_buffer_view_desc* buf_desc = 0;
23145+        if (desc->texture.image.id != SG_INVALID_ID) {
23146+            view_type = SG_VIEWTYPE_TEXTURE;
23147+            tex_desc = &desc->texture;
23148+        }
23149+        if (desc->storage_buffer.buffer.id != SG_INVALID_ID) {
23150+            _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE);
23151+            view_type = SG_VIEWTYPE_STORAGEBUFFER;
23152+            buf_desc = &desc->storage_buffer;
23153+        }
23154+        if (desc->storage_image.image.id != SG_INVALID_ID) {
23155+            _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE);
23156+            view_type = SG_VIEWTYPE_STORAGEIMAGE;
23157+            img_desc = &desc->storage_image;
23158+        }
23159+        if (desc->color_attachment.image.id != SG_INVALID_ID) {
23160+            _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE);
23161+            view_type = SG_VIEWTYPE_COLORATTACHMENT;
23162+            img_desc = &desc->color_attachment;
23163+        }
23164+        if (desc->resolve_attachment.image.id != SG_INVALID_ID) {
23165+            _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE);
23166+            view_type = SG_VIEWTYPE_RESOLVEATTACHMENT;
23167+            img_desc = &desc->resolve_attachment;
23168+        }
23169+        if (desc->depth_stencil_attachment.image.id != SG_INVALID_ID) {
23170+            _SG_VALIDATE(SG_VIEWTYPE_INVALID == view_type, VALIDATE_VIEWDESC_UNIQUE_VIEWTYPE);
23171+            view_type = SG_VIEWTYPE_DEPTHSTENCILATTACHMENT;
23172+            img_desc = &desc->depth_stencil_attachment;
23173+        }
23174+        _SG_VALIDATE(SG_VIEWTYPE_INVALID != view_type, VALIDATE_VIEWDESC_ANY_VIEWTYPE);
23175+
23176+        const _sg_buffer_t* buf = 0;
23177+        const _sg_image_t* img = 0;
23178+        bool res_valid = false;
23179+        if (buf_desc) {
23180+            SOKOL_ASSERT((img_desc == 0) && (tex_desc == 0));
23181+            buf = _sg_lookup_buffer(buf_desc->buffer.id);
23182+            _SG_VALIDATE(buf, VALIDATE_VIEWDESC_RESOURCE_ALIVE);
23183+            if (buf) {
23184+                _SG_VALIDATE(buf->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_VIEWDESC_RESOURCE_FAILED);
23185+                res_valid = buf->slot.state == SG_RESOURCESTATE_VALID;
23186+            }
23187+        } else if (img_desc) {
23188+            SOKOL_ASSERT((tex_desc == 0) && (buf_desc == 0));
23189+            img = _sg_lookup_image(img_desc->image.id);
23190+            _SG_VALIDATE(img, VALIDATE_VIEWDESC_RESOURCE_ALIVE);
23191+            if (img) {
23192+                _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_VIEWDESC_RESOURCE_FAILED);
23193+                res_valid = img->slot.state == SG_RESOURCESTATE_VALID;
23194+            }
23195+        } else {
23196+            SOKOL_ASSERT(tex_desc && (img_desc == 0) && (buf_desc == 0));
23197+            img = _sg_lookup_image(tex_desc->image.id);
23198+            _SG_VALIDATE(img, VALIDATE_VIEWDESC_RESOURCE_ALIVE);
23199+            if (img) {
23200+                _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_VIEWDESC_RESOURCE_FAILED);
23201+                res_valid = img->slot.state == SG_RESOURCESTATE_VALID;
23202+            }
23203+        }
23204+        if (res_valid) {
23205+            // check usage flags
23206+            switch (view_type) {
23207+                case SG_VIEWTYPE_STORAGEBUFFER:
23208+                    SOKOL_ASSERT(buf);
23209+                    _SG_VALIDATE(buf->cmn.usage.storage_buffer, VALIDATE_VIEWDESC_STORAGEBUFFER_USAGE);
23210+                    break;
23211+                case SG_VIEWTYPE_STORAGEIMAGE:
23212+                    SOKOL_ASSERT(img);
23213+                    _SG_VALIDATE(img->cmn.usage.storage_image, VALIDATE_VIEWDESC_STORAGEIMAGE_USAGE);
23214+                    _SG_VALIDATE(_sg_is_valid_storage_image_format(img->cmn.pixel_format), VALIDATE_VIEWDESC_STORAGEIMAGE_PIXELFORMAT);
23215+                    break;
23216+                case SG_VIEWTYPE_TEXTURE:
23217+                    if (!_sg.features.msaa_texture_bindings) {
23218+                        _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_VIEWDESC_TEXTURE_EXPECT_NO_MSAA);
23219+                    }
23220+                    break;
23221+                case SG_VIEWTYPE_COLORATTACHMENT:
23222+                    SOKOL_ASSERT(img);
23223+                    _SG_VALIDATE(img->cmn.usage.color_attachment, VALIDATE_VIEWDESC_COLORATTACHMENT_USAGE);
23224+                    _SG_VALIDATE(_sg_is_valid_attachment_color_format(img->cmn.pixel_format), VALIDATE_VIEWDESC_COLORATTACHMENT_PIXELFORMAT);
23225+                    break;
23226+                case SG_VIEWTYPE_RESOLVEATTACHMENT:
23227+                    SOKOL_ASSERT(img);
23228+                    _SG_VALIDATE(img->cmn.usage.resolve_attachment, VALIDATE_VIEWDESC_RESOLVEATTACHMENT_USAGE);
23229+                    _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_VIEWDESC_RESOLVEATTACHMENT_SAMPLECOUNT);
23230+                    break;
23231+                case SG_VIEWTYPE_DEPTHSTENCILATTACHMENT:
23232+                    SOKOL_ASSERT(img);
23233+                    _SG_VALIDATE(img->cmn.usage.depth_stencil_attachment, VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_USAGE);
23234+                    _SG_VALIDATE(_sg_is_valid_attachment_depth_format(img->cmn.pixel_format), VALIDATE_VIEWDESC_DEPTHSTENCILATTACHMENT_PIXELFORMAT);
23235+                    break;
23236+                default:
23237+                    SOKOL_UNREACHABLE;
23238+                    break;
23239+            }
23240+            if (buf_desc) {
23241+                SOKOL_ASSERT(buf);
23242+                _SG_VALIDATE(buf_desc->offset < buf->cmn.size, VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_VS_BUFFER_SIZE);
23243+                _SG_VALIDATE(_sg_multiple_u64((uint64_t)buf_desc->offset, 256), VALIDATE_VIEWDESC_STORAGEBUFFER_OFFSET_MULTIPLE_256);
23244+            } else if (img_desc) {
23245+                SOKOL_ASSERT(img);
23246+                _SG_VALIDATE((img_desc->mip_level >= 0) && (img_desc->mip_level < img->cmn.num_mipmaps), VALIDATE_VIEWDESC_IMAGE_MIPLEVEL);
23247+                if (img->cmn.type == SG_IMAGETYPE_2D) {
23248+                    _SG_VALIDATE(img_desc->slice == 0, VALIDATE_VIEWDESC_IMAGE_2D_SLICE);
23249+                } else if (img->cmn.type == SG_IMAGETYPE_CUBE) {
23250+                    _SG_VALIDATE((img_desc->slice >= 0) && (img_desc->slice < 6), VALIDATE_VIEWDESC_IMAGE_CUBEMAP_SLICE);
23251+                } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) {
23252+                    _SG_VALIDATE((img_desc->slice >= 0) && (img_desc->slice < img->cmn.num_slices), VALIDATE_VIEWDESC_IMAGE_ARRAY_SLICE);
23253+                } else if (img->cmn.type == SG_IMAGETYPE_3D) {
23254+                    _SG_VALIDATE(img_desc->slice == 0, VALIDATE_VIEWDESC_IMAGE_3D_SLICE);
23255+                }
23256+            } else if (tex_desc) {
23257+                SOKOL_ASSERT(img);
23258+                // NOTE: it doesn't matter here if the mip/slice count is default-zero!
23259+                int max_mip_level = tex_desc->mip_levels.base + tex_desc->mip_levels.count;
23260+                int max_slice = tex_desc->slices.base + tex_desc->slices.count;
23261+                _SG_VALIDATE((tex_desc->mip_levels.base >= 0) && (max_mip_level <= img->cmn.num_mipmaps), VALIDATE_VIEWDESC_TEXTURE_MIPLEVELS);
23262+                if (img->cmn.type == SG_IMAGETYPE_2D) {
23263+                    _SG_VALIDATE((tex_desc->slices.base == 0) && (max_slice <= 1), VALIDATE_VIEWDESC_TEXTURE_2D_SLICES);
23264+                } else if (img->cmn.type == SG_IMAGETYPE_CUBE) {
23265+                    _SG_VALIDATE((tex_desc->slices.base == 0) && (max_slice <= 1), VALIDATE_VIEWDESC_TEXTURE_CUBEMAP_SLICES);
23266+                } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) {
23267+                    _SG_VALIDATE((tex_desc->slices.base >= 0) && (max_slice <= img->cmn.num_slices), VALIDATE_VIEWDESC_TEXTURE_ARRAY_SLICES);
23268+                } else if (img->cmn.type == SG_IMAGETYPE_3D) {
23269+                    _SG_VALIDATE((tex_desc->slices.base == 0) && (max_slice <= 1), VALIDATE_VIEWDESC_TEXTURE_3D_SLICES);
23270+                }
23271+            }
23272+        }
23273+        return _sg_validate_end();
23274+    #endif
23275+}
23276+
23277+_SOKOL_PRIVATE bool _sg_validate_begin_pass(const sg_pass* pass) {
23278+    #if !defined(SOKOL_DEBUG)
23279+        _SOKOL_UNUSED(pass);
23280+        return true;
23281+    #else
23282+        if (_sg.desc.disable_validation) {
23283+            return true;
23284+        }
23285+        const bool is_invalid_swapchain_pass = pass->swapchain.invalid;
23286+        const bool is_compute_pass = pass->compute;
23287+        const bool is_swapchain_pass = !is_compute_pass && _sg_attachments_empty(&pass->attachments);
23288+        const bool is_offscreen_pass = !(is_compute_pass || is_swapchain_pass);
23289+        _sg_validate_begin();
23290+        _SG_VALIDATE(pass->_start_canary == 0, VALIDATE_BEGINPASS_CANARY);
23291+        _SG_VALIDATE(pass->_end_canary == 0, VALIDATE_BEGINPASS_CANARY);
23292+        if (is_compute_pass) {
23293+            _SG_VALIDATE(_sg_attachments_empty(&pass->attachments), VALIDATE_BEGINPASS_COMPUTEPASS_EXPECT_NO_ATTACHMENTS);
23294+        } else if (is_invalid_swapchain_pass) {
23295+            // empty block not a bug, skips to 'swapchain zeroed' validation at the end
23296+        } else if (is_swapchain_pass) {
23297+            _SG_VALIDATE(pass->swapchain.width > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH);
23298+            _SG_VALIDATE(pass->swapchain.height > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT);
23299+            _SG_VALIDATE(pass->swapchain.sample_count > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT);
23300+            _SG_VALIDATE(pass->swapchain.color_format > SG_PIXELFORMAT_NONE, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT);
23301+            // NOTE: depth buffer is optional, so depth_format is allowed to be invalid
23302+            // NOTE: the GL framebuffer handle may actually be 0
23303+            #if defined(SOKOL_METAL)
23304+                _SG_VALIDATE(pass->swapchain.metal.current_drawable != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE);
23305+                if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) {
23306+                    _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET);
23307+                } else {
23308+                    _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE);
23309+                }
23310+                if (pass->swapchain.sample_count > 1) {
23311+                    _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE);
23312+                } else {
23313+                    _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET);
23314+                }
23315+            #elif defined(SOKOL_D3D11)
23316+                _SG_VALIDATE(pass->swapchain.d3d11.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW);
23317+                if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) {
23318+                    _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET);
23319+                } else {
23320+                    _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW);
23321+                }
23322+                if (pass->swapchain.sample_count > 1) {
23323+                    _SG_VALIDATE(pass->swapchain.d3d11.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW);
23324+                } else {
23325+                    _SG_VALIDATE(pass->swapchain.d3d11.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET);
23326+                }
23327+            #elif defined(SOKOL_WGPU)
23328+                _SG_VALIDATE(pass->swapchain.wgpu.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW);
23329+                if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) {
23330+                    _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET);
23331+                } else {
23332+                    _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW);
23333+                }
23334+                if (pass->swapchain.sample_count > 1) {
23335+                    _SG_VALIDATE(pass->swapchain.wgpu.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW);
23336+                } else {
23337+                    _SG_VALIDATE(pass->swapchain.wgpu.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET);
23338+                }
23339+            #elif defined(SOKOL_VULKAN)
23340+                _SG_VALIDATE(pass->swapchain.vulkan.render_image != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERIMAGE);
23341+                _SG_VALIDATE(pass->swapchain.vulkan.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERVIEW);
23342+                if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) {
23343+                    _SG_VALIDATE(pass->swapchain.vulkan.depth_stencil_image == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILIMAGE_NOTSET);
23344+                    _SG_VALIDATE(pass->swapchain.vulkan.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILVIEW_NOTSET);
23345+                } else {
23346+                    _SG_VALIDATE(pass->swapchain.vulkan.depth_stencil_image != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILIMAGE);
23347+                    _SG_VALIDATE(pass->swapchain.vulkan.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILVIEW);
23348+                }
23349+                if (pass->swapchain.sample_count > 1) {
23350+                    _SG_VALIDATE(pass->swapchain.vulkan.resolve_image != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEIMAGE);
23351+                    _SG_VALIDATE(pass->swapchain.vulkan.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEVIEW);
23352+                } else {
23353+                    _SG_VALIDATE(pass->swapchain.vulkan.resolve_image == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEIMAGE_NOTSET);
23354+                    _SG_VALIDATE(pass->swapchain.vulkan.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEVIEW_NOTSET);
23355+                }
23356+                _SG_VALIDATE(pass->swapchain.vulkan.render_finished_semaphore != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERFINISHEDSEMAPHORE);
23357+                _SG_VALIDATE(pass->swapchain.vulkan.present_complete_semaphore != 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_PRESENTCOMPLETESEMAPHORE);
23358+            #endif
23359+        } else {
23360+            // this is an 'offscreen pass'
23361+            bool has_color_atts = false;
23362+            bool has_depth_stencil_atts = false;
23363+            bool atts_cont = true;
23364+            int color_width = -1, color_height = -1, color_sample_count = -1;
23365+            for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) {
23366+                if (pass->attachments.colors[att_index].id == SG_INVALID_ID) {
23367+                    atts_cont = false;
23368+                    continue;
23369+                }
23370+                has_color_atts = true;
23371+                _SG_VALIDATE(atts_cont, VALIDATE_BEGINPASS_COLORATTACHMENTVIEWS_CONTINUOUS);
23372+                const _sg_view_t* view = _sg_lookup_view(pass->attachments.colors[att_index].id);
23373+                // the view object must be alive
23374+                _SG_VALIDATE(view != 0, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_ALIVE);
23375+                if (view) {
23376+                    // the view object must be in valid state
23377+                    _SG_VALIDATE(view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_VALID);
23378+                    if (view->slot.state == SG_RESOURCESTATE_VALID) {
23379+                        // the view object must be a color attachment view
23380+                        _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_COLORATTACHMENT, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_TYPE);
23381+                        // the view's image object must be alive and valid
23382+                        const _sg_image_t* img = _sg_image_ref_ptr_or_null(&view->cmn.img.ref);
23383+                        _SG_VALIDATE(img, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_ALIVE);
23384+                        if (img) {
23385+                            _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_IMAGE_VALID);
23386+                            if (img->slot.state == SG_RESOURCESTATE_VALID) {
23387+                                if (color_width == -1) {
23388+                                    color_width = _sg_image_view_dim(view).width;
23389+                                    color_height = _sg_image_view_dim(view).height;
23390+                                    color_sample_count = img->cmn.sample_count;
23391+                                } else {
23392+                                    _SG_VALIDATE(color_width == _sg_image_view_dim(view).width, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SIZES);
23393+                                    _SG_VALIDATE(color_height == _sg_image_view_dim(view).height, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SIZES);
23394+                                    _SG_VALIDATE(color_sample_count == img->cmn.sample_count, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNTS_EQUAL);
23395+                                }
23396+                            }
23397+                        }
23398+                    }
23399+                }
23400+            }
23401+            // check resolve views
23402+            for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) {
23403+                if (pass->attachments.resolves[att_index].id == SG_INVALID_ID) {
23404+                    continue;
23405+                }
23406+                _SG_VALIDATE(pass->attachments.colors[att_index].id != SG_INVALID_ID, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_NO_COLORATTACHMENTVIEW);
23407+                const _sg_view_t* view = _sg_lookup_view(pass->attachments.resolves[att_index].id);
23408+                // the view object must be alive
23409+                _SG_VALIDATE(view != 0, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_ALIVE);
23410+                if (view) {
23411+                    // the view object must be in valid state
23412+                    _SG_VALIDATE(view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_VALID);
23413+                    if (view->slot.state == SG_RESOURCESTATE_VALID) {
23414+                        // the view object must be a resolve attachment view
23415+                        _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_RESOLVEATTACHMENT, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_TYPE);
23416+                        // the view's image object must be alive and valid
23417+                        const _sg_image_t* img = _sg_image_ref_ptr_or_null(&view->cmn.img.ref);
23418+                        _SG_VALIDATE(img, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_ALIVE);
23419+                        if (img) {
23420+                            _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_IMAGE_VALID);
23421+                            if (img->slot.state == SG_RESOURCESTATE_VALID) {
23422+                                if (color_width != -1) {
23423+                                    _SG_VALIDATE(color_sample_count > 1, VALIDATE_BEGINPASS_COLORATTACHMENTVIEW_SAMPLECOUNT);
23424+                                    _SG_VALIDATE(color_width == _sg_image_view_dim(view).width, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_SIZES);
23425+                                    _SG_VALIDATE(color_height == _sg_image_view_dim(view).height, VALIDATE_BEGINPASS_RESOLVEATTACHMENTVIEW_SIZES);
23426+                                }
23427+                            }
23428+                        }
23429+                    }
23430+                }
23431+            }
23432+            // check depth-stencil view
23433+            if (pass->attachments.depth_stencil.id != SG_INVALID_ID) {
23434+                has_depth_stencil_atts = true;
23435+                const _sg_view_t* view = _sg_lookup_view(pass->attachments.depth_stencil.id);
23436+                // the view object must be valid
23437+                _SG_VALIDATE(view != 0, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_ALIVE);
23438+                if (view) {
23439+                    // the view object must be in valid state
23440+                    _SG_VALIDATE(view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_VALID);
23441+                    if (view->slot.state == SG_RESOURCESTATE_VALID) {
23442+                        // the view object must be a depth stencil attachment view
23443+                        _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_DEPTHSTENCILATTACHMENT, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_TYPE);
23444+                        // the view's image object must be alive and valid
23445+                        const _sg_image_t* img = _sg_image_ref_ptr_or_null(&view->cmn.img.ref);
23446+                        _SG_VALIDATE(img, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_ALIVE);
23447+                        if (img) {
23448+                            _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_IMAGE_VALID);
23449+                            if (img->slot.state == SG_RESOURCESTATE_VALID) {
23450+                                if (color_width != -1) {
23451+                                    _SG_VALIDATE(color_width == _sg_image_view_dim(view).width, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SIZES);
23452+                                    _SG_VALIDATE(color_height == _sg_image_view_dim(view).height, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SIZES);
23453+                                    _SG_VALIDATE(color_sample_count == img->cmn.sample_count, VALIDATE_BEGINPASS_DEPTHSTENCILATTACHMENTVIEW_SAMPLECOUNT);
23454+                                }
23455+                            }
23456+                        }
23457+                    }
23458+                }
23459+            }
23460+            // must have at least color- or depth-stencil-attachments
23461+            _SG_VALIDATE(has_color_atts || has_depth_stencil_atts, VALIDATE_BEGINPASS_ATTACHMENTS_EXPECTED);
23462+        }
23463+        if (is_compute_pass || is_offscreen_pass || is_invalid_swapchain_pass) {
23464+            _SG_VALIDATE(pass->swapchain.width == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET);
23465+            _SG_VALIDATE(pass->swapchain.height == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET);
23466+            _SG_VALIDATE(pass->swapchain.sample_count == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET);
23467+            _SG_VALIDATE(pass->swapchain.color_format == _SG_PIXELFORMAT_DEFAULT, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET);
23468+            _SG_VALIDATE(pass->swapchain.depth_format == _SG_PIXELFORMAT_DEFAULT, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET);
23469+            #if defined(SOKOL_METAL)
23470+                _SG_VALIDATE(pass->swapchain.metal.current_drawable == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET);
23471+                _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET);
23472+                _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET);
23473+            #elif defined(SOKOL_D3D11)
23474+                _SG_VALIDATE(pass->swapchain.d3d11.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET);
23475+                _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET);
23476+                _SG_VALIDATE(pass->swapchain.d3d11.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET);
23477+            #elif defined(SOKOL_WGPU)
23478+                _SG_VALIDATE(pass->swapchain.wgpu.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET);
23479+                _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET);
23480+                _SG_VALIDATE(pass->swapchain.wgpu.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET);
23481+            #elif defined(_SOKOL_ANY_GL)
23482+                _SG_VALIDATE(pass->swapchain.gl.framebuffer == 0, VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET);
23483+            #elif defined(SOKOL_VULKAN)
23484+                _SG_VALIDATE(pass->swapchain.vulkan.render_image == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERIMAGE_NOTSET);
23485+                _SG_VALIDATE(pass->swapchain.vulkan.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERVIEW_NOTSET);
23486+                _SG_VALIDATE(pass->swapchain.vulkan.depth_stencil_image == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILIMAGE_NOTSET);
23487+                _SG_VALIDATE(pass->swapchain.vulkan.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_DEPTHSTENCILVIEW_NOTSET);
23488+                _SG_VALIDATE(pass->swapchain.vulkan.resolve_image == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEIMAGE_NOTSET);
23489+                _SG_VALIDATE(pass->swapchain.vulkan.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RESOLVEVIEW_NOTSET);
23490+                _SG_VALIDATE(pass->swapchain.vulkan.render_finished_semaphore == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_RENDERFINISHEDSEMAPHORE_NOTSET);
23491+                _SG_VALIDATE(pass->swapchain.vulkan.present_complete_semaphore == 0, VALIDATE_BEGINPASS_SWAPCHAIN_VULKAN_EXPECT_PRESENTCOMPLETESEMAPHORE_NOTSET);
23492+            #endif
23493+        }
23494+        return _sg_validate_end();
23495+    #endif
23496+}
23497+
23498+_SOKOL_PRIVATE bool _sg_validate_apply_viewport(int x, int y, int width, int height, bool origin_top_left) {
23499+    _SOKOL_UNUSED(x);
23500+    _SOKOL_UNUSED(y);
23501+    _SOKOL_UNUSED(width);
23502+    _SOKOL_UNUSED(height);
23503+    _SOKOL_UNUSED(origin_top_left);
23504+    #if !defined(SOKOL_DEBUG)
23505+        return true;
23506+    #else
23507+        if (_sg.desc.disable_validation) {
23508+            return true;
23509+        }
23510+        _sg_validate_begin();
23511+        _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_AVP_RENDERPASS_EXPECTED);
23512+        return _sg_validate_end();
23513+    #endif
23514+}
23515+
23516+_SOKOL_PRIVATE bool _sg_validate_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) {
23517+    _SOKOL_UNUSED(x);
23518+    _SOKOL_UNUSED(y);
23519+    _SOKOL_UNUSED(width);
23520+    _SOKOL_UNUSED(height);
23521+    _SOKOL_UNUSED(origin_top_left);
23522+    #if !defined(SOKOL_DEBUG)
23523+        return true;
23524+    #else
23525+        if (_sg.desc.disable_validation) {
23526+            return true;
23527+        }
23528+        _sg_validate_begin();
23529+        _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_ASR_RENDERPASS_EXPECTED);
23530+        return _sg_validate_end();
23531+    #endif
23532+}
23533+
23534+_SOKOL_PRIVATE bool _sg_validate_apply_pipeline(sg_pipeline pip_id) {
23535+    #if !defined(SOKOL_DEBUG)
23536+        _SOKOL_UNUSED(pip_id);
23537+        return true;
23538+    #else
23539+        if (_sg.desc.disable_validation) {
23540+            return true;
23541+        }
23542+        _sg_validate_begin();
23543+        // the pipeline object must be alive and valid
23544+        _SG_VALIDATE(pip_id.id != SG_INVALID_ID, VALIDATE_APIP_PIPELINE_VALID_ID);
23545+        const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
23546+        _SG_VALIDATE(pip != 0, VALIDATE_APIP_PIPELINE_EXISTS);
23547+        if (!pip) {
23548+            return _sg_validate_end();
23549+        }
23550+        _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_VALID);
23551+
23552+        // the pipeline's shader must be alive and valid
23553+        _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_APIP_PASS_EXPECTED);
23554+        const bool shd_alive = _sg_shader_ref_alive(&pip->cmn.shader);
23555+        const _sg_shader_t* shd = shd_alive ? _sg_shader_ref_ptr(&pip->cmn.shader) : 0;
23556+        _SG_VALIDATE(shd_alive, VALIDATE_APIP_PIPELINE_SHADER_ALIVE);
23557+        if (shd_alive) {
23558+            _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_SHADER_VALID);
23559+        } else {
23560+            return _sg_validate_end();
23561+        }
23562+
23563+        if (pip->cmn.is_compute) {
23564+            _SG_VALIDATE(_sg.cur_pass.is_compute, VALIDATE_APIP_COMPUTEPASS_EXPECTED);
23565+        } else {
23566+            _SG_VALIDATE(!_sg.cur_pass.is_compute, VALIDATE_APIP_RENDERPASS_EXPECTED);
23567+            if (_sg_attachments_empty(&_sg.cur_pass.atts)) {
23568+                // a swapchain pass
23569+                _SG_VALIDATE(pip->cmn.color_count == 1, VALIDATE_APIP_SWAPCHAIN_COLOR_COUNT);
23570+                _SG_VALIDATE(pip->cmn.colors[0].pixel_format == _sg.cur_pass.swapchain.color_fmt, VALIDATE_APIP_SWAPCHAIN_COLOR_FORMAT);
23571+                _SG_VALIDATE(pip->cmn.depth.pixel_format == _sg.cur_pass.swapchain.depth_fmt, VALIDATE_APIP_SWAPCHAIN_DEPTH_FORMAT);
23572+                _SG_VALIDATE(pip->cmn.sample_count == _sg.cur_pass.swapchain.sample_count, VALIDATE_APIP_SWAPCHAIN_SAMPLE_COUNT);
23573+            } else {
23574+                // an offscreen render pass check that pipeline attributes match current pass attachment attributes
23575+                const _sg_attachments_ptrs_t atts_ptrs = _sg_attachments_ptrs(&_sg.cur_pass.atts);
23576+                const bool alive = _sg_attachments_alive(&atts_ptrs);
23577+                _SG_VALIDATE(alive, VALIDATE_APIP_ATTACHMENTS_ALIVE);
23578+                if (alive) {
23579+                    _SG_VALIDATE(pip->cmn.color_count == atts_ptrs.num_color_views, VALIDATE_APIP_COLORATTACHMENTS_COUNT);
23580+                    for (int i = 0; i < pip->cmn.color_count; i++) {
23581+                        const _sg_view_t* clr_view = atts_ptrs.color_views[i];
23582+                        SOKOL_ASSERT(clr_view);
23583+                        _SG_VALIDATE(clr_view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_COLORATTACHMENTS_VIEW_VALID);
23584+                        const _sg_image_t* clr_img = _sg_image_ref_ptr(&clr_view->cmn.img.ref);
23585+                        SOKOL_ASSERT(clr_img);
23586+                        _SG_VALIDATE(clr_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_COLORATTACHMENTS_IMAGE_VALID);
23587+                        _SG_VALIDATE(pip->cmn.colors[i].pixel_format == clr_img->cmn.pixel_format, VALIDATE_APIP_COLORATTACHMENTS_FORMAT);
23588+                        _SG_VALIDATE(pip->cmn.sample_count == clr_img->cmn.sample_count, VALIDATE_APIP_ATTACHMENT_SAMPLE_COUNT);
23589+                    }
23590+                    const _sg_view_t* ds_view = atts_ptrs.ds_view;
23591+                    if (ds_view) {
23592+                        _SG_VALIDATE(ds_view->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_VIEW_VALID);
23593+                        const _sg_image_t* ds_img = _sg_image_ref_ptr(&ds_view->cmn.img.ref);
23594+                        SOKOL_ASSERT(ds_img);
23595+                        _SG_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_IMAGE_VALID);
23596+                        _SG_VALIDATE(pip->cmn.depth.pixel_format == ds_img->cmn.pixel_format, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_FORMAT);
23597+                        _SG_VALIDATE(pip->cmn.sample_count == ds_img->cmn.sample_count, VALIDATE_APIP_ATTACHMENT_SAMPLE_COUNT);
23598+                    } else {
23599+                        _SG_VALIDATE(pip->cmn.depth.pixel_format == SG_PIXELFORMAT_NONE, VALIDATE_APIP_DEPTHSTENCILATTACHMENT_FORMAT);
23600+                    }
23601+                }
23602+            }
23603+        }
23604+        return _sg_validate_end();
23605+    #endif
23606+}
23607+
23608+_SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) {
23609+    #if !defined(SOKOL_DEBUG)
23610+        _SOKOL_UNUSED(bindings);
23611+        return true;
23612+    #else
23613+        if (_sg.desc.disable_validation) {
23614+            return true;
23615+        }
23616+        _sg_validate_begin();
23617+
23618+        // must be called in a pass
23619+        _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_ABND_PASS_EXPECTED);
23620+
23621+        // bindings must not be empty
23622+        bool has_any_bindings = bindings->index_buffer.id != SG_INVALID_ID;
23623+        if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
23624+            has_any_bindings |= bindings->vertex_buffers[i].id != SG_INVALID_ID;
23625+        }
23626+        if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
23627+            has_any_bindings |= bindings->views[i].id != SG_INVALID_ID;
23628+        }
23629+        if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
23630+            has_any_bindings |= bindings->samplers[i].id != SG_INVALID_ID;
23631+        }
23632+        _SG_VALIDATE(has_any_bindings, VALIDATE_ABND_EMPTY_BINDINGS);
23633+
23634+        // a pipeline object must have been applied
23635+        const bool pip_null = _sg_pipeline_ref_null(&_sg.cur_pip);
23636+        const bool pip_alive = _sg_pipeline_ref_alive(&_sg.cur_pip);
23637+        _SG_VALIDATE(!pip_null, VALIDATE_ABND_NO_PIPELINE);
23638+        _SG_VALIDATE(pip_alive, VALIDATE_ABND_PIPELINE_ALIVE);
23639+        if (!pip_alive) {
23640+            return _sg_validate_end();
23641+        }
23642+        const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
23643+        _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_VALID);
23644+
23645+        const bool shd_alive = _sg_shader_ref_alive(&pip->cmn.shader);
23646+        _SG_VALIDATE(shd_alive, VALIDATE_ABND_PIPELINE_SHADER_ALIVE);
23647+        if (!shd_alive) {
23648+            return _sg_validate_end();
23649+        }
23650+        const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
23651+        _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_SHADER_VALID);
23652+
23653+        if (_sg.cur_pass.is_compute) {
23654+            for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
23655+                _SG_VALIDATE(bindings->vertex_buffers[i].id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBUFS);
23656+            }
23657+        } else {
23658+            for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
23659+                if (pip->cmn.vertex_buffer_layout_active[i]) {
23660+                    _SG_VALIDATE(bindings->vertex_buffers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_VBUF);
23661+                    if (bindings->vertex_buffers[i].id != SG_INVALID_ID) {
23662+                        const _sg_buffer_t* buf = _sg_lookup_buffer(bindings->vertex_buffers[i].id);
23663+                        _SG_VALIDATE(buf != 0, VALIDATE_ABND_VBUF_ALIVE);
23664+                        // NOTE: state != VALID is legal and skips rendering!
23665+                        if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) {
23666+                            _SG_VALIDATE(buf->cmn.usage.vertex_buffer, VALIDATE_ABND_VBUF_USAGE);
23667+                            _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_VBUF_OVERFLOW);
23668+                        }
23669+                    }
23670+                }
23671+            }
23672+        }
23673+
23674+        if (_sg.cur_pass.is_compute) {
23675+            _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_IBUF);
23676+        } else {
23677+            // index buffer expected or not, and index buffer still exists
23678+            if (pip->cmn.index_type == SG_INDEXTYPE_NONE) {
23679+                // pipeline defines non-indexed rendering, but index buffer provided
23680+                _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_EXPECTED_NO_IBUF);
23681+            } else {
23682+                // pipeline defines indexed rendering, but no index buffer provided
23683+                _SG_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_IBUF);
23684+            }
23685+            if (bindings->index_buffer.id != SG_INVALID_ID) {
23686+                // buffer in index-buffer-slot must have index buffer usage
23687+                const _sg_buffer_t* buf = _sg_lookup_buffer(bindings->index_buffer.id);
23688+                _SG_VALIDATE(buf != 0, VALIDATE_ABND_IBUF_ALIVE);
23689+                // NOTE: state != VALID is legal and skips rendering!
23690+                if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) {
23691+                    _SG_VALIDATE(buf->cmn.usage.index_buffer, VALIDATE_ABND_IBUF_USAGE);
23692+                    _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_IBUF_OVERFLOW);
23693+                }
23694+            }
23695+        }
23696+
23697+        // has expected view bindings
23698+        for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
23699+            if (shd->cmn.views[i].view_type != SG_VIEWTYPE_INVALID) {
23700+                _SG_VALIDATE(bindings->views[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_VIEW_BINDING);
23701+                if (bindings->views[i].id != SG_INVALID_ID) {
23702+                    const _sg_view_t* view = _sg_lookup_view(bindings->views[i].id);
23703+                    _SG_VALIDATE(view != 0, VALIDATE_ABND_VIEW_ALIVE);
23704+                    // the view object must be alive
23705+                    if (view) {
23706+                        // NOTE: an invalid view state is allowed and skips rendering
23707+                        if (view->slot.state == SG_RESOURCESTATE_VALID) {
23708+                            if (shd->cmn.views[i].view_type == SG_VIEWTYPE_TEXTURE) {
23709+                                // the view object must be a texture view
23710+                                _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_TEXTURE, VALIDATE_ABND_EXPECT_TEXVIEW);
23711+                                // NOTE: an invalid image ref is allowed and skips rendering
23712+                                if (_sg_image_ref_valid(&view->cmn.img.ref)) {
23713+                                    const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
23714+                                    _SG_VALIDATE(img->cmn.type == shd->cmn.views[i].image_type, VALIDATE_ABND_TEXVIEW_IMAGETYPE_MISMATCH);
23715+                                    if (shd->cmn.views[i].multisampled) {
23716+                                        _SG_VALIDATE(img->cmn.sample_count > 1, VALIDATE_ABND_TEXVIEW_EXPECTED_MULTISAMPLED_IMAGE);
23717+                                    } else {
23718+                                        _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_ABND_TEXVIEW_EXPECTED_NON_MULTISAMPLED_IMAGE);
23719+                                    }
23720+                                    const _sg_pixelformat_info_t* info = &_sg.formats[img->cmn.pixel_format];
23721+                                    switch (shd->cmn.views[i].sample_type) {
23722+                                        case SG_IMAGESAMPLETYPE_FLOAT:
23723+                                            _SG_VALIDATE(info->filter, VALIDATE_ABND_TEXVIEW_EXPECTED_FILTERABLE_IMAGE);
23724+                                            break;
23725+                                        case SG_IMAGESAMPLETYPE_DEPTH:
23726+                                            _SG_VALIDATE(info->depth, VALIDATE_ABND_TEXVIEW_EXPECTED_DEPTH_IMAGE);
23727+                                            break;
23728+                                        default:
23729+                                            break;
23730+                                    }
23731+                                }
23732+                            } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEBUFFER) {
23733+                                // the view object must be a storage buffer view
23734+                                _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_STORAGEBUFFER, VALIDATE_ABND_EXPECT_SBVIEW);
23735+                                // NOTE: an invalid buffer ref is allowed and skips rendering
23736+                                if (_sg_buffer_ref_valid(&view->cmn.buf.ref)) {
23737+                                    const _sg_buffer_t* buf = _sg_buffer_ref_ptr(&view->cmn.buf.ref);
23738+                                    if (!shd->cmn.views[i].sbuf_readonly) {
23739+                                        _SG_VALIDATE(buf->cmn.usage.immutable, VALIDATE_ABND_SBVIEW_READWRITE_IMMUTABLE);
23740+                                    }
23741+                                }
23742+                            } else if (shd->cmn.views[i].view_type == SG_VIEWTYPE_STORAGEIMAGE) {
23743+                                // the view object must be a storage-image-view
23744+                                _SG_VALIDATE(view->cmn.type == SG_VIEWTYPE_STORAGEIMAGE, VALIDATE_ABND_EXPECT_SIMGVIEW);
23745+                                // storage images only allowed in compute passes
23746+                                _SG_VALIDATE(_sg.cur_pass.is_compute, VALIDATE_ABND_SIMGVIEW_COMPUTE_PASS_EXPECTED);
23747+                                // NOTE: an invalid image ref is allowed and skips rendering
23748+                                if (_sg_image_ref_valid(&view->cmn.img.ref)) {
23749+                                    const _sg_image_t* img = _sg_image_ref_ptr(&view->cmn.img.ref);
23750+                                    _SG_VALIDATE(img->cmn.type == shd->cmn.views[i].image_type, VALIDATE_ABND_SIMGVIEW_IMAGETYPE_MISMATCH);
23751+                                    _SG_VALIDATE(img->cmn.pixel_format == shd->cmn.views[i].access_format, VALIDATE_ABND_SIMGVIEW_ACCESSFORMAT);
23752+                                }
23753+                            }
23754+                        }
23755+                    }
23756+                }
23757+            }
23758+        }
23759+
23760+        // has expected samplers
23761+        for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
23762+            if (shd->cmn.samplers[i].stage != SG_SHADERSTAGE_NONE) {
23763+                _SG_VALIDATE(bindings->samplers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_SAMPLER_BINDING);
23764+                if (bindings->samplers[i].id != SG_INVALID_ID) {
23765+                    const _sg_sampler_t* smp = _sg_lookup_sampler(bindings->samplers[i].id);
23766+                    _SG_VALIDATE(smp != 0, VALIDATE_ABND_SAMPLER_ALIVE);
23767+                    if (smp) {
23768+                        // NOTE: for invalid samplers don't skip rendering, but are actually an error
23769+                        _SG_VALIDATE(smp->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_SAMPLER_VALID);
23770+                        if (shd->cmn.samplers[i].sampler_type == SG_SAMPLERTYPE_COMPARISON) {
23771+                            _SG_VALIDATE(smp->cmn.compare != SG_COMPAREFUNC_NEVER, VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER);
23772+                        } else {
23773+                            _SG_VALIDATE(smp->cmn.compare == SG_COMPAREFUNC_NEVER, VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER);
23774+                        }
23775+                        if (shd->cmn.samplers[i].sampler_type == SG_SAMPLERTYPE_NONFILTERING) {
23776+                            const bool nonfiltering = (smp->cmn.min_filter != SG_FILTER_LINEAR)
23777+                                                   && (smp->cmn.mag_filter != SG_FILTER_LINEAR)
23778+                                                   && (smp->cmn.mipmap_filter != SG_FILTER_LINEAR);
23779+                            _SG_VALIDATE(nonfiltering, VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER);
23780+                        }
23781+                    }
23782+                }
23783+            }
23784+        }
23785+
23786+        // the same image cannot be used as texture binding and pass attachment or storage image binding
23787+        for (size_t tex_view_idx = 0; tex_view_idx < SG_MAX_VIEW_BINDSLOTS; tex_view_idx++) {
23788+            if (shd->cmn.views[tex_view_idx].view_type == SG_VIEWTYPE_TEXTURE) {
23789+                if (bindings->views[tex_view_idx].id == SG_INVALID_ID) {
23790+                    continue;
23791+                }
23792+                const _sg_view_t* tex_view = _sg_lookup_view(bindings->views[tex_view_idx].id);
23793+                if (tex_view) {
23794+                    const uint32_t img_id = tex_view->cmn.img.ref.sref.id;
23795+                    if (!_sg_attachments_empty(&_sg.cur_pass.atts)) {
23796+                        const _sg_view_t* ds_view = _sg_lookup_view(_sg.cur_pass.atts.depth_stencil.id);
23797+                        if (ds_view) {
23798+                            _SG_VALIDATE(img_id != ds_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_BINDING_VS_DEPTHSTENCIL_ATTACHMENT);
23799+                        }
23800+                        for (size_t att_idx = 0; att_idx < SG_MAX_COLOR_ATTACHMENTS; att_idx++) {
23801+                            const _sg_view_t* color_view = _sg_lookup_view(_sg.cur_pass.atts.colors[att_idx].id);
23802+                            if (color_view) {
23803+                                _SG_VALIDATE(img_id != color_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_BINDING_VS_COLOR_ATTACHMENT);
23804+                            }
23805+                            const _sg_view_t* resolve_view = _sg_lookup_view(_sg.cur_pass.atts.resolves[att_idx].id);
23806+                            if (resolve_view) {
23807+                                _SG_VALIDATE(img_id != resolve_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_BINDING_VS_RESOLVE_ATTACHMENT);
23808+                            }
23809+                        }
23810+                    }
23811+                    for (size_t simg_view_idx = 0; simg_view_idx < SG_MAX_VIEW_BINDSLOTS; simg_view_idx++) {
23812+                        if (shd->cmn.views[simg_view_idx].view_type == SG_VIEWTYPE_STORAGEIMAGE) {
23813+                            if (bindings->views[simg_view_idx].id == SG_INVALID_ID) {
23814+                                continue;
23815+                            }
23816+                            const _sg_view_t* simg_view = _sg_lookup_view(bindings->views[simg_view_idx].id);
23817+                            if (simg_view) {
23818+                                _SG_VALIDATE(img_id != simg_view->cmn.img.ref.sref.id, VALIDATE_ABND_TEXTURE_VS_STORAGEIMAGE_BINDING);
23819+                            }
23820+                        }
23821+                    }
23822+                }
23823+            }
23824+        }
23825+        return _sg_validate_end();
23826+    #endif
23827+}
23828+
23829+_SOKOL_PRIVATE bool _sg_validate_apply_uniforms(int ub_slot, const sg_range* data) {
23830+    #if !defined(SOKOL_DEBUG)
23831+        _SOKOL_UNUSED(ub_slot);
23832+        _SOKOL_UNUSED(data);
23833+        return true;
23834+    #else
23835+        if (_sg.desc.disable_validation) {
23836+            return true;
23837+        }
23838+        SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
23839+        _sg_validate_begin();
23840+        _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_AU_PASS_EXPECTED);
23841+        const _sg_pipeline_ref_t* pip_ref = &_sg.cur_pip;
23842+        const bool pip_null = _sg_pipeline_ref_null(pip_ref);
23843+        const bool pip_alive = _sg_pipeline_ref_alive(pip_ref);
23844+        _SG_VALIDATE(!pip_null, VALIDATE_AU_NO_PIPELINE);
23845+        _SG_VALIDATE(pip_alive, VALIDATE_AU_PIPELINE_ALIVE);
23846+        if (pip_alive) {
23847+            const _sg_pipeline_t* pip = _sg_pipeline_ref_ptr(pip_ref);
23848+            _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_AU_PIPELINE_VALID);
23849+            const _sg_shader_ref_t* shd_ref = &pip->cmn.shader;
23850+            const bool shd_alive = _sg_shader_ref_alive(shd_ref);
23851+            _SG_VALIDATE(shd_alive, VALIDATE_AU_PIPELINE_SHADER_ALIVE);
23852+            if (shd_alive) {
23853+                const _sg_shader_t* shd = _sg_shader_ref_ptr(shd_ref);
23854+                _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_AU_PIPELINE_SHADER_VALID);
23855+                _SG_VALIDATE(shd->cmn.uniform_blocks[ub_slot].stage != SG_SHADERSTAGE_NONE, VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT);
23856+                _SG_VALIDATE(data->size == shd->cmn.uniform_blocks[ub_slot].size, VALIDATE_AU_SIZE);
23857+            }
23858+        }
23859+        return _sg_validate_end();
23860+    #endif
23861+}
23862+
23863+_SOKOL_PRIVATE bool _sg_validate_draw(int base_element, int num_elements, int num_instances) {
23864+    #if !defined(SOKOL_DEBUG)
23865+        _SOKOL_UNUSED(base_element);
23866+        _SOKOL_UNUSED(num_elements);
23867+        _SOKOL_UNUSED(num_instances);
23868+        return true;
23869+    #else
23870+        if (_sg.desc.disable_validation) {
23871+            return true;
23872+        }
23873+        _sg_validate_begin();
23874+        _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_DRAW_RENDERPASS_EXPECTED);
23875+        _SG_VALIDATE(base_element >= 0, VALIDATE_DRAW_BASEELEMENT_GE_ZERO);
23876+        _SG_VALIDATE(num_elements >= 0, VALIDATE_DRAW_NUMELEMENTS_GE_ZERO);
23877+        _SG_VALIDATE(num_instances >= 0, VALIDATE_DRAW_NUMINSTANCES_GE_ZERO);
23878+        _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING);
23879+        return _sg_validate_end();
23880+    #endif
23881+}
23882+
23883+_SOKOL_PRIVATE bool _sg_validate_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
23884+    #if !defined(SOKOL_DEBUG)
23885+        _SOKOL_UNUSED(base_element);
23886+        _SOKOL_UNUSED(num_elements);
23887+        _SOKOL_UNUSED(num_instances);
23888+        _SOKOL_UNUSED(base_vertex);
23889+        _SOKOL_UNUSED(base_instance);
23890+        return true;
23891+    #else
23892+        if (_sg.desc.disable_validation) {
23893+            return true;
23894+        }
23895+        _sg_validate_begin();
23896+        _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_DRAW_EX_RENDERPASS_EXPECTED);
23897+        // NOTE: base_vertex is allowed to be < 0
23898+        _SG_VALIDATE(base_element >= 0, VALIDATE_DRAW_EX_BASEELEMENT_GE_ZERO);
23899+        _SG_VALIDATE(num_elements >= 0, VALIDATE_DRAW_EX_NUMELEMENTS_GE_ZERO);
23900+        _SG_VALIDATE(num_instances >= 0, VALIDATE_DRAW_EX_NUMINSTANCES_GE_ZERO);
23901+        _SG_VALIDATE(base_instance >= 0, VALIDATE_DRAW_EX_BASEINSTANCE_GE_ZERO);
23902+        if (base_vertex != 0) {
23903+            _SG_VALIDATE(_sg.features.draw_base_vertex, VALIDATE_DRAW_EX_BASEVERTEX_NOT_SUPPORTED);
23904+        }
23905+        if (base_instance > 0) {
23906+            _SG_VALIDATE(_sg.features.draw_base_instance, VALIDATE_DRAW_EX_BASEINSTANCE_NOT_SUPPORTED);
23907+        }
23908+        if (!_sg.use_indexed_draw) {
23909+            _SG_VALIDATE(base_vertex == 0, VALIDATE_DRAW_EX_BASEVERTEX_VS_INDEXED);
23910+        }
23911+        const bool use_instanced_draw = (num_instances > 1) || _sg.use_instanced_draw;
23912+        if (!use_instanced_draw) {
23913+            _SG_VALIDATE(base_instance == 0, VALIDATE_DRAW_EX_BASEINSTANCE_VS_INSTANCED);
23914+        }
23915+        _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING);
23916+        return _sg_validate_end();
23917+    #endif
23918+}
23919+
23920+_SOKOL_PRIVATE bool _sg_validate_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
23921+    #if !defined(SOKOL_DEBUG)
23922+        _SOKOL_UNUSED(num_groups_x);
23923+        _SOKOL_UNUSED(num_groups_y);
23924+        _SOKOL_UNUSED(num_groups_z);
23925+        return true;
23926+    #else
23927+        if (_sg.desc.disable_validation) {
23928+            return true;
23929+        }
23930+        _sg_validate_begin();
23931+        _SG_VALIDATE(_sg.cur_pass.in_pass && _sg.cur_pass.is_compute, VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED);
23932+        _SG_VALIDATE((num_groups_x >= 0) && (num_groups_x < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSX);
23933+        _SG_VALIDATE((num_groups_y >= 0) && (num_groups_y < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSY);
23934+        _SG_VALIDATE((num_groups_z >= 0) && (num_groups_z < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSZ);
23935+        _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING);
23936+        return _sg_validate_end();
23937+    #endif
23938+}
23939+
23940+_SOKOL_PRIVATE bool _sg_validate_update_buffer(const _sg_buffer_t* buf, const sg_range* data) {
23941+    #if !defined(SOKOL_DEBUG)
23942+        _SOKOL_UNUSED(buf);
23943+        _SOKOL_UNUSED(data);
23944+        return true;
23945+    #else
23946+        if (_sg.desc.disable_validation) {
23947+            return true;
23948+        }
23949+        SOKOL_ASSERT(buf && data && data->ptr);
23950+        _sg_validate_begin();
23951+        _SG_VALIDATE(!buf->cmn.usage.immutable, VALIDATE_UPDATEBUF_USAGE);
23952+        _SG_VALIDATE(buf->cmn.size >= (int)data->size, VALIDATE_UPDATEBUF_SIZE);
23953+        _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_ONCE);
23954+        _SG_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_APPEND);
23955+        return _sg_validate_end();
23956+    #endif
23957+}
23958+
23959+_SOKOL_PRIVATE bool _sg_validate_append_buffer(const _sg_buffer_t* buf, const sg_range* data) {
23960+    #if !defined(SOKOL_DEBUG)
23961+        _SOKOL_UNUSED(buf);
23962+        _SOKOL_UNUSED(data);
23963+        return true;
23964+    #else
23965+        if (_sg.desc.disable_validation) {
23966+            return true;
23967+        }
23968+        SOKOL_ASSERT(buf && data && data->ptr);
23969+        _sg_validate_begin();
23970+        _SG_VALIDATE(!buf->cmn.usage.immutable, VALIDATE_APPENDBUF_USAGE);
23971+        _SG_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), VALIDATE_APPENDBUF_SIZE);
23972+        _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_APPENDBUF_UPDATE);
23973+        return _sg_validate_end();
23974+    #endif
23975+}
23976+
23977+_SOKOL_PRIVATE bool _sg_validate_update_image(const _sg_image_t* img, const sg_image_data* data) {
23978+    #if !defined(SOKOL_DEBUG)
23979+        _SOKOL_UNUSED(img);
23980+        _SOKOL_UNUSED(data);
23981+        return true;
23982+    #else
23983+        if (_sg.desc.disable_validation) {
23984+            return true;
23985+        }
23986+        SOKOL_ASSERT(img && data);
23987+        _sg_validate_begin();
23988+        _SG_VALIDATE(!img->cmn.usage.immutable, VALIDATE_UPDIMG_USAGE);
23989+        _SG_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, VALIDATE_UPDIMG_ONCE);
23990+        _sg_validate_image_data(data,
23991+            img->cmn.pixel_format,
23992+            img->cmn.width,
23993+            img->cmn.height,
23994+            img->cmn.num_mipmaps,
23995+            img->cmn.num_slices);
23996+        return _sg_validate_end();
23997+    #endif
23998+}
23999+
24000+_SOKOL_PRIVATE bool _sg_validate_shader_binding_limits(const sg_shader_desc* desc) {
24001+    SOKOL_ASSERT(desc);
24002+
24003+    // NOTE: this validation check is also active in release mode, if a shader uses
24004+    // more bindings than allowed, shader creation will fail
24005+    int vs_num_tex = 0;
24006+    int fs_num_tex = 0;
24007+    int cs_num_tex = 0;
24008+    int vs_num_sbuf = 0;
24009+    int fs_num_sbuf = 0;
24010+    int cs_num_sbuf = 0;
24011+    int vs_num_simg = 0;
24012+    int fs_num_simg = 0;
24013+    int cs_num_simg = 0;
24014+    int vs_num_texsmp = 0;
24015+    int fs_num_texsmp = 0;
24016+    int cs_num_texsmp = 0;
24017+    for (size_t i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
24018+        switch (desc->views[i].texture.stage) {
24019+            case SG_SHADERSTAGE_VERTEX:   vs_num_tex++; break;
24020+            case SG_SHADERSTAGE_FRAGMENT: fs_num_tex++; break;
24021+            case SG_SHADERSTAGE_COMPUTE:  cs_num_tex++; break;
24022+            default: break;
24023+        }
24024+        switch (desc->views[i].storage_buffer.stage) {
24025+            case SG_SHADERSTAGE_VERTEX:   vs_num_sbuf++; break;
24026+            case SG_SHADERSTAGE_FRAGMENT: fs_num_sbuf++; break;
24027+            case SG_SHADERSTAGE_COMPUTE:  cs_num_sbuf++; break;
24028+            default: break;
24029+        }
24030+        switch (desc->views[i].storage_image.stage) {
24031+            case SG_SHADERSTAGE_VERTEX:   vs_num_simg++; break;
24032+            case SG_SHADERSTAGE_FRAGMENT: fs_num_simg++; break;
24033+            case SG_SHADERSTAGE_COMPUTE:  cs_num_simg++; break;
24034+            default: break;
24035+        }
24036+    }
24037+    for (size_t i = 0; i < SG_MAX_TEXTURE_SAMPLER_PAIRS; i++) {
24038+        switch (desc->texture_sampler_pairs[i].stage) {
24039+            case SG_SHADERSTAGE_VERTEX:   vs_num_texsmp++; break;
24040+            case SG_SHADERSTAGE_FRAGMENT: fs_num_texsmp++; break;
24041+            case SG_SHADERSTAGE_COMPUTE:  cs_num_texsmp++; break;
24042+            default: break;
24043+        }
24044+    }
24045+    const int max_tex = _sg.limits.max_texture_bindings_per_stage;
24046+    const int max_sbuf = _sg.limits.max_storage_buffer_bindings_per_stage;
24047+    const int max_simg = _sg.limits.max_storage_image_bindings_per_stage;
24048+    bool retval = true;
24049+    if (vs_num_tex > max_tex) {
24050+        _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURES);
24051+        retval = false;
24052+    }
24053+    if (fs_num_tex > max_tex) {
24054+        _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURES);
24055+        retval = false;
24056+    }
24057+    if (cs_num_tex > max_tex) {
24058+        _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURES);
24059+        retval = false;
24060+    }
24061+    if (vs_num_sbuf > max_sbuf) {
24062+        _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEBUFFERS);
24063+        retval = false;
24064+    }
24065+    if (fs_num_sbuf > max_sbuf) {
24066+        _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEBUFFERS);
24067+        retval = false;
24068+    }
24069+    if (cs_num_sbuf > max_sbuf) {
24070+        _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEBUFFERS);
24071+        retval = false;
24072+    }
24073+    if (vs_num_simg > max_simg) {
24074+        _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_STORAGEIMAGES);
24075+        retval = false;
24076+    }
24077+    if (fs_num_simg > max_simg) {
24078+        _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_STORAGEIMAGES);
24079+        retval = false;
24080+    }
24081+    if (cs_num_simg > max_simg) {
24082+        _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_STORAGEIMAGES);
24083+        retval = false;
24084+    }
24085+    if (vs_num_texsmp > max_tex) {
24086+        _SG_ERROR(SHADERDESC_TOO_MANY_VERTEXSTAGE_TEXTURESAMPLERPAIRS);
24087+        retval = false;
24088+    }
24089+    if (fs_num_texsmp > max_tex) {
24090+        _SG_ERROR(SHADERDESC_TOO_MANY_FRAGMENTSTAGE_TEXTURESAMPLERPAIRS);
24091+        retval = false;
24092+    }
24093+    if (cs_num_texsmp > max_tex) {
24094+        _SG_ERROR(SHADERDESC_TOO_MANY_COMPUTESTAGE_TEXTURESAMPLERPAIRS);
24095+        retval = false;
24096+    }
24097+    return retval;
24098+}
24099+
24100+_SOKOL_PRIVATE bool _sg_validate_pass_attachment_limits(const sg_pass* pass) {
24101+    SOKOL_ASSERT(pass);
24102+    int num_color_atts = 0;
24103+    int num_resolve_atts = 0;
24104+    for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) {
24105+        if (pass->attachments.colors[att_index].id != SG_INVALID_ID) {
24106+            num_color_atts += 1;
24107+        }
24108+        if (pass->attachments.resolves[att_index].id != SG_INVALID_ID) {
24109+            num_resolve_atts += 1;
24110+        }
24111+    }
24112+    bool retval = true;
24113+    int max_color_atts = _sg.limits.max_color_attachments;
24114+    if (num_color_atts > max_color_atts) {
24115+        _SG_ERROR(BEGINPASS_TOO_MANY_COLOR_ATTACHMENTS);
24116+        retval = false;
24117+    }
24118+    // max_color_attachments not a bug
24119+    if (num_resolve_atts > max_color_atts) {
24120+        _SG_ERROR(BEGINPASS_TOO_MANY_RESOLVE_ATTACHMENTS);
24121+        retval = false;
24122+    }
24123+    return retval;
24124+}
24125+
24126+// ██████  ███████ ███████  ██████  ██    ██ ██████   ██████ ███████ ███████
24127+// ██   ██ ██      ██      ██    ██ ██    ██ ██   ██ ██      ██      ██
24128+// ██████  █████   ███████ ██    ██ ██    ██ ██████  ██      █████   ███████
24129+// ██   ██ ██           ██ ██    ██ ██    ██ ██   ██ ██      ██           ██
24130+// ██   ██ ███████ ███████  ██████   ██████  ██   ██  ██████ ███████ ███████
24131+//
24132+// >>resources
24133+_SOKOL_PRIVATE sg_buffer_usage _sg_buffer_usage_defaults(const sg_buffer_usage* usg) {
24134+    sg_buffer_usage def = *usg;
24135+    if (!(def.vertex_buffer || def.index_buffer || def.storage_buffer)) {
24136+        def.vertex_buffer = true;
24137+    }
24138+    if (!(def.immutable || def.stream_update || def.dynamic_update)) {
24139+        def.immutable = true;
24140+    }
24141+    return def;
24142+}
24143+
24144+
24145+_SOKOL_PRIVATE sg_buffer_desc _sg_buffer_desc_defaults(const sg_buffer_desc* desc) {
24146+    sg_buffer_desc def = *desc;
24147+    def.usage = _sg_buffer_usage_defaults(&def.usage);
24148+    if (def.size == 0) {
24149+        def.size = def.data.size;
24150+    }
24151+    return def;
24152+}
24153+
24154+_SOKOL_PRIVATE sg_image_usage _sg_image_usage_defaults(const sg_image_usage *usg) {
24155+    sg_image_usage def = *usg;
24156+    if (!(def.immutable || def.stream_update || def.dynamic_update)) {
24157+        def.immutable = true;
24158+    }
24159+    return def;
24160+}
24161+
24162+_SOKOL_PRIVATE sg_image_desc _sg_image_desc_defaults(const sg_image_desc* desc) {
24163+    sg_image_desc def = *desc;
24164+    def.type = _sg_def(def.type, SG_IMAGETYPE_2D);
24165+    def.usage = _sg_image_usage_defaults(&def.usage);
24166+    def.num_slices = _sg_def(def.num_slices, def.type == SG_IMAGETYPE_CUBE ? 6 : 1);
24167+    def.num_mipmaps = _sg_def(def.num_mipmaps, 1);
24168+    if (def.usage.color_attachment || def.usage.resolve_attachment) {
24169+        def.pixel_format = _sg_def(def.pixel_format, _sg.desc.environment.defaults.color_format);
24170+        def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count);
24171+    } else if (def.usage.depth_stencil_attachment) {
24172+        def.pixel_format = _sg_def(def.pixel_format, _sg.desc.environment.defaults.depth_format);
24173+        def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count);
24174+    } else {
24175+        def.pixel_format = _sg_def(def.pixel_format, SG_PIXELFORMAT_RGBA8);
24176+        def.sample_count = _sg_def(def.sample_count, 1);
24177+    }
24178+    return def;
24179+}
24180+
24181+_SOKOL_PRIVATE sg_sampler_desc _sg_sampler_desc_defaults(const sg_sampler_desc* desc) {
24182+    sg_sampler_desc def = *desc;
24183+    def.min_filter = _sg_def(def.min_filter, SG_FILTER_NEAREST);
24184+    def.mag_filter = _sg_def(def.mag_filter, SG_FILTER_NEAREST);
24185+    def.mipmap_filter = _sg_def(def.mipmap_filter, SG_FILTER_NEAREST);
24186+    def.wrap_u = _sg_def(def.wrap_u, SG_WRAP_REPEAT);
24187+    def.wrap_v = _sg_def(def.wrap_v, SG_WRAP_REPEAT);
24188+    def.wrap_w = _sg_def(def.wrap_w, SG_WRAP_REPEAT);
24189+    def.max_lod = _sg_def_flt(def.max_lod, FLT_MAX);
24190+    def.border_color = _sg_def(def.border_color, SG_BORDERCOLOR_OPAQUE_BLACK);
24191+    def.compare = _sg_def(def.compare, SG_COMPAREFUNC_NEVER);
24192+    def.max_anisotropy = _sg_def(def.max_anisotropy, 1);
24193+    return def;
24194+}
24195+
24196+_SOKOL_PRIVATE sg_shader_desc _sg_shader_desc_defaults(const sg_shader_desc* desc) {
24197+    sg_shader_desc def = *desc;
24198+    #if defined(SOKOL_METAL)
24199+        def.vertex_func.entry = _sg_def(def.vertex_func.entry, "_main");
24200+        def.fragment_func.entry = _sg_def(def.fragment_func.entry, "_main");
24201+        def.compute_func.entry = _sg_def(def.compute_func.entry, "_main");
24202+    #else
24203+        def.vertex_func.entry = _sg_def(def.vertex_func.entry, "main");
24204+        def.fragment_func.entry = _sg_def(def.fragment_func.entry, "main");
24205+        def.compute_func.entry = _sg_def(def.compute_func.entry, "main");
24206+    #endif
24207+    #if defined(SOKOL_D3D11)
24208+        if (def.vertex_func.source) {
24209+            def.vertex_func.d3d11_target = _sg_def(def.vertex_func.d3d11_target, "vs_4_0");
24210+        }
24211+        if (def.fragment_func.source) {
24212+            def.fragment_func.d3d11_target = _sg_def(def.fragment_func.d3d11_target, "ps_4_0");
24213+        }
24214+        if (def.compute_func.source) {
24215+            def.compute_func.d3d11_target = _sg_def(def.fragment_func.d3d11_target,"cs_5_0");
24216+        }
24217+    #endif
24218+    def.mtl_threads_per_threadgroup.y = _sg_def(desc->mtl_threads_per_threadgroup.y, 1);
24219+    def.mtl_threads_per_threadgroup.z = _sg_def(desc->mtl_threads_per_threadgroup.z, 1);
24220+    for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) {
24221+        sg_shader_uniform_block* ub_desc = &def.uniform_blocks[ub_index];
24222+        if (ub_desc->stage != SG_SHADERSTAGE_NONE) {
24223+            ub_desc->layout = _sg_def(ub_desc->layout, SG_UNIFORMLAYOUT_NATIVE);
24224+            for (size_t u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) {
24225+                sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index];
24226+                if (u_desc->type == SG_UNIFORMTYPE_INVALID) {
24227+                    break;
24228+                }
24229+                u_desc->array_count = _sg_def(u_desc->array_count, 1);
24230+            }
24231+        }
24232+    }
24233+    for (size_t view_index = 0; view_index < SG_MAX_VIEW_BINDSLOTS; view_index++) {
24234+        sg_shader_view* view_desc = &def.views[view_index];
24235+        if (view_desc->texture.stage != SG_SHADERSTAGE_NONE) {
24236+            view_desc->texture.image_type = _sg_def(view_desc->texture.image_type, SG_IMAGETYPE_2D);
24237+            view_desc->texture.sample_type = _sg_def(view_desc->texture.sample_type, SG_IMAGESAMPLETYPE_FLOAT);
24238+        } else if (view_desc->storage_image.stage != SG_SHADERSTAGE_NONE) {
24239+            view_desc->storage_image.image_type = _sg_def(view_desc->storage_image.image_type, SG_IMAGETYPE_2D);
24240+        }
24241+    }
24242+    for (size_t smp_index = 0; smp_index < SG_MAX_SAMPLER_BINDSLOTS; smp_index++) {
24243+        sg_shader_sampler* smp_desc = &def.samplers[smp_index];
24244+        if (smp_desc->stage != SG_SHADERSTAGE_NONE) {
24245+            smp_desc->sampler_type = _sg_def(smp_desc->sampler_type, SG_SAMPLERTYPE_FILTERING);
24246+        }
24247+    }
24248+    return def;
24249+}
24250+
24251+_SOKOL_PRIVATE sg_pipeline_desc _sg_pipeline_desc_defaults(const sg_pipeline_desc* desc) {
24252+    sg_pipeline_desc def = *desc;
24253+
24254+    // FIXME: should we actually do all this stuff for a compute pipeline?
24255+
24256+    def.primitive_type = _sg_def(def.primitive_type, SG_PRIMITIVETYPE_TRIANGLES);
24257+    def.index_type = _sg_def(def.index_type, SG_INDEXTYPE_NONE);
24258+    def.cull_mode = _sg_def(def.cull_mode, SG_CULLMODE_NONE);
24259+    def.face_winding = _sg_def(def.face_winding, SG_FACEWINDING_CW);
24260+    def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count);
24261+
24262+    def.stencil.front.compare = _sg_def(def.stencil.front.compare, SG_COMPAREFUNC_ALWAYS);
24263+    def.stencil.front.fail_op = _sg_def(def.stencil.front.fail_op, SG_STENCILOP_KEEP);
24264+    def.stencil.front.depth_fail_op = _sg_def(def.stencil.front.depth_fail_op, SG_STENCILOP_KEEP);
24265+    def.stencil.front.pass_op = _sg_def(def.stencil.front.pass_op, SG_STENCILOP_KEEP);
24266+    def.stencil.back.compare = _sg_def(def.stencil.back.compare, SG_COMPAREFUNC_ALWAYS);
24267+    def.stencil.back.fail_op = _sg_def(def.stencil.back.fail_op, SG_STENCILOP_KEEP);
24268+    def.stencil.back.depth_fail_op = _sg_def(def.stencil.back.depth_fail_op, SG_STENCILOP_KEEP);
24269+    def.stencil.back.pass_op = _sg_def(def.stencil.back.pass_op, SG_STENCILOP_KEEP);
24270+
24271+    def.depth.compare = _sg_def(def.depth.compare, SG_COMPAREFUNC_ALWAYS);
24272+    def.depth.pixel_format = _sg_def(def.depth.pixel_format, _sg.desc.environment.defaults.depth_format);
24273+    if (def.colors[0].pixel_format == SG_PIXELFORMAT_NONE) {
24274+        // special case depth-only rendering, enforce a color count of 0
24275+        def.color_count = 0;
24276+    } else {
24277+        def.color_count = _sg_def(def.color_count, 1);
24278+    }
24279+    if (def.color_count > SG_MAX_COLOR_ATTACHMENTS) {
24280+        def.color_count = SG_MAX_COLOR_ATTACHMENTS;
24281+    }
24282+    for (int i = 0; i < def.color_count; i++) {
24283+        sg_color_target_state* cs = &def.colors[i];
24284+        cs->pixel_format = _sg_def(cs->pixel_format, _sg.desc.environment.defaults.color_format);
24285+        cs->write_mask = _sg_def(cs->write_mask, SG_COLORMASK_RGBA);
24286+        sg_blend_state* bs = &def.colors[i].blend;
24287+        bs->op_rgb = _sg_def(bs->op_rgb, SG_BLENDOP_ADD);
24288+        bs->src_factor_rgb = _sg_def(bs->src_factor_rgb, SG_BLENDFACTOR_ONE);
24289+        if ((bs->op_rgb == SG_BLENDOP_MIN) || (bs->op_rgb == SG_BLENDOP_MAX)) {
24290+            bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ONE);
24291+        } else {
24292+            bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ZERO);
24293+        }
24294+        bs->op_alpha = _sg_def(bs->op_alpha, SG_BLENDOP_ADD);
24295+        bs->src_factor_alpha = _sg_def(bs->src_factor_alpha, SG_BLENDFACTOR_ONE);
24296+        if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) {
24297+            bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ONE);
24298+        } else {
24299+            bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ZERO);
24300+        }
24301+    }
24302+
24303+    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
24304+        sg_vertex_attr_state* a_state = &def.layout.attrs[attr_index];
24305+        if (a_state->format == SG_VERTEXFORMAT_INVALID) {
24306+            break;
24307+        }
24308+        SOKOL_ASSERT((a_state->buffer_index >= 0) && (a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS));
24309+        sg_vertex_buffer_layout_state* l_state = &def.layout.buffers[a_state->buffer_index];
24310+        l_state->step_func = _sg_def(l_state->step_func, SG_VERTEXSTEP_PER_VERTEX);
24311+        l_state->step_rate = _sg_def(l_state->step_rate, 1);
24312+    }
24313+
24314+    // resolve vertex layout strides and offsets
24315+    _SG_STRUCT(int, auto_offset[SG_MAX_VERTEXBUFFER_BINDSLOTS]);
24316+    bool use_auto_offset = true;
24317+    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
24318+        // to use computed offsets, *all* attr offsets must be 0
24319+        if (def.layout.attrs[attr_index].offset != 0) {
24320+            use_auto_offset = false;
24321+        }
24322+    }
24323+    for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) {
24324+        sg_vertex_attr_state* a_state = &def.layout.attrs[attr_index];
24325+        if (a_state->format == SG_VERTEXFORMAT_INVALID) {
24326+            break;
24327+        }
24328+        SOKOL_ASSERT((a_state->buffer_index >= 0) && (a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS));
24329+        if (use_auto_offset) {
24330+            a_state->offset = auto_offset[a_state->buffer_index];
24331+        }
24332+        auto_offset[a_state->buffer_index] += _sg_vertexformat_bytesize(a_state->format);
24333+    }
24334+    // compute vertex strides if needed
24335+    for (int buf_index = 0; buf_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; buf_index++) {
24336+        sg_vertex_buffer_layout_state* l_state = &def.layout.buffers[buf_index];
24337+        if (l_state->stride == 0) {
24338+            l_state->stride = auto_offset[buf_index];
24339+        }
24340+    }
24341+
24342+    return def;
24343+}
24344+
24345+_SOKOL_PRIVATE sg_view_desc _sg_view_desc_defaults(const sg_view_desc* desc) {
24346+    sg_view_desc def = *desc;
24347+    return def;
24348+}
24349+
24350+_SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) {
24351+    sg_buffer res;
24352+    int slot_index = _sg_pool_alloc_index(&_sg.pools.buffer_pool);
24353+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
24354+        res.id = _sg_slot_alloc(&_sg.pools.buffer_pool, &_sg.pools.buffers[slot_index].slot, slot_index);
24355+        _sg_resource_stats_inc(buffers.allocated);
24356+    } else {
24357+        res.id = SG_INVALID_ID;
24358+        _SG_ERROR(BUFFER_POOL_EXHAUSTED);
24359+    }
24360+    return res;
24361+}
24362+
24363+_SOKOL_PRIVATE sg_image _sg_alloc_image(void) {
24364+    sg_image res;
24365+    int slot_index = _sg_pool_alloc_index(&_sg.pools.image_pool);
24366+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
24367+        res.id = _sg_slot_alloc(&_sg.pools.image_pool, &_sg.pools.images[slot_index].slot, slot_index);
24368+        _sg_resource_stats_inc(images.allocated);
24369+    } else {
24370+        res.id = SG_INVALID_ID;
24371+        _SG_ERROR(IMAGE_POOL_EXHAUSTED);
24372+    }
24373+    return res;
24374+}
24375+
24376+_SOKOL_PRIVATE sg_sampler _sg_alloc_sampler(void) {
24377+    sg_sampler res;
24378+    int slot_index = _sg_pool_alloc_index(&_sg.pools.sampler_pool);
24379+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
24380+        res.id = _sg_slot_alloc(&_sg.pools.sampler_pool, &_sg.pools.samplers[slot_index].slot, slot_index);
24381+        _sg_resource_stats_inc(samplers.allocated);
24382+    } else {
24383+        res.id = SG_INVALID_ID;
24384+        _SG_ERROR(SAMPLER_POOL_EXHAUSTED);
24385+    }
24386+    return res;
24387+}
24388+
24389+_SOKOL_PRIVATE sg_shader _sg_alloc_shader(void) {
24390+    sg_shader res;
24391+    int slot_index = _sg_pool_alloc_index(&_sg.pools.shader_pool);
24392+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
24393+        res.id = _sg_slot_alloc(&_sg.pools.shader_pool, &_sg.pools.shaders[slot_index].slot, slot_index);
24394+        _sg_resource_stats_inc(shaders.allocated);
24395+    } else {
24396+        res.id = SG_INVALID_ID;
24397+        _SG_ERROR(SHADER_POOL_EXHAUSTED);
24398+    }
24399+    return res;
24400+}
24401+
24402+_SOKOL_PRIVATE sg_pipeline _sg_alloc_pipeline(void) {
24403+    sg_pipeline res;
24404+    int slot_index = _sg_pool_alloc_index(&_sg.pools.pipeline_pool);
24405+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
24406+        res.id =_sg_slot_alloc(&_sg.pools.pipeline_pool, &_sg.pools.pipelines[slot_index].slot, slot_index);
24407+        _sg_resource_stats_inc(pipelines.allocated);
24408+    } else {
24409+        res.id = SG_INVALID_ID;
24410+        _SG_ERROR(PIPELINE_POOL_EXHAUSTED);
24411+    }
24412+    return res;
24413+}
24414+
24415+_SOKOL_PRIVATE sg_view _sg_alloc_view(void) {
24416+    sg_view res;
24417+    int slot_index = _sg_pool_alloc_index(&_sg.pools.view_pool);
24418+    if (_SG_INVALID_SLOT_INDEX != slot_index) {
24419+        res.id = _sg_slot_alloc(&_sg.pools.view_pool, &_sg.pools.views[slot_index].slot, slot_index);
24420+        _sg_resource_stats_inc(views.allocated);
24421+    } else {
24422+        res.id = SG_INVALID_ID;
24423+        _SG_ERROR(VIEW_POOL_EXHAUSTED);
24424+    }
24425+    return res;
24426+}
24427+
24428+_SOKOL_PRIVATE void _sg_dealloc_buffer(_sg_buffer_t* buf) {
24429+    SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC) && (buf->slot.id != SG_INVALID_ID));
24430+    _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf->slot.id));
24431+    _sg_slot_reset(&buf->slot);
24432+    _sg_resource_stats_inc(buffers.deallocated);
24433+}
24434+
24435+_SOKOL_PRIVATE void _sg_dealloc_image(_sg_image_t* img) {
24436+    SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC) && (img->slot.id != SG_INVALID_ID));
24437+    _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img->slot.id));
24438+    _sg_slot_reset(&img->slot);
24439+    _sg_resource_stats_inc(images.deallocated);
24440+}
24441+
24442+_SOKOL_PRIVATE void _sg_dealloc_sampler(_sg_sampler_t* smp) {
24443+    SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC) && (smp->slot.id != SG_INVALID_ID));
24444+    _sg_pool_free_index(&_sg.pools.sampler_pool, _sg_slot_index(smp->slot.id));
24445+    _sg_slot_reset(&smp->slot);
24446+    _sg_resource_stats_inc(samplers.deallocated);
24447+}
24448+
24449+_SOKOL_PRIVATE void _sg_dealloc_shader(_sg_shader_t* shd) {
24450+    SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC) && (shd->slot.id != SG_INVALID_ID));
24451+    _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd->slot.id));
24452+    _sg_slot_reset(&shd->slot);
24453+    _sg_resource_stats_inc(shaders.deallocated);
24454+}
24455+
24456+_SOKOL_PRIVATE void _sg_dealloc_pipeline(_sg_pipeline_t* pip) {
24457+    SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC) && (pip->slot.id != SG_INVALID_ID));
24458+    _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip->slot.id));
24459+    _sg_slot_reset(&pip->slot);
24460+    _sg_resource_stats_inc(pipelines.deallocated);
24461+}
24462+
24463+_SOKOL_PRIVATE void _sg_dealloc_view(_sg_view_t* view) {
24464+    SOKOL_ASSERT(view && (view->slot.state == SG_RESOURCESTATE_ALLOC) && (view->slot.id != SG_INVALID_ID));
24465+    _sg_pool_free_index(&_sg.pools.view_pool, _sg_slot_index(view->slot.id));
24466+    _sg_slot_reset(&view->slot);
24467+    _sg_resource_stats_inc(views.deallocated);
24468+}
24469+
24470+_SOKOL_PRIVATE void _sg_init_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) {
24471+    SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC));
24472+    SOKOL_ASSERT(desc);
24473+    if (_sg_validate_buffer_desc(desc)) {
24474+        _sg_buffer_common_init(&buf->cmn, desc);
24475+        buf->slot.state = _sg_create_buffer(buf, desc);
24476+    } else {
24477+        buf->slot.state = SG_RESOURCESTATE_FAILED;
24478+    }
24479+    SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID)||(buf->slot.state == SG_RESOURCESTATE_FAILED));
24480+    _sg_resource_stats_inc(buffers.inited);
24481+}
24482+
24483+_SOKOL_PRIVATE void _sg_init_image(_sg_image_t* img, const sg_image_desc* desc) {
24484+    SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC));
24485+    SOKOL_ASSERT(desc);
24486+    if (_sg_validate_image_desc(desc)) {
24487+        _sg_image_common_init(&img->cmn, desc);
24488+        img->slot.state = _sg_create_image(img, desc);
24489+    } else {
24490+        img->slot.state = SG_RESOURCESTATE_FAILED;
24491+    }
24492+    SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID)||(img->slot.state == SG_RESOURCESTATE_FAILED));
24493+    _sg_resource_stats_inc(images.inited);
24494+}
24495+
24496+_SOKOL_PRIVATE void _sg_init_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) {
24497+    SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC));
24498+    SOKOL_ASSERT(desc);
24499+    if (_sg_validate_sampler_desc(desc)) {
24500+        _sg_sampler_common_init(&smp->cmn, desc);
24501+        smp->slot.state = _sg_create_sampler(smp, desc);
24502+    } else {
24503+        smp->slot.state = SG_RESOURCESTATE_FAILED;
24504+    }
24505+    SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID)||(smp->slot.state == SG_RESOURCESTATE_FAILED));
24506+    _sg_resource_stats_inc(samplers.inited);
24507+}
24508+
24509+_SOKOL_PRIVATE void _sg_init_shader(_sg_shader_t* shd, const sg_shader_desc* desc) {
24510+    SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC));
24511+    SOKOL_ASSERT(desc);
24512+    if (!_sg_validate_shader_desc(desc)) {
24513+        shd->slot.state = SG_RESOURCESTATE_FAILED;
24514+        return;
24515+    }
24516+    if (!_sg_validate_shader_binding_limits(desc)) {
24517+        shd->slot.state = SG_RESOURCESTATE_FAILED;
24518+        return;
24519+    }
24520+    _sg_shader_common_init(&shd->cmn, desc);
24521+    shd->slot.state = _sg_create_shader(shd, desc);
24522+    SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED));
24523+    _sg_resource_stats_inc(shaders.inited);
24524+}
24525+
24526+_SOKOL_PRIVATE void _sg_init_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) {
24527+    SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC));
24528+    SOKOL_ASSERT(desc);
24529+    if (_sg_validate_pipeline_desc(desc)) {
24530+        _sg_shader_t* shd = _sg_lookup_shader(desc->shader.id);
24531+        if (shd && (shd->slot.state == SG_RESOURCESTATE_VALID)) {
24532+            _sg_pipeline_common_init(&pip->cmn, desc, shd);
24533+            pip->slot.state = _sg_create_pipeline(pip, desc);
24534+        } else {
24535+            pip->slot.state = SG_RESOURCESTATE_FAILED;
24536+        }
24537+    } else {
24538+        pip->slot.state = SG_RESOURCESTATE_FAILED;
24539+    }
24540+    SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID)||(pip->slot.state == SG_RESOURCESTATE_FAILED));
24541+    _sg_resource_stats_inc(pipelines.inited);
24542+}
24543+
24544+_SOKOL_PRIVATE void _sg_init_view(_sg_view_t* view, const sg_view_desc* desc) {
24545+    SOKOL_ASSERT(view && view->slot.state == SG_RESOURCESTATE_ALLOC);
24546+    SOKOL_ASSERT(desc);
24547+    if (_sg_validate_view_desc(desc)) {
24548+        uint32_t buf_id = desc->storage_buffer.buffer.id;
24549+        uint32_t img_id = desc->texture.image.id;
24550+        img_id = img_id ? img_id : desc->storage_image.image.id;
24551+        img_id = img_id ? img_id : desc->color_attachment.image.id;
24552+        img_id = img_id ? img_id : desc->resolve_attachment.image.id;
24553+        img_id = img_id ? img_id : desc->depth_stencil_attachment.image.id;
24554+        _sg_buffer_t* buf = buf_id ? _sg_lookup_buffer(buf_id) : 0;
24555+        _sg_image_t* img = img_id ? _sg_lookup_image(img_id) : 0;
24556+        sg_resource_state res_state = SG_RESOURCESTATE_INVALID;
24557+        if (buf) {
24558+            SOKOL_ASSERT(!img);
24559+            res_state = buf->slot.state;
24560+        } else if (img) {
24561+            SOKOL_ASSERT(!buf);
24562+            res_state = img->slot.state;
24563+        }
24564+        if (res_state == SG_RESOURCESTATE_VALID) {
24565+            _sg_view_common_init(&view->cmn, desc, buf, img);
24566+            view->slot.state = _sg_create_view(view, desc);
24567+        } else {
24568+            view->slot.state = SG_RESOURCESTATE_FAILED;
24569+        }
24570+    } else {
24571+       view->slot.state = SG_RESOURCESTATE_FAILED;
24572+    }
24573+    SOKOL_ASSERT((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED));
24574+    _sg_resource_stats_inc(views.inited);
24575+}
24576+
24577+_SOKOL_PRIVATE void _sg_uninit_buffer(_sg_buffer_t* buf) {
24578+    SOKOL_ASSERT(buf && ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)));
24579+    _sg_discard_buffer(buf);
24580+    _sg_reset_buffer_to_alloc_state(buf);
24581+    _sg_resource_stats_inc(buffers.uninited);
24582+}
24583+
24584+_SOKOL_PRIVATE void _sg_uninit_image(_sg_image_t* img) {
24585+    SOKOL_ASSERT(img && ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)));
24586+    _sg_discard_image(img);
24587+    _sg_reset_image_to_alloc_state(img);
24588+    _sg_resource_stats_inc(images.uninited);
24589+}
24590+
24591+_SOKOL_PRIVATE void _sg_uninit_sampler(_sg_sampler_t* smp) {
24592+    SOKOL_ASSERT(smp && ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)));
24593+    _sg_discard_sampler(smp);
24594+    _sg_reset_sampler_to_alloc_state(smp);
24595+    _sg_resource_stats_inc(samplers.uninited);
24596+}
24597+
24598+_SOKOL_PRIVATE void _sg_uninit_shader(_sg_shader_t* shd) {
24599+    SOKOL_ASSERT(shd && ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)));
24600+    _sg_discard_shader(shd);
24601+    _sg_reset_shader_to_alloc_state(shd);
24602+    _sg_resource_stats_inc(shaders.uninited);
24603+}
24604+
24605+_SOKOL_PRIVATE void _sg_uninit_pipeline(_sg_pipeline_t* pip) {
24606+    SOKOL_ASSERT(pip && ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)));
24607+    _sg_discard_pipeline(pip);
24608+    _sg_reset_pipeline_to_alloc_state(pip);
24609+    _sg_resource_stats_inc(pipelines.uninited);
24610+}
24611+
24612+_SOKOL_PRIVATE void _sg_uninit_view(_sg_view_t* view) {
24613+    SOKOL_ASSERT(view && ((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)));
24614+    _sg_discard_view(view);
24615+    _sg_reset_view_to_alloc_state(view);
24616+    _sg_resource_stats_inc(views.uninited);
24617+}
24618+
24619+_SOKOL_PRIVATE void _sg_setup_commit_listeners(const sg_desc* desc) {
24620+    SOKOL_ASSERT(desc->max_commit_listeners > 0);
24621+    SOKOL_ASSERT(0 == _sg.commit_listeners.items);
24622+    SOKOL_ASSERT(0 == _sg.commit_listeners.num);
24623+    SOKOL_ASSERT(0 == _sg.commit_listeners.upper);
24624+    _sg.commit_listeners.num = desc->max_commit_listeners;
24625+    const size_t size = (size_t)_sg.commit_listeners.num * sizeof(sg_commit_listener);
24626+    _sg.commit_listeners.items = (sg_commit_listener*)_sg_malloc_clear(size);
24627+}
24628+
24629+_SOKOL_PRIVATE void _sg_discard_commit_listeners(void) {
24630+    SOKOL_ASSERT(0 != _sg.commit_listeners.items);
24631+    _sg_free(_sg.commit_listeners.items);
24632+    _sg.commit_listeners.items = 0;
24633+}
24634+
24635+_SOKOL_PRIVATE void _sg_notify_commit_listeners(void) {
24636+    SOKOL_ASSERT(_sg.commit_listeners.items);
24637+    for (int i = 0; i < _sg.commit_listeners.upper; i++) {
24638+        const sg_commit_listener* listener = &_sg.commit_listeners.items[i];
24639+        if (listener->func) {
24640+            listener->func(listener->user_data);
24641+        }
24642+    }
24643+}
24644+
24645+_SOKOL_PRIVATE bool _sg_add_commit_listener(const sg_commit_listener* new_listener) {
24646+    SOKOL_ASSERT(new_listener && new_listener->func);
24647+    SOKOL_ASSERT(_sg.commit_listeners.items);
24648+    // first check if the listener hadn't been added already
24649+    for (int i = 0; i < _sg.commit_listeners.upper; i++) {
24650+        const sg_commit_listener* slot = &_sg.commit_listeners.items[i];
24651+        if ((slot->func == new_listener->func) && (slot->user_data == new_listener->user_data)) {
24652+            _SG_ERROR(IDENTICAL_COMMIT_LISTENER);
24653+            return false;
24654+        }
24655+    }
24656+    // first try to plug a hole
24657+    sg_commit_listener* slot = 0;
24658+    for (int i = 0; i < _sg.commit_listeners.upper; i++) {
24659+        if (_sg.commit_listeners.items[i].func == 0) {
24660+            slot = &_sg.commit_listeners.items[i];
24661+            break;
24662+        }
24663+    }
24664+    if (!slot) {
24665+        // append to end
24666+        if (_sg.commit_listeners.upper < _sg.commit_listeners.num) {
24667+            slot = &_sg.commit_listeners.items[_sg.commit_listeners.upper++];
24668+        }
24669+    }
24670+    if (!slot) {
24671+        _SG_ERROR(COMMIT_LISTENER_ARRAY_FULL);
24672+        return false;
24673+    }
24674+    *slot = *new_listener;
24675+    return true;
24676+}
24677+
24678+_SOKOL_PRIVATE bool _sg_remove_commit_listener(const sg_commit_listener* listener) {
24679+    SOKOL_ASSERT(listener && listener->func);
24680+    SOKOL_ASSERT(_sg.commit_listeners.items);
24681+    for (int i = 0; i < _sg.commit_listeners.upper; i++) {
24682+        sg_commit_listener* slot = &_sg.commit_listeners.items[i];
24683+        // both the function pointer and user data must match!
24684+        if ((slot->func == listener->func) && (slot->user_data == listener->user_data)) {
24685+            slot->func = 0;
24686+            slot->user_data = 0;
24687+            // NOTE: since _sg_add_commit_listener() already catches duplicates,
24688+            // we don't need to worry about them here
24689+            return true;
24690+        }
24691+    }
24692+    return false;
24693+}
24694+
24695+_SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) {
24696+    /*
24697+        NOTE: on WebGPU, the default color pixel format MUST be provided,
24698+        it cannot be a default compile-time constant.
24699+    */
24700+    sg_desc res = *desc;
24701+    #if defined(SOKOL_WGPU)
24702+        SOKOL_ASSERT(SG_PIXELFORMAT_NONE < res.environment.defaults.color_format);
24703+    #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11)
24704+        res.environment.defaults.color_format = _sg_def(res.environment.defaults.color_format, SG_PIXELFORMAT_BGRA8);
24705+    #else
24706+        res.environment.defaults.color_format = _sg_def(res.environment.defaults.color_format, SG_PIXELFORMAT_RGBA8);
24707+    #endif
24708+    res.environment.defaults.depth_format = _sg_def(res.environment.defaults.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL);
24709+    res.environment.defaults.sample_count = _sg_def(res.environment.defaults.sample_count, 1);
24710+    res.buffer_pool_size = _sg_def(res.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE);
24711+    res.image_pool_size = _sg_def(res.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE);
24712+    res.sampler_pool_size = _sg_def(res.sampler_pool_size, _SG_DEFAULT_SAMPLER_POOL_SIZE);
24713+    res.shader_pool_size = _sg_def(res.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE);
24714+    res.pipeline_pool_size = _sg_def(res.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE);
24715+    res.view_pool_size = _sg_def(res.view_pool_size, _SG_DEFAULT_VIEW_POOL_SIZE);
24716+    res.uniform_buffer_size = _sg_def(res.uniform_buffer_size, _SG_DEFAULT_UB_SIZE);
24717+    res.max_commit_listeners = _sg_def(res.max_commit_listeners, _SG_DEFAULT_MAX_COMMIT_LISTENERS);
24718+    res.wgpu.bindgroups_cache_size = _sg_def(res.wgpu.bindgroups_cache_size, _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE);
24719+    res.vulkan.copy_staging_buffer_size = _sg_def(res.vulkan.copy_staging_buffer_size, _SG_DEFAULT_VK_COPY_STAGING_SIZE);
24720+    res.vulkan.stream_staging_buffer_size = _sg_def(res.vulkan.stream_staging_buffer_size, _SG_DEFAULT_VK_STREAM_STAGING_SIZE);
24721+    res.vulkan.descriptor_buffer_size = _sg_def(res.vulkan.descriptor_buffer_size, _SG_DEFAULT_VK_DESCRIPTOR_BUFFER_SIZE);
24722+    return res;
24723+}
24724+
24725+_SOKOL_PRIVATE sg_pass _sg_pass_defaults(const sg_pass* pass) {
24726+    sg_pass res = *pass;
24727+    if (!res.compute) {
24728+        if (!pass->swapchain.invalid && _sg_attachments_empty(&pass->attachments)) {
24729+            // this is a swapchain-pass
24730+            res.swapchain.sample_count = _sg_def(res.swapchain.sample_count, _sg.desc.environment.defaults.sample_count);
24731+            res.swapchain.color_format = _sg_def(res.swapchain.color_format, _sg.desc.environment.defaults.color_format);
24732+            res.swapchain.depth_format = _sg_def(res.swapchain.depth_format, _sg.desc.environment.defaults.depth_format);
24733+        }
24734+        res.action = _sg_pass_action_defaults(&res.action);
24735+    }
24736+    return res;
24737+}
24738+
24739+_SOKOL_PRIVATE void _sg_discard_all_resources(void) {
24740+    /*  this is a bit dumb since it loops over all pool slots to
24741+        find the occupied slots, on the other hand it is only ever
24742+        executed at shutdown
24743+        NOTE: ONLY EXECUTE THIS AT SHUTDOWN
24744+              ...because the free queues will not be reset
24745+              and the resource slots not be cleared!
24746+    */
24747+    for (int i = 1; i < _sg.pools.buffer_pool.size; i++) {
24748+        sg_resource_state state = _sg.pools.buffers[i].slot.state;
24749+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
24750+            _sg_discard_buffer(&_sg.pools.buffers[i]);
24751+        }
24752+    }
24753+    for (int i = 1; i < _sg.pools.image_pool.size; i++) {
24754+        sg_resource_state state = _sg.pools.images[i].slot.state;
24755+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
24756+            _sg_discard_image(&_sg.pools.images[i]);
24757+        }
24758+    }
24759+    for (int i = 1; i < _sg.pools.sampler_pool.size; i++) {
24760+        sg_resource_state state = _sg.pools.samplers[i].slot.state;
24761+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
24762+            _sg_discard_sampler(&_sg.pools.samplers[i]);
24763+        }
24764+    }
24765+    for (int i = 1; i < _sg.pools.shader_pool.size; i++) {
24766+        sg_resource_state state = _sg.pools.shaders[i].slot.state;
24767+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
24768+            _sg_discard_shader(&_sg.pools.shaders[i]);
24769+        }
24770+    }
24771+    for (int i = 1; i < _sg.pools.pipeline_pool.size; i++) {
24772+        sg_resource_state state = _sg.pools.pipelines[i].slot.state;
24773+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
24774+            _sg_discard_pipeline(&_sg.pools.pipelines[i]);
24775+        }
24776+    }
24777+    for (int i = 1; i < _sg.pools.view_pool.size; i++) {
24778+        sg_resource_state state = _sg.pools.views[i].slot.state;
24779+        if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) {
24780+            _sg_discard_view(&_sg.pools.views[i]);
24781+        }
24782+    }
24783+}
24784+
24785+_SOKOL_PRIVATE void _sg_override_portable_limits(void) {
24786+    if (_sg.desc.enforce_portable_limits) {
24787+        _sg.limits.max_color_attachments = SG_MAX_PORTABLE_COLOR_ATTACHMENTS;
24788+        _sg.limits.max_texture_bindings_per_stage = SG_MAX_PORTABLE_TEXTURE_BINDINGS_PER_STAGE;
24789+        if (_sg.features.compute) {
24790+            _sg.limits.max_storage_buffer_bindings_per_stage = SG_MAX_PORTABLE_STORAGEBUFFER_BINDINGS_PER_STAGE;
24791+            _sg.limits.max_storage_image_bindings_per_stage = SG_MAX_PORTABLE_STORAGEIMAGE_BINDINGS_PER_STAGE;
24792+        }
24793+    }
24794+}
24795+
24796+// ██████  ██    ██ ██████  ██      ██  ██████
24797+// ██   ██ ██    ██ ██   ██ ██      ██ ██
24798+// ██████  ██    ██ ██████  ██      ██ ██
24799+// ██      ██    ██ ██   ██ ██      ██ ██
24800+// ██       ██████  ██████  ███████ ██  ██████
24801+//
24802+// >>public
24803+SOKOL_API_IMPL void sg_setup(const sg_desc* desc) {
24804+    SOKOL_ASSERT(!_sg.valid);
24805+    SOKOL_ASSERT(desc);
24806+    SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0));
24807+    SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
24808+    _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg);
24809+    _sg.desc = _sg_desc_defaults(desc);
24810+    _sg_setup_pools(&_sg.pools, &_sg.desc);
24811+    _sg_setup_commit_listeners(&_sg.desc);
24812+    _sg.frame_index = 1;
24813+    _sg.stats_enabled = true;
24814+    _sg_setup_backend(&_sg.desc);
24815+    _sg_override_portable_limits();
24816+    _sg.valid = true;
24817+}
24818+
24819+SOKOL_API_IMPL void sg_shutdown(void) {
24820+    SOKOL_ASSERT(_sg.valid);
24821+    _sg_discard_all_resources();
24822+    _sg_discard_backend();
24823+    _sg_discard_commit_listeners();
24824+    _sg_discard_pools(&_sg.pools);
24825+    _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg);
24826+}
24827+
24828+SOKOL_API_IMPL bool sg_isvalid(void) {
24829+    return _sg.valid;
24830+}
24831+
24832+SOKOL_API_IMPL sg_desc sg_query_desc(void) {
24833+    SOKOL_ASSERT(_sg.valid);
24834+    return _sg.desc;
24835+}
24836+
24837+SOKOL_API_IMPL sg_backend sg_query_backend(void) {
24838+    SOKOL_ASSERT(_sg.valid);
24839+    return _sg.backend;
24840+}
24841+
24842+SOKOL_API_IMPL sg_features sg_query_features(void) {
24843+    SOKOL_ASSERT(_sg.valid);
24844+    return _sg.features;
24845+}
24846+
24847+SOKOL_API_IMPL sg_limits sg_query_limits(void) {
24848+    SOKOL_ASSERT(_sg.valid);
24849+    return _sg.limits;
24850+}
24851+
24852+SOKOL_API_IMPL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) {
24853+    SOKOL_ASSERT(_sg.valid);
24854+    int fmt_index = (int) fmt;
24855+    SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM));
24856+    const _sg_pixelformat_info_t* src = &_sg.formats[fmt_index];
24857+    _SG_STRUCT(sg_pixelformat_info, res);
24858+    res.sample = src->sample;
24859+    res.filter = src->filter;
24860+    res.render = src->render;
24861+    res.blend = src->blend;
24862+    res.msaa = src->msaa;
24863+    res.depth = src->depth;
24864+    res.compressed = _sg_is_compressed_pixel_format(fmt);
24865+    res.read = src->read;
24866+    res.write = src->write;
24867+    if (!res.compressed) {
24868+        res.bytes_per_pixel = _sg_pixelformat_bytesize(fmt);
24869+    }
24870+    return res;
24871+}
24872+
24873+SOKOL_API_IMPL int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes) {
24874+    SOKOL_ASSERT(_sg.valid);
24875+    SOKOL_ASSERT(width > 0);
24876+    SOKOL_ASSERT((row_align_bytes > 0) && _sg_ispow2(row_align_bytes));
24877+    SOKOL_ASSERT(((int)fmt > SG_PIXELFORMAT_NONE) && ((int)fmt < _SG_PIXELFORMAT_NUM));
24878+    return _sg_row_pitch(fmt, width, row_align_bytes);
24879+}
24880+
24881+SOKOL_API_IMPL int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes) {
24882+    SOKOL_ASSERT(_sg.valid);
24883+    SOKOL_ASSERT((width > 0) && (height > 0));
24884+    SOKOL_ASSERT((row_align_bytes > 0) && _sg_ispow2(row_align_bytes));
24885+    SOKOL_ASSERT(((int)fmt > SG_PIXELFORMAT_NONE) && ((int)fmt < _SG_PIXELFORMAT_NUM));
24886+    return _sg_surface_pitch(fmt, width, height, row_align_bytes);
24887+}
24888+
24889+SOKOL_API_IMPL sg_stats sg_query_stats(void) {
24890+    SOKOL_ASSERT(_sg.valid);
24891+    _sg_update_alive_free_resource_stats(&_sg.stats.total.buffers, &_sg.pools.buffer_pool);
24892+    _sg_update_alive_free_resource_stats(&_sg.stats.total.images, &_sg.pools.image_pool);
24893+    _sg_update_alive_free_resource_stats(&_sg.stats.total.views, &_sg.pools.view_pool);
24894+    _sg_update_alive_free_resource_stats(&_sg.stats.total.samplers, &_sg.pools.sampler_pool);
24895+    _sg_update_alive_free_resource_stats(&_sg.stats.total.shaders, &_sg.pools.shader_pool);
24896+    _sg_update_alive_free_resource_stats(&_sg.stats.total.pipelines, &_sg.pools.pipeline_pool);
24897+    return _sg.stats;
24898+}
24899+
24900+SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks) {
24901+    SOKOL_ASSERT(_sg.valid);
24902+    SOKOL_ASSERT(trace_hooks);
24903+    _SOKOL_UNUSED(trace_hooks);
24904+    #if defined(SOKOL_TRACE_HOOKS)
24905+        sg_trace_hooks old_hooks = _sg.hooks;
24906+        _sg.hooks = *trace_hooks;
24907+    #else
24908+        static sg_trace_hooks old_hooks;
24909+        _SG_WARN(TRACE_HOOKS_NOT_ENABLED);
24910+    #endif
24911+    return old_hooks;
24912+}
24913+
24914+SOKOL_API_IMPL sg_buffer sg_alloc_buffer(void) {
24915+    SOKOL_ASSERT(_sg.valid);
24916+    sg_buffer res = _sg_alloc_buffer();
24917+    _SG_TRACE_ARGS(alloc_buffer, res);
24918+    return res;
24919+}
24920+
24921+SOKOL_API_IMPL sg_image sg_alloc_image(void) {
24922+    SOKOL_ASSERT(_sg.valid);
24923+    sg_image res = _sg_alloc_image();
24924+    _SG_TRACE_ARGS(alloc_image, res);
24925+    return res;
24926+}
24927+
24928+SOKOL_API_IMPL sg_sampler sg_alloc_sampler(void) {
24929+    SOKOL_ASSERT(_sg.valid);
24930+    sg_sampler res = _sg_alloc_sampler();
24931+    _SG_TRACE_ARGS(alloc_sampler, res);
24932+    return res;
24933+}
24934+
24935+SOKOL_API_IMPL sg_shader sg_alloc_shader(void) {
24936+    SOKOL_ASSERT(_sg.valid);
24937+    sg_shader res = _sg_alloc_shader();
24938+    _SG_TRACE_ARGS(alloc_shader, res);
24939+    return res;
24940+}
24941+
24942+SOKOL_API_IMPL sg_pipeline sg_alloc_pipeline(void) {
24943+    SOKOL_ASSERT(_sg.valid);
24944+    sg_pipeline res = _sg_alloc_pipeline();
24945+    _SG_TRACE_ARGS(alloc_pipeline, res);
24946+    return res;
24947+}
24948+
24949+SOKOL_API_IMPL sg_view sg_alloc_view(void) {
24950+    SOKOL_ASSERT(_sg.valid);
24951+    sg_view res = _sg_alloc_view();
24952+    _SG_TRACE_ARGS(alloc_view, res);
24953+    return res;
24954+}
24955+
24956+SOKOL_API_IMPL void sg_dealloc_buffer(sg_buffer buf_id) {
24957+    SOKOL_ASSERT(_sg.valid);
24958+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
24959+    if (buf) {
24960+        if (buf->slot.state == SG_RESOURCESTATE_ALLOC) {
24961+            _sg_dealloc_buffer(buf);
24962+        } else {
24963+            _SG_ERROR(DEALLOC_BUFFER_INVALID_STATE);
24964+        }
24965+    }
24966+    _SG_TRACE_ARGS(dealloc_buffer, buf_id);
24967+}
24968+
24969+SOKOL_API_IMPL void sg_dealloc_image(sg_image img_id) {
24970+    SOKOL_ASSERT(_sg.valid);
24971+    _sg_image_t* img = _sg_lookup_image(img_id.id);
24972+    if (img) {
24973+        if (img->slot.state == SG_RESOURCESTATE_ALLOC) {
24974+            _sg_dealloc_image(img);
24975+        } else {
24976+            _SG_ERROR(DEALLOC_IMAGE_INVALID_STATE);
24977+        }
24978+    }
24979+    _SG_TRACE_ARGS(dealloc_image, img_id);
24980+}
24981+
24982+SOKOL_API_IMPL void sg_dealloc_sampler(sg_sampler smp_id) {
24983+    SOKOL_ASSERT(_sg.valid);
24984+    _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
24985+    if (smp) {
24986+        if (smp->slot.state == SG_RESOURCESTATE_ALLOC) {
24987+            _sg_dealloc_sampler(smp);
24988+        } else {
24989+            _SG_ERROR(DEALLOC_SAMPLER_INVALID_STATE);
24990+        }
24991+    }
24992+    _SG_TRACE_ARGS(dealloc_sampler, smp_id);
24993+}
24994+
24995+SOKOL_API_IMPL void sg_dealloc_shader(sg_shader shd_id) {
24996+    SOKOL_ASSERT(_sg.valid);
24997+    _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
24998+    if (shd) {
24999+        if (shd->slot.state == SG_RESOURCESTATE_ALLOC) {
25000+            _sg_dealloc_shader(shd);
25001+        } else {
25002+            _SG_ERROR(DEALLOC_SHADER_INVALID_STATE);
25003+        }
25004+    }
25005+    _SG_TRACE_ARGS(dealloc_shader, shd_id);
25006+}
25007+
25008+SOKOL_API_IMPL void sg_dealloc_pipeline(sg_pipeline pip_id) {
25009+    SOKOL_ASSERT(_sg.valid);
25010+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25011+    if (pip) {
25012+        if (pip->slot.state == SG_RESOURCESTATE_ALLOC) {
25013+            _sg_dealloc_pipeline(pip);
25014+        } else {
25015+            _SG_ERROR(DEALLOC_PIPELINE_INVALID_STATE);
25016+        }
25017+    }
25018+    _SG_TRACE_ARGS(dealloc_pipeline, pip_id);
25019+}
25020+
25021+SOKOL_API_IMPL void sg_dealloc_view(sg_view view_id) {
25022+    SOKOL_ASSERT(_sg.valid);
25023+    _sg_view_t* view = _sg_lookup_view(view_id.id);
25024+    if (view) {
25025+        if (view->slot.state == SG_RESOURCESTATE_ALLOC) {
25026+            _sg_dealloc_view(view);
25027+        } else {
25028+            _SG_ERROR(DEALLOC_VIEW_INVALID_STATE);
25029+        }
25030+    }
25031+    _SG_TRACE_ARGS(dealloc_view, view_id);
25032+}
25033+
25034+SOKOL_API_IMPL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) {
25035+    SOKOL_ASSERT(_sg.valid);
25036+    sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc);
25037+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25038+    if (buf) {
25039+        if (buf->slot.state == SG_RESOURCESTATE_ALLOC) {
25040+            _sg_init_buffer(buf, &desc_def);
25041+            SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED));
25042+        } else {
25043+            _SG_ERROR(INIT_BUFFER_INVALID_STATE);
25044+        }
25045+    }
25046+    _SG_TRACE_ARGS(init_buffer, buf_id, &desc_def);
25047+}
25048+
25049+SOKOL_API_IMPL void sg_init_image(sg_image img_id, const sg_image_desc* desc) {
25050+    SOKOL_ASSERT(_sg.valid);
25051+    sg_image_desc desc_def = _sg_image_desc_defaults(desc);
25052+    _sg_image_t* img = _sg_lookup_image(img_id.id);
25053+    if (img) {
25054+        if (img->slot.state == SG_RESOURCESTATE_ALLOC) {
25055+            _sg_init_image(img, &desc_def);
25056+            SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED));
25057+        } else {
25058+            _SG_ERROR(INIT_IMAGE_INVALID_STATE);
25059+        }
25060+    }
25061+    _SG_TRACE_ARGS(init_image, img_id, &desc_def);
25062+}
25063+
25064+SOKOL_API_IMPL void sg_init_sampler(sg_sampler smp_id, const sg_sampler_desc* desc) {
25065+    SOKOL_ASSERT(_sg.valid);
25066+    sg_sampler_desc desc_def = _sg_sampler_desc_defaults(desc);
25067+    _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
25068+    if (smp) {
25069+        if (smp->slot.state == SG_RESOURCESTATE_ALLOC) {
25070+            _sg_init_sampler(smp, &desc_def);
25071+            SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED));
25072+        } else {
25073+            _SG_ERROR(INIT_SAMPLER_INVALID_STATE);
25074+        }
25075+    }
25076+    _SG_TRACE_ARGS(init_sampler, smp_id, &desc_def);
25077+}
25078+
25079+SOKOL_API_IMPL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) {
25080+    SOKOL_ASSERT(_sg.valid);
25081+    sg_shader_desc desc_def = _sg_shader_desc_defaults(desc);
25082+    _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
25083+    if (shd) {
25084+        if (shd->slot.state == SG_RESOURCESTATE_ALLOC) {
25085+            _sg_init_shader(shd, &desc_def);
25086+            SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED));
25087+        } else {
25088+            _SG_ERROR(INIT_SHADER_INVALID_STATE);
25089+        }
25090+    }
25091+    _SG_TRACE_ARGS(init_shader, shd_id, &desc_def);
25092+}
25093+
25094+SOKOL_API_IMPL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) {
25095+    SOKOL_ASSERT(_sg.valid);
25096+    sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc);
25097+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25098+    if (pip) {
25099+        if (pip->slot.state == SG_RESOURCESTATE_ALLOC) {
25100+            _sg_init_pipeline(pip, &desc_def);
25101+            SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED));
25102+        } else {
25103+            _SG_ERROR(INIT_PIPELINE_INVALID_STATE);
25104+        }
25105+    }
25106+    _SG_TRACE_ARGS(init_pipeline, pip_id, &desc_def);
25107+}
25108+
25109+SOKOL_API_IMPL void sg_init_view(sg_view view_id, const sg_view_desc* desc) {
25110+    SOKOL_ASSERT(_sg.valid);
25111+    sg_view_desc desc_def = _sg_view_desc_defaults(desc);
25112+    _sg_view_t* view = _sg_lookup_view(view_id.id);
25113+    if (view) {
25114+        if (view->slot.state == SG_RESOURCESTATE_ALLOC) {
25115+            _sg_init_view(view, &desc_def);
25116+            SOKOL_ASSERT((view->slot.state == SG_RESOURCESTATE_VALID)
25117+                || (view->slot.state == SG_RESOURCESTATE_FAILED)
25118+                || (view->slot.state == SG_RESOURCESTATE_ALLOC));
25119+        } else {
25120+            _SG_ERROR(INIT_VIEW_INVALID_STATE);
25121+        }
25122+    }
25123+    _SG_TRACE_ARGS(init_view, view_id, &desc_def);
25124+}
25125+
25126+SOKOL_API_IMPL void sg_uninit_buffer(sg_buffer buf_id) {
25127+    SOKOL_ASSERT(_sg.valid);
25128+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25129+    if (buf) {
25130+        if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) {
25131+            _sg_uninit_buffer(buf);
25132+            SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC);
25133+        } else if (buf->slot.state != SG_RESOURCESTATE_ALLOC) {
25134+            _SG_ERROR(UNINIT_BUFFER_INVALID_STATE);
25135+        }
25136+    }
25137+    _SG_TRACE_ARGS(uninit_buffer, buf_id);
25138+}
25139+
25140+SOKOL_API_IMPL void sg_uninit_image(sg_image img_id) {
25141+    SOKOL_ASSERT(_sg.valid);
25142+    _sg_image_t* img = _sg_lookup_image(img_id.id);
25143+    if (img) {
25144+        if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) {
25145+            _sg_uninit_image(img);
25146+            SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC);
25147+        } else if (img->slot.state != SG_RESOURCESTATE_ALLOC) {
25148+            _SG_ERROR(UNINIT_IMAGE_INVALID_STATE);
25149+        }
25150+    }
25151+    _SG_TRACE_ARGS(uninit_image, img_id);
25152+}
25153+
25154+SOKOL_API_IMPL void sg_uninit_sampler(sg_sampler smp_id) {
25155+    SOKOL_ASSERT(_sg.valid);
25156+    _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
25157+    if (smp) {
25158+        if ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)) {
25159+            _sg_uninit_sampler(smp);
25160+            SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_ALLOC);
25161+        } else if (smp->slot.state != SG_RESOURCESTATE_ALLOC) {
25162+            _SG_ERROR(UNINIT_SAMPLER_INVALID_STATE);
25163+        }
25164+    }
25165+    _SG_TRACE_ARGS(uninit_sampler, smp_id);
25166+}
25167+
25168+SOKOL_API_IMPL void sg_uninit_shader(sg_shader shd_id) {
25169+    SOKOL_ASSERT(_sg.valid);
25170+    _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
25171+    if (shd) {
25172+        if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) {
25173+            _sg_uninit_shader(shd);
25174+            SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC);
25175+        } else if (shd->slot.state != SG_RESOURCESTATE_ALLOC) {
25176+            _SG_ERROR(UNINIT_SHADER_INVALID_STATE);
25177+        }
25178+    }
25179+    _SG_TRACE_ARGS(uninit_shader, shd_id);
25180+}
25181+
25182+SOKOL_API_IMPL void sg_uninit_pipeline(sg_pipeline pip_id) {
25183+    SOKOL_ASSERT(_sg.valid);
25184+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25185+    if (pip) {
25186+        if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) {
25187+            _sg_uninit_pipeline(pip);
25188+            SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC);
25189+        } else if (pip->slot.state != SG_RESOURCESTATE_ALLOC) {
25190+            _SG_ERROR(UNINIT_PIPELINE_INVALID_STATE);
25191+        }
25192+    }
25193+    _SG_TRACE_ARGS(uninit_pipeline, pip_id);
25194+}
25195+
25196+SOKOL_API_IMPL void sg_uninit_view(sg_view view_id) {
25197+    SOKOL_ASSERT(_sg.valid);
25198+    _sg_view_t* view = _sg_lookup_view(view_id.id);
25199+    if (view) {
25200+        if ((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)) {
25201+            _sg_uninit_view(view);
25202+            SOKOL_ASSERT(view->slot.state == SG_RESOURCESTATE_ALLOC);
25203+        } else if (view->slot.state != SG_RESOURCESTATE_ALLOC) {
25204+            _SG_ERROR(UNINIT_VIEW_INVALID_STATE);
25205+        }
25206+    }
25207+    _SG_TRACE_ARGS(uninit_view, view_id);
25208+}
25209+
25210+SOKOL_API_IMPL void sg_fail_buffer(sg_buffer buf_id) {
25211+    SOKOL_ASSERT(_sg.valid);
25212+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25213+    if (buf) {
25214+        if (buf->slot.state == SG_RESOURCESTATE_ALLOC) {
25215+            buf->slot.state = SG_RESOURCESTATE_FAILED;
25216+        } else {
25217+            _SG_ERROR(FAIL_BUFFER_INVALID_STATE);
25218+        }
25219+    }
25220+    _SG_TRACE_ARGS(fail_buffer, buf_id);
25221+}
25222+
25223+SOKOL_API_IMPL void sg_fail_image(sg_image img_id) {
25224+    SOKOL_ASSERT(_sg.valid);
25225+    _sg_image_t* img = _sg_lookup_image(img_id.id);
25226+    if (img) {
25227+        if (img->slot.state == SG_RESOURCESTATE_ALLOC) {
25228+            img->slot.state = SG_RESOURCESTATE_FAILED;
25229+        } else {
25230+            _SG_ERROR(FAIL_IMAGE_INVALID_STATE);
25231+        }
25232+    }
25233+    _SG_TRACE_ARGS(fail_image, img_id);
25234+}
25235+
25236+SOKOL_API_IMPL void sg_fail_sampler(sg_sampler smp_id) {
25237+    SOKOL_ASSERT(_sg.valid);
25238+    _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
25239+    if (smp) {
25240+        if (smp->slot.state == SG_RESOURCESTATE_ALLOC) {
25241+            smp->slot.state = SG_RESOURCESTATE_FAILED;
25242+        } else {
25243+            _SG_ERROR(FAIL_SAMPLER_INVALID_STATE);
25244+        }
25245+    }
25246+    _SG_TRACE_ARGS(fail_sampler, smp_id);
25247+}
25248+
25249+SOKOL_API_IMPL void sg_fail_shader(sg_shader shd_id) {
25250+    SOKOL_ASSERT(_sg.valid);
25251+    _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
25252+    if (shd) {
25253+        if (shd->slot.state == SG_RESOURCESTATE_ALLOC) {
25254+            shd->slot.state = SG_RESOURCESTATE_FAILED;
25255+        } else {
25256+            _SG_ERROR(FAIL_SHADER_INVALID_STATE);
25257+        }
25258+    }
25259+    _SG_TRACE_ARGS(fail_shader, shd_id);
25260+}
25261+
25262+SOKOL_API_IMPL void sg_fail_pipeline(sg_pipeline pip_id) {
25263+    SOKOL_ASSERT(_sg.valid);
25264+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25265+    if (pip) {
25266+        if (pip->slot.state == SG_RESOURCESTATE_ALLOC) {
25267+            pip->slot.state = SG_RESOURCESTATE_FAILED;
25268+        } else {
25269+            _SG_ERROR(FAIL_PIPELINE_INVALID_STATE);
25270+        }
25271+    }
25272+    _SG_TRACE_ARGS(fail_pipeline, pip_id);
25273+}
25274+
25275+SOKOL_API_IMPL void sg_fail_view(sg_view view_id) {
25276+    SOKOL_ASSERT(_sg.valid);
25277+    _sg_view_t* view = _sg_lookup_view(view_id.id);
25278+    if (view) {
25279+        if (view->slot.state == SG_RESOURCESTATE_ALLOC) {
25280+            view->slot.state = SG_RESOURCESTATE_FAILED;
25281+        } else {
25282+            _SG_ERROR(FAIL_VIEW_INVALID_STATE);
25283+        }
25284+    }
25285+    _SG_TRACE_ARGS(fail_view, view_id);
25286+}
25287+
25288+SOKOL_API_IMPL sg_resource_state sg_query_buffer_state(sg_buffer buf_id) {
25289+    SOKOL_ASSERT(_sg.valid);
25290+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25291+    sg_resource_state res = buf ? buf->slot.state : SG_RESOURCESTATE_INVALID;
25292+    return res;
25293+}
25294+
25295+SOKOL_API_IMPL sg_resource_state sg_query_image_state(sg_image img_id) {
25296+    SOKOL_ASSERT(_sg.valid);
25297+    _sg_image_t* img = _sg_lookup_image(img_id.id);
25298+    sg_resource_state res = img ? img->slot.state : SG_RESOURCESTATE_INVALID;
25299+    return res;
25300+}
25301+
25302+SOKOL_API_IMPL sg_resource_state sg_query_sampler_state(sg_sampler smp_id) {
25303+    SOKOL_ASSERT(_sg.valid);
25304+    _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
25305+    sg_resource_state res = smp ? smp->slot.state : SG_RESOURCESTATE_INVALID;
25306+    return res;
25307+}
25308+
25309+SOKOL_API_IMPL sg_resource_state sg_query_shader_state(sg_shader shd_id) {
25310+    SOKOL_ASSERT(_sg.valid);
25311+    _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
25312+    sg_resource_state res = shd ? shd->slot.state : SG_RESOURCESTATE_INVALID;
25313+    return res;
25314+}
25315+
25316+SOKOL_API_IMPL sg_resource_state sg_query_pipeline_state(sg_pipeline pip_id) {
25317+    SOKOL_ASSERT(_sg.valid);
25318+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25319+    sg_resource_state res = pip ? pip->slot.state : SG_RESOURCESTATE_INVALID;
25320+    return res;
25321+}
25322+
25323+SOKOL_API_IMPL sg_resource_state sg_query_view_state(sg_view view_id) {
25324+    SOKOL_ASSERT(_sg.valid);
25325+    _sg_view_t* view = _sg_lookup_view(view_id.id);
25326+    sg_resource_state res = view ? view->slot.state : SG_RESOURCESTATE_INVALID;
25327+    return res;
25328+}
25329+
25330+SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) {
25331+    SOKOL_ASSERT(_sg.valid);
25332+    SOKOL_ASSERT(desc);
25333+    sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc);
25334+    sg_buffer buf_id = _sg_alloc_buffer();
25335+    if (buf_id.id != SG_INVALID_ID) {
25336+        _sg_buffer_t* buf = _sg_buffer_at(buf_id.id);
25337+        SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC));
25338+        _sg_init_buffer(buf, &desc_def);
25339+        SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED));
25340+    }
25341+    _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id);
25342+    return buf_id;
25343+}
25344+
25345+SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) {
25346+    SOKOL_ASSERT(_sg.valid);
25347+    SOKOL_ASSERT(desc);
25348+    sg_image_desc desc_def = _sg_image_desc_defaults(desc);
25349+    sg_image img_id = _sg_alloc_image();
25350+    if (img_id.id != SG_INVALID_ID) {
25351+        _sg_image_t* img = _sg_image_at(img_id.id);
25352+        SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC));
25353+        _sg_init_image(img, &desc_def);
25354+        SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED));
25355+    }
25356+    _SG_TRACE_ARGS(make_image, &desc_def, img_id);
25357+    return img_id;
25358+}
25359+
25360+SOKOL_API_IMPL sg_sampler sg_make_sampler(const sg_sampler_desc* desc) {
25361+    SOKOL_ASSERT(_sg.valid);
25362+    SOKOL_ASSERT(desc);
25363+    sg_sampler_desc desc_def = _sg_sampler_desc_defaults(desc);
25364+    sg_sampler smp_id = _sg_alloc_sampler();
25365+    if (smp_id.id != SG_INVALID_ID) {
25366+        _sg_sampler_t* smp = _sg_sampler_at(smp_id.id);
25367+        SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC));
25368+        _sg_init_sampler(smp, &desc_def);
25369+        SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED));
25370+    }
25371+    _SG_TRACE_ARGS(make_sampler, &desc_def, smp_id);
25372+    return smp_id;
25373+}
25374+
25375+SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) {
25376+    SOKOL_ASSERT(_sg.valid);
25377+    SOKOL_ASSERT(desc);
25378+    sg_shader_desc desc_def = _sg_shader_desc_defaults(desc);
25379+    sg_shader shd_id = _sg_alloc_shader();
25380+    if (shd_id.id != SG_INVALID_ID) {
25381+        _sg_shader_t* shd = _sg_shader_at(shd_id.id);
25382+        SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC));
25383+        _sg_init_shader(shd, &desc_def);
25384+        SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED));
25385+    }
25386+    _SG_TRACE_ARGS(make_shader, &desc_def, shd_id);
25387+    return shd_id;
25388+}
25389+
25390+SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) {
25391+    SOKOL_ASSERT(_sg.valid);
25392+    SOKOL_ASSERT(desc);
25393+    sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc);
25394+    sg_pipeline pip_id = _sg_alloc_pipeline();
25395+    if (pip_id.id != SG_INVALID_ID) {
25396+        _sg_pipeline_t* pip = _sg_pipeline_at(pip_id.id);
25397+        SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC));
25398+        _sg_init_pipeline(pip, &desc_def);
25399+        SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED));
25400+    }
25401+    _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id);
25402+    return pip_id;
25403+}
25404+
25405+SOKOL_API_IMPL sg_view sg_make_view(const sg_view_desc* desc) {
25406+    SOKOL_ASSERT(_sg.valid);
25407+    SOKOL_ASSERT(desc);
25408+    sg_view_desc desc_def = _sg_view_desc_defaults(desc);
25409+    sg_view view_id = _sg_alloc_view();
25410+    if (view_id.id != SG_INVALID_ID) {
25411+        _sg_view_t* view = _sg_view_at(view_id.id);
25412+        SOKOL_ASSERT(view && (view->slot.state == SG_RESOURCESTATE_ALLOC));
25413+        _sg_init_view(view, &desc_def);
25414+        SOKOL_ASSERT((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED));
25415+    }
25416+    _SG_TRACE_ARGS(make_view, &desc_def, view_id);
25417+    return view_id;
25418+}
25419+
25420+SOKOL_API_IMPL void sg_destroy_buffer(sg_buffer buf_id) {
25421+    SOKOL_ASSERT(_sg.valid);
25422+    _SG_TRACE_ARGS(destroy_buffer, buf_id);
25423+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25424+    if (buf) {
25425+        if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) {
25426+            _sg_uninit_buffer(buf);
25427+            SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC);
25428+        }
25429+        if (buf->slot.state == SG_RESOURCESTATE_ALLOC) {
25430+            _sg_dealloc_buffer(buf);
25431+            SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_INITIAL);
25432+        }
25433+    }
25434+}
25435+
25436+SOKOL_API_IMPL void sg_destroy_image(sg_image img_id) {
25437+    SOKOL_ASSERT(_sg.valid);
25438+    _SG_TRACE_ARGS(destroy_image, img_id);
25439+    _sg_image_t* img = _sg_lookup_image(img_id.id);
25440+    if (img) {
25441+        if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) {
25442+            _sg_uninit_image(img);
25443+            SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC);
25444+        }
25445+        if (img->slot.state == SG_RESOURCESTATE_ALLOC) {
25446+            _sg_dealloc_image(img);
25447+            SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_INITIAL);
25448+        }
25449+    }
25450+}
25451+
25452+SOKOL_API_IMPL void sg_destroy_sampler(sg_sampler smp_id) {
25453+    SOKOL_ASSERT(_sg.valid);
25454+    _SG_TRACE_ARGS(destroy_sampler, smp_id);
25455+    _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
25456+    if (smp) {
25457+        if ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)) {
25458+            _sg_uninit_sampler(smp);
25459+            SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_ALLOC);
25460+        }
25461+        if (smp->slot.state == SG_RESOURCESTATE_ALLOC) {
25462+            _sg_dealloc_sampler(smp);
25463+            SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_INITIAL);
25464+        }
25465+    }
25466+}
25467+
25468+SOKOL_API_IMPL void sg_destroy_shader(sg_shader shd_id) {
25469+    SOKOL_ASSERT(_sg.valid);
25470+    _SG_TRACE_ARGS(destroy_shader, shd_id);
25471+    _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
25472+    if (shd) {
25473+        if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) {
25474+            _sg_uninit_shader(shd);
25475+            SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC);
25476+        }
25477+        if (shd->slot.state == SG_RESOURCESTATE_ALLOC) {
25478+            _sg_dealloc_shader(shd);
25479+            SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_INITIAL);
25480+        }
25481+    }
25482+}
25483+
25484+SOKOL_API_IMPL void sg_destroy_pipeline(sg_pipeline pip_id) {
25485+    SOKOL_ASSERT(_sg.valid);
25486+    _SG_TRACE_ARGS(destroy_pipeline, pip_id);
25487+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25488+    if (pip) {
25489+        if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) {
25490+            _sg_uninit_pipeline(pip);
25491+            SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC);
25492+        }
25493+        if (pip->slot.state == SG_RESOURCESTATE_ALLOC) {
25494+            _sg_dealloc_pipeline(pip);
25495+            SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_INITIAL);
25496+        }
25497+    }
25498+}
25499+
25500+SOKOL_API_IMPL void sg_destroy_view(sg_view view_id) {
25501+    SOKOL_ASSERT(_sg.valid);
25502+    _SG_TRACE_ARGS(destroy_view, view_id);
25503+    _sg_view_t* view = _sg_lookup_view(view_id.id);
25504+    if (view) {
25505+        if ((view->slot.state == SG_RESOURCESTATE_VALID) || (view->slot.state == SG_RESOURCESTATE_FAILED)) {
25506+            _sg_uninit_view(view);
25507+            SOKOL_ASSERT(view->slot.state == SG_RESOURCESTATE_ALLOC);
25508+        }
25509+        if (view->slot.state == SG_RESOURCESTATE_ALLOC) {
25510+            _sg_dealloc_view(view);
25511+            SOKOL_ASSERT(view->slot.state == SG_RESOURCESTATE_INITIAL);
25512+        }
25513+    }
25514+}
25515+
25516+SOKOL_API_IMPL void sg_begin_pass(const sg_pass* pass) {
25517+    SOKOL_ASSERT(_sg.valid);
25518+    SOKOL_ASSERT(!_sg.cur_pass.valid);
25519+    SOKOL_ASSERT(!_sg.cur_pass.in_pass);
25520+    SOKOL_ASSERT(_sg_attachments_empty(&_sg.cur_pass.atts));
25521+    SOKOL_ASSERT(pass);
25522+    SOKOL_ASSERT((pass->_start_canary == 0) && (pass->_end_canary == 0));
25523+    _sg.cur_pass.in_pass = true;
25524+    const sg_pass pass_def = _sg_pass_defaults(pass);
25525+    _SG_TRACE_ARGS(begin_pass, &pass_def);
25526+    if (!_sg_validate_pass_attachment_limits(&pass_def)) {
25527+        return;
25528+    }
25529+    if (!_sg_validate_begin_pass(&pass_def)) {
25530+        return;
25531+    }
25532+    const _sg_attachments_ptrs_t atts_ptrs = _sg_attachments_ptrs(&pass_def.attachments);
25533+    if (!atts_ptrs.empty) {
25534+        if (!_sg_attachments_alive(&atts_ptrs)) {
25535+            _SG_ERROR(BEGINPASS_ATTACHMENTS_ALIVE);
25536+            return;
25537+        }
25538+        _sg.cur_pass.atts = pass->attachments;
25539+        _sg.cur_pass.dim = _sg_attachments_dim(&atts_ptrs);
25540+    } else if (!pass_def.compute) {
25541+        // a swapchain pass
25542+        // NOTE: all values allowed to be zero if swapchain is invalid
25543+        _sg.cur_pass.swapchain.invalid = pass_def.swapchain.invalid;
25544+        _sg.cur_pass.swapchain.color_fmt = pass_def.swapchain.color_format;
25545+        _sg.cur_pass.swapchain.depth_fmt = pass_def.swapchain.depth_format;
25546+        _sg.cur_pass.swapchain.sample_count = pass_def.swapchain.sample_count;
25547+        _sg.cur_pass.dim.width = pass_def.swapchain.width;
25548+        _sg.cur_pass.dim.height = pass_def.swapchain.height;
25549+    }
25550+    _sg.cur_pass.action = pass_def.action;
25551+    _sg.cur_pass.valid = true;  // may be overruled by backend begin-pass functions
25552+    _sg.cur_pass.is_compute = pass_def.compute;
25553+    if (_sg.cur_pass.swapchain.invalid) {
25554+        _sg.cur_pass.valid = false;
25555+        return;
25556+    }
25557+    _sg_begin_pass(&pass_def, &atts_ptrs);
25558+}
25559+
25560+SOKOL_API_IMPL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) {
25561+    SOKOL_ASSERT(_sg.valid);
25562+    _sg_stats_inc(num_apply_viewport);
25563+    _SG_TRACE_ARGS(apply_viewport, x, y, width, height, origin_top_left);
25564+    if (!_sg.cur_pass.valid) {
25565+        return;
25566+    }
25567+    #if defined(SOKOL_DEBUG)
25568+    if (!_sg_validate_apply_viewport(x, y, width, height, origin_top_left)) {
25569+        return;
25570+    }
25571+    #endif
25572+    _sg_apply_viewport(x, y, width, height, origin_top_left);
25573+}
25574+
25575+SOKOL_API_IMPL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) {
25576+    sg_apply_viewport((int)x, (int)y, (int)width, (int)height, origin_top_left);
25577+}
25578+
25579+SOKOL_API_IMPL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) {
25580+    SOKOL_ASSERT(_sg.valid);
25581+    _sg_stats_inc(num_apply_scissor_rect);
25582+    _SG_TRACE_ARGS(apply_scissor_rect, x, y, width, height, origin_top_left);
25583+    if (!_sg.cur_pass.valid) {
25584+        return;
25585+    }
25586+    #if defined(SOKOL_DEBUG)
25587+    if (!_sg_validate_apply_scissor_rect(x, y, width, height, origin_top_left)) {
25588+        return;
25589+    }
25590+    #endif
25591+    _sg_apply_scissor_rect(x, y, width, height, origin_top_left);
25592+}
25593+
25594+SOKOL_API_IMPL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) {
25595+    sg_apply_scissor_rect((int)x, (int)y, (int)width, (int)height, origin_top_left);
25596+}
25597+
25598+SOKOL_API_IMPL void sg_apply_pipeline(sg_pipeline pip_id) {
25599+    SOKOL_ASSERT(_sg.valid);
25600+    _sg_stats_inc(num_apply_pipeline);
25601+    _SG_TRACE_ARGS(apply_pipeline, pip_id);
25602+    if (!_sg.cur_pass.valid) {
25603+        return;
25604+    }
25605+    if (!_sg_validate_apply_pipeline(pip_id)) {
25606+        _sg.next_draw_valid = false;
25607+        return;
25608+    }
25609+    _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
25610+    SOKOL_ASSERT(pip);
25611+    _sg.cur_pip = _sg_pipeline_ref(pip);
25612+
25613+    _sg.next_draw_valid = (SG_RESOURCESTATE_VALID == pip->slot.state);
25614+    if (!_sg.next_draw_valid) {
25615+        return;
25616+    }
25617+    _sg.use_indexed_draw = pip->cmn.index_type != SG_INDEXTYPE_NONE;
25618+    _sg.use_instanced_draw = pip->cmn.use_instanced_draw;
25619+
25620+    _sg_apply_pipeline(pip);
25621+
25622+    // set the expected bindings and uniform block flags
25623+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&pip->cmn.shader);
25624+    _sg.required_bindings_and_uniforms = pip->cmn.required_bindings_and_uniforms | shd->cmn.required_bindings_and_uniforms;
25625+    _sg.applied_bindings_and_uniforms = 0;
25626+}
25627+
25628+SOKOL_API_IMPL void sg_apply_bindings(const sg_bindings* bindings) {
25629+    SOKOL_ASSERT(_sg.valid);
25630+    SOKOL_ASSERT(bindings);
25631+    _sg_stats_inc(num_apply_bindings);
25632+    _SG_TRACE_ARGS(apply_bindings, bindings);
25633+    if (!_sg.cur_pass.valid) {
25634+        return;
25635+    }
25636+    _sg.applied_bindings_and_uniforms |= (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS);
25637+    if (!_sg_validate_apply_bindings(bindings)) {
25638+        _sg.next_draw_valid = false;
25639+    }
25640+    SOKOL_ASSERT((bindings->_start_canary == 0) && (bindings->_end_canary==0));
25641+    if (!_sg_pipeline_ref_alive(&_sg.cur_pip)) {
25642+        _sg.next_draw_valid = false;
25643+    }
25644+    if (!_sg.next_draw_valid) {
25645+        return;
25646+    }
25647+
25648+    _SG_STRUCT(_sg_bindings_ptrs_t, bnd);
25649+    bnd.pip = _sg_pipeline_ref_ptr(&_sg.cur_pip);
25650+    const _sg_shader_t* shd = _sg_shader_ref_ptr(&bnd.pip->cmn.shader);
25651+    if (!_sg.cur_pass.is_compute) {
25652+        for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) {
25653+            if (bnd.pip->cmn.vertex_buffer_layout_active[i]) {
25654+                SOKOL_ASSERT(bindings->vertex_buffers[i].id != SG_INVALID_ID);
25655+                bnd.vbs[i] = _sg_lookup_buffer(bindings->vertex_buffers[i].id);
25656+                bnd.vb_offsets[i] = bindings->vertex_buffer_offsets[i];
25657+                _sg.next_draw_valid &= bnd.vbs[i] && (SG_RESOURCESTATE_VALID == bnd.vbs[i]->slot.state);
25658+            }
25659+        }
25660+        if (bindings->index_buffer.id) {
25661+            bnd.ib = _sg_lookup_buffer(bindings->index_buffer.id);
25662+            bnd.ib_offset = bindings->index_buffer_offset;
25663+            _sg.next_draw_valid &= bnd.ib && (SG_RESOURCESTATE_VALID == bnd.ib->slot.state);
25664+        }
25665+    }
25666+
25667+    for (int i = 0; i < SG_MAX_VIEW_BINDSLOTS; i++) {
25668+        if (shd->cmn.views[i].view_type != SG_VIEWTYPE_INVALID) {
25669+            SOKOL_ASSERT(bindings->views[i].id != SG_INVALID_ID);
25670+            bnd.views[i] = _sg_lookup_view(bindings->views[i].id);
25671+            if (bnd.views[i]) {
25672+                if (bnd.views[i]->cmn.type == SG_VIEWTYPE_STORAGEBUFFER) {
25673+                    _sg.next_draw_valid &= _sg_buffer_ref_valid(&bnd.views[i]->cmn.buf.ref);
25674+                } else {
25675+                    _sg.next_draw_valid &= _sg_image_ref_valid(&bnd.views[i]->cmn.img.ref);
25676+                }
25677+            } else {
25678+                _sg.next_draw_valid = false;
25679+            }
25680+        }
25681+    }
25682+
25683+    for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) {
25684+        if (shd->cmn.samplers[i].stage != SG_SHADERSTAGE_NONE) {
25685+            SOKOL_ASSERT(bindings->samplers[i].id != SG_INVALID_ID);
25686+            bnd.smps[i] = _sg_lookup_sampler(bindings->samplers[i].id);
25687+            SOKOL_ASSERT(bnd.smps[i]);
25688+        }
25689+    }
25690+
25691+    if (_sg.next_draw_valid) {
25692+        _sg.next_draw_valid &= _sg_apply_bindings(&bnd);
25693+    }
25694+}
25695+
25696+SOKOL_API_IMPL void sg_apply_uniforms(int ub_slot, const sg_range* data) {
25697+    SOKOL_ASSERT(_sg.valid);
25698+    SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS));
25699+    SOKOL_ASSERT(data && data->ptr && (data->size > 0));
25700+    _sg_stats_inc(num_apply_uniforms);
25701+    _sg_stats_add(size_apply_uniforms, (uint32_t)data->size);
25702+    _SG_TRACE_ARGS(apply_uniforms, ub_slot, data);
25703+    if (!_sg.cur_pass.valid) {
25704+        return;
25705+    }
25706+    _sg.applied_bindings_and_uniforms |= 1 << ub_slot;
25707+    if (!_sg_validate_apply_uniforms(ub_slot, data)) {
25708+        _sg.next_draw_valid = false;
25709+        return;
25710+    }
25711+    if (!_sg.next_draw_valid) {
25712+        return;
25713+    }
25714+    _sg_apply_uniforms(ub_slot, data);
25715+}
25716+
25717+_SOKOL_PRIVATE bool _sg_check_skip_draw(int num_elements, int num_instances) {
25718+    if (!_sg.cur_pass.valid) {
25719+        return true;
25720+    }
25721+    if (!_sg.next_draw_valid) {
25722+        return true;
25723+    }
25724+    // skip no-op draws
25725+    if ((0 == num_elements) || (0 == num_instances)) {
25726+        return true;
25727+    }
25728+    return false;
25729+}
25730+
25731+SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instances) {
25732+    SOKOL_ASSERT(_sg.valid);
25733+    _sg_stats_inc(num_draw);
25734+    _SG_TRACE_ARGS(draw, base_element, num_elements, num_instances);
25735+    if (_sg_check_skip_draw(num_elements, num_instances)) {
25736+        return;
25737+    }
25738+    #if defined(SOKOL_DEBUG)
25739+    if (!_sg_validate_draw(base_element, num_elements, num_instances)) {
25740+        return;
25741+    }
25742+    #endif
25743+    _sg_draw(base_element, num_elements, num_instances, 0, 0);
25744+}
25745+
25746+SOKOL_API_IMPL void sg_draw_ex(int base_element, int num_elements, int num_instances, int base_vertex, int base_instance) {
25747+    SOKOL_ASSERT(_sg.valid);
25748+    _sg_stats_inc(num_draw_ex);
25749+    _SG_TRACE_ARGS(draw_ex, base_element, num_elements, num_instances, base_vertex, base_instance);
25750+    if (_sg_check_skip_draw(num_elements, num_instances)) {
25751+        return;
25752+    }
25753+    #if defined(SOKOL_DEBUG)
25754+    if (!_sg_validate_draw_ex(base_element, num_elements, num_instances, base_vertex, base_instance)) {
25755+        return;
25756+    }
25757+    #endif
25758+    _sg_draw(base_element, num_elements, num_instances, base_vertex, base_instance);
25759+}
25760+
25761+SOKOL_API_IMPL void sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) {
25762+    SOKOL_ASSERT(_sg.valid);
25763+    _sg_stats_inc(num_dispatch);
25764+    _SG_TRACE_ARGS(dispatch, num_groups_x, num_groups_y, num_groups_z);
25765+    if (!_sg.cur_pass.valid) {
25766+        return;
25767+    }
25768+    if (!_sg.next_draw_valid) {
25769+        return;
25770+    }
25771+    // skip no-op dispatches
25772+    if ((0 == num_groups_x) || (0 == num_groups_y) || (0 == num_groups_z)) {
25773+        return;
25774+    }
25775+    #if defined(SOKOL_DEBUG)
25776+    if (!_sg_validate_dispatch(num_groups_x, num_groups_y, num_groups_z)) {
25777+        return;
25778+    }
25779+    #endif
25780+    _sg_dispatch(num_groups_x, num_groups_y, num_groups_z);
25781+}
25782+
25783+SOKOL_API_IMPL void sg_end_pass(void) {
25784+    SOKOL_ASSERT(_sg.valid);
25785+    SOKOL_ASSERT(_sg.cur_pass.in_pass);
25786+    _sg_stats_inc(num_passes);
25787+    _SG_TRACE_NOARGS(end_pass);
25788+    // NOTE: don't exit early if !_sg.cur_pass.valid
25789+    if (!_sg.cur_pass.swapchain.invalid) {
25790+        const _sg_attachments_ptrs_t atts_ptrs = _sg_attachments_ptrs(&_sg.cur_pass.atts);
25791+        _sg_end_pass(&atts_ptrs);
25792+    }
25793+    _sg.cur_pip = _sg_pipeline_ref(0);
25794+    _sg_clear(&_sg.cur_pass, sizeof(_sg.cur_pass));
25795+}
25796+
25797+SOKOL_API_IMPL void sg_commit(void) {
25798+    SOKOL_ASSERT(_sg.valid);
25799+    SOKOL_ASSERT(!_sg.cur_pass.valid);
25800+    SOKOL_ASSERT(!_sg.cur_pass.in_pass);
25801+    _sg_commit();
25802+    _sg_update_stats();
25803+    _sg_notify_commit_listeners();
25804+    _SG_TRACE_NOARGS(commit);
25805+    _sg.frame_index++;
25806+}
25807+
25808+SOKOL_API_IMPL void sg_reset_state_cache(void) {
25809+    SOKOL_ASSERT(_sg.valid);
25810+    _sg_reset_state_cache();
25811+    _SG_TRACE_NOARGS(reset_state_cache);
25812+}
25813+
25814+SOKOL_API_IMPL void sg_update_buffer(sg_buffer buf_id, const sg_range* data) {
25815+    SOKOL_ASSERT(_sg.valid);
25816+    SOKOL_ASSERT(data && data->ptr && (data->size > 0));
25817+    _sg_stats_inc(num_update_buffer);
25818+    _sg_stats_add(size_update_buffer, (uint32_t)data->size);
25819+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25820+    if ((data->size > 0) && buf && (buf->slot.state == SG_RESOURCESTATE_VALID)) {
25821+        if (_sg_validate_update_buffer(buf, data)) {
25822+            SOKOL_ASSERT(data->size <= (size_t)buf->cmn.size);
25823+            // only one update allowed per buffer and frame
25824+            SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index);
25825+            // update and append on same buffer in same frame not allowed
25826+            SOKOL_ASSERT(buf->cmn.append_frame_index != _sg.frame_index);
25827+            _sg_update_buffer(buf, data);
25828+            buf->cmn.update_frame_index = _sg.frame_index;
25829+        }
25830+    }
25831+    _SG_TRACE_ARGS(update_buffer, buf_id, data);
25832+}
25833+
25834+SOKOL_API_IMPL int sg_append_buffer(sg_buffer buf_id, const sg_range* data) {
25835+    SOKOL_ASSERT(_sg.valid);
25836+    SOKOL_ASSERT(data && data->ptr);
25837+    _sg_stats_inc(num_append_buffer);
25838+    _sg_stats_add(size_append_buffer, (uint32_t)data->size);
25839+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25840+    int result;
25841+    if (buf) {
25842+        // rewind append cursor in a new frame
25843+        if (buf->cmn.append_frame_index != _sg.frame_index) {
25844+            buf->cmn.append_pos = 0;
25845+            buf->cmn.append_overflow = false;
25846+        }
25847+        if (((size_t)buf->cmn.append_pos + data->size) > (size_t)buf->cmn.size) {
25848+            buf->cmn.append_overflow = true;
25849+        }
25850+        const int start_pos = buf->cmn.append_pos;
25851+        // NOTE: the multiple-of-4 requirement for the buffer offset is coming
25852+        // from WebGPU, but we want identical behaviour between backends
25853+        SOKOL_ASSERT(_sg_multiple_u64((uint64_t)start_pos, 4));
25854+        if (buf->slot.state == SG_RESOURCESTATE_VALID) {
25855+            if (_sg_validate_append_buffer(buf, data)) {
25856+                if (!buf->cmn.append_overflow && (data->size > 0)) {
25857+                    // update and append on same buffer in same frame not allowed
25858+                    SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index);
25859+                    _sg_append_buffer(buf, data, buf->cmn.append_frame_index != _sg.frame_index);
25860+                    buf->cmn.append_pos += (int) _sg_roundup_u64(data->size, 4);
25861+                    buf->cmn.append_frame_index = _sg.frame_index;
25862+                }
25863+            }
25864+        }
25865+        result = start_pos;
25866+    } else {
25867+        // FIXME: should we return -1 here?
25868+        result = 0;
25869+    }
25870+    _SG_TRACE_ARGS(append_buffer, buf_id, data, result);
25871+    return result;
25872+}
25873+
25874+SOKOL_API_IMPL bool sg_query_buffer_overflow(sg_buffer buf_id) {
25875+    SOKOL_ASSERT(_sg.valid);
25876+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25877+    bool result = buf ? buf->cmn.append_overflow : false;
25878+    return result;
25879+}
25880+
25881+SOKOL_API_IMPL bool sg_query_buffer_will_overflow(sg_buffer buf_id, size_t size) {
25882+    SOKOL_ASSERT(_sg.valid);
25883+    _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25884+    bool result = false;
25885+    if (buf) {
25886+        int append_pos = buf->cmn.append_pos;
25887+        // rewind append cursor in a new frame
25888+        if (buf->cmn.append_frame_index != _sg.frame_index) {
25889+            append_pos = 0;
25890+        }
25891+        if ((append_pos + _sg_roundup((int)size, 4)) > buf->cmn.size) {
25892+            result = true;
25893+        }
25894+    }
25895+    return result;
25896+}
25897+
25898+SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data) {
25899+    SOKOL_ASSERT(_sg.valid);
25900+    _sg_stats_inc(num_update_image);
25901+    for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) {
25902+        if (data->mip_levels[mip_index].size == 0) {
25903+            break;
25904+        }
25905+        _sg_stats_add(size_update_image, (uint32_t)data->mip_levels[mip_index].size);
25906+    }
25907+    _sg_image_t* img = _sg_lookup_image(img_id.id);
25908+    if (img && img->slot.state == SG_RESOURCESTATE_VALID) {
25909+        if (_sg_validate_update_image(img, data)) {
25910+            SOKOL_ASSERT(img->cmn.upd_frame_index != _sg.frame_index);
25911+            _sg_update_image(img, data);
25912+            img->cmn.upd_frame_index = _sg.frame_index;
25913+        }
25914+    }
25915+    _SG_TRACE_ARGS(update_image, img_id, data);
25916+}
25917+
25918+SOKOL_API_IMPL void sg_push_debug_group(const char* name) {
25919+    SOKOL_ASSERT(_sg.valid);
25920+    SOKOL_ASSERT(name);
25921+    _sg_push_debug_group(name);
25922+    _SG_TRACE_ARGS(push_debug_group, name);
25923+}
25924+
25925+SOKOL_API_IMPL void sg_pop_debug_group(void) {
25926+    SOKOL_ASSERT(_sg.valid);
25927+    _sg_pop_debug_group();
25928+    _SG_TRACE_NOARGS(pop_debug_group);
25929+}
25930+
25931+SOKOL_API_IMPL bool sg_add_commit_listener(sg_commit_listener listener) {
25932+    SOKOL_ASSERT(_sg.valid);
25933+    return _sg_add_commit_listener(&listener);
25934+}
25935+
25936+SOKOL_API_IMPL bool sg_remove_commit_listener(sg_commit_listener listener) {
25937+    SOKOL_ASSERT(_sg.valid);
25938+    return _sg_remove_commit_listener(&listener);
25939+}
25940+
25941+SOKOL_API_IMPL void sg_enable_stats(void) {
25942+    SOKOL_ASSERT(_sg.valid);
25943+    _sg.stats_enabled = true;
25944+}
25945+
25946+SOKOL_API_IMPL void sg_disable_stats(void) {
25947+    SOKOL_ASSERT(_sg.valid);
25948+    _sg.stats_enabled = false;
25949+}
25950+
25951+SOKOL_API_IMPL bool sg_stats_enabled(void) {
25952+    return _sg.stats_enabled;
25953+}
25954+
25955+SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) {
25956+    SOKOL_ASSERT(_sg.valid);
25957+    _SG_STRUCT(sg_buffer_info, info);
25958+    const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
25959+    if (buf) {
25960+        info.slot.state = buf->slot.state;
25961+        info.slot.res_id = buf->slot.id;
25962+        info.slot.uninit_count = buf->slot.uninit_count;
25963+        info.update_frame_index = buf->cmn.update_frame_index;
25964+        info.append_frame_index = buf->cmn.append_frame_index;
25965+        info.append_pos = buf->cmn.append_pos;
25966+        info.append_overflow = buf->cmn.append_overflow;
25967+        #if defined(SOKOL_D3D11)
25968+        info.num_slots = 1;
25969+        info.active_slot = 0;
25970+        #else
25971+        info.num_slots = buf->cmn.num_slots;
25972+        info.active_slot = buf->cmn.active_slot;
25973+        #endif
25974+    }
25975+    return info;
25976+}
25977+
25978+SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) {
25979+    SOKOL_ASSERT(_sg.valid);
25980+    _SG_STRUCT(sg_image_info, info);
25981+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
25982+    if (img) {
25983+        info.slot.state = img->slot.state;
25984+        info.slot.res_id = img->slot.id;
25985+        info.slot.uninit_count = img->slot.uninit_count;
25986+        info.upd_frame_index = img->cmn.upd_frame_index;
25987+        #if defined(SOKOL_D3D11)
25988+        info.num_slots = 1;
25989+        info.active_slot = 0;
25990+        #else
25991+        info.num_slots = img->cmn.num_slots;
25992+        info.active_slot = img->cmn.active_slot;
25993+        #endif
25994+    }
25995+    return info;
25996+}
25997+
25998+SOKOL_API_IMPL sg_sampler_info sg_query_sampler_info(sg_sampler smp_id) {
25999+    SOKOL_ASSERT(_sg.valid);
26000+    _SG_STRUCT(sg_sampler_info, info);
26001+    const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
26002+    if (smp) {
26003+        info.slot.state = smp->slot.state;
26004+        info.slot.res_id = smp->slot.id;
26005+        info.slot.uninit_count = smp->slot.uninit_count;
26006+    }
26007+    return info;
26008+}
26009+
26010+SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) {
26011+    SOKOL_ASSERT(_sg.valid);
26012+    _SG_STRUCT(sg_shader_info, info);
26013+    const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
26014+    if (shd) {
26015+        info.slot.state = shd->slot.state;
26016+        info.slot.res_id = shd->slot.id;
26017+        info.slot.uninit_count = shd->slot.uninit_count;
26018+    }
26019+    return info;
26020+}
26021+
26022+SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) {
26023+    SOKOL_ASSERT(_sg.valid);
26024+    _SG_STRUCT(sg_pipeline_info, info);
26025+    const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
26026+    if (pip) {
26027+        info.slot.state = pip->slot.state;
26028+        info.slot.res_id = pip->slot.id;
26029+        info.slot.uninit_count = pip->slot.uninit_count;
26030+    }
26031+    return info;
26032+}
26033+
26034+SOKOL_API_IMPL sg_view_info sg_query_view_info(sg_view view_id) {
26035+    SOKOL_ASSERT(_sg.valid);
26036+    _SG_STRUCT(sg_view_info, info);
26037+    const _sg_view_t* view = _sg_lookup_view(view_id.id);
26038+    if (view) {
26039+        info.slot.state = view->slot.state;
26040+        info.slot.res_id = view->slot.id;
26041+        info.slot.uninit_count = view->slot.uninit_count;
26042+    }
26043+    return info;
26044+}
26045+
26046+SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf_id) {
26047+    SOKOL_ASSERT(_sg.valid);
26048+    _SG_STRUCT(sg_buffer_desc, desc);
26049+    const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26050+    if (buf) {
26051+        desc.size = (size_t)buf->cmn.size;
26052+        desc.usage = buf->cmn.usage;
26053+    }
26054+    return desc;
26055+}
26056+
26057+SOKOL_API_IMPL size_t sg_query_buffer_size(sg_buffer buf_id) {
26058+    SOKOL_ASSERT(_sg.valid);
26059+    const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26060+    if (buf) {
26061+        return (size_t)buf->cmn.size;
26062+    }
26063+    return 0;
26064+}
26065+
26066+SOKOL_API_IMPL sg_buffer_usage sg_query_buffer_usage(sg_buffer buf_id) {
26067+    SOKOL_ASSERT(_sg.valid);
26068+    _SG_STRUCT(sg_buffer_usage, usg);
26069+    const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26070+    if (buf) {
26071+        usg = buf->cmn.usage;
26072+    }
26073+    return usg;
26074+}
26075+
26076+SOKOL_API_IMPL sg_image_desc sg_query_image_desc(sg_image img_id) {
26077+    SOKOL_ASSERT(_sg.valid);
26078+    _SG_STRUCT(sg_image_desc, desc);
26079+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26080+    if (img) {
26081+        desc.type = img->cmn.type;
26082+        desc.width = img->cmn.width;
26083+        desc.height = img->cmn.height;
26084+        desc.num_slices = img->cmn.num_slices;
26085+        desc.num_mipmaps = img->cmn.num_mipmaps;
26086+        desc.usage = img->cmn.usage;
26087+        desc.pixel_format = img->cmn.pixel_format;
26088+        desc.sample_count = img->cmn.sample_count;
26089+    }
26090+    return desc;
26091+}
26092+
26093+SOKOL_API_IMPL sg_image_type sg_query_image_type(sg_image img_id) {
26094+    SOKOL_ASSERT(_sg.valid);
26095+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26096+    if (img) {
26097+        return img->cmn.type;
26098+    }
26099+    return _SG_IMAGETYPE_DEFAULT;
26100+}
26101+
26102+SOKOL_API_IMPL int sg_query_image_width(sg_image img_id) {
26103+    SOKOL_ASSERT(_sg.valid);
26104+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26105+    if (img) {
26106+        return img->cmn.width;
26107+    }
26108+    return 0;
26109+}
26110+
26111+SOKOL_API_IMPL int sg_query_image_height(sg_image img_id) {
26112+    SOKOL_ASSERT(_sg.valid);
26113+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26114+    if (img) {
26115+        return img->cmn.height;
26116+    }
26117+    return 0;
26118+}
26119+
26120+SOKOL_API_IMPL int sg_query_image_num_slices(sg_image img_id) {
26121+    SOKOL_ASSERT(_sg.valid);
26122+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26123+    if (img) {
26124+        return img->cmn.num_slices;
26125+    }
26126+    return 0;
26127+}
26128+
26129+SOKOL_API_IMPL int sg_query_image_num_mipmaps(sg_image img_id) {
26130+    SOKOL_ASSERT(_sg.valid);
26131+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26132+    if (img) {
26133+        return img->cmn.num_mipmaps;
26134+    }
26135+    return 0;
26136+}
26137+
26138+SOKOL_API_IMPL sg_pixel_format sg_query_image_pixelformat(sg_image img_id) {
26139+    SOKOL_ASSERT(_sg.valid);
26140+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26141+    if (img) {
26142+        return img->cmn.pixel_format;
26143+    }
26144+    return _SG_PIXELFORMAT_DEFAULT;
26145+}
26146+
26147+SOKOL_API_IMPL sg_image_usage sg_query_image_usage(sg_image img_id) {
26148+    SOKOL_ASSERT(_sg.valid);
26149+    _SG_STRUCT(sg_image_usage, usg);
26150+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26151+    if (img) {
26152+        usg = img->cmn.usage;
26153+    }
26154+    return usg;
26155+}
26156+
26157+SOKOL_API_IMPL int sg_query_image_sample_count(sg_image img_id) {
26158+    SOKOL_ASSERT(_sg.valid);
26159+    const _sg_image_t* img = _sg_lookup_image(img_id.id);
26160+    if (img) {
26161+        return img->cmn.sample_count;
26162+    }
26163+    return 0;
26164+}
26165+
26166+SOKOL_API_IMPL sg_view_type sg_query_view_type(sg_view view_id) {
26167+    SOKOL_ASSERT(_sg.valid);
26168+    const _sg_view_t* view = _sg_lookup_view(view_id.id);
26169+    if (view) {
26170+        return view->cmn.type;
26171+    } else {
26172+        return SG_VIEWTYPE_INVALID;
26173+    }
26174+}
26175+
26176+// NOTE: may return SG_INVALID_ID if view invalid or view not an image view
26177+SOKOL_API_IMPL sg_image sg_query_view_image(sg_view view_id) {
26178+    SOKOL_ASSERT(_sg.valid);
26179+    _SG_STRUCT(sg_image, img);
26180+    const _sg_view_t* view = _sg_lookup_view(view_id.id);
26181+    if (view) {
26182+        img.id = view->cmn.img.ref.sref.id;
26183+    }
26184+    return img;
26185+}
26186+
26187+// NOTE: may return SG_INVALID_ID if view invalid or view not a buffer view
26188+SOKOL_API_IMPL sg_buffer sg_query_view_buffer(sg_view view_id) {
26189+    SOKOL_ASSERT(_sg.valid);
26190+    _SG_STRUCT(sg_buffer, buf);
26191+    const _sg_view_t* view = _sg_lookup_view(view_id.id);
26192+    if (view) {
26193+        buf.id = view->cmn.buf.ref.sref.id;
26194+    }
26195+    return buf;
26196+}
26197+
26198+SOKOL_API_IMPL sg_sampler_desc sg_query_sampler_desc(sg_sampler smp_id) {
26199+    SOKOL_ASSERT(_sg.valid);
26200+    _SG_STRUCT(sg_sampler_desc, desc);
26201+    const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
26202+    if (smp) {
26203+        desc.min_filter = smp->cmn.min_filter;
26204+        desc.mag_filter = smp->cmn.mag_filter;
26205+        desc.mipmap_filter = smp->cmn.mipmap_filter;
26206+        desc.wrap_u = smp->cmn.wrap_u;
26207+        desc.wrap_v = smp->cmn.wrap_v;
26208+        desc.wrap_w = smp->cmn.wrap_w;
26209+        desc.min_lod = smp->cmn.min_lod;
26210+        desc.max_lod = smp->cmn.max_lod;
26211+        desc.border_color = smp->cmn.border_color;
26212+        desc.compare = smp->cmn.compare;
26213+        desc.max_anisotropy = smp->cmn.max_anisotropy;
26214+    }
26215+    return desc;
26216+}
26217+
26218+SOKOL_API_IMPL sg_shader_desc sg_query_shader_desc(sg_shader shd_id) {
26219+    SOKOL_ASSERT(_sg.valid);
26220+    _SG_STRUCT(sg_shader_desc, desc);
26221+    const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
26222+    if (shd) {
26223+        for (size_t ub_idx = 0; ub_idx < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_idx++) {
26224+            sg_shader_uniform_block* ub_desc = &desc.uniform_blocks[ub_idx];
26225+            const _sg_shader_uniform_block_t* ub = &shd->cmn.uniform_blocks[ub_idx];
26226+            ub_desc->stage = ub->stage;
26227+            ub_desc->size = ub->size;
26228+        }
26229+        for (size_t view_idx = 0; view_idx < SG_MAX_VIEW_BINDSLOTS; view_idx++) {
26230+            const _sg_shader_view_t* view = &shd->cmn.views[view_idx];
26231+            if (view->view_type == SG_VIEWTYPE_TEXTURE) {
26232+                sg_shader_texture_view* tex_desc = &desc.views[view_idx].texture;
26233+                tex_desc->stage = view->stage;
26234+                tex_desc->image_type = view->image_type;
26235+                tex_desc->sample_type = view->sample_type;
26236+                tex_desc->multisampled = view->multisampled;
26237+            } else if (shd->cmn.views[view_idx].view_type == SG_VIEWTYPE_STORAGEBUFFER) {
26238+                sg_shader_storage_buffer_view* sbuf_desc = &desc.views[view_idx].storage_buffer;
26239+                sbuf_desc->stage = view->stage;
26240+                sbuf_desc->readonly = view->sbuf_readonly;
26241+            } else if (shd->cmn.views[view_idx].view_type == SG_VIEWTYPE_STORAGEIMAGE) {
26242+                sg_shader_storage_image_view* simg_desc = &desc.views[view_idx].storage_image;
26243+                simg_desc->stage = view->stage;
26244+                simg_desc->access_format = view->access_format;
26245+                simg_desc->image_type = view->image_type;
26246+                simg_desc->writeonly = view->simg_writeonly;
26247+            }
26248+        }
26249+        for (size_t smp_idx = 0; smp_idx < SG_MAX_SAMPLER_BINDSLOTS; smp_idx++) {
26250+            sg_shader_sampler* smp_desc = &desc.samplers[smp_idx];
26251+            const _sg_shader_sampler_t* smp = &shd->cmn.samplers[smp_idx];
26252+            smp_desc->stage = smp->stage;
26253+            smp_desc->sampler_type = smp->sampler_type;
26254+        }
26255+        for (size_t tex_smp_idx = 0; tex_smp_idx < SG_MAX_TEXTURE_SAMPLER_PAIRS; tex_smp_idx++) {
26256+            sg_shader_texture_sampler_pair* tex_smp_desc = &desc.texture_sampler_pairs[tex_smp_idx];
26257+            const _sg_shader_texture_sampler_t* tex_smp = &shd->cmn.texture_samplers[tex_smp_idx];
26258+            tex_smp_desc->stage = tex_smp->stage;
26259+            tex_smp_desc->view_slot = tex_smp->view_slot;
26260+            tex_smp_desc->sampler_slot = tex_smp->sampler_slot;
26261+        }
26262+    }
26263+    return desc;
26264+}
26265+
26266+SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip_id) {
26267+    SOKOL_ASSERT(_sg.valid);
26268+    _SG_STRUCT(sg_pipeline_desc, desc);
26269+    const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
26270+    if (pip) {
26271+        desc.compute = pip->cmn.is_compute;
26272+        desc.shader.id = pip->cmn.shader.sref.id;
26273+        desc.layout = pip->cmn.layout;
26274+        desc.depth = pip->cmn.depth;
26275+        desc.stencil = pip->cmn.stencil;
26276+        desc.color_count = pip->cmn.color_count;
26277+        for (int i = 0; i < pip->cmn.color_count; i++) {
26278+            desc.colors[i] = pip->cmn.colors[i];
26279+        }
26280+        desc.primitive_type = pip->cmn.primitive_type;
26281+        desc.index_type = pip->cmn.index_type;
26282+        desc.cull_mode = pip->cmn.cull_mode;
26283+        desc.face_winding = pip->cmn.face_winding;
26284+        desc.sample_count = pip->cmn.sample_count;
26285+        desc.blend_color = pip->cmn.blend_color;
26286+        desc.alpha_to_coverage_enabled = pip->cmn.alpha_to_coverage_enabled;
26287+    }
26288+    return desc;
26289+}
26290+
26291+SOKOL_API_IMPL sg_view_desc sg_query_view_desc(sg_view view_id) {
26292+    SOKOL_ASSERT(_sg.valid);
26293+    _SG_STRUCT(sg_view_desc, desc);
26294+    const _sg_view_t* view = _sg_lookup_view(view_id.id);
26295+    if (view) {
26296+        switch (view->cmn.type) {
26297+            case SG_VIEWTYPE_STORAGEBUFFER:
26298+                desc.storage_buffer.buffer.id = view->cmn.buf.ref.sref.id;
26299+                desc.storage_buffer.offset = view->cmn.buf.offset;
26300+                break;
26301+            case SG_VIEWTYPE_STORAGEIMAGE:
26302+                desc.storage_image.image.id = view->cmn.img.ref.sref.id;
26303+                desc.storage_image.mip_level = view->cmn.img.mip_level;
26304+                desc.storage_image.slice = view->cmn.img.slice;
26305+                break;
26306+            case SG_VIEWTYPE_TEXTURE:
26307+                desc.texture.image.id = view->cmn.img.ref.sref.id;
26308+                desc.texture.mip_levels.base = view->cmn.img.mip_level;
26309+                desc.texture.mip_levels.count = view->cmn.img.mip_level_count;
26310+                desc.texture.slices.base = view->cmn.img.slice;
26311+                desc.texture.slices.count = view->cmn.img.slice_count;
26312+                break;
26313+            case SG_VIEWTYPE_COLORATTACHMENT:
26314+                desc.color_attachment.image.id = view->cmn.img.ref.sref.id;
26315+                desc.color_attachment.mip_level = view->cmn.img.mip_level;
26316+                desc.color_attachment.slice = view->cmn.img.slice;
26317+                break;
26318+            case SG_VIEWTYPE_RESOLVEATTACHMENT:
26319+                desc.resolve_attachment.image.id = view->cmn.img.ref.sref.id;
26320+                desc.resolve_attachment.mip_level = view->cmn.img.mip_level;
26321+                desc.resolve_attachment.slice = view->cmn.img.slice;
26322+                break;
26323+            case SG_VIEWTYPE_DEPTHSTENCILATTACHMENT:
26324+                desc.depth_stencil_attachment.image.id = view->cmn.img.ref.sref.id;
26325+                desc.depth_stencil_attachment.mip_level = view->cmn.img.mip_level;
26326+                desc.depth_stencil_attachment.slice = view->cmn.img.slice;
26327+                break;
26328+            default:
26329+                SOKOL_UNREACHABLE;
26330+        }
26331+    }
26332+    return desc;
26333+}
26334+
26335+SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) {
26336+    SOKOL_ASSERT(_sg.valid && desc);
26337+    return _sg_buffer_desc_defaults(desc);
26338+}
26339+
26340+SOKOL_API_IMPL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) {
26341+    SOKOL_ASSERT(_sg.valid && desc);
26342+    return _sg_image_desc_defaults(desc);
26343+}
26344+
26345+SOKOL_API_IMPL sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc) {
26346+    SOKOL_ASSERT(_sg.valid && desc);
26347+    return _sg_sampler_desc_defaults(desc);
26348+}
26349+
26350+SOKOL_API_IMPL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) {
26351+    SOKOL_ASSERT(_sg.valid && desc);
26352+    return _sg_shader_desc_defaults(desc);
26353+}
26354+
26355+SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) {
26356+    SOKOL_ASSERT(_sg.valid && desc);
26357+    return _sg_pipeline_desc_defaults(desc);
26358+}
26359+
26360+SOKOL_API_IMPL sg_view_desc sg_query_view_defaults(const sg_view_desc* desc) {
26361+    SOKOL_ASSERT(_sg.valid && desc);
26362+    return _sg_view_desc_defaults(desc);
26363+}
26364+
26365+SOKOL_API_IMPL const void* sg_d3d11_device(void) {
26366+    #if defined(SOKOL_D3D11)
26367+        return (const void*) _sg.d3d11.dev;
26368+    #else
26369+        return 0;
26370+    #endif
26371+}
26372+
26373+SOKOL_API_IMPL const void* sg_d3d11_device_context(void) {
26374+    #if defined(SOKOL_D3D11)
26375+        return (const void*) _sg.d3d11.ctx;
26376+    #else
26377+        return 0;
26378+    #endif
26379+}
26380+
26381+SOKOL_API_IMPL sg_d3d11_buffer_info sg_d3d11_query_buffer_info(sg_buffer buf_id) {
26382+    SOKOL_ASSERT(_sg.valid);
26383+    _SG_STRUCT(sg_d3d11_buffer_info, res);
26384+    #if defined(SOKOL_D3D11)
26385+        const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26386+        if (buf) {
26387+            res.buf = (const void*) buf->d3d11.buf;
26388+        }
26389+    #else
26390+        _SOKOL_UNUSED(buf_id);
26391+    #endif
26392+    return res;
26393+}
26394+
26395+SOKOL_API_IMPL sg_d3d11_image_info sg_d3d11_query_image_info(sg_image img_id) {
26396+    SOKOL_ASSERT(_sg.valid);
26397+    _SG_STRUCT(sg_d3d11_image_info, res);
26398+    #if defined(SOKOL_D3D11)
26399+        const _sg_image_t* img = _sg_lookup_image(img_id.id);
26400+        if (img) {
26401+            res.tex2d = (const void*) img->d3d11.tex2d;
26402+            res.tex3d = (const void*) img->d3d11.tex3d;
26403+            res.res = (const void*) img->d3d11.res;
26404+        }
26405+    #else
26406+        _SOKOL_UNUSED(img_id);
26407+    #endif
26408+    return res;
26409+}
26410+
26411+SOKOL_API_IMPL sg_d3d11_sampler_info sg_d3d11_query_sampler_info(sg_sampler smp_id) {
26412+    SOKOL_ASSERT(_sg.valid);
26413+    _SG_STRUCT(sg_d3d11_sampler_info, res);
26414+    #if defined(SOKOL_D3D11)
26415+        const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
26416+        if (smp) {
26417+            res.smp = (const void*) smp->d3d11.smp;
26418+        }
26419+    #else
26420+        _SOKOL_UNUSED(smp_id);
26421+    #endif
26422+    return res;
26423+}
26424+
26425+SOKOL_API_IMPL sg_d3d11_shader_info sg_d3d11_query_shader_info(sg_shader shd_id) {
26426+    SOKOL_ASSERT(_sg.valid);
26427+    _SG_STRUCT(sg_d3d11_shader_info, res);
26428+    #if defined(SOKOL_D3D11)
26429+        const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
26430+        if (shd) {
26431+            for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) {
26432+                res.cbufs[i] = (const void*) shd->d3d11.all_cbufs[i];
26433+            }
26434+            res.vs = (const void*) shd->d3d11.vs;
26435+            res.fs = (const void*) shd->d3d11.fs;
26436+        }
26437+    #else
26438+        _SOKOL_UNUSED(shd_id);
26439+    #endif
26440+    return res;
26441+}
26442+
26443+SOKOL_API_IMPL sg_d3d11_pipeline_info sg_d3d11_query_pipeline_info(sg_pipeline pip_id) {
26444+    SOKOL_ASSERT(_sg.valid);
26445+    _SG_STRUCT(sg_d3d11_pipeline_info, res);
26446+    #if defined(SOKOL_D3D11)
26447+        const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
26448+        if (pip) {
26449+            res.il = (const void*) pip->d3d11.il;
26450+            res.rs = (const void*) pip->d3d11.rs;
26451+            res.dss = (const void*) pip->d3d11.dss;
26452+            res.bs = (const void*) pip->d3d11.bs;
26453+        }
26454+    #else
26455+        _SOKOL_UNUSED(pip_id);
26456+    #endif
26457+    return res;
26458+}
26459+
26460+SOKOL_API_IMPL sg_d3d11_view_info sg_d3d11_query_view_info(sg_view view_id) {
26461+    SOKOL_ASSERT(_sg.valid);
26462+    _SG_STRUCT(sg_d3d11_view_info, res);
26463+    #if defined(SOKOL_D3D11)
26464+        const _sg_view_t* view = _sg_lookup_view(view_id.id);
26465+        res.srv = (const void*) view->d3d11.srv;
26466+        res.uav = (const void*) view->d3d11.uav;
26467+        res.rtv = (const void*) view->d3d11.rtv;
26468+        res.dsv = (const void*) view->d3d11.dsv;
26469+    #else
26470+        _SOKOL_UNUSED(view_id);
26471+    #endif
26472+    return res;
26473+}
26474+
26475+SOKOL_API_IMPL const void* sg_mtl_device(void) {
26476+    #if defined(SOKOL_METAL)
26477+        if (nil != _sg.mtl.device) {
26478+            return (__bridge const void*) _sg.mtl.device;
26479+        } else {
26480+            return 0;
26481+        }
26482+    #else
26483+        return 0;
26484+    #endif
26485+}
26486+
26487+SOKOL_API_IMPL const void* sg_mtl_render_command_encoder(void) {
26488+    #if defined(SOKOL_METAL)
26489+        if (nil != _sg.mtl.render_cmd_encoder) {
26490+            return (__bridge const void*) _sg.mtl.render_cmd_encoder;
26491+        } else {
26492+            return 0;
26493+        }
26494+    #else
26495+        return 0;
26496+    #endif
26497+}
26498+
26499+SOKOL_API_IMPL const void* sg_mtl_compute_command_encoder(void) {
26500+    #if defined(SOKOL_METAL)
26501+        if (nil != _sg.mtl.compute_cmd_encoder) {
26502+            return (__bridge const void*) _sg.mtl.compute_cmd_encoder;
26503+        } else {
26504+            return 0;
26505+        }
26506+    #else
26507+        return 0;
26508+    #endif
26509+}
26510+
26511+SOKOL_API_IMPL const void* sg_mtl_command_queue(void) {
26512+    #if defined(SOKOL_METAL)
26513+        if (nil != _sg.mtl.cmd_queue) {
26514+            return (__bridge const void*) _sg.mtl.cmd_queue;
26515+        } else {
26516+            return 0;
26517+        }
26518+    #else
26519+        return 0;
26520+    #endif
26521+}
26522+
26523+SOKOL_API_IMPL sg_mtl_buffer_info sg_mtl_query_buffer_info(sg_buffer buf_id) {
26524+    SOKOL_ASSERT(_sg.valid);
26525+    _SG_STRUCT(sg_mtl_buffer_info, res);
26526+    #if defined(SOKOL_METAL)
26527+        const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26528+        if (buf) {
26529+            for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
26530+                if (buf->mtl.buf[i] != 0) {
26531+                    res.buf[i] = (__bridge void*) _sg_mtl_id(buf->mtl.buf[i]);
26532+                }
26533+            }
26534+            res.active_slot = buf->cmn.active_slot;
26535+        }
26536+    #else
26537+        _SOKOL_UNUSED(buf_id);
26538+    #endif
26539+    return res;
26540+}
26541+
26542+SOKOL_API_IMPL sg_mtl_image_info sg_mtl_query_image_info(sg_image img_id) {
26543+    SOKOL_ASSERT(_sg.valid);
26544+    _SG_STRUCT(sg_mtl_image_info, res);
26545+    #if defined(SOKOL_METAL)
26546+        const _sg_image_t* img = _sg_lookup_image(img_id.id);
26547+        if (img) {
26548+            for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
26549+                if (img->mtl.tex[i] != 0) {
26550+                    res.tex[i] = (__bridge void*) _sg_mtl_id(img->mtl.tex[i]);
26551+                }
26552+            }
26553+            res.active_slot = img->cmn.active_slot;
26554+        }
26555+    #else
26556+        _SOKOL_UNUSED(img_id);
26557+    #endif
26558+    return res;
26559+}
26560+
26561+SOKOL_API_IMPL sg_mtl_sampler_info sg_mtl_query_sampler_info(sg_sampler smp_id) {
26562+    SOKOL_ASSERT(_sg.valid);
26563+    _SG_STRUCT(sg_mtl_sampler_info, res);
26564+    #if defined(SOKOL_METAL)
26565+        const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
26566+        if (smp) {
26567+            if (smp->mtl.sampler_state != 0) {
26568+                res.smp = (__bridge void*) _sg_mtl_id(smp->mtl.sampler_state);
26569+            }
26570+        }
26571+    #else
26572+        _SOKOL_UNUSED(smp_id);
26573+    #endif
26574+    return res;
26575+}
26576+
26577+SOKOL_API_IMPL sg_mtl_shader_info sg_mtl_query_shader_info(sg_shader shd_id) {
26578+    SOKOL_ASSERT(_sg.valid);
26579+    _SG_STRUCT(sg_mtl_shader_info, res);
26580+    #if defined(SOKOL_METAL)
26581+        const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
26582+        if (shd) {
26583+            const int vertex_lib  = shd->mtl.vertex_func.mtl_lib;
26584+            const int vertex_func = shd->mtl.vertex_func.mtl_func;
26585+            const int fragment_lib  = shd->mtl.fragment_func.mtl_lib;
26586+            const int fragment_func = shd->mtl.fragment_func.mtl_func;
26587+            if (vertex_lib != 0) {
26588+                res.vertex_lib = (__bridge void*) _sg_mtl_id(vertex_lib);
26589+            }
26590+            if (fragment_lib != 0) {
26591+                res.fragment_lib = (__bridge void*) _sg_mtl_id(fragment_lib);
26592+            }
26593+            if (vertex_func != 0) {
26594+                res.vertex_func = (__bridge void*) _sg_mtl_id(vertex_func);
26595+            }
26596+            if (fragment_func != 0) {
26597+                res.fragment_func = (__bridge void*) _sg_mtl_id(fragment_func);
26598+            }
26599+        }
26600+    #else
26601+        _SOKOL_UNUSED(shd_id);
26602+    #endif
26603+    return res;
26604+}
26605+
26606+SOKOL_API_IMPL sg_mtl_pipeline_info sg_mtl_query_pipeline_info(sg_pipeline pip_id) {
26607+    SOKOL_ASSERT(_sg.valid);
26608+    _SG_STRUCT(sg_mtl_pipeline_info, res);
26609+    #if defined(SOKOL_METAL)
26610+        const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
26611+        if (pip) {
26612+            if (pip->mtl.rps != 0) {
26613+                res.rps = (__bridge void*) _sg_mtl_id(pip->mtl.rps);
26614+            }
26615+            if (pip->mtl.dss != 0) {
26616+                res.dss = (__bridge void*) _sg_mtl_id(pip->mtl.dss);
26617+            }
26618+        }
26619+    #else
26620+        _SOKOL_UNUSED(pip_id);
26621+    #endif
26622+    return res;
26623+}
26624+
26625+SOKOL_API_IMPL const void* sg_wgpu_device(void) {
26626+    #if defined(SOKOL_WGPU)
26627+        return (const void*) _sg.wgpu.dev;
26628+    #else
26629+        return 0;
26630+    #endif
26631+}
26632+
26633+SOKOL_API_IMPL const void* sg_wgpu_queue(void) {
26634+    #if defined(SOKOL_WGPU)
26635+        return (const void*) _sg.wgpu.queue;
26636+    #else
26637+        return 0;
26638+    #endif
26639+}
26640+
26641+SOKOL_API_IMPL const void* sg_wgpu_command_encoder(void) {
26642+    #if defined(SOKOL_WGPU)
26643+        return (const void*) _sg.wgpu.cmd_enc;
26644+    #else
26645+        return 0;
26646+    #endif
26647+}
26648+
26649+SOKOL_API_IMPL const void* sg_wgpu_render_pass_encoder(void) {
26650+    #if defined(SOKOL_WGPU)
26651+        return (const void*) _sg.wgpu.rpass_enc;
26652+    #else
26653+        return 0;
26654+    #endif
26655+}
26656+
26657+SOKOL_API_IMPL const void* sg_wgpu_compute_pass_encoder(void) {
26658+    #if defined(SOKOL_WGPU)
26659+        return (const void*) _sg.wgpu.cpass_enc;
26660+    #else
26661+        return 0;
26662+    #endif
26663+}
26664+
26665+SOKOL_API_IMPL sg_wgpu_buffer_info sg_wgpu_query_buffer_info(sg_buffer buf_id) {
26666+    SOKOL_ASSERT(_sg.valid);
26667+    _SG_STRUCT(sg_wgpu_buffer_info, res);
26668+    #if defined(SOKOL_WGPU)
26669+        const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26670+        if (buf) {
26671+            res.buf = (const void*) buf->wgpu.buf;
26672+        }
26673+    #else
26674+        _SOKOL_UNUSED(buf_id);
26675+    #endif
26676+    return res;
26677+}
26678+
26679+SOKOL_API_IMPL sg_wgpu_image_info sg_wgpu_query_image_info(sg_image img_id) {
26680+    SOKOL_ASSERT(_sg.valid);
26681+    _SG_STRUCT(sg_wgpu_image_info, res);
26682+    #if defined(SOKOL_WGPU)
26683+        const _sg_image_t* img = _sg_lookup_image(img_id.id);
26684+        if (img) {
26685+            res.tex = (const void*) img->wgpu.tex;
26686+        }
26687+    #else
26688+        _SOKOL_UNUSED(img_id);
26689+    #endif
26690+    return res;
26691+}
26692+
26693+SOKOL_API_IMPL sg_wgpu_sampler_info sg_wgpu_query_sampler_info(sg_sampler smp_id) {
26694+    SOKOL_ASSERT(_sg.valid);
26695+    _SG_STRUCT(sg_wgpu_sampler_info, res);
26696+    #if defined(SOKOL_WGPU)
26697+        const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
26698+        if (smp) {
26699+            res.smp = (const void*) smp->wgpu.smp;
26700+        }
26701+    #else
26702+        _SOKOL_UNUSED(smp_id);
26703+    #endif
26704+    return res;
26705+}
26706+
26707+SOKOL_API_IMPL sg_wgpu_shader_info sg_wgpu_query_shader_info(sg_shader shd_id) {
26708+    SOKOL_ASSERT(_sg.valid);
26709+    _SG_STRUCT(sg_wgpu_shader_info, res);
26710+    #if defined(SOKOL_WGPU)
26711+        const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
26712+        if (shd) {
26713+            res.vs_mod = (const void*) shd->wgpu.vertex_func.module;
26714+            res.fs_mod = (const void*) shd->wgpu.fragment_func.module;
26715+            res.bgl = (const void*) shd->wgpu.bgl_view_smp;
26716+        }
26717+    #else
26718+        _SOKOL_UNUSED(shd_id);
26719+    #endif
26720+    return res;
26721+}
26722+
26723+SOKOL_API_IMPL sg_wgpu_pipeline_info sg_wgpu_query_pipeline_info(sg_pipeline pip_id) {
26724+    SOKOL_ASSERT(_sg.valid);
26725+    _SG_STRUCT(sg_wgpu_pipeline_info, res);
26726+    #if defined(SOKOL_WGPU)
26727+        const _sg_pipeline_t* pip = _sg_lookup_pipeline(pip_id.id);
26728+        if (pip) {
26729+            res.render_pipeline = (const void*) pip->wgpu.rpip;
26730+            res.compute_pipeline = (const void*) pip->wgpu.cpip;
26731+        }
26732+    #else
26733+        _SOKOL_UNUSED(pip_id);
26734+    #endif
26735+    return res;
26736+}
26737+
26738+SOKOL_API_IMPL sg_wgpu_view_info sg_wgpu_query_view_info(sg_view view_id) {
26739+    SOKOL_ASSERT(_sg.valid);
26740+    _SG_STRUCT(sg_wgpu_view_info, res);
26741+    #if defined(SOKOL_WGPU)
26742+        const _sg_view_t* view = _sg_lookup_view(view_id.id);
26743+        if (view) {
26744+            res.view = (const void*) view->wgpu.view;
26745+        }
26746+    #else
26747+        _SOKOL_UNUSED(view_id);
26748+    #endif
26749+    return res;
26750+}
26751+
26752+SOKOL_API_IMPL sg_gl_buffer_info sg_gl_query_buffer_info(sg_buffer buf_id) {
26753+    SOKOL_ASSERT(_sg.valid);
26754+    _SG_STRUCT(sg_gl_buffer_info, res);
26755+    #if defined(_SOKOL_ANY_GL)
26756+        const _sg_buffer_t* buf = _sg_lookup_buffer(buf_id.id);
26757+        if (buf) {
26758+            for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
26759+                res.buf[i] = buf->gl.buf[i];
26760+            }
26761+            res.active_slot = buf->cmn.active_slot;
26762+        }
26763+    #else
26764+        _SOKOL_UNUSED(buf_id);
26765+    #endif
26766+    return res;
26767+}
26768+
26769+SOKOL_API_IMPL sg_gl_image_info sg_gl_query_image_info(sg_image img_id) {
26770+    SOKOL_ASSERT(_sg.valid);
26771+    _SG_STRUCT(sg_gl_image_info, res);
26772+    #if defined(_SOKOL_ANY_GL)
26773+        const _sg_image_t* img = _sg_lookup_image(img_id.id);
26774+        if (img) {
26775+            for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
26776+                res.tex[i] = img->gl.tex[i];
26777+            }
26778+            res.tex_target = img->gl.target;
26779+            res.active_slot = img->cmn.active_slot;
26780+        }
26781+    #else
26782+        _SOKOL_UNUSED(img_id);
26783+    #endif
26784+    return res;
26785+}
26786+
26787+SOKOL_API_IMPL sg_gl_sampler_info sg_gl_query_sampler_info(sg_sampler smp_id) {
26788+    SOKOL_ASSERT(_sg.valid);
26789+    _SG_STRUCT(sg_gl_sampler_info, res);
26790+    #if defined(_SOKOL_ANY_GL)
26791+        const _sg_sampler_t* smp = _sg_lookup_sampler(smp_id.id);
26792+        if (smp) {
26793+            res.smp = smp->gl.smp;
26794+        }
26795+    #else
26796+        _SOKOL_UNUSED(smp_id);
26797+    #endif
26798+    return res;
26799+}
26800+
26801+SOKOL_API_IMPL sg_gl_shader_info sg_gl_query_shader_info(sg_shader shd_id) {
26802+    SOKOL_ASSERT(_sg.valid);
26803+    _SG_STRUCT(sg_gl_shader_info, res);
26804+    #if defined(_SOKOL_ANY_GL)
26805+        const _sg_shader_t* shd = _sg_lookup_shader(shd_id.id);
26806+        if (shd) {
26807+            res.prog = shd->gl.prog;
26808+        }
26809+    #else
26810+        _SOKOL_UNUSED(shd_id);
26811+    #endif
26812+    return res;
26813+}
26814+
26815+SOKOL_API_IMPL sg_gl_view_info sg_gl_query_view_info(sg_view view_id) {
26816+    SOKOL_ASSERT(_sg.valid);
26817+    _SG_STRUCT(sg_gl_view_info, res);
26818+    #if defined(_SOKOL_ANY_GL)
26819+        const _sg_view_t* view = _sg_lookup_view(view_id.id);
26820+        if (view) {
26821+            for (size_t i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) {
26822+                res.tex_view[i] = view->gl.tex_view[i];
26823+            }
26824+            res.msaa_render_buffer = view->gl.msaa_render_buffer;
26825+            res.msaa_resolve_frame_buffer = view->gl.msaa_resolve_frame_buffer;
26826+        }
26827+    #else
26828+        _SOKOL_UNUSED(view_id);
26829+    #endif
26830+    return res;
26831+}
26832+
26833+#ifdef _MSC_VER
26834+#pragma warning(pop)
26835+#endif
26836+
26837+#endif // SOKOL_GFX_IMPL