创建逻辑设备
译者注:示例代码点击此处
逻辑设备是在我们的应用程序中创建的最重要的对象之一。它代表从真正的硬件启用的所有扩展、特性以及队列的抽象:
逻辑设备允许我们执行通常在渲染应用程序中完成的所有工作,例如创建图像和缓冲区、设置管道状态或加载着色器。 它给我们最重要的能力是记录命令(例如分配绘制调用或调度计算工作)并将它们提交给队列,由给定的硬件执行和处理它们。执行此类操作后,我们将获取提交的操作的结果。些可以是由计算着色器计算的一组值,或由绘制调用生成的其他数据(不一定是图像)。 所有这些都是在逻辑设备上执行的,所以现在我们将看看如何创建一个。
准备就绪
我们将使用自定义结构类型的变量。该类型被称为QueueInfo,定义如下:
struct QueueInfo {
uint32_t FamilyIndex;
std::vector<float> Priorities;
};
在这种类型变量中,我们将储存一个给定设备的请求队列信息。该数据包含我们希望从中创建队列的队列族索引,从该队列族请求的队列总数以及分配给每个队列的优先级列表。由于优先级的数量必须等于从给定队列族请求的队列数量,因此我们从给定队列族请求的队列数量等于优先级向量中的元素数量。
怎么做...
1.根据功能,限制可用扩展和支持操作功能的类型,选择调用vkEnumeratePhysicalDevices()函数获取的其中一个物理设备(请参阅枚举可用的物理设备)。获取其句柄并将其储存在名为physical_device的VkPhysicalDevice类型变量中。
2.准备要启用的设备扩展的列表。在名为desired_extensions的std::vector<char const *>类型变量中储存所需扩展的名称。
3.创建名为available_extensions的std::vector<VkExtensionProperties>类型变量。获取所有可用扩展的列表,并将其储存在available_extensions变量中(参见检查可用的设备扩展)。
4.确保来自desired_extension变量的每个扩展的的名称也出现在available_extensions中。
5.准备名为desired_features的VkPhysicalDeviceFeatures类型变量。
6.获取由physical_device句柄表示的物理设备支持的一组功能(Features)将其储存在desired_features变量中(请参阅获取物理设备的功能和属性)。
7.确保physical_device变量表示的给定物理设备支持所有必需的功能。 通过检查所获取的desired_features结构的相应成员是否设置为1来执行此操作。 清除其余的desired_features结构成员(将它们设置为零)。
8.根据属性(支持的操作功能类型),准备一个队列族列表,从中请求队列。准备每个选定的队列族系列请求的多个队列。为给定组中的每个队列分配优先级:浮点值从0.0f到1.0f(多个队列可能具有相同的优先级)。使用自定义类型QueueInfo的元素创建名为queue_infos的std::vector变量。讲队列族的索引和优先级列表储存在queue_infos向量中,优先级向量的大小应该等于每个族的队列数。
9.准备名为queue_create_infos的std::vector<VkDeviceQueueCreateInfo>类型变量,将存储在queue_infos变量中的每个队列族添加到queue_create_infos向量中,为新元素的成员分配以下值:
1.sType设为VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO。
2.pNext设为nullptr。
3.flags设为0.
4.queueFamilyIndex设为族的索引。
5.queueCount设为族的队列数。
6.pQueuePriorities为指向给定族的队列优先级列表的第一个元素的指针。
10.创建名为device_create_info的VkDeviceCreateInfo类型的变量。为device_create_info变量的成员分配以下值:
1.sType设为VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO。
2.pNext设为nullptr。
3.flags设为0。
4.queueCreateInfoCount设为queue_create_infos的元素数。
5.pQueueCreateInfos设为指向queue_create_infos向量第一个元素。
6.enabledLayerCount设为0.
7.ppEnabledLayerNames设为nullptr。
8.enabledExtensionCount设为desired_extensions的元素数。
9.ppEnabledExtensionNames设为指向desired_extensions向量的第一个元素(如果为空则为nullptr)。
10.pEnabledFeatures指向desired_features变量。
11.准备名为logical_device的VkDevice类型变量。
12.调用vkCreateDevice(physical_device, &device_create_info, nullptr, &logical_device)。第一个参数为物理设备的句柄,第二个参数指向device_create_info变量,第三个参数设为nullptr,第四个参数指向logical_device变量。
13.检查调用vkCreateDevice()函数返回的值是否等于VK_SUCCESS来确保操作成功。
这个怎么运作...
要创建逻辑设备,我们需要准备大量数据。 首先,我们需要获取给定物理设备支持的扩展列表,然后我们需要检查我们要启用的所有扩展是否都可以在支持的扩展列表中找到。 与实例创建类似,我们无法创建不受支持扩展的逻辑设备。 这样的操作将失败:
std::vector<VkExtensionProperties> available_extensions;
//译者注:获取物理设备支持的扩展
if( !CheckAvailableDeviceExtensions( physical_device, available_extensions ) ) {
return false;
}
//译者注:检查物理设备支持的扩展是否支持我们想要的扩展
for( auto & extension : desired_extensions ) {
if( !IsExtensionSupported( available_extensions, extension ) ) {
std::cout << "Extension named '" << extension << "' is not supported by a physical device." << std::endl;
return false;
}
}
接下来,我们准备一个名为queue_create_infos的向量变量,该变量将包含有关我们要为逻辑设备请求的队列和队列族的信息。 此向量的每个元素都是VkDeviceQueueCreateInfo类型。 它包含的最重要的信息是队列族的索引和为该系列请求的队列数。 我们不能在向量中有两个索引相同队列族的元素。
在queue_create_infos向量变量中,我们还提供有关队列优先级的信息。 给定族中的每个队列可能具有不同的优先级:浮点值介于0.0f和1.0f之间,值越高表示优先级越高。 这意味着硬件将尝试根据此优先级来调度多个队列上执行的操作,并可能为具有更高优先级的队列分配更多处理时间。 但是,这只是一个提示,并不能保证。 它也不会影响其他设备的队列:
std::vector<VkDeviceQueueCreateInfo> queue_create_infos;
for( auto & info : queue_infos ) {
queue_create_infos.push_back( { //译者注
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, //sType
nullptr, //pNext
0, //flags
info.FamilyIndex, //queueFamilyIndex
static_cast<uint32_t>(info.Priorities.size()), //queueCount
info.Priorities.size() > 0 ? &info.Priorities[0] : nullptr //pQueuePriorities
} );
};
queue_create_infos向量变量将提供给另一个VkDeviceCreateInfo类型的变量。 在此变量中,我们存储有关我们请求逻辑设备队列的不同队列族的数量,启用的层的数量和名称,以及我们要为设备启用的扩展,和我们想要使用的功能(Features)的信息。
设备无需图层和扩展即可正常工作,但有一些非常有用的扩展,如果我们想要在屏幕上显示Vulkan生成的图像,则必须启用这些扩展。
功能(Features)也没有必要,因为核心Vulkan API为我们提供了大量功能,可以生成漂亮的图像或执行复杂的计算。如果我们不想启用任何功能,我们可以为pEnabledFeatures成员提供nullptr值,或者提供一个填充零的变量。但是,如果我们想要使用更高级的功能,例如几何或曲面细分着色器,我们需要通过提供指向适当变量的指针来启用它们,先前获取支持的功能列表,并确保我们需要的功能可用。可以(甚至应该)禁用不必要的功能,因为有些功能可能会影响性能。这种情况非常罕见,但记住这一点是件好事。在Vulkan中,我们应该只使用那些需要完成和使用的东西:
VkDeviceCreateInfo device_create_info = { //译者注
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, //sType
nullptr, //pNext
0, //flags
static_cast<uint32_t>(queue_create_infos.size()), //queueCreateInfoCount
queue_create_infos.size() > 0 ? &queue_create_infos[0] : nullptr, //pQueueCreateInfos
0, //enabledLayerCount
nullptr, //ppEnabledLayerNames
static_cast<uint32_t>(desired_extensions.size()), //enabledExtensionCount
desired_extensions.size() > 0 ? &desired_extensions[0] : nullptr, //ppEnabledExtensionNames
desired_features //pEnabledFeatures
};
device_create_info变量提供给vkCreateDevice()函数,该函数创建逻辑设备。 为了确保操作成功,我们需要检查vkCreateDevice()函数调用返回的值是否等于VK_SUCCESS。 如果是,则创建的逻辑设备的句柄存储在函数调用的最后一个参数指向的变量中:
VkResult result = vkCreateDevice( physical_device, &device_create_info, nullptr, &logical_device );
if( (result != VK_SUCCESS) || (logical_device == VK_NULL_HANDLE) ) {
std::cout << "Could not create logical device." << std::endl;
return false;
}
return true;