Menggunakan dua shader bukan satu dengan pernyataan IF

9

Saya telah bekerja pada porting sumber opengl ES 1.1 yang relatif besar ke ES 2.0.

Di OpenGL ES 2.0 (yang artinya, semuanya menggunakan shader), saya ingin menggambar teko tiga kali.

  1. Yang pertama, dengan warna yang seragam (ala the old glColor4f).

  2. Yang kedua, dengan warna per-simpul (teko memiliki array warna simpul juga)

  3. Yang ketiga, dengan tekstur per-simpul

  4. Dan mungkin yang keempat, dengan tekstur dan warna per-simpul. Dan kemudian mungkin yang ke-5, dengan normals juga ..

Ada dua pilihan yang saya miliki dengan implementasi, sejauh yang saya tahu. Yang pertama adalah membuat shader yang mendukung semua hal di atas, dengan seragam yang diatur untuk mengubah perilaku (misalnya menggunakan seragam warna singular, atau seragam warna per-simpul).

Pilihan kedua adalah membuat shader yang berbeda untuk setiap situasi. Dengan beberapa preprocessing custom shader, ini tidak terlalu rumit untuk dilakukan, tetapi yang menjadi perhatian adalah biaya kinerja dalam mengalihkan shader di antara objek gambar. Saya pernah membaca bahwa itu tidak kecil.

Maksud saya, cara terbaik untuk melakukan ini adalah membangun keduanya dan mengukur, tetapi akan baik untuk mendengar masukan apa pun.

kamziro
sumber

Jawaban:

10

Biaya kinerja percabangan juga tidak kecil. Dalam kasus Anda semua simpul dan fragmen yang ditarik akan mengambil jalur yang sama melalui shader Anda, jadi pada perangkat keras desktop modern tidak akan seburuk itu, tetapi Anda menggunakan ES2 yang menyiratkan bahwa Anda tidak menggunakan modern perangkat keras desktop.

Kasus terburuk dengan percabangan akan seperti ini:

  • kedua sisi cabang dievaluasi.
  • instruksi "mix" atau "step" akan dihasilkan oleh compiler shader dan dimasukkan ke dalam kode Anda untuk memutuskan sisi mana yang akan digunakan.

Dan semua instruksi tambahan ini akan dijalankan untuk setiap titik atau fragmen yang Anda gambar. Itu berpotensi jutaan instruksi tambahan untuk dipertimbangkan terhadap biaya perubahan shader.

" Panduan Pemrograman ES OpenGL ES untuk iOS " Apple (yang dapat dianggap representatif untuk perangkat keras target Anda) mengatakan hal ini tentang percabangan:

Hindari Bercabang

Cabang tidak disarankan menggunakan shader karena dapat mengurangi kemampuan untuk menjalankan operasi secara paralel pada prosesor grafis 3D. Jika shader Anda harus menggunakan cabang, ikuti rekomendasi ini:

  • Performa terbaik: Cabang pada konstanta yang dikenal saat shader dikompilasi.
  • Dapat diterima: Cabang pada variabel seragam.
  • Berpotensi melambat: Bercabang pada nilai yang dihitung di dalam shader.

Alih-alih membuat shader besar dengan banyak tombol dan tuas, buat shader lebih kecil khusus untuk tugas rendering tertentu. Ada tradeoff antara mengurangi jumlah cabang di shader Anda dan meningkatkan jumlah shader yang Anda buat. Uji berbagai opsi dan pilih solusi tercepat.

Bahkan jika Anda merasa puas bahwa Anda berada di slot "Dapat Diterima" di sini, Anda masih perlu mempertimbangkan bahwa dengan memilih 4 atau 5 kasus, Anda akan menaikkan jumlah instruksi dalam shader Anda. Anda harus mengetahui batasan penghitungan instruksi pada perangkat keras target Anda dan memastikan bahwa Anda tidak menggunakannya, mengutip lagi dari tautan Apple di atas:

Implementasi OpenGL ES tidak diperlukan untuk mengimplementasikan perangkat lunak mundur ketika batas-batas ini terlampaui; sebagai gantinya, shader gagal mengkompilasi atau menautkan.

Tidak ada yang mengatakan bahwa percabangan bukanlah solusi terbaik untuk kebutuhan Anda. Anda mengidentifikasi fakta bahwa Anda harus membuat profil kedua pendekatan dengan benar, jadi itulah rekomendasi terakhir. Namun perlu diketahui bahwa ketika shader menjadi lebih kompleks, solusi berbasis percabangan mungkin memberikan overhead yang jauh lebih tinggi daripada beberapa perubahan shader.

Maximus Minimus
sumber
3

Biaya mengikat shader mungkin tidak sepele, tetapi itu tidak akan menjadi hambatan Anda kecuali Anda merender ribuan item tanpa menumpuk semua objek yang menggunakan shader yang sama.

Meskipun saya tidak yakin apakah ini berlaku untuk perangkat seluler, tetapi GPU tidak terlalu lambat dengan cabang jika kondisinya antara konstan dan seragam. Keduanya valid, keduanya telah digunakan di masa lalu dan akan terus digunakan di masa depan, pilih mana yang menurut Anda akan lebih bersih dalam kasus Anda.

Selain itu, ada beberapa cara lain untuk mencapai hal ini: "Uber-shaders" dan sedikit tipu daya dengan cara program OpenGL shader dihubungkan.

"Uber-shaders" pada dasarnya adalah pilihan pertama, minus percabangan, tetapi Anda akan memiliki beberapa shader. Alih-alih menggunakan ifpernyataan, Anda menggunakan preprocessor - #define, #ifdef, #else, #endif, dan mengkompilasi versi yang berbeda, termasuk yang tepat #definekarena apa yang Anda butuhkan.

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

Anda juga dapat memecah shader menjadi fungsi terpisah. Memiliki satu shader yang mendefinisikan prototipe untuk semua fungsi dan memanggilnya, tautkan sekelompok shader tambahan yang menyertakan implementasi yang tepat. Saya telah menggunakan trik ini untuk pemetaan bayangan, untuk memudahkan menukar cara penyaringan dilakukan pada semua objek tanpa harus memodifikasi semua shader.

//ins, outs, uniforms

float getShadowCoefficient();

void main()
{
    //shading stuff goes here

    gl_FragColor = color * getShadowCoefficient();
}

Lalu, saya bisa memiliki beberapa file shader lain yang menentukan getShadowCoefficient(), seragam yang diperlukan, dan tidak ada yang lain. Misalnya, shadow_none.glslberisi:

float getShadowCoefficient()
{
    return 1;
}

Dan shadow_simple.glslberisi (disederhanakan dari shader saya yang mengimplementasikan 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;
}

Dan Anda bisa memilih apakah Anda ingin shading atau tidak dengan menghubungkan shadow_*shader yang berbeda . Solusi ini mungkin memiliki overhead yang lebih banyak, tetapi saya ingin berpikir bahwa kompiler GLSL cukup baik untuk mengoptimalkan semua overhead tambahan dibandingkan dengan cara lain untuk melakukan ini. Saya belum menjalankan tes apa pun tentang ini, tetapi itulah cara saya suka melakukannya.

Robert Rouhani
sumber