目录
Specifying the queues to be created
Specifying used device features
Introduction
选择要使用的物理设备后,我们需要设置一个逻辑设备来与之交互。逻辑设备创建过程与实例创建过程类似,并描述了我们要使用的功能。我们还需要指定要创建的队列,因为我们已经查询了哪些队列系列可用。如果您有不同的需求,甚至可以从同一物理设备创建多个逻辑设备。
首先添加一个新的类成员来存储逻辑设备句柄。
VkDevice device;
接下来,添加从initVulkan调用的createLogicalDevice函数。
void initVulkan() {
createInstance();
setupDebugMessenger();
pickPhysicalDevice();
createLogicalDevice();
}
void createLogicalDevice() {
}
Specifying the queues to be created
逻辑设备的创建涉及再次在结构中指定一堆细节,其中第一个细节将是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;
Specifying used device features
接下来要指定的信息是我们将使用的一组设备功能。这些是我们在上一章中查询vkGetPhysicalDeviceFeatures支持的功能,如几何体着色器。现在我们不需要任何特殊的东西,所以我们可以简单地定义它,并将所有内容都留给VK_FALSE。一旦我们要开始用Vulkan做更多有趣的事情,我们就会回到这个结构。
VkPhysicalDeviceFeatures deviceFeatures{};
Creating the logical device
有了前面两个结构,我们就可以开始填充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!");
}
这些参数包括要与之接口的物理设备、我们刚刚指定的队列和使用信息、可选的分配回调指针和指向存储逻辑设备句柄的变量的指针。与实例创建函数类似,此调用可以基于启用不存在的扩展或指定不支持的功能的预期使用返回错误。
应使用vkDestroyDevice功能在清理过程中销毁设备:
void cleanup() {
vkDestroyDevice(device, nullptr);
...
}
逻辑设备不直接与实例交互,这就是为什么它不作为参数包含的原因。
Retrieving queue handles
队列是与逻辑设备一起自动创建的,但我们还没有与它们接口的句柄。首先添加一个类成员以将句柄存储到图形队列:
VkQueue graphicsQueue;
当设备被破坏时,设备队列会被隐式清理,因此我们不需要做任何清理工作。
我们可以使用vkGetDeviceQueue函数来检索每个队列系列的队列句柄。参数包括逻辑设备、队列系列、队列索引和指向存储队列句柄的变量的指针。因为我们只是从这个系列创建一个队列,所以我们只使用索引0。
vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
有了逻辑设备和队列句柄,我们现在可以开始使用显卡来做事情了!在接下来的几章中,我们将设置资源以向窗口系统显示结果。