Saya sering menemukan diri saya menyalin kode paste antara beberapa shader. Ini termasuk perhitungan atau data tertentu yang dibagikan di antara semua shader dalam satu pipa tunggal, dan perhitungan umum yang dibutuhkan oleh semua shader vertex saya (atau tahap lainnya).
Tentu saja, itu praktik yang mengerikan: jika saya perlu mengubah kode di mana saja, saya harus memastikan saya mengubahnya di tempat lain.
Apakah ada praktik terbaik yang diterima untuk menjaga KERING ? Apakah orang hanya menambahkan satu file biasa ke semua shader mereka? Apakah mereka menulis preprocessor C-style ala kadarnya sendiri yang menguraikan #include
arahan? Jika ada pola yang diterima di industri, saya ingin mengikuti mereka.
Jawaban:
Ada banyak pendekatan, tetapi tidak ada yang sempurna.
Dimungkinkan untuk berbagi kode dengan menggunakan
glAttachShader
untuk menggabungkan shader, tetapi ini tidak memungkinkan untuk membagikan hal-hal seperti deklarasi struct atau#define
konstanta -d. Itu berfungsi untuk fungsi berbagi.Beberapa orang suka menggunakan array string yang diteruskan
glShaderSource
sebagai cara untuk menambahkan definisi umum sebelum kode Anda, tetapi ini memiliki beberapa kelemahan:#version
, karena pernyataan berikut dalam spesifikasi GLSL:Karena pernyataan ini,
glShaderSource
tidak dapat digunakan untuk menambahkan teks sebelum#version
deklarasi. Ini berarti bahwa#version
baris tersebut perlu dimasukkan dalamglShaderSource
argumen Anda , yang berarti bahwa antarmuka kompiler GLSL Anda perlu entah bagaimana diberitahu versi GLSL apa yang diharapkan akan digunakan. Selain itu, tidak menentukan#version
akan membuat kompiler GLSL default untuk menggunakan GLSL versi 1.10. Jika Anda ingin membiarkan penulis shader menentukan#version
skrip dalam cara standar, maka Anda harus memasukkan#include
-s setelah#version
pernyataan tersebut. Ini bisa dilakukan dengan menguraikan GLSL shader secara eksplisit untuk menemukan#version
string (jika ada) dan membuat inklusi Anda setelahnya, tetapi memiliki akses ke#include
arahan mungkin lebih disukai untuk dikontrol dengan lebih mudah ketika inklusi tersebut perlu dibuat. Di sisi lain, karena GLSL mengabaikan komentar sebelum#version
baris, Anda dapat menambahkan metadata untuk memasukkan dalam komentar di bagian atas file Anda (yuck.)Pertanyaannya sekarang adalah: Apakah ada solusi standar untuk
#include
, atau Anda perlu menggulung ekstensi preprosesor Anda sendiri?Ada
GL_ARB_shading_language_include
ekstensi, tetapi memiliki beberapa kelemahan:"/buffers.glsl"
(seperti yang digunakan dalam#include "/buffers.glsl"
) sesuai dengan isi filebuffer.glsl
(yang telah Anda muat sebelumnya)."/"
, seperti jalur absolut gaya Linux. Notasi ini umumnya tidak dikenal oleh programmer C, dan berarti Anda tidak dapat menentukan jalur relatif.Desain umum adalah untuk mengimplementasikan
#include
mekanisme Anda sendiri , tetapi ini bisa rumit karena Anda juga perlu mem-parsing (dan mengevaluasi) instruksi preprosesor lain seperti#if
untuk menangani dengan benar kompilasi bersyarat (seperti pelindung kepala.)Jika Anda menerapkan sendiri
#include
, Anda juga memiliki beberapa kebebasan dalam cara Anda ingin menerapkannya:GL_ARB_shading_language_include
).Sebagai penyederhanaan, Anda dapat secara otomatis memasukkan pelindung tajuk untuk masing-masing termasuk dalam lapisan preprocessing Anda, sehingga lapisan prosesor Anda terlihat seperti:
(Penghargaan untuk Trent Reed karena menunjukkan saya teknik di atas)
Kesimpulannya , tidak ada solusi otomatis, standar, dan sederhana. Dalam solusi masa depan, Anda bisa menggunakan beberapa antarmuka OpenGL SPIR-V, dalam hal ini kompiler GLSL ke SPIR-V bisa berada di luar API GL. Memiliki kompiler di luar runtime OpenGL sangat menyederhanakan menerapkan hal-hal seperti
#include
karena ini adalah tempat yang lebih tepat untuk berinteraksi dengan sistem file. Saya percaya metode luas saat ini adalah dengan hanya mengimplementasikan preprocessor kustom yang bekerja dengan cara yang harus akrab dengan programmer C.sumber
Saya biasanya hanya menggunakan fakta bahwa glShaderSource (...) menerima array string sebagai inputnya.
Saya menggunakan file definisi shader berbasis json, yang menentukan bagaimana shader (atau program menjadi lebih benar) dikomposisikan, dan di sana saya menentukan preprocessor mendefinisikan yang saya butuhkan, seragam yang digunakan, file shader vertex / fragmen, dan semua file "ketergantungan" tambahan. Ini hanya kumpulan fungsi yang ditambahkan ke sumber sebelum sumber shader yang sebenarnya.
Sekadar menambahkan, AFAIK, Unreal Engine 4 menggunakan arahan #include yang diuraikan dan menambahkan semua file yang relevan, sebelum kompilasi, seperti yang Anda sarankan.
sumber
Saya tidak berpikir ada konvensi umum, tetapi jika saya menebak, saya akan mengatakan bahwa hampir semua orang menerapkan beberapa bentuk inklusi teks sederhana sebagai langkah preprocessing (
#include
ekstensi), karena sangat mudah dilakukan begitu. (Dalam JavaScript / WebGL, Anda dapat melakukannya dengan ekspresi reguler sederhana, misalnya). Kelebihannya adalah Anda dapat melakukan preprocessing dalam langkah offline untuk build "release", ketika kode shader tidak lagi perlu diubah.Bahkan, indikasi bahwa pendekatan ini adalah umum adalah kenyataan bahwa perpanjangan ARB diperkenalkan untuk itu:
GL_ARB_shading_language_include
. Saya tidak yakin apakah ini menjadi fitur inti sekarang atau tidak, tetapi ekstensi itu ditulis terhadap OpenGL 3.2.sumber
Beberapa orang telah menunjukkan bahwa
glShaderSource
dapat mengambil serangkaian string.Selain itu, dalam GLSL kompilasi (
glShaderSource
,glCompileShader
) dan menghubungkan (glAttachShader
,glLinkProgram
) shader terpisah.Saya telah menggunakan itu dalam beberapa proyek untuk membagi shader antara bagian tertentu, dan bagian yang umum bagi sebagian besar shader, yang kemudian dikompilasi dan dibagi dengan semua program shader. Ini berfungsi dan tidak sulit untuk diterapkan: Anda hanya perlu mempertahankan daftar ketergantungan.
Dalam hal rawatan, saya tidak yakin itu menang. Pengamatannya sama, mari kita coba membuat faktor. Meskipun memang menghindari pengulangan, overhead teknik terasa signifikan. Selain itu, shader terakhir lebih sulit untuk diekstrak: Anda tidak bisa hanya menggabungkan sumber shader, karena deklarasi berakhir dalam urutan yang akan ditolak oleh beberapa kompiler, atau akan diduplikasi. Sehingga membuatnya lebih sulit untuk melakukan tes shader cepat di alat yang terpisah.
Pada akhirnya teknik ini membahas beberapa masalah KERING, tetapi jauh dari ideal.
Pada topik sampingan, saya tidak yakin apakah pendekatan ini memiliki dampak dalam hal waktu kompilasi; Saya telah membaca bahwa beberapa driver hanya benar-benar mengkompilasi program shader di tautan, tetapi saya belum mengukur.
sumber