
Olá pessoal. Todo mundo que conhece um pouco sobre o tópico OpenGL sabe que há um grande número de artigos e cursos sobre esse tópico, mas muitos não afetam a API moderna e alguns deles geralmente falam sobre
glBegin e
glEnd . Vou tentar cobrir algumas das nuances da nova API a partir da versão 4.
Link para a segunda parte do artigoDesta vez, tentarei escrever um artigo interessante e informativo, e o que aconteceu cabe aos bons moradores de habra decidirem. Por favor, perdoe-me pela minha gramática ruim (serei grato pelas correções).
Se você gosta, vou escrever sobre como otimizar o OpenGL e reduzir o DrawCalls.
Vamos começar!
O que haverá neste artigo - a funcionalidade do OpenGL modernoO que não estará neste artigo - abordagens modernas para renderização no OpenGLConteúdo:- Acesso direto ao estado
- Depurar
- Objetos Shader separados
- Matrizes de textura
- Vista de textura
- Buffer único para índice e vértice
- Tecelagem e sombreamento de computação
- Renderização de caminho
DSA (acesso direto ao estado)
Acesso direto ao estado - Acesso direto ao estado. Uma ferramenta para modificar objetos OpenGL sem precisar ajustá-los ao contexto. Isso permite alterar o estado de um objeto em um contexto local sem afetar o estado global compartilhado por todas as partes do aplicativo. Isso também torna a API um pouco mais orientada a objetos, pois funções que alteram o estado dos objetos podem ser claramente definidas. Isso é o que o
OpenGL Wiki nos diz.
Como sabemos, o OpenGL é uma API com muitos botões de opção -
glActiveTexture ,
glBindTexture , etc.
A partir daqui, temos alguns
problemas :
- O seletor e os estados atuais podem fazer uma alteração mais profunda no estado.
- Pode ser necessário vincular / alterar a unidade ativa para definir um filtro para texturas
- O gerenciamento de estado se torna problemático, como resultado do aumento da complexidade do aplicativo
- Estado desconhecido leva a configurações adicionais
- Tentativas de salvar / restaurar o estado podem ser problemáticas
O que o grupo Khronos nos oferece e como o DSA ajuda?
- Adiciona funções que funcionam diretamente com o objeto / objetos
- Define o filtro de textura para o objeto de textura especificado, não o atual.
- Vincula uma textura a uma unidade específica, não ao ativo
- Adiciona um número muito grande de novos recursos
- Abrange as coisas até o OpenGL 1.x
- Adiciona recursos extras.
Em teoria, um DSA pode ajudar a reduzir o número de operações sem renderização e com alteração de estado para zero ... Mas isso não é preciso.
Agora, examinarei brevemente algumas das novas funções, não vou me deter nos parâmetros em detalhes, deixarei links no wiki.
- glCreateTextures substitui glGenTextures + glBindTexture (inicialização).
Foi:
glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name);
Tornou-se:
glCreateTextures(GL_TEXTURE_2D, 1, &name);
- glTextureParameterX equivalente a glTexParameterX
glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
Agora vamos escrever assim:
glCreateTextures(GL_TEXTURE_2D, 1, &name); glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTextureStorage2D(name, 1, GL_RGBA8, width, height); glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
- glBindTextureUnit substitui glActiveTexture + glBindTexture
Aqui está como fizemos:
glActiveTexture(GL_TEXTURE0 + 3); glBindTexture(GL_TEXTURE_2D, name);
Agora:
glBindTextureUnit(3, name);
As alterações também afetaram o
glTextureImage , ele não é mais usado e eis o porquê:
glTexImage é bastante inseguro, é muito fácil obter texturas inválidas, porque a função não verifica os valores quando chamada, o driver faz isso enquanto desenha.
GlTexStorage foi adicionado para
substituí-lo .
O glTexStorage fornece uma maneira de criar texturas com verificações realizadas durante uma chamada, o que minimiza erros. O repositório de texturas é resolvido pela maioria dos problemas, se não todos, causados por texturas mutáveis, embora as texturas imutáveis sejam mais confiáveis.
As alterações também afetaram o buffer do quadro:
Esses não são todos os recursos alterados. O próximo na linha são as funções para os buffers:
Aqui está uma lista do que agora está incluído no suporte do DSA:
- Objetos de matriz de vértice
- Objetos framebuffer
- Objetos de programa
- Objetos de buffer
- Pilhas matriciais
- Muitas coisas obsoletas
Depurar
Desde a versão 4.3, novas funcionalidades foram adicionadas para depuração, na minha opinião, muito úteis e convenientes. Agora o OpenGL chamará nosso retorno de chamada para erros e mensagens de depuração, cujo nível podemos ajustar.

Precisamos chamar apenas duas funções para ativar:
glEnable &
glDebugMessageCallback , em nenhum lugar é mais fácil.
glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(message_callback, nullptr);
Agora vamos escrever uma função de retorno de chamada para receber a mensagem:
void callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param) { auto source_str = [source]() -> std::string { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY"; case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; default: return "UNKNOWN"; } }(); std::cout << source_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << std::endl; }
Também podemos configurar o filtro usando
glDebugMessageControl . O filtro pode funcionar no modo de filtragem por origem / tipo / importância ou um conjunto de mensagens usando seus identificadores.
Filtre mensagens em um escopo específico:
glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, DEPTH_FILL_ID, 11, “Depth Fill”);
Será muito útil desativar a chamada assíncrona para que possamos determinar a ordem das chamadas de função, bem como encontrar o local do erro na pilha durante a depuração. Isso é feito de maneira simples:
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
Vale lembrar que não é seguro chamar o OpenGL ou as funções de janela em uma função de chamada agrupada, assim como os retornos de chamada não são gratuitos e você não deve deixá-los incluídos na compilação do release.
Mais detalhes sobre essas e outras pequenas coisas - truques, você pode ler
aqui .
SSO (Objetos Shader separados)
Depois que o OpenGL trabalhou como um "pipeline fixo" - isso significava que o processamento pré-programado era aplicado a todos os dados transferidos para visualização. O próximo passo foi o "pipeline programável" - onde a parte programável implementa shaders, escrita em GLSL, o programa clássico GLSL consistia em shaders de vértice e fragmento, mas no OpenGL moderno foram adicionados novos tipos de shaders, a saber, shaders de geometria, ponderação e cálculos (sobre eles I Vou contar na próxima parte).
Os SSOs nos permitem alterar as etapas do shader rapidamente, sem vinculá-las novamente. A criação e configuração de um pipeline de software simples sem depuração é a seguinte:
GLuint pipe = GL_NONE;
Como vemos,
glCreateProgramPipelines gera um descritor e inicializa o objeto,
glCreateShaderProgramv gera, inicializa, compila e vincula o programa shader usando as fontes especificadas, e
glUseProgramStages anexa as etapas do programa ao objeto de pipeline.
glBindProgramPipeline -
associa um pipeline a um contexto.
Mas há uma ressalva, agora os parâmetros de entrada e saída dos shaders devem corresponder. Podemos declarar os parâmetros de entrada / saída na mesma ordem, com os mesmos nomes, ou fazemos com que a localização deles coincida claramente com a ajuda de qualificadores.
Eu recomendo a última opção, isso nos permitirá configurar uma interface claramente definida, além de ser flexível em relação a nomes e ordem.
Para fornecer uma interface mais rigorosa, também precisamos declarar os blocos de entrada e saída incorporados que queremos usar para cada estágio.
As interfaces de bloco integradas são definidas como (
de um wiki ):
Vértice:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
Controle de mosaico:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_out[];
Avaliação de mosaico:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
Geometria:
out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };
Um exemplo de declarar novamente um módulo interno e usar o local do atributo em um shader de vértice regular:
#version 450 out gl_PerVertex { vec4 gl_Position; }; layout (location = 0) in vec3 position; layout (location = 1) in vec3 color; layout (location = 0) out v_out { vec3 color; } v_out; void main() { v_out.color = color; gl_Position = vec4(position, 1.0); }