逻辑设备和队列
介绍
选择物理设备后,需要创建一个逻辑设备来作为和物理设备交互的接口,逻辑设备的创建过程类似于实例。之前我们已经查询了哪些队列族可用,现在需要指定使用队列所属的队列族。我们可以根据不同的需求,从同一个物理设备创建多个逻辑设备。
首先,添加一个类成员存储逻辑设备对象:
VkDevice device;
接着,添加createLogicalDevice
函数,在initVulkan
中调用它:
void initVulkan() {
createInstance();
setupDebugCallback();
pickPhysicalDevice();
createLogicalDevice();
}
void createLogicalDevice() {
}
指定要创建的队列
创建逻辑设备需要VkDeviceQueueCreateInfo
结构体,这一结构体描述了针对一个队列族我们所需的队列数量,目前我们只使用带有图形能力的队列族。
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = indices.graphicsFamily;
queueCreateInfo.queueCount = 1;
对于每个队列族,驱动程序只允许创建少量队列,通常情况也不需要多个队列。我们可以在多个线程上创建指令缓冲,然后在主线程一次将它们全部提交,降低调用开销。
Vulkan需要我们赋予队列一个0.0到1.0之间的浮点数来控制队列优先级。即使只有一个队列,也要显式地赋予队列优先级:
float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;
指定设备特性
接下来,我们要指定应用程序使用的设备特性,这些是在上一章中vkGetPhysicalDeviceFeatures
查询出的设备支持的功能,例如几何着色器。暂时先不填写,之后再回来填写:
VkPhysicalDeviceFeatures deviceFeatures = {};
创建逻辑设备
填好前面两个结构体后,我们可以开始填写VkDeviceCreateInfo
结构体了:
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
将VkDeviceCreateInfo
结构体的pQueueCreateInfos
指向队列创建信息queueCreateInfo
,pEnabledFeatures
指向设备特性信息deviceFeatures
:
createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;
createInfo.pEnabledFeatures = &deviceFeatures;
结构体的其余成员和VkInstanceCreateInfo
类似,不同的是这次的设置是针对设备的。
VK_KHR_swapchain
是一个设备特定扩展的例子,这一扩展使得我们可以将渲染的图像呈现到窗口上。看起来似乎所有支持Vulkan的设备都应该支持这一扩展,然而,实际上有的Vulkan设备只支持计算指令,不支持图形指令。之后的章节,我们会对交换链进行更加深入地说明。
之前提到,我们可以对设备和Vulkan实例使用相同地校验层,不需要额外的扩展:
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!");
}
vkCreateDevice
函数的参数包括要连接的物理设备、需要使用的队列、可选的分配器回调,以及用来存储逻辑设备句柄的指针。和Vulkan实例对象的创建函数类似,在启用不存在的扩展或指定不支持的功能时,会返回错误码。
逻辑设备对象创建后,需要我们自己在cleanup
函数中调用vkDestroyDevice
来销毁它:
void cleanup() {
vkDestroyDevice(device, nullptr);
...
}
逻辑设备并不直接与Vulkan实例交互,所以创建逻辑设备时不需要使用Vulkan实例作为参数。
获取队列句柄
创建逻辑设备时指定的队列会同时被创建,为了方便,我们添加了一个VkQueue
类成员变量来存储图形队列的句柄:
VkQueue graphicsQueue;
队列会在逻辑设备销毁时被自动销毁,所以不需要在cleanup
函数中进行队列的销毁操作。
vkGetDeviceQueue
函数可以获取指定队列族的队列句柄。它的参数依次是逻辑设备,队列族索引,队列索引和存储返回的队列句柄的指针。因为我们只创建了一个队列,所以可以直接使用索引0:
vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue);
创建完逻辑设备,我们就可以真正使用显卡来做事情了!在接下来的章节,我们将开始设置资源,进行一些绘制操作,将渲染结果显示在窗口上。
本章节代码:C++代码