Vulkan【6】创建一个交换链

创建一个交换链

本节的代码是 05-init_swapchain.cpp

本节描述如何创建交换链,它是最终显示给用户的图像缓冲区列表。这是建立呈现所需的所有缓冲区所需的第一个步骤之一。

这是一个关于交换链的视图,它与系统的其他部分有关。其中一些部分很常见,剩下的部分将在本节中学习。

这里写图片描述

Vulkan和窗口系统

与其他图形API一样,Vulkan将窗口系统方面与核心图形API分离开来。
在Vulkan中,窗口系统的详细信息通过WSI(窗口系统集成)扩展公开。
您可以在使用WSI内容的Vulkan规范文档中找到这些扩展的文档。Vulkan 规范LunarG LunarXchange websiteKhronos Vulkan Registry

WSI扩展包含对各种平台的支持。扩展被激活为一个特定的平台,通过定义:

  • VK_USE_PLATFORM_ANDROID_KHR - Android
  • VK_USE_PLATFORM_MIR_KHR - Mir
  • VK_USE_PLATFORM_WAYLAND_KHR - Wayland
  • VK_USE_PLATFORM_WIN32_KHR - Microsoft Windows
  • VK_USE_PLATFORM_XCB_KHR - X Window System, using the XCB library
  • VK_USE_PLATFORM_XLIB_KHR - X Window System, using the Xlib library

该名称的“KHR”部分表明该符号是在Khronos扩展中定义的。

Surface 抽象

Vulkan使用VkSurfaceKHR对象来抽象本机平台的Surface 或窗口。这个符号定义为VK KHR表面扩展的一部分。WSI扩展中的各种函数被用来创建、操作和销毁这些Surface 对象。

回顾实例和设备扩展

由于您在本教程前几节中推迟了使用扩展,现在是重新回顾它们的时候了,这样您就可以激活WSI扩展,您需要使用它来与窗口系统进行交互。

实例扩展

为了使用WSI扩展,您需要激活通用的surface 扩展。在示例中找到 init_instance_extension_names()函数中的代码,该函数将 init_instance_extension_names()添加到加载的实例扩展列表中。

info.instance_extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME);

还要注意的是,这个函数决定了特定于平台的扩展,这取决于所构建代码的平台。
例如,如果为Windows构建的话,该函数会将VK_KHR_WIN32_SURFACE_EXTENSION_NAME
添加到加载的实例扩展列表中。

info.instance_extension_names.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);

当实例被创建时,在init_instance()函数中加载这些扩展。

设备扩展

交换链是一个GPU渲染图像缓冲区列表,并将其呈现给显示硬件,以便扫描输出到显示器。
由于GPU硬件是为这些图像编写的,所以需要一个设备级的扩展来处理交换链。
因此,样例代码将VK_KHR_SWAPCHAIN_EXTENSION_NAME的设备扩展添加到设备扩展列表中,以便加载init_device_extension_names()

info.device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);

这个扩展在本节稍后将使用,用以创建交换链。

简要概括:

  • 本示例使用init_instance_extension_names()函数来加载一般的surface扩展和特定于平台的surface扩展作为实例扩展。
  • 该示例使用init_device_extension_names()函数来加载交换链扩展作为设备扩展。

通过访问LunarG LunarXchange website.,您可以了解更多关于实例和设备扩展的信息

队列家族 和“呈现”

“呈现”操作包括将一个交换链的图像放到物理显示器上,这样就可以查看它了。当应用程序想要将图像呈现给显示器时,它会使用vkQueuePresentKHR()函数将当前请求放到GPU的队列中。因此,这个函数引用的队列必须能够支持呈现请求,或者图形和呈现请求。示例如下:

// Iterate over each queue to learn whether it supports presenting:
VkBool32 *pSupportsPresent =
    (VkBool32 *)malloc(info.queue_family_count * sizeof(VkBool32));
for (uint32_t i = 0; i < info.queue_family_count; i++) {
    vkGetPhysicalDeviceSurfaceSupportKHR(info.gpus[0], i, info.surface,
                                         &pSupportsPresent[i]);
}

// Search for a graphics and a present queue in the array of queue
// families, try to find one that supports both
info.graphics_queue_family_index = UINT32_MAX;
info.present_queue_family_index = UINT32_MAX;
for (uint32_t i = 0; i < info.queue_family_count; ++i) {
    if ((info.queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
        if (info.graphics_queue_family_index == UINT32_MAX)
            info.graphics_queue_family_index = i;

        if (pSupportsPresent[i] == VK_TRUE) {
            info.graphics_queue_family_index = i;
            info.present_queue_family_index = i;
            break;
        }
    }
}

if (info.present_queue_family_index == UINT32_MAX) {
    // If didn't find a queue that supports both graphics and present, then
    // find a separate present queue.
    for (size_t i = 0; i < info.queue_family_count; ++i)
        if (pSupportsPresent[i] == VK_TRUE) {
            info.present_queue_family_index = i;
            break;
        }
}
free(pSupportsPresent);

这段代码重新使用了之前获得的 info.queue_family_count,因为vkGetPhysicalDeviceSurfaceSupportKHR()为每个队列家族返回一个flag。
然后搜索一个同时支持呈现和图形的队列家族。如果没有队列家族能同时满足呈现和图形,程序会先记住支持图形的队列家族,然后再去搜索支持呈现的队列家族。
graphics_queue_family_indexpresent_queue_family_index都被这段代码设置时,示例的后面必须使用来自graphics_queue_family_index的队列用于图形指令,以及来自present_queue_family_index的队列用于呈现。

是的,这有点冗余,之前在“设备”章节中只进行了搜索一个支持图形的队列家族,这只是为了便于说明。一个真正的应用程序可以以不同的顺序执行这些步骤,以避免重复。

如果没有这样的队列家族,示例程序会直接退出。

交换链创建信息

本节其余部分的大部分工作都是为了填充这个create info结构,用于创建交换链:

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType                  sType;
    const void*                      pNext;
    VkSwapchainCreateFlagsKHR        flags;
    VkSurfaceKHR                     surface;
    uint32_t                         minImageCount;
    VkFormat                         imageFormat;
    VkColorSpaceKHR                  imageColorSpace;
    VkExtent2D                       imageExtent;
    uint32_t                         imageArrayLayers;
    VkImageUsageFlags                imageUsage;
    VkSharingMode                    imageSharingMode;
    uint32_t                         queueFamilyIndexCount;
    const uint32_t*                  pQueueFamilyIndices;
    VkSurfaceTransformFlagBitsKHR    preTransform;
    VkCompositeAlphaFlagBitsKHR      compositeAlpha;
    VkPresentModeKHR                 presentMode;
    VkBool32                         clipped;
    VkSwapchainKHR                   oldSwapchain;
} VkSwapchainCreateInfoKHR;

创建一个表面(Surface)

随着WSI扩展在实例和设备中加载,您现在可以创建一个VkSurface,这样您就可以继续进行构建交换链了。您再次需要使用特定于平台的代码,出现在05-init_swapchain.cpp的顶部:

#ifdef _WIN32
    VkWin32SurfaceCreateInfoKHR createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
    createInfo.pNext = NULL;
    createInfo.hinstance = info.connection;
    createInfo.hwnd = info.window;
    res = vkCreateWin32SurfaceKHR(info.inst, &createInfo, NULL, &info.surface);
#endif

info.connectioninfo.window 的值是在init_connection()init_window()函数中包含的更特定于平台的代码中设置的,您也可以在示例代码中找到它们。

请注意,init_connection()init_window()函数也负责连接到显示器的特定于平台的操作,并创造实际的窗口。您刚刚用vkCreateWin32SurfaceKHR() 函数创建的VkSurfaceKHR是由Vulkan用于平台窗口对象的句柄来表示的。

然后将生成的表面添加到交换链的create info结构:

VkSwapchainCreateInfoKHR swapchain_ci = {};
swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_ci.pNext = NULL;
swapchain_ci.surface = info.surface;
设备表面格式(Device Surface Formats)

您还需要在创建交换链时指定表面的格式。在这种情况下,“格式”指的是VkFormat枚举所描述的像素格式,
VK_FORMAT_B8G8R8A8_UNORM是设备表面格式的一个常见值。

示例中的下一段代码将获得VkSurfaceFormatKHR结构的列表,其中包含由display支持的VkFormat格式,以及其他信息。因为这些示例并不关心
显示图像和表面使用的格式,所以示例只是选择了第一个可用的格式,如果没有指定的话,就会返回到任意的、但常见的格式。

看一看示例中的代码,它最终在create info结构中设置了格式:

swapchain_ci.imageFormat = info.format;

表面能力(Surface Capabilities)

为了继续填充 create info 结构, 示例调用 vkGetPhysicalDeviceSurfaceCapabilitiesKHR()vkGetPhysicalDeviceSurfacePresentModesKHR() 来获得必要的信息。 然后,它可以填充以下字段:

uint32_t desiredNumberOfSwapChainImages = surfCapabilities.minImageCount;

swapchain_ci.minImageCount = desiredNumberOfSwapChainImages;
swapchain_ci.imageExtent.width = swapChainExtent.width;
swapchain_ci.imageExtent.height = swapChainExtent.height;
swapchain_ci.preTransform = preTransform;
swapchain_ci.presentMode = swapchainPresentMode;

您应该将minImageCount成员设置为代表应用程序使用的缓冲策略的值,例如双缓冲或三重缓冲。这个示例查询一个交换链中可以使用的最少数量的图像,这些图像使用的是vkGetPhysicalDeviceSurfaceCapabilitiesKHR()函数,将结果存储在surfCapabilities中。要求这个最小数量的图像可以确保我们可以获得一个可呈现的图像,只要我们在试图获得另一个图像之前就展示它。这表示一个双缓冲配置,因为您将让一个图像进行渲染,而另一个图像正在呈现。如果你想要三次缓冲,那么你会要求一个更多的图像,然后在你展示它们之前,你可以获得两个缓冲。

用于图形和当前的不同队列家族

您在队列家族中确定了图形和呈现队列。如果它们是不同的,您需要做一些额外的工作,以允许在队列家族之间共享图像。

swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_ci.queueFamilyIndexCount = 0;
swapchain_ci.pQueueFamilyIndices = NULL;
uint32_t queueFamilyIndices[2] = {
    (uint32_t)info.graphics_queue_family_index,
    (uint32_t)info.present_queue_family_index};
if (info.graphics_queue_family_index != info.present_queue_family_index) {
    // If the graphics and present queues are from different queue families,
    // we either have to explicitly transfer ownership of images between the
    // queues, or we have to create the swapchain with imageSharingMode
    // as VK_SHARING_MODE_CONCURRENT
    swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    swapchain_ci.queueFamilyIndexCount = 2;
    swapchain_ci.pQueueFamilyIndices = queueFamilyIndices;
}

上面的字段提供了创建交换链所需的更多基本信息。您可以在示例中检查其余的代码,以了解如何获得这些信息,以及如何填写create info结构的其余部分。

创建交换链

当交换链的 create info 结构填充完成,你现在就可以创建交换链了:

res = vkCreateSwapchainKHR(info.device, &swapchain_ci, NULL, &info.swap_chain);

这个调用创建了组成交换链的一组图像。在某种程度上,你需要对单个图像进行处理,这样你就可以告诉GPU哪些图像用于渲染。vkCreateSwapchainKHR()函数创建图像本身,因此保持了对句柄的跟踪。
样例通过使用我们熟悉的查询模式来获得图像句柄的个数,通过调用

vkGetSwapchainImagesKHR(info.device, info.swap_chain,
                        &info.swapchainImageCount, NULL);

然后再次调用这个函数来获取一张图像句柄的列表,并将它们存储在info.buffer中。现在你有了一个目标图像句柄的列表。

创建图像视图(Create Image Views)

您需要告诉Vulkan,通过创建图像视图,您打算如何使用这些交换链图像。“视图”本质上是附加到一个资源的附加信息,该信息了如何使用该资源。

属于图像的内存区域可以以多种方式排列,这取决于图像的预期用途。例如,图像可以是1D、2D或3D。或者可以有一系列的图像,等等。描述图像的格式(例如,VK_FORMAT_R8G8B8A8_UNORM)、组件的顺序和层信息也是很有用的。所有这些信息都是包含在VkImageView中的图像元数据。

创建图像视图非常简单。在05-init_swapchain.cpp中找到VkImageViewCreateInfo 结构。并看到它被填充了你对2D framebuffer所期望的值。注意图像句柄本身存储在图像视图中。

然后,您可以通过在info 数据结构中存储图像视图句柄来完成交换链的创建,以便以后使用。

© Copyright 2016 LunarG, Inc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值