// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gpu/config/gpu_info_collector.h" // This has to be included before windows.h. #include "third_party/re2/re2/re2.h" #include #include #include #include #include #include #include "base/command_line.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/scoped_native_library.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread.h" #include "base/threading/worker_pool.h" #include "base/trace_event/trace_event.h" #include "base/win/registry.h" #include "base/win/scoped_com_initializer.h" #include "base/win/scoped_comptr.h" #include "base/win/windows_version.h" #include "third_party/libxml/chromium/libxml_utils.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_surface_egl.h" namespace gpu { namespace { // This must be kept in sync with histograms.xml. enum DisplayLinkInstallationStatus { DISPLAY_LINK_NOT_INSTALLED, DISPLAY_LINK_7_1_OR_EARLIER, DISPLAY_LINK_7_2_OR_LATER, DISPLAY_LINK_INSTALLATION_STATUS_MAX }; // Returns the display link driver version or an invalid version if it is // not installed. Version DisplayLinkVersion() { base::win::RegKey key; if (key.Open(HKEY_LOCAL_MACHINE, L"SOFTWARE", KEY_READ | KEY_WOW64_64KEY)) return Version(); if (key.OpenKey(L"DisplayLink", KEY_READ | KEY_WOW64_64KEY)) return Version(); if (key.OpenKey(L"Core", KEY_READ | KEY_WOW64_64KEY)) return Version(); base::string16 version; if (key.ReadValue(L"Version", &version)) return Version(); return Version(base::UTF16ToASCII(version)); } // Returns whether Lenovo dCute is installed. bool IsLenovoDCuteInstalled() { base::win::RegKey key; if (key.Open(HKEY_LOCAL_MACHINE, L"SOFTWARE", KEY_READ | KEY_WOW64_64KEY)) return false; if (key.OpenKey(L"Lenovo", KEY_READ | KEY_WOW64_64KEY)) return false; if (key.OpenKey(L"Lenovo dCute", KEY_READ | KEY_WOW64_64KEY)) return false; return true; } void DeviceIDToVendorAndDevice(const std::wstring& id, uint32* vendor_id, uint32* device_id) { *vendor_id = 0; *device_id = 0; if (id.length() < 21) return; base::string16 vendor_id_string = id.substr(8, 4); base::string16 device_id_string = id.substr(17, 4); int vendor = 0; int device = 0; base::HexStringToInt(base::UTF16ToASCII(vendor_id_string), &vendor); base::HexStringToInt(base::UTF16ToASCII(device_id_string), &device); *vendor_id = vendor; *device_id = device; } } // namespace anonymous #if defined(GOOGLE_CHROME_BUILD) && defined(OFFICIAL_BUILD) // This function has a real implementation for official builds that can // be found in src/third_party/amd. void GetAMDVideocardInfo(GPUInfo* gpu_info); #else void GetAMDVideocardInfo(GPUInfo* gpu_info) { DCHECK(gpu_info); return; } #endif CollectInfoResult CollectDriverInfoD3D(const std::wstring& device_id, GPUInfo* gpu_info) { TRACE_EVENT0("gpu", "CollectDriverInfoD3D"); // Display adapter class GUID from // https://msdn.microsoft.com/en-us/library/windows/hardware/ff553426%28v=vs.85%29.aspx GUID display_class = {0x4d36e968, 0xe325, 0x11ce, {0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}}; // create device info for the display device HDEVINFO device_info; if (base::win::GetVersion() <= base::win::VERSION_XP) { // Collection of information on all adapters is much slower on XP (almost // 100ms), and not very useful (as it's not going to use the GPU anyway), so // just collect information on the current device. http://crbug.com/456178 device_info = SetupDiGetClassDevsW(NULL, device_id.c_str(), NULL, DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES); } else { device_info = SetupDiGetClassDevsW(&display_class, NULL, NULL, DIGCF_PRESENT); } if (device_info == INVALID_HANDLE_VALUE) { LOG(ERROR) << "Creating device info failed"; return kCollectInfoNonFatalFailure; } struct GPUDriver { GPUInfo::GPUDevice device; std::string driver_vendor; std::string driver_version; std::string driver_date; }; std::vector drivers; int primary_device = -1; bool found_amd = false; bool found_intel = false; DWORD index = 0; SP_DEVINFO_DATA device_info_data; device_info_data.cbSize = sizeof(device_info_data); while (SetupDiEnumDeviceInfo(device_info, index++, &device_info_data)) { WCHAR value[255]; if (SetupDiGetDeviceRegistryPropertyW(device_info, &device_info_data, SPDRP_DRIVER, NULL, reinterpret_cast(value), sizeof(value), NULL)) { HKEY key; std::wstring driver_key = L"System\\CurrentControlSet\\Control\\Class\\"; driver_key += value; LONG result = RegOpenKeyExW( HKEY_LOCAL_MACHINE, driver_key.c_str(), 0, KEY_QUERY_VALUE, &key); if (result == ERROR_SUCCESS) { DWORD dwcb_data = sizeof(value); std::string driver_version; result = RegQueryValueExW( key, L"DriverVersion", NULL, NULL, reinterpret_cast(value), &dwcb_data); if (result == ERROR_SUCCESS) driver_version = base::UTF16ToASCII(std::wstring(value)); std::string driver_date; dwcb_data = sizeof(value); result = RegQueryValueExW( key, L"DriverDate", NULL, NULL, reinterpret_cast(value), &dwcb_data); if (result == ERROR_SUCCESS) driver_date = base::UTF16ToASCII(std::wstring(value)); std::string driver_vendor; dwcb_data = sizeof(value); result = RegQueryValueExW( key, L"ProviderName", NULL, NULL, reinterpret_cast(value), &dwcb_data); if (result == ERROR_SUCCESS) driver_vendor = base::UTF16ToASCII(std::wstring(value)); wchar_t new_device_id[MAX_DEVICE_ID_LEN]; CONFIGRET status = CM_Get_Device_ID( device_info_data.DevInst, new_device_id, MAX_DEVICE_ID_LEN, 0); if (status == CR_SUCCESS) { GPUDriver driver; driver.driver_vendor = driver_vendor; driver.driver_version = driver_version; driver.driver_date = driver_date; std::wstring id = new_device_id; if (id.compare(0, device_id.size(), device_id) == 0) primary_device = drivers.size(); uint32 vendor_id = 0, device_id = 0; DeviceIDToVendorAndDevice(id, &vendor_id, &device_id); driver.device.vendor_id = vendor_id; driver.device.device_id = device_id; drivers.push_back(driver); if (vendor_id == 0x8086) found_intel = true; if (vendor_id == 0x1002) found_amd = true; } RegCloseKey(key); } } } SetupDiDestroyDeviceInfoList(device_info); bool found = false; if (found_amd && found_intel) { // AMD Switchable system found. for (const auto& driver : drivers) { if (driver.device.vendor_id == 0x8086) { gpu_info->gpu = driver.device; } if (driver.device.vendor_id == 0x1002) { gpu_info->driver_vendor = driver.driver_vendor; gpu_info->driver_version = driver.driver_version; gpu_info->driver_date = driver.driver_date; } } GetAMDVideocardInfo(gpu_info); if (!gpu_info->amd_switchable) { // Some machines aren't properly detected as AMD switchable, but count // them anyway. gpu_info->amd_switchable = true; for (const auto& driver : drivers) { if (driver.device.vendor_id == 0x1002) { gpu_info->gpu = driver.device; } else { gpu_info->secondary_gpus.push_back(driver.device); } } } found = true; } else { for (size_t i = 0; i < drivers.size(); ++i) { const GPUDriver& driver = drivers[i]; if (static_cast(i) == primary_device) { found = true; gpu_info->gpu = driver.device; gpu_info->driver_vendor = driver.driver_vendor; gpu_info->driver_version = driver.driver_version; gpu_info->driver_date = driver.driver_date; } else { gpu_info->secondary_gpus.push_back(driver.device); } } } return found ? kCollectInfoSuccess : kCollectInfoNonFatalFailure; } CollectInfoResult CollectContextGraphicsInfo(GPUInfo* gpu_info) { TRACE_EVENT0("gpu", "CollectGraphicsInfo"); DCHECK(gpu_info); if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseGL)) { std::string requested_implementation_name = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kUseGL); if (requested_implementation_name == "swiftshader") { gpu_info->software_rendering = true; gpu_info->context_info_state = kCollectInfoNonFatalFailure; return kCollectInfoNonFatalFailure; } } CollectInfoResult result = CollectGraphicsInfoGL(gpu_info); if (result != kCollectInfoSuccess) { gpu_info->context_info_state = result; return result; } // ANGLE's renderer strings are of the form: // ANGLE ( Direct3D vs_x_x ps_x_x) std::string direct3d_version; int vertex_shader_major_version = 0; int vertex_shader_minor_version = 0; int pixel_shader_major_version = 0; int pixel_shader_minor_version = 0; gpu_info->adapter_luid = 0; if (RE2::FullMatch(gpu_info->gl_renderer, "ANGLE \\(.*\\)") && RE2::PartialMatch(gpu_info->gl_renderer, " Direct3D(\\w+)", &direct3d_version) && RE2::PartialMatch(gpu_info->gl_renderer, " vs_(\\d+)_(\\d+)", &vertex_shader_major_version, &vertex_shader_minor_version) && RE2::PartialMatch(gpu_info->gl_renderer, " ps_(\\d+)_(\\d+)", &pixel_shader_major_version, &pixel_shader_minor_version)) { gpu_info->can_lose_context = direct3d_version == "9"; gpu_info->vertex_shader_version = base::StringPrintf("%d.%d", vertex_shader_major_version, vertex_shader_minor_version); gpu_info->pixel_shader_version = base::StringPrintf("%d.%d", pixel_shader_major_version, pixel_shader_minor_version); // ANGLE's EGL vendor strings are of the form: // Google, Inc. (adapter LUID: 0123456789ABCDEF) // The LUID is optional and identifies the GPU adapter ANGLE is using. const char* egl_vendor = eglQueryString( gfx::GLSurfaceEGL::GetHardwareDisplay(), EGL_VENDOR); RE2::PartialMatch(egl_vendor, " \\(adapter LUID: ([0-9A-Fa-f]{16})\\)", RE2::Hex(&gpu_info->adapter_luid)); // DirectX diagnostics are collected asynchronously because it takes a // couple of seconds. } else { gpu_info->dx_diagnostics_info_state = kCollectInfoNonFatalFailure; } gpu_info->context_info_state = kCollectInfoSuccess; return kCollectInfoSuccess; } CollectInfoResult CollectGpuID(uint32* vendor_id, uint32* device_id) { DCHECK(vendor_id && device_id); *vendor_id = 0; *device_id = 0; // Taken from http://developer.nvidia.com/object/device_ids.html DISPLAY_DEVICE dd; dd.cb = sizeof(DISPLAY_DEVICE); std::wstring id; for (int i = 0; EnumDisplayDevices(NULL, i, &dd, 0); ++i) { if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) { id = dd.DeviceID; break; } } if (id.length() > 20) { DeviceIDToVendorAndDevice(id, vendor_id, device_id); if (*vendor_id != 0 && *device_id != 0) return kCollectInfoSuccess; } return kCollectInfoNonFatalFailure; } CollectInfoResult CollectBasicGraphicsInfo(GPUInfo* gpu_info) { TRACE_EVENT0("gpu", "CollectPreliminaryGraphicsInfo"); DCHECK(gpu_info); // nvd3d9wrap.dll is loaded into all processes when Optimus is enabled. HMODULE nvd3d9wrap = GetModuleHandleW(L"nvd3d9wrap.dll"); gpu_info->optimus = nvd3d9wrap != NULL; gpu_info->lenovo_dcute = IsLenovoDCuteInstalled(); gpu_info->display_link_version = DisplayLinkVersion(); if (!gpu_info->display_link_version .IsValid()) { UMA_HISTOGRAM_ENUMERATION("GPU.DisplayLinkInstallationStatus", DISPLAY_LINK_NOT_INSTALLED, DISPLAY_LINK_INSTALLATION_STATUS_MAX); } else if (gpu_info->display_link_version.IsOlderThan("7.2")) { UMA_HISTOGRAM_ENUMERATION("GPU.DisplayLinkInstallationStatus", DISPLAY_LINK_7_1_OR_EARLIER, DISPLAY_LINK_INSTALLATION_STATUS_MAX); } else { UMA_HISTOGRAM_ENUMERATION("GPU.DisplayLinkInstallationStatus", DISPLAY_LINK_7_2_OR_LATER, DISPLAY_LINK_INSTALLATION_STATUS_MAX); } // Taken from http://developer.nvidia.com/object/device_ids.html DISPLAY_DEVICE dd; dd.cb = sizeof(DISPLAY_DEVICE); std::wstring id; for (int i = 0; EnumDisplayDevices(NULL, i, &dd, 0); ++i) { if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) { id = dd.DeviceID; break; } } if (id.length() <= 20) { gpu_info->basic_info_state = kCollectInfoNonFatalFailure; return kCollectInfoNonFatalFailure; } DeviceIDToVendorAndDevice(id, &gpu_info->gpu.vendor_id, &gpu_info->gpu.device_id); // TODO(zmo): we only need to call CollectDriverInfoD3D() if we use ANGLE. if (!CollectDriverInfoD3D(id, gpu_info)) { gpu_info->basic_info_state = kCollectInfoNonFatalFailure; return kCollectInfoNonFatalFailure; } gpu_info->basic_info_state = kCollectInfoSuccess; return kCollectInfoSuccess; } CollectInfoResult CollectDriverInfoGL(GPUInfo* gpu_info) { TRACE_EVENT0("gpu", "CollectDriverInfoGL"); if (!gpu_info->driver_version.empty()) return kCollectInfoSuccess; bool parsed = RE2::PartialMatch( gpu_info->gl_version, "([\\d\\.]+)$", &gpu_info->driver_version); return parsed ? kCollectInfoSuccess : kCollectInfoNonFatalFailure; } void MergeGPUInfo(GPUInfo* basic_gpu_info, const GPUInfo& context_gpu_info) { DCHECK(basic_gpu_info); if (context_gpu_info.software_rendering) { basic_gpu_info->software_rendering = true; return; } // Track D3D Shader Model (if available) const std::string& shader_version = context_gpu_info.vertex_shader_version; // Only gather if this is the first time we're seeing // a non-empty shader version string. if (!shader_version.empty() && basic_gpu_info->vertex_shader_version.empty()) { // Note: do not reorder, used by UMA_HISTOGRAM below enum ShaderModel { SHADER_MODEL_UNKNOWN, SHADER_MODEL_2_0, SHADER_MODEL_3_0, SHADER_MODEL_4_0, SHADER_MODEL_4_1, SHADER_MODEL_5_0, NUM_SHADER_MODELS }; ShaderModel shader_model = SHADER_MODEL_UNKNOWN; if (shader_version == "5.0") { shader_model = SHADER_MODEL_5_0; } else if (shader_version == "4.1") { shader_model = SHADER_MODEL_4_1; } else if (shader_version == "4.0") { shader_model = SHADER_MODEL_4_0; } else if (shader_version == "3.0") { shader_model = SHADER_MODEL_3_0; } else if (shader_version == "2.0") { shader_model = SHADER_MODEL_2_0; } UMA_HISTOGRAM_ENUMERATION("GPU.D3DShaderModel", shader_model, NUM_SHADER_MODELS); } MergeGPUInfoGL(basic_gpu_info, context_gpu_info); basic_gpu_info->dx_diagnostics_info_state = context_gpu_info.dx_diagnostics_info_state; basic_gpu_info->dx_diagnostics = context_gpu_info.dx_diagnostics; } } // namespace gpu