用于OpenGL对象的RAII包装器

时间:2013-06-18 05:15:55

标签: c++ visual-studio opengl dry raii

我想为OpenGL对象(纹理,帧缓冲区等)编写一个简单的RAII包装器。我注意到,所有glGen*glDelete*函数共享相同的签名,所以我的第一次尝试是这样的:

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;

它适用于纹理,但帧缓冲区失败:glGenFramebuffersglDeleteFramebuffers函数地址在编译时是未知的,不能用作模板参数。所以我做了第二个版本:

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) {}
};

但是我不喜欢它,因为我必须在每个实例中存储del函数指针,这些指针在运行时不会改变。

如何创建仅在每个实例中存储对象名称的包装类,而不需要创建一堆几乎复制粘贴的类?

我可以这样做:

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) {}
};

有人可以提出更优雅的解决方案吗?

2 个答案:

答案 0 :(得分:15)

真的,你像C程序员一样考虑这个问题。你正在使用C ++,所以像C ++程序员那样解决它。有一个特质类:

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

与正确的C ++ traits类一样,我们让每个对象都声明它自己的value_type。这将允许您使其适应不使用GLuint的OpenGL对象,如sync objects(尽管创建/销毁界面无论如何都不会对他们有好处,所以你可能不应该这样做打扰)。

因此,您为每种类型的OpenGL对象编写一个traits类。您的CreateDestroy函数会将调用转发到C API。

完成后,您只需要围绕那些接口的RAII包装器:

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;
};

OpenGLObject<VertexArrayObjectTraits>会保留VAO

答案 1 :(得分:11)

为什么重新发明轮子?使用std::unique_ptr有一个简洁的解决方案,它已经提供了所需的功能,所以你只需要编写特征(!):

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;
// ...

大多数Create*函数通过参数返回一个数组,这在逐个分配对象时很不方便。可以为单个实例定义一组创建例程:

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

可以直接使用某些OpenGL函数,如glCreateShader。现在我们可以按如下方式使用它:

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...

一个缺点是您无法定义GLuint的隐式强制转换,因此您必须明确调用get()。但是,在第二个想法,防止意外转换为GLuint并不是一件坏事。