<span style="font-size:12px;"> 在上半年举办的GDC2014大会上,微软提供了一个DirectX12的Preview,同时通过这个preview和稍后的Direct3D 12的API介绍,可以大概看出D3D API所采用的新的tips和technique。主要有以下几点,供大家参考:</span>
<span style="font-size:12px;">一、采用Pipeline State Object(PSO)</span>
在D3D11及其以前的版本,图形渲染Pipelin中的各个stage都是可以分开设定的,这意味着driver管理这些对应于各stage的binding resource,跟踪这些resource的state和lifetime,并在APP进行Get*命令时提供正在pipeline中的State。这对CPU来说是个巨大的开销,说成是整个D3D的瓶颈也不为过。
新的D3D12则采用了一种新的PSO的方式,即将整个pileline stages 整合成一个stage
这样做的好处在于仅仅需要设置一次PSO的state,就可以将整个pipeline的状态设置完成。表面上这有点返回到Fix-Function的时代,不过实质上这是一个很聪明的选择。根据Max McMullen(chief of Direct3D development team of MS)的介绍,在游戏开发和其他应用程序中,通常stage仅有12种组合,也就是说, 通常IA+VS+Rast+PS是一种组合A,而A+tessellation又是一种组合B,一次类推。因此在设置PSO的状态的时候,也顺带将12种state 组合的硬件状态也设置了,这将带来非常大的好处。
在硬件条件下,APP的每一次改动对将对应着HW state的reprogram,以及resource的rebinding。而这12种combine state则充分涵盖了绝大部分的app修改,因此不再需要HW的reprogram,不再需要resource的rebinding,只需要更换一个hw state就可以了。由此,CPU的开销将小很多,可以说PSO是D3D12的一个极其重要的tip。在后续介绍的AMD Mantle和Apple Metal中依旧会看到PSO或者换来个马甲的PSO。
二、Descriptor Table & Heaps
resource binding和switch binding总是一个非常头疼的问题,binding问题已经成为了程序开发人员和架构师最需要考虑的问题。传统方法是: resource想要bind到pipeline,需要创建一个device和一个device context。 而采用HLSL编写的shader需要调用资源时,则需要通过自身的slot和context进行绑定,相当于(本来也是)slot成为resource的传递通道。这带来的问题是,如果frame A和frame B之间享有共同的resource,那该怎么做,如何提高CPU的效率。
传统的方法是:frame A release binding,frame B 重新bingding,然后通过driver将binding传递给GPU,即使相同的resource也是如此。D3D12当然不会干这种费力不讨好的事情,相对于binding—submit—release—binding,D12采用Descriptor的方式,将resource采用index的方式映射到Descriptor中,比如Vertex映射到Descriptor A1, Vertex Index映射到Descriptor A2中, Texture映射到Descriptor B1中。则A1, A2, B1等的集合就是heap,而GPU中需要采用哪些Descriptor,比如A1,A2而不需要A3则是GPU中的Descriptor Table。这样,frame与frame之间通过转换Table就可以共享resource了,多么简单明了!
三、Bundle
Bundle是D3D12中最为重要(到目前为止)的一个技术。Descriptor Table & Heaps解决的是resource share的问题,而bundle解决的是command reuse的问题。我们可以将Bundle看成可以构成小的功能模块的command组合。当编写几乎相同的两个渲染命令时,可以适当改变参数而不需要重新编写command。这不仅仅节约空间,更是对CPU效率的大幅度提升。因为app生成命令时,需要对命令进行编译,编译过程中需要跟踪命令执行状态和对条用的资源allocate memory以及命令传递等各种内容。而bundle很好的解决了这个问题,bundle的宗旨是:Recorded once,Reused multiple times!
未使用bundle的代码:
<span style="font-size:12px;">pContext->SetPipelineState(pPSO);
pContext->SetRenderTargetViewTable(0, 1, FALSE, 0);
pContext->SetVertexBufferTable(0, 1);
pContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw 1
pContext->SetConstantBufferViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1);
pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1);
pContext->DrawInstanced(6, 1, 0, 0);
pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1);
pContext->DrawInstanced(6, 1, 6, 0);
// Draw 2
pContext->SetConstantBufferViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1);
pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1);
pContext->DrawInstanced(6, 1, 0, 0);
pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1);
pContext->DrawInstanced(6, 1, 6, 0);</span>
采用bundle执行Draw1 和Draw2:
<span style="font-size:12px;">// Setup
pContext->SetRenderTargetViewTable(0, 1, FALSE, 0);
pContext->SetVertexBufferTable(0, 1);
// Draw 1 and 2
pContext->SetConstantBufferViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1);
pContext->ExecuteBundle(pBundle);
pContext->SetConstantBufferViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1);
pContext->ExecuteBundle(pBundle);</span>
简单明了清晰~
四、other Tips
D3D12中也包含了其他的技术,比如Command Buffer& command Queue 等,用于消除更好的执行command,同时让app更为底层。D3D12中也采用了resource barrier的东西,用于避免resource hazard的问题。这些内容将会在后续的Apple Metal中介绍。
五、summary
D3D12的推出,传言是继承了AMD Mantle的优良品质。通过分析AMD Mantle的new techniques,我几乎可以确定D3D12确实很大程度上依靠了Mantle,他们是如此相像两款sdk。而对于Metal,他也采用了很多的相似的技术,从而获得了30%的性能提高。而Mantle和D3D12则相比于D3D11,获得了近50%的性能提高,这简直不可思议。因此,现在披露的很多技术都将是未来的主流,值得大家好好学习借鉴。
下面是一些链接,可以让大家更了解D3D12:
gdc2014: http://www.gdconf.com/