跳到主要内容

队列

提示

队列的更多信息请参阅 AMDNVIDIA 的资料

vulkan应用通常以VkCommandBuffer稀疏绑定的形式将任务提交到VkQueue

命令缓冲区按顺序提交到VkQueue,但从提交时刻起独立运行并不一定按顺序完成。

提交到不同队列的命令缓冲区彼此间是无序的,除非使用VkSemaphore显式地让它们同步。

app的同一个线程一次只能将任务提交到一个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用于所有传输指令。
    • Spec 中的 VK_PIPELINE_STAGE_TRANSFER_BIT 详细说明了 “传输指令”。
    • 仅有VK_QUEUE_TRANSFER_BIT的队列族能使用 DMA 在离散 GPU 的主机和设备内存之间异步传输数据,因此传输操作可以与独立的图形/计算操作同时进行。
    • VK_QUEUE_GRAPHICS_BITVK_QUEUE_COMPUTE_BIT可以隐式接受VK_QUEUE_TRANSFER_BIT指令。
  • VK_QUEUE_SPARSE_BINDING_BIT用于使用vkQueueBindSparse稀疏资源绑定到内存。
  • VK_QUEUE_PROTECTED_BIT用于受保护的内存
  • VK_QUEUE_VIDEO_DECODE_BIT_KHRVK_QUEUE_VIDEO_ENCODE_BIT_KHR一般与 Vulkan Video 一起使用。

了解需要的队列族

Vulkan Spec中的每个操作命令都有一个“支持的队列类型”部分,它是从 vk.xml 文件生成的。以下是 Spec 中的 3 个不同示例:

queues_cmd_dispatch.png

queues_cmd_dispatch.png

queues_cmd_dispatch.png

查询队列族

如果应用只需要一个图形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;
}
}

创建和获取 Queue

与其他句柄如VkDeviceVkBufferVkDeviceMemory不同,是没有vkCreateQueuevkAllocateQueue这样的接口的 。驱动程序负责在vkCreateDevice / vkDestroyDevice时创建和销毁VkQueue句柄。

假设驱动实现支持 2 个队列族,3个VkQueue

queues_hypothetical.png

以下展示了如何使用逻辑设备创建 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); // fa