HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Type safe program uniform manipulation in OpenGL

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
programtypeuniformmanipulationsafeopengl

Problem

I've made an attempt at writing class wrappers around basic OpenGL objects to make managing them easier and more intuitive. Writing a generic one for program uniforms proved to require a little bit more effort than the other objects, due to there being different OpenGL functions for manipulating uniforms of different types. So, I came upon this solution, using templates and inheritance:

```
/*
An attempt at type-safe OpenGL shader uniform manipulation

_UniformBase forms the base class that the implementation for each data type derives from
*/
template
class _UniformBase
{
protected:
// Each program-uniform pair is stored in a forward_list
std::forward_list> Uniforms;

virtual void SetUniform(const T&, const GLint&) const = 0; // Must be implemented in child classes
virtual void SetUniform_DSA(const T&, const GLuint&, const GLint&) const = 0;

public:
// I don't see the necessity of a copy constructor with the way that I use this class
_UniformBase() {}
virtual ~_UniformBase() {}

// Gets a handle to the OpenGL uniform, given the program and the uniform name
int Register(_Program Program_in, const char *Name_in)
{
GLint Location;
GLuint Program = Program_in.GetHandle();
if((Location = glGetUniformLocation(Program, Name_in)) == -1) return 1;
Uniforms.push_front(std::pair(Program, Location));
return 0;
}

// Clears out the uniform handle list...haven't used it at all
void ClearUniforms()
{
Uniforms.clear();
}

// Sets all stored uniforms to Data_in
void SetData(const T &Data_in)
{
// Can use GL_EXT_direct_state_access extension or plain OpenGL
// Guess which one I like better
#ifdef DIRECT_STATE_ACCESS
for(auto &i : Uniforms)
{
SetUniform_DSA(Data_in, i.first, i.second);
}
#else
Data = Data_in;
GLuint Initial;
glGetIn

Solution

I decided to invert the class scheme: instead of having a _UniformBase class and having each specialization derive from it, I now have a single _Uniform class which owns a copy of a _UniformHandler class that provides the specialized uniform-setting functions. This way, I have static polymorphism without too much complication, and with the same external interface.

template 
class _UniformHandler;

template<>
class _UniformHandler
{
public:
    inline void SetUniform(const glm::mat4& Data, const GLint &Uniform_in) const
    {
        glUniformMatrix4fv(Uniform_in, 1, GL_FALSE, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::mat4& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniformMatrix4fvEXT(Program_in, Uniform_in, 1, GL_FALSE, glm::value_ptr(Data));
    }
};
template <>
class _UniformHandler
{
public:
    inline void SetUniform(const glm::vec4& Data, const GLint &Uniform_in) const
    {
        glUniform4fv(Uniform_in, 1, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::vec4& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform4fvEXT(Program_in, Uniform_in, 1, glm::value_ptr(Data));
    }
};

template <>
class _UniformHandler
{
public:
    inline void SetUniform(const glm::vec3& Data, const GLint &Uniform_in) const
    {
        glUniform3fv(Uniform_in, 1, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::vec3& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform3fvEXT(Program_in, Uniform_in, 1, glm::value_ptr(Data));
    }
};

template <>
class _UniformHandler
{
public:
    inline void SetUniform(const glm::vec2& Data, const GLint &Uniform_in) const
    {
        glUniform2fv(Uniform_in, 1, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::vec2& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform2fvEXT(Program_in, Uniform_in, 1, glm::value_ptr(Data));
    }
};

template <>
class _UniformHandler
{
public:
    inline void SetUniform(const float& Data, const GLint &Uniform_in) const
    {
        glUniform1fv(Uniform_in, 1, &Data);
    }
    inline void SetUniform_DSA(const float& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform1fvEXT(Program_in, Uniform_in, 1, &Data);
    }
};

template <>
class _UniformHandler
{
public:
    inline void SetUniform(const int& Data, const GLint &Uniform_in) const
    {
        glUniform1iv(Uniform_in, 1, &Data);
    }
    inline void SetUniform_DSA(const int& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform1ivEXT(Program_in, Uniform_in, 1, &Data);
    }
};

template 
class _Uniform
{
protected:
    // Each program-uniform pair is stored in a forward_list
    std::forward_list> Uniforms;
    _UniformHandler UniformHandler;

public:
    _Uniform() {}
    _Uniform(const _Uniform& cp): Uniforms(cp.Uniforms) {}
    _Uniform(_Uniform&& mv): Uniforms(std::move(mv.Uniforms)) {}
    _Uniform& operator=(const _Uniform& cp)
    {
        Uniforms = cp.Uniforms;
        return *this;
    }
    _Uniform& operator=(_Uniform&& mv)
    {
        std::swap(Uniforms, mv.Uniforms);
        return *this;
    }
    ~_Uniform() {}

    // Gets a handle to the OpenGL uniform, given the program and the uniform name
    int Register(_Program Program_in, const char *Name_in)
    {
        GLint Location;
        GLuint Program = Program_in.GetHandle();
        if((Location = glGetUniformLocation(Program, Name_in)) == -1) return 1;
        Uniforms.push_front(std::pair(Program, Location));
        return 0;
    }

    // Clears out the uniform handle list...haven't used it at all
    void ClearUniforms()
    {
        Uniforms.clear();
    }

    // Sets all stored uniforms to Data_in
    void SetData(const T &Data_in)
    {
        // Can use GL_EXT_direct_state_access extension or plain OpenGL
        // Guess which one I like better
        #ifdef DIRECT_STATE_ACCESS
            for(auto &i : Uniforms)
            {
                UniformHandler.SetUniform_DSA(Data_in, i.first, i.second);
            }
        #else
            Data = Data_in;
            GLuint Initial;
            glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)(&Initial));
            GLuint Previous = Initial;
            for(auto &i : Uniforms)
            {
                if(i.first != Previous)
                {
                    glUseProgram(i.first);
                    Previous = i.first;
                }
                UniformHandler.SetUniform(&Data_in, i.second);
            }
            if(Initial != Previous) glUseProgram(Initial);
        #endif
    }

};

Code Snippets

template <class T>
class _UniformHandler;

template<>
class _UniformHandler<glm::mat4>
{
public:
    inline void SetUniform(const glm::mat4& Data, const GLint &Uniform_in) const
    {
        glUniformMatrix4fv(Uniform_in, 1, GL_FALSE, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::mat4& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniformMatrix4fvEXT(Program_in, Uniform_in, 1, GL_FALSE, glm::value_ptr(Data));
    }
};
template <>
class _UniformHandler<glm::vec4>
{
public:
    inline void SetUniform(const glm::vec4& Data, const GLint &Uniform_in) const
    {
        glUniform4fv(Uniform_in, 1, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::vec4& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform4fvEXT(Program_in, Uniform_in, 1, glm::value_ptr(Data));
    }
};

template <>
class _UniformHandler<glm::vec3>
{
public:
    inline void SetUniform(const glm::vec3& Data, const GLint &Uniform_in) const
    {
        glUniform3fv(Uniform_in, 1, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::vec3& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform3fvEXT(Program_in, Uniform_in, 1, glm::value_ptr(Data));
    }
};

template <>
class _UniformHandler<glm::vec2>
{
public:
    inline void SetUniform(const glm::vec2& Data, const GLint &Uniform_in) const
    {
        glUniform2fv(Uniform_in, 1, glm::value_ptr(Data));
    }
    inline void SetUniform_DSA(const glm::vec2& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform2fvEXT(Program_in, Uniform_in, 1, glm::value_ptr(Data));
    }
};

template <>
class _UniformHandler<float>
{
public:
    inline void SetUniform(const float& Data, const GLint &Uniform_in) const
    {
        glUniform1fv(Uniform_in, 1, &Data);
    }
    inline void SetUniform_DSA(const float& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform1fvEXT(Program_in, Uniform_in, 1, &Data);
    }
};

template <>
class _UniformHandler<int>
{
public:
    inline void SetUniform(const int& Data, const GLint &Uniform_in) const
    {
        glUniform1iv(Uniform_in, 1, &Data);
    }
    inline void SetUniform_DSA(const int& Data, const GLuint &Program_in, const GLint &Uniform_in) const
    {
        glProgramUniform1ivEXT(Program_in, Uniform_in, 1, &Data);
    }
};

template <class T>
class _Uniform
{
protected:
    // Each program-uniform pair is stored in a forward_list
    std::forward_list<std::pair<GLuint,GLint>> Uniforms;
    _UniformHandler<T> UniformHandler;

public:
    _Uniform() {}
    _Uniform(const _Uniform& cp): Uniforms(cp.Uniforms) {}
    _Uniform(_Uniform&& mv): Uniforms(std::move(mv.Uniforms)) {}
    _Uniform& operator=(const _Uniform& cp)
    {
        Uniforms = cp.Uniforms;
        return *this;
    }
    _Uniform& operator=(_Uniform&& mv)
    {
        std::swap

Context

StackExchange Code Review Q#40117, answer score: 3

Revisions (0)

No revisions yet.