| // 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 <windows.h> |
| #include <cfgmgr32.h> |
| #include <d3d9.h> |
| #include <d3d11.h> |
| #include <dxgi.h> |
| #include <setupapi.h> |
| |
| #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 |
| }; |
| |
| 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<float>(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 <WinSAT> root element. |
| if (!reader.SkipToElement() || !reader.Read()) { |
| LOG(ERROR) << "Could not read WinSAT results file."; |
| return stats; |
| } |
| |
| // Search for <WinSPR> element containing the results. |
| do { |
| if (reader.NodeName() == "WinSPR") |
| break; |
| } while (reader.Next()); |
| // Descend into <WinSPR> 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<base::HistogramBase::Sample>(stats.overall * 10), 10, 200, |
| 50); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "GPU.WinSAT.GraphicsScore2", |
| static_cast<base::HistogramBase::Sample>(stats.graphics * 10), 10, 200, |
| 50); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "GPU.WinSAT.GamingScore2", |
| static_cast<base::HistogramBase::Sample>(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<D3D11CreateDeviceFunc>( |
| 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<ID3D11Device> device; |
| D3D_FEATURE_LEVEL d3d_feature_level; |
| base::win::ScopedComPtr<ID3D11DeviceContext> 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); |
| } |
| |
| 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<GPUDriver> 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<PBYTE>(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<LPBYTE>(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<LPBYTE>(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<LPBYTE>(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<int>(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 (<adapter_identifier> Direct3D<version> 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); |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| // 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<base::FieldTrial> 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 |