逻辑设备和队列
简介
在选择要使用的物理设备之后,我们需要设置一个逻辑设备来与之交互。逻辑设备的创建过程类似于实例的创建过程,并描述了我们想要使用的特性。既然我们已经查询了可用的队列族,现在还需要指定要创建哪些队列。如果您有不同的需求,甚至可以从同一个物理设备创建多个逻辑设备。
首先添加一个新的类成员来存储逻辑设备句柄。
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.0
和 1.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 设备,例如因为它们仅支持计算操作。我们将在交换链章节中回到这个扩展。
以前的 Vulkan 实现区分了实例和设备特定的验证层,但这 不再是这种情况。这意味着 VkDeviceCreateInfo
的 enabledLayerCount
和 ppEnabledLayerNames
字段会被最新的实现忽略。但是,为了与旧的实现兼容,最好还是设置它们。
createInfo.enabledExtensionCount = 0;
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 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);
有了逻辑设备和队列句柄,我们现在实际上可以开始使用显卡来做事了!在接下来的几章中,我们将设置资源以将结果呈现到窗口系统。