窗口已经创建完成(win32的surface),接下来就是创建逻辑设备了。在SceneWidget的头文件中再添加一个bool CreateDevice()的函数,用来创建逻辑设备。
创建逻辑设备,一般要分两步走。
第一步:查找物理设备,先要找到可用的物理设备也就是显止,才能根据物理设备创建逻辑设备。所以在头文件中再添加一个查找的函数:bool FindPhysicalDevice()。和添加一个物理设备的成员变量,用来记录所选择的物理设备:VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE。
(1)罗列出当前可用的物理设备。
可以通过vkEnumeratePhysicalDevices函数来获取当前的物理设备的信息。其用法与查找实例支持的Layer的特性函数vkEnumerateInstanceLayerProperties类似。
先调用一次vkEnumeratePhysicalDevices,第一个参数传入之前创建好的实例m_instance。第二个参数为返回的当前可用的物理设备的数目,所以传一个uint类型的变量deviceCount的引用。第三个参数传入nullptr。这次的调用只是获取当前可用设备的数目。
查检deviceCount的值是否为0,uint类型不会小于0 。如果deviceCount的值合法,再声明一个用于存放物理设备的列表变量,可以在声明时进行初始化,列表的数目初始化为deviceCount,即: std::vector<VkPhysicalDevice> devices(deviceCount)。
第二次调用vkEnumeratePhysicalDevices,前两个参数与第一次调用时的相同。第三个参数为devices.data()。
其代码如下:
(2)找出符合要求的设备。
对物理设备列表进行遍历,遍历时对每个物理设备进行检查。需要在SceneWidget.h文件中添加一个检查物理设备的函数bool CheckPhysicalDevice(VkPhysicalDevice device),然后遍历物理设备时,通过这个检查函数对每个设备进行检查:
然后在CheckPhysicalDevice(VkPhysicalDevice device)函数里进行检测。如果检测成功就用成员变量记录下这个物理设备。
在CheckPhysicalDevice函数中需要做几件事。在这里先检查队列家族特性(自己直译),所以在SceneWidget.h头文件中,添加bool CheckQueue(VkPhysicalDevice device)。然后在CheckPhysicalDevice函数中进行调用。
在CheckQueue函数中检查队列特性:
先要检查一上物理设备的查询队列家族特性。必须支持图形和在surface上进行显示。查询队列家族特性的函数vkGetPhysicalDeviceQueueFamilyProperties与vkEnumeratePhysicalDevices用法类似。
第一次调用查询特性的数目,可以用uint32_t queueFamilyCount = 0;来记录下这个数目。
vkGetPhysicalDeviceQueueFamilyProperties函数第一个参数为device,要查找的设备指针。第二个参数为queueFamilyCount的引用,返回设备支持的队列的数目。第三个参数这里设为nulllptr,这个参数是要返回支持的队列的数组。
设备动支持的特性在vulkan中对应的是VkQueueFamilyProperties结构。所以可以用一个std::vector<VkQueueFamilyProperties> queueFamilies来保存获取到的队列。
VkQueueFamilyProperties结构的成员:
VkQueueFlags queueFlags;标志这个队列的特性如:VK_QUEUE_GRAPHICS_BIT或是VK_QUEUE_COMPUTE_BIT等等。
uint32_t queueCount;在同一特性中可以创建此类队列的个数。
uint32_t timestampValidBits;时间戳。36到64位,也可以是0,表示不支持时间戳。
VkExtent3D minImageTransferGranularity;操作队列时,支持图像传输的最小粒度。
第二次调用,根据特性的数目来获取支持的特性数组。所以前两个参数与第一次调用时一致。第三个参数为queueFamilies.data()。
然后对特性再进行遍历,检查是否支持VK_QUEUE_GRAPHICS_BIT特性:
这里的m_index = i是一个int类型的值,是记录下这个特性在特性列表中的索引,这个索引将来还要用,所以先记录一下。
查询是否支持surface显示,要用到vkGetPhysicalDeviceSurfaceSupportKHR函数。其参数也好理解,第一个参数是当前的物理设备device,第二个参数是当前的特性列表的索引,第三个参数是需要支持的surface指针。第四个参数返回是否支持是一个bool变量的引用。
查找特性时,有可能会出现多个设备都支持所需要的特性。这时,需要返回第一个支持的设备就行。除非是有显卡需要显示场景,在这里只需要一个就可以了,所以直接返回。
再检查物理设备对扩展的支持,在SceneWidget.h中添加bool CheckDeviceExtensionSupport(VkPhysicalDevice device)。
可以通过函数vkEnumerateDeviceExtensionProperties来获取物理设备支持的扩展。也是需要调用两次。一次是获取支持的扩展的数目,一闪根据数目获取支持的扩展的名称。
其用法就不再细述。在获取到支持的扩展列表后,通过名称来找有没有与VK_KHR_SWAPCHAIN_EXTENSION_NAME宏定义相同的字符串。如果有则说明支持,如果没有则不支持。VK_KHR_SWAPCHAIN_EXTENSION_NAME在vulkan_core.h文件中有定义,其实就是"VK_KHR_swapchain"。
再检查一下物理设备对Swapchain(交换链)的支持。
获取物理设备对surface相关的支持,在SceneWidget.h中添加 void CheckDeviceSupportSurface(VkPhysicalDevice device)。在这个函数里执行以下的操作:
可以通过函数vkGetPhysicalDeviceSurfaceCapabilitiesKHR来获取,其参数:
第一个参数为物理设备指针。第二个参数为之前创建的m_surface。第三个参数为VkSurfaceCapabilitiesKHR结构的引用,将查询的结构保存在这个结构中。结构中的数据为:
uint32_t minImageCount; 交换链中最小的图像数据。
uint32_t maxImageCount; 交换链中最大的图像数据。
VkExtent2D currentExtent; 当前的宽高。
VkExtent2D minImageExtent; 最小图像的宽高。
VkExtent2D maxImageExtent; 最大图像的宽高。
uint32_t maxImageArrayLayers; 最大的图层数。
VkSurfaceTransformFlagsKHR supportedTransforms; 所支持的显示时的转换,是VkCompositeAlphaFlagBitsKHR的掩码,如: VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR、K_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR等
VkSurfaceTransformFlagBitsKHR currentTransform;表示当前的转换
VkCompositeAlphaFlagsKHR supportedCompositeAlpha;表示surface上的合成模式,可以通过alpha的值实现不透明的组合,如果图像没有alpha,则默认为1.0。
VkImageUsageFlags supportedUsageFlags;支持的显示模式,是VkPresentModeKHR的掩码。
m_capabilities记录了支持的surface的能力。
然后再取物理设备支持的surface的像素格式,通过vkGetPhysicalDeviceSurfaceFormatsKHR来获取,同样需要调用两次,一次取支持的像素模式的数目,一次取支持的像素的格式内容。将获取的内容记录在VkSurfaceFormatKHR的列表m_formats里。
还要再取一下支持的surface的显示方式:
在CheckPhysicalDevice函数里,调用CheckQueue之后,调用上面添加的两个函数,并在最后检查物理设备是否支持采样的向异性。
最终CheckPhysicalDevice函数的样子如下:
在FindPhysicalDevice函数里,作完之前的检查后需要判断一下m_physicalDevice这个成员是否为空。
在创建RenderPass和pipeline时,还要用到最大采样样本数,正好在这里查这句询一下,并记录下来,之后再用时,可以直接用就行。在这里用一个成员变量来记录这个值VkSampleCountFlagBits m_msaa = VK_SAMPLE_COUNT_1_BIT。
在头文件中再添加一个获取最大采样样本数的函数:
VkSampleCountFlagBits GetMaxUsableSampleCount()。查询选中的物理设备支持的最大采样样本数。
VkSampleCountFlagBits是定义在vulkan_core.h中的。所以还要在头文件中包含vulkan_core.h头文件。
最大采样样本数可以在vulkan的VkPhysicalDeviceProperties结构中有保存,可以通过vkGetPhysicalDeviceProperties函数去查询。
vkGetPhysicalDeviceProperties的第一个参数为要查询的物理设备,此时已经保存在m_physicalDevice成员中。第二个参数为VkPhysicalDeviceProperties结构的指针(对象的引用)。
VkPhysicalDeviceProperties结构的成员
uint32_t apiVersion;vulkan设备支持的vulkan的版本。
uint32_t driverVersion;设备的驱动版本
uint32_t vendorID;物理设备的供应商的唯一标识
uint32_t deviceID;供应商提供的物理设备的唯一标识
VkPhysicalDeviceType deviceType;指定设备类型
char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];设备名称
uint8_t pipelineCacheUUID[VK_UUID_SIZE];pipeline的uuid
VkPhysicalDeviceLimits limits;物理设备的限制
VkPhysicalDeviceSparseProperties sparseProperties;设备支持的各种稀疏属性
获取到最大采样样本数后,将color的与depth的比较取小的那个。
现在所有的检查已完毕,可以创建逻辑设备了。在CreateDevice函数中,先调用FindPhysicalDevice,进行检查,再去创建逻辑设备。
和之前一样,不知道该怎么做时,就先写创建逻辑设备的函数,然后补全参数,vkCreateDevice函数,其参数:
VkPhysicalDevice physicalDevice 物理设备
const VkDeviceCreateInfo* pCreateInfo 创建逻辑设备所需要的信息,这里需要在vkCreateDevice的上面写VkDeviceCreateInfo的对象,并填充数据。
const VkAllocationCallbacks* pAllocator 这个参数依然写为nullptr。
VkDevice* pDevice 生成的逻辑设备的对象指针。所以还需要在SceneWidget.h中添加一个VkDevice类型的m_deivce变量。
添加VkDeviceCreateInfo结构,变量为deviceCreateInfo,并初始化为{}。
VkStructureType sType; 结构的类型,为VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO
const void* pNext; 这个结构现不用扩展,所以为nullptr。
VkDeviceCreateFlags flags; 此标记暂时写0。
uint32_t queueCreateInfoCount;应该是写VkDeviceQueueCreateInfo数组的个数。
const VkDeviceQueueCreateInfo* pQueueCreateInfos; VkDeviceQueueCreateInfo数组,所以在VkDeviceCreateInfo的上面再写一个VkDeviceQueueCreateInfo的结构。
uint32_t enabledLayerCount; 需要打开的Layer的数目
const char* const* ppEnabledLayerNames; 需要打开的Layer的名称
uint32_t enabledExtensionCount;需要支持的扩展数目
const char* const* ppEnabledExtensionNames;需要支持的扩展名称
const VkPhysicalDeviceFeatures* pEnabledFeatures;要开启的Feature的结构指针,所以还要补一个VkPhysicalDeviceFeatures的结构。
再说VkDeviceQueueCreateInfo数组,之前检测时,检查过VK_QUEUE_GRAPHICS_BIT和对present的支持。所以这里,要创建图形队列和显示队列两个。即:
std::vector< VkDeviceQueueCreateInfo > queueCreateInfos;
对于VkDeviceQueueCreateInfo结构要填充的成员:
VkStructureType sType; 值为:VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO
const void* pNext; 值为nullptr;
VkDeviceQueueCreateFlags flags; 保持默认
uint32_t queueFamilyIndex; 这个是队列的索引,就是m_index的值。
uint32_t queueCount; 值为queueCreateInfos.size()
const float* pQueuePriorities; 是队列的优先级,一般是从0到1。由于只有一个队列,所以取0到1之间的值就行,这里任意取1.0。
由于两个队列的索引是一样的。所以queueCreateInfos中只记录一个队列的创建信息就行。
所以在VkDeviceCreateInfo结构中对应的成员应该是
queueCreateInfoCount = queueCreateInfos.size();
pQueueCreateInfos = queueCreateInfos.data();
再定义一个VkPhysicalDeviceFeatures deviceFeatures;变量,之前有检测对采样的支持,即:samplerAnisotropy属性为true;所以将deviceFeatures. samplerAnisotropy设置为true。
创建逻辑设备,对扩展的支持,其实在物理设备检测时也已经有了,当时只检测了VK_KHR_SWAPCHAIN_EXTENSION_NAME,所以:
最后还有检验层,这个其实也可以不加。只是如果不加的话,调试程序很不方便。所以可以先加上。之前创建VkInstance对象时,用了一个bool的变量来决定加或不加。现在也可以使用一个变量来决定加不加。最主要的是,真正应用时,可以通过配置文件、上层调用传参、宏定义等方式来设置逻辑设备是否开启校验层。
最后判断vkCreateDevice函数的返回值,并在SceneWidget::UnInit函数中添加清理函数:
创建完成后,顺便把那两个队列创建出来,并保存在m_graphicsQueue和m_presentQueue:
然后在SceneWidget::Init中调用一下,看看有没有报错。
执行:
没有报错:) 。