交换链
Vulkan没有默认帧缓冲的概念,需要一个能够缓冲渲染结果的组件,在Vulkan中,这一组件就是交换链。Vulkan交换链必须显式创建,不存在默认的交换链。交换链本质上是一个包含了若干等待呈现的图像队列,应用程序从交换链获取一张图像,然后在图像上进行渲染绘制,完成绘制后,将图像返回到交换链的队列中。交换链队列的工作方式和呈现图像到表面的时机取决于交换链的设置,通常来说,交换链呈现图像和屏幕刷新率同步。
检测交换链的支持情况
并不是所有的显卡设备都具有将图像呈现到屏幕的能力。比如,为服务器设计的显卡是没有任何显示输出的。此外,由于图像呈现非常依赖于窗口系统以及与窗口系统密切相关的表面,这些并非Vulkan的核心内容,因此必须在启用了设备扩展VK_KHR_swapchain
后才能使用交换链。
首先需要检测是否支持扩展VK_KHR_swapchain
,之前我们介绍了如何查询VkPhysicalDevice
支持的扩展列表,只需要在列表中检测VK_KHR_swapchain
是否存在即可。Vulkan库头文件有一个VK_KHR_SWAPCHAIN_EXTENSION_NAME
宏,它等价于VK_KHR_swapchain
,使 用这个宏而不是直接使用VK_KHR_swapchain
,防止出现拼写错误。
首先,定义所需的的设备扩展列表,类似启用校验层:
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
接着,添加checkDeviceExtensionSupport
函数,在isDeviceSuitable
中调用它:
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
return indices.isComplete() && extensionsSupported;
}
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
return true;
}
修改checkDeviceExtensionSupport
函数,遍历设备扩展列表,检测所需的扩展是否存在:
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();
}
我们将所需的扩展保存在集合中,然后遍历设备所有的可用扩展,并从集合中的剔除它,如果最后集合中的元素个数为0,说明我们所需的扩展全部满足。实际上,如果设备支持呈现队列,那么就一定支持交换链,但我们最好还是显式地执行交换链扩展检测。
启用交换链扩展,只需要对逻辑设备的创建过程进行微调:
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
查询交换链的细节信息
只检查交换链是否可用还不够,它可能实际上与我们的窗口表面不兼容。创建交换链所需的设置比实例和设备多得多,在创建交换链之前我们需要查询更多信息。
需要检查三种最基本的属性:
- 基础表面功能(交换链的最小/最大图像数量,最小/最大图像宽度、高度)
- 表面格式(像素格式,颜色空间)
- 可用的呈现模式
类似于findQueueFamilies
,我们使用结构体来存储查询到的交换链细节信息:
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
添加querySwapChainSupport
函数返回细节信息:
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
return details;
}
本节先介绍如何查询,下一节再对它们的具体意义进行说明。
首先,调用下面的函数查询基础表面功能:
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
这一函数以VkPhysicalDevice
和VkSurfaceKHR
对象作为参数,查询交换链信息的相关函数都需要这两个参数,它们是交换链的核心组件。
下一步,查询表面支持的格式。查询结果是一个结构体列表,还是熟悉的味道,函数调用2次,首先查询格式数量,然后分配数组保存结果:
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
确保数组大小足够保存结果,最后,使用同样的方式调用vkGetPhysicalDeviceSurfacePresentModesKHR
查询支持的呈现模式:
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
现在所有查询到的信息已经保存了下来,对isDeviceSuitable
函数进行补充,检测交换链的能力是否满足需求。对于本教程而言,只需要交换链至少支持一种图像格式和一种窗口表面的呈现模式即可:
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
综合上面的内容,isDeviceSuitable
函数最后一行修改为:
return indices.isComplete() && extensionsSupported && swapChainAdequate;
正确设置交换链
如果swapChainAdequate
为真,说明交换链的能力满足我们的需要,但不同的设置会有不同的优化结果。接下来,我们编写一组函数来查找合适的设置,主要有三个设置:
- 表面格式(颜色,深度)
- 呈现模式(显示图像到屏幕的时机)
- 交换范围(交换链中的图像的分辨率)
上面的每个设置都有一个理想值,如果理想值不满足,我们会查找一个尽可能好的替代值。
表面格式
添加chooseSwapSurfaceFormat
函数来选择合适的表面格式:
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR> &availableFormats) {
}
每个VkSurfaceFormatKHR
条目包含一个format
和lorSpace
成员。
-
format
用于指定颜色通道和存储类型。比如,
VK_FORMAT_B8G8R8A8_UNORM
表示我们以B,G,R和A的顺序,每个颜色通道用8位无符号整型数,每像素总共使用32位表示。 -
colorSpace
用来表示是否支持SRGB颜色空间(包含VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
标记位)。注意
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
在之前的Vulkan规范中叫做VK_COLORSPACE_SRGB_NONLINEAR_KHR
。
对于颜色空间,我们使用SRGB,它是标准颜色空间,使用它可以得到更加准确的颜色表示,一种常见的sRGB 颜色格式是VK_FORMAT_B8G8R8A8_SRGB
。
检查获取到的格式列表是否包含需要的格式:
for (const auto& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}