Perché questo condizionale nel mio shader di frammenti è così lento?


19

Ho impostato un codice di misurazione FPS in WebGL (basato su questa risposta SO ) e ho scoperto alcune stranezze con le prestazioni del mio shader di frammenti. Il codice esegue semplicemente il rendering di un singolo quad (o meglio due triangoli) su una tela di 1024x1024, quindi tutta la magia avviene nello shader di frammenti.

Considera questo semplice shader (GLSL; lo shader di vertice è solo un pass-through):

// 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);
}

Quindi questo rende solo una tela bianca. La mia macchina ha una media di circa 30 fps.

Ora aumentiamo il numero di scricchiolii e calcoliamo ogni frammento in base a poche ottave di rumore dipendente dalla posizione:

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);
}

Se vuoi eseguire il codice sopra, sto usando questa implementazione disnoise .

Questo porta il fps a qualcosa come 7. Questo ha senso.

Ora la parte strana ... calcoliamo solo uno di ogni 16 frammenti come rumore e lasciamo gli altri bianchi, avvolgendo il calcolo del rumore nel seguente condizionale:

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

Ti aspetteresti che questo sia molto più veloce, ma è ancora solo 7 fps.

Per un altro test, filtriamo invece i pixel con il seguente condizionale:

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

Questo fornisce lo stesso numero esatto di pixel di rumore di prima, ma ora siamo tornati a quasi 30 fps.

Cosa sta succedendo qui? I due modi per filtrare un sedicesimo dei pixel non dovrebbero fornire lo stesso numero esatto di cicli? E perché quello più lento è lento quanto il rendering di tutti i pixel come rumore?

Domanda bonus: cosa posso fare al riguardo? C'è un modo per aggirare la prestazione orribile se io in realtà non voglio speckle mia tela con solo pochi frammenti costosi?

(Per essere sicuro, ho confermato che l'attuale calcolo del modulo non influenza affatto la frequenza dei fotogrammi, rendendo ogni 16 pixel nero anziché bianco.)

Risposte:


22

I pixel vengono raggruppati in quadratini (quanto grande dipende dall'hardware) e calcolati insieme in una singola pipeline SIMD . (tipo di array di tipo SIMD)

Questa pipeline (che ha diversi nomi a seconda del fornitore: orditi, fronti d'onda) eseguirà le operazioni per ciascun pixel / frammento in sequenza. Ciò significa che se 1 pixel necessita di un calcolo fatto, tutti i pixel lo calcoleranno e quelli che non hanno bisogno del risultato lo gettano via.

Se tutti i frammenti seguono lo stesso percorso attraverso uno shader, gli altri rami non verranno eseguiti.

Ciò significa che il tuo primo metodo di elaborazione ogni 16 pixel sarà il caso peggiore.

Se desideri ridurre ancora le dimensioni dell'immagine, esegui il rendering su una trama più piccola e poi ingrandiscila.


5
Il rendering su una trama più piccola e il ricampionamento sono un buon modo per farlo. Ma se per qualche motivo hai davvero bisogno di scrivere su ogni 16 pixel della trama di grandi dimensioni, utilizzare uno shader di calcolo con una chiamata per ogni 16 pixel più carico / archivio di immagini per disperdere le scritture nella destinazione di rendering potrebbe essere una buona opzione.
Nathan Reed,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.