Come eseguire il debug di uno shader GLSL?


193

Devo eseguire il debug di un programma GLSL ma non so come ottenere risultati intermedi. È possibile effettuare alcune tracce di debug (come con printf) con GLSL?


6
... senza utilizzare software esterno come glslDevil.
Franck Freiburger,

dai un'occhiata a questa stampa di debug di variabili float e testi dallo shader di frammenti GLSL hai solo bisogno di una singola unità di texture di riserva per il font e lo stato costante del valore emesso nell'area stampata
Spektre

Risposte:


118

Non è possibile comunicare facilmente alla CPU dall'interno di GLSL. Utilizzare glslDevil o altri strumenti è la soluzione migliore.

Un printf richiederebbe il tentativo di tornare alla CPU dalla GPU che esegue il codice GLSL. Invece, puoi provare a spingere in avanti verso il display. Invece di provare a produrre testo, crea qualcosa di visivamente distintivo sullo schermo. Ad esempio, puoi dipingere qualcosa di un colore specifico solo se raggiungi il punto del codice in cui desideri aggiungere una stampa. Se è necessario stampare un valore, è possibile impostare il colore in base a quel valore.


62
Cosa succede se il motivo esatto per cui si desidera eseguire il debug dello shader è perché sullo schermo non viene visualizzato nulla?
Jeroen,

11
Perché dovresti voler eseguire il debug di qualcosa? Perché il suo codice e vuole esaminare i valori di runtime, rischierei ...
RichieHH,

3
GLSL-Debugger è un fork open source di glslDevil.
Magnus,

@Magnus non è più attivamente gestito e supporta solo GLSL fino a 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;
}

8
È un dispositivo di debug. Se vuoi sapere dove si trova la posizione della luce nella scena, ad esempio, vai: if (lpos.x> 100) bug = 1.0. Se la posizione della luce è maggiore di 100, la scena diventerà rossa.
ste3e,

13

Ho trovato Transform Feedback come uno strumento utile per il debug dei vertex shader. È possibile utilizzarlo per acquisire i valori delle uscite VS e rileggerli sul lato CPU, senza dover passare attraverso il rasterizzatore.

Ecco un altro link a un tutorial su Transform Feedback.


8

Se vuoi visualizzare le variazioni di un valore sullo schermo, puoi usare una funzione heatmap simile a questa (l'ho scritta in hlsl, ma è facile adattarsi a 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);
}

Quindi nel tuo pixel shader hai appena emesso qualcosa del tipo:

return HeatMapColor(myValue, 0.00, 50.00);

E puoi avere un'idea di come varia tra i pixel:

inserisci qui la descrizione dell'immagine

Ovviamente puoi usare qualsiasi set di colori che ti piace.


7

GLSL Sandbox mi è stato molto utile per gli shader.

Non debug di per sé (a cui è stato risposto come incapace) ma utile per vedere rapidamente le modifiche nell'output.


5

Puoi provare questo: https://github.com/msqrt/shader-printf che è un'implementazione chiamata appropriatamente "Funzionalità printf semplice per GLSL".

Potresti anche provare ShaderToy e magari guardare un video come questo ( https://youtu.be/EBrAdahFtuo ) dal canale YouTube "The Art of Code" dove puoi vedere alcune delle tecniche che funzionano bene per il debug e visualizzazione. Consiglio vivamente il suo canale mentre scrive delle cose davvero buone e ha anche un talento per presentare idee complesse in formati innovativi, molto coinvolgenti e facili da digerire (il suo video Mandelbrot è un superbo esempio di questo: https: // youtu.be/6IWXkV82oyY )

Spero che nessuno si preoccupi di questa risposta tardiva, ma la domanda è in cima alle ricerche di Google per il debug GLSL e molto è ovviamente cambiato in 9 anni :-)

PS: Altre alternative potrebbero anche essere NVIDIA nSight e AMD ShaderAnalyzer che offrono un debugger completo per gli shader.


2

Sto condividendo un esempio di shader di frammenti, come effettivamente eseguo il debug.

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

}

inserisci qui la descrizione dell'immagine


2

Alla fine di questa risposta c'è un esempio di codice GLSL che consente di generare l'intero floatvalore come colore, codificando IEEE 754 binary32. Lo uso come segue (questo frammento fornisce un yycomponente della 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);

Dopo averlo visualizzato sullo schermo, puoi semplicemente prendere qualsiasi selettore di colori, formattare il colore come HTML (aggiungendo 00il rgbvalore se non hai bisogno di maggiore precisione e facendo un secondo passaggio per ottenere il byte inferiore se lo fai), e ottieni la rappresentazione esadecimale di floatcome IEEE 754 binary32.

Ecco l'implementazione effettiva di 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

Esegui il rendering offline di una trama e valuta i dati della trama. Puoi trovare il codice correlato cercando su Google "render to texture" Quindi usa glReadPixels per leggere l'output in un array ed eseguire asserzioni su di esso (dal momento che guardare attraverso un array così enorme nel debugger di solito non è davvero utile).

Inoltre, potresti voler disabilitare il bloccaggio sui valori di output che non sono compresi tra 0 e 1, che è supportato solo per le trame a virgola mobile .

Personalmente sono stato infastidito dal problema del corretto debug degli shader per un po '. Non sembra essere un buon modo - Se qualcuno trova un buon debugger (e non obsoleto / deprecato), per favore fatemelo sapere.


3
Qualsiasi risposta o commento che dice "google xyz" dovrebbe essere bannato o rifiutato da StackOverflow.
Gregoiregentil,

1

Le risposte esistenti sono tutte buone cose, ma volevo condividere un altro piccolo gioiello che è stato prezioso nel debug di problemi di precisione difficili in uno shader GLSL. Con numeri int molto grandi rappresentati come virgola mobile, è necessario fare attenzione a usare floor (n) e floor (n + 0,5) correttamente per implementare round () su un int esatto. È quindi possibile eseguire il rendering di un valore float che è un int esatto dalla logica seguente per comprimere i componenti byte in valori di output 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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.