欢迎光临
我们一直在努力

Using Vulkan -- Queues

应用程序通过 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

赞(0)
未经允许不得转载:171主机测评 » Using Vulkan -- Queues
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址