// 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 "base/command_line.h" #include "base/debug/trace_event.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/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 }; float ReadXMLFloatValue(XmlReader* reader) { std::string score_string; if (!reader->ReadElementContent(&score_string)) return 0.0; double score; if (!base::StringToDouble(score_string, &score)) return 0.0; return static_cast(score); } GpuPerformanceStats RetrieveGpuPerformanceStats() { TRACE_EVENT0("gpu", "RetrieveGpuPerformanceStats"); // If the user re-runs the assessment without restarting, the COM API // returns WINSAT_ASSESSMENT_STATE_NOT_AVAILABLE. Because of that and // http://crbug.com/124325, read the assessment result files directly. GpuPerformanceStats stats; // Get path to WinSAT results files. wchar_t winsat_results_path[MAX_PATH]; DWORD size = ExpandEnvironmentStrings( L"%WinDir%\\Performance\\WinSAT\\DataStore\\", winsat_results_path, MAX_PATH); if (size == 0 || size > MAX_PATH) { LOG(ERROR) << "The path to the WinSAT results is too long: " << size << " chars."; return stats; } // Find most recent formal assessment results. base::FileEnumerator file_enumerator( base::FilePath(winsat_results_path), false, // not recursive base::FileEnumerator::FILES, FILE_PATH_LITERAL("* * Formal.Assessment (*).WinSAT.xml")); base::FilePath current_results; for (base::FilePath results = file_enumerator.Next(); !results.empty(); results = file_enumerator.Next()) { // The filenames start with the date and time as yyyy-mm-dd hh.mm.ss.xxx, // so the greatest file lexicographically is also the most recent file. if (base::FilePath::CompareLessIgnoreCase(current_results.value(), results.value())) current_results = results; } std::string current_results_string = current_results.MaybeAsASCII(); if (current_results_string.empty()) return stats; // Get relevant scores from results file. XML schema at: // http://msdn.microsoft.com/en-us/library/windows/desktop/aa969210.aspx XmlReader reader; if (!reader.LoadFile(current_results_string)) { LOG(ERROR) << "Could not open WinSAT results file."; return stats; } // Descend into root element. if (!reader.SkipToElement() || !reader.Read()) { LOG(ERROR) << "Could not read WinSAT results file."; return stats; } // Search for element containing the results. do { if (reader.NodeName() == "WinSPR") break; } while (reader.Next()); // Descend into element. if (!reader.Read()) { LOG(ERROR) << "Could not find WinSPR element in results file."; return stats; } // Read scores. for (int depth = reader.Depth(); reader.Depth() == depth; reader.Next()) { std::string node_name = reader.NodeName(); if (node_name == "SystemScore") stats.overall = ReadXMLFloatValue(&reader); else if (node_name == "GraphicsScore") stats.graphics = ReadXMLFloatValue(&reader); else if (node_name == "GamingScore") stats.gaming = ReadXMLFloatValue(&reader); } if (stats.overall == 0.0) LOG(ERROR) << "Could not read overall score from assessment results."; if (stats.graphics == 0.0) LOG(ERROR) << "Could not read graphics score from assessment results."; if (stats.gaming == 0.0) LOG(ERROR) << "Could not read gaming score from assessment results."; return stats; } GpuPerformanceStats RetrieveGpuPerformanceStatsWithHistograms() { base::TimeTicks start_time = base::TimeTicks::Now(); GpuPerformanceStats stats = RetrieveGpuPerformanceStats(); UMA_HISTOGRAM_TIMES("GPU.WinSAT.ReadResultsFileTime", base::TimeTicks::Now() - start_time); UMA_HISTOGRAM_CUSTOM_COUNTS( "GPU.WinSAT.OverallScore2", static_cast(stats.overall * 10), 10, 200, 50); UMA_HISTOGRAM_CUSTOM_COUNTS( "GPU.WinSAT.GraphicsScore2", static_cast(stats.graphics * 10), 10, 200, 50); UMA_HISTOGRAM_CUSTOM_COUNTS( "GPU.WinSAT.GamingScore2", static_cast(stats.gaming * 10), 10, 200, 50); UMA_HISTOGRAM_BOOLEAN( "GPU.WinSAT.HasResults", stats.overall != 0.0 && stats.graphics != 0.0 && stats.gaming != 0.0); return stats; } // 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; } // Determines whether D3D11 won't work, either because it is not supported on // the machine or because it is known it is likely to crash. bool D3D11ShouldWork(const GPUInfo& gpu_info) { // TODO(apatrick): This is a temporary change to see what impact disabling // D3D11 stats collection has on Canary. #if 1 return false; #else // Windows XP never supports D3D11. It seems to be less stable that D3D9 on // Vista. if (base::win::GetVersion() <= base::win::VERSION_VISTA) return false; // http://crbug.com/175525. if (gpu_info.display_link_version.IsValid()) return false; return true; #endif } // Collects information about the level of D3D11 support and records it in // the UMA stats. Records no stats when D3D11 in not supported at all. void CollectD3D11SupportOnWorkerThread() { TRACE_EVENT0("gpu", "CollectD3D11Support"); typedef HRESULT (WINAPI *D3D11CreateDeviceFunc)( IDXGIAdapter* adapter, D3D_DRIVER_TYPE driver_type, HMODULE software, UINT flags, const D3D_FEATURE_LEVEL* feature_levels, UINT num_feature_levels, UINT sdk_version, ID3D11Device** device, D3D_FEATURE_LEVEL* feature_level, ID3D11DeviceContext** immediate_context); // This enumeration must be kept in sync with histograms.xml. Do not reorder // the members; always add to the end. enum FeatureLevel { FEATURE_LEVEL_UNKNOWN, FEATURE_LEVEL_NO_D3D11_DLL, FEATURE_LEVEL_NO_CREATE_DEVICE_ENTRY_POINT, FEATURE_LEVEL_DEVICE_CREATION_FAILED, FEATURE_LEVEL_9_1, FEATURE_LEVEL_9_2, FEATURE_LEVEL_9_3, FEATURE_LEVEL_10_0, FEATURE_LEVEL_10_1, FEATURE_LEVEL_11_0, NUM_FEATURE_LEVELS }; FeatureLevel feature_level = FEATURE_LEVEL_UNKNOWN; UINT bgra_support = 0; // This module is leaked in case it is hooked by third party software. base::NativeLibrary d3d11_module = base::LoadNativeLibrary( base::FilePath(L"d3d11.dll"), NULL); if (!d3d11_module) { feature_level = FEATURE_LEVEL_NO_D3D11_DLL; } else { D3D11CreateDeviceFunc create_func = reinterpret_cast( base::GetFunctionPointerFromNativeLibrary(d3d11_module, "D3D11CreateDevice")); if (!create_func) { feature_level = FEATURE_LEVEL_NO_CREATE_DEVICE_ENTRY_POINT; } else { static const D3D_FEATURE_LEVEL d3d_feature_levels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 }; base::win::ScopedComPtr device; D3D_FEATURE_LEVEL d3d_feature_level; base::win::ScopedComPtr device_context; HRESULT hr = create_func(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, d3d_feature_levels, arraysize(d3d_feature_levels), D3D11_SDK_VERSION, device.Receive(), &d3d_feature_level, device_context.Receive()); if (FAILED(hr)) { feature_level = FEATURE_LEVEL_DEVICE_CREATION_FAILED; } else { switch (d3d_feature_level) { case D3D_FEATURE_LEVEL_11_0: feature_level = FEATURE_LEVEL_11_0; break; case D3D_FEATURE_LEVEL_10_1: feature_level = FEATURE_LEVEL_10_1; break; case D3D_FEATURE_LEVEL_10_0: feature_level = FEATURE_LEVEL_10_0; break; case D3D_FEATURE_LEVEL_9_3: feature_level = FEATURE_LEVEL_9_3; break; case D3D_FEATURE_LEVEL_9_2: feature_level = FEATURE_LEVEL_9_2; break; case D3D_FEATURE_LEVEL_9_1: feature_level = FEATURE_LEVEL_9_1; break; default: NOTREACHED(); break; } hr = device->CheckFormatSupport(DXGI_FORMAT_B8G8R8A8_UNORM, &bgra_support); DCHECK(SUCCEEDED(hr)); } } } UMA_HISTOGRAM_ENUMERATION("GPU.D3D11_FeatureLevel", feature_level, NUM_FEATURE_LEVELS); // ANGLE requires at least feature level 10.0. Do not record any further // stats if ANGLE would not work anyway. if (feature_level < FEATURE_LEVEL_10_0) return; UMA_HISTOGRAM_BOOLEAN( "GPU.D3D11_B8G8R8A8_Texture2DSupport", (bgra_support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0); UMA_HISTOGRAM_BOOLEAN( "GPU.D3D11_B8G8R8A8_RenderTargetSupport", (bgra_support & D3D11_FORMAT_SUPPORT_RENDER_TARGET) != 0); } // Collects information about the level of D3D11 support and records it in // the UMA stats. Records no stats when D3D11 in not supported at all. void CollectD3D11Support() { // D3D11 takes about 50ms to initialize so do this on a worker thread. base::WorkerPool::PostTask( FROM_HERE, base::Bind(CollectD3D11SupportOnWorkerThread), false); } } // 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"); // create device info for the display device HDEVINFO device_info = SetupDiGetClassDevsW( NULL, device_id.c_str(), NULL, DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES); if (device_info == INVALID_HANDLE_VALUE) { LOG(ERROR) << "Creating device info failed"; return kCollectInfoNonFatalFailure; } DWORD index = 0; bool found = false; 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)); if (driver_vendor == "Advanced Micro Devices, Inc." || driver_vendor == "ATI Technologies Inc.") { // We are conservative and assume that in the absence of a clear // signal the videocard is assumed to be switchable. Additionally, // some switchable systems with Intel GPUs aren't correctly // detected, so always count them. GetAMDVideocardInfo(gpu_info); if (!gpu_info->amd_switchable && gpu_info->gpu.vendor_id == 0x8086) { gpu_info->amd_switchable = true; gpu_info->secondary_gpus.push_back(gpu_info->gpu); gpu_info->gpu.vendor_id = 0x1002; gpu_info->gpu.device_id = 0; // Unknown discrete AMD GPU. } } } gpu_info->driver_vendor = driver_vendor; gpu_info->driver_version = driver_version; gpu_info->driver_date = driver_date; found = true; RegCloseKey(key); break; } } } SetupDiDestroyDeviceInfoList(device_info); return found ? kCollectInfoSuccess : kCollectInfoNonFatalFailure; } CollectInfoResult CollectContextGraphicsInfo(GPUInfo* gpu_info) { TRACE_EVENT0("gpu", "CollectGraphicsInfo"); DCHECK(gpu_info); if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseGL)) { std::string requested_implementation_name = 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) { int vendor = 0, device = 0; std::wstring vendor_string = id.substr(8, 4); std::wstring device_string = id.substr(17, 4); base::HexStringToInt(base::UTF16ToASCII(vendor_string), &vendor); base::HexStringToInt(base::UTF16ToASCII(device_string), &device); *vendor_id = vendor; *device_id = device; if (*vendor_id != 0 && *device_id != 0) return kCollectInfoSuccess; } return kCollectInfoNonFatalFailure; } CollectInfoResult CollectBasicGraphicsInfo(GPUInfo* gpu_info) { TRACE_EVENT0("gpu", "CollectPreliminaryGraphicsInfo"); DCHECK(gpu_info); gpu_info->performance_stats = RetrieveGpuPerformanceStatsWithHistograms(); // 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; } int vendor_id = 0, device_id = 0; base::string16 vendor_id_string = id.substr(8, 4); base::string16 device_id_string = id.substr(17, 4); base::HexStringToInt(base::UTF16ToASCII(vendor_id_string), &vendor_id); base::HexStringToInt(base::UTF16ToASCII(device_id_string), &device_id); gpu_info->gpu.vendor_id = vendor_id; gpu_info->gpu.device_id = 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; } // Collect basic information about supported D3D11 features. Delay for 45 // seconds so as not to regress performance tests. if (D3D11ShouldWork(*gpu_info)) { // This is on a field trial so we can turn it off easily if it blows up // again in stable channel. scoped_refptr trial( base::FieldTrialList::FactoryGetFieldTrial( "D3D11Experiment", 100, "Disabled", 2015, 7, 8, base::FieldTrial::SESSION_RANDOMIZED, NULL)); const int enabled_group = trial->AppendGroup("Enabled", 0); if (trial->group() == enabled_group) { base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&CollectD3D11Support), base::TimeDelta::FromSeconds(45)); } } 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 = context_gpu_info.dx_diagnostics; } } // namespace gpu