开发

教程:优化示例应用程序

更新时间: 2024年7月30日
LINK PC-VR DOCUMENTATION
本主题介绍基于电脑 SDK 的开发。如需了解 Meta Quest 应用性能优化的详细信息,请根据您的开发平台查阅下列主题文章:
本版块中的教程详细演示了应如何进行 VR 性能优化。
本教程将引导您使用以下方法优化示例应用程序:
  • Oculus 调试工具
  • Oculus 性能 HUD
  • 掉帧捕捉工具
  • Windows 事件追踪 (ETW)
  • ovrlog/ovrlog_win10 和 xperf
  • Windows 性能分析器 (WPA)
  • GPUView

安装组件

1.安装 Oculus PC-SDK:
2.安装 Visual Studio 2015:
注意:本教程中使用的 Oculus ORT 演示应用程序和 Windows 驱动程序工具包 (WDK) 尚未与 Visual Studio 2017 兼容。
3.安装 GPUView。
GPUView 包含在 Windows 8.1 SDK 中的 Windows 性能工具包 (WPT) 中,可在以下网址获取:
4.安装 Windows 事件追踪 (ETW)。
ETW 包含在 Windows 驱动程序工具包 8.1 中:https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit
5.安装以下两项:
  • 适用于 Windows 10 的 Windows SDK,版本 1703(或更高版本)
  • 适用于 Windows 10 的 WDK,版本 1703(或更高版本)
6.安装 Windows 性能分析器 (WPA)。
WPA 包含在 Windows 评估和部署工具包 (Windows ADK) 中:

创建一款存在性能问题的应用程序

我们将使用 ORT 缓冲触感示例项目:
{SDK Folder}\Samples\OculusRoomTiny_Advanced\ORT (Buffered Haptics)\Projects\Win\VS2015
1.运行应用程序,并确保您的 Rift 已设置并正常工作。
2.在 Visual Studio 2015 中打开 ORT(缓冲触感)解决方案:
3.在调试器中运行此应用程序(按 F5),并在 Rift 头戴设备中查看场景。
当您转动头部时,场景表现正常。在 Oculus Mirror 中,眼睛显示器保持居中并保持其通常的形状。
4.停止调试器。
5.制造 GPU 性能问题。
我们将通过以下方法来制造问题:编辑此应用程序使用的默认纹理着色器例程,并添加一个 For 循环,该循环通过过多的工作人为地使 GPU 超载,模仿昂贵的着色器例程。
在此示例中,着色器例程包含在以下位置:
{Oculus SDK Folder}\Samples\OculusRoomTiny_Advanced\Common\Win32_DirectXAppUtil.h
在 Visual Studio 中打开此头文件,并将以下代码添加到 defaultPixelShaderSrc 中:
// Artificially overload the GPU with too much work, mimicking an expensive shader routine

" if (TexCol.a!=0) for (int i = 1; i < 10000; i++) { Color = Color * i * 2; Color = Color/i; Color = Color/2; } "
此代码的位置突出显示如下:
Visual Studio code editor with the GPU-overload loop code highlighted in defaultPixelShaderSrc.
注意:您可能希望为循环变量尝试不同的值。上面显示的值 (10,000) 可能过于极端,具体取决于硬件的特性。您可能会发现,使用 1500 左右的值时,该教程的效果更好。在本教程的后面,将显示该循环变量的不同值的结果。这模仿了不同层次的着色器复杂性。
6.再次运行应用程序。
结果是,眼睛渲染会出现延迟,当您转动头部时,这种延迟很明显。在头戴设备中,头部移动时,显示器侧面会出现黑色垂直矩形区域。在 Oculus Mirror 中,当您转动头部时,眼睛显示会变平并偏离中心。
我们知道这个问题是由于我们特意制造的 GPU 问题造成的。然而,一般来说,这种现象可能由许多问题造成,包括 CPU 或 GPU 过载。我们将开始使用 Oculus 调试工具来缩小问题的范围。

使用 Oculus 调试工具

1.运行 Oculus 调试工具:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\OculusDebugTool
2.Oculus 调试工具窗口弹出:
Oculus Debug Tool window with Visible HUD set to Performance and Mode set to Performance Summary.
请确保:
  • 可视 HUD 设置为性能
  • 性能 HUD 下,模式设置为性能摘要
3.为方便起见,您可以通过选择“文件”>“启动应用…”来启动应用程序
4.启动应用程序二进制文件,其位于以下位置:
{Oculus_SDK}\Samples\OculusRoomTiny_Advanced\ORT (Buffered Haptics)\Bin\Windows\Win32\Debug\VS2015\z.Buffered Haptics.exe
5.在头戴设备中,您现在应该会看到 Oculus 性能 HUD:
Oculus Performance HUD showing Performance Summary with high latency and negative headroom values.
HUD 显示性能摘要,表现为四个指标:
  • 应用运动到光子延迟——这表示从应用程序获得控制权到帧完成所用的时间。其中大部分是应用程序渲染时间。上面显示的值约为 284 毫秒,这是一个极高的值,显然存在问题。基于此,HUD 会提供应用程序应改变多少 CPU 利用率水平的建议,在本示例中,建议将利用率降低 15 倍。
  • 性能余量——这是在应用程序开始出现性能问题之前,仍可使用的渲染时间百分比。(计算方式如下:1 - (FrameRenderTime / FrameVSyncToNextVsync)。例如,如果帧的渲染时间为 5 毫秒,则余量将为:1 - (5/11.11) = 0.54 = 54%)在此示例中,性能余量实际上是一个很大的负数,这表明应用程序正在丢弃帧。
  • 应用掉帧——这是应用程序掉帧数量。在此示例中,出现大量掉帧,因此这条线变成了红色。
  • 合成器掉帧——这是合成器掉帧数量。
显然,此应用程序在应用运动到光子延迟类别中花费了太多时间。在这个示例中,这个问题实际上被夸大了。在性能问题中,每帧花费 18 毫秒更为典型。
6.在“性能 HUD”下,将“模式”设置为“应用渲染时间”:
Oculus Debug Tool with Performance HUD Mode set to App Render Timing.
Oculus 性能 HUD 的内容此时会显示应用程序渲染时间数据。在这里,您可以轻松比较 CPU 和 GPU 上的计算负载:
Oculus Performance HUD App Render Timing view showing GPU load far exceeding CPU load.
很容易看出问题出在 GPU 负载上。同样,这是一个非常夸张的例子,但它可以说明使用这些分析工具可以获得哪些成效分析。

使用掉帧捕捉工具

接下来,我们将使用掉帧捕捉工具查看此应用程序掉帧情况。
1.点击 Oculus 调试工具中的掉帧捕捉按钮:
Oculus Debug Tool with the Lost Frame Capture button highlighted.
2.掉帧捕捉工具弹出:
Lost Frame Capture tool window with Record and Stop buttons visible.
3.确保应用程序显示在 Oculus Rift 头戴设备中。
(掉帧捕捉工具特定于 Oculus Rift,它知道为当前运行的应用程序捕捉帧。)
4.在掉帧捕捉工具中点击“记录”。
5.使用应用程序执行一些操作,例如向不同方向移动头戴设备。
6.取下头戴设备,然后点击掉帧捕捉工具窗口中的“停止”按钮
7.(可选)点击“保存”,将捕捉的数据保存到 Oculus 调试归档(ODA)文件中。对其适当命名,例如 app_1000.oda(如果着色器例程中的循环变量设置为 1000)。
您现在可以看到丢失的帧,甚至可以通过在帧列表中上下移动光标来翻阅:
Lost Frame Capture tool displaying a list of captured lost frames that can be stepped through.
如果您看到某些对象时常会出现掉帧的情况,您可能需要通过减少它们包含的多边形数量、简化它们使用的着色器、简化它们的阿尔法混合例程等来优化这些对象的渲染。

使用 ovrlog

我们现在来使用 Windows 事件追踪 (ETW) 捕捉低级事件。这让我们能够更精细地分析应用程序的行为。我们将通过运行 ovrlog 实用程序(它提供了一种启动和停止 ETW 会话的便捷方法)来实现这一点。事件会被捕捉到事件追踪日志 (.etl) 文件中。我们将使用两个工具分析事件流:Windows 性能分析器 (WPA) 和 GPUView。您可以将 WPA 视为提供事件流的更高级别视图。本质上,WPA 提供图表和图形,总结事件流的性能相关特征。而 GPUView 显示事件本身的高精细视图。因此,使用 GPUView,您可以深入了解 CPU 和 GPU 工作负载之间互动的细节,并微调您的应用程序,以优化这些工作负载的时间和内容。
1.以管理员权限启动 Windows CMD 窗口。
Windows Start menu showing the Run as administrator option for Command Prompt.
在本教程的其余部分中,您将运行 ovrlog_win10 脚本或 ovrlog 脚本,具体取决于您运行的是 Windows 10 还是 Windows 的早期版本。此脚本调用 xperf 实用程序,该实用程序启动 ETW 追踪会话,捕捉 VR 应用程序运行时发生的事件。
您必须以管理员权限运行 ovrlog_win10(或 ovrlog)脚本。需要提升权限才能执行内核级事件追踪,这是捕捉与 Oculus 应用程序相关的所有事件所必需的。
第一次运行 ovrlog_win10ovrlog 脚本时,它会执行以下操作:
  • 找到 xperf 实用程序
  • 安装事件定义清单,其中提供有关应用程序性能的分析
  • 运行 xperf 实用程序以启动 ETW 会话
  • 开始捕捉事件
第二次运行 ovrlog_win10ovrlog 脚本时,它将执行以下操作:
  • 暂停 ETW 会话
  • 卸载清单
  • 将结果汇总到追踪文件 ({xperf_folder}\trace\merged.etl) 中,随后可以将该文件加载到 WPA 或 GPUView 中,以便分析发生的事件流
2.确保 Xperf 文件夹为已验证用户提供以下权限:
  • 完全控制权
  • 修改
Xperf 文件夹位于以下位置:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW
因此,Xperf 文件夹的权限应如下所示:
Windows folder permissions dialog showing Full control and Modify enabled for Authenticated Users.
3.准备以合理快速的顺序干净利落地完成以下四个步骤。
完成这四个步骤后,如果在控制台输出中看到错误消息(显示为红色),请尝试删除 %PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\ 文件夹。
这一步骤是必要的,这样当您运行 ovrlog_win10ovrlog 时,中间追踪文件就不会冲突。删除此文件夹后,再次运行以下四个步骤。
4.运行缓冲触感应用程序,并确保目前正在头戴设备中查看此应用。(如果需要,可以直接在 Visual Studio 调试器中运行此应用。)
5.使用管理员权限从命令提示符运行以下脚本:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\overlog_win10.cmd
或者,如果您使用的是早于 Windows 10 的 Windows 版本,则使用以下命令:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\overlog.cmd
6.在 VR 应用程序中执行任何所需的操作,同时继续在头戴设备中查看此应用。
7.再次运行 ovrlog_win10ovrlog 以停止捕捉事件。
或者,您可以运行带有参数“stop”的 ovrlog_win10 或 ovrlog 命令来停止追踪。在同一命令提示符下再次运行不带参数的该命令将停止追踪,这是大多数人通常使用该命令的方式。然而,由于这种方法依赖于环境变量设置,因此更容易失败。
应生成以下文件:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\merged.etl

使用 Windows 性能分析器 (WPA)

1.启动 Windows 性能分析器 (WPA),其位于以下位置:
C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit
2.弹出 WPA 窗口:
Windows Performance Analyzer main window after initial launch.
3.选择“文件”>“打开”,然后打开上面生成的 merged.etl 文件:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\merged.etl
4.您应该可以看到以下内容:
WPA with the merged.etl trace file loaded, showing navigation panels on the left.
5.要检查 CPU 使用情况,请双击左侧导航栏中的小型计算窗口。完整的 CPU 使用率图显示在工作区:
WPA Computation graph showing full CPU usage data for all processes in the trace.
6.向下滚动查看 z.Buffered.Haptics.exe 进程。您可以看到它只占用了很小比例的 CPU 资源:
WPA CPU usage graph scrolled to z.Buffered.Haptics.exe, showing very low CPU utilization.
从上面可以清楚地看出,性能问题不是由于此应用程序中过度使用 CPU 资源造成的。
7.要查看 GPU 使用情况,请展开“视频”选项卡:
WPA Video tab expanded, showing the GPU is heavily overloaded in the preview window.
通过查看小型视频窗口,可以立即清楚地看到 GPU 过载。(实际上,这个例子相当极端。通常的 GPU 使用问题不会像我们在这里看到的那样明显。)
8.从工作区移除计算图表,双击“视频”窗口以在工作区显示 GPU 使用图:
WPA GPU usage graph showing application and Compositor activity over the trace capture period.
在此示例中,应用程序在首次运行 ovrlog_win10ovrlog 时正在运行。大约在事件捕捉期过半时,头戴设备被放下。此时,VR 应用程序被冻结,重新渲染了一帧。在该时间段内未调用合成器,因此 OVRServer_v64.exe 的 GPU 使用率为零。随后,几秒钟后,第二次运行 ovrlog_win10ovrlog,事件捕捉期结束。
9.通过在该区域上水平拖动鼠标,选择图形中应用程序和合成器都在执行的一小部分。然后,使用 Ctrl 和 (鼠标上的)滚轮放大,这样您就可以看到百分之一秒。
注意:此时间标度接近帧速率,即每秒 90 帧。
在我们在着色器例程中创建的循环中,您可以通过设置循环变量进行实验,使其每像素循环 100、500、1000 或更多周期。对于循环变量的不同值,应用将展示的确切行为取决于您使用的电脑的特性,以及事件捕捉期间的负载量。
当循环变量设置为相对较低的值,并且放大到百分之一秒时,您可以看到类似于以下内容的结果:
WPA GPU usage zoomed to hundredths of a second showing clear frame cycles at low shader complexity.
在这里,您可以清楚地看到帧周期。应用程序(以绿色显示)在每一帧中占用了相当大量的 GPU 资源,合成器(以浅棕色显示)在同一帧中占用了较少的 GPU 资源。
当着色器例程中的循环每像素运行更多次数时,情况会变得更加紧张:
WPA GPU usage showing app and Compositor GPU time stacked with very little headroom between frames.
在这里,从应用程序完成处理帧到合成器输出帧之间的时间非常短(几近于无)。合成器还与应用程序同时使用 GPU 处理(两个使用情况图表相堆叠表明了这一点)。在此示例中,着色器例程处于或高于良好用户体验可接受的复杂度极限。
当着色器例程中的循环变量进一步增加时,情况会变得更糟:
WPA GPU usage at high shader complexity where frame cycles are no longer distinguishable.
这里甚至无法清楚地看到帧周期。
从本质上讲,GPU 永远无法完成它正在做的事情。通过突出显示该进程并在图中查看相应的活动,您可以看到 z.Buffered Haptics.exe 这一进程正在大量使用 GPU。其他进程都不太使用 GPU。
我们现在将更仔细地研究 WPA 中的追踪数据。
10.进一步放大,然后点击图标以仅显示图形:
WPA toolbar with the graph-only display icon highlighted.
表格消失,只有图表可见:
WPA GPU usage graph displayed without the data table, showing only the chart view.
11.向下拖动图表底部以放大视图:
WPA GPU usage graph enlarged with the Series box showing process hierarchies on the left.
在左侧的“系列”框中,打开 z.Buffered.Haptics.exeOVRServer_x64.exe 下的层次结构,然后在 z.Buffered.Haptics.exe 下选择“渲染”:
WPA Series box with Render selected under z.Buffered.Haptics.exe and OVRServer_x64.exe expanded.
渲染中涉及的 z.Buffered Haptics.exe 进程上方的空间已填充。如您所见,到目前为止,大部分时间都用于在 z.Buffered Haptics.exe 中进行渲染。(当然,考虑到我们插入着色器例程的循环,这是我们期望的结果。)
WPA GPU graph with z.Buffered.Haptics.exe Render phase filled in, showing most GPU time is rendering.
WPA 并未提供应用程序实际使用 GPU 所执行操作的详细视图,但我们知道 GPU 活动有两个主要阶段:渲染阶段和 BLT 阶段(周期结束时的缓冲区复制)。如果您选择 BLT,您会发现在该阶段花费的时间很少。
12.在左侧的“系列”框中,选择 OVRServer_x64.exe 下的“渲染”。您可以看到服务器使用的所有 GPU 时间都用于渲染:
WPA GPU graph with OVRServer_x64.exe Render selected, showing all server GPU time is rendering.
如果您按住鼠标并从周期中的一个点拖动到下一个周期中的同一点,您可以看到持续时间约为 11.1 微秒,即九十分之一秒。这是 Rift 的帧率。所以,在这个示例中,我们可以看到帧率得到了满足。
WPA GPU graph showing a frame cycle duration of about 11.1 ms, matching the 90 fps Rift frame rate.
无法在 WPA 中实际显示 VSync 事件。但是,您通常可以识别帧周期,如上例所示。

使用 GPUView

在以下步骤中,我们将在 GPUView 中查看 ETW 追踪文件。WPA 不显示每个硬件资源上数据包(不可分割的计算工作集合)的具体队列。GPUView 提供这种级别的分析。
1.启动 GPUView:
c:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\gpuview\GPUView.exe
2.当 GPUView 启动时,它首先显示一个文件管理器对话框。找到要分析的 merged.etl 文件并将其加载到 GPUVIew:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\trace\merged.etl
将弹出 GPUView 窗口,显示应用程序运行期间在 merged.etl 文件中捕捉的 ETW 追踪的高级视图:
GPUView high-level view of the ETW trace captured from the Buffered Haptics application.
在此示例中,merged.etl 文件表示循环变量设置为 1500 时应用程序的行为。不过,这种行为可能因硬件的特性而有所不同。
3.追踪中捕捉了许多进程,包括内核级进程和与其他应用程序相关的进程。这些进程不在本教程关注的范围内。因此,为了简化显示,可以使用以下命令收起所有进程:
查看 > 进程 > 隐藏所有进程
GPUView View menu with Hide All Processes selected to simplify the display.
4.我们只想显示 z.Buffered Haptics.exe 进程。因此,右键点击该进程并选择“隐藏/显示”进程
GPUView context menu on z.Buffered Haptics.exe with Hide/Show Process selected.
在屏幕顶部,显示了硬件资源。屏幕底部显示了应用程序中的相应活动:
GPUView showing hardware resources at the top and Buffered Haptics application activity below.
5.通过选择事件流的相关部分并输入 Ctrl-Z 进行放大:
GPUView with a portion of the event flow selected for zooming in with Ctrl-Z.
要逐步向外放大,请输入 Z。要将原始视图放大,请输入 Ctrl-H。
展开视图如下图所示。在此示例中,当追踪开始时,头戴设备是静止的,然后在场景中主动移动。您可以看到,在显示器的右侧,帧之间用空格隔开。这不能明确表示正在掉帧。是否掉帧取决于时间。
GPUView expanded view showing frames with gaps between them indicating possible dropped frames.
6.要更仔细地检查时间,请选择一个版块并输入 Ctrl-Z 来进一步放大:
GPUView zoomed in further with a section selected for more detailed timing analysis.
7.您现在可以更详细地查看数据包:
GPUView showing GPU packets in detail with colored rectangles representing units of GPU work.
选择一帧,查看屏幕顶部显示的时间标度。时间标度增量为 10 毫秒。为了达到每秒 90 帧的所需帧率,帧周期必须在大约 11.1 毫秒内完成。您可以看到,帧的时间比这要长,事实上,后续帧被延迟了,帧之间的空白表明了这一点。因此,在此示例中,每隔一帧都会被放置在时间线的右侧:
GPUView showing frames exceeding the 11.1 ms budget with blank spaces indicating dropped frames.
在这个缩放级别,您可以看到一堆彩色矩形,称为数据包。数据包是应用程序安排在 CPU 或 GPU 上运行的不可分割的最小工作单元。栅栏是一个同步基元,它确保一个操作在下一个操作开始之前完成。
栅栏显示为上面的粉红色框。
深蓝色数据包是解析为 GPU 工作的图形命令的集合。
8.点击一个数据包。现在,您可以看到该数据包的确切生命周期,及其详细信息:
GPUView with a selected packet showing its life cycle, dependencies, and detailed information.
在 GPUView 中,数据包按堆栈排列,时间沿水平轴前进。底部的数据包是当前正在处理的项目。您越往上走,数据包就越靠后。堆栈的高度随着工作的完成而降低,随着更多的工作加入队列中而增加。点击一个数据包,即可看到它的生命周期,以及数据包之间的依赖性。
在 WPA 中,我们可以放大感兴趣的帧并确定帧的确切帧索引。然后,我们可以使用该帧索引在 GPUView 中找到这一帧。这很有帮助,因为 GPUView 不提供前后相关的自上而下的视图。
在上面的示例中,应用程序调用了 DirectX 驱动程序,并请求它渲染一个基元。该驱动程序调用创建了 GPU 工作。因此,3D 管线正在突出显示的选择中处理该工作。
典型的流程是:初始化、提交 CPU 工作、提交相关图形工作、安装栅栏以确保工作按正确顺序处理、等待 CPU 工作(以及栅栏)完成,然后继续下一帧。
上图中,同一 GPU 数据包在整个时间线中突出显示。但之后,它位于队列的前面(底部),并且正在处理中。根据顶部显示的时间标度,数据包的宽度表示处理它所花费的时间。
您还可以获取数据包的追踪。如果您看到应用程序正在大量使用系统,而您不知道原因,则可以看看导致问题的调用的确切顺序。
能够在 GPUView 中查看 VSyncs 非常有用。但是,菜单项选项 > 切换 VSync不会显示 Oculus VSync。此菜单项会显示 Windows 显示器的 VSyncs。
ovrlog_win10 和 ovrlog 会生成包含 NVIDIA 或 AMD vsync 事件的追踪,具体取决于系统使用的硬件。要在 GPUView 中显示 vsyncs,您必须在生成 ETW 追踪之前运行以下批处理文件:
%PROGRAMFILES%\Oculus\Support\oculus-diagnostics\ETW\IHVETW\setup.bat
安装清单后,为 NVIDIA 捕捉 VSyncs 的事件为:
NVIDIA-VR-DirectMode VsyncDPC
AMD 也有类似的事件。
在 GPUView 中打开追踪,点击“工具”>“事件查看器”,然后启用 NVIDIA-VR-DirectMode,追踪图中应当会显示垂直红线。