Як налагодити шейдер GLSL?


193

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


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

Подивіться на цю налагоджувальну роздруківку плаваючих змінних та текстів з шейдера GLSL Fragment, вам просто потрібна одна запасна одиниця текстури для шрифту та постійного стану вичерпаного значення у друкованій області
Spektre

Відповіді:


118

Ви не можете легко повідомити CPU з GLSL. Використання glslDevil або інших інструментів - найкраща ставка.

Для printf потрібно буде спробувати повернутися до центрального процесора з GPU, на якому працює код GLSL. Натомість ви можете спробувати просуватися до дисплея. Замість того, щоб намагатися вивести текст, виведіть на екран щось візуально відмінне. Наприклад, ви можете пофарбувати щось певним кольором, лише якщо досягнете точки коду, куди потрібно додати printf. Якщо вам потрібно роздрукувати значення, ви можете встановити колір відповідно до цього значення.


62
Що робити, якщо точна причина, за якою ви хочете налагодити свій шейдер, полягає в тому, що на екрані нічого не з’являється?
Jeroen

11
Чому ви хочете щось налагодити? Тому що його код, і він хоче вивчити значення часу запуску, я б загрожував ....
RichieHH

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-виходів та зчитування їх на стороні процесора, без необхідності проходити через растеризатор.

Ось ще одне посилання на навчальний посібник із зворотного зв’язку "Трансформація".


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 Пісочниця була мені дуже зручна для шейдерів.

Не налагодження самостійно (на що відповіли як недієздатне), але зручно швидко переглядати зміни у виході.


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

Робіть офлайн-рендерінг текстури та оцінюйте дані текстури. Ви можете знайти відповідний код, googling для "render to text" opengl. Потім скористайтеся glReadPixels, щоб прочитати вихід у масив і виконувати на ньому твердження (оскільки перегляд такого величезного масиву в налагоджувачі зазвичай не дуже корисний).

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

Мене особисто на деякий час турбувала проблема правильної налагодження шейдерів. Здається, це не є гарним способом. Якщо хтось знайде хороший (а не застарілий / застарілий) налагоджувач, будь ласка, дайте мені знати.


3
Будь-яку відповідь або коментар, що говорить "google xyz", слід заборонити або проголосувати за допомогою Stackoverflow.
gregoiregentil

1

Існуючі відповіді - це все добре, але я хотів поділитися ще однією маленькою дорогоцінною каменем, яка була цінною при налагодженні складних питань точності в шейдері GLSL. З дуже великим числом int, представленим як плаваюча точка, потрібно дбати про те, щоб правильно використовувати підлогу (n) і підлогу (n + 0,5), щоб здійснити круглий () до точного цілого. Тоді можна надати поплавкове значення, яке є точним 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.