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

上一篇把完成了一个最基本的D3D12程序,画一个三角形。同时我也说了,没有回头路。本篇将开始从11on12转向纯D3D12。

上一篇我们的假设假设是最基本的系统,关掉所有post process、UI、文字,就渲染一个三角形。这样的系统至少需要一个vertex buffer、一个rtv、一个vs、一个ps、一次clear、一次draw call。进一步的发展需要一个稍微复杂的系统,有文字和UI。也就是还需要一个index buffer、一个cbv、一个srv、一个sampler。Index buffer和vertex buffer的构建没区别,所以就是cbv、srv和sampler的事情。

在此之前,需要先介绍两个D3D12的概念,heap和root signature。因为CBV/SRV/UAV/Sampler都需要依赖于这两个。

Heap

Heap是D3D12新增的概念。上一篇讲了RTV的heap。与RTV不同的是,CBV/SRV/UAV/Sampler的heap可以建立多个,并通过SetDescroptorHeap进行设置。

D3D12_DESCRIPTOR_HEAP_DESC cbv_srv_heap_desc;
cbv_srv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbv_srv_heap_desc.NumDescriptors = static_cast<UINT>(num_srv + num_uav);
cbv_srv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbv_srv_heap_desc.NodeMask = 0;
ID3D12DescriptorHeap* csu_heap;
d3d_12_device_->CreateDescriptorHeap(&cbv_srv_heap_desc, IID_ID3D12DescriptorHeap, reinterpret_cast<void**>(&csu_heap));

不管是CBV、SRV还是UAV,都可以把handle放到heap里。如此一来,只要通过设置heap就能一次切换一批CBV/SRV/UAV/Sampler,而不需要一个一个设置,降低了CPU开销。就好像普通程序里常见指针数组。

D3D12 Heap

Root signature

Root signature表示的是CBV/SRV/UAV/Sampler的布局。比如一组VS/PS,VS用了2个CBV,PS用了1个CBV、2个SRV、1个Sampler,这样的布局就能生成一个专用的root signature。root signature和view的值无关,只和布局有关。所以其实是可以共享的。

View

有了heap和root signature之后,就能开始建立具体的view了。

CBV

在D3D11里,constant buffer是通过VSSetConstantBuffers等,把buffer设置给shader。在D3D12里,新的方法是通过CreateConstantBufferView把资源放入heap。需要注意的是,目前在KlayGE里为了简化起见,每一次Draw的时候都用Create*View建立起的view。虽然非常慢,但能在开发前期进行快速推进。

在放入view之前,可以用SetGraphicsRootDescriptorTable把root signature和一组view关联起来。

SRV

SRV也是类似,只是建立的view函数名为CreateShaderResourceView。目前SRV也是每一次draw的时候临时建立。

Sampler

Sampler有点不同,它需要放在独立的sampler heap里,而且因为KlayGE里的sampler都是静态的,就只要在载入shader的时候建立一次sampler heap和所有sampler,runtime的时候只要SetGraphicsRootDescriptorTable一次即可。

组装起来

简单地把这些代码都堆进去,可以看到有texture了,但似乎都索引到同一张texture。

D3D12 triangle texture

经过细致排查,发现问题出在heap。在渲染的过程中,CPU和GPU是异步执行的。如果一个heap还没被GPU draw用完,其内容就已经在后来draw里新建了view,结果就是用新的view。也就是说,程序需要自己负责同步heap和draw。目前KlayGE里的简化做法是,每一次draw之间不但建立新的view,还用CreateDescriptorHeap建立新的heap。在每一帧结束之后,CPU/GPU需要一次同步,这时候把这帧积累的所有heap和pso等都清理掉。这么一来,速度更慢了,但结果正确。下图还顺便打开了tessellation:

D3D12 Triangle Tessellation

本篇把渲染所需的基本组件都转移到了纯D3D12,以后会进一步扩充,一支持更多的功能。