适用于 Android 的 Godot Engine Vulkan 优化

Godot Engine 吉祥物图片

概览

Godot Engine 是一个广受欢迎的多平台开源游戏引擎,对 Android 提供强大的支持。Godot 可用于制作几乎任何类型的游戏,并且支持 2D 和 3D 图形。Godot 4 版引入了新的渲染系统,该系统具有用于高保真图形的高级功能。Godot 4 渲染程序专为 Vulkan 等现代图形 API 而设计。

Godot Foundation 聘请了 The Forge Interactive 的图形优化专家,并与 Google 合作分析和进一步改进了 Godot 4 Vulkan 渲染程序,并将这些优化结果合并回项目代码库。这些优化有助于开发者改进 Android 上的自定义 Vulkan 渲染程序。

优化方法和结果

优化流程使用了 Godot 中的两个不同的 3D 场景作为基准测试目标。在每次优化迭代期间,我们都会在多部设备上测量场景的渲染时间。若要符合纳入条件,对渲染程序所做的更改需要在至少部分测试设备上带来性能改进,并且不能在任何设备上引入性能回归。

测试中使用了多种流行的 Android GPU 架构。虽然许多优化带来了总体改进,但有些优化对特定 GPU 架构的影响更大。所有优化工作总和通常可将 GPU 帧时间缩短 10%-20%。

常规 Vulkan 优化

Forge 对 Godot Vulkan 渲染后端进行了一般架构重构,以提升性能并帮助后端随着内容渲染需求的增加而扩容。这些优化并非仅适用于移动硬件,而是对所有 Godot Vulkan 平台都有益。

动态 UBO 偏移支持

绑定包含动态均匀缓冲区对象 (UBO) 的描述符集时,Vulkan 允许在绑定参数中指定 UBO 中的动态偏移量。此功能可用于将多个渲染操作的数据打包到单个 UBO 中,使用不同的动态偏移重新绑定描述符集,以便为着色器选择适当的数据。更新了 Godot Vulkan 渲染程序,使其能够使用动态偏移量,而不是始终将偏移量初始化为零。这项改进有助于日后进行效率优化。

线性描述符集池

以前,Godot Vulkan 渲染程序中的默认行为是使用 VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT 标志创建所有描述符集池,这意味着描述符集分配可以释放回池,并且可以从池中进行重新分配。与仅允许线性分配并随后完全重置池的描述符集池相比,此模式会增加额外的开销。

现在,描述符集池会尽可能以线性池的形式创建,而无需设置 VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT。然后,在需要时,线性池会整体重置。这项工作还包括在可行的情况下对批量描述符集绑定进行额外优化,从而减少对 vkCmdBindDescriptorSets() 的离散调用次数。

对不可变采样器的支持

包含抽样配置数据的采样器对象通常会作为描述符集数据的一部分进行绑定。此方法允许在描述符集数据中动态换出采样器对象。Vulkan 还支持不可变的采样器,可将采样器数据直接编码到描述符集布局中。此采样器配置会在创建描述符集和流水线状态时绑定,并且在创建后无法更改。

不可变的采样器会牺牲灵活性,但不再需要管理和绑定离散的采样器对象。更新了 Godot Vulkan 渲染程序,以支持使用不可变采样器;采样器用法已更改为在可行的情况下使用不可变采样器。

侧重于移动设备的优化

我们还实现了其他优化,以专门提升移动图形硬件的渲染性能。由于架构设计不同,这些优化通常与桌面级图形硬件无关。

优化包括:

  • 替换了使用大型推送常量
  • 延迟缓冲区分配
  • 持久缓冲区支持
  • ASTC 解码模式更改
  • 屏幕预旋转

替换了使用大型推送常量

推送常量是一项功能,可用于将有效着色器程序的常量值注入命令缓冲区。推送常量非常方便,因为它们不需要创建和填充缓冲区,也不会与描述符相关联。不过,推送常量有最大大小限制,并且可能会对移动硬件上的性能产生负面影响。

在 Android 设备上进行测试时,通过将超过 16 字节的推送常量用量替换为统一缓冲区,性能得到了提升。使用 16 个字节或更少常量数据的着色器在使用推送常量时性能更高。除了性能注意事项之外,某些图形硬件对均匀缓冲区的要求是至少 64 字节对齐,这会导致与使用推送常量相比,由于未使用的内存填充而降低内存效率。

延迟缓冲区分配

大多数移动图形硬件都使用基于平铺的延迟渲染 (TBDR) 架构。使用 TBDR 的 GPU 会将较大的屏幕区域拆分为由较小功能块组成的网格,并按功能块进行渲染。每个图块都由少量高速 RAM 支持,GPU 在渲染图块时会使用这些 RAM 进行存储。使用 TBDR 时,在其渲染传递之外从未被其他目标采样的渲染目标可以有效地完全保留在功能块 RAM 中,并且不需要主内存后备存储区缓冲区。

在创建适当的渲染目标(例如主颜色和深度目标)期间添加了 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT,以避免分配永远不会使用的缓冲区内存。在示例场景中,延迟分配内存可节省的内存量最高可达约 50 兆字节的 RAM。

持久缓冲区支持

移动硬件使用统一内存架构 (UMA),而不是在主 RAM 和图形 RAM 之间进行硬件区分。当主 RAM 和图形 RAM 分开时,数据必须从主 RAM 传输到图形 RAM,才能供 GPU 使用。Godot 已在其 Vulkan 渲染程序中通过使用暂存缓冲区实现了此传输过程。在 UMA 硬件上,许多类型的数据不需要临时缓冲区;内存可供 CPU 和 GPU 使用。Forge 在受支持的硬件上实现了对持久共享缓冲区的支持,以尽可能消除分阶段处理。

一组 GoDot 场景图片,显示启用和未启用持久缓冲区时的性能分析信息。
图 1. 对示例场景中启用和停用持久缓冲区之间的性能差异进行性能分析。

ASTC 解码模式更改

自适应可伸缩纹理压缩 (ASTC) 是移动设备硬件上首选的新型纹理压缩格式。在解压缩期间,GPU 默认可能会将纹理单元解码为一个中间值,该值的精度高于视觉保真度所需的精度,导致纹理效率降低。如果目标硬件支持,则在解码时,VK_EXT_astc_decode_mode 扩展用于指定每个组件的 8 位未归一化值,而不是 16 位浮点值。

屏幕预旋转

为了在 Android 上使用 Vulkan 时获得最佳性能,游戏必须使屏幕的设备屏幕方向与其渲染 surface 方向保持一致。此过程称为预旋转。由于 Android OS 需要添加合成器通道才能手动旋转图片,因此未能执行预旋转可能会导致性能下降。在 Godot 渲染程序中添加了对 Android 上的预旋转的支持。

调试功能改进

除了进行性能优化之外,The Forge 还通过以下增强功能改善了在 Godot 渲染程序中调试图形问题的体验:

  • 设备故障延期
  • 路径
  • 调试标记

设备故障延期

当 GPU 在渲染操作期间遇到问题时,Vulkan 驱动程序可以从 Vulkan API 调用返回 VK_ERROR_DEVICE_LOST 结果。默认情况下,系统不会提供有关驱动程序返回 VK_ERROR_DEVICE_LOST 的原因的任何其他背景信息。VK_EXT_device_fault 扩展提供了一种机制,供驱动程序提供有关故障性质的其他信息。Godot 添加了对启用设备故障扩展(如果可用)和报告驱动程序返回的信息的支持。

GPU 崩溃或执行暂停可能很难调试。为帮助确定故障发生时可能呈现的图形内容,我们在 Godot 渲染程序中添加了面包屑导航支持。面包屑导航是用户定义的值,可附加到渲染图中绘制列表中的内容。在开始新的渲染传递之前,系统会写入面包屑数据。如果发生崩溃或执行停滞,您可以使用当前面包屑导航值来确定可能导致问题的数据。

调试标记

调试标记(如果驱动程序支持)用于命名资源。这样一来,在使用 RenderDoc 等图形工具时,用户可读取的字符串便可与渲染传递等操作以及缓冲区和纹理等资源相关联。向 Godot Vulkan 渲染程序添加了调试标记注解支持。

Godot Engine 博客 - 与 Google 和 The Forge 的合作最新动态

Godot Engine Vulkan 协作拉取请求