diff options
Diffstat (limited to 'dom/canvas/SanitizeRenderer.cpp')
-rw-r--r-- | dom/canvas/SanitizeRenderer.cpp | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/dom/canvas/SanitizeRenderer.cpp b/dom/canvas/SanitizeRenderer.cpp new file mode 100644 index 0000000000..19fd3b8ea0 --- /dev/null +++ b/dom/canvas/SanitizeRenderer.cpp @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/gfx/Logging.h" +#include "mozilla/IntegerRange.h" + +#include <functional> +#include <regex> +#include <string> + +namespace mozilla { +namespace webgl { + +static bool Contains(const std::string& str, const std::string& part) { + return str.find(part) != size_t(-1); +} + +/** + * Narrow renderer string space down to representative replacements. + * E.g. "GeForce RTX 3090" => "GeForce GTX 980" + * + * For example strings: + * https://hackmd.io/Ductv3pQTMej74gbveD4yw + */ +static std::optional<std::string> ChooseDeviceReplacement( + const std::string& str) { + if (str.find("llvmpipe") == 0) return "llvmpipe"; + if (str.find("Apple") == 0) return "Apple M1"; + + std::smatch m; + + // - + // AMD + + { + static const std::string RADEON_HD_3000 = "Radeon HD 3200 Graphics"; + static const std::string RADEON_HD_5850 = "Radeon HD 5850"; + static const std::string RADEON_R9_290 = "Radeon R9 200 Series"; + const auto& RADEON_D3D_FL10_1 = RADEON_HD_3000; + + if (Contains(str, "REMBRANDT")) { // Mobile 6xxx iGPUs + return RADEON_R9_290; + } + if (Contains(str, "RENOIR")) { // Desktop 4xxxG iGPUs + return RADEON_R9_290; + } + if (Contains(str, "Vega")) { + return RADEON_R9_290; + } + if (Contains(str, "VII")) { + return RADEON_R9_290; + } + if (Contains(str, "Fury")) { + return RADEON_R9_290; + } + + static const std::regex kRadeon( + "Radeon.*?((R[579X]|HD) )?([0-9][0-9][0-9]+)"); + if (std::regex_search(str, m, kRadeon)) { + const auto& rxOrHd = m.str(2); + const auto modelNum = stoul(m.str(3)); + if (rxOrHd == "HD") { + if (modelNum >= 5000) { + return RADEON_HD_5850; + } + if (modelNum >= 3000) { + return RADEON_HD_3000; // FL10_1 + } + // HD 2000 is FL10_0, but webgl2 needs 10_1, so claim "old". + return RADEON_D3D_FL10_1; + } + // R5/7/9/X + return RADEON_R9_290; + } + + static const std::regex kFirePro("FirePro.*?([VDW])[0-9][0-9][0-9]+"); + if (std::regex_search(str, m, kFirePro)) { + const auto& vdw = m.str(1); + if (vdw == "V") { + return RADEON_D3D_FL10_1; // FL10_1 + } + return RADEON_R9_290; + } + + if (Contains(str, "ARUBA")) { + return RADEON_HD_5850; + } + + if (Contains(str, "AMD ") || Contains(str, "FirePro") || + Contains(str, "Radeon")) { + return RADEON_D3D_FL10_1; + } + } + + // - + + static const std::string GEFORCE_8800 = "GeForce 8800 GTX"; + static const std::string GEFORCE_480 = "GeForce GTX 480"; + static const std::string GEFORCE_980 = "GeForce GTX 980"; + + if (Contains(str, "NVIDIA") || Contains(str, "GeForce") || + Contains(str, "Quadro")) { + auto ret = std::invoke([&]() { + static const std::regex kGeForce("GeForce.*?([0-9][0-9][0-9]+)"); + if (std::regex_search(str, m, kGeForce)) { + const auto modelNum = stoul(m.str(1)); + if (modelNum >= 8000) { + // Tesla+: D3D10.0, SM4.0 + return GEFORCE_8800; + } + if (modelNum >= 900) { + // Maxwell Gen2+: D3D12 FL12_1 + return GEFORCE_980; + } + if (modelNum >= 400) { + // Fermi+: D3D12 FL11_0 + return GEFORCE_480; + } + // Tesla+: D3D10.0, SM4.0 + return GEFORCE_8800; + } + + static const std::regex kQuadro("Quadro.*?([KMPVT]?)[0-9][0-9][0-9]+"); + if (std::regex_search(str, m, kQuadro)) { + if (Contains(str, "RTX")) return GEFORCE_980; + const auto archLetter = m.str(1); + if (!archLetter.empty()) { + switch (archLetter[0]) { + case 'M': // Maxwell + case 'P': // Pascal + case 'V': // Volta + case 'T': // Turing, mobile-only + return GEFORCE_980; + case 'K': // Kepler + default: + return GEFORCE_480; + } + } + return GEFORCE_8800; + } + + /* Similarities for Titans: + * 780 + * * GeForce GTX TITAN + * * - + * * Black + * * Z + * 980 + * * GeForce GTX TITAN X + * 1080 + * * Nvidia TITAN X + * * Nvidia TITAN Xp + * * Nvidia TITAN V + * 2080 + * * Nvidia TITAN RTX + */ + static const std::regex kTitan("TITAN( [BZXVR])?"); + if (std::regex_search(str, m, kTitan)) { + char letter = ' '; + const auto sub = m.str(1); + if (sub.length()) { + letter = sub[1]; + } + switch (letter) { + case ' ': + case 'B': + case 'Z': + return GEFORCE_480; + default: + return GEFORCE_980; + } + } + // CI has str:"Tesla M60" + if (Contains(str, "Tesla")) return GEFORCE_8800; + + return GEFORCE_8800; // Unknown, but NV. + }); + // On ANGLE: NVIDIA GeForce RTX 3070... + // On WGL: GeForce RTX 3070... + if (str.find("NVIDIA") == 0) { + ret = "NVIDIA " + ret; + } + return ret; + } + + static const std::regex kNouveau("NV(1?[0-9A-F][0-9A-F])"); + if (std::regex_match(str, m, kNouveau)) { + const auto modelNum = stoul(m.str(1), nullptr, 16); + // https://nouveau.freedesktop.org/CodeNames.html#NV110 + if (modelNum >= 0x120) return GEFORCE_980; + if (modelNum >= 0xC0) return GEFORCE_480; + return GEFORCE_8800; + } + + // - + + if (Contains(str, "Intel")) { + static const std::string HD_GRAPHICS = "Intel(R) HD Graphics"; + static const std::string HD_GRAPHICS_400 = "Intel(R) HD Graphics 400"; + static const std::string INTEL_945GM = "Intel 945GM"; + // Pick A750 to split the performance difference, but err optimistically on + // the high end. + static const std::string DGPU_ARC = "Intel(R) Arc(TM) A750 Graphics"; + + if (Contains(str, "Intel(R) Arc(TM)")) { + return DGPU_ARC; + } + + static const std::regex kIntelHD("Intel.*Graphics( P?([0-9][0-9][0-9]+))?"); + if (std::regex_search(str, m, kIntelHD)) { + if (m.str(1).empty()) { + return HD_GRAPHICS; + } + const auto modelNum = stoul(m.str(2)); + if (modelNum >= 5000) { + return HD_GRAPHICS_400; + } + if (modelNum >= 1000) { + return HD_GRAPHICS; + } + return HD_GRAPHICS_400; + } + + return INTEL_945GM; + } + + // - + + static const std::regex kAdreno("Adreno.*?([0-9][0-9][0-9]+)"); + if (std::regex_search(str, m, kAdreno)) { + const auto modelNum = stoul(m.str(1)); + if (modelNum >= 600) { + return "Adreno (TM) 650"; + } + if (modelNum >= 500) { + return "Adreno (TM) 540"; + } + if (modelNum >= 400) { + return "Adreno (TM) 430"; + } + if (modelNum >= 300) { + return "Adreno (TM) 330"; + } + return "Adreno (TM) 225"; + } + + static const std::regex kMali("Mali.*?([0-9][0-9]+)"); + if (std::regex_search(str, m, kMali)) { + const auto modelNum = stoul(m.str(1)); + if (modelNum >= 800) { + return "Mali-T880"; + } + if (modelNum >= 700) { + return "Mali-T760"; + } + if (modelNum >= 600) { + return "Mali-T628"; + } + if (modelNum >= 400) { + return "Mali-400 MP"; + } + return "Mali-G51"; + } + + if (Contains(str, "PowerVR")) { + if (Contains(str, "Rogue")) { + return "PowerVR Rogue G6200"; + } + return "PowerVR SGX 540"; + } + + if (Contains(str, "Vivante")) return "Vivante GC1000"; + if (Contains(str, "VideoCore")) return "VideoCore IV HW"; + if (Contains(str, "Tegra")) return "NVIDIA Tegra"; + + // - + + static const std::string D3D_WARP = "Microsoft Basic Render Driver"; + if (Contains(str, D3D_WARP)) return str; + + return {}; +} + +// - + +std::string SanitizeRenderer(const std::string& raw_renderer) { + std::smatch m; + + const std::string GENERIC_RENDERER = "Generic Renderer"; + + const auto replacementDevice = [&]() -> std::optional<std::string> { + // e.g. "ANGLE (AMD, AMD Radeon(TM) Graphics Direct3D11 vs_5_0 ps_5_0, + // D3D11-27.20.1020.2002)" + static const std::regex kReAngle( + "ANGLE [(]([^,]*), ([^,]*)( Direct3D[^,]*), .*[)]"); + if (std::regex_match(raw_renderer, m, kReAngle)) { + const auto& vendor = m.str(1); + const auto& renderer = m.str(2); + const auto& d3d_suffix = m.str(3); + + auto renderer2 = ChooseDeviceReplacement(renderer); + if (!renderer2) { + gfxCriticalNote << "Couldn't sanitize ANGLE renderer \"" << renderer + << "\" from GL_RENDERER \"" << raw_renderer; + renderer2 = GENERIC_RENDERER; + } + return std::string("ANGLE (") + vendor + ", " + *renderer2 + d3d_suffix + + ")"; + } else if (Contains(raw_renderer, "ANGLE")) { + gfxCriticalError() << "Failed to parse ANGLE renderer: " << raw_renderer; + return {}; + } + + static const std::regex kReOpenglEngine("(.*) OpenGL Engine"); + static const std::regex kRePcieSse2("(.*)(/PCIe?/SSE2)"); + static const std::regex kReStandard("(.*)( [(].*[)])"); + if (std::regex_match(raw_renderer, m, kReOpenglEngine)) { + const auto& dev = m.str(1); + return ChooseDeviceReplacement(dev); + } + if (std::regex_match(raw_renderer, m, kRePcieSse2)) { + const auto& dev = m.str(1); + return ChooseDeviceReplacement(dev); + } + if (std::regex_match(raw_renderer, m, kReStandard)) { + const auto& dev = m.str(1); + return ChooseDeviceReplacement(dev); + } + + const auto& dev = raw_renderer; + return ChooseDeviceReplacement(dev); + }(); + + if (!replacementDevice) { + gfxCriticalNote << "Couldn't sanitize GL_RENDERER \"" << raw_renderer + << "\""; + return GENERIC_RENDERER; + } + + return *replacementDevice + ", or similar"; +} + +}; // namespace webgl +}; // namespace mozilla |