OpenGL примитивы в стиле RAII

от автора

Добрый день, хабра юзеры. Я давно не писал и возможно кто-то заждался статей от меня — конечно же нет. Так как свободного времени стало чутка поболее, а мой GitHub совершенно пуст, я решил написать свой клон Mein kampf Minecraft. С большой вероятностью, я задокументирую это — следите за моими статьями на habr.com. Сегодня покажу как я обернул OpenGL примитивы в RAII стиле, если интересно — под кат.

Я буду показывать на примере Buffer Object. Первое, что мы сделаем — вынесем основные вызовы ( glGenBuffers , glDeleteBuffers ) в отдельный класс.

Где-то вот так:

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

Плюсы:

  • Не нужно следить за удалением буфера
  • Включение OpenGL хедера не торчит наружу (если мы перенесем определение функций в *.cpp)

Минусы:

  • Не можем указать тип буфера (array, element …) при биндинге
  • Не можем создать несколько буферов одним вызовом glGenBuffers
  • Проблемы с обращением к удаленному объекту, если буфер был скопирован

Что б решит проблему с копированием, мы просто его запретим. Для этого создадим класс noncopyable и унаследуем его.

Пример класса noncopyable:

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

Отлично, минус одна проблема. Думаем дальше, как бы нам решить проблему с биндингом… добавим функции 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 — enum class, но можно сделать extern const (в *.hpp), а в (*.cpp) сделать что-то типа:

const uint32_t array_buffer = GL_ARRAY_BUFFER;

Опять же, что бы наружу не торчал

#include <GL/glew.h>

Плюсы:

  • Не нужно следить за удалением буфера
  • Включение OpenGL хедера не торчит наружу (если мы перенесем определение функций в *.cpp)
  • Можем указать тип буфера(array, element …) при биндинге
  • Нет копирования — нет проблем с копированием

Минусы:

  • Не можем создать несколько буферов одним вызовом glGenBuffers

Так, почти все супер, но мы еще не можем создавать несколько буферов… Исправим же это.

glGenBuffers может принимать указатель на массив и количество буферов для генерации.
Нам нужен массив, мы могли бы использовать std::vector, но нам надо аллоцировать память всего один раз и я бы предпочел тут std::array, хотя в дальнейшем нам прийдется из-за этого делать еще один уровень абстракции.

Перепишем наш класс на std::array и добавим чуточку шаблонов:

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

И вот, что мы получили.

Плюсы:

  • Не нужно следить за удалением буфера
  • Можем указать тип буфера(array, element …) при биндинге
  • Нет копирования — нет проблем с копированием
  • Можем создать несколько буферов одним вызовом glGenBuffers

Минусы:

  • Не можем биндить разные буферы у одного объекта
  • Включение OpenGL хедера торчит наружу

Ну… минусов многовато, что тут можно сделать?

Убрать включение GL/glew.h, можно добавив еще один уровень абстракции, в котором будут вызываться функции OpenGL (в любом случае это нужно делать, если планируется поддержка OpenGL + DirectX). С биндингом разных буфером чуть посложнее, так как мы можем забыть какой индекс с каким буфером был забинджен, как вариант — добавить еще один массив и в него записывать тип буфера. Я пока не занимался этим, на данном этапе разработки мне хватает и этого.

Бонус

Для более удобного использования, я сделал scoped bind класс. Вот он:

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

У некоторых может вызвать недоумение вот эта строка:

m_ref.template bind<index>();

Если мы не укажем ключевое слова template, то словим такую ошибку:

binder.hpp:22:46: Missing ‘template’ keyword prior to dependent template name ‘bind’
Это означает, что в таком случае, когда T является зависимым типом. Компилятор еще не знает, что такое тип m_ref. Так как связывания еще не было, компилятор обрабатывает ее чисто синтаксически, поэтому < интерпретируется как оператор меньше чем. Чтобы указать компилятору, что это, по сути, вызов специализации шаблона функции-члена, необходимо добавить ключевое слово template сразу после оператор точки.

И пример использования:

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

В данном коде не используется объект для Vertex Array, потому что он не работает, причины я еще не выяснил, но скоро с этим разберусь xD

Так же еще не готовы обертки для Buffer Data и OpenGL вызовы не переписаны на DSA (Direct State Access).

Спасибо тем, кто дочитал это до конца. Очень буду рад критике и комментариям.

ссылка на оригинал статьи https://habr.com/ru/post/495068/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *