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

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 を使わずに自前で
シェーダー用リソースの管理をする場合のみでしょう。
サンプルを流用したデモやシェーダーによる可能性の研究や実験なら使う必要は
無いと思います。