Como depurar um shader GLSL?

193

Preciso depurar um programa GLSL, mas não sei como gerar resultado intermediário. É possível fazer alguns rastreamentos de depuração (como no printf) com o GLSL?

Franck Freiburger
fonte
6
... sem usar software externo como o glslDevil.
Franck Freiburger
dê uma olhada neste impressão de depuração de variáveis flutuador e textos de GLSL Fragmento shader você só precisa de unidade de textura de reposição única para fonte e estado constante de valor outputed na área impressa
Spektre

Respostas:

117

Você não pode se comunicar facilmente com a CPU de dentro do GLSL. Usar o glslDevil ou outras ferramentas é a sua melhor aposta.

Um printf exigiria tentar retornar à CPU a partir da GPU executando o código GLSL. Em vez disso, você pode tentar avançar para a tela. Em vez de tentar produzir texto, produza algo visualmente distinto na tela. Por exemplo, você pode pintar algo de uma cor específica apenas se atingir o ponto do seu código em que deseja adicionar um printf. Se você precisar imprimir um valor, poderá definir a cor de acordo com esse valor.

Mr. Berna
fonte
61
E se o motivo exato pelo qual você deseja depurar seu shader for porque nada está aparecendo na tela?
Jeroen
11
Por que você deseja depurar alguma coisa? Porque o seu código e ele quer examinar os valores de tempo de execução eu arriscaria ....
RichieHH
3
O GLSL-Debugger é um fork de código aberto do glslDevil.
Magnus
O @Magnus não é mais mantido ativamente e suporta apenas GLSL até 1.20.
Ruslan
57
void main(){
  float bug=0.0;
  vec3 tile=texture2D(colMap, coords.st).xyz;
  vec4 col=vec4(tile, 1.0);

  if(something) bug=1.0;

  col.x+=bug;

  gl_FragColor=col;
}
ste3e
fonte
8
É um dispositivo de depuração. Se você quiser saber onde está a posição da luz na cena, por exemplo, vá: if (lpos.x> 100) bug = 1.0. Se a posição da luz for maior que 100, a cena ficará vermelha.
ste3e
12

Eu achei o Transform Feedback uma ferramenta útil para depurar shaders de vértices. Você pode usar isso para capturar os valores das saídas do VS e lê-los novamente no lado da CPU, sem precisar passar pelo rasterizador.

Aqui está outro link para um tutorial sobre o Transform Feedback.

Espaço nulo
fonte
8

Se você deseja visualizar as variações de um valor na tela, pode usar uma função de mapa de calor semelhante a esta (escrevi em hlsl, mas é fácil adaptar-se ao glsl):

float4 HeatMapColor(float value, float minValue, float maxValue)
{
    #define HEATMAP_COLORS_COUNT 6
    float4 colors[HEATMAP_COLORS_COUNT] =
    {
        float4(0.32, 0.00, 0.32, 1.00),
        float4(0.00, 0.00, 1.00, 1.00),
        float4(0.00, 1.00, 0.00, 1.00),
        float4(1.00, 1.00, 0.00, 1.00),
        float4(1.00, 0.60, 0.00, 1.00),
        float4(1.00, 0.00, 0.00, 1.00),
    };
    float ratio=(HEATMAP_COLORS_COUNT-1.0)*saturate((value-minValue)/(maxValue-minValue));
    float indexMin=floor(ratio);
    float indexMax=min(indexMin+1,HEATMAP_COLORS_COUNT-1);
    return lerp(colors[indexMin], colors[indexMax], ratio-indexMin);
}

Então, no seu pixel shader, você apenas gera algo como:

return HeatMapColor(myValue, 0.00, 50.00);

E pode ter uma idéia de como isso varia entre os pixels:

insira a descrição da imagem aqui

Claro que você pode usar qualquer conjunto de cores que desejar.

limpe
fonte
6

O GLSL Sandbox tem sido bastante útil para mim para shaders.

Não é a depuração propriamente dita (que foi respondida como incapaz), mas útil para ver as alterações na saída rapidamente.

Nick Devereaux
fonte
4

Você pode tentar o seguinte: https://github.com/msqrt/shader-printf, que é uma implementação chamada apropriadamente "Funcionalidade simples de impressão para GLSL".

Você também pode experimentar o ShaderToy e assistir a um vídeo como este ( https://youtu.be/EBrAdahFtuo ) no canal do YouTube "The Art of Code", onde você pode ver algumas das técnicas que funcionam bem para depuração e visualizando. Eu recomendo fortemente o canal dele, pois ele escreve algumas coisas realmente boas e ele também tem a habilidade de apresentar idéias complexas em formatos inovadores, altamente envolventes e fáceis de digerir (o vídeo de Mandelbrot é um excelente exemplo disso: https: // youtu.be/6IWXkV82oyY )

Espero que ninguém se importe com essa resposta tardia, mas a pergunta está no alto das pesquisas no Google por depuração do GLSL e, é claro, muita coisa mudou em 9 anos :-)

PS: Outras alternativas também podem ser o NVIDIA nSight e o AMD ShaderAnalyzer, que oferecem um depurador completo para shaders.

Ian Macintosh
fonte
2

Estou compartilhando um exemplo de shader de fragmento, como eu realmente depuro.

#version 410 core

uniform sampler2D samp;
in VS_OUT
{
    vec4 color;
    vec2 texcoord;
} fs_in;

out vec4 color;

void main(void)
{
    vec4 sampColor;
    if( texture2D(samp, fs_in.texcoord).x > 0.8f)  //Check if Color contains red
        sampColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);  //If yes, set it to white
    else
        sampColor = texture2D(samp, fs_in.texcoord); //else sample from original
    color = sampColor;

}

insira a descrição da imagem aqui

user1767754
fonte
2

Na parte inferior desta resposta, há um exemplo de código GLSL que permite gerar o floatvalor completo como cor, codificando IEEE 754 binary32. Eu o uso da seguinte forma (este trecho fornece o yycomponente da matriz modelview):

vec4 xAsColor=toColor(gl_ModelViewMatrix[1][1]);
if(bool(1)) // put 0 here to get lowest byte instead of three highest
    gl_FrontColor=vec4(xAsColor.rgb,1);
else
    gl_FrontColor=vec4(xAsColor.a,0,0,1);

Depois de obtê-lo na tela, você pode escolher qualquer seletor de cores, formatar a cor como HTML (acrescentando 00ao rgbvalor se você não precisar de maior precisão e fazendo uma segunda passagem para obter o byte mais baixo, se necessário), e você obtém a representação hexadecimal do floatIEEE 754 binary32.

Aqui está a implementação real de toColor():

const int emax=127;
// Input: x>=0
// Output: base 2 exponent of x if (x!=0 && !isnan(x) && !isinf(x))
//         -emax if x==0
//         emax+1 otherwise
int floorLog2(float x)
{
    if(x==0.) return -emax;
    // NOTE: there exist values of x, for which floor(log2(x)) will give wrong
    // (off by one) result as compared to the one calculated with infinite precision.
    // Thus we do it in a brute-force way.
    for(int e=emax;e>=1-emax;--e)
        if(x>=exp2(float(e))) return e;
    // If we are here, x must be infinity or NaN
    return emax+1;
}

// Input: any x
// Output: IEEE 754 biased exponent with bias=emax
int biasedExp(float x) { return emax+floorLog2(abs(x)); }

// Input: any x such that (!isnan(x) && !isinf(x))
// Output: significand AKA mantissa of x if !isnan(x) && !isinf(x)
//         undefined otherwise
float significand(float x)
{
    // converting int to float so that exp2(genType) gets correctly-typed value
    float expo=float(floorLog2(abs(x)));
    return abs(x)/exp2(expo);
}

// Input: x\in[0,1)
//        N>=0
// Output: Nth byte as counted from the highest byte in the fraction
int part(float x,int N)
{
    // All comments about exactness here assume that underflow and overflow don't occur
    const float byteShift=256.;
    // Multiplication is exact since it's just an increase of exponent by 8
    for(int n=0;n<N;++n)
        x*=byteShift;

    // Cut higher bits away.
    // $q \in [0,1) \cap \mathbb Q'.$
    float q=fract(x);

    // Shift and cut lower bits away. Cutting lower bits prevents potentially unexpected
    // results of rounding by the GPU later in the pipeline when transforming to TrueColor
    // the resulting subpixel value.
    // $c \in [0,255] \cap \mathbb Z.$
    // Multiplication is exact since it's just and increase of exponent by 8
    float c=floor(byteShift*q);
    return int(c);
}

// Input: any x acceptable to significand()
// Output: significand of x split to (8,8,8)-bit data vector
ivec3 significandAsIVec3(float x)
{
    ivec3 result;
    float sig=significand(x)/2.; // shift all bits to fractional part
    result.x=part(sig,0);
    result.y=part(sig,1);
    result.z=part(sig,2);
    return result;
}

// Input: any x such that !isnan(x)
// Output: IEEE 754 defined binary32 number, packed as ivec4(byte3,byte2,byte1,byte0)
ivec4 packIEEE754binary32(float x)
{
    int e = biasedExp(x);
    // sign to bit 7
    int s = x<0. ? 128 : 0;

    ivec4 binary32;
    binary32.yzw=significandAsIVec3(x);
    // clear the implicit integer bit of significand
    if(binary32.y>=128) binary32.y-=128;
    // put lowest bit of exponent into its position, replacing just cleared integer bit
    binary32.y+=128*int(mod(float(e),2.));
    // prepare high bits of exponent for fitting into their positions
    e/=2;
    // pack highest byte
    binary32.x=e+s;

    return binary32;
}

vec4 toColor(float x)
{
    ivec4 binary32=packIEEE754binary32(x);
    // Transform color components to [0,1] range.
    // Division is inexact, but works reliably for all integers from 0 to 255 if
    // the transformation to TrueColor by GPU uses rounding to nearest or upwards.
    // The result will be multiplied by 255 back when transformed
    // to TrueColor subpixel value by OpenGL.
    return vec4(binary32)/255.;
}
Ruslan
fonte
1

Faça a renderização offline de uma textura e avalie os dados da textura. Você pode encontrar código relacionado pesquisando no opengl "renderizar textura". Em seguida, use o glReadPixels para ler a saída em uma matriz e executar asserções (uma vez que analisar uma matriz tão grande no depurador geralmente não é realmente útil).

Além disso, convém desativar a fixação para valores de saída que não estão entre 0 e 1, que são suportados apenas para texturas de ponto flutuante .

Pessoalmente, fiquei incomodado com o problema de depurar shaders corretamente por um tempo. Não parece ser uma boa maneira - Se alguém encontrar um bom depurador (e não desatualizado / obsoleto), informe-me.

Domi
fonte
3
Qualquer resposta ou comentário que diga "google xyz" deve ser banido ou recusado pelo Stackoverflow.
gregoiregentil
1

As respostas existentes são boas, mas eu queria compartilhar mais uma pequena gema que foi valiosa na depuração de problemas complicados de precisão em um shader GLSL. Com números int muito grandes representados como um ponto flutuante, é necessário ter o cuidado de usar o piso (n) e o piso (n + 0,5) adequadamente para implementar round () com um int exato. É então possível renderizar um valor flutuante que é um int exato pela lógica a seguir para compactar os componentes de bytes nos valores de saída R, G e B.

  // Break components out of 24 bit float with rounded int value
  // scaledWOB = (offset >> 8) & 0xFFFF
  float scaledWOB = floor(offset / 256.0);
  // c2 = (scaledWOB >> 8) & 0xFF
  float c2 = floor(scaledWOB / 256.0);
  // c0 = offset - (scaledWOB << 8)
  float c0 = offset - floor(scaledWOB * 256.0);
  // c1 = scaledWOB - (c2 << 8)
  float c1 = scaledWOB - floor(c2 * 256.0);

  // Normalize to byte range
  vec4 pix;  
  pix.r = c0 / 255.0;
  pix.g = c1 / 255.0;
  pix.b = c2 / 255.0;
  pix.a = 1.0;
  gl_FragColor = pix;
MoDJ
fonte