Wie kann ich GLSL-Shader debuggen?

45

Beim Schreiben nicht-trivialer Shader (genau wie beim Schreiben eines anderen nicht-trivialen Codes) machen die Leute Fehler. [Bearbeiten] Ich kann aber nicht einfach debuggen wie jeder andere Code - Sie können nicht nur gdb anhängen oder das Visual Studio - Debugger , nachdem alle. Sie können nicht einmal printf debuggen, da es keine Form der Konsolenausgabe gibt. Normalerweise rendere ich die Daten, die ich betrachten möchte, als Farbe, aber das ist eine sehr rudimentäre und amateurhafte Lösung. Ich bin sicher, die Leute haben bessere Lösungen gefunden.

Wie kann ich einen Shader tatsächlich debuggen? Gibt es eine Möglichkeit, durch einen Shader zu treten? Kann ich mir die Ausführung des Shaders auf einem bestimmten Vertex / Primitiv / Fragment ansehen?

(Diese Frage bezieht sich speziell auf das Debuggen von Shader-Code, ähnlich wie man "normalen" Code debuggen würde, und nicht auf das Debuggen von Dingen wie Statusänderungen.)

Martin Ender
quelle
Haben Sie sich mit gDEBugger befasst? Zitieren der Site: "gDEBugger ist ein fortschrittlicher OpenGL- und OpenCL-Debugger, Profiler und Speicheranalysator. GDEBugger macht, was kein anderes Tool kann - Sie können die Anwendungsaktivität über die OpenGL- und OpenCL-APIs verfolgen und sehen, was in der Systemimplementierung geschieht. " Zugegeben, kein Debuggen / Durchlaufen von Code im VS-Stil, aber es kann Ihnen einen Einblick geben, was Ihr Shader tut (oder tun sollte). Crytec hat ein ähnliches Tool für das "Debugging" von Direct Shadern namens RenderDoc veröffentlicht (kostenlos, jedoch ausschließlich für HLSL-Shader, daher möglicherweise nicht relevant für Sie).
Bert
@ Bert Hm ja, ich denke gDEBugger ist das OpenGL-Äquivalent zu WebGL-Inspector? Ich habe das letztere benutzt. Es ist immens nützlich, aber es ist definitiv mehr das Debuggen von OpenGL-Aufrufen und Statusänderungen als die Shader-Ausführung.
Martin Ender
1
Ich habe noch nie eine WebGL-Programmierung durchgeführt und bin daher mit WebGL-Inspector nicht vertraut. Mit gDEBugger können Sie zumindest den gesamten Status Ihrer Shader-Pipeline einschließlich Texturspeicher, Scheitelpunktdaten usw. überprüfen.
Bert
gDEBugger ist extrem alt und wird seit einiger Zeit nicht mehr unterstützt. Wenn Sie nach einer
Frame-
Hier ist eine Debug-Methode, die ich zu einer verwandten Frage vorgeschlagen habe: stackoverflow.com/a/29816231/758666
wip

Antworten:

26

Soweit mir bekannt ist, gibt es keine Tools, mit denen Sie in einem Shader durch den Code navigieren können (auch wenn Sie in diesem Fall nur einen Pixel / Vertex auswählen müssen, den Sie "debuggen" möchten, ist die Ausführung wahrscheinlich variieren je nachdem).

Was ich persönlich mache, ist ein sehr hackiges "buntes Debugging". Also streue ich ein paar dynamische Äste mit #if DEBUG / #endifWachen, die im Grunde genommen sagen

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

.. rest of code .. 

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

Auf diese Weise können Sie Debug-Informationen "beobachten". Normalerweise mache ich verschiedene Tricks wie Lerping oder Mischen zwischen verschiedenen "Farbcodes", um verschiedene komplexere Ereignisse oder nicht-binäre Dinge zu testen.

In diesem "Rahmen" finde ich es auch nützlich, eine Reihe von festen Konventionen für häufig vorkommende Fälle zu haben, damit ich nicht ständig zurückgehen und überprüfen muss, welche Farbe ich mit welcher assoziiert habe. Das Wichtigste ist, eine gute Unterstützung für das Hot-Reload von Shader-Code zu haben, sodass Sie Ihre verfolgten Daten / Ereignisse fast interaktiv ändern und die Debug-Visualisierung einfach ein- und ausschalten können.

Wenn Sie Fehler beheben müssen, die nicht einfach auf dem Bildschirm angezeigt werden können, können Sie immer dasselbe tun und ein Frame-Analysetool verwenden, um Ihre Ergebnisse zu überprüfen. Ich habe einige von ihnen als Antwort auf diese andere Frage aufgelistet .

Es versteht sich von selbst, dass ich, wenn ich keinen Pixel-Shader oder Compute-Shader "debugge", diese "debugColor" -Information in der gesamten Pipeline weitergebe, ohne sie zu interpolieren (in GLSL mit flat Schlüsselwort).

Auch dies ist sehr hackig und weit davon entfernt, das richtige Debuggen durchzuführen, aber ich bin fest davon überzeugt, dass ich keine richtige Alternative kenne.

cifz
quelle
Wenn sie verfügbar sind, können Sie SSBOs verwenden, um ein flexibleres Ausgabeformat zu erhalten, bei dem Sie keine Farben codieren müssen. Der große Nachteil dieses Ansatzes ist jedoch, dass er den Code verändert, der Fehler verbergen / ändern kann, insbesondere wenn UB beteiligt ist. +1 Trotzdem ist es die direkteste Methode, die verfügbar ist.
Niemand
9

Es gibt auch einen GLSL-Debugger . Es ist ein Debugger, der früher als "GLSL Devil" bekannt war.

Der Debugger selbst ist nicht nur für GLSL-Code, sondern auch für OpenGL selbst sehr praktisch. Sie haben die Möglichkeit, zwischen Zeichnungsaufrufen zu wechseln und Shader-Schalter zu deaktivieren. Außerdem werden Fehlermeldungen angezeigt, die von OpenGL an die Anwendung selbst zurückgemeldet werden.

Sepehr
quelle
2
Beachten Sie, dass es ab dem 07.08.2018 keine höheren Versionen als GLSL 1.2 unterstützt und nicht aktiv gewartet wird.
Ruslan
Dieser Kommentar hat mich zu
Recht
Das Projekt ist Open Source und würde sehr gerne dabei helfen, es zu modernisieren. Es gibt kein anderes Tool, das das tut, was es getan hat.
XenonofArcticus
7

Es gibt verschiedene Angebote von GPU-Anbietern wie AMDs CodeXL oder NVIDIAs nSight / Linux GFX-Debugger, die das Durchlaufen von Shadern ermöglichen, jedoch an die Hardware des jeweiligen Anbieters gebunden sind.

Lassen Sie mich bemerken, dass ich, obwohl sie unter Linux verfügbar sind, dort immer nur sehr wenig Erfolg hatte. Ich kann die Situation unter Windows nicht kommentieren.

Die Option, die ich kürzlich verwendet habe, besteht darin, meinen Shader-Code über zu modularisieren #includesund den enthaltenen Code auf eine gemeinsame Teilmenge von GLSL und C ++ & glm zu beschränken .

Wenn ich auf ein Problem stoße, versuche ich, es auf einem anderen Gerät zu reproduzieren, um festzustellen, ob das Problem dasselbe ist, was auf einen logischen Fehler hinweist (anstelle eines Treiberproblems / undefinierten Verhaltens). Es besteht auch die Möglichkeit, dass falsche Daten an die GPU übergeben werden (z. B. durch falsch gebundene Puffer usw.). Dies schließe ich normalerweise aus, indem ich wie in cifz answer ein Debugging durchführe oder die Daten per Apitrace überprüfe .

Wenn es sich um einen logischen Fehler handelt, versuche ich, die Situation von der GPU auf der CPU wiederherzustellen, indem ich den enthaltenen Code auf der CPU mit denselben Daten aufrufe. Dann kann ich es auf CPU durchlaufen.

Aufbauend auf der Modularität des Codes können Sie auch versuchen, Unittest dafür zu schreiben und die Ergebnisse zwischen einem GPU-Lauf und einem CPU-Lauf zu vergleichen. Sie müssen sich jedoch bewusst sein, dass es Eckfälle gibt, in denen sich C ++ möglicherweise anders als GLSL verhält, sodass Sie bei diesen Vergleichen falsch positive Ergebnisse erhalten.

Wenn Sie das Problem nicht auf einem anderen Gerät reproduzieren können, können Sie nur herausfinden, woher der Unterschied stammt. Mit Unittests können Sie möglicherweise eingrenzen, wo dies geschieht. Am Ende müssen Sie jedoch möglicherweise zusätzliche Debug-Informationen vom Shader wie in cifz answer ausschreiben .

Und um Ihnen einen Überblick zu geben, hier ein Flussdiagramm meines Debugging-Prozesses: Ablaufdiagramm der im Text beschriebenen Vorgehensweise

Abgerundet wird dies hier durch eine Liste zufälliger Vor- und Nachteile:

Profi

  • Schritt durch mit üblichen Debugger
  • zusätzliche (oft bessere) Compiler-Diagnose

con

Niemand
quelle
Dies ist eine großartige Idee und wahrscheinlich die beste, die Sie mit einem schrittweisen Shader-Code erreichen können. Ich frage mich, ob das Durchlaufen eines Software-Renderers (Mesa?) Ähnliche Vorteile hätte.
@racarate: Ich dachte auch darüber nach, hatte aber noch keine Zeit, es zu versuchen. Ich bin kein Experte für Mesa, aber ich denke, es könnte schwierig sein, den Shader zu debuggen, da die Debuginformationen des Shaders den Debugger irgendwie erreichen müssen. Andererseits haben die Leute von Mesa vielleicht bereits eine Schnittstelle, um Mesa selbst zu debuggen :)
Nobody
5

Es scheint zwar nicht möglich zu sein, einen OpenGL-Shader tatsächlich zu durchlaufen, es ist jedoch möglich, die Kompilierungsergebnisse abzurufen.
Das Folgende ist aus dem Android Cardboard Sample entnommen .

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

Wenn Ihr Code ordnungsgemäß kompiliert wurde, haben Sie keine andere Wahl, als den Status des Programms auf andere Weise zu kommunizieren. Sie können signalisieren, dass ein Teil des Codes erreicht wurde, indem Sie beispielsweise die Farbe eines Scheitelpunkts ändern oder eine andere Textur verwenden. Was umständlich ist, aber vorerst der einzige Weg zu sein scheint.

EDIT: Für WebGL schaue ich mir dieses Projekt an , aber ich habe es gerade erst gefunden ... kann nicht dafür bürgen.

SL Barth - Wiedereinsetzung von Monica
quelle
3
Hm ja, mir ist bewusst, dass ich Compilerfehler bekommen kann. Ich hatte auf ein besseres Laufzeit-Debugging gehofft. Ich habe in der Vergangenheit auch den WebGL-Inspektor verwendet, aber ich glaube, er zeigt nur Statusänderungen an, aber Sie können einen Shader-Aufruf nicht untersuchen. Ich denke, das hätte in der Frage klarer sein können.
Martin Ender
2

Dies ist eine Kopie meiner Antwort auf die gleiche Frage bei StackOverflow .


Am Ende dieser Antwort befindet sich ein Beispiel für einen GLSL-Code, mit dem der vollständige floatWert als Farbe ausgegeben werden kann und der IEEE 754 codiert binary32. Ich benutze es wie folgt (dieses Snippet gibt eine yyKomponente der Modelview-Matrix aus):

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

Nachdem Sie dies auf dem Bildschirm erhalten haben, können Sie einfach eine beliebige Farbauswahl treffen, die Farbe als HTML formatieren ( 00an den rgbWert anhängen , wenn Sie keine höhere Genauigkeit benötigen, und einen zweiten Durchgang durchführen, um das niedrigere Byte zu erhalten, wenn Sie dies tun) und Sie erhalten die hexadezimale Darstellung des floatals IEEE 754 binary32.

Hier ist die tatsächliche Implementierung von 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
quelle
1

Die Lösung, die für mich funktioniert hat, ist die Kompilierung von Shader-Code in C ++ - wie von Nobody erwähnt. Es hat sich als sehr effizient erwiesen, wenn an einem komplexen Code gearbeitet wurde, obwohl einige Einstellungen erforderlich sind.

Ich habe hauptsächlich mit HLSL-Compute-Shadern gearbeitet, für die ich eine Proof-of-Concept-Bibliothek entwickelt habe, die hier verfügbar ist:

https://github.com/cezbloch/shaderator

Auf einem Compute Shader aus DirectX SDK-Beispielen wird gezeigt, wie Sie C ++ - ähnliches HLSL-Debugging aktivieren und Unit-Tests einrichten.

Die Kompilierung des GLSL-Compute-Shaders in C ++ ist einfacher als die von HLSL. Hauptsächlich aufgrund von Syntaxkonstruktionen in HLSL. Ich habe ein triviales Beispiel für einen ausführbaren Unit-Test auf einem GLSL-Ray-Tracer-Compute-Shader hinzugefügt, den Sie auch in den Quellen des Shaderator-Projekts unter dem obigen Link finden.

SpaceKees
quelle