Direct3D 10 ビデオカードの違いはわからない

Direct3D10 になって「caps がなくなった」のは大きな特徴のひとつです。
MS の DX10 ppt スライド等でも何度も出てきており、メリットとして強調
しています。

従来メーカー毎に、ビデオカードごとに、さらにはドライバごとに対応機能も
能力もばらばらなのが当たり前でした。

新しいビデオカードを手に入れたなら、まずは caps を調べます。
対応するサーフェースフォーマットを調べて、テクスチャサイズの制限を調べて、
使えるブレンドモードを調べて、、次に各種パフォーマンスと
やることがたくさんありました。

DirectX API のすべての機能が最初から使えるビデオカードなど無いので、
手に入れてから見たことがない機能が enable されていたりすると
妙に嬉しかったりするわけです。(←それは一部のマニアだけです)

nVIDIA と ATI の2強になってからは手間が大幅に減りましたが、このような
能力の差はかなりの開発の負担となります。結局どれでも動く当たり障りの
無い機能しか使えないし、動作確認もばかになりません。

Direct3D10 ではこの負担を減らすために、機能はどのビデオカードでも
同じように使えることが前提となりました。なので、caps が廃止された
ことの本当の意味は

 caps によって機能の違いを調べる必要が無い

ということです。

実際に RADEON HD2900XT を使ってみました。
caps が違ったり機能が違ったり、列挙の値が違ったりすることも全く無くて、
普段使ってる GeForce8800GTS と一見何も変わりません。今までのように API
を使う上での違いが全く見えません。ちょっとさびしいくらいです。

これは API から見えないだけで本当はハードの特性は異なっており、
機能ではなく速度面で調査する段階になったらかなりの個性が見えてくる
はずです。

だけどそのような違いも一見ドライバレベルで隠蔽されていて、おそらく下位
モデルで試しても全く同じなのでしょう。

プログラムの開発上は機能的な差がなくなりましたが、パフォーマンスを維持
するならある程度の機能的取捨選択のノウハウは必要になると考えられます。

一見違いが無くても、下位モデルでは一部機能がソフトウエア実装だったり
使えるリソースが大幅に減っていたりする可能性が十分考えられるからです。

これらの可能性があるとしても機能的な違いが開発側から見えないということは、
開発が楽になることの裏返しとして、
実はその選択をユーザー側に押し付けているだけなのかもしれません。

アプリケーション側での選択の余地が減った代わりに、ユーザーが自分で
パフォーマンスが足りなければ機能を落として、またはより良いハードを購入
して対処しなければならないからです。

作る側としてはそうならないよう、努力を忘れないよう、
肝に銘じておきます。

D3D10 ConstantBuffer のアクセス補足など

以前こちらで ConstantBuffer を書き換える方法を調べました。
D3D10 ConstantBuffer の更新方法
書き忘れがあったので補足します。
ID3D10Effect 自体は内部で (2) の方法、つまり

  ID3D10Device::UpdateSubresource()

を使っているようです。なぜかというと、ID3D10Effect で作成した Effect から
内部の ConstantBuffer を参照して DESC を見ると D3D10_USAGE_DEFAULT で
作られているからです。
なので、ConstantBuffer の書き換えは UpdateSubresource() が標準的な
使い方なのかもしれません。

だけど、ConstantBuffer に直接アクセスしている唯一のサンプル
HLSLWithoutFX10 では Map()/Unmap() を使っているんですよね。

長かったですが、ようやく方針も固まって、周辺ライブラリとかツールとか
できてきたので実際の描画実験に手を出せそうです。
リソース管理とかシェーダーの管理、パラメータのリンク方法、スクリプト、
データフォーマットなどかなり遠回りしました。非常に時間がかかってます。
DXUT は使わず、D3DX もテクスチャローダーだけ使っています。

fx は使ってますが、ID3DEffect から ID3D10Shader を取り出して、あとは
直接管理することにしました。この方法だと内部に無駄な ID3D10Buffer が
たくさん出来ているので、最終的には直接 ID3D~Shader を生成するつもりです。

Direct3D 10 June2007 SDK の非常に細

「Direct3D10 June2007 SDK の非常に細かいこと」
ID3D10ShaderReflection から取れる D3D10_SHADER_DESC のうち
マニュアルに載ってる最後の 4つ
 UINT MovInstructionCount;
 UINT MovcInstructionCount;
 UINT ConversionInstructionCount;
 UINT BitwiseInstructionCount;
は実際には定義されていないです。

動作上全然問題ないので、これはたぶん必要ない、何の特にもならない情報だと
思います。(コンパイルされた命令の詳細とか普段使わないし)

ID3D10Effect から tbuffer を取るには ConstantBuffer 扱いでした。
GetConstantBufferBy~()->GetTextureBuffer()
でいけます。
リソースなので
GetVariableBy~()->AsResource()->GetResource()
かと思ったら違いました。
この手順を使うのは Buffer 宣言した場合です。
ID3D10ShaderReflection からアクセスする場合も TBUFFER は
ConstantBuffer ではなく Texture (ShaderResourceView)扱いです。

Direct3D 10 ShaderReflection を

「Direct3D 10 ShaderReflection を安全に取得する方法」
以前こちらで ID3D10Effect から ID3D10ShaderReflection を取得する方法を
紹介しましたが、このままだと少々問題がありました。
D3D10/DX10 Effect Interface内情報
というのは、以前も書いたように Effect 内で NULL が渡されたシェーダーで
あっても D3D10Effect の API はほとんど有効なまま素通りしてしまうからです。

例えば

ID3D10Effect* iEffect; // Input

ID3D10EffectTechnique* iEffectTechnique= iEffect->GetTechniqueByIndex( 0 );
if( !iEffectTechnique || !iEffectTechnique->IsValid() ){
 return FALSE;
}
ID3D10EffectPass* iEffectPass= iEffectTechnique->GetPassByIndex( 0 );
if( !iEffectPass->IsValid() ){
 return FALSE;
}
D3D10_PASS_SHADER_DESC pass_shader_desc;
if( FAILED( ipass->GetGeometryShaderDesc( &pass_shader_desc ) ) ){
 return FALSE;
}
ID3D10EffectShaderVariable* iShaderVariable= pass_shader_desc.pShaderVariable;
if( !iShaderVariable->IsValid() ){
 return FALSE;
}
D3D10_EFFECT_SHADER_DESC effect_shader_desc;
if( FAILED( shader->GetShaderDesc( pass_shader_desc.ShaderIndex, &effect_shader_desc ) ){
 return FALSE;
}
ID3D10ShaderReflection* iReflection; // Output
if( FAILED( D3D10ReflectShader( esdesc.pBytecode, esdesc.BytecodeLength, &iReflection ) ){
}

と幾重にチェックを入れても引っかからず、最後の D3D10ReflectShader() で
E_INVALIDARG になります。一旦 iShaderVariable->GetGeometryShader() で実際の
シェーダーインターフェース
(ID3D10GeometryShader, ID3D10VertexShader, ID3D10PixelShader)
を取得して、設定されているシェーダーが NULL かどうか判定しても良いのですが、
この場合 Get で AddRef() されるため Release() が必要になってしまいます。

D3D10_EFFECT_SHADER_DESC effect_shader_desc が取れたところで、バイトコード
サイズによって

if( !effect_shader_desc.BytecodeLength ){
 return FALSE;
}

とはじいてあげるのが良さそうです。

またすっかり忘れてましたが、NULL になる可能性があるのは GeometryShader
だけではありませんでした。StreamOutput を使ったときは PixelShader も NULL
設定される可能性があります。

D3D10 DrawIndexedInstanced()が

「DrawIndexedInstanced()がおかしい?」
Direct3D10 では ID3D10Device::Draw 系命令が5つあります。
Draw()/DrawIndexed() は従来の DrawPrimitive()/DrawIndexedPrimitive()
に非常に近くほとんどの描画はこの2つを使います。

Draw(
  UINT VertexCount, // 描画する頂点数
  UINT StartVertexLocation // 頂点の開始位置
 )

DrawIndexed(
  UINT IndexCount, // 描画するインデックス数
  UINT StartIndexLocation, // インデックスの開始位置
  INT BaseVertexLocation // インデックス指定する頂点の開始位置
 )

DrawInstanced()/DrawIndexedInstanced() は同じデータを指定回数分
繰り返し描画することができます。この Geometry Instancing については
以前こちらの方でも書きました。
D3D10 Geometry Instancing の問題と

DrawInstanced(
  UINT VertexCountPerInstance, // 描画する頂点数
  UINT InstanceCount, // 繰り返し描画回数
  UINT StartVertexLocation, // 頂点の開始位置
  UINT StartInstanceLocation, // インスタンスの開始位置
 )

DrawIndexedInstanced(
  UINT IndexCountPerInstance, // 描画するインデックス数
  UINT InstanceCount, // 繰り返し描画回数
  UINT StartIndexLocation, // インデックスの開始位置
  INT BaseVertexLocation, // インデックス指定する頂点の開始位置
  UINT StartInstanceLocation // インスタンスの開始位置
 )

これらの命令は Instance の描画回数を指定する引数が追加されています。
同時に Instance に対するオフセットも指定できるようになっています。

DrawIndexedInstanced() でこのオフセットである StartInstanceLocation を
使うとなぜかきちんと描画してくれません。
描画されなかったりポリゴンが飛んだり。

DrawInstanced() ではうまく行っており、こちらは意図通りに動作しています。
指定した値の分だけ Instance バッファをずらしてそこから描画してくれます。

実行は GeForce8800GTS ForceWare 158.24 ですが、Reference Driver でも
RADEON HD 2900XT でも全く同じ描画になるので、使い方を勘違いしている
だけかもしれません。もうしばらく検証してみます。

ちなみに Indexed 系の命令は BaseVertexLocation だけ INT 型で、負の値を
受け付けるようです。

考えられる用途としては、例えばもともと1つの巨大な頂点バッファを複数の
バッファに分割したとき、Index バッファの値を書き換えなくても描画可能な
ことでしょうか。

後ろにずらすだけなら IASetVertexBuffers() でもできますが、前方にずらす
ことは今まで確かにこれまでできなかったかもしれません。