作者:王文斓
虚拟现实 (VR) 能够带给用户前所未有的沉浸体验,但同时由于双目渲染、低延迟、高分辨率、强制垂直同步 (vsync) 等特性使 VR 对 CPU 渲染线程和逻辑线程,以及 GPU 的计算压力较大[1]。如何能有效分析 VR 应用的性能瓶颈,优化 CPU 线程提高工作的并行化程度,从而降低 GPU 等待时间提升利用率将成为 VR 应用是否流畅、会否眩晕、沉浸感是否足够的关键。Unreal* Engine 4 (UE4) 作为目前 VR 开发者主要使用的两大游戏引擎之一,了解 UE4 的 CPU 线程结构和相关优化工具能够帮助我们开发出更优质的 VR 应用。本文将集中介绍 UE4 的 CPU 性能分析和调试指令、线程结构、优化方法和工具、以及如何在 UE4 里发挥闲置 CPU 核心的计算资源来增强 VR 内容的表现,为不同配置的玩家提供相应的视听表现和内容,优化 VR 的沉浸感。
本文已发表于《程序员》杂志 2017 年 08 月期,如需转载,可与《程序员》联系。
Asynchronous Timewarp(ATW),Asynchronous Spacewarp(ASW) 和 Asynchronous Reprojection 等 VR runtime 提供的技术可以在 VR 应用出现掉帧的时候,以插帧的方式生成一张合成帧,等效降低延时。然而,这些并不是完美的解决方案,分别存在不同的限制:
ATW 和 Asynchronous Reprojection 能够补偿头部转动 (rotational movement) 产生的 Motion-To-Photon(MTP)延迟,但如果头部位置发生改变 (positional movement) 或者画面中有运动对象,即使用上 ATW 和 Asynchronous Reprojection,也无法降低 MTP 延迟; 另外 ATW 和 Asynchronous Reprojection 需要在 GPU 的 drawcall 之间插入,一旦某个 drawcall 时间太长 (例如后处理) 或者剩下来给 ATW 和 Asynchronous Reprojection 的时间不够,都会导致插帧失败。而 ASW 会在渲染跟不上的时候将帧率锁定在 45fps,让一帧有 22.2ms 的时间做渲染,两张渲染帧之间则以传统图像运动估算 (motion estimation) 的方式插入一张合成帧,如图 1 所示。但合成帧中运动剧烈或者透明的部分会产生形变(例如图 1 中红圈框起来的部分),另外光照变化剧烈的时候也容易产生估算错误,导致持续用 ASW 插帧的时候用户容易感觉到画面抖动。这些 VR runtime 的技术在频繁使用的情况下都不能够很好解决掉帧问题,因此开发者还是应该保证 VR 应用在绝大部分情况下都能够稳定跑在 90fps,只有偶然的掉帧才依赖上述的插帧方法解决。
图 1.ASW 插帧效果。
用 UE4 开发的应用可以通过控制台命令 (console command) 中的 "stat" 指令查询各种实时性能数据 [2-3]。其中 "stat unit" 指令可以看到一帧渲染总消耗时间(Frame)、渲染线程消耗时间(Draw)、逻辑线程消耗时间(Game) 以及 GPU 消耗时间 (GPU)。从中可以简单看出哪部分是制约一帧渲染时间的主要原因,如图 2 所示。结合 "show" 或 "showflag" 指令动态开关各种功能(features) 来观察分别对渲染时间的影响,找出影响性能的原因,期间可以用 "pause" 指令暂停逻辑线程来观察。需要注意的是其中 GPU 消耗时间包括了 GPU 工作时间和 GPU 闲置时间,所以即使在 "stat unit" 下看到 GPU 花了最长的时间,也并不代表问题就出在 GPU 上,很有可能是因为 CPU 瓶颈导致 GPU 大部分时间处于闲置状态,拉长了 GPU 完成一帧渲染所需时间。因此还是需要结合其他工具,例如 GPUView[4]来分析 CPU 和 GPU 的时间图,从中找出实际瓶颈位置。
表 1.stat unit" 统计数字。
另外,因为 VR 是强制开启垂直同步的,所以只要一帧的渲染时间超过 11.1ms,即使只超过 0.1ms,也会导致一帧需要花两个完整的垂直同步周期完成,使得 VR 应用很容易因为场景稍微改变而出现性能大降的情形。这时候可以用 "–emulatestereo" 指令,同时把分辨率 (resolution) 设为 2160x1200,屏幕百分比 (screenpercentage) 设为 140,就可以在没有接 VR 头显及关闭垂直同步的情况下分析性能。
而渲染线程相关的性能数据可以通过 "stat scenerendering" 来看,包括绘制调用 (drawcall) 数目、可见性剔除 (visibility culling) 时长、光照处理时间等。其中可见性剔除又可以通过 "stat initviews" 指令进一步了解和分柝各部分的处理时长,包括视锥剔除 (frustum culling)、预计算遮挡剔除(precomputed visibility culling) 和动态遮挡剔除 (dynamic occlusion culling) 等,用以判断各剔除的效率; 输入 "stat sceneupdate" 指令查看更新世界场景例如添加、更新和移除灯光所花费的时间。另外,也可以通过 "stat dumphitches" 指令,指定一帧的渲染时间超过 t.HitchThreshold 时把渲染帧信息写进日志。
如果要使游戏效果能够适配不同等级的 PC,那么 "stat physics","stat anim" 和 "stat particles" 会是三个经常用到跟 CPU 性能相关的指令,分别对应到物理计算时间 (布料模拟,破坏效果等)、蒙皮网格(skin meshing) 计算时间和 CPU 粒子计算时间。由于这三个计算在 UE4 里都能够分到工作线程作并行处理,因此对这些部分进行扩展能够把 VR 应用有效适配到不同等级的硬件,使 VR 体验以及效果能够随着 CPU 的核数增加而增强。
另外,可以直接输入控制台命令 "stat startfile" 和 "stat stopfile" 采集一段实时运行的数据,然后用 UE4 Session Frontend 里的 Stats Viewer 查看运行期间各时间段 CPU 线程的利用率和调用堆栈 (call stack),寻找 CPU 热点并进行相应的优化,如图 3 所示,功能类似 Windows* Assessment and Deployment Kit(ADK) 里的 Windows* Performance Analyzer(WPA)。
来源: http://geek.csdn.net/news/detail/248106