diff options
Diffstat (limited to 'util/sokol_spritebatch.h')
| -rw-r--r-- | util/sokol_spritebatch.h | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/util/sokol_spritebatch.h b/util/sokol_spritebatch.h new file mode 100644 index 00000000..a213ea37 --- /dev/null +++ b/util/sokol_spritebatch.h @@ -0,0 +1,852 @@ +#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 + +typedef enum sb_sprite_flags { + SB_FLIP_NONE = 0, + SB_FLIP_X = 1 << 0, + SB_FLIP_Y = 1 << 1, + SB_FLIP_BOTH = SB_FLIP_Y | SB_FLIP_X, + SB_Z_TILT = 1<< 2 +} sb_sprite_flags; + +typedef enum sb_sort_mode { + SB_SORT_MODE_DEFERRED, + SB_SORT_MODE_TEXTURE, + SB_SORT_MODE_BACK_TO_FRONT, + SB_SORT_MODE_FRONT_TO_BACK +} sb_sort_mode; + +typedef struct sb_float2 { + float x; + float y; +} sb_float2; + +typedef struct sb_sprite_info { + sg_image image; + float width; + float height; + sb_float2 position; + float depth; + float rotation; + sb_float2 origin; + sb_float2 scale; + uint32_t flags; + sb_float2 source; + sg_color color; +} sb_sprite_info; + +typedef struct sb_desc { + int max_quads; +} sb_desc; + +typedef struct sb_matrix { + float m[4][4]; +} sb_matrix; + +typedef struct sb_viewport { + int x, y, width, height; + bool origin_top_left; +} sb_viewport; + +typedef struct sb_render_state { + sb_sort_mode sort_mode; + sg_pipeline pipeline; + sb_matrix transform_matrix; + sb_viewport viewport; +} sb_render_state; + +SOKOL_SPRITEBATCH_API_DECL void sb_setup(const sb_desc* desc); +SOKOL_SPRITEBATCH_API_DECL void sb_shutdown(void); + +SOKOL_SPRITEBATCH_API_DECL void sb_begin(const sb_render_state* render_state); +SOKOL_SPRITEBATCH_API_DECL void sb_sprite(const sb_sprite_info* sprite); +SOKOL_SPRITEBATCH_API_DECL void sb_end(void); + +SOKOL_SPRITEBATCH_API_DECL void sb_draw(void); + +SOKOL_SPRITEBATCH_API_DECL void sb_premultiply_alpha(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 + +#define _SB_IMAGE_SLOT_MASK (0xFFFF) +#define _SB_DEFAULT(val, def) (((val) == 0) ? (def) : (val)) +#define _SB_MAX_VERTICES (1 << 16) +#define _SB_MAX_QUADS (_SB_MAX_VERTICES / 4) +#define _SB_MAX_INDICES (_SB_MAX_QUADS * 6) +#define _SB_INITIAL_BATCH_CAPACITY 32 + +#ifndef SB_MAX_DEPTH +#define SB_MAX_DEPTH 1000.0f +#endif + +typedef struct { + sg_image image; + int width; + int height; +} _sb_sprite_data; + +typedef struct { + float x; + float y; + float z; + float u; + float v; + uint32_t rgba; +} _sb_vertex; + +typedef struct { + _sb_vertex top_left; + _sb_vertex top_right; + _sb_vertex bottom_left; + _sb_vertex bottom_right; + sg_image image; + uint64_t sort_key; +} _sb_quad; + +typedef struct { + _sb_sprite_data* data; + size_t size; +} _sb_sprite_pool; + +typedef struct { + sg_image image; + int base_element; + int num_elements; + sb_matrix matrix; +} _sb_batch; + +typedef struct { + size_t batch_size; + size_t batch_capacity; + _sb_batch* batches; +} _sb_batch_data; + +typedef struct { + _sb_sprite_pool sprite_pool; + _sb_quad* quads; + size_t quad_count; + _sb_vertex* vertex_buffer_data; + sg_buffer vertex_buffer; + sg_buffer index_buffer; + _sb_batch_data batch_data; + sg_bindings bindings; + bool begin_called; + sb_render_state render_state; + sg_shader default_shader; + sg_pipeline default_pipeline; + sb_matrix projection_matrix; +} _sb_t; + +static _sb_t _sb; + +#if defined(SOKOL_D3D11) +static const uint8_t vs_bytecode_hlsl4[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_hlsl4[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, +}; +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before spritebatch-sapp.glsl.h" +#endif + +static inline 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_hlsl4; + desc.vs.bytecode.size = 884; + desc.vs.entry = "main"; + desc.vs.uniform_blocks[0].size = 64; + desc.fs.bytecode.ptr = fs_bytecode_hlsl4; + 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 inline bool _sb_matrix_is_null(const sb_matrix* m) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (0.0f != m->m[y][x]) { + return false; + } + } + } + return true; +} + +static inline sb_matrix _sb_matrix_identity(void) { + sb_matrix m = { + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + return m; +} + +static inline sb_matrix _sb_orthographic_off_center(float left, float right, float bottom, float top, float near, float far) { + sb_matrix 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 / (near - 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] = near / (near - far); + result.m[3][3] = 1.0f; + + return result; +} + +static inline int _sg_image_slot_index(uint32_t id) { + int slot_index = (int)(id & _SB_IMAGE_SLOT_MASK); + SOKOL_ASSERT(0 != slot_index); + return slot_index; +} + +static inline void _sb_init_sprite_pool(void) { + sg_desc sg_desc = sg_query_desc(); + int num_slots = sg_desc.image_pool_size; + _sb.sprite_pool.size = (size_t)num_slots * sizeof(_sb_sprite_data); + _sb.sprite_pool.data = (_sb_sprite_data*)SOKOL_MALLOC(_sb.sprite_pool.size); + SOKOL_ASSERT(_sb.sprite_pool.data); + memset(_sb.sprite_pool.data, 0, _sb.sprite_pool.size); +} + +static inline void _sb_init_batch_data(void) { + _sb.batch_data.batch_capacity = _SB_INITIAL_BATCH_CAPACITY; + _sb.batch_data.batches = (_sb_batch*)SOKOL_MALLOC(_SB_INITIAL_BATCH_CAPACITY * sizeof(_sb_batch)); +} + +static inline void _sb_init_index_buffer(void) { + uint16_t* index_buffer = (uint16_t*)SOKOL_MALLOC(_SB_MAX_INDICES * sizeof(uint16_t)); + SOKOL_ASSERT(index_buffer); + + uint16_t* index_ptr = index_buffer; + for (uint32_t i = 0; i < _SB_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 = _SB_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 = _SB_MAX_INDICES * sizeof(uint16_t); + index_buffer_desc.data.ptr = index_buffer; + _sb.index_buffer = sg_make_buffer(&index_buffer_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sb.index_buffer.id); + + SOKOL_FREE(index_buffer); + + _sb.bindings.index_buffer = _sb.index_buffer; +} + +static inline void _sb_init_quad_rotated( + _sb_quad* quad, float x, float y, float dx, float dy, + float w, float h, float sin, float cos, uint32_t rgba, + sb_float2 top_left, sb_float2 bottom_right, float depth) { + + /* TODO: Z-tilt: what the hell do we do? */ + + quad->top_left.x = x + dx * cos - dy * sin; + quad->top_left.y = y + dx * sin + dy * cos; + quad->top_left.z = depth; + quad->top_left.rgba = rgba; + quad->top_left.u = top_left.x; + quad->top_left.v = top_left.y; + + quad->top_right.x = x + (dx + w) * cos - dy * sin; + quad->top_right.y = y + (dx + w) * sin + dy * cos; + quad->top_right.z = depth; + quad->top_right.rgba = rgba; + quad->top_right.u = bottom_right.x; + quad->top_right.v = top_left.y; + + quad->bottom_left.x = x + dx * cos - (dy + h) * sin; + quad->bottom_left.y = y + dx * sin + (dy + h) * cos; + quad->bottom_left.z = depth; + quad->bottom_left.rgba = rgba; + quad->bottom_left.u = top_left.x; + quad->bottom_left.v = bottom_right.y; + + quad->bottom_right.x = x + (dx + w) * cos - (dy + h) * sin; + quad->bottom_right.y = y + (dx + w) * sin + (dy + h) * cos; + quad->bottom_right.z = depth; + quad->bottom_right.rgba = rgba; + quad->bottom_right.u = bottom_right.x; + quad->bottom_right.v = bottom_right.y; +} + +static inline void _sb_init_quad( + _sb_quad* quad, uint32_t flags, float x, float y, float w, float h, uint32_t rgba, + sb_float2 top_left, sb_float2 bottom_right, float depth) { + + quad->top_left.x = x; + quad->top_left.y = y; + quad->top_left.z = depth; + quad->top_left.rgba = rgba; + quad->top_left.u = top_left.x; + quad->top_left.v = top_left.y; + + quad->top_right.x = x + w; + quad->top_right.y = y; + quad->top_right.z = depth; + quad->top_right.rgba = rgba; + quad->top_right.u = bottom_right.x; + quad->top_right.v = top_left.y; + + if ((flags & SB_Z_TILT) != SB_FLIP_NONE) { + // move the topmost vertices further out to enable z-tilting + const float angle = 0.785398f; // 45 degrees + const float depth = h * tanf(angle); + quad->top_left.z -= depth; + quad->top_right.z -= depth; + } + + quad->bottom_left.x = x; + quad->bottom_left.y = y + h; + quad->bottom_left.z = depth; + quad->bottom_left.rgba = rgba; + quad->bottom_left.u = top_left.x; + quad->bottom_left.v = bottom_right.y; + + quad->bottom_right.x = x + w; + quad->bottom_right.y = y + h; + quad->bottom_right.z = depth; + quad->bottom_right.rgba = rgba; + quad->bottom_right.u = bottom_right.x; + quad->bottom_right.v = bottom_right.y; +} + +static inline void _sb_init_vertex_buffer(void) { + sg_buffer_desc vertex_buffer_desc; + memset(&vertex_buffer_desc, 0, sizeof(vertex_buffer_desc)); + vertex_buffer_desc.size = _SB_MAX_VERTICES * sizeof(_sb_vertex); + vertex_buffer_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vertex_buffer_desc.usage = SG_USAGE_STREAM; + vertex_buffer_desc.label = "sokol-spritebatch-vertices"; + _sb.vertex_buffer = sg_make_buffer(&vertex_buffer_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sb.vertex_buffer.id); + + _sb.vertex_buffer_data = (_sb_vertex*)SOKOL_MALLOC(_SB_MAX_VERTICES * sizeof(_sb_vertex)); + SOKOL_ASSERT(_sb.vertex_buffer_data); + + _sb.bindings.vertex_buffers[0] = _sb.vertex_buffer; +} + +static inline uint32_t _sb_float_flip(uint32_t f) { + uint32_t mask = -(int32_t)(f >> 31) | 0x80000000; + return f ^ mask; +} + +static inline uint32_t _sb_depth_to_bits(float value) { + // https://aras-p.info/blog/2014/01/16/rough-sorting-by-depth/ + // Taking highest 10 bits for rough sort of positive floats. + // Sign is always zero, so only 9 bits in the result are used. + // 0.01 maps to 240; 0.1 to 247; 1.0 to 254; 10.0 to 260; + // 100.0 to 267; 1000.0 to 273 etc. + uint32_t i; + memcpy(&i, &value, sizeof(value)); + i = _sb_float_flip(i); + return i >> 22; // take highest 10 bits +} + +static inline uint64_t _sb_make_sort_key(const sb_sprite_info* sprite) { + switch (_sb.render_state.sort_mode) { + case SB_SORT_MODE_TEXTURE: { + return (uint64_t)sprite->image.id; + } + case SB_SORT_MODE_BACK_TO_FRONT: { + return (uint64_t)_sb_depth_to_bits(-sprite->depth) << 32 | sprite->image.id; + } + case SB_SORT_MODE_FRONT_TO_BACK: { + return (uint64_t)_sb_depth_to_bits(sprite->depth) << 32 | sprite->image.id; + } + } + return 0; +} + +static inline uint32_t _sb_pack_color_bytes(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return (uint32_t)(((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | r); +} + +static inline float _sb_clampf(float v, float low, float high) { + if (v < low) { + return low; + } + else if (v > high) { + return high; + } + return v; +} + +static inline uint32_t _sb_pack_color(const sg_color* color) { + const uint8_t r = (uint8_t)(_sb_clampf(color->r, 0.0f, 1.0f) * 255.0f); + const uint8_t g = (uint8_t)(_sb_clampf(color->g, 0.0f, 1.0f) * 255.0f); + const uint8_t b = (uint8_t)(_sb_clampf(color->b, 0.0f, 1.0f) * 255.0f); + const uint8_t a = (uint8_t)(_sb_clampf(color->a, 0.0f, 1.0f) * 255.0f); + return _sb_pack_color_bytes(r, g, b, a); +} + +static inline int _sb_quad_compare(const void* a, const void* b) { + uint64_t key_a = ((const _sb_quad*)a)->sort_key; + uint64_t key_b = ((const _sb_quad*)b)->sort_key; + if (key_a < key_b) return -1; + if (key_a > key_b) return 1; + return 0; +} + +static inline void* _sb_realloc(void* old_ptr, size_t old_size, size_t new_size) { + SOKOL_ASSERT((new_size > 0) && (new_size > old_size)); + void* new_ptr = SOKOL_MALLOC(new_size); + SOKOL_ASSERT(new_ptr); + if (old_ptr) { + if (old_size > 0) { + memcpy(new_ptr, old_ptr, old_size); + } + SOKOL_FREE(old_ptr); + } + return new_ptr; +} + +static inline void _sb_grow_batch_buffer(void) { + const size_t new_capacity = _sb.batch_data.batch_capacity * 2; + _sb.batch_data.batches = (_sb_batch*)_sb_realloc(_sb.batch_data.batches, _sb.batch_data.batch_capacity, new_capacity); + _sb.batch_data.batch_capacity = new_capacity; +} + +static inline _sb_batch* _sb_create_batch(void) { + if (_sb.batch_data.batch_size >= _sb.batch_data.batch_capacity) { + _sb_grow_batch_buffer(); + } + return &_sb.batch_data.batches[_sb.batch_data.batch_size++]; +} + +static inline void _sb_init_batch(sg_image image, int num_elements, int base_element) { + _sb_batch* batch = _sb_create_batch(); + batch->image = image; + batch->num_elements = num_elements; + batch->base_element = base_element; + batch->matrix = _sb.projection_matrix; +} + +static inline void _sb_init_batches(void) { + int batch_size = 0; + int base_element = 0; + sg_image current_image = { SG_INVALID_ID }; + + for (int i = 0; i < _sb.quad_count; ++i, ++batch_size) { + if (_sb.quads[i].image.id != current_image.id) { + const int num_elements = batch_size * 6; + _sb_init_batch(current_image, num_elements, base_element); + current_image = _sb.quads[i].image; + batch_size = 0; + base_element += num_elements; + } + } + + const int num_elements = batch_size * 6; + _sb_init_batch(current_image, num_elements, base_element); +} + +SOKOL_API_IMPL void sb_premultiply_alpha(uint8_t* pixels, int pixel_count) { + /* + http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/ + https://shawnhargreaves.com/blog/premultiplied-alpha.html + */ + 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; + } +} + +SOKOL_API_IMPL void sb_setup(const sb_desc* desc) { + SOKOL_ASSERT(desc); + memset(&_sb, 0, sizeof(_sb_t)); + _sb_init_sprite_pool(); + _sb.quads = (_sb_quad*)SOKOL_MALLOC(_SB_MAX_QUADS * sizeof(_sb_quad)); + SOKOL_ASSERT(_sb.quads); + _sb_init_vertex_buffer(); + _sb_init_index_buffer(); + _sb_init_batch_data(); + + _sb.default_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.color_count = 1; + pipeline_desc.colors[0].blend.enabled = true; + pipeline_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + pipeline_desc.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + pipeline_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + pipeline_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + pipeline_desc.shader = _sb.default_shader; + 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 = "spritebatch-default-pipeline"; + + _sb.default_pipeline = sg_make_pipeline(&pipeline_desc); +} + +SOKOL_API_IMPL void sb_shutdown(void) { + sg_destroy_pipeline(_sb.default_pipeline); + sg_destroy_shader(_sb.default_shader); + SOKOL_FREE(_sb.batch_data.batches); + sg_destroy_buffer(_sb.index_buffer); + sg_destroy_buffer(_sb.vertex_buffer); + SOKOL_FREE(_sb.vertex_buffer_data); + SOKOL_FREE(_sb.quads); + SOKOL_FREE(_sb.sprite_pool.data); +} + +SOKOL_API_IMPL void sb_begin(const sb_render_state* render_state) { + SOKOL_ASSERT(render_state); + SOKOL_ASSERT(!_sb.begin_called); + _sb.begin_called = true; + + _sb.render_state.viewport = render_state->viewport; + _sb.render_state.sort_mode = render_state->sort_mode; + _sb.render_state.pipeline.id + = _SB_DEFAULT(render_state->pipeline.id, _sb.default_pipeline.id); + + const float width = (float)_sb.render_state.viewport.width; + const float height = (float)_sb.render_state.viewport.height; + + _sb.projection_matrix + = _sb_orthographic_off_center(0.0f, width, height, 0.0f, 0.0f, SB_MAX_DEPTH); + + _sb.render_state.transform_matrix + = _sb_matrix_is_null(&render_state->transform_matrix) + ? _sb_matrix_identity() + : render_state->transform_matrix; +} + +SOKOL_API_IMPL void sb_sprite(const sb_sprite_info* sprite) { + /* TODO: maybe take an array of sprite info? */ + SOKOL_ASSERT(sprite); + SOKOL_ASSERT(sprite->image.id != SG_INVALID_ID); + SOKOL_ASSERT(_sb.quad_count < _SB_MAX_QUADS); + + _sb_sprite_data* cached_sprite_data = &_sb.sprite_pool.data[_sg_image_slot_index(sprite->image.id)]; + if (cached_sprite_data->image.id != sprite->image.id) + { + 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->image = sprite->image; + } + + const float scale_x = _SB_DEFAULT(sprite->scale.x, 1.0f); + const float scale_y = _SB_DEFAULT(sprite->scale.y, 1.0f); + + const float sprite_width = _SB_DEFAULT(sprite->width, (float)cached_sprite_data->width); + const float sprite_height = _SB_DEFAULT(sprite->height, (float)cached_sprite_data->height); + + const float width = sprite_width * scale_x; + const float height = sprite_height * scale_y; + + const float texel_width = (1.0f / cached_sprite_data->width); + const float texel_height = (1.0f / cached_sprite_data->height); + + sb_float2 tex_coord_top_left = { + sprite->source.x * texel_width, + sprite->source.y * texel_height + }; + + sb_float2 tex_coord_bottom_right = { + (sprite->source.x + sprite_width) * texel_width, + (sprite->source.y + sprite_height) * texel_height + }; + + if ((sprite->flags & SB_FLIP_Y) != SB_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 & SB_FLIP_X) != SB_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; + } + + const float scaled_origin_x = scale_x * sprite->origin.x; + const float scaled_origin_y = scale_y * sprite->origin.y; + + _sb_quad* quad = &_sb.quads[_sb.quad_count++]; + quad->sort_key = _sb_make_sort_key(sprite); + quad->image = sprite->image; + + uint32_t packed_color = _sb_pack_color(&sprite->color); + packed_color = packed_color == 0 ? 0xFFFFFFFF : packed_color; + + if (sprite->rotation == 0.0f) + { + _sb_init_quad(quad, + sprite->flags, + 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 + { + _sb_init_quad_rotated(quad, + 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); + } +} + +SOKOL_API_IMPL void sb_end(void) { + + SOKOL_ASSERT(_sb.begin_called); + _sb.begin_called = false; + + if (_sb.quad_count == 0) { + return; + } + + if (_sb.render_state.sort_mode != SB_SORT_MODE_DEFERRED) { + qsort(_sb.quads, _sb.quad_count, sizeof(_sb_quad), _sb_quad_compare); + } + + /* + If this is moved to storing the quad data SOA, we would be able to use the array that stores all the + vertex data verbatim without copying vertex data into an auxiliary buffer. This would significantly + complicate the code though, especially sorting the quads before submission. Can't use a standard sorting + algorithm to sort SOA data. Would need to implement a bespoke solution and that'd be a bit of a pain to + profile and maintain. It is something to think about though. + */ + for (size_t i = 0; i < _sb.quad_count; i++) { + memcpy(_sb.vertex_buffer_data + (i * 4), &_sb.quads[i], 4 * sizeof(_sb_vertex)); + } + + _sb_init_batches(); +} + +SOKOL_API_IMPL void sb_draw(void) { + + if (_sb.batch_data.batch_size == 0) { + return; + } + + const sg_range range = { _sb.vertex_buffer_data, _sb.quad_count * 4 * sizeof(_sb_vertex) }; + sg_update_buffer(_sb.vertex_buffer, &range); + + for (size_t i = 1; i < _sb.batch_data.batch_size; i++) { + _sb_batch* batch = _sb.batch_data.batches + i; + sg_apply_pipeline(_sb.default_pipeline); + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &SG_RANGE(batch->matrix)); + _sb.bindings.fs_images[0] = batch->image; + sg_apply_bindings(&_sb.bindings); + sg_draw(batch->base_element, batch->num_elements, 1); + } + + _sb.quad_count = 0; + _sb.batch_data.batch_size = 0; +} + +#endif /* SOKOL_SPRITEBATCH_IMPL */ |