Обертка 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 не так уж плохо.