|
动态资源
动态资源更新的一个最佳范例是游戏线程动画每帧生成的骨骼网格体骨骼变形。目的是:在每个动画更新进渲染线程上(在此可将变形设为着色器常数)的一个阵列后,从游戏线程中获取变形。如在每帧更新索引或顶点缓冲,结果相同。以下是操作顺序:
USkinnedMeshComponent::CreateRenderState_Concurrent 分配 USkinnedMeshComponent::MeshObject。从此时起,游戏线程只可写入 MeshObject 指示器,但不可写入 FSkeletalMeshObject 的内存。
USkinnedMeshComponent::UpdateTransform 每帧被调用至少一次,更新组件的移动。在 GPU 蒙皮中将调用 FSkeletalMeshObjectGPUSkin::Update。现在游戏线程上拥有最新的变形,需要将它们转移到渲染线程中。操作方法:首先在堆(FDynamicSkelMeshObjectData)上分配内存,然后将骨骼变形复制进去,再使用 ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER 将此拷贝传到渲染线程。渲染线程现在拥有此拷贝,并负责删除。ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER 宏包含复制变形到最终目的地的代码,因此变形可被设为着色器常数。如更新顶点位置,这就是锁定和更新顶点缓冲的位置。
在一些情况下,组件会被分离。游戏线程使渲染命令入列,以释放所有动态 FRenderResources,现在可将 MeshObject 指示器设为 NULL;然而实际内存仍被渲染线程引用,无法删除。此时延迟删除机制即可发挥效果。从 FDeferredCleanupInterface 派生的类可按对线程无害的异步方式进行删除。FSkeletalMeshObject 应用此接口。游戏线程需要开始 FSkeletalMeshObject 的延迟删除,因此它调用了 BeginCleanup(MeshObject)。安全且完成清理后,内存将被逐步删除。
更新状态 VS 遍历渲染场景
在开发一个拥有独特更新和渲染操作的系统时,将两者合并进 DrawDynamicElements 看上去很美,而实际上却是不是个好点子。更好的方法是将更新从渲染遍历中独立出来,例如使来自游戏线程 Tick 事件的更新入列。
通过高阶渲染代码调用 DrawDynamicElements,绘制原始组件的元素。高阶代码假定不对 RHI 进行改变,在每帧中可将 DrawDynamicElements 调用任意次(取决于着色通路、画面数量、以及场景中的场景捕捉)。甚至可能调用 DrawDynamicElements,但底层绘制规则会因为多种原因放弃结果(例如:深度通路中提交的半透明 FMeshElement 将被放弃)。如原始组件实际为不可见,遮挡系统可能会/不会实际调用 DrawDynamicElements(取决于其使用的启发法)。这些所有因素均可能和每帧发生一次的状态更新产生冲突。
更好的解决方法是将更新和渲染遍历独立开来。游戏线程 Tick 事件可使渲染命令入列,执行更新操作。渲染命令可基于可见性略过更新。如使用情况允许,可使用原始场景信息的 LastRenderTime 执行操作。如更新操作以这样的方式单独入列,任意 RHI 函数皆可使用(包括设置不同的渲染目标)。
状态缓存(与更新相反)是此规则的例外。状态缓存将渲染遍历的中间结果作为优化保存。它与遍历密切相关,且不改变 RHI 状态,因此它不受上文提到的负面影响(设置正确时机进行缓存即可)。
|
|