2023年3月26日日曜日

Readback Heap

今までCPUのメモリからVRAMへ一方通行の使い方しかしていなかった。
Heap管理をまとめて、ReadbackのHeapも用意できるようになったので使えるようにしてみる。


Heapの種類


Heapの種類にはUpload、Default、Readback、Customと4つ定義されている。
Customは使うつもりがないので対象は3つ。

CPUのメモリと各種Heapの関係

CPUのメモリと各種Heapの関係はこんな感じ。
それぞれのHeapに利用用途に合わせて各種Viewを付けて、GPUから参照する。

Upload Heap


Uploadは書き込み専用のポインタをMapして、CPU側からデータを直接書き込める。ただし遅いので常時使うのは小さな定数バッファぐらい。また、Default HeapはCPU側から直接扱えないので、データをコピーするために一時的にUploadを経由させるために使う。


Default Heap


GPUが一番効率よくメモリを使える場所。
CPU側からは直接参照できない。


Readback Heap


Readbackは読込専用のポインタをMapして、CPU側からデータを直接参照できる。

Default HeapをReadback経由で参照する場合、まずデータをコピーする。
Uploadでデータをコピーする際、コピー先のリソースのステータスはD3D12_RESOURCE_STATE_COPY_DESTだったがReadbackでデータをコピーする際は、コピー元のリソースのステータスはD3D12_RESOURCE_STATE_COPY_SOURCEにする必要がある。
コピーの完了を待って、Mapで読み取り用のポインタを取得する。
このときD3D12_RANGEを渡すが、Uploadの場合0,0で初期化していた。これはCPU側から読み取らない宣言だったわけだけど、Readbackの場合は読み取るので、読み取るオフセットを指定する。


UAV経由でデータを書き換える


GPUでデータを書き換える代表格はレンダーターゲットや深度バッファになるけど、Compute Shaderで計算結果を書き込むのはUnordered Access Viewを使うことになる。その時のデータ型にもいろいろ種類があるが、ここでは2つで例を挙げる。

RWStructuredBuffer


StructuredBufferの書き込める版。
データソースとしてStructuredBufferで渡されたデータをCompute Shaderで、計算した結果を同じインデックスのRWStructuredBufferに書き込むといったような使い方をする。

AppendStructuredBuffer


Appendはキューのようなデータ構造で、スレッドの処理が完了した順に追加していく。例えばカリングを行って対象データのみ追加する場合、処理後のデータ数がデータ元と異なる。
その変わったサイズを管理するのがUAV Counter。そのためAppendを使う場合UAVにはカウンタが必須になる。

UAV Counterとは


以前UAVのことについて書いたが、このときはUAVカウンタのことを理解していなかった。
このカウンタはUAVにオプションで付けることが出来て、RWStructuredBufferの場合、IncrementCounter、DecrementCounter関数、AppendStructuredBufferの場合、Append関数を使うとカウントが制御される。
バッファに入っているデータ数を管理するカウンタとして利用できるということがわかった。
そのため、データを書き込む前には一旦0にリセットする必要がある。

ExecuteIndirectを使う場合、Readback不要でシェーダで処理したUAVリソースとカウンタのオフセットをそのまま渡すようなI/Fになっている)


#define DefRS "RootFlags(DENY_VERTEX_SHADER_ROOT_ACCESS|DENY_HULL_SHADER_ROOT_ACCESS|DENY_DOMAIN_SHADER_ROOT_ACCESS|DENY_GEOMETRY_SHADER_ROOT_ACCESS|DENY_PIXEL_SHADER_ROOT_ACCESS),DescriptorTable( SRV(t0, flags=DATA_STATIC_WHILE_SET_AT_EXECUTE),visibility=SHADER_VISIBILITY_ALL),DescriptorTable( UAV(u0, flags=DATA_VOLATILE),visibility=SHADER_VISIBILITY_ALL)"

StructuredBuffer<uint> Src : register(t0) ;
AppendStructuredBuffer<uint> Dst : register(u0) ;

struct CSInput
{
	uint3 ID : SV_DispatchThreadID ;
} ;

[RootSignature(DefRS)] 
[numthreads( 8, 1, 1 )]
void CSMain( CSInput In )
{
	int i = In.ID.x ;
	if( Src[i] > 30 ) {
		Dst.Append( Src[i]) ;
	}
	return ;
}

試しに作ったサンプルでは、Srcで渡した点数データをDstにAppendしていく。
その際、30点以下の場合は除外するようにした。

100、80、60、50、40、30、20、10の8個のデータをSrcで渡すと
Dstで100、80、60、50、40の5個のデータと、カウンタには5という値が渡る。

Src

Dst

Counter

Readback経由でCounterを取得して、その数分Dstの先頭から値を取得するとGPUで処理した結果が受け取れるようになった。
これでGPUの処理結果を受け取れるようになったので、単純で大量の計算をGPUに任せることも出来る。


今回のハマりポイント


データ元を構造化バッファにしていたけど、最初は定数バッファで試していた。
定数バッファの場合、全体を配列で使えないので構造体内に配列を定義した。

struct _Src
	uint Val[8] ;
} ;

ConstantBuffer<_Src> Src : register(b0) ;
AppendStructuredBuffer<uint> Dst : register(u0) ;

struct CSInput
{
	uint3 ID : SV_DispatchThreadID ;
} ;

[RootSignature(DefRS)] 
[numthreads( 8, 1, 1 )]
void CSMain( CSInput In )
{
	int i = In.ID.x ;
	if( Src.Val[i] > 30 ) {
		Dst.Append( Src.Val[i]) ;
	}
	return ;
}

C++側からは、バイナリで4バイトずつ8つ(32バイト)のデータを書き込んだ。
PIXで確認してもきちんとデータが入っている。
にも関わらず出力結果は構造化バッファと同じにならず、2件、100と40が返却された。

DXILの_Src構造体のサイズ情報が116となっており、32バイトではない。
どうやら配列の1要素単位で16バイトアライメントになっているみたいで、最後の項目だけ4バイトと考えるとサイズが一致する。

試しに渡すバイナリを16バイト単位にすると、同じ結果が得られた。
PIX上のデータには0、4に値が入っていて、1,2,3,5,6,7には値が入っていないように見えるが、シェーダは結果は正しい値になっている。
BufferFormatが実際のデータと合っていないっぽい。

struct _Src
	uint4 Val[2] ;
} ;

ConstantBuffer<_Src> Src : register(b0) ;
AppendStructuredBuffer<uint> Dst : register(u0) ;

struct CSInput
{
	uint3 ID : SV_DispatchThreadID ;
} ;

[RootSignature(DefRS)] 
[numthreads( 8, 1, 1 )]
void CSMain( CSInput In )
{
	int i = In.ID.x / 4 ;
	int j = In.ID.x % 4 ;
	if( Src.Val[i][j] > 30 ) {
		Dst.Append( Src.Val[i][j]) ;
	}
	return ;
}

定義をuint4に変更して、渡すデータも4バイト単位に戻して実行したらうまく行った。

バイナリデータとアライメントが間違っていただけだった。
配列で定義すると、1要素単位で16バイトアライメントになる。
配列にせず、uintを8つ変数定義すると期待通り4バイトアライメントになる。floatなども同じ。



2023年3月24日金曜日

Heap Allocator

前回、リソースのアライメントが64KBという衝撃を受けた。

中間レンダーターゲットや、深度バッファなどはある程度大きいデータなので、64KBというアライメントでもそんなに問題ない。

問題ありなのは定数バッファだ。
例えばメッシュのマテリアル情報は現在52バイト利用している。
今サンプルで使ってるデータは1メッシュに付き6つのマテリアル情報を持っていて、実質のデータサイズは52×6=312バイトなのに対して、64KBアライメントだとするとVRAMは64KB×6=384KBも消費していることになる。

今まで定数バッファのアライメントは256バイトだと思っていて、52バイトだけど256に補正して無駄な領域ができてるなぁと思っていたけど、そんなレベルではなかった。
256のアライメントは、CBVで連続したメモリを配置する際のアライメントだ。

リソースの管理の仕方が根本的に間違っているっぽい。


Bufferのアライメント


小さいテクスチャは条件を満たすと4KBアライメントにすることが出来る。
Bufferは無条件で64KBになってしまうと書かれていたが、本当なのか?

QueryVideoMemoryInfo関数を使うと、利用しているVRAMのサイズを調べることが出来るので実験をしてみた。

サイズを取得した後に、CreateHeapを呼んで、再度取得して増えたサイズを確認してみる。

サイズを64KBで作成すると、消費は64KB。
4KBで作成すると、消費は64KB。
8KBで作成すると、消費は64KB。ここまでは予想通り。
問題はこの後。
64KB+1バイトで作成すると、消費は64KB+4KB。
4KB+1バイトで作成すると、消費は4KB+4KB。
4KB-1バイトで作成すると、消費は4KB。
8KB+1バイトで作成すると、消費は8KB+4KB。
8KB-1バイトで作成すると、消費は8KB。

4KB、8KBピッタリの場合の挙動は謎だけど、それ以外は4KB単位で確保されるっぽい。これは確保されるサイズであってアライメントとは別なのか?
ということでマテリアル情報の定数バッファは384KBまでは使っておらず、4KB×6=24KBで済んでるということになる。でも大きい。


1つのHeapに複数の定数バッファをまとめる


1つのHeapに対して、複数のリソースを配置できるっぽいのでこれを利用すれば定数バッファの問題は解決するのではないか?
ある程度のサイズのHeapを確保して、CreatePlacedResourceで256バイトずつずらしてリソースを作成しようとしたところ、1つ目は問題ないが2つ目からエラーが発生する。

D3D12 ERROR: ID3D12Device::CreatePlacedResource: This resource cannot be created on this heap, due to unsatisfied resource alignment requirements. The resource must be aligned to 65536. The heap must also be aligned to a value greater to or equal than the resource. The heap is aligned to 65536, and the resource offset in the heap is 256. [ STATE_CREATION ERROR #638: CREATERESOURCEANDHEAP_INVALIDHEAPPROPERTIES]

ヒープのアライメントは64KBで、256のオフセット単位ではリソースは作れないらしい。
ダメ元でオフセットを4KBにしてみたがやっぱりダメ、CreateHeapのD3D12_HEAP_DESCのAlignmentに4KBを設定してもエラーになる。実質の消費サイズが4KBになっているにも関わらず、0以外を設定すると怒られる模様。

次に考えたのが、1つのHeapで複数のCBV。
これはすでに必要サイズを256単位に補正したサイズ×フレーム数分のサイズを確保したHeapに対してフレーム数分のCBVを用意して、フレーム毎に参照するCBVを変えて更新していくということをやっているので実績あり。


Heap管理



ヒープの管理戦略にはいくつか方法があるみたいだ。
そのなかに「何も管理しない」というものがあり、それが今の実装だった。
CreateCommittedResourceを使って、一切Heap管理をしない方法。
サンプルプログラムやプログラムの説明に用いる場合、かなり便利でその範囲ではまともに動作する。

これが製品レベルのプログラムになるとそうもいかないらしい。


Segregated free listsとかBuddy systemとか、名前がついたアルゴリズムがあるみたいだけど、今回実装したのはSegregated free listsに近いのか?

Heapの利用用途で作成時にフラグを指定するが、自分の使い方として下記の5つに分類した。

Upload Buffer


D3D12_HEAP_TYPE_UPLOADで作る。用途は定数バッファやDefaultバッファのコピー用。

Readback Buffer


D3D12_HEAP_TYPE_READBACKで作る。用途はComputeShaderの結果受け取り用。

Default Buffer


D3D12_HEAP_TYPE_DEFAULTで作る。用途はマテリアルなどの定数バッファ。

Default SRV


D3D12_HEAP_TYPE_DEFAULTで作る。用途はテクスチャや構造化バッファ。

Default RTDS


D3D12_HEAP_TYPE_DEFAULTで作る。用途はレンダーターゲット、深度バッファ。


確保したいサイズを64KB単位に補正して、ツリーを検索。見つからない場合は新しくHeapを確保する。このとき、大きいサイズが余っていても使わない。ぴったり一致するサイズが余ってる場合だけ利用する。

開放時は確保サイズをキーとしてツリーに戻す。
ツリーは64KB単位のサイズキーで構成され、似たようなサイズを取り扱うだろうという想定の割り切り仕様。分割もマージもしないので管理上の断片化はしない。
欠点はレアなサイズを利用すると、確保したまま再利用されなくなる。
シーンの切り替え処理の最後で使ってないHeapの開放など入れれば解決できそう。


4KBアライメントテクスチャ対応


前回の記事で扱った4KBアライメントのリソースは、1つのHeapに対して複数のリソースを載せる。
サイズをキー、値にオフセットをツリー管理する。指定サイズギリギリ上回るサイズから切り取って、サイズを減らし、オフセットをずらす。
開放時は、元のオフセットと確保サイズを元にマージできるノードを検索。見つかった場合はマージして再帰する。マージできない場合は単純に追加する。


定数バッファリソース対応


冒頭の定数バッファ用のリソース確保の仕組み。
64KBのHeapを確保&256分割して、1Heapに付き256スロット用意する。
スロット数をキー、値にオフセットをツリー管理する。
確保する際は256バイト単位に補正して、256で割った数が必要スロット数。
必要なスロット数をギリギリ上回るノードを探して、スロット数を減らして、オフセットをずらす。
リソース自体は同じものを参照して、オフセットを元にCBVを用意する。
開放時は、元のオフセットと確保スロット数を元にマージできるノードを検索。見つかった場合はマージして再帰する。マージできない場合は単純に追加する。


Aliasingバリア


マイクロソフトのリファレンスにはCreatePlacedResourceで出来たリソースは非アクティブ状態なので、Aliasingバリアによってアクティブ化する必要があると書いてある。
今回の対応により、すべてのCreateCommittedResourceをCreatePlacedResourceに置き換えたが、Aliasingバリアは未使用。DebugLayerにも何も表示されていない。
最初はリソースを重ねなければ、バリア不要かと思っていたけど、マイクロソフトのSmallResourceのサンプルでは、重ならない小さな4KBアライメントテクスチャをAliasingバリアしている。サンプルのバリア部分をコメントにしても問題なく動く。

具体的な問題が出るまでは、このまま行ってみる。


2023年3月13日月曜日

小さいサイズのテクスチャ

DirectX11では問題なく読み込めていたはずだけど、DirectX12になって64×64未満のサイズのテクスチャを作ろうとするとエラーになる。

D3D12 ERROR: ID3D12CommandList::CopyTextureRegion: The region specified by D3D12_TEXTURE_COPY_LOCATION:PlacedFootprint extends past the end of the buffer it is placed on. The size required by PlacedFootprint is 8064, as the fields of PlacedFootprint::Placement are as follows: RowPitch is 256, Height is 32, and Format is R8G8B8A8_UNORM. PlacedFootprint::Offset is 0, which requires the buffer to have 8064 bytes; but the buffer only has 4096 bytes. [ RESOURCE_MANIPULATION ERROR #869: COPYTEXTUREREGION_INVALIDSRCPLACEMENT]

上記は32×32のテクスチャを読み込んでUploadのリソースから、DefaultのリソースにCopyTextureRegionをしようとしたところで怒られている。

今までDirectX12では読み込めないのかと思って、一度GDI+上で64×64に拡大してからテクスチャに使っていた。

今回の件とは関係ない調べ物をしていたときこんなサイトを見つけた。
そこからたどれるリンク先も含めて読むと衝撃的な内容とともに、いままでCreateCommittedResourceを使ってきたけど、実はCreatePlacedResourceの方が一般的で、CreateCommittedResourceは手抜きだと言うことがわかった。
CreateHeapを使って、リソースと紐づけるというのを自動的にCommittedはやってくれていたらしい。


4KB Alignment Resource


小さいサイズリソースをそのままCreateCommittedResourceに任せると64KB単位のリソースになってしまう。例えば4KB未満しかないリソースなのに64KBの領域が確保されてしまう。これはもったいない。
しかし、ある条件を満たしている場合4KB単位のリソースを作ることが出来るみたいだ。

マイクロソフトのサンプルではGetResourceAllocationInfoを使ってその条件を満たしているかを確認して処理を切り替えている。
ただし条件を満たしていない場合、D3D12 Debug Layerエラーが出力される。

D3D12 ERROR: ID3D12Device::GetResourceAllocationInfo: D3D12_RESOURCE_DESC::Alignment is invalid. The value is 4096. Resources with D3D12_RESOURCE_DESC::Flags with either D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET or D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL must set Alignment equal to 65536 (aka. D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), or 0. [ STATE_CREATION ERROR #721: CREATERESOURCE_INVALIDALIGNMENT]

D3D12 ERROR: ID3D12Device::GetResourceAllocationInfo: D3D12_RESOURCE_DESC::Alignment cannot be 4KB, since D3D12_RESOURCE_DESC::Width, D3D12_RESOURCE_DESC::Height, and/ or D3D12_RESOURCE_DESC::DepthOrArraySize are too large. 4KB alignment requires the most detailed mip level be theoretically 64KB or smaller. A 4KB tile shape for D3D12_RESOURCE_DIMENSION_TEXTURE2D and R8G8B8A8_UNORM is 32 texels wide, 32 texels high, and 1 texels deep. When Width = 256, Height = 256, and Depth = 1, the number of tiles needed is 64, while 16 tiles is the maximum. [ STATE_CREATION ERROR #721: CREATERESOURCE_INVALIDALIGNMENT]

1つ目のエラーはレンダーターゲット、深度バッファの場合対象外。
2つ目のエラーはサイズの問題。
ピクセルと幅と高さから必要なサイズが一定以下の場合使えるらしい。

チェック関数として利用したいのにエラーが出てしまうのでこれは却下。
サンプルではチェックに通るデータのみで利用しているのでエラーが出てない。
仕方がないので自前でチェックするようにした。

チェックに通った場合、今までのCreateCommittedResourceではなく、CreateHeap & CreatePlacedResourceでリソースを用意する。
このときCreatePlacedResourceに渡すD3D12_RESOURCE_DESCのAlignmentにD3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENTをセットしてやると4KBアライメントのリソースが作れる。

最後に、今まで使っていなかった新しいリソースバリア(D3D12_RESOURCE_BARRIER_TYPE_ALIASING)を使ってアクティブ化した後、Clear、Discard、Copyのいずれかを行うと初期化したことになるらしい。
Heap領域を単純にAliasingで切り替えたとしてもメタデータの準備が整っておらず最悪GPUがクラッシュするので、利用目的にあったリソースの初期化が必要。


Clear


レンダーターゲットの場合ClearRenderTargetView関数、深度バッファの場合ClearDepthStencilView関数を呼ぶことになる。


Discard


DiscardResource関数を呼ぶとリソースのメタデータが再初期化される。
この後全体を描画することがわかっている場合、Clearよりも高速に動作するみたい。

Copy


CopyResource関数、CopyBufferRegion関数、CopyTextureRegion関数でリソース全体をコピーする。


テクスチャのコピー


冒頭のエラーについては、エラー自体を回避するだけならUpload用のリソースのサイズを8064バイト用意すればいいだけだった。
ただ、元データは4096しかないし、FootPrintのRowPitchは128のハズなのに256になっている。データ全体をそのままコピーしても上半分しか埋まってなかった。

FootPrintのRowPitchに合わせてデータをコピーする必要があるようで、元データとRowPitchが一致してない場合、1行ずつ書き込んでやることにした。

	tNum nByte = DirectX::BitsPerPixel( nFormat ) / 8 ;
	tNum nSrcRowPitch = oDesc.Width * nByte ;
	tNum nDstRowPitch = oFP.Footprint.RowPitch ;
	if( nDstRowPitch == nSrcRowPitch ) {
		memcpy( pMap, oImg.Ptr(), oImg.Count()) ;
	} else {
		for( tNum y = 0 ; y < oDesc.Height ; y++ ) {
			memcpy( pMap + nDstRowPitch * y, oImg.Ptr() + nSrcRowPitch * y, nSrcRowPitch ) ;
		}
	}

8064という数字も最初意味が分からなかったけど、最終行が128バイト分不要なのでその分少なくなっていると理解した。

この処理によって、今まで64以下の場合と2のべき乗で無いサイズのテクスチャを事前にGDI+で拡大してから処理していたのが不要になった。




2023年3月5日日曜日

リソース管理リファクタリング

今まで何回かリソースに追加、修正を加えてきていろいろなリソースが増えてきた。

なにか新しくリソースを増やす場合、リソース自体の内部構造、横断的に利用している部分の修正など複雑になってしまっている。

一旦データをすべて整理して、もっと汎用的にあまりなんのリソースかを意識しなくてもいいように作れないかを考えた。


リソースのデータの構造


CreateCommittedResourceで確保する実体のバッファをベースに、そのバッファをどう参照するかの各種Viewで構成されている。
1つだけではなくフレーム数分用意する場合もある。
各種Viewは今のところ下記の6つ。

読み込み

・Shader Resource View(SRV)
・Constant Buffer View(CBV)
・SaMPler(SMP)

書き込み

・Unordered Access View(UAV)
・Render Target View(RTV)
・Depth Stencil View(DSV)

SRV、CBV、SMPは読み込み用で、UAV、RTV、DSVは書き込み用のView。
例えば中間レンダーターゲットは1つのバッファに対して2つのViewを持ち、レンダリングするときにはRTV、テクスチャとして参照するときにはSRVを利用する。

Viewはスロットとして、読み込み用、書き込み用の2スロット持てるようにする。
(今までは1スロットのみで管理していて、拡張データに別スロットを持つ形で対応していた。そのため、使う場面でリソースのタイプをみてどっちのスロットを使うのかなどの分岐が生まれて複雑になる要因だった)

リソースを使う場面で、I/Oを指定出来るようにしてどちらのスロットを参照するか汎用的に処理するようにした。
これにより2つのスロット(View)を持つ中間レンダーターゲットの場合でもリソースタイプを参照せずに、引数のI/Oでどちらのスロットを利用するのか判断ができシンプルになった。
リソースタイプが増えても、この部分にソースの修正は不要になる。

フレーム依存のリソースの場合、バッファをフレーム数分持つことになる。
これも可変配列で管理して汎用的にした。
定数バッファと読み取り専用の定数バッファがいままではリソースタイプとして分かれ個別に管理していた。定数バッファはフレーム依存でスロットを2つ持つ。読み取り専用の方はデータが書き換わらないのでスロットは1つのみ。
フレーム依存のフラグを持たせて、依存するリソースの場合内部的に2つバッファがあるので、今のバックバッファインデックスに従って参照するバッファを制御する。ブレームに依存しない場合は、インデックス0を参照する。

上記のような修正の結果、リソース対応は大幅に減って下記になった。

・頂点バッファ
・インデックスバッファ
・構造化バッファ
・定数バッファ
・テクスチャ
・レンダーターゲット
・中間レンダーターゲット
・深度バッファ
・サンプラ



フレーム依存リソース


今回の修正で同じリソースタイプのフレーム依存、独立バージョンを簡単に用意することが出来るようになった。
前からいまいち納得が出来ていないフレーム依存のリソースを本当に2つ用意しないと行けないのか?1つだけにして実行するとどうなるのか試してみた。
定数バッファ(CBV)、構造化バッファ(SRV)を1つ分に減らして実行してみると、結果は何も変わらない。

両方ともD3D12_HEAP_TYPE_UPLOADで、mapしっぱなしのリソースで、マップで得られたポインタにデータを毎フレーム書き込む。
書き込んだ時点でGPUにUploadされて、その後呼び出すShaderで値が参照できているとすれば、フレーム毎にバッファを用意する必要はないはず。


前回やった視錐台カリングで、よく見るとオブジェクトが消えるとき別のオブジェクトがちらつく現象が起こっていた。

ログを出しながら確認したり色々していく内、PIXで複数フレームキャプチャして原因を突き止めた。

フレーム0

フレーム1

フレーム2

フレーム1で書かれるべきマップのオブジェクトが消えてしまっている。
構造化バッファで渡しているWorld Matrixのデータがおかしくなっている可能性があるので確認してみる。

フレーム0、1のWorld Matrix

フレーム2のWorld Matrix

同じ3つのマップオブジェクトをX座標をずらして表示している。
World Matrixのデータはフレーム2の時点で0番目のオブジェクトがカリングされて、1番目と2番目のデータがずれて、0,1の部分に反映されている。

フレーム0のDrawコール

フレーム1,2のDrawコール

Drawコールを見てみると、フレーム0がインスタンス数3で、フレーム1,2がインスタンス数が2で指定されている。
ということはWorld Matrixがまだ反映されていないのでフレーム1はインスタンス数3で呼ぶ必要があったと言うことだ。

CPU側の判定としてはフレーム1の時点でオブジェクト0番がカリング対象になって、オブジェクト1、2番だけが表示される状態になった。
構造化バッファに2つ分書き込みを行ったが実際にはまだ反映はされておらず、Drawコールのインスタンス数だけが最新状態で指定してしまっている。

修正は表示されるオブジェクト数自体もフレーム依存データのようにフレーム数分保持するようにして、ワンテンポ遅れさせることで解決した。


このことからUploadでは即座に反映されず次のフレームになることは確認できたが、1つ分のバッファに減らしても動作が変わらない疑問が残る。
テストプログラムで1フレームに殆ど時間が掛かっていないから、問題が起きていないだけなのか?上記の理屈からすると、1つしかバッファがないので次フレームで 実際にバッファを参照する前に書き換えてしまうと結果がおかしくなる。
Uploadする際のCPU側のメモリと、GPU側のメモリの2つがあるので今はその反映タイミングに助けられているだけなのかと推測する。