floooh / sokol

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

[sokol_gfx] Array of struct uniform #1002

Closed mathewmariani closed 6 months ago

mathewmariani commented 9 months ago

I've been looking around for more information on passing an array of structs to my shader, but can't find a lot of information on the subject. I should mention that I'm working with OpenGLES for the time being, and I'm not using the sokol-shdc shader compiler tool.

Here is my structure in both C++ and GLSL.

struct pointlight_t
{
  float radius;
  glm::vec3 color;
  glm::vec3 position;
};
static pointlight_t light_data[MAX_LIGHTS];
struct PointLight {
  float radius;
  vec3 color;
  vec3 position;
};
uniform PointLight[MAX_LIGHTS] lights;

I'm not sure if this is completely correct but this is how I'm describing the uniform block in my sg_shader_desc. At this point, I will add that I have no compile or runtime errors.

// [...]
.uniform_blocks[1] = {
    .layout = SG_UNIFORMLAYOUT_NATIVE,
    .size = sizeof(pointlight_t) * MAX_LIGHTS,
    .uniforms = {
        [0] = {.name = "light.radius", .type = SG_UNIFORMTYPE_FLOAT, .array_count = MAX_LIGHTS},
        [1] = {.name = "light.color", .type = SG_UNIFORMTYPE_FLOAT3, .array_count = MAX_LIGHTS},
        [2] = {.name = "light.position", .type = SG_UNIFORMTYPE_FLOAT3, .array_count = MAX_LIGHTS},
    },
},

// [...]
sg_apply_uniforms(SG_SHADERSTAGE_FS, 1, SG_RANGE(light_data));

I'm not exactly sure what is the proper way to approach this using sokol_gfx. There are no build or runtime errors, yet I don't seem to be getting any data passed to the shader. I wasn't able to find a whole lot of information about this, hopefully, you can clarify my understanding.

floooh commented 9 months ago

Hmm... without using the sokol-shdc compiler that's most likely not possible in the GL backend. You would need to convert the struct array into a plain vec4 array.

With the sokol-shdc compiler this conversion happens automatically for the GL backend (e.g. uniform blocks are flattened into a vec4 array), and then something like this could work. TBH I never tried that though.

uniform vs_params {
    PointLight lights[MAX_LIGHTS];
};

PS: maybe it doesn't work though... sokol-shdc has restrictions on what can be declared as an array because that uniform block must also be mapped to a matching C struct, and that's gnarly because of std140 alignment and padding rules, so I'm allowing arrays only for floats, vec2, vec4 and mat4.

I would probably recommend a 'structure of arrays' approach instead:

uniform vs_params {
    float light_radii[MAX_LIGHTS];
    vec4 light_colors[MAX_LIGHTS];
    vec4 light_positions[MAX_LIGHTS];
};

(vec4 instead of vec3 because of the above mentioned alignment restrictions - this is all assuming sokol-shdc though).

Without sokol-shdc this should also work for regular uniforms, but you'd need to experiment..

floooh commented 6 months ago

The recent storage buffer support might make this a little bit less awkward since storage buffer array items have much fewer memory layout restrictions (the restrictions for uniforms need to remain in place however as long as OpenGL is supported).