Zoeken…


Invoering

Voorbeelden van verschillende manieren om OpenGL-objecten te laten werken met C ++ RAII.

Opmerkingen

RAII-inkapseling van OpenGL-objecten heeft gevaren. Het meest onvermijdelijke is dat OpenGL-objecten zijn gekoppeld aan de OpenGL-context waardoor ze zijn gemaakt. De vernietiging van een C ++ RAII-object moet dus worden gedaan in een OpenGL-context die het eigendom deelt van het OpenGL-object dat wordt beheerd door dat C ++ -object.

Dit betekent ook dat als alle contexten die het object bezitten worden vernietigd, bestaande RAII ingekapselde OpenGL-objecten zullen proberen objecten te vernietigen die niet langer bestaan.

U moet handmatige stappen ondernemen om dergelijke contextproblemen aan te pakken.

In C ++ 98/03

Voor het inkapselen van een OpenGL-object in C ++ 98/03 moet de C ++ -regel van 3 worden gevolgd. Dit betekent dat een kopieerconstructor, kopieertoekenningsoperator en destructor moet worden toegevoegd.

Kopieerbouwers moeten het object echter logisch kopiëren. En het kopiëren van een OpenGL-object is een niet-triviale onderneming. Even belangrijk is dat het vrijwel zeker iets is dat de gebruiker niet wenst te doen.

Daarom maken we het object in plaats daarvan niet-kopieerbaar:

class BufferObject
{
public:
    BufferObject(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
    {
        glGenBuffers(1, &object_);
        glBindBuffer(target, object_);
        glBufferData(target, size, data, usage);
        glBindBuffer(target, 0);
    }
    
    ~BufferObject()
    {
        glDeleteBuffers(1, &object_);
    }
    
    //Accessors and manipulators
    void Bind(GLenum target) const {glBindBuffer(target, object_);}
    GLuint GetObject() const {return object_;}

private:
    GLuint object_;
    
    //Prototypes, but no implementation.
    BufferObject(const BufferObject &);
    BufferObject &operator=(const BufferObject &);
};

De constructor maakt het object en initialiseert de gegevens van het bufferobject. De vernietiger zal het object vernietigen. Door de copy constructor / opdracht te declareren zonder ze te definiëren, geeft de linker een foutmelding als een code ze probeert aan te roepen. En door ze privé te verklaren, kunnen alleen leden van BufferObject ze zelfs bellen.

Merk op dat BufferObject het aan de constructor doorgegeven target niet behoudt. Dat komt omdat een OpenGL-bufferobject met elk doel kan worden gebruikt, niet alleen het doel waarmee het oorspronkelijk is gemaakt. Dit is in tegenstelling tot textuurobjecten, die altijd moeten worden gebonden aan het doel waarmee ze oorspronkelijk zijn gemaakt.

Omdat OpenGL voor verschillende doeleinden erg afhankelijk is van het binden van objecten aan de context, is het vaak handig om ook objectgebonden binding in RAII-stijl te hebben. Omdat verschillende objecten verschillende bindende behoeften hebben (sommige hebben doelen, andere niet), moeten we er één voor elk object afzonderlijk implementeren.

class BindBuffer
{
public:
    BindBuffer(GLenum target, const BufferObject &buff) : target_(target)
    {
        buff.Bind(target_);
    }
    
    ~BindBuffer()
    {
        glBindBuffer(target_, 0);
    }
    
private:
    GLenum target_;

    //Also non-copyable.
    BindBuffer(const BindBuffer &);
    BindBuffer &operator=(const BindBuffer &);
};

BindBuffer kan niet worden gekopieerd, omdat kopiëren geen zin heeft. Merk op dat het geen toegang behoudt tot het BufferObject het bindt. Dat komt omdat het niet nodig is.

In C ++ 11 en hoger

C ++ 11 biedt tools die de functionaliteit van RAII-ingekapselde OpenGL-objecten verbeteren. Zonder C ++ 11-functies zoals verplaatsingssemantiek, zouden dergelijke objecten dynamisch moeten worden toegewezen als u ze wilt doorgeven, omdat ze niet kunnen worden gekopieerd. Met ondersteuning voor verplaatsen kunnen ze als normale waarden heen en weer worden doorgegeven, maar niet door te kopiëren:

class BufferObject
{
public:
    BufferObject(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
    {
        glGenBuffers(1, &object_);
        glBindBuffer(target, object_);
        glBufferData(target, size, data, usage);
        glBindBuffer(target, 0);
    }
    
    //Cannot be copied.
    BufferObject(const BufferObject &) = delete;
    BufferObject &operator=(const BufferObject &) = delete;
    
    //Can be moved
    BufferObject(BufferObject &&other) noexcept : object_(other.Release())
    {}
    
    //Self-assignment is OK with this implementation.
    BufferObject &operator=(BufferObject &&other) noexcept
    {
        Reset(other.Release());
    }
    
    //Destroys the old buffer and claims ownership of a new buffer object.
    //It's OK to call glDeleteBuffers on buffer object 0.
    GLuint Reset(GLuint object = 0)
    {
        glDeleteBuffers(1, &object_);
        object_ = object;
    }
    
    //Relinquishes ownership of the object without destroying it
    GLuint Release()
    {
        GLuint ret = object_;
        object_ = 0;
        return ret;
    }    
    
    ~BufferObject()
    {
        Reset();
    }
    
    //Accessors and manipulators
    void Bind(GLenum target) const {glBindBuffer(target, object_);}
    GLuint GetObject() const {return object_;}

private:
    GLuint object_;
};

Een dergelijk type kan worden geretourneerd door een functie:

BufferObject CreateStaticBuffer(GLsizeiptr byteSize) {return BufferObject(GL_ARRAY_BUFFER, byteSize, nullptr, GL_STATIC_DRAW);}

Hiermee kunt u ze opslaan in uw eigen (impliciet alleen) typen:

struct Mesh
{
public:
private:
    //Default member initializer.
    BufferObject buff_ = CreateStaticBuffer(someSize);
};

Een scoped-bindmiddelklasse kan ook semantiek van verplaatsing hebben, waardoor het bindmiddel kan worden teruggestuurd vanuit functies en kan worden opgeslagen in standaard bibliotheekcontainers van C ++:

class BindBuffer
{
public:
    BindBuffer(GLenum target, const BufferObject &buff) : target_(target)
    {
        buff.Bind(target_);
    }

    //Non-copyable.
    BindBuffer(const BindBuffer &) = delete;
    BindBuffer &operator=(const BindBuffer &) = delete;
    
    //Move-constructible.
    BindBuffer(BindBuffer &&other) noexcept : target_(other.target_)
    {
        other.target_ = 0;
    }
    
    //Not move-assignable.
    BindBuffer &operator=(BindBuffer &&) = delete;
    
    ~BindBuffer()
    {
        //Only unbind if not moved from.
        if(target_)
            glBindBuffer(target_, 0);
    }
    
private:
    GLenum target_;
};

Merk op dat het object verplaatsbaar is maar niet verplaatsbaar. Het idee hierbij is om te voorkomen dat een scopedbuffer opnieuw wordt ingebonden. Als het eenmaal is ingesteld, wordt het enige dat het kan uitschakelen, verplaatst.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow