現在リニューアル中です。一部ページが正しく表示されない場合がありますが、ご勘弁願います
Direct3D9 の分析、考察
Windows で 3D といえば、真っ先に浮かぶのが Direct3D(以下、D3D) でしょう。
OpenGL はもはや、過去の遺産となりました。
D3D を使ってプログラミングをする人でも、
大体の人は初期化やウィンドウ作成の部分は使い回しにしていることと思います。
今日は原点を振り返って、D3D について色々と検証してみたり、語ったりしたいと思います。
D3DCREATE_*
まずは IDirect3D9::CreateDevice に渡す、D3DCREATE_* フラグを検証してみましょう。
このフラグに含まれる値で、必ずひとつは指定しなければならないフラグがあります。
問題は MIXED です。これは、IDirect3DDevice9::SetSoftwareVertexProcessing を呼び出すことで、 頂点処理をハードにやらせるのかソフトにやらせるのかを決められるというものですが、 果たして速度は HARDWARE に勝てるのか?と疑問に思い、検証してみました。
以下がその結果です。ティーポットを Y 軸周りにぐるぐる回すだけのプログラムを実行したときの、 FPS 10 回分の平均を表にしたものです。 ただし、最初 2 回分の FPS は無視してあります。 コンパイラは VC6、Core 2 Duo 2.66GHz, GeForce 7200GS/7300LE, 640x480 X8R8G8B8 上でのものです。
- D3DCREATE_HARDWARE_VERTEXPROCESSING
- D3DCREATE_SOFTWARE_VERTEXPROCESSING
- D3DCREATE_MIXED_VERTEXPROCESSING
問題は MIXED です。これは、IDirect3DDevice9::SetSoftwareVertexProcessing を呼び出すことで、 頂点処理をハードにやらせるのかソフトにやらせるのかを決められるというものですが、 果たして速度は HARDWARE に勝てるのか?と疑問に思い、検証してみました。
以下がその結果です。ティーポットを Y 軸周りにぐるぐる回すだけのプログラムを実行したときの、 FPS 10 回分の平均を表にしたものです。 ただし、最初 2 回分の FPS は無視してあります。 コンパイラは VC6、Core 2 Duo 2.66GHz, GeForce 7200GS/7300LE, 640x480 X8R8G8B8 上でのものです。
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MIXED_VERTEXPROCESSING | |
Windowed | 556.9 [fps] | 556.7 [fps] |
Full screen | 776.9 [fps] | 777.0 [fps] |
なんだかつまらない結果になりました。
ご覧のとおり、どちらのフラグもほぼ同じ速度が出ています。
MIXED を使うと HARDWARE より処理が遅くなる という危惧は、
いらぬ心配だったようです。
D3DDEVTYPE_REF
今度は同じく CreateDevice に渡す D3DDEVTYPE についてです。
GPU が対応していれば、D3DDEVTYPE_HAL で作成することができます。
対して D3DDEVTYPE_REF は、全てソフトウェア上での手作業、
つまり、頂点変換やら透視投影やらピクセルサンプリングやらを、
すべて D3D がやってしまうというものです。
問題は、これが驚くべき遅さなのです。
上記のプログラムをあえて D3DDEVTYPE_REF で実行したところ、 ウィンドウモードで 28.5[fps], フルスクリーン でも 30.5[fps] しかでませんでした。 GPU に頼らないということは、すべて CPU が計算をするということになります。 しかし、Core 2 Duo 2.66GHz でこの数字です。 しかも、描画オブジェクトはティーポットをひとつだけときました。 つまり、D3DDEVTYPE_REF は使い物にならないという結果です。
私が言いたいのは、あえてこのモードでデバイスを作成して、ゲームなりなんなりを続行する必要があるのか、 ということです。 ティーポットひとつでこの結果ですから、まともにゲームが動くはずがありません。 リリース時には、D3DDEVTYPE_REF は切り捨てて良いと言えるでしょう。
上記のプログラムをあえて D3DDEVTYPE_REF で実行したところ、 ウィンドウモードで 28.5[fps], フルスクリーン でも 30.5[fps] しかでませんでした。 GPU に頼らないということは、すべて CPU が計算をするということになります。 しかし、Core 2 Duo 2.66GHz でこの数字です。 しかも、描画オブジェクトはティーポットをひとつだけときました。 つまり、D3DDEVTYPE_REF は使い物にならないという結果です。
私が言いたいのは、あえてこのモードでデバイスを作成して、ゲームなりなんなりを続行する必要があるのか、 ということです。 ティーポットひとつでこの結果ですから、まともにゲームが動くはずがありません。 リリース時には、D3DDEVTYPE_REF は切り捨てて良いと言えるでしょう。
余談ですが、D3DDEVTYPE_REF と D3DCREATE_HARDWARE_VERTEXPROCESSING の組み合わせでも
正しくデバイスを作成できました。
D3DCREATE_HARDWARE_VERTEXPROCESSING が有効ということは、
HAL が有効ということになります(もしかしたら頂点処理ができない GPU なんてのもあるのかも知れませんが)。
余談その 2。D3DDEVTYPE_REF がまったくいらないか、というと、そうでもありません。
シェーダをデバッグするには、このモードでないと出来ないからです
(ハードウェアに渡したデータをデバッガで追う、なんて超人技は現在はまだできません)。
シェーダデバッガ・・・私も使いたいです・・・。
巷じゃ .NET 専用という文書が見受けられますが、2008 とかじゃダメなのかしらん?
シェーダオブジェクト作成時の落とし穴
たとえば、D3DCapsViewer なんかで GPU の性能を調べると、
サポートしている頂点、ピクセルシェーダのバージョンが分かります。
上記の 7200GS/7300LE ではともに 3.0 までサポートしていますが、
私のサブのノート PC のチップ Intel 82830M ではシェーダをサポートしていません。
とりあえず、色々な環境で動くかチェックするのが D3D ゲームの基本というかお約束というかなので、
そちらでも走らせてみました。
すると、奇妙な現象が起きます。
IDirect3DDevice9::CreateVertexShader は、シェーダをサポートしていないにもかかわらず、成功します
(ちなみに、D3DDEVTYPE_HAL/D3DCREATE_SOFTWARE_VERTEXPROCESSING で作成)。
ですが、IDirect3DDevice9::CreatePixelShader は失敗します。
随分何でだろうと悩みましたが、よくよく考えれば簡単なことでした。 頂点シェーダは、D3DCREATE_SOFTWARE_VERTEXPROCESSING で言っているとおり、ソフト上で実行できるのです。 ですから、別に問題は無いわけです。 ですが、ピクセルシェーダは頂点処理では無いので、 実行できるかどうか分からないわけです。
ピクセルシェーダは大して使いどころはない(少なくともシロートには)上、 ちょっと昔のチップだとまずサポートしていないという問題児です。 特に後者は重要です。ピクセルシェーダを使うと途端に対応ハードが減るので、 現状では絶対に必要でない限りは使用を控えた方が良さそうですね。
随分何でだろうと悩みましたが、よくよく考えれば簡単なことでした。 頂点シェーダは、D3DCREATE_SOFTWARE_VERTEXPROCESSING で言っているとおり、ソフト上で実行できるのです。 ですから、別に問題は無いわけです。 ですが、ピクセルシェーダは頂点処理では無いので、 実行できるかどうか分からないわけです。
ピクセルシェーダは大して使いどころはない(少なくともシロートには)上、 ちょっと昔のチップだとまずサポートしていないという問題児です。 特に後者は重要です。ピクセルシェーダを使うと途端に対応ハードが減るので、 現状では絶対に必要でない限りは使用を控えた方が良さそうですね。
頂点シェーダでのアドレスレジスタ使用
頂点シェーダでもう一つ対応が気になるのが D3DDECLTYPE_UBYTE4 です。
D3D9 で対応している頂点シェーダは、全て a0 レジスタを使用できます。
これは、定数レジスタのオフセットを使用するとき専用のレジスタなのですが、
こいつがあると、特にポイントスプライトを使うときとかは涙が溢れるほど便利なのです。
他にも、アニメーションをするときにこれを使って行列を掛け合わせるとかなんとか。
私はアニメーションには疎い、というか私には難しすぎるので、良くは知りませんけども。
ところで、このアドレスレジスタにインデックスをロードするには、昔は以下のように書きました。
ところで、このアドレスレジスタにインデックスをロードするには、昔は以下のように書きました。
// c1: (256.0f, 0.0f, 0.0f, 0.0f) // v1: Diffuse vs.1.1 dcl_color v1 mul r0.x, v1.z, c1.x mov a0.x, r0.x
詳しい説明は省きますが、まず入力頂点に D3DDECLTYPE_D3DCOLOR を指定したフィールドを用意します。
そこへ、インデックスとなる整数値を書き込みます。
このフィールドがシェーダに渡されると、RGBA の形に 0.0 - 1.0 の間として渡されます。 このとき、頂点に入力した時には ARGB ですので、8 ビット分ずれることになります。 また、各バイトが 0.0f - 1.0 にクランプされるので、下位 8 ビット分はシェーダ内では v1.z の位置に来るわけです。 そこへ、256 をかけてやることで、最初に入力した整数インデックスが得られる、というものです。 0 - 255 をクランプするのだから 255.0f をかけてやれば上手くいきそうなものですが、 精度の関係なのか、上手くいきませんでした。
このフィールドがシェーダに渡されると、RGBA の形に 0.0 - 1.0 の間として渡されます。 このとき、頂点に入力した時には ARGB ですので、8 ビット分ずれることになります。 また、各バイトが 0.0f - 1.0 にクランプされるので、下位 8 ビット分はシェーダ内では v1.z の位置に来るわけです。 そこへ、256 をかけてやることで、最初に入力した整数インデックスが得られる、というものです。 0 - 255 をクランプするのだから 255.0f をかけてやれば上手くいきそうなものですが、 精度の関係なのか、上手くいきませんでした。
別にこれで問題は無いのですが、もっと手っ取り早いのは D3DDECLTYPE_UBYTE4 を使うことです。
次のコードは、D3DDECLTYPE_UBYTE4 に書き換えて実行したときのものです。
// c1: (256.0f, 0.0f, 0.0f, 0.0f) // v1: Diffuse vs.1.1 dcl_color v1 mov a0.x, v1
命令がひとつ減っています。
このように、整数値を直接使用できるので、かけ算をする必要が無くなりました。
問題は、この D3DDECLTYPE_UBYTE4 が GeForce 3 シリーズでは未対応(らしい)ということです。 D3DDECLTYPE_D3DCOLOR は、ハードにとってはサポート必須なので確実に使えます。 ですが、若干使用が面倒ですし、1 クロック分命令が増え、潔癖性の人は気に入らないと思います。 とはいえ、GeForce は現在 9 シリーズ。GeForce 3 シリーズは切り捨てずサポート内に入れるべきか、それとも・・・
問題は、この D3DDECLTYPE_UBYTE4 が GeForce 3 シリーズでは未対応(らしい)ということです。 D3DDECLTYPE_D3DCOLOR は、ハードにとってはサポート必須なので確実に使えます。 ですが、若干使用が面倒ですし、1 クロック分命令が増え、潔癖性の人は気に入らないと思います。 とはいえ、GeForce は現在 9 シリーズ。GeForce 3 シリーズは切り捨てずサポート内に入れるべきか、それとも・・・