Hi,
First of all, thanks so much for the library!
I am currently learning how to use it by writing a simple mesh-viewer application, however I am running into trouble when submitting my vertex data. In my application I am keeping my vertex positions and vertex normals as separate buffers, however all examples showcase interleaved vertex data.
Is there a way to inform sokol about non-interleaved vertex data? (VVVNNN) vs (VNVNVN)
OpenGL pseudocode snipped to show what I am trying to achieve:
glBufferSubData( GL_ARRAY_BUFFER, 0, mesh->n_vertices*3*sizeof(float), &(mesh->positions[0]) );
glEnableVertexAttribArray( 0 );
glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glBufferSubData( GL_ARRAY_BUFFER, 3 * sizeof(float) * mesh->n_vertices, 3 * sizeof(float) * mesh->n_vertices, &(mesh->normals[0]) );
glEnableVertexAttribArray( 1 );
glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 0, (void*) ( 3 * sizeof(float) * mesh->n_vertices) );
Yes you can spread vertex components over up to 4 input vertex buffers (I guess I should add a sample for that)... The instancing sample is pretty close (https://github.com/floooh/sokol-samples/blob/master/glfw/instancing-glfw.c), but instead of creating one immutable and one dynamic vertex buffer you would create 2 (or more) immutable vertex buffers.
PS: the example at the end is closer to your GL code, since it keeps the vertex components in the same buffer...
The steps are basically:
draw_state.pipeline = sg_make_pipeline(&(sg_pipeline_desc){
.layout = {
.attrs = {
/* position from vertex buffer in slot 0*/
[0] = { .name="pos", .format=SG_VERTEXFORMAT_FLOAT3, .buffer_index=0 },
/* normals from vertex buffer in slot 1 */
[1] = { .name="norm", .format=SG_VERTEXFORMAT_FLOAT3, .buffer_index=1 }
}
},
...
(if there are gaps between vertex components, you also need to provide per-buffer-strides in the sg_pipeline_desc.layout.buffers array, like here: https://github.com/floooh/sokol-samples/blob/bc05b045d29977708b03a9d5ad78534c690b3fb1/glfw/instancing-glfw.c#L119)
sg_draw_state, put the 2 buffers into the resource binding slots used in the vertex layout description in sg_pipeline_desc abovesg_draw_state ds = {
.vertex_buffers = {
[0] = pos_buffer,
[1] = normals_buffer,
},
.index_buffer = ...
};
...and that should be it! (again, the instancing sample has all the needed stuff, if I missed anything here).
Since a few days ago, you can also put the vertex component data in chunks into the same buffer (e.g. first all position components, followed by all normals)... You would then put the same buffer into the resource binding slots, but use offsets to tell sokol where the input data starts, e.g.:
sg_draw_state ds = {
.vertex_buffers = {
/* put the same buffer into both input slots... */
[0] = vertex_buffer,
[1] = vertex_buffer,
},
.vertex_buffer_offsets = {
[0] = 0, /* positions come first */
[1] = normals_offset, /* byte offset to start of normal components */
},
.index_buffer = ...
};
Alright, here's a new sample which demonstrates this: https://github.com/floooh/sokol-samples/blob/master/glfw/noninterleaved-glfw.c, this is for GL+GLFW. The other platforms are coming right up. D3D11 only next week though because I don't have access to a PC over the weekend :)
This is excellent, thank you so much! .buffer index was the parameter I was missing - did not expect to find it in the instancing sample, should've checked that.
Since the issue is already open - would you mind elaborating on why there's limitation on having 4 separate buffers? Is that just something that could be controlled by a #define?
The reason for 4 bind slots is that I was not thinking of non-interleaved vertex layout, but more for cases like instanced rendering, or mixing static and dynamic vertex data. In those situations the number of slots usually doesn't have to be so high. With non-interleaved vertex components in mind the max number of bind slots should probably be 16 (that's the max number of vertex attributes in many GL implementations), but it would mean lots of 'dead weight' in the sg_draw_state struct for typical use cases. I guess I'll bump the number of slots to 8 or so for now, and see if this is sufficient for most use cases...
I've been thinking about controlling this with a define, but there's another problem with this... currently all configuration defines only affect the implementation part, so that those configuration defines are only set in the one place where SOKOL_IMPL is defined (and that's how it should be).
But for the sg_draw_state struct the define would need to be set everywhere where sokol_gfx.h is included, not only in the one place where SOKOL_IMPL is defined... or the define would need to be provided as compiler command line arg, so that the configuration would leak into the build system. Both cases are pretty bad :)
What would work is to change the define right in your own copy of the sokol_gfx.h header for your needs, maybe that's the best option...
Thanks for the exhaustive reply! The cases I was considering was exactly the mixing of dynamic and immutable vertex data, and I guess I was lazy to just keep >4 separate vertex attrib arrays in some of my code. Thanks again, this was quite educational :)