From de768afbdcf9dd6a588ac975bc56e3fd755091c2 Mon Sep 17 00:00:00 2001 From: MJacred Date: Tue, 11 Oct 2022 12:39:41 +0200 Subject: [PATCH] Fetch video adapter driver name and version from OS on Linux/*BSD and Windows --- core/core_bind.cpp | 6 + core/core_bind.h | 2 + core/os/os.h | 2 + doc/classes/OS.xml | 9 ++ drivers/unix/os_unix.cpp | 4 + drivers/unix/os_unix.h | 2 + platform/linuxbsd/os_linuxbsd.cpp | 199 ++++++++++++++++++++++++++++++ platform/linuxbsd/os_linuxbsd.h | 5 + platform/windows/detect.py | 2 + platform/windows/os_windows.cpp | 92 ++++++++++++++ platform/windows/os_windows.h | 2 + 11 files changed, 325 insertions(+) diff --git a/core/core_bind.cpp b/core/core_bind.cpp index ba48adff6ab7..c506403133f9 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -330,6 +330,10 @@ String OS::get_version() const { return ::OS::get_singleton()->get_version(); } +Vector OS::get_video_adapter_driver_info() const { + return ::OS::get_singleton()->get_video_adapter_driver_info(); +} + Vector OS::get_cmdline_args() { List cmdline = ::OS::get_singleton()->get_cmdline_args(); Vector cmdlinev; @@ -548,6 +552,8 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cmdline_args"), &OS::get_cmdline_args); ClassDB::bind_method(D_METHOD("get_cmdline_user_args"), &OS::get_cmdline_user_args); + ClassDB::bind_method(D_METHOD("get_video_adapter_driver_info"), &OS::get_video_adapter_driver_info); + ClassDB::bind_method(D_METHOD("set_restart_on_exit", "restart", "arguments"), &OS::set_restart_on_exit, DEFVAL(Vector())); ClassDB::bind_method(D_METHOD("is_restart_on_exit_set"), &OS::is_restart_on_exit_set); ClassDB::bind_method(D_METHOD("get_restart_on_exit_arguments"), &OS::get_restart_on_exit_arguments); diff --git a/core/core_bind.h b/core/core_bind.h index 0f85e473a518..bedfbd40895a 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -198,6 +198,8 @@ public: Vector get_cmdline_args(); Vector get_cmdline_user_args(); + Vector get_video_adapter_driver_info() const; + String get_locale() const; String get_locale_language() const; diff --git a/core/os/os.h b/core/os/os.h index 6944d29eebca..c8821d07bf50 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -118,6 +118,8 @@ public: String get_current_rendering_driver_name() const { return _current_rendering_driver_name; } int get_display_driver_id() const { return _display_driver_id; } + virtual Vector get_video_adapter_driver_info() const = 0; + void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR); void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void print_rich(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 54f31d791877..a7d535472d88 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -451,6 +451,15 @@ [b]Note:[/b] This method is not supported on the web platform. It returns an empty string. + + + + Returns the video adapter driver name and version for the user's currently active graphics card. + The first element holds the driver name, such as [code]nvidia[/code], [code]amdgpu[/code], etc. + The second element holds the driver version. For e.g. the [code]nvidia[/code] driver on a Linux/BSD platform, the version is in the format [code]510.85.02[/code]. For Windows, the driver's format is [code]31.0.15.1659[/code]. + [b]Note:[/b] This method is only supported on the platforms Linux/BSD and Windows. It returns an empty array on other platforms. + + diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index ab298a0e49f5..92ddeb5582c1 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -154,6 +154,10 @@ void OS_Unix::finalize_core() { NetSocketPosix::cleanup(); } +Vector OS_Unix::get_video_adapter_driver_info() const { + return Vector(); +} + String OS_Unix::get_stdin_string(bool p_block) { if (p_block) { char buff[1024]; diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index 8ef650f28b8b..aa42ed0b273c 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -51,6 +51,8 @@ protected: public: OS_Unix(); + virtual Vector get_video_adapter_driver_info() const override; + virtual String get_stdin_string(bool p_block) override; virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override; // Should return cryptographycally-safe random bytes. diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index f4e94f1a919e..64b35654ccf5 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -34,6 +34,11 @@ #include "main/main.h" #include "servers/display_server.h" +#include "modules/modules_enabled.gen.h" // For regex. +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" +#endif + #ifdef X11_ENABLED #include "display_server_x11.h" #endif @@ -242,6 +247,200 @@ String OS_LinuxBSD::get_version() const { return uts.version; } +Vector OS_LinuxBSD::get_video_adapter_driver_info() const { + const String rendering_device_name = RenderingServer::get_singleton()->get_rendering_device()->get_device_name(); // e.g. `NVIDIA GeForce GTX 970` + const String rendering_device_vendor = RenderingServer::get_singleton()->get_rendering_device()->get_device_vendor_name(); // e.g. `NVIDIA` + const String card_name = rendering_device_name.trim_prefix(rendering_device_vendor).strip_edges(); // -> `GeForce GTX 970` + + String vendor_device_id_mappings; + List lspci_args; + lspci_args.push_back("-n"); + Error err = const_cast(this)->execute("lspci", lspci_args, &vendor_device_id_mappings); + if (err != OK || vendor_device_id_mappings.is_empty()) { + return Vector(); + } + + // Usually found under "VGA", but for example NVIDIA mobile/laptop adapters are often listed under "3D" and some AMD adapters are under "Display". + const String dc_vga = "0300"; // VGA compatible controller + const String dc_display = "0302"; // Display controller + const String dc_3d = "0380"; // 3D controller + + // splitting results by device class allows prioritizing, if multiple devices are found. + Vector class_vga_device_candidates; + Vector class_display_device_candidates; + Vector class_3d_device_candidates; + +#ifdef MODULE_REGEX_ENABLED + RegEx regex_id_format = RegEx(); + regex_id_format.compile("^[a-f0-9]{4}:[a-f0-9]{4}$"); // e.g. `10de:13c2`; IDs are always in hexadecimal +#endif + + Vector value_lines = vendor_device_id_mappings.split("\n", false); // example: `02:00.0 0300: 10de:13c2 (rev a1)` + for (const String &line : value_lines) { + Vector columns = line.split(" ", false); + if (columns.size() < 3) { + continue; + } + String device_class = columns[1].trim_suffix(":"); + String vendor_device_id_mapping = columns[2]; + +#ifdef MODULE_REGEX_ENABLED + if (regex_id_format.search(vendor_device_id_mapping).is_null()) { + continue; + } +#endif + + if (device_class == dc_vga) { + class_vga_device_candidates.push_back(vendor_device_id_mapping); + } else if (device_class == dc_display) { + class_display_device_candidates.push_back(vendor_device_id_mapping); + } else if (device_class == dc_3d) { + class_3d_device_candidates.push_back(vendor_device_id_mapping); + } + } + + // Check results against currently used device (`card_name`), in case the user has multiple graphics cards. + const String device_lit = "Device"; // line of interest + class_vga_device_candidates = OS_LinuxBSD::lspci_device_filter(class_vga_device_candidates, dc_vga, device_lit, card_name); + class_display_device_candidates = OS_LinuxBSD::lspci_device_filter(class_display_device_candidates, dc_display, device_lit, card_name); + class_3d_device_candidates = OS_LinuxBSD::lspci_device_filter(class_3d_device_candidates, dc_3d, device_lit, card_name); + + // Get driver names and filter out invalid ones, because some adapters are dummys used only for passthrough. + // And they have no indicator besides certain driver names. + const String kernel_lit = "Kernel driver in use"; // line of interest + const String dummys = "vfio"; // for e.g. pci passthrough dummy kernel driver `vfio-pci` + Vector class_vga_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_vga_device_candidates, kernel_lit, dummys); + Vector class_display_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_display_device_candidates, kernel_lit, dummys); + Vector class_3d_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_3d_device_candidates, kernel_lit, dummys); + + static String driver_name; + static String driver_version; + + // Use first valid value: + for (const String &driver : class_3d_device_drivers) { + driver_name = driver; + break; + } + if (driver_name.is_empty()) { + for (const String &driver : class_display_device_drivers) { + driver_name = driver; + break; + } + } + if (driver_name.is_empty()) { + for (const String &driver : class_vga_device_drivers) { + driver_name = driver; + break; + } + } + + Vector info; + info.push_back(driver_name); + + String modinfo; + List modinfo_args; + modinfo_args.push_back(driver_name); + err = const_cast(this)->execute("modinfo", modinfo_args, &modinfo); + if (err != OK || modinfo.is_empty()) { + info.push_back(""); // So that this method always either returns an empty array, or an array of length 2. + return info; + } + Vector lines = modinfo.split("\n", false); + for (const String &line : lines) { + Vector columns = line.split(":", false, 1); + if (columns.size() < 2) { + continue; + } + if (columns[0].strip_edges() == "version") { + driver_version = columns[1].strip_edges(); // example value: `510.85.02` on Linux/BSD + break; + } + } + + info.push_back(driver_version); + + return info; +} + +Vector OS_LinuxBSD::lspci_device_filter(Vector vendor_device_id_mapping, String class_suffix, String check_column, String whitelist) const { + // NOTE: whitelist can be changed to `Vector`, if the need arises. + const String sep = ":"; + Vector devices; + for (const String &mapping : vendor_device_id_mapping) { + String device; + List d_args; + d_args.push_back("-d"); + d_args.push_back(mapping + sep + class_suffix); + d_args.push_back("-vmm"); + Error err = const_cast(this)->execute("lspci", d_args, &device); // e.g. `lspci -d 10de:13c2:0300 -vmm` + if (err != OK) { + return Vector(); + } else if (device.is_empty()) { + continue; + } + + Vector device_lines = device.split("\n", false); + for (const String &line : device_lines) { + Vector columns = line.split(":", false, 1); + if (columns.size() < 2) { + continue; + } + if (columns[0].strip_edges() == check_column) { + // for `column[0] == "Device"` this may contain `GM204 [GeForce GTX 970]` + bool is_valid = true; + if (!whitelist.is_empty()) { + is_valid = columns[1].strip_edges().contains(whitelist); + } + if (is_valid) { + devices.push_back(mapping); + } + break; + } + } + } + return devices; +} + +Vector OS_LinuxBSD::lspci_get_device_value(Vector vendor_device_id_mapping, String check_column, String blacklist) const { + // NOTE: blacklist can be changed to `Vector`, if the need arises. + const String sep = ":"; + Vector values; + for (const String &mapping : vendor_device_id_mapping) { + String device; + List d_args; + d_args.push_back("-d"); + d_args.push_back(mapping); + d_args.push_back("-k"); + Error err = const_cast(this)->execute("lspci", d_args, &device); // e.g. `lspci -d 10de:13c2 -k` + if (err != OK) { + return Vector(); + } else if (device.is_empty()) { + continue; + } + + Vector device_lines = device.split("\n", false); + for (const String &line : device_lines) { + Vector columns = line.split(":", false, 1); + if (columns.size() < 2) { + continue; + } + if (columns[0].strip_edges() == check_column) { + // for `column[0] == "Kernel driver in use"` this may contain `nvidia` + bool is_valid = true; + const String value = columns[1].strip_edges(); + if (!blacklist.is_empty()) { + is_valid = !value.contains(blacklist); + } + if (is_valid) { + values.push_back(value); + } + break; + } + } + } + return values; +} + Error OS_LinuxBSD::shell_open(String p_uri) { Error ok; int err_code; diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 722d83ba19d5..aea04c136349 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -69,6 +69,9 @@ class OS_LinuxBSD : public OS_Unix { String get_systemd_os_release_info_value(const String &key) const; + Vector lspci_device_filter(Vector vendor_device_id_mapping, String class_suffix, String check_column, String whitelist) const; + Vector lspci_get_device_value(Vector vendor_device_id_mapping, String check_column, String blacklist) const; + protected: virtual void initialize() override; virtual void finalize() override; @@ -82,6 +85,8 @@ public: virtual String get_distribution_name() const override; virtual String get_version() const override; + virtual Vector get_video_adapter_driver_info() const override; + virtual MainLoop *get_main_loop() const override; virtual uint64_t get_embedded_pck_offset() const override; diff --git a/platform/windows/detect.py b/platform/windows/detect.py index e6e1874fc07d..6caa14cbd368 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -427,6 +427,7 @@ def configure_msvc(env, vcvars_msvc_config): "Avrt", "dwmapi", "dwrite", + "wbemuuid", ] if env["vulkan"]: @@ -616,6 +617,7 @@ def configure_mingw(env): "uuid", "dwmapi", "dwrite", + "wbemuuid", ] ) diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 1978ec5ab65a..113e716688b5 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -53,6 +53,7 @@ #include #include #include +#include extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 1; @@ -308,6 +309,97 @@ String OS_Windows::get_version() const { return ""; } +Vector OS_Windows::get_video_adapter_driver_info() const { + REFCLSID clsid = CLSID_WbemLocator; // Unmarshaler CLSID + REFIID uuid = IID_IWbemLocator; // Interface UUID + IWbemLocator *wbemLocator = NULL; // to get the services + IWbemServices *wbemServices = NULL; // to get the class + IEnumWbemClassObject *iter = NULL; + IWbemClassObject *pnpSDriverObject[1]; // contains driver name, version, etc. + static String driver_name; + static String driver_version; + + const String device_name = RenderingServer::get_singleton()->get_rendering_device()->get_device_name(); + if (device_name.is_empty()) { + return Vector(); + } + + CoInitialize(nullptr); + + HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator); + if (hr != S_OK) { + return Vector(); + } + + hr = wbemLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, 0, NULL, 0, 0, &wbemServices); + SAFE_RELEASE(wbemLocator) // from now on, use `wbemServices` + if (hr != S_OK) { + SAFE_RELEASE(wbemServices) + return Vector(); + } + + const String gpu_device_class_query = vformat("SELECT * FROM Win32_PnPSignedDriver WHERE DeviceName = \"%s\"", device_name); + BSTR query = SysAllocString((const WCHAR *)gpu_device_class_query.utf16().get_data()); + BSTR query_lang = SysAllocString(L"WQL"); + hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &iter); + SysFreeString(query_lang); + SysFreeString(query); + if (hr == S_OK) { + ULONG resultCount; + hr = iter->Next(5000, 1, pnpSDriverObject, &resultCount); // Get exactly 1. Wait max 5 seconds. + + if (hr == S_OK && resultCount > 0) { + VARIANT dn; + VariantInit(&dn); + + BSTR object_name = SysAllocString(L"DriverName"); + hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, NULL, NULL); + SysFreeString(object_name); + if (hr == S_OK) { + String d_name = String(V_BSTR(&dn)); + if (d_name.is_empty()) { + object_name = SysAllocString(L"DriverProviderName"); + hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, NULL, NULL); + SysFreeString(object_name); + if (hr == S_OK) { + driver_name = String(V_BSTR(&dn)); + } + } else { + driver_name = d_name; + } + } else { + object_name = SysAllocString(L"DriverProviderName"); + hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, NULL, NULL); + SysFreeString(object_name); + if (hr == S_OK) { + driver_name = String(V_BSTR(&dn)); + } + } + + VARIANT dv; + VariantInit(&dv); + object_name = SysAllocString(L"DriverVersion"); + hr = pnpSDriverObject[0]->Get(object_name, 0, &dv, NULL, NULL); + SysFreeString(object_name); + if (hr == S_OK) { + driver_version = String(V_BSTR(&dv)); + } + for (ULONG i = 0; i < resultCount; i++) { + SAFE_RELEASE(pnpSDriverObject[i]) + } + } + } + + SAFE_RELEASE(wbemServices) + SAFE_RELEASE(iter) + + Vector info; + info.push_back(driver_name); + info.push_back(driver_version); + + return info; +} + OS::DateTime OS_Windows::get_datetime(bool p_utc) const { SYSTEMTIME systemtime; if (p_utc) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 491de2266f88..7f55f871c36d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -146,6 +146,8 @@ public: virtual String get_distribution_name() const override; virtual String get_version() const override; + virtual Vector get_video_adapter_driver_info() const override; + virtual void initialize_joypads() override {} virtual DateTime get_datetime(bool p_utc) const override;