进击的 Vulkan 移动开发之 SwapChain

简介: 在之前的文章中,讲到了 Command-Buffer 提交给 Queue 去执行,也提到了 Vulkan 实现跨平台机制,是有一些拓展的,这里就讲讲 Vulkan 窗口系统的拓展(Vulkan Window System Integration WSI),如下图所示:

作者:星陨

来源:音视频开发进阶


image.png

回顾一下在 Android 中我们是需要 EGL 机制为 OpenGL ES 建立渲染环境,好让 OpenGL ES 渲染后的结果可以呈现在 Surface 上,而 iOS 上肯定是没有 EGL 机制的,当然它肯定也有其他方法呈现 OpenGL ES 渲染结果。

为了实现这一机制的统一,做到跨平台,Vulkan 就把这部分抽出来做成了一个拓展的形式,实现了接口调用上的统一,那么接下来就一起了解一下 Vulkan 的 SwapChain 交换链机制。

开启 SwapChain 的拓展

还记得在 进击的 Vulkan 移动开发之 Instance & Device & Queue 文章中创建 InstanceDevice 组件时的 VkXXXXCreateInfo 结构体中都有表示拓展的一些字段,之前就没有用到就直接忽略了,现在就得开启这个拓展来启用 SwapChain ,需要分别为 InstanceDevice 指定拓展。

// 定义两个容器来 指定对应的 拓展
    // instance 的 extension 拓展
    std::vector<const char *> instance_extension_names;
    // device 的 extension 拓展
    std::vector<const char *> device_extension_names;
    // 初始化 Instance 和 Device 的拓展
    void vulkan_init_instance_extension_name(struct vulkan_tutorial_info &info) {
        info.instance_extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
        info.instance_extension_names.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
    }
    void vulkan_init_device_extension_name(struct vulkan_tutorial_info &info) {
        info.device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
    }

要注意,这里的拓展,其实是对应的宏所代表的字符串数组指针,并不是要搞什么特殊的标志位之类的,而且这些字符串宏在都在 vulkan.hvulkan_android.h 头文件中都包含了。

如果是在 Android 平台上,那么就照着上面的写就好了,并不需要修改什么了,都是固定的。

另外,在创建 Instance 组件的时候就可以启用这些拓展了:

VkInstanceCreateInfo instance_info = {};
    // Extension and Layer
    instance_info.enabledExtensionCount = info.instance_extension_names.size();
    instance_info.ppEnabledExtensionNames = info.instance_extension_names.data();
    // 这里没有用到 Layer 就还是设为 null 就好了
    instance_info.ppEnabledLayerNames = nullptr;
    instance_info.enabledLayerCount = 0;

接下来在 VkInstanceCreateInfo 结构中去声明要启用拓展。

VkInstanceCreateInfo instance_info = {};
    instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    instance_info.pNext = nullptr;
    instance_info.pApplicationInfo = &app_info;
    instance_info.flags = 0;
    // 在这里启用拓展相关设定
    instance_info.enabledExtensionCount = info.instance_extension_names.size();
    instance_info.ppEnabledExtensionNames = info.instance_extension_names.data();
    // 这里没有用到 Layer 就还是设为 null 就好了
    instance_info.ppEnabledLayerNames = nullptr;
    instance_info.enabledLayerCount = 0;

创建 SwapChain 的步骤

创建 SwapChain 有的基本步骤如下:

  1. 创建 VkSurfaceKHR 组件,解决平台之间的差异。在 Android 上就是根据 Native 中的 ANativeWindow 去创建。
  2. 从某个物理设备 (也就是 GPU,因为可能存在多个物理设备) 所有的 Queue 中找到那个即支持图形(graphics)又支持显示(present)的 Queue 的索引(index)。
  3. 如果没有Queue同时支持两者,那么就找到两个各自支持的,分别是:
  1. present queue(用于展示的 Queue)
  2. graphics queue(用于图形的 Queue)
  3. 有了这两个索引之后,要得到索引所对应的 Queue 。
  1. 如果连各自支持的都没有,那 SwapChain 也建立不了了,干脆就退出吧。
  2. 从某个物理设备 (也就是 GPU,因为可能存在多个物理设备) 找到所有支持 VKSurfaceKHR 的色彩空间格式(VKFormat),并选取第一个。
  3. 根据 VKSurfaceKHR 的能力和呈现模式,以及相关参数设定去创建 SwapChain 。
  1. Surface 能力对应 SurfaceCapabilitiesKHR
  2. Surface 呈现模式对应于 SurfacePresenttModesKHR
  3. Surface 旋转的设定,对应于 SurfaceTransformFlagBitsKHR
  4. Surface 透明度合成的设定,对应于 CompositeAlphaFlagBitsKHT
  5. Surface 相关的参数设定有很多,但是对于有些不常用的设定基本可以选择固定值了
  6. 相关参数的设定都明确之后,就创建 SwapChain
  1. 创建 SwapChain 之后,获取 SwapChain 支持的 Image 对象列表以及个数,并创建相应数量的 ImageView 数量。

现在,对每一个步骤来添加它的代码实现。

创建 VkSurfaceKHR

在开始之前,要根据 Android 上的 Surface 来创建 Vulkan 中的 VkSurfaceKHR

在 Vulkan 中,后缀是 KHR 的大多是拓展功能里面的。

Android 的 Surface 在 Native 中对应的是 ANativeWindow,根据它创建 VkSurfaceKHR

// 宏定义的函数
    GET_INSTANCE_PROC_ADDR(info.instance, CreateAndroidSurfaceKHR);
    VkAndroidSurfaceCreateInfoKHR createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
    createInfo.pNext = nullptr;
    createInfo.flags = 0;
    // window 参数就是 ANativeWindow
    createInfo.window = window;
    // fpCreateAndroidSurfaceKHR 其实是一个函数指针
    VkResult res = info.fpCreateAndroidSurfaceKHR(info.instance, &createInfo, nullptr, &info.surface);

通过 VkSurfaceKHR 相当于就屏蔽了不同平台上的实现,统一了调用对象。

要注意一个调用方式:在 fpCreateAndroidSurfaceKHR 函数执行之前,要运行 GET_INSTANCE_PROC_ADDR 宏函数,它的定义如下:

#define GET_INSTANCE_PROC_ADDR(inst, entrypoint)                               \
    {                                                                          \
        info.fp##entrypoint =                                                  \
            (PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk" #entrypoint); \
        if (info.fp##entrypoint == nullptr) {                                  \
            LOGE("entry point is null");                                       \
        }                                                                      \
    }

这段宏函数的作用就是将 fpCreateAndroidSurfaceKHR 的函数指针指向具体的地址,不然就会报 null pointer exception 。

present queue 和 graphics queue 的索引

接下来就是从所有的 Queue 中找到 present queuegraphics queue 的索引。

// 先将所有的 index 都初始化到一个最大值,好用于后续的判断过程
    info.graphics_queue_family_index = UINT32_MAX;
    info.present_queue_family_index = UINT32_MAX;

这里是要找到两个索引,那么就先初始化到最大值,好用于后续的比较过程。

接下来从所有的 Queue 找到支持 呈现(present) 的 。

// 分配个数为 queue size 大小的数组,数组元素是 VkBool32 类型
    VkBool32 *supportPresent = static_cast<VkBool32 *>(malloc(info.queue_family_size * sizeof(VkBool32)));
    // queue_family_size 为所有 queue 的个数
    for (uint32_t i = 0; i < info.queue_family_size; i++) {
        // physical device 的 queue 是否支持 surface
        vkGetPhysicalDeviceSurfaceSupportKHR(info.gpu_physical_devices[0], i, info.surface, &supportPresent[i]);
    }

通过 vkGetPhysicalDeviceSurfaceSupportKHR 函数判断该物理设备的某个 Queue 是否支持上面创建的 VkSurfaceKHR ,也就是该 Queue 是否支持 呈现(present),并把结果记录到 supportPresent 数组中。

至于判断该 Queue 是否支持 图像(Graphics),在前面的文章中已经提过了,只要判断 queueFlagsVK_QUEUE_GRAPHICS_BIT 相与 & 的结果就好了。

for (uint32_t i = 0; i < info.queue_family_size; ++i) {
        // queue 是否图像,通过 queueFlags 来判断
        if ((info.queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
            if (info.graphics_queue_family_index == UINT32_MAX) {
                info.graphics_queue_family_index = i;
            }
            // 找到了即支持 graphics 也支持 present 的 queue index
            if (supportPresent[i] == VK_TRUE) {
                info.graphics_queue_family_index = i;
                info.present_queue_family_index = i;
                break;
            }
        }
    }

在查找 Queue 是否支持 图像(Graphics) 的同时,也顺便判断一下该 Queue 是否支持 呈现(present) ,如果两者都满足,那是最好了。

// 没有找到一个 queue 两者都支持,那么找到一个支持 present 的 queue  index
    if (info.present_queue_family_index == UINT32_MAX) {
        for (size_t i = 0; i < info.queue_family_size; ++i) {
            if (supportPresent[i] == VK_TRUE) {
                info.present_queue_family_index = i;
                break;
            }
        }
    }
    // 释放创建的数组,及时释放,后续用不到了
    free(supportPresent);

如果 present_queue_family_index 仍为初始值,那说明没有 Queue 同时满足两者,两个 index 只能各取不同的值了。

// 如果都没找到,就报错了
    if (info.graphics_queue_family_index == UINT32_MAX || info.present_queue_family_index == UINT32_MAX) {
        LOGE("could not find a queue for graphics and present");
    }

如果都为初始值,那么就可以报错了。

另外要注意的是,之前创建 Device 需要支持 图像(Graphics) 的队列索引,而现在可以把创建 Device 和 Queue 的工作放到完成 PresentGraphics 索引检索之后了。

查找 VkFormat 格式

接下来要查询物理设备所支持的 SurfaceFormat 格式,也就是用来绘制的 Surface 支持哪些 色彩空间格式(ColorSpace) ,查询的操作调用还是 Vulkan 的固定套路:

// 先获得数量
    uint32_t formatCount;
    res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.gpu_physical_devices[0], info.surface, &formatCount, nullptr);
    // 再获得具体内容 VkSurfaceFormatKHR
    VkSurfaceFormatKHR *surfaceFormats = static_cast<VkSurfaceFormatKHR *>(malloc(
            formatCount * sizeof(VkSurfaceFormatKHR)));
    // 获得物理设备的 SurfaceFormat
    res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.gpu_physical_devices[0], info.surface, &formatCount,
                                               surfaceFormats);

SurfaceFormat 结构体包含了如下的信息:

typedef struct VkSurfaceFormatKHR {
        VkFormat           format;
        VkColorSpaceKHR    colorSpace;
    } VkSurfaceFormatKHR;

主要就是 VkFormatVkColorSpaceKHR 两个属性。

接下来是对获取的格式做处理,主要是得到 VkFormat 格式信息:

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);

如果支持的格式数量只有一个,并且它还是 VK_FORMAT_UNDEFINED ,说明 Surface 并没有一个首选的格式,此时可以使用任何一个有效的格式。

如果数量大于一个,那使用第一个就好了。

VkSurfaceCapabilitiesKHR 和 VkPresentModeKHR 以及相关参数的设定

除了 Surface 的 VkFormat 格式 ,还需要查询它的能力(Capabilities) ,得到 VkSurfaceCapabilitiesKHR 对象。

VkSurfaceCapabilitiesKHR surfaceCapabilitiesKHR;
    res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(info.gpu_physical_devices[0], info.surface,&surfaceCapabilitiesKHR);

VkSurfaceCapabilitiesKHR 对象包含了很多属性,如下:

typedef struct VkSurfaceCapabilitiesKHR {
    // 交换链 SwapChain 能够创建的最小的 Image 数量,最少是 1
    uint32_t                         minImageCount;
    // 交换链 SwapChain 能够创建的最大的 Image 数量
    uint32_t                         maxImageCount;
    // Surface 当前的宽和高,如果宽高都是是 0xFFFFFFFF,表明宽高由交换链拓展的目标 Surface 来决定
    VkExtent2D                       currentExtent;
    // 最小的
    VkExtent2D                       minImageExtent;
    VkExtent2D                       maxImageExtent;
    uint32_t                         maxImageArrayLayers;
    // 支持的旋转模式
    VkSurfaceTransformFlagsKHR       supportedTransforms;
    // 当前的旋转模式
    VkSurfaceTransformFlagBitsKHR    currentTransform;
    // Surface 透明度的设定
    VkCompositeAlphaFlagsKHR         supportedCompositeAlpha;
    VkImageUsageFlags                supportedUsageFlags;
} VkSurfaceCapabilitiesKHR;

当我们创建 SwapChain 的时候,需要设定很多的参数,比如 Surface 旋转和透明度等,而这些参数就要考虑到 VkSurfaceCapabilitiesKHR 是不是支持了,这也是为什么要查询 SurfaceCapabilities 的原因。

关于 Surface 透明度合成的设定

关于 Surface 透明度合成的模式,在 Vulkan 中定义了如下几种模式:

typedef enum VkCompositeAlphaFlagBitsKHR {
    VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,
    VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR = 0x00000002,
    VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR = 0x00000004,
    VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR = 0x00000008,
    VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
} VkCompositeAlphaFlagBitsKHR;

我们要根据 VkSurfaceCapabilitiesKHR 的支持能力去选择最合适的。

// 预定义 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR 模式
    VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    // 在以下四种模式去选择 VkSurfaceCapabilitiesKHR 所支持的
    VkCompositeAlphaFlagBitsKHR compositeAlphaFlags[4] = {
            VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
            VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
            VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
            VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
    };
    // 通过相与 & 的操作去判断,找到合适的就退出
    for (uint32_t i = 0; i < sizeof(compositeAlphaFlags); i++) {
        if (surfaceCapabilitiesKHR.supportedCompositeAlpha & compositeAlphaFlags[i]) {
            compositeAlpha = compositeAlphaFlags[i];
            break;
        }
    }

关于 Surface 旋转的设定

关于 Surface 的旋转模式,在 Vulkan 中也定义了如下几种模式:

typedef enum VkSurfaceTransformFlagBitsKHR {
    // 不旋转
    VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001,
    // 旋转 90°
    VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002,
    // 旋转 180°
    VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004,
    // 旋转 270°
    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 的支持能力去选择最合适的。

// 如果 Surface 支持不旋转,那么就不旋转,否则使用当前的旋转模式
    VkSurfaceTransformFlagBitsKHR preTransform;
    if (surfaceCapabilitiesKHR.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
        preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    } else {
        preTransform = surfaceCapabilitiesKHR.currentTransform;
    }

接下来是获得 Surface 的呈现模式 VkPresentModeKHR

// 呈现模式个数
    uint32_t presentModeCount;
    // 先获取数量
    res = vkGetPhysicalDeviceSurfacePresentModesKHR(info.gpus[0], info.surface, &presentModeCount, NULL);
    assert(res == VK_SUCCESS);
    // 再获取具体值
    VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR));
    res = vkGetPhysicalDeviceSurfacePresentModesKHR(info.gpus[0], info.surface, &presentModeCount, presentModes);
    assert(res == VK_SUCCESS);

关于 Surface 呈现模式的设定

关于 Surface 的呈现模式,在 Vulkan 中定义了很多种:

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_SHARED_DEMAND_REFRESH_KHR = 1000111000,
    VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR = 1000111001,
    VK_PRESENT_MODE_BEGIN_RANGE_KHR = VK_PRESENT_MODE_IMMEDIATE_KHR,
    VK_PRESENT_MODE_END_RANGE_KHR = VK_PRESENT_MODE_FIFO_RELAXED_KHR,
    VK_PRESENT_MODE_RANGE_SIZE_KHR = (VK_PRESENT_MODE_FIFO_RELAXED_KHR - VK_PRESENT_MODE_IMMEDIATE_KHR + 1),
    VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
} VkPresentModeKHR;

一般来说,我们就只要使用 VK_PRESENT_MODE_FIFO_KHR 模式就行了。

VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;

当然,也可以通过 vkGetPhysicalDeviceSurfacePresentModesKHR 函数得到所有的呈现模式,通过判断看自己想要的模式是否支持。

除了上面,还有其他很多参数设定,就不一一的阐述了,在创建 SwapChain 的时候都会遇到的。

SwapChain 创建

完成了相关的准备工作之后,就可以调用 vkCreateSwapchainKHR 来创建 SwapChain 了。在此之前,还需要创建 VkSwapchainCreateInfoKHR 对象来包含所需要的参数。

VkSwapchainCreateInfoKHR 定义了很多的相关参数,有的是上面讨论过的,有的没有讨论但是一般都是用固定值了,想要了解的话,也可以查阅官方的文档。

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType                  sType;
    const void*                      pNext;
    VkSwapchainCreateFlagsKHR        flags;
    VkSurfaceKHR                     surface;
    uint32_t                         minImageCount;
    // format 格式,上面讨论过的
    VkFormat                         imageFormat;
    // colorspace 颜色空间,
    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;

具体的代码和相关参数的设定如下:

// 包含了创建 SwapChain 所需要的信息
    VkSwapchainCreateInfoKHR swapchain_ci = {};
    swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    swapchain_ci.pNext = NULL;
    swapchain_ci.surface = info.surface;
    // swapchain 最小的 image 数量
    // 这里就使用了 VkSurfaceCapabilitiesKHR 的最小数量
    swapchain_ci.minImageCount = desiredNumberOfSwapChainImages;
    // Image 的格式
    swapchain_ci.imageFormat = info.format;
    // Image 的宽高信息
    swapchain_ci.imageExtent.width = swapchainExtent.width;
    swapchain_ci.imageExtent.height = swapchainExtent.height;
    // 旋转模式
    swapchain_ci.preTransform = preTransform;
    // alpha 混合模式
    swapchain_ci.compositeAlpha = compositeAlpha;
    swapchain_ci.imageArrayLayers = 1;
    // 呈现模式
    swapchain_ci.presentMode = swapchainPresentMode;
    swapchain_ci.oldSwapchain = VK_NULL_HANDLE;
    swapchain_ci.clipped = true;
    // Image 颜色空间
    swapchain_ci.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
    // Image 的用途
    swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    // 设定 swapchain 需要的 Queue 信息
    swapchain_ci.queueFamilyIndexCount = 0;
    swapchain_ci.pQueueFamilyIndices = NULL;
    // 如果图像和显示的 Queue 索引不一致,就要设置 Image 为共享模式
    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;
    }
    // 创建 SwapChain 需要用到 Device,创建 Device 又需要用到 图像队列 的索引值
    res = vkCreateSwapchainKHR(info.device, &swapchain_ci, NULL, &info.swap_chain);
    // 判断创建是否成功
    assert(res == VK_SUCCESS);

由此,就完成了 SwapChain 的创建。

可以看到创建 SwapChain 在前期还是需要不少的准备工作,猜想是因为 Vulkan 要做到跨平台,所以就会把很多硬件方面的设定暴露出来,导致在 API 层面上更接近于硬件底层,开发者在使用时也要了解更多的细节。比如有一些参数设定有很多个选项,但针对某一特定平台,只有两三个选项是有效的,其他选项是为了给其他平台的。

但是,只要我们完成了一次创建,相对于代码模块来说,就不会再有太多的变化了。把这块做好封装之后,在后续开发中直接复用,把精力专注于更加有意思的方面~~~

SwapChain 的销毁

当程序运行结束后,就可以通过 vkDestroySwapchainKHR 函数来销毁 SwapChain 了,使用起来就和销毁其他组件一样,不再赘述了。

参考

文章中的代码地址,具体可以参考我的 Github 项目:

https://github.com/glumes/vulkan_tutorial

总结

比较长的篇幅介绍了 SwapChain 的创建,尤其是创建过程中的一些参数设定,更多的要去理解它的意图。

SwapChain 顾名思义就是交换链,那么拿什么去交换呢?这就是后续内容中会讲到的 Image 对象。可以先简单理解成从 SwapChain 中申请一个 Image,然后对它进行渲染绘制,之后再把它放到 SwapChain 中去渲染呈现。

等我继续更新下去,你就会看到的~~~


Vulkan  系列文章

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。  

阿里云社区.png

相关实践学习
基于阿里云DeepGPU实例,用AI画唯美国风少女
本实验基于阿里云DeepGPU实例,使用aiacctorch加速stable-diffusion-webui,用AI画唯美国风少女,可提升性能至高至原性能的2.6倍。
相关文章
|
缓存 移动开发 图形学
进击的 Vulkan 移动开发(二)之谈谈对渲染流程的理解
都说 OpenGL 、Vulkan 是用来绘制二维、三维图形的,那么这个绘制渲染的流程到底是怎么样的呢?这里,谈谈我自己对它的理解。
489 0
进击的 Vulkan 移动开发(二)之谈谈对渲染流程的理解
|
移动开发 异构计算 索引
进击的 Vulkan 移动开发之 Command Buffer
此篇文章继续学习 Vulkan 中的组件:Command-Buffer 。
503 0
进击的 Vulkan 移动开发之 Command Buffer
|
移动开发 API Android开发
进击的 Vulkan 移动开发之 Instance & Device & Queue
针对每个组件进行学习讲解并配上相关的示例代码,首先是 Instance、Device 和 Queue 组件。
327 0
进击的 Vulkan 移动开发之 Instance & Device & Queue
|
17天前
|
开发框架 前端开发 Android开发
移动应用开发的未来:跨平台框架与原生系统的融合
【4月更文挑战第9天】随着移动设备成为日常生活的核心,移动应用的重要性日益凸显。本文探讨了移动应用开发的新趋势,特别是跨平台开发框架的兴起以及它们与传统移动操作系统之间的融合。分析了Flutter、React Native等流行的跨平台工具,并考察了它们如何优化性能、提高开发效率及对市场的影响。同时,文章也着眼于移动操作系统的最新进展,包括Android和iOS在兼容性、安全性和用户体验方面的创新。最后,展望了未来移动应用开发可能的方向,包括人工智能的集成、物联网的交互性以及5G网络带来的变革。
33 0
|
1月前
|
开发框架 人工智能 前端开发
移动应用开发的未来:跨平台框架与原生系统的融合
随着移动设备成为日常生活的延伸,移动应用的开发正面临着前所未有的挑战和机遇。本文将探讨当前移动应用开发的热点话题——跨平台开发框架与原生操作系统之间的互动,分析其对开发者、用户以及整个生态系统所带来的深远影响。我们将从技术演进的角度出发,讨论跨平台工具如React Native和Flutter等如何优化性能、提供更接近原生的体验,同时考察它们在解决不同移动操作系统特有问题时的有效性。此外,文中还将预测未来移动应用开发的趋势,包括人工智能、云计算及物联网技术的集成,为读者提供一个全面而深入的移动应用开发未来展望。
26 6
|
1月前
|
移动开发 前端开发 Android开发
mPaaS 常见问题之移动开发平台 mpaas的H5 前端 Kylin 框架引入vant后包特别大如何解决
mPaaS(移动平台即服务,Mobile Platform as a Service)是阿里巴巴集团提供的一套移动开发解决方案,它包含了一系列移动开发、测试、监控和运营的工具和服务。以下是mPaaS常见问题的汇总,旨在帮助开发者和企业用户解决在使用mPaaS产品过程中遇到的各种挑战
39 0
|
26天前
|
开发框架 前端开发 JavaScript
常见的移动应用开发框架有哪些?
跨平台移动开发框架概览:React Native用JavaScript构建UI;Google的Flutter打造原生体验;Ionic结合Angular与Cordova;Xamarin用C#开发iOS和Android;Apple的SwiftUI专注iOS和macOS界面;Android Jetpack提供官方工具集;Kotlin Multiplatform实现多平台共享;NativeScript用JavaScript做原生应用;Cocos2d-x则用于2D游戏开发。选择框架需考虑项目需求、平台、技术栈和团队经验。
36 3
|
1天前
|
开发框架 前端开发 搜索推荐
移动应用开发的未来:跨平台框架与原生系统的融合
【4月更文挑战第25天】随着移动设备成为日常生活的核心,移动应用开发正经历着前所未有的变革。本文探讨了移动应用开发的最新趋势,特别是跨平台开发框架的兴起以及它们如何与原生移动操作系统相结合,以提供更高效、更灵活且具有高性能的应用程序。我们将深入剖析Flutter、React Native等框架的技术细节,并讨论它们如何优化用户体验和简化开发流程。同时,将着眼于未来,预测这些技术如何塑造移动应用开发的景观。
|
1天前
|
机器学习/深度学习 开发框架 人工智能
移动应用开发的未来:跨平台框架与原生操作系统的融合
【4月更文挑战第25天】 随着移动设备的普及,移动应用已成为日常生活和商业活动的重要组成部分。本文探讨了移动应用开发的新趋势,特别是跨平台开发框架的崛起以及它们如何与原生移动操作系统相融合,以提供更高效、更优质的用户体验。我们将分析当前市场上流行的跨平台工具,如React Native、Flutter和Xamarin,并深入讨论它们在性能优化、用户界面一致性以及与原生系统功能集成方面的优势和挑战。此外,文章还将着眼于未来的技术发展,包括人工智能、机器学习在移动应用开发中的应用前景,以及这些技术如何影响开发者构建更加智能和个性化的移动体验。
|
1天前
|
开发框架 前端开发 Android开发
移动应用开发的未来:跨平台框架与原生操作系统的融合
【4月更文挑战第25天】随着移动互联网的飞速发展,移动应用已成为日常生活的重要组成部分。本文探讨了移动应用开发的当前趋势和未来展望,特别是跨平台开发框架的兴起以及它们与原生移动操作系统之间的互动。分析了Flutter、React Native等主流跨平台工具的优势与局限,并预测了未来移动应用开发可能面临的挑战和机遇。

热门文章

最新文章