web-dev-qa-db-de.com

Wie kann man einen GLSL-Shader debuggen?

Ich muss ein GLSL-Programm debuggen, weiß aber nicht, wie ich ein Zwischenergebnis ausgeben soll. Ist es möglich, einige Debug-Traces (wie bei printf) mit GLSL zu erstellen?

173

Innerhalb von GLSL können Sie nicht einfach mit der CPU kommunizieren. Verwenden Sie am besten glslDevil oder andere Tools.

Ein printf würde den Versuch erfordern, von der GPU, auf der der GLSL-Code ausgeführt wird, zur CPU zurückzukehren. Stattdessen können Sie versuchen, die Anzeige voranzutreiben. Anstatt zu versuchen, Text auszugeben, geben Sie etwas visuell Besonderes auf dem Bildschirm aus. Sie können beispielsweise nur dann etwas mit einer bestimmten Farbe malen, wenn Sie den Punkt Ihres Codes erreichen, an dem Sie einen Ausdruck hinzufügen möchten. Wenn Sie einen Wert drucken müssen, können Sie die Farbe entsprechend diesem Wert einstellen.

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

Ich habe festgestellt, dass Transform Feedback ein nützliches Werkzeug zum Debuggen von Vertex-Shadern ist. Auf diese Weise können Sie die Werte der VS-Ausgänge erfassen und auf der CPU-Seite zurücklesen, ohne den Rasterizer durchlaufen zu müssen.

hier ist ein weiterer Link zu einem Tutorial zu Transform Feedback.

12
nullspace

Wenn Sie die Variationen eines Werts auf dem Bildschirm visualisieren möchten, können Sie eine Heatmap-Funktion verwenden, die der folgenden ähnelt (ich habe sie in hlsl geschrieben, sie lässt sich jedoch leicht an glsl anpassen):

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

Dann geben Sie in Ihrem Pixel-Shader einfach Folgendes aus:

return HeatMapColor(myValue, 0.00, 50.00);

Und Sie können sich ein Bild davon machen, wie unterschiedlich es in Ihren Pixeln ist:

enter image description here

Natürlich können Sie auch beliebige Farben verwenden.

8
wip

GLSL Sandbox war für Shader ziemlich praktisch für mich.

Nicht das Debuggen per se (was als unfähig beantwortet wurde), aber praktisch, um die Änderungen in der Ausgabe schnell zu sehen.

6
Nick Devereaux

Führen Sie ein Offline-Rendering für eine Textur durch und werten Sie die Texturdaten aus. Sie können verwandten Code finden, indem Sie auf "Textur rendern" googeln. Opengl Verwenden Sie dann glReadPixels, um die Ausgabe in ein Array einzulesen und Behauptungen darauf auszuführen (da das Durchsuchen eines so großen Arrays im Debugger normalerweise nicht wirklich nützlich ist).

Außerdem möchten Sie möglicherweise die Begrenzung auf Ausgabewerte deaktivieren, die nicht zwischen 0 und 1 liegen. Dies wird nur für Gleitkomma-Texturen unterstützt.

Ich persönlich hatte das Problem, Shader für eine Weile richtig zu debuggen. Es scheint keinen guten Weg zu geben - Wenn jemand einen guten (und nicht veralteten/veralteten) Debugger findet, lassen Sie es mich bitte wissen.

2
Domi

Ich teile ein Fragment-Shader-Beispiel, wie ich tatsächlich debugge.

#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

Sie können dies versuchen: https://github.com/msqrt/shader-printf Dies ist eine Implementierung mit dem Namen "Einfache printf-Funktionalität für GLSL".

Vielleicht möchten Sie auch ShaderToy ausprobieren und sich ein Video wie dieses ( https://youtu.be/EBrAdahFtuo ) vom YouTube-Kanal "The Art of Code" ansehen, in dem Sie einige der Videos sehen können Techniken, die sich gut zum Debuggen und Visualisieren eignen. Ich kann seinen Kanal wärmstens empfehlen, da er einige wirklich gute Sachen schreibt und auch ein Händchen für die Präsentation komplexer Ideen in neuartigen, hochinteressanten und leicht verdaulichen Formaten hat. (Sein Mandelbrot-Video ist ein hervorragendes Beispiel dafür: https://youtu.be/6IWXkV82oyY )

Ich hoffe, dass diese späte Antwort niemanden stört, aber die Frage steht bei den Google-Suchen nach GLSL-Debugging ganz oben und in 9 Jahren hat sich natürlich viel geändert :-)

PS: Andere Alternativen könnten NVIDIA nSight und AMD ShaderAnalyzer sein, die einen vollständigen Debugger für Shader bieten.

1
Ian Macintosh

Am Ende dieser Antwort befindet sich ein Beispiel für einen GLSL-Code, mit dem der vollständige float -Wert als Farbe ausgegeben werden kann, wobei IEEE 754 binary32 Codiert wird. Ich benutze es wie folgt (dieses Snippet gibt die yy Komponente 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 einen beliebigen Farbwähler nehmen, die Farbe als HTML formatieren (00 An den Wert rgb anhängen, wenn Sie keine höhere Genauigkeit benötigen, und eine Sekunde ausführen pass, um das niedrigere Byte zu erhalten, wenn Sie dies tun), und Sie erhalten die hexadezimale Darstellung des float als 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.;
}
1
Ruslan

Die vorhandenen Antworten sind alles gute Sachen, aber ich wollte noch ein kleines Juwel teilen, das beim Debuggen kniffliger Präzisionsprobleme in einem GLSL-Shader von Nutzen war. Bei sehr großen int-Zahlen, die als Gleitkomma dargestellt werden, muss darauf geachtet werden, dass floor (n) und floor (n + 0.5) ordnungsgemäß verwendet werden, um round () auf ein genaues int zu implementieren. Es ist dann möglich, einen Gleitkommawert zu rendern, der durch die folgende Logik ein genaues int ist, um die Bytekomponenten in R-, G- und B-Ausgabewerte zu packen.

  // 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