vFetchってなに?

vFetchでスキンアニメーション、その1:vFetchってなに?

Xbox 360のGPUはDirect X 9.0とDirect X 10の中間であると言われることがありますが、vFetchはその特徴を示すひとつの機能です。vFetchはシェーダー内で使えるアセンブリ命令で、Vertex Fetchの略、つまり頂点データをフェッチをするための命令です。

vFetchを使うことで頂点数の増減こそできませんが、Direct X 10で追加されたジオメトリシェーダーを使ったファーシェーダーのように隣接する頂点データをフェッチしてフィンポリゴンをリアルタイムに生成したりすることもできます。

自由に頂点をフェッチできるというのが最大の利点ですが、弱点もあります。

  • Windows上では使えない
  • アセンブリ命令なので、頂点フェッチの最適化などを自前でする必要がある

vFetchはWindows上では使うことができないので、Windows/Xbox 360両対応のゲームを製作する場合にはそれぞれのプラットフォーム別に違う手法を実装する必要があります。

vFetch命令はメモリアクセス命令でもあるので、使用する場所を間違えると大きなパフォーマンスロスになってしまいます。通常はシェーダーコンパイラーが最適化してくれるのですが、明示的にvFetchを使うときには注意が必要です。vFetchを最適な状態で使用していないと40%程のパフォーマンスロスになってしまった、という経験が私自身ありました。

GPUはどうやって頂点データを読んでいるのか?

vFetchの使い方を知るには、GPUがどのように頂点データを読み込んでいるのかを知っておくと理解しやすいと思います。

頂点シェーダー内で頂点データを読み込むには以下の三つの情報が不可欠です。

  • シェーダー内の入力セマンティクス
  • 頂点宣言(VertexDeclaration)
  • 頂点ストリーム

シェーダー内の入力セマンティクス指定は以下のコードように 「変数名 : セマンティクス名」 となります。この宣言によって、以下の例では、position変数にはPOSITIONセマンティクス、normal変数にはNORMALセマンティクスをといった感じに、任意の変数に指定したセマンティクスを割り当てています。

 VS_OUTPUT VertexShader(float4 position : POSITION, float3 normal : NORMAL)
{
    // ...

次に、頂点宣言では複数の頂点要素(VertexElement)を指定することで、セマンティクスとメモリレイアウトとの関連付けをします。セマンティクスはVertexElementUsageで表されます。頂点宣言によって、任意のセマンティクスがどのストリームのどのメモリアドレスからどんなフォーマットで読み込むのかを指定します。

下の頂点要素の宣言は 「POSITIONはストリーム番号0番、頂点データの12バイト目からVector3フォーマットで格納されている」 という意味になります。

 new VertexElement(0, 12, VertexElementFormat.Vector3,
                        VertexElementMethod.Default,
                        VertexElementUsage.Position, 0),

そして、最後に頂点ストリームの設定です。頂点ストリームの設定はVertexStream.SetSourceメソッドで行います。以下のコードの意味は 「ストリーム0番はメッシュの頂点バッファの100バイト目から始まり、ストライドサイズは64バイト」 という意味です。通常、ストライドサイズはひとつの頂点データと同じサイズですが、別のサイズでも問題ありません。例えば頂点サイズは20バイトだけど、他にも頂点データ以外のデータが入っているのでストライドサイズは48バイトなんていう指定の仕方もできます。

 GraphicsDevice.Vertices[0].SetSource( mesh.VertexBuffer, 100, 64 );

ストリームと頂点宣言の関係をまとめると以下の図のようになります。

stream

頂点バッファには頂点という名前がついているので、GPUが読み込める頂点データ形式だけを格納するべきものだと思われがちですが、GPUからすると頂点バッファは単なるメモリの塊に過ぎないので上図のように、GPUで読み込むべきPosition, Normal, Color以外にGPUが呼ぶ必要がないSpeedがあっても全く問題ありません。GPUが気にするのはストリーム情報と頂点宣言だけです。

GameFest Japan 2008のデモでは、このことを利用してゲームオブジェクトのリストをそのまんま頂点バッファに設定して使用することでCPU側の無駄なコピー処理を省くDirect Mapping(私が勝手に命名)は、この仕組みを利用しています。

ただし、ストライドの最大サイズは256バイトなので、大きなデータ構造を指定できないことに注意してください。

GPUはシェーダー内のセマンティクス、頂点宣言、そしてストリームの情報を元に以下のように頂点データを読み込んでいます。ここではDrawIndexedPrimitiveを使い、頂点シェーダー内でposition変数の値を取得するまでの過程を説明します。

  1. インデックスバッファから頂点インデックスを取得
  2. POSITIONセマンティクスに該当する頂点要素の情報を見つける
  3. 頂点アドレスの計算
    • 頂点アドレス = 頂点バッファのアドレス + ストリームオフセット + ストライド × 頂点インデックス
  4. 頂点要素のメモリアドレスの計算
    • 頂点要素アドレス = 頂点アドレス + 頂点要素のオフセット
  5. 頂点要素のメモリアドレスから指定されたフォーマット形式でデータをposition変数に読み込む

以上のステップは普段はGPUで自動的に行われているのですが、この過程の1と2の部分をシェーダー内でやってしまおうというのがvFetch命令です。

次回に続く