I have just merged DeviceSelection branches of VulkanSceneGraph and vsgExamples with their respective masters. This work adds the ability to provide user preferences for which PhysicalDevice to use when creating Windows, and the better controls in vsg::Window to enable application level set up of Instance/Surface/PhysicalDevice/Device to use.
- Provide better default/out of the box experience by automatically selecting dedicated GPUs over integrated GPUs - something that is common on high end laptops.
- Ability for applications to control the preferences of which PhysicalDevices to use.
- Explicit Application control over Window setup of Instance/Surface/PhysicalDevice/Device for power users that need fine grained control.
Item 1 and 2 and provided by a new WindowTraits member:
vsg::PhysicalDeviceTypes deviceTypePreferences;
The WindowTraits::deafults() method initializes this deviceTypePreferences member with:
// prefer discrete gpu over integrated gpu over virtual gpu
deviceTypePreferences = { VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU };
To change the preference to one that selects an integated GPU when available, such as for a low power graphics application, you'd simply do:
autp windowTraits = vsg::WindowTraits::create();
windowTraits->deviceTypePreferences = {VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU};
The the Window creates it'll select an integrated GPU if available, if only a dedicated GPU or a virtual GPU is available then it'll select this instead, so you'll still get a Window created.
My expectation is that these default preferences will provide a good out of experience for the vast majority of VulkanSceneGraph applications, the remaining say 5% of application developers would want more control where the overriding of preferences would satisfy 4% more leaving 1% of power users to that want. need, must have ULTIMATE POWER!
--
To list the devices available on your system you can run:
$ vsgdeviceselection --list
physicalDevices.size() = 3
matched ref_ptr<vsg::PhysicalDevice>(vsg::PhysicalDevice 0x55ee8b31bed0) GeForce RTX 2060 deviceType = 2
not matched ref_ptr<vsg::PhysicalDevice>(vsg::PhysicalDevice 0x55ee8b31c340) GeForce GTX 1650 deviceType = 2
not matched ref_ptr<vsg::PhysicalDevice>(vsg::PhysicalDevice 0x55ee8b31c800) GeForce GTX 1650 deviceType = 2
The device type value of 2 is VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU. The VkPhysicalDeviceType values are list by Khronos docs as:
The code to output these values is:
// use the Window implementation to create the device and surface
auto instance = window->getOrCreateInstance();
auto surface = window->getOrCreateSurface();
auto physicalDevices = instance->getPhysicalDevices();
std::cout<<"\nphysicalDevices.size() = "<<physicalDevices.size()<<std::endl;
for(auto& physicalDevice : physicalDevices)
{
auto properties = physicalDevice->getProperties();
auto [graphicsFamily, presentFamily] = physicalDevice->getQueueFamily(windowTraits->queueFlags, surface);
if (graphicsFamily >= 0 && presentFamily >= 0) std::cout<<" matched "<<physicalDevice<<" "<<properties.deviceName<<" deviceType = "<<properties.deviceType<<std::endl;
else std::cout<<" not matched "<<physicalDevice<<" "<<properties.deviceName<<" deviceType = "<<properties.deviceType<<std::endl;
}
A second branch is selected using --pd or --PhyscialDevice command line option, this invokes explicit creation of which vsg::PhysicalDevice to use when creating the window:
// use the Window implementation to create the Instance and Surface
auto instance = window->getOrCreateInstance();
auto surface = window->getOrCreateSurface();
// create a vk/vsg::PhysicalDevice, prefer descrete GPU over integrated GPUs when they area available.
auto physicalDevice = instance->getPhysicalDevice(windowTraits->queueFlags, surface, {VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU});
std::cout<<"Ceated our own vsg::PhysicalDevice "<<physicalDevice<<std::endl;
window->setPhysicalDevice(physicalDevice);
When the Window then goes to create it's vk/vsg::Device it'll use this vk/vsg::PhysicalDevice. Note the use of the new preferences options in the Instance::getPhysicalDevice(..) call above. If the list of perferneces is empty then it'll just select the first PhysicalDevice that matches the queueFlags and Surface.
Then there is a final --deivce branch that creates both the vsg::PhysicalDevice and vsg:::Device and passes these to the Window to use by the newly added Window::setPhysicalDevice(..) and Window::setDevice(..) methods:
// use the Window implementation to create the Instance and Surface
auto instance = window->getOrCreateInstance();
auto surface = window->getOrCreateSurface();
vsg::Names requestedLayers;
if (windowTraits->debugLayer)
{
requestedLayers.push_back("VK_LAYER_LUNARG_standard_validation");
if (windowTraits->apiDumpLayer) requestedLayers.push_back("VK_LAYER_LUNARG_api_dump");
}
vsg::Names validatedNames = vsg::validateInstancelayerNames(requestedLayers);
vsg::Names deviceExtensions;
deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
deviceExtensions.insert(deviceExtensions.end(), windowTraits->deviceExtensionNames.begin(), windowTraits->deviceExtensionNames.end());
// set up vk/vsg::Device
auto [physicalDevice, queueFamily, presentFamily] = instance->getPhysicalDeviceAndQueueFamily(windowTraits->queueFlags, surface);
if (!physicalDevice || queueFamily < 0 || presentFamily < 0)
{
std::cout<<"Error: vsg::Window::create(...) failed to create Window, no Vulkan PhysicalDevice supported."<< VK_ERROR_INVALID_EXTERNAL_HANDLE<<std::endl;
return 1;
}
vsg::QueueSettings queueSettings{vsg::QueueSetting{queueFamily, {1.0}}, vsg::QueueSetting{presentFamily, {1.0}}};
auto device = vsg::Device::create(physicalDevice, queueSettings, validatedNames, deviceExtensions, windowTraits->deviceFeatures, instance->getAllocationCallbacks());
std::cout<<"Ceated our own vsg::Device "<<device<<std::endl;
window->setDevice(device);
The vsg::Window class also has new Widow::setInstance(..) and Window::setSurface(..) methods if you need even more control, though I've not added code in the example for this. If you are enough of power user to actually need to do this you don't need an example for it!
With all this extra control the original WindowTraits::device property is no longer required as a means of assigning a user created Device so to avoid confusion I've removed this so if you relied upon this so far then you'll need to amend your code to use one of the above methods.
There is even a chance that you'll be delete all the custom code as the default preference for dedicated GPUs over integrated GPUs should solve one of the main reasons why users had to override the original default behavior of just selecting the first list physical device that matched the queue and surface capabilities.
Cheers,
Robert.