Vulkan教程之Swap chain(交换链)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

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, surface, &presentModeCount, details.presentModes.data());
}

All of the details are in the struct now, so let's extend isDeviceSuitable once more to utilize this function to verify that swap chain support is adequate. Swap chain support is sufficient for this tutorial if there is at least one supported image format and one supported presentation mode given the window surface we have.

现在所有的详细信息都在这个(SwapChainSupportDetails 类型的)结构体对象中所以让我们来扩展 isDeviceSuitable 函数, 再用一次这个函数来验证当前运行环境对 swap chain 的支持是否满足需求。假如我们当前使用的窗口表面对象至少支持一种图像格式和一种显示模式那么此教程所需的 swap chain 功能支持是够用的。

bool swapChainAdequate = false;
if (extensionsSupported) {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
    swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}

It is important that we only try to query for swap chain support after verifying that the extension is available. The last line of the function changes to:

重要的是验证这些扩展是可用的之后我们才尝试查询 swap chain 的支持情况。

return indices.isComplete() && extensionsSupported && swapChainAdequate;

Choosing the right settings for the swap chain

为 swap chain 功能选择正确的设置

If the swapChainAdequate conditions were met then the support is definitely sufficient, but there may still be many different modes of varying optimality. We'll now write a couple of functions to find the right settings for the best possible swap chain. There are three types of settings to determine:

  • Surface format (color depth)

  • Presentation mode (conditions for "swapping" images to the screen)

  • Swap extent (resolution of images in swap chain)

For each of these settings we'll have an ideal value in mind that we'll go with if it's available and otherwise we'll create some logic to find the next best thing.

如果 swapChainAdequate 变量表示的条件得到满足那么(对于 swap chain 功能正常运行所需的)支持肯定是足够了但是可能仍然有许多不同的可变最优性能的模式。我们现在就编写几个函数去找到构建最优swap chain功能的正确设定。由下面三种设置类型决定

  • 表面对象(颜色深度)

  • 显示模式(将图像内容交互到屏幕的条件)

  • 交换范围(swap chain 中图像的分辨率)

针对每一个设置我们心中都有一个理想值如果(在当前运行环境中)它可用就使用它否则我们会构造一些(搜索)逻辑来找到别的最合适的值。

Surface format

表面对象格式

The function for this setting starts out like this. We'll later pass the formats member of the SwapChainSupportDetails struct as argument.

这个函数开始时候(的形式)如下所示。我们后续会传入SwapChainSupportDetails 结构体中表示表面对象格式的成员变量 formats 作为函数参数。

VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    
}

Each VkSurfaceFormatKHR entry contains a format and a colorSpace member. The format member specifies the color channels and types. For example, VK_FORMAT_B8G8R8A8_SRGB means that we store the B, G, R and alpha channels in that order with an 8 bit unsigned integer for a total of 32 bits per pixel. The colorSpace member indicates if the SRGB color space is supported or not using the VK_COLOR_SPACE_SRGB_NONLINEAR_KHR flag. Note that this flag used to be called VK_COLORSPACE_SRGB_NONLINEAR_KHR in old versions of the specification.

每一个 VkSurfaceFormatKHR 实例包含一个 VKFormat类型的表面对象成员变量format 和一个VkColorSpaceKHR类型的颜色空间成员变量colorSpace。例如 VK_FORMAT_B8G8R8A8_SRGB 这个VKFormat类型枚举变量的意思是我们按照B, G, R, 和 alpha 这四个通道的顺序每个通道8bit(1字节)来存储(像素颜色数据)每个像素(的颜色数据)总共(4字节)32位。VKSurfaceFormatHR结构体中的 colorSpace(颜色空间) 这个成员变量表示当前运行环境是否支持 SRGB 颜色空间或者没有使用VK_COLOR_SPACE_SRGB_NONLINEAR_KHR标志(使用了其他颜色空间)。注意现在的VK_COLOR_SPACE_SRGB_NONLINEAR_KHR 在旧版技术说明里面是VK_COLORSPACE_SRGB_NONLINEAR_KHR 。

For the color space we'll use SRGB if it is available, because it results in more accurate perceived colors. It is also pretty much the standard color space for images, like the textures we'll use later on. Because of that we should also use an SRGB color format, of which one of the most common ones is VK_FORMAT_B8G8R8A8_SRGB.

说到颜色空间如果SRGB这类颜色空间可用我们就用它因为它生成的颜色更能被准确地感知到。它也是非常棒的图像标准颜色空间例如我们后面会用到的纹理(图像)。因此我们还应该使用一个SRGB颜色数据格式(和SRGB颜色空间匹配)其中最常见的一种就是VK_FORMAT_B8G8R8A8_SRGB颜色数据格式。

Let's go through the list and see if the preferred combination is available:

让我们通过列表看看(availableFormats列表中)是否有首选(格式)组合是可用的:

for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}

If that also fails then we could start ranking the available formats based on how "good" they are, but in most cases it's okay to just settle with the first format that is specified.

如果上述操作没有找到可用的组合我们可以开始根据可用的颜色数据格式"好"的程度来排名但是在大多数情况下只需设定第一个指定的格式就可以了。

VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}

Presentation mode

显示模式

The presentation mode is arguably the most important setting for the swap chain, because it represents the actual conditions for showing images to the screen. There are four possible modes available in Vulkan:

  • VK_PRESENT_MODE_IMMEDIATE_KHR: Images submitted by your application are transferred to the screen right away, which may result in tearing.

  • VK_PRESENT_MODE_FIFO_KHR: The swap chain is a queue where the display takes an image from the front of the queue when the display is refreshed and the program inserts rendered images at the back of the queue. If the queue is full then the program has to wait. This is most similar to vertical sync as found in modern games. The moment that the display is refreshed is known as "vertical blank".

  • VK_PRESENT_MODE_FIFO_RELAXED_KHR: This mode only differs from the previous one if the application is late and the queue was empty at the last vertical blank. Instead of waiting for the next vertical blank, the image is transferred right away when it finally arrives. This may result in visible tearing.

  • VK_PRESENT_MODE_MAILBOX_KHR: This is another variation of the second mode. Instead of blocking the application when the queue is full, the images that are already queued are simply replaced with the newer ones. This mode can be used to render frames as fast as possible while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked.

Only the VK_PRESENT_MODE_FIFO_KHR mode is guaranteed to be available, so we'll again have to write a function that looks for the best mode that is available:

显示模式按理说是 swap chain 功能中最重要的设置因为它直接关系到图像如何显示到屏幕上。在Vulkan中有四个可能有用的模式:

  • VK_PRESENT_MODE_IMMEDIATE_KHR: 你的应用程序提交的图像(数据) 立即被转移到屏幕(这种模式)可能会产生屏幕中显示画面的撕裂现象.

  • VK_PRESENT_MODE_FIFO_KHR : Swap chain 功能是一个当显示画面刷新时显示器从(队列)头部获取图像的队列而程序则在此队列尾部插入待渲染显示的图像。如果这个队列满了程序就会处于等待状态。这与出现在现代游戏中的垂直同步机制最类似。刷新显示的时刻叫做"垂直空白"。

  • VK_PRESENT_MODE_FIFO_RELAXED_KHR : 这个模式与前一个不同之处仅仅在于最后的垂直空白状态下是否应用程序的操作延迟了以及是否此队列为空。当图像数据一旦送达图像数据会被立即转移(到屏幕)而不是等待下一个垂直空白状态。这可能导致可见的(显示)画面撕裂(因为画面的某一部分数据可能被立即更新了)。

  • VK_PRESENT_MODE_MAILBOX_KHR : 此模式是第二种模式(VK_PRESENT_MODE_FIFO_KHR)的另外一种变体。如果 swap chain 功能中的图像队列已满那么(在此模式下)已经在队列中的图形被直接替换为新图像而不是将程序阻塞住。这个模式能尽可能快地渲染大量帧画面而且能避免画面撕裂问题产生的延迟问题也比标准的垂直同步机制要少。这就是俗称的"三重缓冲"机制尽管(这个机制)由三个缓冲区独立实现但是并不意味着帧率不需要被锁定。

只有VK_PRESENT_MODE_FIFO_KHR 模式是保证可用的因此我们必须再次编写一个函数来查找可用的最优显示模式:

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    return VK_PRESENT_MODE_FIFO_KHR;
}

I personally think that VK_PRESENT_MODE_MAILBOX_KHR is a very nice trade-off if energy usage is not a concern. It allows us to avoid tearing while still maintaining a fairly low latency by rendering new images that are as up-to-date as possible right until the vertical blank. On mobile devices, where energy usage is more important, you will probably want to use VK_PRESENT_MODE_FIFO_KHR instead. Now, let's look through the list to see if VK_PRESENT_MODE_MAILBOX_KHR is available:

假如能耗问题不是一个顾虑我个人认为VK_PRESENT_MODE_MAILBOX_KHR 模式是一个非常不错的选择。它允许我们避免画面撕裂的情况下保持低延迟渲染新的图像在垂直空白状态之前尽可能地正确显示出最新的图像数据。在移动端能耗是一个重要的考量你大概率会用VK_PRESENT_MODE_FIFO_KHR 模式作为替代方案。现在让我们通过列表(availablePresentModes)看看VK_PRESENT_MODE_MAILBOX_KHR是否可用

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}

Swap extent

交换范围

That leaves only one major property, for which we'll add one last function:

Swap extent 功能仅剩一个主要的特性为此我们再增加最后一个函数

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}

The swap extent is the resolution of the swap chain images and it's almost always exactly equal to the resolution of the window that we're drawing to in pixels (more on that in a moment). The range of the possible resolutions is defined in the VkSurfaceCapabilitiesKHR structure. Vulkan tells us to match the resolution of the window by setting the width and height in the currentExtent member. However, some window managers do allow us to differ here and this is indicated by setting the width and height in currentExtent to a special value: the maximum value of uint32_t. In that casewe'll pick the resolution that best matches the window within the minImageExtent and maxImageExtent bounds. But we must specify the resolution in the correct unit.

交换范围就是 swap chain 功能中的图像分辨率而且它几乎完全准确地与用像素数据的方式绘制图像的窗口分辨率相等(分辨率以像素为单位)。(存放)可能的分辨率(取值)范围的变量在VkSurfaceCapabilitiesKHR 结构体中定义。Vulkan文档告诉我们匹配窗口分辨率在currentExtent 成员变量中设置宽和高就可以了。然而一些窗口管理器确实允许我们在这里有所不同这是通过在currentExtent 里将宽和高设置为一个特殊值来表示这个特殊值是uint32_t 类型的最大值。在那种特殊的情况下我们会在minImageExtent 和 maxImageExtent 范围内选择最匹配窗口的分辨率。但是我们必须用正确的单位来具体说明分辨率。

GLFW uses two units when measuring sizes: pixels and screen coordinates. For example, the resolution {WIDTH, HEIGHT} that we specified earlier when creating the window is measured in screen coordinates. But Vulkan works with pixels, so the swap chain extent must be specified in pixels as well. Unfortunately, if you are using a high DPI display (like Apple's Retina display), screen coordinates don't correspond to pixels. Instead, due to the higher pixel density, the resolution of the window in pixel will be larger than the resolution in screen coordinates. So if Vulkan doesn't fix the swap extent for us, we can't just use the original {WIDTH, HEIGHT}. Instead, we must use glfwGetFramebufferSize to query the resolution of the window in pixels before matching it against the minimum and maximum image extent.

GLFW(Graphics Library Framework, GLFW is a free, Open Source, multi-platform library for OpenGL, OpenGL ES and Vulkan application development) 使用了两种单位来度量分辨率尺寸一个是像素单位一个是屏幕坐标单位。例如我们之前在创建窗口时指定的分辨率宽和高是以屏幕坐标来计算的。但是Vulkan操作的是像素因此 swap chain 范围也必须指定像素作为计算单位。不幸的是假如你正在使用一个高 DPI 显示器(例如苹果的Retina视网膜显示器)他的屏幕坐标与像素不对应。相反由于这类显示器像素密度更高用像素单位表示的窗口分辨率将会比屏幕坐标表示的窗口分辨率更大。因此如果Vulkan没有为我们使用者操作交换范围我们不能只使用原始的宽高。相反我们必须使用glfwGetFramebufferSize 来查询以像素为单位的窗口分辨率然后将其匹配到最大最小值的范围之内。

#include <cstdint> // Necessary for uint32_t
#include <limits> // Necessary for std::numeric_limits
#include <algorithm> // Necessary for std::clamp

...

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
        return capabilities.currentExtent;
    } else {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);

        VkExtent2D actualExtent = {
            static_cast<uint32_t>(width),
            static_cast<uint32_t>(height)
        };

        actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
        actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);

        return actualExtent;
    }
}

The clamp function is used here to bound the values of width and height between the allowed minimum and maximum extents that are supported by the implementation.

这个 clamp 函数在这里用来将宽和高的取值范围限定在(当前运行环境已经实现的)支持的最大值和最小值之间。

Creating the swap chain

Now that we have all of these helper functions assisting us with the choices we have to make at runtime, we finally have all the information that is needed to create a working swap chain.

Create a createSwapChain function that starts out with the results of these calls and make sure to call it from initVulkan after logical device creation.

现在用我们已有的所有辅助函数来帮助我们必须在程序运行时做出选择我们最终会得到能创建出正常工作的swap chain的所有必要信息。

创建一个createSwapChain 函数函数开头是调用这些辅助函数获取(创建swap chain 所需的特性信息数据的)结果确保createSwapChain 函数在initVulkan 中创建逻辑设备之后调用它。

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
}

void createSwapChain() {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

    VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
    VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
    VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
}

Aside from these properties we also have to decide how many images we would like to have in the swap chain. The implementation specifies the minimum number that it requires to function:

除了这些特性我们还必须决定我们希望在 swap chain 图像队列中用多少图像。该实现指定了(swap chain)功能正常运行所需的最少(图像)数量如下代码所示 :

uint32_t imageCount = swapChainSupport.capabilities.minImageCount;

However, simply sticking to this minimum means that we may sometimes have to wait on the driver to complete internal operations before we can acquire another image to render to. Therefore it is recommended to request at least one more image than the minimum:

然而这么简单地确定用这个最小值意味着我们可能有时候必须在驱动底层等待其内部运算完成之后我们才能获取另外一个图像用于渲染。因此这里建议系统请求的(图像数量)至少比(swap chain系统所需的)最少图像数量多一个如下代码所示

uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;

We should also make sure to not exceed the maximum number of images while doing this, where 0 is a special value that means that there is no maximum:

当我们使用 minImageCount + 1 这种操作我们也应该确保图像数量不会超过系统支持的最大值(maxImageCount)(maxImageCount 为 0的时候)这个0是一个特殊值它表示当前系统没有最大值限制

if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
}

As is tradition with Vulkan objects, creating the swap chain object requires filling in a large structure. It starts out very familiarly:

依照 Vulkan 对象的传统创建 swap chain 对象需要填充一个大的数据结构。这个开头非常熟悉

VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;

After specifying which surface the swap chain should be tied to, the details of the swap chain images are specified:

指定swap chain应该绑定到哪个表面对象之后接着指定swap chain图像对象的详情

createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

The imageArrayLayers specifies the amount of layers each image consists of. This is always 1 unless you are developing a stereoscopic 3D application. The imageUsage bit field specifies what kind of operations we'll use the images in the swap chain for. In this tutorial we're going to render directly to them, which means that they're used as color attachment. It is also possible that you'll render images to a separate image first to perform operations like post-processing. In that case, you may use a value like VK_IMAGE_USAGE_TRANSFER_DST_BIT instead and use a memory operation to transfer the rendered image to a swap chain image.

createInfo实例的 imageArrayLayers 字段指定了组成每个图像的层数。这个值总是1除非要开发一个立体视角的3D应用程序(例如VR)。imageUsage 位域字段指定我们用 swap chain 功能中的图像实现何种具体操作。本教程中我们将直接渲染他们这表示图像会被用作颜色附件。你也可以先将图像渲染为单独的图像以执行像后处理这样的操作。这种情况下你可以改用一个类似于VK_IMAGE_USAGE_TRANSFER_DST_BIT这样的值并且使用一个内存操作将渲染好的图像传递给一个 swap chain 图像。

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    createInfo.queueFamilyIndexCount = 2;
    createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0; // Optional
    createInfo.pQueueFamilyIndices = nullptr; // Optional
}

Next, we need to specify how to handle swap chain images that will be used across multiple queue families. That will be the case in our application if the graphics queue family is different from the presentation queue. We'll be drawing on the images in the swap chain from the graphics queue and then submitting them to the presentation queue. There are two ways to handle images that are accessed from multiple queues:

  • VK_SHARING_MODE_EXCLUSIVE: An image is owned by one queue family at a time and ownership must be explicitly transferred before using it in another queue family. This option offers the best performance.

  • VK_SHARING_MODE_CONCURRENT: Images can be used across multiple queue families without explicit ownership transfers.

接下来我们需要指定如何处理被多个队列族交叉使用的 swap chain 图像。如果图形队列和显示队列不是同样的族(family)我们的应用程序就会出现这种情况。我们将图形队列绘制到 swap chain 图像中然后将他们(swap chain 中的图像)提交到显示队列。有两种方式处理多个队列中访问到的图像:

  • VK_SHARING_MODE_EXCLUSIVE: 一个图像在同一时间(只能)被一个队列族拥有并且在另一个队列族使用这个图像之前必须明确获得此图像的所有权。这个选项提供的性能最好。这属于独享模式。

  • VK_SHARING_MODE_CONCURRENT: 图像没有明确的所有权可以跨多个队列族交叉使用。这属于并行共享模式也叫并发模式或并行模式。

If the queue families differ, then we'll be using the concurrent mode in this tutorial to avoid having to do the ownership chapters, because these involve some concepts that are better explained at a later time. Concurrent mode requires you to specify in advance between which queue families ownership will be shared using the queueFamilyIndexCount and pQueueFamilyIndices parameters. If the graphics queue family and presentation queue family are the same, which will be the case on most hardware, then we should stick to exclusive mode, because concurrent mode requires you to specify at least two distinct queue families.

如果队列族不同那么我们会在此教程中用并行模式免得还要熟知(后面才讲的)所有权章节的内容因为这些内容涉及到一些概念后续更好解释。并行模式要求您使用queueFamilyIndexCount和pQueueFamilyIndices参数预先指定将在哪些队列族之间共享所有权。如果图形队列族和显示队列族是一样的大多数硬件应该都是这种情况那么我们应该选定独享模式因为并行模式需要你指定至少两个不一样的队列族。

createInfo.preTransform = swapChainSupport.capabilities.currentTransform;

We can specify that a certain transform should be applied to images in the swap chain if it is supported (supportedTransforms in capabilities), like a 90 degree clockwise rotation or horizontal flip. To specify that you do not want any transformation, simply specify the current transformation.

如果当前系统支持空间变换(supportedTransforms 字段在capabilities 对象中)应该对 swap chain 中的图像应用我们能指定的一个确切的空间变换像顺时针旋转90度或者水平翻转。要指定你不需要任何变化就用当前的变化就可以了。

createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;

The compositeAlpha field specifies if the alpha channel should be used for blending with other windows in the window system. You'll almost always want to simply ignore the alpha channel, hence VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR.

compositeAlpha字段表示是否alpha通道被用在窗口系统中(当前表面对象)和其他窗口混合。使用 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR 值你就直接忽略了alpha通道。

createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;

The presentMode member speaks for itself. If the clipped member is set to VK_TRUE then that means that we don't care about the color of pixels that are obscured, for example, because another window is in front of them. Unless you really need to be able to read these pixels back and get predictable results, you'll get the best performance by enabling clipping.

presentMode (显示模式) 成员变量不言自明不用多说。举个例子由于一个窗口挡在当前窗口前面如果clipped 成员变量设置为VK_TRUE 那就意味着我们不必关心被遮挡的像素颜色。启用裁剪机制你能获得最佳性能除非你确实需要能回读这些像素数据获取已知的计算结果。

createInfo.oldSwapchain = VK_NULL_HANDLE;

That leaves one last field, oldSwapChain. With Vulkan it's possible that your swap chain becomes invalid or unoptimized while your application is running, for example because the window was resized. In that case the swap chain actually needs to be recreated from scratch and a reference to the old one must be specified in this field. This is a complex topic that we'll learn more about in a future chapter. For now, we'll assume that we'll only ever create one swap chain.

剩下最后一个字段 oldSwapChain。对于 Vulkan 当你的应用程运行的时候可能你的 swap chain 会变成无效的或者未优化的例如由窗口缩放就会导致这些问题。这种情况下 这个 swap chain 实际上需要重头新建指向旧 swap chain 对象的引用必须用这个字段指定。在后面的章节中我们才会学习这个复杂的话题。就现在而言我会假定从来只会创建一个 swap chain。

Now add a class member to store the VkSwapchainKHR object:

现在增加一个类成员变量来存储VkSwapchainKHR 类型的对象

VkSwapchainKHR swapChain;

Creating the swap chain is now as simple as calling vkCreateSwapchainKHR:

简单的调用vkCreateSwapchainKHR 函数就能创建 swap chain 对象

if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
    throw std::runtime_error("failed to create swap chain!");
}

The parameters are the logical device, swap chain creation info, optional custom allocators and a pointer to the variable to store the handle in. No surprises there. It should be cleaned up using vkDestroySwapchainKHR before the device:

这些参数分别是逻辑设备swap chain 创建信息分配器选项存放新建的 vkCreateSwapchainKHR 对象句柄的指针变量(swapChain)。没啥特别的。(如果程序运行结束需要做销毁操作)应在销毁逻辑设备之前销毁swapChain实例

void cleanup() {
    vkDestroySwapchainKHR(device, swapChain, nullptr);
    ...
}

Now run the application to ensure that the swap chain is created successfully! If at this point you get an access violation error in vkCreateSwapchainKHR or see a message like Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll, then see the FAQ entry about the Steam overlay layer.

现在运行此应用程序确保上面所说的 swap chain 被创建成功如果此时你在 vkCreateSwapchainKHR 中遇到访问冲突错误或者看到像 Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll 这样的信息则(需要)查看有关 Steam 覆盖层的常见问题条目。

Try removing the createInfo.imageExtent = extent; line with validation layers enabled. You'll see that one of the validation layers immediately catches the mistake and a helpful message is printed:

试着删除createInfo.imageExtent = extent;这行启用了验证层的代码。你将会看到一个验证层立即捕捉到相关错误接着打印出相关帮助信息(如下图所示)

Retrieving the swap chain images

获取swap chain 中的图像

The swap chain has been created now, so all that remains is retrieving the handles of the VkImages in it. We'll reference these during rendering operations in later chapters. Add a class member to store the handles:

现在 swap chain 对象创建好了所以剩下的就是访问这个swap chain 对象中的VKImage类型的对象了。我们将在后续章节的渲染操作过程中引用这些图像对象。添加一个类成员变量来存放这些句柄

std::vector<VkImage> swapChainImages;

The images were created by the implementation for the swap chain and they will be automatically cleaned up once the swap chain has been destroyed, therefore we don't need to add any cleanup code.

Swap chain 实现过程创建相关图像一旦 swap chain 对象被销毁这些图像也会自动被清理因此我们不需要添加任何相关清理代码。

I'm adding the code to retrieve the handles to the end of the createSwapChain function, right after the vkCreateSwapchainKHR call. Retrieving them is very similar to the other times where we retrieved an array of objects from Vulkan. Remember that we only specified a minimum number of images in the swap chain, so the implementation is allowed to create a swap chain with more. That's why we'll first query the final number of images with vkGetSwapchainImagesKHR, then resize the container and finally call it again to retrieve the handles.

正确调用 vkCreateSwapchainKHR 函数之后我在 createSwapChain 函数结尾处添加代码来访问(VKImage图像)句柄。访问他们和其他多次我们访问一组Vulkan对象(的情况)非常类似。请记住我们在 swap chain 中仅仅指定最少的图像数量因此创建 swap chain 的具体实现过程允许创建更多。这也是为什么我们会用vkGetSwapchainImagesKHR 函数首先查询最终的图像数量调整好这个容器(swapChainImages)的大小后最后调用vkGetSwapchainImagesKHR 函数获取(实际的)图像句柄(即引用)。

vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

One last thing, store the format and extent we've chosen for the swap chain images in member variables. We'll need them in future chapters.

最后一件事把我们已经选中用于 swap chain 图像的格式和范围存储在成员变量里面。我们在后面章节中需要用到他们。

VkSwapchainKHR swapChain;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;

We now have a set of images that can be drawn onto and can be presented to the window. The next chapter will begin to cover how we can set up the images as render targets and then we start looking into the actual graphics pipeline and drawing commands!

我们现在有了一批能被用于绘制和显示到窗口的图像。下个章节我们开始介绍如何将图像设置为渲染目标并且我们开始研究实际的图形管线和绘制命令。

源码

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <limits>
#include <optional>
#include <set>

const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};

#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
    auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
    if (func != nullptr) {
        return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}

void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;
    std::optional<uint32_t> presentFamily;

    bool isComplete() {
        return graphicsFamily.has_value() && presentFamily.has_value();
    }
};

struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};

class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;

    VkInstance instance;
    VkDebugUtilsMessengerEXT debugMessenger;
    VkSurfaceKHR surface;

    VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
    VkDevice device;

    VkQueue graphicsQueue;
    VkQueue presentQueue;

    VkSwapchainKHR swapChain;
    std::vector<VkImage> swapChainImages;
    VkFormat swapChainImageFormat;
    VkExtent2D swapChainExtent;

    void initWindow() {
        glfwInit();

        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }

    void initVulkan() {
        createInstance();
        setupDebugMessenger();
        createSurface();
        pickPhysicalDevice();
        createLogicalDevice();
        createSwapChain();
    }

    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    void cleanup() {
        vkDestroySwapchainKHR(device, swapChain, nullptr);
        vkDestroyDevice(device, nullptr);

        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }

        vkDestroySurfaceKHR(instance, surface, nullptr);
        vkDestroyInstance(instance, nullptr);

        glfwDestroyWindow(window);

        glfwTerminate();
    }

    void createInstance() {
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw std::runtime_error("validation layers requested, but not available!");
        }

        VkApplicationInfo appInfo{};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "Hello Triangle";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "No Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_API_VERSION_1_0;

        VkInstanceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;

        auto extensions = getRequiredExtensions();
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        createInfo.ppEnabledExtensionNames = extensions.data();

        VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
        if (enableValidationLayers) {
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();

            populateDebugMessengerCreateInfo(debugCreateInfo);
            createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
        } else {
            createInfo.enabledLayerCount = 0;

            createInfo.pNext = nullptr;
        }

        if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }

    void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
        createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
        createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
        createInfo.pfnUserCallback = debugCallback;
    }

    void setupDebugMessenger() {
        if (!enableValidationLayers) return;

        VkDebugUtilsMessengerCreateInfoEXT createInfo;
        populateDebugMessengerCreateInfo(createInfo);

        if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
            throw std::runtime_error("failed to set up debug messenger!");
        }
    }

    void createSurface() {
        if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
            throw std::runtime_error("failed to create window surface!");
        }
    }

    void pickPhysicalDevice() {
        uint32_t deviceCount = 0;
        vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

        if (deviceCount == 0) {
            throw std::runtime_error("failed to find GPUs with Vulkan support!");
        }

        std::vector<VkPhysicalDevice> devices(deviceCount);
        vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

        for (const auto& device : devices) {
            if (isDeviceSuitable(device)) {
                physicalDevice = device;
                break;
            }
        }

        if (physicalDevice == VK_NULL_HANDLE) {
            throw std::runtime_error("failed to find a suitable GPU!");
        }
    }

    void createLogicalDevice() {
        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

        std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
        std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};

        float queuePriority = 1.0f;
        for (uint32_t queueFamily : uniqueQueueFamilies) {
            VkDeviceQueueCreateInfo queueCreateInfo{};
            queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
            queueCreateInfo.queueFamilyIndex = queueFamily;
            queueCreateInfo.queueCount = 1;
            queueCreateInfo.pQueuePriorities = &queuePriority;
            queueCreateInfos.push_back(queueCreateInfo);
        }

        VkPhysicalDeviceFeatures deviceFeatures{};

        VkDeviceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

        createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
        createInfo.pQueueCreateInfos = queueCreateInfos.data();

        createInfo.pEnabledFeatures = &deviceFeatures;

        createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
        createInfo.ppEnabledExtensionNames = deviceExtensions.data();

        if (enableValidationLayers) {
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();
        } else {
            createInfo.enabledLayerCount = 0;
        }

        if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
            throw std::runtime_error("failed to create logical device!");
        }

        vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
        vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
    }

    void createSwapChain() {
        SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

        VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
        VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
        VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);

        uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
        if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
            imageCount = swapChainSupport.capabilities.maxImageCount;
        }

        VkSwapchainCreateInfoKHR createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        createInfo.surface = surface;

        createInfo.minImageCount = imageCount;
        createInfo.imageFormat = surfaceFormat.format;
        createInfo.imageColorSpace = surfaceFormat.colorSpace;
        createInfo.imageExtent = extent;
        createInfo.imageArrayLayers = 1;
        createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
        uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

        if (indices.graphicsFamily != indices.presentFamily) {
            createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
            createInfo.queueFamilyIndexCount = 2;
            createInfo.pQueueFamilyIndices = queueFamilyIndices;
        } else {
            createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        }

        createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
        createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        createInfo.presentMode = presentMode;
        createInfo.clipped = VK_TRUE;

        createInfo.oldSwapchain = VK_NULL_HANDLE;

        if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
            throw std::runtime_error("failed to create swap chain!");
        }

        vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
        swapChainImages.resize(imageCount);
        vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

        swapChainImageFormat = surfaceFormat.format;
        swapChainExtent = extent;
    }

    VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
        for (const auto& availableFormat : availableFormats) {
            if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
                return availableFormat;
            }
        }

        return availableFormats[0];
    }

    VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
        for (const auto& availablePresentMode : availablePresentModes) {
            if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
                return availablePresentMode;
            }
        }

        return VK_PRESENT_MODE_FIFO_KHR;
    }

    VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
        if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
            return capabilities.currentExtent;
        } else {
            int width, height;
            glfwGetFramebufferSize(window, &width, &height);

            VkExtent2D actualExtent = {
                static_cast<uint32_t>(width),
                static_cast<uint32_t>(height)
            };

            actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
            actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);

            return actualExtent;
        }
    }

    SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
        SwapChainSupportDetails details;

        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);

        uint32_t formatCount;
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

        if (formatCount != 0) {
            details.formats.resize(formatCount);
            vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
        }

        uint32_t presentModeCount;
        vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

        if (presentModeCount != 0) {
            details.presentModes.resize(presentModeCount);
            vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
        }

        return details;
    }

    bool isDeviceSuitable(VkPhysicalDevice device) {
        QueueFamilyIndices indices = findQueueFamilies(device);

        bool extensionsSupported = checkDeviceExtensionSupport(device);

        bool swapChainAdequate = false;
        if (extensionsSupported) {
            SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
            swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
        }

        return indices.isComplete() && extensionsSupported && swapChainAdequate;
    }

    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();
    }

    QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
        QueueFamilyIndices indices;

        uint32_t queueFamilyCount = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

        std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

        int i = 0;
        for (const auto& queueFamily : queueFamilies) {
            if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                indices.graphicsFamily = i;
            }

            VkBool32 presentSupport = false;
            vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);

            if (presentSupport) {
                indices.presentFamily = i;
            }

            if (indices.isComplete()) {
                break;
            }

            i++;
        }

        return indices;
    }

    std::vector<const char*> getRequiredExtensions() {
        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

        std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

        if (enableValidationLayers) {
            extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        }

        return extensions;
    }

    bool checkValidationLayerSupport() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

        std::vector<VkLayerProperties> availableLayers(layerCount);
        vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

        for (const char* layerName : validationLayers) {
            bool layerFound = false;

            for (const auto& layerProperties : availableLayers) {
                if (strcmp(layerName, layerProperties.layerName) == 0) {
                    layerFound = true;
                    break;
                }
            }

            if (!layerFound) {
                return false;
            }
        }

        return true;
    }

    static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
        std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;

        return VK_FALSE;
    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6