Link

简介

在尝试 Atlus 的新游戏 Metaphor: Refantazio 时,我注意到它在 Steam Deck 上的性能对于这款游戏的图形来说是低于标准的。虽然它具有出色的美术风格,但性能与其中看到的技术不符,因为它似乎来自 Persona 5 引擎,而 Persona 5 引擎是在 PS3 上运行的。因此,我捕获了一些不同的场景,并分析了结果,试图找出造成这种性能的原因。

我在这里的重点是 Steam Deck 的 GPU,以及这款游戏中看到的渲染技术如何适用于它。这款游戏专注于 PS5 和 PC,并没有特别针对 Steam Deck,但 Steam Deck 为中低端 GPU 提供了一个很好的代表,并且它具有非常好的性能测量工具,我们可以在其上使用。这里解释的工作流程也适用于 vkguide 代码,你可以看到这些技术在设备上的运行情况。

性能分析工作流程

分析将使用 2 个主要工具完成。第一个是 RenderDoc https://renderdoc.org,第二个是 Radeon GPU Profiler https://gpuopen.com/rgp/。RenderDoc 将让我们逐个绘制调用地重放帧,并检查帧期间究竟发生了什么,而 Radeon Profiler 将为我们提供执行的命令的计时以及有关它们的信息。通过比较两个程序上的相同捕获,我们可以看到发生了什么,以及哪些特定绘制在其上是慢速/快速的。

Steam Deck 使用 AMD 的 Van Gogh 集成 GPU,作为 CPU 旁边的芯片的一部分。它为 CPU 和 GPU 使用相同的内存。它的主要弱点是它是一个相当小的 GPU(就像所有集成 GPU 一样),并且它的内存带宽相当差,因为它使用普通的 DDR 内存而不是专用 GPU 上看到的更快的 GDDR 内存。由于这种共享内存方案,它类似于 PS4 GPU 的架构。虽然 GPU 很小,但它是一个现代 GPU,并且支持 Vulkan 中的大多数最新功能。vkguide 教程的两个版本都可以在其上未更改地运行。

Steam Deck 以 1280x800p 的低分辨率渲染,即使在内存带宽较低的情况下,它仍然可以运行得相对良好。

为了测试这款游戏的性能,我捕获了游戏中 3 个不同的场景。第一个是沙漠的可玩区域,第二个是森林中一个小型但密集的休息区,第三个是夜晚的主要城市。所有这些都可以在演示开始后的几个小时内找到。

请记住,本文写于 2024 年 8 月 10 日,并且基于游戏的演示版本。开发人员有可能在最终版本或之后的补丁中改进这些方面。该游戏没有原生使用 Vulkan,但 Steam Deck Proton 系统中的转换层将它使用的 DirectX 11 转换为 Vulkan,我们可以对其进行分析。由于这一点,RenderDoc 中有一些奇怪的东西,例如渲染通道的奇怪用法,但这没关系,并且这些系统已经过高度优化。

帧分析

让我们从游戏开始,检查帧中究竟发生了什么,以及游戏如何渲染其世界。

以下是用于分析的 3 个场景的图像。我们将重点关注城市场景,因为它更有趣,但其他 2 个场景将用于比较不同区域的性能。游戏除了环境光遮蔽开关之外没有其他图形设置,我们保持开启状态。原生分辨率为 100% 缩放

沙漠区域看起来像这样。我们有一个开放空间,其中包含一堆透明的灰尘效果和一个地形,以及主角在上面。 地图 运行在约 45 FPS

森林中的休息区看起来像这样。非常小的地图,有一些角色坐在篝火旁。 地图 运行在约 40 FPS

最后,我们有城市。大型地图,上面有大量的物体,以及许多角色在周围走动。我们还在上面有一些文本。 地图 运行在约 30 FPS

这 3 个区域的运行速度都很慢,原因各不相同,但它们有一些共同之处。在其他区域(如地下城或战斗场景)中,游戏保持 60 FPS。

使用 Steam Deck 调试/开发实用程序在 RenderDoc 中捕获场景,让我们看看城市场景以了解游戏正在做什么。

我们可以在 RenderDoc 中看到这一点 地图 这里有很多绘制命令。最终输出,一个 1280x800 的图像,正如我们所预期的那样。

游戏以计算通道开始,在其中运行一堆计算着色器。 地图

这些着色器做什么,我不知道,因为我们无法访问原始着色器或任何可以给我们提示的调试信息。可以看到的是,它们是纯粹在数据缓冲区上工作的着色器,而不是纹理。最有可能的是,它们类似于 GPU 粒子计算和角色蒙皮计算。我们不会在这个引擎中看到任何进一步的计算着色器使用。鉴于城市 RenderDoc 捕获比只有 3 个角色的休息区具有更多的初始计算,更可能的选择是这些计算着色器用于预先计算动画角色姿势。

接下来要看到的是多个仅深度通道。这些是级联阴影贴图,从天空渲染场景,以便稍后在帧中可以使用它们来计算太阳阴影。其中 3 个。对于我们所看到的,这里有太多可疑的命令,但我们稍后会检查。

以下是不同的阴影贴图。每个都是 2048x2048 D32S8 格式的纹理。 地图

如果有人注意,可以在 3 个级联阴影中的一个上看到角色,最后一个级联阴影绘制了大部分地图。

在渲染阴影后,游戏使用经典的延迟渲染方案将主环境渲染到一组 G 缓冲区中。 地图

完整图像中的一个是 RGBA 8 位颜色,其中包含对象的主纹理。然后我们有另一个 G 缓冲区,它以 RGBA 8 位格式存储材质属性,这似乎是粗糙度和金属感。这里的窗户可以看到粉红色,因此它似乎还在多个材质之间进行选择。接下来我们有一个 RGBA16f 格式的法线 G 缓冲区,这是一个相当大的错误,因为它使用了明显过度的格式来存储法线。通常,G 缓冲区法线将被编码为 RGB 8 位(无 alpha),或 RG 16(2 个通道),并使用诸如八面体编码之类的编码。因此,游戏使用 RGBA16f 比它需要的内存多两倍。在像 Steam Deck 这样受限的平台上,这不好,并且它取决于游戏分辨率,这意味着这种内存使用量直接随绘制分辨率缩放。上面没有图片,但还有一个使用 32 位浮点数的场景深度纹理。这包含朝向相机的距离,并且是 z 缓冲区的副本,但数字刻度不同(似乎是在游戏内单位而不是从 0 到 1)。对于这一点,没有必要在此处绘制它,因为可以将其作为单独的步骤来完成,在该步骤中,我们将本地深度缓冲区加载到此距离 G 缓冲区中,从而节省了大量的像素写入带宽。

下一个通道是仅深度通道,其中将角色和其他非环境对象绘制到深度缓冲区中。这会将角色组合到深度缓冲区中以便稍后处理。 地图

现在我们开始延迟照明。这似乎有错误,因为它首先开始将每个光源绘制为球体,并将光源覆盖的像素设置为模板值。一旦为每个光源完成此操作,它将再次绘制光源,并且其像素着色器将光应用于球体覆盖的像素。

第一个通道未在第二个通道中使用,因此第一个通道设置模板值是完全无用的。这可能是开发人员方面的错误,它将导致我们在延迟照明方面付出显著的性能代价,我们稍后将对此进行检查。设置模板值的第一个通道很可能可以完全删除,而对最终图像没有任何影响。

地图 在此图像中,我们可以看到光源的作用。绘制一个覆盖点光源边界的球体,并将光应用于其中的像素。光源没有剔除,因此场景中的每个点光源都会绘制其球体,即使它在屏幕外也是如此。

一旦渲染了所有点光源,它将应用一个全屏四边形,绘制场景的全局光照,并应用太阳光照。 地图

我们将看到很多这样的全屏四边形。游戏不使用计算着色器进行绘制,而是选择使用全屏四边形,在像素着色器中完成该逻辑。虽然如果您确实需要在所有像素上运行该逻辑,这样做是可以的,但该引擎喜欢将后期处理和效果作为多层全屏四边形,性能低下。

在全局光照之后,我们连续看到另外 3 个全屏四边形。其中一个使用较低分辨率的图像绘制天空/背景,另一个将背景应用到全分辨率的图像之上,第三个应用全局雾。这些都很微妙,因此它们看起来与上面的图像非常相似。

接下来,它在我们拥有的完全渲染的背景之上绘制角色。有趣的是,在此通道中,角色比环境亮得多。然后再次绘制角色,以使用常用的反向外壳方法绘制其轮廓。之后,它看起来像这样。 地图

请注意,与角色相比,背景更暗。

紧随其后,是另一个全屏通道,它使角色变暗一点,以及更多的全屏后期处理通道。其中一个更有趣的是场景的降采样,它将用于稍后的 Bloom 和一些视觉效果。它执行的一些效果是游戏轮廓着色器以实现绘制效果,景深模糊远处的物体(在过场动画中清晰可见),或为阴影添加滤镜以获得绘制感。

有一系列全屏通道来组合这种花哨的镜头光晕效果。这些至少是低分辨率的。每个通道都使用降采样模糊方法使用较低的分辨率。在这里,你可以看到此链的最终结果。 地图

所有这些都集成回主场景中,主场景不再具有角色与背景相比那么亮的效果。

map

图像提高了对比度,以便我们可以清楚地看到窗户和灯光上的 Bloom 效果。

下一个大的通道是 VFX 和透明物体,其结果如下。 地图

透明对象被绘制到屏幕外的 RGBA16f 纹理中,并在之后立即合成。但当然,并非没有经过更多的后期处理通道来改变其光照并添加 Bloom 效果。

一旦合成到主场景中,它们看起来像这样。 地图

我们几乎完成了帧。接下来是用户界面和诸如语音气泡之类的东西,最终场景的结果如下。

map

在渲染文本时,它为每个字母执行 1 次绘制调用,即单个四边形,这是次优的。许多 UI 都是这样做的,最终总共达到了相当多的绘制次数。

所有这些渲染通道都以 720p 分辨率进行,而 Steam Deck 的分辨率为 800p,因此作为帧中的最后一件事,它将其复制到最终显示图像,从而获得最终外观,顶部和底部带有黑色边框。这些边框不是人为的或绘制的,游戏只是没有以 Steam Deck 的宽高比进行渲染,而是选择以常见的 720p 分辨率进行所有渲染,然后复制到最终图像。在执行最终复制时,它应用伽玛校正和亮度以创建最终图像。

我们现在已经完成了整个帧,并且已经确定了一些奇怪的部分,这些部分可能是性能瓶颈,也可能不是。RenderDoc 不是分析器,而是一个了解正在发生的事情的系统。要真正对其进行分析,我们需要继续使用 Radeon GPU Profiler,在同一位置进行捕获。

性能分析

加载捕获后,我们看到了这个时间线。 地图

在顶部,我们有 wavefront 占用率视图。这是 GPU“使用率”的全局图表。较高的条表示正在使用更多的 GPU,并且它们的颜色取决于 GPU 在该点正在执行的操作。我们可以看到,在前一半中,我们的使用率非常低,特别是在最初的 14 毫秒内,出于某种原因,所有都是顶点着色器调用。然后是一个占用率较低但混合了顶点和片段着色器的区域,然后我们有 2 个片段着色器大量使用的块。帧以 1 毫秒的计算着色器开始,这些是我们在 RenderDoc 中首次看到的神秘计算着色器。

现在我们需要尝试识别场景的这些部分实际在做什么,因此让我们检查命令流,并尝试将其与我们在 RenderDoc 中分析的内容同步。

在比较绘制和时间线后,所有这些初始顶点着色器部分都来自阴影渲染,其中花费了总共 12.5 毫秒来渲染 3 个级联阴影。我们甚至可以在性能时间线中轻松地看到这 3 个“部分”作为 3 个仅顶点着色器工作的块。那么这里究竟发生了什么?一个以 60 fps 运行的游戏需要在 16.6 毫秒内绘制其整个场景,而我们这里的阴影,仅仅是 3 个级联阴影,就花费了 12.5 毫秒,完全膨胀了我们为帧分配的预算。

如果我们选择这些阴影的区域,我们可以在右侧看到 RGP 所说的内容。

map

RGP 说它执行的大部分工作是图元着色器调用,这基本上是顶点着色器。执行像素着色器的着色器调用非常少,只有 15%。让我们回到 RenderDoc,更详细地了解这些阴影正在绘制什么。

一些有趣的事情出现了,那就是 3 个阴影正在渲染完全相同的对象,并且顺序相同。但是每个级联阴影都在渲染明显不同的图像。其中一个靠近角色,仅渲染角色 + 一点地面,而远距离比例的阴影绘制了大部分城市。事实证明,游戏没有对其阴影投射进行任何形式的剔除。即使是小的阴影级联阴影也会将地图中的每个对象都抛给它。这太疯狂了,这就是我们在 RGP 上看到的时间线的结果。如此多的几何体被发送到 GPU 以绘制这些级联阴影,以至于整个执行完全瓶颈在顶点着色器中的纯三角形处理上。即使对象在屏幕外,它仍然需要执行其顶点着色器,这在分析器中显示为 85% 的顶点着色器执行调用。在现代引擎中,很少会受到顶点着色器的瓶颈限制,因为通常总是像素着色器需要最多的计算。受到顶点着色器的瓶颈限制几乎总是表明游戏正在向 GPU 发送太多和/或太密集的网格。

如果在此处进行正确的剔除,唯一绘制起来会很昂贵的阴影级联阴影将是远距离的那个,而其他 2 个阴影将非常便宜。估计它可能会使阴影的时间从 12.5 毫秒降至 4 毫秒。仍然很昂贵,但随后也可以以较低的分辨率渲染阴影,因为 3 个 2k 分辨率的阴影有点昂贵。即便如此,最大的瓶颈是为这些阴影抛向 GPU 的原始三角形数量。对于像 Steam Deck 这样的低分辨率机器,3 个 2k 分辨率的阴影贴图过于精细,毕竟我们正在为 100 万像素的主屏幕渲染价值 1200 万像素的阴影。1024x1024 的阴影分辨率,甚至更低,看起来也会一样。

与其他 2 个区域的捕获进行比较,显示出类似的结果。这是一个引擎范围的问题,并且可能发生在每个地图上。在夜间营地地图上,阴影的绘制成本特别高,因为它具有许多高度密集的植被网格,使其更加糟糕。

我们可以识别的下一个区域是 G 缓冲区环境绘制。这个区域的总体占用率也很低,但顶点瓶颈不那么极端。在城市场景中,绘制背景大约需要 4 毫秒,绘制角色需要另外 2 毫秒,其中有很多 NPC。

在背景中,有些绘制以一种奇怪的方式很慢。虽然大多数绘制执行时间很少,但在这个城市场景中,2 个特定的绘制本身花费了 1 毫秒。在 RenderDoc 中查看它们,它们是地面地形绘制,并且具有与其他网格不同的更复杂的着色器。我真的不知道着色器在做什么,但分析器告诉我们它的占用率很低,片段着色器为 10/16,顶点着色器为 12/16。查看另一个捕获,在沙漠中,情况更加戏剧化。在那个沙漠中,地形网格更大,覆盖了更多的屏幕,因此最终本身花费了 3 毫秒。

map

如果我们查看原始着色器,我们很可能会找到一种方法来稍微优化它并使其更快,这将提供一些额外的性能。其他绘制使用简单的着色器并且运行速度很快,只是有很多绘制,并且它们的多边形数量相当大。RGP 告诉我们,我们有 97% 的 GPU 工作在像素着色器上,因此无论那些地形着色器在做什么,它们的像素着色器都非常繁重,并且是主要成本,而顶点着色器无关紧要。

同样,角色绘制也没有什么特别之处。它们是正向光照的,但它们的着色器并不是很复杂,并且由于保真度,它们的多边形数量很高。由于它们的轮廓,它们被渲染了两次。只有在像城市这样的场景中角色数量庞大的场景中,该部分才真正花费大量时间。在只有 3 个角色的休息区地图上,该部分的绘制时间不长。

有一个问题,那就是 G 缓冲区。法线存储在 4 通道、每通道 16 位的浮点纹理中,但法线不需要这样做。大多数游戏将法线存储为 RGB 8 位,然后将第 4 个 alpha 通道用于其他用途。或者,可以使用 2 通道 16 位纹理,但为这些法线使用八面体编码,在提高质量的同时,将所需的大小减半,与 8 位 3 通道相比。在进行 G 缓冲区时,最大的性能瓶颈始终是内存带宽,因为最终存储的每个像素的数据,因此尝试找到编码数据的最佳方法可以节省大量性能。

继续沿着渲染管线向下,我们到达延迟照明部分。上面,我提到我们将关注这一点。延迟照明花费 2.5 毫秒。球形点光源花费 2 毫秒,环境/太阳光花费半毫秒,后者以全屏四边形渲染。

这简直太慢了。这种光照使用简单的公式,如果使用得当,Steam Deck 有足够的能力绘制数千个花哨的数学 PBR 光照。这种照明比 Cyberpunk 或 Elden Ring 之类的东西还要慢。那么这里发生了什么?

问题再次是由于带宽和光源引起的过度绘制。

地图 RenderDoc 可以选择“覆盖:四边形过度绘制(通道)”显示模式来检查给定渲染通道的过度绘制。这会根据场景中几何体的过度绘制程度,通过彩虹的颜色变为白色。如果你有浅蓝色,那已经是一个警告。这远远超出了。

这里的延迟渲染已损坏,它没有正确地执行模板部分,并且每个光源都绘制一个禁用深度测试的透明球体,这将在其中的每个像素上运行数学计算。这是一种常见但非常糟糕的实现延迟光照的方法(教程就是这样做的),而简单的暴力算法将显著优于它。

这里的过度绘制相当可观,并且使用的带宽非常大,因为对于这些球体绘制中的每个像素,它都需要从 G 缓冲区加载所有数据(G 缓冲区已经比它应该的更大!),并将光照值写回。为了改进这一点,开发人员可以实现一个简单的全屏四边形,其中包含一个暴力着色器(最简单的可能,在每个像素处循环每个光源),这将使 2.5 毫秒的性能使用率降至 0.5 毫秒左右。它们的光源数量对于我所看到的来说相当少,因此在着色器中循环遍历每个光源并不重要。也可以进行平铺延迟方案或其他类似的更高级的延迟光照算法,以进一步提高性能,即使在具有大量光源的场景中也是如此。 这篇文章很好地解释了这些花哨的技术。

一旦角色和背景被渲染,我们就进入帧的最后一部分,包括透明度、后期特效和 UI。这需要 5.5 毫秒。还记得提到那些用于不同效果的全屏四边形吗?由于内存带宽限制,Steam Deck 上原生分辨率的全屏四边形本身将花费 0.25 到 0.4 毫秒,而我们这里有很多。

许多后期处理和几种效果都在彼此之上应用效果层,每个效果层都作为一个全屏四边形。这意味着在它们中的每一个中,它都需要加载这些像素的内存,进行后期处理计算,然后将其存储回去。如果它在一个四边形(或计算着色器)中完成它,一次完成多项操作,那么很可能会获得显著的性能提升。这里的透明度和 VFX 绘制不需要太多时间,基本上可以忽略不计,与全屏四边形相比。在透明度、后期特效和 UI 的 5.5 毫秒中,大约有 3.5 到 4 毫秒是由于一遍又一遍地执行全屏四边形的开销造成的。其中很多确实有意义并且是需要的,就像 Bloom 光照效果一样,但整个管线可以通过合并着色器来显着优化。

通过这种完整的分析,游戏可以运行多快?假设资产没有变化,只是优化渲染代码本身。

所有改进中最大的胜利将是对阴影的更改。通过修复剔除可以节省 8 毫秒,这应该是一个简单的代码修复。改进 G 缓冲区布局压缩内存将节省 RAM,并且可能会使 G 缓冲区和角色通道减少约半毫秒,同时也将使下一个渲染系统再提高半毫秒。修复地形网格及其着色器将节省 0.5 毫秒到 1 毫秒,具体取决于场景。将过度绘制的地狱般的延迟照明变为暴力算法或更高级的方案将节省 2 毫秒,并通过合并着色器改进后期特效管线将再节省 1 到 3 毫秒。总共将节省 12 到 15 毫秒,这将使游戏渲染速度提高近 2 倍。在那个水平上,游戏在 Steam Deck 上的大多数地方都将保持 60 fps。这些事情中的大多数也适用于 PS5,假设渲染代码相似,因此我们可以看到它在原生 4k 分辨率下以 60 fps 运行。通过执行诸如以稍微低一点的分辨率渲染阴影之类的事情可以节省更多的性能,并将其作为设置中的选项提供。以相同的方式,删除第 3 个阴影级联阴影作为选项将从游戏中节省另外 1-2 毫秒,并且对于大多数情况来说,作为低端选项来说会很棒,但这确实会改变图像质量。