Vulkan官方英文原文: https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain
对应的Vulkan技术规格说明书版本: Vulkan 1.3.2
Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own the buffers we will render to before we visualize them on the screen. This infrastructure is known as the swap chain and must be created explicitly in Vulkan. The swap chain is essentially a queue of images that are waiting to be presented to the screen. Our application will acquire such an image to draw to it, and then return it to the queue. How exactly the queue works and the conditions for presenting an image from the queue depend on how the swap chain is set up, but the general purpose of the swap chain is to synchronize the presentation of images with the refresh rate of the screen.
Vulkan没有”默认帧缓冲区”的概念,因此它需要一种基础架构, 这个基础架构会拥有一些缓冲对象,这些缓冲对象后面会被我们渲染到屏幕上显示出来。这个基础架构叫做 swap chain , 它必须在Vulkan中明确地创建出来。这个 swap chain 功能本质上是一种图像队列,此队列会按顺序依次将队列中的若干图形显示在屏幕上。我们的应用程序需要获得这个队列中的图像,并在此图像中执行绘制操作,绘制完毕以后,将它重新放置到原来的队列中。这里所说的图像队列确切的工作机制以及正常显示一个队列中图像的相关条件取决于当前 swap chain 的具体设置情况,但是 swap chain 的常规作用却是保障图形显示与幕刷新率同步。
Checking for swap chain support
检查当前运行环境对 swap chain 支持情况
Not all graphics cards are capable of presenting images directly to a screen for various reasons, for example because they are designed for servers and don't have any display outputs. Secondly, since image presentation is heavily tied into the window system and the surfaces associated with windows, it is not actually part of the Vulkan core. You have to enable the VK_KHR_swapchain device extension after querying for its support.
For that purpose we'll first extend the isDeviceSuitable function to check if this extension is supported. We've previously seen how to list the extensions that are supported by a VkPhysicalDevice, so doing that should be fairly straightforward. Note that the Vulkan header file provides a nice macro VK_KHR_SWAPCHAIN_EXTENSION_NAME that is defined as VK_KHR_swapchain. The advantage of using this macro is that the compiler will catch misspellings.
First declare a list of required device extensions, similar to the list of validation layers to enable them.
由于各种原因,不是所有的图形卡都具备将(渲染出来的)图像直接显示在屏幕上的功能,例如一些图形卡就设计为只运行在服务端或者没有任何输出显示内容的能力。其次,实际上表面对象并没有包含在Vulkan的核心模块中,这是由于图像显示和窗口系统紧密关联而表面对象又与窗口结合在一起。在查询到当前运行环境对 swap chain 的支持情况之后,你需要明确启用 VK_KHR_swapchain 所代表的设备扩展功能。
为了达到这个目的我们首先扩展之前定义的 isDeviceSuitable 函数,这个函数扩展之后用于检查当前所需的 swa_chain 扩展是否能被当前运行环境支持。我们之前已经知道如何列举这些被 VkPhysicalDevice 支持的扩展信息,因此实现这个过程相当简单。注意,Vulkan头文件中提供了一个不错的宏定义 VK_KHR_SWAPCHAIN_EXTENSION_NAME ,它定义了 VK_KHR_swapchain(一种swap chain的实现)。使用这个宏定义的优势在于,编译或(编辑)过程中编译器可以直接检查出拼写错误(如果直接使用字符串 "VK_KHR_swapchain" 则无法让编译器去检查拼写错误)。
第一步,我们声明一个swap chain功能所需的扩展列表,和验证层的扩展列表一样去启用他们。
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
Next, create a new function checkDeviceExtensionSupport that is called from isDeviceSuitable as an additional check:
第二步,创建一个新函数,叫做 checkDeviceExtensionSupport ,在 isDeviceSuitable 函数中调用,作为一个检查条件,代码如下:
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
return indices.isComplete() && extensionsSupported;
}
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
return true;
}
Modify the body of the function to enumerate the extensions and check if all of the required extensions are amongst them.
调整函数中的代码,枚举出相关扩展信息,检查是否所有需要的扩展都包含在内。
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto& extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
I've chosen to use a set of strings here to represent the unconfirmed required extensions. That way we can easily tick them off while enumerating the sequence of available extensions. Of course you can also use a nested loop like in checkValidationLayerSupport. The performance difference is irrelevant. Now run the code and verify that your graphics card is indeed capable of creating a swap chain. It should be noted that the availability of a presentation queue, as we checked in the previous chapter, implies that the swap chain extension must be supported. However, it's still good to be explicit about things, and the extension does have to be explicitly enabled.
我在这里选择使用一个字符串的集合对象去记录这些不确定是否需要的扩展。用这个方式我们能轻松的筛选出枚举出来的一系列可用的扩展信息。当然你也可以像 checkValidationLayerSupport 函数中一样使用嵌套循环的机制来实现(两层for循环)。(不同方式的代码实现带来的)性能差异无关紧要。现在运行这些代码, 来验证你的图形卡的确具备创建 swap chain 的能力。和上一章节中我们所做的检查一样,要注意图像显示队列(presentation queue)的可用性,意味着 swap chain 扩展必须被支持。不管怎样,把事情明确地说清楚总是好的,而且(swap chain)扩展必须被明确地(手动)启用。
Enabling device extensions
启用设备扩展
Using a swap chain requires enabling the VK_KHR_swapchain extension first. Enabling the extension just requires a small change to the logical device creation structure:
启动一个swap chain对象首先要启用 VK_KHR_swapchain 扩展。启用此扩展只需在逻辑设备创建过程中对代码做一点小改动就可以了:
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
Make sure to replace the existing line createInfo.enabledExtensionCount = 0; when you do so.
确保替换之前写好的这行代码 createInfo.enabledExtensionCount = 0;,将其替换为createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());(如上代码块所示)。
Querying details of swap chain support
查询swap chain支持情况的详情
Just checking if a swap chain is available is not sufficient, because it may not actually be compatible with our window surface. Creating a swap chain also involves a lot more settings than instance and device creation, so we need to query for some more details before we're able to proceed.
只检查 swap chain 能否可用是不够的,因为实际上它可能不会被我们运行环境下的窗口表面对象兼容。由于创建 swap chain 对象的过程涉及到一大堆设置,比创建实例和创建设备的过程更多,所以我们继续进行后续操作之前需要查询更多详细信息。
There are basically three kinds of properties we need to check:
Basic surface capabilities (min/max number of images in swap chain, min/max width and height of images)
Surface formats (pixel format, color space)
Available presentation modes
我们基本上需要检查三种特性:
基本的表面对象能力(swap chain 中图像数量的最大最小值,图像宽或高的最大最小值)
表面格式(像素数据格式,色彩空间)
可用的显示模式
Similar to findQueueFamilies, we'll use a struct to pass these details around once they've been queried. The three aforementioned types of properties come in the form of the following structs and lists of structs:
类似于 findQueueFamilies函数,一旦这些详细信息被查询到,我们将使用一个结构体来(记录并)传递。前面提到的这三种类型属性存放在如下结构体和相应的结构体列表数据形式中:
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
We'll now create a new function querySwapChainSupport that will populate this struct.
我们创建一个新的函数 querySwapChainSupport 来填充这个结构体对象(的具体数据)。
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
return details;
}
This section covers how to query the structs that include this information. The meaning of these structs and exactly which data they contain is discussed in the next section.
本小节介绍如何查询包含这些信息的结构体。这些结构体的含义和他们包含的确切数据在下一小节讨论。
Let's start with the basic surface capabilities. These properties are simple to query and are returned into a single VkSurfaceCapabilitiesKHR struct.
让我们从基本的表面对象能力开始。这些属性简单地就能查询到,并且通过单个 VkSurfaceCapabilitiesKHR 结构体对象返回。
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
This function takes the specified VkPhysicalDevice and VkSurfaceKHR window surface into account when determining the supported capabilities. All of the support querying functions have these two as first parameters because they are the core components of the swap chain.
在确定可支持功能的时候,这个函数(vkGetPhysicalDeviceSurfaceCapabilitiesKHR)将VkPhysicalDevice对象和 VkSurfaceKHR窗口表面对象考虑在内。所有可用的查询函数都把这两个对象作为前置的输入参数,因为他们是 swap chain 功能的核心组件。
The next step is about querying the supported surface formats. Because this is a list of structs, it follows the familiar ritual of 2 function calls:
下一步,查询当前运行环境支持的所有表面对象格式。由于这个查询结果是一个结构体列表,此查询过程遵循两次函数调用的熟知习惯:
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
Make sure that the vector is resized to hold all the available formats. And finally, querying the supported presentation modes works exactly the same way with vkGetPhysicalDeviceSurfacePresentModesKHR:
确保vector类型列表对象的大小调整到能容纳所有可用的格式。最后,查询可支持的显示模式,查询操作调用vkGetPhysicalDeviceSurfacePresentModesKHR函数,与查询表面对象格式的实现方式完全一样。
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, s