应用程序通过 VkQueue 提交工作,通常以 VkCommandBuffer 对象或稀疏绑定的形式提交。
提交到同一个 VkQueue 的命令缓冲区按提交顺序开始执行,但开始后允许独立推进并乱序完成。
提交到不同队列的命令缓冲区彼此之间是无序的,除非使用 VkSemaphore 显式同步。
同一时间只能由一个线程向某个 VkQueue 提交工作,但不同线程可以同时向不同 VkQueue 提交工作。
VkQueue 如何映射到底层硬件是实现定义的。部分实现会拥有多个硬件队列,向多个 VkQueue 提交工作可以独立并发执行;部分实现会在内核驱动层面做调度后再提交给硬件。Vulkan 目前没有办法暴露每个 VkQueue 具体的映射细节。
并非所有应用都需要或能从多队列中受益。使用单个支持图形的 “通用” 队列向 GPU 提交所有工作是完全合理的。
Queue Family
一个 VkQueue 可以支持多种操作类型。Queue Family(队列族) 表示一组拥有相同属性、支持相同功能的 VkQueue,具体能力在 VkQueueFamilyProperties 中声明。
以下是 VkQueueFlagBits 中定义的队列操作:
-
VK_QUEUE_GRAPHICS_BIT用于 vkCmdDraw* 及图形管线相关命令。
-
VK_QUEUE_COMPUTE_BIT用于 vkCmdDispatch*、vkCmdTraceRays* 及计算管线相关命令。
-
VK_QUEUE_TRANSFER_BIT用于所有传输(transfer)命令。规范中的 VK_PIPELINE_STAGE_TRANSFER_BIT 对 “传输命令” 有详细说明。
仅包含 VK_QUEUE_TRANSFER_BIT 的队列族通常用于在独立显卡上利用 DMA 异步在主机与设备内存之间传输数据,使得传输可以与独立的图形 / 计算操作并发执行。
VK_QUEUE_GRAPHICS_BIT 和 VK_QUEUE_COMPUTE_BIT 总是可以隐式执行 VK_QUEUE_TRANSFER_BIT 命令。
-
VK_QUEUE_SPARSE_BINDING_BIT用于通过 vkQueueBindSparse 对稀疏资源进行内存绑定。
-
VK_QUEUE_PROTECTED_BIT用于受保护内存。
-
VK_QUEUE_VIDEO_DECODE_BIT_KHR 和 VK_QUEUE_VIDEO_ENCODE_BIT_KHR用于 Vulkan Video。
Knowing which Queue Family is needed
Vulkan 规范中的每个操作都有一个从vk.xml 生成的 Supported Queue Types 章节。以下是规范中的三种典型示



Querying for Queue Family
如果应用只需要一个图形队列,最简单的查询逻辑如下:
uint32_t count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, nullptr);
std::vector<VkQueueFamilyProperties> properties(count);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, properties.data());
// Vulkan 要求实现至少暴露一个支持图形的队列族
uint32_t graphicsQueueFamilyIndex;
for (uint32_t i = 0; i < count; i++) {
if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
// 此队列族支持图形
graphicsQueueFamilyIndex = i;
break;
}
}
Creating and getting a Queue
与 VkDevice、VkBuffer、VkDeviceMemory 等句柄不同,Vulkan 没有 vkCreateQueue 或 vkAllocateQueue。驱动会在 vkCreateDevice / vkDestroyDevice 期间负责创建和销毁 VkQueue 句柄。
下面示例假设某个实现来自 2 个队列族,共支持 3 个 VkQueue:
以下示例展示如何在逻辑设备上创建全部 3 个 VkQueue:
VkDeviceQueueCreateInfo queueCreateInfo[2];
queueCreateInfo[0].queueFamilyIndex = 0; // Transfer
queueCreateInfo[0].queueCount = 1;
queueCreateInfo[1].queueFamilyIndex = 1; // Graphics
queueCreateInfo[1].queueCount = 2;
VkDeviceCreateInfo deviceCreateInfo = {};
deviceCreateInfo.pQueueCreateInfos = queueCreateInfo;
deviceCreateInfo.queueCreateInfoCount = 2;
vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device);
创建 VkDevice 后,应用可以使用 vkGetDeviceQueue 获取 VkQueue 句柄:
VkQueue graphicsQueue0 = VK_NULL_HANDLE;
VkQueue graphicsQueue1 = VK_NULL_HANDLE;
VkQueue transferQueue0 = VK_NULL_HANDLE;
// 获取顺序可以任意
vkGetDeviceQueue(device, 0, 0, &transferQueue0); // family 0 – queue 0
vkGetDeviceQueue(device, 1, 1, &graphicsQueue1); // family 1 – queue 1
vkGetDeviceQueue(device, 1, 0, &graphicsQueue0); // family 1 – queue 0






