Як я можу налагоджувати шейдери GLSL?


45

Під час написання нетривіальних шейдерів (як і під час написання будь-якого іншого фрагмента нетривіального коду) люди роблять помилки. [потрібне цитування] Однак я не можу просто налагодити його як будь-який інший код - ви не можете просто приєднати gdb або налагоджувач Visual Studio. Ви навіть не можете виконати налагодження printf, тому що немає форми консольного виводу. Що я зазвичай роблю - це надання даних, які я хочу розглянути як кольорові, але це дуже рудиментарне та любительське рішення. Я впевнений, що люди придумали кращі рішення.

Так як я можу насправді налагодити шейдер? Чи є спосіб переступити через шейдер? Чи можу я переглянути виконання шейдера на певній вершині / примітиві / фрагменті?

(Це питання спеціально про те, як налагодити шейдерний код, подібний тому, як можна було б налагоджувати "звичайний" код, а не про налагодження речей, таких як зміни стану.)


Ви заглянули в gDEBugger? Цитуючи сайт: "gDEBugger - це вдосконалений налагоджувач OpenGL та OpenCL, Profiler і аналізатор пам'яті. GDEBugger робить те, що не може інший інструмент - дозволяє відстежувати активність додатків поверх API API OpenGL та OpenCL і бачити, що відбувається в рамках впровадження системи. " Зрозуміло, немає налагодження / переходу через код VS коду, але це може дати вам деяке розуміння того, що робить (або повинен робити) ваш шейдер. Crytec випустив аналогічний інструмент для «налагодження» Direct Shader під назвою RenderDoc (безкоштовно, але строго для шейдерів HLSL, тому, можливо, для вас не стосується).
Берт

@Bert Гм так, я вважаю, що gDEBugger є еквівалентом OpenGL WebGL-інспектором? Я використав останнє. Це надзвичайно корисно, але це, безумовно, більше налагодження викликів OpenGL та змін стану, ніж виконання шейдера.
Мартін Ендер

1
Я ніколи не робив жодного WebGL-програмування, тому я не знайомий з WebGL-Inspector. За допомогою gDEBugger ви можете принаймні перевірити весь стан вашої трубопроводу шейдерів, включаючи текстурну пам'ять, вершинні дані тощо. Проте, жодного фактичного переходу через код afaik.
Берт

gDEBugger надзвичайно старий і не підтримується з певного часу. Якщо ви дивитесь на аналіз кадру та стану графічного процесора, то це інше питання, яке сильно пов'язане з цим: computergraphics.stackexchange.com/questions/23/…
cifz

Ось метод налагодження, який я запропонував із пов’язаним питанням: stackoverflow.com/a/29816231/758666
витирайте

Відповіді:


26

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

Те, що я особисто роблю, - це дуже хитра "барвиста налагодження". Тому я посипаю купу динамічних гілок #if DEBUG / #endifохоронцями, які в основному говорять

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

.. rest of code .. 

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

Таким чином, ви можете "спостерігати" інформацію про налагодження таким чином. Зазвичай я роблю різні трюки, такі як лерпінг або змішування між різними «кольоровими кодами», щоб перевірити різні складніші події або небінарні речі.

У цьому "рамках" я також вважаю корисним встановити набір фіксованих умов для звичайних випадків, так що якщо мені не доведеться постійно повертатися назад і перевіряти, з яким кольором я пов'язаний. Важливим є наявність хорошої підтримки для гарячого перезавантаження шейдерного коду, тому ви можете майже інтерактивно змінювати відстежувані дані / подію та легко включати / вимикати візуалізацію налагодження.

Якщо вам потрібно налагодити щось, що ви не можете легко відобразити на екрані, ви завжди можете зробити те ж саме і використовувати один інструмент аналізатора кадрів для перевірки результатів. Я перерахував пару з них як відповідь на це інше питання.

Очевидно, що, якщо я не «налагоджую» піксельний шейдер або обчислюючу шейдер, я передаю цю інформацію про «debugColor» у всьому конвеєрі, не інтерполюючи її (у GLSL з flat ключовим словом)

Знову ж таки, це дуже хакітно і далеко не належне налагодження, але це те, що я застряг, не знаючи жодної належної альтернативи.


Коли вони доступні, ви можете використовувати SSBO, щоб отримати більш гнучкий формат виводу, де вам не потрібно кодувати кольорами. Однак, великим недоліком такого підходу є те, що він змінює код, який може приховувати / змінювати помилки, особливо коли бере участь UB. +1 Тим не менш, для нього є найбільш прямий доступний метод.
Ніхто

9

Також є GLSL-налагоджувач . Це налагоджувач, який раніше був відомий як "GLSL Devil".

Сам налагоджувач дуже зручний не тільки для GLSL-коду, але й для самого OpenGL. У вас є можливість переходити між дзвінками на розрив і перериватися на перемикачі Shader. Тут також відображаються повідомлення про помилки, які повідомляються OpenGL назад до самої програми.


2
Зауважте, що станом на 2018-08-07, він не підтримує нічого вище, ніж GLSL 1.2, і він не підтримується активно.
Руслан

Цей коментар правомірно змусив мене сумувати :(
rdelfin

Проект є відкритим кодом, і він дуже любить його модернізувати. Немає іншого інструменту, який би робив те, що робив.
XenonofArcticus

7

Існує кілька пропозицій постачальників GPU, таких як AMD CodeXL або NVIDIA nSight / Linux GFX Debugger, які дозволяють переходити через шейдери, але прив’язані до обладнання відповідного постачальника.

Зазначу, що, хоча вони доступні під Linux, я завжди мав дуже малий успіх у використанні їх там. Я не можу коментувати ситуацію в Windows.

Варіант, який я нещодавно почав використовувати, - це модулювати свій шейдерний код за допомогою #includesта обмежити включений код до загального підмножини GLSL та C ++ & glm .

Коли я потрапляю на проблему, я намагаюся відтворити її на іншому пристрої, щоб побачити, чи проблема є такою ж, яка натякає на логічну помилку (замість проблеми з драйвером / невизначеної поведінки). Також є ймовірність передачі неправильних даних до графічного процесора (наприклад, через неправильно пов'язані буфери тощо), які, як правило, виключаю або за допомогою налагодження виводу, як у відповіді cifz, або шляхом перевірки даних за допомогою apitrace .

Коли це логічна помилка, я намагаюся відновити ситуацію з графічного процесора на процесорі, викликаючи включений код у процесорі з тими ж даними. Тоді я можу перейти через це на процесорі.

Спираючись на модульність коду, ви також можете спробувати написати одиничний тест для нього та порівняти результати між запуском GPU та процесором. Однак ви повинні знати, що є випадкові випадки, коли C ++ може поводитись інакше, ніж GLSL, таким чином, ви даєте помилкові позитиви в цих порівняннях.

Нарешті, коли ви не можете відтворити проблему на іншому пристрої, ви можете почати копати лише там, де походить різниця. Unittests можуть допомогти вам звузити місце, де це відбувається, але, зрештою, вам, мабуть, потрібно буде виписати додаткову інформацію про налагодження з шейдера, як у відповіді cifz .

А для того, щоб дати вам огляд, тут є блок-схема мого процесу налагодження: Блок-схема процедури, описаної в тексті

Щоб завершити це, наведено список випадкових плюсів і мінусів:

профі

  • перейти через звичайний налагоджувач
  • додаткова (часто краще) компіляційна діагностика

кон


Це відмінна ідея, і, мабуть, найближчий ви можете дістатися до одношагового коду шейдера. Цікаво, якби пробіг через програмний рендер (Mesa?) Матиме подібні переваги?

@racarate: Я також про це думав, але ще не встиг спробувати. Я не фахівець з меса, але я думаю, що може бути важко налагодити шейдер, оскільки інформація про налагодження шейдера повинна якось дістатися до налагоджувача. Знову ж таки, можливо, у людей у ​​меса вже є інтерфейс для того, щоб налагодити месу :)
Ніхто

5

Хоча, здається, неможливо реально перейти через шейдер OpenGL, можливо отримати результати компіляції.
Далі взято із зразка картону Android .

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

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

EDIT: Для WebGL я дивлюся на цей проект , але я тільки що знайшов його ... не можу поручитися за нього.


3
Гм так, я знаю, що можу отримати помилки компілятора. Я сподівався на кращу налагодження часу виконання. У минулому я також використовував інспектор WebGL, але я вважаю, що він показує лише зміни стану, але ви не можете шукати виклик шейдера. Я думаю, це могло бути зрозумілішим у питанні.
Мартін Ендер

2

Це копія-вставка моєї відповіді на те саме запитання в StackOverflow .


Внизу цієї відповіді - приклад коду 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

Рішення, яке працювало для мене, - це компіляція шейдерного коду до C ++ - про що ніхто не згадував. Це було дуже ефективно під час роботи над складним кодом, хоча він вимагає трохи налаштування.

Я працюю здебільшого з обчислювальними шейдерами HLSL, для яких я розробив бібліотеку підтверджених концепцій, доступну тут:

https://github.com/cezbloch/shaderator

Це демонструє на Compute Shader з DirectX SDK Samples, як увімкнути C ++, як налагодження HLSL та як налаштувати тести модулів.

Компіляція обчислювального шейдера GLSL до C ++ виглядає простіше, ніж HLSL. В основному завдяки синтаксичним конструкціям у HLSL. Я додав тривіальний приклад виконуваного модуля Тестування на обчислювальному шейдері променевих променів GLSL, який ви також можете знайти в джерелах проекту Shaderator за посиланням вище.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.