Шейдер GLSL не разворачивает цикл при необходимости

Моя 9600GT меня ненавидит.

Фрагментный шейдер:

#version 130

uint aa[33] = uint[33](
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0
);

void main() {
    int i=0;
    int a=26;

    for (i=0; i<a; i++) aa[i]=aa[i+1];

    gl_FragColor=vec4(1.0,0.0,0.0,1.0);

}

Если a=25 программа работает со скоростью 3000 кадров в секунду.
Если a=26 программа работает со скоростью 20 кадров в секунду.
Если размер aa ‹=32, проблема не возникает.
Размер окна просмотра 1000x1000.
Проблема возникает только тогда, когда размер aa > 32.
Значение a, поскольку пороговое значение изменяется в зависимости от вызовов массива внутри цикла (aa[i]=aa[i+1]+aa[i-1] дает другой крайний срок).
Я знаю, что gl_FragColor устарел. Но это не проблема.

Я предполагаю, что GLSL не разворачивает цикл автоматически, если a>25 и size(aa)>32. Почему. Причина, по которой это зависит от размера массива, человечеству неизвестна.

Очень похожее поведение объясняется здесь:
http://www.gamedev.net/topic/519511-glsl-for-loops/

Разматывание цикла вручную решает проблему (3000 кадров в секунду), даже если размер aa > 32:

    aa[0]=aa[1];
    aa[1]=aa[2];
    aa[2]=aa[3];
    aa[3]=aa[4];
    aa[4]=aa[5];
    aa[5]=aa[6];
    aa[6]=aa[7];
    aa[7]=aa[8];
    aa[8]=aa[9];
    aa[9]=aa[10];
    aa[10]=aa[11];
    aa[11]=aa[12];
    aa[12]=aa[13];
    aa[13]=aa[14];
    aa[14]=aa[15];
    aa[15]=aa[16];
    aa[16]=aa[17];
    aa[17]=aa[18];
    aa[18]=aa[19];
    aa[19]=aa[20];
    aa[20]=aa[21];
    aa[21]=aa[22];
    aa[22]=aa[23];
    aa[23]=aa[24];
    aa[24]=aa[25];
    aa[25]=aa[26];
    aa[26]=aa[27];
    aa[27]=aa[28];
    aa[28]=aa[29];
    aa[29]=aa[30];
    aa[30]=aa[31];
    aa[31]=aa[32];
    aa[32]=aa[33];

person user2464424    schedule 01.09.2013    source источник
comment
Итак... какой у тебя вопрос?   -  person Nicol Bolas    schedule 01.09.2013
comment
@NicolBolas Почему при a=26 частота кадров резко падает?   -  person user2464424    schedule 01.09.2013
comment
Единственный человек, который мог бы это знать, — это человек, который реализовал ваш компилятор OpenGL.   -  person Nicol Bolas    schedule 01.09.2013
comment
Есть ли такая команда, как #pragma optionNV(unroll none), которая заставляет opengl всегда выполнять развертывание?   -  person user2464424    schedule 01.09.2013
comment
@ user2464424: Да, у NV довольно много проприетарных директив GLSL #pragma. И развертывание циклов — одна из таких директив. Конкретная непереносимая прагма, которую вы хотите, это #pragma optionNV (unroll all). Обычно лучше развернуть материал самостоятельно, так как AMD/Intel/... не знают, что это за #pragma. Радости от того, что каждый поставщик реализует свой собственный компилятор - одна вещь, которая мне нравится в HLSL, Microsoft реализует один-единственный компилятор, поэтому там все довольно последовательно.   -  person Andon M. Coleman    schedule 01.09.2013
comment
@ AndonM.Coleman Это помогло. Спасибо!   -  person user2464424    schedule 01.09.2013
comment
Я ожидаю, что разница в скорости будет в первую очередь связана с тем, что весь код цикла удаляется после развертывания (и не устраняется, если он не развернут), а не из-за накладных расходов на цикл. Если вы замените цикл чем-то, что не может быть удалено из мертвого кода или свернуто почти до нуля, я ожидаю, что вы не увидите большой разницы в скорости между развернутым и не развернутым кодом...   -  person Chris Dodd    schedule 01.09.2013
comment
Попробуйте перенести свой код GLSL в основной профиль. это обычно помогает, потому что в новых драйверах поддержка старых вещей не так хороша, как раньше. Это решило мне много странных проблем, связанных с компилятором в прошлом (на картах nVidia, а также на картах ATI).   -  person Spektre    schedule 16.10.2013


Ответы (1)


Я просто помещаю здесь обобщающий ответ на комментарии, чтобы он больше не оставался без ответа.

"#pragma optionNV (развернуть все)"

устраняет непосредственную проблему на nvidia.

Однако в целом компиляторы GLSL очень зависят от реализации. Причина, по которой происходит падение ровно на 32, легко объясняется использованием эвристики компилятора, такой как «не разворачивать циклы длиннее 32». Кроме того, огромная разница в скорости может быть связана с развернутым циклом с использованием констант, в то время как для динамического цикла потребуется адресная память массива. Другая причина может заключаться в том, что при развертывании устранения мертвого кода постоянное свертывание приводит к сведению всего цикла к нулю.

Самый портативный способ исправить это — ручное развертывание или, что еще лучше, ручное постоянное складывание. Всегда сомнительно вычислять константы во фрагментном шейдере, которые могут быть вычислены снаружи. Некоторые водители могут его поймать в некоторых случаях, но на это лучше не полагаться.

person starmole    schedule 16.02.2014
comment
Отмечу, что длина цикла 26, а не 32. 32 — это размер массива. В общем, это адский крайний случай. Есть как минимум 3 фактора, конфликтующих друг с другом: 1) компилятор удаляет мертвый код только в том случае, если цикл развернут. 2) постоянный массив, превращающийся в массив, выделенный VRAM, после 32 чисел с плавающей запятой. 3) порог длины цикла до его автоматического развертывания зависит от типов переменных, участвующих в самом цикле. Вывод: в этом случае НИКОГДА ничего не должно происходить, так как код, который я разместил, является очень плохой практикой и никогда не должен использоваться ни в одном реальном сценарии. - person user2464424; 16.02.2014