队列

来自 AMDNVIDIA 的关于队列的更多资源

应用程序将工作提交给 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_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_KHRVulkan 视频 一起使用。

了解需要哪个队列族

Vulkan 规范中的每个操作都有一个从 vk.xml 文件生成的“支持的队列类型”部分。以下是规范中 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;
    }
}

创建和获取队列

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); // family 1 - queue 0