## PC VR 游戏的 CPU 性能分析与优化
文/王文斓
>伴随着全新 VR 体验所带来的双目渲染、高分辨率和低延时等要求,对 CPU 和 GPU 都造成了极大的计算压力,一旦 VR 应用出现性能问题,非常容易造成用户眩晕并带来极差的用户体验,因此性能问题对于 VR 体验的好坏格外重要。本文将集中介绍 VR 需要高计算量的原因,以及分享如何利用工具查找 VR 应用的性能问题和 CPU 瓶颈所在。
自从三大头显厂商 Oculus、HTC 和 Sony 在2016年发布了虚拟现实(VR)头显产品后,由于能够带来卓越的沉浸式体验,VR 越来越受到市场的关注和重视,而 VR 也被认为会取代智能手机成为下一代的计算平台。然而,尽管虚拟现实能给用户带来身临其境般的沉浸式体验,但相比传统应用,其具有双目渲染、低延迟、高分辨率以及高帧率等严苛要求,因此极大地增加了 CPU 和 GPU 的计算负载。鉴于此,性能问题对于虚拟现实应用尤为重要,因为 VR 体验如果没有经过优化,容易出现掉帧等问题,让用户使用时发生眩晕的情况。在本文中,我们将介绍一种适用于所有游戏引擎及虚拟现实运行时(VR runtime)的通用分析方法,分析基于 PC 的 VR 游戏面临的性能问题。我们以腾讯的一款 PC VR 游戏《猎影计划》为例展示如何利用这套方法进行分析。在此之前我们先来了解一下 VR 游戏对性能要求较传统游戏高的四大原因。
### VR 游戏和传统游戏在硬件性能需求上的区别
相较于传统游戏,VR 游戏由于存在高帧率、双目渲染及容易产生眩晕等特性,导致对于硬件计算能力的需求显著上升。下面从四个方面比较一下 VR 游戏和传统游戏的区别:
#### 像素填充率
以一个 1080p 60fps 游戏为例,像素填充率为 124M pixels/sec。如果是支持高端 VR 头盔(Oculus Rift、HTC Vive)的游戏,像素填充率为 233M pixels/sec(分辨率 2160x1200,帧率 90fps)。但是中间需要一个较大的渲染目标,避免图像经过反形变校正后产生用户可见并且没被渲染到的区域,导致视角(FOV)降低。根据 SteamVR 的建议,需要放大的比率为1.4倍,所以实际的像素填充率为 457M pixels/sec(分辨率 3024x1680,帧率90fps),我们可以通过 stencil mesh 把最终不会被用户看到的区域剔除掉以减少需要渲染的像素,经过优化后的像素填充率为 378M pixels/sec,但仍然是传统 1080p 60fps 游戏的3倍像素填充率。
图1 传统游戏渲染流程
#### 双目渲染
从游戏渲染管线的角度来看,传统游戏中每一帧的渲染流程大致如下,其中蓝色的部分是 CPU 的工作,绿色的部分是 GPU 的工作。但由于视差的关系,VR 游戏需要对左右眼看到的画面分别渲染不同的图像,所以下面的渲染管线也要对左右眼各做一次,从而增加了计算需求(在 VR 中两眼的视差较小,可以利用 GBuffer 或提交渲染指令后用 view matrix 变换等方法降低实际计算)。
#### 用户体验
对于传统游戏来说,平均帧率达标往往就代表了一个流畅的游戏体验。然而对于 VR 游戏来说,即使平均帧率达标,但只要出现了连续掉帧,哪怕只有非常少数的情况下才发生,都会破坏了整个游戏体验。这是由于连续掉帧会使用户产生眩晕,一旦产生眩晕的感觉,即使后续的画面不掉帧,用户已经感觉到不适,游戏体验已经打了折扣。所以在游戏设计的时候,需要确保场景在最差的情况下也能达标(高端头显下为 90fps),否则会影响游戏体验。
另外,由于在 VR 场景中用户可以跟可移动区域内的对象作近距离观察和交互,所以必须开启抗锯齿以保证画面的清晰度。
#### 延迟
在传统游戏里从控制输入到画面输出的延迟往往达到约 100ms 的等级[1],FPS 类别的游戏对延迟要求较高,但一般也在约 40ms 的等级。而 VR 里 MTP 延迟(motion-to-photon latency,从用户运动开始到相应画面显示到屏幕上所花的时间)低于 20ms 是基本要求,研究发现对于部分比较敏感的用户,延迟需要达到 15ms 甚至 7ms 以下[2]。
低延迟的要求除了使 VR 游戏必须运行在高帧率外,同时也降低了硬件的运行效率,导致同样的工作量需要更强的硬件来驱动,原因正是低延迟要求使 VR 游戏的渲染管线必须和传统游戏不一样,而其中 CPU 对 VR 性能的影响扮演了重要的角色。
### VR 游戏和传统游戏在渲染管线上的区别
我们先来看看 VR 渲染管线和传统渲染管线的区别。如图2所示为传统游戏的渲染管线,其中 CPU 和 GPU 是并行处理的,以实现最高的硬件利用效率。但此方案并不适用于 VR,因为 VR 需要较低和稳定的渲染延迟,传统游戏的渲染管线无法满足此项要求。
图2 传统游戏的渲染管线
以图2为例,第 N+2 帧的渲染延迟会远高于 VR 对延迟的最低要求,因为 GPU 必须先完成第 N+1 帧的工作,再来处理第 N+2 帧的工作,因而使得第 N+2 帧产生了较高的延迟。此外,由于运行情况不同,我们可以发现第 N 帧、第 N+1 帧和第 N+2 帧的渲染延迟也会有所差异,这对 VR 的体验也是不利的,因为一直变动的延迟会让用户产生晕动症(simulation sickness)。
因此,VR 的渲染管线实际上如图3所示,这样能确保每帧可以达到最低的延迟。在图3中,CPU 和 GPU 的并行计算被打破了,这样虽然降低了效率,但可确保每帧实现较低和稳定的渲染延迟。在这种情况下,CPU 很容易成为 VR 的性能瓶颈,因为 GPU 必须等待 CPU 完成预渲染(绘制调用准备、动态阴影初始化、遮挡剔除等)才能开始工作。所以 CPU 优化有助于减少 GPU 的闲置时间,提高性能。
图3 VR 游戏的渲染管线
### 《猎影计划》VR 游戏背景
《猎影计划》是腾讯旗下利用 Unreal Engine 4 开发的一款基于 PC 的 DirectX 11 FPS 虚拟现实游戏,支持 Oculus Rift 和 HTC Vive。为了使《猎影计划》在英特尔处理器上实现最佳的游戏体验,我们与腾讯紧密合作,努力提升该游戏的性能与用户体验。测试结果显示,在本文所述的开发阶段,经优化后帧率得到了显着提升,从早期测试时跑在 Oculus Rift DK2(分辨率 1920x1080)上的每秒 36.4fps 提升至本次测试时跑在 HTC Vive(分辨率 2160x1200)上的每秒 71.4fps。以下为各阶段使用的引擎和 VR 运行时版本:
1. 初始开发环境:Oculus v0.8 x64 运行时和 Unreal 4.10.2;
2. 本次测试的开发环境:SteamVR v1463169981 和 Unreal 4.11.2。
之所以在开发阶段会使用到不同的 VR 运行时的原因在于,《猎影计划》最初是基于 Oculus Rift DK2 开发的,稍后才迁移至 HTC Vive。而测试显示采用不同的 VR 运行时在性能方面没有显着的差异,因为 SteamVR 和 Oculus 运行时采用了相同的 VR 渲染管线(如图3所示)。在此情况下,渲染性能主要由游戏引擎决定。这点可在图6和图15中得到验证,SteamVR 和 Oculus 运行时在每帧的 GPU 渲染结束后才插入 GPU 任务(用于镜头畸变校正),而且仅消耗了少量 GPU 时间(约 1ms)。
图4 优化前(左)后(右)的游戏截图
如图4所示为优化工作前后的游戏截图,优化之后绘制调用次数减少至原来的1/5,每帧的 GPU 执行时间平均从 15.1ms 缩短至 9.6ms,如图3和4所示:
测试平台的规格:
1. 英特尔酷睿 i7-6820HK 处理器(4核,8线程)2.7GHz
2. NVIDIA GeForce GTX980 16GB GDDR5
3. 图形驱动程序版本:364.72
4. 16GB DDR4 RAM
5. Windows10 RTM Build 10586.164
#### 初步分析性能问题
为了更好地了解《猎影计划》的性能瓶颈,我们先综合分析了该游戏的基本性能指标,详情见表1。表中数据通过几种不同的工具收集,包括 GPU -Z、TypePerf 和 Unreal Frontend 等。将这些数据与系统空闲时的数据比较可得出以下几点结论:
表1 优化前游戏的基本性能指标
1. 游戏运行时的帧率低(36.4fps)而且 GPU 利用率也低(GTX980 上为49.64%)。如果能够提高 GPU 利用率,帧率也会提高。
2. 大量的绘制调用。DirectX 11 中的渲染为单线程渲染,虽然微软提出 deferred rendering context[3]可以用另一线程对渲染指令进行缓存以实现多线程渲染,但结果差强人意[4]。所以相对于 DirectX 12,DirectX 11 渲染线程具有相对较高的绘制调用开销。由于该游戏是在 DirectX 11 上开发的,并且为了达到低延迟,VR 的渲染管线打破了 CPU 和 GPU 的并行计算,因此如果游戏的渲染线程工作较重,很容易会出现 CPU 瓶颈导致帧率显着降低。在这种情况下,较少的绘制调用有助于缓解渲染线程瓶颈。
3. 由表中可以看出, CPU 利用率似乎不是问题,因为其平均值只有13.58%。但从下文更进一步的分析可以看出,《猎影计划》实际上存在 CPU 性能瓶颈,而平均 CPU 利用率高低并不能说明游戏是否存在 CPU 性能瓶颈。
下面我们会利用 GPU View 和 Windows 评估和部署工具包(Windows Assessment and Deployment Kit,Windows ADK)[5]中的 Windows 性能分析器(Windows Performance Analyzer,WPA)对《猎影计划》的性能瓶颈进行分析。
#### 深入探查性能问题
GPU View[6]工具可用于调查图形应用、CPU 线程、图形驱动程序、Windows 图形内核等性能和相互之间的交互情况。该工具还可以在时间轴上显示程序是否存在 CPU 或 GPU 性能瓶颈。而 Windows 性能分析器可用于跟踪 Windows 事件(Event Tracing for Windows,ETW),并生成相应事件的数据和图表;WPA 同时具备灵活的用户界面(UI),通过简单操作即可查看调用堆栈、 CPU 热点、上下文切换热点等,它还可以用来定位引发性能问题的函数。 GPU View 和 Windows 性能分析器都可以用于分析由 Windows 性能记录器(Windows Performance Recorder,WPR)采集到的事件追踪日志(Event Trace Log,ETL)。Windows 性能记录器可通过用户界面或命令行运行,其内建的配置文件可用来选择要记录的事件。
对于 VR 应用,最好先确定其计算是否受限于 CPU、GPU 或二者皆是,以便将优化工作的重点集中在对性能影响最大的瓶颈,最大限度提升性能。
图5 GPUView 分析《猎影计划》时间线视图
图5为优化前《猎影计划》在 GPUView 中的时间线视图,其中包括 GPU 工作队列、CPU 上下文队列和 CPU 线程。根据图表我们可以看出:
1. 帧率大约为 37fps。
2. GPU 负载大约为50%。
3. 此版本容易使用户眩晕,因为运行帧率远低于 90fps。
4. 如 GPU 工作队列所示,只有两个进程向 GPU 提交了任务:Oculus VR 运行时和游戏本身。Oculus VR 运行时在帧渲染的最后阶段插入后处理工作,包括畸变校正、色彩校正和时间扭曲等。
5. 从图中可以看出《猎影计划》同时存在 CPU 和 GPU 瓶颈。
6. 在 CPU 瓶颈方面, GPU 有大约50%的时间都处于空闲状态,主要原因是受到了一些 CPU 线程的影响而导致 GPU 工作没法及时被提交,只有这些线程中的 CPU 任务完成后 GPU 任务才能被执行。这种情况下如果对 CPU 任务进行优化,将能够极大地提升 GPU 的利用率,使 GPU 能执行更多的任务,从而提高帧率。
7. 在 GPU 瓶颈方面,从图中我们可以看出,即使所有 GPU 空闲时间都能够被消除,GPU 仍然需要大于 11.1ms 的时间才能完成一帧的渲染(这里约为 14.7ms),因此如果不对 GPU 进行优化,此游戏的帧率不可能达到 Oculus Rift CV1 和 HTC Vive 等 VR 头显要求的 90fps。
改善帧率的几点建议:
1. 物理和 AI 等非紧急的 CPU 任务可以延后处理,使图形渲染工作能够尽早被提交,以缩短 CPU 瓶颈时间。
2. 有效应用多线程技术可增加 CPU 并行性,减少游戏中的 CPU 瓶颈时间。
3. 尽量减少或优化容易导致 CPU 瓶颈的渲染线程任务,如绘制调用、遮挡剔除等。
4. 提前提交下一帧的 CPU 任务以提高 GPU 利用率。尽管 MTP 延迟会略有增加,但性能与效率会显着提高。
5. DirectX 11 具有高绘制调用和驱动程序开销。绘制调用过多时渲染线程会造成严重的 CPU 瓶颈。如果可以的话考虑迁移至 DirectX 12。
6. 优化 GPU 工作(如过度绘制、带宽、纹理填充率等),因为单帧的 GPU 处理时间大于 11.1ms,所以会发生丢帧。
为了更深入探查 CPU 的性能问题,我们结合 Windows 性能分析器来分析从 GPUView 中发现的 CPU 瓶颈(通过分析同一个 ETL 文件),以下介绍分析和优化的主要流程(Windows 性能分析器也可用于发现 CPU 上下文切换的性能热点,对该主题有兴趣的读者可以参考了解更多详情)。
图6 GPUView 分析《猎影计划》时间线视图(单帧)
首先我们需要在 GPUView 中定位出 VR 游戏存在性能问题的区间。在 GPU 完成一帧的渲染后,当前画面会通过显示桌面内容(Present)函数被提交到显示缓存,两个 Present 函数的执行所相隔的时间段为一帧的周期,如图6所示(26.78ms,相当于 37.34fps)。
注意导致 GPU 闲置的 CPU 线程。
注意在 GPU 工作队列中有不少时间 GPU 是闲置的(例如一开头的 7.37ms),这实际上是由 CPU 线程瓶颈所造成,即红框所圈起来的部分。原因在于绘制调用准备、遮挡剔除等 CPU 任务必须在 GPU 渲染命令提交之前完成。
如果使用 Windows 性能分析器分析 GPUView 所示的 CPU 瓶颈,我们就能找出导致 GPU 无法马上执行工作的对应 CPU 热点函数。图7-11显示 Windows 性能分析器在 GPUView 所示的同一区间下,各 CPU 线程的利用率和的调用堆栈。
图7 Windows 性能分析器分析《猎影计划》时间线视图,与图6为同一时间段
接下来让我们详细分析每个 CPU 线程的瓶颈。
由图8的调用堆栈可以看出,渲染线程中最主要的三个瓶颈是:
图8 渲染线程 T1864 的调用堆栈
1. 静态网格的基本信道渲染(50%);
2. 动态阴影初始化(17%);
3. 计算视图可视性(17%)。
以上瓶颈是由于渲染线程中存在太多的绘制调用、状态变换和阴影图渲染所造成。优化渲染线程性能的几点建议如下:
1. 在 Unity 中应用批处理或在 Unreal 中应用 actor 融合以减少静态网格绘制。将相近对象组合在一起,并使用细节层次(Level Of Detail,LOD)。合并材质以及将不同的纹理融入较大的纹理集都有助于提升性能。
2. 在 Unity 中使用双宽渲染(Double Wide Rendering)或在 Unreal 中使用实例化立体渲染(Instanced Stereo Rendering),减少双目渲染的绘制调用提交开销。
3. 减少或关闭实时阴影。因为接收动态阴影的对象将不会进行批处理,从而造成绘制调用问题。
4. 减少使用会导致对象被多次渲染的特效(反射,逐像素光照,透明或多材质对象)。
图9显示游戏线程最主要的三个瓶颈是:
图9 游戏线程 T8292 的调用堆栈
- 设置动画评估并行处理的前置工作(36.4%);
- 重绘视口(view port)(21.2%);
- 处理鼠标移动事件(21.2%)。
以上三大问题可以通过减少视口数量,以及优化 CPU 并行动画评估的开销来解决,另外需要检查 CPU 方面的鼠标控制使用情况。
图10 工作线程 T8288 的调用堆栈
图11 工作线程 T4672 的调用堆栈
图12 工作线程 T8308 的调用堆栈
工作线程(T8288,T4672,T8308):
这些工作线程的瓶颈主要集中在物理模拟,比如布料模拟、动画和粒子系统更新。表2列出了在 GPU 闲置(等待执行)时的 CPU 热点。
表2 优化前 GPU 闲置时的 CPU 热点
#### 优化
在实施了包括细节层次、实体化立体渲染、动态阴影消除、延迟 CPU 任务以及优化物理等措施后,《猎影计划》的运行帧率从 Oculus Rift DK2(1920x1080)上的 36.4fps 提升至 HTC Vive (2160x1200)上的 71.4fps;同时由于 CPU 瓶颈减少,GPU 的利用率从54.7%提升至74.3%。
如图13和图14所示,分别为《猎影计划》优化前后的 GPU 利用率,如 GPU 工作队列所示。
图13 优化前《猎影计划》的 GPU 利用率
图14 优化后《猎影计划》的 GPU 利用率
图15所示为优化后《猎影计划》的 GPUView 视图。从图中可见优化后 CPU 瓶颈时间从 7.37ms 降至 2.62ms,所用的优化措施包括:
图15 优化后 GPUView分析《猎影计划》时间线视图
1. 提前运行渲染线程(一种通过产生额外的 MTP 延迟来减少 CPU 瓶颈的方法);
2. 优化绘制调用,包括采用细节层次、实体化立体渲染和移除动态阴影;
3. 延迟处理逻辑线程和工作线程的任务。
图16 渲染线程 T10404 的调用堆栈
如图16所示为优化后 CPU 瓶颈期的渲染线程调用堆栈,即图15的红框标记起来的部分。
表3列出了优化后 GPU 闲置(等待执行)时的所有 CPU 热点,注意相对于表2,许多热点和线程已从 CPU 瓶颈中被移除。
表3 优化后 GPU 闲置时的 CPU 热点
更多的优化措施,比如 actor 融合或者精简材质,都可以优化渲染线程中的静态网络渲染,进一步提高帧率。假若能对 CPU 任务进行充分的优化,单帧的处理时间能进一步减少 2.62ms(单帧的 CPU 瓶颈时间),达到 87.8fps。
表4 优化前后游戏的基本性能指标
### 结论
利用多种工具分析 VR 应用可以帮助我们了解该应用的性能表现和瓶颈所在,这对于优化 VR 性能非常重要,因为单凭性能指标可能无法真正反映问题所在。本文讨论的方法与工具可用于分析使用任何游戏引擎及 VR 运行时开发的 PC VR 应用,确定应用是否存在 CPU 或 GPU 瓶颈。由于绘制调用准备、物理模拟、光照或阴影等因素的影响,有时候 CPU 对 VR 应用性能的影响比 GPU 更大。通过分析多个存在性能问题的 VR 游戏,我们发现其中许多都存在 CPU 瓶颈,这意味着优化 CPU 可以提升 GPU 利用率、性能及用户体验。
#### 参考链接
[1]http://www.anandtech.com/show/2803
[2]http://blogs.valvesoftware.com/abrash/latency-the-sine-qua-non-of-ar-and-vr/
[3]https://msdn.microsoft.com/en-us/library/windows/desktop/ff476891(v=vs.85).aspx
[4]https://www.pcper.com/reviews/Editorial/What-Exactly-Draw-Call-and-What-Can-It-Do
[5]https://developer.microsoft.com/en-us/windows/hardware/windows-assessment-deployment-kit
[6]http://graphics.stanford.edu/~mdfisher/GPUView.html