DirectX一覧

D3DPOOL_MANAGEDのデータ保存先について

D3DPOOL_MANAGEDについて。
どうやら生成元となったバイナリデータ列はユーザー空間に置かれる模様。そんな挙動のため、結局のところは使用メモリがどんどん増えていくように見える。
仮に256MBほどテクスチャがあったとすると、このフラグ付きで送った場合には結局メインメモリにも同じ容量だけメモリを使われていることになってしまう。

また先日作業用のメモリとって、必要に応じてUpdateTextureで転送すればいいと書いたけど、これも結局のところD3DPOOL_MANAGED相当を自前でやっているだけに過ぎない…。

意外といけてなかった。
もっとよりよい方法を考えてみることにします。


テクスチャとサンプラーステートの関係

テクスチャとひとことで言った場合、この中には2つの要素が含まれていると思います。
1つは、ポリゴン表面に貼りたい画像のデータ。
そしてもう1つは、その画像に適用するフィルタ設定やUV設定といったの各設定情報。このうち後者はテクスチャフィルタやラッピングモード(アドレッシングモード)とも言われるため
テクスチャの情報としてまとめられてしまうことが多いと感じます。
特に昔からのOpenGLではテクスチャオブジェクト内にラップモードとフィルタ設定を行っておき、
テクスチャの使用時にはその設定が有効になるという挙動を示します。

DirectX9においては後者の設定はテクスチャの処理するステージに対して設定するものとなっています。
SetSamplerStateメソッドのヘルプを見てみても、サンプラー番号といわれる部分はサンプラステージインデックスと表記されています。
HLSLシェーダ内にレジスタ指定できますが、そのときの register(s0);という”s”の部分については、おそらくステージの意味合いなのではと思います。

しかし近年、DirectX10(D3D10)以降これらの関係がAPIの上から変わりました。
前述の各設定情報がサンプラーオブジェクトとして分離されました。
ハードウェアの面から考えると、画像が置いてある部分の内容と、そこから読み出すときのフィルタ設定は確かに分離でき
こちらのほうが自然な印象を受けます。
この時代においても、レジスタ指定の部分では従来どおり register(s0); ですが、
このときの”s”の意味合いは、サンプラーの意味を持つ”s”へと変わっていると思います。

また、OpenGLのほうでもテクスチャの設定関連が見直され、
新しいOpenGL*1では、テクスチャとサンプラーとそれぞれ別のオブジェクトとして使うことが可能になってきました。
これらにおいては、glGenSamplers, glBindSamplerといった生成、割り当ての関数が追加されています*2

新しいものだけを見れば、DirectXもOpenGLも同じ流れになっていてすっきりしました。
しかし、気になる点も1点あります。
それはこのサンプラーの設定はサンプラーの番号に対して設定するという方法しかないという点です。
HLSLのエフェクト(.fx)を使う上では、サンプラーステートと参照するテクスチャを結びつけて記述が出来ますが、
それぞれのシェーダーを別々に設定するような機構では、この設定は出来ないようです。
まだ見つけられてないだけかも知れませんが、OpenGL(GLSL)ではそういった機構すらないように思えます。

これらの点から、テクスチャの割り当てとサンプラの割り当ての番号は一致するように実装しておくのがいいのかなと思いました。
テクスチャの割り当ての番号自体は、シェーダーリフレクションを使えば求められます。
DirectX9については、そもそも同一のステージ番号に閉じ込められていますし、
旧OpenGLではテクスチャオブジェクト内にまとまってしまっています。
全てにおいて統一の取れたものにするには、この方法しかないのでは?と考えています。

*1 : OpenGL3.2以降だと思われる

*2 : GL_sampler_objects


CPU書き換えテクスチャ転送

DirectX9で、CPU上でごりごりとテクスチャ処理して
VRAMに転送し、普段使うのはその転送後のテクスチャという方式を考えてみました。CPU上のテクスチャは D3DPOOL_SYSMEMで作り、
VRAM上のテクスチャは D3DPOOL_DEFAULTで作るという方法です。
この転送には、IDirect3DDevice9::UpdateTexture メソッドが使えます。

少々メモリは食いますが、
CPUからの操作のやりやすさと、実行パフォーマンスという点では
この方法は結構いけてると思っています。

で、1つ手間取った部分がありました。
デバイスロスト等で再度テクスチャを転送しなくてはいけないときに、
何も考えずにUpdateTextureをしても反映されませんでした。
どう対処するかというと、転送元のテクスチャにダーティフラグをつけておかなくてはならないようです。

これには、AddDirtyRect メソッドがあります。
とりあえずロック&書き込みしないような転送前には全領域をダーティとしてマークしておけば、UpdateTextureで正しく送ることが出来ます。

知っていれば、なんてことはないネタですが
自分の防備録として日記に残しておきました。


DirectX vs OpenGL テクスチャ編

DirectXとOpenGLとでテクスチャに関していろいろと違うので
ここでそれらについて書いてみたいと思います。

用語

DirectXでは、テクスチャアドレッシングモードとテクスチャラッピングモードがあります。
OpenGLでは、テクスチャラッピングモード だけです。

しかも両者でそれぞれの言葉が意味しているものがずれており、
DirectX::アドレッシングモード = OpenGL::テクスチャラッピングモード
という状態となっています。
これは酷いですね。

個人的にはDirectXのアドレッシングモードという部分を廃止して、
各テクスチャの張り方を指定するラッピングモードだけに統一してほしいと思います。
円筒のUVラッピングモードってあまり使いませんし、
必要になったら通常のUVにオフセットや変換式をかませれば済むことでしょう。

設定方法について

DirectXでは、縮小フィルタ、ミップフィルタ、拡大フィルタの設定がそれぞれ設定できます。
OpenGLでは、縮小フィルタ+ミップフィルタ、拡大フィルタの2つです。

このあたり、最初はすごく困った覚えがあります。
DirextXの縮小フィルタ=D3DTEXF_POINT, ミップフィルタ=D3DTEXF_NONE
これが、
OpenGLだと GL_NEAREST_MIPMAP_NEAREST になるとか。

また、OpenGLではテクスチャフィルタの設定もきちんとやらないと
テクスチャが使用可能にならない点も注意しなくてはなりません。

RADEON HDについて

そして、RADEON HDとCatalyst 10.7の(少なくとも)組み合わせでは
glTexImage2Dをすべてやり終えた後で、テクスチャフィルタの設定を行う必要があります。

赤本の通りのコードでも実は動かないという可能性があるので、注意が必要です。
もしかしたらシェーダー使わずに、固定機能だけで完結させるならば
このトリックは必要ないかもしれません。


続・ATIドライバの謎?

Direct3D9: (INFO) :Failed to create driver indexbuffer を調べてみた。
海外のForumでは割とよくあがっている話っぽい。このメッセージが意味するところは、
IndexBufferをVRAM上に作るときに、VRAM上に作れなかった、という話になるっぽい。
あとは、ドライバがハードウェアのインデックスバッファに対応していないということもあるらしい。

考えられる原因は以下の通り

  • DrawPrimitiveUPを使用した
  • HWが対応していてもドライバで無効状態

また、INFOカテゴリに入っているメッセージなので、
操作している側には特に問題はないメッセージであるとのことで無視してもよさそう。

なお、この場合のインデックスバッファは
AGPメモリやシステムメモリに確保されるような感じです。


DirectX9のRenderStateからGL読替

DirectX9のRenderState設定がGLのどれにつながるかわからないって話を聞いた。
知っている範囲で書いてみます。

DirectX9 OpenGL
D3DFILL_WIREFRAME glPolygonMode( GL_FRONT_AND_BACK, GL_LINE )
D3DCULL_NONE glDisable( GL_CULL_FACE )
D3DCULL_CW,CCW GL_CULL_FACE有効化してGL_CW,GL_CCWらも設定
D3DRS_SCISSORTESTENABLE GL_SCISSOR_TEST
D3DRS_STENCILENABLE GL_STENCIL_TEST
D3DRS_ZENABLE GL_DEPTH_TEST
D3DRS_ZWRITEENABLE glDepthMaskで設定
D3DRS_STENCILREF glStencilFuncで設定

各種比較関数、ブレンド設定の列挙値は大変なので除外で。
調べてみるとすぐにわかりそうな感じですし。

感想

OpenGL側はDirectXとちがって、必要要素を一括に設定する気配を感じる。
このほうがデバイスに値を流すのには適しているんだろう。

○○さんへ

他にこれは?というのがあれば、コメントにでも~。
知ってる範囲で追記しようかと思います。


DirectX9でATIカードに振り回される

どうもATIのグラフィックボードおよびドライバは
DirectXを正しく使わないと描画が不正になる模様。DrawIndexedPrimitiveでnMinVertex, nMaxVertexを正しく設定しないと、
それだけでポリゴンが壊れて描画されたりすることを確認できた。
ちなみにNVIDIAではこれらの情報を参考にしていないのか、
適当な値を放り込んでもきちんと動いてくれる。

症状としては、全く描画されないか、変な部分とくっついたりしたポリゴンなどが確認できたので、
動作としては未定義動作に近いのかもしれない。

環境

Core i7 870, 4GB
RADEON 5450 1GB(Catalyst 10.7)
Windows7 Ultimate(x64)

まとめ

開発環境では NVIDIAのカードがおすすめ。
ドライバで振り回されることも比較的少なめ。
でもテスト環境にATIのカードを準備しておくとよさそう。
なぜならATIの環境では、正しく命令(API)を使えているかの確認に使えそうだからである。

むしろ、NVIDIAが緩いせいか、NVIDIAのカードでしか動作確認していないと、
ATIの環境に持って行ったときにうまく動かないということが発覚しそうな気がする。

いやはや、勉強になった。


続・CubeMapGS

以前の7/29日記で、CubeMapGSの動作を書いたので、
NVIDIAのGeforce9800GTではどうだったんだろうと思ってチェックしてみた。

球体モデルの結果

カード Instancing fps値
RADEON 5450 TRUE 28.2
RADEON 5450 FALSE 38.2
GeForce9800GT TRUE 119.2
GeForce9800GT FALSE 61.2

これだけ見ると世代的には前の9800GTのほうが性能がいい。
またインスタンシングフラグの影響がそれぞれ逆転しているのも不思議。

車体モデルの結果

カード Instancing fps値
RADEON 5450 TRUE 8.8
RADEON 5450 FALSE 9.7
GeForce9800GT TRUE 44.8
GeForce9800GT FALSE 32.6

製品のレンジが違うとはいえ、ここまで差があるのか。
GSはNVIDIAの圧勝です。

まとめ

この状況を見るとGSで6面のキューブマップについては
リアルタイムに毎フレーム処理できるものではなさそうです。
せいぜいシーン初回で描いてしまうとかその程度かなと思います。
他のメリットは、レンダーパスの処理がシンプルになるというのもあるけど、
ローエンドでもこれくらいの形状ならある程度(60fps維持)の性能が出せないと、
安心して使えないですね。


Scissorによる挙動違い(DX vs GL)

DirectXとOpenGLとで、シザーの設定時のクリア挙動が異なるようです。座標系がそれぞれ異なるってのもあるけど、
そこは上下を逆転して対処したとしても、それだけでは互換動作は不可能。

フレームバッファの縦横と、ビューポートの縦横と、シザー設定の縦横
それぞれ違う設定にしてみて、双方のAPIで実行してみるとわかりやすいかと。

DirectXではビューポートとシザー設定のお互い被っている領域でクリア
GLではシザー設定に従ってクリア。

同じような振る舞いにさせるならシザー領域とビューポートの領域のAND集合を取るようにして、
できあがった矩形でシザーテストするようにしてクリア処理を入れるようにする。

GL面倒だけど、こういうった細かいことをきちんと実装できるだけ
柔軟性はあると思う。


Vertex Texture Fetchによるスキニング

Vertex Texture Fetchを利用して、スキニングメッシュのサンプルを動かしてみました。
ひにけにxnaさんのところでは、XNAを用いての解説があったので、
ここでは、普通のDirectX SDKとC++による組み合わせでこれをやってみたいと思います。

使用するもの

  • SkinnedMeshのサンプルプログラム
  • ShaderModel 3.0に対応したグラフィックボード
    • Radeon X1000シリーズを除く。これはVTFに対応していないため。

ソースコード

各準備

まずは、ボーンのマトリックスを格納するためのテクスチャを準備します。
ここではグローバルにおいて、手抜きしました。
また、毎回書き換えるので、ダイナミックテクスチャにしています。

ボーン用テクスチャの概要

ボーン用のテクスチャは、横方向に使用したいボーンの数、縦方向に4というサイズで作成しました。
というのも、行列は4x4のfloat要素で、今回のテクスチャはRGBA32Fという形式のため、1テクセルに4要素格納できます。
そのため、4テクセル使えば、1つの行列が生成できるということを目的にしています。

横方向(U方向)はボーンのインデックスを示すことにして、
V方向には行列の行を格納することにしました。
このほうが配列っぽく見えるかなと思っています。

マトリックスパレット数調整のための変換部分

またついでに、ConvertToIndexedBlendedMesh を呼び出している部分も
これらの設定のボーン数をみるように修正が必要です。

マトリックスパレットの設定部分の修正

マトリックスパレット設定部分を次のように変更します。

テクスチャにボーンのマトリックスを格納していきます。
注意点としてはテクスチャのY方向へずらすときは、ロックしたときのピッチバイト数でずらさないといけない点です。
それと、シェーダー内ではマトリックスとしては取れず、
float4の4つの組で取れる感じになります。
そのためシェーダーに送る際に転置しておきます。
こうすることで、内積を利用して計算が可能になります。

プロジェクション用行列の設定修正

サンプルのSkinnedMeshのサンプルではmViewProjというシェーダー定数に
実はプロジェクション用の行列しか送っていないです。
ビュー行列はマトリックスパレットに対して適用していました。

この部分を変更します。
mViewProjにg_matProjを設定している部分で、g_matViewとの演算結果を格納するようにしておきます。

シェーダーコード

ほとんどの部分はサンプルのそのままです。
変更点は以下の通り

  • tex2Dlodにて該当するマトリックスパレット相当のテクセルから値を取得
  • 浮動小数用にサンプラを設定
  • VTF使うために、vs_3_0を設定。あわせてピクセルシェーダーも

実行結果

こんな感じになります。
理由はピクセルシェーダーでディフューズ用のテクスチャ等を処理せず、
単純にライトの強度を出しているからです。

しかし、これがVTF利用して動いています。

今は、横方向128でやりましたが、これを256にすることで
ボーンの上限256個までを一度に使うことが出来るようになります。