原文链接: Vulakn-tutorial
这一章节我们将学习这样一种结构/基础(infrastructure),它能为我们提供要渲染的图片,然后渲染结的果可以显到屏幕上。这样的结构就是Swap Chain , Swap Chain必须被Vulkan显示的创建。从本质上讲,Swap Chain就是一个图片的队列(a queue of images),这里的图片等着被显示到屏幕上。我们的应用将会获得一个图片,然后绘画它,之后将它提交到队列中去。Swap Chain 通常的作用是通过屏幕刷新率(refresh rate of the screen)来同步控制图片的显示。
显卡有各种理由阻止你将images直接提交给屏幕,例如系统只是个服务器(Server),不支持显示。其次,图片显示和窗口系统(window system)以及和窗口相连的window surface密切相关。所以Swap Chain并不属于核心Vulkan 功能。这就要求必须支持VK_KHR_swapchain扩展。
我们需要检查显卡是否支持Swap chain,首先我们要有能力获取显卡的所有扩展:
VkResult vkEnumerateDeviceExtensionProperties(
VkPhysicalDevice physicalDevice,
const char* pLayerName,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties);
现在,你肯定对这种结构再熟悉不过了。pLayerName是一个过滤选项,它是一个Validation layer的名字,表示只枚举这个Validation layer所对应的所有扩展,如果pLayerName为nullptr (NULL),表示要获取对应physicalDevice下的所有扩展。
Vulkan.h 下有如下定义:
#define VK_KHR_SWAPCHAIN_EXTENSION_NAME "VK_KHR_swapchain"
physicalDevice 必须支持此扩展,我们才能创建Swap Chain.
添加:
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
修改isDeviceSuitable(…):
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
return indices.isComplete() && extensionsSupported;
}
添加检查支持deviceExtensions扩展的函数checkDeviceExtensionSupport:
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector 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();
}
修改在创建Logical Device时的VkDeviceCreateInfo createInfo = {};
结构,添加extensions选项:
createInfo.enabledExtensionCount = deviceExtensions.size();
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
只是检查显卡是否支持Swap Chain还不够,因为Swap Chain还可能和我们的window surface不兼容。创建一个Swap Chain还需要一系列配置工作,比创建Instance和Logical Device复杂多了。
所以我们还要检查以下三种属性:
Surface 的性能(Capabilities)(比如 : min/max number of images in swap chain, min/max width and height of images)。
Surface 的格式(formats)(比如 : pixel format, color space)。
可用的显示模式(present mode)。
如同QueueFamilyIndices,定义结构SwapChainSupportDetails:
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector formats;
std::vector presentModes;
};
先从VkSurfaceCapabilitiesKHR 开始介绍吧。
VkSurfaceCapabilitiesKHR 结构:
typedef struct VkSurfaceCapabilitiesKHR {
uint32_t minImageCount;
uint32_t maxImageCount;
VkExtent2D currentExtent;
VkExtent2D minImageExtent;
VkExtent2D maxImageExtent;
uint32_t maxImageArrayLayers;
VkSurfaceTransformFlagsKHR supportedTransforms;
VkSurfaceTransformFlagBitsKHR currentTransform;
VkCompositeAlphaFlagsKHR supportedCompositeAlpha;
VkImageUsageFlags supportedUsageFlags;
} VkSurfaceCapabilitiesKHR;
VkExtent2D 的结构比较简单:
typedef struct VkExtent2D {
uint32_t width;
uint32_t height;
} VkExtent2D;
VkSurfaceTransformFlagsKHR同VkSurfaceTransformFlagBitsKHR表示如何转换图片:
typedef enum VkSurfaceTransformFlagBitsKHR {
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001,
VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002,
VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004,
VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR = 0x00000008,
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR = 0x00000010,
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR = 0x00000020,
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR = 0x00000040,
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR = 0x00000080,
VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR = 0x00000100,
VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
} VkSurfaceTransformFlagBitsKHR;
获取VkSurfaceCapabilitiesKHR:
VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
VkPhysicalDevice physicalDevice,
VkSurfaceKHR surface,
VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
VkSurfaceFormatKHR 结构:
typedef struct VkSurfaceFormatKHR {
VkFormat format;
VkColorSpaceKHR colorSpace;
} VkSurfaceFormatKHR;
VkFormat 一个非常庞大的结构:
typedef enum VkFormat {
VK_FORMAT_UNDEFINED = 0,
VK_FORMAT_R4G4_UNORM_PACK8 = 1,
VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2,
VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3,
VK_FORMAT_R5G6B5_UNORM_PACK16 = 4,
VK_FORMAT_B5G6R5_UNORM_PACK16 = 5,
...
...
} VkFormat;
VkColorSpaceKHR :
typedef enum VkColorSpaceKHR {
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR = 0,
} VkColorSpaceKHR;
VkColorSpaceKHR 表示是否支持SRGB颜色,是观察者获取更加精确的颜色感知效果。(more accurate perceived colors).
获取支持的VkSurfaceFormatKHR:
VkResult vkGetPhysicalDeviceSurfaceFormatsKHR(
VkPhysicalDevice physicalDevice,
VkSurfaceKHR surface,
uint32_t* pSurfaceFormatCount,
VkSurfaceFormatKHR* pSurfaceFormats);
VkPresentModeKHR 结构:
typedef enum VkPresentModeKHR {
VK_PRESENT_MODE_IMMEDIATE_KHR = 0,
VK_PRESENT_MODE_MAILBOX_KHR = 1,
VK_PRESENT_MODE_FIFO_KHR = 2,
VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3,
...
VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
} VkPresentModeKHR;
VkPresentModeKHR 指显示模式。
获取支持的VkPresentModeKHR:
VkResult vkGetPhysicalDeviceSurfacePresentModesKHR(
VkPhysicalDevice physicalDevice,
VkSurfaceKHR surface,
uint32_t* pPresentModeCount,
VkPresentModeKHR* pPresentModes);
添加函数 :
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
实现 :
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
// Capabilities
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
//formats
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
// presentMode
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
现在修改选择Physical Device时的 isDeviceSuitable(…) ,使它具有检测Swap Chain的各种支持的功能:
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
//支持swap chain ?
bool extensionsSupported = checkDeviceExtensionSupport(device);
//swap chain的细节特性也支持? Format,mode...?
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); //调用此函数
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
return indices.isComplete() && extensionsSupported && swapChainAdequate;
}
只要有一个format和presentMode我们就认为此显卡支持Swap Chain 并且兼容surface。 现在我们的Physical Device 越发强大。
如果swapChainAdequate
的值为true.那么我们有理由肯定Swap Chain已经被支持了。而且Swap Chain 的所有特性支持也都被写入swapChainSupport
变量中了。但是针对不同的优化有着不同的设置选项,为了寻找最佳的Swap Chain设置,我们决定从以下三个方面入手:
正如之前所说,所有被支持的Format都已存在 swapChainSupport.fromats中,每一个Format是一个VkSurfaceFormatKHR结构,包含format 和 colorSpace。
我们的需求:
format 为:VK_FORMAT_B8G8R8A8_UNORM ,因为这种颜色比较通用;
colorSpace 为:VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,即支持SRGB颜色。
定义函数:
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector & availableFormats) {
}
swapChainSupport.fromats
将作为参数传递进去。
这里有三个选择方案:
plan A:
如果surface没有首选的Format,则只会有一个Format,且Format.format值为VK_FORMAT_UNDEFINED,那么我们就按我们的需求来返回Format:
if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
Plan B:
如果我们不能自由的选择,Format, 那么我们就从Format的列表中查找是否有我们感兴趣的Format:
for (const auto& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
Plan C:
如果plan A 和 plane B 都失败了,我们可以将Format列表中的Format根据某种最佳(“good”)准则进行排序,然后选择最好的。但我不想这么复杂,因为Format列表中的第一个往往就能满足我们的需求:
return availableFormats[0]; //直接 返回第一个Format
将上述思想综合起来,我们将得到 :
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector & availableFormats) {
// plan A
if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
// plan B
for (const auto& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
// plan C
return availableFormats[0];
}
正如之前所列出的,我们所列出的,VkpresentModeKHR
有:
VK_PRESENT_MODE_IMMEDIATE_KHR = 0, //单 ,立即显示,可能出现撕裂
VK_PRESENT_MODE_MAILBOX_KHR = 1, // 三缓冲
VK_PRESENT_MODE_FIFO_KHR = 2, //双
VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3, //类似 FIFO
…... 详细参阅文档 ..
因为VK_PRESENT_MODE_FIFO_KHR
一定是可以得到的,又可以避免撕裂,所以presentMode
我们选择VK_PRESENT_MODE_FIFO_KHR
。
但我个人认为三缓冲更加绝妙(nice),可以先看看有没有它,所以我们的实现是:
VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) {
for (const auto& availablePresentMode : availablePresentModes) {
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentMode;
}
}
return VK_PRESENT_MODE_FIFO_KHR;
}
现在就剩下一个最重要的属性需要设置了,extent
是Swap Chain中image
的分辨率(resolution) ,通常它与window的尺寸一样,vulkan让我们通过设置currentExtent
设置width
和height
来匹配window
的分辨率。但是有些window Manager会将currentExtent
设置为uint32_t
的最大值,来表示允许我们设置不同的值,这个时候我们可以从minImageExtent
和maxImageExtent
中选择最匹配window
的尺寸值。
实现如下:
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
if (capabilities.currentExtent.width != std::numeric_limits::max()) {
return capabilities.currentExtent;
} else {
// window 的尺寸
VkExtent2D actualExtent = {WIDTH, HEIGHT};
actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
return actualExtent;
}
}
以上逻辑还是很好理解的:
当currentExtent.width/height
任何一个值不是uint32_t
的最大值时,直接选择Capabilities.currentExtent
, currentExtent
最符合window
尺寸。
否则,从Window 尺寸和支持的最大尺寸(maxImageExtent
)取最小的,因为尺寸不能超过window,也不能超过capabilities
支持的最大尺寸。最后为了得到最好的效果,取结果值和minImageExtent
的最大值。
现在我们已经获得了所有创建Swap Chain的必要信息,接下来就开始创建Swap Chain 吧。
首先我们获取上一步的操作结果,它们包含了我们应该设置的参数:
void createSwapChain() {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent =chooseSwapExtent(swapChainSupport.capabilities);
...
...
}
设置Swap Chain 中image的数量,本质上是指队列的长度:
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
minImageCount
个image 已经十分合适了,但是为了更好的支持三缓冲,我们又多加了一个。maxImageCount
如果为0
, 表示不对最大数量做任何限制。
作为创建Vulkan对象的惯例,在创建Swap Chain 之前我们需要填充一个大的结构体:
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;
imageArrayLayers
表示image的层次,除非创建3D应用,否则这个值将为1.
imageUsage
我们要使用Swap Chain里的image做什么操作,在这个教程中我们将直接对image进行渲染,这就意味着Image将被当做颜色附件使用(color attachment)。如果你想先渲染一个单独的图片然后再进行处理,那就应该使用VK_IMAGE_USAGE_TRANSFER_DST_BIT
并使用内存转换操作将渲染好的image 转换到SwapChain里。VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
表示image可以用作创建VkImageView
,在VkFrameBuffer
中适合使用color 或者 reslove attachment.
QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = {
(uint32_t) indices.graphicsFamily,
(uint32_t) indices.presentFamily
};
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
}
imageSharingMode
表示多种队列中,image如何使用,如果grapics queue 和 present queue不相同,就会出现这多种队列访问image的情况:我们在grapics queue 中绘画image
,然后将它提交到presention queue 去等待显示。
imageSharingMode
的取值为:
VK_SHARING_MODE_EXCLUSIVE : image 一段时间内只能属于一种队列,所有权的转换必须明确声明,这个选项可以提供较好的性能。
VK_SHARING_MODE_CONCURRENT : image 可以跨多种队列使用,所有权的转换不必明确声明。
如果grapics queue 和 present queue 不同,我们将采用Concurrent模式,主要目的是避免明确的所有权的转换,这一概念现在不讲。如果两个queue 种类相同,我们采用Exclusive模式,因为Concurrent模式需要至少两个不同种类的队列。
// 不对Image 变换
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
// 忽略和其他窗口颜色混合时的Alpha 通道
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE; // 不处理那些被遮盖的像素
createInfo.oldSwapchain = VK_NULL_HANDLE; // 暂时不用置空即可
最后创建 Swap Chain :
VkSwapchainKHR swapChain; // 声明
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}
声明几个变量,我们在接下来的步骤要用到:
std::vector swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
swapChain
里的image
随swapChain
一起创建、一起销毁。
获取swapChain
里所有的images
:
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
Image
的清理工作将随着SwapChain的清理而被清理。
源码:
源码太多了,后续源码不再贴在这里。
原文源码地址: source code
CSDN 站内地址: csdn 故障,没传上去。