Do silly constructs like “classes” sometimes get in the way of your programming? Are loosely typed languages “too loose” for you? Well boy, do I have a blog post for you! I was fiddling around with my OpenGL game engine and decided to make a container that can store and run custom functions on any object type.
The Background
In OpenGL, you feed variables to the shader by making calls like this:
// mat4
glm::mat4 toWorld = glm::translate(glm::mat4(1.0f), position);
GLuint matrixid = glGetUniformLocation(shaderProgram, "model");
glUniformMatrix4fv(matrixid, 1, GL_FALSE, &toWorld[0][0]);
// vec3
glm::vec3 red(1.0f, 0.0f, 0.0f);
GLuint colorId = glGetUniformLocation(shaderProgram, "color");
glUniform3fv(colorId, 1, &red[0]);
// int
int width = 1920;
GLuint texWidthId = glGetUniformLocation(shaderProgram, "width");
glUniform1i(texWidthId, width);
// float
float materialShine = 0.7f;
GLuint matShineId = glGetUniformLocation(shaderProgram, "shine");
glUniform1f(matShineId, materialShine);
// bool (I know, weird)
int usesTexture = 1; // true
GLuint usesTexId = glGetUniformLocation(shaderProgram, "usesTexture");
glUniform1i(usesTexId, usesTexture);
In order to keep my code base tidy, I wanted to isolate this process of setting “uniforms” into a single function. I quickly realized that, unless I wanted to overload the crap out of my Asset::Render
function signature, I would need something generic to pass into the function, which it could understand and be able to set shader uniforms, uhmm… uniformly.
The Goal
I want a class that can be used as a single parameter in my Asset::Render
function.
This class contains an array of objects that are instances of multiple different types.
This class should be easy to add to, and easy to process. I don’t want a huge if/else if
block that checks the type of the object, and then runs the right glUniform*
function call.
The Design
Let’s start by making a simple non-templated class. Our container of objects will consist of pointers to this type
class UniformWrapper
{
public:
UniformWrapper(const std::string& uniformName) : m_uniformName(uniformName) {};
virtual void SetUniform(GLuint shaderProgram) const = 0;
protected:
std::string m_uniformName;
};
Okay, now let’s make the templated version, which stores an arbitrary data type and defines the per-type SetUniform
function:
template<typename T>
class TemplatedUniformWrapper : public UniformWrapper
{
public:
TemplatedUniformWrapper(const std::string& uniformName, const T& data)
: UniformWrapper(uniformName)
, m_data(data) {};
void SetUniform(GLuint shaderProgram) const override;
private:
const T& m_data;
};
Finally, let’s make a class that can store many uniform wrappers. (I used a vector as a container, you can use anything you want!)
class UniformContainer
{
public:
template<typename T>
void AddObject(const std::string& uniformName, const T& obj)
{
m_vector.push_back(new TemplatedUniformWrapper<T>(uniformName, obj));
};
void SetUniforms(GLuint shaderProgram) const;
private:
std::vector<UniformWrapper*> m_vector;
};
The Usage
Now we just have to make template specializations for SetUniform
. In a separate cpp file, just write out the implementation for various supported types:
// mat4
void TemplatedUniformWrapper<glm::mat4>::SetUniform(unsigned int shaderProgram) const
{
GLuint uniformIntId = glGetUniformLocation(shaderProgram, m_uniformName.c_str());
glUniformMatrix4fv(uniformIntId, 1, GL_FALSE, &m_data[0][0]);
}
// vec3
void TemplatedUniformWrapper<glm::vec3>::SetUniform(unsigned int shaderProgram) const
{
GLuint uniformVec3Id = glGetUniformLocation(shaderProgram, m_uniformName.c_str());
glUniform3fv(uniformVec3Id, 1, &m_data[0]);
}
// int
void TemplatedUniformWrapper<int>::SetUniform(unsigned int shaderProgram) const
{
GLuint uniformIntId = glGetUniformLocation(shaderProgram, m_uniformName.c_str());
glUniform1i(uniformIntId, m_data);
}
// float
void TemplatedUniformWrapper<float>::SetUniform(unsigned int shaderProgram) const
{
GLuint uniformFltId = glGetUniformLocation(shaderProgram, m_uniformName.c_str());
glUniform1f(uniformFltId, m_data);
}
// bool (I know, weird)
void TemplatedUniformWrapper<bool>::SetUniform(unsigned int shaderProgram) const
{
int isTrue = m_data ? 1 : 0;
GLuint uniformBoolId = glGetUniformLocation(shaderProgram, m_uniformName.c_str());
glUniform1i(uniformBoolId, isTrue);
}
Done! That wasn’t so painful, right? Now, here’s how to use this new class:
UniformContainer uniforms;
uniforms.AddObject("objCenter", glm::vec3(1.0f, 3.0f, 5.0f));
uniforms.AddObject("animationFrame", 2);
m_asset->Render(uniforms);
Inside of Asset::Render
, add a call to uniforms.SetUniforms()
, and you’re ready to roll.
Reinventing the Wheel?
If you want a typeless container, why not just use an std::vector<void*>
or boost::any?
The problem with void*
is that it’s too loosely typed. After casting something to a void*
it’s virtually impossible to get the original object type. At that point, I wouldn’t be able to run specialized functions based on the object’s type.
I personally have an aversion of any kind of if (type1) doStuff(); else if (type2) doOtherStuff();
code, and if I wanted to iterate through an std::vector<boost::any>
, some form of that code would be implemented.
More importantly, I enjoy making these things. Even if there is a better, more optimal out-of-the-box implementation, I get those sweet sweet dopamine hits when I get stuff like this actually working.