vulkan

第 1 章 次世代 3D 图形 API 入门

2018-10-08  本文已影响0人  雨中亭_听雨中

Vulkan 是一套革命性的高性能 3D 图形、计算 API,适用于现代 GPU 管线系统,用来满足社区的苛刻要求。 这套 API 提供了一种全新的方式来克服现有传统 API 的复杂性和差异性。
Vulkan 是一套“显式”的 API,承诺可预测的行为,并允许您在不引起延迟或拖尾的情况下拥有平滑的渲染帧率。 本章将对 Vulkan API 以及相对于它的前身 OpenGL API 来说一些独特的功能进行一下简单的概述。 我们首先来了解一下 Vulkan 的生态系统,并理解它的图形系统。

所以我们将涵盖以下主题:

Vulkan 及其演变

距有名的 OpenGL API 诞生已有将近四分之一世纪的时间,而且还在不断发展中, 在其内部,它是一个纯粹的状态机,包含多个工作在二进制状态下(开状态 / 关状态)的开关。 这些状态用于在驱动程序中构建依赖关系的映射,实现资源的管理并以最优的方式控制资源,从而获得最佳的性能。 这种状态机隐式地进行自动化资源的管理,但是对于捕获应用程序的逻辑来说,还不够智能,而这恰恰正是资源管理背后的驱动力, 因此,可能会出现一些意想不到的情况,例如实现中断,即使应用程序未请求重新编译着色器,也会导致重新编译着色器。 另外,OpenGL API 可能还会受到其他因素的影响,例如不可预知的行为,多线程可伸缩性,渲染故障等。 在本章的后续内容中,我们将比较 OpenGL 和 Vulkan API,以了解两者之间的区别。

Vulkan API 于 2016 年由 Khronos 推出,是一种具有革命性的架构,充分利用了现代图形处理器单元,用来生成高性能的图形和计算应用程序。 如果您不知道 Khronos,它是一个多个会员和组织的协会,专注于为免授权费的 API 制定开放标准。 有关更多信息,请参阅 https://www.khronos.org

Vulkan 的最初概念是由 AMD 根据其专有的 Mantle API 设计和开发的。 该 API 通过几款游戏展示了先进的功能,从而证明了其革命性的方法并满足了行业的所有竞争需求。 AMD 将他们的 Mantle API 开源并将其捐赠给 Khronos。 Khronos 联盟在许多其他硬件和软件供应商的帮助下,共同努力发布 Vulkan。

Vulkan 并非唯一的次世代 3D 图形 API; 还有一些竞争对手,比如微软的 Direct-X 12 和苹果的 Metal。 但是,Direct-X 仅限于 Windows 系统,而 Metal 只适用于 Mac 系统(OS X 和 iOS)。 Vulkan 在这方面比较与众不同。 其跨平台特性支持几乎所有可用的操作系统平台,其中包括 Windows(XP,Vista,7,8 和 10),Linux,Tizen,SteamOS 以及 Android。

1-001.png

Vulkan 对比 OpenGL

以下是 Vulkan 中的特性和改进,相较于 OpenGL 具有更多的优势:

注意!

用于源语言的编译器,例如 GLSL,HLSL 或 LLVM 必须符合 SPIR-V 规范,并提供实用的工具程序来提供 SPIR-V 的输入。 Vulkan 采用 Vulkan 这种即时执行的二进制中间输入形式并会在着色器阶段使用。

1-002.png

在我们开始之前重要的话题

在深入探讨基本细节之前,先来看看 Vulkan 中用到的一些比较重要技术术语。 随着我们的进一步深入,本书还会涵盖更多的技术术语。

在下一节中,我们会对 Vulkan 进行阐述,以帮助我们了解它的工作模式以及一般的基础知识。 我们还会理解其命令的语法规则,通过简单地查看它们的声明来了解 API 命令的概念。

学习 Vulkan 的基础知识

本节将会介绍 Vulkan 的基础知识。 这里我们将讨论以下内容:

Vulkan 的执行模型

具有 Vulkan 功能的系统能够查询并显示系统上可用的物理设备的数量。 每个物理设备会暴露一个或多个队列。 这些队列分为不同的族,每个族都有特定的功能。 例如,这些功能可能包括图形、计算、数据传输以及稀疏内存管理。 队列族的每个成员都可以包含一个或多个类似的队列,从而使它们相互兼容。 例如,给定的实现可能支持同一队列上的数据传输和图形操作。

Vulkan 允许开发人员通过应用程序对内存控制进行显式的管理, 它公开了设备上可用的各种类型的堆,其中的每个堆属于不同的内存区域。 Vulkan 的执行模式非常简单直接, 此处,命令缓冲区会被提交到队列中,然后由物理设备使用,以便进行各种处理。

Vulkan 应用程序负责控制各种 Vulkan 设备,这是通过把大量的命令记录到命令缓冲区并将它们提交到队列中实现的。 该队列由驱动程序读取,驱动程序会按提交的顺序预先执行作业。 命令缓冲区的构建非常昂贵, 因此,一旦构建完成,就可以对它进行缓存以及将其提交到队列中,以便根据具体的需求执行若干次。 此外,在应用程序中,可以使用多线程同时并行构建多个命令缓冲区。

下图显示了执行模型的简化图示:

1-003.png

在这里,应用程序记录了两个包含多个命令的命令缓冲区。 然后根据工作性质将这些命令提供给一个或多个队列。 队列将这些命令缓冲区作业提交给设备进行处理。 最后,设备处理结果并将其显示在输出显示屏上,或将它们返回给应用程序进行进一步的处理。

在 Vulkan 中,应用程序负责以下内容:

Vulkan 的队列

队列是 Vulkan 中的媒介,就是通过它将命令缓冲区送入设备的。 命令缓冲区会记录一个或多个命令并将它们提交到所需的队列。 设备也可能会公开多种队列,因此,应用程序有责任将命令缓冲区提交给正确的队列。

可以将命令缓冲区提交给以下几项:

Vulkan 提供了几种同步方式,使您可以在单个队列或跨越多个队列对作业的执行进行相对控制。 这些同步方式有:

对象模型

在应用程序级别,所有的实体(包括设备、队列、命令缓冲区、帧缓冲区、管线等)都称为 Vulkan 对象。 在内部,在 API 级别,这些 Vulkan 对象用句柄进行识别。 这些句柄有两种类型:可分发句柄和不可分发句柄。

VkInstance VkCommandBuffer VkPhysicalDevice VkDevice VkQueue
VkSemaphore VkFence VkQueryPool VkBufferView
VkDeviceMemory VkBuffer VkImage VkPipeline
VkShaderModule VkSampler VkRenderPass VkDescriptorPool
VkDescriptorSetLayout VkFramebuffer VkPipelineCache VkCommandPool
VkDescriptorSet VkEvent VkPipelineLayout VkImageView

对象生命周期和命令语法

在 Vulkan 中,根据每个应用程序的逻辑,都要显式创建和销毁对象,并且应用程序要负责管理这些对象。

Vulkan 中的对象使用 Create 创建并使用 Destroy 命令销毁:

作为现有对象池或堆的一部分而创建的对象要使用 Allocate 命令创建并使用 Free 命令从池或者堆中进行释放。

使用 vkGet *命令可以轻松访问任何给定的实现信息。 vkCmd *形式的 API 实现用于在命令缓冲区中记录命令。

错误检查和验证

Vulkan 专为提供高性能而设计,这是通过保持错误检查和验证功能作为一种可选项的形式实现的。 在运行时,错误检查和验证的部分少之又少,从而使得构建命令缓冲区和提交变得更加高效。 这些可选功能可以通过 Vulkan 的分层体系结构来实现,该分层体系结构允许把各种层(调试层和验证层)动态注入到正在运行的系统中。

了解 Vulkan 应用

本节将为您提供有助于构建 Vulkan 应用程序的各种组件的概述。

以下框图显示了系统中不同组件模块以及对应的联系:

1-004.png

驱动程序

具有 Vulkan 功能的系统至少包含一个 CPU 和 GPU。 IHV 的供应商为其专用 GPU 架构提供了指定 Vulkan 规范实现的驱动程序。 驱动程序充当应用程序和设备本身之间的接口。 它为应用程序提供了一些高级设施,以便能够与设备进行通信。 例如,驱动程序通知了系统上可用的设备数量、它们的队列和队列功能、可用的堆及其相关属性等。

应用程序

应用程序是指用户编写的程序,旨在利用 Vulkan API 来执行图形或计算任务。 应用程序从硬件和软件的初始化开始,它会检测驱动程序并加载所有的 Vulkan API。 展示层(presentation layer)使用 Vulkan 的 窗口系统集成 ---Window System Integration ---(WSI)API 进行初始化;WSI 将用于渲染期间在显示表面(display surface)上绘制图形图像。 应用程序创建资源并使用描述符(descriptors)将它们绑定到着色器阶段(shader stage)。 描述符集布局(descriptor set layout)用于把创建的资源绑定到创建的底层管线对象 pipeline object(图形或计算类型 graphics or compute type)。 最后,记录命令缓冲区并将其提交到队列进行处理。

WSI

窗口系统集成 WSI(Windows System Integration )是来自 Khronos 的一组扩展,用于跨平台(如 Linux,Windows 和 Android)。

SPIR-V

SPIR-V 提供了一种预编译的二进制格式,用于指定 Vulkan 着色器。 编译器可用于各种着色器语言,其中包括能够生成 SPIR-V 的 GLSL 和 HLSL 变种。

LunarG SDK

LunarG 的 Vulkan SDK 包含了各种工具和资源,用以辅助 Vulkan 应用程序的开发。 这些工具和资源包括 Vulkan 加载程序、验证层、跟踪和回放工具、SPIR-V 工具、Vulkan 运行时安装程序、文档,示例以及演示,请参阅第 3 章“与设备握手”查看详细的介绍,以便开始使用 LunarG SDK。 你可以在 http://lunarg.com/vulkan-sdk 阅读关于它的更多信息。

Vulkan 编程模型入门

我们来详细讨论一下 Vulkan 的编程模型。 在本章,考虑到了一些读者可能是初学者,因此使用循序渐进的方式。各位看官在这里将会理解以下一些概念:

下图显示了 Vulkan 应用程序编程模型自顶向下的方法;我们会详细了解这一过程,并深入研究各个子组件及其功能:

1-005.png

硬件初始化

当 Vulkan 应用程序启动的时候,它的第一个工作就是硬件的初始化。 在这个阶段,应用程序通过与加载器进行通信来激活 Vulkan 驱动程序。 下图展示了一个加载器 Loader 及其子组件的框图:

1-006.png

Loader: 加载程序是应用程序启动时使用的一段代码,可以跨平台、以一种统一的方式在系统中定位 Vulkan 驱动程序。 以下是加载程序 Loader 的职责:

Vulkan 应用程序首先执行与加载程序库的握手并初始化 Vulkan 具体实现的驱动程序。 加载程序库会动态加载 Vulkan API。 加载程序还提供了一种机制,允许将特定的层自动加载到所有 Vulkan 应用程序中;这被称为隐式启用层。

一旦加载程序找到驱动程序并成功链接到 API,应用程序就要负责以下操作:

窗口展示表面 Window presentation surfaces

一旦加载器找到 Vulkan 具体实现的驱动程序,我们就可以用 Vulkan API 绘制一些东西。 为此,我们需要一个图像来执行绘图任务,并将其放在展示窗口中进行显示:

1-007.png

构建展示图像和创建窗口是特定于具体平台的操作。 在 OpenGL 中,窗口更是和平台密切相关的;窗口系统的帧缓冲区与上下文或设备一起创建。 与 GL 的最大区别在于,Vulkan 中的上下文或者设备的创建根本不需要涉及窗口系统;它会通过窗口系统集成Window System Integration(WSI)进行管理。

WSI 包含一组跨平台的窗口管理扩展:

WSI 支持 Wayland,X 和 Windows 等多种窗口系统,并且通过交换链管理图像的所有权。

WSI 提供了交换链机制,这允许以这样的方式使用多个图像,即当窗口系统显示一个图像时,应用程序可以准备下一个图像。

以下屏幕截图显示了双缓冲交换图像的过程。 它包含名为第一幅图像第二幅图像的两个图像。 在 WSI 的帮助下,这些图像在 ApplicationDisplay 之间交换:

1-008.png

WSI 作为 Display 和 Application 之间的接口。 它确保两个图像都是通过显示 Display 和应用程序 Application 以互斥的方式获取的。 因此,当应用程序 Application 在第一张图像上工作时,WSI 将切换第二张图像以显示其内容。 一旦应用程序 Application 完成绘画第一张图片,它将其提交给 WSI,并作为回报获得第二张图片,反之亦然。

此时,请执行以下任务:

设置资源

设置资源意味着将数据存储到内存区域,资源可以是任何类型的数据,例如,顶点属性,如位置、颜色或图像类型、名称。 当然,为了 Vulkan 能欧访问到这些数据,它们已经存储在了内存中。

与使用提示在背后管理内存的 OpenGL 不同,Vulkan 提供了完全底层的访问和内存控制。 Vulkan 发布了可以在物理设备上使用的各种内存类型,这为应用程序提供了一个很好的机会来显式管理这些不同类型的内存。

本地主机:这是一种较慢的内存类型
本地设备:这是一种高带宽的内存;速度更快

堆内存可以根据其内存类型配置进一步划分:

在 Vulkan 中,资源由应用程序显式处理,并独占内存管理的控制权。 以下是资源管理的过程:

1-009.png

物理内存分配很昂贵;因此,一个好的做法是分配一个大的物理内存,然后在其中二次分配对象。

相比之下,OpenGL 资源管理不提供对内存的精细控制。 没有主机内存和设备内存的概念;驱动程序在后台中秘密地做了所有的分配工作, 而且,这些分配和二次分配的过程并非完全透明,并且不同的驱动程序之间可能还会对这个操作进行调整,从而产生一些差异。 缺乏一致性以及隐藏的内存管理会导致不可预知的行为。 而另一方面,Vulkan 在所选内存中分配对象,使其具有高度的可预测性。

因此,在资源设置阶段,您需要执行以下任务:

  1. 创建资源对象。
  2. 查询适当的内存实例并创建内存对象,比如缓冲区和图像。
  3. 获取分配的内存要求。
  4. 在其中再次分配空间并存储数据。
  5. 将内存与我们创建的资源对象绑定。

管线设置

管线是由应用程序逻辑定义的、固定序列中发生的一组事件。 这些事件包含以下内容:提供着色器,将它们绑定到资源并管理状态:

1-010.png

描述符集和描述符池

描述符集是资源和着色器之间的接口。 这是一个简单的结构,用于将着色器与资源信息(如图像或缓冲区)绑定。 它关联或绑定着色器将要使用的资源内存。 以下是与描述符集相关的特征:

提示

在 Vulkan 渲染中,更新或更改描述符集是改善性能最关键的途径之一。 因此,描述符集的设计是实现性能最大化的一个重要方面。 Vulkan 支持在场景(低频更新),模型(中频更新)和绘图级别(高频更新)的多个描述符集的逻辑分区。 这确保了高频更新描述符不影响低频率描述符资源。

使用 SPIR-V 的着色器

在 Vulkan 中指定着色器或计算核心的唯一方法是通过 SPIR-V。 以下是与之相关的一些特性:

管线管理

物理设备包含一系列的硬件设置,用于确定提交的、给定的几何图形需要的输入数据如何解释和绘制。 这些设置统称为管线状态 pipeline states。 这其中包括光栅器状态、混合状态和深度模板状态;它们还包括提交几何体的基本拓扑类型(点 / 线 / 三角形)以及要用于渲染的着色器。 有两种状态类型:动态和静态。 管线状态用于创建管线对象(图形对象或计算对象),这是一个性能优化关键点。 因此,我们不想一次又一次地重复创造管线对象;我们希望创建一次并对其进行重用。

Vulkan 允许您使用管线对象以及管线缓存对象 Pipeline Cache Object (PCO)管线布局 pipeline layout来控制状态:

管线缓存是不透明的,并且驱动程序使用它们的细节也并未做详细说明。 如果应用程序希望在运行时重用缓存,并且在创建管线时提供合适的缓存(如果应用程序希望获得潜在的好处),则应用程序要对这个缓存负责持久化。

在管线管理阶段,会发生如下操作:

录制命令

录制命令是命令缓冲区形成的过程。 命令缓冲区是从命令池存储空间中进行分配的。 命令池也可以用于多个分配。 在由应用程序定义的给定的起始和结束范围内,通过提供命令来记录命令缓冲区。 下图说明了绘图命令缓冲区的记录情况,如您所见,它包含许多以自上而下的顺序记录的命令,负责对象的绘制。

1-011.png

注意

请注意,命令缓冲区中的命令可能因作业要求而异。 此图只是一个说明,其中包括了绘制元素时所执行的最常见步骤。

绘图的主要部分覆盖以下几个方面:

提示

创建命令缓冲区是一个昂贵的操作;它被认为是性能关键的所在。 如果许多帧需要进行相同的操作,则它可以重复使用很多次。 命令缓冲区可以重新提交而无需重新录制。 此外,可以使用多个线程同时生成多个命令缓冲区。 Vulkan 是专门为利用多线程的可伸缩性而设计的。 如果在多线程环境中使用,则命令池确保不存在锁定竞争。

下图显示了一个具有多核和多线程方法的、可扩展的命令缓冲区创建模型。 该模型使用多核处理器的特性提供了真正意义上的并行性。

1-012.png

在这里,每个线程都使用一个独立的命令缓冲池,同时该命令缓冲池又分配了一个或多个命令缓冲区,不允许与资源锁冲突。

队列提交

一旦构建了命令缓冲区,就可以将它们提交到队列进行处理。 Vulkan 向应用程序公开了不同类型的队列,例如图形队列、DMA 队列、传输队列以及计算队列。 用于提交的、队列的选择在很大程度上取决于作业的性质。 例如,图形相关的任务必须提交给图形队列。

同样,对于计算操作,计算队列就是最佳的选择。 提交的作业以异步方式执行。 命令缓冲区可以被 push 到独立的兼容队列中,并允许并行执行。 应用程序负责命令缓冲区或队列之间的所有类型的同步,即使在主机和设备本身之间也是如此。

队列提交会执行以下工作:

总结

本章对 Vulkan 进行了介绍性的描述,以便让初学者更容易理解。 在本章中,我们了解了 Vulkan 的发展历程,并了解了其背后的历史和人物。 然后,我们将这套 Vulkan API 与 OpenGL 进行了一些对比区分,并理解了 Vulkan 在现代计算时代存在的原因。 我们还查看了与此 API 关联的、重要技术术语的简单定义。 Vulkan API 的基本原理为其工作模型提供了精确以及丰富的概述。 我们还看到了 Vulkan 生态系统的基本组成部分,并了解它们在互连方面的作用和责任。 最后,在本章的末尾,我们了解到 Vulkan 如何使用易于理解的分步操作的伪代码编程模式的方法。

完成本章的学习后,您会对 Vulkan API 及其详细的工作模型会有一个基本的理解,并熟悉了其技术术语,以便在 Vulkan 编程中迈出第一步。

在下一章中,我们将使用伪代码的方式开始 Vulkan 编程。 我们将创建一个简单的示例,但不会涉及太多细节,不过仍会涵盖重要的核心方面、Vulkan API 的基础知识和数据结构,以此来了解 Vulkan 中图形管线编程的完整流程。

上一篇 下一篇

猜你喜欢

热点阅读