Mengapa persyaratan ini dalam shader fragmen saya begitu lambat?

19

Saya telah menyiapkan beberapa kode pengukur FPS di WebGL (berdasarkan jawaban SO ini ) dan telah menemukan beberapa keanehan dengan kinerja shader fragmen saya. Kode hanya membuat quad tunggal (atau lebih tepatnya dua segitiga) di atas kanvas 1024x1024, sehingga semua keajaiban terjadi dalam fragmen shader.

Pertimbangkan shader sederhana ini (GLSL; vertex shader hanyalah 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);
}

Jadi ini hanya membuat kanvas putih. Rata-rata sekitar 30 fps pada mesin saya.

Sekarang mari kita tingkatkan angka yang berderak dan hitung setiap fragmen berdasarkan pada beberapa oktaf suara yang bergantung pada posisi:

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

Jika Anda ingin menjalankan kode di atas, saya telah menggunakan implementasi inisnoise .

Ini membawa fps ke sesuatu seperti 7. Itu masuk akal.

Sekarang bagian yang aneh ... mari kita hitung hanya satu dari setiap 16 fragmen sebagai noise dan biarkan yang lain putih, dengan membungkus perhitungan noise dalam kondisi berikut:

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

Anda akan mengharapkan ini menjadi lebih cepat, tetapi masih hanya 7 fps.

Untuk satu pengujian lagi, mari filter saja piksel dengan persyaratan berikut:

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

Ini memberikan jumlah piksel noise yang sama persis seperti sebelumnya, tetapi sekarang kita kembali ke hampir 30 fps.

Apa yang terjadi disini? Tidakkah kedua cara untuk menyaring piksel ke-16 ini menghasilkan jumlah siklus yang sama persis? Dan mengapa yang lebih lambat memperlambat semua piksel sebagai noise?

Pertanyaan bonus: Apa yang bisa saya lakukan tentang ini? Apakah ada cara untuk mengatasi kinerja yang mengerikan jika saya benar - benar ingin berbintik kanvas saya dengan hanya beberapa fragmen mahal?

(Hanya untuk memastikan, saya telah mengkonfirmasi bahwa perhitungan modulo yang sebenarnya tidak mempengaruhi laju bingkai sama sekali, dengan merender setiap piksel 16 hitam bukan putih.)

Martin Ender
sumber

Jawaban:

22

Pixel dikelompokkan ke dalam kotak kecil (seberapa besar tergantung pada perangkat keras) dan dihitung bersama dalam satu pipa SIMD . (struct dari jenis array SIMD)

Pipeline ini (yang memiliki beberapa nama berbeda tergantung pada vendor: warps, wavefronts) akan menjalankan operasi untuk setiap pixel / fragmen di lockstep. Ini berarti bahwa jika 1 piksel membutuhkan perhitungan, maka semua piksel akan menghitungnya dan yang tidak membutuhkan hasilnya akan membuangnya.

Jika semua fragmen mengikuti jalur yang sama melalui shader maka cabang lainnya tidak akan dieksekusi.

Ini berarti bahwa metode komputasi pertama Anda setiap piksel ke-16 akan menjadi percabangan terburuk.

Jika Anda ingin menurunkan ukuran gambar Anda, maka render ke tekstur yang lebih kecil dan kemudian skalakan.

ratchet freak
sumber
5
Rendering ke tekstur yang lebih kecil dan upsampling adalah cara yang baik untuk melakukannya. Tetapi jika karena alasan tertentu Anda benar-benar perlu menulis ke setiap piksel ke-16 dari tekstur besar, menggunakan penghitung komputasi dengan satu permintaan untuk setiap piksel ke-16 ditambah pemuatan / penyimpanan gambar untuk menyebarkan tulisan ke dalam target render bisa menjadi pilihan yang baik.
Nathan Reed