《Vulkan Tutorial》 笔记 09: Swap Chain
本部分结果可参考 09_SwapChain
在 Vulkan 中必须显式的创建 Swap Chain。SwapChain 是与 Surface 绑定的数据结构,其包含了多个 Image,应用渲染时会将渲染的结果放置到这些 Image 中,当调用 Present 时,SwapChain 会将这些 Image 通过其与 Surface 绑定,传递给 Surface,Surface 再将这些 Image 显示到平台的窗口或屏幕上。
关于 Swap Chain 中的 queue 如何工作,以及何时将 queue 中的 Image present 到屏幕上,都可以在创建 Swap Chain 时配置。
创建类 SwapChainMgr
来管理 Swap Chain 的创建,销毁和关键的数据,其定义如下,在本节的后续部分将逐步实现这些函数:
``cpp
class SwapChainMgr
{
public:
static SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
static void createSwapChain();
static void destroySwapChain();
static VkSwapchainKHR swapChain;
static std::vector
static VkFormat imageFormat;
static VkExtent2D imageExtent;
private:
static VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector
static VkPresentModeKHR chooseSwapPresentMode(const std::vector
static VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
};
1 |
|
其中 deviceExtensions
定义如下,其中的 ``VK_KHR_SWAPCHAIN_EXTENSION_NAME为 Vulkan 头文件中用来表示
VK_KHR_swapchain` 的宏:
1 | const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; |
在 PhysicalDeviceMgr::isDeviceSuitable
函数中需要添加对 checkDeviceExtensionsSupport
的调用:
1 | bool PhysicalDevicesMgr::isDeviceSuitable(VkPhysicalDevice device) |
#启用 Device Extension
一旦 Physical Device 支持 SwapChain 拓展,我们就需要在创建 Logical Device 时需要指明 Extensions 的数目和类型:
1 | void LogicDevicesMgr::createLogicalDevice() |
#查询更多关于 SwapChain 的细节
在确保了 Physical Device 支持 Swap Chain 后,还需要查询更多关于 Swap Chain 的信息。因为不同的设备可能支持不同的 Surface 格式,Presentation 模式等。这些信息在后续创建 Swap Chain 时会用到。
需要查询的更多的关于 swap chain 的信息,基本由三种信息需要检查:
- 与 Surface 的兼容性:如 images 的最小,最大数量,Image 的最小最大宽度和高度
- Surface 的格式:如纹理的格式,Color Space
- 可选的 Presentation 模式
首先定义一个结构体存储上述需要的所有信息:
1 | struct SwapChainSupportDetails |
实现函数 querySwapChainSupport
生成上述结构体,其中调用了一系列的 vkGetPhysicalDeviceSurfaceXXX
函数来获取 Surface 的信息:
1 | SwapChainSupportDetails SwapChainMgr::querySwapChainSupport(VkPhysicalDevice device) |
再次修改函数 PhysicalDevicesMgr::isDeviceSuitable
,在支持的 Image Format 和 Presentation Mode 都不为空时才认为设备合适:
1 | bool PhysicalDevicesMgr::isDeviceSuitable(VkPhysicalDevice device) |
#为 SwapChain 选择正确的设置
在确认 Swap Chain 可以获取后,还需要一个函数为 SwapChain 选择最合适的设置,不同的设置适合不同的场景,也可能带来不同的性能表现。Swap chain 主要有三种类型数据需要设置:
- Surface Format:如颜色 / 深度缓冲
- Presentation Mode:如交换 Image 到屏幕上的条件
- Swap Extent:Swap Chain 图片的分辨率
对于上述每一种数据类型,都会尝试找寻最合适的值,如果该值无法获取即寻找次优的值,这三种数据类型分别在 SwapChainMgr
的三个 chooseSwapSurfaceFormat
,chooseSwapPresentMode
和 chooseSwapExtent
函数中实现。
#Surface format
定义函数 chooseSwapSurfaceFormat
找寻最合适的 Surface format:
1 | VkSurfaceFormatKHR SwapChainMgr::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) |
#Presentation mode
在 Vulkan 中一共有如下四种可能的 Present 模式:
VK_PRESENT_MODE_IMMEDIATE_KHR
:当应用提交了一个画面后,马上将画面显示到屏幕上。该模式可能造成画面撕裂。VK_PRESENT_MODE_FIFO_KHR
:当设备的 VSync 到来后,从 queue 中获取一个画面显式到屏幕上。当应用提交画面时,放置到 queue 的末尾。如果应用提交画面时,发现 queue 已满,则会阻塞应用。如果 VSync 到来但是 Queue 为空时,则会等待下一个 VSync 重新尝试获取。Vk_PRESENT_MODE_FIFI_RELAXED_KHR
:与VK_PRESENT_MODE_FIFO_KHR
类似,只不过在 VSync 到来但 Queue 为空后,不会再次等到设备 VSync 时做第二次查询,而是当新的图像一到来就刷新到屏幕上VK_PRESENT_MODE_MAILBOX_KHR
:与VK_PRESENT_MODE_FIFO_KHR
类似,只不过当应用提交画面且 Queue 已满时,不再阻塞应用提交画面,而是会用新画面取代 Queue 中最老的画面。
在设备支持 Swap Chain 后,VK_PRESENT_MODE_FIFO_KHR
模式可保证必然支持。但这里选择使用 VK_PRESENT_MODE_MAILBOX_KHR
模式,避免应用意外的阻塞,仅当 VK_PRESENT_MODE_MAILBOX_KHR
不存在时再使用 VK_PRESENT_MODE_FIFO_KHR
。
封装函数 chooseSwapPresentMode
选择最合适的 Present Mode:
1 | VkPresentModeKHR SwapChainMgr::chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) |
#Swap extent
最后一个需要设置的属性是 Swap Extent,该属性表示 Swap Chain Image 的分辨率,Swap Extent 的上下限在 VkSurfaceCapabilitiesKHR
结构体中定义。
Swap Extent 分辨率几乎一直和屏幕的分辨率相同,如果平台的 Window Managers 允许开发者定义与屏幕不相同的分辨率,那么会将 VkSurfaceCapabilitiesHKR.current
的宽高都设定为 UINT32_MAX
,反之 VkSurfaceCapabilitiesHKR.current
即为即为屏幕分辨率。
定义函数 chooseSwapExtent
在允许的情况下,将 Swap Extent 设定为之前窗口的宽高:
1 | VkExtent2D SwapChainMgr::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) |
#创建 SwapChain
在 initVulkan
函数中,添加对 createSwapChain
的调用:
1 | void HelloTriangleApplication::initVulkan() |
完整的 SwapChainMgr::createSwapChain
函数为:
1 | void SwapChainMgr::createSwapChain() |
上述函数主要的工作还是装填 VkSwapchainCreateInfoKHR
数据,并最终调用 vkCreateSwapchainKHR
创建出需要的 swapchain
对象。
在 SwapChain 创建完成后,调用 vkGetSwapchainImagesKHR
获取 SwapChain 中的 Image 数量和数据,并将 Image 的 Extent 和 Format 存储下来,即完整函数实现中的如下部分:
1 | vkGetSwapchainImagesKHR(device, swapchain, &imageCount, nullptr); |
当程序结束时,也应当清理 SwapChain,因为创建 SwapChain 时依赖 device,因此销毁时 SwapChain 也必须在 Device 被销毁前销毁:
1 | void HelloTriangleApplication::cleanup() |
createInfo
中 surfaceFormat
,presentMode
和 extent
都是在 Choosing the right settings for the swap chain 中获取的数据。
createInfo
中 剩下还有的比较重要的参数:
#minImageCount
minImageCount
是 swapChain 中最小需要使用到的纹理数量,该数量可以通过 swapChainSupport.capabilities.minImageCount
获得。但如果直接将 swapChainSupport.capabilities.minImageCount
的数量赋值给 createInfo.minImageCount
会在某些时刻导致程序必须等待驱动完成一些操作后,才能去拿取下一张图片。
因此这里通常会取 swapChainSupport.capabilities.minImageCount + 1
,且需要确认 + 1 后不会超过 swapChainSupport.capabilities.maxImageCount
。
#imageArrayLayers
createInfo.imageArrayLayers
指定了每个 SwapChain 中 image 由几层 layer 构成。通常这个参数永远是 1,除非需要 XR 的应用。
#imageUsage
createInfo.imageUsage
是指定 SwapChain 中 Image 的作用,因为这里仅需要将 swapChain 作为 Color Attachment,因此设定为 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
。
#ImageSharingMode
createrInfo.ImageSharingMode
指定了 SwapChain 中的 image 该如何在多个 Queue families 中处理。
ImageSharingMode
可以指定两种模式:
VK_SHARING_MODE_EXCLUSIVE
:Image 在同一时刻只会被一个 Queue Family 拥有,且另一个 Queue Family 需要处理该 Image 时,需要显示的转移拥有权。该模式下拥有最好的性能。VK_SHARING_MODE_CONCURRENT
:当不同的 Queue Families 使用同一个 Image 时,不需要显式的转移拥有权。
在这里,会需要首先判断之前获取的 Queue Families( graphicsFamily
和 presentFamily
)是否是同一个 Queue Family。如果是的话,使用 VK_SHARING_MODE_EXCLUSIVE
获取更好的性能,否则使用 VK_SHARING_MODE_CONCURRENT
因为目前还不需要显示的控制 Image 拥有权。
之所以 VK_SHARING_MODE_EXCLUSIVE
性能更好,是因为当资源处于独占模式时,GPU 驱动可以优化访问,而如果是 VK_SHARING_MODE_CONCURRENT
共享模式,GPU 必须有同步机制确保多个 Queue Family 访问同一个 Image 时不会发生冲突。
#preTransform
preTransform
可以指定设置在 SwapChain Image 的 Transform 变化,如顺时针旋转 90 °。
如果不需要这种变化的话,简单设置为 swapChainSupport.capabilities.currentTransform
就好。
#compositeAlpha
compositeAlpha
指定了有多个 Window 叠加时,该如何处理混合模式。通常而言当前的 Window 并不需要与背后的 Window 进行混合,因此可以设为 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
。
#clipped
clipped
表示当一部分像素无法显示时(如被其他窗口遮挡),是否需要舍弃这些像素。通常而言,除非时真的需要依赖所有像素计算某些数据,否则为了更好的性能,都应该设置为 VK_TRUE
。
#oldSwapchain
在 Vulkan 程序的整个生命周期内,可能 SwapChain 会变得不合法或者出现了性能问题,此时 Vulkan 是支持重新创建 SwapChain 的,当创建时,需要通过 oldSwapchain
指定上一个使用 SwapChain,这里即为 VK_NULL_HANDLE
。