没有任何秘密的 API:Vulkan* 前言的实用方法

全新的开始

欢迎回到 Vulkan* API 系列的后续文章。在 Vulkan* 简介 教程中,我们了解了关于这个低级图形编程库的最重要的基本知识。您已经知道如何编写使用 Vulkan 的简单程序。您知道如何在屏幕上展示场景。现在,您可以使用这些知识来展示更复杂的场景。

Vulkan API 非常有条理,而且极为简洁。但是它也非常繁琐,需要编写大量代码。Vulkan 的学习曲线非常陡峭。编写基本程序极为困难。但是从 API 的角度来看,您一旦掌握了它,便能开发更复杂的场景,无需学习更多知识。展示波澜壮阔的场景比展示简单的三角形在难度上没有增加太多。另一方面,以最佳的方式完成它具有一定的挑战性。因此,我们认为是时候继续推出教程,以尝试另一种方法和观点。

目标

更复杂的场景需要更高级的任务处理方法,如资源管理、多线程或同步。可以以多种不同的方式来执行这些任务,但是您可能不知道哪种方法最适合您的用例。有时,您需要将应用移植到不同的平台。有时,您希望能轻松维护您的代码。或者,您想准备一套可以简化未来应用开发工作的工具。我们每个人的优先考虑事项可能有所不同,但是在 3D 图形和 3D 工具中,最常见的目标通常是获得最佳的性能。那么,您应该采取哪种方法来实现提升性能的目标,以及如何避免性能下降?

遗憾的是,Vulkan 没有提供简单的答案。和所有低级 API 一样,单个解决方案能够完美契合特定平台,但是其他平台可能要求完全不同的方法。如果您想最大限度发挥目标图形硬件的性能,便不能为所有平台准备一款解决方案。每个平台都有各自的特征 - 架构、内存、能力、特性、限制 - Vulkan 将它们全部显示出来。此外,我们负责合理使用这些特征。幸运的是,某些话题和问题普遍存在于多个平台。这些文章的重点在于以上共性以及在应用中使用 Vulkan 的实用之处。因此,我们对该图形库的各个方面进行测试和深入介绍。

同时,我们将为您提供工具,以帮助您清楚了解如何处理每个特定领域以及微调您的应用。为此,我们将向您展示专注于 Vulkan API 具体部件的代码示例,包括命令缓冲区、描述符集、内存类型、缓冲区和图像资源、管道、着色器等。复杂的场景需要您巧妙地同时使用并管理上述所有小部件。由于它们彼此之间存在很多依赖性,充分了解每个领域至关重要。

代码

每个代码示例被编制为单独的项目,您可以在 Windows* 和 Linux* 操作系统上编译与执行它们。此外,每个示例显示一系列参数。您可以在运行时调整参数,以查看它们如何影响应用行为。

GitHub* 存储库免费提供该系列文章的代码。遗憾的是,使用 Vulkan 时,您仍需编写大量代码。此外,鉴于各种资源之间的依赖性,您无法创建通用代码集,或通过调整来满足特定需求。思考以下场景:假设您想绘制一个简单的场景。为此,您需要渲染一个通道和管道,还需要渲染其他资源。管道创建要求您指定在哪个渲染通道的子通道中使用管道。如果您想修改渲染通道设置,可能需要以不同的方式重新创建管道。这就是您发现代码有些重复的原因。为了缩短代码,我们使用 vulkan.hpp 头文件(面向所有 Vulkan 对象和函数的 C++ 包装程序)来开发示例。该包装程序支持您使用异常、默认函数参数或自动资源析构。使用 Vulkan SDK 分配该包装程序文件。

我们还准备了一个简单的帮助函数,以将焦点转移到我们的目标而不是方式上。我们的目标是使您了解代码,并从这些示例中学会使用 Vulkan,无需反复参阅多个文件或搜索其他地方。

每个示例的代码结构如下所示:

  • Vulkan 通用文件负责创建运行基于 Vulkan 的应用所需的基本资源,包括示例、设备和交换链。它们基于《没有任何秘密的 API:Vulkan 简介》代码。
  • 操作系统标头和源文件包含依赖于操作系统的代码部分,比如窗口创建、消息处理和渲染循环。这些文件包含面向 Linux 和 Windows 的代码,不过我尽可能地保持它们的统一。
  • 示例通用文件实施简单的函数集,以执行资源创建,通过分期资源传输数据或设置并执行管道壁垒。编写这些文件是为了提高简洁性和可读性,而非优化性能(如数据传输函数总是等待传输完成)。它们并非用于性能关键型代码部分,但是您可以利用它们快速、轻松地执行操作,无需复制过多代码。
  • 示例标头和源文件实施面向特定代码示例的场景绘制。无论从哪个话题的角度来看,它们都是最重要的文件。
  • 工具提供更多效用函数,如读取二进制文件的内容或准备透视投影矩阵。

如果没有用于加载图像数据 (stb_image) 和显示用户界面 (Dear ImGui*) 的外部库,项目将更难准备,并且耗费更多时间。

参数

应用行为最明显的参数是帧速率。对应用开发人员来说,性能上升或下降是最重要的因素之一。为此,示例程序支持您查看每秒生成的帧数。在这些文章中,我们不想展示具体、绝对的测量结果,因为测量取决于特定硬件平台、可用内存、操作系统、后台安装与执行的软件、电源管理、连接计算机的配件、显示器数量等多个因素。相反,我们想展示哪些参数可能影响应用的性能以及影响的方式。在之前讨论的每个话题中,我们的目标是指出采取哪种方法,如何实施该方法,哪些参数与该话题相关以及参数对应用有什么影响。

Example of frame resource window with adjustable parameters
图 1:包含可调参数的窗口示例。

这些文章中的每个示例程序均展示了一系列参数,您可以在运行时调整这些参数,以查看它们如何影响应用的行为和性能。就每秒帧数 (fps) 和帧生成时长两者中,哪个指标能更好地体现性能测量结果,业界进行了广泛的讨论,认为两者各有利弊。时长更普遍,对开发人员更有价值,而 fps 对最终用户更重要。fps 更易感知性能下降,但是转换并非线性。

例如,如果性能从 60 fps 降至 50 fps,fps 的测量结果下降了 16.67%。但是,将相同的性能损失转换为时间(60 fps 意味着 16.67 毫秒,50 fps 意味着 20 毫秒)后,相当于帧生成时长增加了 20%。
60 FPS == 约 16.67 毫秒
50 FPS == 20 毫秒
100% * (50 – 60) / 60 = -16.67% [FPS]
100% * (20 – 16.67) / 16.67 = +20% [ms]

为了满足偏爱两个指标的用户,用 fps 来测量并显示性能,帧生成时长的单位为毫秒。基于最近 10 秒的应用执行计算测量结果的平均值,以显示稳定的结果。您也可以根据 10 秒的历史来预测趋势。请记住,在该系列文章中,绝对性能不是最重要的概念。性能的相对变化才是关键,更重要的是应用的一般行为。

建议您在观察性能差异和应用行为时,不要忘记监控功耗。电源管理对于以移动设备为目标的开发人员和在小型设备上运行软件的用户尤为重要。在这些平台上,低功耗至关重要。因此,了解电源管理功能的工作原理也很重要,切记,电源管理可能影响应用性能。

减少 CPU 和图形处理单元 (GPU) 工作负载时,您通常期待性能提升。但是在这种情况下,您实际上可能会发现性能下降。怎么会这样?这是因为 CPU 或 GPU 的任务量减少,根据设置,电源管理可能降低 CPU 或 GPU 频率,以减少整体功耗。换言之,如果电源管理功能决定不在简单的任务上浪费功耗,将降低 CPU 或 GPU 频率。这是一个可取的行为,可延长移动设备的电池续航时间。

但是,尽管您的目标并不是将功耗降至最低,我们建议您监控 CPU 和 GPU 工作负载,并了解电源管理与当前设置。出于开发目的,您可以禁用电源管理或将其切换至最大性能模式,以确保性能能正确反映应用的变化。尽管与 Vulkan 相关的设计决策可能影响您的应用,但是请记住,这些设计修改只是影响应用行为和性能的众多因素中的一个。

欢迎反馈

您对 Vulkan 或文章仍有疑问或意见?或者您对最新测试有何想法?是否有与 Vulkan 相关的特别有趣的话题?立即发表评论。我们将尽全力准备更多代码示例与文章。我们希望该系列文章的发表将引起关于 Vulkan API 和低级图形库的公开讨论。

有关编译器优化的更完整信息,请参阅优化通知