SanitizeRenderer.cpp (11696B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include <functional> 7 #include <regex> 8 #include <string> 9 10 #include "mozilla/gfx/Logging.h" 11 12 namespace mozilla { 13 namespace webgl { 14 15 static bool Contains(const std::string& str, const std::string& part) { 16 return str.find(part) != size_t(-1); 17 } 18 19 /** 20 * Narrow renderer string space down to representative replacements. 21 * E.g. "GeForce RTX 3090" => "GeForce GTX 980" 22 * 23 * For example strings: 24 * https://hackmd.io/Ductv3pQTMej74gbveD4yw 25 */ 26 static std::optional<std::string> ChooseDeviceReplacement( 27 const std::string& str) { 28 if (str.find("llvmpipe") == 0) return "llvmpipe"; 29 if (str.find("Apple") == 0) return "Apple M1"; 30 31 std::smatch m; 32 33 // - 34 // AMD 35 36 { 37 static const std::string RADEON_HD_3000 = "Radeon HD 3200 Graphics"; 38 static const std::string RADEON_HD_5850 = "Radeon HD 5850"; 39 static const std::string RADEON_R9_290 = "Radeon R9 200 Series"; 40 const auto& RADEON_D3D_FL10_1 = RADEON_HD_3000; 41 42 if (Contains(str, "REMBRANDT")) { // Mobile 6xxx iGPUs 43 return RADEON_R9_290; 44 } 45 if (Contains(str, "RENOIR")) { // Desktop 4xxxG iGPUs 46 return RADEON_R9_290; 47 } 48 if (Contains(str, "Vega")) { 49 return RADEON_R9_290; 50 } 51 if (Contains(str, "VII")) { 52 return RADEON_R9_290; 53 } 54 if (Contains(str, "Fury")) { 55 return RADEON_R9_290; 56 } 57 58 static const std::regex kRadeon( 59 "Radeon.*?((R[579X]|HD) )?([0-9][0-9][0-9]+)"); 60 if (std::regex_search(str, m, kRadeon)) { 61 const auto& rxOrHd = m.str(2); 62 const auto modelNum = stoul(m.str(3)); 63 if (rxOrHd == "HD") { 64 if (modelNum >= 5000) { 65 return RADEON_HD_5850; 66 } 67 if (modelNum >= 3000) { 68 return RADEON_HD_3000; // FL10_1 69 } 70 // HD 2000 is FL10_0, but webgl2 needs 10_1, so claim "old". 71 return RADEON_D3D_FL10_1; 72 } 73 // R5/7/9/X 74 return RADEON_R9_290; 75 } 76 77 static const std::regex kFirePro("FirePro.*?([VDW])[0-9][0-9][0-9]+"); 78 if (std::regex_search(str, m, kFirePro)) { 79 const auto& vdw = m.str(1); 80 if (vdw == "V") { 81 return RADEON_D3D_FL10_1; // FL10_1 82 } 83 return RADEON_R9_290; 84 } 85 86 if (Contains(str, "ARUBA")) { 87 return RADEON_HD_5850; 88 } 89 90 if (Contains(str, "AMD ") || Contains(str, "FirePro") || 91 Contains(str, "Radeon")) { 92 return RADEON_D3D_FL10_1; 93 } 94 } 95 96 // - 97 98 static const std::string GEFORCE_8800 = "GeForce 8800 GTX"; 99 static const std::string GEFORCE_480 = "GeForce GTX 480"; 100 static const std::string GEFORCE_980 = "GeForce GTX 980"; 101 102 if (Contains(str, "NVIDIA") || Contains(str, "GeForce") || 103 Contains(str, "Quadro")) { 104 auto ret = std::invoke([&]() { 105 static const std::regex kGeForce("GeForce.*?([0-9][0-9][0-9]+)"); 106 if (std::regex_search(str, m, kGeForce)) { 107 const auto modelNum = stoul(m.str(1)); 108 if (modelNum >= 8000) { 109 // Tesla+: D3D10.0, SM4.0 110 return GEFORCE_8800; 111 } 112 if (modelNum >= 900) { 113 // Maxwell Gen2+: D3D12 FL12_1 114 return GEFORCE_980; 115 } 116 if (modelNum >= 400) { 117 // Fermi+: D3D12 FL11_0 118 return GEFORCE_480; 119 } 120 // Tesla+: D3D10.0, SM4.0 121 return GEFORCE_8800; 122 } 123 124 static const std::regex kQuadro("Quadro.*?([KMPVT]?)[0-9][0-9][0-9]+"); 125 if (std::regex_search(str, m, kQuadro)) { 126 if (Contains(str, "RTX")) return GEFORCE_980; 127 const auto archLetter = m.str(1); 128 if (!archLetter.empty()) { 129 switch (archLetter[0]) { 130 case 'M': // Maxwell 131 case 'P': // Pascal 132 case 'V': // Volta 133 case 'T': // Turing, mobile-only 134 return GEFORCE_980; 135 case 'K': // Kepler 136 default: 137 return GEFORCE_480; 138 } 139 } 140 return GEFORCE_8800; 141 } 142 143 /* Similarities for Titans: 144 * 780 145 * * GeForce GTX TITAN 146 * * - 147 * * Black 148 * * Z 149 * 980 150 * * GeForce GTX TITAN X 151 * 1080 152 * * Nvidia TITAN X 153 * * Nvidia TITAN Xp 154 * * Nvidia TITAN V 155 * 2080 156 * * Nvidia TITAN RTX 157 */ 158 static const std::regex kTitan("TITAN( [BZXVR])?"); 159 if (std::regex_search(str, m, kTitan)) { 160 char letter = ' '; 161 const auto sub = m.str(1); 162 if (sub.length()) { 163 letter = sub[1]; 164 } 165 switch (letter) { 166 case ' ': 167 case 'B': 168 case 'Z': 169 return GEFORCE_480; 170 default: 171 return GEFORCE_980; 172 } 173 } 174 // CI has str:"Tesla M60" 175 if (Contains(str, "Tesla")) return GEFORCE_8800; 176 177 return GEFORCE_8800; // Unknown, but NV. 178 }); 179 // On ANGLE: NVIDIA GeForce RTX 3070... 180 // On WGL: GeForce RTX 3070... 181 if (str.find("NVIDIA") == 0) { 182 ret = "NVIDIA " + ret; 183 } 184 return ret; 185 } 186 187 static const std::regex kNouveau("NV(1?[0-9A-F][0-9A-F])"); 188 if (std::regex_match(str, m, kNouveau)) { 189 const auto modelNum = stoul(m.str(1), nullptr, 16); 190 // https://nouveau.freedesktop.org/CodeNames.html#NV110 191 if (modelNum >= 0x120) return GEFORCE_980; 192 if (modelNum >= 0xC0) return GEFORCE_480; 193 return GEFORCE_8800; 194 } 195 196 // - 197 198 if (Contains(str, "Intel")) { 199 static const std::string HD_GRAPHICS = "Intel(R) HD Graphics"; 200 static const std::string HD_GRAPHICS_400 = "Intel(R) HD Graphics 400"; 201 static const std::string INTEL_945GM = "Intel 945GM"; 202 // Pick A750 to split the performance difference, but err optimistically on 203 // the high end. 204 static const std::string DGPU_ARC = "Intel(R) Arc(TM) A750 Graphics"; 205 206 if (Contains(str, "Intel(R) Arc(TM)")) { 207 return DGPU_ARC; 208 } 209 210 static const std::regex kIntelHD("Intel.*Graphics( P?([0-9][0-9][0-9]+))?"); 211 if (std::regex_search(str, m, kIntelHD)) { 212 if (m.str(1).empty()) { 213 return HD_GRAPHICS; 214 } 215 const auto modelNum = stoul(m.str(2)); 216 if (modelNum >= 5000) { 217 return HD_GRAPHICS_400; 218 } 219 if (modelNum >= 1000) { 220 return HD_GRAPHICS; 221 } 222 return HD_GRAPHICS_400; 223 } 224 225 return INTEL_945GM; 226 } 227 228 // - 229 230 static const std::regex kAdreno("Adreno.*?([0-9][0-9][0-9]+)"); 231 if (std::regex_search(str, m, kAdreno)) { 232 const auto modelNum = stoul(m.str(1)); 233 if (modelNum >= 600) { 234 return "Adreno (TM) 650"; 235 } 236 if (modelNum >= 500) { 237 return "Adreno (TM) 540"; 238 } 239 if (modelNum >= 400) { 240 return "Adreno (TM) 430"; 241 } 242 if (modelNum >= 300) { 243 return "Adreno (TM) 330"; 244 } 245 return "Adreno (TM) 225"; 246 } 247 248 static const std::regex kMali("Mali.*?([0-9][0-9]+)"); 249 if (std::regex_search(str, m, kMali)) { 250 const auto modelNum = stoul(m.str(1)); 251 if (modelNum >= 800) { 252 return "Mali-T880"; 253 } 254 if (modelNum >= 700) { 255 return "Mali-T760"; 256 } 257 if (modelNum >= 600) { 258 return "Mali-T628"; 259 } 260 if (modelNum >= 400) { 261 return "Mali-400 MP"; 262 } 263 return "Mali-G51"; 264 } 265 266 if (Contains(str, "PowerVR")) { 267 if (Contains(str, "Rogue")) { 268 return "PowerVR Rogue G6200"; 269 } 270 return "PowerVR SGX 540"; 271 } 272 273 if (Contains(str, "Samsung Xclipse")) { 274 return "Samsung Xclipse 920"; 275 } 276 277 if (Contains(str, "Vivante")) return "Vivante GC1000"; 278 if (Contains(str, "VideoCore")) return "VideoCore IV HW"; 279 if (Contains(str, "Tegra")) return "NVIDIA Tegra"; 280 281 // - 282 283 static const std::string D3D_WARP = "Microsoft Basic Render Driver"; 284 if (Contains(str, D3D_WARP)) return str; 285 286 return {}; 287 } 288 289 // - 290 291 std::string SanitizeRenderer(const std::string& raw_renderer) { 292 std::smatch m; 293 294 const std::string GENERIC_RENDERER = "Generic Renderer"; 295 296 const auto replacementDevice = [&]() -> std::optional<std::string> { 297 // e.g. "ANGLE (AMD, AMD Radeon(TM) Graphics Direct3D11 vs_5_0 ps_5_0, 298 // D3D11-27.20.1020.2002)" 299 static const std::regex kReAngleDirect3D( 300 "ANGLE [(]([^,]*), ([^,]*)( Direct3D[^,]*), .*[)]"); 301 // e.g. "ANGLE (Samsung Xclipse 940) on Vulkan 1.3.264" 302 static const std::regex kReAngleVulkan( 303 "ANGLE [(]+(.*)[)]( on Vulkan) [0-9\\.]*[)]*"); 304 305 if (std::regex_match(raw_renderer, m, kReAngleDirect3D)) { 306 const auto& vendor = m.str(1); 307 const auto& renderer = m.str(2); 308 const auto& d3d_suffix = m.str(3); 309 310 auto renderer2 = ChooseDeviceReplacement(renderer); 311 if (!renderer2) { 312 gfxCriticalNote << "Couldn't sanitize Direct3D ANGLE renderer \"" 313 << renderer << "\" from GL_RENDERER \"" << raw_renderer; 314 renderer2 = GENERIC_RENDERER; 315 } 316 return std::string("ANGLE (") + vendor + ", " + *renderer2 + d3d_suffix + 317 ")"; 318 } else if (std::regex_match(raw_renderer, m, kReAngleVulkan)) { 319 const auto& renderer = m.str(1); 320 const auto& vulkan_suffix = m.str(2); 321 322 auto renderer2 = ChooseDeviceReplacement(renderer); 323 if (!renderer2) { 324 gfxCriticalNote << "Couldn't sanitize Vulkan ANGLE renderer \"" 325 << renderer << "\" from GL_RENDERER \"" << raw_renderer; 326 renderer2 = GENERIC_RENDERER; 327 } 328 return std::string("ANGLE (") + *renderer2 + ")" + vulkan_suffix; 329 } else if (Contains(raw_renderer, "ANGLE")) { 330 gfxCriticalError() << "Failed to parse ANGLE renderer: " << raw_renderer; 331 return {}; 332 } 333 334 static const std::regex kReOpenglEngine("(.*) OpenGL Engine"); 335 static const std::regex kRePcieSse2("(.*)(/PCIe?/SSE2)"); 336 static const std::regex kReStandard("(.*)( [(].*[)])"); 337 if (std::regex_match(raw_renderer, m, kReOpenglEngine)) { 338 const auto& dev = m.str(1); 339 return ChooseDeviceReplacement(dev); 340 } 341 if (std::regex_match(raw_renderer, m, kRePcieSse2)) { 342 const auto& dev = m.str(1); 343 return ChooseDeviceReplacement(dev); 344 } 345 if (std::regex_match(raw_renderer, m, kReStandard)) { 346 const auto& dev = m.str(1); 347 return ChooseDeviceReplacement(dev); 348 } 349 350 const auto& dev = raw_renderer; 351 return ChooseDeviceReplacement(dev); 352 }(); 353 354 if (!replacementDevice) { 355 gfxCriticalNote << "Couldn't sanitize GL_RENDERER \"" << raw_renderer 356 << "\""; 357 return GENERIC_RENDERER; 358 } 359 360 return *replacementDevice + ", or similar"; 361 } 362 363 // - 364 365 /** 366 * Sanitize vendor string to standardized buckets. 367 * E.g. "NVIDIA Corporation" => "NVIDIA Corporation" 368 */ 369 std::string SanitizeVendor(const std::string& raw_vendor) { 370 if (Contains(raw_vendor, "NVIDIA")) { 371 return "NVIDIA Corporation"; 372 } 373 if (Contains(raw_vendor, "Intel")) { 374 return "Intel"; 375 } 376 if (Contains(raw_vendor, "AMD") || Contains(raw_vendor, "ATI") || 377 Contains(raw_vendor, "Advanced Micro Devices")) { 378 return "AMD"; 379 } 380 if (Contains(raw_vendor, "Qualcomm")) { 381 return "Qualcomm"; 382 } 383 if (Contains(raw_vendor, "ARM")) { 384 return "ARM"; 385 } 386 if (Contains(raw_vendor, "Apple")) { 387 return "Apple"; 388 } 389 if (Contains(raw_vendor, "Samsung")) { 390 return "Samsung"; 391 } 392 if (Contains(raw_vendor, "Mesa") || Contains(raw_vendor, "X.Org")) { 393 return "Mesa"; 394 } 395 if (Contains(raw_vendor, "Microsoft")) { 396 return "Microsoft"; 397 } 398 if (Contains(raw_vendor, "VMware")) { 399 return "VMware"; 400 } 401 if (Contains(raw_vendor, "Google")) { 402 return "Google"; 403 } 404 405 return "Other"; 406 } 407 408 }; // namespace webgl 409 }; // namespace mozilla