A programação gráfica não é apenas uma fonte de diversão, mas também de frustração quando algo não é exibido como pretendido, ou nada é exibido na tela. Vendo que a maior parte do que fazemos está relacionada à manipulação de pixels, pode ser difícil descobrir a causa do erro quando algo não funciona como deveria. Depurar esse tipo de erro é mais difícil do que erros na CPU. Não temos um console no qual podemos produzir o texto, não podemos colocar um ponto de interrupção no sombreador e não podemos apenas verificar e verificar o status do programa na GPU.
Neste tutorial, apresentaremos alguns dos métodos e técnicas de depuração para o seu programa OpenGL. Depurar no OpenGL não é tão difícil, e aprender alguns truques definitivamente valerá a pena.
ConteúdoParte 2. Iluminação básica Parte 3. Baixe modelos 3D Parte 4. Recursos avançados do OpenGL Parte 5. Iluminação Avançada glGetError ()
Quando você usa o OpenGL incorretamente (por exemplo, quando configura um buffer e esquece de vinculá-lo), o OpenGL notará e criará um ou mais sinalizadores de erro personalizados nos bastidores. Podemos rastrear esses erros chamando a função glGetError()
, que simplesmente verifica os sinalizadores de erro definidos e retorna o valor do erro se ocorrerem erros.
GLenum glGetError();
Esta função retorna um sinalizador de erro ou nenhum erro. Lista de valores de retorno:
Dentro da documentação para funções OpenGL, você pode encontrar códigos de erro gerados por funções usadas incorretamente. Por exemplo, se você olhar para a documentação da função glBindTexture()
, poderá encontrar os códigos de erro gerados por essa função na seção Erros.
Quando o sinalizador de erro é definido, nenhum outro sinalizador de erro será gerado. Além disso, quando glGetError
é chamado, a função apaga todos os sinalizadores de erro (ou apenas um em um sistema distribuído, veja abaixo). Isso significa que, se você chamar glGetError
uma vez após cada quadro e receber um erro, isso não significa que esse é o único erro e você ainda não sabe onde ocorreu esse erro.
Observe que, quando o OpenGL trabalha de maneira distribuída, como geralmente ocorre em sistemas com X11, outros erros podem ser gerados enquanto eles têm códigos diferentes. Chamar glGetError
simplesmente libera apenas um dos sinalizadores de código de erro em vez de todos. Por esse motivo, eles recomendam chamar essa função em um loop.
glBindTexture(GL_TEXTURE_2D, tex); std::cout << glGetError() << std::endl;
Um recurso distintivo do glGetError
é que torna relativamente fácil determinar onde qualquer erro pode ocorrer e verificar se o OpenGL está sendo usado corretamente. Digamos que você não desenhe nada e não saiba qual é o motivo: o buffer de quadro foi configurado incorretamente? Esqueceu de definir a textura? Ao chamar glGetError
em glGetError
lugar, você pode descobrir rapidamente onde ocorre o primeiro erro.
Por padrão, glGetError
relata apenas o número do erro, o que não é fácil de entender até que você memorize os números de código. Geralmente, faz sentido escrever uma pequena função para ajudar a imprimir uma sequência de erros junto com o local de onde a função é chamada.
GLenum glCheckError_(const char *file, int line) { GLenum errorCode; while ((errorCode = glGetError()) != GL_NO_ERROR) { std::string error; switch (errorCode) { case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break; } std::cout << error << " | " << file << " (" << line << ")" << std::endl; } return errorCode; } #define glCheckError() glCheckError_(__FILE__, __LINE__)
Se você decidir fazer mais chamadas para glCheckError
, será útil saber onde ocorreu o erro.
glBindBuffer(GL_VERTEX_ARRAY, vbo); glCheckError();
Conclusão:

Uma coisa importante permanece: há um bug de longa data no GLEW: glewInit()
sempre define o sinalizador GL_INVALID_ENUM
. Para corrigir isso, basta chamar glGetError
após glewInit
para limpar o sinalizador:
glewInit(); glGetError();
glGetError
não ajuda muito, pois as informações retornadas são relativamente simples, mas geralmente ajudam a detectar erros de digitação ou a localizar o local onde ocorreu o erro. Esta é uma ferramenta de depuração simples, mas eficaz.
Saída de depuração
A ferramenta é menos conhecida, mas mais útil que o glCheckError
, a extensão "debug output" do OpenGL, incluída no Perfil Principal do OpenGL 4.3. Com esta extensão, o OpenGL enviará uma mensagem de erro ao usuário com os detalhes do erro. Essa extensão não apenas fornece mais informações, mas também permite detectar erros onde eles ocorrem usando o depurador.
A saída de depuração está incluída no OpenGL a partir da versão 4.3, o que significa que você encontrará essa funcionalidade em qualquer máquina que suporte o OpenGL 4.3 e superior. Se esta versão não estiver disponível, você poderá verificar as extensões ARB_debug_output
e AMD_debug_output
. Também há informações não verificadas de que a saída de depuração não é suportada no OS X (o autor do original e o tradutor não testaram, informe o autor do original ou a mim em mensagens privadas através do mecanismo de correção de erros, se você encontrar confirmação ou refutação desse fato; UPD: Jeka178RUS verificou isso fato: fora da caixa, a saída de depuração não funciona, ele não verificou as extensões).
Para começar a usar a saída de depuração, precisamos solicitar o contexto de depuração do OpenGL durante o processo de inicialização. Esse processo é diferente em diferentes sistemas de janelas, mas aqui discutiremos apenas o GLFW, mas no final do artigo na seção "Materiais adicionais" você pode encontrar informações sobre outros sistemas de janelas.
Saída de depuração no GLFW
Solicitar contextos de depuração no GLFW é surpreendentemente simples: tudo o que você precisa fazer é dar ao GLFW uma dica de que queremos um contexto que suporte a saída de depuração. Precisamos fazer isso antes de chamar glfwCreateWindow
:
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
Assim que inicializamos o GLFW, devemos ter um contexto de depuração se usarmos o OpenGL 4.3 ou superior; caso contrário, precisamos tentar a sorte e esperar que o sistema ainda possa criar um contexto de depuração. Em caso de falha, precisamos solicitar a saída de depuração por meio do mecanismo de extensão OpenGL.
O contexto de depuração do OpenGL pode ser mais lento que o normal, portanto, você deve remover ou comentar esta linha enquanto trabalha em otimizações ou antes do lançamento.
Para verificar o resultado da inicialização do contexto de depuração, basta executar o seguinte código:
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
Como funciona a saída de depuração? Passamos uma função de retorno de chamada para um manipulador de mensagens no OpenGL (semelhante aos retornos de chamada no GLFW) e nessa função podemos processar os dados do OpenGL como desejamos, enviando mensagens de erro úteis para o console. O protótipo desta função:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, void *userParam);
Observe que em alguns sistemas operacionais o tipo do último parâmetro pode ser const void*
.
Dado o grande conjunto de dados que possuímos, podemos criar uma ferramenta útil de impressão de erros, como mostrado abaixo:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, void *userParam) {
Quando a extensão detecta um erro do OpenGL, ela chama essa função e podemos imprimir uma quantidade enorme de informações de erro. Observe que ignoramos alguns erros, pois eles são inúteis (por exemplo, 131185 nos drivers da NVidia indica que o buffer foi criado com sucesso).
Agora que temos o retorno de chamada desejado, é hora de inicializar a saída de depuração:
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glDebugOutput, nullptr); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); }
Então, dizemos ao OpenGL que queremos ativar a saída de depuração. A chamada para glEnable(GL_DEBUG_SYNCRHONOUS)
informa ao OpenGL que queremos uma mensagem de erro quando isso aconteceu.
Filtragem de saída de depuração
Com a função glDebugMessageControl
você pode selecionar os tipos de erros que deseja receber. No nosso caso, temos todos os tipos de erros. Se desejássemos apenas os erros da API do OpenGL, como Erro e o nível de significância Alto, escreveríamos o seguinte código:
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE);
Com esse contexto de configuração e depuração, todo comando incorreto do OpenGL envia muitas informações úteis:

Encontre a origem do erro através da pilha de chamadas
Outro truque com a saída de depuração é que você pode estabelecer com relativa facilidade o local exato do erro no seu código. Ao definir um ponto de interrupção na função DebugOutput
no tipo de erro desejado (ou no início da função, se você deseja capturar todos os erros), o depurador detectará o erro e você poderá navegar na pilha de chamadas para descobrir onde ocorreu o erro:

Isso requer alguma intervenção manual, mas se você souber aproximadamente o que está procurando, é incrivelmente útil determinar rapidamente qual chamada está causando o erro.
Erros próprios
Juntamente com os erros de leitura, podemos enviá-los ao sistema de saída de depuração usando glDebugMessageInsert
:
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM, -1, "error message here");
Isso é muito útil se você estiver se conectando a outro aplicativo ou código OpenGL que usa um contexto de depuração. Outros desenvolvedores poderão descobrir rapidamente qualquer erro relatado que ocorra no seu código OpenGL personalizado.
Em geral, a saída de depuração (se disponível) é muito útil para detectar erros rapidamente e definitivamente vale o esforço gasto no ajuste, pois economiza um tempo de desenvolvimento significativo. Você pode encontrar uma cópia do código fonte aqui usando glGetError
e saída de depuração. Existem erros, tente corrigi-los.
Saída de depuração do shader
Quando se trata de GLSL, não temos acesso a funções como glGetError
ou a capacidade de percorrer o código em etapas no depurador. Quando você encontra uma tela preta ou uma exibição completamente incorreta, pode ser muito difícil entender o que acontece se o problema estiver no sombreador. Sim, erros de compilação relatam erros de sintaxe, mas capturar erros semânticos é essa música.
Um dos métodos mais usados para descobrir o que há de errado com um sombreador é enviar todas as variáveis relevantes no programa de sombreador diretamente para o canal de saída do sombreador de fragmento. Ao enviar variáveis de sombreador diretamente para o canal de saída com cores, podemos encontrar informações interessantes verificando a imagem na saída. Por exemplo, precisamos descobrir se as normais estão corretas para o modelo. Podemos enviá-los (transformados ou não) do vértice para o sombreador de fragmentos, onde derivamos os normais mais ou menos assim:
(note lane: por que não há destaque de sintaxe para GLSL?)
#version 330 core out vec4 FragColor; in vec3 Normal; [...] void main() { [...] FragColor.rgb = Normal; FragColor.a = 1.0f; }
Ao enviar uma variável que não seja de cor para o canal de saída com a cor como está agora, podemos verificar rapidamente o valor da variável. Se, por exemplo, o resultado for uma tela preta, é evidente que as normais são transferidas incorretamente para os shaders e, quando exibidas, é relativamente fácil verificar se estão corretas:

A partir dos resultados visuais, podemos ver que os normais estão corretos, pois o lado direito do traje é predominantemente vermelho (o que significa que os normais aparecem aproximadamente na direção do eixo x de enxágüe) e também a frente do traje é colorida na direção do eixo z positivo (azul).
Essa abordagem pode ser estendida a qualquer variável que você deseja testar. Toda vez que você ficar preso e presumir que o erro está nos shaders, tente desenhar algumas variáveis ou resultados intermediários e descubra em qual parte do algoritmo há um erro.
Compilador de referência OpenGL GLSL
Cada driver de vídeo tem suas próprias peculiaridades. Por exemplo, os drivers da NVIDIA suavizam levemente os requisitos da especificação, e os drivers da AMD atendem melhor às especificações (o que é melhor, eu acho). O problema é que os shaders executados em uma máquina podem não ganhar dinheiro com outra por causa das diferenças nos drivers.
Por vários anos de experiência, você pode aprender todas as diferenças entre diferentes GPUs, mas se quiser ter certeza de que seus sombreadores funcionarão em todos os lugares, poderá verificar seu código com a especificação oficial usando o compilador de referência GLSL . Você pode fazer o download do chamado validador GLSL lang aqui ( fonte ).
Com este programa, você pode testar seus shaders passando-os como o primeiro argumento para o programa. Lembre-se de que o programa determina o tipo de sombreador por extensão:
.vert
: vertex shader.frag
: fragment shader.geom
: shader geométrico.tesc
: shader de controle de mosaico.tese
: sombreador de computação em mosaico.comp
: sombreador de computação
A execução do programa é fácil:
glslangValidator shader.vert
Observe que, se não houver erros, o programa não produzirá nada. Em um shader de vértice quebrado, a saída será semelhante a:

O programa não mostrará as diferenças entre os compiladores GLSL da AMD, NVidia ou Intel, e nem sequer pode relatar todos os erros no shader, mas pelo menos verifica os shaders quanto à conformidade com os padrões.
Saída do buffer de quadro
Outro método para o seu kit de ferramentas é exibir o conteúdo do buffer de quadros em uma parte específica da tela. Provavelmente, você costuma usar buffers de quadros e, como toda a mágica acontece nos bastidores, pode ser difícil determinar o que está acontecendo. A saída do conteúdo do buffer de quadros é um truque útil para verificar se as coisas estão corretas.
Observe que o conteúdo do buffer de quadros, conforme explicado aqui, funciona com texturas, não com objetos nos buffers de desenho
Usando um sombreador simples que desenha uma única textura, podemos escrever uma pequena função que desenha rapidamente qualquer textura no canto superior direito da tela:
Isso fornecerá uma pequena janela no canto da tela para depurar a saída do buffer do quadro. É útil, por exemplo, quando você tenta verificar a correção dos normais:

Você também pode expandir esta função para renderizar mais de 1 textura. Essa é uma maneira rápida de obter feedback contínuo de qualquer coisa nos buffers de quadro.
Programas de depurador externo
Quando tudo mais falha, há mais um truque: usar programas de terceiros. Eles estão embutidos no driver OpenGL e podem interceptar todas as chamadas OpenGL para fornecer muitos dados interessantes sobre o seu aplicativo. Esses aplicativos podem criar um perfil do uso das funções do OpenGL, procurar gargalos e monitorar buffers de quadro, texturas e memória. Ao trabalhar com código (grande), essas ferramentas podem se tornar inestimáveis.
Eu listei várias ferramentas populares. Experimente cada um e escolha o que melhor lhe convier.
Renderderoc
O RenderDoc é uma boa ferramenta de depuração separada (totalmente aberta ). Para iniciar a captura, selecione o arquivo executável e o diretório de trabalho. Seu aplicativo funciona normalmente e, quando você deseja assistir a um único quadro, permite que o RenderDoc capture vários quadros do seu aplicativo. Entre os quadros capturados, é possível visualizar o status do pipeline, todos os comandos do OpenGL, armazenamento em buffer e texturas usadas.

Codexl
A ferramenta de depuração CodeXL - GPU, funciona como um aplicativo e plug-in autônomo para o Visual Studio. CodeXL Fornece muitas informações e é ótimo para criar perfis de aplicativos gráficos. O CodeXL também roda em placas gráficas da NVidia e Intel, mas sem o suporte à depuração OpenCL.

Não usei muito o CodeXL, porque o RenderDoc parecia mais fácil para mim, mas incluí o CodeXL nesta lista porque ela parece uma ferramenta bastante confiável e é desenvolvida principalmente por um dos maiores fabricantes de GPUs.
NVIDIA Nsight
O Nsight é uma ferramenta popular de depuração da GPU NUIDIA . Não é apenas um plug-in para o Visual Studio e Eclipse, mas também um aplicativo separado . O plugin Nsight é uma coisa muito útil para desenvolvedores gráficos, pois coleta muitas estatísticas em tempo real sobre o uso da GPU e o estado quadro a quadro da GPU.
No momento em que você iniciar seu aplicativo através do Visual Studio ou Eclipse usando os comandos debug ou a criação de perfil do Nsight, ele será iniciado dentro do próprio aplicativo. Uma coisa boa no Nsight: renderizar um sistema de GUI (GUI, interface gráfica do usuário) sobre um aplicativo em execução que você pode usar para coletar todos os tipos de informações sobre seu aplicativo em tempo real ou análise quadro a quadro.

O Nsight é uma ferramenta muito útil, que, na minha opinião, ultrapassa as ferramentas acima, mas tem uma séria desvantagem: funciona apenas nas placas gráficas NVIDIA. Se você estiver usando placas gráficas NVIDIA e Visual Studio, vale a pena tentar o Nsight.
, ( , VOGL APItrace ), , . , , () ( , ).
- ? — Reto Koradi.
- — Vallentin Source.
PS : Temos um telegrama conf para coordenação de transferências. Se você tem um desejo sério de ajudar com a tradução, é bem-vindo!