GLSLシェーダーをデバッグするにはどうすればよいですか?


45

自明ではないシェーダーを書くとき(他の自明でないコードを書くときと同じように)、人々は間違いを犯します。[要出典]ただし、他のコードのようにデバッグすることはできません。結局、gdbまたはVisual Studioデバッガーを添付することはできません。コンソール出力の形式がないため、printfデバッグすらできません。私が通常していることは、見たいデータを色としてレンダリングすることですが、それは非常に初歩的で素人的な解決策です。私は人々がより良い解決策を思いついたと確信しています。

では、シェーダーを実際にデバッグするにはどうすればよいですか?シェーダーをステップスルーする方法はありますか?特定の頂点/プリミティブ/フラグメントでシェーダーの実行を見ることはできますか?

(この質問は、「通常の」コードをデバッグする方法に似たシェーダーコードのデバッグ方法に関するものであり、状態の変更などのデバッグに関するものではありません。)


gDEBuggerを検討しましたか?サイトの引用:「gDEBuggerは、高度なOpenGLおよびOpenCLデバッガー、プロファイラー、およびメモリアナライザーです。gDEBuggerは、他のツールではできないことを実行します。 」確かに、VSスタイルのデバッグ/コードのステップ実行はありませんが、シェーダーが何をする(または行う必要がある)かについての洞察が得られる場合があります。Crytecは、RenderDocと呼ばれるダイレクトシェーダー「デバッグ」用の同様のツールをリリースしました(無料ですが、HLSLシェーダー専用であるため、おそらくあなたには関係ありません)。
バート

@Bert Hmそう、gDEBuggerはOpenGLのWebGL-Inspectorに相当すると思いますか?後者を使用しました。これは非常に便利ですが、シェーダーの実行よりも間違いなくOpenGL呼び出しと状態の変更のデバッグに役立ちます。
マーティンエンダー

1
WebGLプログラミングを行ったことがないので、WebGL-Inspectorに精通していません。gDEBuggerを使用すると、少なくともテクスチャメモリ、頂点データなどを含むシェーダーパイプラインの状態全体を検査できます。それでも、実際にコードをステップスルーすることはありません。
バート

gDEBuggerは非常に古く、しばらくの間サポートされていません。あなたがフレームとGPUの状態分析から見ている場合、これは他の質問と強く関連しています:computergraphics.stackexchange.com/questions/23/…–
cifz

関連する質問に対して提案したデバッグ方法を次に示します。stackoverflow.com
wip

回答:


26

私が知っている限り、シェーダーでコードをステップスルーできるツールはありません(また、その場合、「デバッグ」したいピクセル/頂点のみを選択できるようにする必要があり、実行は可能性が高いですそれによって異なります)。

私が個人的に行っているのは、非常にハックの多い「カラフルなデバッグ」です。だから私#if DEBUG / #endifは基本的に言うガードで動的な枝の束を振りかける

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

.. rest of code .. 

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

したがって、この方法でデバッグ情報を「観察」できます。私は通常、さまざまな「色コード」を組み合わせたり組み合わせたりするなど、さまざまなトリックを実行して、より複雑なさまざまなイベントや非バイナリのものをテストします。

この「フレームワーク」では、よくあるケースのための一連の固定された規則があると便利です。そうすれば、常に戻って、どの色と関連付けたのかを確認する必要がなくなります。重要なことは、シェーダーコードのホットリロードを適切にサポートしているため、追跡データ/イベントをほぼインタラクティブに変更し、デバッグビジュアライゼーションのオン/オフを簡単に切り替えることができることです。

画面に簡単に表示できないものをデバッグする必要がある場合は、いつでも同じことを行い、1つのフレームアナライザツールを使用して結果を検査できます。この他の質問の答えとして、それらのいくつかをリストしました

言うまでもなく、ピクセルシェーダーまたはコンピューティングシェーダーを「デバッグ」していない場合、パイプライン全体にこの「debugColor」情報を挿入します( flat キーワードGLSLで)。

繰り返しますが、これは非常にハック的であり、適切なデバッグとはほど遠いですが、適切な代替手段を知らないことで立ち往生しています。


それらが利用可能になったら、SSBOを使用して、色でエンコードする必要のない、より柔軟な出力形式を取得できます。ただし、このアプローチの大きな欠点は、特にUBが関与している場合に、バグを隠したり変更したりできるコードを変更することです。+1それにもかかわらず、それは利用可能な最も直接的な方法だからです。
誰も

9

GLSL-Debuggerもあります。これは、「GLSL Devil」として知られるデバッガーです。

デバッガー自体は、GLSLコードだけでなく、OpenGL自体にも非常に便利です。描画呼び出し間をジャンプし、シェーダースイッチでブレークする機能があります。また、OpenGLによってアプリケーション自体に返されるエラーメッセージも表示されます。


2
2018-08-07の時点では、GLSL 1.2を超えるものはサポートしておらず、積極的に保守されていないことに注意してください。
ルスラン

そのコメントは合法的に私を悲しませた:(
rdelfin

このプロジェクトはオープンソースであり、プロジェクトの近代化を支援するのが大好きです。それがしたことをする他のツールはありません。
XenonofArcticus

7

AMDのCodeXLやNVIDIAのnSight / Linux GFXデバッガーなど、GPUベンダーによる製品がいくつかあります。これらの製品は、シェーダーをステップスルーできますが、各ベンダーのハードウェアに関連付けられています。

Linuxで利用できますが、Linuxでの使用はほとんど成功していません。Windowsの状況についてコメントすることはできません。

最近使用するようになったオプションは、シェーダーコードをモジュール化して#includes、含まれるコードをGLSLとC ++&glmの共通サブセットに制限することです。

問題にぶつかったとき、別のデバイスで問題を再現して、問題が同じかどうかを確認します(ドライバーの問題/未定義の動作の代わりに)。また、誤ったデータをGPUに渡す可能性があります(たとえば、バッファーが正しくバインドされていないなど)。通常、cifz answerのような出力デバッグによって、またはapitraceを介してデータを検査して除外します。

論理エラーの場合、同じデータでCPUに含まれるコードを呼び出して、CPUのGPUから状況を再構築しようとします。その後、CPUでステップ実行できます。

コードのモジュール性に基づいて、ユニットテストを記述し、GPU実行とCPU実行の結果を比較することもできます。ただし、C ++がGLSLとは異なる動作をする可能性があるため、これらの比較で誤検知が発生する可能性があることに注意する必要があります。

最後に、別のデバイスで問題を再現できない場合、違いがどこから来たのかを掘り始めることができます。ユニットテストはそれが起こる場所を絞り込むのに役立つかもしれませんが、最終的にはおそらくcifz answerのようにシェーダーから追加のデバッグ情報を書き出す必要があります

ここで概要を説明するのは、デバッグプロセスのフローチャートです。 本文に記載されている手順のフローチャート

これを締めくくるのは、ランダムな長所と短所のリストです。

プロ

  • 通常のデバッガーでステップスルーする
  • 追加の(多くの場合より良い)コンパイラー診断

詐欺


これは素晴らしいアイデアであり、おそらくシングルステップシェーダーコードに最も近い方法です。ソフトウェアレンダラー(Mesa?)を実行すると同様の利点があるのでしょうか?

@racarate:私もそれについて考えましたが、まだ試してみる時間がありませんでした。私はメサの専門家ではありませんが、シェーダーのデバッグ情報は何らかの形でデバッガーに到達する必要があるため、シェーダーのデバッグは難しいと思います。それから、メサの人たちはメサ自体をデバッグするためのインターフェースをすでに持っているかもしれません:)
誰も

5

実際にOpenGLシェーダーをステップスルーすることはできないようですが、コンパイル結果を取得することは可能です。
以下はAndroid Cardboard Sampleから抜粋したものです。

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

コードが適切にコンパイルされる場合、プログラムの状態を伝える別の方法を試す以外に選択肢はほとんどありません。たとえば、頂点の色を変更したり、別のテクスチャを使用したりすることで、コードの一部に到達したことを通知できます。これは厄介ですが、今のところ唯一の方法のようです。

編集:WebGLの場合、私はこのプロジェクトを見ていますが、私はそれを見つけたばかりです...それを保証することはできません。


3
ええ、私はコンパイラーのエラーが出ることを知っています。ランタイムデバッグの改善を望んでいました。過去にWebGLインスペクターも使用しましたが、状態の変化を表示するだけで、シェーダー呼び出しを調べることはできません。これは質問でもっと明確だったと思います。
マーティンエンダー

2

これは、StackOverflowでの同じ質問に対する私の答えのコピーアンドペーストです


この答えの最後にfloat、IEEE 754をエンコードして、色として完全な値を出力できるGLSLコードの例がありますbinary32。私は次のようにそれを使用します(このスニペットはyymodelview行列のコンポーネントを提供します):

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

これを画面で取得した後、任意のカラーピッカーを使用して、色をHTMLとしてフォーマットします(高精度が必要ない場合00rgb値に追加し、必要であれば2番目のパスを実行して下位バイトを取得します)。floatIEEE 754 の16進表現を取得しますbinary32

の実際の実装は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

私のために働いた解決策は、シェーダーコードをC ++にコンパイルすることです-Nobodyが言及したように。少しセットアップが必要ですが、複雑なコードで作業する場合、非常に効率的でした。

私は主にHLSL Compute Shadersで作業しており、ここで利用可能な概念実証ライブラリを開発しました。

https://github.com/cezbloch/shaderator

DirectX SDKサンプルのCompute Shaderで、HLSLデバッグのようなC ++を有効にする方法、および単体テストのセットアップ方法を示します。

GLSL計算シェーダーのC ++へのコンパイルは、HLSLより簡単に見えます。主にHLSLの構文構成が原因です。GLSLレイトレーサーの計算シェーダーで実行可能な単体テストの簡単な例を追加しました。これは、上記のリンクの下にあるShaderatorプロジェクトのソースにもあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.