Как две строки GLSL едва не свели меня с ума 😁

Недавно столкнулся с весьма неприятным багом в движке The Great Tribes.

Как две строки GLSL едва не свели меня с ума 😁

После обновления драйвера AMD игра начала намертво зависать при перемещении камеры, особенно когда на карте открывалась большая территория. Иногда зависал графический драйвер, иногда JVM падала внутри atio6axx.dll.

Самое интересное — на старом драйвере и других видеокартах всё работало нормально!

Начались поиски. Под подозрение попало практически всё: многопоточность OpenGL, VAO, VBO, индексные буферы, тени, модели юнитов и даже glfwSwapBuffers().

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

В нём обнаружилась вот такая конструкция:

int segment = (3 - int(min(3, outTexCoord.y * 4.0))) * 4 + int(min(3, outTexCoord.x * 4.0));

Переменная segment использовалась для обращения к массивам из 16 элементов. Проблема заключалась в том, что координаты ограничивались сверху, но не снизу. При отрицательных текстурных координатах индекс мог выходить за пределы массива.

Исправление оказалось удивительно простым:

ivec2 cell = clamp( ivec2(floor(outTexCoord * 4.0)), ivec2(0), ivec2(3) ); int segment = (3 - cell.y) * 4 + cell.x;

И зависания исчезли!

Получилась отличная иллюстрация неопределённого поведения в GLSL. Старый драйвер каким-то образом справлялся с некорректным обращением к массиву, другие видеокарты тоже не показывали проблемы, а новая версия драйвера AMD превратила его в настоящий кошмар.

Мораль истории: если ваш шейдер прекрасно работает на одной видеокарте, это ещё не означает, что в нём нет ошибок.

А иногда за многочасовой отладкой собственного 3D-движка скрываются всего две строчки кода. 😁

66