您的游戏是否受 GPU 限制
在本文中,我们将通过一个高层次的系统概述,快速轻松地了解您的游戏是否受 CPU 限制。
计算机图形学是我们日常生活中一个重要且令人惊叹的部分——我们使用计算机工作、观看电影、使用智能手机,甚至驾驶汽车。在过去的 10 年里,图形处理器的性能得到了显著提升。视频游戏行业在此过程中的影响力不可低估。同时,GPU 功能的持续增长为游戏开发者提供了新的机遇,鼓励他们发明突破性的渲染技术和效果,以获得一切可能的硬件优势。但是,GPU 硬件开发者和游戏开发者之间的这场竞赛也带来了一个缺点。创新的渲染技术往往会遇到硬件限制。
在本文中,我们将通过一个高层次的系统概述,快速轻松地了解您的游戏是否受 CPU 限制。
游戏性能基础
视频游戏制作成本高昂。投资性能优化是提高游戏项目盈利能力的重要因素。通常,不同的游戏类型——动作、冒险、策略等——对性能的要求也不同。如果一款游戏在视觉上看起来很慢,出现明显的卡顿或绘制伪影延迟,那么它肯定存在性能问题,必须予以解决。
衡量游戏性能的一个正式指标是帧率,即每秒渲染的帧数 (FPS)。FPS 用于对不同应用程序进行基准测试和排名:FPS 越高越好。在大多数情况下,这种方法是可行的。例如,一个有很多动作的游戏,如果 FPS 很低,看起来就不会很好。
现代游戏是一个由多个组件组成的复杂产品
- 渲染图形
- 计算物理
- 播放声音
- 执行脚本
- 托管网络
- 还有更多
每个组件,单独或组合使用,都可能影响游戏性能。因此,要确定应用程序是受 GPU 限制还是受 CPU 限制,可能会很棘手。虽然这只是游戏的一个方面,但从渲染开始分析是合理的,因为图形对于创造游戏的独特风格、精神和氛围至关重要。
经典渲染管线
如果不了解图形渲染管线、图形编程模型以及图形驱动程序在此过程中的作用,就无法精确地确定 GPU 是否是性能瓶颈。对整个堆栈——从应用程序代码到硬件——的 GPU 活动进行全面分析需要大量的专业知识。幸运的是,进行基本的性能分析以了解整体 GPU 利用率,而无需深入细节,就已经足够了。
渲染管线使用资源和状态进行操作。绑定到管线的资源指定要渲染什么以及在哪里渲染。它们可以包含几何体、纹理和正确格式的渲染目标。光栅化参数、深度测试条件、混合属性和其他状态指定了如何解释和处理这些资源以在屏幕上生成图像。渲染资源和状态与 GPU 程序(也称为着色器)紧密相连,这些着色器在渲染管线的不同阶段执行(**图 1**)。
管线的经典渲染过程会按顺序修改源数据,将其通过相同的阶段,直到到达目标。任何渲染对象首先会在虚拟空间中进行变换,然后投影到屏幕表面。之后,该投影的可见部分会被着色并与帧缓冲区中的其他渲染对象合并。
图形编程模型很简单。配置、打开并使用绘图上下文,按正确的顺序提交渲染命令:在调用任何绘图命令(指示管线执行任务)之前,需要将所需的资源、状态和程序绑定到管线。此过程根据需要对对象进行渲染的次数重复,直到在帧缓冲区中形成最终场景。我们可以使用缓冲区交换命令将其推送到屏幕上。无论应用程序使用 OpenGL*、DirectX*、Vulkan* 还是任何其他图形 API,这个概念都是相同的。
现在显而易见,单个绘制操作包含多少复杂的步骤。每个操作都会影响绘制的持续时间。单个绘制的持续时间各不相同,影响总帧渲染时间。较长的帧时间可能表明 GPU 受限的情况,我们可以通过根据图形驱动程序性能标记估算 GPU 负载来确认或否定这一点。
图形驱动程序活动
常见的图形程序与图形驱动程序一起工作,但从不直接与 GPU 交互。每当我们应用程序中打开一个绘图上下文时,我们实际上就创建了一个对应的图形驱动程序接口,称为驱动程序设备上下文。为了使渲染成为可能,驱动程序必须执行大量工作
- 在 GPU 上释放和分配内存块
- 将渲染所需的资源从 CPU 上传到 GPU
- 设置 GPU 执行单元的寄存器
- 上传 GPU 程序
- 将结果传回 CPU
- 还有更多
每当我们应用程序代码中调用一系列图形 API 调用时,驱动程序会将它们转换为一系列适用于 GPU 的命令。命令不会立即在 GPU 上执行。相反,它们会累积在命令缓冲区中。驱动程序会不断地将一系列命令打包成数据包,然后将这些数据包推送到设备上下文队列,安排它们执行(**图 2**)。
设备上下文队列可以包含不同类型的具有不同类型命令的数据包。数据包中占主导地位的命令类型决定了数据包的类型。每个数据包都保留在队列中,等待上一个数据包中写入的最后一个命令在 GPU 上执行。(例如,请参阅**图 2** 中选定的渲染数据包。)
探索设备上下文队列可以为我们提供一些有用的性能洞察。例如,巨大的队列大小通常对应于提交给 GPU 的大量图形工作。数据包执行时间长可能是由于计算密集型的绘图过程。数据包等待时间长可能是由于低效的渲染算法或同步问题。
当我们识别出与单个帧相关的所有数据包后,我们就可以粗略估计帧持续时间,该持续时间可以计算为从提交队列中的第一个命令数据包到该帧中执行最后一个提交数据包中的最后一个命令的时间范围。
但是,即使帧时间很长,我们也不能确定我们的应用程序是否受 GPU 限制,除非我们检查与执行渲染的图形处理器关联的相应 GPU 硬件队列。GPU 是一个共享资源,可以同时为多个应用程序提供服务,进行图形渲染。渲染时间长可能是由于与同时获取了 GPU 上下文的另一个应用程序并发执行造成的。
硬件 GPU 队列(**图 3**)提供了 GPU 整体利用率的清晰图景。我们可以使用此队列来确定 GPU 的繁忙程度以及当前正在渲染的应用程序。
图 3 中的 GPU 队列快照显示了至少两个同时渲染的应用程序,它们通过命令数据包的颜色进行区分。没有一个应用程序受到 GPU 限制。以蓝色突出显示的应用程序的帧时间仅略长于 11 毫秒,大约对应 80 FPS。而 80 FPS 通常足够高了。绿色队列似乎是一个后台进程,帧时间非常短(每个大约 5 毫秒)。此外,GPU 的忙碌程度甚至还没有达到其最大能力,因为我们可以在执行命令数据包之间看到多个间隙,这些间隙对应于 GPU 空闲的时期。
从可靠性角度来看,分析软件和硬件队列的概念非常有前景。此外,这些队列很容易构建,因为我们知道如何获取所需的性能数据。
系统事件跟踪
无论我们是在 Windows*、Linux*、macOS* 还是任何其他操作系统上工作,我们都可以连接到系统事件跟踪层,该层会记录与不同系统模块内关键执行点相关的各种事件。有些事件适用于性能分析。图形驱动程序也不例外。每当驱动程序将命令数据包推送到设备上下文队列、将命令数据包上传到 GPU 或执行命令数据包中写入的最后一个命令时,它就会将相应的事件提交到系统跟踪层,以便我们轻松获取它们。例如,如果我们想在 Windows 上构建设备上下文和 GPU 命令数据包队列,我们需要从 Event Tracing for Windows* (ETW*) 系统的 Microsoft-Windows-DxgKrnl 提供程序捕获几个事件。
编码到事件数据中的不同属性使得可以将不同的事件绑定在一起,以随时区分队列中每个数据包的当前状态。
图形应用程序分析
系统事件跟踪文档齐全,并且可以在任何平台上使用。有许多工具可以捕获或可视化系统跟踪数据。但是,能够同时正确分析设备上下文和 GPU 硬件队列的工具数量有限。Intel® GPA Graphics Trace Analyzer 就是其中一款工具,它旨在以不同级别的详细程度分析图形应用程序的性能,从高层次的系统分析到单帧每绘制分析。
现在,让我们将学到的知识应用到一个现实的、图形密集型游戏中。我们可以尝试在一台具有中等性能图形处理器的工作站上进行此操作,以使我们的实验可预测。我们将使用刚刚发布的《无主之地 3》*,这是一款著名的第一人称射击游戏。我们将在Intel® NUC Mini PC NUC8i7HVK 上运行它,该设备有两个图形处理器:集成的 Intel® HD Graphics 630 和独立的 AMD Radeon RX Vega M GL*。如果我们以 2560x1440 分辨率运行该游戏,并为宽屏显示器进行适配,并将游戏中的所有图形选项都设置为高配,则游戏引擎会选择最高性能的图形处理器进行渲染,在该设备上似乎是 Radeon Vega。
玩了五分钟后,首先引起我们注意的是,在更改输入设备(如鼠标或游戏手柄)的状态与屏幕上的场景发生变化之间存在延迟。一些移动对象的动画也显得有些粗糙。如果我们使用 Intel GPA Graphics Trace Analyzer 捕获并打开一个跟踪文件,我们就可以从时间轴视图的设备上下文队列和 GPU 硬件队列(**图 4**)中一眼看出 GPU 受限场景的所有属性。
GPU 队列没有间隙。它完全处于繁忙状态,不断执行游戏提交的命令。设备上下文队列大小足够大,这意味着大量的图形工作已经准备就绪,正在等待渲染。我们还可以测量帧持续时间,选择在一个帧内执行的所有命令数据包。大约 48.8 毫秒的帧持续时间对应大约 21 FPS,这对于这款动作游戏来说绝对是不够的。像这样的游戏通常需要 60 FPS 以上才能获得最佳游戏体验。
通过高层次的系统概述分析 GPU 受限场景,并探索软件和硬件队列的细分,可以为我们带来几项好处。这种分析既快速又准确。它不需要图形专业知识,也不取决于用于渲染的图形 API 类型。它还可以用于任何可以捕获系统跟踪中的相应性能事件的平台。