Sat
16
Dec 2023
This article is for you if you are a graphics programmer who develops for Windows using Direct3D 11, 12, or Vulkan, and you want to fetch the version of the graphics driver currently installed in your user's system programmatically. If you are in a hurry, you can jump straight to the recommended solution in section "DXGI way" below. However, because this topic is non-trivial, I invite you to read the entire article, where I explain it comprehensively.
We know that devices need drivers installed in the system to function properly. Back in the days, drivers (just like other software) were often installed once and then used for months or years. New devices were coming with a CD that contained the driver.
These days are long gone. Now, we download the latest drivers from the Internet, and we are asked to update them frequently. New version of the graphics driver (whether from AMD, Nvidia, or Intel) is released as often as every month or even multiple times per month. Updating it is an inconvenience, but it has its advantages: new versions usually contain bug fixes and performance optimizations, often targeted for notable, newly released games.
Graphics API like Direct3D or Vulkan is a standardized contract between a game, game engine, or other graphics application, and the driver. In an ideal world, every game that correctly uses the API would work with any driver that correctly implements the API.
However, we know that software is not ideal and often contains bugs. Knowing about a specific issue, we may want to check if the user has an outdated driver and if so, we may apply some workaround, disable some features in our code, or at least warn him about the problem and recommend a driver update. To do this, we need to learn how to retrieve the current version of the driver installed in his system programmatically using some API, preferably in a numerical format like "major . minor . patch", which can be compared lexicographically. Below, we will explore various methods to do this, with sample code in C++.
AMD offers a C++ library called AMD GPU Services (AGS), which provides AMD-specific extensions to DirectX 11, 12, as well as some generic functions related to AMD GPUs. Among them, we can find a function and a structure that provides the driver version:
AGSContext* ctx = nullptr;
AGSGPUInfo gpu_info = {};
if(agsInitialize(AGS_CURRENT_VERSION, nullptr, &ctx, &gpu_info) == AGS_SUCCESS)
{
printf("driverVersion: %s\n", gpu_info.driverVersion);
printf("radeonSoftwareVersion: %s\n", gpu_info.radeonSoftwareVersion);
}
// At the end, don't forget to:
agsDeInitialize(ctx);
This is where things get complicated, because we have two strings here. It is because AMD uses two ways of versioning in their drivers:
driverVersion
is the most user-friendly version number that follows format "year . month . ordinal_number". For example, on my current PC with Radeon RX 7900 XTX card, this variable is "23.12.1"
, which means first driver released in December 2023. This is also the number you can see when you go to the page Driver and Software at amd.com, where you can find and download the latest AMD driver.radeonSoftwareVersion
carries an internal driver version that usually starts with 4 numbers. For example, on my PC it is "23.30.13.01-231128a-398226C-AMD-Software-Adrenalin-Edition"
. We can suspect this number is related to how driver code is developed, so that when the first or second number is incremented, it is likely a bigger change in the code compared to a change in only the third or fourth number.If you open AMD Software app, you can see both these versions on the Settings > System tab. There is also a table available online that provides a correlation between these two versioning schemes for historical drivers - see Radeon™ Vulkan® Drivers Version Table at gpuopen.com. Note the title is misleading - the page is not about Vulkan only. There is also a third version number provided on this page as "Windows Driver Store Version" column, also marked on the screenshot below in blue. Please ignore it for now - we will get back to it later. The table is also offered in a machine-readable XML format. Scroll the AMD page to the bottom to see the links.
These two versions provided by AMD are strings of type char*
. If you think about parsing them into numbers, you must be aware of some pitfalls:
driverVersion
doesn't always follow the "year . month . ordinal_number" format. It seems to do it for discrete graphics cards, but there are cases where a platform uses some special driver. This happens on laptops with AMD integrated graphics. For example, on Lenovo ThinkPad Z16 laptop, this variable is "22.20.46.19"
. As you can see, we have the internal version here instead of the user-friendly version.radeonSoftwareVersion
is more reliable. This string seems to always start with numbers meaning the internal version, followed by some other characters. However, as you can see in the table mentioned before, there were cases in the past when the internal version had only 3 not 4 numbers (e.g. most recent one was "21.4.1" = "21.10.02"), so you should consider this when implementing your parser.There is one more pitfall. Successfully initializing AGS library and retrieving driver version doesn't necessarily mean our application works on an AMD GPU! There may be an Nvidia or Intel card (or an integrated processor graphics) also present in the system. We may (intentionally or accidentally) select that one to use when creating the logical Device object. Thus, we should control what Adapter (speaking in DirectX terms) / Physical Device (speaking in Vulkan terms) do we use and correlate it with the information found in AGS.
DXGI_ADAPTER_DESC::VendorId
. Value 0x1002 = AMD, 0x10DE = Nvidia, 0x8086 (sic!) = Intel. This should be enough to know if our adapter of choice refers to the AMD driver we just described.AGSGPUInfo
structure returned by AGS further, we will find a list of GPUs there. To find the one corresponding to our Adapter, we should compare all 3 variables: DXGI_ADAPTER_DESC::VendorId
, DeviceId
, Revision
with corresponding variables in each gpu_info.devices[i]
to find the matching one.Note that although AGS library provides extensions specific to Direct3D 11 and 12, some functions are generic and not related to any graphics API. This includes the functions shown above, which are about the initialization of the main AGSContext
object. You can use them to get the information about GPUs installed in the system and the driver version even you if use some other API like Vulkan, or none.
Nvidia also provides a C++ library with vendor-specific extensions, called NVAPI. One difference is that their website requires free registration before you can download it. Once you do it, you can find a function there that retrieves the version of the Nvidia driver installed in the system:
NvU32 DriverVersion;
NvAPI_ShortString BuildBranchString;
if(NvAPI_SYS_GetDriverAndBranchVersion(&DriverVersion, BuildBranchString) == NVAPI_OK)
{
printf("DriverVersion: %u\n", DriverVersion);
printf("BuildBranchString: %s\n", BuildBranchString);
}
Nvidia seems to use a simpler versioning scheme for their drivers with just two numbers. On my GeForce RTX 3080 card, with driver recently updated, DriverVersion
is 54629 and BuildBranchString
is "r546_21"
. The former seems to carry the version number, in its 3+2 decimal digits (not bits!), that is also shown in the GeForce Experience app, as you can see on the screenshot below. I think this one should be good for checks and comparisons. The latter carries a string that roughly matches this number, but not exactly. I don't know how to interpret it. It may be a corresponding internal version of their code, similar to how AMD uses two ways of driver versioning.
Like mentioned above for AMD, please remember that successfully retrieving the Nvidia driver version doesn't necessarily mean we will use an Nvidia GPU. To make sure, check Adapter VendorId
whether it indicates Nvidia. If you want to find a specific GPU on the list offered by NVAPI, you must correlate it with DXGI_ADAPTER_DESC::AdapterLuid
. This identifier can be found on Nvidia side as NV_LOGICAL_GPU_DATA::pOSAdapterId
, retrieved using function NvAPI_GPU_GetLogicalGpuInfo
. The library offers a two-level hierarchy with "logical devices" pointing to "physical devices", but describing it is out of the scope of this article.
Note that the function shown above is just a global function that doesn't need a pointer to a Direct3D Device object or anything like that, so you can use it regardless of whether you are going to do graphics using D3D, Vulkan, or none. This is similar to AMD AGS.
There is a way of checking the driver version that works across all vendors. I only learned about it recently. It is part of DXGI - a standard Windows API that offers a foundation for Direct3D 11 and 12. To use this method, we need a pointer to the Adapter object. The code looks obscure, but it is documented in the description of IDXGIAdapter::CheckInterfaceSupport method.
IDXGIAdapter* adapter = ...
LARGE_INTEGER i;
if(SUCCEEDED(adapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &i)))
{
printf("User mode driver version: %u.%u.%u.%u\n",
i.QuadPart >> 48,
(i.QuadPart >> 32) & 0xFFFF,
(i.QuadPart >> 16) & 0xFFFF,
i.QuadPart & 0xFFFF);
}
As you can see, when queried for __uuidof(IDXGIDevice)
, this method returns a LARGE_INTEGER
, which is a WinAPI wrapper over a UINT64
number. This number can be split into 4x 16-bit numbers, starting from the most significant bytes. When formatted into a string as decimal numbers with dots between them, they give a version like "31.0.23013.1023"
in case of my AMD card mentioned earlier.
It is called a User Mode Driver (UMD) version. It corresponds to what we can see in the system Device Manager when we right-click on the display adapter > select Properties > Driver tab > look at the Driver Version parameter. This number also corresponds to the "Window Driver Store Version" column in the AMD table.
This may be the most reliable way of retrieving and comparing versions of the graphics driver. It works regardless of the GPU vendor. It is returned as a number, so we don't need to parse any string whose format may change in the future. On the other hand, a disadvantage of this method is that this number is also the most obscure and hidden from the user, as opposed to AMD's driver version like "23.12.1" or Nvidia's "546.29". If you display a message like "Your graphics driver version is 31.0.23013.1023. Please update it to the latest.", users may feel confused.
Intel way of driver versioning seems to be the simplest, as their driver application shows the same four-number version as system Device Manager and DXGI described above. There is no second or third way of numbering introduced.
Intel also offers a library with custom vendor extensions, called GPU Detect. If you use it, in array GPUDetect::GPUData::dxDriverVersion[i]
you will find 4 numbers indicating the driver version. The library reads them from system registry, from "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DirectX\{adapter LUID}\DriverVersion", but it seems to be equal to the version returned by DXGI, as described above.
So far, we focused on Direct3D and DXGI, but I also want to mention how to retrieve driver version in Vulkan. In the initial Vulkan version, we only had variable VkPhysicalDeviceProperties::driverVersion
, which carries a "vendor-specific" number. In case of my AMD card, it is 8388891. It may be good for comparisons, but it seems there is no official way of decoding this number into some user-friendly version that would work consistently cross-vendor. See this discussion on Reddit.
Fortunately, Vulkan 1.2 added structure VkPhysicalDeviceVulkan12Properties
, which carries two additional strings that identify the driver. In case of my cards, they are:
driverName
on AMD: "AMD proprietary driver"
, on Nvidia: "NVIDIA"
, on Intel: "Intel Corporation"
driverInfo
on AMD: "23.12.1 (LLPC)"
, on Nvidia: "546.29"
, on Intel: "Intel driver"
The first one is not very useful, but the second one seems to be exactly what we are looking for - a version that can be parsed into numbers, compared, as well as displayed to the user. At least on AMD and Nvidia, not so much on Intel. Example code that retrieves this version is:
VkPhysicalDevice physicalDevice = ...
VkPhysicalDeviceVulkan12Properties vulkan12Properties = {};
vulkan12Properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES;
VkPhysicalDeviceProperties2 properties2 = {};
properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
properties2.pNext = &vulkan12Properties;
vkGetPhysicalDeviceProperties2(physicalDevice, &properties2);
printf("driverName: %s\n", vulkan12Properties.driverName);
printf("driverInfo: %s\n", vulkan12Properties.driverInfo);
Note that here, like in DXGI method, we need to select a specific GPU (called Physical Device in Vulkan). If you want to correlate it with a DXGI Adapter, you can do this by comparing VkPhysicalDeviceIDProperties::deviceLUID
with DXGI_ADAPTER_DESC::AdapterLuid
using memcmp
.
As you can see, detecting version of the currently installed graphics driver in non-trivial. There are many ways of driver versioning and many APIs that can be used to fetch them. Ideally, we would never need to do it, but if you want to know this version, pick a method that suits you best.
If you decide to parse one of these strings into numbers, make sure your code is robust and future-proof. Remember that the format of this string may change in the future, so if you fail to parse it, better assume the driver is good, up-to-date, and supports all the features, rather than the other way around, so that your application won't stop working.
If you just want to see this version for the current GPU, and you don't need to integrate this logic into your code, you can use my small command-line tool: D3d12info. It implements all the methods described in this article and prints this information to the console, along with all other information about the current GPU that can be fetched using DXGI, Direct3D 12, AGS, NVAPI, and Intel GPU Detect. It also supports JSON output format suitable for automated processing, so you can incorporate it as part of your tool chain. The app is open-source, so you can also browse its source code to see a full, working example for the code snippets shown in this article.
Big thanks to Szymon Nowacki for all the information and testing on Intel GPU!
Comments | #rendering #directx #vulkan #windows #winapi Share