サンプルプロジェクト実行画面以前と同様のサンプルプロジェクトを作成して、パフォーマンスを比較した。意外と遅い?
◎動的頂点バッファXNAのモデルインスタンシングについて調べていたら、DynamicVertexBuffer(動的頂点バッファ)に関する記事を見付けました。頂点データの動的変更は、これを使った方が速くなるかも…?
というわけで、インスタンシングの前にこれをやります。
◎SetDataのエラーと対処DynamicVertexBufferに関する記事はちらほら見かけるのですが、シンプルなサンプルはなかなか見当たりません。そこで、ソーサリーフォースの
IndexBufferサンプルを自力でDynamicに改修することにしました。
まず、VertexBufferやIndexBufferを単純にDynamicVertexBuffer等に置き替えると…

エラーもなく普通に動きました。(ちょっと意外w)
次に、頂点データの内容を動的に変更すると…SetDataでエラーが発生しました。
操作は中断されました。デバイスで設定されているリソースは変更できません。また、タイリング ブラケット内で使用した後に変更することもできません。以前発生したのと同じエラーですが、今回はDynamicVertexBufferを使用しているので対処可能です(
こちらの記事によると、SetDataOptions.NoOverwriteが良いらしい)。
this.vertexBuffer.SetData(vertives, 0, vertives.GetLength(0), SetDataOptions.NoOverwrite);
時間経過と共に、頂点が上方向に移動しました。
成功ですねw
ちなみに、今回はSetDataOptions.Discardとしても結果は一緒でした。モデル数や頂点数で違いが出るのかな?
◎パフォーマンス比較以前と同様のサンプルプロジェクトを作成して、パフォーマンスを比較しましょう。
…で、実際に作ってみると、ちょっと悩ましい点が出てきました。
・バッファ領域を全インスタンス分確保すべきか?
→多分その方が早いので、全インスタンス分確保する
・頂点/インデックスデータを毎回取得/設定すべきか?
→公平に比較するため、前回同様毎回取得/設定する
そもそもデモ内でインデックスデータは変更してないので、インデックスデータの取得/設定は全く無駄なんですが、実際のゲームに適用する場合を考慮して以前のデモに組み込んでしまったので、今回も同様の想定としました。
よく考えると、頂点データを変更してもインデックスデータは変更しないケースの方が多い気がしますが…まぁいいかw

シェーダー改修方式 → 今回方式
板ポリアニメ 100枚:60 → 60(fps)
板ポリアニメ1000枚:59 → 38(fps)
板ポリアニメ1500枚:40 → 30(fps)
板ポリアニメ2000枚:30 → 20(fps)
板ポリアニメ3000枚:20 → 15(fps)
板ポリアニメ5000枚:15 → 10(fps)
ありゃ?遅くなっちゃった?
と言うか、これまでで一番遅いですね…。
◎バッファ更新タイミングの変更頂点データとインデックスデータを毎回バッファにセットしてるから遅いのかもしれません。アニメ画像番号が更新された時だけ、バッファ操作するように修正しましょう。
改修前 → 改修後
板ポリアニメ 100枚:60 → 60(fps)
板ポリアニメ1000枚:38 → 59(fps)
板ポリアニメ1500枚:30 → 30(fps)
板ポリアニメ2000枚:20 → 23(fps)
板ポリアニメ3000枚:15 → 15(fps)
板ポリアニメ5000枚:10 → 9(fps)
1000枚はシェーダー改修方式と同等まで改善されましたが、1500枚以上は大差無いようです。1000枚超えたあたりを細かく測定してみました。
板ポリアニメ1050枚:58(fps)
板ポリアニメ1080枚:50(fps)
板ポリアニメ1100枚:45(fps)
板ポリアニメ1150枚:40(fps)
板ポリアニメ1200枚:30(fps)
ふむ…1000枚を超えたあたりから急に遅くなるのは間違いないようですね。もしかして、1024枚を超えるとGDCメモリを超えてスワップが発生するとか?でも、バッファメモリが足りなければ領域確保時にエラーになりそうだけどなぁ…。
以前の方式と今回の方式をPIXで細かく比較すれば、原因がわかるかもしれませんが、わかった所で以前の方式を超えることは出来ないので、この点に関してはこれ以上追及しないことにします。
◎以前の方式を超えられない理由シェーダー改修方式のようなイレギュラー方式はともかく、DrawUserIndexedPrimitives方式よりも遅い理由は把握しておきたいですね。予想としては、頂点データの更新頻度が頻繁すぎるからじゃないでしょうか?
アニメは80ミリ秒毎に更新するので、Draw()呼び出し(約17ミリ秒毎)5回毎に、バッファを1回更新することになります。ということは、「DrawUserIndexedPrimitivesで5回表示」と「バッファ更新+DrawIndexedPrimitivesで5回表示」のどちらが早いか?を比較していたことになり、「後者がやや遅い」と仮定すれば、これまでの計測結果が納得できます。
では、この仮説が正しいか検証しましょう。アニメの更新頻度を変更して、両者のパフォーマンスを比較します。
○アニメ更新頻度:80 → 160
DrawUser〜方式:今回方式
板ポリアニメ 100枚:60 → 60:60 → 60 (fps)
板ポリアニメ1000枚:59 → 59:59 → 59 (fps)
板ポリアニメ1500枚:30 → 30:30 → 30 (fps)
板ポリアニメ2000枚:30 → 30:23 → 30 (fps)
板ポリアニメ3000枚:20 → 20:15 → 20 (fps)
板ポリアニメ5000枚:12 → 12: 9 → 10 (fps)
○アニメ更新頻度:80 → 500
DrawUser〜方式:今回方式
板ポリアニメ 100枚:60 → 60:60 → 60 (fps)
板ポリアニメ1000枚:59 → 59:59 → 59 (fps)
板ポリアニメ1500枚:30 → 30:30 → 30 (fps)
板ポリアニメ2000枚:30 → 30:23 → 30 (fps)
板ポリアニメ3000枚:20 → 20:15 → 20 (fps)
板ポリアニメ5000枚:12 → 12: 9 → 12 (fps)
DrawUser〜方式は、更新頻度を下げてもパフォーマンスが変化しなかったのに対し、今回方式は、更新頻度を下げるとDrawUser〜方式と同等のパフォーマンスになりました。
この結果だけで決めつけるにはデータ不足とは思いますが、先程の仮説は正しい気がします。(多分w)
◎まとめ今回の結果だけを見ると、動的頂点バッファ方式はDrawUserIndexedPrimitives方式を超えられません。但し、別のケースであれば逆の結果になる可能性もあるはずです。
予想としては、
・頂点/インデックスデータを頻繁
(80ミリ秒以内毎)に更新する場合
→DrawUserIndexedPrimitives方式が良い
・頂点/インデックスデータを時々
(160ミリ秒以上毎)更新する場合
→動的頂点バッファ方式が良い
・頂点/インデックスデータを全く更新しない場合
→通常のDrawIndexedPrimitives方式が良い
こうなることを期待していましたが、動的頂点バッファ方式が良いケースを実証できなかったのは残念ですね。
それと、私のPCでは1000枚を超えると急激にパフォーマンスが低下するらしく、1500枚で30fpsを超えられるのはシェーダー改修方式だけのようです。どの方式にも長所短所があるはずですが、あの方式の短所って何かなぁ?
◎サンプルプロジェクトとりあえず今回のサンプルプロジェクトをHPにUPしました。なるべくシンプルにまとめたつもりですのでご参考まで。
XNA3.1でUVアニメーション(動的頂点バッファ方式)◎次回予告「動的頂点バッファ」という言葉が何となくカッコ良くて、ちょっと憧れていたのですが、今回はその効果が実感できなくてちょっとガッカリ。どうすればその効果が見えるようになるんですかねぇ?
それはともかく、次回からモデルインスタンシングです。
…って前回も言ったなぁ(汗)
テーマ : ゲーム製作 関連 - ジャンル : ゲーム