15

I want to write a simple RAII wrapper for OpenGL objects (textures, frame buffers, etc.) I have noticed, that all glGen* and glDelete* functions share the same signature, so my first attempt was like this:

typedef void (__stdcall *GLGenFunction)(GLsizei, GLuint *);
typedef void (__stdcall *GLDelFunction)(GLsizei, const GLuint *);

template <GLGenFunction glGenFunction, GLDelFunction glDelFunction>
class GLObject
{
    GLuint m_name;
public:
    GLObject() 
    {  
        glGenFunction(1, &m_name);
    }

    ~GLObject()
    {
        glDelFunction(1, &m_name);
    }

    GLuint getName() {return m_name;}
};

typedef GLObject<glGenTextures, glDeleteTextures> GLTexture;

It works fine for textures, but fails for frame buffers: glGenFramebuffers and glDeleteFramebuffers function addresses are not known at compile time, and cannot be used as template arguments. So I made second version:

class GLObjectBase
{
    GLuint m_name;
    GLDelFunction m_delFunction;

public:
    GLObjectBase(GLGenFunction genFunc, GLDelFunction delFunction)
        : m_delFunction(delFunction)
    {
        genFunc(1, &m_name);
    }

    GLuint getName()
    {
        return m_name;
    }

protected:
    ~GLObjectBase()
    {
        m_delFunction(1, &m_name);
    }
};

class GLFrameBuffer : public GLObjectBase
{
public:
    GLFrameBuffer() : GLObjectBase(glGenFramebuffers, glDeleteFramebuffers) {}
};

But I don't like it since I have to store del function pointer in each instance that will not change at run-time.

How do I make wrapper class that stores only object name in each instance without resorting to create a bunch of almost copy-pasted classes?

I could do something like this:

template <int N>
class GLObject2
{
    GLuint m_name;
    static GLDelFunction glDelFunction;
public:
    GLObject2(GLGenFunction genFunction, GLDelFunction delFunc)
    {  
        genFunction(1, &m_name);
        if ( glDelFunction == nullptr )
            glDelFunction = delFunc;
        ASSERT(glDelFunction == delFunc);
    }

    GLuint getName() {return m_name;}

protected:
    ~GLObject2()
    {
        glDelFunction(1, &m_name);
    }
};

template <int N>
GLDelFunction GLObject2<N>::glDelFunction = nullptr;

class GLTexture: public GLObject2<1>
{
public:
    GLTexture(): GLObject2<1>(glGenTextures, glDeleteTextures) {}
};

class GLRenderBuffer: public GLObject2<2>
{
public:
    GLRenderBuffer(): GLObject2<2>(glGenRenderbuffers, glDeleteRenderbuffers) {}
};

Can anyone suggest more elegant solution?

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
n0rd
  • 11,850
  • 5
  • 35
  • 56
  • IMHO, the OpenGL APIs are probably not quite unified enough for this kind of generic approach to be very successful. Different generations of features have used different API design approaches. – Trillian Jun 18 '13 at 05:23
  • 3
    @Trillian: Nonsense. Of the 12 OpenGL object types, [only 3 use non-standard creation/destruction methadologies](http://www.opengl.org/wiki/OpenGL_Object#Non-standard_objects). This is a bad idea for other reasons, but not for that one. – Nicol Bolas Jun 18 '13 at 06:10
  • 1
    @NicolBolas: Out of curiosity: What makes this idea bad? And is you answer therefore part of this bad decision or would you recommend it? – Skalli Jun 18 '13 at 09:49
  • @NicolBolas: Oops, I stand corrected, thank you. Sounds like it's been too long since I've played with those APIs. – Trillian Jun 18 '13 at 14:19
  • 1
    Why not make `GLDelFunction m_delFunction;` into a `static` member? – Mooing Duck Jun 18 '13 at 21:26
  • @MooingDuck it is static in last code snippet. – n0rd Jun 18 '13 at 22:11
  • 2
    @Skalli for one glGenXXX and glDeleteXXX should be called in the same OpenGL context. It may be difficult to manage these objects with respect to contexts creation/destruction. – Alexander B Nov 05 '13 at 18:33

2 Answers2

18

Really, you're thinking about this like a C programmer. You're using C++, so solve it the way a C++ programmer would. With a traits class:

struct VertexArrayObjectTraits
{
  typedef GLuint value_type;
  static value_type Create();
  static void Destroy(value_type);
};

Like a proper C++ traits class, we have each object declare it's own value_type. This will allow you to adapt it to OpenGL objects that don't use GLuints, like sync objects (though the Create/Destroy interface wouldn't be good for them anyway, so you probably shouldn't bother).

So you write one traits class for each type of OpenGL object. Your Create and Destroy functions will forward the calls on to the C API appropriately.

After doing that, all you need is a RAII-wrapper around those interfaces:

template<typename T>
class OpenGLObject
{
public:
  OpenGLObject() : m_obj(T::Create()) {}
  ~OpenGLObject() {T::Destroy(m_obj);}

  operator typename T::value_type() {return m_obj;}

private:
  typename T::value_type m_obj;
};

An OpenGLObject<VertexArrayObjectTraits> would hold a VAO.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 5
    I hastily marked this answer as accepted, but after giving it some thought I don't like it very much. I could have created a half-dozen of copy-pasted classes that will only differ in the functions used to build and destroy objects. Your proposition while being [slightly] better architecturally still leaves me with same amount of copy-pasted now traits-classes plus the object class. – n0rd Jun 18 '13 at 21:18
  • 3
    @n0rd: Same number of classes yes, but the classes are 5 EASY lines each, except for one that's still relatively straightforward and 20ish lines. That's still a big win in complexity. – Mooing Duck Jun 18 '13 at 21:27
  • Naïve implementation would not be much bigger or complex: variable for holding object name, one-line constructor, one-line destructor, one-line getter. Compare to traits class: `value_type` typedef, `Create` function, `Destroy` function. – n0rd Jun 18 '13 at 21:49
  • 4
    @n0rd: Don't forget a release function, just in case you want to extract such an object from the RAII. Also, don't forget a swap function. And a proper move constructor if you're using C++11. Also, you can put the traits classes in a macro if you want to save typing. – Nicol Bolas Jun 18 '13 at 22:00
  • I really like this method but as far as I can see it immediately falls down as soon as you try to implement any non-trivial GL object, a Vertex Buffer Object trait for instance, as `bind()` requires arguments which vary in number and type per object. Does anyone have a solution to this problem? I thought about implementing it as a map and passing it as a reference to bind but it not an entirely clean method and probably incurs a performance hit. – Jonathan Hatchett Apr 08 '15 at 21:21
  • 1
    @JonHatchett: "*a Vertex Buffer Object trait for instance, as bind() requires arguments which vary in number and type per object*" No, it does not. The only OpenGL objects where creating the object requires specialized parameters are shader objects (which need to be told which stage they are for at creation time). And in a lot of code, shader objects are ephemeral, so you don't need special classes to manage them. – Nicol Bolas Mar 22 '16 at 14:08
  • 1
    @JonHatchett: Also, there are ways to use SFINAE such that particular overloads are only available if the template class has certain callables. One could upgrade `OpenGLObject` with such SFINAE code, so that objects which require construction parameters can have them. – Nicol Bolas Mar 22 '16 at 14:10
14

Why reinvent the wheel? There is a neat solution using std::unique_ptr, which already provides the needed functionality, so you need to only write the traits (!):

template<void (*func)(GLuint)>
struct gl_object_deleter {
    struct pointer { // I wish we could inherit from GLuint...
        GLuint x;
        pointer(std::nullptr_t = nullptr) : x(0) {}
        pointer(GLuint x) : x(x) {}
        operator GLuint() const { return x; }
        friend bool operator == (pointer x, pointer y) { return x.x == y.x; }
        friend bool operator != (pointer x, pointer y) { return x.x != y.x; }
    };
    void operator()(GLuint p) const { func(p); }
};

void delete_texture(GLuint p) { glDeleteTextures(1, &p); }
void delete_shader(GLuint p) { glDeleteShader(p); }
// ...
typedef std::unique_ptr<void, gl_object_deleter<delete_texture>> GLtexture;
typedef std::unique_ptr<void, gl_object_deleter<delete_shader>> GLshader;
// ...

Most the Create* functions return an array through their argument, which is inconvenient when you allocate your objects one-by-one. It is possible to define a set of creation routines for single instances:

GLuint glCreateTextureSN(GLenum target) { GLuint ret; glCreateTextures(target, 1, &ret); return ret; }
GLuint glCreateBufferSN() { GLuint ret; glCreateBuffers(1, &ret); return ret; }
// ...

Some OpenGL functions, like glCreateShader can be used directly. Now we can use it as follows:

GLtexture tex(glCreateTextureSN(GL_TEXTURE_2D));
glTextureParameteri(tex.get(), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// ...
GLtexture tex2 = std::move(tex); // we can move
tex2.reset(); // delete
return tex2; // return...

One downside is that you cannot define an implicit cast to GLuint, so you must call get() explicitly. But, on a second thought, preventing an accidental cast to GLuint is not such a bad thing.

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • 2
    OpenGL-like objects are also one of the classical examples of when the usage of shared_ptr is really good. Textures and stuff are usually shared between various places in your renderer, and deleting it only when nobody needs it anymore is exactly what a shared_ptr does. – TravisG Jan 24 '14 at 11:56
  • @TravisG: One problem of `shared_ptr` compared to `unique_ptr` is that its `get()` function always returns a pointer. You end up allocating the `GLuint` on the heap, or writing an explicit `reinterpret_cast(tex.get())`. Maybe you could write a wrapper of some sort though. – Yakov Galka Apr 24 '14 at 06:24
  • 2
    You may want to look at JohannesD's post [from this thread](http://stackoverflow.com/a/6272139). The type GLuint may not satisfy the NullablePointer requirement of the unique_ptr custom storage type. – Bovinedragon Nov 23 '14 at 23:38
  • 1
    @Bovinedragon: Thanks, fixed it. It's definitely not that neat anymore, but I still prefer it to reimplementing `unique_ptr` :) – Yakov Galka Apr 22 '15 at 19:37
  • I was wondering what actually means `unique_ptr` and... see [this thread](http://stackoverflow.com/questions/19840937/should-stdunique-ptrvoid-be-permitted) – Criss Apr 07 '17 at 16:40