Primitif gaya RAII OpenGL

Selamat siang, pengguna Habra. Saya belum menulis untuk waktu yang lama dan mungkin seseorang sedang menunggu artikel dari saya - tentu saja tidak. Karena waktu luang saya menjadi sedikit lebih, dan GitHub saya benar-benar kosong, saya memutuskan untuk menulis klon saya Mein kampf Minecraft. Dengan probabilitas tinggi, saya akan mendokumentasikan ini - ikuti artikel saya di habr.com . Hari ini saya akan menunjukkan bagaimana saya membungkus OpenGL primitif dengan gaya RAII, jika menarik - di bawah kucing.

Tulis di komentar apa yang ingin Anda baca. Haruskah saya menulis Minecraft dari artikel awal? Sekarang mari kita mulai. Saya akan menunjukkan Buffer Object sebagai contoh . Hal pertama yang akan kita lakukan adalah melakukan panggilan utama ( glGenBuffers , glDeleteBuffers ) di kelas yang berbeda.

Di suatu tempat seperti ini:

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

Pro:

  • Tidak perlu memonitor penghapusan buffer
  • Mengaktifkan tajuk OpenGL tidak menonjol (jika kami mentransfer definisi fungsi ke * .cpp)

Minus:

  • Kami tidak dapat menentukan jenis buffer (array, elemen ...) saat mengikat
  • Tidak dapat membuat beberapa buffer dengan satu panggilan ke glGenBuffers
  • Masalah mengakses objek jarak jauh jika buffer disalin

Untuk mengatasi masalah penyalinan, kami hanya melarangnya. Untuk melakukan ini, buat kelas yang tidak dapat disalin dan mewarisinya.

Contoh kelas noncopyable:

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

Hebat, minus satu masalah. Kami berpikir lebih jauh, bagaimana kami memecahkan masalah dengan mengikat ... menambahkan fungsi bind / unbind:

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

buffer_type adalah kelas enum, tetapi Anda bisa membuat extern const (di * .hpp), dan di (* .cpp) lakukan sesuatu seperti:

const uint32_t array_buffer = GL_ARRAY_BUFFER;

Sekali lagi, itu tidak akan bertahan

#include <GL/glew.h>

Pro:

  • Tidak perlu memonitor penghapusan buffer
  • Mengaktifkan tajuk OpenGL tidak menonjol (jika kami mentransfer definisi fungsi ke * .cpp)
  • Kami dapat menunjukkan jenis buffer (array, elemen ...) saat mengikat
  • Tanpa penyalinan - tidak ada masalah penyalinan

Minus:

  • Tidak dapat membuat beberapa buffer dengan satu panggilan ke glGenBuffers

Jadi, hampir semuanya super, tapi kami masih belum bisa membuat beberapa buffer ... Ayo perbaiki.

glGenBuffers dapat mengambil pointer ke array dan jumlah buffer untuk dihasilkan.
Kita memerlukan sebuah array, kita dapat menggunakan std :: vector, tetapi kita perlu mengalokasikan memori hanya sekali, dan saya lebih suka std :: array di sini, walaupun di masa depan kita harus melakukan satu lagi level abstraksi karena hal ini.

Kami menulis ulang kelas kami ke std :: array dan menambahkan sedikit templat:

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

Dan inilah yang kami dapatkan.

Pro:

  • Tidak perlu memonitor penghapusan buffer
  • Kami dapat menunjukkan jenis buffer (array, elemen ...) saat mengikat
  • Tanpa penyalinan - tidak ada masalah penyalinan
  • Kami dapat membuat beberapa buffer dengan satu panggilan ke glGenBuffers

Minus:

  • Tidak dapat mengikat buffer berbeda pada objek yang sama
  • Mengaktifkan OpenGL Header Sticks Out

Yah ... terlalu banyak kontra, apa yang bisa dilakukan?

Anda dapat menghapus penyertaan GL / glew.h dengan menambahkan tingkat abstraksi lain di mana fungsi OpenGL akan dipanggil (dalam hal apa pun, ini harus dilakukan jika dukungan untuk OpenGL + DirectX direncanakan). Mengikat dengan buffer berbeda sedikit lebih rumit, karena kita bisa lupa indeks mana yang buffernya dilarang, sebagai opsi, tambahkan array lain dan tulis tipe buffernya. Saya belum melakukan ini, pada tahap pengembangan ini, ini sudah cukup bagi saya.

Bonus


Untuk penggunaan yang lebih nyaman, saya membuat kelas bind scoped. Itu dia:

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

Beberapa mungkin bingung oleh garis ini:

m_ref.template bind<index>();

Jika kami tidak menentukan kata kunci templat, maka kami menangkap galat berikut:

binder.hpp: 22: 46: Kata kunci 'templat' tidak ada sebelum nama templat tempelan 'bind'
Ini berarti bahwa dalam kasus ini, ketika T adalah jenis dependen. Kompiler belum tahu apa tipe m_ref. Karena belum ada pengikatan, kompiler memprosesnya secara sintaksis murni, oleh karena itu, ditafsirkan sebagai operator kurang dari. Untuk menunjukkan kepada kompiler bahwa ini, pada kenyataannya, adalah spesialisasi dari templat fungsi anggota, Anda harus menambahkan kata kunci templat segera setelah operator titik.

Dan contoh penggunaan:

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

Kode ini tidak menggunakan objek untuk Vertex Array, karena tidak berfungsi, saya belum tahu alasannya, tapi saya akan segera mengetahuinya. XD

Pembungkus untuk Data Buffer dan panggilan OpenGL belum siap, panggilan belum ditulis ulang untuk DSA (Akses Langsung Ke Negara) .

Terima kasih kepada mereka yang membacanya sampai akhir. Saya akan sangat senang menerima kritik dan komentar.

All Articles