Direct3D 10 Buffer Rename を検出してみる

Buffer への書き込みに DISCARD + Map() を使うことで、ドライバが
バッファの Rename を行うことができます。つまりアプリケーション
側では同一のバッファのつもりでも、実際はぜんぜん違うメモリに
書き込んでいたりするわけです。
これは最適化のためで、フレームバッファと同じようにドライバ側で
勝手に SwapChain 化してくれるようなもの。

その代わり Rename 数の上限によってバッファが枯渇する可能性が
あるそうです。(関連は下記エントリ)
Gamefest Japan 2007 (2) Direct3D 10

この Rename の状況を軽く調べてみました。

●同じバッファが割り当てられているかどうか確認する方法

(1) Map() した時のアドレス値 (*ppData の値) を比較する。
(2) D3D10_MAP_WRITE_DISCARD かつ D3D10_CPU_ACCESS_WRITEのみ
  であるにもかかわらず、敢えて前回書き込んだ値を読み出してみる。

(1) だけでも十分かもしれませんが念のため (2) も調べました。

●条件

ConstantBuffer を作成してみます。Map() でアクセスするには
下記の指定が必要です。

 ・D3D10_USAGE_DYNAMIC
 ・D3D10_CPU_ACCESS_WRITE
 ・D3D10_BIND_CONSTANT_BUFFER

使用した GPU は GeForce8800GTS です。

●未使用バッファ 16byte

16byte のバッファを作って、毎フレーム Map() で書き込んでみました。
このバッファは特に ~SetConstantBuffer() していないので、Busy に
なることはないはずです。

左から
・アドレス
・Bufferサイズ
・これから書き込む値
・書き込み前に読み出したメモリの値
と並んでいます。

addr     size    write     read
073eb400 16byte (00000000) 00000000
073eb800 16byte (00000001) 00000000
073eba00 16byte (00000002) 00000000
073ebc00 16byte (00000003) 00000000
073ebe00 16byte (00000004) 00000000
073ec000 16byte (00000005) 00000000
073ec200 16byte (00000006) 00000000
073ec400 16byte (00000007) 00000000 (A)
073ec600 16byte (00000008) 00000000
~
073f4600 16byte (00000042) 00000000
073f4800 16byte (00000043) 00000000
073ec400 16byte (00000044) 00000007 (B) 一周 (A)で書き込んだ 7
073ec600 16byte (00000045) 00000008
~
073f4600 16byte (0000007f) 00000042
073f4800 16byte (00000080) 00000043
073ec400 16byte (00000081) 00000044 (C) 一周 (B)で書き込んだ 44
073ec600 16byte (00000082) 00000045
073ec800 16byte (00000083) 00000046
~

アドレスは 512byte 単位で割り振られています。60個程度のバッファが
順次割り当てられ、繰り返し利用されているように見えます。
未使用バッファでも毎回違うアドレスになるのは予想外でした。

●未使用バッファ 512byte

512byte のバッファではずれが小さくなっています。どちらにしろ
未使用バッファなのでたまたまかもしれません。

addr     size     write     read
0727b600 512byte (00000000) 00000000
0727b800 512byte (00000001) 00000000
0727b600 512byte (00000002) 00000000
0727b800 512byte (00000003) 00000001
0727b600 512byte (00000004) 00000002
~

●実バッファ 1088byte

実際に ~SetConstantBuffer() してシェーダー参照されている
描画に使っているバッファを見てみます。

    addr     size   write read
*L1 0754b000 1088byte (0) 0
*L1 0754b000 1088byte (1) 1
*L1 0754b000 1088byte (2) 2
*L1 0754b000 1088byte (3) 3
*L1 0754b000 1088byte (4) 4
~

ここでの write は、1つ前のフレームで書き込んだ値です。
アドレスも、前回書き込んだ値も一致しています。
同じバッファが割り当てられています。
このプログラムは厳密に CPU と GPU の同期を管理しているため、
次の Map() までに描画が完了してしまっている可能性があります。

●実バッファ 1088byte ×2

1フレーム中、同じバッファに2度転送を行ってみました。
L0/L1 のペアで1フレームです。

    addr     size   write read
*L0 0735b000 1088byte (0) 0
*L1 0735b500 1088byte (1) 0
*L0 0735b000 1088byte (2) 1
*L1 0735b500 1088byte (3) 2
*L0 0735b000 1088byte (4) 3
*L1 0735b500 1088byte (5) 4
~

今度はそれぞれ異なるバッファが割り当てられています。
参照されている Buffer は実際の描画が完了するまで Busy に
なり、アクセスから保護されている様子がわかります。
やっと予想した動作が見えてきました。

●実バッファ 1088byte ×20

今度は1フレーム中、同じバッファに20回書き込みます。

    addr     size   write read
*L0 0719b000 1088byte (0) 0
*L1 0719b500 1088byte (1) 0
*L2 0719ba00 1088byte (2) 0
~
*L17 071a0500 1088byte (17) 0
*L18 071a0a00 1088byte (18) 0
*L19 071a0f00 1088byte (19) 0
*L0 0719b000 1088byte (20) 1
*L1 0719b500 1088byte (21) 2
*L2 0719ba00 1088byte (22) 3
~

Rename の様子が良くわかります。
ちなみに各 Map() と Map() の間に Draw を挟んでいないので、
本来は最後の Map() 以外は破棄してかまわない状況です。
今のところ Map( DISCARD ) を実行した数分だけバッファが
割り当てられているようです。
Busy の情報はリネーム元のバッファ1つ分しか持っていない
のでしょうか。

●実バッファ 1088byte ×20000

2000回では変化無かったので、一気に 20000回行きます。

*L0 071fb000 1088byte (0) 0
*L1 071fb500 1088byte (1) 0
~
*L3916 0aee5c00 1088byte (3916) 0
*L3917 0aee6100 1088byte (3917) 0
*L3918 071fb000 1088byte (3918) 1
*L3919 071fb500 1088byte (3919) 2
*L3920 071fba00 1088byte (3920) 3
~

20000回を待たずに再利用されています。
やっぱり本当は使っていないことがばれているのかもしれません。
ここまでで考えられるバッファサイズはおよそ 8Mbyte でしょうか。
動きは見えましたが、完全に捕まえるためにはきちんとテスト
プログラムを作った方がよさそうです。ちょっと試したくらいでは
まだわからないですね。