floooh / sokol

minimal cross-platform standalone C headers
https://floooh.github.io/sokol-html5
zlib License
6.54k stars 469 forks source link

sdhc + sokol_app.h: Uniform Arrays of smaller than vec4 type lead to validation errors or silently introduce buggy behaviour #1011

Closed OetkenPurveyorOfCode closed 3 months ago

OetkenPurveyorOfCode commented 3 months ago

For example the following in GLSL:

uniform fs_params {
    vec3 colors[16];
    vec2 zeroes[16];
};

would compile to:

SOKOL_SHDC_ALIGN(16) typedef struct fs_params_t {
    float colors[16][3];
    uint8_t _pad_192[64];
    float zeroes[16][2];
} fs_params_t;

which would then result in the following warning when run in the debugger:

 warning: [sg][error][id:275] ./sokol_gfx.h(16376):
        VALIDATE_AUB_SIZE: sg_apply_uniforms: data size doesn't match declared uniform block size

warning: [sg][panic][id:285] ./sokol_gfx.h(15556):
        VALIDATION_FAILED: validation layer checks failed

ABORTING because of [panic]

Worse still sometimes this error does not trigger but the uniforms are not all passed correctly:

uniform fs_params {
    vec2 zeroes[16];
    vec4 colors[16];
};

which compiles to the following header:

SOKOL_SHDC_ALIGN(16) typedef struct fs_params_t {
    float zeroes[16][2];
    uint8_t _pad_128[128];
    float colors[16][4];
} fs_params_t;

But when I set the uniforms and render it, the uniform values are all messed up.

I noticed that when using vec4 arrays, the problem does not occur (my current workaround).

So am I doing something wrong, or is there something wrong in sdhc/sokol when calculating padding and alignment of the data structure, or are uniform arrays of types smaller than vec4 not supported? In that last case I would recommend adding a warning message to either sokol or sdhc.

Here is a full example (I modified cube-sapp.c):

//------------------------------------------------------------------------------
//  cube-sapp.c
//------------------------------------------------------------------------------
#define SOKOL_IMPL
#include "sokol_gfx.h"
#include "sokol_app.h"
#include "sokol_log.h"
#include "sokol_glue.h"
#include "dbgui.h"
#include "cube-sapp.glsl.h"
#include "sokol_time.h"

static struct {
    float rx, ry;
    sg_pipeline pip;
    sg_bindings bind;
} state;

typedef struct {
    float x;
    float y;
} Vec2;

typedef struct {
    float r;
    float g;
    float b;
} Vec3;

void init(void) {
    stm_setup();
    sg_setup(&(sg_desc){
        .environment = sglue_environment(),
        .logger.func = slog_func,
    });
    __dbgui_setup(sapp_sample_count());
    float vertices[] = {
        -1.0, -1.0,  0.0f,  0.0f,
         1.0, -1.0,  1.0f,  0.0f,
         1.0,  1.0,  1.0f,  1.0f,
        -1.0,  1.0,  0.0f,  1.0f,
    };
    int vertices_stride = 2*4 + 2*4;
    sg_buffer vbuf = sg_make_buffer(&(sg_buffer_desc){
        .data = SG_RANGE(vertices),
        .label = "rect-vertices",
        .usage = SG_USAGE_IMMUTABLE,
    });
    uint16_t indices[] = {
        0, 1, 2,  0, 2, 3,
    };
    sg_buffer ibuf = sg_make_buffer(&(sg_buffer_desc){
        .type = SG_BUFFERTYPE_INDEXBUFFER,
        .data = SG_RANGE(indices),
        .usage = SG_USAGE_IMMUTABLE,
        .label = "rect-indices"
    });

    /* create shader */
    sg_shader shd = sg_make_shader(cube_shader_desc(sg_query_backend()));

    /* create pipeline object */
    state.pip = sg_make_pipeline(&(sg_pipeline_desc){
        .layout = {
            /* test to provide buffer stride, but no attr offsets */
            .buffers[0].stride = vertices_stride,
            .attrs = {
                [ATTR_vs_position].format = SG_VERTEXFORMAT_FLOAT2,
                [ATTR_vs_texcoord0].format = SG_VERTEXFORMAT_FLOAT2,
            }
        },
        .shader = shd,
        .index_type = SG_INDEXTYPE_UINT16,
        .cull_mode = SG_CULLMODE_NONE,
        .label = "rect-pipeline"
    });

    /* setup resource bindings */
    state.bind = (sg_bindings) {
        .vertex_buffers[0] = vbuf,
        .index_buffer = ibuf
    };
}

void frame(void) {
    /* NOTE: the vs_params_t struct has been code-generated by the shader-code-gen */
    const float w = sapp_widthf();
    const float h = sapp_heightf();
    const float t = stm_sec(stm_now());

    sg_begin_pass(&(sg_pass){
        .action = {
            .colors[0] = {
                .load_action = SG_LOADACTION_CLEAR,
                .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f },
            },
        },
        .swapchain = sglue_swapchain()
    });

    #define DARK_RED {0.47f, 0.02f, 0.02f, 1.0f} 
    #define DARK_GREY {0.15f, 0.15f, 0.15f, 1.0f} 
    #define GOLD {0.9f, 0.77f, 0.2f, 1.0f}
    #define BLUE {0.0f, 0.0f, 1.0f, 1.0f}
    #define WHITE {1.0f, 1.0f, 1.0f, 1.0f}
    fs_params_t fs_params = {
        .zeroes = {
            {0.1, 0.1},
            {0.2f, 0.1f},
            {0.3f, 0.1},
            {0.4f, 0.1f},

            {-0.1f, 0.5f},
            {-0.1f, 0.6f},
            {-0.1f, 0.7f},
            {-0.1f, 0.8f},

            {0.7f, -0.7f},
            {0.73f, -0.73f},
            {0.76f, -0.76f},
            {0.79f, -0.79f},

            {+1.0f, +1.0f},
            {-1.0f, -1.0f},
            {+1.0f, -1.0f},
            {-1.0f, +1.0f},
        },
        .colors = {
            DARK_GREY,
            DARK_GREY,
            DARK_GREY,
            DARK_GREY,

            DARK_GREY,
            DARK_GREY,
            DARK_GREY,
            DARK_GREY,

            WHITE,
            DARK_RED,
            WHITE,
            DARK_GREY,

            GOLD,
            GOLD,
            GOLD,
            GOLD,
        },
    };

    sg_apply_pipeline(state.pip);
    sg_apply_bindings(&state.bind);
    sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_fs_params, &SG_RANGE(fs_params));
    sg_draw(0, 6, 1);
    __dbgui_draw();
    sg_end_pass();
    sg_commit();
}

void cleanup(void) {
    __dbgui_shutdown();
    sg_shutdown();
}

sapp_desc sokol_main(int argc, char* argv[]) {
    (void)argc;
    (void)argv;
    return (sapp_desc){
        .init_cb = init,
        .frame_cb = frame,
        .cleanup_cb = cleanup,
        .event_cb = __dbgui_event,
        .width = 800,
        .height = 600,
        .sample_count = 4,
        .window_title = "Newtonian Pong",
        .icon.sokol_default = true,
        .logger.func = slog_func,
    };
}

with the following glsl

@vs vs
in vec2 position;
in vec2 texcoord0;
out vec2 texcoord;

void main() {
    gl_Position = vec4(position, 0.0f, 1.0f);
    texcoord = texcoord0;
}
@end

@fs fs
uniform fs_params {
    vec2 zeroes[16];
    vec4 colors[16];
};
out vec4 fragColor;
in vec2 texcoord;

void main() {
    vec2 z = vec2(texcoord.x* 2.0 - 1.0, texcoord.y*2.0-1.0);
    float threshold2 = 0.05;
    vec4 color;
    if (length(z-zeroes[0]) < threshold2) {
        color = colors[0];
    }
    else if (length(z-zeroes[1]) < threshold2) {
        color = colors[1];
    }
    else if (length(z-zeroes[2]) < threshold2) {
        color = colors[2];
    }
    else if (length(z-zeroes[3]) < threshold2) {
        color = colors[3];
    }
    else if (length(z-zeroes[4]) < threshold2) {
        color = colors[4];
    }
    else if (length(z-zeroes[5]) < threshold2) {
        color = colors[5];
    }
    else if (length(z-zeroes[6]) < threshold2) {
        color = colors[6];
    }
    else if (length(z-zeroes[7]) < threshold2) {
        color = colors[7];
    }
    else if (length(z-zeroes[8]) < threshold2) {
        color = colors[8];
    }
    else if (length(z-zeroes[9]) < threshold2) {
        color = colors[9];
    }
    else if (length(z-zeroes[10]) < threshold2) {
        color = colors[10];
    }
    else if (length(z-zeroes[11]) < threshold2) {
        color = colors[11];
    }
    else if (length(z-zeroes[12]) < threshold2) {
        color = colors[12];
    }
    else if (length(z-zeroes[13]) < threshold2) {
        color = colors[13];
    }
    else if (length(z-zeroes[14]) < threshold2) {
        color = colors[14];
    }
    else if (length(z-zeroes[15]) < threshold2) {
        color = colors[15];
    }
    else {
        color = vec4(0.0, 0.0, 0.0, 1.0);
    }
    fragColor = color;
}
@end

@program cube vs f

build with the following batchfile:

call sokol-shdc.exe -i cube-sapp.glsl -o cube-sapp.glsl.h -l glsl330
call clang -o cube-sapp.exe -DSOKOL_GLCORE33 cube-sapp.c

Which renders like this (not what I expected): Screenshot 2024-03-15 002248

floooh commented 3 months ago

Hmm, yeah, looks like bugs in the sokol-shdc reflection parser code. The padding values are all messed up, and what's also strange is that float, vec2, vec3 arrays shouldn't be allowed at all (only vec4 and mat4).

floooh commented 3 months ago

...hmm, when I try to put a vec3 array into a uniform block I get this (expected) error:

cube-sapp.glsl:0:0: error: uniform block 'vs_params': arrays must be of type vec4[], ivec4[] or mat4[]

...I wonder how the float, vec2 and vec3 arrays actually make it through the shader compiler. Can you paste your complete shader code here?

Do you use the latest version of sokol-shdc?

floooh commented 3 months ago

PS: this was the commit which added those stricter array requirements and error checks (from Jan-2022):

https://github.com/floooh/sokol-tools/commit/54a99d8370ab280173d71d02d4fbe5a0e43fd31a

OetkenPurveyorOfCode commented 3 months ago

I thought I was, but maybe I used the wrong download link, I could not find a version info commandline flag for sokol-sdhc.exe.

But when I downloaded sokol-sdhc.exe from https://github.com/floooh/sokol-tools-bin/blob/master/bin/win32/sokol-shdc.exe again I get the correct error message: cube-sapp.glsl:0:0: error: uniform block 'fs_params': arrays must be of type vec4[], ivec4[] or mat4[]

So I guess it's my bad for downloading an old version and humbly ask for forgiveness.

floooh commented 3 months ago

No problem! The easiest way to get the binaries is to simply clone the repository btw:

git clone --depth=1 https://github.com/floooh/sokol-tools-bin