Skip to content

Commit

Permalink
Merge pull request #1096 from floooh/issue1092_sokol-gl-error-fix
Browse files Browse the repository at this point in the history
sokol_gl.h: don't skip rendering completely in case of errors
  • Loading branch information
floooh authored Aug 26, 2024
2 parents 3c9719d + 15ffc89 commit 501acaf
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 37 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
## Updates

### 26-Aug-2024

A small behaviour update for sokol_gl.h (may be breaking if you call `sgl_error()`):

- Instead of skipping rendering completely for the current frame if an error is encountered
(for instance the vertex- or command-buffer running full), sokol-gl will now
render all successfully recorded draw commands before the error was recorded.
- Minor breaking change: `sgl_error` has been changed from an error code enum to
a struct with a boolean flag per error type, that way no error information is
lost if multiple error happen in the same frame.
- Two new functions to query the current number of recorded vertices and commands
in the current frame:
- `int sgl_num_vertices(void)`
- `int sgl_num_commands(void)`

Also see ticket #1092 and PR #1096 for details!

### 14-Aug-2024

The previously 'inofficial' Jai bindings at https://github.com/colinbellino/sokol-jai
Expand Down
13 changes: 11 additions & 2 deletions tests/functional/sokol_gl_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ UTEST(sokol_gl, default_init_shutdown) {
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->error.any == false);
T(!_sgl.cur_ctx->in_begin);
T(_sgl.cur_ctx->def_pip.id != SG_INVALID_ID);
T(_sgl.pip_pool.pool.size == (_SGL_DEFAULT_PIPELINE_POOL_SIZE + 1));
TFLT(_sgl.cur_ctx->u, 0.0f, FLT_MIN);
TFLT(_sgl.cur_ctx->v, 0.0f, FLT_MIN);
T(_sgl.cur_ctx->rgba == 0xFFFFFFFF);
T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id);
T(sgl_num_commands() == 0);
T(sgl_num_vertices() == 0);
shutdown();
}

Expand Down Expand Up @@ -70,6 +72,7 @@ UTEST(sokol_gl, viewport) {
UTEST(sokol_gl, scissor_rect) {
init();
sgl_scissor_rect(10, 20, 30, 40, true);
T(sgl_num_commands() == 1);
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);
Expand All @@ -78,6 +81,7 @@ UTEST(sokol_gl, scissor_rect) {
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_num_commands() == 2);
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);
Expand Down Expand Up @@ -141,11 +145,15 @@ UTEST(sokol_gl, texture_noimage_nosampler) {
}
UTEST(sokol_gl, begin_end) {
init();
T(sgl_num_commands() == 0);
T(sgl_num_vertices() == 0);
sgl_begin_triangles();
sgl_v3f(1.0f, 2.0f, 3.0f);
sgl_v3f(4.0f, 5.0f, 6.0f);
sgl_v3f(7.0f, 8.0f, 9.0f);
sgl_end();
T(sgl_num_commands() == 1);
T(sgl_num_vertices() == 3);
T(_sgl.cur_ctx->base_vertex == 0);
T(_sgl.cur_ctx->vertices.next == 3);
T(_sgl.cur_ctx->commands.next == 1);
Expand Down Expand Up @@ -282,7 +290,8 @@ UTEST(sokol_gl, destroy_active_context) {
sgl_set_context(ctx);
sgl_destroy_context(ctx);
T(_sgl.cur_ctx == 0);
T(sgl_error() == SGL_ERROR_NO_CONTEXT);
T(sgl_error().no_context);
T(sgl_error().any);
shutdown();
}

Expand Down
140 changes: 105 additions & 35 deletions util/sokol_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -372,27 +372,37 @@
the last call to sgl_draw() through sokol-gfx, and will 'rewind' the internal
vertex-, uniform- and command-buffers.

--- each sokol-gl context tracks an internal error code, to query the
current error code for the currently active context call:
--- each sokol-gl context tracks internal error states which can
be obtains via:

sgl_error_t sgl_error()

...alternatively with an explicit context argument:

sgl_error_t sgl_context_error(ctx);

...which can return the following error codes:
...this returns a struct with the following booleans:

SGL_NO_ERROR - all OK, no error occurred since last sgl_draw()
SGL_ERROR_VERTICES_FULL - internal vertex buffer is full (checked in sgl_end())
SGL_ERROR_UNIFORMS_FULL - the internal uniforms buffer is full (checked in sgl_end())
SGL_ERROR_COMMANDS_FULL - the internal command buffer is full (checked in sgl_end())
SGL_ERROR_STACK_OVERFLOW - matrix- or pipeline-stack overflow
SGL_ERROR_STACK_UNDERFLOW - matrix- or pipeline-stack underflow
SGL_ERROR_NO_CONTEXT - the active context no longer exists
.any - true if any of the below errors is true
.vertices_full - internal vertex buffer is full (checked in sgl_end())
.uniforms_full - the internal uniforms buffer is full (checked in sgl_end())
.commands_full - the internal command buffer is full (checked in sgl_end())
.stack_overflow - matrix- or pipeline-stack overflow
.stack_underflow - matrix- or pipeline-stack underflow
.no_context - the active context no longer exists

...if sokol-gl is in an error-state, sgl_draw() will skip any rendering,
and reset the error code to SGL_NO_ERROR.
...depending on the above error state, sgl_draw() may skip rendering
completely, or only draw partial geometry

--- you can get the number of recorded vertices and draw commands in the current
frame and active sokol-gl context via:

int sgl_num_vertices()
int sgl_num_commands()

...this allows you to check whether the vertex or command pools are running
full before the overflow actually happens (in this case you could also
check the error booleans in the result of sgl_error()).

RENDER LAYERS
=============
Expand Down Expand Up @@ -762,14 +772,14 @@ typedef struct sgl_context { uint32_t id; } sgl_context;
Errors are reset each frame after calling sgl_draw(),
get the last error code with sgl_error()
*/
typedef enum sgl_error_t {
SGL_NO_ERROR = 0,
SGL_ERROR_VERTICES_FULL,
SGL_ERROR_UNIFORMS_FULL,
SGL_ERROR_COMMANDS_FULL,
SGL_ERROR_STACK_OVERFLOW,
SGL_ERROR_STACK_UNDERFLOW,
SGL_ERROR_NO_CONTEXT,
typedef struct sgl_error_t {
bool any;
bool vertices_full;
bool uniforms_full;
bool commands_full;
bool stack_overflow;
bool stack_underflow;
bool no_context;
} sgl_error_t;

/*
Expand Down Expand Up @@ -832,6 +842,10 @@ SOKOL_GL_API_DECL void sgl_set_context(sgl_context ctx);
SOKOL_GL_API_DECL sgl_context sgl_get_context(void);
SOKOL_GL_API_DECL sgl_context sgl_default_context(void);

/* get information about recorded vertices and commands in current context */
SOKOL_GL_API_DECL int sgl_num_vertices(void);
SOKOL_GL_API_DECL int sgl_num_commands(void);

/* draw recorded commands (call inside a sokol-gfx render pass) */
SOKOL_GL_API_DECL void sgl_draw(void);
SOKOL_GL_API_DECL void sgl_context_draw(sgl_context ctx);
Expand Down Expand Up @@ -2342,7 +2356,7 @@ typedef struct {

/* state tracking */
int base_vertex;
int vtx_count; /* number of times vtx function has been called, used for non-triangle primitives */
int quad_vtx_count; /* number of times vtx function has been called, used for non-triangle primitives */
sgl_error_t error;
bool in_begin;
int layer_id;
Expand Down Expand Up @@ -2903,10 +2917,24 @@ static void _sgl_destroy_context(sgl_context ctx_id) {
// ██ ██ ██ ███████ ██████
//
// >>misc

static sgl_error_t _sgl_error_defaults(void) {
sgl_error_t defaults = {0};
return defaults;
}

static int _sgl_num_vertices(_sgl_context_t* ctx) {
return ctx->vertices.next;
}

static int _sgl_num_commands(_sgl_context_t* ctx) {
return ctx->commands.next;
}

static void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) {
ctx->in_begin = true;
ctx->base_vertex = ctx->vertices.next;
ctx->vtx_count = 0;
ctx->quad_vtx_count = 0;
ctx->cur_prim_type = mode;
}

Expand All @@ -2916,7 +2944,7 @@ static void _sgl_rewind(_sgl_context_t* ctx) {
ctx->uniforms.next = 0;
ctx->commands.next = 0;
ctx->base_vertex = 0;
ctx->error = SGL_NO_ERROR;
ctx->error = _sgl_error_defaults();
ctx->layer_id = 0;
ctx->matrix_dirty = true;
}
Expand All @@ -2938,7 +2966,8 @@ 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;
ctx->error.vertices_full = true;
ctx->error.any = true;
return 0;
}
}
Expand All @@ -2947,7 +2976,8 @@ 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;
ctx->error.uniforms_full = true;
ctx->error.any = true;
return 0;
}
}
Expand All @@ -2964,7 +2994,8 @@ 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;
ctx->error.commands_full = true;
ctx->error.any = true;
return 0;
}
}
Expand All @@ -2991,7 +3022,7 @@ static void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, fl
SOKOL_ASSERT(ctx->in_begin);
_sgl_vertex_t* vtx;
/* handle non-native primitive types */
if ((ctx->cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((ctx->vtx_count & 3) == 3)) {
if ((ctx->cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((ctx->quad_vtx_count & 3) == 3)) {
/* for quads, before writing the last quad vertex, reuse
the first and third vertex to start the second triangle in the quad
*/
Expand All @@ -3007,7 +3038,7 @@ static void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, fl
vtx->rgba = rgba;
vtx->psize = ctx->point_size;
}
ctx->vtx_count++;
ctx->quad_vtx_count++;
}

static void _sgl_identity(_sgl_matrix_t* m) {
Expand Down Expand Up @@ -3342,7 +3373,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->vertices.next > 0) && (ctx->commands.next > 0)) {
if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) {
sg_push_debug_group("sokol-gl");

uint32_t cur_pip_id = SG_INVALID_ID;
Expand All @@ -3356,6 +3387,8 @@ static void _sgl_draw(_sgl_context_t* ctx, int layer_id) {
sg_update_buffer(ctx->vbuf, &range);
}

// render all successfully recorded commands (this may be less than the
// issued commands if we're in an error state)
for (int i = 0; i < ctx->commands.next; i++) {
const _sgl_command_t* cmd = &ctx->commands.ptr[i];
if (cmd->layer_id != layer_id) {
Expand Down Expand Up @@ -3463,7 +3496,10 @@ SOKOL_API_IMPL sgl_error_t sgl_error(void) {
if (ctx) {
return ctx->error;
} else {
return SGL_ERROR_NO_CONTEXT;
sgl_error_t err = _sgl_error_defaults();
err.no_context = true;
err.any = true;
return err;
}
}

Expand All @@ -3472,7 +3508,10 @@ SOKOL_API_IMPL sgl_error_t sgl_context_error(sgl_context ctx_id) {
if (ctx) {
return ctx->error;
} else {
return SGL_ERROR_NO_CONTEXT;
sgl_error_t err = _sgl_error_defaults();
err.no_context = true;
err.any = true;
return err;
}
}

Expand Down Expand Up @@ -3521,6 +3560,26 @@ SOKOL_API_IMPL sgl_context sgl_default_context(void) {
return SGL_DEFAULT_CONTEXT;
}

SOKOL_API_IMPL int sgl_num_vertices(void) {
SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie);
_sgl_context_t* ctx = _sgl.cur_ctx;
if (ctx) {
return _sgl_num_vertices(ctx);
} else {
return 0;
}
}

SOKOL_API_IMPL int sgl_num_commands(void) {
SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie);
_sgl_context_t* ctx = _sgl.cur_ctx;
if (ctx) {
return _sgl_num_commands(ctx);
} else {
return 0;
}
}

SOKOL_API_IMPL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc) {
SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie);
_sgl_context_t* ctx = _sgl.cur_ctx;
Expand Down Expand Up @@ -3576,7 +3635,8 @@ SOKOL_API_IMPL void sgl_push_pipeline(void) {
ctx->pip_tos++;
ctx->pip_stack[ctx->pip_tos] = ctx->pip_stack[ctx->pip_tos-1];
} else {
ctx->error = SGL_ERROR_STACK_OVERFLOW;
ctx->error.stack_overflow = true;
ctx->error.any = true;
}
}

Expand All @@ -3589,7 +3649,8 @@ SOKOL_API_IMPL void sgl_pop_pipeline(void) {
if (ctx->pip_tos > 0) {
ctx->pip_tos--;
} else {
ctx->error = SGL_ERROR_STACK_UNDERFLOW;
ctx->error.stack_underflow = true;
ctx->error.any = true;
}
}

Expand Down Expand Up @@ -3778,6 +3839,7 @@ SOKOL_API_IMPL void sgl_end(void) {
SOKOL_ASSERT(ctx->in_begin);
SOKOL_ASSERT(ctx->vertices.next >= ctx->base_vertex);
ctx->in_begin = false;

bool matrix_dirty = ctx->matrix_dirty;
if (matrix_dirty) {
ctx->matrix_dirty = false;
Expand All @@ -3787,6 +3849,12 @@ SOKOL_API_IMPL void sgl_end(void) {
uni->tm = *_sgl_matrix_texture(ctx);
}
}

// don't record any new commands when we're in an error state
if (ctx->error.any) {
return;
}

// 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;
Expand Down Expand Up @@ -4205,7 +4273,8 @@ SOKOL_GL_API_DECL void sgl_push_matrix(void) {
_sgl_matrix_t* dst = _sgl_matrix(ctx);
*dst = *src;
} else {
ctx->error = SGL_ERROR_STACK_OVERFLOW;
ctx->error.stack_overflow = true;
ctx->error.any = true;
}
}

Expand All @@ -4220,7 +4289,8 @@ SOKOL_GL_API_DECL void sgl_pop_matrix(void) {
if (ctx->matrix_tos[ctx->cur_matrix_mode] > 0) {
ctx->matrix_tos[ctx->cur_matrix_mode]--;
} else {
ctx->error = SGL_ERROR_STACK_UNDERFLOW;
ctx->error.stack_underflow = true;
ctx->error.any = true;
}
}

Expand Down

0 comments on commit 501acaf

Please sign in to comment.