GLSL gölgelendiricileri nasıl hata ayıklayabilirim?


45

Önemsiz gölgelendiriciler yazarken (önemsiz olmayan başka bir kod yazarken olduğu gibi) insanlar hata yapar. [kaynak belirtilmeli] Ancak, diğer kodlar gibi hata ayıklayamıyorum - sonuçta gdb veya Visual Studio hata ayıklayıcısını ekleyemezsiniz. Konsol çıktısı yok çünkü printf hata ayıklamayı bile yapamazsınız. Genelde yaptığım şey, renkli olarak bakmak istediğim verileri oluşturmak, ancak bu çok basit ve amatör bir çözüm. İnsanların daha iyi çözümler bulduklarına eminim.

Peki bir gölgelendiriciyi nasıl hata ayıklayabilirim? Gölgelendiriciden geçmenin bir yolu var mı? Gölgelendiricinin belirli bir köşe / ilkel / parça üzerinde uygulanmasına bakabilir miyim?

(Bu soru, durum değişimleri gibi şeyleri hata ayıklamak değil, “normal” kodda nasıl hata ayıklayacağına benzer şekilde, gölgelendirici kodunun nasıl hata ayıklanacağıyla ilgilidir.


GDEBugger'a baktınız mı? Site'den alıntı: "gDEBugger gelişmiş bir OpenGL ve OpenCL Debugger, Profiler ve Bellek Analizörüdür. GDEBugger, başka hiçbir aracın yapamayacağını yapar - OpenGL ve OpenCL API'lerinin üstünde uygulama etkinliğini izlemenizi ve sistem uygulamasında neler olduğunu görmenizi sağlar. " Verilmiş, hiçbir VS stili hata ayıklama / kod içinde adım atma, ancak gölgelendiricinizin ne yaptığı (veya ne yapması gerektiği) konusunda size bazı bilgiler verebilir. Crytec, Direkt gölgelendirici "hata ayıklama" için RenderDoc (ücretsiz, ancak kesinlikle HLSL gölgelendiriciler için, bu nedenle sizin için alakalı değil) için benzer bir araç yayımladı.
Bert,

@Bert Hm evet, sanırım gDEBugger, OpenGL'nin WebGL-Inspector'a eşdeğer olduğunu düşünüyorum? İkincisini kullandım. Son derece yararlıdır, ancak kesinlikle OpenGL çağrılarını ve durum değişikliklerini gölgelendirici çalıştırmasından daha fazla hata ayıklamaktır.
Martin Ender

1
Hiç bir WebGL programlaması yapmadım ve bu nedenle WebGL-Inspector'a aşina değilim. GDEBugger ile en azından doku hafızası, köşe verisi, vb. Dahil olmak üzere gölgelendirici boru hattınızın bütün durumunu kontrol edebilirsiniz. Yine de, afaik kodu boyunca gerçek bir adım atmama.
Bert,

gDEBugger çok eski ve bir süredir desteklenmiyor. Çerçeve ve GPU durum analizine bakıyorsanız, bu başka bir soruyla güçlü bir şekilde ilgilidir: computergraphics.stackexchange.com/questions/23/…
cifz

: Burada ilgili soruya önerilen bir hata ayıklama yöntemi ben var stackoverflow.com/a/29816231/758666
çalışması devam ediyor

Yanıtlar:


26

Bildiğim kadarıyla, bir gölgelendiricideki kod içinde adım adım ilerlemenizi sağlayan hiçbir araç yok (ayrıca, bu durumda "hata ayıklamak" istediğiniz sadece bir piksel / tepe noktası seçmeniz gerekebilir, yürütme büyük olasılıkla buna bağlı olarak değişir).

Şahsen yaptığım şey, çok "hararetli hata ayıklama". Bu yüzden #if DEBUG / #endiftemelde söyleyen muhafızlara sahip bir sürü dinamik dal serpin

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

.. rest of code .. 

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

Böylece, bu yolla hata ayıklama bilgisini "gözlemleyebilirsiniz". Genellikle daha karmaşık olayları ya da ikili olmayan olayları test etmek için çeşitli "renk kodları" arasında kayma ya da karıştırma gibi çeşitli hileler yapıyorum.

Bu "çerçeve" de, genel durumlar için bir takım sabit sözleşmelere sahip olmamın yararlı olacağını düşünüyorum. Önemli olan, gölgelendirici kodunun etkin şekilde yeniden yüklenmesi için iyi bir desteğe sahip olmaktır; böylece izlenen verileri / olayı neredeyse etkileşimli olarak değiştirebilir ve hata ayıklama görselleştirmesini kolayca açıp kapatabilirsiniz.

Ekranda kolayca görüntüleyemeyeceğiniz bir şeyde hata ayıklamanız gerekirse, sonuçlarınızı incelemek için her zaman aynısını yapabilir ve bir kare analiz aracı kullanabilirsiniz. Bir kaçını da bu diğer sorunun cevabı olarak listeledim .

Obv, bir piksel gölgelendiriciyi veya hesaplama gölgelendiricisini "hata ayıklamıyor" değilsem, bu "debugColor" bilgisini boru hattı boyunca araya sokmadan ( flat anahtar kelime ile GLSL'de) ilettiğimi söylemeye gerek yok.

Yine, bu çok kaba ve uygun hata ayıklama olmaktan uzak, ancak uygun bir alternatif bilmeme ile karşı karşıya kaldığım şey.


Mevcut olduklarında, renkleri kodlamanız gerekmeyen daha esnek bir çıktı biçimi elde etmek için SSBO'ları kullanabilirsiniz. Bununla birlikte, bu yaklaşımın en büyük dezavantajı, özellikle UB söz konusu olduğunda hataları gizleyebilen / değiştirebilen kodu değiştirmesidir. +1 Bununla birlikte, mevcut olan en doğrudan yöntemdir.
Hiç kimse

9

Orada da GLSL-Debugger . "GLSL Şeytan" olarak bilinen bir hata ayıklayıcıdır.

Hata ayıklayıcının kendisi yalnızca GLSL kodu için değil, OpenGL'nin kendisi için de son derece kullanışlıdır. Çizim aramaları arasında atlama ve Gölgelendirici anahtarlarında kesme yeteneğine sahipsiniz. Ayrıca, OpenGL tarafından uygulamanın kendisine geri iletilen hata mesajlarını da gösterir.


2
2018-08-07 itibariyle, GLSL 1.2'den daha yüksek olan hiçbir şeyi desteklemediğini ve aktif olarak korunmadığını unutmayın.
Ruslan

Bu yorum meşru beni üzdü :(
rdelfin

Proje açık kaynak kodlu ve modernizasyonu gerçekten çok seviyorum. Yaptığı şeyi yapan başka bir araç yok.
XenonofArcticus

7

GPU satıcıları tarafından AMD'nin CodeXL veya NVIDIA'nın nSight / Linux GFX Hata Ayıklayıcısı gibi , gölgelendiriciler arasında adım atmaya izin veren ancak ilgili satıcının donanımına bağlı birkaç teklif vardır .

Linux altında olmasına rağmen, orada kullanmakta her zaman çok az başarılı olduğumu not edeyim. Windows altındaki durum hakkında yorum yapamam.

Yakın zamanda kullanmaya #includesbaşladığım seçenek, gölgelendirici kodumu modülerleştirmek ve dahil edilen kodu GLSL ve C ++ & glm'nin ortak bir alt kümesiyle sınırlamak .

Bir soruna çarptığımda, sorunun aynı mantık hatasını (sürücü problemi / tanımsız davranış yerine) gösterenle aynı olup olmadığını görmek için başka bir cihazda çoğaltmaya çalışıyorum. Ayrıca, genellikle cifz cevabında olduğu gibi çıktı hata ayıklamasıyla veya apitrace yoluyla verileri inceleyerek ekarte ettiğim GPU'ya (örneğin, yanlış bağlanmış tamponlar vb.) Yanlış veri aktarma şansı da vardır .

Mantıksal bir hata olduğunda, CPU üzerindeki GPU kodunu aynı verilerle çağırarak durumu CPU üzerindeki GPU'dan yeniden yapmaya çalışıyorum. O zaman işlemciden CPU'ya geçebilirim.

Kodun modülerliğine dayanarak, bunun için en küçük olanı yazmaya çalışabilir ve sonuçları bir GPU çalışması ile bir CPU çalışması arasında karşılaştırabilirsiniz. Bununla birlikte, C ++ 'nın GLSL'den farklı davranabileceği köşe durumları olduğunun farkında olmalısınız, bu nedenle bu karşılaştırmalarda size yanlış pozitifler verebilir.

Son olarak, sorunu başka bir cihazda çoğaltamazsanız, yalnızca farkın nereden geldiğini bulmaya başlayabilirsiniz. Unittest'ler bunun nerede olduğunu daraltmanıza yardımcı olabilir, ancak sonunda cifz cevabında olduğu gibi gölgelendiriciden ek hata ayıklama bilgileri yazmanız gerekecektir .

Ve size burada genel bir bakış vermek için, hata ayıklama işlemimin bir akış şeması: Metinde açıklanan prosedürün akış şeması

Bunu buraya getirmek, rastgele artıların ve eksilerin bir listesi:

profesyonel

  • normal hata ayıklayıcı ile adım
  • ek (genellikle daha iyi) derleyici tanılama

aleyhte


Bu harika bir fikir ve muhtemelen tek adımlı gölgelendirici koduna en yakın olan şey. Acaba bir yazılım geliştiriciden (Mesa?) Geçmenin de benzer faydaları olur mu?

@racarate: Bunu da düşündüm ama henüz denemek için vaktim olmadı. Mesa konusunda uzman değilim ama gölgelendiricinin hata ayıklayıcı bilgisinin bir şekilde hata ayıklayıcıya ulaşması gerektiğinden, gölgelendiriciyi hata ayıklamanın zor olabileceğini düşünüyorum. Sonra tekrar, belki de mesa'daki millet zaten mesa'nın hatalarını ayıklamak için bir arayüze sahip :) :)
Nobody

5

Bir OpenGL gölgelendiricisinden gerçekten geçmek mümkün görünmese de, derleme sonuçlarını elde etmek mümkündür.
Aşağıdaki Android Karton Örneği'nden alınmıştır .

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

Kodunuz doğru bir şekilde derlenirse, programın durumunu size bildirmenin farklı bir yolunu denemekten başka seçeneğiniz yoktur. Örneğin bir tepe noktasının rengini değiştirerek veya farklı bir doku kullanarak kodun bir bölümüne ulaşıldığının sinyalini verebilirsiniz. Garip olan, ancak şimdilik tek yol gibi görünüyor.

EDIT: WebGL için bu projeye bakıyorum , ancak daha yeni buldum ... bunun için kefil olamaz.


3
Evet, derleyici hataları alabileceğimin farkındayım. Daha iyi çalışma zamanı hata ayıklama için umuyordum. Geçmişte WebGL müfettişini de kullandım, ancak yalnızca durum değişikliklerini gösterdiğine inanıyorum, ancak gölgelendirici çağrısına bakamıyorsunuz. Sanırım bu soruda daha net olabilirdi.
Martin Ender

2

Bu, StackOverflow'taki aynı soruya verdiğim cevabın bir kopyasıdır .


Bu cevabın en altında, floatIEEE 754'ü kodlayan , tam değerin renkli çıktısını veren GLSL kodunun bir örneği bulunmaktadır binary32. Aşağıdaki gibi kullanıyorum (bu pasaj yy, modelview matrisinin bileşenini veriyor ):

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

Bunu ekrana getirdikten sonra, sadece herhangi bir renk seçiciyi alabilir, rengi HTML olarak biçimlendirebilirsiniz ( daha yüksek hassasiyete ihtiyacınız yoksa değere ekleyerek 00ve rgbeğer daha düşük byte almak için ikinci bir geçiş yaparak) ve floatIEEE 754'ün onaltılık gösterimini alıyorsunuz binary32.

İşte gerçek uygulama 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.;
}

1

Benim için çalışan çözüm, Hiç kimsenin dediği gibi, gölgelendirici kodunun C ++ 'a derlenmesidir. Biraz kurulum gerektirse de, karmaşık bir kod üzerinde çalışırken çok verimli oldu.

Çoğunlukla burada mevcut bir kavram kanıtı kütüphanesi geliştirdiğim HLSL Hesaplama Gölgelendiricileri ile çalışıyorum:

https://github.com/cezbloch/shaderator

DirectX SDK Örneklerinden Hesaplanan Bir Gölgelendiricide, C ++ benzeri HLSL hata ayıklamanın nasıl etkinleştirileceğini ve Ünite Testlerinin nasıl kurulacağını gösterir.

GLSL hesaplama gölgelendiricisinin C ++ 'a derlenmesi HLSL'den daha kolay görünüyor. Esas olarak HLSL'deki sözdizimi yapıları nedeniyle. Shaderator projesinin kaynaklarında yukarıdaki linkte bulabileceğiniz bir GLSL ışını izleyici Hesaplama Shader'ına sıradan bir çalıştırılabilir Ünite Testi örneği ekledim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.