Organizing Shaders with C++ Templates

Igor Pener
Aug 9, 2017 · 3 min read

If you’ve written an OpenGL, DirectX, Metal, or Vulkan application, you know that there’s always a ton of boilerplate code to write. Even to get the simplest things working like loading shaders, generating textures and so on. Of course this is by design because these libraries try to be as generic as possible, allowing for low-level use. However, writing a lot of repetitive code convolutes a project, makes it less readable and hence slows down development.
This is where C++ templates come in handy. The concept of compile-time code generation exists in many object oriented languages but I would argue that templates in C++ are by far the most powerful one. So let’s take a look at how to organize GLSL shaders using some advanced templating tricks.
Typically, shader programs (and actually all kinds of objects) within any graphics framework are referred to by a simple ID (GLuint program in our case). Also, the CPU usually submits data like modelview-projection matrices, color vectors, samplers, etc. to shader programs before executing them.
These data chunks are called uniforms and they are identified by a simple ID as well (GLuint uniform). Uniform IDs are a mapping to variable names in the shader code so one has to retrieve them during the shader initialization after the shaderes are compiled.
The first observation is that we could create a simple structure representing a uniform, which creates the before mentioned mapping in its constructor. Since we’d need the same kind of structure but with a different name per uniform we can do some pre-processor magic:

#define UNIFORM(uniform)\
struct uniform##_t {\
GLuint uniform = 0;\
protected:\
uniform##_t(GLuint program) {\
uniform = glGetUniformLocation(program, #uniform);\
}\
};

This is one way of generating code at pre-processing time. Passing a parameter to this UNIFORM macro, the pre-processor expands this piece of code, but with all instances of uniform litteraly replaced with the parameter name. The # operator makes whatever comes after it a string and the ## operator concatenates whatever characters are before and after it. So writing UNIFORM(color) expands to:

struct color_t {
GLuint color = 0;
protected:
color_t(GLuint program) {
color = glGetUniformLocation(program, "color");
}
};

Now we can simply write:

namespace uniforms {
UNIFORM(mvp_mat);
UNIFORM(color);
UNIFORM(sampler);
}

After pre-processing, we’d have the three structures: mvp_mat_t, color_t, and sampler_t. Adding new uniforms is just one line of code. There is a reason why we call getUniformLocation() in the constructor. Imagine we have a shader which needs all these uniforms, so we could create another structure uniforms_t, which inherits from all these uniforms:

struct uniforms_t : mvp_mat_t, color_t, sampler_t {
uniforms_t(GLuint program) :
mvp_mat_t(program), color_t(program), sampler_t(program) {}
};

The cool thing about this is that when we instantiate uniforms_t, we would invoke the constructors of its base classes and hence load the uniforms. The problem is, however, that a graphics application like a game or some modelling software has dozens of uniforms and hundreds of shaders which can all use many different uniform combinations.
And this is where variadic C++ templates come in handy. We can create a template class GLShaders which has a variable number of template arguments. These arguments will be the uniforms this shader program needs.

template<typename... T>
class GLShaders {
struct uniforms_t : T... {
uniforms_t(GLuint program) : T(program)... {}
};

public:
~GLShaders() {
if (uniforms) {
delete uniforms;
}
}

void getUniforms() {
if (program) {
uniforms = new uniforms_t(program);
}
}

uniforms_t *uniforms = nullptr;
GLuint program = 0;
};

Whenever getUniforms() is called uniforms_t is instantiated, which invokes its respective base constructors. Note that we use the powerful C++ feature called parameter pack expansion. What’s happening here is that a pattern followed by ... expands into comma-seperated instantiations of this pattern. This is quite a useful technique. Now we can create shaders with any number of uniforms like that:

using namespace uniforms;

GLShaders<mvp_mat_t, color_t> color_shaders;
GLShaders<mvp_mat_t, sampler_t> texture_shaders;
GLShaders<mvp_mat_t, sampler_t, color_t> blend_shaders;

There is one more advantage this method has. Because of the code generation, our uniform structures have an ID which corresponds to the structure name. Therefore, code completion in pretty much any IDE will show you which uniforms are available for a particular shader program. After several rounds of refactoring we came up with this pattern during the development of Shnips, our iOS and Android game. It significantly reduced the amount of code we had to write but also helped prevent bugs caused by typos when calling glGetUniformLocation(program, "color") and passing the wrong ID or string. Enjoy coding and don’t forget: the best code is the one you don’t have to write!

 by the author.

Igor Pener

Written by

Founder / CEO of techOS. Built Shnips, a mobile game for iOS and Android. Computer graphics 👾, AR 👓, AI 🧠.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade