web-dev-qa-db-ja.com

値のリストをフラグメントシェーダーに渡す

値のリストをフラグメントシェーダーに送信したい。これは、単精度浮動小数点数のリスト(場合によっては数千項目の長さ)です。フラグメントシェーダーはこのリストへのランダムアクセスを必要とし、各フレームでCPUから値を更新します。

私はこれをどのように行うことができるかについて私のオプションを検討しています:

  1. 配列型の均一変数として( "uniform float x [10];")。しかし、ここには制限があるようです。数百以上の値を送信する私のGPUでは非常に遅く、またランタイムでそれを変更したい場合はシェーダーで上限をハードコーディングする必要があります。

  2. リストの高さと幅が1のテクスチャとして、glCopyTexSubImage2Dを使用してデータを更新します。

  3. 他の方法?私は最近GL仕様のすべての変更に追いついていませんが、おそらくこの目的のために特別に設計された他の方法がありますか?

67
Ville Krumlinde

現在、これを行うには4つの方法があります。標準の1Dテクスチャ、バッファテクスチャ、ユニフォームバッファ、シェーダストレージバッファです。

1Dテクスチャ

このメソッドでは、glTex(Sub)Image1Dを使用して1Dテクスチャをデータで塗りつぶします。データは単なるfloatの配列であるため、 image formatGL_R32Fである必要があります。次に、シェーダーで単純なtexelFetch呼び出しを使用してアクセスします。 texelFetchはTexel座標を取得し(名前の由来)、すべてのフィルタリングを停止します。したがって、テクセルは1つだけです。

注:texelFetchは3.0以上です。以前のGLバージョンを使用する場合は、サイズをシェーダーに渡し、テクスチャ座標を手動で正規化する必要があります。

ここでの主な利点は、互換性とコンパクトさです。これはGL 2.1ハードウェア(表記法を使用)で動作します。また、GL_R32F形式を使用するhaveしません。 GL_R16F half-floatsを使用することも、データが正規化されたバイトに対して妥当な場合はGL_R8を使用することもできます。

主な欠点は、サイズの制限です。最大テクスチャサイズの1Dテクスチャを持つことに制限されています。 GL 3.x-classハードウェアでは、これは約8,192になりますが、4,096以上になることが保証されています。

均一バッファオブジェクト

これが機能する方法は、シェーダーで均一ブロックを宣言することです。

layout(std140) uniform MyBlock
{
  float myDataArray[size];
};

次に、配列のようにシェーダーでそのデータにアクセスします。

C/C++/etcコードに戻って、バッファオブジェクトを作成し、浮動小数点データで埋めます。次に、そのバッファオブジェクトをMyBlockユニフォームブロックに関連付けることができます。 詳細はこちらをご覧ください

この手法の主な利点は、速度とセマンティクスです。速度は、実装がテクスチャと比較して均一なバッファを処理する方法に起因します。テクスチャフェッチは、グローバルメモリアクセスです。通常、均一なバッファアクセスはそうではありません。通常、均一バッファデータは、シェーダがレンダリングで使用されるときに初期化されるときにシェーダにロードされます。そこからは、ローカルアクセスであり、はるかに高速です。

意味的には、これは単なるフラット配列ではないため、より優れています。特定のニーズのために、必要なのがfloat[]だけである場合、それは重要ではありません。しかし、より複雑なデータ構造がある場合は、セマンティクスが重要になる可能性があります。たとえば、ライトの配列を考えます。ライトには位置と色があります。テクスチャを使用する場合、特定のライトの位置と色を取得するコードは次のようになります。

vec4 position = texelFetch(myDataArray, 2*index);
vec4 color = texelFetch(myDataArray, 2*index + 1);

均一バッファを使用すると、他の均一アクセスと同じように見えます。 positionおよびcolorと呼ばれる名前のメンバーがあります。したがって、すべての意味情報がそこにあります。何が起こっているかを理解するのが簡単です。

これにもサイズ制限があります。 OpenGLでは、実装が均一ブロックの最大サイズに対して少なくとも16,384バイトを提供する必要があります。つまり、float配列の場合、取得できる要素は4,096だけです。繰り返しますが、これは実装に必要なminimumです。一部のハードウェアは、はるかに大きなバッファを提供できます。 AMDは、たとえば、DX10クラスのハードウェアで65,536を提供しています。

バッファテクスチャ

これらは一種の「スーパー1Dテクスチャ」です。これらにより、実質的に テクスチャユニットからバッファオブジェクトにアクセス が可能になります。これらは1次元ですが、1Dテクスチャではありません。

これらはGL 3.0以上からのみ使用できます。また、texelFetch関数を介してのみアクセスできます。

ここでの主な利点はサイズです。バッファテクスチャは、一般的に非常に巨大になる可能性があります。仕様は一般的に控えめで、バッファテクスチャに少なくとも65,536バイトを義務付けていますが、ほとんどのGL実装では、 メガサイズがバイト。実際、通常、最大サイズはハードウェアの制限ではなく、利用可能なGPUメモリによって制限されます。

また、バッファテクスチャは、1Dテクスチャのような不透明なテクスチャオブジェクトではなく、バッファオブジェクトに保存されます。これは、いくつかの バッファオブジェクトストリーミング技術 を使用して更新できることを意味します。

ここでの主な欠点は、1Dテクスチャと同様にパフォーマンスです。バッファテクスチャはおそらく1Dテクスチャより遅くなることはありませんが、UBOほど高速になることはありません。あなたがそれらから1つのフロートを引っ張っているだけなら、それは心配するべきではありません。ただし、大量のデータを取得する場合は、代わりにUBOの使用を検討してください。

シェーダーストレージバッファーオブジェクト

OpenGL 4.3は、これを処理する別の方法を提供します: shader storage buffers 。それらは均一なバッファによく似ています。均一ブロックの構文とほぼ同じ構文を使用して指定します。主な違いは、それらに書き込むことができることです。明らかにそれはあなたのニーズには役立ちませんが、他の違いがあります。

シェーダーストレージバッファーは、概念的には、バッファーテクスチャの代替形式です。したがって、シェーダーストレージバッファーのサイズ制限は、均一バッファーの場合よりもlot大きくなります。最大UBOサイズのOpenGL最小値は16KBです。最大SSBOサイズのOpenGL最小は16MBです。したがって、ハードウェアがあれば、UBOの代替として興味深いものです。

必ずreadonlyとして宣言してください。あなたはそれらに書いていないからです。

ここでの潜在的な不利な点は、UBOと比較した場合のパフォーマンスです。 SSBOは、バッファテクスチャを介して イメージのロード/ストア操作 のように機能します。基本的に、それはimageBufferイメージタイプを囲む(非常に素晴らしい)構文糖です。そのため、これらからの読み取りは、おそらくreadonly imageBufferからの読み取りの速度で実行されます。

バッファイメージを介したイメージの読み込み/保存による読み取りが、バッファテクスチャよりも速いか遅いかは、現時点では不明です。

もう1つの潜在的な問題は、 非同期メモリアクセス のルールに従う必要があることです。これらは複雑で、非常に簡単に人をつまずかせる可能性があります。

127
Nicol Bolas

これは、 texture buffer objects のニースユースケースのように聞こえます。これらは通常のテクスチャとはあまり関係がなく、基本的にシェーダーのバッファーオブジェクトのメモリに単純な線形配列としてアクセスできます。これらは1Dテクスチャに似ていますが、フィルター処理されておらず、整数インデックスによってのみアクセスされます。これは、値のリストを呼び出すときに行う必要があるように聞こえます。また、1Dテクスチャよりもはるかに大きなサイズもサポートしています。それを更新するには、標準のバッファオブジェクトメソッド(glBufferDataglMapBuffer、...)を使用できます。

しかし、一方では、GL3/DX10ハードウェアを使用する必要があり、OpenGL 3.1でコアになったとさえ考えています。ハードウェア/ドライバーがそれをサポートしていない場合、2番目の解決策が選択方法になりますが、幅x 1 2Dテクスチャよりも1Dテクスチャを使用します。この場合、非フラット2Dテクスチャとインデックスマジックを使用して、最大テクスチャサイズより大きいリストをサポートすることもできます。

しかし、テクスチャバッファは問題に最適です。より正確な洞察については、対応する 拡張仕様 を調べることもできます。

EDIT:ユニフォームバッファオブジェクト に関するニコルのコメントに応答して、 こちら 2つの小さな比較。私はまだTBOを使用する傾向がありますが、概念的にはより適切だと思うからといって、その理由を実際に推論することはできません。しかし、ニコルはこの問題に対する洞察をさらに提供することができます。

7
Christian Rau

1つの方法は、あなたが言及したような均一な配列を使用することです。別の方法は、1D「テクスチャ」を使用することです。 GL_TEXTURE_1DおよびglTexImage1Dを探します。あなたが言ったようにシェーダーコードで配列のサイズをハードコードする必要がないので、私は個人的にこの方法を好みます、そして、openglはGPUで1Dデータをアップロード/アクセスするための組み込み関数をすでに持っています。

6
edvaldig

おそらく1番ではないでしょう。シェーダーのユニフォーム用のレジスタの数は限られていますが、これはカードによって異なります。 GL_MAX_FRAGMENT_UNIFORM_COMPONENTSを照会して、制限を確認できます。新しいカードでは、数千に達します。 Quadro FX 5500には2048があるようです。 (http://www.nvnews.net/vbulletin/showthread.php?t=85925)。実行するハードウェアと、シェーダーに送信する他のユニフォームによって異なります。

番号2は、要件に応じて機能させることができます。ここでのあいまいさについては申し訳ありませんが、できれば他の誰かがより正確な答えを与えることができますが、古いシェーダーモデルカードで行うテクスチャ呼び出しの数を明示する必要があります。また、フラグメントごとに実行するテクスチャ読み取りの数にも依存します。シェーダーモデルとパフォーマンス要件に応じて、フラグメントごとに数千の要素を読み取ろうとは思わないでしょう。テクスチャのRGBAに値をパックして、テクスチャ呼び出しごとに4回の読み取りを行うことができますが、要件としてランダムアクセスでは、これは役に立たない可能性があります。

番号3についてはわかりませんが、UAV(順不同のアクセスビュー)を参照することをお勧めしますが、これはDirectXのみであり、適切なopenGLに相当するものはありません。 openGLにはnVidia拡張機能があると思いますが、それでもかなり厳しい最小スペックに制限されます。

数千のデータ項目をフラグメントシェーダーに渡すことが問題の最良の解決策になるとは考えられません。おそらく、達成しようとしていることの詳細を提供した場合、別の提案が得られるでしょうか。

2
Hybrid