C++ Conditional Compiling Using constexpr and Templates
Written on July 9, 2020
Estimated Read Time: ~5 minutes
After another year of game industry C++ experience coupled with watching the CppCon 2017: constexpr ALL the Things! talk of Jason Turner & Ben Deane, I decided it is time to have some meta programming fun using the C++17 constexpr features while revisiting some old code and making improvements on it.
To give some context:
I’m generating some data that represents a triangle in 3D space
Consists of a collection of Vertices & Indices
In addition to the 3D position, the Vertices can have some additional attributes such as
texture coordinates (uv)
Let’s ignore the details of conversions from vec3 to float and the Mesh struct for simplicity
At the time this function was written, it was good enough to have ALL the vertices of the same data layout for the scope of the project. However, not all vertices actually needed all the attributes. This can be bad! Having redundant/unused data in the vertex buffer will lead to less optimized cache & bandwidth usage on the GPU when rendering geometry, which can result in a performance penalty depending on the application’s graphics workload.
In practice, (Positions + Texture Coordinates) would suffice while rendering a fullscreen triangle for Ambient Occlusion, Post Processing & Deferred Lighting passes. On the other hand, the Normal Vectors would be needed in addition to the (Positions + Texture Coordinates) for the lighting calculations if the triangle was being used in the 3D scene and was lit.
If we slice up the vertex data, we end up using a combination of the vertex attributes:
Now that we have different types of Vertices, we will need to use templates when representing the geometry data. While we’re at templating the vertices, why not do the same for indices too? After all, they can be either 16-bits or 32-bits wide.
Let’s define a generic data type representing the geometry:
Next, refactoring the original geometry generator function to
be generic for all Vertex and Index types by taking in template arguments
change the return type to the generic geometry data type we just defined
We can adjust the triangle generation algorithm for the generic vertex data type as follows:
assign uv texture coordinates
assign normals IF vertex has normals
assign tangents IF vertex has tangents
assign color IF vertex has color
C++17 expands the context of constexpr and makes it usable with if/else blocks to allow for conditional compilation. We can use constexpr if in conjunction with the std::is_same<T1, T2> shown earlier to make the compiler generate code based on the Vertex type provided by the template argument TVertex.
We start with defining some compile-time bool variables, then calculate the indices, position and uv as they’re shared by all the vertex types:
Finally, we can use the constexpr if for the conditional compilation.
The complete function would look like this:
Here’s an example how the caller would invoke the function with different vertex types:
This setup will work as long as the vertex buffer definitions keep the naming consistent among different types.
In other words, there cannot be a vertex type with float position and another with float pos
With this small constraint, the compiler will generate code based on the vertex type, resulting in a more flexible geometry generator and I find this pretty neat!