[Vulkan教程]绘制一个三角形/设置/逻辑设备和队列(Logical device and queues)
介绍
选择一个物理设备之后,我们需要整个逻辑设备作为接口。物理设备的创建过程和实例相似,需要描述一下我们要用的特性。我们还要根据之前查到的队列簇指定要用的队列。你可以从同一个物理设备创建多个逻辑设备,如果你有需要。
新建一个成员变量来存储逻辑设备句柄。
VkDevice device;
1
然后,创建一个createLogicalDevice
函数并在initVulkan
函数中调用。
void initVulkan() {
createInstance();
setupDebugMessenger();
pickPhysicalDevice();
createLogicalDevice();
}
void createLogicalDevice() {
}
12345678910
指定要创建队列
创建逻辑设备同样需要填充一些结构体,第一个就是VkDeviceQueueCreateInfo
。这个结构体说明了我们要从队列簇创建的队列的数量。我们现在只关注图形队列。
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
queueCreateInfo.queueCount = 1;
123456
当前可用的驱动程序只允许你为每个队列簇创建少量队列,实际上我们也不需要多个。你可以在多个线程上创建命令缓冲区,然后通过一次主线程上的低开销调用一次性提交所有命令缓冲区。
Vulkan允许你使用0.0
到1.0
之间的浮点数为队列分配优先级,以影响命令缓冲区的执行调度。
float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;
12
指定使用的设备特性
下一个要指定的我们会用到的设备特性。这些特性是我们在前面的章节通过vkGetPhysicalDevicefeatures
函数查询的,像几何着色器等。现在我们还不需要什么花里胡哨的东西,直接指定VK_FALSE
就行了。当我们要通过Vulkan做一些有趣的事情时,我们还会回头讨论这个结构体。
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
12
创建逻辑设备
有了前面两个结构体后,我们就可以填充主要的结构体VkDeviceCreateInfo
了。
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
12
首先,指定队列创建信息和设备特性信息的指针:
createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;
createInfo.pEnabledFeatures = &deviceFeatures;
1234
剩下的信息和VkInstanceCreateInfo
详细,要求你指定扩展和验证层信息。不同支持在于,这里指定的是特定与设备的。
举个例子就是,VK_KHR_swapchain
扩展可以指定给特定设备,它允许我们将设备中渲染好的图片展现到窗口。系统中的Vulkan设备是有可能没有这种能力的,有可能它们只支持计算操作。我们还会在交换链一章中介绍这个扩展。
之前的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;
}
12345678
我们现在还不需要特定于设备的扩展。
好了,我们现在就可以用vkCreateDevice
函数来实例化逻辑设备了。
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
throw std::runtime_error("failed to create logical device!");
}
123
参数分别是:我们要用的物理设备、刚才创建的结构体指针、可选的分配器和存储逻辑设备句柄变量的指针。和实例创建函数一样,如果启用了不存在的扩展或指定了不支持的特性,它会返回错误。
设备需要在cleanup
函数中通过vkDestroyDevice
函数销毁:
void cleanup() {
vkDestroyDevice(device, nullptr);
...
}
1234
逻辑设备并不直接与实例交互,所以它不作为参数。
检索队列句柄
队列会与逻辑设备一起自动创建,但我们还没有句柄与它们交互。首先,添加一个类成员变量:
VkQueue graphicsQueue;
1
设备队列会在设备销毁时隐式清理,所以我们不需要再cleanup
时做什么。
我们可以使用vkGetDeviceQueue
函数来根据队列簇检索队列句柄。参数分别是逻辑设备
队列簇、队列下标和一个指向保存队列句柄的指针。因为我们只创建了一个队列,我们简单地使用0
。
vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1
有了逻辑设备和队列句柄后,我们现在可以真正开始使用显卡做事情了!在接下来的几个章中,我们会设置资源以将结果呈现到窗口系统中。