当前位置: 首页 > article >正文

vulkan从小白到专家——VulkanSwapChain

Vulkan没有默认帧缓冲区的概念,在送显上屏之前,需要一个基础结构来持有一些缓冲区。Vulkan引入了交换链的概念。
交换链本质上就是一个(用于送显上屏)图像的等待队列。应用程序获取一个图像,绘制内容,然后返回到队列。队列如何工作,队列中图像如何送显上屏 取决于交换链的配置参数。
交换链的作用就是同步图像的上屏和屏幕的刷新。

0 Surface的校验

验证GPU是否支持surface的显示,vulkan标准内没有窗体surface的概念,外在的因素不可控所以要校验,

    // Section1: check
    VkBool32 vk_queue_family_supports_present = false;
    auto hr = vkGetPhysicalDeviceSurfaceSupportKHR(device.gpuDevice_, device.queueFamilyIndex_, device.surface_, &vk_queue_family_supports_present);
    if (hr != VK_SUCCESS || !vk_queue_family_supports_present) {
        LOGE("surface not founded...");
        exit(1);
    }

这个接口第二个参数是queue的索引,多个队列需要循环遍历,手机上一般1个队列有效。

1 Surface Format相关

同样手段,先取长度 再填充vector。

    // Query the list of supported surface format and choose one we like
    uint32_t formatCount = 0;
    hr = vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
                                         &formatCount, nullptr);
    if (hr != VK_SUCCESS || formatCount == 0) {
        LOGE("could not get number of physical device formats for device");
        exit(1);
    }

    std::vector<VkSurfaceFormatKHR> formats; formats.resize(formatCount);
    vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
                                         &formatCount, formats);
    LOGI("Got %d formats", formatCount);

    // 至少必须有RGBA8888啊,
    uint32_t chosenFormat;
    for (chosenFormat = 0; chosenFormat < formatCount; chosenFormat++) {
        if (formats[chosenFormat].format == VK_FORMAT_R8G8B8A8_UNORM) break;
    }
    assert(chosenFormat < formatCount);

surfaceFormat结构里有两个字段,format和colorSpace:

  • 格式主要sRGB最常见的,SCRGB是FP16表示每个通道,R16G16B16A16。RGB也有两种:最普通的UNORM,和非线性的,后者非常少见。
  • 颜色空间,依赖于硬件是否支持,最广泛和古老的格式是sRGB,P3(广色域),BT2020,HDR10,杜比,后面几种在HDR渲染中经常遇到,目前旗舰手机都支持hdr,iphone12是第一款集采集、处理、显示的终端设备。

要正常渲染,至少得有一种格式,R8G8B8A8_UNORM,否则就退出。P40Pro手机查询到支持5种格式,

(VkSurfaceFormatKHR)  (format = VK_FORMAT_R8G8B8A8_UNORM, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR)  (format = VK_FORMAT_R8G8B8A8_SRGB, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR)  (format = VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR)  (format = VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR)  (format = VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)

几种格式介绍:

  • 常规的至少要有RGBA8888,
  • 3th:565是压缩格式,
  • 5th:支持HDR显示需要的格式至少1010102,
  • 4th:要到HDR-vivid,则得用格式12121212,

3 SurfaceCapabilities

    VkSurfaceCapabilitiesKHR surfaceCapabilities;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.gpuDevice_, device.surface_,&surfaceCapabilities);

主要获取交换链的长度,以及渲染目标的尺寸限制。

3.1 窗口尺寸

相关的三个变量,单位都是像素,跟渲染
VkExtent2D currentExtent; 当前窗口的尺寸,可能无效,
VkExtent2D minImageExtent; 最小尺寸
VkExtent2D maxImageExtent; 最大尺寸

    // SectionX
    VkExtent2D swap_chain_extent;
    if (surfaceCapabilities.currentExtent.width == 0xFFFFFFFF)
    {
        // the surface size is undefined
//        swap_chain_extent.width = std::clamp(swap_chain_extent.width, surfaceCapabilities.minImageExtent.width,
//                                             surfaceCapabilities.maxImageExtent.width);
        swap_chain_extent.width = std::min(std::max(window_width, surfaceCapabilities.minImageExtent.width),
                                            surfaceCapabilities.maxImageExtent.width);
        swap_chain_extent.height = std::min(std::max(window_height, surfaceCapabilities.minImageExtent.height),
                                            surfaceCapabilities.maxImageExtent.height);
    }
    else {
        swap_chain_extent = surfaceCapabilities.currentExtent;
    }

手机上查询结果:

surfaceCapabilities = {VkSurfaceCapabilitiesKHR} 
 minImageCount = {uint32_t} 2
 maxImageCount = {uint32_t} 64
 currentExtent = {VkExtent2D} 
  width = {uint32_t} 800
  height = {uint32_t} 1657
 minImageExtent = {VkExtent2D} 
  width = {uint32_t} 1
  height = {uint32_t} 1
 maxImageExtent = {VkExtent2D} 
  width = {uint32_t} 4096
  height = {uint32_t} 4096
 maxImageArrayLayers = {uint32_t} 1
 supportedTransforms = {VkSurfaceTransformFlagsKHR} 511
 currentTransform = {VkSurfaceTransformFlagBitsKHR} VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
 supportedCompositeAlpha = {VkCompositeAlphaFlagsKHR} 8
 supportedUsageFlags = {VkImageUsageFlags} 159

swap_chain_extent在用于填充交换链的createInfo结构体。

3.2 交换链策略/VkPresentModeKHR

下面来确定呈现模式,也就是交换链池子中的image使用策略:

  • 及时模式,不需要等待垂直同步的周期,直接上屏,可能会导致屏幕撕裂tearing
  • 信箱模式,保证新的图像能被及时送显,内部有1个元素的队列,新的到来直接替换旧的。当队列满时,不会阻塞应用程序,而是将已经排队的图像替换为新图像,能够避免撕裂但是会有能耗的问题
  • FIFO模式,队列缓冲,队列的个数未知,实际效果3缓冲性能优于2缓冲。队列满的时候,应用程序必须等待,

最常听说的是双缓冲模式,android 从4.1引入黄油架构,支持垂直同步、tripple buffer等技术,极大地提升了渲染流畅度。
缓冲数的确定:

    // set desired number of swap chain images
    uint32_t  desired_number_of_swap_chain_images = surfaceCapabilities.minImageCount;
    if (desired_number_of_swap_chain_images > surfaceCapabilities.maxImageCount) {
        desired_number_of_swap_chain_images = surfaceCapabilities.maxImageCount;
    }
    // get count of present modes
    uint32_t  present_mode_count = 0;
    hr = vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
                                                   &present_mode_count, nullptr);
    std::vector<VkPresentModeKHR> present_modes(present_mode_count);
    vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
                                                   &present_mode_count, present_modes.data());
    // 如果窗口支持vsync,多个buffer的策略定制
    std::vector<VkPresentModeKHR> desired_present_modes;
    bool support_vsync = true;
    if (support_vsync) {
        desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
        desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_RELAXED_KHR);
        desired_present_modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
    } else {
        desired_present_modes.push_back(VK_PRESENT_MODE_IMMEDIATE_KHR);
    }
    int select_present_mode_found = -1;
    for (size_t i = 0; i < desired_present_modes.size(); ++i) {
        for (auto avaiable : present_modes) {
            if (desired_present_modes[i] == avaiable) {
                select_present_mode_found = i;
                break;
            }
            if (select_present_mode_found != -1) break;
        }
    }

select_present_mode_found 用于交换链CreateInfo填充

4 CompositeAlpha

透明度组合定义窗口半透明的效果,alpha通道是否跟其他窗体混合。

    //find supported composite alpha format
    VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    std::vector<VkCompositeAlphaFlagBitsKHR> desired_composite_alphas = {
        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 (auto flag : desired_composite_alphas) {
        if (surfaceCapabilities.supportedCompositeAlpha & flag) {
            composite_alpha = flag;
            break;
        }
    }

关于alpha预乘是个大坑:设计给的美术资源,程序化处理以后的纹理,不同平台的shader处理逻辑(unity、android、ios),一定要高度保持一致,否则CPU、GPU侧代码分支会很多,可维护性会很差。

5 preTransform

处理surface旋转的场景,这东西在手机横竖屏切换场景应用有用,原理和效果还没深度研究。

6 code/创建交换链

void CreateSwapChain(uint32_t window_width, uint32_t window_height) {
    LOGI("->createSwapChain");
    memset(&swapchain, 0, sizeof(swapchain));

    // Section1: check
    VkBool32 vk_queue_family_supports_present = false;
    auto hr = vkGetPhysicalDeviceSurfaceSupportKHR(device.gpuDevice_, device.queueFamilyIndex_, device.surface_, &vk_queue_family_supports_present);
    if (hr != VK_SUCCESS || !vk_queue_family_supports_present) {
        LOGE("surface not founded...");
        exit(1);
    }

    // **********************************************************
    // Get the surface capabilities because:
    //   - It contains the minimal and max length of the chain, we will need it
    //   - It's necessary to query the supported surface format (R8G8B8A8 for
    //   instance ...)
    VkSurfaceCapabilitiesKHR surfaceCapabilities;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.gpuDevice_, device.surface_,
                                              &surfaceCapabilities);

    // Section3 获取支持格式列表
    // Query the list of supported surface format and choose one we like
    uint32_t formatCount = 0;
    hr = vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
                                         &formatCount, nullptr);
    if (hr != VK_SUCCESS || formatCount == 0) {
        LOGE("could not get number of physical device formats for device");
        exit(1);
    }

    std::vector<VkSurfaceFormatKHR> formats; formats.resize(formatCount);
    vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
                                         &formatCount, formats);
    LOGI("Got %d formats", formatCount);

    // 至少必须有RGBA8888啊,
    uint32_t chosenFormat;
    for (chosenFormat = 0; chosenFormat < formatCount; chosenFormat++) {
        if (formats[chosenFormat].format == VK_FORMAT_R8G8B8A8_UNORM) break;
    }
    assert(chosenFormat < formatCount);

    // SectionX
    VkExtent2D swap_chain_extent;
    if (surfaceCapabilities.currentExtent.width == 0xFFFFFFFF)
    {
        // the surface size is undefined
//        swap_chain_extent.width = std::clamp(swap_chain_extent.width, surfaceCapabilities.minImageExtent.width,
//                                             surfaceCapabilities.maxImageExtent.width);
        swap_chain_extent.width = std::min(std::max(window_width, surfaceCapabilities.minImageExtent.width),
                                            surfaceCapabilities.maxImageExtent.width);
        swap_chain_extent.height = std::min(std::max(window_height, surfaceCapabilities.minImageExtent.height),
                                            surfaceCapabilities.maxImageExtent.height);
    }
    else {
        swap_chain_extent = surfaceCapabilities.currentExtent;
    }

    // Section4
    swapchain.displaySize_ = swap_chain_extent;
    swapchain.displayFormat_ = formats[chosenFormat].format;

    //find supported composite alpha format
    VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    std::vector<VkCompositeAlphaFlagBitsKHR> desired_composite_alphas = {
        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 (auto flag : desired_composite_alphas) {
        if (surfaceCapabilities.supportedCompositeAlpha & flag) {
            composite_alpha = flag;
            break;
        }
    }

    // set desired number of swap chain images
    uint32_t  desired_number_of_swap_chain_images = surfaceCapabilities.minImageCount;
    if (desired_number_of_swap_chain_images > surfaceCapabilities.maxImageCount) {
        desired_number_of_swap_chain_images = surfaceCapabilities.maxImageCount;
    }

    // get count of present modes
    uint32_t  present_mode_count = 0;
    hr = vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
                                                   &present_mode_count, nullptr);
    std::vector<VkPresentModeKHR> present_modes(present_mode_count);
    vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
                                                   &present_mode_count, present_modes.data());
    // 如果窗口支持vsync,多个buffer的策略定制
    std::vector<VkPresentModeKHR> desired_present_modes;
    bool support_vsync = true;
    if (support_vsync) {
        desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
        desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_RELAXED_KHR);
        desired_present_modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
    } else {
        desired_present_modes.push_back(VK_PRESENT_MODE_IMMEDIATE_KHR);
    }
    int select_present_mode_found = -1;
    for (size_t i = 0; i < desired_present_modes.size(); ++i) {
        for (auto avaiable : present_modes) {
            if (desired_present_modes[i] == avaiable) {
                select_present_mode_found = i;
                break;
            }
            if (select_present_mode_found != -1) break;
        }
    }

    // **********************************************************
    // Create a swap chain (here we choose the minimum available number of surface
    // in the chain)
    VkSwapchainCreateInfoKHR swapchainCreateInfo{
            .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
            .pNext = nullptr,
            .surface = device.surface_,
            .minImageCount = desired_number_of_swap_chain_images,
            .imageFormat = formats[chosenFormat].format,
            .imageColorSpace = formats[chosenFormat].colorSpace,
            .imageExtent = surfaceCapabilities.currentExtent,
            .imageArrayLayers = 1,
            .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
            .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
            .queueFamilyIndexCount = 1,
            .pQueueFamilyIndices = &device.queueFamilyIndex_,
            .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
            .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
            // 动态制定呈现模式
            .presentMode = select_present_mode_found == -1 ? present_modes[0] :
                    desired_present_modes[select_present_mode_found],
            .clipped = VK_FALSE,
            .oldSwapchain = VK_NULL_HANDLE,
    };
    CALL_VK(vkCreateSwapchainKHR(device.device_, &swapchainCreateInfo, nullptr,
                                 &swapchain.swapchain_));

    // Get the length of the created swap chain
    CALL_VK(vkGetSwapchainImagesKHR(device.device_, swapchain.swapchain_,
                                    &swapchain.swapchainLength_, nullptr));

http://www.kler.cn/a/467872.html

相关文章:

  • 《Rust权威指南》学习笔记(一)
  • Linux一些问题
  • Android 系统 `android.app.Application` 类的深度定制
  • Jellyfin播放卡顿,占CPU的解决方法
  • 数学常用术语作用reminder
  • 供应链系统设计-供应链中台系统设计(七)- 商品中心设计篇
  • 大白话拆解——多线程中关于死锁的一切(七)(已完结)
  • SpringBoot中常用的 Redis 命令实现
  • Linux Red Hat 7.9 Server安装GitLab
  • 【Ubuntu】 Ubuntu22.04搭建NFS服务
  • ARM CCA机密计算安全模型之固件更新
  • 自定义有序Map
  • 【Java基础】力扣3、4
  • java项目之读书笔记共享平台(源码+文档)
  • ros常用命令记录
  • GOGOGO 抽象
  • 「Mac畅玩鸿蒙与硬件51」UI互动应用篇28 - 模拟记账应用
  • 大数据技术(七)—— HBase数据结构与架构
  • java接口下载zip,不生成中间文件,返回前端文件流
  • Fabric部署-docker安装