Vulkan学习指南(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-17 10:43:50

点击下载

作者:(新加坡)帕敏德·辛格(Parminder Singh)

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Vulkan学习指南

Vulkan学习指南试读:

前言

本书将帮助你从零开始学习Vulkan。Vulkan是新一代的跨平台图形和计算API接口。尽管它是以OpenGL API的继承者的身份出现的,但是它实际上是一套经过了底层全部重新设计的API,可以满足使用者的更多竞争性需求,并且与底层GPU硬件之间的联系更为紧密。Vulkan是一套可以对GPU硬件设置进行控制的软件接口,并且充分使用了硬件并行计算的能力。Vulkan的设备驱动层非常轻量化,它把更多的权力交到了应用软件开发者的手里,从而实现应用程序和资源的管理、内存的管理、同步等功能,这一显式特性使Vulkan显得有些烦琐。本书将手把手地帮助初学者逐步掌握这些内容,通过简单易懂的例子来完成每个章节的学习。本书的各个章节之间是循序渐进的,新的一章总是构建于前一章的内容之上,这样读者可以更好地理解各个功能模块之间的细节差异。

Vulkan API的学习显然需要读者对计算机图形学和通用计算方面有一定的理解,然后才可以开始进行程序的开发。本书中会直接用到很多通用的概念和术语。

本书实战性很强,目的就是帮助读者学习Vulkan的思想、概念以及API标准,并且通过随书的案例来进行实践。本书中涉及大量的参考文献,读者可以从中了解到更多的相关概念,在学习和实践过程中巩固自己的基础知识。本书内容

第1章将介绍Vulkan API的一些基础知识,并且通过与它的前身OpenGL API进行比较的方式,对Vulkan的一些核心特性进行概述。该章会涵盖Vulkan编程过程中必须了解的基础、概念、应用程序模型以及技术术语,这些对于初次接触Vulkan的学习者来说至关重要。你可以快速地概览Vulkan的编程模型,了解每一个模块的主要作用和学习要点。

第2章将帮助你用伪代码的方法编写一个简单的Hello World程序。初学者可以感受Vulkan编程的基本方法,并且逐步地学会构建第一个Vulkan应用程序。你还可以从该章中了解安装必需软件以及SDK的方法。

第3章将带你设置开发环境来构建你的第一个Vulkan案例。你可以创建一个Vulkan实例并初始化程序;还可以连接物理硬件设备,探索该设备所提供的不同队列,查询各个可用的层和扩展。该章会详细地讲解设备队列和队列族的概念,以及它与逻辑设备的关系。

第4章将介绍Vulkan应用程序中进行调试的方法。Vulkan允许我们通过验证层来进行调试。该章我们会讨论每种验证层的功能并编写一个简单的例子来实际理解调试的过程。此外,我们还会查询层的扩展属性,以便增加更多的功能。这些功能可能并非是Vulkan标准中定义的。

第5章将深入讨论和实现Vulkan中的指令缓存。读者将了解到指令池的作用,并学习如何使用Vulkann中的指令缓存进行录制。该章的后半部分会介绍Vulkan中的内存管理。你将深入理解设备内存,并学习GPU内存的分配和销毁操作,理解CPU和GPU内存之间的映射关系。

第6章将介绍图像资源的概念,并讨论相关内存管理的概念,包括图像的创建、分配、绑定和映射。我们会使用这种方法来创建一个深度图像,以便进行深度测试。该章将介绍WSI交换链,使用它来实现渲染可绘制对象并展示到屏幕的功能。我们会尝试获取交换链的颜色图像,并创建颜色视图以便用于绘制图元。

第7章将讨论缓存资源及其相关用途,即可绘制对象的几何体顶点缓存的构建。该章会详细地介绍使用渲染通道定义绘制操作的相关工作的方法,并使用多个附件和子通道。我们将使用渲染通道在Vulkan中实现帧缓存,并使用一个简单的示例演示如何清除背景颜色。在该章的结尾部分,我们将使用SPIR-V实现第一个Vulkan着色器程序。我们会学习一些SDK工具的用法,将GLSL代码转换为SPIR-V的中间语言格式。

第8章将介绍Vulkan中的计算流水线和图形流水线。该章将会对图形流水线的流程做一个概述,并且涵盖整个流程中所用到的各个模块的作用讲解。我们将讨论流水线状态对象、流水线缓冲对象,以及流水线布局。该章还包含所有流水线状态的介绍,包括动态状态、输入装配(以及图元绘制)、光栅化、融混、视口、深度/模板测试,以及多重采样。我们将使用这些状态对象来实现一个完整的图形流水线。

第9章将全面地介绍Vulkan中对象绘制的过程。我们将录制和执行绘制物体的指令缓存。录制过程中的视口和几何体数据将关联到渲染通道、帧缓存,以及流水线。指令缓存的执行过程包括将指令缓存提交到设备队列,以及将交换链图像提交给展示引擎。我们还将讨论Vulkan中的同步机制,并理解有关栅栏、信号量,以及内存屏障的知识。此外,我们还会介绍如何绘制相关的API并通过一些简单的案例演示它们的用法。

第10章将介绍Vulkan程序中对着色器资源进行更新的过程,这里用到了描述符和推送常数的概念。我们会讨论描述符池以及描述符集布局的创建过程。读者将从中学到有关流水线布局的用法,并使用描述符来更新设备内存端的缓存资源,以及将更新后的几何体渲染到屏幕上。与描述符不同的是,推送常数并不会使用指令缓存,而是使用一种更为优化的方式来更新资源。用户可以尝试实现一个简单的程序来深入理解推送常数的相关知识。

第11章将尝试使用更为真实的方法,即贴敷纹理来渲染3D可绘制对象。用户将会学习如何创建图像资源并为它设置采样器对象,还将学习使用线性和优化平铺的方式来更新纹理。在优化平铺的模式下,用户还需要考虑使用传输缓存和图像内存的指令方法。本书的目标读者

本书适用于对跨平台、高性能的图形开发,以及桌面级和嵌入式的计算程序开发技术感兴趣的读者。读者需要对C/C++编程有较为深入的理解,同时具备一定的实际开发经验,还需要具备一些图形学和计算领域的知识和经验,以便更好地理解Vulkan中的概念。排版约定

新的术语以及重要名词以黑体字的方式显示。警告或者重要的提示信息会出现在这里。提示和技巧说明会出现在这里。下载示例代码及彩色图像

本书的示例代码及所有截图和样图,可以从http://www.packtpub.com通过个人账号下载,也可以访问华章图书官网http://www.hzbook.com,通过注册并登录个人账号下载。致谢

我要将这本书献给我美丽的女儿Raskeerat,她是在本书刚开始编写的时候出生的。正因为身边多了一个婴儿,写作本书的过程变得极富挑战性。我非常感谢我深爱的妻子Gurpreet Kaur,以及我的家人,感谢他们帮助我完成了这本著作。

我十分感激Ulrich Kabatek先生以及整个Continental Automotive的图形团队。团队的每一个人都向我提供了力所能及的支持。同样感谢Blackmagic Design的成员,他们帮助我将自己的GPU编程水平提升到了一个全新的阶段。在这里向Mohit Sindhwani以及整个Quantum Invention团队致以崇高的敬意。与他们在一起工作我感到十分愉快,这是一段非常了不起的学习经历。

由衷感谢Google的Chris Forbes。他在图形学方面的丰富经验提升了本书内容的专业性。他的审核意见和提交的高质量成果让我印象深刻。Chris仔细地审阅了本书的内容,帮助我们全面提高了内容质量,并通过深入的讲解帮助我们更深刻地理解其中的概念。

最后,我特别感谢Packt出版社,尤其是Sachin Karnani,她在我整个写作过程中都充满热情。Murtaza Tinwala在本书的最终阶段很好地展现了自己在内容管理方面的技能和知识。能够和她们一起完成本书的写作和出版工作,我由衷地感到高兴。作者简介

Parminder Singh是一位计算机图形学工程师,来自于新加坡的Blackmagic Design。在过去的十年里,他一直致力于网络模拟、几何建模、导航、自动驾驶、信息娱乐系统、图像处理及后期制作等领域的图形应用开发。他的研究领域包括泛图形的GPU编程及其通用计算、平台移植以及性能优化技术。

他是Vulkan、Metal以及OpenGL ES方面的培训教师,并且编写了同样由Packt出版的OpenGL ES 3.0 Cookbook一书。他的业余爱好包括旅行、轻食料理,他很喜欢与自己年幼的女儿共同享受美好的时光。

你可以直接从网址https://www.linkedin.com/in/parmindersingh18联系到Parminder,也可以访问http://openglescookbook.com与他交流。审阅者简介

Chris Forbes是来自Google的一位软件工程师,主要负责Vulkan的验证支持工作以及其他生态组件。之前他参与过在Linux开源图形驱动中实现OpenGL 3和4的支持工作(www.mesa3d.org),以及在现代系统上重构经典策略游戏的工程(www.openra.net)。第1章 开始学习新一代3D图形API

Vulkan是一款革命性的高性能3D图形和计算API,可用于现代GPU管线架构,能满足社区的最新需求。该API提供了一套全新的方法来克服传统API的复杂性和缺陷。Vulkan是一个显式API,保证用户在渲染时保持稳定平滑的帧速率,不会造成延迟或者故障。本章将概述Vulkan API及其与其前身OpenGL API相比的独特特性。我们将简单了解Vulkan的生态系统,以及它的图形体系。

本章所述的主题如下:

·Vulkan及其演化史。

·Vulkan与OpenGL的对比。

·一些必需的预备知识。

·学习Vulkan的基础概念。

·理解Vulkan的应用程序。

·了解Vulkan的编程模型。1.1 Vulkan及其演化史

著名的OpenGL API问世已经差不多四分之一个世纪,而且它还在不断发展。本质上来说,OpenGL是一个纯粹的状态机,其中包含了若干个开关量,可以设置为开/关的状态(on/off)。这些状态数据被用来构建设备中的依赖映射关系,对资源进行管理,并通过最优的方法进行控制以达到性能的最大化。

这种状态机可以隐式地自动化资源管理,但是它对应用程序逻辑的解读不够智能化,而应用程序正是资源管理背后的驱动力所在。其产生的结果可能是用户无法预测的,比如实现中断,导致着色器代码被重新编译,但是应用程序并不需要系统这么做。此外,OpenGL API也会受到其他因素的限制,例如不可预测的程序行为、多线程的扩展性、渲染的故障等。本章后续会将OpenGL与Vulkan API进行比较来理解两者之间的各种差异。

Khronos于2016发布了革命性的新架构Vulkan API,它充分利用了现代图形处理器单元的优势,来实现高性能图形和计算应用程序的开发。Khronos是一个会员制的社区和专注于发布开放标准和免费API的组织。更多信息请参阅网站:https://www.khronos.org。

Vulkan的原始概念是由AMD基于它们的私有Mantle API设计和实现的。这个API已经在几款不同的游戏中体现了自己的先进特性,它有着革命性的实现方案,完全满足了工业界的竞争性需求。AMD开源了自己的Mantle API并且贡献给Khronos组织。在多家硬件和软件供应商的协同帮助下,Khronos发布了Vulkan标准。

Vulkan并不是目前唯一的新一代3D图形API,它还有多家不同的竞争者,例如Microsoft的Direct-X 12和Apple的Metal。不过,Direct-X只能用于不同的Windows系统,而Metal只能用在Mac系统(OS X和iOS)。因此,Vulkan得以脱颖而出。它的跨平台特性可以支持所有现存的OS平台,其中已经包括了Windows(XP、Vista、7、8、10)、Linux、Tizen、SteamOS和Android。如图1-1所示。图 1-11.2 Vulkan与OpenGL的对比

Vulkan相比OpenGL有了不少的新特性和性能提升,如下所示。

·降低驱动负载和CPU的使用量:Vulkan在设计上更为接近于底层的图形硬件。因此,它可以向应用程序的开发者提供更为直接的控制权力,来操作宿主机上的计算资源,使用GPU来尽可能高效地完成渲染工作。这一特性使得相关软件可以直接访问图形处理器,从而达到更好的性能要求。

·多线程的可扩展性:OpenGL中的多线程扩展能力是非常有限的,所以很难利用多线程的各种优势来管理CPU资源。不过,Vulkan在设计时特别考虑了终端用户对于多线程功能的迫切需求,并且通过非常透明的方式予以支持(并不会隐含任何的全局状态变化)。不同线程下的任务、任务的创建过程,以及任务提交执行的过程之间都是完全独立的,不存在数据耦合。

·显式的API定义:OpenGL的API是隐式的,资源管理的工作交给驱动层去完成。驱动层负责读取应用程序端的提示参数并跟踪资源的处理,这样带来了很多不必要的负担。

·Vulkan采用了显式的API定义,驱动并不负责资源以及资源之间相互关系的管理。这些工作由应用程序处理。这种清晰的实现方式更容易预测,驱动层也不需要在用户场景的后面偷偷做资源管理的小动作了(这正是OpenGL的弊端)。这样的结果是,用户任务的处理可以直截了当地以流水线的方式完成,从而获得最佳性能和可预测的行为模式。

·预编译的中间级着色语言:OpenGL需要使用OpenGL着色语言(GLSL)源代码的形式来实现着色器,而Vulkan使用可移植的标准化中间级语言(SPIR-V)作为中间级语言标准,为并行计算和图形处理提供了着色器支持。其他源代码语言的编译器(例如GLSL、HLSL或者LLVM)必须将SPIR-V作为输出的目标语言,并且提供工具来实现SPIR-V输入数据的支持。Vulkan可以读取这种能够马上执行的二进制中间数据,并且在着色器执行阶段直接使用。

·驱动层和应用程序层:OpenGL中的应用程序层相比驱动层而言要单薄得多,因为驱动层会自动完成资源管理和状态跟踪的工作。Vulkan与之相反。它的驱动层更为接近硬件底层,负载较小。而应用程序层需要负责逻辑、资源和状态的管理。图1-2给出了这两个API各自的驱动层和应用程序层代码的总量对比。图 1-2

内存的控制:Vulkan暴露了系统当中的多种不同类型的内存接口,交由应用开发者去选择适合自己的内存类型,实现各种资源的管理和使用。与之相反,OpenGL是通过驱动层的内部处理机制来完成资源存储的,不同的供应商可能因此有完全不同的实现,并且当驱动层改变了资源的存储位置时。很可能产生预期之外的内存碎片,或者降低存储效率。

·可预测行为:Vulkan与OpenGL相比,其行为具有很高的可预测性,它不会在渲染时产生任何延迟或者抖动。用户任务传递到驱动层之后会被立即提交,而OpenGL的任务提交过程不是立即完成的,它需要等待驱动层再去进行调度。

·单一的API:OpenGL有多个独立的版本,包括桌面端的API(OpenGL)和嵌入式系统的API(OpenGL ES)。Vulkan相比之下更为清晰,它只提供了单一的API接口来面对所有类型的系统平台。Vulkan会优先支持移动平台,这一点与OpenGL也是不一样的。OpenGL通常会优先实现某个功能的桌面端版本,然后再将它更新到OpenGL ES API当中。

·直接访问GPU:Vulkan暴露了自己的底层功能和硬件特性,从而给应用层用户提供了大量的控制手段。它提供了多种不同类型的物理设备、内存类型、指令缓存队列,以及功能扩展。这样的模式确保软件层更接近实际硬件的特性。

·错误检查和验证:我们使用OpenGL进行开发的时候,即使已经实现了非常优化的应用程序,本身运行时不会有任何错误发生,我们也依然需要强制进行错误检查并有所损耗。与之对应的是,Vulkan通过插件服务的形式提供了错误检查和验证的支持,因此可以根据实际需要随时开启或者关闭。这样的检查是可选的,可以在程序运行时再开启错误检查和验证功能。由于避免了非必需的检查过程,因此CPU的负载也就更低。事实上,在理想状态下,错误检查和验证功能只需要在开发阶段启用,从而完成功能的调试;而程序发布之后该功能即可关闭。

·支持多种类型的GPU硬件:Vulkan已经将移动端和桌面端的光栅化操作集成到自己的实现当中。它也可以在嵌入式系统上支持基于瓦片的光栅化或者延迟渲染光栅化,同时也支持基于瓦片的本地前向渲染光栅化操作。1.3 重要术语

在开始深入学习基础知识之前,我们首先了解一些Vulkan中的重要术语。本书将逐步使用以下术语。

·物理设备(physical device)与设备(device):一台计算机系统中可能包含了不止一个支持Vulkan的物理硬件设备。我们所说的物理设备表示一个独立的设备,而设备指的是该物理设备在应用程序中的逻辑表示。

·队列(queue):队列表示执行引擎与应用程序之间的接口。一个物理设备总是包含了一个或者多个队列(图形、计算、DMA/传输,等等)。队列的职责是收集准备执行的工作(指令缓存)并且分发到物理设备执行。

·内存类型(memory type):Vulkan暴露了多种内存类型。广义上来说,总共有两种类型的内存:宿主内存和设备内存。在本章后面的内容里,我们会讨论这些内容。

·指令(command):每个指令中都可以执行一些用户行为。指令从广义上可以划分为动作、状态设置,以及同步。

·动作指令(action command):包括绘制图元、清除表面、复制缓存、查询/时间戳操作,以及子通道的开始/结束操作。这些指令可以修改帧缓存附件、读取或者写入内存(缓存或者图像),以及写入查询池。

·状态设置指令(set state command):这些指令可以用来绑定流水线、描述字集合以及缓存,或者设置一个动态状态,以及渲染通道/子通道的状态。

·同步指令(synchronization command):同步指令用于处理两个或者更多动作指令同时发生的情况,此时指令之间可能会争夺资源或者依赖于某些内存。该指令用来设置同步事件或者等待事件、插入流水线屏障对象,以及渲染通道/子通道的依赖。

·指令缓存(command buffer):指令缓存是一组指令的集合,它可以记录多个指令并统一发送到队列中。

下面我们将从总体上了解Vulkan的工作模型和一些基础概念。我们还会尝试理解指令的语法规则,并通过直接阅读API列表的方式简单了解所有的API指令。1.4 Vulkan的原理

本节将介绍Vulkan的基础知识,涉及以下内容:

·Vulkan的执行模型

·Vulkan的队列

·对象模型

·对象生命周期与指令语法

·错误检查与验证1.4.1 Vulkan的执行模型

支持Vulkan的系统可以直接查询系统信息,并返回可用的物理设备的数量。每个物理设备可以支持一个或多个队列。这些队列被划分到不同的族群中,每个族群有自己独特的功能设定。例如,一个族群可能会支持诸如图形、计算、数据传输,或者内存管理相关的功能。队列族群中的每个成员可能包含了一个或者多个相似的队列,因此它们互相之间是兼容的。例如,某个具体的驱动实现中,可能在同一个队列里同时支持数据传输和图形操作。

Vulkan允许用户显式地在应用程序中管理和控制内存。它暴露了设备中支持的所有不同类型的内存堆(heap),每个堆属于一个不同的内存区域。Vulkan的执行模型是非常简单和直接的。在这里,指令缓存会被发送到队列中,后者将被物理设备按顺序依次执行和消耗。

Vulkan的应用程序负责控制一组Vulkan设备,将一系列指令记录到指令缓存中,并发送到队列。驱动会读取队列并按照记录的顺序依次执行各个工作。指令队列的构建是需要较大代价的,而构建一旦完成,它就可以被缓存和发送到队列中,根据自己的需要多次执行。此外,有些指令缓存在应用程序中也可以以多线程的方式并行同步地构建。

如图1-3所示给出了一个简化后的执行模型。图 1-3

在这里,应用程序记录了两个指令缓存,其中各包含了多个指令。这些指令随即按照作业性质的不同,被传递给一个或者多个队列。队列将这些缓存作业提交给设备加以执行。最后,设备处理得到结果,并将它们显示到输出设备上,或者返回给用户程序做进一步的处理。

Vulkan中,用户应用程序主要负责以下工作。

·生产指令执行所必需的所有先决内容:

·其中可能包括资源的准备、着色器的预编译、将资源关联到着色器、设置着色器的状态、构建流水线,以及绘制调用。

·内存管理。

·同步。

·宿主和设备之间。

·设备上不同的队列之间。

·风险管理。1.4.2 Vulkan的队列

队列是Vulkan的一种中间层机制,负责接收指令缓存并传递给设备。指令缓存记录了一个或者多个指令,并发送给相关的队列。设备则提供了多个可选的队列,然后应用程序负责将指令缓存传递给正确的队列。

指令缓存的发送可以按照以下两种方式来完成:

·单一队列:

·按照指令缓存发送的顺序进行维护,以及执行或者回放。

·指令缓存按照串行的方式执行。

·多重队列:

·允许指令缓存以并行的方式在两个或者更多队列中执行。

·除非特别指定,否则无法保证指令缓存发送和执行的顺序不变。应用程序需要负责进行此类同步操作,否则实际的执行顺序可能会完全与发送时的顺序无关。

Vulkan提供了多种同步图元,让用户可以相应地在单一队列中,或者跨队列地完成工作执行顺序的管理。如下所示:

·信号量(semaphore):这种同步机制可以跨队列或在单一队列中的粗粒度指令缓存中执行。

·事件(event):事件控制细粒度同步,并应用于单个队列,从而确保单一指令缓存中或者多个指令缓存之间的同步要求。宿主系统也可以参与到事件触发的同步机制当中。

·栅栏(fence):允许在宿主和设备之间完成同步。

·流水线屏障(pipeline barrier):流水线屏障是插入到指令缓存中的一种指令,它可以确保在它之前的指令始终优先执行,而在它之后的指令一定随后执行。1.4.3 对象模型

在应用程序端,包括设备、队列、指令缓存、帧缓存、流水线等在内的所有对象,统称为Vulkan对象。而在内部的API层面,这些Vulkan对象会被识别为不同的句柄。这些句柄可以分为两种类型:可分发的以及不可分发的。

·可分发的句柄:这类指针指向了一个不透明的内部图形实体。不透明的数据类型不允许直接访问它的内部结构体成员。你只能通过API函数来访问结构体的内部域。所有的可分发句柄都会关联一个可分发的类型,这样它就可以作为一个参数被传递给其他的API指令。一些典型的示例见表1-1。表 1-1

·不可分发的句柄:这些64位整型类型的句柄通常不会指向一个结构体,而是直接包含了对象自身的信息。一些典型的示例见表1-2。表 1-21.4.4 对象生命周期与指令语法

Vulkan当中的对象是根据应用程序的逻辑需求显式地创建和销毁的,应用程序需要自己管理这些对象。

Vulkan的对象需要使用Create指令创建,以及使用Destroy指令销毁:

·Create语法:对象的创建需要通过vkCreate*指令完成,它需要一个Vk*Createinfo结构体作为输入参数。

·Destroy语法:使用Create指令创建的对象总是需要使用vkDestroy*指令销毁。

如果对象是作为已有的对象池或者堆的一部分创建的,那么需要使用Allocate指令创建,并使用Free指令从池或者堆中销毁。

·Allocate语法:一个对象如果是作为对象池的一部分创建,那么需要使用vkAllocate*指令,并且需要一个Vk*AllocateInfo作为输入参数。

·Free语法:已创建的对象需要使用vkFree*指令从对象池或者内存中释放。

上述所有的实现方法都可以通过vkGet*指令轻松获取。而使用了vkCmd*形式的API接口主要用于把指令记录到指令缓存当中。1.4.5 错误检查与验证

Vulkan的设计在性能最大化的前提下提供了可选的错误检查和验证功能。在程序运行时,错误检查和验证的需求非常少,因此构建指令缓存和发送到设备的效率很高。这类可选的功能可以在Vulkan的层次化结构中使用,从而在运行系统中实现多个层之间的动态注入(调试和验证)。1.5 理解Vulkan应用程序

本节会简要介绍Vulkan中的各种功能组件,以及它们是如何用来构建一个Vulkan应用程序的。

图1-4给出了不同的组件,以及它们在系统中的内部关系。图 1-41.5.1 驱动

支持Vulkan的系统至少要包含一个CPU和一个GPU。独立的硬件供应商会根据自己的GPU架构为某个Vulkan标准提供完整的驱动实现。驱动相当于应用程序与设备本身之间的接口。它为应用程序提供了高级的功能接口,使其可以与设备进行通信。例如,它可以列举出系统中所有可用的设备、可用的每个队列以及队列的类型、可用的内存堆以及相关属性,等等。1.5.2 应用程序

应用程序指的是用户编写的程序,可以调用Vulkan API来执行图形或者计算的工作。应用程序启动的时候需要先初始化硬件和软件,它可以检测驱动并调取所有的Vulkan API。其中展示层的初始化需要通过Vulkan的窗口系统集成API(Window System Integration,WSI)完成。WSI对于绘制图像到显示表面的渲染非常有用。应用程序负责创建资源并且把它们绑定到着色器阶段,这里用到了描述符(descriptor)。描述符可以辅助将创建后的资源绑定到底层的(基于某种图形或者计算类型的)流水线对象。最后,应用程序还要记录指令缓存并发送到队列中继续执行。1.5.3 WSI

窗口系统集成库是由Khronos提供的一套功能扩展,可以将不同操作系统平台上的展示层统一起来,包括Linux、Windows和Android。1.5.4 SPIR-V

SPIR-V提供了一套预编译的二进制数据格式,用来设置给Vulkan的着色器。不同的着色器源代码语言,包括GLSL和HLSL的各种变种,都可以通过编译器产生SPIR-V格式的数据。1.5.5 LunarG SDK

LunarG提供了一套Vulkan SDK,其中带有多种不同的工具和资源,可以辅助Vulkan程序的开发。这些工具和资源包括Vulkan的加载器、验证层、跟踪和回放工具、SPIR-V工具、Vulkan的运行时安装程序、文档、示例和演示程序,详细的介绍和LunarG SDK的使用方法请参见第3章。你也可以直接访问网站:http://lunarg.com/vulkan-sdk。1.6 开始学习Vulkan编程模型

下面我们将深入讨论Vulkan的编程模型。对于初学者来说,首先需要理解下面几个概念:

·Vulkan的编程模型。

·渲染的执行模型,我们将通过伪代码的形式一步一步地进行解释。

·Vulkan的工作流程。

图1-5给出了Vulkan应用程序编程模型的自顶向下的实现过程,我们将深入学习这个过程,探索一些更细分级别的模块和子功能。图 1-51.6.1 硬件初始化

当一个Vulkan应用程序启动的时候,它的第一项工作就是初始化硬件设备。应用程序需要与加载器进行通信来激活Vulkan的驱动。图1-6给出了加载器(Loader)的各个子模块的框图。图 1-6

加载器:加载器是一段应用程序启动时执行的代码,它使用平台无关的方式来定位系统中的Vulkan驱动。以下给出了加载器的职责说明:

·定位驱动:这是加载器的主要职责,它需要从当前系统中的指定位置来定位Vulkan驱动。如果找到了驱动程序,那么将加载它。

·平台无关性:初始化Vulkan的过程对于所有的系统平台来说都是相同的。这一点与OpenGL不同,后者创建设备环境的过程在不同环境的窗口系统API下都是不一样的,包括EGL、GLX和WGL。Vulkan的平台差异性都是通过功能扩展的方式来体现的。

·注入层:加载器支持层次化的结构,并且可以在运行过程中随时注入不同类型的层。这样做的一个巨大的好处是,驱动不需要做任何验证(也不需要在继续执行之前保存当前的任何状态)来判断应用程序使用的API是否是合法的。因此,我们完全可以根据程序开发的需要,在开发阶段打开所有需要注入的层,而在发布程序的时候关闭它们。例如,可注入的层能够实现的功能包括:

·跟踪Vulkan API的指令执行。

·捕获渲染的场景,稍后再继续执行。

·为了满足调试需要,进行错误处理和验证。

Vulkan应用程序首先需要和加载器库执行一次握手操作,并初始化Vulkan的功能驱动,加载器库负责动态地载入Vulkan API。加载器还提供了一种机制来实现某个层自动加载到所有的Vulkan应用程序,这一特性被称作隐式启用层(Implicit-Enabled layer)。

当加载器定位到驱动位置并成功链接到API之后,应用程序就可以开始执行下面的操作了:

·创建一个Vulkan实例。

·查询物理设备上所有的可用队列。

·查询扩展功能并保存为新的函数指针,例如WSI或者有特定功能的API。

·支持注入层来实现错误检查、调试或者验证的功能。1.6.2 窗口展示表面

当加载器成功地定位到Vulkan的驱动程序后,我们就可以使用Vulkan API来绘制一些内容了。为此我们需要用一幅图像来承载绘制的任务,并且将它放到展示窗口上进行显示,如图1-7所示。图 1-7

构建展示图像和创建窗口的工作与平台密切相关。在OpenGL中,窗口是通过底层平台进行链接的,而窗口系统负责创建设备/环境以及对应的帧缓存。与OpenGL不同,Vulkan在创建设备/环境的过程完全不需要包含一套窗口系统。这是通过窗口系统集成(Window System Integration,WSI)API完成的。

WSI包括了一系列跨平台的窗口系统管理功能:

·一套独立的跨平台实现,可以支持大多数系统平台,包括Windows、Linux、Android等OS。

·一套一致的API标准,可以简便地创建窗口表面并显示它们,不需要关注过多的细节。

WSI支持多个窗口系统,包括Wayland、X、Windows,它同时还通过交换链的方式实现了图像所有权的管理。

WSI提供了交换链机制来实现多幅图像的同时使用,此时窗口系统只显示一幅图像,而应用程序同步开始准备下一幅。

图1-8显示了这种双缓存的图像交换过程。它包括两幅图像,分别命名为第一幅图和第二幅图。通过WSI的使用,我们可以在应用程序和显示设备之间反复交换这两幅图像。图 1-8

WSI是作为显示设备和应用程序之间的接口使用的。它可以确保显示设备和应用程序处理图像的过程互不干涉。因此,当应用程序在处理第一幅图的时候,WSI会将第二幅图传递给显示设备进行内容的渲染。当应用程序完成了第一幅图的绘制之后,它将图像提交到WSI,然后获取第二幅图并继续处理,如此往复。

在这里,系统需要先后执行如下任务:

·创建一个本地窗口(类似Windows OS中的CreateWindow方法)。

·创建WSI表面并关联到窗口上。

·创建交换链来显示表面。

·从创建后的交换链中获取绘制后的图像。1.6.3 资源设置

设置资源的过程意味着将数据存储到内存区域中。数据可以是任何类型的,例如,顶点属性,如位置、颜色或者图像类型/名称。当然,数据总是保存在内存当中,以便Vulkan访问它。

OpenGL会通过隐式的方式来管理场景背后的内存数据,与之不同的是,Vulkan提供了一整套底层接口来控制和管理内存。Vulkan在物理设备之上提供了多种多样的内存类型,以便应用程序有更好的机制来显式地管理各种不同类型的内存数据。

内存堆可以按照其表现形式,划分为两种不同的类型:

·宿主本地(host local):这是一种速度较慢的内存。

·设备本地(device local):这是一种带宽更高的内存类型,速度较快。

内存堆也可以按照其配置方式进行划分:

·设备本地(device local):这种类型的内存是关联到物理设备的。

·对设备可见。

·对宿主不可见。

·设备本地,宿主可见(device local,host visible):这种类型的内存也是关联到物理设备的。

·对设备可见。

·对宿主可见。

·宿主本地,宿主可见(host local,host visible):它是宿主机的本地内存,但是速度比本地设备更慢。

·对设备可见。

·对宿主可见。

Vulkan中的资源是交由应用程序显式地进行管理的,所有的内存控制接口都直接暴露出来。以下给出了资源管理的主要过程:

·资源对象(resource object):设置资源的时候,应用程序需要负责分配资源所用的内存。资源可以是图像,也可以是缓存对象。

·分配(allocation)和子分配(suballocation):当我们创建了资源对象之后,它们只关联了一个逻辑地址,并没有真的物理地址可用。应用程序负责分配物理内存并且将逻辑地址绑定到内存。完全的分配过程是非常耗时的,而子分配则是一种高效的内存管理的方式,它可以将物理内存的很大一部分立即分配完成并存入不同的资源对象。子分配是由应用程序负责完成的。图1-9给出了从物理内存中实现对象的子分配的过程。图 1-9

·稀疏内存(sparse memory):对于非常庞大的图像对象,Vulkan可以支持全部稀疏内存相关的功能。稀疏内存是一种特殊的功能,可以存储巨大的图像资源,并且可以比实际的内存容量更大。这一技术会将图像分割为多个小块,并且根据应用程序的实际逻辑,只加载当前必需的小块。

·阶段缓存(staging buffer):对象和图像缓存数据的布设是通过不同阶段来完成的,这里通过两类不同的内存区域来完成物理分配的过程。存储资源的理想内存区域对于宿主机是不可见的。因此,应用程序需要首先将资源设置到阶段缓存中,它对于宿主机是可见的,然后再传递到理想的存储区域。

·异步传输(asynchronous transfer):我们通过各种异步指令来实现数据在任意图形或者DMA/传输队列中的异步传递。

物理内存的分配需要付出很大的代价。因此,一种良好的习惯是先分配一块较大的内存,然后使用子分配的方法来创建对象。与之相反的是,OpenGL的资源管理过程并没有提供这么细节的内存控制方法。它不具备宿主或者设备内存的概念。驱动会私下完成所有的幕后分配工作。此外,分配和子分配的过程也不是完全透明的,不同驱动层面的实现方法可能有所不同。这种连续性的缺失和隐式的内存管理会导致各种无法预测的问题。以此为戒,Vulkan会严格地在选定的内存中进行对象的分配,这样它的行为就是高度可预测的。

因此,在资源设置的阶段,用户需要先后执行如下任务:

1)创建一个资源对象;

2)查询应用程序内存实例,创建一个内存对象,例如缓存或者图像;

3)获取对象分配相应的内存需求;

4)分配空间并且保存数据到其中;

5)将内存绑定到我们创建的资源对象上。1.6.4 流水线设置

流水线指的是根据应用程序逻辑定义的一系列事件,它们按照固定的顺序执行。事件主要包含以下几种:设置着色器、绑定到资源,以及状态的管理。图 1-10

1.描述符集以及描述符缓冲池

描述符集合指的是资源和着色器之间的接口。它的结构非常简单,可以将着色器绑定到资源,例如图像或者缓存。它也可以将资源内存关联或者绑定到准备使用的着色器实例上。以下给出了描述符集合相关的一些特性:

·频繁变化:描述符集的自然特性就是可以频繁地进行修改。通常来说,它对应于材质、纹理等数据。

·描述符缓冲池(descriptor pool):它与描述符集的特性密切相关,后者就是从描述符缓冲池中分配而来的,不需要引入全局的数据同步。

·多线程的扩展性:支持多线程同步进行描述符集合的更新。更新或者改变描述符集的过程是Vulkan渲染中最为关键的性能瓶颈之一。因此,描述符的设计对于性能最优化的需求而言也是最重要的一个方面。Vulkan支持场景中的多个描述符集的逻辑分割(低频率更新)、建模(中频率更新),以及渲染层(高频率更新)。这样就确保了高频率更新的描述符不会影响到低频率的描述符了。

2.基于SPIR-V的着色器

Vulkan中设置着色器或者计算内核的唯一方法就是通过SPIR-V完成。下面给出了与之相关的一些特性:

·多重输入:SPIR-V提供了针对不同源语言的编译器工具,包括GLSL和HLSL。它可以将人类可读的着色器语言代码转换为SPIR-V格式的中间层解释语言。

·离线编译:着色器/内核的编译是离线完成的,不过预先就进行了注入。

·glslang验证器:LunarG SDK提供了一个glslangValidator编译器,它可以将GLSL着色器源码转换成等价的SPIR-V着色器。

·多重程序入口:着色器对象提供了多种不同的程序入口。这样我们可以很方便地减小SPIR-V着色器的装运尺寸(以及加载尺寸)。着色器中的不同功能可以以单独的模块形式打包使用。

3.流水线的管理

物理设备包括一系列硬件设置,用来定义准备发送的几何输入数据是如何解释和绘制的。这些设置可以被统称为流水线状态。其中包括了光栅化状态、融混状态,以及深度/模板状态,此外还包括了输入几何数据的图元拓扑类型(点/线/三角形)以及渲染所用的着色器。状态的类型有两种:动态状态和静态状态。流水线状态可以用来创建流水线对象(图形或者计算),后者对于性能的优化来说至关重要。因此,我们不希望反复不断地进行创建,而是经过一次创建之后可以反复地使用它们。

Vulkan允许用户使用流水线对象与流水线缓冲对象(Pipeline Cache Object,PCO)和流水线布局一起,来进行状态的控制:

·流水线对象(pipeline object):流水线的创建是非常耗费资源的。它包含了着色器的重编译、资源的绑定、渲染通道(render pass)、帧缓存的管理,以及其他相关操作。流水线对象的数量可以成百上千,因此,每个不同的状态组合都可以保存到一个独立的流水线对象当中。

·PCO:流水线的创建是非常耗费资源的,因此当流水线被创建之后,它也可以进行缓冲使用。如果我们需要建立新的流水线,那么驱动将首先做一个近似匹配,然后在基础流水线之上构建新的流水线对象。

流水线缓冲的实现是不透明的,它的实现细节是驱动完成的,没有公开定义。如果应用程序希望在运行过程中,能够利用这个潜在的对象复用特性,则需要从创建伊始就自行维护这个缓冲区。

·流水线布局(pipeline layout):流水线布局提供了流水线中所用的描述符集,其中设置了各种不同的资源关联到着色器的不同方法。不同的流水线对象可以使用相同的流水线布局。

在流水线管理的阶段,可能会有以下用例发生:

·应用程序将着色器编译到SPIR-V格式,然后将它设置给流水线的着色器状态。

·描述符帮助我们将资源链接到着色器本身。应用程序从描述符缓冲池中分配了描述符集,并将着色器中的输入和输出资源槽联系起来。

·应用程序创建了流水线对象,其中包含了静态状态和动态状态,用来控制不同的硬件设定。流水线最好是从流水线缓冲池中创建的,这样可以获得更好的性能。

4.指令的记录

指令的记录是逐渐构成指令缓存的过程。指令缓存是从指令内存池当中分配而来的。指令池可以用来同时分配多个指令缓存。应用程序定义了指令的开始和结束位置之后,就可以将指令记录到指令缓存当中。图1-11给出了一个绘制指令缓存的记录过程,正如你所看到的那样,这里包含了很多不同的指令,它们按照自顶向下的顺序逐步实现物体的绘制工作。图 1-11注意,指令缓存中的指令可能会随着工作的需求而发生变化。图1-11只是作为演示,它包含了大部分图元绘制操作中最常见的一些步骤。

图中所描述的绘制过程主要如下:

·范围(scope):范围定义了指令缓存记录的起始和截止位置。

·渲染通道(render pass):这里所定义的用户工作的执行过程可能会直接影响到帧缓存的内容。它可能包含了附件、子通道以及子通道之间的一些依赖关系。附件指的是准备执行绘制的图像表面。在子通道中,作为附件的图像可以用于多重采样的操作。渲染通道同时还负责设置初始的帧缓存状态:可以让它维持之前的状态,或者使用给定的颜色清除帧缓存。与之类似,当渲染通道结束的时候,它所保存的结果也可以被舍弃或者保留。

·流水线(pipeline):其中包含了流水线对象所用的各种(动态/静态)状态信息。

·描述符(descriptor):它负责将资源信息绑定到流水线。

·绑定资源(bind resource):它负责设置顶点缓存、图像等几何相关的信息。

·视口(viewport):它定义了绘制表面上可供执行图元渲染的部分矩形。

·裁切器(scissor):它定义了一个矩形空间区域,并舍弃这个区域之外的所有绘制信息。

·绘制(drawing):绘制指令将设置几何体的缓存属性,例如开始索引、总计数值等。指令缓存的创建是一项耗时耗力的工作。它可以被看作是对性能影响最大的一项操作。如果某一项工作在多帧之间会被反复执行,那么对应的指令缓存也可以被反复使用。我们也可以不做重新记录,直接重新提交指令缓存。此外,我们也可以通过多线程的方式同步生成多个指令缓存。Vulkan的设计可以很好地支持多线程的特性。指令池的设计确保了多线程环境下不会出现资源互锁的问题。

图1-12给出了使用多核以及多线程的方式实现可扩展的指令缓存创建模型的过程。该模型在多核环境下可以提供真正的并行实现方法。

这里的每个线程都维护了一个独立的指令缓存池,并且从中分配一个或者多个指令缓存,互相之间不存在冲突或者资源互锁的问题。

5.队列的提交

当指令缓存构建完成后,我们就可以将它们提交到队列中处理。Vulkan向应用程序暴露了不同类型的队列接口,例如图形、DMA/传输,或者计算队列。队列的选择和提交非常依赖于工作本身的性质。例如,图像相关的任务是必须提交给图像队列的。与此类似,对于计算相关的操作,最好的选择肯定是传递给计算队列。工作的提交是通过异步的方式执行的。多个指令缓存可以被压送到独立的兼容队列里,从而实现并行的执行。应用程序需要负责指令缓存中的各种同步,以及队列之间的同步操作,甚至还有宿主机和设备之间的同步操作。图 1-12

队列的提交需要执行下面的操作:

·从交换链中获取当前图像,决定下一帧绘制所用的表面。

·如需要,在这里执行各种同步的机制,例如信号量、栅栏等。

·收集指令缓存,并且发布到对应的设备队列中,准备处理。

·请求将输出设备中已经渲染完毕的图像显示出来。1.7 总结

本章通过初学者简单易懂的方式对Vulkan进行概要性的介绍讲解。在本章中,我们了解了Vulkan的演化和历史,以及相关的贡献者。然后我们对比了Vulkan与OpenGL API的区别,了解了现代计算机时代引入Vulkan的意义。我们还对一些重要的API相关的技术词汇做了尽量简单和明确的解释。Vulkan API的基础,也就是对它的工作模型进行准确和详细的剖析。我们还认识了Vulkan生态系统中的一些基本功能组件,了解了它们的角色、职责,以及内在联系。最后,在本章末尾,我们通过引入伪编程模型的方法学习了Vulkan的基本工作流程。

完成本章学习后,我们将具备对于Vulkan API和细节工作模型的基本理解,并且学会了一些重要的术语,从而拉开Vulkan编程学习的序幕。

下一章我们将开始通过伪代码的方式学习Vulkan编程。我们会创建一个简单的例子,其中涵盖了一些重要的核心概念、Vulkan API的基础知识,以及数据结构,但是暂时不会深入讨论细节。在下一章中我们会理解Vulkan图形流水线编程的完整流程。第2章 你的第一个Vulkan伪代码程序

上一章我们对新的Vulkan API做了非常基本的讲解。我们概览了API的高级生态系统设计,并了解了系统的内部功能模块和执行模型。

本章我们将学习如何安装Vulkan以及使用伪代码进行编程的方法。Vulkan自身的显式特性使得相关的程序代码开发变得非常烦琐。在Vulkan中,要编写一个简单的Hello World程序可能需要大概1500行代码。也就是说,对于初学者来说就算是编写简单的程序也是非常有挑战性的。不过我们不用着急动手,先使用简单的伪代码编程模型来尝试编写完整的Hello World程序吧!

初学者将从本章开始,以用户友好的方式循序渐进地构建他们自己的Vulkan程序。而在本书后面的章节中,我们会逐步开始使用真实的代码去进行Vulkan程序的实战开发。整个学习过程会被划分为多个模块,进而在多个章节中逐一进行介绍。

本章同时也是后续各个章节的基础所在。我们会开始构建一个非常简单的Hello World伪代码程序,从中理解一个简单的Vulkan三角形着色程序的实现过程。本章介绍以下内容:

·Vulkan的安装。

·Hello World伪代码程序。

·全部整合到一起。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载