Как отлаживать шейдер GLSL?


193

Мне нужно отладить программу GLSL, но я не знаю, как вывести промежуточный результат. Можно ли сделать несколько отладочных трасс (например, с printf) с помощью GLSL?


6
... без использования внешнего программного обеспечения, такого как glslDevil.
Франк Фрайбургер

взгляните на этот отладочный файл с плавающими переменными и текстами из шейдерного фрагмента GLSL, вам просто нужен один запасной текстурный блок для шрифта и постоянное состояние расчетного значения в области печати
Spektre

Ответы:


118

Вы не можете легко связаться с процессором из GLSL. Использование glslDevil или других инструментов - ваш лучший выбор.

Для printf потребуется попытаться вернуться к ЦП с графического процессора, на котором выполняется код GLSL. Вместо этого вы можете попытаться продвинуться к дисплею. Вместо того, чтобы пытаться выводить текст, выведите на экран что-то визуально отличительное. Например, вы можете нарисовать что-то определенным цветом, только если вы достигнете точки своего кода, где вы хотите добавить printf. Если вам нужно напечатать значение, вы можете установить цвет в соответствии с этим значением.


62
Что если точная причина отладки вашего шейдера в том, что на экране ничего не появляется?
Йерун

11
Почему вы хотите отладить что-нибудь? Потому что его код, и он хочет проверить значения времени выполнения, я бы
рискнул

3
GLSL-Debugger - это форк с открытым исходным кодом glslDevil.
Магнус

@Magnus больше не поддерживается, и поддерживает только GLSL до 1,20.
Руслан

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
Это отладочное устройство. Например, если вы хотите узнать, где находится светлая сцена на сцене, перейдите: if (lpos.x> 100) bug = 1.0. Если положение освещения больше 100, сцена станет красной.
Ste3e

13

Я нашел Transform Feedback полезным инструментом для отладки вершинных шейдеров. Вы можете использовать это для захвата значений выходов VS и считывания их обратно на стороне процессора, без необходимости проходить через растеризатор.

Вот еще одна ссылка на учебник по Transform Feedback.


8

Если вы хотите визуализировать изменения значения на экране, вы можете использовать функцию тепловой карты, аналогичную этой (я написал ее в hlsl, но ее легко адаптировать к 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);
}

Затем в вашем пиксельном шейдере вы просто выводите что-то вроде:

return HeatMapColor(myValue, 0.00, 50.00);

И можете получить представление о том, как оно меняется в зависимости от ваших пикселей:

введите описание изображения здесь

Конечно, вы можете использовать любой набор цветов, которые вам нравятся.


7

GLSL Sandbox мне очень пригодился для шейдеров.

Не отладка как таковая (на которую был дан ответ как неспособный), но удобная для быстрого просмотра изменений в выводе.


5

Вы можете попробовать это: https://github.com/msqrt/shader-printf, который является реализацией, которая называется «Простая функциональность printf для GLSL».

Вы также можете попробовать ShaderToy и, возможно, посмотреть видео, подобное этому ( https://youtu.be/EBrAdahFtuo ), на канале YouTube «Искусство кода», где вы можете увидеть некоторые методы, которые хорошо работают для отладки и визуализации. Я настоятельно рекомендую его канал, так как он пишет действительно хорошие вещи, и он также умеет представлять сложные идеи в новых, увлекательных и легко усваиваемых форматах (его видео Мандельброта является превосходным примером именно этого: https: // youtu.be/6IWXkV82oyY )

Я надеюсь, что никто не возражает против этого позднего ответа, но этот вопрос занимает первое место в поиске Google для отладки GLSL и, конечно, многое изменилось за 9 лет :-)

PS: Другими альтернативами также могут быть NVIDIA nSight и AMD ShaderAnalyzer, которые предлагают полный пошаговый отладчик для шейдеров.


2

Я делюсь примером фрагмента шейдера, как я на самом деле отлаживать.

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

}

введите описание изображения здесь


2

Внизу этого ответа приведен пример кода GLSL, который позволяет выводить полное floatзначение в виде цвета, кодируя IEEE 754 binary32. Я использую его следующим образом (этот фрагмент yyкода выдает компонент матрицы вида модели):

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

После того, как вы получите это на экране, вы можете просто взять любой палитру цветов, отформатировать цвет как HTML (добавив 00к rgbзначению, если вам не нужна более высокая точность, и сделав второй проход, чтобы получить младший байт, если вы это сделаете), и Вы получите шестнадцатеричное представление floatкак IEEE 754 binary32.

Вот фактическая реализация 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

Сделайте рендеринг текстуры в автономном режиме и оцените данные текстуры. Вы можете найти связанный код путем поиска в Google для «рендеринга в текстуру» opengl. Затем используйте glReadPixels, чтобы прочитать выходные данные в массив и выполнить утверждения для него (поскольку просмотр такого огромного массива в отладчике обычно не очень полезен).

Также вы можете отключить ограничение для выходных значений, которые не находятся между 0 и 1, что поддерживается только для текстур с плавающей запятой .

Меня лично некоторое время беспокоила проблема правильной отладки шейдеров. Кажется, нет хорошего пути - если кто-то найдет хороший (и не устаревший / устаревший) отладчик, пожалуйста, дайте мне знать.


3
Любой ответ или комментарий, в которых говорится, что "google xyz" должен быть забанен или отклонен из Stackoverflow.
gregoiregentil

1

Все существующие ответы - хорошие вещи, но я хотел бы поделиться еще одной маленькой драгоценностью, которая была полезна при отладке сложных проблем точности в шейдере GLSL. С очень большими числами int, представленными в виде числа с плавающей запятой, нужно позаботиться о том, чтобы правильно использовать floor (n) и floor (n + 0.5), чтобы реализовать round () с точностью до int. Затем можно отобразить значение с плавающей точкой, которое является точным int, с помощью следующей логики, чтобы упаковать байтовые компоненты в выходные значения R, G и 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;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.