在 Direct3D12 中使用 Stream Output

本文详细介绍了在Direct3D12中使用Stream Output功能进行Skining Animation的优化,通过对比D3D11和D3D12的差异,分析了在D3D12中创建Graphics Pipeline State时遇到的错误,并提供了解决方案,强调了Shader输出Semantic的一致性和无Geometry Shader时的处理方式。

一. 引子

从 DirectX10 开始 Direct3D 引入了 Stream Output 功能(下称 SO ),它能利用 GPU 进行一些模型操作,其中利用 SO 受益最大的就是 Skinning Animation。在没有 SO 之前蒙皮动画要不就是由 CPU 完成,要不就是每次渲染的时候都需要重新做一次蒙皮,这在多光源光照渲染 Shadow Map 中是非常耗时的,引入 SO 之后只要进行一次蒙皮动画所得到的 Vertex Buffer 可以重用,这样既节省了 CPU 又获得了 GPU 加速。

二.一切从 CreateGeometryShaderWithStreamOutput 说起

1. D3D11 / D3D10 与 Stream Output

D3D10 和 D3D11 使用 SO 的时候需要调用 CreateGeometryShaderWithStreamOutput 函数。虽然看函数名这个函数是要创建一个 Geometry Shader(下称 GS ),但是在实际使用的时候并不一定需要给出 GS 的 Binary Code。像 Skinning 这种操作实际上只需要在 Vertex Shader(下称 VS ) 中就能完成,根本不需要 GS。在调用 CreateGeometryShaderWithStreamOutput 的时候 pShaderBytecode 参数给的是 VS 的 Binary Code,创建好的 ID3D11GeometryShader 对象其实并没有实际代码,只是用来对应 SO 之后数据输出的信息。
把 VB / IB / VS / GS 设置进渲染管线,再把 VS 所需要的参数准备好,调用 Draw 就可以完成 SO 得到 Skinning 之后模型的 VB。这就是 D3D10 / D3D11 使用 SO 的流程。

2. D3D12 与 Stream Output

现在我们要在 D3D12 使用 SO,其本质的流程和数据是没有变化的:准备 Skinning 的 VS , VB / IB 就可以了。但是从 D3D12 开始 API 发生了巨大的变化,所有渲染状态都进了 Pipeline State Object(下称 PSO )。按照 D3D11 的经验把 PSO 其中 GS 的信息填充为 VS 的 Binary Code 之后开始 DrawInstance ,然后一系列的错误就让我开启了一条懵逼 Debug 之路。

三. 在 D3D12 中使用 Stream Output

1. Error

就像之前描述的那样,把要使用的信息绑到 PSO 中,可是执行到 CreateGraphicsPipelineState 的时候,D3D 返回了我一个错误信息:

D3D12 ERROR: ID3D12Device::CreateGeometryShaderWithStreamOutput: The GS bytecode passed in the graphics pipeline state object does not contain a geometry shader. [ STATE_CREATION ERROR #74: CREATEGEOMETRYSHADERWITHSTREAMOUTPUT_INVALIDSHADERTYPE]

嗯?GS bytecode 中没有 geometry shader?确实是没有啊,D3D11就是这么干的啊。那怎么办呢?要不把 GS 信息置空试试?但是获得了一个新的错误信息:

D3D12 ERROR: ID3D12Device::CreateGeometryShaderWithStreamOutput: Stream Output Declaration Element[0]'s semantic (POSITION 0) not found in output signature of shader. [ STATE_CREATION ERROR #87: CREATEGEOMETRYSHADERWITHSTREAMOUTPUT_MISSINGSEMANTIC]

填 GS 也不对不填也不对。这怎么办?

2. 解决方法

主要的错误信息就这两条,还有其他奇奇怪怪的错误信息就不说了。我尝试了各种方法去解决问题,可是回给我的错误信息更是五花八门。那么怎么解决呢?
最直接的方法就是找一个能够正常执行 SO 工作的代码,然后比较和自己代码的区别以发现自己代码的问题。可是 MSDN 上搜了个遍,除了与 SO 相关 API 的介绍和解释,压根就没有 SO 的例子!
然而天无绝人之路,最后我在一个个人引擎里找到了相关的代码,这才解决了问题,这个引擎就是Klay。分析了 Klay 中相关 SO 的代码,我逐渐找到了解决问题的门路。
其实种种问题的出现,不仅仅在于换用 D3D12 API,其实原先在 D3D11 中使用 SO 的时候有些概念就没有正确树立。

3. Debug

D3D12 使用无 GS 进行 SO 的时候不需要像 D3D11 那样把 GS 设置进去,GS 直接置空就可以了。
但是上面那第二个错误信息是怎么回事?这是我在写 Shader 的时候出现问题,我在 PSO 中的 D3D12_SO_DECLARATION_ENTRY 给出的 SemanticName 是 “POSITION”,可是在 VS 中对应的是 “SV_Position”,出现这个问题的原因是 Shader 我是拷贝过来忘了改,在渲染的时候没有问题,结果在 SO 中翻车了。

四. 总结

现在我们需要重新理顺一下 SO 的流程以及其使用数据的原因。

  • 在 CreateGeometryShaderWithStreamOutput 时需要 Shader 是因为需要使用 Shader 中的输出(output)去对应 D3D12_SO_DECLARATION_ENTRY ,这样才能知道对应的数据应该写入哪个 Buffer 的什么位置(比如 POSITION / NORMAL)。所以 Shader 中的输出的 Semantic 必须和 D3D12_SO_DECLARATION_ENTRY 中的一致,否则编译出错。
  • 无 GS 使用 SO 的时候调用CreateGeometryShaderWithStreamOutput 给出的 Shader 参数不一定是 VS,其实其正确的表述应该是距离 SO 最近的那个 Stage 的 Shader,其中有可能是 VS 或者 DS(domain-shader),这个在 MSDN上有说明。
  • D3D12 中无 GS 使用 SO 的时候,是不需要给出 GS 的,D3D 会根据 PSO 中的数据自己去寻找距离 SO 最近一个 Shader Stage。

至此,所有问题解决完毕,D3D12 终于可以进行 SO 的操作了。
最后多说一点关于 Skinning Animation 的实现。在 Compute Shader (下称 CS ) 诞生之前使用 SO 是 GPU 加速进行 Skinning 的唯一途径,即使是 CS 诞生的初期由于过于羸弱也没人用它加速 Skinning。但是现在已经是 D3D12 了,CS 的强大已经不可忽视,现在连以前很多图形管线的工作都移到 CS 中,比如 Post Processing。由于 CS 不受渲染管线的限制,它的灵活度非常高,所以现在 SO 已经不是 GPU 加速 Skinning 的唯一选择,CS 也许可以更好的胜任 GPU Skinning。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值