OpenGL RAII-Grundelemente

Guten Tag, Habra-Benutzer. Ich habe lange nicht mehr geschrieben und vielleicht hat jemand auf Artikel von mir gewartet - natürlich nicht. Da meine Freizeit etwas länger wurde und mein GitHub komplett leer ist, habe ich beschlossen, meinen Klon Mein kampf Minecraft zu schreiben . Mit hoher Wahrscheinlichkeit werde ich dies dokumentieren - folgen Sie meinen Artikeln auf habr.com . Heute werde ich zeigen, wie ich OpenGL-Grundelemente im RAII-Stil verpackt habe, wenn es interessant ist - unter Katze.

Schreiben Sie in die Kommentare, worüber Sie lesen möchten. Soll ich Minecraft von Grund auf neu schreiben? Jetzt fangen wir an. Ich werde das Pufferobjekt als Beispiel zeigen . Als erstes werden wir die Hauptaufrufe ( glGenBuffers , glDeleteBuffers ) in einer separaten Klasse ausführen .

Irgendwo so:

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

Vorteile:

  • Das Löschen des Puffers muss nicht überwacht werden
  • Das Aktivieren des OpenGL-Headers ragt nicht heraus (wenn wir die Definition von Funktionen auf * .cpp übertragen).

Minuspunkte:

  • Wir können den Bindertyp (Array, Element ...) beim Binden nicht angeben
  • Mit einem einzigen Aufruf von glGenBuffers können nicht mehrere Puffer erstellt werden
  • Probleme beim Zugriff auf ein Remote-Objekt, wenn der Puffer kopiert wurde

Um das Problem beim Kopieren zu lösen, verbieten wir es einfach. Erstellen Sie dazu eine nicht kopierbare Klasse und erben Sie sie.

Nicht kopierbares Klassenbeispiel:

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

Großartig, abzüglich eines Problems. Wir überlegen weiter, wie wir das Problem mit dem Binden lösen könnten ... fügen Sie die Bind / Unbind-Funktionen hinzu:

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

buffer_type ist eine Enum-Klasse, aber Sie können extern const (in * .hpp) und in (* .cpp) dazu bringen, Folgendes zu tun:

const uint32_t array_buffer = GL_ARRAY_BUFFER;

Auch das würde nicht auffallen

#include <GL/glew.h>

Vorteile:

  • Das Löschen des Puffers muss nicht überwacht werden
  • Das Aktivieren des OpenGL-Headers ragt nicht heraus (wenn wir die Definition von Funktionen auf * .cpp übertragen).
  • Wir können die Art des Puffers (Array, Element ...) beim Binden angeben
  • Kein Kopieren - keine Kopierprobleme

Minuspunkte:

  • Mit einem einzigen Aufruf von glGenBuffers können nicht mehrere Puffer erstellt werden

Also, fast alles ist super, aber wir können immer noch nicht mehrere Puffer erstellen ... Lassen Sie es uns beheben.

glGenBuffers können einen Zeiger auf ein Array und die Anzahl der zu generierenden Puffer verwenden.
Wir brauchen ein Array, wir könnten std :: vector verwenden, aber wir müssen nur einmal Speicher zuweisen, und ich würde std :: array hier vorziehen, obwohl wir in Zukunft aus diesem Grund eine weitere Abstraktionsebene durchführen müssen.

Wir schreiben unsere Klasse in std :: array um und fügen ein paar Vorlagen hinzu:

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

Und hier ist was wir haben.

Vorteile:

  • Das Löschen des Puffers muss nicht überwacht werden
  • Wir können die Art des Puffers (Array, Element ...) beim Binden angeben
  • Kein Kopieren - keine Kopierprobleme
  • Mit einem einzigen Aufruf von glGenBuffers können mehrere Puffer erstellt werden

Minuspunkte:

  • Es können keine unterschiedlichen Puffer für dasselbe Objekt gebunden werden
  • Das Aktivieren von OpenGL Header Sticks Out

Nun ... zu viele Nachteile, was kann getan werden?

Sie können die Aufnahme von GL / glew.h entfernen, indem Sie eine weitere Abstraktionsebene hinzufügen, in der die OpenGL-Funktionen aufgerufen werden (dies sollte in jedem Fall erfolgen, wenn die Unterstützung für OpenGL + DirectX geplant ist). Das Binden mit verschiedenen Puffern ist etwas komplizierter, da wir vergessen können, welcher Index mit welchem ​​Puffer gesperrt wurde. Fügen Sie optional ein weiteres Array hinzu und schreiben Sie den Puffertyp darauf. Ich habe das noch nicht getan, in diesem Entwicklungsstadium reicht mir das.

Bonus


Zur bequemeren Verwendung habe ich eine Bindeklasse mit Gültigkeitsbereich erstellt. Da ist er:

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

Einige mögen durch diese Zeile verwirrt sein:

m_ref.template bind<index>();

Wenn wir das Schlüsselwort template nicht angeben, wird der folgende Fehler

angezeigt : binder.hpp: 22: 46: Fehlendes Schlüsselwort 'template' vor dem Namen der abhängigen Vorlage 'bind'
Dies bedeutet in diesem Fall, dass T ein abhängiger Typ ist. Der Compiler weiß noch nicht, welcher Typ m_ref ist. Da es noch keine Bindung gab, verarbeitet der Compiler sie rein syntaktisch, daher wird <als Operator kleiner als interpretiert. Um dem Compiler anzuzeigen, dass es sich tatsächlich um eine Spezialisierung einer Elementfunktionsvorlage handelt, müssen Sie das Schlüsselwort template unmittelbar nach dem Punktoperator hinzufügen.

Und ein Anwendungsbeispiel:

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

Dieser Code verwendet das Objekt nicht für Vertex Array, da es nicht funktioniert. Ich habe die Gründe noch nicht herausgefunden, aber ich werde es bald herausfinden. XD

Wrapper für Pufferdaten und OpenGL-Aufrufe sind noch nicht bereit. Die Aufrufe werden nicht in DSA (Direct State Access) umgeschrieben .

Vielen Dank an diejenigen, die es bis zum Ende gelesen haben. Ich werde mich sehr über Kritik und Kommentare freuen.

All Articles