Ulasan singkat
Penyangga kedalaman hierarkis adalah penyangga kedalaman multi-level (penyangga-Z) yang digunakan sebagai struktur percepatan untuk kueri kedalaman. Seperti dalam kasus rantai mip tekstur, ukuran masing-masing tingkat biasanya merupakan hasil dari pembagian oleh dua ukuran buffer resolusi penuh. Pada artikel ini, saya akan berbicara tentang dua cara untuk menghasilkan buffer kedalaman hirarkis dari buffer resolusi penuh.Pertama, saya akan menunjukkan cara menghasilkan rantai mip lengkap untuk buffer kedalaman, yang menjaga keakuratan kueri kedalaman dalam ruang koordinat tekstur (atau NDC) bahkan untuk ukuran buffer kedalaman tidak sama dengan kekuatan dua. (Di Internet, saya menemukan contoh kode yang tidak menjamin keakuratan ini, yang mempersulit pelaksanaan kueri akurat pada level mip tinggi.)Kemudian, untuk kasus-kasus di mana hanya satu level downsampling diperlukan, saya akan mendemonstrasikan bagaimana menghasilkan level ini dengan satu panggilan ke shader komputasi yang menggunakan operasi atom dalam memori bersama dari workgroup. Untuk aplikasi saya, di mana hanya diperlukan resolusi 1/16 x 1/16 (mip level 4), metode dengan shader komputasi 2-3 kali lebih cepat daripada pendekatan biasa dengan downsampling rantai mip dalam beberapa lintasan.pengantar
Hierarchical Depths (juga disebut Hi-Z) adalah teknik yang sering digunakan dalam grafik 3D. Hal ini digunakan untuk mempercepat pemangkasan geometri tak terlihat (oklusi culling) (di CPU , serta di GPU ), perhitungan refleksi di ruang layar , kabut volumetrik, dan banyak lagi.Selain itu, GPU Hi-Z sering diimplementasikan sebagai bagian dari pipa rasterisasi. Operasi pencarian Hi-Z yang cepat dalam cache pada chip memungkinkan Anda untuk melompati ubin fragmen sepenuhnya jika tertutup sepenuhnya oleh primitif yang sebelumnya diberikan.Ide dasar Hi-Z adalah untuk mempercepat operasi kueri mendalam dengan membaca dari buffer resolusi lebih rendah. Ini lebih cepat daripada membaca kedalaman resolusi penuh dari buffer, karena dua alasan:- Satu texel (atau hanya beberapa texels) dari buffer resolusi yang lebih rendah dapat digunakan sebagai nilai perkiraan sejumlah pluralitas dari buffer resolusi tinggi.
- Penyangga resolusi yang lebih rendah bisa cukup kecil dan di-cache, yang sangat mempercepat pelaksanaan operasi pencarian (terutama dengan akses acak).
Isi level buffer Hi-Z sampel-rendah tergantung pada bagaimana mereka digunakan (apakah buffer kedalaman akan "terbalik" , jenis permintaan apa yang harus digunakan). Secara umum, sebuah texel di level penyangga Hi-Z menyimpan min
atau max
semua texel yang sesuai dengannya di level sebelumnya. Terkadang nilai-nilai min
dan disimpan pada saat yang sama max
. Nilai rata-rata sederhana (yang sering digunakan dalam level mip dari tekstur reguler) jarang digunakan karena jarang digunakan untuk jenis pertanyaan seperti itu.Buffer Hi-Z paling sering diminta segera di pintu keluar untuk menghindari pemrosesan lebih lanjut dan operasi pencarian yang lebih akurat dalam buffer resolusi penuh. Misalnya, jika kita menyimpan nilaimax
untuk buffer kedalaman non-terbalik (di mana semakin besar nilai kedalaman, semakin jauh objeknya), kita dapat dengan cepat menentukan dengan tepat apakah posisi tertentu di ruang layar ditutupi oleh buffer kedalaman (jika koordinatnya Z> adalah nilai (maks) yang disimpan dalam beberapa tingkat yang lebih tinggi (mis., resolusi lebih rendah) dari buffer Hi-Z).Harap dicatat bahwa saya menggunakan frasa "tepat": jika koordinat Z <= nilai yang diterima (maks), maka tidak diketahui apakah buffernya tumpang tindih. Dalam beberapa aplikasi, dalam kasus ketidakpastian, mungkin perlu mencari buffer untuk kedalaman resolusi penuh; dalam kasus lain, ini tidak diperlukan (misalnya, jika hanya perhitungan yang tidak perlu dipertaruhkan, dan bukan render yang benar).Aplikasi saya: rendering partikel dalam shader komputasi
Saya dihadapkan dengan kebutuhan untuk menggunakan Hi-Z ketika mengimplementasikan rendering partikel dalam shader komputasi di mesin aplikasi VR PARTICULATE saya . Karena teknik rendering ini tidak menggunakan rasterisasi dengan fungsi tetap, ia perlu menggunakan pemeriksaan kedalaman sendiri untuk setiap partikel dengan ukuran satu piksel. Dan karena partikel tidak diurutkan dengan cara apa pun, akses ke buffer kedalaman (dalam kasus terburuk) hampir acak.Operasi pencarian pada tekstur akses acak layar penuh adalah cara untuk kinerja yang buruk. Untuk mengurangi beban, pertama-tama saya mencari kedalaman di buffer kedalaman yang dikurangi dengan resolusi 1/16 x 1/16 dari aslinya. Buffer ini berisi nilai kedalaman.min
, yang memungkinkan shading rendering komputasi untuk sebagian besar partikel yang terlihat melewati tes kedalaman resolusi penuh. (Jika kedalaman partikel <kedalaman minimum disimpan dalam buffer resolusi lebih rendah, maka kita tahu bahwa itu benar-benar terlihat. Jika itu adalah = = min, maka kita perlu memeriksa buffer kedalaman resolusi penuh.)Berkat ini, tes kedalaman untuk partikel yang terlihat dalam kasus umum menjadi operasi berbiaya rendah. (Untuk partikel yang tumpang tindih oleh geometri, itu lebih mahal, tetapi itu cocok untuk kita karena tidak menyebabkan biaya rendering, oleh karena itu partikel masih memerlukan sedikit perhitungan.)Karena fakta bahwa pencarian pertama kali dilakukan dalam buffer kedalaman resolusi yang lebih rendah (seperti yang disebutkan di atas) , waktu rendering partikel berkurang maksimum 35%dibandingkan dengan kasus ketika pencarian hanya dilakukan dalam buffer resolusi penuh. Karena itu, untuk aplikasi saya, Hi-Z sangat bermanfaat.Sekarang kita akan melihat dua teknik untuk menghasilkan buffer kedalaman hirarkis.Teknik 1: menghasilkan rantai Mip lengkap
Dalam banyak aplikasi Hi-Z, penciptaan rantai mip buffer kedalaman lengkap diperlukan. Sebagai contoh, ketika melakukan oklusi pemusnahan menggunakan Hi-Z, volume pembatas diproyeksikan ke ruang layar dan ukuran yang diproyeksikan digunakan untuk memilih tingkat mip yang sesuai (sehingga sejumlah texel tetap terlibat dalam setiap uji tumpang tindih).Menghasilkan rantai mip dari buffer kedalaman resolusi penuh biasanya merupakan tugas sederhana - untuk setiap texel pada level N kami mengambil max
(atau min
, atau keduanya) 4 texel yang sesuai pada level N-1 yang dihasilkan sebelumnya. Kami melakukan operan berurutan (setiap kali mengurangi ukurannya setengah) sampai kami mendapatkan ukuran mip level 1x1 terakhir.Namun, dalam hal buffer kedalaman, ukuran yang tidak sesuai dengan kekuatan dua, semuanya lebih rumit. Karena Hi-Z untuk buffer dalam sering dibangun dari resolusi layar standar (yang jarang memiliki kekuatan dua), kita perlu menemukan solusi yang dapat diandalkan untuk masalah ini.Pertama-tama mari kita putuskan apa artinya nilai dari setiap penyangga kedalaman tingkat-mex texel. Dalam artikel ini, kita akan mengasumsikan bahwa rantai mip menyimpan nilai min
. Operasi pencarian kedalaman harus menggunakan pemfilteran tetangga terdekat, karena interpolasi nilai tidak min
berguna bagi kami dan akan merusak sifat hierarkis rantai kedalaman mip yang dibuat.Jadi, apa sebenarnya nilai dari masing-masing texel pada mip-level N yang diperoleh oleh kita? Ini harus menjadi nilai minimum (min
) dari semua texels dari buffer kedalaman layar penuh yang menempati ruang yang sama dalam ruang koordinat tekstur (dinormalisasi).Dengan kata lain, jika koordinat terpisah dari tekstur (dalam interval)) dipetakan (dengan memfilter tetangga terdekat) ke masing-masing texel buffer resolusi penuh, maka texel resolusi penuh ini harus dianggap sebagai kandidat untuk nilai yang min
dihitung untuk texel pada setiap tingkat mip berikutnya yang lebih tinggi yang dipetakan dengan koordinat tekstur yang sama.Jika korespondensi ini dijamin, maka kami akan yakin bahwa operasi pencarian pada level mip tinggi tidak akan pernah mengembalikan nilai kedalaman> nilai texel dalam tekstur yang sama sesuai dengan buffer resolusi penuh (level 0). Dalam kasus N yang terpisah, jaminan ini dipertahankan untuk semua level di bawahnya (<N).Untuk dimensi genap (dan dalam hal buffer resolusi penuh, yang merupakan kekuatan dua, dimensi genap di setiap level hingga yang terakhir, di mana dimensi menjadi sama dengan 1) akan mudah dilakukan. Dalam kasus satu dimensi, untuk texel dengan indeks pada level N kita perlu mengambil texels pada level N-1 dengan indeks dan dan temukan artinya min
. Yaitu. Kita dapat langsung membandingkan texel dalam rasio "2 banding 1" (dan karena itu ukuran koordinat tekstur), karena ukuran setiap level tepat dua kali lebih kecil dari yang sebelumnya.Contoh ukuran level genap: 6 texels pada level ini dikurangi menjadi 3 pada level yang lebih tinggi. Ukuran koordinat tekstur dari masing-masing tiga texel tingkat tinggi tepat ditumpangkan pada setiap dua texel tingkat rendah. (Titik adalah pusat dari texels, dan kotak adalah dimensi dari koordinat tekstur ketika menyaring tetangga terdekat.)Dalam kasus ukuran tingkat ganjil (dan buffer resolusi penuh yang bukan kekuatan dua akan memiliki setidaknya satu tingkat dengan ukuran ganjil) semuanya semakin sulit. Untuk level N-1 ukuran aneh ukuran level berikutnya (N) akan sama , yaitu .Ini berarti bahwa sekarang kita tidak memiliki pemetaan 2-ke-1 yang jelas dari texel level N-1 ke texels level N. Sekarang ukuran koordinat tekstur dari setiap texel pada level N ditumpangkan pada ukuran 3 texels pada level N-1.Contoh ukuran level ganjil: 7 texels dari level ini dikurangi menjadi 3 texels di level berikutnya. Dimensi koordinat tekstur tiga texel tingkat tinggi ditumpangkan pada ukuran tiga texels dari tingkat yang lebih rendah.Karenanya. Ini berarti bahwa satu texel pada level N-1 kadang-kadang mempengaruhi nilai yang min
dihitung untuk 2 texel pada level N. Hal ini diperlukan untuk mempertahankan perbandingan yang dijelaskan di atas.Deskripsi di atas telah disajikan hanya dalam satu dimensi untuk kesederhanaan. Dalam dua dimensi, jika kedua dimensi level N-1 genap, maka wilayah 2x2 texel di level N-1 dipetakan ke satu texel di level N. Jika salah satu dimensi ganjil, wilayah 2x3 atau 3x2 di level N-1 dipetakan ke satu texel pada level N. Jika kedua dimensi ganjil , maka texel βangularβ juga harus diperhitungkan, yaitu, wilayah 3x3 pada level N-1 dibandingkan dengan satu texel pada level N.Contoh kode
Kode shader GLSL yang ditunjukkan di bawah ini mengimplementasikan algoritma yang kami jelaskan. Itu harus dijalankan untuk setiap mip berikutnya, mulai dari level 1 (level 0 adalah level resolusi penuh).uniform sampler2D u_depthBuffer;
uniform int u_previousLevel;
uniform ivec2 u_previousLevelDimensions;
void main() {
ivec2 thisLevelTexelCoord = ivec2(gl_FragCoord);
ivec2 previousLevelBaseTexelCoord = 2 * thisLevelTexelCoord;
vec4 depthTexelValues;
depthTexelValues.x = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord,
u_previousLevel).r;
depthTexelValues.y = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(1, 0),
u_previousLevel).r;
depthTexelValues.z = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(1, 1),
u_previousLevel).r;
depthTexelValues.w = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(0, 1),
u_previousLevel).r;
float minDepth = min(min(depthTexelValues.x, depthTexelValues.y),
min(depthTexelValues.z, depthTexelValues.w));
bool shouldIncludeExtraColumnFromPreviousLevel = ((u_previousLevelDimensions.x & 1) != 0);
bool shouldIncludeExtraRowFromPreviousLevel = ((u_previousLevelDimensions.y & 1) != 0);
if (shouldIncludeExtraColumnFromPreviousLevel) {
vec2 extraColumnTexelValues;
extraColumnTexelValues.x = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(2, 0),
u_previousLevel).r;
extraColumnTexelValues.y = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(2, 1),
u_previousLevel).r;
if (shouldIncludeExtraRowFromPreviousLevel) {
float cornerTexelValue = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(2, 2),
u_previousLevel).r;
minDepth = min(minDepth, cornerTexelValue);
}
minDepth = min(minDepth, min(extraColumnTexelValues.x, extraColumnTexelValues.y));
}
if (shouldIncludeExtraRowFromPreviousLevel) {
vec2 extraRowTexelValues;
extraRowTexelValues.x = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(0, 2),
u_previousLevel).r;
extraRowTexelValues.y = texelFetch(u_depthBuffer,
previousLevelBaseTexelCoord + ivec2(1, 2),
u_previousLevel).r;
minDepth = min(minDepth, min(extraRowTexelValues.x, extraRowTexelValues.y));
}
gl_FragDepth = minDepth;
}
Kelemahan dalam kode ini
Pertama, dalam hal buffer kedalaman resolusi penuh yang satu dimensi lebih dari dua kali ukuran dimensi lain, indeks panggilan texelFetch
mungkin melampaui u_depthBuffer
. (Dalam kasus seperti itu, dimensi yang lebih kecil berubah menjadi 1 sebelum yang lain.) Saya ingin menggunakan dalam contoh ini texelFetch
(menggunakan koordinat bilangan bulat), sehingga apa yang terjadi sejelas mungkin, dan tidak secara pribadi menemukan penyangga kedalaman lebar / tinggi seperti itu. Jika Anda mengalami masalah seperti itu, Anda dapat membatasi ( clamp
) texelFetch
koordinat yang ditransmisikan atau menggunakan texture
koordinat tekstur yang dinormalisasi (dalam sampler, menetapkan batas di tepi). Ketika menghitung min
atau max
harus selalu mempertimbangkan satu texel beberapa kali untuk adanya kasus batas.Kedua, meskipun fakta bahwa empat panggilan pertama texelFetch
dapat diganti dengan satu textureGather
, ini mempersulit hal-hal (karena textureGather
tingkat mip tidak dapat diindikasikan); Selain itu, saya tidak melihat peningkatan kecepatan saat menggunakan textureGather
.Performa
Saya menggunakan shader fragmen di atas untuk menghasilkan dua rantai mip penuh untuk dua (satu untuk setiap mata) buffer kedalaman di mesin VR saya. Dalam tes tersebut, resolusi untuk setiap mata adalah 1648x1776, yang menyebabkan terciptanya 10 level mip tambahan yang dikurangi (yang berarti 10 lintasan). Butuh 0,25 ms pada NVIDIA GTX 980 dan 0,30 ms pada AMD R9 290 untuk menghasilkan rantai penuh untuk kedua mata.Mip- 4, 5 6, , . ( , , , .) Mip- 4 β , (103x111) 2.mip-
Tugas algoritma yang dijelaskan di atas adalah untuk menjaga keakuratan kueri kedalaman dalam ruang koordinat tekstur (atau NDC). Untuk kelengkapan (dan karena saya menolak jaminan ini dalam teknik di bawah 2), saya ingin menunjukkan metode lain yang saya temui (misalnya, dalam artikel ini ).Perhatikan bahwa, seperti yang sebelumnya, metode alternatif ini dirancang untuk bekerja dengan buffer resolusi penuh yang ukurannya bukan kekuatan dua (tetapi, tentu saja, mereka bekerja dengan ukuran yang sama dengan kekuatan dua).Dalam metode alternatif ini, ketika melakukan downsampling level dengan lebar ganjil (atau tinggi) alih-alih menambahkan untuk setiap output texel kolom tambahan (atau baris) dari texels dari level sebelumnya (lebih rendah), kami melakukan operasi ini hanya untuk texels output dengan indeks maksimum (texels "ekstrim" ) Satu-satunya hal yang berubah dalam fragmen shader yang disajikan di atas adalah menetapkan nilai shouldIncludeExtraColumnFromPreviousLevel
dan shouldIncludeExtraRowFromPreviousLevel
:
bool shouldIncludeExtraColumnFromPreviousLevel =
(previousMipLevelBaseTexelCoords.x == u_previousLevelDimensions.x - 3);
bool shouldIncludeExtraRowFromPreviousLevel =
(previousMipLevelBaseTexelCoords.y == u_previousLevelDimensions.y - 3);
Karena itu, texels ekstrem dengan indeks tertinggi menjadi sangat "tebal", karena setiap divisi dengan 2 dimensi ganjil mengarah pada fakta bahwa mereka menempati interval yang lebih besar secara proporsional dari ruang koordinat tekstur yang dinormalisasi.Kerugian dari pendekatan ini adalah bahwa menjadi lebih sulit untuk melakukan query kedalaman level mip tinggi. Alih-alih hanya menggunakan koordinat tekstur yang dinormalisasi, pertama-tama kita perlu menentukan resolusi penuh texel yang sesuai dengan koordinat ini, dan kemudian mentransfer koordinat texel ini ke koordinat tingkat mip yang sesuai, permintaan yang sedang dieksekusi.Cuplikan kode di bawah ini bermigrasi dari ruang NDCuntuk mengirim koordinat di tingkat mip higherMipLevel
:vec2 windowCoords = (0.5 * ndc.xy + vec2(0.5)) * textureSize(u_depthBuffer, 0);
ivec2 texelCoords = ivec2(round(windowCoords.xy - vec2(0.5)));
ivec2 higherMipLevelTexelCoords =
min(texelCoords / (1 << higherMipLevel),
textureSize(u_depthBuffer, higherMipLevel).xy - ivec2(1));
Teknik 2: Hasilkan Level Hi-Z Tunggal Menggunakan Compader Shader
Menghasilkan rantai mip lengkap cukup cepat, tetapi agak mengganggu saya bahwa aplikasi saya menghasilkan semua level ini dan hanya menggunakan salah satunya (level 4). Selain menghilangkan sedikit inefisiensi ini, saya juga ingin melihat seberapa banyak semuanya bisa dipercepat jika saya hanya menggunakan satu panggilan shader komputasi untuk menghasilkan level yang saya butuhkan. (Perlu dicatat bahwa aplikasi saya dapat berhenti pada level 4 saat menggunakan solusi dengan shader fragmen multi-pass, jadi pada akhir bagian ini saya menggunakannya sebagai dasar untuk membandingkan runtimes.)Di sebagian besar aplikasi Hi-Z, hanya satu tingkat kedalaman yang diperlukan, jadi saya menemukan situasi ini biasa. Saya menulis shader komputasi untuk persyaratan spesifik saya sendiri (menghasilkan level 4, yang memiliki resolusi 1/16 x 1/16 dari aslinya). Kode serupa dapat digunakan untuk menghasilkan level yang berbeda.Shader komputasi sangat cocok untuk tugas ini, karena dapat menggunakan memori kelompok kerja bersama untuk bertukar data antara utas. Setiap kelompok kerja bertanggung jawab untuk satu texel keluaran (dikurangi dengan downsampling buffer), dan utas kelompok kerja berbagi pekerjaan menghitung min
texel yang sesuai dengan resolusi penuh, membagikan hasilnya melalui memori bersama.Saya mencoba dua solusi utama berdasarkan komputasi shader. Di bagian pertama, setiap utas meminta atomicMin
satu variabel memori bersama.Perhatikan bahwa karena programmer tidak dapat (tanpa ekstensi untuk perangkat keras dari pabrikan tertentu) melakukan operasi atom pada nilai-nilai non-integer (dan kedalaman saya disimpan sebagai float
), beberapa trik diperlukan di sini. Karena nilai floating-point non-negatif dari standar IEEE 754 menjaga urutannya ketika bit mereka diproses sebagai nilai integer yang tidak ditandatangani, kita dapat menggunakan floatBitsToUint
untuk membawa (menggunakan reinterpretasi gips) nilai kedalaman float
ke uint
, dan kemudian memanggil atomicMin
(untuk kemudian mengeksekusi uintBitsToFloat
untuk nilai minimum selesai uint
) .Solusi paling jelas atomicMin
adalah membuat grup thread 16x16 di mana setiap thread menerima satu texel dan kemudian mengeksekusinya atomicMin
dengan nilai di memori bersama. Saya membandingkan pendekatan ini menggunakan blok aliran yang lebih kecil (8x8, 4x8, 4x4, 2x4, 2x2), di mana setiap aliran menerima wilayah texel dan menghitung minimum lokalnya sendiri, dan kemudian memanggil atomicMin
.Yang tercepat dari semua solusi teruji iniatomicMin
NVIDIA dan AMD ternyata memiliki solusi dengan blok aliran 4x4 (di mana setiap aliran itu sendiri menerima area texel 4x4). Saya tidak begitu mengerti mengapa opsi ini ternyata menjadi yang tercepat, tetapi mungkin ini mencerminkan kompromi antara persaingan operasi atom dan perhitungan dalam arus independen. Perlu juga dicatat bahwa ukuran workgroup 4x4 hanya menggunakan 16 utas per warp / wave (dan juga mungkin menggunakan 32 atau 64), yang menarik. Contoh di bawah ini menerapkan pendekatan ini.Sebagai alternatif untuk menggunakan, atomicMin
saya mencoba melakukan reduksi paralel menggunakan teknik yang digunakan dalam presentasi NVIDIA yang dikutip secara aktif ini. (Ide dasarnya adalah menggunakan array memori bersama dengan ukuran yang sama dengan jumlah utas di workgroup, sertalulus untuk perhitungan bersama berurutan dari min
minimum setiap aliran sampai minimum akhir dari seluruh kelompok kerja diperoleh.)Saya mencoba solusi ini dengan semua ukuran yang sama dari kelompok kerja seperti dalam solusi c atomicMin
. Bahkan dengan semua optimasi yang dijelaskan dalam presentasi NVIDIA, solusi reduksi paralel sedikit lebih lambat (dalam sepuluh GPU pada kedua GPU) daripada solusi yang atomicMin
saya gunakan. Selain itu, solusi dengan atomicMin
jauh lebih sederhana dalam hal kode.Contoh kode
Dengan metode ini, cara termudah adalah tidak mencoba mempertahankan konsistensi dalam ruang koordinat tekstur yang dinormalisasi antara texel buffer yang direduksi dan resolusi penuh. Anda cukup melakukan konversi dari koordinat texel resolusi penuh ke koordinat texel resolusi berkurang:ivec2 reducedResTexelCoords = texelCoords / ivec2(downscalingFactor);
Dalam kasus saya (menghasilkan setara dengan mip-level 4) downscalingFactor
adalah 16.Seperti disebutkan di atas, shader komputasi GLSL ini mengimplementasikan solusi dengan atomicMin
ukuran workgroup 4x4, di mana setiap thread menerima area texel 4x4 dari buffer resolusi penuh. Buffer kedalaman nilai yang dikurangi yang dihasilkan min
adalah 1/16 x 1/16 dari ukuran buffer resolusi penuh (dibulatkan ketika ukuran resolusi penuh tidak habis dibagi 16 oleh sepenuhnya).uniform sampler2D u_inputDepthBuffer;
uniform restrict writeonly image2DArray u_outputDownsampledMinDepthBufferImage;
uniform vec2 u_texelDimensions;
layout(local_size_x = 16/4, local_size_y = 16/4, local_size_z = 1) in;
shared uint s_workgroupMinDepthEncodedAsUint;
void main() {
if (gl_LocalInvocationIndex == 0) {
s_workgroupMinDepthEncodedAsUint = floatBitsToUint(1.0);
}
memoryBarrierShared();
barrier();
uvec2 baseTexelCoords = 4 * gl_GlobalInvocationID.xy;
vec2 gatherCoords1 = (baseTexelCoords + uvec2(1, 1)) * u_texelDimensions;
vec2 gatherCoords2 = (baseTexelCoords + uvec2(3, 1)) * u_texelDimensions;
vec2 gatherCoords3 = (baseTexelCoords + uvec2(1, 3)) * u_texelDimensions;
vec2 gatherCoords4 = (baseTexelCoords + uvec2(3, 3)) * u_texelDimensions;
vec4 gatheredTexelValues1 = textureGather(u_inputDepthBuffer, gatherCoords1);
vec4 gatheredTexelValues2 = textureGather(u_inputDepthBuffer, gatherCoords2);
vec4 gatheredTexelValues3 = textureGather(u_inputDepthBuffer, gatherCoords3);
vec4 gatheredTexelValues4 = textureGather(u_inputDepthBuffer, gatherCoords4);
vec4 gatheredTexelMins = min(min(gatheredTexelValues1, gatheredTexelValues2),
min(gatheredTexelValues3, gatheredTexelValues4));
float finalMin = min(min(gatheredTexelMins.x, gatheredTexelMins.y),
min(gatheredTexelMins.z, gatheredTexelMins.w));
atomicMin(s_workgroupMinDepthEncodedAsUint, floatBitsToUint(finalMin));
memoryBarrierShared();
barrier();
if (gl_LocalInvocationIndex == 0) {
float workgroupMinDepth = uintBitsToFloat(s_workgroupMinDepthEncodedAsUint);
imageStore(u_outputDownsampledMinDepthBufferImage,
ivec2(gl_WorkGroupID.xy),
vec4(workgroupMinDepth));
}
}
Performa
Saya menggunakan shader komputasi di atas untuk memproses buffer kedalaman resolusi penuh dengan dimensi yang sama yang digunakan untuk menghasilkan rantai mip penuh (buffer 1648x1776 untuk setiap mata). Ini berjalan dalam 0,12 ms pada NVIDIA GTX 980 dan 0,08 ms pada AMD R9 290. Jika kita bandingkan dengan waktu pembuatan hanya tingkat mip 1β4 (0,22 ms pada NVIDIA, 0,25 ms AMD), maka Solusi dengan komputasi shader ternyata menjadi 87% lebih cepat dengan GPU NVIDIA dan 197% lebih cepat dari GPU AMD .Secara absolut, akselerasi tidak begitu besar, tetapi setiap 0,1 ms penting, terutama di VR :)