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

栏目: 后端 · 发布时间: 5年前

内容简介:[译]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 theVulkan没有“默认帧缓存”的概念,因此它

[译]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_NAME that 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<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 函数中那样。性能差异是不相干的。现在运行代码,验证你的图形卡确实支持创建交换链。要注意到,一个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<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;

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

我们从基础surface功能开始。这些属性很容易查询,且包含在返回的 VkSurfaceCapabilitiesKHR 结构体。

(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<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_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<VkSurfaceFormatKHR>& 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<VkPresentModeKHR>& 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<VkPresentModeKHR>& 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<VkPresentModeKHR>& 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 的最大值。此时,我们将拾取最匹配窗口的解析度,其以 minImageExtentmaxImageExtent 为边界。

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::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  <algorithm> header to use them.

maxmin 函数这里用于裁切 WIDTHHEIGHT 的值,使其置于实现允许的最大值和最小值之间。为了使用它们,确保include <algorithm> 头文件。

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

如果队列家族不同,那么本教程将用并发模式,以避免再开个所有权章节,因为这些涉及到一些概念,晚一点再解释它们更好。并发模式要求你用 queueFamilyIndexCountpQueueFamilyIndices 参数,提前标明,所有权将在哪些队列家族中共享。如果图形队列家族和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  VkImage s in it. We'll reference these during rendering operations in later chapters. Add a class member to store the handles:

现在交换链已经创建了,剩下的就是检索其中 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.

这些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<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!

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

C++ code C++代码


以上所述就是小编给大家介绍的《[译]Vulkan教程(10)交换链》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

游戏开发的数学和物理

游戏开发的数学和物理

[ 日] 加藤洁 / 徐 谦 / 人民邮电出版社 / 59.00元

本书严格选取了游戏开发中最常用的数学和物理学知识,通过游戏开发实例,配上丰富的插图,以从易到难的顺序进行讲解。第1章到第5章分别讲解了物体的运动、卷动、碰撞检测、光线的制作、画面切换的细分处理。这五章将2D游戏必需的知识一网打尽,同时还严格挑选了少量3D游戏编程的基础内容以供参考。第6章系统梳理了游戏开发的数学和物理学理论,帮助读者更好地理解前五章的内容。 本书适合网络和手机游戏开发者阅读。一起来看看 《游戏开发的数学和物理》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具