¿Cómo puedo depurar sombreadores GLSL?


45

Al escribir sombreadores no triviales (al igual que al escribir cualquier otra pieza de código no trivial), las personas cometen errores. [cita requerida] Sin embargo, no puedo depurarlo como cualquier otro código; después de todo, no se puede simplemente adjuntar gdb o el depurador de Visual Studio. Ni siquiera puede hacer la depuración de printf, porque no hay forma de salida de la consola. Lo que suelo hacer es representar los datos que quiero ver como color, pero esa es una solución muy rudimentaria y amateur. Estoy seguro de que la gente ha encontrado mejores soluciones.

Entonces, ¿cómo puedo depurar un sombreador? ¿Hay alguna manera de pasar por un sombreador? ¿Puedo ver la ejecución del sombreador en un vértice / primitivo / fragmento específico?

(Esta pregunta es específicamente sobre cómo depurar el código del sombreador similar a cómo se depuraría el código "normal", no sobre cómo depurar cosas como los cambios de estado).


¿Has mirado en gDEBugger? Citando el sitio: "gDEBugger es un avanzado depurador, generador de perfiles y analizador de memoria OpenGL y OpenCL. GDEBugger hace lo que ninguna otra herramienta puede hacer: le permite rastrear la actividad de la aplicación en la parte superior de las API OpenGL y OpenCL y ver qué está sucediendo dentro de la implementación del sistema. " De acuerdo, no hay depuración de estilo VS / paso a través del código, pero podría darle una idea de lo que hace (o debe hacer) su sombreador. Crytec lanzó una herramienta similar para la "depuración" de sombreadores directos llamada RenderDoc (gratuita, pero estrictamente para sombreadores HLSL, por lo que tal vez no sea relevante para usted).
Bert

@Bert Hm sí, supongo que gDEBugger es el equivalente de OpenGL a WebGL-Inspector. He usado este último. Es inmensamente útil, pero definitivamente depura más las llamadas de OpenGL y los cambios de estado que la ejecución del sombreador.
Martin Ender

1
Nunca he hecho ninguna programación WebGL y, por lo tanto, no estoy familiarizado con WebGL-Inspector. Con gDEBugger, al menos puede inspeccionar todo el estado de la tubería del sombreador, incluida la memoria de texturas, los datos de vértices, etc. Sin embargo, no es necesario pasar por el código afaik.
Bert

gDEBugger es extremadamente antiguo y no es compatible desde hace un tiempo. Si está buscando un análisis de estado de marco y GPU, esta es otra pregunta que está muy relacionada: computergraphics.stackexchange.com/questions/23/…
cifz

Aquí hay un método de depuración que sugerí para una pregunta relacionada: stackoverflow.com/a/29816231/758666
wip

Respuestas:


26

Por lo que sé, no hay herramientas que le permitan pasar a través del código en un sombreador (también, en ese caso, debería poder seleccionar solo un píxel / vértice que desea "depurar", es probable que la ejecución variar dependiendo de eso).

Lo que personalmente hago es una "depuración colorida" muy hacky. Así que espolvoree un montón de ramas dinámicas con #if DEBUG / #endifguardias que básicamente dicen

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

.. rest of code .. 

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

Para que pueda "observar" la información de depuración de esta manera. Usualmente hago varios trucos como saltar o mezclar entre varios "códigos de color" para probar varios eventos más complejos o cosas no binarias.

En este "marco" también me parece útil tener un conjunto de convenciones fijas para casos comunes, de modo que si no tengo que volver constantemente y verificar qué color asocie con qué. Lo importante es tener un buen soporte para la recarga en caliente del código de sombreador, por lo que puede cambiar casi interactivamente sus datos / eventos rastreados y activar / desactivar fácilmente la visualización de depuración.

Si necesita depurar algo que no puede mostrar fácilmente en la pantalla, siempre puede hacer lo mismo y usar una herramienta de análisis de cuadros para inspeccionar sus resultados. He enumerado un par de ellos como respuesta a esta otra pregunta.

Obvio, no hace falta decir que si no estoy "depurando" un sombreador de píxeles o sombreador de cálculo, paso esta información de "debugColor" a través de la tubería sin interpolarla (en GLSL con la flat palabra clave)

Una vez más, esto es muy hacky y está lejos de la depuración adecuada, pero es lo que estoy atrapado al no conocer ninguna alternativa adecuada.


Cuando estén disponibles, puede usar SSBO para obtener un formato de salida más flexible donde no necesita codificar en colores. Sin embargo, el gran inconveniente de este enfoque es que altera el código que puede ocultar / alterar errores, especialmente cuando UB está involucrado. +1 Sin embargo, porque es el método más directo que está disponible.
Nadie el

9

También hay GLSL-Debugger . Es un depurador que solía ser conocido como "GLSL Devil".

El depurador en sí mismo es muy útil no solo para el código GLSL, sino también para OpenGL. Tiene la capacidad de saltar entre llamadas de extracción y romper interruptores de sombreador. También le muestra mensajes de error comunicados por OpenGL a la aplicación misma.


2
Tenga en cuenta que a partir de 2018-08-07, no admite nada más alto que GLSL 1.2, y no se mantiene activamente.
Ruslan

Ese comentario legítimamente me puso triste :(
rdelfin

El proyecto es de código abierto y realmente me encantaría ayudar a modernizarlo. No hay otra herramienta que haga lo que hizo.
XenonofArcticus

7

Hay varias ofertas de proveedores de GPU como CodeXL de AMD o nSight / Linux GFX Debugger de NVIDIA que permiten pasar por sombreadores pero están vinculados al hardware del proveedor respectivo.

Permítanme señalar que, aunque están disponibles en Linux, siempre tuve muy poco éxito al usarlos allí. No puedo comentar sobre la situación en Windows.

La opción que utilicé recientemente es modularizar mi código de sombreador a través de #includesy restringir el código incluido a un subconjunto común de GLSL y C ++ y glm .

Cuando encuentro un problema, trato de reproducirlo en otro dispositivo para ver si el problema es el mismo, lo que sugiere un error lógico (en lugar de un problema del controlador / comportamiento indefinido). También existe la posibilidad de pasar datos incorrectos a la GPU (p. Ej., Por búferes enlazados incorrectamente, etc.) que generalmente descarto ya sea por depuración de salida como en la respuesta cifz o inspeccionando los datos a través de un apitrace .

Cuando se trata de un error lógico, trato de reconstruir la situación desde la GPU en la CPU llamando al código incluido en la CPU con los mismos datos. Entonces puedo atravesarlo en la CPU.

Sobre la base de la modularidad del código, también puede intentar escribir pruebas unitarias para él y comparar los resultados entre una ejecución de GPU y una ejecución de CPU. Sin embargo, debe tener en cuenta que hay casos de esquina en los que C ++ podría comportarse de manera diferente que GLSL, lo que le da falsos positivos en estas comparaciones.

Finalmente, cuando no puede reproducir el problema en otro dispositivo, solo puede comenzar a excavar de dónde proviene la diferencia. Las pruebas unitarias pueden ayudarlo a reducir dónde sucede eso, pero al final probablemente necesite escribir información de depuración adicional desde el sombreador como en la respuesta cifz .

Y para darle una visión general aquí hay un diagrama de flujo de mi proceso de depuración: Diagrama de flujo del procedimiento descrito en el texto.

Para completar esto, aquí hay una lista de ventajas y desventajas aleatorias:

Pro

  • paso a paso con el depurador habitual
  • diagnóstico de compilador adicional (a menudo mejor)

estafa


Esta es una gran idea, y probablemente lo más cercano que pueda estar al código de sombreador de un solo paso. Me pregunto si correr a través de un procesador de software (¿Mesa?) Tendría beneficios similares?

@racarate: Pensé en eso también, pero aún no tuve tiempo para intentarlo. No soy un experto en mesa, pero creo que puede ser difícil depurar el sombreador ya que la información de depuración del sombreador tiene que llegar de alguna manera al depurador. Por otra parte, tal vez la gente de mesa ya tenga una interfaz para depurar la mesa misma :)
Nadie

5

Si bien no parece posible pasar a través de un sombreador OpenGL, es posible obtener los resultados de la compilación.
Lo siguiente está tomado de la muestra de cartón de Android .

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

Si su código se compila correctamente, no tiene más remedio que probar una forma diferente de comunicarle el estado del programa. Puede indicar que se alcanzó una parte del código, por ejemplo, cambiando el color de un vértice o usando una textura diferente. Lo cual es incómodo, pero parece ser la única forma por ahora.

EDITAR: para WebGL, estoy mirando este proyecto , pero lo acabo de encontrar ... no puedo dar fe de ello.


3
Hm, sí, soy consciente de que puedo obtener errores del compilador. Esperaba una mejor depuración en tiempo de ejecución. También he usado el inspector WebGL en el pasado, pero creo que solo muestra los cambios de estado, pero no puede ver una invocación de sombreador. Supongo que esto podría haber sido más claro en la pregunta.
Martin Ender

2

Esta es una copia y pega de mi respuesta a la misma pregunta en StackOverflow .


Al final de esta respuesta hay un ejemplo de código GLSL que permite generar el floatvalor completo como color, codificando IEEE 754 binary32. Lo uso de la siguiente manera (este fragmento da el yycomponente de la matriz de 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);

Después de obtener esto en la pantalla, puede tomar cualquier selector de color, formatear el color como HTML (agregando 00el rgbvalor si no necesita mayor precisión y haciendo un segundo pase para obtener el byte inferior si lo necesita), y obtienes la representación hexadecimal de floatcomo IEEE 754 binary32.

Aquí está la implementación real 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.;
}

1

La solución que funcionó para mí es la compilación de código de sombreador para C ++, como lo mencionó Nadie. Resultó muy eficiente cuando se trabaja en un código complejo a pesar de que requiere un poco de configuración.

He estado trabajando principalmente con HLSL Compute Shaders para los cuales he desarrollado una biblioteca de prueba de concepto disponible aquí:

https://github.com/cezbloch/shaderator

Demuestra en un Compute Shader de DirectX SDK Samples, cómo habilitar C ++ como la depuración HLSL y cómo configurar las Pruebas de Unidad.

La compilación del sombreador de cómputo GLSL para C ++ parece más fácil que HLSL. Principalmente debido a construcciones de sintaxis en HLSL. He agregado un ejemplo trivial de Prueba de unidad ejecutable en un trazador de rayos GLSL Compute Shader que también puede encontrar en las fuentes del proyecto Shaderator en el enlace de arriba.