Как я могу отлаживать шейдеры GLSL?

45

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

Так как же я могу отладить шейдер? Есть ли способ пройти через шейдер? Могу ли я посмотреть на выполнение шейдера на определенной вершине / примитиве / фрагменте?

(Этот вопрос конкретно о том, как отлаживать шейдерный код, похожий на то, как можно было бы отлаживать «нормальный» код, а не об отладке таких вещей, как изменения состояния.)

Мартин Эндер
источник
Вы смотрели в gDEBugger? Цитирую сайт: «gDEBugger - продвинутый отладчик OpenGL и OpenCL, Profiler и Memory Analyzer. GDEBugger делает то, что не может другой инструмент - позволяет отслеживать активность приложения поверх API OpenGL и OpenCL и видеть, что происходит в реализации системы. " Конечно, нет отладки / пошагового прохождения кода в стиле VS, но это может дать вам некоторое представление о том, что делает (или должен делать) ваш шейдер. Crytec выпустила аналогичный инструмент для «отладки» шейдеров Direct под названием RenderDoc (бесплатный, но строго для шейдеров HLSL, так что, возможно, он вам не подходит).
Берт
@Bert Хм, да, я думаю, gDEBugger - это OpenGL-эквивалент WebGL-Inspector? Я использовал последний. Это очень полезно, но это определенно больше отладки вызовов OpenGL и изменений состояния, чем выполнение шейдеров.
Мартин Эндер
1
Я никогда не занимался программированием на WebGL и, следовательно, я не знаком с WebGL-Inspector. С помощью gDEBugger вы можете, по крайней мере, проверить все состояние вашего конвейера шейдеров, включая память текстур, данные вершин и т. Д. Тем не менее, на самом деле нет никакого шага по коду.
Берт
gDEBugger очень старый и с тех пор не поддерживается. Если вы смотрите на анализ состояния фрейма и графического процессора, то это другой вопрос, который тесно связан с: computergraphics.stackexchange.com/questions/23/…
cifz
Вот метод отладки, который я предложил для связанного вопроса: stackoverflow.com/a/29816231/758666
wip

Ответы:

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 ключевым словом)

Опять же, это очень глупо и далеко от правильной отладки, но я застрял в том, что не знаю какой-либо правильной альтернативы.

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

Также есть GLSL-отладчик . Этот отладчик раньше назывался GLSL Devil.

Сам отладчик очень удобен не только для кода GLSL, но и для самого OpenGL. У вас есть возможность переключаться между вызовами отрисовки и прерывать переключатели шейдеров. Он также показывает вам сообщения об ошибках, переданные OpenGL обратно в само приложение.

Sepehr
источник
2
Обратите внимание, что по состоянию на 2018-08-07 он не поддерживает ничего выше, чем GLSL 1.2, и не поддерживается активно.
Руслан
Этот комментарий на законных основаниях
огорчил
Проект с открытым исходным кодом и очень хотел бы помочь модернизировать его. Там нет другого инструмента, который делает то, что он сделал.
Ксенонофарктик
7

Поставщики графических процессоров предлагают несколько предложений, таких как AMD CodeXL или отладчик nSight / Linux GFX от NVIDIA, которые позволяют проходить через шейдеры, но привязаны к аппаратному обеспечению соответствующего поставщика.

Позвольте мне заметить, что, хотя они доступны в Linux, у меня всегда был небольшой успех с их использованием там. Я не могу комментировать ситуацию под Windows.

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

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

Когда это логическая ошибка, я пытаюсь восстановить ситуацию с GPU на CPU, вызывая включенный код на CPU с теми же данными. Тогда я могу пройти через это на процессоре.

Опираясь на модульность кода, вы также можете попробовать написать для него unittest и сравнить результаты между запуском GPU и CPU. Однако вы должны знать, что в некоторых случаях C ++ может вести себя не так, как GLSL, что дает ложные срабатывания в этих сравнениях.

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

А для краткого обзора приведу блок-схему моего процесса отладки: Блок-схема процедуры, описанной в тексте

В заключение приведу список случайных плюсов и минусов:

профессионал

  • шаг через обычный отладчик
  • дополнительная (часто лучше) диагностика компилятора

против

Никто
источник
Это отличная идея, и, вероятно, наиболее близкая к шейдерному коду. Интересно, если бы запуск через рендерер программного обеспечения (Mesa?) Имел бы подобные преимущества?
@racarate: Я тоже думал об этом, но у меня еще не было времени попробовать. Я не эксперт по mesa, но думаю, что отладка шейдера может быть затруднена, поскольку отладочная информация шейдера должна каким-то образом доходить до отладчика. С другой стороны, возможно, у людей в mesa уже есть интерфейс для того, чтобы отлаживать саму mesa :)
Никто
5

Хотя кажется, что на самом деле невозможно пройти через шейдер OpenGL, возможно получить результаты компиляции.
Следующее взято из образца картона Android .

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

Если ваш код компилируется правильно, то у вас нет другого выбора, кроме как попробовать другой способ сообщить вам о состоянии программы. Вы можете сигнализировать, что часть кода была достигнута, например, путем изменения цвета вершины или использования другой текстуры. Что неудобно, но пока кажется единственным выходом.

РЕДАКТИРОВАТЬ: Для WebGL, я смотрю на этот проект , но я только что нашел его ... не могу ручаться за это.

SL Barth - Восстановить Монику
источник
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 ++ - как уже упоминал Nobody. Он оказался очень эффективным при работе со сложным кодом, хотя и требует небольшой настройки.

Я работал в основном с HLSL Compute Shaders, для которых я разработал библиотеку для проверки концепции, доступную здесь:

https://github.com/cezbloch/shaderator

Он демонстрирует на вычислительном шейдере из примеров DirectX SDK, как включить отладку на C ++, например HLSL, и как настроить модульные тесты.

Компиляция вычислительного шейдера GLSL в C ++ выглядит проще, чем HLSL. Главным образом из-за синтаксических конструкций в HLSL. Я добавил тривиальный пример Исполняемого модульного теста на GLSL ray tracer Compute Shader, который вы также можете найти в источниках проекта Shaderator по ссылке выше.

Спасекис
источник