floooh / sokol

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

How do I provide an sg_image as a uniform so that I can reuse vertex buffers? #870

Closed Ashe closed 1 year ago

Ashe commented 1 year ago

Hello, sorry for the beginnery question.

I'm trying to make a simple sprite pipeline; from your samples it's been trivial so far to get something rendered on screen, but I'm struggling to deviate from the way the samples are set up.

From [the loadpng sample])https://github.com/floooh/sokol-samples/blob/578d71daed073dbeb2e5e3d74a3485cf46733cd7/sapp/loadpng-sapp.c#L176)

static void fetch_callback(const sfetch_response_t* response) {
            // ok, time to actually initialize the sokol-gfx texture
            sg_init_image(state.bind.fs.images[SLOT_tex], &(sg_image_desc){
                .width = png_width,
                .height = png_height,
                .pixel_format = SG_PIXELFORMAT_RGBA8,
                .data.subimage[0][0] = {
                    .ptr = pixels,
                    .size = (size_t)(png_width * png_height * 4),
                }
            });

when the image is loaded, you directly load it into the bindings struct. I'm not sure though how you'd do this if for example you wanted to render multiple images that all use the same vertex buffers. In my mind you'd either have to:

  1. Change the data in the bindings before calling sg_apply_bindings, or
  2. Do sg_apply_uniforms and handle uniforms like you do with vs_params.

I can't seem to work out which the best way forward is; I'd rather not have to mutate my bindings constantly, when I try this

  sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_tex, &SG_RANGE(*texture));

I get

[sg][error][id:222] /nix/store/7a6vvs5ckl36g98bq3gsqr5xh4zikn8c-source/sokol_gfx.h:15289:0: 
        VALIDATE_AUB_NO_UB_AT_SLOT: sg_apply_uniforms: no uniform block declaration at this shader stage UB slot

[sg][error][id:223] /nix/store/7a6vvs5ckl36g98bq3gsqr5xh4zikn8c-source/sokol_gfx.h:15292:0: 
        VALIDATE_AUB_SIZE: sg_apply_uniforms: data size exceeds declared uniform block size

and so I'm not quite sure where I'm going wrong and what steps are needed for me to be able to have one set of vertices/indices, one shader, but multiple images.

Sorry if the answer is fairly obvious! I also wasn't sure where to ask questions like this so I hope issues is okay? Thank you for sokol!

floooh commented 1 year ago

Hi, uniform data and 'resource bindings' are different things. Basically, everything that's a buffer, image or sampler needs to be set via sg_apply_bindings(), and only "uniform data" (simple float or integer values which can be efficiently updated for each draw call) must be set with sg_apply_uniforms().

Don't worry too much about calling sg_apply_bindings() too often and with mostly similar bindings (as long as you don't do this for each individual sprite if you need to render hundreds or thousands of them, see below for 'batching').

For instance if you have 4 different textures, and only want to change those (but use the same pipeline and uniform data) you'd do something like this:

    sg_bindings bnd = {
        .vertex_buffers[0] = ...,
        .index_buffer = ...,
        .fs.samplers[0] = ...,
    };

    sg_image img0 = sg_make_image(...);
    sg_image img1 = sg_make_image(...);
    sg_image img2 = sg_make_image(...);
    sg_image img3 = sg_make_image(...);

    sg_apply_pipeline(...);
    sg_apply_uniforms(...);

    bnd.fs.images[0] = img0;
    sg_apply_bindings(&bnd);
    sg_draw(...)

    bnd.fs.images[0] = img1;
    sg_apply_bindings(&bnd);
    sg_draw(...)

    bnd.fs.images[0] = img2;
    sg_apply_bindings(&bnd);
    sg_draw(...)

    bnd.fs.images[0] = img3;
    sg_apply_bindings(&bnd);
    sg_draw(...)

...for a small number of draws this is completely fine, but if it would become hundreds or thousands of draw calls you should still try to implement some sort of batching to minimizes texture changes and draw calls (e.g. don't call sg_draw() for each single sprite, only when the texture needs to change, and don't keep each individual sprite in its own texture, instead merge many sprite images into a single "texture atlas" image).

For sprite rendering I would actually recommend starting with sokol_gl.h, this has a simple form of batching implemented already (e.g. see https://github.com/floooh/sokol/blob/47d92ff86298fc96b3b84d93d0ee8c8533d3a2d2/util/sokol_gl.h#L571-L591)

PS: edited this comment with lots of typo fixes.

Ashe commented 1 year ago

Thank you for clearing this up! Not that hard to implement either. Great advice as usual :)