7

私はOpenGL32Dエンジンをプログラミングしています。現在、ボトルネックを解決しようとしています。したがって、AMDプロファイラーの次の出力をお願いします:http: //h7.abload.de/img/profilerausa.png

データは数千のスプライトを使用して作成されました。

ただし、50.000スプライトでは、testappは5fpsではすでに使用できません。

これは、私のボトルネックが私が使用する変換関数であることを示しています。これは対応する関数です: http ://code.google.com/p/nightlight2d/source/browse/NightLightDLL/NLBoundingBox.cpp#130

void NLBoundingBox::applyTransform(NLVertexData* vertices) 
{
    if ( needsTransform() )
    {
            // Apply Matrix
            for ( int i=0; i<6; i++ )
            {
                glm::vec4 transformed = m_rotation * m_translation * glm::vec4(vertices[i].x, vertices[i].y, 0, 1.0f);
                vertices[i].x = transformed.x;
                vertices[i].y = transformed.y;
            }
            m_translation = glm::mat4(1);
            m_rotation    = glm::mat4(1);
            m_needsTransform = false;
    }
}

すべてのスプライトを一度にバッチ処理しているため、シェーダーではそれを実行できません。つまり、変換を計算するためにCPUを使用する必要があります。

私の質問は:このボトルネックを解決するための最良の方法は何ですか?

私はスレッドATMを使用していません。そのため、vsyncを使用すると、画面が終了するのを待つため、パフォーマンスがさらに低下します。それは私がスレッドを使用する必要があることを教えてくれます。

もう1つの方法は、OpenCLを使用することです。CUDAは、私が知る限り、NVIDIAカードでのみ実行されるため、避けたいと思います。そうですか?

ポストスクリプト:

必要に応じて、ここからデモをダウンロードできます。

http://www63.zippyshare.com/v/45025690/file.html

これはプロファイラーを実行するためのデバッグバージョンであるため、VC++2008がインストールされている必要があることに注意してください。

4

4 に答える 4

4

私が最初にすることは、forループに入る前に、回転を連結して行列を1つの行列に変換することです。そうすれば、すべてのforループで2つの行列の乗算とベクトルを計算しません。代わりに、単一のベクトルと行列のみを乗算します。次に、ループを展開してから、より高い最適化レベルでコンパイルすることを検討することをお勧めします(g++少なくとも-O2、私はMSVCに精通していないため、その最適化レベルを自分で変換する必要があります)。これにより、特にキャッシュフラッシュで、コード内の分岐で発生する可能性のあるオーバーヘッドを回避できます。最後に、まだ調べていない場合は、ベクトルを扱っているので、SSEの最適化を行うことを確認してください。

更新:スレッド化を含む最後のアイデアを1つ追加します...基本的に、スレッド化を行うときに頂点をパイプライン化します。たとえば、8つの使用可能なCPUスレッド(つまり、ハイパースレッディングを備えたクアッドコア)を備えたマシンがあるとします。頂点パイプライン処理用に6つのスレッドをセットアップし、非ロックの単一コンシューマー/プロデューサーキューを使用して、パイプラインのステージ間でメッセージを渡します。各ステージは、6メンバーの頂点配列の1つのメンバーを変換します。これらの6メンバーの頂点配列がたくさんあると思います。パイプラインを通過するストリームにセットアップすると、ストリームを非常に効率的に処理でき、ミューテックスやその他のロックセマフォなどの使用を回避できます。高速でロックされていない単一のプロデューサー/コンシューマーキューの詳細については、こちらの回答を参照してください

更新2:デュアルコアプロセッサしかありません...各スレッドがCPUリソースを奪い合うため、ボトルネックが発生するため、パイプラインのアイデアをダンプしてください。

于 2011-08-02T19:12:15.107 に答える
3

すべてのスプライトを一度にバッチ処理しているため、シェーダーではそれを実行できません。つまり、変換を計算するためにCPUを使用する必要があります。

これは、バッチ処理が実行できる最も重要なことであるという前提の下で、あなたが行った時期尚早の最適化のように疑わしいように聞こえます。したがって、描画呼び出しの数が最も少ないようにレンダラーを構成しました。そして今、それはあなたを噛むために戻ってきています。

あなたがする必要があるのは、より少ないバッチを持っていることではありません。適切な数のバッチが必要です。CPU変換を優先して、GPU頂点変換を放棄した場合、バッチ処理が行き過ぎていることをご存知でしょう。

Datenwolfが提案したように、GPUで変換を元に戻すには、インスタンス化を行う必要があります。しかし、それでも、ここで得た過剰なバッチ処理の一部を元に戻す必要があります。レンダリングしているシーンの種類(スプライトが上にあるタイルマップ、大きなパーティクルシステムなど)についてはあまり話していないので、何を提案すればよいかわかりません。

また、GLMは優れた数学ライブラリですが、最大のパフォーマンスを発揮するようには設計されていません。通常、フレームごとにCPU上の300,000の頂点を変換する必要がある場合は、これを使用することはありません。

于 2011-08-02T22:17:06.493 に答える
1

ループ内の割り当てが問題になる可能性がありますが、私はライブラリに精通していません。これをforループの外に移動し、フィールドの割り当てを手動で行うと役立つ場合があります。変換をループの外に移動することも役立ちます。

編集:

これは私が考えていたものに沿ったものです。

// Apply Matrix
glm::vec4 transformed;
glm::mat4 translation = m_rotation * m_translation;
for ( int i=0; i<6; i++ )
{
    transformed.x = vertices[i].x;
    transformed.y = vertices[i].y;
    transformed.z = vertices[i].z;
    transformed.w = 1.f; // ?
    /* I can't find docs, but assume they have an in-place multiply
    transformed.mult(translation);
    // */
    vertices[i].x = transformed.x;
    vertices[i].y = transformed.y;
}

たぶん、たぶん、割り当てはコンパイラが何かをインライン化または展開するのを妨げています。しかし、乗算はこれを命令キャッシュから追い出すのに十分な大きさだと思います。実際、キャッシュのサイズについて話し始めると、多くのプラットフォームで回復力を発揮できなくなります。

いくつかのスタックを複製して、より多くのより小さなループを作成することを試みることができます。

glm::vec4 transformed[6];
for (size_t i = 0; i < 6; i++) {
    transformed[i].x = vertices[i].x;
    transformed[i].y = vertices[i].y;
    transformed[i].z = vertices[i].z;
    transformed.w = 1.f; // ?
}
glm::mat4 translation = m_rotation * m_translation;
for (size_t i = 0; i < 6; i++) {
    /* I can't find docs, but assume they have an in-place multiply
    transformed.mult(translation);
    // */
}
for (size_t i = 0; i < 6; i++) {
    vertices[i].x = transformed[i].x;
    vertices[i].y = transformed[i].y;
}

ジェイソンが述べたように、これらのループを手動で展開することは興味深いかもしれません。

ただし、これらの変更のいずれにおいても、桁違いの改善が見られるとは思いません。

この関数を高速化するよりも、この関数の呼び出しを少なくする方が重要だと思います。この関数内にこのneedsTransformチェックがあるという事実は、これがおそらく関連していると私に思わせます。

低レベルのコードにこのような高レベルの懸念がある場合、このメソッドを無料だと思って何度も盲目的に呼び出すことになります。必要な頻度がtrueであるという仮定が真であるかどうかは、非常に間違っている可能性があります。

現実には、このメソッドを1回だけ呼び出す必要があります。applyTransformを使用する場合は、applyTransformを適用する必要があります。applyTransformが必要な場合は、applyTransformを呼び出さないでください。インターフェイスはコントラクトである必要があり、そのように扱います。

于 2011-08-02T19:28:29.800 に答える
1

CPUで計算を行うことを主張する場合は、自分で計算を行う必要があります。

現在、2D環境で4x4行列を使用しています。この場合、回転用の2x2行列が1つと、平行移動用の単純なベクトルで十分です。これは、回転の場合は4回の乗算と4回の加算、変換の場合は2回の加算です。

どうしても2つの行列が必要な場合(平行移動と回転を組み合わせる必要があるため)、現在の行列よりもはるかに少なくなります。ただし、ベクトルの位置を移動し、回転させてから再び戻すことで、これら2つを「手動で」組み合わせることができます。これは、乗算よりも少し速いかもしれませんが、それについてはよくわかりません。

それらの4x4行列が現在実行している操作と比較すると、それははるかに少ないです。

于 2011-08-03T00:18:03.330 に答える