フラグメントシェーダーでこれが非常に遅いのはなぜですか?


19

私はいくつかのFPS測定コードをWebGLでセットアップしました(このSO回答に基づいて)、フラグメントシェーダーのパフォーマンスに奇妙な点を発見しました。このコードは、1024x1024のキャンバス上に単一のクワッド(または2つの三角形)をレンダリングするだけなので、すべての魔法はフラグメントシェーダーで発生します。

このシンプルなシェーダー(GLSL。頂点シェーダーは単なるパススルーです)を検討してください。

// some definitions

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

    // Nothing to see here...

    gl_FragColor = vec4(value, value, value, 1.0);
}

したがって、これは白いキャンバスをレンダリングするだけです。私のマシンでは平均で約30 fpsです。

それでは、数値演算を増やし、数オクターブの位置依存ノイズに基づいて各フラグメントを計算しましょう。

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

      float noise;
      for ( int j=0; j<10; ++j)
      {
        noise = 0.0;
        for ( int i=4; i>0; i-- )
        {
            float oct = pow(2.0,float(i));
            noise += snoise(vec2(mod(seed,13.0)+x*oct,mod(seed*seed,11.0)+y*oct))/oct*4.0;
        }
      }

      value = noise/2.0+0.5;

    gl_FragColor = vec4(value, value, value, 1.0);
}

上記のコードを実行する場合は、この実装をsnoise使用しています。

これにより、fpsが7のようになります。これは理にかなっています。

奇妙な部分...ノイズ計算を次の条件でラップすることにより、16個のフラグメントごとに1つだけをノイズとして計算し、他のフラグメントを白のままにしてみましょう。

if (int(mod(x*512.0,4.0)) == 0 && int(mod(y*512.0,4.0)) == 0)) {
    // same noise computation
}

これははるかに高速であると予想されますが、それでもまだわずか7 fpsです。

もう1つのテストでは、代わりに、次の条件でピクセルをフィルターします。

if (x > 0.5 && y > 0.5) {
    // same noise computation
}

これにより、以前とまったく同じ数のノイズピクセルが得られますが、現在ではほぼ30 fpsに戻っています。

ここで何が起こっていますか?16番目のピクセルを除外する2つの方法で、まったく同じサイクル数が得られないのですか?そして、なぜすべてのピクセルをノイズとしてレンダリングするほど遅いのはなぜですか?

ボーナス質問:これについて私は何ができますか?私が実際に行う場合、恐ろしいパフォーマンスを回避する方法はありますかいくつかの高価なフラグメントだけでキャンバスに斑点をたいか?

(確かに、16ピクセルごとに白ではなく黒をレンダリングすることにより、実際のモジュロ計算がフレームレートにまったく影響しないことを確認しました。)

回答:


22

ピクセルは小さな正方形にグループ化され(大きさはハードウェアに依存します)、単一のSIMDパイプラインで一緒に計算されます。(SIMDの配列タイプの構造)

このパイプライン(ベンダーに応じていくつかの異なる名前があります:ワープ、ウェーブフロント)は、ロックステップで各ピクセル/フラグメントに対して操作を実行します。つまり、1つのピクセルで計算が必要な場合、すべてのピクセルが計算を行い、結果を必要としないピクセルは破棄します。

すべてのフラグメントがシェーダーを介して同じパスをたどる場合、他のブランチは実行されません。

これは、16番目のピクセルごとに計算する最初の方法が最悪の場合の分岐であることを意味します。

それでも画像のサイズを小さくしたい場合は、小さなテクスチャにレンダリングしてから、拡大します。


5
これを行うには、より小さなテクスチャとアップサンプリングにレンダリングするのが良い方法です。しかし、何らかの理由で大きなテクスチャの16ピクセルごとに書き込む必要がある場合は、16ピクセルごとに1回呼び出すコンピューティングシェーダーとイメージのロード/ストアを使用して、書き込みターゲットに書き込みを分散させることをお勧めします。
ネイサンリード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.