Vulkan教程翻译之六 创建 Swapchain

本文详细介绍了在 Vulkan 图形 API 中创建 Swapchain 的过程,包括初始化、Surface 创建、Surface 能力查询、图像格式选择及 Swapchain 实例化等关键步骤。Swapchain 是连接 GPU 渲染和屏幕显示的重要桥梁。

原文链接:https://vulkan.lunarg.com/doc/sdk/1.2.131.2/windows/tutorial/html/05-init_swapchain.html

创建 Swapchain

这一章节的代码文件是 05-init_swapchain.cpp

本章节描述了如何创建 swapchain,它是最终显示给用户的图像缓冲区列表。这是设置渲染需要的所有缓冲区的第一步。

这是一张 Vulkan swapchain 的视图,它关联着系统的其他各个部分。其中一些部分我们很熟悉,剩下的你将会在这一节里进行学习。

Vulkan 和 窗口系统

像其他的图形API一样,Vulkan 保持窗口系统和核心图形API分离。在Vulkan里,窗口系统通过 WSI(Window System Integration) extension 部分暴露出来。你可以在包含WSI内容的 Vulkan 规范文档里找到这些 extension 的文档。在 Vulkan SDK download website 和 Khronos Vulkan Registry 中可以找到包含这些内容的 Vulkan 规范。

WSI extension 支持各种不同平台。可以通过以下定义激活特定平台的 extension :

  • VK_USE_PLATFORM_ANDROID_KHR - Android
  • 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 extension 里定义的。

Surface 抽象

Vulkan 使用 VkSurfaceKHR 对象来抽象出原生平台的 surface 或 window。该符号定义为 VK_KHR_surface 扩展的一部分。WSI extension 的各种函数用来创建,操作和销毁这些 surface 对象。

回顾 Instance 和 Device Extensions

因为你在本教程前面的章节中跳过了使用 extension,现在是回顾它们的时候了,好让你可以激活接入窗口系统所要用的WSI extension。

Instance Extensions

为了使用 WSI extension,你需要激活通用的 surface 扩展。在该示例中 init_instance_extension_name() 函数里,找到把 VK_KHR_SURFACE_EXTENSION_NAME 插入要加载的 instance extension 列表的代码。

info.instance_extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME);

也要注意到,相同的函数在特定平台的 extension 里,依赖于代码要编译的平台。例如,如果为 Windows 编译,该函数就要把 VK_KHR_WIN32_SURFACE_EXTENSION_NAME 插入到要加载的 instance extension 列表里。

info.instance_extension_names.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);

当创建 instance 的时候,在 init_instance() 函数里加载这些 extension。

Device Extensionss

swapchain 是一个 GPU 用来绘制的图像缓存区的列表,并且提供给显示硬件来扫描到显示屏上。因为是GPU硬件在写这些 image,所以设备层面的 extension 需要和 swapchain 协同工作。因此,示例代码把 VK_KHR_SWAPCHAIN_EXTENSION_NAME 添加到 init_device_extension_names() 加载的 device extension 列表里。

info.device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);

这个 extension 稍后在本节中用来创建 swapchain。

回顾一下:

  • 本示例调用 init_instance_extension_names() 函数来加载常用的和特定平台的 surface extension 作为 instance extension。
  • 本示例调用 init_device_extension_names() 函数来加载 swapchain extension 作为 device extension。

通过查看在 Vulkan SDK download site 里的文档,你可以学习更多关于 instance extension 和 device extension。

Queue Family 和 Present

"present" 操作包括把其中一个 swapchain image 传给物理显示器以使它可以被看见。当应用想要显示一张 image 到显示器上,调用 vkQueuePresentKHR() 函数把一个 present 请求放到其中一个GPU queue里。因此,该函数引用的 queue 必须能支持 present 请求,或者是 graphics 和 present 请求。示例为此做了如下的检查:

// 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() 为每一个 queue family 返回一个标记。然后搜索同时支持 present 和 graphics 的 queue family。如果没有 queue family 能同时支持两个,程序就会先记录一个支持 graphics 的 queue family,然后再搜索一个支持 present 的。因为这段代码里同时设置了 graphics_queue_family_index 和 present_queue_family_index,后续示例必须使用 graphics_queue_family_index 里的队列放 graphics 命令,用 present_queue_family_index 里的队列放 present 命令。

是的,前面在 init_device 里已经执行了搜索仅支持 graphics 的 queue family,这里有点多余,这么做仅仅是出于说明的目的。真正的应用也许用不同的顺序做这些步骤来避免重复。

如果找不到这些 queue family,应用就会退出。

Swapchain 创建信息

本节中其余大部分工作都是为了填充这个创建信息的结构体,用来创建 swapchain:

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

有了在 instance 和 device 里加载的 WSI extension,你就可以创建一个 VkSurface 来使你可以继续创建 swapchain。你需要再次使用特定平台的代码,在 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.connection 和 info.window 值是被包含在 init_connection() 和 inti_window()里的更多的特定平台代码设置的,这些你也可以在示例代码里找到。

请注意,init_connection() 和 inti_window() 函数还负责特定平台操作,包括连接到显示层和创建实际窗口。你刚用 vkCreateWin32SurfaceKHR() 函数创建的 VkSurfaceKHR surface 用句柄表示,该句柄可通过 Vulkan 用于平台窗口对象。

然后你把返回的 surface 加到 swapchain 创建信息结构体里:

VkSwapchainCreateInfoKHR swapchain_ci = {};
swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_ci.pNext = NULL;
swapchain_ci.surface = info.surface;

设备 Surface Format

在创建 swapchain 的时候,你还需要指定 format。在当前上下文中,“format”指如 VkFormat 枚举所描述的像素格式,在这里 VK_FORMAT_B8G8R8A8_UNORM 是设备 surface format 的一个常用例子。

示例代码中的下一段获取一个 VkSurfaceFormatKHR 结构体列表,它包含了显示器支持的 VkFormat 格式和其他信息。因为示例不用关心显示屏 image 和 surface 使用什么 format,示例就选取了第一个可用的 format,如果未指定 format,会回退到一个通用任意的。

查看示例中的代码,可以看到它最后在创建信息结构体里设置了 format:

swapchain_ci.imageFormat = info.format;

Surface Capabilities

为了继续填充 swapchain 创建信息的结构体,示例里调用了 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() 函数请求可以用在 swapchain 里的最小 image 数量,结果存在 surfCapabilities 里。请求 image 的最小数量值,保证了我们在尝试申请另一个 image 之前,可以只获取一个可显示的 image。这表示的就是双缓冲配置,因为你将能够申请一个image来渲染,同时另一个image正在显示。如果你想要三重缓冲,那么你就得再申请一个image,然后在你显示之前,能够申请两个缓冲区来渲染。

Graphics 和 Present 的不同 Queue Family

你之前确定了graphics 和 present 队列的 queue family。如果它们是不同的,你需要做些额外的工作来允许 image 在 queue family 之间能共享。

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

以上字段提供了更多的创建 swapchain 所需基本信息。你可以检查示例中其余代码,来看看这些信息是如何获取的,以及创建信息结构体里其他部分是如何填充的。

创建 Swapchain

填完了 swapchain 创建信息结构体,现在你可以创建 swapchain 了:

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

该调用创建了一个 image 的集合并构建成了 swapchain。在某些时刻,你需要各个 image 的句柄,来告诉GPU用哪一个 image 来渲染。vkCreateSwapchainKHR() 函数本身创建了 image,所以就一直追踪着句柄。示例使用了熟悉模式来获取 image 句柄,先查询存在多少个句柄,通过调用

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

获取了计数,然后再次调用函数获取 image 句柄的列表,并且把它们保存在 info.buffers[]。现在你拥有了一个目标 image 的句柄列表。

创建 Image View

你需要通过创建 image view 告诉 Vulkan 你想如何使用这些 swapchain。"view" 是附加给资源的必须的额外信息,描述这些资源如何使用。

属于一个 image 的内存区域可以被安排成多种方式,这取决于该 image 的用途。例如,一个 image 可以是1D,2D或3D。或者也可以是一个 image 数组等等。描述 image 格式(例如 VK_FORMAT_R8G8B8A8_UNORM),组件的顺序 和 layer 信息也很有用。所有这些信息是包含在 VkImageView 里的 image 元数据。

创建一个 image view 很简单。找到 05-init_swapchain.cpp 文件里的 VkImageViewCreateInfo 结构体,看一下它是用 你所期望的2D framebuffer 值来填充的。注意,image 句柄本身是存在 image view 里的。

然后,你可以存储 image view 句柄到 info 数据结构体以备之后使用,以此结束 swapchain 的创建。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值