Bagaimana saya bisa men-debug GLSL shaders?

45

Saat menulis shader non-sepele (seperti saat menulis kode non-sepele lainnya), orang membuat kesalahan. [rujukan?] Namun, saya tidak bisa hanya debug itu seperti kode lain - Anda tidak bisa hanya melampirkan gdb atau Visual Studio debugger setelah semua. Anda bahkan tidak dapat melakukan debugging printf, karena tidak ada bentuk output konsol. Apa yang biasanya saya lakukan adalah membuat data yang ingin saya lihat sebagai warna, tetapi itu adalah solusi yang sangat sederhana dan amatir. Saya yakin orang-orang telah menemukan solusi yang lebih baik.

Jadi bagaimana saya bisa benar-benar men-debug shader? Apakah ada cara untuk melangkah melalui shader? Bisakah saya melihat eksekusi shader pada vertex / primitive / fragmen tertentu?

(Pertanyaan ini secara khusus tentang cara men-debug kode shader seperti bagaimana seseorang akan men-debug kode "normal", bukan tentang debugging hal-hal seperti perubahan status.)

Martin Ender
sumber
Sudahkah Anda melihat ke gDEBugger? Mengutip situs: "gDEBugger adalah OpenGL dan OpenCL Debugger, Profiler dan Memory Analyzer canggih. GDEBugger melakukan apa yang tidak bisa dilakukan oleh alat lain - memungkinkan Anda melacak aktivitas aplikasi di atas OpenGL dan OpenCL APIs dan melihat apa yang terjadi dalam implementasi sistem. " Memang, tidak ada gaya debugging VS / melangkah melalui kode, tetapi mungkin memberi Anda beberapa wawasan tentang apa yang shader Anda lakukan (atau harus lakukan). Crytec merilis alat serupa untuk "debugging" shader Direct bernama RenderDoc (gratis, tetapi hanya untuk HLSL shaders, jadi mungkin tidak relevan untuk Anda).
Bert
@Bert Hm ya, saya kira gDEBugger adalah setara OpenGL dengan WebGL-Inspector? Saya telah menggunakan yang terakhir. Ini sangat berguna, tetapi jelas lebih banyak men-debug panggilan OpenGL dan perubahan status daripada eksekusi shader.
Martin Ender
1
Saya tidak pernah melakukan pemrograman WebGL dan karenanya saya tidak terbiasa dengan WebGL-Inspektur. Dengan gDEBugger Anda setidaknya dapat memeriksa seluruh keadaan pipa shader Anda termasuk memori tekstur, data vertex, dll. Namun, tidak ada langkah nyata melalui kode afaik.
Bert
gDEBugger sudah sangat tua dan tidak didukung sejak beberapa saat. Jika Anda melihat dari analisis frame dan GPU Anda, ini adalah pertanyaan lain yang sangat terkait: computergraphics.stackexchange.com/questions/23/…
cifz
Berikut ini adalah metode debug yang saya sarankan untuk pertanyaan terkait: stackoverflow.com/a/29816231/758666
hapus

Jawaban:

26

Sejauh yang saya tahu tidak ada alat yang memungkinkan Anda untuk melangkah melalui kode dalam shader (juga, dalam hal ini Anda harus dapat memilih hanya pixel / vertex yang ingin Anda "debug", pelaksanaannya cenderung bervariasi tergantung pada itu).

Apa yang saya pribadi lakukan adalah "debugging warna-warni" yang sangat rumit. Jadi saya menaburkan banyak cabang dinamis dengan #if DEBUG / #endifpenjaga yang pada dasarnya mengatakan

#if DEBUG
if( condition ) 
    outDebugColour = aColorSignal;
#endif

.. rest of code .. 

// Last line of the pixel shader
#if DEBUG
OutColor = outDebugColour;
#endif

Jadi Anda dapat "mengamati" informasi debug dengan cara ini. Saya biasanya melakukan berbagai trik seperti menyortir atau memadukan berbagai "kode warna" untuk menguji berbagai peristiwa yang lebih kompleks atau hal-hal non-biner.

Dalam "kerangka" ini saya juga menemukan berguna untuk memiliki satu set konvensi tetap untuk kasus-kasus umum sehingga jika saya tidak harus terus-menerus kembali dan memeriksa warna apa yang saya kaitkan dengan apa. Yang penting adalah memiliki dukungan yang baik untuk hot-reload kode shader, sehingga Anda hampir dapat secara interaktif mengubah data / acara yang dilacak dan mengaktifkan / menonaktifkan visualisasi debug dengan mudah.

Jika perlu men-debug sesuatu yang tidak dapat Anda tampilkan di layar dengan mudah, Anda selalu dapat melakukan hal yang sama dan menggunakan alat analisis satu bingkai untuk memeriksa hasil Anda. Saya telah mendaftarkan beberapa dari mereka sebagai jawaban dari pertanyaan lain ini.

Obv, tidak perlu dikatakan lagi bahwa jika saya tidak "men-debug" pixel shader atau compute shader, saya meneruskan info "debugColor" ini melalui pipa tanpa menginterpolasinya (dalam GLSL dengan flat kata kunci)

Sekali lagi, ini sangat macet dan jauh dari debugging yang tepat, tetapi saya terjebak dengan tidak mengetahui alternatif yang tepat.

cifz
sumber
Ketika tersedia, Anda dapat menggunakan SSBO untuk mendapatkan format output yang lebih fleksibel di mana Anda tidak perlu menyandikan dalam warna. Namun, kelemahan utama dari pendekatan ini adalah bahwa ia mengubah kode yang dapat menyembunyikan / mengubah bug, terutama ketika UB terlibat. +1 Namun, untuk itu adalah metode paling langsung yang tersedia.
Tidak seorang pun
9

Ada juga GLSL-Debugger . Ini adalah debugger yang dulu dikenal sebagai "GLSL Devil".

Debugger sendiri sangat berguna tidak hanya untuk kode GLSL, tetapi juga untuk OpenGL. Anda memiliki kemampuan untuk melompat di antara panggilan draw dan mematahkan sakelar Shader. Ini juga menunjukkan Anda pesan kesalahan yang dikomunikasikan oleh OpenGL kembali ke aplikasi itu sendiri.

Sepehr
sumber
2
Perhatikan bahwa pada 2018-08-07, tidak mendukung apa pun yang lebih tinggi dari GLSL 1.2, dan itu tidak dikelola secara aktif.
Ruslan
Komentar itu secara sah membuat saya sedih :(
rdelfin
Proyek ini bersifat open source dan akan sangat membantu memodernkannya. Tidak ada alat lain yang melakukan apa yang dilakukannya.
XenonofArcticus
7

Ada beberapa penawaran oleh vendor GPU seperti AMD CodeXL atau NVIDIA's nSight / Linux GFX Debugger yang memungkinkan melangkah melalui shader tetapi terikat pada perangkat keras masing-masing vendor.

Biarkan saya perhatikan bahwa, meskipun mereka tersedia di Linux, saya selalu memiliki sedikit keberhasilan dengan menggunakannya di sana. Saya tidak dapat mengomentari situasi di bawah Windows.

Opsi yang saya gunakan baru-baru ini, adalah memodulasi kode shader saya melalui #includesdan membatasi kode yang disertakan ke subset umum GLSL dan C ++ & glm .

Ketika saya menemukan masalah, saya mencoba mereproduksinya di perangkat lain untuk melihat apakah masalahnya sama yang mengisyaratkan kesalahan logika (alih-alih masalah driver / perilaku tidak terdefinisi). Ada juga kesempatan untuk mengirimkan data yang salah ke GPU (misalnya dengan buffer yang tidak benar, dll.) Yang biasanya saya hilangkan dengan output debugging seperti dalam jawaban cifz atau dengan memeriksa data melalui apitrace .

Ketika itu adalah kesalahan logika saya mencoba untuk membangun kembali situasi dari GPU pada CPU dengan memanggil kode yang disertakan pada CPU dengan data yang sama. Lalu saya bisa melangkah melalui CPU.

Membangun berdasarkan modularitas kode Anda juga dapat mencoba untuk menulis unittest untuk itu dan membandingkan hasil antara menjalankan GPU dan menjalankan CPU. Namun, Anda harus menyadari bahwa ada kasus sudut di mana C ++ mungkin berperilaku berbeda dari GLSL, sehingga memberi Anda positif palsu dalam perbandingan ini.

Akhirnya, ketika Anda tidak dapat mereproduksi masalah pada perangkat lain, Anda hanya dapat mulai menggali dari mana perbedaan itu berasal. Unittests mungkin membantu Anda mempersempit di mana hal itu terjadi, tetapi pada akhirnya Anda mungkin perlu menuliskan informasi debug tambahan dari shader seperti dalam jawaban cifz .

Dan untuk memberi Anda gambaran di sini adalah diagram alur proses debug saya: Bagan alur prosedur yang dijelaskan dalam teks

Untuk mengatasinya di sini adalah daftar pro dan kontra acak:

pro

  • selesaikan dengan debugger biasa
  • diagnostik kompiler tambahan (seringkali lebih baik)

menipu

Tak seorangpun
sumber
Ini adalah ide bagus, dan mungkin yang terdekat yang bisa Anda dapatkan dengan kode shader langkah-tunggal. Saya ingin tahu apakah menjalankan melalui perender perangkat lunak (Mesa?) Akan memiliki manfaat yang serupa?
@racarate: Saya juga memikirkannya tetapi belum sempat mencoba. Saya bukan ahli tentang mesa tapi saya pikir mungkin sulit untuk men-debug shader karena informasi debug shader harus mencapai debugger. Kemudian lagi, mungkin orang-orang di mesa sudah memiliki antarmuka untuk itu untuk men-debug mesa sendiri :)
Tidak ada yang
5

Walaupun sepertinya tidak mungkin untuk benar-benar melangkah melalui shader OpenGL, adalah mungkin untuk mendapatkan hasil kompilasi.
Berikut ini diambil dari Sampel Karton Android .

while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
    Log.e(TAG, label + ": glError " + error);
    throw new RuntimeException(label + ": glError " + error);

Jika kode Anda dikompilasi dengan benar, maka Anda tidak punya banyak pilihan selain mencoba cara lain untuk mengomunikasikan status program kepada Anda. Anda bisa memberi isyarat bahwa sebagian kode tercapai, misalnya, mengubah warna titik atau menggunakan tekstur yang berbeda. Yang aneh, tetapi tampaknya menjadi satu-satunya cara untuk saat ini.

EDIT: Untuk WebGL, saya melihat proyek ini , tetapi saya baru saja menemukannya ... tidak dapat menjaminnya.

SL Barth - Pasang kembali Monica
sumber
3
Hm ya, saya sadar bahwa saya bisa mendapatkan kesalahan kompilator. Saya berharap untuk debugging runtime yang lebih baik. Saya juga pernah menggunakan inspektur WebGL di masa lalu, tapi saya percaya itu hanya menunjukkan Anda perubahan negara, tetapi Anda tidak bisa melihat permintaan shader. Saya kira ini bisa lebih jelas dalam pertanyaan.
Martin Ender
2

Ini adalah copy-paste jawaban saya untuk pertanyaan yang sama di StackOverflow .


Di bagian bawah jawaban ini adalah contoh kode GLSL yang memungkinkan untuk menampilkan nilai penuh floatsebagai warna, pengkodean IEEE 754 binary32. Saya menggunakannya seperti berikut (cuplikan ini memberikan yykomponen matriks modelview):

vec4 xAsColor=toColor(gl_ModelViewMatrix[1][1]);
if(bool(1)) // put 0 here to get lowest byte instead of three highest
    gl_FrontColor=vec4(xAsColor.rgb,1);
else
    gl_FrontColor=vec4(xAsColor.a,0,0,1);

Setelah Anda mendapatkan ini di layar, Anda bisa mengambil pemetik warna apa saja, memformat warna sebagai HTML (menambahkan 00ke rgbnilai jika Anda tidak membutuhkan presisi yang lebih tinggi, dan melakukan pass kedua untuk mendapatkan byte yang lebih rendah jika Anda melakukannya), dan Anda mendapatkan representasi heksadesimal floatsebagai IEEE 754 binary32.

Inilah implementasi aktual dari toColor():

const int emax=127;
// Input: x>=0
// Output: base 2 exponent of x if (x!=0 && !isnan(x) && !isinf(x))
//         -emax if x==0
//         emax+1 otherwise
int floorLog2(float x)
{
    if(x==0.) return -emax;
    // NOTE: there exist values of x, for which floor(log2(x)) will give wrong
    // (off by one) result as compared to the one calculated with infinite precision.
    // Thus we do it in a brute-force way.
    for(int e=emax;e>=1-emax;--e)
        if(x>=exp2(float(e))) return e;
    // If we are here, x must be infinity or NaN
    return emax+1;
}

// Input: any x
// Output: IEEE 754 biased exponent with bias=emax
int biasedExp(float x) { return emax+floorLog2(abs(x)); }

// Input: any x such that (!isnan(x) && !isinf(x))
// Output: significand AKA mantissa of x if !isnan(x) && !isinf(x)
//         undefined otherwise
float significand(float x)
{
    // converting int to float so that exp2(genType) gets correctly-typed value
    float expo=float(floorLog2(abs(x)));
    return abs(x)/exp2(expo);
}

// Input: x\in[0,1)
//        N>=0
// Output: Nth byte as counted from the highest byte in the fraction
int part(float x,int N)
{
    // All comments about exactness here assume that underflow and overflow don't occur
    const float byteShift=256.;
    // Multiplication is exact since it's just an increase of exponent by 8
    for(int n=0;n<N;++n)
        x*=byteShift;

    // Cut higher bits away.
    // $q \in [0,1) \cap \mathbb Q'.$
    float q=fract(x);

    // Shift and cut lower bits away. Cutting lower bits prevents potentially unexpected
    // results of rounding by the GPU later in the pipeline when transforming to TrueColor
    // the resulting subpixel value.
    // $c \in [0,255] \cap \mathbb Z.$
    // Multiplication is exact since it's just and increase of exponent by 8
    float c=floor(byteShift*q);
    return int(c);
}

// Input: any x acceptable to significand()
// Output: significand of x split to (8,8,8)-bit data vector
ivec3 significandAsIVec3(float x)
{
    ivec3 result;
    float sig=significand(x)/2.; // shift all bits to fractional part
    result.x=part(sig,0);
    result.y=part(sig,1);
    result.z=part(sig,2);
    return result;
}

// Input: any x such that !isnan(x)
// Output: IEEE 754 defined binary32 number, packed as ivec4(byte3,byte2,byte1,byte0)
ivec4 packIEEE754binary32(float x)
{
    int e = biasedExp(x);
    // sign to bit 7
    int s = x<0. ? 128 : 0;

    ivec4 binary32;
    binary32.yzw=significandAsIVec3(x);
    // clear the implicit integer bit of significand
    if(binary32.y>=128) binary32.y-=128;
    // put lowest bit of exponent into its position, replacing just cleared integer bit
    binary32.y+=128*int(mod(float(e),2.));
    // prepare high bits of exponent for fitting into their positions
    e/=2;
    // pack highest byte
    binary32.x=e+s;

    return binary32;
}

vec4 toColor(float x)
{
    ivec4 binary32=packIEEE754binary32(x);
    // Transform color components to [0,1] range.
    // Division is inexact, but works reliably for all integers from 0 to 255 if
    // the transformation to TrueColor by GPU uses rounding to nearest or upwards.
    // The result will be multiplied by 255 back when transformed
    // to TrueColor subpixel value by OpenGL.
    return vec4(binary32)/255.;
}
Ruslan
sumber
1

Solusi yang bekerja untuk saya adalah kompilasi kode shader ke C ++ - seperti yang disebutkan oleh Nobody. Itu terbukti sangat efisien ketika bekerja pada kode yang kompleks meskipun memerlukan sedikit pengaturan.

Saya telah bekerja sebagian besar dengan HLSL Compute Shaders yang saya kembangkan perpustakaan proof-of-concept yang tersedia di sini:

https://github.com/cezbloch/shaderator

Ini menunjukkan pada Compute Shader dari DirectX SDK Samples, cara mengaktifkan C ++ seperti debugging HLSL dan cara mengatur Tes Unit.

Kompilasi penghitung komputasi GLSL ke C ++ terlihat lebih mudah daripada HLSL. Terutama karena konstruksi sintaks di HLSL. Saya telah menambahkan contoh sepele dari Uji Unit yang dapat dieksekusi pada pelacak ray GLSL Compute Shader yang juga dapat Anda temukan di sumber proyek Shaderator di bawah tautan di atas.

SpaceKees
sumber