OpenGL ultramoderno. Parte 1



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 artigo

Desta 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 moderno
O que não estará neste artigo - abordagens modernas para renderização no OpenGL

Conteú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”); //  Render_Depth_Only_Pass(); //  glPopDebugGroup(); //  

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; // Create shaders GLuint fprog = glCreateShaderProgramv( GL_FRAGMENT_SHADER, 1, &text); GLuint vprog = glCreateShaderProgramv( GL_VERTEX_SHADER, 1, &text); // Bind pipeline glGenProgramPipelines( 1, &pipe); glBindProgramPipelines( pipe); // Bind shaders glUseProgramStages( pipe, GL_FRAGMENT_SHADER_BIT, fprog); glUseProgramStages( pipe, GL_VERTEX_SHADER_BIT, vprog); 

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); } 

Source: https://habr.com/ru/post/pt456932/


All Articles