Vraag RAII-wrapper voor OpenGL-objecten


Ik wil een eenvoudige RAII-wrapper schrijven voor OpenGL-objecten (texturen, framebuffers, enz.). Ik heb gemerkt dat alles glGen* en glDelete* functies delen dezelfde handtekening, dus mijn eerste poging was als volgt:

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;

Het werkt prima voor texturen, maar faalt voor framebuffers: glGenFramebuffers en glDeleteFramebuffers functie-adressen zijn niet bekend tijdens het compileren en kunnen niet worden gebruikt als sjabloonargumenten. Dus maakte ik de tweede versie:

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

Maar ik vind het niet leuk omdat ik in elke instantie een delfunctie-aanwijzer moet opslaan die niet tijdens runtime zal veranderen.

Hoe maak ik een wrapper-klasse die in elke instantie alleen de objectnaam opslaat zonder dat er een reeks bijna gekopieerde klassen wordt gemaakt?

Ik zou zoiets als dit kunnen doen:

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

Kan iemand een elegantere oplossing voorstellen?


12
2018-06-18 05:15


oorsprong


antwoorden:


Echt, je denkt hier aan als een C-programmeur. Je gebruikt C ++, dus los het op zoals een C ++ -programmeur dat zou doen. Met een eigenschapklasse:

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

Net als een goede C ++ -kenmerkenklasse, verklaren we dat elk object het heeft value_type. Hiermee kunt u het aanpassen aan OpenGL-objecten die niet gebruiken GLuints, zoals objecten synchroniseren (hoewel de Create / Destroy-interface sowieso niet goed voor ze zou zijn, dus je zou waarschijnlijk niet moeten storen).

Dus je schrijft een eigenschapklasse voor elk type OpenGL-object. Jouw Create en Destroy functies zullen de oproepen doorschakelen naar de C API op de juiste manier.

Nadat je dat gedaan hebt, heb je alleen nog een RAII-verpakking nodig die interfaces:

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

Een OpenGLObject<VertexArrayObjectTraits> zou houden VAO.


15
2018-06-18 06:19



Waarom het wiel opnieuw uitvinden? Er is een nette oplossing met std::unique_ptr, die al de benodigde functionaliteit biedt, dus je hoeft alleen de eigenschappen te schrijven (!):

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;
// ...

De meeste van de Create* functies retourneren een array door hun argument, wat onhandig is wanneer u uw objecten één voor één toewijst. Het is mogelijk om een ​​set aanmaakroutines voor enkele instanties te definiëren:

GLuint glCreateTextureSN(GLenum target) { GLuint ret; glCreateTextures(target, 1, &ret); return ret; }
GLuint glCreateBufferSN() { GLuint ret; glCreateBuffers(1, &ret); return ret; }
// ...

Sommige OpenGL-functies, zoals glCreateShader kan direct worden gebruikt. Nu kunnen we het als volgt gebruiken:

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...

Een nadeel is dat je een impliciete cast niet kunt definiëren voor GLuint, dus je moet bellen get() uitdrukkelijk. Maar bij een tweede gedachte, voorkomt het voorkomen van een toevallige cast GLuint is niet zo erg.


9
2017-12-07 00:10