OpenGL RAII风格的原语

下午好,哈布拉用户。我已经写了很长时间了,也许有人在等我发来的文章-当然不是。由于我的空闲时间变得更多了,并且我的GitHub完全是空的,所以我决定编写我的克隆Mein kampf Minecraft。我极有可能对此进行记录-在habr.com上关注我的文章今天,我将展示如何将RAII样式的OpenGL原语(如果有意思的话)包装在cat下。

在评论中写下您想阅读的内容。我应该从头开始写Minecraft吗?现在开始吧。我将以Buffer Object为例我们要做的第一件事是在一个单独的类中进行主调用(glGenBuffersglDeleteBuffers)。

像这样的地方:

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,因为它不起作用,我还没有找到原因,但是我很快会弄清楚它的xD

Wrappers for Buffer Data和OpenGL调用尚未准备好,这些调用不会重写为DSA(直接状态访问)

感谢那些读到最后的人。我将很高兴提出批评和评论。

All Articles