队列
应用程序将工作提交给 VkQueue
,通常以 VkCommandBuffer
对象或 稀疏绑定 的形式。
提交到 VkQueue
的命令缓冲区按顺序启动,但之后允许独立进行并以无序完成。
提交到不同队列的命令缓冲区相对于彼此是无序的,除非你使用 VkSemaphore
显式地同步它们。
你一次只能从一个线程将工作提交到 VkQueue
,但是不同的线程可以同时将工作提交到不同的 VkQueue
。
VkQueue
如何映射到底层硬件是实现定义的。某些实现将有多个硬件队列,并且将工作提交到多个 VkQueue
将独立且并发地进行。某些实现将在将工作提交到硬件之前在内核驱动程序级别进行调度。目前在 Vulkan 中没有办法暴露每个 VkQueue
如何映射的确切细节。
并非所有应用程序都需要或受益于多个队列。应用程序拥有单个“通用”图形支持队列来将所有工作提交到 GPU 是合理的。 |
队列族
VkQueue
可以支持各种类型的操作。“队列族”只是描述一组具有共同属性并支持相同功能的 VkQueue
,如 VkQueueFamilyProperties
中所通告的那样。
以下是 VkQueueFlagBits 中找到的队列操作
-
VK_QUEUE_GRAPHICS_BIT
用于vkCmdDraw*
和图形管线命令。 -
VK_QUEUE_COMPUTE_BIT
用于vkCmdDispatch*
和vkCmdTraceRays*
以及计算管线相关命令。 -
VK_QUEUE_TRANSFER_BIT
用于所有传输命令。-
规范中的 VK_PIPELINE_STAGE_TRANSFER_BIT 描述了“传输命令”。
-
仅具有
VK_QUEUE_TRANSFER_BIT
的队列族通常用于使用 DMA 来在离散 GPU 上异步传输主机和设备内存之间的数据,因此传输可以与独立的图形/计算操作并发完成。 -
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 视频 一起使用。
查询队列族
如果应用程序只需要单个图形 VkQueue
,则以下是最简单的逻辑
uint32_t count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, nullptr);
std::vector<VkQueueFamilyProperties> properties(count);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, properties.data());
// Vulkan requires an implementation to expose at least 1 queue family with graphics
uint32_t graphicsQueueFamilyIndex;
for (uint32_t i = 0; i < count; i++) {
if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
// This Queue Family support graphics
graphicsQueueFamilyIndex = i;
break;
}
}
创建和获取队列
与 VkDevice
、VkBuffer
、VkDeviceMemory
等其他句柄不同,没有 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;
// Can be obtained in any order
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