ユーザ用ツール

サイト用ツール


opengl:optimize

文書の過去の版を表示しています。


最適化

  • 構造を理解することが最適化の近道
  • アプリケーションの高速化ならボトルネックがどこにあるか調べる必要あり

大きく分けて、CPU ボトルネック、GPU ボトルネックの 2種類あります。 もしパフォーマンスが上がらないなら、まずは原因がどこにあるのか調査を行います。

最適化によりどこまで速くなる余地があるのか、GPU/CPU のおおよそのピーク性能を知っておくと手を入れる場所の目安になります。

ボトルネックの例

  • CPU ボトルネック
    • 同期待ち
    • Driver 負荷
    • アプリケーション自体の負荷
    • メモリ転送量超過
  • GPU ボトルネック
    • Shader 負荷
      • Vertex 負荷
      • Pixel (Fragment) 負荷
        • Texture Fetch
    • フィルレート制限
    • メモリ帯域制限

CPU 負荷

Lock

リソースの Lock で CPU と GPU が競合すると、CPU は GPU の動作が完了するまで待つことになります。

CPU と GPU は非同期に動作しているので、 GPU は裏でまだ 1frame 前に発行されたコマンドのレンダリングを行っている可能性があります。

もしリソースの Lock 待ちが発生しているなら、できるだけお互いの動作を邪魔しないよう対策が必要です。 ダブルバッファ化を行う、読み出しが不要なら Write Only (Discard) 設定にする、Fence で実行完了を先に確認しておくなど。

Driver 負荷

各種 API を呼び出すと、ドライバーはその情報をメモリに蓄積します。

目的のひとつは、描画時に必要なステートを参照するためで、 もう一つは Query API により現在設定されているステートをアプリケーションが読み出す可能性があるためです。

記録された情報を実際に参照するのは glDrawElements (DrawIndexed) や glDrawArrays (Draw) などの Draw API です。 Draw API は現在設定されている描画に必要なステートを集めて、 GPU に送信するためのコマンドを構築します。

この描画コマンドは GPU に送るための Command Buffer (Command Queue または Push Buffer) に登録されます。

登録された Command Buffer は任意のタイミングで Kick され、 実際に GPU での描画が始まります。

つまり通常の API 呼び出しは、メモリ内にデータを記録するだけで GPU には何も送りません。 実際に GPU へ送る情報コマンドを生成するのは Draw API に集中していることになります。 (例外としてリソース転送 API もあります。)

描画が行われるまでに、ステートは何度も変更される可能性があるためです。 実際に描画に必要なのは、描画の直前に設定されているステートだけです。

また GPU コマンドの量を減らすために、前回 Command Buffer に登録したステートを保存しておいて差分だけ 再設定するような最適化も考えられます。

API が記録したステートは GPU に依存しない汎用のものです。 その後各 Command を、GPU 毎の Native な命令に変換する作業も発生します。

DirectX API が登場し、GPU が当たり前になって何世代もバージョンが上がってからも、Draw API の CPU 負荷の高さは問題となってきました。 というのも、CPU でレンダリングしていた時代はもちろん、またはコンシューマゲーム機などのゲーム専用ハードでは、 Draw API ボトルネックがほとんど存在していなかったからです。 GPU の種類が固定のゲーム専用機では、さまざまな GPU を想定する必要がありません。 ステート記録時に GPU に適した形に変換してしまうことが可能で、 描画時のステート切り替えも PC と比べると非常に低コストで実現できます。

最適化のためには、Draw Call 回数をできるだけ減らすことが重要となります。 一度の Draw Call で出来るだけ多くの描画を行えば効率が上がることがわかっているので、 画命令をまとめられるようにステート切り替えを減らす必要があります。 Geometry Instancing を使う、Texture Atlas を使う、などの手法が用いられるようになりました。

また Driver 負荷を下げると同時に、GPU の Pipeline フラッシュを避ける意味でも効果があります。

このような Driver 負荷に対して、API 自体を改良する動きが出てきました。 AMD の Mantle がその先陣を切っており、MS の DirectX12 が追従しています。

この両者とも、大きく削減されるのは CPU 負荷の方であり、GPU 自体に何らかの新機能が増えるわけではありません。 現在発売している GPU がすでに DirectX12 対応を謳っていることからも明らかです。

この点で、今までのメジャーアップデートとは目的が大きく異なっています。

API の刷新により、CPU 負荷を減らすことが最大の目的です。

アプリケーション負荷

  • Thread による CPU core への分散
  • SIMD による最適化
  • GPU へのオフロード

CPU の特性上、ARM Cortex-A8 では SIMD 最適化が劇的な効果をあげていましたが、 それ以外では Multi core への Thread 化の方が効果を得やすいでしょう。

Mobile Device では Core 数や GPU 種類を一定に見積もりできないため、 ゲーム専用機と比べるとぎりぎりまでの最適化はできず、妥協は必要。 むしろ負荷よりも互換性の方が問題になりがちです。

GPU 負荷

GPU の構造によって特性がかなり異なってきます。 そのためどの手法を用いた方が良いのか、必ずしも最適な方法があるとは限りません。

Alpha Test (discard) の是非

新しい GPU では、Alpha Test (discard) はパフォーマンスの低下を招く要因であると言われています。 一般的には Eary Z culling を阻害するためで、 Face の Pixel に穴が開くとプリミティブ単位の代表 Z 値を使うことができなくなります。

特に PowerVR などの Deferred Rendering (TBDR) と相性が悪く、ハードウエアの利点をスポイルしてしまうことになります。 PowerVR は先に Depth Buffer を生成して隠面除去を行ってから、表面の必要なピクセルに対してのみ Pixel Shader (Fragment Shader) を走らせます。 ちょうど Desktop GPU の Deferred Lighting を、ハードウエアが自動的に行ってくれるわけです。

もし Shader に discard 命令が含まれており、途中で pixel を捨てる可能性があるならば Pixel Shader を実行してみないと Depth Buffer が正しいものにならないことがわかります。

ところが古い GPU アーキテクチャでは、discard (texkill) によって早いタイミングで Pixel Shader を終了させることにより、 無駄な Texture Fetch や演算を省くことが可能です。 Tegra 2/3/4 の ULP GeForce は G70 世代の古い GPU core なので、使用した方がシェーダーの実行効率を上げることができると言われています。 例えば半透明描画時に Alpha 値が 0 ならば、カラーを出力しないで discard (texkill) してピクセルを捨てることができます。

ただし他の GPU との互換性や今後のことを考えると、使わない方が良いと思われます。

opengl/optimize.1397710844.txt.gz · 最終更新: 2014/04/17 14:00 by oga

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki