队列
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_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 一起使用。
了解需要的队列族
Vulkan Spec中的每个操作命令都有一个“支持的队列类型”部分,它是从 vk.xml 文件生成的。以下是 Spec 中的 3 个不同示例:
查询队列族
如果应用只需要一个图形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
与其他句柄如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); // fa