Explorar o código

Merge pull request #744 from floooh/sokol-debugtext-layers

Implement layered rendering for sokol_debugtext.h

...also some code cleanup in sokol_gl.h and sokol_spine.h.
Andre Weissflog %!s(int64=2) %!d(string=hai) anos
pai
achega
02f39b3f3f
Modificáronse 7 ficheiros con 529 adicións e 244 borrados
  1. 8 0
      CHANGELOG.md
  2. 1 1
      README.md
  3. 92 17
      tests/functional/sokol_debugtext_test.c
  4. 48 48
      tests/functional/sokol_gl_test.c
  5. 261 63
      util/sokol_debugtext.h
  6. 75 71
      util/sokol_gl.h
  7. 44 44
      util/sokol_spine.h

+ 8 - 0
CHANGELOG.md

@@ -1,5 +1,13 @@
 ## Updates
 
+- **16-Nov-2022**: Render layer support has been added to sokol_debugtext.h,
+  same general changes as in sokol_gl.h with two new functions:
+  sdtx_layer(layer_id) to select the layer to record text into, and
+  sdtx_draw_layer(layer_id) to draw the recorded text in that layer inside a
+  sokol-gfx render pass. The new sample [debugtext-layers-sapp](https://floooh.github.io/sokol-html5/debugtext-layers-sapp) demonstrates the feature together with
+  sokol-gl.
+
+
 - **11-Nov-2022**: sokol_gl.h has 2 new public API functions which enable
   layered rendering: sgl_layer(), sgl_draw_layer() (technically it's three
   functions: there's also sgl_context_draw_layer(), but that's just a variant of

+ 1 - 1
README.md

@@ -4,7 +4,7 @@ Simple
 [STB-style](https://github.com/nothings/stb/blob/master/docs/stb_howto.txt)
 cross-platform libraries for C and C++, written in C.
 
-[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**11-Nov-2022** sokol_gl.h learned layered rendering)
+[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**16-Nov-2022** sokol_debugtext.h learned layered rendering)
 
 [![Build](/../../actions/workflows/main.yml/badge.svg)](/../../actions/workflows/main.yml) [![Bindings](/../../actions/workflows/gen_bindings.yml/badge.svg)](/../../actions/workflows/gen_bindings.yml) [![build](https://github.com/floooh/sokol-zig/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-zig/actions/workflows/main.yml) [![build](https://github.com/floooh/sokol-nim/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-nim/actions/workflows/main.yml) [![Odin](https://github.com/floooh/sokol-odin/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-odin/actions/workflows/main.yml)
 

+ 92 - 17
tests/functional/sokol_debugtext_test.c

@@ -57,11 +57,12 @@ UTEST(sokol_debugtext, default_init_shutdown) {
     T(_sdtx.cur_ctx->desc.color_format == 0);
     T(_sdtx.cur_ctx->desc.depth_format == 0);
     T(_sdtx.cur_ctx->desc.sample_count == 0);
-    T(_sdtx.cur_ctx->cur_vertex_ptr);
-    T(_sdtx.cur_ctx->max_vertex_ptr);
-    T(_sdtx.cur_ctx->vertices);
-    T(_sdtx.cur_ctx->vertices == _sdtx.cur_ctx->cur_vertex_ptr);
-    T(_sdtx.cur_ctx->max_vertex_ptr == (_sdtx.cur_ctx->vertices + _SDTX_DEFAULT_CHAR_BUF_SIZE * 6));
+    T(_sdtx.cur_ctx->vertices.cap == _SDTX_DEFAULT_CHAR_BUF_SIZE * 6);
+    T(_sdtx.cur_ctx->vertices.next == 0);
+    T(_sdtx.cur_ctx->vertices.ptr);
+    T(_sdtx.cur_ctx->commands.cap == _SDTX_DEFAULT_MAX_COMMANDS);
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr);
     T(_sdtx.cur_ctx->vbuf.id != 0);
     T(_sdtx.cur_ctx->pip.id != 0);
     TFLT(_sdtx.cur_ctx->canvas_size.x, 640.0f);
@@ -112,7 +113,7 @@ UTEST(sokol_debugtext, init_with_params) {
     T(_sdtx.cur_ctx->desc.color_format == SG_PIXELFORMAT_RGBA8);
     T(_sdtx.cur_ctx->desc.depth_format == SG_PIXELFORMAT_DEPTH_STENCIL);
     T(_sdtx.cur_ctx->desc.sample_count == 4);
-    T(_sdtx.cur_ctx->max_vertex_ptr == (_sdtx.cur_ctx->vertices + 256 * 6));
+    T(_sdtx.cur_ctx->vertices.cap == (256 * 6));
     TFLT(_sdtx.cur_ctx->canvas_size.x, 320.0f);
     TFLT(_sdtx.cur_ctx->canvas_size.y, 200.0f);
     TFLT(_sdtx.cur_ctx->glyph_size.x, 8.0f / 320.0f);
@@ -144,9 +145,9 @@ UTEST(sokol_debugtext, make_destroy_context) {
     T(ctx->desc.color_format == SG_PIXELFORMAT_RGBA32F);
     T(ctx->desc.depth_format == 0);
     T(ctx->desc.sample_count == 2);
-    T(ctx->vertices);
-    T(ctx->cur_vertex_ptr == ctx->vertices);
-    T(ctx->max_vertex_ptr == ctx->vertices + 64 * 6);
+    T(ctx->vertices.ptr);
+    T(ctx->vertices.next == 0);
+    T(ctx->vertices.cap == (64 * 6));
     TFLT(ctx->canvas_size.x, 1024.0f);
     TFLT(ctx->canvas_size.y, 768.0f);
     TFLT(ctx->glyph_size.x, 8.0f / 1024.0f);
@@ -155,7 +156,7 @@ UTEST(sokol_debugtext, make_destroy_context) {
     sdtx_destroy_context(ctx_id);
     T(0 == _sdtx_lookup_context(ctx_id.id));
     T(ctx->desc.char_buf_size == 0);
-    T(ctx->vertices == 0);
+    T(ctx->vertices.ptr == 0);
     shutdown();
 }
 
@@ -336,7 +337,7 @@ UTEST(sokol_debugtext, vertex_overflow) {
     sdtx_puts("1234567890");
     sdtx_putr("1234567890", 5);
     sdtx_printf("Hello World %d!\n", 12);
-    T(_sdtx.cur_ctx->cur_vertex_ptr == _sdtx.cur_ctx->max_vertex_ptr);
+    T(_sdtx.cur_ctx->vertices.next == _sdtx.cur_ctx->vertices.cap);
     shutdown();
 }
 
@@ -399,7 +400,7 @@ UTEST(sokol_debugtext, rewind_after_draw) {
     sdtx_font(3);
     T(_sdtx.cur_ctx->cur_font == 3);
     sdtx_printf("Hello World!\n");
-    T(_sdtx.cur_ctx->cur_vertex_ptr != _sdtx.cur_ctx->vertices);
+    T(_sdtx.cur_ctx->vertices.next != 0);
     sg_begin_default_pass(&(sg_pass_action){ 0 }, 256, 256);
     sdtx_draw();
     sg_end_pass();
@@ -411,21 +412,21 @@ UTEST(sokol_debugtext, rewind_after_draw) {
     TFLT(_sdtx.cur_ctx->pos.x, 0);
     TFLT(_sdtx.cur_ctx->pos.x, 0);
     T(_sdtx.cur_ctx->cur_font == 0);
-    T(_sdtx.cur_ctx->cur_vertex_ptr == _sdtx.cur_ctx->vertices);
+    T(_sdtx.cur_ctx->vertices.next == 0);
     shutdown();
 }
 
 UTEST(sokol_debugtext, putr) {
     // test if sdtx_putr() draws the right amount of characters
     init();
-    _sdtx_vertex_t* start_ptr = _sdtx.cur_ctx->cur_vertex_ptr;
+    int start_index = _sdtx.cur_ctx->vertices.next;
     sdtx_putr("Hello World!", 5);
-    T((5 * 6) == (_sdtx.cur_ctx->cur_vertex_ptr - start_ptr));
+    T((5 * 6) == (_sdtx.cur_ctx->vertices.next - start_index));
 
-    start_ptr = _sdtx.cur_ctx->cur_vertex_ptr;
+    start_index = _sdtx.cur_ctx->vertices.next;
     sdtx_putr("Hello!\n\n\n\n\n\n\n\n\n\n\n", 10);
     // NOTE: the \n's don't result in rendered vertices
-    T((6 * 6) == (_sdtx.cur_ctx->cur_vertex_ptr - start_ptr));
+    T((6 * 6) == (_sdtx.cur_ctx->vertices.next - start_index));
     shutdown();
 }
 
@@ -434,3 +435,77 @@ UTEST(sokol_debugtext, default_context) {
     T(sdtx_default_context().id == SDTX_DEFAULT_CONTEXT.id);
     shutdown();
 }
+
+// switching layers without any text inbetween should not advance the current draw command
+UTEST(sokol_debug_text, empty_layers) {
+    init();
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0);
+    sdtx_layer(1);
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 1);
+    sdtx_layer(2);
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 2);
+    sdtx_layer(0);
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0);
+    shutdown();
+}
+
+// switching layers with text inbetween should advance the current draw command
+UTEST(sokol_debug_text, non_empty_layers) {
+    init();
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0);
+    T(_sdtx.cur_ctx->commands.ptr[0].first_vertex == 0);
+    T(_sdtx.cur_ctx->commands.ptr[0].num_vertices == 0);
+    sdtx_puts("123");
+    T(_sdtx.cur_ctx->commands.next == 1);
+    T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0);
+    T(_sdtx.cur_ctx->commands.ptr[0].first_vertex == 0);
+    T(_sdtx.cur_ctx->commands.ptr[0].num_vertices == (3 * 6));
+    sdtx_layer(1);
+    sdtx_puts("1234");
+    T(_sdtx.cur_ctx->commands.next == 2);
+    T(_sdtx.cur_ctx->commands.ptr[1].layer_id == 1);
+    T(_sdtx.cur_ctx->commands.ptr[1].first_vertex == (3 * 6));
+    T(_sdtx.cur_ctx->commands.ptr[1].num_vertices == (4 * 6));
+    // switching to same layer should not start a new draw commands
+    sdtx_layer(1);
+    sdtx_puts("12345");
+    T(_sdtx.cur_ctx->commands.next == 2);
+    T(_sdtx.cur_ctx->commands.ptr[1].layer_id == 1);
+    T(_sdtx.cur_ctx->commands.ptr[1].first_vertex == (3 * 6));
+    T(_sdtx.cur_ctx->commands.ptr[1].num_vertices == (9 * 6));
+    sdtx_layer(0);
+    sdtx_puts("123456");
+    T(_sdtx.cur_ctx->commands.next == 3);
+    T(_sdtx.cur_ctx->commands.ptr[2].layer_id == 0);
+    T(_sdtx.cur_ctx->commands.ptr[2].first_vertex == (12 * 6));
+    T(_sdtx.cur_ctx->commands.ptr[2].num_vertices == (6 * 6));
+    shutdown();
+}
+
+UTEST(sokol_debug_text, command_buffer_overflow) {
+    init_with(&(sdtx_desc_t){
+        .context = {
+            .max_commands = 4
+        }
+    });
+    sdtx_puts("0");
+    T(_sdtx.cur_ctx->commands.next == 1);
+    sdtx_layer(1);
+    sdtx_puts("1");
+    T(_sdtx.cur_ctx->commands.next == 2);
+    sdtx_layer(2);
+    sdtx_puts("2");
+    T(_sdtx.cur_ctx->commands.next == 3);
+    sdtx_layer(3);
+    sdtx_puts("3");
+    T(_sdtx.cur_ctx->commands.next == 4);
+    // from here on should fail
+    sdtx_layer(4);
+    sdtx_puts("4");
+    T(_sdtx.cur_ctx->commands.next == 4);
+}

+ 48 - 48
tests/functional/sokol_gl_test.c

@@ -26,15 +26,15 @@ UTEST(sokol_gl, default_init_shutdown) {
     T(_sgl.def_ctx_id.id == SGL_DEFAULT_CONTEXT.id);
     T(_sgl.cur_ctx_id.id == _sgl.def_ctx_id.id);
     T(_sgl.cur_ctx);
-    T(_sgl.cur_ctx->num_vertices == 65536);
-    T(_sgl.cur_ctx->num_commands == 16384);
-    T(_sgl.cur_ctx->num_uniforms == 16384);
-    T(_sgl.cur_ctx->cur_vertex == 0);
-    T(_sgl.cur_ctx->cur_command == 0);
-    T(_sgl.cur_ctx->cur_uniform == 0);
-    T(_sgl.cur_ctx->vertices != 0);
-    T(_sgl.cur_ctx->uniforms != 0);
-    T(_sgl.cur_ctx->commands != 0);
+    T(_sgl.cur_ctx->vertices.cap == 65536);
+    T(_sgl.cur_ctx->commands.cap == 16384);
+    T(_sgl.cur_ctx->uniforms.cap == 16384);
+    T(_sgl.cur_ctx->vertices.next == 0);
+    T(_sgl.cur_ctx->commands.next == 0);
+    T(_sgl.cur_ctx->uniforms.next == 0);
+    T(_sgl.cur_ctx->vertices.ptr != 0);
+    T(_sgl.cur_ctx->uniforms.ptr != 0);
+    T(_sgl.cur_ctx->commands.ptr != 0);
     T(_sgl.cur_ctx->error == SGL_NO_ERROR);
     T(!_sgl.cur_ctx->in_begin);
     T(_sgl.cur_ctx->def_pip.id != SG_INVALID_ID);
@@ -49,42 +49,42 @@ UTEST(sokol_gl, default_init_shutdown) {
 UTEST(sokol_gl, viewport) {
     init();
     sgl_viewport(1, 2, 3, 4, true);
-    T(_sgl.cur_ctx->cur_command == 1);
-    T(_sgl.cur_ctx->commands[0].cmd == SGL_COMMAND_VIEWPORT);
-    T(_sgl.cur_ctx->commands[0].args.viewport.x == 1);
-    T(_sgl.cur_ctx->commands[0].args.viewport.y == 2);
-    T(_sgl.cur_ctx->commands[0].args.viewport.w == 3);
-    T(_sgl.cur_ctx->commands[0].args.viewport.h == 4);
-    T(_sgl.cur_ctx->commands[0].args.viewport.origin_top_left);
+    T(_sgl.cur_ctx->commands.next == 1);
+    T(_sgl.cur_ctx->commands.ptr[0].cmd == SGL_COMMAND_VIEWPORT);
+    T(_sgl.cur_ctx->commands.ptr[0].args.viewport.x == 1);
+    T(_sgl.cur_ctx->commands.ptr[0].args.viewport.y == 2);
+    T(_sgl.cur_ctx->commands.ptr[0].args.viewport.w == 3);
+    T(_sgl.cur_ctx->commands.ptr[0].args.viewport.h == 4);
+    T(_sgl.cur_ctx->commands.ptr[0].args.viewport.origin_top_left);
     sgl_viewport(5, 6, 7, 8, false);
-    T(_sgl.cur_ctx->cur_command == 2);
-    T(_sgl.cur_ctx->commands[1].cmd == SGL_COMMAND_VIEWPORT);
-    T(_sgl.cur_ctx->commands[1].args.viewport.x == 5);
-    T(_sgl.cur_ctx->commands[1].args.viewport.y == 6);
-    T(_sgl.cur_ctx->commands[1].args.viewport.w == 7);
-    T(_sgl.cur_ctx->commands[1].args.viewport.h == 8);
-    T(!_sgl.cur_ctx->commands[1].args.viewport.origin_top_left);
+    T(_sgl.cur_ctx->commands.next == 2);
+    T(_sgl.cur_ctx->commands.ptr[1].cmd == SGL_COMMAND_VIEWPORT);
+    T(_sgl.cur_ctx->commands.ptr[1].args.viewport.x == 5);
+    T(_sgl.cur_ctx->commands.ptr[1].args.viewport.y == 6);
+    T(_sgl.cur_ctx->commands.ptr[1].args.viewport.w == 7);
+    T(_sgl.cur_ctx->commands.ptr[1].args.viewport.h == 8);
+    T(!_sgl.cur_ctx->commands.ptr[1].args.viewport.origin_top_left);
     shutdown();
 }
 
 UTEST(sokol_gl, scissor_rect) {
     init();
     sgl_scissor_rect(10, 20, 30, 40, true);
-    T(_sgl.cur_ctx->cur_command == 1);
-    T(_sgl.cur_ctx->commands[0].cmd == SGL_COMMAND_SCISSOR_RECT);
-    T(_sgl.cur_ctx->commands[0].args.scissor_rect.x == 10);
-    T(_sgl.cur_ctx->commands[0].args.scissor_rect.y == 20);
-    T(_sgl.cur_ctx->commands[0].args.scissor_rect.w == 30);
-    T(_sgl.cur_ctx->commands[0].args.scissor_rect.h == 40);
-    T(_sgl.cur_ctx->commands[0].args.scissor_rect.origin_top_left);
+    T(_sgl.cur_ctx->commands.next == 1);
+    T(_sgl.cur_ctx->commands.ptr[0].cmd == SGL_COMMAND_SCISSOR_RECT);
+    T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.x == 10);
+    T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.y == 20);
+    T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.w == 30);
+    T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.h == 40);
+    T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.origin_top_left);
     sgl_scissor_rect(50, 60, 70, 80, false);
-    T(_sgl.cur_ctx->cur_command == 2);
-    T(_sgl.cur_ctx->commands[1].cmd == SGL_COMMAND_SCISSOR_RECT);
-    T(_sgl.cur_ctx->commands[1].args.scissor_rect.x == 50);
-    T(_sgl.cur_ctx->commands[1].args.scissor_rect.y == 60);
-    T(_sgl.cur_ctx->commands[1].args.scissor_rect.w == 70);
-    T(_sgl.cur_ctx->commands[1].args.scissor_rect.h == 80);
-    T(!_sgl.cur_ctx->commands[1].args.scissor_rect.origin_top_left);
+    T(_sgl.cur_ctx->commands.next == 2);
+    T(_sgl.cur_ctx->commands.ptr[1].cmd == SGL_COMMAND_SCISSOR_RECT);
+    T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.x == 50);
+    T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.y == 60);
+    T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.w == 70);
+    T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.h == 80);
+    T(!_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.origin_top_left);
     shutdown();
 }
 
@@ -111,14 +111,14 @@ UTEST(sokol_gl, begin_end) {
     sgl_v3f(7.0f, 8.0f, 9.0f);
     sgl_end();
     T(_sgl.cur_ctx->base_vertex == 0);
-    T(_sgl.cur_ctx->cur_vertex == 3);
-    T(_sgl.cur_ctx->cur_command == 1);
-    T(_sgl.cur_ctx->cur_uniform == 1);
-    T(_sgl.cur_ctx->commands[0].cmd == SGL_COMMAND_DRAW);
-    T(_sgl.cur_ctx->commands[0].args.draw.pip.id == _sgl_pipeline_at(_sgl.cur_ctx->def_pip.id)->pip[SGL_PRIMITIVETYPE_TRIANGLES].id);
-    T(_sgl.cur_ctx->commands[0].args.draw.base_vertex == 0);
-    T(_sgl.cur_ctx->commands[0].args.draw.num_vertices == 3);
-    T(_sgl.cur_ctx->commands[0].args.draw.uniform_index == 0);
+    T(_sgl.cur_ctx->vertices.next == 3);
+    T(_sgl.cur_ctx->commands.next == 1);
+    T(_sgl.cur_ctx->uniforms.next == 1);
+    T(_sgl.cur_ctx->commands.ptr[0].cmd == SGL_COMMAND_DRAW);
+    T(_sgl.cur_ctx->commands.ptr[0].args.draw.pip.id == _sgl_pipeline_at(_sgl.cur_ctx->def_pip.id)->pip[SGL_PRIMITIVETYPE_TRIANGLES].id);
+    T(_sgl.cur_ctx->commands.ptr[0].args.draw.base_vertex == 0);
+    T(_sgl.cur_ctx->commands.ptr[0].args.draw.num_vertices == 3);
+    T(_sgl.cur_ctx->commands.ptr[0].args.draw.uniform_index == 0);
     shutdown();
 }
 
@@ -223,9 +223,9 @@ UTEST(sokol_gl, make_destroy_contexts) {
     // creating a context should not change the current context
     T(ctx.id != _sgl.cur_ctx_id.id);
     sgl_set_context(ctx);
-    T(_sgl.cur_ctx->num_vertices == 1024);
-    T(_sgl.cur_ctx->num_commands == 256);
-    T(_sgl.cur_ctx->num_uniforms == 256);
+    T(_sgl.cur_ctx->vertices.cap == 1024);
+    T(_sgl.cur_ctx->commands.cap == 256);
+    T(_sgl.cur_ctx->uniforms.cap == 256);
     T(ctx.id == _sgl.cur_ctx_id.id);
     T(sgl_get_context().id == ctx.id);
     sgl_set_context(SGL_DEFAULT_CONTEXT);

+ 261 - 63
util/sokol_debugtext.h

@@ -93,6 +93,11 @@
             be active right after sdtx_setup(), or when calling
             sdtx_set_context(SDTX_DEFAULT_CONTEXT):
 
+            .max_commands (default: 4096)
+                The max number of render commands that can be recorded
+                into the internal command buffer. This directly translates
+                to the number of render layer changes in a single frame.
+
             .char_buf_size (default: 4096)
                 The number of characters that can be rendered per frame in this
                 context, defines the size of an internal fixed-size vertex
@@ -220,14 +225,36 @@
             \n  - carriage return + line feed (same as stdx_crlf())
             \t  - a tab character
 
+    --- You can 'record' text into render layers, this allows to mix/interleave
+        sokol-debugtext rendering with other rendering operations inside
+        sokol-gfx render passes. To start recording text into a different render
+        layer, call:
+
+            sdtx_layer(int layer_id)
+
+        ...outside a sokol-gfx render pass.
+
     --- finally, from within a sokol-gfx render pass, call:
 
             sdtx_draw()
 
-        ...to actually render the text. Calling sdtx_draw() will also rewind
-        the text context:
+        ...for non-layered rendering, or to draw a specific layer:
+
+            sdtx_draw_layer(int layer_id)
+
+        NOTE that sdtx_draw() is equivalent to:
 
-            - the internal vertex buffer pointer is reset to the beginning
+            sdtx_draw_layer(0)
+
+        ...so sdtx_draw() will *NOT* render all text layers, instead it will
+        only render the 'default layer' 0.
+
+    --- at the end of a frame (defined by the call to sg_commit()), sokol-debugtext
+        will rewind all contexts:
+
+            - the internal vertex index is set to 0
+            - the internal command index is set to 0
+            - the current layer id is set to 0
             - the current font is set to 0
             - the cursor position is reset
 
@@ -268,7 +295,8 @@
         - the origin position
         - the current cursor position
         - the current tab width
-        - and the current color
+        - the current color
+        - and the current layer-id
 
     You can get the currently active context with:
 
@@ -291,6 +319,12 @@
     If a context is set as active that no longer exists, all sokol-debugtext
     functions that require an active context will silently fail.
 
+    You can directly draw the recorded text in a specific context without
+    setting the active context:
+
+        sdtx_context_draw(ctx)
+        sdtx_context_draw_layer(ctx, layer_id)
+
     USING YOUR OWN FONT DATA
     ========================
 
@@ -540,6 +574,7 @@ typedef struct sdtx_font_desc_t {
     of text.
 */
 typedef struct sdtx_context_desc_t {
+    int max_commands;                       // max number of draw commands, each layer transition counts as a command, default: 4096
     int char_buf_size;                      // max number of characters rendered in one frame, default: 4096
     float canvas_width;                     // the initial virtual canvas width, default: 640
     float canvas_height;                    // the initial virtual canvas height, default: 400
@@ -618,8 +653,14 @@ SOKOL_DEBUGTEXT_API_DECL void sdtx_set_context(sdtx_context ctx);
 SOKOL_DEBUGTEXT_API_DECL sdtx_context sdtx_get_context(void);
 SOKOL_DEBUGTEXT_API_DECL sdtx_context sdtx_default_context(void);
 
-/* draw and rewind the current context */
+/* drawing functions (call inside sokol-gfx render pass) */
 SOKOL_DEBUGTEXT_API_DECL void sdtx_draw(void);
+SOKOL_DEBUGTEXT_API_DECL void sdtx_context_draw(sdtx_context ctx);
+SOKOL_DEBUGTEXT_API_DECL void sdtx_draw_layer(int layer_id);
+SOKOL_DEBUGTEXT_API_DECL void sdtx_context_draw_layer(sdtx_context ctx, int layer_id);
+
+/* switch render layer */
+SOKOL_DEBUGTEXT_API_DECL void sdtx_layer(int layer_id);
 
 /* switch to a different font */
 SOKOL_DEBUGTEXT_API_DECL void sdtx_font(int font_index);
@@ -718,9 +759,10 @@ inline sdtx_context sdtx_make_context(const sdtx_context_desc_t& desc) { return
 #define _sdtx_def(val, def) (((val) == 0) ? (def) : (val))
 #define _SDTX_INIT_COOKIE (0xACBAABCA)
 
+#define _SDTX_DEFAULT_MAX_COMMANDS (4096)
 #define _SDTX_DEFAULT_CONTEXT_POOL_SIZE (8)
-#define _SDTX_DEFAULT_CHAR_BUF_SIZE (1<<12)
-#define _SDTX_DEFAULT_PRINTF_BUF_SIZE (1<<12)
+#define _SDTX_DEFAULT_CHAR_BUF_SIZE (4096)
+#define _SDTX_DEFAULT_PRINTF_BUF_SIZE (4096)
 #define _SDTX_DEFAULT_CANVAS_WIDTH (640)
 #define _SDTX_DEFAULT_CANVAS_HEIGHT (480)
 #define _SDTX_DEFAULT_TAB_WIDTH (4)
@@ -3478,15 +3520,31 @@ typedef struct {
     uint32_t color;
 } _sdtx_vertex_t;
 
+typedef struct {
+    int layer_id;
+    int first_vertex;
+    int num_vertices;
+} _sdtx_command_t;
+
 typedef struct {
     _sdtx_slot_t slot;
     sdtx_context_desc_t desc;
-    _sdtx_vertex_t* cur_vertex_ptr;
-    const _sdtx_vertex_t* max_vertex_ptr;
-    _sdtx_vertex_t* vertices;
+    uint32_t frame_id;
+    uint32_t update_frame_id;
+    struct {
+        int cap;
+        int next;
+        _sdtx_vertex_t* ptr;
+    } vertices;
+    struct {
+        int cap;
+        int next;
+        _sdtx_command_t* ptr;
+    } commands;
     sg_buffer vbuf;
     sg_pipeline pip;
     int cur_font;
+    int cur_layer_id;
     _sdtx_float2_t canvas_size;
     _sdtx_float2_t glyph_size;
     _sdtx_float2_t origin;
@@ -3704,6 +3762,7 @@ static sdtx_context _sdtx_alloc_context(void) {
 
 static sdtx_context_desc_t _sdtx_context_desc_defaults(const sdtx_context_desc_t* desc) {
     sdtx_context_desc_t res = *desc;
+    res.max_commands = _sdtx_def(res.max_commands, _SDTX_DEFAULT_MAX_COMMANDS);
     res.char_buf_size = _sdtx_def(res.char_buf_size, _SDTX_DEFAULT_CHAR_BUF_SIZE);
     res.canvas_width = _sdtx_def(res.canvas_width, _SDTX_DEFAULT_CANVAS_WIDTH);
     res.canvas_height = _sdtx_def(res.canvas_height, _SDTX_DEFAULT_CANVAS_HEIGHT);
@@ -3715,6 +3774,30 @@ static sdtx_context_desc_t _sdtx_context_desc_defaults(const sdtx_context_desc_t
     return res;
 }
 
+static void _sdtx_set_layer(_sdtx_context_t* ctx, int layer_id);
+static void _sdtx_rewind(_sdtx_context_t* ctx) {
+    SOKOL_ASSERT(ctx);
+    ctx->frame_id++;
+    ctx->vertices.next = 0;
+    ctx->commands.next = 0;
+    _sdtx_set_layer(ctx, 0);
+    ctx->cur_font = 0;
+    ctx->pos.x = 0.0f;
+    ctx->pos.y = 0.0f;
+}
+
+static void _sdtx_commit_listener(void* userdata) {
+    _sdtx_context_t* ctx = _sdtx_lookup_context((uint32_t)(uintptr_t)userdata);
+    if (ctx) {
+        _sdtx_rewind(ctx);
+    }
+}
+
+static sg_commit_listener _sdtx_make_commit_listener(_sdtx_context_t* ctx) {
+    sg_commit_listener listener = { _sdtx_commit_listener, (void*)(uintptr_t)(ctx->slot.id) };
+    return listener;
+}
+
 static void _sdtx_init_context(sdtx_context ctx_id, const sdtx_context_desc_t* in_desc) {
     sg_push_debug_group("sokol-debugtext");
 
@@ -3722,12 +3805,16 @@ static void _sdtx_init_context(sdtx_context ctx_id, const sdtx_context_desc_t* i
     _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id);
     SOKOL_ASSERT(ctx);
     ctx->desc = _sdtx_context_desc_defaults(in_desc);
+    // NOTE: frame_id must be non-zero, so that updates trigger in first frame
+    ctx->frame_id = 1;
+
+    ctx->vertices.cap = 6 * ctx->desc.char_buf_size;
+    const size_t vbuf_size = (size_t)ctx->vertices.cap * sizeof(_sdtx_vertex_t);
+    ctx->vertices.ptr = (_sdtx_vertex_t*) _sdtx_malloc(vbuf_size);
 
-    const int max_vertices = 6 * ctx->desc.char_buf_size;
-    const size_t vbuf_size = (size_t)max_vertices * sizeof(_sdtx_vertex_t);
-    ctx->vertices = (_sdtx_vertex_t*) _sdtx_malloc(vbuf_size);
-    ctx->cur_vertex_ptr = ctx->vertices;
-    ctx->max_vertex_ptr = ctx->vertices + max_vertices;
+    ctx->commands.cap = ctx->desc.max_commands;
+    ctx->commands.ptr = (_sdtx_command_t*) _sdtx_malloc((size_t)ctx->commands.cap * sizeof(_sdtx_command_t));
+    _sdtx_set_layer(ctx, 0);
 
     sg_buffer_desc vbuf_desc;
     _sdtx_clear(&vbuf_desc, sizeof(vbuf_desc));
@@ -3765,21 +3852,33 @@ static void _sdtx_init_context(sdtx_context ctx_id, const sdtx_context_desc_t* i
     ctx->tab_width = (float) ctx->desc.tab_width;
     ctx->color = _SDTX_DEFAULT_COLOR;
 
+    if (!sg_add_commit_listener(_sdtx_make_commit_listener(ctx))) {
+        // FIXME: this should actually result in an invalid context,
+        // fix this when proper error logging/reporting is added
+        SDTX_LOG("sokol_debugtext.h: failed to add sokol-gfx commit listener");
+    }
     sg_pop_debug_group();
 }
 
 static void _sdtx_destroy_context(sdtx_context ctx_id) {
     _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id);
     if (ctx) {
-        if (ctx->vertices) {
-            _sdtx_free(ctx->vertices);
-            ctx->vertices = 0;
-            ctx->cur_vertex_ptr = 0;
-            ctx->max_vertex_ptr = 0;
+        if (ctx->vertices.ptr) {
+            _sdtx_free(ctx->vertices.ptr);
+            ctx->vertices.ptr = 0;
+            ctx->vertices.cap = 0;
+            ctx->vertices.next = 0;
+        }
+        if (ctx->commands.ptr) {
+            _sdtx_free(ctx->commands.ptr);
+            ctx->commands.ptr = 0;
+            ctx->commands.cap = 0;
+            ctx->commands.next = 0;
         }
         sg_push_debug_group("sokol_debugtext");
         sg_destroy_buffer(ctx->vbuf);
         sg_destroy_pipeline(ctx->pip);
+        sg_remove_commit_listener(_sdtx_make_commit_listener(ctx));
         sg_pop_debug_group();
         _sdtx_clear(ctx, sizeof(*ctx));
         _sdtx_pool_free_index(&_sdtx.context_pool.pool, _sdtx_slot_index(ctx_id.id));
@@ -3869,7 +3968,7 @@ static void _sdtx_setup_common(void) {
 
     /* unpack font data */
     memset(_sdtx.font_pixels, 0xFF, sizeof(_sdtx.font_pixels));
-    const int unpacked_font_size = 256 * 8 * 8;
+    const int unpacked_font_size = (int) (sizeof(_sdtx.font_pixels) / SDTX_MAX_FONTS);
     for (int i = 0; i < SDTX_MAX_FONTS; i++) {
         if (_sdtx.desc.fonts[i].data.ptr) {
             _sdtx_unpack_font(&_sdtx.desc.fonts[i], &_sdtx.font_pixels[i * unpacked_font_size]);
@@ -3887,6 +3986,7 @@ static void _sdtx_setup_common(void) {
     img_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE;
     img_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE;
     img_desc.data.subimage[0][0] = SG_RANGE(_sdtx.font_pixels);
+    img_desc.label = "sdtx-font-texture";
     _sdtx.font_img = sg_make_image(&img_desc);
     SOKOL_ASSERT(SG_INVALID_ID != _sdtx.font_img.id);
 
@@ -3904,17 +4004,17 @@ static void _sdtx_discard_common(void) {
     sg_pop_debug_group();
 }
 
-static inline uint32_t _sdtx_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+static uint32_t _sdtx_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
     return (uint32_t)(((uint32_t)a<<24)|((uint32_t)b<<16)|((uint32_t)g<<8)|r);
 }
 
-static inline float _sdtx_clamp(float v, float lo, float hi) {
+static float _sdtx_clamp(float v, float lo, float hi) {
     if (v < lo) return lo;
     else if (v > hi) return hi;
     else return v;
 }
 
-static inline uint32_t _sdtx_pack_rgbaf(float r, float g, float b, float a) {
+static uint32_t _sdtx_pack_rgbaf(float r, float g, float b, float a) {
     uint8_t r_u8 = (uint8_t) (_sdtx_clamp(r, 0.0f, 1.0f) * 255.0f);
     uint8_t g_u8 = (uint8_t) (_sdtx_clamp(g, 0.0f, 1.0f) * 255.0f);
     uint8_t b_u8 = (uint8_t) (_sdtx_clamp(b, 0.0f, 1.0f) * 255.0f);
@@ -3922,7 +4022,7 @@ static inline uint32_t _sdtx_pack_rgbaf(float r, float g, float b, float a) {
     return _sdtx_pack_rgbab(r_u8, g_u8, b_u8, a_u8);
 }
 
-static inline void _sdtx_ctrl_char(_sdtx_context_t* ctx, uint8_t c) {
+static void _sdtx_ctrl_char(_sdtx_context_t* ctx, uint8_t c) {
     switch (c) {
         case '\r':
             ctx->pos.x = 0.0f;
@@ -3940,31 +4040,88 @@ static inline void _sdtx_ctrl_char(_sdtx_context_t* ctx, uint8_t c) {
     }
 }
 
-static inline void _sdtx_draw_char(_sdtx_context_t* ctx, uint8_t c) {
-    if ((ctx->cur_vertex_ptr + 6) <= ctx->max_vertex_ptr) {
+static _sdtx_vertex_t* _sdtx_next_vertex(_sdtx_context_t* ctx) {
+    if ((ctx->vertices.next + 6) <= ctx->vertices.cap) {
+        _sdtx_vertex_t* vx = &ctx->vertices.ptr[ctx->vertices.next];
+        ctx->vertices.next += 6;
+        return vx;
+    }
+    else {
+        return 0;
+    }
+}
+
+static _sdtx_command_t* _sdtx_cur_command(_sdtx_context_t* ctx) {
+    if (ctx->commands.next > 0) {
+        return &ctx->commands.ptr[ctx->commands.next - 1];
+    }
+    else {
+        return 0;
+    }
+}
+
+static _sdtx_command_t* _sdtx_next_command(_sdtx_context_t* ctx) {
+    if (ctx->commands.next < ctx->commands.cap) {
+        return &ctx->commands.ptr[ctx->commands.next++];
+    }
+    else {
+        SDTX_LOG("sokol_debugtext.h: command buffer full");
+        return 0;
+    }
+}
+
+static void _sdtx_set_layer(_sdtx_context_t* ctx, int layer_id) {
+    ctx->cur_layer_id = layer_id;
+    _sdtx_command_t* cur_cmd = _sdtx_cur_command(ctx);
+    if (cur_cmd) {
+        if ((cur_cmd->num_vertices == 0) || (cur_cmd->layer_id == layer_id)) {
+            // no vertices recorded in current draw command, or layer hasn't changed, can just reuse this
+            cur_cmd->layer_id = layer_id;
+        }
+        else {
+            // layer has changed, need to start a new draw command
+            _sdtx_command_t* next_cmd = _sdtx_next_command(ctx);
+            if (next_cmd) {
+                next_cmd->layer_id = layer_id;
+                next_cmd->first_vertex = cur_cmd->first_vertex + cur_cmd->num_vertices;
+                next_cmd->num_vertices = 0;
+            }
+        }
+    }
+    else {
+        // first draw command in frame
+        _sdtx_command_t* next_cmd = _sdtx_next_command(ctx);
+        if (next_cmd) {
+            next_cmd->layer_id = layer_id;
+            next_cmd->first_vertex = 0;
+            next_cmd->num_vertices = 0;
+        }
+    }
+}
+
+static void _sdtx_render_char(_sdtx_context_t* ctx, uint8_t c) {
+    _sdtx_vertex_t* vx = _sdtx_next_vertex(ctx);
+    _sdtx_command_t* cmd = _sdtx_cur_command(ctx);
+    if (vx && cmd) {
+        // update vertex count in current draw command
+        cmd->num_vertices += 6;
+
         const float x0 = (ctx->origin.x + ctx->pos.x) * ctx->glyph_size.x;
         const float y0 = (ctx->origin.y + ctx->pos.y) * ctx->glyph_size.y;
         const float x1 = x0 + ctx->glyph_size.x;
         const float y1 = y0 + ctx->glyph_size.y;
 
         // glyph width and heigth in font texture space
+        // NOTE: the '+1' and '-2' fixes texture bleeding into the neighboring font texture cell
         const uint16_t uvw = 0x10000 / 0x100;
         const uint16_t uvh = 0x10000 / SDTX_MAX_FONTS;
-        const uint16_t u0 = ((uint16_t)c) * uvw;
-        const uint16_t v0 = ((uint16_t)ctx->cur_font) * uvh;
-        uint16_t u1 = u0 + uvw;
-        uint16_t v1 = v0 + uvh;
-        if (u1 == 0x0000) {
-            u1 = 0xFFFF;
-        }
-        if (v1 == 0x0000) {
-            v1 = 0xFFFF;
-        }
+        const uint16_t u0 = (((uint16_t)c) * uvw) + 1;
+        const uint16_t v0 = (((uint16_t)ctx->cur_font) * uvh) + 1;
+        uint16_t u1 = (u0 + uvw) - 2;
+        uint16_t v1 = (v0 + uvh) - 2;
         const uint32_t color = ctx->color;
 
         // write 6 vertices
-        _sdtx_vertex_t* vx = ctx->cur_vertex_ptr;
-
         vx->x=x0; vx->y=y0; vx->u = u0; vx->v = v0; vx->color = color; vx++;
         vx->x=x1; vx->y=y0; vx->u = u1; vx->v = v0; vx->color = color; vx++;
         vx->x=x1; vx->y=y1; vx->u = u1; vx->v = v1; vx->color = color; vx++;
@@ -3972,22 +4129,51 @@ static inline void _sdtx_draw_char(_sdtx_context_t* ctx, uint8_t c) {
         vx->x=x0; vx->y=y0; vx->u = u0; vx->v = v0; vx->color = color; vx++;
         vx->x=x1; vx->y=y1; vx->u = u1; vx->v = v1; vx->color = color; vx++;
         vx->x=x0; vx->y=y1; vx->u = u0; vx->v = v1; vx->color = color; vx++;
-
-        ctx->cur_vertex_ptr = vx;
     }
     ctx->pos.x += 1.0f;
 }
 
-static inline void _sdtx_put_char(_sdtx_context_t* ctx, char c) {
+static void _sdtx_put_char(_sdtx_context_t* ctx, char c) {
     uint8_t c_u8 = (uint8_t)c;
     if (c_u8 <= 32) {
         _sdtx_ctrl_char(ctx, c_u8);
     }
     else {
-        _sdtx_draw_char(ctx, c_u8);
+        _sdtx_render_char(ctx, c_u8);
     }
 }
 
+SOKOL_API_IMPL void _sdtx_draw_layer(_sdtx_context_t* ctx, int layer_id) {
+    SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
+    SOKOL_ASSERT(ctx);
+    if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) {
+        sg_push_debug_group("sokol-debugtext");
+
+        if (ctx->update_frame_id != ctx->frame_id) {
+            ctx->update_frame_id = ctx->frame_id;
+            const sg_range range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sdtx_vertex_t) };
+            sg_update_buffer(ctx->vbuf, &range);
+        }
+
+        sg_apply_pipeline(ctx->pip);
+        sg_bindings bindings;
+        _sdtx_clear(&bindings, sizeof(bindings));
+        bindings.vertex_buffers[0] = ctx->vbuf;
+        bindings.fs_images[0] = _sdtx.font_img;
+        sg_apply_bindings(&bindings);
+        for (int cmd_index = 0; cmd_index < ctx->commands.next; cmd_index++) {
+            const _sdtx_command_t* cmd = &ctx->commands.ptr[cmd_index];
+            if (cmd->layer_id != layer_id) {
+                continue;
+            }
+            SOKOL_ASSERT((cmd->num_vertices % 6) == 0);
+            sg_draw(cmd->first_vertex, cmd->num_vertices, 1);
+        }
+        sg_pop_debug_group();
+    }
+}
+
+
 static sdtx_desc_t _sdtx_desc_defaults(const sdtx_desc_t* desc) {
     SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
     sdtx_desc_t res = *desc;
@@ -4106,6 +4292,14 @@ SOKOL_API_IMPL sdtx_context sdtx_default_context(void) {
     return SDTX_DEFAULT_CONTEXT;
 }
 
+SOKOL_API_IMPL void sdtx_layer(int layer_id) {
+    SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
+    _sdtx_context_t* ctx = _sdtx.cur_ctx;
+    if (ctx) {
+        _sdtx_set_layer(ctx, layer_id);
+    }
+}
+
 SOKOL_API_IMPL void sdtx_font(int font_index) {
     SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
     SOKOL_ASSERT((font_index >= 0) && (font_index < SDTX_MAX_FONTS));
@@ -4308,27 +4502,31 @@ SOKOL_API_IMPL void sdtx_draw(void) {
     SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
     _sdtx_context_t* ctx = _sdtx.cur_ctx;
     if (ctx) {
-        const int num_verts = (int) (ctx->cur_vertex_ptr - ctx->vertices);
-        if (num_verts > 0) {
-            SOKOL_ASSERT((num_verts % 6) == 0);
-            sg_push_debug_group("sokol-debugtext");
-            const sg_range range = { ctx->vertices, (size_t)num_verts * sizeof(_sdtx_vertex_t) };
-            int vbuf_offset = sg_append_buffer(ctx->vbuf, &range);
-            sg_apply_pipeline(ctx->pip);
-            sg_bindings bindings;
-            _sdtx_clear(&bindings, sizeof(bindings));
-            bindings.vertex_buffers[0] = ctx->vbuf;
-            bindings.vertex_buffer_offsets[0] = vbuf_offset;
-            bindings.fs_images[0] = _sdtx.font_img;
-            sg_apply_bindings(&bindings);
-            sg_draw(0, num_verts, 1);
-            sg_pop_debug_group();
-        }
-        ctx->cur_vertex_ptr = ctx->vertices;
-        ctx->cur_font = 0;
-        ctx->pos.x = 0.0f;
-        ctx->pos.y = 0.0f;
+        _sdtx_draw_layer(ctx, 0);
+    }
+}
+
+SOKOL_API_IMPL void sdtx_context_draw(sdtx_context ctx_id) {
+    SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
+    _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id);
+    if (ctx) {
+        _sdtx_draw_layer(ctx, 0);
     }
 }
 
+SOKOL_API_IMPL void sdtx_draw_layer(int layer_id) {
+    SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
+    _sdtx_context_t* ctx = _sdtx.cur_ctx;
+    if (ctx) {
+        _sdtx_draw_layer(ctx, layer_id);
+    }
+}
+
+SOKOL_API_IMPL void sdtx_context_draw_layer(sdtx_context ctx_id, int layer_id) {
+    SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie);
+    _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id);
+    if (ctx) {
+        _sdtx_draw_layer(ctx, layer_id);
+    }
+}
 #endif /* SOKOL_DEBUGTEXT_IMPL */

+ 75 - 71
util/sokol_gl.h

@@ -2321,16 +2321,21 @@ typedef struct {
     sgl_context_desc_t desc;
     uint32_t frame_id;
     uint32_t update_frame_id;
-
-    int num_vertices;
-    int num_uniforms;
-    int num_commands;
-    int cur_vertex;
-    int cur_uniform;
-    int cur_command;
-    _sgl_vertex_t* vertices;
-    _sgl_uniform_t* uniforms;
-    _sgl_command_t* commands;
+    struct {
+        int cap;
+        int next;
+        _sgl_vertex_t* ptr;
+    } vertices;
+    struct {
+        int cap;
+        int next;
+        _sgl_uniform_t* ptr;
+    } uniforms;
+    struct {
+        int cap;
+        int next;
+        _sgl_command_t* ptr;
+    } commands;
 
     /* state tracking */
     int base_vertex;
@@ -2483,9 +2488,9 @@ static void _sgl_pool_free_index(_sgl_pool_t* pool, int slot_index) {
 
 static void _sgl_reset_context(_sgl_context_t* ctx) {
     SOKOL_ASSERT(ctx);
-    SOKOL_ASSERT(0 == ctx->vertices);
-    SOKOL_ASSERT(0 == ctx->uniforms);
-    SOKOL_ASSERT(0 == ctx->commands);
+    SOKOL_ASSERT(0 == ctx->vertices.ptr);
+    SOKOL_ASSERT(0 == ctx->uniforms.ptr);
+    SOKOL_ASSERT(0 == ctx->commands.ptr);
     _sgl_clear(ctx, sizeof(_sgl_context_t));
 }
 
@@ -2763,18 +2768,18 @@ static void _sgl_init_context(sgl_context ctx_id, const sgl_context_desc_t* in_d
     ctx->cur_img = _sgl.def_img;
 
     // allocate buffers and pools
-    ctx->num_vertices = ctx->desc.max_vertices;
-    ctx->num_commands = ctx->num_uniforms = ctx->desc.max_commands;
-    ctx->vertices = (_sgl_vertex_t*) _sgl_malloc((size_t)ctx->num_vertices * sizeof(_sgl_vertex_t));
-    ctx->uniforms = (_sgl_uniform_t*) _sgl_malloc((size_t)ctx->num_uniforms * sizeof(_sgl_uniform_t));
-    ctx->commands = (_sgl_command_t*) _sgl_malloc((size_t)ctx->num_commands * sizeof(_sgl_command_t));
+    ctx->vertices.cap = ctx->desc.max_vertices;
+    ctx->commands.cap = ctx->uniforms.cap = ctx->desc.max_commands;
+    ctx->vertices.ptr = (_sgl_vertex_t*) _sgl_malloc((size_t)ctx->vertices.cap * sizeof(_sgl_vertex_t));
+    ctx->uniforms.ptr = (_sgl_uniform_t*) _sgl_malloc((size_t)ctx->uniforms.cap * sizeof(_sgl_uniform_t));
+    ctx->commands.ptr = (_sgl_command_t*) _sgl_malloc((size_t)ctx->commands.cap * sizeof(_sgl_command_t));
 
     // create sokol-gfx resource objects
     sg_push_debug_group("sokol-gl");
 
     sg_buffer_desc vbuf_desc;
     _sgl_clear(&vbuf_desc, sizeof(vbuf_desc));
-    vbuf_desc.size = (size_t)ctx->num_vertices * sizeof(_sgl_vertex_t);
+    vbuf_desc.size = (size_t)ctx->vertices.cap * sizeof(_sgl_vertex_t);
     vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER;
     vbuf_desc.usage = SG_USAGE_STREAM;
     vbuf_desc.label = "sgl-vertex-buffer";
@@ -2818,17 +2823,16 @@ static sgl_context _sgl_make_context(const sgl_context_desc_t* desc) {
 static void _sgl_destroy_context(sgl_context ctx_id) {
     _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id);
     if (ctx) {
-        SOKOL_ASSERT(ctx->vertices);
-        SOKOL_ASSERT(ctx->uniforms);
-        SOKOL_ASSERT(ctx->commands);
-
-        _sgl_free(ctx->vertices);
-        _sgl_free(ctx->uniforms);
-        _sgl_free(ctx->commands);
+        SOKOL_ASSERT(ctx->vertices.ptr);
+        SOKOL_ASSERT(ctx->uniforms.ptr);
+        SOKOL_ASSERT(ctx->commands.ptr);
 
-        ctx->vertices = 0;
-        ctx->uniforms = 0;
-        ctx->commands = 0;
+        _sgl_free(ctx->vertices.ptr);
+        _sgl_free(ctx->uniforms.ptr);
+        _sgl_free(ctx->commands.ptr);
+        ctx->vertices.ptr = 0;
+        ctx->uniforms.ptr = 0;
+        ctx->commands.ptr = 0;
 
         sg_push_debug_group("sokol-gl");
         sg_destroy_buffer(ctx->vbuf);
@@ -2841,18 +2845,18 @@ static void _sgl_destroy_context(sgl_context ctx_id) {
     }
 }
 
-static inline void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) {
+static void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) {
     ctx->in_begin = true;
-    ctx->base_vertex = ctx->cur_vertex;
+    ctx->base_vertex = ctx->vertices.next;
     ctx->vtx_count = 0;
     ctx->cur_prim_type = mode;
 }
 
 static void _sgl_rewind(_sgl_context_t* ctx) {
     ctx->frame_id++;
-    ctx->cur_vertex = 0;
-    ctx->cur_uniform = 0;
-    ctx->cur_command = 0;
+    ctx->vertices.next = 0;
+    ctx->uniforms.next = 0;
+    ctx->commands.next = 0;
     ctx->base_vertex = 0;
     ctx->error = SGL_NO_ERROR;
     ctx->layer_id = 0;
@@ -2872,9 +2876,9 @@ static sg_commit_listener _sgl_make_commit_listener(_sgl_context_t* ctx) {
     return listener;
 }
 
-static inline _sgl_vertex_t* _sgl_next_vertex(_sgl_context_t* ctx) {
-    if (ctx->cur_vertex < ctx->num_vertices) {
-        return &ctx->vertices[ctx->cur_vertex++];
+static _sgl_vertex_t* _sgl_next_vertex(_sgl_context_t* ctx) {
+    if (ctx->vertices.next < ctx->vertices.cap) {
+        return &ctx->vertices.ptr[ctx->vertices.next++];
     }
     else {
         ctx->error = SGL_ERROR_VERTICES_FULL;
@@ -2882,9 +2886,9 @@ static inline _sgl_vertex_t* _sgl_next_vertex(_sgl_context_t* ctx) {
     }
 }
 
-static inline _sgl_uniform_t* _sgl_next_uniform(_sgl_context_t* ctx) {
-    if (ctx->cur_uniform < ctx->num_uniforms) {
-        return &ctx->uniforms[ctx->cur_uniform++];
+static _sgl_uniform_t* _sgl_next_uniform(_sgl_context_t* ctx) {
+    if (ctx->uniforms.next < ctx->uniforms.cap) {
+        return &ctx->uniforms.ptr[ctx->uniforms.next++];
     }
     else {
         ctx->error = SGL_ERROR_UNIFORMS_FULL;
@@ -2892,18 +2896,18 @@ static inline _sgl_uniform_t* _sgl_next_uniform(_sgl_context_t* ctx) {
     }
 }
 
-static inline _sgl_command_t* _sgl_prev_command(_sgl_context_t* ctx) {
-    if (ctx->cur_command > 0) {
-        return &ctx->commands[ctx->cur_command - 1];
+static _sgl_command_t* _sgl_cur_command(_sgl_context_t* ctx) {
+    if (ctx->commands.next > 0) {
+        return &ctx->commands.ptr[ctx->commands.next - 1];
     }
     else {
         return 0;
     }
 }
 
-static inline _sgl_command_t* _sgl_next_command(_sgl_context_t* ctx) {
-    if (ctx->cur_command < ctx->num_commands) {
-        return &ctx->commands[ctx->cur_command++];
+static _sgl_command_t* _sgl_next_command(_sgl_context_t* ctx) {
+    if (ctx->commands.next < ctx->commands.cap) {
+        return &ctx->commands.ptr[ctx->commands.next++];
     }
     else {
         ctx->error = SGL_ERROR_COMMANDS_FULL;
@@ -2911,17 +2915,17 @@ static inline _sgl_command_t* _sgl_next_command(_sgl_context_t* ctx) {
     }
 }
 
-static inline uint32_t _sgl_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+static uint32_t _sgl_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
     return (uint32_t)(((uint32_t)a<<24)|((uint32_t)b<<16)|((uint32_t)g<<8)|r);
 }
 
-static inline float _sgl_clamp(float v, float lo, float hi) {
+static float _sgl_clamp(float v, float lo, float hi) {
     if (v < lo) return lo;
     else if (v > hi) return hi;
     else return v;
 }
 
-static inline uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) {
+static uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) {
     uint8_t r_u8 = (uint8_t) (_sgl_clamp(r, 0.0f, 1.0f) * 255.0f);
     uint8_t g_u8 = (uint8_t) (_sgl_clamp(g, 0.0f, 1.0f) * 255.0f);
     uint8_t b_u8 = (uint8_t) (_sgl_clamp(b, 0.0f, 1.0f) * 255.0f);
@@ -2929,7 +2933,7 @@ static inline uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) {
     return _sgl_pack_rgbab(r_u8, g_u8, b_u8, a_u8);
 }
 
-static inline void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, float v, uint32_t rgba) {
+static void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, float v, uint32_t rgba) {
     SOKOL_ASSERT(ctx->in_begin);
     _sgl_vertex_t* vtx;
     /* handle non-native primitive types */
@@ -3143,22 +3147,22 @@ static void _sgl_lookat(_sgl_matrix_t* dst,
 }
 
 /* current top-of-stack projection matrix */
-static inline _sgl_matrix_t* _sgl_matrix_projection(_sgl_context_t* ctx) {
+static _sgl_matrix_t* _sgl_matrix_projection(_sgl_context_t* ctx) {
     return &ctx->matrix_stack[SGL_MATRIXMODE_PROJECTION][ctx->matrix_tos[SGL_MATRIXMODE_PROJECTION]];
 }
 
 /* get top-of-stack modelview matrix */
-static inline _sgl_matrix_t* _sgl_matrix_modelview(_sgl_context_t* ctx) {
+static _sgl_matrix_t* _sgl_matrix_modelview(_sgl_context_t* ctx) {
     return &ctx->matrix_stack[SGL_MATRIXMODE_MODELVIEW][ctx->matrix_tos[SGL_MATRIXMODE_MODELVIEW]];
 }
 
 /* get top-of-stack texture matrix */
-static inline _sgl_matrix_t* _sgl_matrix_texture(_sgl_context_t* ctx) {
+static _sgl_matrix_t* _sgl_matrix_texture(_sgl_context_t* ctx) {
     return &ctx->matrix_stack[SGL_MATRIXMODE_TEXTURE][ctx->matrix_tos[SGL_MATRIXMODE_TEXTURE]];
 }
 
 /* get pointer to current top-of-stack of current matrix mode */
-static inline _sgl_matrix_t* _sgl_matrix(_sgl_context_t* ctx) {
+static _sgl_matrix_t* _sgl_matrix(_sgl_context_t* ctx) {
     return &ctx->matrix_stack[ctx->cur_matrix_mode][ctx->matrix_tos[ctx->cur_matrix_mode]];
 }
 
@@ -3272,7 +3276,7 @@ static bool _sgl_is_default_context(sgl_context ctx_id) {
 
 static void _sgl_draw(_sgl_context_t* ctx, int layer_id) {
     SOKOL_ASSERT(ctx);
-    if ((ctx->error == SGL_NO_ERROR) && (ctx->cur_vertex > 0) && (ctx->cur_command > 0)) {
+    if ((ctx->error == SGL_NO_ERROR) && (ctx->vertices.next > 0) && (ctx->commands.next > 0)) {
         sg_push_debug_group("sokol-gl");
 
         uint32_t cur_pip_id = SG_INVALID_ID;
@@ -3281,12 +3285,12 @@ static void _sgl_draw(_sgl_context_t* ctx, int layer_id) {
 
         if (ctx->update_frame_id != ctx->frame_id) {
             ctx->update_frame_id = ctx->frame_id;
-            const sg_range range = { ctx->vertices, (size_t)ctx->cur_vertex * sizeof(_sgl_vertex_t) };
+            const sg_range range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sgl_vertex_t) };
             sg_update_buffer(ctx->vbuf, &range);
         }
 
-        for (int i = 0; i < ctx->cur_command; i++) {
-            const _sgl_command_t* cmd = &ctx->commands[i];
+        for (int i = 0; i < ctx->commands.next; i++) {
+            const _sgl_command_t* cmd = &ctx->commands.ptr[i];
             if (cmd->layer_id != layer_id) {
                 continue;
             }
@@ -3319,7 +3323,7 @@ static void _sgl_draw(_sgl_context_t* ctx, int layer_id) {
                             cur_img_id = args->img.id;
                         }
                         if (cur_uniform_index != args->uniform_index) {
-                            const sg_range ub_range = { &ctx->uniforms[args->uniform_index], sizeof(_sgl_uniform_t) };
+                            const sg_range ub_range = { &ctx->uniforms.ptr[args->uniform_index], sizeof(_sgl_uniform_t) };
                             sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &ub_range);
                             cur_uniform_index = args->uniform_index;
                         }
@@ -3698,7 +3702,7 @@ SOKOL_API_IMPL void sgl_end(void) {
         return;
     }
     SOKOL_ASSERT(ctx->in_begin);
-    SOKOL_ASSERT(ctx->cur_vertex >= ctx->base_vertex);
+    SOKOL_ASSERT(ctx->vertices.next >= ctx->base_vertex);
     ctx->in_begin = false;
     bool matrix_dirty = ctx->matrix_dirty;
     if (matrix_dirty) {
@@ -3709,39 +3713,39 @@ SOKOL_API_IMPL void sgl_end(void) {
             uni->tm = *_sgl_matrix_texture(ctx);
         }
     }
-    /* check if command can be merged with previous command */
+    /* check if command can be merged with current command */
     sg_pipeline pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type);
     sg_image img = ctx->texturing_enabled ? ctx->cur_img : _sgl.def_img;
-    _sgl_command_t* prev_cmd = _sgl_prev_command(ctx);
+    _sgl_command_t* cur_cmd = _sgl_cur_command(ctx);
     bool merge_cmd = false;
-    if (prev_cmd) {
-        if ((prev_cmd->cmd == SGL_COMMAND_DRAW) &&
-            (prev_cmd->layer_id == ctx->layer_id) &&
+    if (cur_cmd) {
+        if ((cur_cmd->cmd == SGL_COMMAND_DRAW) &&
+            (cur_cmd->layer_id == ctx->layer_id) &&
             (ctx->cur_prim_type != SGL_PRIMITIVETYPE_LINE_STRIP) &&
             (ctx->cur_prim_type != SGL_PRIMITIVETYPE_TRIANGLE_STRIP) &&
             !matrix_dirty &&
-            (prev_cmd->args.draw.img.id == img.id) &&
-            (prev_cmd->args.draw.pip.id == pip.id))
+            (cur_cmd->args.draw.img.id == img.id) &&
+            (cur_cmd->args.draw.pip.id == pip.id))
         {
             merge_cmd = true;
         }
     }
     if (merge_cmd) {
         /* draw command can be merged with the previous command */
-        prev_cmd->args.draw.num_vertices += ctx->cur_vertex - ctx->base_vertex;
+        cur_cmd->args.draw.num_vertices += ctx->vertices.next - ctx->base_vertex;
     }
     else {
         /* append a new draw command */
         _sgl_command_t* cmd = _sgl_next_command(ctx);
         if (cmd) {
-            SOKOL_ASSERT(ctx->cur_uniform > 0);
+            SOKOL_ASSERT(ctx->uniforms.next > 0);
             cmd->cmd = SGL_COMMAND_DRAW;
             cmd->layer_id = ctx->layer_id;
             cmd->args.draw.img = img;
             cmd->args.draw.pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type);
             cmd->args.draw.base_vertex = ctx->base_vertex;
-            cmd->args.draw.num_vertices = ctx->cur_vertex - ctx->base_vertex;
-            cmd->args.draw.uniform_index = ctx->cur_uniform - 1;
+            cmd->args.draw.num_vertices = ctx->vertices.next - ctx->base_vertex;
+            cmd->args.draw.uniform_index = ctx->uniforms.next - 1;
         }
     }
 }

+ 44 - 44
util/sokol_spine.h

@@ -2619,7 +2619,7 @@ typedef struct {
     spSkeletonData* sp_skel_data;
     spAnimationStateData* sp_anim_data;
     struct {
-        int num;
+        int cap;
         sspine_vec2* ptr;
     } tform_buf;
 } _sspine_skeleton_t;
@@ -2696,20 +2696,20 @@ typedef struct {
     _sspine_slot_t slot;
     float transform[16];
     struct {
-        int num;
-        int cur;
+        int cap;
+        int next;
         uint32_t rewind_frame_id;
         _sspine_vertex_t* ptr;
     } vertices;
     struct {
-        int num;
-        int cur;
+        int cap;
+        int next;
         uint32_t rewind_frame_id;
         uint32_t* ptr;
     } indices;
     struct {
-        int num;
-        int cur;
+        int cap;
+        int next;
         uint32_t rewind_frame_id;
         _sspine_command_t* ptr;
     } commands;
@@ -3114,13 +3114,13 @@ static sspine_resource_state _sspine_init_context(_sspine_context_t* ctx, const
     SOKOL_ASSERT(desc);
 
     // setup vertex, index and command storage
-    ctx->vertices.num = desc->max_vertices;
-    ctx->indices.num = ctx->vertices.num * 3;
-    ctx->commands.num = desc->max_commands;
+    ctx->vertices.cap = desc->max_vertices;
+    ctx->indices.cap = ctx->vertices.cap * 3;
+    ctx->commands.cap = desc->max_commands;
 
-    const size_t vbuf_size = (size_t)ctx->vertices.num * sizeof(_sspine_vertex_t);
-    const size_t ibuf_size = (size_t)ctx->indices.num * sizeof(uint32_t);
-    const size_t cbuf_size = (size_t)ctx->commands.num * sizeof(_sspine_command_t);
+    const size_t vbuf_size = (size_t)ctx->vertices.cap * sizeof(_sspine_vertex_t);
+    const size_t ibuf_size = (size_t)ctx->indices.cap * sizeof(uint32_t);
+    const size_t cbuf_size = (size_t)ctx->commands.cap * sizeof(_sspine_command_t);
 
     ctx->vertices.ptr = (_sspine_vertex_t*) _sspine_malloc(vbuf_size);
     ctx->indices.ptr = (uint32_t*) _sspine_malloc(ibuf_size);
@@ -3483,8 +3483,8 @@ static sspine_resource_state _sspine_init_skeleton(_sspine_skeleton_t* skeleton,
     }
 
     // allocate a shared vertex transform buffer (big enough to hold vertices for biggest mesh attachment)
-    skeleton->tform_buf.num = max_vertex_count;
-    skeleton->tform_buf.ptr = (sspine_vec2*) _sspine_malloc((size_t)skeleton->tform_buf.num * sizeof(sspine_vec2));
+    skeleton->tform_buf.cap = max_vertex_count;
+    skeleton->tform_buf.ptr = (sspine_vec2*) _sspine_malloc((size_t)skeleton->tform_buf.cap * sizeof(sspine_vec2));
 
     return SSPINE_RESOURCESTATE_VALID;
 }
@@ -4035,15 +4035,15 @@ static void _sspine_init_image_info(const _sspine_atlas_t* atlas, int index, ssp
 
 static void _sspine_check_rewind_commands(_sspine_context_t* ctx) {
     if (_sspine.frame_id != ctx->commands.rewind_frame_id) {
-        ctx->commands.cur = 0;
+        ctx->commands.next = 0;
         ctx->commands.rewind_frame_id = _sspine.frame_id;
     }
 }
 
 static _sspine_command_t* _sspine_next_command(_sspine_context_t* ctx) {
     _sspine_check_rewind_commands(ctx);
-    if ((ctx->commands.cur + 1) <= ctx->commands.num) {
-        return &(ctx->commands.ptr[ctx->commands.cur++]);
+    if (ctx->commands.next < ctx->commands.cap) {
+        return &(ctx->commands.ptr[ctx->commands.next++]);
     }
     else {
         _SSPINE_ERROR(COMMAND_BUFFER_OVERFLOW);
@@ -4051,10 +4051,10 @@ static _sspine_command_t* _sspine_next_command(_sspine_context_t* ctx) {
     }
 }
 
-static _sspine_command_t* _sspine_prev_command(_sspine_context_t* ctx) {
+static _sspine_command_t* _sspine_cur_command(_sspine_context_t* ctx) {
     _sspine_check_rewind_commands(ctx);
-    if ((ctx->commands.cur > 0) && (ctx->commands.cur <= ctx->commands.num)) {
-        return &ctx->commands.ptr[ctx->commands.cur - 1];
+    if (ctx->commands.next > 0) {
+        return &ctx->commands.ptr[ctx->commands.next - 1];
     }
     else {
         return 0;
@@ -4063,7 +4063,7 @@ static _sspine_command_t* _sspine_prev_command(_sspine_context_t* ctx) {
 
 static void _sspine_check_rewind_vertices(_sspine_context_t* ctx) {
     if (_sspine.frame_id != ctx->vertices.rewind_frame_id) {
-        ctx->vertices.cur = 0;
+        ctx->vertices.next = 0;
         ctx->vertices.rewind_frame_id = _sspine.frame_id;
     }
 }
@@ -4072,10 +4072,10 @@ static _sspine_alloc_vertices_result_t _sspine_alloc_vertices(_sspine_context_t*
     _sspine_check_rewind_vertices(ctx);
     _sspine_alloc_vertices_result_t res;
     _sspine_clear(&res, sizeof(res));
-    if ((ctx->vertices.cur + num) <= ctx->vertices.num) {
-        res.ptr = &(ctx->vertices.ptr[ctx->vertices.cur]);
-        res.index = ctx->vertices.cur;
-        ctx->vertices.cur += num;
+    if ((ctx->vertices.next + num) <= ctx->vertices.cap) {
+        res.ptr = &(ctx->vertices.ptr[ctx->vertices.next]);
+        res.index = ctx->vertices.next;
+        ctx->vertices.next += num;
     }
     else {
         _SSPINE_ERROR(VERTEX_BUFFER_OVERFLOW);
@@ -4085,7 +4085,7 @@ static _sspine_alloc_vertices_result_t _sspine_alloc_vertices(_sspine_context_t*
 
 static void _sspine_check_rewind_indices(_sspine_context_t* ctx) {
     if (_sspine.frame_id != ctx->indices.rewind_frame_id) {
-        ctx->indices.cur = 0;
+        ctx->indices.next = 0;
         ctx->indices.rewind_frame_id = _sspine.frame_id;
     }
 }
@@ -4094,10 +4094,10 @@ static _sspine_alloc_indices_result_t _sspine_alloc_indices(_sspine_context_t* c
     _sspine_check_rewind_indices(ctx);
     _sspine_alloc_indices_result_t res;
     _sspine_clear(&res, sizeof(res));
-    if ((ctx->indices.cur + num) <= ctx->indices.num) {
-        res.ptr = &(ctx->indices.ptr[ctx->indices.cur]);
-        res.index = ctx->indices.cur;
-        ctx->indices.cur += num;
+    if ((ctx->indices.next + num) <= ctx->indices.cap) {
+        res.ptr = &(ctx->indices.ptr[ctx->indices.next]);
+        res.index = ctx->indices.next;
+        ctx->indices.next += num;
     }
     else {
         _SSPINE_ERROR(INDEX_BUFFER_OVERFLOW);
@@ -4114,7 +4114,7 @@ static void _sspine_draw_instance(_sspine_context_t* ctx, _sspine_instance_t* in
     // see: https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-sdl/src/spine-sdl-c.c
     const spSkeleton* sp_skel = instance->sp_skel;
     float* tform_buf = (float*)instance->skel.ptr->tform_buf.ptr;
-    const int max_tform_buf_verts = instance->skel.ptr->tform_buf.num;
+    const int max_tform_buf_verts = instance->skel.ptr->tform_buf.cap;
     SOKOL_UNUSED(max_tform_buf_verts); // only used in asserts
     const int tform_buf_stride = 2; // each element is 2 floats
     spSkeletonClipping* sp_clip = instance->sp_clip;
@@ -4254,11 +4254,11 @@ static void _sspine_draw_instance(_sspine_context_t* ctx, _sspine_instance_t* in
                 break;
         }
 
-        // write new draw command, or merge with previous draw command
-        _sspine_command_t* prev_cmd = _sspine_prev_command(ctx);
-        if (prev_cmd && (prev_cmd->layer == layer) && (prev_cmd->pip.id == pip.id) && (prev_cmd->img.id == img.id) && (prev_cmd->pma == pma)) {
-            // merge with previous command
-            prev_cmd->num_elements += num_indices;
+        // write new draw command, or merge with current draw command
+        _sspine_command_t* cur_cmd = _sspine_cur_command(ctx);
+        if (cur_cmd && (cur_cmd->layer == layer) && (cur_cmd->pip.id == pip.id) && (cur_cmd->img.id == img.id) && (cur_cmd->pma == pma)) {
+            // merge with current command
+            cur_cmd->num_elements += num_indices;
         }
         else {
             // record a new command
@@ -4311,14 +4311,14 @@ static _sspine_vsparams_t _sspine_compute_vsparams(const sspine_layer_transform*
 }
 
 static void _sspine_draw_layer(_sspine_context_t* ctx, int layer, const sspine_layer_transform* tform) {
-    if ((ctx->vertices.cur > 0) && (ctx->commands.cur > 0)) {
+    if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) {
         sg_push_debug_group("sokol-spine");
 
         if (ctx->update_frame_id != _sspine.frame_id) {
             ctx->update_frame_id = _sspine.frame_id;
-            const sg_range vtx_range = { ctx->vertices.ptr, (size_t)ctx->vertices.cur * sizeof(_sspine_vertex_t) };
+            const sg_range vtx_range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sspine_vertex_t) };
             sg_update_buffer(ctx->vbuf, &vtx_range);
-            const sg_range idx_range = { ctx->indices.ptr, (size_t)ctx->indices.cur * sizeof(uint32_t) };
+            const sg_range idx_range = { ctx->indices.ptr, (size_t)ctx->indices.next * sizeof(uint32_t) };
             sg_update_buffer(ctx->ibuf, &idx_range);
         }
 
@@ -4331,7 +4331,7 @@ static void _sspine_draw_layer(_sspine_context_t* ctx, int layer, const sspine_l
         uint32_t cur_pip_id = SG_INVALID_ID;
         uint32_t cur_img_id = SG_INVALID_ID;
         float cur_pma = -1.0f;
-        for (int i = 0; i < ctx->commands.cur; i++) {
+        for (int i = 0; i < ctx->commands.next; i++) {
             const _sspine_command_t* cmd = &ctx->commands.ptr[i];
             if ((layer == cmd->layer) && (sg_query_image_state(cmd->img) == SG_RESOURCESTATE_VALID)) {
                 if (cur_pip_id != cmd->pip.id) {
@@ -4542,9 +4542,9 @@ SOKOL_API_IMPL sspine_context_info sspine_get_context_info(sspine_context ctx_id
     _sspine_clear(&res, sizeof(res));
     const _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id);
     if (ctx) {
-        res.num_vertices = ctx->vertices.cur;
-        res.num_indices  = ctx->indices.cur;
-        res.num_commands = ctx->commands.cur;
+        res.num_vertices = ctx->vertices.next;
+        res.num_indices  = ctx->indices.next;
+        res.num_commands = ctx->commands.next;
     }
     return res;
 }