原文链接:https://vulkan.lunarg.com/doc/sdk/1.2.131.2/windows/tutorial/html/03-init_device.html
这一章节的代码文件是 03-init_device.cpp
下一步是创建一个对应系统里 physical device 的 VkDevice logical device 对象。logical device 是稍后用来给硬件指定图形命令的关键对象。
目前为止,我们的示例已经确定了你有多少个 physical device。示例里列举 device 的实际函数已经确定了至少有一个,否则它就会给个非真的结果然后停止了。
为了事情更简单,示例就从device枚举返回的列表里选取了第一个 device。你可以在 device 程序里使用 info.gpus[0] 的地方看出来这一点。如果有多个 device,复杂的程序也许需要做额外的工作来决定使用哪一个 device。在这里,示例简单地假设列表里第一个 device 足够满足示例的目的。
现在你已经选择了一个 physical deivce,你需要创建 VkDevice,或者说 logical device 对象。但为了这么做,你还需要提供一些关于 queue 的信息。
不像其他图形API,Vulkan 暴露 device queue 给开发者,因此开发者可以决定使用多少个 queue 以及使用什么类型的 queue。
queue 是用来给硬件提交命令的抽象机制。之后你将看到,一个 Vulkan 应用是如何构建一个充满命令的 command buffer,然后把它们提交到一个queue 里以供GPU硬件异步处理。
Vulkan 根据它们的类型把 queue 排列成 queue families。为了找到你感兴趣的 queue 的类型和特征,你得从 physical device 里查询QueueFamilyProperties:
typedef struct VkQueueFamilyProperties {
VkQueueFlags queueFlags;
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
typedef enum VkQueueFlagBits {
VK_QUEUE_GRAPHICS_BIT = 0x00000001,
VK_QUEUE_COMPUTE_BIT = 0x00000002,
VK_QUEUE_TRANSFER_BIT = 0x00000004,
VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
} VkQueueFlagBits;
示例程序通过执行以下调用来获取 queue 信息:
vkGetPhysicalDeviceQueueFamilyProperties(
info.gpus[0], &info.queue_family_count, info.queue_props.data());
info.queue_props 是一个 VkQueueFamilyProperties 向量。你检查代码就可以看出来,它遵循在 列举Physical Device 章节中描述的Vulkan API里如何获取对象列表的模式。它调用 vkGetPhysicalDeviceQueueFamilyProperties 一次来获取计数,再次调用它来获取数据。
VkQueueFamilyProperties 结构体之所以称为一个”family“,是因为一个特定的 queueFlags,可能会有许多个可用的(queueCount)queue。例如,可能会有 8 个 queue 存在于已设置 VK_QUEUE_GRAPHICS_BIT 的 family。
这样一个 device 上的 queues 和 queue families 也许看起来像:
VkQueueFlagBits 表明了每一个硬件 queue 可用处理什么类型的工作流。例如,一个 device 可能定义了一个设置 VK_QUEUE_GRAPHICS_BIT 的 queue family,为了一般的3D图形工作。但是如果同一个 device 拥有做像素块拷贝(blit)的特殊硬件,它也许会定义另一个 queue family 就设置成 VK_QUEUE_TRANSFER_BIT 。这使得硬件有可能并行处理图形工作流和 blit 工作流。
请注意还有一种做计算操作的 queue 类型,本教程未涉及。
一些简单的GPU硬件可能只提供一个 queue family 有着多个 queue type 标记:
现在,假设示例程序真的只对绘制简单图形感兴趣,所以他们只需要查找图形位:
bool found = false;
for (unsigned int i = 0; i < info.queue_family_count; i++) {
if (info.queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
queue_info.queueFamilyIndex = i;
found = true;
break;
}
}
注意到这一点很重要,queue family 不是用句柄表示的,而是用索引。
一旦你选择了一个 queue family 索引,你还要填充结构体的剩余部分:
float queue_priorities[1] = {0.0};
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = NULL;
queue_info.queueCount = 1;
queue_info.pQueuePriorities = queue_priorities;
如果多个 queue 可用的话,更复杂点的程序也许会需要在多个 queue 上提交图形命令。但是因为示例很简单,在这里你需要申请一个。因为只有一个queue,queue_priorities[] 里的值也就不重要了。
解决了 queue 的问题,创建 logical device 就很直接了:
VkDeviceCreateInfo device_info = {};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.pNext = NULL;
device_info.queueCreateInfoCount = 1;
device_info.pQueueCreateInfos = &queue_info;
device_info.enabledExtensionCount = 0;
device_info.ppEnabledExtensionNames = NULL;
device_info.enabledLayerCount = 0;
device_info.ppEnabledLayerNames = NULL;
device_info.pEnabledFeatures = NULL;
VkDevice device;
VkResult res = vkCreateDevice(info.gpus[0], &device_info, NULL, &device);
assert(res == VK_SUCCESS);
在本教程的当前节点,你不用太过担心 extension。你将很快有机会使用 extension。
至于 layer,在 Vulkan 里目前不推荐 device layer,所以你在创建一个device的时候应该不必去指定任何 layer。
现在你拥有了一个 device,你已经可以通过创建 command buffer 来使它准备好接收图形命令。