用于OpenGL对象的RAII包装器

n0r*_*0rd 14 c++ opengl dry raii visual-studio

我想为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;
Run Code Online (Sandbox Code Playgroud)

它工作正常的纹理,但没有对帧缓冲器: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) {}
};
Run Code Online (Sandbox Code Playgroud)

但是我不喜欢它,因为我必须在每个实例中存储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) {}
};
Run Code Online (Sandbox Code Playgroud)

有谁能建议更优雅的解决方案?

Nic*_*las 17

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

struct VertexArrayObjectTraits
{
  typedef GLuint value_type;
  static value_type Create();
  static void Destroy(value_type);
};
Run Code Online (Sandbox Code Playgroud)

就像一个合适的C++ traits类一样,我们让每个对象都声明它是自己的value_type.这将允许您将其适应不使用GLuints的OpenGL对象,例如同步对象(尽管创建/销毁界面无论如何都不会对他们有好处,所以你可能不应该打扰).

因此,您为每种类型的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;
};
Run Code Online (Sandbox Code Playgroud)

一个OpenGLObject<VertexArrayObjectTraits>会持有VAO.

  • 我匆匆将这个答案标记为已被接受,但在考虑之后我不太喜欢它.我本可以创建六个复制粘贴类,这些类只会在用于构建和销毁对象的函数上有所不同.你的命题在[略微]更好的架构上仍然留给我相同数量的复制粘贴现在traits-classes加上对象类. (4认同)
  • @ n0rd:不要忘记释放函数,以防你想从RAII中提取这样的对象.另外,不要忘记交换功能.如果您正在使用C++ 11,那么这是一个正确的移动构造函数.此外,如果要保存键入,可以将特征类放在宏中. (4认同)
  • @ n0rd:相同数量的类是,但是每个类都是5个EASY行,除了一个仍然相对简单和20行的类.这仍然是复杂性的重大胜利. (3认同)

ybu*_*ill 12

为什么重新发明轮子?有一个简洁的解决方案使用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;
// ...
Run Code Online (Sandbox Code Playgroud)

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

GLuint glCreateTextureSN(GLenum target) { GLuint ret; glCreateTextures(target, 1, &ret); return ret; }
GLuint glCreateBufferSN() { GLuint ret; glCreateBuffers(1, &ret); return ret; }
// ...
Run Code Online (Sandbox Code Playgroud)

一些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...
Run Code Online (Sandbox Code Playgroud)

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

  • 类似OpenGL的对象也是使用shared_ptr时非常好的经典示例之一.纹理和东西通常在渲染器中的各个位置之间共享,只有当没有人需要它时才删除它正是shared_ptr所做的. (2认同)
  • 您可能想看看JohannesD的帖子[从此线程](http://stackoverflow.com/a/6272139)。类型GLuint可能不满足unique_ptr自定义存储类型的NullablePointer要求。 (2认同)