Boa tarde, usuários Habra. Não escrevo há muito tempo e talvez alguém estivesse esperando artigos meus - claro que não. Como meu tempo livre ficou um pouco mais e meu GitHub está completamente vazio, decidi escrever meu clone Mein kampf Minecraft. Com alta probabilidade, vou documentar isso - siga meus artigos no habr.com . Hoje vou mostrar como envolvi as primitivas OpenGL no estilo RAII, se for interessante - sob o gato.Escreva nos comentários sobre o que você gostaria de ler. Devo escrever artigos do zero do Minecraft? Agora vamos começar. Vou mostrar o Buffer Object como um exemplo . A primeira coisa que faremos é fazer as chamadas principais ( glGenBuffers , glDeleteBuffers ) em uma classe separada.Em algum lugar como este: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;
};
Prós:- Não há necessidade de monitorar a exclusão do buffer
- A ativação do cabeçalho OpenGL não se destaca (se transferirmos a definição de funções para * .cpp)
Minuses:- Não podemos especificar o tipo de buffer (matriz, elemento ...) ao vincular
- Não é possível criar vários buffers com uma única chamada para glGenBuffers
- Problemas ao acessar um objeto remoto se o buffer foi copiado
Para resolver o problema da cópia, simplesmente o proibimos. Para fazer isso, crie uma classe não copiável e a herde.Exemplo de classe não copiável:struct noncopyable
{
noncopyable() = default;
noncopyable(noncopyable&&) = default;
noncopyable& operator = (noncopyable&&) = default;
noncopyable(const noncopyable&) = delete;
noncopyable& operator = (const noncopyable&) = delete;
};
Ótimo, menos um problema. Pensamos ainda mais, como resolveríamos o problema com a ligação ... adicione as funções 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 é uma classe enum, mas você pode fazer const externo (em * .hpp) e em (* .cpp) fazer algo como:const uint32_t array_buffer = GL_ARRAY_BUFFER;
Mais uma vez, isso não se destacaria#include <GL/glew.h>
Prós:- Não há necessidade de monitorar a exclusão do buffer
- A ativação do cabeçalho OpenGL não se destaca (se transferirmos a definição de funções para * .cpp)
- Podemos indicar o tipo de buffer (matriz, elemento ...) ao vincular
- Sem cópia - sem problemas de cópia
Minuses:- Não é possível criar vários buffers com uma única chamada para glGenBuffers
Então, quase tudo é super, mas ainda não podemos criar vários buffers ... Vamos corrigi-lo.O glGenBuffers pode levar um ponteiro para uma matriz e o número de buffers a serem gerados.Precisamos de uma matriz, poderíamos usar std :: vector, mas precisamos alocar memória apenas uma vez, e eu preferiria std :: array aqui, embora no futuro tenhamos que fazer mais um nível de abstração por causa disso.Reescrevemos nossa classe para std :: array e adicionamos alguns modelos: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;
};
E aqui está o que conseguimos.Prós:- Não há necessidade de monitorar a exclusão do buffer
- Podemos indicar o tipo de buffer (matriz, elemento ...) ao vincular
- Sem cópia - sem problemas de cópia
- Podemos criar vários buffers com uma única chamada para glGenBuffers
Minuses:- Não é possível vincular buffers diferentes no mesmo objeto
- Habilitando o cabeçalho do OpenGL
Bem ... muitos contras, o que pode ser feito?Você pode remover a inclusão de GL / glew.h adicionando outro nível de abstração no qual as funções do OpenGL serão chamadas (em qualquer caso, isso deve ser feito se o suporte ao OpenGL + DirectX estiver planejado). A ligação com buffers diferentes é um pouco mais complicada, pois podemos esquecer qual índice com qual buffer foi banido, como opção, adicionar outra matriz e gravar o tipo de buffer. Eu ainda não o fiz; neste estágio de desenvolvimento, isso é suficiente para mim.Bônus
Para uso mais conveniente, criei uma classe de ligação com escopo definido. Ali está ele: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;
};
Alguns podem ficar intrigados com esta linha:m_ref.template bind<index>();
Se não especificarmos a palavra-chave do modelo, capturaremos o seguinte erro:binder.hpp: 22: 46 : Palavra-chave 'template' ausente antes do nome do modelo dependente 'bind'Isso significa que, nesse caso, quando T é um tipo dependente. O compilador ainda não sabe qual é o tipo m_ref. Como ainda não havia ligação, o compilador a processa de forma puramente sintática, portanto, <é interpretado como um operador menor que. Para indicar ao compilador que isso é, de fato, uma especialização de um modelo de função de membro, você deve adicionar a palavra-chave template imediatamente após o operador de ponto.E um exemplo de uso: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);
}
Esse código não usa o objeto para a matriz de vértices, porque não funciona, não descobri os motivos, mas logo descobrirei o xDAlém disso, os wrappers para chamadas de dados de buffer e OpenGL ainda não estão prontos, eles não são reescritos no DSA (Direct State Access) .Obrigado a quem leu até o fim. Ficarei muito feliz em receber críticas e comentários.