Good afternoon, Habra users. I haven’t written for a long time and maybe someone was waiting for articles from me - of course not. Since my free time became a bit more, and my GitHub is completely empty, I decided to write my clone Mein kampf Minecraft. With high probability, I will document this - follow my articles on habr.com . Today I will show how I wrapped OpenGL primitives in the RAII style, if it’s interesting - under cat.Write in the comments what you would like to read about. Should I write Minecraft from scratch articles? Now let's get started. I will show the Buffer Object as an example . The first thing we will do is make the main calls ( glGenBuffers , glDeleteBuffers ) in a separate class.Somewhere like this: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;
};
Pros:- No need to monitor buffer deletion
- Enabling the OpenGL header does not stick out (if we transfer the definition of functions to * .cpp)
Minuses:- We cannot specify the type of buffer (array, element ...) when binding
- Cannot create multiple buffers with a single call to glGenBuffers
- Problems accessing a remote object if the buffer was copied
In order to solve the problem with copying, we simply prohibit it. To do this, create a noncopyable class and inherit it.Noncopyable class example:struct noncopyable
{
noncopyable() = default;
noncopyable(noncopyable&&) = default;
noncopyable& operator = (noncopyable&&) = default;
noncopyable(const noncopyable&) = delete;
noncopyable& operator = (const noncopyable&) = delete;
};
Great, minus one problem. We think further, how would we solve the problem with binding ... add the bind / unbind functions:void bind(buffer_type type) noexcept {
glBindBuffer(static_cast<GLenum>(type), m_object);
}
void unbind() noexcept {
glBindBuffer(static_cast<GLenum>(type), 0);
}
buffer_type is an enum class, but you can do extern const (in * .hpp), and in (* .cpp) do something like:const uint32_t array_buffer = GL_ARRAY_BUFFER;
Again, that would not stick out#include <GL/glew.h>
Pros:- No need to monitor buffer deletion
- Enabling the OpenGL header does not stick out (if we transfer the definition of functions to * .cpp)
- We can indicate the type of buffer (array, element ...) when binding
- No copying - no copying problems
Minuses:- Cannot create multiple buffers with a single call to glGenBuffers
So, almost everything is super, but we still cannot create several buffers ... Let's fix it.glGenBuffers can take a pointer to an array and the number of buffers to generate.We need an array, we could use std :: vector, but we need to allocate memory only once and I would prefer std :: array here, although in the future we will have to do this because of this another level of abstraction.We rewrite our class to std :: array and add a bit of templates: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;
};
And here is what we got.Pros:- No need to monitor buffer deletion
- We can indicate the type of buffer (array, element ...) when binding
- No copying - no copying problems
- We can create several buffers with a single call to glGenBuffers
Minuses:- Cannot bind different buffers on the same object
- Enabling OpenGL Header Sticks Out
Well ... too many cons, what can be done?You can remove the inclusion of GL / glew.h by adding another level of abstraction in which the OpenGL functions will be called (in any case, this should be done if support for OpenGL + DirectX is planned). Binding with different buffers is a bit more complicated, since we can forget which index with which buffer was banned, as an option, add another array and write the buffer type to it. I have not done this yet; at this stage of development, this is enough for me.Bonus
For more convenient use, I made a scoped bind class. There he is: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;
};
Some may be puzzled by this line:m_ref.template bind<index>();
If we do not specify the template keyword, then we catch the following error:binder.hpp: 22: 46: Missing 'template' keyword prior to dependent template name 'bind'This means that in this case, when T is a dependent type. The compiler does not yet know what type m_ref is. Since there was no binding yet, the compiler processes it purely syntactically, therefore, <is interpreted as an operator less than. To indicate to the compiler that this is, in fact, a specialization of a member function template, you must add the template keyword immediately after the dot operator.And an example of use: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);
}
This code does not use the object for Vertex Array, because it does not work, I haven’t figured out the reasons yet, but I will figure it out soon xD Thewrappers for Buffer Data and OpenGL calls are not ready yet, the calls are not rewritten to DSA (Direct State Access) .Thanks to those who read it to the end. I will be very glad to criticism and comments.