我想为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) {}
};
有人可以提出更优雅的解决方案吗?
答案 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类。您的Create
和Destroy
函数会将调用转发到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
并不是一件坏事。