web-dev-qa-db-fra.com

Comment déboguer un shader GLSL?

Je dois déboguer un programme GLSL mais je ne sais pas comment afficher un résultat intermédiaire. Est-il possible de créer des traces de débogage (comme avec printf) avec GLSL?

173
Franck Freiburger

Vous ne pouvez pas communiquer facilement avec la CPU depuis GLSL. Utiliser glslDevil ou d’autres outils est votre meilleur choix.

Une printf nécessiterait d’essayer de revenir dans la CPU à partir du GPU exécutant le code GLSL. Au lieu de cela, vous pouvez essayer d’avancer à l’écran. Au lieu d'essayer de produire du texte, affichez quelque chose de distinctif à l'écran. Par exemple, vous pouvez peindre quelque chose d'une couleur spécifique uniquement si vous atteignez le point de votre code où vous souhaitez ajouter un printf. Si vous devez imprimer une valeur, vous pouvez définir la couleur en fonction de cette valeur.

105
Mr. Berna
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;
}
55
ste3e

J'ai trouvé Transform Feedback un outil utile pour le débogage des shaders de vertex. Vous pouvez l'utiliser pour capturer les valeurs des sorties du système virtuel et les relire du côté de la CPU, sans avoir à passer par le rasterizer.

Here est un autre lien vers un tutoriel sur Transform Feedback.

12
nullspace

Si vous souhaitez visualiser les variations d'une valeur sur l'écran, vous pouvez utiliser une fonction heatmap similaire à celle-ci (je l'ai écrite en hlsl, mais il est facile de s'adapter à 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);
}

Ensuite, dans votre pixel shader, vous produisez quelque chose comme:

return HeatMapColor(myValue, 0.00, 50.00);

Et pouvez avoir une idée de la façon dont cela varie selon vos pixels:

enter image description here

Bien sûr, vous pouvez utiliser n'importe quel jeu de couleurs que vous aimez.

8
wip

GLSL Sandbox m'a été très utile pour les shaders.

Pas de débogage en tant que tel (auquel on a répondu comme étant incapable), mais pratique pour voir rapidement les changements de sortie.

6
Nick Devereaux

Effectuez un rendu hors ligne d'une texture et évaluez les données de la texture. Ensuite, utilisez glReadPixels pour lire la sortie dans un tableau et effectuer des assertions sur celui-ci (dans la mesure où regarder à travers un aussi grand tableau dans le débogueur n'est pas vraiment utile).

De même, vous pouvez également désactiver le verrouillage pour obtenir des valeurs non comprises entre 0 et 1, qui ne sont prises en charge que par textures à virgule flottante .

Personnellement, le problème de corriger correctement les shaders pendant un certain temps me gênait. Cela ne semble pas être un bon moyen - Si quelqu'un trouve un bon débogueur (et non obsolète/obsolète), veuillez me le faire savoir.

2
Domi

Je partage un exemple de fragment shader, comment je débogue réellement.

#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;

}

enter image description here

2
user1767754

Vous pouvez essayer ceci: https://github.com/msqrt/shader-printf qui est une implémentation appelée "Fonctionnalité d'impression simple pour GLSL".

Vous pouvez également essayer ShaderToy, et peut-être regarder une vidéo comme celle-ci ( https://youtu.be/EBrAdahFtuo ) de la chaîne YouTube "The Art of Code" où vous pouvez voir certaines des techniques qui fonctionnent bien pour le débogage et la visualisation. Je peux fortement recommander sa chaîne car il écrit de très bonnes choses et il a également le don de présenter des idées complexes dans des formats nouveaux, très attrayants et faciles à digérer (sa vidéo sur Mandelbrot en est un superbe exemple: https://youtu.be/6IWXkV82oyY )

J'espère que cette réponse tardive ne dérangera personne, mais la question occupe une place importante dans les recherches Google pour le débogage GLSL et beaucoup a bien sûr changé en 9 ans :-)

PS: D'autres alternatives pourraient également être NVIDIA nSight et AMD ShaderAnalyzer, qui offrent un débogueur progressif complet pour les shaders.

1
Ian Macintosh

Au bas de cette réponse se trouve un exemple de code GLSL qui permet de sortir la valeur complète de float en couleur, codant pour IEEE 754 binary32. Je l'utilise comme suit (cet extrait donne le composant yy de la matrice 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);

Une fois que vous l’aurez à l’écran, vous pouvez choisir n’importe quel sélecteur de couleur, formater la couleur au format HTML (en ajoutant 00 À la valeur rgb si vous n’avez pas besoin de plus de précision et en effectuant une seconde. passez pour obtenir l’octet inférieur si vous le faites), et vous obtenez la représentation hexadécimale de float sous la forme IEEE 754 binary32.

Voici l'implémentation réelle 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.;
}
1
Ruslan

Les réponses existantes sont toutes bonnes, mais je voulais partager un petit bijou supplémentaire qui a été précieux pour le débogage de problèmes de précision délicats dans un shader GLSL. Avec de très grands nombres int représentés sous forme de virgule flottante, il faut veiller à utiliser sol (n) et sol (n + 0,5) correctement pour mettre en œuvre round () avec un int exact. Il est ensuite possible de rendre une valeur float qui est un entier exact à l'aide de la logique suivante pour compresser les composants d'octet dans les valeurs de sortie R, V et 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;
1
MoDJ