1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/gpu/gpu_info_collector.h"
11 #include "base/command_line.h"
12 #include "base/debug/trace_event.h"
13 #include "base/file_path.h"
14 #include "base/file_util.h"
15 #include "base/logging.h"
16 #include "base/metrics/histogram.h"
17 #include "base/scoped_native_library.h"
18 #include "base/string_number_conversions.h"
19 #include "base/string_util.h"
20 #include "base/win/scoped_com_initializer.h"
21 #include "base/win/scoped_comptr.h"
22 #include "third_party/libxml/chromium/libxml_utils.h"
23 #include "ui/gl/gl_implementation.h"
24 #include "ui/gl/gl_surface_egl.h"
26 // ANGLE seems to require that main.h be included before any other ANGLE header.
27 #include "libEGL/Display.h"
28 #include "libEGL/main.h"
32 // The version number stores the major and minor version in the least 16 bits;
33 // for example, 2.5 is 0x00000205.
34 // Returned string is in the format of "major.minor".
35 std::string
VersionNumberToString(uint32 version_number
) {
36 int hi
= (version_number
>> 8) & 0xff;
37 int low
= version_number
& 0xff;
38 return base::IntToString(hi
) + "." + base::IntToString(low
);
41 float ReadXMLFloatValue(XmlReader
* reader
) {
42 std::string score_string
;
43 if (!reader
->ReadElementContent(&score_string
))
47 if (!base::StringToDouble(score_string
, &score
))
50 return static_cast<float>(score
);
53 content::GpuPerformanceStats
RetrieveGpuPerformanceStats() {
54 TRACE_EVENT0("gpu", "RetrieveGpuPerformanceStats");
56 // If the user re-runs the assessment without restarting, the COM API
57 // returns WINSAT_ASSESSMENT_STATE_NOT_AVAILABLE. Because of that and
58 // http://crbug.com/124325, read the assessment result files directly.
59 content::GpuPerformanceStats stats
;
61 // Get path to WinSAT results files.
62 wchar_t winsat_results_path
[MAX_PATH
];
63 DWORD size
= ExpandEnvironmentStrings(
64 L
"%WinDir%\\Performance\\WinSAT\\DataStore\\",
65 winsat_results_path
, MAX_PATH
);
66 if (size
== 0 || size
> MAX_PATH
) {
67 LOG(ERROR
) << "The path to the WinSAT results is too long: "
72 // Find most recent formal assessment results.
73 file_util::FileEnumerator
file_enumerator(
74 FilePath(winsat_results_path
),
75 false, // not recursive
76 file_util::FileEnumerator::FILES
,
77 FILE_PATH_LITERAL("* * Formal.Assessment (*).WinSAT.xml"));
79 FilePath current_results
;
80 for (FilePath results
= file_enumerator
.Next(); !results
.empty();
81 results
= file_enumerator
.Next()) {
82 // The filenames start with the date and time as yyyy-mm-dd hh.mm.ss.xxx,
83 // so the greatest file lexicographically is also the most recent file.
84 if (FilePath::CompareLessIgnoreCase(current_results
.value(),
86 current_results
= results
;
89 std::string current_results_string
= current_results
.MaybeAsASCII();
90 if (current_results_string
.empty()) {
91 LOG(ERROR
) << "Can't retrieve a valid WinSAT assessment.";
95 // Get relevant scores from results file. XML schema at:
96 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa969210.aspx
98 if (!reader
.LoadFile(current_results_string
)) {
99 LOG(ERROR
) << "Could not open WinSAT results file.";
102 // Descend into <WinSAT> root element.
103 if (!reader
.SkipToElement() || !reader
.Read()) {
104 LOG(ERROR
) << "Could not read WinSAT results file.";
108 // Search for <WinSPR> element containing the results.
110 if (reader
.NodeName() == "WinSPR")
112 } while (reader
.Next());
113 // Descend into <WinSPR> element.
114 if (!reader
.Read()) {
115 LOG(ERROR
) << "Could not find WinSPR element in results file.";
120 for (int depth
= reader
.Depth(); reader
.Depth() == depth
; reader
.Next()) {
121 std::string node_name
= reader
.NodeName();
122 if (node_name
== "SystemScore")
123 stats
.overall
= ReadXMLFloatValue(&reader
);
124 else if (node_name
== "GraphicsScore")
125 stats
.graphics
= ReadXMLFloatValue(&reader
);
126 else if (node_name
== "GamingScore")
127 stats
.gaming
= ReadXMLFloatValue(&reader
);
130 if (stats
.overall
== 0.0)
131 LOG(ERROR
) << "Could not read overall score from assessment results.";
132 if (stats
.graphics
== 0.0)
133 LOG(ERROR
) << "Could not read graphics score from assessment results.";
134 if (stats
.gaming
== 0.0)
135 LOG(ERROR
) << "Could not read gaming score from assessment results.";
140 content::GpuPerformanceStats
RetrieveGpuPerformanceStatsWithHistograms() {
141 base::TimeTicks start_time
= base::TimeTicks::Now();
143 content::GpuPerformanceStats stats
= RetrieveGpuPerformanceStats();
145 UMA_HISTOGRAM_TIMES("GPU.WinSAT.ReadResultsFileTime",
146 base::TimeTicks::Now() - start_time
);
147 UMA_HISTOGRAM_CUSTOM_COUNTS("GPU.WinSAT.OverallScore2",
148 stats
.overall
* 10, 10, 200, 50);
149 UMA_HISTOGRAM_CUSTOM_COUNTS("GPU.WinSAT.GraphicsScore2",
150 stats
.graphics
* 10, 10, 200, 50);
151 UMA_HISTOGRAM_CUSTOM_COUNTS("GPU.WinSAT.GamingScore2",
152 stats
.gaming
* 10, 10, 200, 50);
153 UMA_HISTOGRAM_BOOLEAN(
154 "GPU.WinSAT.HasResults",
155 stats
.overall
!= 0.0 && stats
.graphics
!= 0.0 && stats
.gaming
!= 0.0);
160 } // namespace anonymous
162 namespace gpu_info_collector
{
164 #if !defined(GOOGLE_CHROME_BUILD)
165 AMDVideoCardType
GetAMDVideocardType() {
169 // This function has a real implementation for official builds that can
170 // be found in src/third_party/amd.
171 AMDVideoCardType
GetAMDVideocardType();
174 bool CollectGraphicsInfo(content::GPUInfo
* gpu_info
) {
175 TRACE_EVENT0("gpu", "CollectGraphicsInfo");
179 gpu_info
->performance_stats
= RetrieveGpuPerformanceStats();
181 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseGL
)) {
182 std::string requested_implementation_name
=
183 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switches::kUseGL
);
184 if (requested_implementation_name
== "swiftshader") {
185 gpu_info
->software_rendering
= true;
190 if (gfx::GetGLImplementation() != gfx::kGLImplementationEGLGLES2
) {
191 gpu_info
->finalized
= true;
192 return CollectGraphicsInfoGL(gpu_info
);
195 // TODO(zmo): the following code only works if running on top of ANGLE.
196 // Need to handle the case when running on top of real EGL/GLES2 drivers.
198 egl::Display
* display
= static_cast<egl::Display
*>(
199 gfx::GLSurfaceEGL::GetHardwareDisplay());
201 LOG(ERROR
) << "gfx::BaseEGLContext::GetDisplay() failed";
205 IDirect3DDevice9
* device
= display
->getDevice();
207 LOG(ERROR
) << "display->getDevice() failed";
211 base::win::ScopedComPtr
<IDirect3D9
> d3d
;
212 if (FAILED(device
->GetDirect3D(d3d
.Receive()))) {
213 LOG(ERROR
) << "device->GetDirect3D(&d3d) failed";
217 if (!CollectGraphicsInfoD3D(d3d
, gpu_info
))
220 // DirectX diagnostics are collected asynchronously because it takes a
221 // couple of seconds. Do not mark gpu_info as complete until that is done.
225 bool CollectPreliminaryGraphicsInfo(content::GPUInfo
* gpu_info
) {
226 TRACE_EVENT0("gpu", "CollectPreliminaryGraphicsInfo");
231 if (!CollectVideoCardInfo(gpu_info
))
234 gpu_info
->performance_stats
= RetrieveGpuPerformanceStatsWithHistograms();
239 bool CollectGraphicsInfoD3D(IDirect3D9
* d3d
, content::GPUInfo
* gpu_info
) {
240 TRACE_EVENT0("gpu", "CollectGraphicsInfoD3D");
245 bool succeed
= CollectVideoCardInfo(gpu_info
);
247 // Get version information
249 if (d3d
->GetDeviceCaps(D3DADAPTER_DEFAULT
,
251 &d3d_caps
) == D3D_OK
) {
252 gpu_info
->pixel_shader_version
=
253 VersionNumberToString(d3d_caps
.PixelShaderVersion
);
254 gpu_info
->vertex_shader_version
=
255 VersionNumberToString(d3d_caps
.VertexShaderVersion
);
257 LOG(ERROR
) << "d3d->GetDeviceCaps() failed";
261 // Get can_lose_context
262 base::win::ScopedComPtr
<IDirect3D9Ex
> d3dex
;
263 if (SUCCEEDED(d3dex
.QueryFrom(d3d
)))
264 gpu_info
->can_lose_context
= false;
266 gpu_info
->can_lose_context
= true;
271 bool CollectVideoCardInfo(content::GPUInfo
* gpu_info
) {
272 TRACE_EVENT0("gpu", "CollectVideoCardInfo");
276 // nvd3d9wrap.dll is loaded into all processes when Optimus is enabled.
277 HMODULE nvd3d9wrap
= GetModuleHandleW(L
"nvd3d9wrap.dll");
278 gpu_info
->optimus
= nvd3d9wrap
!= NULL
;
280 // Taken from http://developer.nvidia.com/object/device_ids.html
282 dd
.cb
= sizeof(DISPLAY_DEVICE
);
285 for (int i
= 0; EnumDisplayDevices(NULL
, i
, &dd
, 0); ++i
) {
286 if (dd
.StateFlags
& DISPLAY_DEVICE_PRIMARY_DEVICE
) {
292 if (id
.length() > 20) {
293 int vendor_id
= 0, device_id
= 0;
294 std::wstring vendor_id_string
= id
.substr(8, 4);
295 std::wstring device_id_string
= id
.substr(17, 4);
296 base::HexStringToInt(WideToASCII(vendor_id_string
), &vendor_id
);
297 base::HexStringToInt(WideToASCII(device_id_string
), &device_id
);
298 gpu_info
->gpu
.vendor_id
= vendor_id
;
299 gpu_info
->gpu
.device_id
= device_id
;
300 // TODO(zmo): we only need to call CollectDriverInfoD3D() if we use ANGLE.
301 return CollectDriverInfoD3D(id
, gpu_info
);
306 bool CollectDriverInfoD3D(const std::wstring
& device_id
,
307 content::GPUInfo
* gpu_info
) {
308 TRACE_EVENT0("gpu", "CollectDriverInfoD3D");
310 // create device info for the display device
311 HDEVINFO device_info
= SetupDiGetClassDevsW(
312 NULL
, device_id
.c_str(), NULL
,
313 DIGCF_PRESENT
| DIGCF_PROFILE
| DIGCF_ALLCLASSES
);
314 if (device_info
== INVALID_HANDLE_VALUE
) {
315 LOG(ERROR
) << "Creating device info failed";
321 SP_DEVINFO_DATA device_info_data
;
322 device_info_data
.cbSize
= sizeof(device_info_data
);
323 while (SetupDiEnumDeviceInfo(device_info
, index
++, &device_info_data
)) {
325 if (SetupDiGetDeviceRegistryPropertyW(device_info
,
329 reinterpret_cast<PBYTE
>(value
),
333 std::wstring driver_key
= L
"System\\CurrentControlSet\\Control\\Class\\";
335 LONG result
= RegOpenKeyExW(
336 HKEY_LOCAL_MACHINE
, driver_key
.c_str(), 0, KEY_QUERY_VALUE
, &key
);
337 if (result
== ERROR_SUCCESS
) {
338 DWORD dwcb_data
= sizeof(value
);
339 std::string driver_version
;
340 result
= RegQueryValueExW(
341 key
, L
"DriverVersion", NULL
, NULL
,
342 reinterpret_cast<LPBYTE
>(value
), &dwcb_data
);
343 if (result
== ERROR_SUCCESS
)
344 driver_version
= WideToASCII(std::wstring(value
));
346 std::string driver_date
;
347 dwcb_data
= sizeof(value
);
348 result
= RegQueryValueExW(
349 key
, L
"DriverDate", NULL
, NULL
,
350 reinterpret_cast<LPBYTE
>(value
), &dwcb_data
);
351 if (result
== ERROR_SUCCESS
)
352 driver_date
= WideToASCII(std::wstring(value
));
354 std::string driver_vendor
;
355 dwcb_data
= sizeof(value
);
356 result
= RegQueryValueExW(
357 key
, L
"ProviderName", NULL
, NULL
,
358 reinterpret_cast<LPBYTE
>(value
), &dwcb_data
);
359 if (result
== ERROR_SUCCESS
) {
360 driver_vendor
= WideToASCII(std::wstring(value
));
361 if (driver_vendor
== "Advanced Micro Devices, Inc." ||
362 driver_vendor
== "ATI Technologies Inc.") {
363 // We are conservative and assume that in the absense of a clear
364 // signal the videocard is assumed to be switchable.
365 AMDVideoCardType amd_card_type
= GetAMDVideocardType();
366 gpu_info
->amd_switchable
= (amd_card_type
!= STANDALONE
);
370 gpu_info
->driver_vendor
= driver_vendor
;
371 gpu_info
->driver_version
= driver_version
;
372 gpu_info
->driver_date
= driver_date
;
379 SetupDiDestroyDeviceInfoList(device_info
);
383 bool CollectDriverInfoGL(content::GPUInfo
* gpu_info
) {
384 TRACE_EVENT0("gpu", "CollectDriverInfoGL");
388 std::string gl_version_string
= gpu_info
->gl_version_string
;
390 // TODO(zmo): We assume the driver version is in the end of GL_VERSION
391 // string. Need to verify if it is true for majority drivers.
393 size_t pos
= gl_version_string
.find_last_not_of("0123456789.");
394 if (pos
!= std::string::npos
&& pos
< gl_version_string
.length() - 1) {
395 gpu_info
->driver_version
= gl_version_string
.substr(pos
+ 1);
401 } // namespace gpu_info_collector