Ver código fonte

Fetch video adapter driver name and version from OS on Linux/*BSD and Windows

MJacred 2 anos atrás
pai
commit
de768afbdc

+ 6 - 0
core/core_bind.cpp

@@ -330,6 +330,10 @@ String OS::get_version() const {
 	return ::OS::get_singleton()->get_version();
 }
 
+Vector<String> OS::get_video_adapter_driver_info() const {
+	return ::OS::get_singleton()->get_video_adapter_driver_info();
+}
+
 Vector<String> OS::get_cmdline_args() {
 	List<String> cmdline = ::OS::get_singleton()->get_cmdline_args();
 	Vector<String> 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<String>()));
 	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);

+ 2 - 0
core/core_bind.h

@@ -198,6 +198,8 @@ public:
 	Vector<String> get_cmdline_args();
 	Vector<String> get_cmdline_user_args();
 
+	Vector<String> get_video_adapter_driver_info() const;
+
 	String get_locale() const;
 	String get_locale_language() const;
 

+ 2 - 0
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<String> 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;

+ 9 - 0
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.
 			</description>
 		</method>
+		<method name="get_video_adapter_driver_info" qualifiers="const">
+			<return type="PackedStringArray" />
+			<description>
+				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.
+			</description>
+		</method>
 		<method name="has_environment" qualifiers="const">
 			<return type="bool" />
 			<param index="0" name="variable" type="String" />

+ 4 - 0
drivers/unix/os_unix.cpp

@@ -154,6 +154,10 @@ void OS_Unix::finalize_core() {
 	NetSocketPosix::cleanup();
 }
 
+Vector<String> OS_Unix::get_video_adapter_driver_info() const {
+	return Vector<String>();
+}
+
 String OS_Unix::get_stdin_string(bool p_block) {
 	if (p_block) {
 		char buff[1024];

+ 2 - 0
drivers/unix/os_unix.h

@@ -51,6 +51,8 @@ protected:
 public:
 	OS_Unix();
 
+	virtual Vector<String> 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.

+ 199 - 0
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<String> 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<String> lspci_args;
+	lspci_args.push_back("-n");
+	Error err = const_cast<OS_LinuxBSD *>(this)->execute("lspci", lspci_args, &vendor_device_id_mappings);
+	if (err != OK || vendor_device_id_mappings.is_empty()) {
+		return Vector<String>();
+	}
+
+	// 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<String> class_vga_device_candidates;
+	Vector<String> class_display_device_candidates;
+	Vector<String> 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<String> 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<String> 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<String> class_vga_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_vga_device_candidates, kernel_lit, dummys);
+	Vector<String> class_display_device_drivers = OS_LinuxBSD::lspci_get_device_value(class_display_device_candidates, kernel_lit, dummys);
+	Vector<String> 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<String> info;
+	info.push_back(driver_name);
+
+	String modinfo;
+	List<String> modinfo_args;
+	modinfo_args.push_back(driver_name);
+	err = const_cast<OS_LinuxBSD *>(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<String> lines = modinfo.split("\n", false);
+	for (const String &line : lines) {
+		Vector<String> 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<String> OS_LinuxBSD::lspci_device_filter(Vector<String> vendor_device_id_mapping, String class_suffix, String check_column, String whitelist) const {
+	// NOTE: whitelist can be changed to `Vector<String>`, if the need arises.
+	const String sep = ":";
+	Vector<String> devices;
+	for (const String &mapping : vendor_device_id_mapping) {
+		String device;
+		List<String> d_args;
+		d_args.push_back("-d");
+		d_args.push_back(mapping + sep + class_suffix);
+		d_args.push_back("-vmm");
+		Error err = const_cast<OS_LinuxBSD *>(this)->execute("lspci", d_args, &device); // e.g. `lspci -d 10de:13c2:0300 -vmm`
+		if (err != OK) {
+			return Vector<String>();
+		} else if (device.is_empty()) {
+			continue;
+		}
+
+		Vector<String> device_lines = device.split("\n", false);
+		for (const String &line : device_lines) {
+			Vector<String> 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<String> OS_LinuxBSD::lspci_get_device_value(Vector<String> vendor_device_id_mapping, String check_column, String blacklist) const {
+	// NOTE: blacklist can be changed to `Vector<String>`, if the need arises.
+	const String sep = ":";
+	Vector<String> values;
+	for (const String &mapping : vendor_device_id_mapping) {
+		String device;
+		List<String> d_args;
+		d_args.push_back("-d");
+		d_args.push_back(mapping);
+		d_args.push_back("-k");
+		Error err = const_cast<OS_LinuxBSD *>(this)->execute("lspci", d_args, &device); // e.g. `lspci -d 10de:13c2 -k`
+		if (err != OK) {
+			return Vector<String>();
+		} else if (device.is_empty()) {
+			continue;
+		}
+
+		Vector<String> device_lines = device.split("\n", false);
+		for (const String &line : device_lines) {
+			Vector<String> 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;

+ 5 - 0
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<String> lspci_device_filter(Vector<String> vendor_device_id_mapping, String class_suffix, String check_column, String whitelist) const;
+	Vector<String> lspci_get_device_value(Vector<String> 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<String> get_video_adapter_driver_info() const override;
+
 	virtual MainLoop *get_main_loop() const override;
 
 	virtual uint64_t get_embedded_pck_offset() const override;

+ 2 - 0
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",
         ]
     )
 

+ 92 - 0
platform/windows/os_windows.cpp

@@ -53,6 +53,7 @@
 #include <process.h>
 #include <regstr.h>
 #include <shlobj.h>
+#include <wbemcli.h>
 
 extern "C" {
 __declspec(dllexport) DWORD NvOptimusEnablement = 1;
@@ -308,6 +309,97 @@ String OS_Windows::get_version() const {
 	return "";
 }
 
+Vector<String> 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<String>();
+	}
+
+	CoInitialize(nullptr);
+
+	HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator);
+	if (hr != S_OK) {
+		return Vector<String>();
+	}
+
+	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<String>();
+	}
+
+	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<String> 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) {

+ 2 - 0
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<String> get_video_adapter_driver_info() const override;
+
 	virtual void initialize_joypads() override {}
 
 	virtual DateTime get_datetime(bool p_utc) const override;