Vulkan 开发的系列文章:
在之前的文章中,讲到了 Command-Buffer
提交给 Queue
去执行,也提到了 Vulkan 实现跨平台机制,是有一些拓展的,这里就讲讲 Vulkan 窗口系统的拓展(Vulkan Window System Integration WSI),如下图所示:
回顾一下在 Android 中我们是需要 EGL
机制为 OpenGL ES 建立渲染环境,好让 OpenGL ES 渲染后的结果可以呈现在 Surface 上,而 iOS 上肯定是没有 EGL
机制的,当然它肯定也有其他方法呈现 OpenGL ES 渲染结果。
为了实现这一机制的统一,做到跨平台,Vulkan 就把这部分抽出来做成了一个拓展的形式,实现了接口调用上的统一,那么接下来就一起了解一下 Vulkan 的 SwapChain 交换链机制。
开启 SwapChain 的拓展
还记得在 进击的 Vulkan 移动开发之 Instance & Device & Queue 文章中创建 Instance
、Device
组件时的 VkXXXXCreateInfo
结构体中都有表示拓展的一些字段,之前就没有用到就直接忽略了,现在就得开启这个拓展来启用 SwapChain
,需要分别为 Instance
和 Device
指定拓展。
1 // 定义两个容器来 指定对应的 拓展
2 // instance 的 extension 拓展
3 std::vector<const char *> instance_extension_names;
4 // device 的 extension 拓展
5 std::vector<const char *> device_extension_names;
6
7 // 初始化 Instance 和 Device 的拓展
8 void vulkan_init_instance_extension_name(struct vulkan_tutorial_info &info) {
9 info.instance_extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
10 info.instance_extension_names.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
11 }
12
13 void vulkan_init_device_extension_name(struct vulkan_tutorial_info &info) {
14 info.device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
15 }
要注意,这里的拓展,其实是对应的宏所代表的字符串数组指针,并不是要搞什么特殊的标志位之类的,而且这些字符串宏在都在 vulkan.h
和 vulkan_android.h
头文件中都包含了。
如果是在 Android 平台上,那么就照着上面的写就好了,并不需要修改什么了,都是固定的。
另外,在创建 Instance
组件的时候就可以启用这些拓展了:
1 VkInstanceCreateInfo instance_info = {};
2
3 // Extension and Layer
4 instance_info.enabledExtensionCount = info.instance_extension_names.size();
5 instance_info.ppEnabledExtensionNames = info.instance_extension_names.data();
6 // 这里没有用到 Layer 就还是设为 null 就好了
7 instance_info.ppEnabledLayerNames = nullptr;
8 instance_info.enabledLayerCount = 0;
接下来在 VkInstanceCreateInfo
结构中去声明要启用拓展。
1 VkInstanceCreateInfo instance_info = {};
2
3 instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
4 instance_info.pNext = nullptr;
5 instance_info.pApplicationInfo = &app_info;
6 instance_info.flags = 0;
7 // 在这里启用拓展相关设定
8 instance_info.enabledExtensionCount = info.instance_extension_names.size();
9 instance_info.ppEnabledExtensionNames = info.instance_extension_names.data();
10 // 这里没有用到 Layer 就还是设为 null 就好了
11 instance_info.ppEnabledLayerNames = nullptr;
12 instance_info.enabledLayerCount = 0;
创建 SwapChain 的步骤
创建 SwapChain 有的基本步骤如下:
- 创建 VkSurfaceKHR 组件,解决平台之间的差异。在 Android 上就是根据 Native 中的
ANativeWindow
去创建。 - 从某个物理设备 (也就是 GPU,因为可能存在多个物理设备) 所有的
Queue
中找到那个即支持图形(graphics)又支持显示(present)的Queue
的索引(index)。 - 如果没有
Queue
同时支持两者,那么就找到两个各自支持的,分别是:- present queue(用于展示的 Queue)
- graphics queue(用于图形的 Queue)
- 有了这两个索引之后,要得到索引所对应的 Queue 。
- 如果连各自支持的都没有,那 SwapChain 也建立不了了,干脆就退出吧。
- 从某个物理设备 (也就是 GPU,因为可能存在多个物理设备) 找到所有支持 VKSurfaceKHR 的色彩空间格式(VKFormat),并选取第一个。
- 根据 VKSurfaceKHR 的能力和呈现模式,以及相关参数设定去创建 SwapChain 。
- Surface 能力对应 SurfaceCapabilitiesKHR
- Surface 呈现模式对应于 SurfacePresenttModesKHR
- Surface 旋转的设定,对应于 SurfaceTransformFlagBitsKHR
- Surface 透明度合成的设定,对应于 CompositeAlphaFlagBitsKHT
- Surface 相关的参数设定有很多,但是对于有些不常用的设定基本可以选择固定值了
- 相关参数的设定都明确之后,就创建 SwapChain
- 创建 SwapChain 之后,获取 SwapChain 支持的 Image 对象列表以及个数,并创建相应数量的 ImageView 数量。
现在,对每一个步骤来添加它的代码实现。
创建 VkSurfaceKHR
在开始之前,要根据 Android 上的 Surface 来创建 Vulkan 中的 VkSurfaceKHR
。
在 Vulkan 中,后缀是 KHR 的大多是拓展功能里面的。
Android 的 Surface 在 Native 中对应的是 ANativeWindow
,根据它创建 VkSurfaceKHR
。
1 // 宏定义的函数
2 GET_INSTANCE_PROC_ADDR(info.instance, CreateAndroidSurfaceKHR);
3 VkAndroidSurfaceCreateInfoKHR createInfo{};
4 createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
5 createInfo.pNext = nullptr;
6 createInfo.flags = 0;
7 // window 参数就是 ANativeWindow
8 createInfo.window = window;
9 // fpCreateAndroidSurfaceKHR 其实是一个函数指针
10 VkResult res = info.fpCreateAndroidSurfaceKHR(info.instance, &createInfo, nullptr, &info.surface);
通过 VkSurfaceKHR
相当于就屏蔽了不同平台上的实现,统一了调用对象。
要注意一个调用方式:在 fpCreateAndroidSurfaceKHR
函数执行之前,要运行 GET_INSTANCE_PROC_ADDR
宏函数,它的定义如下:
1#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
2 { \
3 info.fp##entrypoint = \
4 (PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk" #entrypoint); \
5 if (info.fp##entrypoint == nullptr) { \
6 LOGE("entry point is null"); \
7 } \
8 }
这段宏函数的作用就是将 fpCreateAndroidSurfaceKHR
的函数指针指向具体的地址,不然就会报 null pointer exception 。
present queue 和 graphics queue 的索引
接下来就是从所有的 Queue
中找到 present queue
和 graphics queue
的索引。
1 // 先将所有的 index 都初始化到一个最大值,好用于后续的判断过程
2 info.graphics_queue_family_index = UINT32_MAX;
3 info.present_queue_family_index = UINT32_MAX;
这里是要找到两个索引,那么就先初始化到最大值,好用于后续的比较过程。
接下来从所有的 Queue
找到支持 呈现(present)
的 。
1 // 分配个数为 queue size 大小的数组,数组元素是 VkBool32 类型
2 VkBool32 *supportPresent = static_cast<VkBool32 *>(malloc(info.queue_family_size * sizeof(VkBool32)));
3 // queue_family_size 为所有 queue 的个数
4 for (uint32_t i = 0; i < info.queue_family_size; i++) {
5 // physical device 的 queue 是否支持 surface
6 vkGetPhysicalDeviceSurfaceSupportKHR(info.gpu_physical_devices[0], i, info.surface, &supportPresent[i]);
7 }
通过 vkGetPhysicalDeviceSurfaceSupportKHR
函数判断该物理设备的某个 Queue
是否支持上面创建的 VkSurfaceKHR
,也就是该 Queue 是否支持 呈现(present)
,并把结果记录到 supportPresent
数组中。
至于判断该 Queue 是否支持 图像(Graphics)
,在前面的文章中已经提过了,只要判断 queueFlags
与 VK_QUEUE_GRAPHICS_BIT
相与 &
的结果就好了。
1 for (uint32_t i = 0; i < info.queue_family_size; ++i) {
2 // queue 是否图像,通过 queueFlags 来判断
3 if ((info.queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
4 if (info.graphics_queue_family_index == UINT32_MAX) {
5 info.graphics_queue_family_index = i;
6 }
7
8 // 找到了即支持 graphics 也支持 present 的 queue index
9 if (supportPresent[i] == VK_TRUE) {
10 info.graphics_queue_family_index = i;
11 info.present_queue_family_index = i;
12 break;
13 }
14 }
15 }
在查找 Queue
是否支持 图像(Graphics)
的同时,也顺便判断一下该 Queue
是否支持 呈现(present)
,如果两者都满足,那是最好了。
1 // 没有找到一个 queue 两者都支持,那么找到一个支持 present 的 queue index
2 if (info.present_queue_family_index == UINT32_MAX) {
3 for (size_t i = 0; i < info.queue_family_size; ++i) {
4 if (supportPresent[i] == VK_TRUE) {
5 info.present_queue_family_index = i;
6 break;
7 }
8 }
9 }
10 // 释放创建的数组,及时释放,后续用不到了
11 free(supportPresent);
如果 present_queue_family_index
仍为初始值,那说明没有 Queue
同时满足两者,两个 index 只能各取不同的值了。
1 // 如果都没找到,就报错了
2 if (info.graphics_queue_family_index == UINT32_MAX || info.present_queue_family_index == UINT32_MAX) {
3 LOGE("could not find a queue for graphics and present");
4 }
如果都为初始值,那么就可以报错了。
另外要注意的是,之前创建 Device 需要支持 图像(Graphics)
的队列索引,而现在可以把创建 Device 和 Queue 的工作放到完成 Present
和 Graphics
索引检索之后了。
查找 VkFormat 格式
接下来要查询物理设备所支持的 SurfaceFormat
格式,也就是用来绘制的 Surface 支持哪些 色彩空间格式(ColorSpace)
,查询的操作调用还是 Vulkan 的固定套路:
1 // 先获得数量
2 uint32_t formatCount;
3 res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.gpu_physical_devices[0], info.surface, &formatCount, nullptr);
4 // 再获得具体内容 VkSurfaceFormatKHR
5 VkSurfaceFormatKHR *surfaceFormats = static_cast<VkSurfaceFormatKHR *>(malloc(
6 formatCount * sizeof(VkSurfaceFormatKHR)));
7 // 获得物理设备的 SurfaceFormat
8 res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.gpu_physical_devices[0], info.surface, &formatCount,
9 surfaceFormats);
SurfaceFormat
结构体包含了如下的信息:
1 typedef struct VkSurfaceFormatKHR {
2 VkFormat format;
3 VkColorSpaceKHR colorSpace;
4 } VkSurfaceFormatKHR;
主要就是 VkFormat
和 VkColorSpaceKHR
两个属性。
接下来是对获取的格式做处理,主要是得到 VkFormat
格式信息:
1 if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) {
2 info.format = VK_FORMAT_B8G8R8A8_UNORM;
3 } else {
4 assert(formatCount >= 1);
5 info.format = surfFormats[0].format;
6 }
7 free(surfFormats);
如果支持的格式数量只有一个,并且它还是 VK_FORMAT_UNDEFINED
,说明 Surface 并没有一个首选的格式,此时可以使用任何一个有效的格式。
如果数量大于一个,那使用第一个就好了。
VkSurfaceCapabilitiesKHR 和 VkPresentModeKHR 以及相关参数的设定
除了 Surface 的 VkFormat
格式 ,还需要查询它的能力(Capabilities)
,得到 VkSurfaceCapabilitiesKHR
对象。
1 VkSurfaceCapabilitiesKHR surfaceCapabilitiesKHR;
2 res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(info.gpu_physical_devices[0], info.surface,&surfaceCapabilitiesKHR);
VkSurfaceCapabilitiesKHR
对象包含了很多属性,如下:
1typedef struct VkSurfaceCapabilitiesKHR {
2 // 交换链 SwapChain 能够创建的最小的 Image 数量,最少是 1
3 uint32_t minImageCount;
4 // 交换链 SwapChain 能够创建的最大的 Image 数量
5 uint32_t maxImageCount;
6 // Surface 当前的宽和高,如果宽高都是是 0xFFFFFFFF,表明宽高由交换链拓展的目标 Surface 来决定
7 VkExtent2D currentExtent;
8 // 最小的
9 VkExtent2D minImageExtent;
10 VkExtent2D maxImageExtent;
11 uint32_t maxImageArrayLayers;
12 // 支持的旋转模式
13 VkSurfaceTransformFlagsKHR supportedTransforms;
14 // 当前的旋转模式
15 VkSurfaceTransformFlagBitsKHR currentTransform;
16 // Surface 透明度的设定
17 VkCompositeAlphaFlagsKHR supportedCompositeAlpha;
18 VkImageUsageFlags supportedUsageFlags;
19} VkSurfaceCapabilitiesKHR;
当我们创建 SwapChain 的时候,需要设定很多的参数,比如 Surface 旋转和透明度等,而这些参数就要考虑到 VkSurfaceCapabilitiesKHR
是不是支持了,这也是为什么要查询 SurfaceCapabilities 的原因。
关于 Surface 透明度合成的设定
关于 Surface 透明度合成的模式,在 Vulkan 中定义了如下几种模式:
1typedef enum VkCompositeAlphaFlagBitsKHR {
2 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,
3 VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR = 0x00000002,
4 VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR = 0x00000004,
5 VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR = 0x00000008,
6 VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
7} VkCompositeAlphaFlagBitsKHR;
我们要根据 VkSurfaceCapabilitiesKHR
的支持能力去选择最合适的。
1 // 预定义 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR 模式
2 VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
3 // 在以下四种模式去选择 VkSurfaceCapabilitiesKHR 所支持的
4 VkCompositeAlphaFlagBitsKHR compositeAlphaFlags[4] = {
5 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
6 VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
7 VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
8 VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
9 };
10 // 通过相与 & 的操作去判断,找到合适的就退出
11 for (uint32_t i = 0; i < sizeof(compositeAlphaFlags); i++) {
12 if (surfaceCapabilitiesKHR.supportedCompositeAlpha & compositeAlphaFlags[i]) {
13 compositeAlpha = compositeAlphaFlags[i];
14 break;
15 }
16 }
关于 Surface 旋转的设定
关于 Surface 的旋转模式,在 Vulkan 中也定义了如下几种模式:
1typedef enum VkSurfaceTransformFlagBitsKHR {
2 // 不旋转
3 VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001,
4 // 旋转 90°
5 VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002,
6 // 旋转 180°
7 VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004,
8 // 旋转 270°
9 VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR = 0x00000008,
10 // 水平镜像
11 VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR = 0x00000010,
12 VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR = 0x00000020,
13 VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR = 0x00000040,
14 VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR = 0x00000080,
15 VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR = 0x00000100,
16 VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
17} VkSurfaceTransformFlagBitsKHR;
同样也要根据 VkSurfaceCapabilitiesKHR
的支持能力去选择最合适的。
1 // 如果 Surface 支持不旋转,那么就不旋转,否则使用当前的旋转模式
2 VkSurfaceTransformFlagBitsKHR preTransform;
3 if (surfaceCapabilitiesKHR.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
4 preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
5 } else {
6 preTransform = surfaceCapabilitiesKHR.currentTransform;
7 }
接下来是获得 Surface
的呈现模式 VkPresentModeKHR
。
1 // 呈现模式个数
2 uint32_t presentModeCount;
3 // 先获取数量
4 res = vkGetPhysicalDeviceSurfacePresentModesKHR(info.gpus[0], info.surface, &presentModeCount, NULL);
5 assert(res == VK_SUCCESS);
6 // 再获取具体值
7 VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR));
8 res = vkGetPhysicalDeviceSurfacePresentModesKHR(info.gpus[0], info.surface, &presentModeCount, presentModes);
9 assert(res == VK_SUCCESS);
关于 Surface 呈现模式的设定
关于 Surface 的呈现模式,在 Vulkan 中定义了很多种:
1typedef enum VkPresentModeKHR {
2 VK_PRESENT_MODE_IMMEDIATE_KHR = 0,
3 VK_PRESENT_MODE_MAILBOX_KHR = 1,
4 VK_PRESENT_MODE_FIFO_KHR = 2,
5 VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3,
6 VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR = 1000111000,
7 VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR = 1000111001,
8 VK_PRESENT_MODE_BEGIN_RANGE_KHR = VK_PRESENT_MODE_IMMEDIATE_KHR,
9 VK_PRESENT_MODE_END_RANGE_KHR = VK_PRESENT_MODE_FIFO_RELAXED_KHR,
10 VK_PRESENT_MODE_RANGE_SIZE_KHR = (VK_PRESENT_MODE_FIFO_RELAXED_KHR - VK_PRESENT_MODE_IMMEDIATE_KHR + 1),
11 VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
12} VkPresentModeKHR;
一般来说,我们就只要使用 VK_PRESENT_MODE_FIFO_KHR
模式就行了。
1 VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
当然,也可以通过 vkGetPhysicalDeviceSurfacePresentModesKHR
函数得到所有的呈现模式,通过判断看自己想要的模式是否支持。
除了上面,还有其他很多参数设定,就不一一的阐述了,在创建 SwapChain 的时候都会遇到的。
SwapChain 创建
完成了相关的准备工作之后,就可以调用 vkCreateSwapchainKHR
来创建 SwapChain 了。在此之前,还需要创建 VkSwapchainCreateInfoKHR
对象来包含所需要的参数。
VkSwapchainCreateInfoKHR
定义了很多的相关参数,有的是上面讨论过的,有的没有讨论但是一般都是用固定值了,想要了解的话,也可以查阅官方的文档。
1typedef struct VkSwapchainCreateInfoKHR {
2 VkStructureType sType;
3 const void* pNext;
4 VkSwapchainCreateFlagsKHR flags;
5 VkSurfaceKHR surface;
6 uint32_t minImageCount;
7 // format 格式,上面讨论过的
8 VkFormat imageFormat;
9 // colorspace 颜色空间,
10 VkColorSpaceKHR imageColorSpace;
11 VkExtent2D imageExtent;
12 uint32_t imageArrayLayers;
13 VkImageUsageFlags imageUsage;
14 VkSharingMode imageSharingMode;
15 uint32_t queueFamilyIndexCount;
16 const uint32_t* pQueueFamilyIndices;
17 // 旋转模式
18 VkSurfaceTransformFlagBitsKHR preTransform;
19 // 透明度合成模式
20 VkCompositeAlphaFlagBitsKHR compositeAlpha;
21 // 呈现模式
22 VkPresentModeKHR presentMode;
23 VkBool32 clipped;
24 VkSwapchainKHR oldSwapchain;
25} VkSwapchainCreateInfoKHR;
具体的代码和相关参数的设定如下:
1 // 包含了创建 SwapChain 所需要的信息
2 VkSwapchainCreateInfoKHR swapchain_ci = {};
3 swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
4 swapchain_ci.pNext = NULL;
5 swapchain_ci.surface = info.surface;
6 // swapchain 最小的 image 数量
7 // 这里就使用了 VkSurfaceCapabilitiesKHR 的最小数量
8 swapchain_ci.minImageCount = desiredNumberOfSwapChainImages;
9 // Image 的格式
10 swapchain_ci.imageFormat = info.format;
11 // Image 的宽高信息
12 swapchain_ci.imageExtent.width = swapchainExtent.width;
13 swapchain_ci.imageExtent.height = swapchainExtent.height;
14 // 旋转模式
15 swapchain_ci.preTransform = preTransform;
16 // alpha 混合模式
17 swapchain_ci.compositeAlpha = compositeAlpha;
18 swapchain_ci.imageArrayLayers = 1;
19 // 呈现模式
20 swapchain_ci.presentMode = swapchainPresentMode;
21 swapchain_ci.oldSwapchain = VK_NULL_HANDLE;
22 swapchain_ci.clipped = true;
23 // Image 颜色空间
24 swapchain_ci.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
25 // Image 的用途
26 swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
27 swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
28 // 设定 swapchain 需要的 Queue 信息
29 swapchain_ci.queueFamilyIndexCount = 0;
30 swapchain_ci.pQueueFamilyIndices = NULL;
31 // 如果图像和显示的 Queue 索引不一致,就要设置 Image 为共享模式
32 uint32_t queueFamilyIndices[2] = {(uint32_t) info.graphics_queue_family_index,
33 (uint32_t) info.present_queue_family_index};
34 if (info.graphics_queue_family_index != info.present_queue_family_index) {
35 // If the graphics and present queues are from different queue families,
36 // we either have to explicitly transfer ownership of images between
37 // the queues, or we have to create the swapchain with imageSharingMode
38 // as VK_SHARING_MODE_CONCURRENT
39 swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
40 swapchain_ci.queueFamilyIndexCount = 2;
41 swapchain_ci.pQueueFamilyIndices = queueFamilyIndices;
42 }
43 // 创建 SwapChain 需要用到 Device,创建 Device 又需要用到 图像队列 的索引值
44 res = vkCreateSwapchainKHR(info.device, &swapchain_ci, NULL, &info.swap_chain);
45 // 判断创建是否成功
46 assert(res == VK_SUCCESS);
由此,就完成了 SwapChain 的创建。
可以看到创建 SwapChain 在前期还是需要不少的准备工作,猜想是因为 Vulkan 要做到跨平台,所以就会把很多硬件方面的设定暴露出来,导致在 API 层面上更接近于硬件底层,开发者在使用时也要了解更多的细节。比如有一些参数设定有很多个选项,但针对某一特定平台,只有两三个选项是有效的,其他选项是为了给其他平台的。
但是,只要我们完成了一次创建,相对于代码模块来说,就不会再有太多的变化了。把这块做好封装之后,在后续开发中直接复用,把精力专注于更加有意思的方面~~~
SwapChain 的销毁
当程序运行结束后,就可以通过 vkDestroySwapchainKHR
函数来销毁 SwapChain 了,使用起来就和销毁其他组件一样,不再赘述了。
参考
文章中的代码地址,具体可以参考我的 Github 项目:
总结
比较长的篇幅介绍了 SwapChain 的创建,尤其是创建过程中的一些参数设定,更多的要去理解它的意图。
SwapChain 顾名思义就是交换链,那么拿什么去交换呢?这就是后续内容中会讲到的 Image
对象。可以先简单理解成从 SwapChain 中申请一个 Image,然后对它进行渲染绘制,之后再把它放到 SwapChain 中去渲染呈现。
等我继续更新下去,你就会看到的~~~
原创文章,转载请注明来源: 进击的 Vulkan 移动开发之 SwapChain
相关文章
- 进击的 Vulkan 移动开发之 Command Buffer
- 进击的 Vulkan 移动开发之 Instance & Device & Queue
- 进击的 Vulkan 移动开发(一)之今生前世
- Java 显式锁 Lock 与条件队列
- C++ 标准容器库小结
留言