上文我们创建了实例,根据上文的那张图,创建完实例,我们需要查找实例相兼容的物理设置。因为毕竟没有物理设备的支持,只有软件,我们也是没有办法渲染出图像的。这里的物理设备就是指显卡(当然不一定只是显卡)。
实际上我们这里要查找系统支持的物理设备,然后才能进行下一步的操作,这里用到了下面的函数1
2
3
4
5// Provided by VK_VERSION_1_0
VkResult vkEnumeratePhysicalDevices(
VkInstance instance,
uint32_t* pPhysicalDeviceCount,
VkPhysicalDevice* pPhysicalDevices);
这个函数有两种用法(这实际上有点违背了函数的单一原则,这要是在我司,得被喷死),当pPhysicalDevices==NULL时,得到的pPhysicalDeviceCount是系统中物理设备的数量,当我们传入一个数组时,得到的时具体的设备信息。
具体用法如下:1
2
3
4
5
6
7
8uint32_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());
一般来讲,我们获取到物理设备后,要继续遍历所有得到的物理设备,查看那个设备支持我们需要的特性,然后就选择这个物理设置。但是对于当前阶段的显卡来说,大部分都会支持我们需要的特性(如swapchain的扩展支持等等),但是这一步骤尽量还是不要省略。1
2
3
4
5
6
7for (const auto& device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
msaaSamples = getMaxUsableSampleCount();
break;
}
}
慢慢的我们会发现,我们的后面的所有的操作命令都是需要提交到队列中去执行的,队列都是属于不同的队列簇的,而不同的队列簇支持的命令也是不同的,所以我们在获取到了物理设备后,还要检查一下我们的物理设备是否支持这些队列簇。1
2
3
4
5// Provided by VK_VERSION_1_0
void vkGetPhysicalDeviceQueueFamilyProperties(
VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties* pQueueFamilyProperties);
这个函数也是有两种用法:1
2
3
4
5uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
最后我们得到了某个设备支持的设备簇。
我们看下这个结构:1
2
3
4
5
6typedef struct VkQueueFamilyProperties {
VkQueueFlags queueFlags;
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
queueFlags表示支持的队列类型的位标志。对于我们渲染来说,我们希望它至少支持VK_QUEUE_GRAPHICS_BIT,所以可以用下面的方式来判断:1
2
3
4
5
6for (const auto& queueFamily : queueFamilies) {
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
break;
}
}
在vulkan_core.h中定义了如下的队列类型:1
2
3
4
5
6
7
8typedef enum VkQueueFlagBits {
VK_QUEUE_GRAPHICS_BIT = 0x00000001,
VK_QUEUE_COMPUTE_BIT = 0x00000002,
VK_QUEUE_TRANSFER_BIT = 0x00000004,
VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
VK_QUEUE_PROTECTED_BIT = 0x00000010,
VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkQueueFlagBits;
这就意味着我们在选择物理设备时,要及其注意要看这个设备是否支持都写队列簇的类型。
当然如果我们希望做的时渲染的话,我们还要看下设备是否支持渲染到屏幕的特性,用如下的函数:1
2
3
4
5VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceSupportKHR(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex,
VkSurfaceKHR surface,
VkBool32* pSupported);
如果支持,那么就可以选择出合适的物理设备了。
具体的代码如下: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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
VkPhysicalDeviceFeatures supportedFeatures;
vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
}
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto& extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
QueueFamilyIndices 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;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (presentSupport) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
i++;
}
return indices;
}
总结:
我们在创建了instance后,下面要做的就是选择物理设备,物理设备可能会有很多,我们需要在其中选出支持我们需要的扩展的,并且支持指定的队列簇的设备。这样我们选择完物理设备后,就可以进行下一步的逻辑设备的创建了。