交换链是图片的列表。其中一个图片在前台显示,其他图片在后台。Vulkan的绘制会在后台图片上进行一些列的绘制操作,直到绘制完成,然后交换前台图片和绘制完成的后台图片。此时,当前后台图片成为前台图片,当前前台图片成为后台图片。交换链如下所示:
Vulkan和视窗系统
与其他图形API一样,Vulkan也将视窗系统与核心图形API分离。在Vulkan中,窗口系统细节通过WSI(Window System Integration 窗口系统集成)扩展来公开。
WSI扩展包含对各种平台的支持,可通过以下宏定义开启:
- VK_USE_PLATFORM_ANDROID_KHR - Android
- VK_USE_PLATFORM_WAYLAND_KHR - Wayland
- VK_USE_PLATFORM_WIN32_KHR - Microsoft Windows
- VK_USE_PLATFORM_XCB_KHR - X Window System, using the XCB library
- VK_USE_PLATFORM_XLIB_KHR - X Window System, using the XLib library
名称中的KHR表示在Khronos中定义
Surface抽象
Vulkan用VkSurfaceKHR对象抽象本地平台Surface和窗口。这个符号被定义为VK_KHR_surface扩展的一部分。WSI扩展提供各种函数用于创建、操作、释放这个对象。
访问实例和设备扩展
实例扩展
为了使用WSI扩展,必须激活一般surface扩展。以下代码添加VK_KHR_SURFACE_EXTENSION_NAME添加到要加载的实例扩展列表中。
void init_instance_extension_names(struct sample_info &info) {
info.instance_extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
#ifdef __ANDROID__
info.instance_extension_names.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#elif defined(_WIN32)
info.instance_extension_names.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_IOS_MVK)
info.instance_extension_names.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
info.instance_extension_names.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
info.instance_extension_names.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#else
info.instance_extension_names.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
#endif
}
还要注意,添加与平台匹配的扩展,如果是windows,则用VK_KHR_WIN32_SURFACE_EXTENSION_NAME。当Vulkan实例被创建时,这些扩展被加载。
设备扩展
交换链是一个图片列表,用于GPU绘制和显示设备扫描输出。因为是GPU硬件负责写入图片,因此设备级扩展需要与交换链一起工作。所以在代码中需要将VK_KHR_SWAPCHAIN_EXTENSION_NAME加入设备扩展列表。
info.device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
关于实例和设备扩展的更详细的信息可以在LunarG LunarXchange website中找到。
队列族和Present(呈现?)
Present操作其实就是在交换链里面获取一张图片,并且显示在显示设备。当应用程序要在屏幕上显示一张图片时,应用程序调用vkQueuePresentKHR()函数将请求放入一个GPU队列。所以,此函数引用的队列必须能够支持呈现请求或者同时支持图形和Present请求。验证是否支持,可如下操作:
// Iterate over each queue to learn whether it supports presenting:
VkBool32 *pSupportsPresent = (VkBool32 *)malloc(info.queue_family_count * sizeof(VkBool32));
for (uint32_t i = 0; i < info.queue_family_count; i++) {
vkGetPhysicalDeviceSurfaceSupportKHR(info.gpus[0], i, info.surface, &pSupportsPresent[i]);
}
// Search for a graphics and a present queue in the array of queue
// families, try to find one that supports both
info.graphics_queue_family_index = UINT32_MAX;
info.present_queue_family_index = UINT32_MAX;
for (uint32_t i = 0; i < info.queue_family_count; ++i) {
if ((info.queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
if (info.graphics_queue_family_index == UINT32_MAX)
info.graphics_queue_family_index = i;
if (pSupportsPresent[i] == VK_TRUE) {
info.graphics_queue_family_index = i;
info.present_queue_family_index = i;
break;
}
}
}
if (info.present_queue_family_index == UINT32_MAX) {
// If didn't find a queue that supports both graphics and present, then
// find a separate present queue.
for (size_t i = 0; i < info.queue_family_count; ++i)
if (pSupportsPresent[i] == VK_TRUE) {
info.present_queue_family_index = i;
break;
}
}
free(pSupportsPresent);
交换链创建信息
交换链创建信息VkSwapchainCreateInfoKHR声明如下:
typedef struct VkSwapchainCreateInfoKHR {
VkStructureType sType;
const void* pNext;
VkSwapchainCreateFlagsKHR flags;
VkSurfaceKHR surface;
uint32_t minImageCount;
VkFormat imageFormat;
VkColorSpaceKHR imageColorSpace;
VkExtent2D imageExtent;
uint32_t imageArrayLayers;
VkImageUsageFlags imageUsage;
VkSharingMode imageSharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
VkSurfaceTransformFlagBitsKHR preTransform;
VkCompositeAlphaFlagBitsKHR compositeAlpha;
VkPresentModeKHR presentMode;
VkBool32 clipped;
VkSwapchainKHR oldSwapchain;
} VkSwapchainCreateInfoKHR;
创建Surface
在实例和设备中加载了WSI扩展之后,现在可以创建VkSurface,以便继续创建交换链。创建Surface必须区分平台,例如windows平台:
#ifdef _WIN32
VkWin32SurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = NULL;
createInfo.hinstance = info.connection;
createInfo.hwnd = info.window;
res = vkCreateWin32SurfaceKHR(info.inst, &createInfo, NULL, &info.surface);
#endif
info.connection和info.window可在例子里面找到。查看代码,发现,windows平台下,info.connection其实是应用程序实例句柄,info.window是窗口句柄。
接下来就是将创建好的surface加入交换链的创建信息结构中。
VkSwapchainCreateInfoKHR swapchain_ci = {};
swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_ci.pNext = NULL;
swapchain_ci.surface = info.surface;
设备Surface格式
创建交换链时必须设置Surface的像素格式。像素格式用VkFormat枚举出来。
接下来就是创建VkSurfaceFormatKHR列表,里面包含支持所创建的Surface的一些信息。
uint32_t formatCount;
res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.gpus[0], info.surface, &formatCount, NULL);
assert(res == VK_SUCCESS);
VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR));
res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.gpus[0], info.surface, &formatCount, surfFormats);
assert(res == VK_SUCCESS);
// If the format list includes just one entry of VK_FORMAT_UNDEFINED,
// the surface has no preferred format. Otherwise, at least one
// supported format will be returned.
if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) {
info.format = VK_FORMAT_B8G8R8A8_UNORM;
} else {
assert(formatCount >= 1);
info.format = surfFormats[0].format;
}
free(surfFormats);
Surface能力集
物理设备Surface能力集通过vkGetPhysicalDeviceSurfaceCapabilitiesKHR()函数获取和物理设备Surface呈现模式由vkGetPhysicalDeviceSurfacePresentModesKHR()获取。将获取到的信息设置到交换链创建信息:
uint32_t desiredNumberOfSwapChainImages = surfCapabilities.minImageCount;
swapchain_ci.minImageCount = desiredNumberOfSwapChainImages;
swapchain_ci.imageExtent.width = swapChainExtent.width;
swapchain_ci.imageExtent.height = swapChainExtent.height;
swapchain_ci.preTransform = preTransform;
swapchain_ci.presentMode = swapchainPresentMode;
minImageCount表示应用程序所用的缓冲策略,例如双缓冲和三缓冲。关于minImageCount的大小范围,可通过Surface能力集获取,即调用vkGetPhysicalDeviceSurfaceCapabilitiesKHR。
不同队列族的图形和呈现
如果支持图形的队列和支持呈现的队列不同,则需要做如下操作:
swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_ci.queueFamilyIndexCount = 0;
swapchain_ci.pQueueFamilyIndices = NULL;
uint32_t queueFamilyIndices[2] = {
(uint32_t)info.graphics_queue_family_index,
(uint32_t)info.present_queue_family_index};
if (info.graphics_queue_family_index != info.present_queue_family_index) {
// If the graphics and present queues are from different queue families,
// we either have to explicitly transfer ownership of images between the
// queues, or we have to create the swapchain with imageSharingMode
// as VK_SHARING_MODE_CONCURRENT
swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_ci.queueFamilyIndexCount = 2;
swapchain_ci.pQueueFamilyIndices = queueFamilyIndices;
}
创建交换链
当VkSwapchainCreateInfoKHR设置完毕,可直接创建交换链:
res = vkCreateSwapchainKHR(info.device, &swapchain_ci, NULL, &info.swap_chain);
创建完交换链之后,可通过如下方法获取交换链里面的图像缓存:
res = vkGetSwapchainImagesKHR(info.device, info.swap_chain, &info.swapchainImageCount, NULL);
assert(res == VK_SUCCESS);
VkImage *swapchainImages = (VkImage *)malloc(info.swapchainImageCount * sizeof(VkImage));
assert(swapchainImages);
res = vkGetSwapchainImagesKHR(info.device, info.swap_chain, &info.swapchainImageCount, swapchainImages);
assert(res == VK_SUCCESS);
创建Image视图
创建完交换链后,还需要创建Image视图,视图就是一些附加信息,用于告诉Vulkan如何使用交换链。
视图可以定义图像内存的存储方式,比如RGBA的排列方式,以及维度等。例如,图像可以是1D、2D和3D。创建视图的代码如下:
for (uint32_t i = 0; i < info.swapchainImageCount; i++) {
VkImageViewCreateInfo color_image_view = {};
color_image_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
color_image_view.pNext = NULL;
color_image_view.flags = 0;
color_image_view.image = info.buffers[i].image;
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.format = info.format;
color_image_view.components.r = VK_COMPONENT_SWIZZLE_R;
color_image_view.components.g = VK_COMPONENT_SWIZZLE_G;
color_image_view.components.b = VK_COMPONENT_SWIZZLE_B;
color_image_view.components.a = VK_COMPONENT_SWIZZLE_A;
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
color_image_view.subresourceRange.layerCount = 1;
res = vkCreateImageView(info.device, &color_image_view, NULL, &info.buffers[i].view);
assert(res == VK_SUCCESS);
}