Комментарии 20
Работу модификаторов точности не использую, возможно напрасно. Особенности разных девайсов тут тоже не учитываются. Можно сказать, что такая проверка ближе к юнит-тестам, чем к интеграционным/приемочным. Тестирование на «железе» статический анализ все равно не заменит.
А может логичнее попытаться с символьными вычислениями? Т.е. вместо диапазонов вы будете хранить набор уравнений и неравенств, определяющих как диапазон входных данных (и только их, диапазоны зависимых переменных вами не вычисляются), так и всё, что происходило с разными переменными. А при нахождении строчки, на которой можно навернуться, добавлять недопустимое условие и скармливать какой‐нибудь CAS (computer algebra system). Если она успешно решает данный набор, то могут быть проблемы и можно доложить пользователю. Если говорит решений нет — то проблем нет. Если зависает или выдаёт ошибку — то либо у вас ошибка в программе, либо система уравнений и неравенств просто слишком сложная для CAS. В обоих случаях нужно использовать запасной план — либо игнорирование (т.е. «в анализируемом коде нет ошибки»), либо уточнение условий (не у пользователя, а на основании эвристики) и перезапуск CAS, либо использование другого алгоритма без символьных вычислений.
Я не думаю, что такой вариант бы сработал с языком общего назначения. Но с GLSL может и зайдёт.
Что, кстати, автоматически означает, что и на входе значения из текстуры могут быть вне этого диапазона.
Но идея интересная. Отладка сложных шейдеров иногда занятие весёлое.
И проверка выходных значений — только одно из применений. Меня больше волновали выходы за границы при вызове всяких `pow`, `mix`, `step` — разные видеокарты по-разному обрабатывают такие случаи.
Отладка сложных шейдеров иногда занятие весёлое.
Да даже простые шейдеры отлаживать — сплошное веселье )
. Юнит-тесты на GLSL не напишешь.
Дилетантский вопрос… собственно почему не напишешь?!
Если бы был транслятор GLSL в какой-нибудь другой язык, то можно было бы на транслируемую версию писать юнит-тесты. Но опять же — я такого не встречал…
Или скажем ваш шейдер записывает в выходной цвет в красный канал значение 2, при допустимом диапазоне от 0 до 1. На экран такое все равно не выведется, т.е. ошибку мы не увидим. А увидим что-то в зависимости от конкретной видеокарты — может быть максимально яркий красный цвет. А может черный. Т.е. выходной информации может просто не хватать для анализа.
Однако если вам надо убедиться что в таких-то ситуациях в таких-то точках должен получиться такой-то цвет — то ваш способ идеально подходит. Просто это ближе к интеграиционным/приемочным тестам, чем к юнит-тестами.
Однако это явный false alarm, а для статического анализатора нет ничего хуже ложных срабатываний — если его не отключат после первого крика «волки», то после десятого точно.
Порой это лучше, чем ничего, особенно если есть возможность поставить исключения после первого просмотра.
При этом на других языках можно хотя бы юнит-тесты писать, а тут — никак.
В своём небольшом проекте мы директивой #include включали код шейдеров в код на c++, и с помощью нескольких дефайнов и обёрток превращали шейдеры в обычные функции, которые возможно вызвать.
В своём небольшом проекте мы директивой #include включали код шейдеров в код на c++, и с помощью нескольких дефайнов и обёрток превращали шейдеры в обычные функции, которые возможно вызвать.
Пример не приведёте?
Было бы здорово в виде статьи.
uniform float value; //-> [0,1];
void main() {
float val2 = value - 1.;
gl_FragColor = vec4(value - val2);
}
#include <iostream>
#define uniform
typedef float vec4;
struct shader_test
{
vec4 gl_FragColor;
shader_test():
gl_FragColor(0) {}
#include "shader.glsl"
};
int main(int, char**)
{
shader_test tst;
tst.value = 0.5;
tst.main();
std::cerr << "gl_FragColor = " << tst.gl_FragColor << std::endl;
return 0;
}
А как вы собираетесь разбираться с доступами к элементам вектора в таком коде?
vec4 color = vec4(sin(coord.yzx),0)
template<class T>
struct v2
{
T& x;
T& y;
v2(T& _x, T& _y) : x(_x), y(_y) {}
template<class X>
v2(const X& other) : x(other.x), y(other.y) {}
template<class X>
v2<T>& operator = (const X& other)
{
T temp_x(other.x);
T temp_y(other.y);
x = temp_x;
y = temp_y;
return *this;
}
v2<T>& operator = (const v2<T>& other)
{
T temp_x(other.x);
T temp_y(other.y);
x = temp_x;
y = temp_y;
return *this;
}
};
template<class T>
struct vec2
{
T x;
T y;
v2<T> xx;
v2<T> yy;
v2<T> xy;
v2<T> yx;
vec2(T _x, T _y) : x(_x), y(_y), xx(x, x), yy(y, y), xy(x, y), yx(y, x) {}
template<class X>
vec2(const X& other) : x(other.x), y(other.y) {}
template<class X>
vec2<T>& operator = (const X& other)
{
x = other.x;
y = other.y;
return *this;
}
};
void test_swap()
{
{
vec2<float> point1(0, 0);
vec2<float> point2(1, 2);
point1.xy = point2;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
point1.yx = point2;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
}
{
vec2<float> point1(0, 0);
vec2<float> point2(1, 2);
point1 = point2.xy;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
point1 = point2.yx;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
}
{
vec2<float> point1(0, 0);
vec2<float> point2(1, 2);
point1.yx = point2.xx;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
point1.xy = point2.yy;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
}
{
vec2<float> point1(3, 4);
point1.xy = point1.yx;
std::cerr << "point = " << point1.x << " " << point1.y << std::endl;
}
}
Как я попробовал сделать статический анализатор GLSL (и что пошло не так)