Buscar..


Introducción

Ejemplos de varias formas de hacer que los objetos OpenGL funcionen con C ++ RAII.

Observaciones

La encapsulación RAII de objetos OpenGL tiene peligros. Lo más inevitable es que los objetos OpenGL están asociados con el contexto OpenGL que los creó. Por lo tanto, la destrucción de un objeto RAII de C ++ debe realizarse en un contexto de OpenGL que comparta la propiedad del objeto OpenGL gestionado por ese objeto de C ++.

Esto también significa que si se destruyen todos los contextos que poseen el objeto, cualquier objeto OpenGL encapsulado RAII existente intentará destruir los objetos que ya no existen.

Debes tomar pasos manuales para lidiar con problemas de contexto como este.

En C ++ 98/03

Encapsular un objeto OpenGL en C ++ 98/03 requiere obedecer la regla de C ++ de 3. Esto significa agregar un constructor de copia, operador de asignación de copia y destructor.

Sin embargo, los constructores de copia deben copiar lógicamente el objeto. Y copiar un objeto OpenGL es una tarea no trivial. Igualmente importante, es casi seguro que es algo que el usuario no desea hacer.

Así que en su lugar haremos el objeto no copiable:

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

El constructor creará el objeto e inicializará los datos del objeto del búfer. El destructor destruirá el objeto. Al declarar el constructor / asignación de copia sin definirlos, el vinculador dará un error si algún código intenta llamarlos. Y al declararlos privados, solo los miembros de BufferObject podrán llamarlos.

Tenga en cuenta que BufferObject no conserva el target pasado al constructor. Esto se debe a que un objeto de búfer OpenGL se puede usar con cualquier objetivo, no solo con el que se creó inicialmente. Esto es diferente a los objetos de textura, que siempre deben estar vinculados al objetivo con el que se crearon inicialmente.

Debido a que OpenGL es muy dependiente de los objetos de enlace al contexto para varios propósitos, a menudo es útil tener también un enlace de objeto de estilo RAII. Debido a que diferentes objetos tienen diferentes necesidades de enlace (algunos tienen objetivos, otros no), tenemos que implementar uno para cada objeto individualmente.

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 no se puede copiar, ya que copiarlo no tiene sentido. Tenga en cuenta que no retiene el acceso al objeto BufferObject se enlaza. Eso es porque es innecesario.

En C ++ 11 y posteriores.

C ++ 11 ofrece herramientas que mejoran la funcionalidad de los objetos OpenGL encapsulados en RAII. Sin las características de C ++ 11, como la semántica de movimiento, dichos objetos tendrían que asignarse dinámicamente si desea pasarlos, ya que no se pueden copiar. El soporte para mover permite que se pasen de un lado a otro como valores normales, aunque no 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_;
};

Este tipo puede ser devuelto por una función:

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

Lo que le permite almacenarlos en sus propios tipos (implícitamente de solo movimiento):

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

Una clase de enlace con ámbito también puede tener semántica de movimiento, lo que permite que el enlace se devuelva desde las funciones y se almacene en contenedores de biblioteca estándar de 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_;
};

Tenga en cuenta que el objeto se puede mover pero no se puede asignar. La idea con esto es evitar la reconexión de un enlace de búfer de ámbito. Una vez que se configura, la única cosa que lo puede desactivar es que se está moviendo.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow