unreal引擎中动态vb和ib的使用策略改进(二)

因为有过移植D3D12的经验,所以我们知道如何对动态buffer进行显性化的管理。不需要依赖runtime底层的renaming功能,我们也可以通过一些特别的手段在外部达到类似的效果。为了不让动态的buffer越来越多,我们就必须知道这些被Drawcall所引用的buffer何时能够安全的重用。很显然,buffer能够被重用的前提是那些引用它的渲染命令已经执行完毕了。原生的动态buffer之所以被应用程序反复使用,是因为我们在获取cpu端指针前,Map函数里填入了D3D11_MAP_WRITE_DISCARD这个参数值。这个参数值可以令runtime主动丢弃前面绑定的内部buffer,然后重新分配出新的未被使用的buffer,这样外部的逻辑又可以往这个新buffer里写入新的内容了,同时之前的那个内部buffer还能够被gpu使用。

回到前面我说叙述的手动renaming的功能上,有没有什么办法可以让runtime不要擅自分配新的内部buffer呢?答案是肯定的,那就是向Map函数传入另一个参数值,即D3D11_MAP_WRITE_NO_OVERWRITE。这个参数值能让应用程序永久性的使用同一个内部buffer,这是我们显式实现renaming功能的先决条件。有了固定的内部buffer,是不是就达成我们预想的目标了呢?暂时还没有,因为我们尚不知道这个内部的buffer何时结束gpu的使用。如果gpu正在读取buffer里的内容,而cpu却在写入新的数据,那必然会造成资源冲突和访问竞争,最终引起渲染异常,我们已经观察到画面不停闪烁的奇怪现象。D3D11有什么机制能够获得Drawcall已经执行完毕的信息呢?很简单,我们可以在帧尾插入一个D3D11_QUERY_EVENT的异步事件,然后在帧头对这些已经issue的事件状态进行轮询。D3D11_QUERY_EVENT的对象一旦触发就能够判定该事件之前的所有Command已经被GPU执行完毕,同时显而易见,绑定在Command上的Buffer也就可以被重用了,我们又能够把新的数据填充到这个Buffer中。

除了HUD和粒子系统外,我们还能把这种高效的动态VB管理机制应用到骨骼矩阵更新逻辑里。但是需要将动态VB的偏移值通过ConstantBuffer传给Shader,否则难以把不同SkeletalMesh的骨骼矩阵信息放到同一个VertexBuffer里。通过不同的渲染对象间共享buffer,在一定程度上能够提高内存空间的使用效率。骨骼矩阵有一个与粒子和HUD不一样的特点,就是它的数据有时候需要持久化。例如当SkinnedMeshComponent从可见进入到不可见的状态时,引擎可以不用每帧都去更新BoneData,但是当下次再变成可见时就需要读取旧的骨骼矩阵数据。另外DestructibleMeshComponent也有这一特点,当所有的碎片刚体停止活动时,引擎同样不会再更新BoneData,因此存储骨骼矩阵的Buffer必须能够持久化,否则Shader就会读取到脏数据。

编辑于 2021-01-03 10:02