[Vulkan教程]绘制一个三角形/设置/逻辑设备和队列(Logical device and queues)

介绍

选择一个物理设备之后,我们需要整个逻辑设备作为接口。物理设备的创建过程和实例相似,需要描述一下我们要用的特性。我们还要根据之前查到的队列簇指定要用的队列。你可以从同一个物理设备创建多个逻辑设备,如果你有需要。

新建一个成员变量来存储逻辑设备句柄。

  1. VkDevice device;
  2. 1

然后,创建一个createLogicalDevice函数并在initVulkan函数中调用。

  1. void initVulkan() {
  2. createInstance();
  3. setupDebugMessenger();
  4. pickPhysicalDevice();
  5. createLogicalDevice();
  6. }
  7. void createLogicalDevice() {
  8. }
  9. 12345678910

指定要创建队列

创建逻辑设备同样需要填充一些结构体,第一个就是VkDeviceQueueCreateInfo。这个结构体说明了我们要从队列簇创建的队列的数量。我们现在只关注图形队列。

  1. QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
  2. VkDeviceQueueCreateInfo queueCreateInfo{};
  3. queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  4. queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
  5. queueCreateInfo.queueCount = 1;
  6. 123456

当前可用的驱动程序只允许你为每个队列簇创建少量队列,实际上我们也不需要多个。你可以在多个线程上创建命令缓冲区,然后通过一次主线程上的低开销调用一次性提交所有命令缓冲区。

Vulkan允许你使用0.01.0之间的浮点数为队列分配优先级,以影响命令缓冲区的执行调度。

  1. float queuePriority = 1.0f;
  2. queueCreateInfo.pQueuePriorities = &queuePriority;
  3. 12

指定使用的设备特性

下一个要指定的我们会用到的设备特性。这些特性是我们在前面的章节通过vkGetPhysicalDevicefeatures函数查询的,像几何着色器等。现在我们还不需要什么花里胡哨的东西,直接指定VK_FALSE就行了。当我们要通过Vulkan做一些有趣的事情时,我们还会回头讨论这个结构体。

  1. VkDeviceCreateInfo createInfo{};
  2. createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  3. 12

创建逻辑设备

有了前面两个结构体后,我们就可以填充主要的结构体VkDeviceCreateInfo了。

  1. VkDeviceCreateInfo createInfo{};
  2. createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  3. 12

首先,指定队列创建信息和设备特性信息的指针:

  1. createInfo.pQueueCreateInfos = &queueCreateInfo;
  2. createInfo.queueCreateInfoCount = 1;
  3. createInfo.pEnabledFeatures = &deviceFeatures;
  4. 1234

剩下的信息和VkInstanceCreateInfo详细,要求你指定扩展和验证层信息。不同支持在于,这里指定的是特定与设备的。

举个例子就是,VK_KHR_swapchain扩展可以指定给特定设备,它允许我们将设备中渲染好的图片展现到窗口。系统中的Vulkan设备是有可能没有这种能力的,有可能它们只支持计算操作。我们还会在交换链一章中介绍这个扩展。

之前的Vulkan实现区分了实例和特定设备的验证层,但现在不再如此了。这意味着,在较新的Vulkan实现中,VkDeviceCreateInfo结构体中的enabledLayerCount字段和ppEnabledLayerNames字段会被忽略。为了和旧版兼容,我们最好还是设置一下它们:

  1. createInfo.enabledExtensionCount = 0;
  2. if (enableValidationLayers) {
  3. createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
  4. createInfo.ppEnabledLayerNames = validationLayers.data();
  5. } else {
  6. createInfo.enabledLayerCount = 0;
  7. }
  8. 12345678

我们现在还不需要特定于设备的扩展。

好了,我们现在就可以用vkCreateDevice函数来实例化逻辑设备了。

  1. if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
  2. throw std::runtime_error("failed to create logical device!");
  3. }
  4. 123

参数分别是:我们要用的物理设备、刚才创建的结构体指针、可选的分配器和存储逻辑设备句柄变量的指针。和实例创建函数一样,如果启用了不存在的扩展或指定了不支持的特性,它会返回错误。

设备需要在cleanup函数中通过vkDestroyDevice函数销毁:

  1. void cleanup() {
  2. vkDestroyDevice(device, nullptr);
  3. ...
  4. }
  5. 1234

逻辑设备并不直接与实例交互,所以它不作为参数。

检索队列句柄

队列会与逻辑设备一起自动创建,但我们还没有句柄与它们交互。首先,添加一个类成员变量:

  1. VkQueue graphicsQueue;
  2. 1

设备队列会在设备销毁时隐式清理,所以我们不需要再cleanup时做什么。

我们可以使用vkGetDeviceQueue函数来根据队列簇检索队列句柄。参数分别是逻辑设备
队列簇、队列下标和一个指向保存队列句柄的指针。因为我们只创建了一个队列,我们简单地使用0

  1. vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
  2. 1

有了逻辑设备和队列句柄后,我们现在可以真正开始使用显卡做事情了!在接下来的几个章中,我们会设置资源以将结果呈现到窗口系统中。