月別アーカイブ: 2007年7月

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() でもできますが、前方にずらす
ことは今まで確かにこれまでできなかったかもしれません。

Direct3D10 blog について

DirectX10/Direct3D10 というと、やっぱり性能があがって機能的にすごい!
そんなイメージがあります。GPU メーカーや Microsoft が宣伝しているように
綺麗な見た目のインパクトや、今までと違う画期的なシェーダー、派手な
エフェクトなどのデモを想像するかもしれません。

だけどこの blog では、実際に Direct3D10 を使った描画エンジンを開発しながら
その過程で調べたことやわかったこと、考えたことなどを細々と記録しています。
見た目でわかりやすい部分にはあんまり触れてなくて、非常に地味かつ狭い分野
の話が多いです。

汎用的な描画やデータの流用を考えたエンジンの設計は非常に地味で地道で、
どのようにシェーダーやリソースを管理して、ツールとどう連携するかといった
部分に結構時間を取られます。
派手なのは地道に積み上げてきた各パーツが一気につながった最後の最後
だと思います。

また Direct3D10 の利点は単に「描画性能がすごい」だけでは決してなく、
設計上の自由度が上がって柔軟な設計を許容し、開発者の負担を減らす効果も
あります。

まだ効果よりは、自由度があがったゆえの設計の苦しみに悩んでいるのが実情
なのですが。

D3D10 ConstantBuffer の更新方法

Direct3D10 の Constant Buffer は、レジスタではなくリソースとしての
メモリになりました。作成方法や更新方法など、扱いは VertexBuffer,
IndexBuffer, Texture など他のバッファとほぼ同じです。

ですがシェーダー内部からは従来どおりレジスタとして見えているようで、
何らかの高速にアクセスするための専用のメモリモードやキャッシュを
備えていると考えられます。

Constant Buffer の容量は最大 4096 vecotr で、他の用途に Bind された
バッファと違い上限があります。これもハードウエアデザイン上、キャッシュ
等の特殊な高速アクセスを実現するために必要な制限だったのかもしれません。

余談ですがなぜ 4096 が上限なのか理由を考えてみました。
 ・エンコードされた内部 OP コードで、アドレスフィールドが 12bit だから
 ・128byte (32bit×4) × 4096 = 64KByte だから
 ・Shader で一度にアクセス可能な ConstantBuffer 数は 16個。つまり
  Constant アクセスの命令フィールドは 16bit で、上位4bit がバッファ
  選択に使われている。

どれも想像で根拠はありません。そうえば DirectX3 の Direct3D では
作成可能な ExecuteBuffer のサイズが 64KByte まででした。
(ビデオカードやドライバによる変動はあるかもしれない)
ExecuteBuffer というのは今の PushBuffer のようなもので、当時は
これに直接値を書き込んでコマンドを組み立てていました。

また VertexBuffer ができて、ハードウエア Transform が可能になったあたり
でも 64KByte の壁があったように覚えています。(DirectX7 頃)
その後大きなバッファが作れるようになった DirectX8 でも 32bit IndexBuffer
しか対応していなければ 64K 頂点の上限がありました。
(どれも GPU と driver 依存の可能性があります)

また余談ですが DirectX9 の ShaderModel3.0 で、PixelShader の
Constant Register 数が 224 なのは 32個分他のレジスタにマッピングされて
いるからと考えられます。
i0~i15 と b0~b15 合わせて 32個か、または r0~r31 の分かもしれません。

冒頭で述べたように、Direct3D10 の ConstantBuffer は Shader からはレジスタ
として見えていても CPU からはあくまでメモリです。書き換えるためには他の
バッファと同じように次の2種類の方法を用いる必要があります。

ID3D10Buffer::Map()/Unmap()
ID3D10Device::UpdateSubresource()

Map()/Unmap() は D3D9 のリソースに対する Lock() と同じで、メモリ上の
イメージとして直接アクセスすることができます。

リソースのロックは GPU/CPU の同期を阻害してしまう可能性があります。
D3D10 では更新をスムーズに行うために、USAGE や GPU ACCESS Flag、
CPU ACCESS Flag 等で細かな動作指定が可能です。

ただし ConstantBuffer の場合は基本的にランダムにアクセスされるので、
ストリーム系のバッファと違って

 USAGE_DYNAMIC + CPU_ACCESS_WRITE + MAP_WRITE_NO_OVERWRITE

の組み合わせは使えません。USAGE_DYNAMIC + CPU_ACCESS_WRITE で Map() を
使う場合は必ず全領域書き換えの MAP_WRITE_DISCARD が必要となります。

UpdateSubresource() の方はメモリイメージの直接更新ではなく、PushBuffer
を使ったコマンドの発行に相当します。そのため従来のようなレジスタの更新
は、UpdateSubresource() を使ったほうが動作上近いかもしれません。

ConstantBuffer を書き換える手段をまとめると次の二通りです。

(1) Map()/Unmap() を使う方法
 ・D3D10_USAGE_DYNAMIC
 ・D3D10_CPU_ACCESS_WRITE
 ・D3D10_MAP_WRITE_DISCARD (全領域更新)
 ・ID3D10Buffer::Map()/Unmap()

(2) UpdateSubresource() を使う方法
 ・D3D10_USAGE_DEFAULT
 ・CPU Access Flag は 0
 ・ID3D10Device::UpdateSubresourece()

この辺の動作も、やっぱり ID3D10Effect を使ってシェーダーを使っている分には
全く意識する必要がありません。Constant Buffer の更新は ID3D10Effect 内部で
勝手にやってくれます。

そのせいか、Constant Buffer に絞ったリソースアクセスについては、あまり
ドキュメント化されていないようです。

もっともこの辺の処理が必要になるのは、ID3D10Effect を使わずに自前で
シェーダー用リソースの管理をする場合のみでしょう。
サンプルを流用したデモやシェーダーによる可能性の研究や実験なら使う必要は
無いと思います。