下午好,哈布拉用户。我已经写了很长时间了,也许有人在等我发来的文章-当然不是。由于我的空闲时间变得更多了,并且我的GitHub完全是空的,所以我决定编写我的克隆Mein kampf Minecraft。我极有可能对此进行记录-在habr.com上关注我的文章。今天,我将展示如何将RAII样式的OpenGL原语(如果有意思的话)包装在cat下。在评论中写下您想阅读的内容。我应该从头开始写Minecraft吗?现在开始吧。我将以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)
缺点:- 绑定时我们无法指定缓冲区的类型(数组,元素...)
- 一次调用glGenBuffers不能创建多个缓冲区
- 如果复制了缓冲区,则访问远程对象时出现问题
为了解决复制问题,我们只是禁止它。为此,创建一个不可复制的类并继承它。不可复制的类示例: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是一个枚举类,但是您可以使extern const(在* .hpp中)和在(* .cpp)中执行以下操作:const uint32_t array_buffer = GL_ARRAY_BUFFER;
再次,那不会伸出#include <GL/glew.h>
优点:- 无需监视缓冲区删除
- 启用OpenGL标头不会突出(如果我们将函数的定义转移到* .cpp)
- 我们可以在绑定时指出缓冲区的类型(数组,元素...)
- 没有复制-没有复制问题
缺点:- 一次调用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;
};
这就是我们得到的。优点:- 无需监视缓冲区删除
- 我们可以在绑定时指出缓冲区的类型(数组,元素...)
- 没有复制-没有复制问题
- 我们可以通过一次调用glGenBuffers创建多个缓冲区
缺点:- 无法在同一对象上绑定不同的缓冲区
- 启用OpenGL标头突出
好吧...太多弊端,该怎么办?您可以通过添加另一个抽象级别来删除GL / glew.h的抽象,在该抽象级别中将调用OpenGL函数(无论如何,如果计划支持OpenGL + DirectX,则应这样做)。与不同的缓冲区绑定要复杂一些,因为我们可以忘记禁止使用哪个缓冲区的哪个索引,因此可以选择添加另一个数组并向其中写入缓冲区类型。我还没有这样做;在开发的这个阶段,这对我来说已经足够了。奖金
为了更方便使用,我制作了一个范围绑定类。他在那: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:在依赖模板名称“ bind”之前缺少“ template”关键字。这意味着在这种情况下,当T为依赖类型时。编译器尚不知道m_ref是什么类型。由于尚无绑定,因此编译器仅在语法上对其进行处理,因此,<解释为小于。为了向编译器指示这实际上是对成员函数模板进行特殊化的调用,必须在dot运算符之后立即添加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,因为它不起作用,我还没有找到原因,但是我很快会弄清楚它的xDWrappers for Buffer Data和OpenGL调用尚未准备好,这些调用不会重写为DSA(直接状态访问)。感谢那些读到最后的人。我将很高兴提出批评和评论。