逻辑设备和队列

介绍

在选择要使用的物理设备后,我们需要设置一个逻辑设备来与它交互。逻辑设备的创建过程类似于实例的创建过程,并描述了我们想要使用的功能。我们还需要指定要创建哪些队列,现在我们已经查询了哪些队列族可用。如果您有不同的要求,甚至可以从同一个物理设备创建多个逻辑设备。

首先添加一个新的类成员来存储逻辑设备句柄。

VkDevice device;

接下来,添加一个从 initVulkan 调用的 createLogicalDevice 函数。

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    pickPhysicalDevice();
    createLogicalDevice();
}

void createLogicalDevice() {

}

指定要创建的队列

逻辑设备的创建涉及到再次在结构体中指定一堆细节,其中第一个将是 VkDeviceQueueCreateInfo。此结构描述了我们想要为一个队列族提供的队列数量。现在我们只对具有图形功能的队列感兴趣。

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
queueCreateInfo.queueCount = 1;

当前可用的驱动程序只允许您为每个队列族创建少量队列,而您实际上不需要超过一个。这是因为您可以在多个线程上创建所有命令缓冲区,然后在主线程上使用单个低开销调用一次性提交它们。

Vulkan 允许您使用介于 0.01.0 之间的浮点数来分配优先级给队列,以影响命令缓冲区执行的调度。即使只有一个队列,这也是必需的。

float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;

指定使用的设备功能

接下来要指定的信息是我们将要使用的一组设备功能。这些是我们在上一章中使用 vkGetPhysicalDeviceFeatures 查询支持的功能,例如几何着色器。现在我们不需要任何特殊的东西,所以我们可以简单地定义它并将所有内容保留为 VK_FALSE。一旦我们开始使用 Vulkan 做更多有趣的事情,我们将回到这个结构。

VkPhysicalDeviceFeatures deviceFeatures{};

创建逻辑设备

有了前面的两个结构,我们就可以开始填写主要的 VkDeviceCreateInfo 结构了。

VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

首先添加指向队列创建信息和设备功能结构的指针

createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;

createInfo.pEnabledFeatures = &deviceFeatures;

其余信息类似于 VkInstanceCreateInfo 结构,允许您指定扩展。不同之处在于这次这些是特定于设备的。

一个特定于设备的扩展的例子是 VK_KHR_swapchain,它允许您将从该设备渲染的图像呈现到窗口。系统中可能存在缺乏此功能的 Vulkan 设备,例如因为它们只支持计算操作。我们将在交换链章节中回到这个扩展。

createInfo.enabledExtensionCount = 0;

目前我们不需要任何特定于设备的扩展。

就是这样,我们现在准备好通过调用适当命名的 vkCreateDevice 函数来实例化逻辑设备。

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

参数是要交互的物理设备、我们刚刚指定的队列和使用信息、可选的分配回调指针以及指向存储逻辑设备句柄的变量的指针。与实例创建函数类似,此调用可以根据启用不存在的扩展或指定不支持的功能的所需使用情况返回错误。

应该在 cleanup 中使用 vkDestroyDevice 函数销毁设备

void cleanup() {
    vkDestroyDevice(device, nullptr);
    ...
}

逻辑设备不与实例直接交互,这就是为什么它不包含在参数中。

检索队列句柄

队列与逻辑设备一起自动创建,但我们还没有句柄来与它们交互。首先添加一个类成员来存储图形队列的句柄

VkQueue graphicsQueue;

设备队列在设备销毁时隐式清理,因此我们不需要在 cleanup 中做任何事情。

我们可以使用 vkGetDeviceQueue 函数来检索每个队列族的队列句柄。参数是逻辑设备、队列族、队列索引以及指向存储队列句柄的变量的指针。因为我们只从这个族中创建一个队列,所以我们将简单地使用索引 0

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

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