Добрый день, хабра юзеры. Я давно не писал и возможно кто-то заждался статей от меня — конечно же нет. Так как свободного времени стало чутка поболее, а мой GitHub совершенно пуст, я решил написать свой клон Я буду показывать на примере 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/
Добавить комментарий