交换链(swapChain)
Vulkan 没有“默认帧缓冲区”的概念,因此它需要一个基础设施来拥有我们将渲染到的缓冲区,然后再将它们可视化到屏幕上。此基础结构是称为交换链,必须在 Vulkan 中显式创建。交换链本质上是等待呈现给屏幕。我们的应用程序将获取这样的图像以绘制到它,然后将其返回到队列中。队列到底是如何工作的,以及从队列中呈现图像取决于交换链的设置方式,但是交换链的一般目的是同步具有屏幕刷新率的图像。
并非所有显卡都能够将图像直接呈现到屏幕上。例如为服务器设计的不具有任何显示输出功能。由于图像与呈现是紧密相关的,所以要想显示必须启用设备扩展VK_KHR_swapchain。先检查设备是否支持交换链的扩展。
const std::vector<const char*> deviceExtensionNames = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
bool CVulkanApp::checkDeviceExtensionSupport(VkPhysicalDevice dev)
{
uint32_t deviceExtensionCount;
vkEnumerateDeviceExtensionProperties(dev, nullptr, &deviceExtensionCount, nullptr);
std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
vkEnumerateDeviceExtensionProperties(dev, nullptr, &deviceExtensionCount, deviceExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensionNames.begin(), deviceExtensionNames.end());
for ( const auto& extension : deviceExtensions)
{
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
枚举设备扩展的个数,然后再获取具体扩展属性。这里的做了一个清空需要扩展属性的名字的处理。我们要设置的属性名存放在临时容器requiredExtensions中,如果全被删除,说明枚举设备扩展中包含了我们所设置的扩展属性,以此方式来判断是否支持此扩展。
bool CVulkanApp::isDeviceSuitable(VkPhysicalDevice dev)
{
VkPhysicalDeviceProperties deviceProperties;
VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceProperties(dev, &deviceProperties);
vkGetPhysicalDeviceFeatures(dev, &deviceFeatures);
bool deviceSupport = checkDeviceExtensionSupport(dev);
QueueFamilyIndices indices = findQueueFamilies(dev);
return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
&& deviceFeatures.geometryShader
&& indices.isComplete()
&& deviceSupport;
}
在设备判断是否支持的函数中增加物理设备是否支持交换链的判断。
VkDeviceCreateInfo createInfo{};
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensionNames.size());
createInfo.ppEnabledExtensionNames = deviceExtensionNames.data();
在创建逻辑设备的变量中,设置我们增加索要扩展的属性。仅检查交换链是否可用是不够的,因为它可能不可用。
我们基本上需要检查三种属性:
-
基本表面功能(交换链中的最小/最大图像数,最小/最大值 图像的宽度和高度)
-
表面格式(像素格式、色彩空间)
-
可用的演示模式
struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector<VkSurfaceFormatKHR> formats; std::vector<VkPresentModeKHR> presentModes; };
SwapChainSupportDetails CVulkanApp::querySwapChainSupport(VkPhysicalDevice dev) { SwapChainSupportDetails details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surface_, &details.capabilities); uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface_, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface_, &formatCount, details.formats.data()); } uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(dev, surface_, &presentModeCount, nullptr); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(dev, surface_, &presentModeCount, details.presentModes.data()); } return details; }
以上代码就是获取三个属性信息用于Vulkan与窗口界面的支持。仅支持还不够,我们要从中选择合适的属性支持。
VkSurfaceFormatKHR CVulkanApp::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats) { for (const auto& format : formats) { if (format.format == VK_FORMAT_B8G8R8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return format; } } return formats[0]; }
选取一种符合的标准颜色深度以及色彩空间SRGB非线性设置。
VkPresentModeKHR CVulkanApp::chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& presentModes) { for (const auto& mode : presentModes) { if (mode == VK_PRESENT_MODE_MAILBOX_KHR) { return mode; } } return VK_PRESENT_MODE_FIFO_KHR; }
选择一种图片绘制到界面的模式 三缓冲 或 双缓冲处理。
VkExtent2D CVulkanApp::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != UINT32_MAX) { return capabilities.currentExtent; } int width, height; glfwGetFramebufferSize(window_, &width, &height); VkExtent2D actualExtent = { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; actualExtent.width = glm::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); actualExtent.height = glm::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; }
这个就是选择设备支持的与屏幕设置的尺寸大小的分辨率。比如(800 * 600)。高分屏就要设置设备相匹配的分辨率。
void CVulkanApp::createSwapChain()
{
assert(logicDevice_ != VK_NULL_HANDLE);
assert(surface_ != VK_NULL_HANDLE);
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice_);
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0
&& imageCount > swapChainSupport.capabilities.maxImageCount)
{
imageCount = swapChainSupport.capabilities.maxImageCount;
}
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;
QueueFamilyIndices indices = findQueueFamilies(physicalDevice_);
qFamily_.graphicsFamily = indices.graphicsFamily.value();
qFamily_.presentFamily = indices.presentFamily.value();
std::vector<uint32_t> uniqueQueueFamilies = {
indices.graphicsFamily.value(),
indices.presentFamily.value()
};
if (indices.graphicsFamily != indices.presentFamily)
{//并发模式
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = static_cast<uint32_t>(uniqueQueueFamilies.size());
createInfo.pQueueFamilyIndices = uniqueQueueFamilies.data();
}
else
{//独占模式
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
}
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(logicDevice_, &createInfo, nullptr, &swapChain_) != VK_SUCCESS)
{
throw std::runtime_error("failed to crate swap chain!");
}
vkGetSwapchainImagesKHR(logicDevice_, swapChain_, &imageCount, nullptr);
swapChainImages_.resize(imageCount);
vkGetSwapchainImagesKHR(logicDevice_, swapChain_, &imageCount, swapChainImages_.data());
swapChainImageFormat_ = surfaceFormat.format;
swapChainExtent_ = extent;
}
交换链设置的完整流程。