Używanie dwóch shaderów zamiast jednego z instrukcjami IF

9

Pracowałem nad przeniesieniem stosunkowo dużego źródła opengl ES 1.1 do ES 2.0.

W OpenGL ES 2.0 (co oznacza, że ​​wszystko korzysta z shaderów) chcę narysować czajnik trzy razy.

  1. Pierwszy, o jednolitym kolorze (ala stary glColor4f).

  2. Drugi, z kolorem na wierzchołek (czajniczek ma również tablicę kolorów wierzchołków)

  3. Trzeci z teksturą na wierzchołek

  4. I może czwarty z teksturami i kolorami dla wierzchołków. A potem może piąty, z normalnymi również ...

O ile wiem, mam dwie możliwości wdrożenia. Pierwszym z nich jest stworzenie modułu cieniującego, który obsługuje wszystkie powyższe elementy, z jednolitym ustawionym w celu zmiany zachowania (np. Użyj jednolitego jednolitego koloru lub jednolitego koloru na wierzchołek).

Drugim wyborem jest utworzenie innego modułu cieniującego dla każdej sytuacji. W przypadku niektórych niestandardowych procesów wstępnego przetwarzania modułu cieniującego nie jest to takie skomplikowane, ale problemem jest koszt wydajności przełączania modułów cieniujących między obiektami rysującymi. Czytałem, że to nie jest trywialnie małe.

To znaczy, najlepszym sposobem na zrobienie tego jest zbudowanie obu i zmierzenie, ale dobrze byłoby usłyszeć wszelkie dane wejściowe.

kamziro
źródło

Odpowiedzi:

10

Koszt wydajności rozgałęzienia również nie może być banalnie mały. W twoim przypadku wszystkie wyciągane wierzchołki i fragmenty będą podążać tą samą ścieżką przez twoje shadery, więc na nowoczesnym sprzęcie stacjonarnym nie byłoby tak źle, jak mogłoby być, ale używasz ES2, co oznacza, że ​​nie używasz nowoczesnego sprzęt stacjonarny.

Najgorszy przypadek z rozgałęzianiem będzie wyglądał tak:

  • oceniane są obie strony oddziału.
  • instrukcja „mix” lub „step” zostanie wygenerowana przez kompilator modułu cieniującego i wstawiona do kodu, aby zdecydować, której strony użyć.

Wszystkie te dodatkowe instrukcje będą uruchamiane dla każdego narysowanego wierzchołka lub fragmentu. To potencjalnie miliony dodatkowych instrukcji, które należy porównać z kosztem zmiany modułu cieniującego.

Przewodnik po programowaniu OpenGL ES dla systemu iOS ” firmy Apple (który można uznać za reprezentatywny dla docelowego sprzętu) ma następujące zdanie na temat rozgałęziania:

Unikaj rozgałęzień

Oddziały są odradzane w shaderach, ponieważ mogą zmniejszać zdolność do równoległego wykonywania operacji na procesorach graficznych 3D. Jeśli twoje shadery muszą używać gałęzi, postępuj zgodnie z tymi zaleceniami:

  • Najlepsza wydajność: rozgałęzienie na stałej znanej podczas kompilacji modułu cieniującego.
  • Dopuszczalne: rozgałęzić się na zmiennej jednolitej.
  • Potencjalnie wolny: rozgałęzienie na wartości obliczonej w module cieniującym.

Zamiast tworzyć duży moduł cieniujący z wieloma pokrętłami i dźwigniami, należy tworzyć mniejsze moduły cieniujące specjalizowane do określonych zadań renderowania. Istnieje kompromis między zmniejszaniem liczby rozgałęzień w modułach cieniujących a zwiększaniem liczby tworzonych modułów cieniujących. Przetestuj różne opcje i wybierz najszybsze rozwiązanie.

Nawet jeśli jesteś usatysfakcjonowany, że znajdujesz się w polu „Dopuszczalne”, nadal musisz wziąć pod uwagę, że mając 4 lub 5 przypadków do wyboru, zwiększysz liczbę instrukcji w swoich shaderach. Powinieneś być świadomy ograniczeń liczby instrukcji na docelowym sprzęcie i upewnić się, że nie przekroczysz ich, cytując ponownie z linku Apple powyżej:

Implementacje OpenGL ES nie są wymagane do wdrożenia rezerwowego oprogramowania po przekroczeniu tych limitów; zamiast tego moduł cieniujący po prostu nie kompiluje się ani nie łączy.

Nie oznacza to, że rozgałęzienie nie jest najlepszym rozwiązaniem dla twoich potrzeb. Poprawnie zidentyfikowałeś fakt, że powinieneś profilować oba podejścia, więc to jest ostateczna rekomendacja. Należy jednak pamiętać, że w miarę jak moduły cieniujące stają się bardziej złożone, rozwiązanie oparte na rozgałęzieniach może znacznie zwiększyć koszty ogólne niż kilka zmian modułu cieniującego.

Maximus Minimus
źródło
3

Koszt wiązania shaderów może nie być trywialny, ale nie będzie twoim wąskim gardłem, chyba że renderujesz tysiące przedmiotów bez grupowania wszystkich obiektów, które używają tych samych shaderów.

Chociaż nie jestem pewien, czy dotyczy to urządzeń mobilnych, ale procesory graficzne nie są strasznie powolne z oddziałami, jeśli warunek jest między stałą a jednolitą. Oba są ważne, oba były używane w przeszłości i będą nadal używane w przyszłości, wybierz ten, który Twoim zdaniem byłby czystszy w twoim przypadku.

Dodatkowo istnieje kilka innych sposobów na osiągnięcie tego: „Uber-shadery” i trochę sztuczki ze sposobem łączenia programów cieniujących OpenGL.

„Uber-shadery” są zasadniczo pierwszym wyborem, pomijając rozgałęzienia, ale będziesz mieć wiele shaderów. Zamiast używać ifstwierdzeń, należy użyć preprocesor - #define, #ifdef, #else, #endif, i skompilować różne wersje, w tym prawidłowego #defines dla co trzeba.

vec4 color;
#ifdef PER_VERTEX_COLOR
color = in_color;
#else
color = obj_color;
#endif

Możesz także podzielić moduł cieniujący na osobne funkcje. Posiadaj jeden moduł cieniujący, który definiuje prototypy dla wszystkich funkcji i wywołuje je, połącz kilka dodatkowych modułów cieniujących, które zawierają odpowiednie implementacje. Użyłem tej sztuczki do mapowania cieni, aby ułatwić wymianę sposobu filtrowania na wszystkich obiektach bez konieczności modyfikowania wszystkich shaderów.

//ins, outs, uniforms

float getShadowCoefficient();

void main()
{
    //shading stuff goes here

    gl_FragColor = color * getShadowCoefficient();
}

Następnie mógłbym mieć wiele innych plików cieniujących, które definiują getShadowCoefficient(), niezbędne mundury i nic więcej. Na przykład shadow_none.glslzawiera:

float getShadowCoefficient()
{
    return 1;
}

I shadow_simple.glslzawiera (uproszczony z mojego modułu cieniującego, który implementuje CSM):

in vec4 eye_position;

uniform sampler2DShadow shad_tex;
uniform mat4 shad_mat;

float getShadowCoefficient()
{
    vec4 shad_coord = shad_mat * eye_position;
    return texture(shad_tex, shad_coord).x;
}

Możesz po prostu wybrać, czy chcesz cieniować, czy nie, łącząc inny shadow_*moduł cieniujący. To rozwiązanie może mieć znacznie większy narzut, ale chciałbym myśleć, że kompilator GLSL jest wystarczająco dobry, aby zoptymalizować wszelkie dodatkowe koszty w porównaniu z innymi sposobami na to. Nie przeprowadziłem na tym żadnych testów, ale lubię to robić.

Robert Rouhani
źródło