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游戏面临的性能问题。我们以腾讯的一款PCVR游戏《猎影计划》为例展示如何利用这套方法进行分析。在此之前我们先来了解一下VR游戏对性能要求较传统游戏高的四大原因。
VR游戏和传统游戏在硬件性能需求上的区别
相较于传统游戏,VR游戏由于存在高帧率、双目渲染及容易产生眩晕等特性,导致对于硬件计算能力的需求显著上升。下面从四个方面比较一下VR游戏和传统游戏的区别:
像素填充率
以一个1080p 60fps游戏为例,像素填充率为124Mpixels/sec。如果是支持高端VR头盔(Oculus Rift、HTC Vive)的游戏,像素填充率为233Mpixels/sec(分辨率2160x1200,帧率90fps)。但是中间需要一个较大的渲染目标,避免图像经过反形变校正后产生用户可见并且没被渲染到的区域,导致视角(FOV)降低。根据SteamVR的建议,需要放大的比率为1.4倍,所以实际的像素填充率为457M pixels/sec(分辨率3024x1680,帧率90fps),我们可以通过stencil mesh把最终不会被用户看到的区域剔除掉以减少需要渲染的像素,经过优化后的像素填充率为378M pixels/sec,但仍然是传统1080p 60fps游戏的3倍像素填充率。
双目渲染
从游戏渲染管线的角度来看,传统游戏中每一帧的渲染流程大致如下,其中蓝色的部分是CPU的工作,绿色的部分是GPU的工作。但由于视差的关系,VR游戏需要对左右眼看到的画面分别渲染不同的图像,所以下面的渲染管线也要对左右眼各做一次,从而增加了计算需求(在VR中两眼的视差较小,可以利用GBuffer或提交渲染指令后用view matrix变换等方法降低实际计算)。
图1 传统游戏渲染流程
用户体验
对于传统游戏来说,平均帧率达标往往就代表了一个流畅的游戏体验。然而对于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为例,第N+2帧的渲染延迟会远高于VR对延迟的最低要求,因为GPU必须先完成第N+1帧的工作,再来处理第N+2帧的工作,因而使得第N+2帧产生了较高的延迟。此外,由于运行情况不同,我们可以发现第N帧、第N+1帧和第N+2帧的渲染延迟也会有所差异,这对VR的体验也是不利的,因为一直变动的延迟会让用户产生晕动症(simulation sickness)。
图 2 传统游戏的渲染管线
因此,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运行时版本:
初始开发环境:Oculus v0.8 x64运行时和Unreal 4.10.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所示为优化工作前后的游戏截图,优化之后绘制调用次数减少至原来的1/5,每帧的 GPU执行时间平均从15.1ms缩短至9.6ms,如图3和4所示:
图4 优化前(左)后(右)的游戏截图
测试平台的规格:
英特尔酷睿i7-6820HK处理器(4核,8线程)2.7GHz
NVIDIA GeForce GTX980 16GB GDDR5
图形驱动程序版本:364.72
16GB DDR4 RAM
Windows10 RTM Build 10586.164
初步分析性能问题
为了更好地了解《猎影计划》的性能瓶颈,我们先综合分析了该游戏的基本性能指标,详情见表1。表中数据通过几种不同的工具收集,包括GPU-Z、TypePerf和Unreal Frontend等。将这些数据与系统空闲时的数据比较可得出以下几点结论:
游戏运行时的帧率低(36.4fps)而且GPU利用率也低(GTX980上为49.64%)。如果能够提高GPU利用率,帧率也会提高。
大量的绘制调用。DirectX 11中的渲染为单线程渲染,虽然微软提出deferred rendering context[3]可以用另一线程对渲染指令进行缓存以实现多线程渲染,但结果差强人意[4]。所以相对于DirectX 12,DirectX 11渲染线程具有相对较高的绘制调用开销。由于该游戏是在DirectX 11上开发的,并且为了达到低延迟,VR的渲染管线打破了CPU和GPU的并行计算,因此如果游戏的渲染线程工作较重,很容易会出现CPU瓶颈导致帧率显着降低。在这种情况下,较少的绘制调用有助于缓解渲染线程瓶颈。
由表中可以看出,CPU利用率似乎不是问题,因为其平均值只有13.58%。但从下文更进一步的分析可以看出,《猎影计划》实际上存在CPU性能瓶颈,而平均CPU利用率高低并不能说明游戏是否存在CPU性能瓶颈。
表1 优化前游戏的基本性能指标
下面我们会利用GPUView和Windows评估和部署工具包(Windows Assessment and Deployment Kit,Windows ADK)[5]中的Windows性能分析器(Windows Performance Analyzer,WPA)对《猎影计划》的性能瓶颈进行分析。
深入探查性能问题
GPUView[6]工具可用于调查图形应用、CPU线程、图形驱动程序、Windows图形内核等性能和相互之间的交互情况。该工具还可以在时间轴上显示程序是否存在CPU或GPU性能瓶颈。而Windows性能分析器可用于跟踪Windows 事件(Event Tracing for Windows,ETW),并生成相应事件的数据和图表;WPA同时具备灵活的用户界面(UI),通过简单操作即可查看调用堆栈、CPU 热点、上下文切换热点等,它还可以用来定位引发性能问题的函数。GPUView和Windows性能分析器都可以用于分析由Windows性能记录器(Windows Performance Recorder,WPR)采集到的事件追踪日志(Event Trace Log,ETL)。Windows性能记录器可通过用户界面或命令行运行,其内建的配置文件可用来选择要记录的事件。
对于VR应用,最好先确定其计算是否受限于CPU、GPU或二者皆是,以便将优化工作的重点集中在对性能影响最大的瓶颈,最大限度提升性能。
图5为优化前《猎影计划》在GPUView中的时间线视图,其中包括GPU工作队列、CPU上下文队列和CPU线程。根据图表我们可以看出:
帧率大约为37fps。
GPU负载大约为50%。
此版本容易使用户眩晕,因为运行帧率远低于90fps。
如GPU工作队列所示,只有两个进程向GPU提交了任务:Oculus VR运行时和游戏本身。Oculus VR运行时在帧渲染的最后阶段插入后处理工作,包括畸变校正、色彩校正和时间扭曲等。
从图中可以看出《猎影计划》同时存在CPU和GPU瓶颈。
在CPU瓶颈方面,GPU有大约50%的时间都处于空闲状态,主要原因是受到了一些CPU线程的影响而导致GPU工作没法及时被提交,只有这些线程中的CPU任务完成后GPU任务才能被执行。这种情况下如果对CPU任务进行优化,将能够极大地提升GPU的利用率,使GPU能执行更多的任务,从而提高帧率。
在GPU瓶颈方面,从图中我们可以看出,即使所有GPU空闲时间都能够被消除,GPU仍然需要大于11.1ms的时间才能完成一帧的渲染(这里约为14.7ms),因此如果不对GPU进行优化,此游戏的帧率不可能达到Oculus Rift CV1和HTC Vive等VR头显要求的90fps。
图 5 GPUView分析《猎影计划》时间线视图
改善帧率的几点建议:
物理和AI等非紧急的CPU任务可以延后处理,使图形渲染工作能够尽早被提交,以缩短CPU瓶颈时间。
有效应用多线程技术可增加CPU并行性,减少游戏中的CPU瓶颈时间。
尽量减少或优化容易导致CPU瓶颈的渲染线程任务,如绘制调用、遮挡剔除等。
提前提交下一帧的CPU任务以提高GPU利用率。尽管MTP延迟会略有增加,但性能与效率会显着提高。
DirectX 11具有高绘制调用和驱动程序开销。绘制调用过多时渲染线程会造成严重的CPU瓶颈。如果可以的话考虑迁移至DirectX 12。
优化GPU工作(如过度绘制、带宽、纹理填充率等),因为单帧的GPU处理时间大于11.1ms,所以会发生丢帧。
为了更深入探查CPU的性能问题,我们结合Windows性能分析器来分析从GPUView中发现的CPU瓶颈(通过分析同一个ETL文件),以下介绍分析和优化的主要流程(Windows性能分析器也可用于发现CPU上下文切换的性能热点,对该主题有兴趣的读者可以参考这里了解更多详情)。
首先我们需要在GPUView中定位出VR游戏存在性能问题的区间。在GPU完成一帧的渲染后,当前画面会通过显示桌面内容(Present)函数被提交到显示缓存,两个Present函数的执行所相隔的时间段为一帧的周期,如图6所示(26.78ms,相当于37.34fps)。
图 6: GPUView分析《猎影计划》时间线视图(单帧)注意导致GPU闲置的CPU线程
注意在GPU工作队列中有不少时间GPU是闲置的(例如一开头的7.37ms),这实际上是由CPU线程瓶颈所造成,即红框所圈起来的部分。原因在于绘制调用准备、遮挡剔除等CPU任务必须在GPU渲染命令提交之前完成。
如果使用Windows性能分析器分析GPUView所示的CPU瓶颈,我们就能找出导致GPU无法马上执行工作的对应CPU热点函数。图7-11显示Windows性能分析器在GPUView所示的同一区间下,各CPU线程的利用率和的调用堆栈。
图 7 Windows性能分析器分析《猎影计划》时间线视图,与图6为同一时间段
接下来让我们详细分析每个CPU线程的瓶颈。
图 8: 渲染线程T1864的调用堆栈
由图8的调用堆栈可以看出,渲染线程中最主要的三个瓶颈是:
静态网格的基本信道渲染(50%);
动态阴影初始化(17%);
计算视图可视性(17%)。
以上瓶颈是由于渲染线程中存在太多的绘制调用、状态变换和阴影图渲染所造成。优化渲染线程性能的几点建议如下:
在Unity中应用批处理或在Unreal中应用actor融合以减少静态网格绘制。将相近对象组合在一起,并使用细节层次(Level Of Detail,LOD)。合并材质以及将不同的纹理融入较大的纹理集都有助于提升性能。
在Unity中使用双宽渲染(Double WideRendering)或在Unreal中使用实例化立体渲染(Instanced Stereo Rendering),减少双目渲染的绘制调用提交开销。
减少或关闭实时阴影。因为接收动态阴影的对象将不会进行批处理,从而造成绘制调用问题。
减少使用会导致对象被多次渲染的特效(反射,逐像素光照,透明或多材质对象)。
图 9 游戏线程T8292的调用堆栈
图9显示游戏线程最主要的三个瓶颈是:
设置动画评估并行处理的前置工作(36.4%);
重绘视口(view port)(21.2%);
处理鼠标移动事件(21.2%)。
以上三大问题可以通过减少视口数量,以及优化CPU并行动画评估的开销来解决,另外需要检查CPU方面的鼠标控制使用情况。
工作线程(T8288,T4672,T8308):
图 10 工作线程T8288的调用堆栈
图 11 工作线程T4672的调用堆栈
图 12 工作线程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分析《猎影计划》时间线视图
图15所示为优化后《猎影计划》的GPUView视图。从图中可见优化后CPU瓶颈时间从7.37ms降至2.62ms,所用的优化措施包括:
提前运行渲染线程(一种通过产生额外的MTP延迟来减少CPU瓶颈的方法);
优化绘制调用,包括采用细节层次、实体化立体渲染和移除动态阴影;
延迟处理逻辑线程和工作线程的任务。
如图16所示为优化后CPU瓶颈期的渲染线程调用堆栈,即图15的红框标记起来的部分。
图 16 渲染线程T10404的调用堆栈
表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利用率、性能及用户体验。