概览
Godot Engine 是一款流行的多平台开源游戏引擎,对 Android 提供强大的支持。Godot 可用于创建几乎任何类型的游戏,并支持 2D 和 3D 图形。Godot 4 版本引入了一个新的渲染系统,具有用于高保真图形的先进功能。Godot 4 渲染器专为 Vulkan 等现代图形 API 设计。
Godot 基金会与 The Forge Interactive 的图形优化专家合作,并与 Google 协作,分析并进一步改进了 Godot 4 Vulkan 渲染器,并将这些优化合并回项目仓库。这些优化有助于开发者改进 Android 上的自定义 Vulkan 渲染器。
优化方法和结果
优化过程使用了 Godot 中的两个不同 3D 场景作为基准测试目标。在每次优化迭代中,都在多个设备上测量了场景的渲染时间。为了获得收录资格,渲染器的更改需要至少在某些测试设备上显示出性能提升,并且不能在任何设备上引入性能下降。
测试中使用了多种流行的 Android GPU 架构。尽管许多优化带来了普遍的改进,但某些优化对特定 GPU 架构的影响更大。所有优化工作的总和使 GPU 帧时间普遍减少了 10%-20%。
通用 Vulkan 优化
The 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 渲染瓦片时,GPU 会将其用于存储。使用 TBDR,在渲染通道之外从未被其他目标采样的渲染目标可以有效地完全保留在瓦片 RAM 中,并且不需要主内存后备存储的缓冲区。
在创建适当的渲染目标(如主颜色和深度目标)时,添加了 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
,以避免分配永远不会使用的缓冲区内存。测得示例场景中的惰性分配内存节省高达约 50 兆字节的 RAM。
持久缓冲区支持
移动硬件使用统一内存架构 (UMA),而不是在主 RAM 和图形 RAM 之间进行硬件区分。当主 RAM 和图形 RAM 分开时,数据必须从主 RAM 传输到图形 RAM 才能被 GPU 使用。Godot 在其 Vulkan 渲染器中已经通过使用暂存缓冲区实现了此传输过程。在 UMA 硬件上,对于许多类型的数据,暂存缓冲区是不必要的;CPU 和 GPU 都可以使用内存。The Forge 在受支持的硬件上实现了对持久共享缓冲区的支持,以尽可能消除暂存。

ASTC 解码模式更改
自适应可伸缩纹理压缩 (ASTC) 是移动硬件上首选的现代纹理压缩格式。在解压缩期间,GPU 默认可能会将纹素解码为中间值,该值的精度高于视觉保真度所需的精度,从而导致纹理效率损失。如果目标硬件支持,VK_EXT_astc_decode_mode
扩展用于在解码时指定每组件 8 位非归一化值,而不是 16 位浮点值。
屏幕预旋转
为了在使用 Android 上的 Vulkan 时获得最佳性能,游戏必须协调屏幕的设备方向与其渲染表面方向。此过程称为预旋转。未能执行预旋转可能会导致性能下降,因为 Android 操作系统需要添加一个合成器通道来手动旋转图像。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 合作的最新进展