Ch5-0 VKBase+.h

之前在Ch2-3 创建管线并绘制三角形中应当创建过VKBase+.h,并且在里面定义了graphicsPipelineCreateInfoPack类。
第五章中对各种常用对象的封装会被放在VKBase+.h当中。
在这一节定义graphicsBasePlus类,以及图像格式相关的一些函数,方便后续编程。

VKBase+.h中包含Ch3-2 图像与缓冲区一节中写的VKFormat.h,于是VKBase+.h的内容应当如下:

#pragma once
#include "VKBase.h"
#include "VKFormat.h"

namespace vulkan {
    struct graphicsPipelineCreateInfoPack { /*内容略*/ };
}

graphicsBasePlus

graphicsBasePlus扩展graphicsBase的功能,用于默认创建一些对于Vulkan图形编程有必要的对象。
首先通过pimpl(不知道这是啥的请自行百度)来实现这个graphicsBase的“扩展”:

//前置声明
class graphicsBasePlus;

class graphicsBase {
    /*其他成员变量略*/
    graphicsBasePlus* pPlus = nullptr;//=nullptr可以省略,因为是单例类的成员,自动零初始化
public:
    /*其他成员函数略*/
    //*pPlus的Getter
    static graphicsBasePlus& Plus() { return *singleton.pPlus; }
    //*pPlus的Setter,只允许设置pPlus一次
    static void Plus(graphicsBasePlus& plus) { if (!singleton.pPlus) singleton.pPlus = + }
};
  • 这么一来你就可以用graphicsBase::Plus()来访问graphicsBasePlus::singleton,虽然这么做有点多此一举。。。

graphicsBasePlus当然也是单例:

class graphicsBasePlus {
    //静态变量
    static graphicsBasePlus singleton;
    //--------------------
    graphicsBasePlus() {
        /*待后续填充*/
    }
    graphicsBasePlus(graphicsBasePlus&&) = delete;
    ~graphicsBasePlus() = default;
};
inline graphicsBasePlus graphicsBasePlus::singleton;

首先使graphicsBasePlus::singleton能在初始化时自动设置graphicsBase::pPlus,填充graphicsBasePlus():

graphicsBasePlus() {
    /*待后续填充*/
    graphicsBase::Plus(singleton);
    /*待后续填充*/
}
  • 定义时以inline修饰的静态成员变量(graphicsBase::singletongraphicsBasePlus::singleton都以inline修饰)首先发生静态初始化,数值变为零值或默认值,然后按定义顺序发生动态初始化(若有必要),全局/静态变量逆初始化顺序发生析构。由于VKBase+.h包含了VKBase.hgraphicsBasePlus::singleton的初始化器在graphicsBase::singleton初始化后执行,由此可以确保graphicsBase::pPlus不会在被设置后又被刷成nullptr

Vulkan程序必然需要命令池以及命令缓冲区,这里为图形、呈现、计算队列定义不同的commandPool对象。以及由于之后的一些常用对象封装当中经常需要数据传送,定义一个commandBuffer对象专用于此,若图形与呈现的队列族不同,那么还需要一个能被提交给呈现队列的命令缓冲区。

class graphicsBasePlus {
    commandPool commandPool_graphics;
    commandPool commandPool_presentation;
    commandPool commandPool_compute;
    commandBuffer commandBuffer_transfer;//从commandPool_graphics分配
    commandBuffer commandBuffer_presentation;
    //静态变量
    static graphicsBasePlus singleton;
    //--------------------
    graphicsBasePlus() {
        /*待后续填充*/
        graphicsBase::Plus(singleton);
        /*待后续填充*/
   }
    graphicsBasePlus(graphicsBasePlus&&) = delete;
    ~graphicsBasePlus() = default;
public:
    //Getter
    const commandPool& CommandPool_Graphics() const { return commandPool_graphics; }
    const commandPool& CommandPool_Compute() const { return commandPool_compute; }
    const commandBuffer& CommandBuffer_Transfer() const { return commandBuffer_transfer; }
    //Const Function
    //简化命令提交
    result_t ExecuteCommandBuffer_Graphics(VkCommandBuffer commandBuffer) const {
        fence fence;
        VkSubmitInfo submitInfo = {
            .commandBufferCount = 1,
            .pCommandBuffers = &commandBuffer
        };
        VkResult result = graphicsBase::Base().SubmitCommandBuffer_Graphics(submitInfo, fence);
        if (!result)
            fence.Wait();
        return result;
    }
    //该函数专用于向呈现队列提交用于接收交换链图像的队列族所有权的命令缓冲区
    result_t AcquireImageOwnership_Presentation(VkSemaphore semaphore_renderingIsOver, VkSemaphore semaphore_ownershipIsTransfered, VkFence fence = VK_NULL_HANDLE) const {
        if (VkResult result = commandBuffer_presentation.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT))
            return result;
        graphicsBase::Base().CmdTransferImageOwnership(commandBuffer_presentation);
        if (VkResult result = commandBuffer_presentation.End())
            return result;
        return graphicsBase::Base().SubmitCommandBuffer_Presentation(commandBuffer_presentation, semaphore_renderingIsOver, semaphore_ownershipIsTransfered, fence);
    }
};
inline graphicsBasePlus graphicsBasePlus::singleton;
  • 由于专用于呈现队列的命令缓冲区只用于接收交换链图像的队列族所有权,不必暴露相应的命令池和命令缓冲的handle。

  • 在渲染循环外提交命令缓冲区时,常常会有只需要带一个用完就丢的栅栏的情形(比如进入渲染循环前将各种资源加载到设备内存的时候),定义ExecuteCommandBuffer_Graphics(...)便于后续使用。

我打算无论图形和计算的队列是否相同,让它们有各自的命令池,而若负责呈现的队列与图形队列不同,才为其创建命令池:

graphicsBasePlus() {
    //在创建逻辑设备时执行Initialize()
    auto Initialize = [] {
        if (graphicsBase::Base().QueueFamilyIndex_Graphics() != VK_QUEUE_FAMILY_IGNORED)
            singleton.commandPool_graphics.Create(graphicsBase::Base().QueueFamilyIndex_Graphics(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
            singleton.commandPool_graphics.AllocateBuffers(singleton.commandBuffer_transfer);
        if (graphicsBase::Base().QueueFamilyIndex_Compute() != VK_QUEUE_FAMILY_IGNORED)
            singleton.commandPool_compute.Create(graphicsBase::Base().QueueFamilyIndex_Compute(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
        if (graphicsBase::Base().QueueFamilyIndex_Presentation() != VK_QUEUE_FAMILY_IGNORED &&
            graphicsBase::Base().QueueFamilyIndex_Presentation() != graphicsBase::Base().QueueFamilyIndex_Graphics() &&
            graphicsBase::Base().SwapchainCreateInfo().imageSharingMode == VK_SHARING_MODE_EXCLUSIVE)
            singleton.commandPool_presentation.Create(graphicsBase::Base().QueueFamilyIndex_Presentation(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
            singleton.commandPool_presentation.AllocateBuffers(singleton.commandBuffer_presentation);
        /*待后续填充*/
    };
    //在销毁逻辑设备时执行CleanUp()
    //如果你不需要更换物理设备或在运行中重启Vulkan(皆涉及重建逻辑设备),那么此CleanUp回调非必要
    //程序运行结束时,无论是否有这个回调,graphicsBasePlus中的对象必会在析构graphicsBase前被析构掉
    auto CleanUp = [] {
        singleton.commandPool_graphics.~commandPool();
        singleton.commandPool_presentation.~commandPool();
        singleton.commandPool_compute.~commandPool();
    };
    graphicsBase::Plus(singleton);
    graphicsBase::Base().AddCallback_CreateDevice(Initialize);
    graphicsBase::Base().AddCallback_DestroyDevice(CleanUp);
}

于是乎,利用C++中静态成员变量的初始化机制,上述代码使得在创建逻辑设备后,程序会自动创建前文所述的命令池和命令缓冲区。

图像格式相关

VkFormatProperties类型记录了对于某一图像格式,数据线性排列的图像、数据最优排列的图像,以及存储该格式数据的缓冲区所支持的格式特性:

struct VkFormatProperties 的成员说明

VkFormatFeatureFlagBits linearTilingFeatures

数据线性排列的图像所支持的格式特性

VkFormatFeatureFlagBits optimalTilingFeatures

数据最优排列的图像所支持的格式特性

VkFormatFeatureFlagBits bufferFeatures

存储该格式数据的缓冲区所支持的格式特性

版本要求

VkFormatProperties 的枚举项(略去跟YCbCr色彩空间相关的枚举项)

1.0

VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT 表示该格式支持作为被采样图像的格式

1.0

VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT 表示该格式支持作为storage图像的格式

1.0

VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT 表示该格式支持作为storage图像的格式,且支持原子性操作

1.0

VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT 表示该格式支持作为uniform纹素缓冲区的格式

1.0

VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT 表示该格式支持作为storage纹素缓冲区的格式

1.0

VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT 表示该格式支持作为storage纹素缓冲区的格式,且支持原子性操作

1.0

VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT 表示该格式支持作为顶点缓冲区的格式

1.0

VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT 表示该格式支持作为颜色附件的格式

1.0

VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT 表示该格式支持作为颜色附件的格式,并且支持混色

1.0

VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT 表示该格式支持作为深度模板附件的格式

1.0

VK_FORMAT_FEATURE_BLIT_SRC_BIT 表示该格式支持作为blit命令的来源图像的格式

1.0

VK_FORMAT_FEATURE_BLIT_DST_BIT 表示该格式支持作为blit命令的目标图像的格式

1.0

VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT 表示该格式支持作为被采样图像的格式,且支持线性插值过滤

1.1

VK_FORMAT_FEATURE_TRANSFER_SRC_BIT 表示该格式支持作为copy命令的来源图像的格式

1.1

VK_FORMAT_FEATURE_TRANSFER_DST_BIT 表示该格式支持作为copy命令的目标图像的格式

1.1

VK_FORMAT_FEATURE_DISJOINT_BIT 表示该格式的多层面(multi-planar)图像可以在创建时注明VK_IMAGE_CREATE_DISJOINT_BIT

1.2

VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT 表示该格式支持作为被采样图像的格式,且支持minmax过滤

  • Blit命令是专用于图像的数据传送命令,可以在不同大小的图像间进行。

  • minmax过滤需要在Vulkan1.2以上版本中使得VkSamplerCreateInfo::pNext指向VkSamplerReductionModeCreateInfo结构体,本套教程中不会用使用(也不需要教怎么用,很简单没啥要点),有兴趣请自行了解。

在后续的编程中,许多情况下会出现需要检查图像格式所支持格式特性的情况,有必要在创建逻辑设备后事先取得各个格式的VkFormatProperties

class graphicsBasePlus {
    /*新增*/VkFormatProperties formatProperties[std::size(formatInfos_v1_0)] = {};
    commandPool commandPool_graphics;
    commandPool commandPool_presentation;
    commandPool commandPool_compute;
    commandBuffer commandBuffer_transfer;//从commandPool_graphics分配
    commandBuffer commandBuffer_presentation;
    //静态变量
    static graphicsBasePlus singleton;
    //--------------------
    graphicsBasePlus() {
        auto Initialize = [] {
            if (graphicsBase::Base().QueueFamilyIndex_Graphics() != VK_QUEUE_FAMILY_IGNORED)
                singleton.commandPool_graphics.Create(graphicsBase::Base().QueueFamilyIndex_Graphics(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
                singleton.commandPool_graphics.AllocateBuffers(singleton.commandBuffer_transfer);
            if (graphicsBase::Base().QueueFamilyIndex_Compute() != VK_QUEUE_FAMILY_IGNORED)
                singleton.commandPool_compute.Create(graphicsBase::Base().QueueFamilyIndex_Compute(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
            if (graphicsBase::Base().QueueFamilyIndex_Presentation() != VK_QUEUE_FAMILY_IGNORED &&
                graphicsBase::Base().QueueFamilyIndex_Presentation() != graphicsBase::Base().QueueFamilyIndex_Graphics() &&
                graphicsBase::Base().SwapchainCreateInfo().imageSharingMode == VK_SHARING_MODE_EXCLUSIVE)
                singleton.commandPool_presentation.Create(graphicsBase::Base().QueueFamilyIndex_Presentation(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
                singleton.commandPool_presentation.AllocateBuffers(singleton.commandBuffer_presentation);
            //新增------------------------------------
            for (size_t i = 0; i < std::size(singleton.formatProperties); i++)
                vkGetPhysicalDeviceFormatProperties(graphicsBase::Base().PhysicalDevice(), VkFormat(i), &singleton.formatProperties[i]);
            //----------------------------------------
        };
        auto CleanUp = [] {
            singleton.commandPool_graphics.~commandPool();
            singleton.commandPool_presentation.~commandPool();
            singleton.commandPool_compute.~commandPool();
        };
        graphicsBase::Plus(singleton);
        graphicsBase::Base().AddCallback_CreateDevice(Initialize);
        graphicsBase::Base().AddCallback_DestroyDevice(CleanUp);
    }
    graphicsBasePlus(graphicsBasePlus&&) = delete;
    ~graphicsBasePlus() = default;
public:
    //Getter
    /*新增*/const VkFormatProperties& FormatProperties(VkFormat format) const {
#ifndef NDEBUG
       if (uint32_t(format) >= std::size(formatInfos_v1_0))
           outStream << std::format("[ FormatProperties ] ERROR\nThis function only supports definite formats provided by VK_VERSION_1_0.\n"),
           abort();
#endif
        return formatProperties[format];
    }
    const commandPool& CommandPool_Graphics() const { return commandPool_graphics; }
    const commandPool& CommandPool_Compute() const { return commandPool_compute; }
    const commandBuffer& CommandBuffer_Transfer() const { return commandBuffer_transfer; }
    //Const Function
    result_t ExecuteCommandBuffer_Graphics(VkCommandBuffer commandBuffer) const {
        fence fence;
        VkSubmitInfo submitInfo = {
            .commandBufferCount = 1,
            .pCommandBuffers = &commandBuffer
        };
        VkResult result = graphicsBase::Base().SubmitCommandBuffer_Graphics(submitInfo, fence);
        if (!result)
            fence.Wait();
        return result;
    }
    result_t AcquireImageOwnership_Presentation(VkSemaphore semaphore_renderingIsOver, VkSemaphore semaphore_ownershipIsTransfered, VkFence fence = VK_NULL_HANDLE) const {
        if (VkResult result = commandBuffer_presentation.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT))
            return result;
        graphicsBase::Base().CmdTransferImageOwnership(commandBuffer_presentation);
        if (VkResult result = commandBuffer_presentation.End())
            return result;
        return graphicsBase::Base().SubmitCommandBuffer_Presentation(commandBuffer_presentation, semaphore_renderingIsOver, semaphore_ownershipIsTransfered, fence);
    }
};
inline graphicsBasePlus graphicsBasePlus::singleton;
  • formatInfos_v1_0来源于VKFormat.h(如果你是跳着读的,见前文),这里仅获取Vulkan1.0中所提供格式的格式特性。

再在vulkan命名空间中添加三个函数:
1.对formatInfos_v1_0进一步包装,函数FormatInfo(...)使得能直接以格式(而不是还要转到uint32_t)为参数获取格式信息。
2.Corresponding16BitFloatFormat(...)获取32位浮点数格式对应的16位浮点数格式类型,这会在之后将32位浮点数的hdr图像数据转到16位浮点数数据时发挥作用。
3.FormatProperties(...)用于简化graphicsBase::Plus().FormatProperties(...)。

constexpr formatInfo FormatInfo(VkFormat format) {
#ifndef NDEBUG
    if (uint32_t(format) >= std::size(formatInfos_v1_0))
        outStream << std::format("[ FormatInfo ] ERROR\nThis function only supports definite formats provided by VK_VERSION_1_0.\n"),
        abort();
#endif
    return formatInfos_v1_0[uint32_t(format)];
}
constexpr VkFormat Corresponding16BitFloatFormat(VkFormat format_32BitFloat) {
    switch (format_32BitFloat) {
    case VK_FORMAT_R32_SFLOAT:
        return VK_FORMAT_R16_SFLOAT;
    case VK_FORMAT_R32G32_SFLOAT:
        return VK_FORMAT_R16G16_SFLOAT;
    case VK_FORMAT_R32G32B32_SFLOAT:
        return VK_FORMAT_R16G16B16_SFLOAT;
    case VK_FORMAT_R32G32B32A32_SFLOAT:
        return VK_FORMAT_R16G16B16A16_SFLOAT;
    }
    return format_32BitFloat;
}
inline const VkFormatProperties& FormatProperties(VkFormat format) {
    return graphicsBase::Plus().FormatProperties(format);
}