Come posso eseguire il debug degli shader GLSL?


45

Quando si scrivono shader non banali (proprio come quando si scrive qualsiasi altro pezzo di codice non banale), le persone commettono errori. [citazione necessaria] Tuttavia, non posso semplicemente eseguirne il debug come qualsiasi altro codice - dopo tutto non puoi semplicemente collegare gdb o il debugger di Visual Studio. Non è nemmeno possibile eseguire il debug di printf, poiché non esiste alcuna forma di output della console. Quello che faccio di solito è rendere i dati che voglio guardare come colore, ma questa è una soluzione molto rudimentale e amatoriale. Sono sicuro che le persone hanno trovato soluzioni migliori.

Quindi, come posso effettivamente eseguire il debug di uno shader? C'è un modo per attraversare uno shader? Posso guardare l'esecuzione dello shader su un vertice / primitivo / frammento specifico?

(Questa domanda riguarda in particolare il modo in cui eseguire il debug del codice shader in modo simile al modo in cui si dovrebbe eseguire il debug del codice "normale", non sul debug di cose come i cambiamenti di stato.)


Hai esaminato gDEBugger? Citando il sito: "gDEBugger è un debugger, un profiler e un analizzatore di memoria OpenGL e OpenCL avanzato. GDEBugger fa ciò che nessun altro strumento può - consente di tracciare l'attività delle applicazioni sulle API OpenGL e OpenCL e vedere cosa sta succedendo nell'implementazione del sistema. " Concesso, nessun debugging / passaggio del codice in stile VS, ma potrebbe darti un'idea di ciò che fa (o dovrebbe fare) il tuo shader. Crytec ha rilasciato uno strumento simile per il "debug" dello shader diretto chiamato RenderDoc (gratuito, ma rigorosamente per gli shader HLSL, quindi forse non pertinente per te).
Bert,

@Bert Hm, sì, immagino che gDEBugger sia l'equivalente OpenGL di WebGL-Inspector? Ho usato quest'ultimo. È immensamente utile, ma è sicuramente più debug delle chiamate OpenGL e dei cambiamenti di stato rispetto all'esecuzione dello shader.
Martin Ender,

1
Non ho mai fatto alcuna programmazione WebGL e quindi non ho familiarità con WebGL-Inspector. Con gDEBugger è possibile almeno ispezionare l'intero stato della pipeline dello shader, inclusa la memoria delle trame, i dati dei vertici, ecc. Tuttavia, nessun vero passaggio attraverso il codice.
Bert,

gDEBugger è estremamente vecchio e non è supportato da un po 'di tempo. Se stai osservando l'analisi dello stato del frame e della GPU, questa è un'altra domanda fortemente correlata: computergraphics.stackexchange.com/questions/23/…
cifz

Ecco un metodo di debug che ho suggerito a una domanda correlata: stackoverflow.com/a/29816231/758666
cancellare il

Risposte:


26

Per quanto ne so non ci sono strumenti che ti permettano di scorrere il codice in uno shader (inoltre, in tal caso dovresti essere in grado di selezionare solo un pixel / vertice che vuoi "debug", è probabile che l'esecuzione variare a seconda di quello).

Quello che faccio personalmente è un "debugging colorato" molto confuso. Quindi cospargo un mucchio di rami dinamici con #if DEBUG / #endifguardie che sostanzialmente dicono

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

.. rest of code .. 

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

Quindi puoi "osservare" le informazioni di debug in questo modo. Di solito faccio vari trucchi come lerping o fusione tra vari "codici colore" per testare vari eventi più complessi o cose non binarie.

In questo "quadro" trovo anche utile avere una serie di convenzioni fisse per casi comuni in modo che se non devo tornare costantemente indietro e controllare di che colore sono associato a cosa. L'importante è avere un buon supporto per la ricarica a caldo del codice shader, in modo da poter modificare in modo quasi interattivo i dati / eventi tracciati e attivare / disattivare facilmente la visualizzazione del debug.

Se è necessario eseguire il debug di qualcosa che non è possibile visualizzare facilmente sullo schermo, è sempre possibile fare lo stesso e utilizzare uno strumento di analisi dei frame per ispezionare i risultati. Ne ho elencati un paio come risposta a quest'altra domanda.

Ovviamente, se non sto "eseguendo il debug" di uno shader di pixel o di uno shader di calcolo, passo queste informazioni "debugColor" in tutta la pipeline senza interpolarle (in GLSL con flat parola chiave)

Ancora una volta, questo è molto confuso e lungi dal corretto debug, ma è ciò di cui sono bloccato a non conoscere alcuna alternativa adeguata.


Quando sono disponibili, è possibile utilizzare SSBO per ottenere un formato di output più flessibile in cui non è necessario codificare a colori. Tuttavia, il grande svantaggio di questo approccio è che altera il codice che può nascondere / alterare i bug, specialmente quando è coinvolto UB. +1 Tuttavia, poiché è il metodo più diretto disponibile.
Nessuno il

9

Esiste anche il debugger GLSL . È un debugger noto come "GLSL Devil".

Lo stesso Debugger è molto utile non solo per il codice GLSL, ma anche per OpenGL stesso. Hai la possibilità di saltare tra le chiamate di disegno e interrompere gli interruttori Shader. Mostra anche i messaggi di errore comunicati da OpenGL all'applicazione stessa.


2
Si noti che a partire dal 08-08-2018, non supporta nulla di superiore a GLSL 1.2 e non è attivamente mantenuto.
Ruslan,

Quel commento mi ha legittimamente reso triste :(
relfelfin

Il progetto è open source e mi piacerebbe davvero aiutare a modernizzarlo. Non c'è altro strumento che fa quello che ha fatto.
XenonofArcticus,

7

Esistono diverse offerte di venditori di GPU come CodeXL di AMD o Debugger GFX nSight / Linux di NVIDIA che consentono di passare attraverso shader ma sono collegati all'hardware del rispettivo fornitore.

Vorrei sottolineare che, sebbene siano disponibili su Linux, ho sempre avuto ben poco successo nell'usarli lì. Non posso commentare la situazione sotto Windows.

L'opzione che ho usato di recente è quella di modulare il mio codice shader tramite #includese limitare il codice incluso a un sottoinsieme comune di GLSL e C ++ & glm .

Quando ho riscontrato un problema, provo a riprodurlo su un altro dispositivo per vedere se il problema è lo stesso che suggerisce un errore logico (invece di un problema del driver / comportamento indefinito). Esiste anche la possibilità di trasmettere dati errati alla GPU (ad es. Buffer non correttamente associati ecc.) Che di solito escludo eseguendo il debug dell'output come nella risposta cifz o ispezionando i dati tramite apitrace .

Quando si tratta di un errore logico, provo a ricostruire la situazione dalla GPU sulla CPU chiamando il codice incluso sulla CPU con gli stessi dati. Quindi posso passare attraverso di esso sulla CPU.

Sulla base della modularità del codice, puoi anche provare a scrivere unittest per esso e confrontare i risultati tra una corsa GPU e una corsa CPU. Tuttavia, devi essere consapevole che ci sono casi angolari in cui il C ++ potrebbe comportarsi in modo diverso rispetto alla GLSL, dandoti così falsi positivi in ​​questi confronti.

Infine, quando non riesci a riprodurre il problema su un altro dispositivo, puoi iniziare a scavare solo da dove proviene la differenza. Gli unittest potrebbero aiutarti a restringere il punto in cui ciò accade, ma alla fine probabilmente dovrai scrivere ulteriori informazioni di debug dallo shader come nella risposta cifz .

E per darti una panoramica qui è un diagramma di flusso del mio processo di debug: Diagramma di flusso della procedura descritta nel testo

Per concludere, ecco un elenco di pro e contro casuali:

professionista

  • passo con il solito debugger
  • diagnostica aggiuntiva (spesso migliore) del compilatore

contro


Questa è un'ottima idea, e probabilmente il più vicino che puoi ottenere al codice shader a passo singolo. Mi chiedo se l'esecuzione di un renderer software (Mesa?) Avrebbe benefici simili?

@racarate: ci ho pensato anche io ma non ho ancora avuto il tempo di provare. Non sono un esperto di mesa ma penso che potrebbe essere difficile eseguire il debug dello shader poiché le informazioni di debug dello shader devono in qualche modo raggiungere il debugger. Poi di nuovo, forse la gente di mesa ha già un'interfaccia per il debug di mesa stessa :)
Nessuno

5

Anche se non sembra essere possibile passare attraverso uno shader OpenGL, è possibile ottenere i risultati della compilazione.
Quanto segue è tratto dall'esempio di cartone Android .

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

Se il codice viene compilato correttamente, allora non hai altra scelta che provare un modo diverso di comunicare lo stato del programma. È possibile segnalare che una parte del codice è stata raggiunta, ad esempio, modificando il colore di un vertice o utilizzando una trama diversa. Che è imbarazzante, ma sembra essere l'unico modo per ora.

EDIT: Per WebGL, sto guardando questo progetto , ma l'ho appena trovato ... non posso garantirlo.


3
Sì, sono consapevole che posso ottenere errori del compilatore. Speravo in un migliore debug di runtime. Ho usato anche l'ispettore WebGL in passato, ma credo che mostri solo cambiamenti di stato, ma non è possibile esaminare un'invocazione shader. Immagino che questo avrebbe potuto essere più chiaro nella domanda.
Martin Ender,

2

Questo è un copia-incolla della mia risposta alla stessa domanda su StackOverflow .


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 il 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 00al 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

La soluzione che ha funzionato per me è la compilazione del codice shader in C ++, come menzionato da Nessuno. È risultato molto efficiente quando si lavora su un codice complesso anche se richiede un po 'di installazione.

Ho lavorato principalmente con HLSL Compute Shader per i quali ho sviluppato una libreria di prove di concetto disponibile qui:

https://github.com/cezbloch/shaderator

Dimostra su uno Shader di calcolo dagli esempi SDK di DirectX, come abilitare il C ++ come il debug HLSL e come impostare Test unità.

La compilazione dello shader di calcolo GLSL in C ++ sembra più semplice di HLSL. Principalmente a causa di costruzioni di sintassi in HLSL. Ho aggiunto un banale esempio di Unit Test eseguibile su un ray tracing GLSL Compute Shader che puoi trovare anche nelle fonti del progetto Shaderator sotto il link sopra.

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.