Ricerca…


introduzione

Esempi di vari modi per far funzionare oggetti OpenGL con C ++ RAII.

Osservazioni

L'incapsulamento RAII di oggetti OpenGL presenta pericoli. Il più inevitabile è che gli oggetti OpenGL siano associati al contesto OpenGL che li ha creati. Quindi la distruzione di un oggetto RAI C ++ deve essere eseguita in un contesto OpenGL che condivide la proprietà dell'oggetto OpenGL gestito da tale oggetto C ++.

Ciò significa anche che se tutti i contesti che possiedono l'oggetto vengono distrutti, allora qualsiasi oggetto OpenGL incapsulato RAII esistente cercherà di distruggere oggetti che non esistono più.

È necessario adottare misure manuali per affrontare problemi di contesto come questo.

In C ++ 98/03

Incapsulare un oggetto OpenGL in C ++ 98/03 richiede l'obbedienza alla regola C ++ di 3. Ciò significa aggiungere un costruttore di copia, un operatore di assegnazione di copia e un distruttore.

Tuttavia, i costruttori di copie dovrebbero copiare logicamente l'oggetto. E copiare un oggetto OpenGL è un'impresa non banale. Altrettanto importante, è quasi certamente qualcosa che l'utente non desidera fare.

Quindi renderemo l'oggetto non copiabile:

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

Il costruttore creerà l'oggetto e inizializzerà i dati dell'oggetto buffer. Il distruttore distruggerà l'oggetto. Dichiarando il costruttore / compito di copia senza definirli, il linker darà un errore se qualsiasi codice tenta di chiamarli. E dichiarandoli in privato, solo i membri di BufferObject saranno in grado di chiamarli.

Si noti che BufferObject non mantiene il target passato al costruttore. Questo perché un oggetto buffer OpenGL può essere utilizzato con qualsiasi target, non solo con quello inizialmente creato. Questo è diverso dagli oggetti texture, che devono sempre essere associati al target con cui sono stati inizialmente creati.

Poiché OpenGL dipende molto dagli oggetti vincolanti nel contesto per vari scopi, è spesso utile avere anche il binding di oggetti con ambito RAII. Poiché oggetti diversi hanno esigenze vincolanti diverse (alcuni hanno obiettivi, altri no), dobbiamo implementarne uno per ogni oggetto singolarmente.

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 non è copiabile, poiché copiare non ha senso. Si noti che non mantiene l'accesso al BufferObject che lega. Questo perché non è necessario.

In C ++ 11 e versioni successive

C ++ 11 offre strumenti che migliorano la funzionalità degli oggetti OpenGL incapsulati RAII. Senza le caratteristiche del C ++ 11 come la semantica del movimento, tali oggetti dovrebbero essere allocati dinamicamente se si desidera passarli in giro, poiché non possono essere copiati. Il supporto Sposta consente loro di passare avanti e indietro come valori normali, anche se non copiando:

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

Un tipo di questo tipo può essere restituito da una funzione:

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

Che ti consente di archiviarli nei tuoi tipi (implicitamente mossi solo):

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

Una classe di binder con scope può anche avere semantica di movimento, consentendo così al raccoglitore di essere restituito da funzioni e memorizzato in contenitori di librerie standard 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_;
};

Si noti che l'oggetto è mossa costruibile ma non trasferibile. L'idea con questo è di impedire il rebinding di un binding buffer con scope. Una volta impostato, l'unica cosa che può annullarlo viene spostato da.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow