floooh / sokol-samples

Sample code for https://github.com/floooh/sokol
MIT License
615 stars 79 forks source link

Example of multiple VBOs #139

Closed hmih closed 6 months ago

hmih commented 6 months ago

Hello,

I'm learning graphics programming and reading up on OpenGL concepts. I'm using sokol to recreate Pong. I'm using the Rust bindings and I'm rendering two rectangles on the screen. I see that all tutorials bind a VBO to vertex_buffers[0] but I want to try a separate VBO per rectangle. When I try that I get the following error:

VALIDATE_ABND_VBS: sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts

Here is the corresponding code of my init function:

extern "C" fn init() {
    let state: &mut State = unsafe { &mut (*addr_of_mut!(STATE)) };
    let players: &mut Entities = unsafe { &mut (*addr_of_mut!(PLAYERS)) };

    sg::setup(&sg::Desc {
        environment: sglue::environment(),
        logger: sg::Logger {
            func: Some(sokol::log::slog_func),
            ..Default::default()
        },
        ..Default::default()
    });

    let vertices = to_vertices(players);
    for (ix, vs) in vertices.iter().enumerate() {
        let label = CString::new(format!("squares-vs{}", ix)).expect("no way");
        state.bind.vertex_buffers[ix] = sg::make_buffer(&sg::BufferDesc {
            data: sg::slice_as_range(vs.as_slice()),
            _type: sg::BufferType::Vertexbuffer,
            label: label.as_ptr(),
            ..Default::default()
        });
    }

    let indices = to_indices();
    state.bind.index_buffer = sg::make_buffer(&sg::BufferDesc {
        data: sg::slice_as_range(indices.as_slice()),
        _type: sg::BufferType::Indexbuffer,
        label: c"squares-ixs".as_ptr(),
        ..Default::default()
    });

    // shader and pipeline object
    let be = sg::query_backend();
    let shader = &shader::simple_shader_desc(be);
    state.pip = sg::make_pipeline(&sg::PipelineDesc {
        shader: sg::make_shader(shader),
        index_type: sg::IndexType::Uint32,
        layout: sg::VertexLayoutState {
            attrs: {
                let mut attrs = [sg::VertexAttrState::new(); sg::MAX_VERTEX_ATTRIBUTES];
                attrs[shader::ATTR_VS_POS0].format = sg::VertexFormat::Float2;
                attrs[shader::ATTR_VS_COL0].format = sg::VertexFormat::Ubyte4n;
                attrs
            },
            ..Default::default()
        },
        ..Default::default()
    });
}

I am wondering, is there an example of this already? Does this even make sense from an API usage perspective? I read that I can use a single VBO without an issue, then recreate it during the frame pass, when the state of my objects changes.

floooh commented 6 months ago

Here's a couple of C samples which use multiple vertex buffer bindings, but it's probably not what you want ;)

The validation error you are seeing is because in your pipeline object, all vertex attributes are pulled from the default vertex buffer bind slot 0. If you want to compose the vertex shader input vertices from vertex components pulled from different buffers, you need to add a buffer_index to your vertex attributes, like this:

    attrs[shader::ATTR_VS_POS0].format = sg::VertexFormat::Float2;
    attrs[shader::ATTR_VS_POS0].buffer_index = 0;  // take positions from vertex buffer at bind slot 0
    attrs[shader::ATTR_VS_COL0].format = sg::VertexFormat::Ubyte4n;
    attrs[shader::ATTR_VS_POS0].buffer_index = 1;  // take colors from vertex buffer at bind slot 1

...you would then need to create 2 vertex buffers, one with only vertex positions, and the other only with colors, and bind them like this:

    state.bind.vertex_buffers[0] = vertex_buffer_with_positions;
    state.bind.vertex_buffers[1] = vertex_buffer_with_colors;

The vertex shader will then be called with the vertex positions taken from the first vertex buffers, and vertex colors taken from the second vertex buffer.

does this even make sense from an API usage perspective?

...this I'm actually not sure about. Using multiple vertex buffers in a single draw call only makes sense when an input vertex to the vertex shader is composed from vertex components that are pulled from different buffers. But that doesn't fit your idea of "but I want to try a separate VBO per rectangle".

...then recreate it during the frame pass...

I would definitely try to avoid recreating sokol resource objects during the render loop. If you want to update the vertex data during rendering, use a dynamic vertex buffer created with SG_USAGE_STREAM and then call sg_update_buffer() to write new vertex data to the buffer before rendering.

But tbh, I would recommend starting with sokol_gl.h instead (also available as Rust bindings, check the sgl-* samples. sokol-gl is modelled after the OpenGL 1.x API, and for rendering 2D rectangles it's just the right thing (and also does this efficiently).