Minor Sokol API Updates
I’ve been integrating some valuable Twitter-verse feedback into sokol_gfx.h and with this some things I wrote in the last post are no longer true, here’s what has changed:
No more universal resource id type
The universal resource handle sg_id has changed into one type per resource: sg_buffer, sg_image, sg_shader, sg_pipeline and sg_pass. With this the compiler can now do proper type checking on resource ids and will complain at compile-time if the wrong resource type is accidently used somewhere.
No more dual color/depth-stencil images
Previously it was possible to create an image as render target which could have both a color-buffer and depth-stencil-buffer, the same object would then be plugged into a color-attachment-, and the depth-stencil-attachment-slot of a pass object.
Now an image can either be a color-render-target, or a depth-stencil- render-target, but not both at the same time.
Zero-init and C99 designated initializers
This is the most interesting change:
Previously, desc-structures had to be initialized to a default state by calling a special initializer function, which basically implemented the ‘missing C++ constructor’.
There are 2 changes related to initialization:
- all initializer functions have been removed
- a zero-initialized structure is now considered to be in ‘default state’
This means:
/* instead of this: */
sg_buffer_desc buf_desc;
sg_init_buffer_desc(&buf_desc);
/* ...simply do this: */
sg_buffer_desc buf_desc = { 0 };
But that’s not all, using zero-initialized fields as ‘default’ means that C99 designated initializers can be used, enabling an ‘option bag’-style initialization popular in more dynamic languages like Javascript or Typescript:
/* instead of this: */
sg_buffer_desc buf_desc;
sg_init_buffer_desc(&buf_desc);
buf_desc.type = SG_BUFFERTYPE_INDEXBUFFER;
buf_desc.size = sizeof(indices);
buf_desc.data_ptr = indices;
buf_desc.data_size = sizeof(indices);
/* ...you can now do this: */
sg_buffer_desc buf_desc = {
.type = SG_BUFFERTYPE_INDEXBUFFER,
.size = sizeof(indices),
.data_ptr = indices,
.data_size = sizeof(indices)
};
Missing fields in such a designated initializer block will be set to 0 (that’s why treating the value 0 as default is so important).
It’s also possible to put the entire struct initialization into the resource creation function call:
sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){
.type = SG_BUFFERTYPE_INDEXBUFFER,
.size = sizeof(indices),
.data_ptr = indices,
.data_size = sizeof(indices)
});
This basically gives you optional, named parameters in C. Pretty cool huh? Nearly feels like a whole new language :)
If used extensively the initialization code can look very ‘declarative’. Whether this is always a good idea remains to be seen, but the nice thing is that nothing in the sokol_gfx API itself ‘enforces’ this style.
For instance here is an example from the triangle sample which sets up a complete draw-state from a single nested struct initialization (note the 3 embedded calls to resource creation functions, these are a bit easy to overlook but important to understand what’s going on):
sg_draw_state draw_state = {
.pipeline = sg_make_pipeline(&(sg_pipeline_desc){
.vertex_layouts[0] = {
.stride = 28,
.attrs = {
[0] = { .name="position", .offset=0, .format=SG_VERTEXFORMAT_FLOAT3 },
[1] = { .name="color0", .offset=12, .format=SG_VERTEXFORMAT_FLOAT4 }
}
},
.shader = sg_make_shader(&(sg_shader_desc){
.vs.source =
"#version 330\n"
"in vec4 position;\n"
"in vec4 color0;\n"
"out vec4 color;\n"
"void main() {\n"
" gl_Position = position;\n"
" color = color0;\n"
"}\n",
.fs.source =
"#version 330\n"
"in vec4 color;\n"
"out vec4 frag_color;\n"
"void main() {\n"
" frag_color = color;\n"
"}\n"
})
}),
.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(vertices),
.data_ptr = vertices,
.data_size = sizeof(vertices)
})
};
Again, you don’t have to use this style, it’s just a C99 language feature, and (apart from treating the zero-init state as default), isn’t “encoded” into the sokol_gfx.h API.
In C++ this is not yet possible across all compilers (a slightly more restrictive version of C99 designated initializers are on the roadmap for C++20), so I may add back a handful of helper functions for the vertex- and uniform-declarations (which are a bit inconvenient to setup without designated initializers).