[译]Vulkan教程(10)交换链


[译]Vulkan教程(10)交换链

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没有“默认帧缓存”的概念,因此它需要一个基础设施,其拥有buffer,在我们将其内存显示带屏幕上之前,渲染之。这个基础设施被称为交换链,它必须在Vulkan中被显式地创建。交换链本质上是一系列image,其等待被呈现到屏幕上。我们的app将请求这样的一个image,在其上绘画,然后将其返回队列。队列如何工作,呈现image的条件,依赖于交换链的配置,但是交换链的一般目的是同步image的呈现与屏幕的刷新。

Checking for swap chain support 检查对交换链的支持

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.

由于种种原因,不是所有的图形卡都能直接将image呈现到屏幕上,例如如果它们是被设计用于服务器的,没有任何显示输出功能。其次,由于image呈现严重关联到窗口系统,surface关联到窗口,实际上这也不是Vulkan的核心。你不得不在查询其支持性后,启用设备VK_KHR_swapchain 扩展。

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_NAMEthat is defined as VK_KHR_swapchain. The advantage of using this macro is that the compiler will catch misspellings.

为此我们首先扩展函数,检查是否支持这个扩展。我们之前见过了如何得到一个VkPhysicalDevice支持的扩展列表,所以现在的工作应当十分直观。注意,Vulkan头文件提供一个好用的宏VK_KHR_SWAPCHAIN_EXTENSION_NAME,它被定义为VK_KHR_swapchain。用这个宏的有点是,编译器会捕捉到拼写错误。

First declare a list of required device extensions, similar to the list of validation layers to enable.

首先声明需要的设备扩展的列表,这与验证层的列表相似。

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 availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
 
    std::setstring> 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函数中那样。性能差异是不相干的。现在运行代码,验证你的图形卡确实支持创建交换链。要注意到,一个presentation队列的功能(我们在之前的章节检查过的)暗示了交换链扩展必须被支持。但是,显示地检查一下还是好的,且这个扩展也必须被显式地启用。

Enabling device extensions 启用设备扩展

Using a swapchain requires enabling the VK_KHR_swapchain extension first. Enabling the extension just requires a small change to the logical device creation structure:

使用交换链,首先要启用VK_KHR_swapchain 扩展。启用这个扩展,值要求对逻辑设备的创建结构体做一点修改:

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

Make sure to replace the existing line createInfo.enabledExtensionCount = 0; when you do so.

确保替换这一行createInfo.enabledExtensionCount = 0;

Querying details of swap chain support 查询交换链的细节

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.

仅仅检查交换链是否可用,不够高效,因为它可能不与我们的窗口surface兼容。创建交换链也设计很多配置,比instance和设备创建的配置还多,所以我们需要查询一些细节,然后再进行下一步。

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

我们需要至少检查3种属性:

  • 基础surface功能(交换链包含的image的最大\小数量,image的宽度和高度的最大\最小值)
  • Surface格式(像素格式,颜色空间)
  • 可用的presentation模式

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相似,我们将用一个结构体传入这些细节信息。上述3种属性按如下方式陈列:

struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector formats;
    std::vector 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.

我们从基础surface功能开始。这些属性很容易查询,且包含在返回的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.

这个函数在判定支持的功能时,考虑了VkPhysicalDevice 和VkSurfaceKHR 窗口surface。所有的支持查询函数都有这2个参数,因为它们是交换链的核心组件。

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:

下一步,要查询支持的surface格式。因为这是结构体的列表,它遵循2个函数调用的熟悉的惯例:

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:

确保数组大小被重新设定,以记录所有可用的格式。最后,查询支持的presentation模式与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.

所有细节都在下面的结构体中,所以我们再扩展isDeviceSuitable 一次,以验证胜任本教程需要的对交换链的支持。如果至少支持一个image格式和一个presentation模式,交换链支持是足够的。

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:

重要的一点是,我们只在验证了扩展可用后,尝试查询交换链支持。函数的最后一行修改为:

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

Choosing the right settings for the 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)

如果swapChainAdequate 条件符合,那么支持就是绝对高效的,但是可能还有很多不同的模式可供优化。我们现在写一些函数,以找到最好的交换链的正确的设置。有3个类型的设置:

  • Surface格式(颜色 深度)
  • Presentation模式(“交换”image到屏幕的条件)
  • 交换扩展(交换链中image的解析度)

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.

对每个设置,如果可以的话,我们将用一个理想值,否则我们就创建一些逻辑来找到次优的。

Surface format surface格式

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& 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_UNORM 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 ,包含一个format 和一个colorSpace 成员。format 成员表面颜色通道和类型。例如,VK_FORMAT_B8G8R8A8_UNORM 的意思是,我们按BRG和alpha通道的顺序保存,每个通道占8位,每个像素一共占32位。colorSpace 成员用VK_COLOR_SPACE_SRGB_NONLINEAR_KHR标志标明SRGB颜色空间是否被支持。注意,这个标志在老版说明书里被称为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. Working directly with SRGB colors is a little bit challenging, so we'll use standard RGB for the color format, of which one of the most common ones is VK_FORMAT_B8G8R8A8_UNORM.

如果可用,我们的颜色空间将使用SRGB,因为它给出更精确的感知颜色。直接用SRGB颜色工作,有点挑战性,所以我们用标准RGB作为颜色格式,其中最常用的一个格式是VK_FORMAT_B8G8R8A8_UNORM

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

我们过一遍列表,看看想要的组合是否可用:

for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && 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& availableFormats) {
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }
 
    return availableFormats[0];
}

Presentation mode presentation模式

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 implement triple buffering, which allows you to avoid tearing with significantly less latency issues than standard vertical sync that uses double buffering.

Presentation模式是交换链配置中最重要的一个,因为它代表了呈现image到屏幕的条件。Vulkan中有4个可能的模式:

  • VK_PRESENT_MODE_IMMEDIATE_KHR :你的app提交的image会被立即传送到屏幕上,这可能导致撕裂。
  • VK_PRESENT_MODE_FIFO_KHR:交换链是一个队列,显示器刷新时,从队列头部拿一个image,程序将渲染好的image放到队列尾部。如果队列满了,程序就必须等待。这与现代游戏中的垂直同步最相似。显示器被刷新的时刻被称为“垂直回归”。
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR:只有当垂直回归结束后,app晚了,队列空了,这一模式才与上一个模式有所区别。它不等待下一次垂直回归,而是当image到达时立即传送。这可能导致可见的撕裂。
  • VK_PRESENT_MODE_MAILBOX_KHR:这是第二个模式的另一个变种。队列满的时候,它不是阻塞app,而是队列中的image直接就被新的替换掉了。这个模式可以被用于实现三缓存,其允许你避免撕裂,且大幅减少延迟问题(与双缓存的垂直同步模式相比)。

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:

只有VK_PRESENT_MODE_FIFO_KHR模式是绝对可用的,所以我们再次不得不写个函数,其查询可用的最佳模式:

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

I personally think that triple buffering is a very nice trade-off. 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. So, let's look through the list to see if it's available:

我个人认为,三缓存是非常好的平衡之术。它允许我们避免撕裂,且仍旧保持了相当低的延迟(通过尽可能快地渲染新的image,直到垂直回归)。所以,让我们过一下列表,看看他是否可用:

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

Unfortunately some drivers currently don't properly support VK_PRESENT_MODE_FIFO_KHR, so we should prefer VK_PRESENT_MODE_IMMEDIATE_KHR if VK_PRESENT_MODE_MAILBOX_KHR is not available:

不幸的是,目前有的driver不支持VK_PRESENT_MODE_FIFO_KHR,所以当VK_PRESENT_MODE_MAILBOX_KHR不可用时,我们倾向于VK_PRESENT_MODE_IMMEDIATE_KHR

VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) {
    VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;
 
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
            bestMode = availablePresentMode;
        }
    }
 
    return bestMode;
}

Swap extent 交换外延

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

只剩下一个主要属性了,我们添加最后一个函数:

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. 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 case we'll pick the resolution that best matches the window within the minImageExtent and maxImageExtent bounds.

交换外延是交换链image的解析度,它几乎总数等于我们要绘制到的窗口的解析度。可能的解析度范围由VkSurfaceCapabilitiesKHR 结构体定义。Vulkan告诉我们,通过在currentExtent 成员中设置宽度和高度来匹配窗口的解析度。但是,有的窗口管理器确实允许我们有所不同,这可以通过将设置为一个特殊值来标示:uint32_t的最大值。此时,我们将拾取最匹配窗口的解析度,其以minImageExtent 和maxImageExtent 为边界。

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits::max()) {
        return capabilities.currentExtent;
    } else {
        VkExtent2D actualExtent = {WIDTH, HEIGHT};
 
        actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
        actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
 
        return actualExtent;
    }
}

The max and min functions are used here to clamp the value of WIDTH and HEIGHT between the allowed minimum and maximum extents that are supported by the implementation. Make sure to include the  header to use them.

max 和min 函数这里用于裁切WIDTH 和HEIGHT 的值,使其置于实现允许的最大值和最小值之间。为了使用它们,确保include头文件。

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.

创建一个函数,它记录这些调用的结果。确保在initVulkan 中逻辑创建创建函数之后调用它。

void initVulkan() {
    createInstance();
    setupDebugCallback();
    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:

除了这些属性,我们也不得不决定在交换链中要有多少个image。实现标明了它需要的最小值:

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:

但是,简单地使用这个最小值,意味着在请求另一个image以渲染前,我们可能有时候不得不等待driver完成内部操作。因此推荐请求至少比最小值多1个的image:

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:

我们应该也确保不超过image的最大值,而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对象的传统,创建交换链对象要求填入一个巨大的结构体。开始时似曾相识:

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:

在标示了交换链要绑定到哪个surface后,要说明交换链image的细节:

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.

imageArrayLayers 标明了每个image包含的层的数量。除非你在开发体视3D应用程序,它总是1imageUsage 位字段标明我们将用交换链中的image在哪种操作上。本教程中我们将直接在上面渲染,这意味着它们用于颜色附件。也可能你先将渲染图片到单独的image,然后实施后处理之类的操作,此时你可能用VK_IMAGE_USAGE_TRANSFER_DST_BIT这样的值,且用一个内存操作来转移渲染到图片到一个交换链image。

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 on 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 transfered 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.

下一步,我们需要标明如何出列交换链中的image,它们将被用于多个队列家族中。如果一个图形队列家族与presentation队列不一样,image就将被用于多个队列家族中。我们将在交换链的image中在图形队列中绘制图片,然后提交到presentation队列。有2种方法来处理多队列读写的image:

  • VK_SHARING_MODE_EXCLUSIVE:一个image在同一时间只能属于一个队列家族,所有权必须被显式地转移后,才能在另一个队列家族中使用。这个选项提供最佳性能。
  • VK_SHARING_MODE_CONCURRENT:。Image可以在多个队列家族使用,无需显式地转移所有权。

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 参数,提前标明,所有权将在哪些队列家族中共享。如果图形队列家族和presentation队列家族相同(大多数硬件上都是这样),那么我们就用exclusive模式,因为并发模式要求你标明至少2个不同的队列家族。

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.

如果某个变换受支持(capabilities中的supportedTransforms ),例如顺时针选择90度或水平翻转,我们可以标明让它应用到交换链中的image上。为标明这个,你不用费别的劲,简单地标明当前当前的变换即可。

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通道是否应当被用于与窗口系统中的其他窗口混合。你将几乎总是想要简单地忽略这个alpha通道,因此选择VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR

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。在你的app运行过程中,在Vulkan中有可能你的交换链变得无效或不够优化,例如梵蒂冈窗口大小改变时。此时,交换链实际上需要被重新创建,一个对旧交换链的引用就必须写入此字段。这是个复杂的话题,我们将在未来的篇章学习更多。现在我们假设我们只创建一次交换链。

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即可:

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:

参数是逻辑设备,交换链创建信息,可选的自定义内存申请函数和一个保存句柄的变量的指针。没有新鲜事。它应该在销毁设备前被vkDestroySwapchainKHR 销毁:

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.

现在运行程序以确保交换链被成功创建了。如果此时你在vkCreateSwapchainKHR 碰到存取微观错误,或看到这样的消息:Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll,那么查阅FAQ记录中关于流覆盖层的问题。

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 image 检索交换链image

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:

现在交换链已经创建了,剩下的就是检索其中VkImage的句柄。我们将在渲染操作中(后续章节)引用这些句柄。添加成员以保存这些句柄:

std::vector 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.

这些image是实现为交换链创建的,一旦交换链被销毁,它们将被自动地清理,因此我们不需添加任何清理代码。

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.

我将检索句柄的代码发那个到createSwapChain 函数的最后,在调用vkCreateSwapchainKHR 之后。检索它们与我们检索Vulkan对象数组时很相似。回忆一下,我们只标明了交换链中image的最小值,所以实现可以创建更多image。这就是为什么我们先用vkGetSwapchainImagesKHR查询image的数量,然后调整数组大小,最后再次调用它来检索句柄。

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.

最后预计算,保存我们为交换链image选择的格式和外延到成员变量中。我们在以后的章节会需要它们。

VkSwapchainKHR swapChain;
std::vector 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!

现在我们有了image,其可被绘画,可被呈现到窗口。下一章将开始讲述我们如何设置image为渲染目标,然后开始深入研究实际的图形管道和绘制命令!

C++ code C++代码

  • Previous上一章

 

  • Next下一章