1

glslを使用してGPUでガウスフィルタリング、バイラテラルフィルタリングなどの画像処理アルゴリズムを実装しようとしています。

そして、どの部分が「実際に」並列実行されているのか混乱しています。たとえば、テクスチャとして 1280*720 のプレビューがあります。どの部分が実際に 1280*720 回実行され、どの部分が実行されていないかはよくわかりません。

glsl コードのディスパッチ メカニズムは何ですか?

私のガウスフィルタリングコードは次のようなものです:

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vTextureCoord;
uniform samplerExternalOES sTexture;
uniform sampler2D sTextureMask;

void main() {

float r=texture2D(sTexture, vTextureCoord).r;
float g=texture2D(sTexture, vTextureCoord).g;
float b=texture2D(sTexture, vTextureCoord).b;

// a test sample
float test=1.0*0.5;

float width=1280.0;
float height=720.0;

vec4 sum;   

//offsets of a 3*3 kernel
vec2 offset0=vec2(-1.0,-1.0); vec2 offset1=vec2(0.0,-1.0); vec2 offset2=vec2(1.0,-1.0);
vec2 offset3=vec2(-1.0,0.0); vec2 offset4=vec2(0.0,0.0); vec2 offset5=vec2(1.0,0.0);
vec2 offset6=vec2(-1.0,1.0); vec2 offset7=vec2(0.0,1.0); vec2 offset8=vec2(1.0,1.0); 

//gaussina kernel with sigma==100.0;
float kernelValue0 = 0.999900; float kernelValue1 = 0.999950; float kernelValue2 = 0.999900;
float kernelValue3 = 0.999950; float kernelValue4 =1.000000; float kernelValue5 = 0.999950;
float kernelValue6 = 0.999900; float kernelValue7 = 0.999950; float kernelValue8 = 0.999900;

vec4 cTemp0;vec4 cTemp1;vec4 cTemp2;vec4 cTemp3;vec4 cTemp4;vec4 cTemp5;vec4 cTemp6;vec4 cTemp7;vec4 cTemp8;



//getting 3*3 pixel values around current pixel
vec2 src_coor_2;
src_coor_2=vec2(vTextureCoord[0]+offset0.x/width,vTextureCoord[1]+offset0.y/height);
cTemp0=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset1.x/width,vTextureCoord[1]+offset1.y/height);
cTemp1=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset2.x/width,vTextureCoord[1]+offset2.y/height);
cTemp2=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset3.x/width,vTextureCoord[1]+offset3.y/height);
cTemp3=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset4.x/width,vTextureCoord[1]+offset4.y/height);
cTemp4=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset5.x/width,vTextureCoord[1]+offset5.y/height);
cTemp5=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset6.x/width,vTextureCoord[1]+offset6.y/height);
cTemp6=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset7.x/width,vTextureCoord[1]+offset7.y/height);
cTemp7=texture2D(sTexture, src_coor_2);
src_coor_2=vec2(vTextureCoord[0]+offset8.x/width,vTextureCoord[1]+offset8.y/height);
cTemp8=texture2D(sTexture, src_coor_2);

//convolution
sum =kernelValue0*cTemp0+kernelValue1*cTemp1+kernelValue2*cTemp2+
    kernelValue3*cTemp3+kernelValue4*cTemp4+kernelValue5*cTemp5+
    kernelValue6*cTemp6+kernelValue7*cTemp7+kernelValue8*cTemp8; 

float factor=kernelValue0+kernelValue1+kernelValue2+kernelValue3+kernelValue4+kernelValue5+kernelValue6+kernelValue7+kernelValue8;

gl_FragColor = sum/factor;
//gl_FragColor=texture2D(sTexture, vTextureCoord);

}

このコードは、携帯電話 (galaxy nexus) の純粋なプレビューに対して、より低い fps で実行されています。

しかし、コードの最後の部分を元のピクセル値で直接出力するように変更すると、

    //gl_FragColor = sum/factor;
gl_FragColor=texture2D(sTexture, vTextureCoord);

純粋なプレビューと同じ fps で高速に実行されます。

質問は: 私がテスト用に書いたもので、最初は役に立たないもの:

float test=1.0*0.5;

何回実行されますか?

他の部分のような:

sum =kernelValue0*cTemp0+kernelValue1*cTemp1+kernelValue2*cTemp2+
    kernelValue3*cTemp3+kernelValue4*cTemp4+kernelValue5*cTemp5+
    kernelValue6*cTemp6+kernelValue7*cTemp7+kernelValue8*cTemp8; 

変更しただけで1280 * 720回実行されない

gl_FragColor = sum/factor;

gl_FragColor=texture2D(sTexture, vTextureCoord);

1280*720 回実行するかどうかを決定するメカニズムはどのようになっていますか?これは、ピクセルを並行して実行する場合には役に立ちません。それは自動的に行われますか?

glslプログラムのアーキテクチャ、ディスパッチ、GPUへのデータの整理方法、その他のことは何ですか?

バイラテラル フィルタリングのようなより複雑な操作と、この 3*3 ガウス カーネルよりもピクセルあたり 9*9 および 9 倍のカーネル サイズで、どうすればよいか考えています。

4

2 に答える 2

2

フラグメント シェーダー コード全体が、フラグメントごとにまとめて実行されます。フラグメントは、出力ピクセルでアンチエイリアシングが行われない場合、またはフレームバッファのサンプルをマルチサンプル アンチエイリアシングで近似します。フラグメントが正確に何であるかは、OpenGL 仕様では詳細に指定されていませんが、フラグメント ステージの出力であり、フレーム バッファー ビットプレーンで値に変換されます。

ラスタライザは、ポイント、ライン セグメント、またはポリゴンの 2 次元記述を使用して、一連のフレームバッファ アドレスと値を生成します。このように生成された各フラグメントは、最終的にフレームバッファを変更する前に、個々のフラグメントに対して操作を実行する次のステージに送られます。これらの操作には、

[OpenGL-3.3 コア仕様、セクション 2.4]


変更しただけで1280 * 720回実行されない

gl_FragColor = sum/factor;

gl_FragColor=texture2D(sTexture, vTextureCoord);?

除算はコストがかかり複雑な操作です。カーネルの合計は定数であり、フラグメントごとに変化しないため、シェーダーでそれを評価しないでください。それを CPU で評価し1./factor、uniform (すべてのフラグメントに等しい定数) として供給し、sum除算よりもはるかに高速に乗算します。

ガウス カーネルは実際には 3×3 行列であり、GLSL には専用の型があります。実行する計算は内積(数学的に正しい用語はスカラーまたは内積) で書き直すことができ、GPU には専用の高速化された命令があります。

また、テクスチャのコンポーネントを個々のフロートに分割しないでください。

全体として、かなりの数のスピード バンプをコードに組み込みました。

于 2013-09-17T11:45:58.367 に答える
1

最新の (シェーダー モデル 3.0+) GPU では、フラグメント シェーダーは一度に 2x2 のピクセル ブロック (ピクセル クワッド) で動作するようにスケジュールされています。興味深いことに、これは Shader Model 3.0 で派生命令を実装するために必要であり、それ以来、GPU アーキテクチャ設計の一部として残っています。ピクセル クワッドは、フラグメント シェーダー スケジューリングで得られる最小レベルの粒度です。実際、discardフラグメント シェーダーを使用していた場合、ピクセル クワッド内のすべてのフラグメントもdiscard. discard.

これに加えて、ほとんどの GPU には複数のストリーム処理ユニットがあり、ピクセル クワッドをより大きなワークグループにスケジュールします (NV はワープと呼び、AMD はウェーブフロントと呼びます)。簡単に言えば、すべてが並行して行われます。これが GPU の全体的な前提です。GPU は単一のタスクを複数のスレッドにまたがって適用し、すべてが同じデータを並行して処理します。これが、CPU とは対照的にコアが増加したときに非常にうまくスケーリングする理由です。

簡単に言えば、GLSL シェーダーで個別の命令をディスパッチして個別の機能ユニットで実行するのではなく、実際にこれが行われます。GLSL シェーダーは複数の処理ユニットで同時に実行され (概念的には、フラグメントごとに 1 つのスレッド)、これらのスレッドはすべて、SIMT(単一命令複数スレッド) として知られるパラダイムで同じ命令シーケンスを実行します。

基本的なスケジューリング ユニット (ワープ/ウェーブフロント) に戻ると、シェーダーの 1 つのインスタンスがメモリのフェッチを停止すると、そのスケジューリング ユニットの残りのインスタンスも停止します。これは、それらがすべて同じ命令を同時に実行するためです。これが、依存するテクスチャの読み取りと大きなフィルター カーネルが悪いモジョである理由です。フラグメントの特定のグループに必要なテクスチャ メモリは、実行時まで不確定であったり、広がりすぎたりする可能性があるため、スケジューリング ユニット内でテクスチャ データを効率的にプリフェッチしてキャッシュすることは、不可能ではないにしても困難になる可能性があります。

並列処理のレベルを正確に記述する上での最大の問題は、GPU アーキテクチャが変化し続けることです (上記の議論のほとんどは Shader Model 3.0+ GPU に関連しています)。少し前まで、GPU はベクトル化された ISA を使用していましたが、現在では AMD と NV の両方がスーパースカラーに切り替えました。これは、命令スケジューリングの効率が実際に向上するためです。特殊な組み込み GPU をミックスに投入すると、本当に悪夢に襲われます。それらが実行するシェーダー モデルを実際に言うのは困難です (派生物は OpenGL ES 2.0 ではオプションであるため)。


私が書いた内容のより簡潔な説明については、スタック オーバーフローに関する この他の質問を参照してください。

いくつかのきれいな図については、これはやや時代遅れですが、nVIDIA からの有用なプレゼンテーションです。

于 2013-09-17T21:35:31.423 に答える