Обертка RAII для объектов OpenGL
Я хочу написать простую оболочку RAII для объектов OpenGL (текстуры, буферы кадров и т.д.). Я заметил, что все функции 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) {}
};
Может ли кто-нибудь предложить более элегантное решение?
Ответы
Ответ 1
Действительно, вы думаете об этом, как программист на C. Вы используете С++, так что решайте его так, как программист на С++. С классом признаков:
struct VertexArrayObjectTraits
{
typedef GLuint value_type;
static value_type Create();
static void Destroy(value_type);
};
Как и собственный класс признаков С++, каждый объект объявляет его собственным value_type
. Это позволит вам адаптировать его к объектам OpenGL, которые не используют GLuint
s, например объекты синхронизации (хотя интерфейс Create/Destroy wouldn В любом случае, это будет хорошо для них, поэтому вы, вероятно, не должны беспокоиться).
Итак, вы пишете один класс признаков для каждого типа объекта OpenGL. Ваши функции Create
и Destroy
будут перенаправлять вызовы в API C соответственно.
После этого все, что вам нужно, это 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;
};
An OpenGLObject<VertexArrayObjectTraits>
будет содержать VAO.
Ответ 2
Зачем изобретать колесо? Существует четкое решение, использующее 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
не так уж плохо.