swap chain可以直接理解为等待渲染到屏幕上的图片队列。具体的工作方式和swap chain的设置方式有关,但是一般都是将图像的显示和屏幕的刷新率同步。
swap chain检测
并非所有的显卡都支持直接将图像显示在屏幕上,例如服务器端的显卡。并且由于图像的展示和所在平台的窗口系统联系很紧密,而vulkan又是跨平台的,所以这并不是vulkan的核心组件,需要检测VK_KHR_swapchain扩展并启用。
swap chain扩展检测
这里的检测用于检测物理设备,像前面检测实例扩展很类似
// 要使用的swap chain扩展
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
availableExtensions.data());
for (auto& extension : deviceExtensions) {
bool found = false;
for (const auto& availableExtension : availableExtensions) {
if (strcmp(extension, availableExtension.extensionName) == 0) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}window surface对swap chain的支持能力检测
仅仅只是检查物理设备是否支持swap chain还不够,还需要检查其是否和window surface兼容。还需要检测
- 基本的surface能力
- surface格式
- 展示模式
// 封装了一下swap chain要检测的信息
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
// surface的能力
SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface,
&details.capabilities);
// surface的格式
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,
nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,
details.formats.data());
}
// surface的呈现模式
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface,
&presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(
device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
// 设备的选择再加上一个检测条件
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
// 设备加上swap chain的扩展检查
bool extensionsSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() &&
!swapChainSupport.presentModes.empty();
}
return indices.isComplete() && extensionsSupported && swapChainAdequate;
}启动扩展
就是填充createInfo,注意是逻辑设备的createInfo结构体。我们只是在物理设备上进行检测,但是最终还是使用逻辑设备和队列来和物理设备交互的。
createInfo.enabledExtensionCount =
static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();设置swap chain
window surface对swap chain有很多设置,通过检测的时候我们也获取到了surface对swap chain的支持情况。现在就是选择最适合的swap chain设置。
其实也就是前面检测的三种类型参数。
surface format
这里设置图像的格式和色彩空间。
- 格式推荐
VK_FORMAT_B8G8R8A8_SRGB - 色彩空间推荐
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
VkSurfaceFormatKHR chooseSwapSurfaceFormat(
const std::vector<VkSurfaceFormatKHR>& availableFormats) {
for (const auto& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB &&
availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
return availableFormats[0];
}显示模式
这是swap chain最重要的部分,vulkan有下面四种可用的模式
VK_PRESENT_MODE_IMMEDIATE_KHR:提交的图片立即展示到屏幕上,可能导致画面撕裂VK_PRESENT_MODE_FIFO_KHR:swap chain是一个队列,屏幕从队列当中取出图像,程序提交的图片放在队列的尾部。队列要是满了,那么程序就必须阻塞,与现代游戏当中的垂直同步类似,屏幕刷新的那一刻叫做垂直空白。VK_PRESENT_MODE_FIFO_RELAXED_KHR:与前者的不同在于,当swap chain为空之后,应用提交的图像会立即被传输到显示上而不是等待下一个垂直空白。这回造成画面撕裂。VK_PRESENT_MODE_MAILBOX_KHR:第二种方式另一个变体。swap chain队列满了不会阻塞应用程序则是将队列中的图像替换成图像。保证尽可能渲染新图像同时避免撕裂。可以比标准的垂直同步延迟更小。这通常被称为“三重缓冲”,尽管仅存在三个缓冲液,并不一定意味着帧率已解锁。
其中VK_PRESENT_MODE_FIFO_KHR是保证支持的。在检测其他模式的支持情况的时候,VK_PRESENT_MODE_MAILBOX_KHR是更加推荐的,但是在移动平台上更新注重能效,因此优先使用VK_PRESENT_MODE_FIFO_KHR。
那么这里的选择逻辑就很清晰了
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;
}Swap extent
这个东西可以简单理解为图像的分辨率。通常设置成显示器相同。再简单点就是设置宽度和高度。
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
if (capabilities.currentExtent.width != UINT32_MAX) {
return capabilities.currentExtent;
} else {
int width, height;
// 这里选择创建的窗口的宽和高,也就是填充窗口
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D actualExtent = {static_cast<uint32_t>(width),
static_cast<uint32_t>(height)};
// 设置了最大值和最小值
actualExtent.width =
std::clamp(actualExtent.width, capabilities.minImageExtent.width,
capabilities.maxImageExtent.width);
actualExtent.height =
std::clamp(actualExtent.height, capabilities.minImageExtent.height,
capabilities.maxImageExtent.height);
return actualExtent;
}
}创建swap chain
基于上面查询到的swap chain的支持情况,以及选择的推荐配置项,创建swap chain
VkSwapchainKHR swapChain;
void createSwapChain() {
SwapChainSupportDetails swapChainSupport =
querySwapChainSupport(physicalDevice);
VkSurfaceFormatKHR surfaceFormat =
chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode =
chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
// 设置交换链的图像数量,图像数量最小为capabilities.minImageCount+1,避免等待
// 交换链的图像数量capabilities.maxImageCount ==
// 0,说明交换链的图像数量不受限制
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 &&
imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
// 填充createInfo
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
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;
// graphics和present队列不同,需要设置共享模式来避免拥有权的转移。这里设置为并发共享模式只是为了方便
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
}
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) !=
VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}
}在应用退出之前进行销毁
vkDestroySwapchainKHR(device, swapChain, nullptr);获取swap chain中的图像
拿到swap chain当中的图像,方便后续处理。这里的数据会在swap chain销毁的时候也自动销毁,无需手动清理
std::vector<VkImage> swapChainImages;
// 在上面的createSwapChain函数尾部添加下面代码
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount,
swapChainImages.data());再存储一下前面创建swap chain设置的属性,图像格式和分辨率,方便后续处理
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;创建ImageView
为了swap chain中的每个VkImage都创建对应的ImageView
std::vector<VkImageView> swapChainImageViews;
swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); i++) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapChainImageFormat;
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device, &createInfo, nullptr,
&swapChainImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create image views!");
}
}ImageView不像Image是swap chain自动创建和释放的,我们手动创建的资源当然也要手动释放
for (auto imageView : swapChainImageViews) {
vkDestroyImageView(device, imageView, nullptr);
}