Ch6-0 使用新版本特性

本章的示例代码参见:EasyVulkan_Ch6
本节的示例代码参见:VKBase.h

先前(以及之后第七章)基本只使用了Vulkan1.0的版本特性,本章会对一些新版本特性进行介绍。

前情提要,先前已书写了用于获取版本号的函数graphicsBase::UseLatestApiVersion():

VkResult UseLatestApiVersion() {
    if (vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"))
        return vkEnumerateInstanceVersion(&apiVersion);
    return VK_SUCCESS;
}

获取物理设备特性

先前在graphicsBase::CreateDevice(...)中获取了物理设备特性VkPhysicalDeviceFeatures,但并没有保存到成员变量。考虑到可能会有需要检验物理设备特性的情况,需要将其保存下来。
VkPhysicalDeviceFeatures没有可扩展性,在Vulkan1.0以上版本,使用VkPhysicalDeviceFeatures2结构体,它包含原有的VkPhysicalDeviceFeatures,并包含一个pNext指针:

struct VkPhysicalDeviceFeatures2 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkPhysicalDeviceFeatures features

Vulkan1.0中的物理设备特性

graphicsBase中加入以下内容:

    VkPhysicalDeviceFeatures2 physicalDeviceFeatures;
    VkPhysicalDeviceVulkan11Features physicalDeviceVulkan11Features;//Provided by VK_API_VERSION_1_2
    VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features;
    VkPhysicalDeviceVulkan13Features physicalDeviceVulkan13Features;
    //获取物理设备特性
    void GetPhysicalDeviceFeatures() {
        /*待填充*/
    }
public:
    constexpr const VkPhysicalDeviceFeatures2& PhysicalDeviceFeatures() const { return physicalDeviceFeatures.features; }
    constexpr const VkPhysicalDeviceVulkan11Features& PhysicalDeviceVulkan11Features() const { return physicalDeviceVulkan11Features; }
    constexpr const VkPhysicalDeviceVulkan12Features& PhysicalDeviceVulkan12Features() const { return physicalDeviceVulkan12Features; }
    constexpr const VkPhysicalDeviceVulkan13Features& PhysicalDeviceVulkan13Features() const { return physicalDeviceVulkan13Features; }
  • Vulkan1.1版本中的各种新增特性对应其各自独立的结构体,VkPhysicalDeviceVulkan11Features整合了这些特性,但它是Vulkan1.2版本中的结构体。

填充GetPhysicalDeviceFeatures(),首先判定版本,若Vulkan版本未到1.1,直接获取1.0版本中的全部特性:

void GetPhysicalDeviceFeatures() {
    if (apiVersion >= VK_API_VERSION_1_1) {
        //不管之后需不需要,先把所有结构体名称填完
        physicalDeviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
        physicalDeviceVulkan11Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES };
        physicalDeviceVulkan12Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES };
        physicalDeviceVulkan13Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES };
        /*待后续填充*/
    }
    else
        vkGetPhysicalDeviceFeatures(physicalDevice, &physicalDeviceFeatures.features);
}

如果当前设备的Vulkan最高版本没到1.2,则理论上你不能让VkPhysicalDeviceFeatures2::pNext指向VkPhysicalDeviceVulkan11Features,要获取新增特性,需要将VkPhysicalDeviceVulkan11Features中各种特性对应的结构体按需要逐一加入pNext链,这里就不做赘述了。
于是版本号只到1.1而没到1.2的话,这里姑且只获取1.0版本的全部特性:

void GetPhysicalDeviceFeatures() {
    if (apiVersion >= VK_API_VERSION_1_1) {
        physicalDeviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
        physicalDeviceVulkan11Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES };
        physicalDeviceVulkan12Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES };
        physicalDeviceVulkan13Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES };
        if (apiVersion >= VK_API_VERSION_1_2) {
            /*待后续填充*/
        }
        vkGetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures);
    }
    else
        vkGetPhysicalDeviceFeatures(physicalDevice, &physicalDeviceFeatures.features);
}

版本号到1.2的话,接上VkPhysicalDeviceVulkan11FeaturesVkPhysicalDeviceVulkan12Features
版本号到1.3的话,接上VkPhysicalDeviceVulkan13Features

void GetPhysicalDeviceFeatures() {
    if (apiVersion >= VK_API_VERSION_1_1) {
        physicalDeviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
        physicalDeviceVulkan11Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES };
        physicalDeviceVulkan12Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES };
        physicalDeviceVulkan13Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES };
        if (apiVersion >= VK_API_VERSION_1_2) {
            physicalDeviceFeatures.pNext = &physicalDeviceVulkan11Features;
            physicalDeviceVulkan11Features.pNext = &physicalDeviceVulkan12Features;
            if (apiVersion >= VK_API_VERSION_1_3)
                physicalDeviceVulkan12Features.pNext = &physicalDeviceVulkan13Features;
        }
        vkGetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures);
    }
    else
        vkGetPhysicalDeviceFeatures(physicalDevice, &physicalDeviceFeatures.features);
}

获取物理设备属性和物理设备内存属性

类似物理设备特性,物理设备属性和物理设备内存属性在后续版本中也有相应的可扩展类型VkPhysicalDeviceProperties2VkPhysicalDeviceMemoryProperties2

struct VkPhysicalDeviceProperties2 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkPhysicalDeviceProperties properties

Vulkan1.0中的物理设备属性

struct VkPhysicalDeviceMemoryProperties2 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体(到1.3为止只能指向VkPhysicalDeviceMemoryBudgetPropertiesEXT)

VkPhysicalDeviceMemoryProperties memoryProperties

Vulkan1.0中的物理设备内存属性

去掉原先graphicsBase中的成员变量声明VkPhysicalDeviceProperties physicalDeviceProperties;VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties;及相应Getter函数,加入以下内容:

    VkPhysicalDeviceProperties2 physicalDeviceProperties;
    VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties;//Provided by VK_API_VERSION_1_2
    VkPhysicalDeviceVulkan12Properties physicalDeviceVulkan12Properties;
    VkPhysicalDeviceVulkan13Properties physicalDeviceVulkan13Properties;
    VkPhysicalDeviceMemoryProperties2 physicalDeviceMemoryProperties;   //截至Vulkan1.3为止,不存在VkPhysicalDeviceVulkan11MemoryProperties等结构体
    //获取物理设备属性,逻辑跟GetPhysicalDeviceFeatures()一样,解说略
    void GetPhysicalDeviceProperties() {
        if (apiVersion >= VK_API_VERSION_1_1) {
            physicalDeviceProperties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 };
            physicalDeviceVulkan11Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES };
            physicalDeviceVulkan12Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES };
            physicalDeviceVulkan13Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES };
            if (apiVersion >= VK_API_VERSION_1_2) {
                physicalDeviceProperties.pNext = &physicalDeviceVulkan11Properties;
                physicalDeviceVulkan11Properties.pNext = &physicalDeviceVulkan12Properties;
                if (apiVersion >= VK_API_VERSION_1_3) {
                    physicalDeviceVulkan12Properties.pNext = &physicalDeviceVulkan13Properties;
            }
            vkGetPhysicalDeviceProperties2(physicalDevice, &physicalDeviceProperties);
            physicalDeviceMemoryProperties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2 };
            vkGetPhysicalDeviceMemoryProperties2(physicalDevice, &physicalDeviceMemoryProperties);
        }
        else
            vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties.properties),
            vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physicalDeviceMemoryProperties.memoryProperties);
    }
public:
    constexpr const VkPhysicalDeviceProperties2& PhysicalDeviceProperties() const { return physicalDeviceProperties.properties; }
    constexpr const VkPhysicalDeviceVulkan11Properties& PhysicalDeviceVulkan11Properties() const { return physicalDeviceVulkan11Properties; }
    constexpr const VkPhysicalDeviceVulkan12Properties& PhysicalDeviceVulkan12Properties() const { return physicalDeviceVulkan12Properties; }
    constexpr const VkPhysicalDeviceVulkan13Properties& PhysicalDeviceVulkan13Properties() const { return physicalDeviceVulkan13Properties; }
    constexpr const VkPhysicalDeviceMemoryProperties2& PhysicalDeviceMemoryProperties() const { return physicalDeviceProperties.properties; }

设置pNext

我在此前的教程中几乎完全忽略了各种结构体的pNext成员。
一个结构体的pNext成员指向下一个结构体,下一个结构体的pNext又指向再下一个结构体,这种链表在Vulkan官方标准中被称为pNext链(pNext chain)。

若要使用一些由扩展提供的功能特性,需要将涉及的结构体加入pNext链来查询并开启特性,比如若要开启光追的话,需要在VkPhysicalDeviceFeatures2的后续pNext链中加入VkPhysicalDeviceAccelerationStructureFeaturesKHRVkPhysicalDeviceAccelerationStructureFeaturesKHR
而在你设计程序时,可能会把不同功能的部分放在各自八竿子打不着的代码中,不同的功能可能需要开启相同的设备特性。定义一个graphicsBase的静态成员函数SetPNext(...)来确保正确处理pNext链,旨在让结构体能以任何顺序被加入pNext链并查重,以及定义其他相关的成员变量和函数:

    void* pNext_instanceCreateInfo;
    void* pNext_deviceCreateInfo;
    void* pNext_physicalDeviceFeatures;
    void* pNext_physicalDeviceProperties;
    void* pNext_physicalDeviceMemoryProperties;
    static void** SetPNext(void*& pBegin, void* pNext, bool allowDuplicate = false) {
        /*待填充*/
    }
public:
    //以下各函数将指定的next结构体添加到各个pNext链末尾
    void AddNextStructure_InstanceCreateInfo(auto& next, bool allowDuplicate = false) {
        SetPNext(pNext_instanceCreateInfo, &next, allowDuplicate);
    }
    void AddNextStructure_DeviceCreateInfo(auto& next, bool allowDuplicate = false) {
        SetPNext(pNext_deviceCreateInfo, &next, allowDuplicate);
    }
    void AddNextStructure_PhysicalDeviceFeatures(auto& next, bool allowDuplicate = false) {
        SetPNext(pNext_physicalDeviceFeatures, &next, allowDuplicate);
    }
    void AddNextStructure_PhysicalDeviceProperties(auto& next, bool allowDuplicate = false) {
        SetPNext(pNext_physicalDeviceProperties, &next, allowDuplicate);
    }
    void AddNextStructure_PhysicalDeviceMemoryProperties(auto& next, bool allowDuplicate = false) {
        SetPNext(pNext_physicalDeviceMemoryProperties, &next, allowDuplicate);
    }
    void AddNextStructure_SwapchainCreateInfo(auto& next, bool allowDuplicate = false) {
        SetPNext(const_cast<void*&>(swapchainCreateInfo.pNext), &next, allowDuplicate);
    }
  • 各成员变量名已经顾名思义地说明了其用处。VkSwapchainCreateInfoKHR则因需要重建交换链而保存为了成员变量,直接使用其中的pNext成员(除非你不喜欢const_cast)。

  • SetPNext(...)的参数allowDuplicate指定是否允许某种结构体重复出现。对于某些函数,某些结构体可以在向其提供的pNext链中出现多个,比如VkDeviceCreateInfo的pNext链中,允许有多个VkDeviceDeviceMemoryReportCreateInfoEXTVkDevicePrivateDataCreateInfo

以引用传递的pBegin是向SetPNext(...)提供的pNext链中的首个指针。
SetPNext(...)的返回类型是void**,这个无法一上来就解释清楚,但我打算让函数在给定的pNext参数无法被接到以pBegin起始的pNext链时,返回nullptr

static void** SetPNext(void*& pBegin, void* pNext, bool allowDuplicate = false) {
    struct vkStructureHead {
        VkStructureType sType;
        void* pNext;
    };
    if (!pNext)
        return nullptr;
    /*待后续填充*/
}
  • pNext链中各个结构体都有不同的构造,但开头是相同的,可以通过reinterpret_cast<vkStructureHead>(...),获得pBeginpNext所指向结构体的sType和pNext成员。

  • 因为pNext链尾部的指针本就是nullptr,若传入函数的参数pNextnullptr,不必有后续执行,直接返回nullptr

既然pNext链是链表,很容易想到使用递归来处理相关逻辑。SetPNext(...)剩下的部分我打算使用一个能自我调用的lambda表达式来实现:

static void** SetPNext(void*& pBegin, void* pNext, bool allowDuplicate = false) {
    struct vkStructureHead {
        VkStructureType sType;
        void* pNext;
    };
    if (!pNext)
        return nullptr;
    auto SetPNext_Internal = [](this auto&& self, void*& pCurrentNode, void* pNext, bool allowDuplicate)->void** {
        /*待后续填充*/
    };
    retur SetPNext_Internal(pBegin, pNext, allowDuplicate);
}
  • this auto&&这个写法叫lambda的显式对象形参(explicit object parameter),是C++23的语法。显式对象参数对于类成员函数和lambda有不同的含义,这里的lambda显式对象参数self指代lambda自身。Lambda表达式属于函数对象,由于对象的初始化器中不能递归地提及对象本身的名称,在C++23以前,实现递归lambda的方式要略微繁琐一些。

填充SetPNext_Internal(...),先只用关心pCurrentNodepNext两个参数的关系,这里pCurrentNode是pNext链中的任意节点(即某一结构体的pNext成员),pNext则跟传入SetPNext(...)的pNext数值一致。
显然,若pCurrentNodepNext一致,则说明pNext指定的结构体已在pNext链中,则函数执行失败(pNext链中可能允许某种结构体出现多个,但特定对象只能出现一次,否则理所当然地会出现无限递归链表)。
然后,若pCurrentNodenullptr,说明达到了链表尾端,将pCurrentNode赋值为pNext,成功时的返回值是pCurrentNode的地址,即被赋值为了pNext的那个节点的地址,用处到本节末尾再解释。

static void** SetPNext(void*& pBegin, void* pNext, bool allowDuplicate = false) {
    struct vkStructureHead {
        VkStructureType sType;
        void* pNext;
    };
    if (!pNext)
        return nullptr;
    auto SetPNext_Internal = [](this auto&& self, void*& pCurrentNode, void* pNext, bool allowDuplicate)->void** {
        if (pCurrentNode == pNext)
            return nullptr;
        if (pCurrentNode)
            /*待后续填充*/
        else
            return &(pCurrentNode = pNext);
    };
    retur SetPNext_Internal(pBegin, pNext, allowDuplicate);
}

allowDuplicatefalse,比较pCurrentNodepNext指向结构体的sType成员,一致则函数执行失败:

auto SetPNext_Internal = [](this auto&& self, void*& pCurrentNode, void* pNext, bool allowDuplicate)->void** {
    if (pCurrentNode == pNext)
        return nullptr;
    if (pCurrentNode)
        if (!allowDuplicate &&
            reinterpret_cast<vkStructureHead>(pCurrentNode)->sType == reinterpret_cast<vkStructureHead>(pNext)->sType)
            return nullptr;
        else
            /*待后续填充*/
    else
        return &(pCurrentNode = pNext);
};

还剩的一个else分支就是pCurrentNode非空并且满足查重条件的情况,这时候就继续向链表后续节点迭代,后续节点即当前节点pCurrentNode所指结构体的pNext成员:

static void** SetPNext(void*& pBegin, void* pNext, bool allowDuplicate = false) {
    struct vkStructureHead {
        VkStructureType sType;
        void* pNext;
    };
    if (!pNext)
        return nullptr;
    auto SetPNext_Internal = [](this auto&& self, void*& pCurrentNode, void* pNext, bool allowDuplicate)->void** {
        if (pCurrentNode == pNext)
            return nullptr;
        if (pCurrentNode)
            if (!allowDuplicate &&
                reinterpret_cast<vkStructureHead>(pCurrentNode)->sType == reinterpret_cast<vkStructureHead>(pNext)->sType)
                return nullptr;
            else
                return self(reinterpret_cast<vkStructureHead>(pCurrentNode)->pNext, pNext, allowDuplicate);
        else
            return &(pCurrentNode = pNext);
    };
    retur SetPNext_Internal(pBegin, pNext, allowDuplicate);
}

接着考虑更改创建Vulkan实例、逻辑设备、交换链的函数。
24.03.20 注:原本在Ch1中,是在CreateInstance(...)和CreateDevice(...)等函数的参数中传入创建信息的pNext,在写本文时对以往教程和示例代码一并修改,去掉了这些函数参数中的const void* pNext。在此日期前没读过我的教程的话不必在意。
CreateSwapchain(...)不用修改,CreateInstance(...)中只要赋值创建信息的pNext成员就行:

VkResult CreateInstance(VkInstanceCreateFlags flags = 0) {
    /*...*/
    VkInstanceCreateInfo instanceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .pNext = pNext_instanceCreateInfo,//新增
        .flags = flags,
        .pApplicationInfo = &applicatianInfo,
        .enabledLayerCount = uint32_t(instanceLayers.size()),
        .ppEnabledLayerNames = instanceLayers.data(),
        .enabledExtensionCount = uint32_t(instanceExtensions.size()),
        .ppEnabledExtensionNames = instanceExtensions.data()
    };
    /*...*/
}

更改CreateDevice(...)前,简单修改GetPhysicalDeviceFeaturess()和GetPhysicalDeviceProperties(),调用SetPNext(...):

void GetPhysicalDeviceFeatures() {
    if (apiVersion >= VK_API_VERSION_1_1) {
        physicalDeviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
        physicalDeviceVulkan11Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES };
        physicalDeviceVulkan12Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES };
        physicalDeviceVulkan13Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES };
        if (apiVersion >= VK_API_VERSION_1_2) {
            physicalDeviceFeatures.pNext = &physicalDeviceVulkan11Features;
            physicalDeviceVulkan11Features.pNext = &physicalDeviceVulkan12Features;
            if (apiVersion >= VK_API_VERSION_1_3)
                physicalDeviceVulkan12Features.pNext = &physicalDeviceVulkan13Features;
        }
        SetPNext(physicalDeviceFeatures.pNext, pNext_physicalDeviceFeatures);
        vkGetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures);
    }
    else
        vkGetPhysicalDeviceFeatures(physicalDevice, &physicalDeviceFeatures.features);
}
void GetPhysicalDeviceProperties() {
    if (apiVersion >= VK_API_VERSION_1_1) {
        physicalDeviceProperties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 };
        physicalDeviceVulkan11Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES };
        physicalDeviceVulkan12Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES };
        physicalDeviceVulkan13Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES };
        if (apiVersion >= VK_API_VERSION_1_2) {
            physicalDeviceProperties.pNext = &physicalDeviceVulkan11Properties;
            physicalDeviceVulkan11Properties.pNext = &physicalDeviceVulkan12Properties;
            if (apiVersion >= VK_API_VERSION_1_3) {
                physicalDeviceVulkan12Properties.pNext = &physicalDeviceVulkan13Properties;
        }
        SetPNext(physicalDeviceProperties.pNext, pNext_physicalDeviceProperties);
        vkGetPhysicalDeviceProperties2(physicalDevice, &physicalDeviceProperties);
        physicalDeviceMemoryProperties = {
            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2,
            pNext_physicalDeviceMemoryProperties //没必要调用SetPNext(...)
        };
        vkGetPhysicalDeviceMemoryProperties2(physicalDevice, &physicalDeviceMemoryProperties);
    }
    else
        vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties.properties),
        vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physicalDeviceMemoryProperties.memoryProperties);
}

更改CreateDevice(...),类似CreateInstance(...),赋值创建信息的pNext成员,然后将获取设备特性、属性、内存属性的部分用前述封装好的函数替代:

VkResult CreateDevice(VkDeviceCreateFlags flags = 0) {
    float queuePriority = 1.f;
    VkDeviceQueueCreateInfo queueCreateInfos[3] = {
        {
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .queueCount = 1,
            .pQueuePriorities = &queuePriority },
        {
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .queueCount = 1,
            .pQueuePriorities = &queuePriority },
        {
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .queueCount = 1,
            .pQueuePriorities = &queuePriority } };
    uint32_t queueCreateInfoCount = 0;
    if (queueFamilyIndex_graphics != VK_QUEUE_FAMILY_IGNORED)
        queueCreateInfos[queueCreateInfoCount++].queueFamilyIndex = queueFamilyIndex_graphics;
    if (queueFamilyIndex_presentation != VK_QUEUE_FAMILY_IGNORED &&
        queueFamilyIndex_presentation != queueFamilyIndex_graphics)
        queueCreateInfos[queueCreateInfoCount++].queueFamilyIndex = queueFamilyIndex_presentation;
    if (queueFamilyIndex_compute != VK_QUEUE_FAMILY_IGNORED &&
        queueFamilyIndex_compute != queueFamilyIndex_graphics &&
        queueFamilyIndex_compute != queueFamilyIndex_presentation)
        queueCreateInfos[queueCreateInfoCount++].queueFamilyIndex = queueFamilyIndex_compute;
    /*变更*/GetPhysicalDeviceFeatures();
    VkDeviceCreateInfo deviceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext = pNext_deviceCreateInfo,//新增,不过一会儿还会删掉
        .flags = flags,
        .queueCreateInfoCount = queueCreateInfoCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = uint32_t(deviceExtensions.size()),
        .ppEnabledExtensionNames = deviceExtensions.data(),
        .pEnabledFeatures = &physicalDeviceFeatures
    };
    if (VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device)) {
        outStream << std::format("[ graphicsBase ] ERROR\nFailed to create a vulkan logical device!\nError code: {}\n", int32_t(result));
        return result;
    }
    if (queueFamilyIndex_graphics != VK_QUEUE_FAMILY_IGNORED)
        vkGetDeviceQueue(device, queueFamilyIndex_graphics, 0, &queue_graphics);
    if (queueFamilyIndex_presentation != VK_QUEUE_FAMILY_IGNORED)
        vkGetDeviceQueue(device, queueFamilyIndex_presentation, 0, &queue_presentation);
    if (queueFamilyIndex_compute != VK_QUEUE_FAMILY_IGNORED)
        vkGetDeviceQueue(device, queueFamilyIndex_compute, 0, &queue_compute);
    /*变更*/GetPhysicalDeviceProperties();
    //下一行的physicalDeviceProperties.deviceName变成了physicalDeviceProperties.properties.deviceName
    outStream << std::format("Renderer: {}\n", physicalDeviceProperties.properties.deviceName);
    for (auto& i : callbacks_createDevice)
        i();
    return VK_SUCCESS;
}

事情没完,标准中规定:如果开启的设备特性由VkPhysicalDeviceFeatures2而非VkPhysicalDeviceFeatures指定,那么VkPhysicalDeviceFeatures2被接在VkDeviceCreateInfo的pNext链中,而VkDeviceCreateInfo::pEnabledFeatures必须为空指针:

VkResult CreateDevice(VkDeviceCreateFlags flags = 0) {
    /*...*/
    GetPhysicalDeviceFeatures();
    VkDeviceCreateInfo deviceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        //.pNext = pNext_deviceCreateInfo, //若pNext_deviceCreateInfo为nullptr,在此赋值没有意义,到执行SetPNext(...)后再赋值pNext
        .flags = flags,
        .queueCreateInfoCount = queueCreateInfoCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = uint32_t(deviceExtensions.size()),
        .ppEnabledExtensionNames = deviceExtensions.data(),
        //.pEnabledFeatures = &physicalDeviceFeatures
    };
    if (apiVersion >= VK_API_VERSION_1_1)
        SetPNext(pNext_deviceCreateInfo, &physicalDeviceFeatures);
    else
        deviceCreateInfo.pEnabledFeatures = &physicalDeviceFeatures.features;
    deviceCreateInfo.pNext = pNext_deviceCreateInfo;
    if (VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device)) {
        outStream << std::format("[ graphicsBase ] ERROR\nFailed to create a vulkan logical device!\nError code: {}\n", int32_t(result));
        return result;
    }
    /*...*/
}

事情还是没完,上述代码有个隐藏的风险。
执行SetPNext(pNext_deviceCreateInfo, &physicalDeviceFeatures);后,deviceCreateInfo的pNext链变成了这么个顺序:
deviceCreateInfo本身 -> 调用CreateDevice(...)前以pNext_deviceCreateInfo起始的pNext链 -> 设备特性结构体的链表

考虑到重建逻辑设备的需要,CreateDevice(...)会被RecreateDevice(...)调用,然后再次执行GetPhysicalDeviceFeatures()。
可是,如果重建逻辑设备前在以pNext_deviceCreateInfo起始的pNext链最后接了新的结构体呢(基于开启某些显卡特定的功能等理由)?
按上述代码的写法,新的结构体会被接在设备特性结构体链表的最后,而vkGetPhysicalDeviceFeatures2(...)只接收与设备特性有关的的结构体,于是很可能运行出错。

因此,有必要在创建逻辑设备后,再将设备特性结构体链表,从以pNext_deviceCreateInfo起始的pNext链中分离出来(这就是让SetPNext(...)返回一个void**类型指针的原因):

VkResult CreateDevice(VkDeviceCreateFlags flags = 0) {
    /*...*/
    GetPhysicalDeviceFeatures();
    VkDeviceCreateInfo deviceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        //.pNext = pNext_deviceCreateInfo,
        .flags = flags,
        .queueCreateInfoCount = queueCreateInfoCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = uint32_t(deviceExtensions.size()),
        .ppEnabledExtensionNames = deviceExtensions.data(),
        //.pEnabledFeatures = &physicalDeviceFeatures
    };
    void** ppNext = nullptr;
    if (apiVersion >= VK_API_VERSION_1_1)
        ppNext = SetPNext(pNext_deviceCreateInfo, &physicalDeviceFeatures);
    else
        deviceCreateInfo.pEnabledFeatures = &physicalDeviceFeatures.features;
    deviceCreateInfo.pNext = pNext_deviceCreateInfo;
    VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device)
    if (ppNext)
        *ppNext = nullptr;
    if (result) {
        outStream << std::format("[ graphicsBase ] ERROR\nFailed to create a vulkan logical device!\nError code: {}\n", int32_t(result));
        return result;
    }
    /*...*/
}