Vulkan Tutorial 0 引言

1 开发环境

在这一章中,我们将设置您开发Vulkan应用程序的环境,并安装一些有用的库。除了编译器之外,我们将使用的所有工具都与Windows、Linux和MacOS兼容,但安装它们的步骤有些不同,这就是为什么它们在这里被单独描述。

Windows

如果你在为Windows开发,那么我将假设你正在使用Visual Studio来编译你的代码。为了完全支持C++17,你需要使用Visual Studio 2017或更高。下面概述的步骤是为VS 2022编写的。

Vulkan SDK

开发Vulkan应用程序所需的最重要组件是SDK。它包括头文件、标准验证层、调试工具和Vulkan函数的加载器。该加载器在运行时查找驱动程序中的函数,类似于OpenGL的GLEW–如果你熟悉的话。SDK可以通过页面底部的按钮从LunarG网站下载。

Vulkan Tutorial 0 引言_第1张图片

继续进行安装,并注意SDK的安装位置。我们要做的第一件事是验证你的显卡和驱动是否正确支持Vulkan。进入你安装SDK的目录,打开Bin目录,运行vkcube.exe演示。你应该看到以下情况。

Vulkan Tutorial 0 引言_第2张图片

GLFW

如前所述,Vulkan本身是一个与平台无关的API,不包括创建窗口来显示渲染结果的工具。为了受益于Vulkan的跨平台优势并避免Win32的恐怖,我们将使用GLFW库来创建一个窗口,它支持Windows、Linux和MacOS。还有其他的库可以用于这个目的,比如SDL,但是GLFW的优势在于它还抽象出了Vulkan中除了创建窗口之外的一些其他平台特有的东西。

GLM

与DirectX 12不同,Vulkan不包括一个用于线性代数操作的库,所以我们必须下载一个。GLM是一个不错的库,它是为图形API设计的,也常用于OpenGL。

2 画一个三角形需要什么

我们现在来看看在一个良好的Vulkan程序中渲染一个三角形所需的所有步骤的概述。这里介绍的所有概念都将在接下来的章节中详细阐述。这只是为了给你一个大的画面,以便将所有单独的组件联系起来。

第1步-实例和物理设备选择

一个Vulkan应用程序从通过**VkInstance**设置Vulkan API开始。通过描述您的应用程序和您将要使用的任何API扩展来创建一个实例。在创建实例后,你可以查询Vulkan支持的硬件,并选择一个或多个**VkPhysicalDevices**来进行操作。您可以查询VRAM大小和设备能力等属性,以选择所需的设备,例如,倾向于使用专用显卡。

第2步-逻辑设备和队列系列

在选择了正确的硬件设备后,你需要创建一个VkDevice(逻辑设备),在这里你可以更具体地描述你将使用哪些VkPhysicalDeviceFeatures,比如多视口渲染和64位浮点。你还需要指定你想使用的队列系列。使用Vulkan进行的大多数操作,如绘制命令和内存操作,都是通过提交给VkQueue来异步执行的。队列是由队列家族分配的,每个队列家族在其队列中支持一组特定的操作。例如,可以为图形、计算和内存传输操作提供单独的队列家族。队列家族的可用性也可以作为物理设备选择中的一个区分因素。支持Vulkan的设备有可能不提供任何图形功能,但是目前所有支持Vulkan的显卡一般都会支持我们感兴趣的所有队列操作。

第3步-窗口表面和交换链

除非你只对屏幕外的渲染感兴趣,否则你将需要创建一个窗口来呈现渲染的图像。窗口可以通过本地平台的API或者GLFW和SDL等库来创建。我们将在本教程中使用GLFW,但在下一章中会有更多的介绍。

我们还需要两个组件来实际渲染到一个窗口:一个窗口表面(VkSurfaceKHR)和一个交换链(VkSwapchainKHR)。注意KHR的后缀,这意味着这些对象是Vulkan扩展的一部分。Vulkan API本身是完全与平台无关的,这就是为什么我们需要使用标准化的WSI(Window System Interface)扩展来与窗口管理器交互。表面是一个跨平台的窗口抽象,用于渲染,一般通过提供对本地窗口句柄的引用来实例化,例如Windows的HWND。幸运的是,GLFW库有一个内置函数来处理这个平台的具体细节。

交换链是一个渲染目标的集合。它的基本目的是确保我们当前正在渲染的图像与当前屏幕上的图像不同。这对于确保只显示完整的图像是很重要的。每次我们想画一个帧时,我们必须要求交换链为我们提供一个要渲染的图像。当我们完成了一个帧的绘制,图像就会被送回交换链,以便在某一时刻被呈现到屏幕上。渲染目标的数量和将完成的图像呈现到屏幕上的条件取决于呈现模式。常见的呈现模式是双缓冲(vsync)和三缓冲。我们将在交换链创建章节中研究这些。

一些平台允许你通过VK_KHR_displayVK_KHR_display_swapchain扩展直接渲染到显示器上,而不与任何窗口管理器交互。这些允许你创建一个代表整个屏幕的表面,例如,可以用来实现你自己的窗口管理器。

第4步-图像视图和帧缓冲器

为了绘制从交换链获得的图像,我们必须把它包装成一个VkImageViewVkFramebuffer。一个图像视图引用一个要使用的图像的特定部分,而一个帧缓冲区引用要用于颜色、深度和模板目标的图像视图。因为在交换链中可能有许多不同的图像,我们将预先为每个图像创建一个图像视图和framebuffer,并在绘制时选择合适的。

第5步-渲染通道

Vulkan中的渲染传递描述了在渲染操作中使用的图像类型,它们将如何被使用,以及它们的内容应该如何被处理。在我们最初的三角形渲染应用中,我们将告诉Vulkan,我们将使用一个单一的图像作为颜色目标,并且我们希望在绘图操作之前将其清除为纯色。渲染通道只描述了图像的类型,而VkFramebuffer实际上是将特定的图像绑定到这些槽中。

第6步-图形管线

Vulkan中的图形管道是通过创建一个VkPipeline对象来设置的。它描述了显卡的可配置状态,比如视口大小和深度缓冲器的操作,以及使用VkShaderModule对象的可编程状态。VkShaderModule对象是由着色器字节码创建的。驱动程序还需要知道哪些渲染目标将在管道中使用,我们通过引用渲染通道来指定。

与现有的API相比,Vulkan最突出的特点之一是几乎所有的图形管道配置都需要提前设置。这意味着,如果你想切换到一个不同的着色器或稍微改变你的顶点布局,那么你需要完全重新创建图形管道。这意味着你将不得不为你的渲染操作所需的所有不同组合提前创建许多VkPipeline对象。只有一些基本的配置,比如视口大小和透明颜色,可以动态地改变。所有的状态也需要明确地描述,例如,没有默认的颜色混合状态。

好消息是,因为你做的是相当于提前编译而不是及时编译,所以驱动程序有更多的优化机会,运行时的性能也更可预测,因为像切换到不同的图形管道的大型状态变化是非常明确的。

第7步-命令池和命令缓冲区

如前所述,Vulkan中许多我们想要执行的操作,如绘图操作,都需要提交给队列。这些操作首先需要被记录到VkCommandBuffer中,然后才能被提交。这些命令缓冲区是从VkCommandPool中分配的,它与特定的队列系列相关。为了画一个简单的三角形,我们需要记录一个有以下操作的命令缓冲区。

  • 开始渲染通道
  • 绑定图形管线
  • 绘制3个顶点
  • 结束渲染过程

因为帧缓冲区中的图像取决于交换链将给我们的具体图像,所以我们需要为每个可能的图像记录一个命令缓冲区,并在绘制时选择正确的图像。另一种方法是每一帧都重新记录命令缓冲区,这样做的效率就不高了。

第8步-主循环

现在,绘图命令已经被包装成一个命令缓冲区,主循环就非常简单了。我们首先用vkAcquireNextImageKHR从交换链中获取一个图像。然后,我们可以为该图像选择合适的命令缓冲区,并用vkQueueSubmit来执行它。最后,我们用vkQueuePresentKHR将图像返回到交换链,以便呈现在屏幕上。

提交给队列的操作是异步执行的。因此,我们必须使用同步对象,如semaphores来确保正确的执行顺序。绘制命令缓冲区的执行必须被设置为等待图像采集完成,否则可能会发生我们开始对一个仍在读取的图像进行渲染,以便在屏幕上呈现。vkQueuePresentKHR调用又需要等待渲染完成,为此我们将使用第二个semaphore,在渲染完成后发出信号。

总结

这次旋风式的旅行应该让你对绘制第一个三角形的工作有一个基本的了解。一个真实世界的程序包含更多的步骤,比如分配顶点缓冲区、创建统一缓冲区和上传纹理图像,这些将在随后的章节中讲述,但我们将从简单的开始,因为Vulkan的学习曲线已经足够陡峭了。请注意,我们将通过最初在顶点着色器中嵌入顶点坐标而不是使用顶点缓冲器来欺骗一下。这是因为管理顶点缓冲器需要先熟悉一下命令缓冲器。

所以简而言之,为了绘制第一个三角形,我们需要。

  • 创建一个VkInstance
  • 选择一个支持的显卡(VkPhysicalDevice)
  • 创建一个VkDevice和VkQueue,用于绘制和展示
  • 创建一个窗口、窗口表面和交换链
  • 将交换链的图像包裹到VkImageView中
  • 创建一个渲染通道,指定渲染目标和用途
  • 为渲染通道创建帧缓冲区
  • 设置图形管线
  • 为每个可能的交换链图像分配并记录一个命令缓冲区的绘制命令
  • 通过获取图像来绘制帧,提交正确的绘制命令缓冲区,并将图像返回给交换链。

你可能感兴趣的:(vulkan,图形渲染)