[email protected] | b4097b1 | 2012-03-29 03:28:30 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | 623c0bd | 2011-03-12 01:00:41 | [diff] [blame] | 5 | #include "content/gpu/gpu_info_collector.h" |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 6 | |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 7 | #include <X11/Xlib.h> |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 8 | #include <vector> |
| 9 | |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 10 | #include "base/command_line.h" |
[email protected] | d13f35d | 2012-05-18 02:28:15 | [diff] [blame] | 11 | #include "base/debug/trace_event.h" |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 12 | #include "base/file_util.h" |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 13 | #include "base/logging.h" |
[email protected] | 3b63f8f4 | 2011-03-28 01:54:15 | [diff] [blame] | 14 | #include "base/memory/scoped_ptr.h" |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 15 | #include "base/message_loop.h" |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 16 | #include "base/string_piece.h" |
| 17 | #include "base/string_split.h" |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 18 | #include "base/string_tokenizer.h" |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 19 | #include "base/string_util.h" |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 20 | #include "library_loaders/libpci.h" |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 21 | #include "third_party/libXNVCtrl/NVCtrl.h" |
| 22 | #include "third_party/libXNVCtrl/NVCtrlLib.h" |
[email protected] | c9e2cbbb | 2012-05-12 21:17:27 | [diff] [blame] | 23 | #include "ui/gl/gl_bindings.h" |
| 24 | #include "ui/gl/gl_context.h" |
| 25 | #include "ui/gl/gl_implementation.h" |
| 26 | #include "ui/gl/gl_surface.h" |
| 27 | #include "ui/gl/gl_switches.h" |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 28 | |
| 29 | namespace { |
| 30 | |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 31 | // This checks if a system supports PCI bus. |
| 32 | // We check the existence of /sys/bus/pci or /sys/bug/pci_express. |
| 33 | bool IsPciSupported() { |
| 34 | const FilePath pci_path("/sys/bus/pci/"); |
| 35 | const FilePath pcie_path("/sys/bus/pci_express/"); |
| 36 | return (file_util::PathExists(pci_path) || |
| 37 | file_util::PathExists(pcie_path)); |
| 38 | } |
| 39 | |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 40 | // Scan /etc/ati/amdpcsdb.default for "ReleaseVersion". |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 41 | // Return empty string on failing. |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 42 | std::string CollectDriverVersionATI() { |
| 43 | const FilePath::CharType kATIFileName[] = |
| 44 | FILE_PATH_LITERAL("/etc/ati/amdpcsdb.default"); |
| 45 | FilePath ati_file_path(kATIFileName); |
| 46 | if (!file_util::PathExists(ati_file_path)) |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 47 | return std::string(); |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 48 | std::string contents; |
| 49 | if (!file_util::ReadFileToString(ati_file_path, &contents)) |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 50 | return std::string(); |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 51 | StringTokenizer t(contents, "\r\n"); |
| 52 | while (t.GetNext()) { |
| 53 | std::string line = t.token(); |
| 54 | if (StartsWithASCII(line, "ReleaseVersion=", true)) { |
| 55 | size_t begin = line.find_first_of("0123456789"); |
| 56 | if (begin != std::string::npos) { |
| 57 | size_t end = line.find_first_not_of("0123456789.", begin); |
| 58 | if (end == std::string::npos) |
| 59 | return line.substr(begin); |
| 60 | else |
| 61 | return line.substr(begin, end - begin); |
| 62 | } |
| 63 | } |
| 64 | } |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 65 | return std::string(); |
| 66 | } |
| 67 | |
| 68 | // Use NVCtrl extention to query NV driver version. |
| 69 | // Return empty string on failing. |
| 70 | std::string CollectDriverVersionNVidia() { |
| 71 | Display* display = base::MessagePumpForUI::GetDefaultXDisplay(); |
| 72 | if (!display) { |
| 73 | LOG(ERROR) << "XOpenDisplay failed."; |
| 74 | return std::string(); |
| 75 | } |
| 76 | int event_base = 0, error_base = 0; |
| 77 | if (!XNVCTRLQueryExtension(display, &event_base, &error_base)) { |
[email protected] | 2e1f57c | 2012-09-05 03:56:33 | [diff] [blame] | 78 | LOG(INFO) << "NVCtrl extension does not exist."; |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 79 | return std::string(); |
| 80 | } |
| 81 | int screen_count = ScreenCount(display); |
| 82 | for (int screen = 0; screen < screen_count; ++screen) { |
| 83 | char* buffer = NULL; |
| 84 | if (XNVCTRLIsNvScreen(display, screen) && |
| 85 | XNVCTRLQueryStringAttribute(display, screen, 0, |
| 86 | NV_CTRL_STRING_NVIDIA_DRIVER_VERSION, |
| 87 | &buffer)) { |
| 88 | std::string driver_version(buffer); |
| 89 | XFree(buffer); |
| 90 | return driver_version; |
| 91 | } |
| 92 | } |
| 93 | return std::string(); |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 94 | } |
| 95 | |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 96 | const uint32 kVendorIDIntel = 0x8086; |
| 97 | const uint32 kVendorIDNVidia = 0x10de; |
| 98 | const uint32 kVendorIDAMD = 0x1002; |
| 99 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 100 | } // namespace anonymous |
| 101 | |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 102 | namespace gpu_info_collector { |
| 103 | |
[email protected] | a80f5ece | 2011-10-20 23:56:55 | [diff] [blame] | 104 | bool CollectGraphicsInfo(content::GPUInfo* gpu_info) { |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 105 | DCHECK(gpu_info); |
[email protected] | 21de1e4 | 2012-11-14 23:38:40 | [diff] [blame] | 106 | *gpu_info = content::GPUInfo(); |
[email protected] | 41579ae | 2010-11-15 22:31:26 | [diff] [blame] | 107 | |
[email protected] | d13f35d | 2012-05-18 02:28:15 | [diff] [blame] | 108 | TRACE_EVENT0("gpu", "gpu_info_collector::CollectGraphicsInfo"); |
| 109 | |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 110 | if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 111 | switches::kGpuNoContextLost)) { |
| 112 | gpu_info->can_lose_context = false; |
| 113 | } else { |
[email protected] | 6df3b33e | 2012-11-01 19:33:41 | [diff] [blame] | 114 | #if defined(OS_CHROMEOS) |
| 115 | gpu_info->can_lose_context = false; |
| 116 | #else |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 117 | // TODO(zmo): need to consider the case where we are running on top |
| 118 | // of desktop GL and GL_ARB_robustness extension is available. |
| 119 | gpu_info->can_lose_context = |
| 120 | (gfx::GetGLImplementation() == gfx::kGLImplementationEGLGLES2); |
[email protected] | 6df3b33e | 2012-11-01 19:33:41 | [diff] [blame] | 121 | #endif |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 122 | } |
| 123 | |
[email protected] | 6cc8ece6 | 2011-03-14 20:05:47 | [diff] [blame] | 124 | gpu_info->finalized = true; |
[email protected] | 29ad10c | 2011-11-23 19:48:06 | [diff] [blame] | 125 | bool rt = CollectGraphicsInfoGL(gpu_info); |
[email protected] | 079c4ad | 2011-02-19 00:05:57 | [diff] [blame] | 126 | |
[email protected] | 079c4ad | 2011-02-19 00:05:57 | [diff] [blame] | 127 | return rt; |
| 128 | } |
| 129 | |
[email protected] | 29ad10c | 2011-11-23 19:48:06 | [diff] [blame] | 130 | bool CollectPreliminaryGraphicsInfo(content::GPUInfo* gpu_info) { |
| 131 | DCHECK(gpu_info); |
| 132 | |
[email protected] | b4097b1 | 2012-03-29 03:28:30 | [diff] [blame] | 133 | bool rt = CollectVideoCardInfo(gpu_info); |
| 134 | |
[email protected] | ba8a61c | 2012-06-21 22:31:06 | [diff] [blame] | 135 | std::string driver_version; |
| 136 | switch (gpu_info->gpu.vendor_id) { |
| 137 | case kVendorIDAMD: |
| 138 | driver_version = CollectDriverVersionATI(); |
| 139 | if (!driver_version.empty()) { |
| 140 | gpu_info->driver_vendor = "ATI / AMD"; |
| 141 | gpu_info->driver_version = driver_version; |
| 142 | } |
| 143 | break; |
| 144 | case kVendorIDNVidia: |
| 145 | driver_version = CollectDriverVersionNVidia(); |
| 146 | if (!driver_version.empty()) { |
| 147 | gpu_info->driver_vendor = "NVIDIA"; |
| 148 | gpu_info->driver_version = driver_version; |
| 149 | } |
| 150 | break; |
[email protected] | e98b9843 | 2012-08-22 23:03:13 | [diff] [blame] | 151 | case kVendorIDIntel: |
| 152 | // In dual-GPU cases, sometimes PCI scan only gives us the |
| 153 | // integrated GPU (i.e., the Intel one). |
| 154 | driver_version = CollectDriverVersionNVidia(); |
| 155 | if (!driver_version.empty()) { |
| 156 | gpu_info->driver_vendor = "NVIDIA"; |
| 157 | gpu_info->driver_version = driver_version; |
| 158 | // Machines with more than two GPUs are not handled. |
| 159 | if (gpu_info->secondary_gpus.size() <= 1) |
| 160 | gpu_info->optimus = true; |
| 161 | } |
| 162 | break; |
[email protected] | b4097b1 | 2012-03-29 03:28:30 | [diff] [blame] | 163 | } |
| 164 | |
| 165 | return rt; |
[email protected] | 29ad10c | 2011-11-23 19:48:06 | [diff] [blame] | 166 | } |
| 167 | |
[email protected] | a80f5ece | 2011-10-20 23:56:55 | [diff] [blame] | 168 | bool CollectVideoCardInfo(content::GPUInfo* gpu_info) { |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 169 | DCHECK(gpu_info); |
[email protected] | 41579ae | 2010-11-15 22:31:26 | [diff] [blame] | 170 | |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 171 | if (IsPciSupported() == false) { |
[email protected] | 7ace0ef4e | 2011-03-17 21:23:05 | [diff] [blame] | 172 | VLOG(1) << "PCI bus scanning is not supported"; |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 173 | return false; |
| 174 | } |
| 175 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 176 | // TODO(zmo): be more flexible about library name. |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 177 | LibPciLoader libpci_loader; |
| 178 | if (!libpci_loader.Load("libpci.so.3") && |
| 179 | !libpci_loader.Load("libpci.so")) { |
[email protected] | 7ace0ef4e | 2011-03-17 21:23:05 | [diff] [blame] | 180 | VLOG(1) << "Failed to locate libpci"; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 181 | return false; |
[email protected] | c5790ae | 2011-03-01 16:51:49 | [diff] [blame] | 182 | } |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 183 | |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 184 | pci_access* access = (libpci_loader.pci_alloc)(); |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 185 | DCHECK(access != NULL); |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 186 | (libpci_loader.pci_init)(access); |
| 187 | (libpci_loader.pci_scan_bus)(access); |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 188 | bool primary_gpu_identified = false; |
[email protected] | 9c87f01 | 2012-11-01 20:08:46 | [diff] [blame] | 189 | for (pci_dev* device = access->devices; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 190 | device != NULL; device = device->next) { |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 191 | // Fill the IDs and class fields. |
| 192 | (libpci_loader.pci_fill_info)(device, 33); |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 193 | // TODO(zmo): there might be other classes that qualify as display devices. |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 194 | if (device->device_class != 0x0300) // Device class is DISPLAY_VGA. |
| 195 | continue; |
| 196 | |
| 197 | content::GPUInfo::GPUDevice gpu; |
| 198 | gpu.vendor_id = device->vendor_id; |
| 199 | gpu.device_id = device->device_id; |
| 200 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 201 | const int buffer_size = 255; |
| 202 | scoped_array<char> buffer(new char[buffer_size]); |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 203 | // The current implementation of pci_lookup_name returns the same pointer |
| 204 | // as the passed in upon success, and a different one (NULL or a pointer |
| 205 | // to an error message) upon failure. |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 206 | if ((libpci_loader.pci_lookup_name)(access, |
| 207 | buffer.get(), |
| 208 | buffer_size, |
| 209 | 1, |
| 210 | device->vendor_id) == buffer.get()) { |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 211 | gpu.vendor_string = buffer.get(); |
| 212 | } |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 213 | if ((libpci_loader.pci_lookup_name)(access, |
| 214 | buffer.get(), |
| 215 | buffer_size, |
| 216 | 2, |
| 217 | device->vendor_id, |
| 218 | device->device_id) == buffer.get()) { |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 219 | std::string device_string = buffer.get(); |
| 220 | size_t begin = device_string.find_first_of('['); |
| 221 | size_t end = device_string.find_last_of(']'); |
| 222 | if (begin != std::string::npos && end != std::string::npos && |
| 223 | begin < end) { |
| 224 | device_string = device_string.substr(begin + 1, end - begin - 1); |
| 225 | } |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 226 | gpu.device_string = device_string; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 227 | } |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 228 | |
| 229 | if (!primary_gpu_identified) { |
| 230 | primary_gpu_identified = true; |
| 231 | gpu_info->gpu = gpu; |
| 232 | } else { |
| 233 | // TODO(zmo): if there are multiple GPUs, we assume the non Intel |
| 234 | // one is primary. Revisit this logic because we actually don't know |
| 235 | // which GPU we are using at this point. |
| 236 | if (gpu_info->gpu.vendor_id == kVendorIDIntel && |
| 237 | gpu.vendor_id != kVendorIDIntel) { |
| 238 | gpu_info->secondary_gpus.push_back(gpu_info->gpu); |
| 239 | gpu_info->gpu = gpu; |
| 240 | } else { |
| 241 | gpu_info->secondary_gpus.push_back(gpu); |
| 242 | } |
| 243 | } |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 244 | } |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 245 | |
| 246 | // Detect Optimus or AMD Switchable GPU. |
| 247 | if (gpu_info->secondary_gpus.size() == 1 && |
| 248 | gpu_info->secondary_gpus[0].vendor_id == kVendorIDIntel) { |
| 249 | if (gpu_info->gpu.vendor_id == kVendorIDNVidia) |
| 250 | gpu_info->optimus = true; |
| 251 | if (gpu_info->gpu.vendor_id == kVendorIDAMD) |
| 252 | gpu_info->amd_switchable = true; |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 253 | } |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 254 | |
[email protected] | dfbff86 | 2012-11-28 19:08:14 | [diff] [blame] | 255 | (libpci_loader.pci_cleanup)(access); |
[email protected] | a094e2c | 2012-05-10 23:02:42 | [diff] [blame] | 256 | return (primary_gpu_identified); |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 257 | } |
| 258 | |
[email protected] | a80f5ece | 2011-10-20 23:56:55 | [diff] [blame] | 259 | bool CollectDriverInfoGL(content::GPUInfo* gpu_info) { |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 260 | DCHECK(gpu_info); |
| 261 | |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 262 | std::string gl_version_string = gpu_info->gl_version_string; |
[email protected] | aa2284d | 2011-08-04 18:36:03 | [diff] [blame] | 263 | if (StartsWithASCII(gl_version_string, "OpenGL ES", true)) |
| 264 | gl_version_string = gl_version_string.substr(10); |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 265 | std::vector<std::string> pieces; |
| 266 | base::SplitStringAlongWhitespace(gl_version_string, &pieces); |
| 267 | // In linux, the gl version string might be in the format of |
| 268 | // GLVersion DriverVendor DriverVersion |
| 269 | if (pieces.size() < 3) |
| 270 | return false; |
| 271 | |
| 272 | std::string driver_version = pieces[2]; |
| 273 | size_t pos = driver_version.find_first_not_of("0123456789."); |
| 274 | if (pos == 0) |
| 275 | return false; |
| 276 | if (pos != std::string::npos) |
| 277 | driver_version = driver_version.substr(0, pos); |
| 278 | |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 279 | gpu_info->driver_vendor = pieces[1]; |
| 280 | gpu_info->driver_version = driver_version; |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 281 | return true; |
| 282 | } |
| 283 | |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 284 | } // namespace gpu_info_collector |