Comment puis-je déboguer des shaders GLSL?

45

Lorsque vous écrivez des shaders non triviaux (comme pour tout autre élément de code non trivial), les gens font des erreurs. [la citation nécessaire] Cependant, je ne peux pas simplement le déboguer comme n'importe quel autre code - vous ne pouvez pas attacher simplement gdb ou le débogueur de Visual Studio après tout. Vous ne pouvez même pas faire de débogage printf, car il n’existe aucune forme de sortie de console. Ce que je fais habituellement, c'est rendre les données que je veux voir en couleur, mais c'est une solution très rudimentaire et amateur. Je suis sûr que les gens ont proposé de meilleures solutions.

Alors, comment puis-je déboguer un shader? Existe-t-il un moyen de parcourir un shader? Puis-je regarder l'exécution du shader sur un sommet / primitif / fragment spécifique?

(Cette question porte spécifiquement sur la façon de déboguer le code shader de la même manière que pour déboguer du code "normal", et non sur le débogage d'éléments tels que les changements d'état.)

Martin Ender
la source
Avez-vous examiné gDEBugger? Citant le site: "gDEBugger est un débogueur, analyseur de profils et analyseur de mémoire OpenGL et OpenCL avancé. GDEBugger fait ce qu'aucun autre outil ne peut vous permettre: suivre l'activité des applications au-dessus des API OpenGL et OpenCL et voir ce qu'il se passe dans la mise en œuvre du système. " Certes, pas de code de débogage / style VS, mais cela pourrait vous donner une idée de ce que votre shader fait (ou devrait faire). Crytec a publié un outil similaire pour le "débogage" de shader direct appelé RenderDoc (gratuit, mais strictement pour les shaders HLSL, donc peut-être pas pertinent pour vous).
Bert
@ Bert Hm ouais, je suppose que gDEBugger est l'équivalent OpenGL de WebGL-Inspector? J'ai utilisé ce dernier. C'est extrêmement utile, mais c'est certainement plus de débogage des appels OpenGL et des changements d'état que l'exécution du shader.
Martin Ender
1
Je n'ai jamais programmé WebGL et je ne connais donc pas WebGL-Inspector. Avec gDEBugger, vous pouvez au moins inspecter l’ensemble de l’état de votre pipeline de shader, y compris la mémoire de texture, les données de sommet, etc.
Bert
gDEBugger est extrêmement ancien et n'est pas supporté depuis longtemps. Si vous regardez à partir d'une analyse de l'état du GPU et de l'image, cela est une autre question qui est fortement liée: computergraphics.stackexchange.com/questions/23/…
cifz
Voici une méthode de débogage que j'ai suggérée à une question connexe: stackoverflow.com/a/29816231/758666
wip

Réponses:

26

Pour autant que je sache, aucun outil ne vous permet de parcourir le code dans un shader (dans ce cas également, vous devrez pouvoir sélectionner uniquement un pixel / sommet que vous souhaitez "déboguer", l'exécution risque fort de varient en fonction de cela).

Ce que je fais personnellement est un "débogage coloré" très fastidieux. Donc, je saupoudre un tas de branches dynamiques avec des #if DEBUG / #endifgardes qui disent essentiellement

#if DEBUG
if( condition ) 
    outDebugColour = aColorSignal;
#endif

.. rest of code .. 

// Last line of the pixel shader
#if DEBUG
OutColor = outDebugColour;
#endif

Ainsi, vous pouvez "observer" les informations de débogage de cette façon. Je fais habituellement diverses astuces, comme lerping ou mélange entre différents "codes de couleur" pour tester divers événements plus complexes ou des éléments non binaires.

Dans ce "cadre", je trouve également utile d’avoir un ensemble de conventions fixes pour les cas courants afin que, si je n’ai pas à revenir constamment en arrière et vérifier quelle couleur j’ai associée à quoi. L'important est d'avoir un bon support pour le rechargement à chaud du code shader, de sorte que vous puissiez modifier de manière presque interactive vos données / événements suivis et activer / désactiver facilement la visualisation de débogage.

Si vous avez besoin de déboguer quelque chose que vous ne pouvez pas afficher facilement à l'écran, vous pouvez toujours en faire de même et utiliser un analyseur à cadre unique pour inspecter vos résultats. J'ai énuméré quelques-uns d'entre eux comme réponse à cette autre question.

Bien sûr, il va sans dire que si je ne "débogue" pas un pixel shader ou un calcul shader, je transmets cette information "debugColor" à travers le pipeline sans l'interpoler (en GLSL avec flat mot clé).

Encore une fois, c’est très hacky et loin d’être un bon débogage, mais c’est ce qui m’empêche de ne pas connaître d’alternative appropriée.

cifz
la source
Quand ils sont disponibles, vous pouvez utiliser les SSBO pour obtenir un format de sortie plus flexible sans codage en couleurs. Cependant, le gros inconvénient de cette approche est qu’elle modifie le code ce qui peut masquer / modifier les bogues, en particulier lorsque UB est impliqué. +1 Néanmoins, c'est la méthode la plus directe disponible.
Personne le
9

Il y a aussi GLSL-Debugger . C'est un débogueur qui s'appelait auparavant "GLSL Devil".

Le débogueur lui-même est très pratique non seulement pour le code GLSL, mais également pour OpenGL. Vous avez la possibilité de passer d'un appel à un autre à un interrupteur Shader. Il affiche également les messages d'erreur communiqués par OpenGL à l'application elle-même.

Sepehr
la source
2
Notez que depuis le 2018-08-07, il ne prend en charge rien de plus grand que GLSL 1.2 et n'est pas maintenu activement.
Ruslan
Ce commentaire m'a légitimement rendu triste :(
rdelfin
Le projet est open source et aimerait vraiment aider à le moderniser. Il n'y a pas d'autre outil qui fait ce qu'il a fait.
XenonofArcticus
7

Il existe plusieurs offres de fournisseurs de GPU, telles que CodeXL d’AMD ou le débogueur nSight / Linux GFX de NVIDIA, qui permettent de passer à travers les shaders mais qui sont liées au matériel du fournisseur respectif.

Permettez-moi de noter que, bien qu’ils soient disponibles sous Linux, j’ai toujours eu très peu de succès à les utiliser là-bas. Je ne peux pas commenter la situation sous Windows.

L’option que j’ai récemment utilisée est de modulariser mon code de shader via #includeset de limiter le code inclus à un sous-ensemble commun de GLSL et C ++ & glm .

Lorsque je rencontre un problème, j'essaie de le reproduire sur un autre périphérique pour voir si le problème est identique, ce qui laisse supposer une erreur de logique (au lieu d'un problème de pilote / comportement non défini). Il existe également un risque de transmission de données erronées au GPU (par exemple, par des tampons mal liés, etc.) que j’élimine généralement en déboguant la sortie comme dans cifz answer ou en inspectant les données via apitrace .

Lorsqu'il s'agit d'une erreur de logique, j'essaie de reconstruire la situation à partir du GPU sur la CPU en appelant le code inclus sur la CPU avec les mêmes données. Ensuite, je peux le parcourir sur le processeur.

En vous appuyant sur la modularité du code, vous pouvez également essayer d'écrire unittest pour lui et comparer les résultats entre une exécution GPU et une exécution CPU. Cependant, vous devez savoir qu'il existe des cas où C ++ pourrait se comporter différemment du GLSL, vous donnant ainsi de faux positifs lors de ces comparaisons.

Enfin, lorsque vous ne pouvez pas reproduire le problème sur un autre périphérique, vous ne pouvez que commencer à comprendre d'où vient la différence. Unittests peut vous aider à préciser où cela se produit mais à la fin, vous aurez probablement besoin d'écrire des informations de débogage supplémentaires à partir du shader, comme dans cifz answer .

Et pour vous donner un aperçu, voici un organigramme de mon processus de débogage: Organigramme de la procédure décrite dans le texte

Pour terminer, voici une liste des avantages et inconvénients aléatoires:

pro

  • passer en revue avec le débogueur habituel
  • diagnostics supplémentaires (souvent meilleurs) du compilateur

con

Personne
la source
C'est une excellente idée et c'est probablement ce qui se rapproche le plus du code de shader à pas unique. Je me demande si le fait d'utiliser un moteur de rendu logiciel (Mesa?) Aurait des avantages similaires?
@racarate: J'y ai pensé aussi mais je n'ai pas encore eu le temps d'essayer. Je ne suis pas un expert en matière de mesa mais je pense qu'il pourrait être difficile de déboguer le shader, car les informations de débogage du shader doivent atteindre le débogueur. Là encore, peut-être que les gens de mesa ont déjà une interface pour le débogage de mesa elle-même :)
Personne
5

Bien qu'il ne semble pas possible de parcourir un shader OpenGL, il est possible d'obtenir les résultats de la compilation.
Ce qui suit est tiré de l' exemple de carton Android .

while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
    Log.e(TAG, label + ": glError " + error);
    throw new RuntimeException(label + ": glError " + error);

Si votre code est compilé correctement, vous n'avez alors pas d'autre choix que d'essayer une autre façon de vous communiquer l'état du programme. Vous pouvez indiquer qu'une partie du code a été atteinte, par exemple en modifiant la couleur d'un sommet ou en utilisant une texture différente. Ce qui est maladroit, mais semble être le seul moyen pour le moment.

EDIT: Pour WebGL, je regarde ce projet , mais je viens juste de le trouver ... je ne peux pas le garantir.

SL Barth - Rétablir Monica
la source
3
Hm ouais, je suis conscient que je peux avoir des erreurs de compilation. J'espérais un meilleur débogage d'exécution. J'ai également utilisé l'inspecteur WebGL dans le passé, mais je pense que cela ne montre que les changements d'état, mais vous ne pouvez pas envisager une invocation de shader. Je suppose que cela aurait pu être plus clair dans la question.
Martin Ender
2

Ceci est un copier-coller de ma réponse à la même question chez StackOverflow .


Au bas de cette réponse se trouve un exemple de code GLSL qui permet de générer la floatvaleur complète en couleur, codant pour IEEE 754 binary32. Je l'utilise comme suit (cet extrait donne le yycomposant 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 obtenez ceci à l’écran, vous pouvez n’importe quel sélecteur de couleur, formater la couleur au format HTML (en ajoutant 00à la rgbvaleur si vous n’avez pas besoin d’une précision supérieure, et en effectuant une deuxième passe pour obtenir l’octet inférieur si vous le faites), et vous obtenez la représentation hexadécimale de la floatcomme 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.;
}
Ruslan
la source
1

La solution qui a fonctionné pour moi est la compilation du code shader en C ++ - comme mentionné par Nobody. Il s’est avéré très efficace lorsque vous travaillez sur un code complexe même s’il nécessite un peu de configuration.

Je travaille principalement avec HLSL Compute Shaders pour lequel j'ai développé une bibliothèque de démonstration de concept disponible ici:

https://github.com/cezbloch/shaderator

Il explique sur un Compute Shader à partir d’exemples DirectX SDK, comment activer le débogage HLSL en C ++ et comment configurer des tests unitaires.

La compilation du calcul GLSL en C ++ semble plus facile que HLSL. Principalement en raison de constructions de syntaxe en HLSL. J'ai ajouté un exemple trivial de test unitaire exécutable sur un compilateur Shader GLSL que vous pouvez également trouver dans les sources du projet Shaderator sous le lien ci-dessus.

SpaceKees
la source