选择物理设备和 Queue Family
在创建了 VkInstance
后,需要选择满足需求的显卡,这里定义类 PhysicalDevicesMgr
来管理显卡的选择,其头文件如下所示:
1 2 3 4 5 6 7 8 9 class PhysicalDevicesMgr { public : void static pickPhysicalDevice (VkInstance instance) ; static VkPhysicalDevice physicalDevice; private : static bool isDeviceSuitable (VkPhysicalDevice device) ; };
在 HelloTriangleApplication::initVulkan
函数中调用 pickPhysicalDevice
函数来选择合适的显卡:
1 2 3 4 5 6 void HelloTriangleApplication::initVulkan () { createInstance (); DebugMessengerMgr::setupDebugMessenger (instance); PhysicalDevicesMgr::pickPhysicalDevice (instance); }
对于 Vulkan 而言,可以选择多张适合的显卡并同时使用,但在教程中仅会使用一张。
显卡设备以 VkPhysicalDevice
表示,该物体会在 VkInstance
销毁时被隐式的卸载,因此不需要在 cleanup
中进行额外的删除操作。
找寻适合的显卡的过程与找寻 Extensions 的过程很接近,通过 vkEnumeratePhysicalDevices
函数找寻支持 Vulkan 的显卡列表和数目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void PhysicalDevicesMgr::pickPhysicalDevice (VkInstance instance) { uint32_t deviceCount = 0 ; vkEnumeratePhysicalDevices (instance, &deviceCount, nullptr ); if (deviceCount == 0 ) throw std::runtime_error ("failed to find GPUs with Vulkan support!" ); std::vector<VkPhysicalDevice> devices (deviceCount) ; vkEnumeratePhysicalDevices (instance, &deviceCount, devices.data ()); for (const auto & device : devices) { if (isDeviceSuitable (device)) { physicalDevice = device; break ; } } if (physicalDevice == VK_NULL_HANDLE) throw std::runtime_error ("failed to find a suitable GPU!" ); }
其中 isDeviceSuitable(device)
函数用来确认一个显卡是否合适。
检查设备是否合适
对于设备的基础属性,如 name
,type
和支持的 Vulkan 类型都可以通过 vkGetPhysicalDeviceProperties
查询。对于设备的可选特性,如纹理压缩,64 bit floats 等则可以通过 vkGetPhysicalDeviceFeatures
查询。
如下,查询设备的基础属性和可选特性后,将独显且支持几何 Shader 的显卡视作为合适显卡:
1 2 3 4 5 6 7 8 9 10 bool PhysicalDevicesMgr::isDeviceSuitable (VkPhysicalDevice device) { VkPhysicalDeviceProperties deviceProperties; VkPhysicalDeviceFeatures deviceFeatures; vkGetPhysicalDeviceProperties (device, &deviceProperties); vkGetPhysicalDeviceFeatures (device, &deviceFeatures); return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.geometryShader; }
除此之外,我们还需要判断一张显卡是否支持我们需要的 Queue Family。
Queue families
Vulkan 中的几乎所有操作,都需要提交指令给 Queue。Vulkan 中存在各种不同类型的队列,如会存在一个 Queue 只允许处理计算的命令,而存在另一个 Queue 只允许内存的传输,再另一个 Queue 可以处理计算和内存传输的命令等。同一种类型的 Queue 会被封装在一个Queue Family 中。
首先需要从 Physical Devices 中查询设备支持的 Queue Families 列表,并找出支持想要执行的命令的特定 Queue Family。因为需要寻找的 Queue Family 可能不止一个,因此封装一个结构体表示所有需要的 Queue Family 的 indices,在这一章暂时只有一个表示 graphics 的 queue family 的 index,同时增加函数 isComplete
表示是否正确找到了 graphics queue family:
1 2 3 4 5 6 7 8 struct QueueFamilyIndices { std::optional<uint32_t > graphicsFamily; bool isComplete () { return graphicsFamily.has_value (); } };
函数 findQueueFamilies
的作用仅是找寻 Queue,因此即使一个 queue 未找到也不一定是错误发生。这里用 Optional 来表示 Queue Index,因为任何的 Int 值都可能是 Queue Index,用一个 Magic Number 表示未找到的情形并不安全。
如下可定义类 QueueFamilyMgr
并封装函数 findQueueFamilies
函数获取需要的 Queue Families 的 indices:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class QueueFamilyMgr { public : static QueueFamilyIndices findQueueFamilies (VkPhysicalDevice device) ; }; QueueFamilyIndices QueueFamilyMgr::findQueueFamilies (VkPhysicalDevice device) { QueueFamilyIndices indices; uint32_t queueFamilyCount = 0 ; vkGetPhysicalDeviceQueueFamilyProperties (device, &queueFamilyCount, nullptr ); std::vector<VkQueueFamilyProperties> queueFamilies (queueFamilyCount) ; vkGetPhysicalDeviceQueueFamilyProperties (device, &queueFamilyCount, queueFamilies.data ()); int i = 0 ; for (const auto & queueFamily : queueFamilies) { if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } if (indices.isComplete ()) break ; ++i; } return indices; }
其中的 VkQueueFamilyProperties
结构体中包含一些关于 Queue Family 的细节,包括支持的 Operations 类型(queueFlags
)以及可以从这个 Family 中创建出多少个 queue(queueCount
)。
此时需要修改 PhysicalDevicesMgr::isDeviceSuitable
函数中需要检测找寻到的 QueueFamilyIndices 是否满足需求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool PhysicalDevicesMgr::isDeviceSuitable (VkPhysicalDevice device) { VkPhysicalDeviceProperties deviceProperties; VkPhysicalDeviceFeatures deviceFeatures; vkGetPhysicalDeviceProperties (device, &deviceProperties); vkGetPhysicalDeviceFeatures (device, &deviceFeatures); QueueFamilyIndices indices = QueueFamilyMgr::findQueueFamilies (device); const bool deviceSuitable = deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.geometryShader; const bool queueFamilySuitable = indices.isComplete (); return deviceSuitable && queueFamilySuitable; }
至此,已经完整的找寻了正确的物理显卡,下一步就是创建合适的逻辑设备与之交互。