ユーザ用ツール

サイト用ツール


opengl:optimize

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


最適化

GPU にしろ、描画 API にしろ、構造を理解することが最適化の近道

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

原因を調べるためには、GPU メーカーが提供している Profiler などの Tool を用いる方法があります。 これらのツールは使い方や機能がそれぞれ異なっています。

または Fence や TimeStamp などの GPU 同期コマンドを利用して、自分で測定する方法もあります。 (OpenGL の同期と描画速度の測定)

最適化によりどこまで速くなる余地があるのか、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 へのオフロード

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

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

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

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki