Dlaczego jest to warunkowe w moim module cieniującym fragmenty tak wolno?


19

Skonfigurowałem trochę kodu mierzącego FPS w WebGL (w oparciu o tę odpowiedź SO ) i odkryłem pewne dziwactwa w działaniu mojego modułu cieniującego fragmenty. Kod po prostu renderuje pojedynczy quad (a raczej dwa trójkąty) na kanwie 1024x1024, więc cała magia dzieje się w module cieniującym fragmenty.

Rozważ ten prosty moduł cieniujący (GLSL; moduł cieniujący wierzchołek to tylko przejście):

// some definitions

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

    // Nothing to see here...

    gl_FragColor = vec4(value, value, value, 1.0);
}

To po prostu renderuje białe płótno. To średnio około 30 fps na moim komputerze.

Teraz zwiększmy liczbę pęknięć i obliczmy każdy fragment na podstawie kilku oktaw hałasu zależnego od pozycji:

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

      float noise;
      for ( int j=0; j<10; ++j)
      {
        noise = 0.0;
        for ( int i=4; i>0; i-- )
        {
            float oct = pow(2.0,float(i));
            noise += snoise(vec2(mod(seed,13.0)+x*oct,mod(seed*seed,11.0)+y*oct))/oct*4.0;
        }
      }

      value = noise/2.0+0.5;

    gl_FragColor = vec4(value, value, value, 1.0);
}

Jeśli chcesz uruchomić powyższy kod, korzystałem z tej implementacjisnoise .

Sprowadza to fps do czegoś takiego jak 7. To ma sens.

Teraz dziwna część ... obliczyć tylko jeden z 16 fragmentów jako hałas, a pozostałe pozostawić białe, zawijając obliczenia hałasu w następujący warunek:

if (int(mod(x*512.0,4.0)) == 0 && int(mod(y*512.0,4.0)) == 0)) {
    // same noise computation
}

Można się spodziewać, że będzie to znacznie szybsze, ale wciąż jest to tylko 7 fps.

Dla jeszcze jednego testu przefiltrujmy piksele według następującego warunku:

if (x > 0.5 && y > 0.5) {
    // same noise computation
}

Daje to dokładnie taką samą liczbę pikseli szumu jak poprzednio, ale teraz wracamy do prawie 30 fps.

Co tu się dzieje? Czy dwa sposoby filtrowania 16 pikseli nie powinny dawać dokładnie takiej samej liczby cykli? I dlaczego wolniejszy jest tak wolny, jak renderowanie wszystkich pikseli jak szum?

Pytanie dodatkowe: Co mogę z tym zrobić? Czy istnieje jakiś sposób, aby obejść okropnym wydajności gdybym rzeczywiście nie chcą speckle moje płótno tylko kilka drogich fragmentów?

(Dla pewności potwierdziłem, że faktyczne obliczenia modulo w ogóle nie wpływają na częstotliwość klatek, renderując co 16 pikseli na czarno zamiast na biało).

Odpowiedzi:


22

Piksele są grupowane w małe kwadraty (wielkość zależy od sprzętu) i obliczane razem w jednym potoku SIMD . (struct typu tablic SIMD)

Ten potok (który ma kilka różnych nazw w zależności od dostawcy: wypaczenia, fronty fal) wykona operacje dla każdego piksela / fragmentu w fazie blokowania. Oznacza to, że jeśli 1 piksel wymaga obliczenia, wszystkie piksele go obliczą, a te, które nie potrzebują wyniku, wyrzucą go.

Jeśli wszystkie fragmenty podążają tą samą ścieżką przez moduł cieniujący, inne gałęzie nie zostaną wykonane.

Oznacza to, że pierwszą metodą obliczania co 16 pikseli będzie najgorszy przypadek rozgałęzienia.

Jeśli nadal chcesz zmniejszyć rozmiar obrazu, po prostu renderuj go na mniejszą teksturę, a następnie przeskaluj.


5
Renderowanie do mniejszej tekstury i upsampling to dobry sposób na zrobienie tego. Ale jeśli z jakiegoś powodu naprawdę musisz pisać do każdego 16 piksela dużej tekstury, dobrym rozwiązaniem może być użycie modułu obliczeniowego z jednym wywołaniem dla każdego 16 piksela plus ładowanie / przechowywanie obrazu w celu rozproszenia zapisów w celu renderowania.
Nathan Reed
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.