Primitivas de estilo OpenGL RAII

Buenas tardes, usuarios de Habra. No he escrito durante mucho tiempo y tal vez alguien estaba esperando artículos míos, por supuesto que no. Como mi tiempo libre se volvió un poco más y mi GitHub está completamente vacío, decidí escribir mi clon Mein kampf Minecraft. Con alta probabilidad, documentaré esto: siga mis artículos en habr.com . Hoy mostraré cómo envolví las primitivas de OpenGL en el estilo RAII, si es interesante, bajo cat.

Escriba en los comentarios sobre lo que le gustaría leer. ¿Debo escribir Minecraft desde cero? Ahora comencemos. Mostraré el objeto Buffer como ejemplo . Lo primero que haremos es hacer las llamadas principales ( glGenBuffers , glDeleteBuffers ) en una clase separada.

En algún lugar como este:

class buffer_object
{
public:
    buffer_object() noexcept {
        glGenBuffers(1, m_object);
    }
    
    ~buffer_object() noexcept {
        glDeleteBuffers(1, m_object);
    }
    
    uint32_t get_object() const noexcept {
        return m_object;
    }
private:
    uint32_t m_object;
};

Pros:

  • No es necesario controlar la eliminación del búfer
  • La habilitación del encabezado OpenGL no sobresale (si transferimos la definición de funciones a * .cpp)

Desventajas:

  • No podemos especificar el tipo de búfer (matriz, elemento ...) al vincular
  • No se pueden crear múltiples buffers con una sola llamada a glGenBuffers
  • Problemas para acceder a un objeto remoto si se copió el búfer

Para resolver el problema con la copia, simplemente lo prohibimos. Para hacer esto, cree una clase no copiable y herede.

Ejemplo de clase no copiable:

struct noncopyable
{
    noncopyable() = default;
            
    noncopyable(noncopyable&&) = default;
    noncopyable& operator = (noncopyable&&) = default;
            
    noncopyable(const noncopyable&) = delete;
    noncopyable& operator = (const noncopyable&) = delete;
};

Genial, menos un problema. Pensamos más, cómo resolveríamos el problema con el enlace ... agregue las funciones de vinculación / desvinculación:

void bind(buffer_type type) noexcept {
    glBindBuffer(static_cast<GLenum>(type), m_object);
}
    
void unbind() noexcept {
    glBindBuffer(static_cast<GLenum>(type), 0);
}

buffer_type es una clase enum, pero puede hacer que extern const (en * .hpp) y en (* .cpp) hagan algo como:

const uint32_t array_buffer = GL_ARRAY_BUFFER;

De nuevo, eso no sobresaldría

#include <GL/glew.h>

Pros:

  • No es necesario controlar la eliminación del búfer
  • La habilitación del encabezado OpenGL no sobresale (si transferimos la definición de funciones a * .cpp)
  • Podemos indicar el tipo de búfer (matriz, elemento ...) cuando se vincula
  • Sin copia - sin problemas de copia

Desventajas:

  • No se pueden crear múltiples buffers con una sola llamada a glGenBuffers

Entonces, casi todo es super, pero aún no podemos crear varios buffers ... Arreglemoslo.

glGenBuffers puede llevar un puntero a una matriz y el número de buffers para generar.
Necesitamos una matriz, podríamos usar std :: vector, pero necesitamos asignar memoria solo una vez, y preferiría std :: array aquí, aunque en el futuro tendremos que hacer un nivel más de abstracción debido a esto.

Reescribimos nuestra clase en std :: array y agregamos un poco de plantillas:

constexpr uint8_t default_object_count = 1;
constexpr size_t  default_index        = 0;

template <type::buffer_type type, uint32_t count = default_object_count>
class buffer_object : noncopyable
{
public:
    buffer_object() noexcept {
        glGenBuffers(count, m_object.data());
    }
    
    ~buffer_object() noexcept {
        glDeleteBuffers(count, m_object.data());
    }
    
    template <size_t index = default_index>
    void bind() noexcept
    {
        static_assert(index < count, "index larger than array size");
        glBindBuffer(static_cast<GLenum>(type), m_object[index]);
    }
    
    void unbind() noexcept {
        glBindBuffer(static_cast<GLenum>(type), 0);
    }
    
    buffer_object(buffer_object&&) = default;
    buffer_object& operator = (buffer_object&&) = default;
    
private:
    std::array<uint32_t, count> m_object;
};

Y aquí está lo que tenemos.

Pros:

  • No es necesario controlar la eliminación del búfer
  • Podemos indicar el tipo de búfer (matriz, elemento ...) cuando se vincula
  • Sin copia - sin problemas de copia
  • Podemos crear varios buffers con una sola llamada a glGenBuffers

Desventajas:

  • No se pueden vincular diferentes buffers en el mismo objeto.
  • Habilitar el encabezado OpenGL sobresale

Bueno ... demasiados contras, ¿qué se puede hacer?

Puede eliminar la inclusión de GL / glew.h agregando otro nivel de abstracción en el que se llamarán las funciones de OpenGL (en cualquier caso, esto debería hacerse si se planea la compatibilidad con OpenGL + DirectX). La vinculación con diferentes búferes es un poco más complicada, ya que podemos olvidar qué índice con qué búfer fue prohibido, como opción, agregar otra matriz y escribirle el tipo de búfer. Todavía no he hecho esto; en esta etapa de desarrollo, esto es suficiente para mí.

Prima


Para un uso más conveniente, hice una clase de vinculación con ámbito. Ahi esta:

constexpr uint32_t default_bind_index = 0;
template <class T>
class binder : utils::noncopyable
{
public:
      template <uint32_t index = default_bind_index>
      binder(T& ref) : m_ref(ref) { m_ref.template bind<index>(); }
    ~binder()                            { m_ref.unbind();}
        
private:
    T& m_ref;
};

Algunos pueden estar desconcertados por esta línea:

m_ref.template bind<index>();

Si no especificamos la palabra clave de plantilla,

detectamos el siguiente error: binder.hpp: 22: 46: falta la palabra clave 'plantilla' antes del nombre de plantilla dependiente 'bind'.
Esto significa que en este caso, cuando T es un tipo dependiente. El compilador aún no sabe de qué tipo es m_ref. Como todavía no había ningún enlace, el compilador lo procesa de manera puramente sintáctica, por lo tanto, <se interpreta como un operador menor que. Para indicarle al compilador que esto es, de hecho, una especialización de una plantilla de función miembro, debe agregar la palabra clave de plantilla inmediatamente después del operador de punto.

Y un ejemplo de uso:

GLuint VAO;
    
primitive::buffer_object<type::buffer_type::array> vbo;
primitive::buffer_object<type::buffer_type::element> ebo;
    
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
    
{
    primitive::binder bind{vbo};
        
    glBufferData(GL_ARRAY_BUFFER, ...);
        
    glVertexAttribPointer(...);
    glEnableVertexAttribArray(...);
        
    glVertexAttribPointer(...);
    glEnableVertexAttribArray(...);
}
    
{
    primitive::binder bind{ebo};
        
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, ...);
        
    glBindVertexArray(0);
}

Este código no usa el objeto para Vertex Array, porque no funciona, no he descubierto las razones, pero pronto lo descubriré xD

Además, los envoltorios para las llamadas de datos de búfer y OpenGL aún no están listos, no se han reescrito en DSA (Acceso directo al estado) .

Gracias a quienes lo leyeron hasta el final. Estaré muy contento con las críticas y los comentarios.

All Articles