Vulkan教程翻译之四 创建(Logical)Device

原文链接:https://vulkan.lunarg.com/doc/sdk/1.2.131.2/windows/tutorial/html/03-init_device.html

创建(Logical)Device

这一章节的代码文件是 03-init_device.cpp

下一步是创建一个对应系统里 physical device 的 VkDevice logical device 对象。logical device 是稍后用来给硬件指定图形命令的关键对象。

目前为止,我们的示例已经确定了你有多少个 physical device。示例里列举 device 的实际函数已经确定了至少有一个,否则它就会给个非真的结果然后停止了。

选择一个Device

为了事情更简单,示例就从device枚举返回的列表里选取了第一个 device。你可以在 device 程序里使用 info.gpus[0] 的地方看出来这一点。如果有多个 device,复杂的程序也许需要做额外的工作来决定使用哪一个 device。在这里,示例简单地假设列表里第一个 device 足够满足示例的目的。

现在你已经选择了一个 physical deivce,你需要创建 VkDevice,或者说 logical device 对象。但为了这么做,你还需要提供一些关于 queue 的信息。

Device Queues 和 Queue Families

不像其他图形API,Vulkan 暴露 device queue 给开发者,因此开发者可以决定使用多少个 queue 以及使用什么类型的 queue。

queue 是用来给硬件提交命令的抽象机制。之后你将看到,一个 Vulkan 应用是如何构建一个充满命令的 command buffer,然后把它们提交到一个queue 里以供GPU硬件异步处理。

Vulkan 根据它们的类型把 queue 排列成 queue families。为了找到你感兴趣的 queue 的类型和特征,你得从 physical device 里查询QueueFamilyProperties:

Vulkan教程翻译之四 创建(Logical)Device_第1张图片

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 也许看起来像:

Vulkan教程翻译之四 创建(Logical)Device_第2张图片

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 标记:

Vulkan教程翻译之四 创建(Logical)Device_第3张图片

现在,假设示例程序真的只对绘制简单图形感兴趣,所以他们只需要查找图形位:

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[] 里的值也就不重要了。

创建Logical Device

解决了 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 来使它准备好接收图形命令。

你可能感兴趣的:(Vulkan教程翻译)