Browse Source

Merge branch 'spritebatch' of https://github.com/nyalloc/sokol into nyalloc-spritebatch

Andre Weissflog 4 years ago
parent
commit
6468178cb7
2 changed files with 1264 additions and 0 deletions
  1. 1 0
      README.md
  2. 1263 0
      util/sokol_spritebatch.h

+ 1 - 0
README.md

@@ -41,6 +41,7 @@ useful details for integrating the Sokol headers into your own project with your
 
 ## Utility libraries
 
+- [**sokol\_spritebatch.h**](https://github.com/floooh/sokol/blob/master/util/sokol_spritebatch.h): XNA/MonoGame style 2D sprite batcher on top of sokol_gfx.h
 - [**sokol\_imgui.h**](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h): sokol_gfx.h rendering backend for [Dear ImGui](https://github.com/ocornut/imgui)
 - [**sokol\_nuklear.h**](https://github.com/floooh/sokol/blob/master/util/sokol_nuklear.h): sokol_gfx.h rendering backend for [Nuklear](https://github.com/Immediate-Mode-UI/Nuklear)
 - [**sokol\_gl.h**](https://github.com/floooh/sokol/blob/master/util/sokol_gl.h): OpenGL 1.x style immediate-mode rendering API on top of sokol_gfx.h

+ 1263 - 0
util/sokol_spritebatch.h

@@ -0,0 +1,1263 @@
+#if defined(SOKOL_IMPL) && !defined(SOKOL_SPRITEBATCH_IMPL)
+#define SOKOL_SPRITEBATCH_IMPL
+#endif
+
+#ifndef SOKOL_SPRITEBATCH_INCLUDED
+#define SOKOL_SPRITEBATCH_INCLUDED (1)
+
+#if !defined(SOKOL_GFX_INCLUDED)
+#error "Please include sokol_gfx.h before sokol_spritebatch.h"
+#endif
+
+#if defined(SOKOL_API_DECL) && !defined(SOKOL_SPRITEBATCH_API_DECL)
+#define SOKOL_SPRITEBATCH_API_DECL SOKOL_API_DECL
+#endif
+#ifndef SOKOL_SPRITEBATCH_API_DECL
+#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_SPRITEBATCH_IMPL)
+#define SOKOL_SPRITEBATCH_API_DECL __declspec(dllexport)
+#elif defined(_WIN32) && defined(SOKOL_DLL)
+#define SOKOL_SPRITEBATCH_API_DECL __declspec(dllimport)
+#else
+#define SOKOL_SPRITEBATCH_API_DECL extern
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    typedef enum sbatch_sprite_flags {
+        SBATCH_FLIP_NONE = 0,
+        SBATCH_FLIP_X = 1 << 0,
+        SBATCH_FLIP_Y = 1 << 1,
+        SBATCH_FLIP_BOTH = SBATCH_FLIP_Y | SBATCH_FLIP_X
+    } sbatch_sprite_flags;
+
+    typedef struct sbatch_float2 {
+        float x;
+        float y;
+    } sbatch_float2;
+
+    typedef struct sbatch_rect {
+        float x;
+        float y;
+        float width;
+        float height;
+    } sbatch_rect;
+
+    typedef struct sbatch_sprite {
+        sg_image        image;
+        sbatch_float2   position;
+        sbatch_rect     source;
+        const sg_color* color;
+        float           rotation;
+        sbatch_float2   origin;
+        sbatch_float2   scale;
+        uint32_t        flags;
+        float           depth;
+    } sbatch_sprite;
+
+    typedef struct sbatch_sprite_rect {
+        sg_image        image;
+        sbatch_rect     destination;
+        sbatch_rect     source;
+        const sg_color* color;
+        float           rotation;
+        sbatch_float2   origin;
+        uint32_t        flags;
+        float           depth;
+    } sbatch_sprite_rect;
+
+    typedef struct sbatch_desc {
+        int context_pool_size;
+        int pipeline_pool_size;
+        sg_pixel_format color_format;
+        sg_pixel_format depth_format;
+        int sample_count;
+    } sbatch_desc;
+
+    typedef struct sbatch_context {
+        uint32_t id;
+    } sbatch_context;
+
+    typedef struct sbatch_pipeline {
+        uint32_t id;
+    } sbatch_pipeline;
+
+    typedef struct sbatch_context_desc {
+        int canvas_width;
+        int canvas_height;
+        int max_sprites;
+        sbatch_pipeline pipeline;
+        const char* label;
+    } sbatch_context_desc;
+
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_setup(const sbatch_desc* desc);
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_shutdown(void);
+    SOKOL_SPRITEBATCH_API_DECL int sbatch_frame();
+
+    SOKOL_SPRITEBATCH_API_DECL sbatch_pipeline sbatch_make_pipeline(const sg_pipeline_desc* desc);
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_destroy_pipeline(sbatch_pipeline context);
+
+    SOKOL_SPRITEBATCH_API_DECL sbatch_context sbatch_make_context(const sbatch_context_desc* desc);
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_destroy_context(sbatch_context context);
+
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_begin(sbatch_context context);
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_push_sprite(const sbatch_sprite* sprite);
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_push_sprite_rect(const sbatch_sprite_rect* sprite);
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_end(void);
+
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_apply_fs_uniforms(int ub_index, const sg_range* data);
+
+    SOKOL_SPRITEBATCH_API_DECL void sbatch_premultiply_alpha_rgba8(uint8_t* pixels, int pixel_count);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SOKOL_SPRITEBATCH_INCLUDED */
+
+/*-- IMPLEMENTATION ----------------------------------------------------------*/
+#ifdef SOKOL_SPRITEBATCH_IMPL
+#define SOKOL_SPRITEBATCH_IMPL_INCLUDED (1)
+
+#include <string.h> /* memset */
+#include <math.h>   /* sinf, cosf */
+
+#ifndef SOKOL_API_IMPL
+#define SOKOL_API_IMPL
+#endif
+
+#ifndef SOKOL_ASSERT
+#include <assert.h>
+#define SOKOL_ASSERT(c) assert(c)
+#endif
+
+#ifndef SOKOL_MALLOC
+#include <stdlib.h>
+#define SOKOL_MALLOC(s) malloc(s)
+#define SOKOL_FREE(result) free(result)
+#endif
+
+#ifndef SOKOL_DEBUG
+#ifndef NDEBUG
+#define SOKOL_DEBUG (1)
+#endif
+#endif
+
+#ifndef SOKOL_LOG
+#ifdef SOKOL_DEBUG
+#include <stdio.h>
+#define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
+#else
+#define SOKOL_LOG(s)
+#endif
+#endif
+
+#define _SBATCH_IMAGE_SLOT_MASK (0xFFFF)
+
+#define _SBATCH_DEFAULT(val, def) (((val) == 0) ? (def) : (val))
+
+#define _SBATCH_MAX_VERTICES (1 << 16)
+#define _SBATCH_MAX_QUADS (_SBATCH_MAX_VERTICES / 4)
+#define _SBATCH_MAX_INDICES (_SBATCH_MAX_QUADS * 6)
+
+#define _SBATCH_INITIAL_BATCH_CAPACITY (32)
+
+#define _SBATCH_INVALID_SLOT_INDEX (0)
+#define _SBATCH_SLOT_SHIFT (16)
+#define _SBATCH_MAX_POOL_SIZE (1<<_SBATCH_SLOT_SHIFT)
+#define _SBATCH_SLOT_MASK (_SBATCH_MAX_POOL_SIZE-1)
+#define _SBATCH_STRBUF_LEN (96)
+
+typedef struct {
+    float    x;
+    float    y;
+    float    z;
+    float    u;
+    float    v;
+    uint32_t rgba;
+} _sbatch_vertex;
+
+typedef struct {
+    uint32_t id;
+    sg_resource_state state;
+} _sbatch_slot;
+
+typedef struct {
+    char buf[_SBATCH_STRBUF_LEN];
+} _sbatch_str;
+
+typedef struct _sbatch_fs_uniform_state {
+    int ub_index;
+    const sg_range* data;
+} _sbatch_fs_uniform_state;
+
+typedef struct {
+    _sbatch_str label;
+    _sbatch_slot slot;
+    sbatch_context_desc desc;
+    int sprite_count;
+    _sbatch_vertex* vertices;
+    sg_image* images;
+    sg_buffer vertex_buffer;
+    sbatch_pipeline pipeline;
+    int update_frame_index;
+    _sbatch_fs_uniform_state fs_uniform_state;
+} _sbatch_context;
+
+typedef struct {
+    int size;
+    int queue_top;
+    uint32_t* gen_ctrs;
+    int* free_queue;
+} _sbatch_pool;
+
+typedef struct {
+    _sbatch_pool pool;
+    _sbatch_context* contexts;
+} _sbatch_context_pool;
+
+typedef struct {
+    _sbatch_str label;
+    _sbatch_slot slot;
+    sg_pipeline_desc desc;
+    sg_pipeline pipeline;
+} _sbatch_pipeline;
+
+typedef struct {
+    _sbatch_pool pool;
+    _sbatch_pipeline* pipelines;
+} _sbatch_pipeline_pool;
+
+typedef struct {
+    sg_image image;
+    int      width;
+    int      height;
+    float    texel_width;
+    float    texel_height;
+} _sbatch_sprite_data;
+
+typedef struct {
+    _sbatch_sprite_data* data;
+    size_t           size;
+} _sbatch_sprite_pool;
+
+typedef struct {
+    bool begin_called;
+    _sbatch_slot slot;
+    sg_bindings bindings;
+    sg_shader shader;
+    sbatch_pipeline pipeline;
+    sbatch_context ctx_id;
+    _sbatch_context_pool context_pool;
+    _sbatch_pipeline_pool pipeline_pool;
+    _sbatch_sprite_pool sprite_pool;
+    sg_buffer index_buffer;
+    int frame_index;
+} _sbatch_t;
+
+typedef struct {
+    float m[4][4];
+} _sbatch_mat4x4;
+
+static _sbatch_t _sbatch;
+
+#if defined(SOKOL_D3D11)
+static const uint8_t vs_bytecode[884] = {
+    0x44,0x58,0x42,0x43,0x5f,0x8c,0xaf,0xe1,0x5e,0x2d,0xba,0x0e,0x85,0xba,0xeb,0xc5,
+    0x0c,0x64,0x6d,0x0c,0x01,0x00,0x00,0x00,0x74,0x03,0x00,0x00,0x05,0x00,0x00,0x00,
+    0x34,0x00,0x00,0x00,0xf4,0x00,0x00,0x00,0x58,0x01,0x00,0x00,0xc8,0x01,0x00,0x00,
+    0xf8,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0xb8,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
+    0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff,
+    0x10,0x81,0x00,0x00,0x90,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
+    0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,
+    0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x80,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x5f,0x32,0x31,0x5f,0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,
+    0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4d,0x69,0x63,0x72,
+    0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,
+    0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,
+    0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
+    0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x50,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
+    0x03,0x03,0x00,0x00,0x50,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,
+    0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,
+    0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0c,0x00,0x00,
+    0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,
+    0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,
+    0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab,0x53,0x48,0x44,0x52,0x28,0x01,0x00,0x00,
+    0x40,0x00,0x01,0x00,0x4a,0x00,0x00,0x00,0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,
+    0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x72,0x10,0x10,0x00,
+    0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,
+    0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,
+    0x32,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,
+    0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x36,0x00,0x00,0x05,
+    0x32,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x01,0x00,0x00,0x00,
+    0x36,0x00,0x00,0x05,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,
+    0x02,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,
+    0x56,0x15,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,
+    0x06,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,
+    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00,0x00,0x00,0x00,0x00,
+    0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,
+    0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,
+    0x03,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,
+    0x07,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,
+    0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,
+};
+
+static const uint8_t fs_bytecode[620] = {
+    0x44,0x58,0x42,0x43,0xd1,0x93,0x1f,0x1b,0x9d,0x70,0x90,0xeb,0xc2,0x7c,0x26,0x07,
+    0xdf,0x52,0xda,0x49,0x01,0x00,0x00,0x00,0x6c,0x02,0x00,0x00,0x05,0x00,0x00,0x00,
+    0x34,0x00,0x00,0x00,0xd4,0x00,0x00,0x00,0x20,0x01,0x00,0x00,0x54,0x01,0x00,0x00,
+    0xf0,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff,
+    0x10,0x81,0x00,0x00,0x6d,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x69,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
+    0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x5f,0x74,0x65,0x78,0x5f,0x73,0x61,0x6d,
+    0x70,0x6c,0x65,0x72,0x00,0x74,0x65,0x78,0x00,0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,
+    0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68,0x61,0x64,
+    0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30,0x2e,0x31,
+    0x00,0xab,0xab,0xab,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
+    0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x38,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
+    0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab,
+    0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,
+    0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,
+    0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0x94,0x00,0x00,0x00,0x40,0x00,0x00,0x00,
+    0x25,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00,
+    0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00,0x55,0x55,0x00,0x00,
+    0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x62,0x10,0x00,0x03,
+    0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,
+    0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x45,0x00,0x00,0x09,
+    0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00,
+    0x46,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00,
+    0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,
+    0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,
+    0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+};
+
+static const sg_shader_desc* spritebatch_shader_desc(sg_backend backend) {
+    if (backend == SG_BACKEND_D3D11) {
+        static sg_shader_desc desc;
+        static bool valid;
+        if (!valid) {
+            valid = true;
+            desc.attrs[0].sem_name = "TEXCOORD";
+            desc.attrs[0].sem_index = 0;
+            desc.attrs[1].sem_name = "TEXCOORD";
+            desc.attrs[1].sem_index = 1;
+            desc.attrs[2].sem_name = "TEXCOORD";
+            desc.attrs[2].sem_index = 2;
+            desc.vs.bytecode.ptr = vs_bytecode;
+            desc.vs.bytecode.size = 884;
+            desc.vs.entry = "main";
+            desc.vs.uniform_blocks[0].size = 64;
+            desc.fs.bytecode.ptr = fs_bytecode;
+            desc.fs.bytecode.size = 620;
+            desc.fs.entry = "main";
+            desc.fs.images[0].name = "tex";
+            desc.fs.images[0].image_type = SG_IMAGETYPE_2D;
+            desc.fs.images[0].sampler_type = SG_SAMPLERTYPE_FLOAT;
+            desc.label = "spritebatch_shader";
+        };
+        return &desc;
+    }
+    return 0;
+}
+
+#endif
+
+static void _sbatch_strcpy(_sbatch_str* dst, const char* src) {
+    SOKOL_ASSERT(dst);
+    if (src) {
+#if defined(_MSC_VER)
+        strncpy_s(dst->buf, _SBATCH_STRBUF_LEN, src, (_SBATCH_STRBUF_LEN - 1));
+#else
+        strncpy(dst->buf, src, SG_IMGUI_STRBUF_LEN);
+#endif
+        dst->buf[_SBATCH_STRBUF_LEN - 1] = 0;
+    }
+    else {
+        memset(dst->buf, 0, _SBATCH_STRBUF_LEN);
+    }
+}
+
+static _sbatch_str _sbatch_make_str(const char* str) {
+    _sbatch_str res;
+    _sbatch_strcpy(&res, str);
+    return res;
+}
+
+static void _sbatch_init_pool(_sbatch_pool* pool, int num) {
+    SOKOL_ASSERT(pool && (num >= 1));
+    /* slot 0 is reserved for the 'invalid id', so bump the pool size by 1 */
+    pool->size = num + 1;
+    pool->queue_top = 0;
+    /* generation counters indexable by pool slot index, slot 0 is reserved */
+    const size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size;
+    pool->gen_ctrs = (uint32_t*)SOKOL_MALLOC(gen_ctrs_size);
+    SOKOL_ASSERT(pool->gen_ctrs);
+    memset(pool->gen_ctrs, 0, gen_ctrs_size);
+    /* it's not a bug to only reserve 'num' here */
+    pool->free_queue = (int*)SOKOL_MALLOC(sizeof(int) * (size_t)num);
+    SOKOL_ASSERT(pool->free_queue);
+    /* never allocate the zero-th pool item since the invalid id is 0 */
+    for (int i = pool->size - 1; i >= 1; i--) {
+        pool->free_queue[pool->queue_top++] = i;
+    }
+}
+
+static void _sbatch_discard_pool(_sbatch_pool* pool) {
+    SOKOL_ASSERT(pool);
+    SOKOL_ASSERT(pool->free_queue);
+    SOKOL_FREE(pool->free_queue);
+    pool->free_queue = 0;
+    SOKOL_ASSERT(pool->gen_ctrs);
+    SOKOL_FREE(pool->gen_ctrs);
+    pool->gen_ctrs = 0;
+    pool->size = 0;
+    pool->queue_top = 0;
+}
+
+static int _sbatch_pool_alloc_index(_sbatch_pool* pool) {
+    SOKOL_ASSERT(pool);
+    SOKOL_ASSERT(pool->free_queue);
+    if (pool->queue_top > 0) {
+        const int slot_index = pool->free_queue[--pool->queue_top];
+        SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size));
+        return slot_index;
+    }
+    /* pool exhausted */
+    return _SBATCH_INVALID_SLOT_INDEX;
+}
+
+static void _sbatch_pool_free_index(_sbatch_pool* pool, int slot_index) {
+    SOKOL_ASSERT((slot_index > _SBATCH_INVALID_SLOT_INDEX) && (slot_index < pool->size));
+    SOKOL_ASSERT(pool);
+    SOKOL_ASSERT(pool->free_queue);
+    SOKOL_ASSERT(pool->queue_top < pool->size);
+#ifdef SOKOL_DEBUG
+    /* debug check against double-free */
+    for (int i = 0; i < pool->queue_top; i++) {
+        SOKOL_ASSERT(pool->free_queue[i] != slot_index);
+    }
+#endif
+    pool->free_queue[pool->queue_top++] = slot_index;
+    SOKOL_ASSERT(pool->queue_top <= (pool->size - 1));
+}
+
+/* allocate the slot at slot_index:
+    - bump the slot's generation counter
+    - create a resource id from the generation counter and slot index
+    - set the slot's id to this id
+    - set the slot's state to ALLOC
+    - return the resource id
+*/
+static uint32_t _sbatch_slot_alloc(_sbatch_pool* pool, _sbatch_slot* slot, int slot_index) {
+    /* FIXME: add handling for an overflowing generation counter,
+       for now, just overflow (another option is to disable
+       the slot)
+    */
+    SOKOL_ASSERT(pool && pool->gen_ctrs);
+    SOKOL_ASSERT((slot_index > _SBATCH_INVALID_SLOT_INDEX) && (slot_index < pool->size));
+    SOKOL_ASSERT((slot->state == SG_RESOURCESTATE_INITIAL) && (slot->id == SG_INVALID_ID));
+    const uint32_t ctr = ++pool->gen_ctrs[slot_index];
+    slot->id = (ctr << _SBATCH_SLOT_SHIFT) | (slot_index & _SBATCH_SLOT_MASK);
+    slot->state = SG_RESOURCESTATE_ALLOC;
+    return slot->id;
+}
+
+/* extract slot index from id */
+static int _sbatch_slot_index(uint32_t id) {
+    const int slot_index = (int)(id & _SBATCH_SLOT_MASK);
+    SOKOL_ASSERT(_SBATCH_INVALID_SLOT_INDEX != slot_index);
+    return slot_index;
+}
+
+static void _sbatch_setup_context_pool(const sbatch_desc* desc) {
+    SOKOL_ASSERT(desc);
+    /* note: the pool will have an additional item, since slot 0 is reserved */
+    SOKOL_ASSERT((desc->context_pool_size > 0) && (desc->context_pool_size < _SBATCH_MAX_POOL_SIZE));
+    _sbatch_init_pool(&_sbatch.context_pool.pool, desc->context_pool_size);
+    const size_t pool_byte_size = sizeof(_sbatch_context) * (size_t)_sbatch.context_pool.pool.size;
+    _sbatch.context_pool.contexts = (_sbatch_context*)SOKOL_MALLOC(pool_byte_size);
+    SOKOL_ASSERT(_sbatch.context_pool.contexts);
+    memset(_sbatch.context_pool.contexts, 0, pool_byte_size);
+}
+
+static void _sbatch_discard_context_pool(void) {
+    SOKOL_ASSERT(_sbatch.context_pool.contexts);
+    SOKOL_FREE(_sbatch.context_pool.contexts);
+    _sbatch.context_pool.contexts = 0;
+    _sbatch_discard_pool(&_sbatch.context_pool.pool);
+}
+
+/* get context pointer without id-check */
+static _sbatch_context* _sbatch_context_at(uint32_t ctx_id) {
+    SOKOL_ASSERT(SG_INVALID_ID != ctx_id);
+    const int slot_index = _sbatch_slot_index(ctx_id);
+    SOKOL_ASSERT((slot_index > _SBATCH_INVALID_SLOT_INDEX) && (slot_index < _sbatch.context_pool.pool.size));
+    return &_sbatch.context_pool.contexts[slot_index];
+}
+
+/* get context pointer with id-check, returns 0 if no match */
+static _sbatch_context* _sbatch_lookup_context(uint32_t ctx_id) {
+    if (SG_INVALID_ID != ctx_id) {
+        _sbatch_context* ctx = _sbatch_context_at(ctx_id);
+        if (ctx->slot.id == ctx_id) {
+            return ctx;
+        }
+    }
+    return NULL;
+}
+
+/* make context handle from raw uint32_t id */
+static sbatch_context _sbatch_make_ctx_id(uint32_t ctx_id) {
+    sbatch_context ctx;
+    ctx.id = ctx_id;
+    return ctx;
+}
+
+static sbatch_context _sbatch_alloc_context(void) {
+    sbatch_context ctx_id;
+    const int slot_index = _sbatch_pool_alloc_index(&_sbatch.context_pool.pool);
+    if (_SBATCH_INVALID_SLOT_INDEX != slot_index) {
+        ctx_id = _sbatch_make_ctx_id(_sbatch_slot_alloc(&_sbatch.context_pool.pool, &_sbatch.context_pool.contexts[slot_index].slot, slot_index));
+    }
+    else {
+        /* pool is exhausted */
+        ctx_id = _sbatch_make_ctx_id(SG_INVALID_ID);
+    }
+    return ctx_id;
+}
+
+///////////////////////////////
+
+static void _sbatch_setup_pipeline_pool(const sbatch_desc* desc) {
+    SOKOL_ASSERT(desc);
+    /* note: the pool will have an additional item, since slot 0 is reserved */
+    SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SBATCH_MAX_POOL_SIZE));
+    _sbatch_init_pool(&_sbatch.pipeline_pool.pool, desc->pipeline_pool_size);
+    const size_t pool_byte_size = sizeof(_sbatch_pipeline) * (size_t)_sbatch.pipeline_pool.pool.size;
+    _sbatch.pipeline_pool.pipelines = (_sbatch_pipeline*)SOKOL_MALLOC(pool_byte_size);
+    SOKOL_ASSERT(_sbatch.pipeline_pool.pipelines);
+    memset(_sbatch.pipeline_pool.pipelines, 0, pool_byte_size);
+}
+
+static void _sbatch_discard_pipeline_pool(void) {
+    SOKOL_ASSERT(_sbatch.pipeline_pool.pipelines);
+    SOKOL_FREE(_sbatch.pipeline_pool.pipelines);
+    _sbatch.pipeline_pool.pipelines = 0;
+    _sbatch_discard_pool(&_sbatch.pipeline_pool.pool);
+}
+
+/* get pipeline pointer without id-check */
+static _sbatch_pipeline* _sbatch_pipeline_at(uint32_t pip_id) {
+    SOKOL_ASSERT(SG_INVALID_ID != pip_id);
+    const int slot_index = _sbatch_slot_index(pip_id);
+    SOKOL_ASSERT((slot_index > _SBATCH_INVALID_SLOT_INDEX) && (slot_index < _sbatch.pipeline_pool.pool.size));
+    return &_sbatch.pipeline_pool.pipelines[slot_index];
+}
+
+/* get pipeline pointer with id-check, returns 0 if no match */
+static _sbatch_pipeline* _sbatch_lookup_pipeline(uint32_t pip_id) {
+    if (SG_INVALID_ID != pip_id) {
+        _sbatch_pipeline* pip = _sbatch_pipeline_at(pip_id);
+        if (pip->slot.id == pip_id) {
+            return pip;
+        }
+    }
+    return NULL;
+}
+
+/* make pipeline handle from raw uint32_t id */
+static sbatch_pipeline _sbatch_make_pip_id(uint32_t pip_id) {
+    sbatch_pipeline pip;
+    pip.id = pip_id;
+    return pip;
+}
+
+static sbatch_pipeline _sbatch_alloc_pipeline(void) {
+    sbatch_pipeline pip_id;
+    const int slot_index = _sbatch_pool_alloc_index(&_sbatch.pipeline_pool.pool);
+    if (_SBATCH_INVALID_SLOT_INDEX != slot_index) {
+        pip_id = _sbatch_make_pip_id(_sbatch_slot_alloc(&_sbatch.pipeline_pool.pool, &_sbatch.pipeline_pool.pipelines[slot_index].slot, slot_index));
+    }
+    else {
+        /* pool is exhausted */
+        pip_id = _sbatch_make_pip_id(SG_INVALID_ID);
+    }
+    return pip_id;
+}
+
+static sbatch_context_desc _sbatch_context_desc_defaults(const sbatch_context_desc* desc) {
+    sbatch_context_desc res = *desc;
+    res.max_sprites = _SBATCH_DEFAULT(res.max_sprites, 4096);
+    res.canvas_height = _SBATCH_DEFAULT(res.canvas_height, 480);
+    res.canvas_width = _SBATCH_DEFAULT(res.canvas_width, 640);
+    return res;
+}
+
+static void _sbatch_init_context(sbatch_context ctx_id, const sbatch_context_desc* in_desc) {
+    sg_push_debug_group("sokol-spritebatch");
+
+    SOKOL_ASSERT((ctx_id.id != SG_INVALID_ID) && in_desc);
+
+    _sbatch_context* ctx = _sbatch_lookup_context(ctx_id.id);
+    SOKOL_ASSERT(ctx);
+
+    ctx->update_frame_index = -1;
+
+    ctx->desc = _sbatch_context_desc_defaults(in_desc);
+
+    ctx->label = _sbatch_make_str(ctx->desc.label);
+
+    ctx->desc.label = NULL;
+
+    const int max_vertices = 4 * ctx->desc.max_sprites;
+    const size_t vbuf_size = (size_t)max_vertices * sizeof(_sbatch_vertex);
+
+    ctx->vertices = (_sbatch_vertex*)SOKOL_MALLOC(vbuf_size);
+    SOKOL_ASSERT(ctx->vertices);
+
+    ctx->images = (sg_image*)SOKOL_MALLOC(ctx->desc.max_sprites * sizeof(sg_image));
+    SOKOL_ASSERT(ctx->images);
+
+    ctx->pipeline.id = _SBATCH_DEFAULT(in_desc->pipeline.id, _sbatch.pipeline.id);
+
+    ctx->sprite_count = 0;
+
+    sg_buffer_desc vbuf_desc;
+    memset(&vbuf_desc, 0, sizeof(vbuf_desc));
+    vbuf_desc.size = vbuf_size;
+    vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER;
+    vbuf_desc.usage = SG_USAGE_STREAM;
+    vbuf_desc.label = "sokol-spritebatch-vertices";
+    ctx->vertex_buffer = sg_make_buffer(&vbuf_desc);
+    SOKOL_ASSERT(SG_INVALID_ID != ctx->vertex_buffer.id);
+
+    sg_pop_debug_group();
+}
+
+static void _sbatch_destroy_context(sbatch_context ctx_id) {
+    _sbatch_context* ctx = _sbatch_lookup_context(ctx_id.id);
+    if (ctx) {
+        if (ctx->vertices) {
+            SOKOL_FREE(ctx->vertices);
+        }
+        if (ctx->images) {
+            SOKOL_FREE(ctx->images);
+        }
+        sg_push_debug_group("sokol-spritebatch");
+        sg_destroy_buffer(ctx->vertex_buffer);
+        sg_pop_debug_group();
+        memset(ctx, 0, sizeof(*ctx));
+        _sbatch_pool_free_index(&_sbatch.context_pool.pool, _sbatch_slot_index(ctx_id.id));
+    }
+}
+
+
+static sg_pipeline_desc _sbatch_pipeline_desc_defaults(const sg_pipeline_desc* desc) {
+    sg_pipeline_desc pipeline_desc = *desc;
+
+    pipeline_desc.color_count = _SBATCH_DEFAULT(pipeline_desc.color_count, 1);
+    if (pipeline_desc.color_count == 1) {
+        pipeline_desc.colors[0].blend.enabled = _SBATCH_DEFAULT(desc->colors[0].blend.enabled, true);
+        pipeline_desc.colors[0].blend.src_factor_rgb = _SBATCH_DEFAULT(desc->colors[0].blend.src_factor_rgb, SG_BLENDFACTOR_ONE);
+        pipeline_desc.colors[0].blend.src_factor_alpha = _SBATCH_DEFAULT(desc->colors[0].blend.src_factor_rgb, SG_BLENDFACTOR_ONE);
+        pipeline_desc.colors[0].blend.dst_factor_rgb = _SBATCH_DEFAULT(desc->colors[0].blend.src_factor_rgb, SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA);
+        pipeline_desc.colors[0].blend.dst_factor_alpha = _SBATCH_DEFAULT(desc->colors[0].blend.src_factor_rgb, SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA);
+    }
+
+    pipeline_desc.shader.id = _SBATCH_DEFAULT(desc->shader.id, _sbatch.shader.id);
+    pipeline_desc.index_type = SG_INDEXTYPE_UINT16;
+    pipeline_desc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT3;
+    pipeline_desc.layout.attrs[1].format = SG_VERTEXFORMAT_FLOAT2;
+    pipeline_desc.layout.attrs[2].format = SG_VERTEXFORMAT_UBYTE4N;
+    pipeline_desc.label = _SBATCH_DEFAULT(desc->label, "sokol-spritebatch-pipline");
+    return pipeline_desc;
+}
+
+static void _sbatch_init_pipeline(sbatch_pipeline pip_id, const sg_pipeline_desc* in_desc) {
+    sg_push_debug_group("sokol-spritebatch");
+
+    SOKOL_ASSERT((pip_id.id != SG_INVALID_ID) && in_desc);
+
+    _sbatch_pipeline* pip = _sbatch_lookup_pipeline(pip_id.id);
+    SOKOL_ASSERT(pip);
+
+    pip->desc = _sbatch_pipeline_desc_defaults(in_desc);
+
+    pip->label = _sbatch_make_str(pip->desc.label);
+
+    pip->desc.label = NULL;
+
+    pip->pipeline = sg_make_pipeline(&pip->desc);
+    SOKOL_ASSERT(pip->pipeline.id != SG_INVALID_ID);
+
+    sg_pop_debug_group();
+}
+
+static void _sbatch_destroy_pipeline(sbatch_pipeline pip_id) {
+    _sbatch_pipeline* pip = _sbatch_lookup_pipeline(pip_id.id);
+    if (pip) {
+        sg_push_debug_group("sokol-spritebatch");
+        sg_destroy_pipeline(pip->pipeline);
+        sg_pop_debug_group();
+        memset(pip, 0, sizeof(*pip));
+        _sbatch_pool_free_index(&_sbatch.pipeline_pool.pool, _sbatch_slot_index(pip_id.id));
+    }
+}
+
+static uint32_t _sbatch_pack_color_bytes(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+    return (uint32_t)a << 24 | (uint32_t)b << 16 | (uint32_t)g << 8 | (uint32_t)r;
+}
+
+static float _sbatch_clampf(float v, float low, float high) {
+    if (v < low) {
+        return low;
+    }
+    if (v > high) {
+        return high;
+    }
+    return v;
+}
+
+static uint32_t _sbatch_pack_color(const sg_color* color) {
+    const uint8_t r = (uint8_t)(_sbatch_clampf(color->r, 0.0f, 1.0f) * 255.0f);
+    const uint8_t g = (uint8_t)(_sbatch_clampf(color->g, 0.0f, 1.0f) * 255.0f);
+    const uint8_t b = (uint8_t)(_sbatch_clampf(color->b, 0.0f, 1.0f) * 255.0f);
+    const uint8_t a = (uint8_t)(_sbatch_clampf(color->a, 0.0f, 1.0f) * 255.0f);
+    return _sbatch_pack_color_bytes(r, g, b, a);
+}
+
+static int _sg_image_slot_index(uint32_t id) {
+    int slot_index = (int)(id & _SBATCH_IMAGE_SLOT_MASK);
+    SOKOL_ASSERT(0 != slot_index);
+    return slot_index;
+}
+
+static void _sbatch_setup_sprite_pool(void) {
+    sg_desc sg_desc = sg_query_desc();
+    const int num_slots = sg_desc.image_pool_size;
+    _sbatch.sprite_pool.size = (size_t)num_slots * sizeof(_sbatch_sprite_data);
+    _sbatch.sprite_pool.data = (_sbatch_sprite_data*)SOKOL_MALLOC(_sbatch.sprite_pool.size);
+    SOKOL_ASSERT(_sbatch.sprite_pool.data);
+    memset(_sbatch.sprite_pool.data, 0, _sbatch.sprite_pool.size);
+}
+
+static void _sbatch_init_quad_rotated(
+    _sbatch_vertex* vertices, float x, float y, float dx, float dy,
+    float w, float h, float sin, float cos, uint32_t rgba,
+    sbatch_float2 top_left, sbatch_float2 bottom_right, float depth) {
+
+    _sbatch_vertex* top_left_vertex = vertices;
+    top_left_vertex->x = x + dx * cos - dy * sin;
+    top_left_vertex->y = y + dx * sin + dy * cos;
+    top_left_vertex->z = depth;
+    top_left_vertex->rgba = rgba;
+    top_left_vertex->u = top_left.x;
+    top_left_vertex->v = top_left.y;
+
+    _sbatch_vertex* top_right_vertex = vertices + 1;
+    top_right_vertex->x = x + (dx + w) * cos - dy * sin;
+    top_right_vertex->y = y + (dx + w) * sin + dy * cos;
+    top_right_vertex->z = depth;
+    top_right_vertex->rgba = rgba;
+    top_right_vertex->u = bottom_right.x;
+    top_right_vertex->v = top_left.y;
+
+    _sbatch_vertex* bottom_left_vertex = vertices + 2;
+    bottom_left_vertex->x = x + dx * cos - (dy + h) * sin;
+    bottom_left_vertex->y = y + dx * sin + (dy + h) * cos;
+    bottom_left_vertex->z = depth;
+    bottom_left_vertex->rgba = rgba;
+    bottom_left_vertex->u = top_left.x;
+    bottom_left_vertex->v = bottom_right.y;
+
+    _sbatch_vertex* bottom_right_vertex = vertices + 3;
+    bottom_right_vertex->x = x + (dx + w) * cos - (dy + h) * sin;
+    bottom_right_vertex->y = y + (dx + w) * sin + (dy + h) * cos;
+    bottom_right_vertex->z = depth;
+    bottom_right_vertex->rgba = rgba;
+    bottom_right_vertex->u = bottom_right.x;
+    bottom_right_vertex->v = bottom_right.y;
+}
+
+static void _sbatch_init_quad(
+    _sbatch_vertex* vertices, float x, float y, float w, float h, uint32_t rgba,
+    sbatch_float2 top_left, sbatch_float2 bottom_right, float depth) {
+
+    _sbatch_vertex* top_left_vertex = vertices;
+    top_left_vertex->x = x;
+    top_left_vertex->y = y;
+    top_left_vertex->z = depth;
+    top_left_vertex->rgba = rgba;
+    top_left_vertex->u = top_left.x;
+    top_left_vertex->v = top_left.y;
+
+    _sbatch_vertex* top_right_vertex = vertices + 1;
+    top_right_vertex->x = x + w;
+    top_right_vertex->y = y;
+    top_right_vertex->z = depth;
+    top_right_vertex->rgba = rgba;
+    top_right_vertex->u = bottom_right.x;
+    top_right_vertex->v = top_left.y;
+
+    _sbatch_vertex* bottom_left_vertex = vertices + 2;
+    bottom_left_vertex->x = x;
+    bottom_left_vertex->y = y + h;
+    bottom_left_vertex->z = depth;
+    bottom_left_vertex->rgba = rgba;
+    bottom_left_vertex->u = top_left.x;
+    bottom_left_vertex->v = bottom_right.y;
+
+    _sbatch_vertex* bottom_right_vertex = vertices + 3;
+    bottom_right_vertex->x = x + w;
+    bottom_right_vertex->y = y + h;
+    bottom_right_vertex->z = depth;
+    bottom_right_vertex->rgba = rgba;
+    bottom_right_vertex->u = bottom_right.x;
+    bottom_right_vertex->v = bottom_right.y;
+}
+
+static void _sbatch_init_index_buffer(void) {
+    uint16_t* index_buffer = (uint16_t*)SOKOL_MALLOC(_SBATCH_MAX_INDICES * sizeof(uint16_t));
+    SOKOL_ASSERT(index_buffer);
+
+    uint16_t* index_ptr = index_buffer;
+    for (uint32_t i = 0; i < _SBATCH_MAX_QUADS; i++, index_ptr += 6) {
+        // Triangle 1
+        *(index_ptr + 0) = (uint16_t)(i * 4);
+        *(index_ptr + 1) = (uint16_t)(i * 4 + 1);
+        *(index_ptr + 2) = (uint16_t)(i * 4 + 2);
+        // Triangle 2
+        *(index_ptr + 3) = (uint16_t)(i * 4 + 1);
+        *(index_ptr + 4) = (uint16_t)(i * 4 + 3);
+        *(index_ptr + 5) = (uint16_t)(i * 4 + 2);
+    }
+
+    sg_buffer_desc index_buffer_desc;
+    memset(&index_buffer_desc, 0, sizeof(index_buffer_desc));
+    index_buffer_desc.size = _SBATCH_MAX_INDICES * sizeof(uint16_t);
+    index_buffer_desc.type = SG_BUFFERTYPE_INDEXBUFFER;
+    index_buffer_desc.usage = SG_USAGE_IMMUTABLE;
+    index_buffer_desc.label = "sokol-spritebatch-indices";
+    index_buffer_desc.data.size = _SBATCH_MAX_INDICES * sizeof(uint16_t);
+    index_buffer_desc.data.ptr = index_buffer;
+    _sbatch.index_buffer = sg_make_buffer(&index_buffer_desc);
+    SOKOL_ASSERT(SG_INVALID_ID != _sbatch.index_buffer.id);
+
+    SOKOL_FREE(index_buffer);
+
+    _sbatch.bindings.index_buffer = _sbatch.index_buffer;
+}
+
+static bool _sbatch_rect_is_valid(const sbatch_rect* rect) {
+    if (rect->width != 0.0f && rect->height != 0.0f) {
+        return true;
+    }
+    return false;
+}
+
+SOKOL_SPRITEBATCH_API_DECL int sbatch_frame() {
+    return ++_sbatch.frame_index;
+}
+
+SOKOL_API_IMPL void sbatch_setup(const sbatch_desc* desc) {
+
+    memset(&_sbatch, 0, sizeof _sbatch);
+
+    sbatch_desc batch_desc = *desc;
+    batch_desc.context_pool_size = _SBATCH_DEFAULT(batch_desc.context_pool_size, 32);
+    batch_desc.pipeline_pool_size = _SBATCH_DEFAULT(batch_desc.pipeline_pool_size, 32);
+    _sbatch_setup_context_pool(&batch_desc);
+    _sbatch_setup_pipeline_pool(&batch_desc);
+    _sbatch_setup_sprite_pool();
+
+    _sbatch.shader = sg_make_shader(spritebatch_shader_desc(sg_query_backend()));
+
+    sg_pipeline_desc pipeline_desc;
+    memset(&pipeline_desc, 0, sizeof(sg_pipeline_desc));
+    pipeline_desc.shader = _sbatch.shader;
+    pipeline_desc.label = "spritebatch-default-pipeline";
+    _sbatch.pipeline = sbatch_make_pipeline(&pipeline_desc);
+
+    _sbatch_init_index_buffer();
+}
+
+SOKOL_API_IMPL void sbatch_shutdown(void) {
+    sg_destroy_buffer(_sbatch.index_buffer);
+    sbatch_destroy_pipeline(_sbatch.pipeline);
+    sg_destroy_shader(_sbatch.shader);
+    _sbatch_discard_context_pool();
+    SOKOL_FREE(_sbatch.sprite_pool.data);
+}
+
+SOKOL_SPRITEBATCH_API_DECL sbatch_pipeline sbatch_make_pipeline(const sg_pipeline_desc* desc) {
+    SOKOL_ASSERT(desc);
+    const sbatch_pipeline result = _sbatch_alloc_pipeline();
+    _sbatch_init_pipeline(result, desc);
+    return result;
+}
+
+SOKOL_SPRITEBATCH_API_DECL void sbatch_destroy_pipeline(sbatch_pipeline pipeline) {
+    SOKOL_ASSERT(pipeline.id != SG_INVALID_ID);
+    _sbatch_destroy_pipeline(pipeline);
+}
+
+SOKOL_API_IMPL sbatch_context sbatch_make_context(const sbatch_context_desc* desc) {
+    SOKOL_ASSERT(desc);
+    const sbatch_context result = _sbatch_alloc_context();
+    _sbatch_init_context(result, desc);
+    return result;
+}
+
+SOKOL_API_IMPL void sbatch_destroy_context(sbatch_context context) {
+    SOKOL_ASSERT(context.id != SG_INVALID_ID);
+    _sbatch_destroy_context(context);
+}
+
+SOKOL_API_IMPL void sbatch_begin(sbatch_context context) {
+    SOKOL_ASSERT(context.id != SG_INVALID_ID);
+    _sbatch.ctx_id = context;
+    SOKOL_ASSERT(!_sbatch.begin_called);
+    _sbatch.begin_called = true;
+
+    _sbatch_context* ctx = _sbatch_context_at(_sbatch.ctx_id.id);
+
+    // a sbatch_context object can only be used in one sbatch_begin call per frame.
+    SOKOL_ASSERT(_sbatch.frame_index != ctx->update_frame_index);
+    ctx->update_frame_index = _sbatch.frame_index;
+
+    SOKOL_ASSERT(ctx);
+    _sbatch.bindings.vertex_buffers[0] = ctx->vertex_buffer;
+}
+
+SOKOL_API_IMPL void sbatch_push_sprite(const sbatch_sprite* sprite) {
+    SOKOL_ASSERT(sprite);
+    SOKOL_ASSERT(sprite->image.id != SG_INVALID_ID);
+
+    _sbatch_context* ctx = _sbatch_context_at(_sbatch.ctx_id.id);
+    SOKOL_ASSERT(ctx);
+
+    if (ctx->sprite_count < ctx->desc.max_sprites) {
+
+        const int sprite_index = ctx->sprite_count++;
+
+        _sbatch_sprite_data* cached_sprite_data = &_sbatch.sprite_pool.data[_sg_image_slot_index(sprite->image.id)];
+
+        if (cached_sprite_data->image.id != sprite->image.id) {
+            const sg_image_info info = sg_query_image_info(sprite->image);
+            cached_sprite_data->height = info.height;
+            cached_sprite_data->width = info.width;
+            cached_sprite_data->texel_height = 1.0f / (float)info.height;
+            cached_sprite_data->texel_width = 1.0f / (float)info.width;
+            cached_sprite_data->image = sprite->image;
+        }
+
+        const sbatch_float2 scale = { _SBATCH_DEFAULT(sprite->scale.x, 1.0f),  _SBATCH_DEFAULT(sprite->scale.y, 1.0f) };
+        const sbatch_float2 scaled_origin = { scale.x * sprite->origin.x, scale.y * sprite->origin.y };
+        float width, height;
+        sbatch_float2 tex_coord_top_left;
+        sbatch_float2 tex_coord_bottom_right;
+
+        if (_sbatch_rect_is_valid(&sprite->source)) {
+            width = sprite->source.width * scale.x;
+            height = sprite->source.height * scale.y;
+            tex_coord_top_left.x = sprite->source.x * cached_sprite_data->texel_width;
+            tex_coord_top_left.y = sprite->source.y * cached_sprite_data->texel_height;
+            tex_coord_bottom_right.x = (sprite->source.x + sprite->source.width) * cached_sprite_data->texel_width;
+            tex_coord_bottom_right.y = (sprite->source.y + sprite->source.height) * cached_sprite_data->texel_height;
+        } else {
+            width = (float)cached_sprite_data->width * scale.x;
+            height = (float)cached_sprite_data->height * scale.y;
+            tex_coord_top_left.x = 0.0f;
+            tex_coord_top_left.y = 0.0f;
+            tex_coord_bottom_right.x = 1.0f;
+            tex_coord_bottom_right.y = 1.0f;
+        }
+
+        if ((sprite->flags & SBATCH_FLIP_Y) != SBATCH_FLIP_NONE) {
+            const float temp = tex_coord_bottom_right.y;
+            tex_coord_bottom_right.y = tex_coord_top_left.y;
+            tex_coord_top_left.y = temp;
+        }
+
+        if ((sprite->flags & SBATCH_FLIP_X) != SBATCH_FLIP_NONE) {
+            const float temp = tex_coord_bottom_right.x;
+            tex_coord_bottom_right.x = tex_coord_top_left.x;
+            tex_coord_top_left.x = temp;
+        }
+
+        uint32_t packed_color = sprite->color ? _sbatch_pack_color(sprite->color) : 0xFFFFFFFF;
+
+        ctx->images[sprite_index] = sprite->image;
+
+        const int base_vertex_index = sprite_index * 4;
+        _sbatch_vertex* vertices = ctx->vertices + base_vertex_index;
+
+        if (sprite->rotation == 0.0f) {
+            _sbatch_init_quad(vertices,
+                sprite->position.x - scaled_origin.x,
+                sprite->position.y - scaled_origin.y,
+                width,
+                height,
+                packed_color,
+                tex_coord_top_left,
+                tex_coord_bottom_right,
+                sprite->depth);
+        }
+        else {
+            _sbatch_init_quad_rotated(vertices,
+                sprite->position.x,
+                sprite->position.y,
+                -scaled_origin.x,
+                -scaled_origin.y,
+                width,
+                height,
+                sinf(sprite->rotation),
+                cosf(sprite->rotation),
+                packed_color,
+                tex_coord_top_left,
+                tex_coord_bottom_right,
+                sprite->depth);
+        }
+    }
+    else {
+        if (ctx->label.buf[0] != '\0') {
+            SOKOL_LOG("sokol_spritebatch.h: dropped sprites, increase max_sprites of sbatch_context:");
+            SOKOL_LOG(ctx->label.buf);
+        }
+        else {
+            SOKOL_LOG("sokol_spritebatch.h: dropped sprites, increase max_sprites");
+        }
+    }
+}
+
+SOKOL_API_IMPL void sbatch_push_sprite_rect(const sbatch_sprite_rect* sprite) {
+    SOKOL_ASSERT(sprite);
+    SOKOL_ASSERT(sprite->image.id != SG_INVALID_ID);
+
+    _sbatch_context* ctx = _sbatch_context_at(_sbatch.ctx_id.id);
+    SOKOL_ASSERT(ctx);
+
+    if (ctx->sprite_count < ctx->desc.max_sprites) {
+
+        const int sprite_index = ctx->sprite_count++;
+
+        _sbatch_sprite_data* cached_sprite_data = &_sbatch.sprite_pool.data[_sg_image_slot_index(sprite->image.id)];
+
+        if (cached_sprite_data->image.id != sprite->image.id) {
+            const sg_image_info info = sg_query_image_info(sprite->image);
+            cached_sprite_data->height = info.height;
+            cached_sprite_data->width = info.width;
+            cached_sprite_data->texel_height = 1.0f / (float)info.height;
+            cached_sprite_data->texel_width = 1.0f / (float)info.width;
+            cached_sprite_data->image = sprite->image;
+        }
+
+        sbatch_float2 scaled_origin;
+        sbatch_float2 tex_coord_top_left;
+        sbatch_float2 tex_coord_bottom_right;
+
+        if (_sbatch_rect_is_valid(&sprite->source)) {
+            tex_coord_top_left.x = sprite->source.x * cached_sprite_data->texel_width;
+            tex_coord_top_left.y = sprite->source.y * cached_sprite_data->texel_height;
+            tex_coord_bottom_right.x = (sprite->source.x + sprite->source.width) * cached_sprite_data->texel_width;
+            tex_coord_bottom_right.y = (sprite->source.y + sprite->source.height) * cached_sprite_data->texel_height;
+            scaled_origin.x = sprite->origin.x * sprite->destination.width / sprite->source.width;
+            scaled_origin.y = sprite->origin.y * sprite->destination.height / sprite->source.height;
+        } else {
+            tex_coord_top_left.x = 0.0f;
+            tex_coord_top_left.y = 0.0f;
+            tex_coord_bottom_right.x = 1.0f;
+            tex_coord_bottom_right.y = 1.0f;
+            scaled_origin.x = sprite->origin.x * sprite->destination.width * cached_sprite_data->texel_width;
+            scaled_origin.y = sprite->origin.y * sprite->destination.height * cached_sprite_data->texel_height;
+        }
+
+        if ((sprite->flags & SBATCH_FLIP_Y) != SBATCH_FLIP_NONE) {
+            const float temp = tex_coord_bottom_right.y;
+            tex_coord_bottom_right.y = tex_coord_top_left.y;
+            tex_coord_top_left.y = temp;
+        }
+
+        if ((sprite->flags & SBATCH_FLIP_X) != SBATCH_FLIP_NONE) {
+            const float temp = tex_coord_bottom_right.x;
+            tex_coord_bottom_right.x = tex_coord_top_left.x;
+            tex_coord_top_left.x = temp;
+        }
+
+        uint32_t packed_color = sprite->color ? _sbatch_pack_color(sprite->color) : 0xFFFFFFFF;
+
+        ctx->images[sprite_index] = sprite->image;
+
+        const int base_vertex_index = sprite_index * 4;
+        _sbatch_vertex* vertices = ctx->vertices + base_vertex_index;
+
+        if (sprite->rotation == 0.0f) {
+            _sbatch_init_quad(vertices,
+                sprite->destination.x - scaled_origin.x,
+                sprite->destination.y - scaled_origin.y,
+                sprite->destination.width,
+                sprite->destination.height,
+                packed_color,
+                tex_coord_top_left,
+                tex_coord_bottom_right,
+                sprite->depth);
+        } else {
+            _sbatch_init_quad_rotated(vertices,
+                sprite->destination.x,
+                sprite->destination.y,
+                -scaled_origin.x,
+                -scaled_origin.y,
+                sprite->destination.width,
+                sprite->destination.height,
+                sinf(sprite->rotation),
+                cosf(sprite->rotation),
+                packed_color,
+                tex_coord_top_left,
+                tex_coord_bottom_right,
+                sprite->depth);
+        }
+    }
+    else {
+        if (ctx->label.buf[0] != '\0') {
+            SOKOL_LOG("sokol_spritebatch.h: dropped sprites, increase max_sprites of sbatch_context:");
+            SOKOL_LOG(ctx->label.buf);
+        } else {
+            SOKOL_LOG("sokol_spritebatch.h: dropped sprites, increase max_sprites");
+        }
+    }
+}
+
+static void _sbatch_draw(int base_element, sg_image current_image, int num_elements) {
+    _sbatch.bindings.fs_images[0] = current_image;
+    sg_apply_bindings(&_sbatch.bindings);
+    sg_draw(base_element, num_elements, 1);
+}
+
+static _sbatch_mat4x4 _sbatch_orthographic_off_center(float left, float right, float bottom, float top, float z_near, float z_far) {
+    _sbatch_mat4x4 result;
+
+    result.m[0][0] = 2.0f / (right - left);
+    result.m[0][1] = 0.0f;
+    result.m[0][2] = 0.0f;
+    result.m[0][3] = 0.0f;
+
+    result.m[1][0] = 0.0f;
+    result.m[1][1] = 2.0f / (top - bottom);
+    result.m[1][2] = 0.0f;
+    result.m[1][3] = 0.0f;
+
+    result.m[2][0] = 0.0f;
+    result.m[2][1] = 0.0f;
+    result.m[2][2] = 1.0f / (z_near - z_far);
+    result.m[2][3] = 0.0f;
+
+    result.m[3][0] = (left + right) / (left - right);
+    result.m[3][1] = (bottom + top) / (bottom - top);
+    result.m[3][2] = z_near / (z_near - z_far);
+    result.m[3][3] = 1.0f;
+
+    return result;
+}
+
+void sbatch_end(void) {
+    SOKOL_ASSERT(_sbatch.begin_called);
+    _sbatch.begin_called = false;
+
+    _sbatch_context* ctx = _sbatch_context_at(_sbatch.ctx_id.id);
+    SOKOL_ASSERT(ctx);
+
+    if (ctx->sprite_count == 0) {
+        return;
+    }
+
+    const int max_vertices = 4 * ctx->sprite_count;
+    const size_t vbuf_size = (size_t)max_vertices * sizeof(_sbatch_vertex);
+    const sg_range range = { ctx->vertices, vbuf_size };
+    sg_update_buffer(ctx->vertex_buffer, &range);
+
+    int batch_size = 0;
+    int base_element = 0;
+    sg_image current_image = ctx->images[0];
+
+    const _sbatch_pipeline* pipeline = _sbatch_pipeline_at(ctx->pipeline.id);
+    sg_apply_pipeline(pipeline->pipeline);
+
+    _sbatch_mat4x4 matrix =
+        _sbatch_orthographic_off_center(0.0f, (float)ctx->desc.canvas_width, (float)ctx->desc.canvas_height, 0.0f, 0.0f, 1000.0f);
+
+    const sg_range matrix_range = { &matrix, sizeof matrix };
+    sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &matrix_range);
+
+    if(ctx->fs_uniform_state.data) {
+        sg_apply_uniforms(SG_SHADERSTAGE_FS, ctx->fs_uniform_state.ub_index, ctx->fs_uniform_state.data);
+    }
+
+    for (int i = 0; i < ctx->sprite_count; ++i, ++batch_size) {
+        if (ctx->images[i].id != current_image.id) {
+            const int num_elements = batch_size * 6;
+            _sbatch_draw(base_element, current_image, num_elements);
+            batch_size = 0;
+            base_element += num_elements;
+            current_image = ctx->images[i];
+        }
+    }
+
+    const int num_elements = batch_size * 6;
+    _sbatch_draw(base_element, current_image, num_elements);
+
+    ctx->sprite_count = 0;
+}
+
+SOKOL_API_IMPL void sbatch_apply_fs_uniforms(int ub_index, const sg_range* data) {
+    SOKOL_ASSERT(data);
+    SOKOL_ASSERT(data->ptr);
+    _sbatch_context* ctx = _sbatch_context_at(_sbatch.ctx_id.id);
+    ctx->fs_uniform_state.ub_index = ub_index;
+    ctx->fs_uniform_state.data = data;
+}
+
+SOKOL_API_IMPL void sbatch_premultiply_alpha_rgba8(uint8_t* pixels, int pixel_count) {
+    SOKOL_ASSERT(pixels);
+    for (int i = 0; i < pixel_count; ++i) {
+        pixels[0] = pixels[0] * pixels[3] / 255;
+        pixels[1] = pixels[1] * pixels[3] / 255;
+        pixels[2] = pixels[2] * pixels[3] / 255;
+        pixels += 4;
+    }
+}
+
+#endif /* SOKOL_SPRITEBATCH_IMPL */