Overview

本章将首先介绍Vulkan及其解决的问题。之后,我们将研究第一个三角形所需的成分。这将为您提供一个大画面,以放置后续章节。最后,我们将介绍Vulkan API的结构和一般使用模式。

目录

Origin of Vulkan

What it takes to draw a triangle

Step 1 - Instance and physical device selection

Step 2 - Logical device and queue families

Step 3 - Window surface and swap chain

Step 4 - Image views and framebuffers

Step 5 - Render passes

Step 6 - Graphics pipeline

Step 7 - Command pools and command buffers

Step 8 - Main loop

Summary

API concepts

Coding conventions

Validation layers


Origin of Vulkan

与之前的图形API一样,Vulkan被设计为GPU上的跨平台抽象。大多数这些API的问题是,在设计它们的时代,图形硬件主要局限于可配置的固定功能。程序员必须以标准格式提供顶点数据,并且在照明和着色选项方面受制于GPU制造商。

随着显卡体系结构的成熟,它们开始提供越来越多的可编程功能。所有这些新功能都必须以某种方式与现有API集成。这导致了图形驱动程序方面不太理想的抽象和大量猜测,以将程序员的意图映射到现代图形架构。这就是为什么有如此多的驱动程序更新来提高游戏的性能,有时会带来巨大的利润。由于这些驱动程序的复杂性,应用程序开发人员还需要处理供应商之间的不一致,例如着色器所接受的语法。除了这些新功能外,过去十年还出现了大量具有强大图形硬件的移动设备。这些移动GPU根据其能量和空间需求具有不同的架构。例如平铺渲染,通过为程序员提供对该功能的更多控制,它将受益于改进的性能。源于这些API时代的另一个限制是有限的多线程支持,这可能会导致CPU方面的瓶颈。

Vulkan通过为现代图形架构从头设计来解决这些问题。它通过允许程序员使用更详细的API明确指定其意图,并允许多个线程并行创建和提交命令,从而减少了驱动程序开销。它通过使用单个编译器切换到标准化字节码格式来减少着色器编译中的不一致性。最后,通过将图形和计算功能统一到一个API中,它承认了现代图形卡的通用处理能力。

What it takes to draw a triangle

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

Step 1 - Instance and physical device selection

Vulkan应用程序首先通过VkInstance设置Vulkan API。通过描述应用程序和将要使用的任何API扩展来创建实例。创建实例后,您可以查询Vulkan支持的硬件,并选择一个或多个VkPhysicalDevices用于操作。您可以查询VRAM大小和设备功能等财产,以选择所需的设备,例如首选使用专用图形卡。

Step 2 - Logical device and queue families

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

Step 3 - Window surface and swap chain

除非您只对屏幕外渲染感兴趣,否则需要创建一个窗口来显示渲染图像。可以使用本地平台API或库(如GLFW和SDL)创建窗口。我们将在本教程中使用GLFW,但更多信息将在下一章中介绍。

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

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

某些平台允许您直接渲染到显示器,而无需通过VK_KHR_display和VK_KHR_display_swapchain扩展与任何窗口管理器交互。例如,它们允许您创建一个表示整个屏幕的曲面,并可用于实现自己的窗口管理器。

Step 4 - Image views and framebuffers

要绘制从交换链获取的图像,我们必须将其包装到VkImageView和VkFramebuffer中。图像视图引用要使用的图像的特定部分,帧缓冲区引用要用于颜色、深度和模板目标的图像视图。因为交换链中可能有许多不同的图像,所以我们将先为每个图像创建一个图像视图和帧缓冲区,然后在绘制时选择合适的图像。

Step 5 - Render passes

Vulkan中的渲染过程描述了渲染操作期间使用的图像类型、如何使用它们以及如何处理它们的内容。在我们的初始三角形渲染应用程序中,我们将告诉Vulkan,我们将使用单个图像作为颜色目标,并希望在绘制操作之前将其清除为纯色。虽然渲染过程仅描述图像的类型,但VkFramebuffer实际上将特定图像绑定到这些插槽。

Step 6 - Graphics pipeline

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

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

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

Step 7 - Command pools and command buffers

如前所述,我们希望在Vulkan中执行的许多操作,如绘图操作,都需要提交到队列中。这些操作首先需要记录到VkCommandBuffer中,然后才能提交。这些命令缓冲区是从与特定队列系列关联的VkCommandPool中分配的。要绘制一个简单的三角形,我们需要用以下操作记录一个命令缓冲区:

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

因为帧缓冲区中的图像取决于交换链将提供给我们的特定图像,所以我们需要为每个可能的图像记录一个命令缓冲区,并在绘制时选择正确的图像。另一种方法是在每一帧再次记录命令缓冲区,这效率不高。

Step 8 - Main loop

现在,绘图命令已被打包到命令缓冲区中,主循环非常简单。我们首先使用vkAcquireNextImageKHR从交换链获取图像。然后,我们可以为该图像选择适当的命令缓冲区,并使用vkQueueSubmit执行它。最后,我们将图像返回到交换链,以使用vkQueuePresentKHR显示在屏幕上。

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

Summary

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

简而言之,要绘制第一个三角形,我们需要:

  • 创建VkInstance
  • 选择支持的图形卡(VkPhysicalDevice)
  • 为绘图和演示创建VkDevice和VkQueue
  • 创建窗口、窗口曲面和交换链
  • 将交换链图像包装到VkImageView中
  • 创建指定渲染目标和用法的渲染过程
  • 为渲染过程创建帧缓冲区
  • 设置图形管道
  • 为每个可能的交换链图像分配并记录带有绘制命令的命令缓冲区
  • 通过获取图像、提交正确的绘制命令缓冲区并将图像返回到交换链来绘制帧

这需要很多步骤,但在接下来的章节中,每个步骤的目的都会非常简单明了。如果你对单个步骤与整个程序的关系感到困惑,你应该回到本章。

API concepts

本章将简要概述Vulkan API是如何在较低层次上构建的。

Coding conventions

所有Vulkan函数、枚举和结构都在Vulkan.h标头中定义,该标头包含在LunarG开发的Vulkan SDK中。我们将在下一章研究如何安装此SDK。

函数具有小写vk前缀,枚举和结构等类型具有vk前缀,而枚举值具有vk_前缀。API大量使用结构为函数提供参数。例如,对象创建通常遵循以下模式:

VkXXXCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.foo = ...;
createInfo.bar = ...;

VkXXX object;
if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) {
    std::cerr << "failed to create object" << std::endl;
    return false;
}

Vulkan中的许多结构要求您在sType成员中显式指定结构类型。pNext成员可以指向扩展结构,并且在本教程中始终为nullptr。创建或销毁对象的函数将具有VkAllocationCallbacks参数,该参数允许您为驱动程序内存使用自定义分配器,在本教程中,该分配器也将保留为空。

几乎所有函数都返回一个VkResult,即VK_SUCCESS或错误代码。该规范描述了每个函数可以返回哪些错误代码以及它们的含义。

Validation layers

如前所述,Vulkan旨在实现高性能和低驱动程序开销。因此,默认情况下,它将包含非常有限的错误检查和调试功能。如果你做错了什么,驱动程序通常会崩溃,而不是返回错误代码,或者更糟的是,它会在你的显卡上运行,而在其他显卡上完全失败。

Vulkan允许您通过称为验证层的功能进行广泛检查。验证层是可以插入API和图形驱动程序之间的代码片段,用于执行诸如对功能参数进行额外检查和跟踪内存管理问题等操作。好的是,您可以在开发期间启用它们,然后在发布应用程序时完全禁用它们,从而实现零开销。任何人都可以编写自己的验证层,但LunarG的Vulkan SDK提供了一组标准的验证层。您还需要注册一个回调函数以从层接收调试消息。

因为Vulkan对每个操作都非常明确,验证层也非常广泛,所以与OpenGL和Direct3D相比,实际上可以更容易地找出为什么屏幕是黑色的!

在我们开始编写代码之前,只有一个步骤,那就是设置开发环境。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值