逻辑设备

更新记录
  • 2024/1/2 增加该文档。

  • 2024/2/5 更新该文档。

  • 2024/2/5 增加 创建逻辑设备 章节。

  • 2024/2/5 增加 vkCreateDevice 章节。

  • 2024/2/5 增加 VkDeviceCreateInfo 章节。

  • 2024/2/7 增加 VkDeviceQueueCreateInfo 章节。

  • 2024/2/7 增加 设备扩展 章节。

  • 2024/2/7 增加 vkEnumerateDeviceExtensionProperties 章节。

  • 2024/2/7 增加 VkExtensionProperties 章节。

  • 2024/2/7 增加 销毁逻辑设备 章节。

  • 2024/2/7 增加 vkDestroyDevice 章节。

  • 2024/2/7 增加 设备特性 章节。

  • 2024/2/7 增加 示例 章节。

  • 2024/2/7 增加 vkGetPhysicalDeviceFeatures 章节。

  • 2024/2/7 增加 VkPhysicalDeviceFeatures 章节。

  • 2024/2/8 更新 VkPhysicalDeviceFeatures 章节。

  • 2024/2/8 更新 VkDeviceQueueCreateInfo 章节。

  • 2024/2/8 增加 获取设备队列 章节。

  • 2024/2/8 增加 获取设备队列 章节下的 示例

  • 2024/2/16 更新 vkDestroyDevice 章节。

物理设备 章节中我们已经知道,可以获取系统中支持 Vulkan 的多个物理设备 VkPhysicalDevice 。我们需要确定使用哪一个或哪几个物理设备作为目标设备为我们所用,为此 Vulkan 将物理设备抽象成逻辑设备 VkDevice

当确定了逻辑设备之后,我们就可以在该设备上进行资源的创建,分配,操作,销毁和回收等各种各样的操作。

创建逻辑设备

我们可以通过 vkCreateDevice(...) 函数创建逻辑设备。其定义如下:

vkCreateDevice

// 由 VK_VERSION_1_0 提供
VkResult vkCreateDevice(
    VkPhysicalDevice                            physicalDevice,
    const VkDeviceCreateInfo*                   pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkDevice*                                   pDevice);
  • physicalDevice 指定在哪一个物理设备上创建逻辑设备。

  • pCreateInfo 创建逻辑设备的配置信息。

  • pAllocator 内存分配器。为 nullptr 表示使用内部默认分配器,否则为自定义分配器。

  • pDevice 创建逻辑设备的结果。

其中 VkDeviceCreateInfo 定义如下:

VkDeviceCreateInfo

// 由 VK_VERSION_1_0 提供
typedef struct VkDeviceCreateInfo {
    VkStructureType                    sType;
    const void*                        pNext;
    VkDeviceCreateFlags                flags;
    uint32_t                           queueCreateInfoCount;
    const VkDeviceQueueCreateInfo*     pQueueCreateInfos;
    uint32_t                           enabledLayerCount;
    const char* const*                 ppEnabledLayerNames;
    uint32_t                           enabledExtensionCount;
    const char* const*                 ppEnabledExtensionNames;
    const VkPhysicalDeviceFeatures*    pEnabledFeatures;
} VkDeviceCreateInfo;
  • sType 是该结构体的类型枚举值, 必须VkStructureType::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO

  • pNext 要么是 NULL 要么指向其他结构体来扩展该结构体。

  • flags 目前没用上,为将来做准备。

  • queueCreateInfoCount 指定 pQueueCreateInfos 数组元素个数。

  • pQueueCreateInfos 指定 VkDeviceQueueCreateInfo 数组。用于配置要创建的设备队列信息。

  • enabledLayerCount 指定 ppEnabledLayerNames 数组元素个数。该成员已被 遗弃忽略

  • ppEnabledLayerNames 指定要开启的验证层。该成员已被 遗弃忽略

  • enabledExtensionCount 指定 ppEnabledExtensionNames 数组中元素个数。

  • ppEnabledExtensionNames 指定要开启的扩展。该数组数量必须大于等于 enabledExtensionCount

  • pEnabledFeatures 配置要开启的特性。

其中 queueCreateInfoCountpQueueCreateInfos 用于指定在逻辑设备中需要创建的 设备队列 。其中 VkDeviceQueueCreateInfo 定义如下:

VkDeviceQueueCreateInfo

// 由 VK_VERSION_1_0 提供
typedef struct VkDeviceQueueCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkDeviceQueueCreateFlags    flags;
    uint32_t                    queueFamilyIndex;
    uint32_t                    queueCount;
    const float*                pQueuePriorities;
} VkDeviceQueueCreateInfo;
  • sType 是该结构体的类型枚举值, 必须VkStructureType::VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO

  • pNext 要么是 NULL 要么指向其他结构体来扩展该结构体。

  • flags 配置额外的信息。可设置的值定义在 VkDeviceQueueCreateFlagBits 枚举中。

  • queueFamilyIndex 指定目标设备队列族的索引。

  • queueCount 指定要在 queueFamilyIndex 中创建设备队列的数量。

  • pQueuePriorities 指向元素数量为 queueCountfloat 数组。用于配置创建的每一个设备队列的优先级。

其中 queueFamilyIndex 必须 是目标物理设备中有效的设备队列族索引,并且 queueCount 必须 小于等于 queueFamilyIndex 索引对应的设备队列族中的队列数量。

其中 pQueuePriorities 配置的优先级的有效等级范围为 [0, 1] ,优先级越大,优先级越高。其中 0.0 是最低的优先级, 1.0 是最高的优先级。在某些设备中,优先级越高意味着将会得到更多的执行机会,具体的队列调由设备自身管理, Vulkan 并不规定调度规则。 在同一逻辑设备上优先级高的设备队列可能会导致低优先级的设备队列长时间处于 饥饿 状态,直到高级别的设备队列执行完所有指令。但不同的逻辑设备中的某一设备队列饥饿不会影响另一个逻辑设备上的设备队列。

VkDeviceQueueCreateInfo::flags

VkDeviceQueueCreateFlagBitsVulkan 1.0 版本中没用定义任何成员。

饥饿

队列饥饿。指的是在系统调度中,总是优先调度优先级高的队列,如果在运行时,有源源不断的任务进行高优先级队列,则系统调度会一直调度该高优先级队列,而不调度低优先级的队列。这就会导致低优先级的队列长期处于无响应阶段得不到执行。

设备扩展

VkDeviceCreateInfo 我们需要通过 enabledExtensionCountppEnabledExtensionNames 来指定该逻辑设备要开启的 设备扩展Device Extension )。在开启设备扩展之前,我们需要通过 vkEnumerateDeviceExtensionProperties(...) 函数获取目标设备支持的扩展。其定义如下:

vkEnumerateDeviceExtensionProperties

// 由 VK_VERSION_1_0 提供
VkResult vkEnumerateDeviceExtensionProperties(
    VkPhysicalDevice                            physicalDevice,
    const char*                                 pLayerName,
    uint32_t*                                   pPropertyCount,
    VkExtensionProperties*                      pProperties);
  • physicalDevice 要查询扩展的目标物理设备。

  • pLayerName 要么为 要么为 的名称。

  • pPropertyCount 要么为 要么为 pProperties 中元素的数量。

  • pProperties 为扩展信息数组。元素个数 必须 大于等于 pPropertyCount 中指定数量。

如果 pLayerName 为有效的 名,则该函数将会返回该层内部使用的 设备扩展

如果 pLayerNamenullptr ,则该函数将会返回 Vulkan 实现和默认启用的 支持的设备扩展信息。

该函数调用与 vkEnumerateInstanceExtensionProperties(...) 类似,这里不在赘述。通过两次调用 vkEnumerateDeviceExtensionProperties(...) 函数获取设备扩展信息:

VkPhysicalDevice physical_device = 之前获取的物理设备;

uint32_t extension_property_count = 0;
vkEnumerateDeviceExtensionProperties(physical_device, &extension_property_count, nullptr);

std::vector<VkExtensionProperties> extension_properties(extension_property_count);
vkEnumerateDeviceExtensionProperties(physical_device, &extension_property_count, extension_properties.data());

获取的设备扩展信息类型 VkExtensionPropertiesvkEnumerateInstanceExtensionProperties(...) 中的一样,这里只给出定义,不再赘述:

VkExtensionProperties

// 由 VK_VERSION_1_0 提供
typedef struct VkExtensionProperties {
    char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];
    uint32_t    specVersion;
} VkExtensionProperties;

有一些设备扩展我们需要重点关注一下

  • VK_KHR_swapchain 交换链。用于与 VK_KHR_surface 和平台相关的 VK_{vender}_{platform}_surface 扩展配合使用。用于窗口化显示渲染结果。

  • VK_KHR_display 某些平台支持直接全屏显示渲染结果(比如嵌入式平台:车载、移动平台等)。

  • VK_KHR_display_swapchain 全屏显示交换链。与 VK_KHR_display 扩展配合使用。

  • VK_EXT_mesh_shader 网格着色器。一开始为 NVIDIA 推出的全新管线,有很多优点,后来用的多了就形成了一套标准。

  • VK_KHR_dynamic_rendering 动态渲染。为简单渲染时配置过于复杂的诟病提供的一套解决方案。该扩展在 Vulkan 1.3 被提升至核心。

  • VK_KHR_external_memory 外部内存。一般用于 OpenGLVulkan 联动。

  • VK_KHR_buffer_device_address 着色器中支持使用设备地址(类似于特殊的指针)。常用于 硬件实时光追

  • VK_KHR_spirv_1_4 SPIR-V 1.4 支持。常用于 硬件实时光追

硬件实时光追

  • VK_KHR_acceleration_structure 用于光追加速结构。

  • VK_KHR_ray_tracing_pipeline 用于光追管线。

  • VK_KHR_ray_query 用于光线查询。

  • VK_KHR_pipeline_library 用于整合光追管线。

设备特性

在创建逻辑设备时需要配置 VkDeviceCreateInfo::pEnabledFeatures 参数,该参数用于配置该逻辑设备要开启的设备特性。一个物理设备会支持一系列特性。可以通过 vkGetPhysicalDeviceFeatures(...) 获取该物理设备支持的特性,其定义如下:

vkGetPhysicalDeviceFeatures

// 由 VK_VERSION_1_0 提供
void vkGetPhysicalDeviceFeatures(
    VkPhysicalDevice                            physicalDevice,
    VkPhysicalDeviceFeatures*                   pFeatures);
  • physicalDevice 目标物理设备。

  • pFeatures 支持的特性信息将会写入该指针指向的内存中。

其中 VkPhysicalDeviceFeatures 定义如下:

VkPhysicalDeviceFeatures

// 由 VK_VERSION_1_0 提供
typedef struct VkPhysicalDeviceFeatures {
    VkBool32    robustBufferAccess;
    VkBool32    fullDrawIndexUint32;
    VkBool32    imageCubeArray;
    VkBool32    independentBlend;
    VkBool32    geometryShader;
    VkBool32    tessellationShader;
    VkBool32    sampleRateShading;
    VkBool32    dualSrcBlend;
    VkBool32    logicOp;
    VkBool32    multiDrawIndirect;
    VkBool32    drawIndirectFirstInstance;
    VkBool32    depthClamp;
    VkBool32    depthBiasClamp;
    VkBool32    fillModeNonSolid;
    VkBool32    depthBounds;
    VkBool32    wideLines;
    VkBool32    largePoints;
    VkBool32    alphaToOne;
    VkBool32    multiViewport;
    VkBool32    samplerAnisotropy;
    VkBool32    textureCompressionETC2;
    VkBool32    textureCompressionASTC_LDR;
    VkBool32    textureCompressionBC;
    VkBool32    occlusionQueryPrecise;
    VkBool32    pipelineStatisticsQuery;
    VkBool32    vertexPipelineStoresAndAtomics;
    VkBool32    fragmentStoresAndAtomics;
    VkBool32    shaderTessellationAndGeometryPointSize;
    VkBool32    shaderImageGatherExtended;
    VkBool32    shaderStorageImageExtendedFormats;
    VkBool32    shaderStorageImageMultisample;
    VkBool32    shaderStorageImageReadWithoutFormat;
    VkBool32    shaderStorageImageWriteWithoutFormat;
    VkBool32    shaderUniformBufferArrayDynamicIndexing;
    VkBool32    shaderSampledImageArrayDynamicIndexing;
    VkBool32    shaderStorageBufferArrayDynamicIndexing;
    VkBool32    shaderStorageImageArrayDynamicIndexing;
    VkBool32    shaderClipDistance;
    VkBool32    shaderCullDistance;
    VkBool32    shaderFloat64;
    VkBool32    shaderInt64;
    VkBool32    shaderInt16;
    VkBool32    shaderResourceResidency;
    VkBool32    shaderResourceMinLod;
    VkBool32    sparseBinding;
    VkBool32    sparseResidencyBuffer;
    VkBool32    sparseResidencyImage2D;
    VkBool32    sparseResidencyImage3D;
    VkBool32    sparseResidency2Samples;
    VkBool32    sparseResidency4Samples;
    VkBool32    sparseResidency8Samples;
    VkBool32    sparseResidency16Samples;
    VkBool32    sparseResidencyAliased;
    VkBool32    variableMultisampleRate;
    VkBool32    inheritedQueries;
} VkPhysicalDeviceFeatures;

VkPhysicalDeviceFeatures 中定义了 Vulkan 1.0 标准设备特性。由于该结构体中成员过多,这里会挑选几个常用的进行讲解。其他的特性在需要使用时会进行说明。

  • geometryShader 几何着色器。将会在之后的 渲染管线 章节中进行讲解。

  • tessellationShader 细分着色器。将会在之后的 渲染管线 章节中进行讲解。

  • wideLines 线宽。当绘制线时可以动态设置线宽。

备注

您可以直接开启所有支持的设备特性。但这不是一个明智的选择,特性开启后多少都会消耗设备资源,所以尽量只开启需要的特性。

扩展和特性

有些特性是与设备扩展绑定的。换句话说就是,当开启了某些设备扩展,相应的特性也需要开启。比如:

  • 在开启 VK_KHR_ray_tracing_pipeline 光追管线扩展之后,需要使用 VkPhysicalDeviceRayTracingPipelineFeaturesKHR 特性结构体配置开启光追特性。其定义如下:

// 由 VK_KHR_ray_tracing_pipeline 提供
typedef struct VkPhysicalDeviceRayTracingPipelineFeaturesKHR {
    VkStructureType    sType;
    void*              pNext;
    VkBool32           rayTracingPipeline;
    VkBool32           rayTracingPipelineShaderGroupHandleCaptureReplay;
    VkBool32           rayTracingPipelineShaderGroupHandleCaptureReplayMixed;
    VkBool32           rayTracingPipelineTraceRaysIndirect;
    VkBool32           rayTraversalPrimitiveCulling;
} VkPhysicalDeviceRayTracingPipelineFeaturesKHR;

可以看到 VkPhysicalDeviceRayTracingPipelineFeaturesKHR 为扩展 VK_KHR_ray_tracing_pipeline 提供的结构体。也就是说只有在 VK_KHR_ray_tracing_pipeline 扩展被成功激活后才可以使用该结构体。

重要

由于目前以 Vulkan 1.0 核心进行讲解,所以目前不会对于扩展和高版本的 Vulkan 设备特性进行展开讲解,为了知识的连贯性会在必要的时候提一嘴。但会在未来规划章节中进行详细讲解。

备注

有关 Vulkan 的硬件实时光追相关教程可以先浏览 文献 中相关资料。

销毁逻辑设备

在创建完逻辑设备之后,可以通过 vkDestroyDevice(...) 销毁创建的逻辑设备。其定义如下:

vkDestroyDevice

// 由 VK_VERSION_1_0 提供
void vkDestroyDevice(
    VkDevice                                    device,
    const VkAllocationCallbacks*                pAllocator);
  • device 要销毁的逻辑设备。

  • pAllocator 内存分配器。需要与 vkCreateDevice(...) 时使用的分配器保持一致。

device 销毁时,需要确保所有该逻辑设备下创建的对象(句柄)都已经回收或销毁。

示例

VkPhysicalDevice physical_device = 之前获取到的物理设备;
uint32_t support_graphics_queue_family_index = 之前获取到支持图形功能的队列族索引;

std::vector<float> queue_priorities;
queue_priorities.push_back(0.0f);
queue_priorities.push_back(0.0f);

VkDeviceQueueCreateInfo device_queue_create_info = {};
device_queue_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO ;
device_queue_create_info.pNext = nullptr;
device_queue_create_info.flags = 0;
device_queue_create_info.queueFamilyIndex = support_graphics_queue_family_index;
device_queue_create_info.queueCount = 2; // 一般创建 1 个图形队列即可。这里创建 2 个支持图形的设备队列(假如 support_graphics_queue_family_index 对应的设备族中有 2 个以上设备队列)。
device_queue_create_info.pQueuePriorities = queue_priorities.data();

uint32_t extension_property_count = 0;
vkEnumerateDeviceExtensionProperties(physical_device, &extension_property_count, nullptr);

std::vector<VkExtensionProperties> extension_properties(extension_property_count);
vkEnumerateDeviceExtensionProperties(physical_device, &extension_property_count, extension_properties.data());

std::vector<char*> enable_device_extensions;
for(const VkExtensionProperties& extension_property_item : extension_properties)
{
   if(std::strcmp(extension_property_item.extensionName, "VK_KHR_swapchain") == 0)
   {
      enable_device_extensions.push_back("VK_KHR_swapchain");
      break;
   }
}

if(enable_device_extensions.empty())
{
   throw std::runtime_error("设备不支持交换链扩展");
}

VkPhysicalDeviceFeatures support_physical_device_features = {};
vkGetPhysicalDeviceFeatures(physical_device, &support_physical_device_features);

VkPhysicalDeviceFeatures enable_physical_device_features = {};
if(support_physical_device_features.geometryShader == VK_TRUE && support_physical_device_features.tessellationShader == VK_TRUE && support_physical_device_features.wideLines == VK_TRUE )
{
   enable_physical_device_features.geometryShader = support_physical_device_features.geometryShader;
   enable_physical_device_features.tessellationShader = support_physical_device_features.tessellationShader;
   enable_physical_device_features.wideLines = support_physical_device_features.wideLines;
}
else
{
   throw std::runtime_error("设备不支持几何着色器、细分着色器和线宽特性");
}

VkDeviceCreateInfo device_create_info = {};
device_create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.pNext = nullptr;
device_create_info.flags = 0;
device_create_info.queueCreateInfoCount=1;
device_create_info.pQueueCreateInfos = &device_queue_create_info;
device_create_info.enabledLayerCount = 0;
device_create_info.ppEnabledLayerNames = nullptr;
device_create_info.enabledExtensionCount = enable_device_extensions.size();
device_create_info.ppEnabledExtensionNames = enable_device_extensions.data();
device_create_info.pEnabledFeatures = &enable_physical_device_features;

VkDevice device = VK_NULL_HANDLE;
VkResult result = vkCreateDevice(physical_device, &device_create_info, nullptr, &device);
if(result != VkResult::VK_SUCCESS)
{
   throw std::runtime_error("逻辑设备创建失败");
}

// 获取设备队列 ...
// 缤纷绚丽的 Vulkan 程序 ...

vkDestroyDevice(device, nullptr);

获取设备队列

在创建完逻辑设备后,就可以通过 vkGetDeviceQueue(...) 函数获取。其定义如下:

// 由 VK_VERSION_1_0 提供
void vkGetDeviceQueue(
    VkDevice                                    device,
    uint32_t                                    queueFamilyIndex,
    uint32_t                                    queueIndex,
    VkQueue*                                    pQueue);
  • device 目标逻辑设备。

  • queueFamilyIndex 目标设备队列的队列族索引。

  • queueIndex 对应 VkDeviceQueueCreateInfo::queueCount 的对应设备队列索引。

  • pQueue 对应 VkDeviceQueueCreateInfo::queueCount 创建的第 queueIndex 的设备队列。

其中 queueFamilyIndexqueueIndex 的取值与创建逻辑设备时 VkDeviceCreateInfo::pQueueCreateInfos 参数相匹配。

示例

该示例紧接着上面的逻辑设备创建示例 示例

VkDevice device = 之前创建的逻辑设备;
uint32_t support_graphics_queue_family_index = 之前获取到支持图形功能的队列族索引;

//由于我们在 support_graphics_queue_family_index 索引的设备族上创建了 2 个设备队列,所以需要获取 2 个设备队列
VkQueue graphics_queue_0 = VK_NULL_HANDLE;
vkGetDeviceQueue(device, support_graphics_queue_family_index, 0, &graphics_queue_0);

VkQueue graphics_queue_1 = VK_NULL_HANDLE;
vkGetDeviceQueue(device, support_graphics_queue_family_index, 1, &graphics_queue_1);