转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3406

经过前面的练习,实现query已经没什么难的了。

Query heap

在D3D12里,query也是放在heap中。但和其他heap不同的是,query heap与其说是heap,不如说是array。它并不需要设置给设备,而是相当于一次可以提交多个query。Query heap的数量不限,所以为了简单实现,可以给每个query建立一个query heap。

D3D12_QUERY_HEAP_DESC query_heap_desc;
query_heap_desc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION;
query_heap_desc.Count = 1;
query_heap_desc.NodeMask = 0;

ID3D12QueryHeap* query_heap;
TIF(device->CreateQueryHeap(&query_heap_desc, IID_ID3D12QueryHeap,
	reinterpret_cast<void**>(&query_heap)));
query_heap_ = MakeCOMPtr(query_heap);

这就建立出一个occlusion query来了。使用的时候只要

cmd_list->BeginQuery(query_heap_.get(), D3D12_QUERY_TYPE_OCCLUSION, 0);
...
cmd_list->EndQuery(query_heap_.get(), D3D12_QUERY_TYPE_OCCLUSION, 0);

读回结果

在D3D12里,query的结果是放在buffer里,而不是直接存入某个变量。所以,在建立出query heap之后,需要接着建立两个buffer。

D3D12_HEAP_PROPERTIES heap_prop;
heap_prop.Type = D3D12_HEAP_TYPE_DEFAULT;
heap_prop.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_prop.CreationNodeMask = 0;
heap_prop.VisibleNodeMask = 0;
 
D3D12_RESOURCE_DESC res_desc;
res_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
res_desc.Alignment = 0;
res_desc.Width = sizeof(uint64_t);
res_desc.Height = 1;
res_desc.DepthOrArraySize = 1;
res_desc.MipLevels = 1;
res_desc.Format = DXGI_FORMAT_UNKNOWN;
res_desc.SampleDesc.Count = 1;
res_desc.SampleDesc.Quality = 0;
res_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
res_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
 
ID3D12Resource* query_result;
TIF(device->CreateCommittedResource(&heap_prop, D3D12_HEAP_FLAG_NONE,
	&res_desc, D3D12_RESOURCE_STATE_PREDICATION, nullptr,
	IID_ID3D12Resource, reinterpret_cast<void**>(&query_result)));
query_result_ = MakeCOMPtr(query_result);
 
heap_prop.Type = D3D12_HEAP_TYPE_READBACK;
ID3D12Resource* query_result_readback;
TIF(device->CreateCommittedResource(&heap_prop, D3D12_HEAP_FLAG_NONE,
	&res_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
	IID_ID3D12Resource, reinterpret_cast<void**>(&query_result_readback)));
query_result_readback_ = MakeCOMPtr(query_result_readback);

query_result_这个buffer用来从query取回数据,也就是在EndQuery之后

D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = query_result_.get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PREDICATION;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.Subresource = 0;
cmd_list->ResourceBarrier(1, &barrier);

cmd_list->ResolveQueryData(query_heap_.get(), D3D12_QUERY_TYPE_OCCLUSION, 0, 1, query_result_.get(), 0);

barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
cmd_list->ResourceBarrier(1, &barrier);

这个buffer的heap type是default,所以不能被CPU读取。需要在此拷贝到那个query_result_readback_。

cmd_list->CopyResource(query_result_readback_.get(), query_result_.get());
 
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PREDICATION;
cmd_list->ResourceBarrier(1, &barrier);

在从query_result_readback_取出数据之前,需要用fence进行一次同步。D3D11里是用while轮询的方式等query完成,D3D12则变成了fence等待,占用CPU少得多。

总结

D3D12的Query并不难用。在KlayGE里实现了occlusion query、conditional render(也叫binary occlusion query)、以及timer query。由于变成query heap的形式,API调用可以更少。