15

我想为 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;

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

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

谁能提出更优雅的解决方案?

4

2 回答 2

18

真的,你像 C 程序员一样思考这个问题。您正在使用 C++,因此请以 C++ 程序员的方式解决它。使用特征类:

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

就像一个适当的 C++ 特征类一样,我们让每个对象都声明它是自己的value_type。这将允许您将其调整为不使用GLuints 的 OpenGL 对象,例如同步对象(尽管 Create/Destroy 接口对它们没有好处,所以您可能不应该打扰)。

因此,您为每种类型的 OpenGL 对象编写一个特征类。您的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;
};

AnOpenGLObject<VertexArrayObjectTraits>将持有VAO

于 2013-06-18T06:19:07.157 回答
14

为什么要重新发明轮子?使用 有一个简洁的解决方案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并不是一件坏事。

于 2013-12-07T00:10:48.463 回答