コード探偵 ファイル05 「無口なGPU」

前回はCPUのボトルネックを割り出す方法を紹介しましたが、今回はGPUのボトルネック部分を割り出す方法を紹介します。

 

今日、取調室に呼び出されたのはGPUです。CPUと違ってGPUは決して自供することはありません。どんな質問を投げかけてもかたくなに黙秘権を行使します。ただし、いくつかの質問をすると微妙に表情が変わることが判りました。

グラフィクスプログラマーはこのGPUの微妙な表情の違いからGPUのボトルネック部分を探し出す必要があります。

 

GPUのボトルネックを見つけ出すにはGPUのパイプラインがどのようになっているのかを知る必要があります。実際には様々なグラフィクスカードがあり、それぞれに多少の違いはありますが、共通して以下の重要なステージがあります。

  1. 頂点フェッチ・ユニット: 頂点データをメモリから読み込む
  2. 頂点シェーダー: 読み込んだ頂点データを処理する
  3. ラスタライザ: 描画する三角形の各ピクセルを処理する
  4. ピクセルシェーダー: ピクセル毎の色を計算する
  5. テクスチャフェッチ・ユニット: テクスチャデータを読み込む
  6. 深度ステンシル・ユニット: 深度バッファからの読み込み、更新をする
  7. フレームバッファ: 最終カラーを格納し、アルファブレンディングをする

これらのいずれもがボトルネックになる可能性があり、このボトルネックを知ることは非常に役立ちます。例えば頂点シェーダーがボトルネックになっていると判っているのなら、テクスチャフェッチ数を減らすよりも頂点シェーダー内の命令数を減らすほうが効果的です。また、ピクセルシェーダーがボトルネックとなっている状態であれば、フレームレートを減らすことなくモデルのポリゴン数を増やすこともできると判断できます。

 

では、それぞれのパイプライン・ステージでどんな要素がパフォーマンスに影響を与えるのでしょうか?

  1. 頂点フェッチ
    • 頂点数
    • 各頂点のデータサイズ
    • 頂点データはキャッシュ効率が良い順番に並んでいるか?
  2. 頂点シェーダー
    • 頂点数
    • シェーダープログラムの長さ
    • 三角形の並びがキャッシュ効率の良い順番に並んでいるか?
  3. ラスタライザ
    • 描画ピクセル数
    • 頂点シェーダーからピクセルシェーダーへ渡すパラメーターの数
  4. ピクセルシェーダー
    • 描画ピクセル数
    • ピクセルシェーダーのプログラムの長さ
  5. テクスチャ・フェッチ
    • 描画ピクセル数
    • 各ピクセル毎に使われるテクスチャの数
    • メインメモリから読み込まれるデータ量
      • ミップマップ形式のテクスチャはキャッシュ効率が非常に高い
      • DXT形式のテクスチャは非圧縮テクスチャより小さい
    • フィルタリングの種類
      • Anisotropic(異方性フィルタリング)は重い
      • トライリニアはバイリニアフィルタリングより少し重いだけ
      • バイリニアとポイントサンプリングの速度は殆どの場合では一緒
  6. 深度/ステンシルバッファ
    • 描画ピクセル数
    • マルチサンプリングの有無
    • read/writeとreadオンリー
  7. フレームバッファ
    • 描画ピクセル数
    • マルチサンプリングの有無
    • フレームバッファの各ピクセルのサイズ(MRTを含む)
    • read/write(アルファブレンディング)とwriteオンリー(不透明)

GPUのボトルネックを見つけるには、これらの要素をCPUで処理される部分に大きな変化を起こすことなく変える必要があります(CPUパフォーマンスが変わればGPUにも影響を与えることになるので、正確な判断ができなくなる)。

 

まずは、思いっきり小さな解像度、例えば100x50くらいの解像度にしてゲームを実行してみます。小さな解像度にしてもCPUの処理、頂点フェッチ、頂点シェーダーのパフォーマンスには変化がありません。

もし、低解像度にしてもフレームレートが変わらない場合(GPUバウンドであることが前提)、頂点処理がボトルネックになっている証拠です。

頂点処理を軽くするには、少ないポリゴン数のモデルに変更するか、頂点シェーダーを簡略化します。もし、頂点フェッチが問題だと思う場合はカスタムモデルプロセッサーを作り、VertexChannelCollection.ConvertChannelContentを使ってPackedVectorフォーマットを使って頂点データを小さくします。例えばNormalized101010は法線データには向いていますし、HalfVector2はテクスチャ座標データの圧縮によく使われます。

もし、低解像度にしてフレームレートが上がった場合、ピクセル処理がボトルネックになっている証拠です。

ミップマップを使っている場合(使っていない場合はミップマップを使うようにするだけでパフォーマンスアップになります)、SamplerStates[n].MipMapLevelofDetailBiasに4や5を設定してみましょう。これでテクスチャがぼやけた感じになります。フレームレートがあがっていればテクスチャの読み込みがボトルネックになっているということです。この場合、DXT圧縮にしたり、テクスチャサイズを小さくしたり、テクスチャの数を減らすことでパフォーマンスアップになります。

次にピクセルシェーダーを単色を返すだけの単純なものにする。これはピクセルシェーダーとテクスチャフェッチに影響しますが、テクスチャフェッチについては既にテスト済みなので、このテストはミップマップバイアスを変更してもフレームレートが上がらなかった場合にします。この変更でフレームレートが上がった場合は、ピクセルシェーダーがボトルネックになっています。

 

まだ、ボトルネックが見つからない?と、いうことは残った可能性は#3、#6、もしくは#7になります。

マルチサンプルを有効にしてみて、フレームレートが変わらなければラスタライザがボトルネックになっています。

フレームバッファのピクセルフォーマットをSurfaceFormat.Bgr565などの小さいものに変更してみます。これでフレームレートが上がればフレームバッファがボトルネックになっています。

これでもフレームレートが上がらないということは消去法で深度/ステンシルバッファがボトルネックになっているということです。

 

元ネタ:

http://blogs.msdn.com/shawnhar/archive/2008/04/11/santa-s-production-line.aspx