// Copyright (c) 2013 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_control_list.h" #include #include "base/json/values_util.h" #include "base/logging.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/system/sys_info.h" #include "base/values.h" #include "build/build_config.h" #include "components/crash/core/common/crash_key.h" #include "gpu/config/gpu_util.h" #include "third_party/re2/src/re2/re2.h" namespace gpu { namespace { // Break a version string into segments. Return true if each segment is // a valid number, and not all segment is 0. bool ProcessVersionString(const std::string& version_string, char splitter, std::vector* version) { DCHECK(version); *version = base::SplitString( version_string, std::string(1, splitter), base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); if (version->size() == 0) return false; // If the splitter is '-', we assume it's a date with format "mm-dd-yyyy"; // we split it into the order of "yyyy", "mm", "dd". if (splitter == '-') { std::string year = version->back(); for (size_t i = version->size() - 1; i > 0; --i) { (*version)[i] = (*version)[i - 1]; } (*version)[0] = year; } bool all_zero = true; for (size_t i = 0; i < version->size(); ++i) { unsigned num = 0; if (!base::StringToUint((*version)[i], &num)) { version->resize(i); break; } if (num) all_zero = false; } return !all_zero; } // Compare two number strings using numerical ordering. // Return 0 if number = number_ref, // 1 if number > number_ref, // -1 if number < number_ref. int CompareNumericalNumberStrings( const std::string& number, const std::string& number_ref) { unsigned value1 = 0; unsigned value2 = 0; bool valid = base::StringToUint(number, &value1); DCHECK(valid); valid = base::StringToUint(number_ref, &value2); DCHECK(valid); if (value1 == value2) return 0; if (value1 > value2) return 1; return -1; } // Compare two number strings using lexical ordering. // Return 0 if number = number_ref, // 1 if number > number_ref, // -1 if number < number_ref. // We only compare as many digits as number_ref contains. // If number_ref is xxx, it's considered as xxx* // For example: CompareLexicalNumberStrings("121", "12") returns 0, // CompareLexicalNumberStrings("12", "121") returns -1. int CompareLexicalNumberStrings( const std::string& number, const std::string& number_ref) { for (size_t i = 0; i < number_ref.length(); ++i) { unsigned value1 = 0; if (i < number.length()) value1 = number[i] - '0'; unsigned value2 = number_ref[i] - '0'; if (value1 > value2) return 1; if (value1 < value2) return -1; } return 0; } // A mismatch is identified only if both |input| and |pattern| are not empty. bool StringMismatch(const std::string& input, const std::string& pattern) { if (input.empty() || pattern.empty()) return false; static crash_reporter::CrashKeyString<128> crash_key( "StringMismatch::pattern"); crash_reporter::ScopedCrashKeyString scoped_crash_key(&crash_key, pattern); return !RE2::FullMatch(input, pattern); } bool StringMismatch(const std::string& input, const char* pattern) { if (!pattern) return false; std::string pattern_string(pattern); return StringMismatch(input, pattern_string); } bool ProcessANGLEGLRenderer(const std::string& gl_renderer, std::string* vendor, std::string* renderer, std::string* version) { constexpr char kANGLEPrefix[] = "ANGLE ("; if (!base::StartsWith(gl_renderer, kANGLEPrefix)) return false; std::vector segments; // ANGLE GL_RENDERER string: // ANGLE (vendor,renderer,version) size_t len = gl_renderer.size(); std::string vendor_renderer_version = gl_renderer.substr(sizeof(kANGLEPrefix) - 1, len - sizeof(kANGLEPrefix)); segments = base::SplitString(vendor_renderer_version, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); if (segments.size() != 3) { LOG(DFATAL) << "Cannot parse ANGLE GL_RENDERER: " << gl_renderer; return false; } // Check ANGLE backend. // It could be `OpenGL, D3D, Vulkan, etc` if (!base::StartsWith(segments[2], "OpenGL")) { return false; } if (vendor) *vendor = segments[0]; if (renderer) *renderer = segments[1]; if (version) *version = segments[2]; return true; } } // namespace bool GpuControlList::Version::Contains(const std::string& version_string, char splitter) const { if (op == kUnknown) return false; if (op == kAny) return true; std::vector version; if (!ProcessVersionString(version_string, splitter, &version)) return false; std::vector ref_version1, ref_version2; bool valid = ProcessVersionString(value1, '.', &ref_version1); DCHECK(valid); if (op == kBetween) { valid = ProcessVersionString(value2, '.', &ref_version2); DCHECK(valid); } if (schema == kVersionSchemaIntelDriver) { // Intel graphics driver version schema should only be specified on Windows. // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html // If either of the two versions doesn't match the Intel driver version // schema, they should not be compared. if (version.size() != 4 || ref_version1.size() != 4) return false; if (op == kBetween && ref_version2.size() != 4) { return false; } for (size_t ii = 0; ii < 2; ++ii) { version.erase(version.begin()); ref_version1.erase(ref_version1.begin()); if (op == kBetween) ref_version2.erase(ref_version2.begin()); } } else if (schema == kVersionSchemaNvidiaDriver) { // The driver version we get from the os is "XX.XX.XXXA.BBCC", while the // workaround is of the form "ABB.CC". Drop the first two stanzas from the // detected version, erase all but the last character of the third, and move // "B" to the previous stanza. if (version.size() != 4) return false; // Remember that the detected version might not have leading zeros, so we // have to be a bit careful. [2] is of the form "001A", where A > 0, so we // just care that there's at least one digit. However, if there's less than // that, the splitter stops anyway on that stanza, and the check for four // stanzas will fail instead. version.erase(version.begin(), version.begin() + 2); version[0].erase(0, version[0].length() - 1); // The last stanza may be missing leading zeros, so handle them. if (version[1].length() < 3) { // Two or more removed leading zeros, so BB are both zero. version[0] += "00"; } else if (version[1].length() < 4) { // One removed leading zero. BB is 0[1-9]. version[0] += "0" + version[1].substr(0, 1); version[1].erase(0, 1); } else { // No leading zeros. version[0] += version[1].substr(0, 2); version[1].erase(0, 2); } } int relation = Version::Compare(version, ref_version1, style); switch (op) { case kEQ: return (relation == 0); case kLT: return (relation < 0); case kLE: return (relation <= 0); case kGT: return (relation > 0); case kGE: return (relation >= 0); case kBetween: if (relation < 0) return false; return Version::Compare(version, ref_version2, style) <= 0; default: NOTREACHED(); return false; } } // static int GpuControlList::Version::Compare( const std::vector& version, const std::vector& version_ref, VersionStyle version_style) { DCHECK(version.size() > 0 && version_ref.size() > 0); DCHECK(version_style != kVersionStyleUnknown); for (size_t i = 0; i < version_ref.size(); ++i) { if (i >= version.size()) return 0; int ret = 0; // We assume both versions are checked by ProcessVersionString(). if (i > 0 && version_style == kVersionStyleLexical) ret = CompareLexicalNumberStrings(version[i], version_ref[i]); else ret = CompareNumericalNumberStrings(version[i], version_ref[i]); if (ret != 0) return ret; } return 0; } bool GpuControlList::More::GLVersionInfoMismatch( const std::string& gl_version_string) const { if (gl_version_string.empty()) return false; if (!gl_version.IsSpecified() && gl_type == kGLTypeNone) return false; std::vector segments = base::SplitString( gl_version_string, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); std::string number; GLType target_gl_type = kGLTypeNone; if (segments.size() > 2 && segments[0] == "OpenGL" && segments[1] == "ES") { bool full_match = RE2::FullMatch(segments[2], "([\\d.]+).*", &number); DCHECK(full_match); target_gl_type = kGLTypeGLES; if (segments.size() > 3 && base::StartsWith(segments[3], "(ANGLE", base::CompareCase::INSENSITIVE_ASCII)) { target_gl_type = kGLTypeANGLE; } } else { number = segments[0]; target_gl_type = kGLTypeGL; } GLType entry_gl_type = gl_type; if (entry_gl_type == kGLTypeNone && gl_version.IsSpecified()) { entry_gl_type = GetDefaultGLType(); } if (entry_gl_type != kGLTypeNone && entry_gl_type != target_gl_type) { return true; } if (gl_version.IsSpecified() && !gl_version.Contains(number)) { return true; } return false; } // static GpuControlList::GLType GpuControlList::More::GetDefaultGLType() { #if BUILDFLAG(IS_CHROMEOS) return kGLTypeGL; #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_OPENBSD) return kGLTypeGL; #elif BUILDFLAG(IS_MAC) return kGLTypeGL; #elif BUILDFLAG(IS_WIN) return kGLTypeANGLE; #elif BUILDFLAG(IS_ANDROID) return kGLTypeGLES; #else return kGLTypeNone; #endif } void GpuControlList::Entry::LogControlListMatch( const std::string& control_list_logging_name) const { static const char kControlListMatchMessage[] = "Control list match for rule #%u in %s."; VLOG(1) << base::StringPrintf(kControlListMatchMessage, id, control_list_logging_name.c_str()); } bool GpuControlList::DriverInfo::Contains( const std::vector& gpus) const { for (auto& gpu : gpus) { if (StringMismatch(gpu.driver_vendor, driver_vendor)) continue; if (driver_version.IsSpecified() && !gpu.driver_version.empty() && !driver_version.Contains(gpu.driver_version)) { continue; } return true; } return false; } bool GpuControlList::GLStrings::Contains(const GPUInfo& gpu_info) const { if (StringMismatch(gpu_info.gl_extensions, gl_extensions)) return false; std::string vendor; std::string renderer; std::string version; bool is_angle_gl = ProcessANGLEGLRenderer(gpu_info.gl_renderer, &vendor, &renderer, &version); if (StringMismatch(is_angle_gl ? vendor : gpu_info.gl_vendor, gl_vendor)) { return false; } if (StringMismatch(is_angle_gl ? renderer : gpu_info.gl_renderer, gl_renderer)) { return false; } if (StringMismatch(is_angle_gl ? version : gpu_info.gl_version, gl_version)) { return false; } return true; } bool GpuControlList::MachineModelInfo::Contains(const GPUInfo& gpu_info) const { if (machine_model_name_size > 0) { if (gpu_info.machine_model_name.empty()) return false; bool found_match = false; for (size_t ii = 0; ii < machine_model_name_size; ++ii) { if (RE2::FullMatch(gpu_info.machine_model_name, machine_model_names[ii])) { found_match = true; break; } } if (!found_match) return false; } if (machine_model_version.IsSpecified() && (gpu_info.machine_model_version.empty() || !machine_model_version.Contains(gpu_info.machine_model_version))) { return false; } return true; } bool GpuControlList::More::Contains(const GPUInfo& gpu_info) const { std::string gl_version_string; bool is_angle_gl = ProcessANGLEGLRenderer(gpu_info.gl_renderer, nullptr, nullptr, &gl_version_string); if (GLVersionInfoMismatch(is_angle_gl ? gl_version_string : gpu_info.gl_version)) { return false; } if (gl_reset_notification_strategy != 0 && gl_reset_notification_strategy != gpu_info.gl_reset_notification_strategy) { return false; } if (gpu_count.IsSpecified()) { size_t count = gpu_info.secondary_gpus.size() + 1; if (!gpu_count.Contains(std::to_string(count))) { return false; } } if (direct_rendering_version.IsSpecified() && !direct_rendering_version.Contains(gpu_info.direct_rendering_version)) { return false; } if (in_process_gpu && !gpu_info.in_process_gpu) { return false; } if (pixel_shader_version.IsSpecified() && !pixel_shader_version.Contains(gpu_info.pixel_shader_version)) { return false; } switch (hardware_overlay) { case kDontCare: break; case kSupported: #if BUILDFLAG(IS_WIN) if (!gpu_info.overlay_info.supports_overlays) return false; #endif // BUILDFLAG(IS_WIN) break; case kUnsupported: #if BUILDFLAG(IS_WIN) if (gpu_info.overlay_info.supports_overlays) return false; #endif // BUILDFLAG(IS_WIN) break; } if ((subpixel_font_rendering == kUnsupported && gpu_info.subpixel_font_rendering) || (subpixel_font_rendering == kSupported && !gpu_info.subpixel_font_rendering)) { return false; } return true; } bool GpuControlList::Conditions::Contains(OsType target_os_type, const std::string& target_os_version, const GPUInfo& gpu_info) const { DCHECK(target_os_type != kOsAny); if (os_type != kOsAny) { if (os_type != target_os_type) return false; if (os_version.IsSpecified() && !os_version.Contains(target_os_version)) return false; } std::vector candidates; switch (multi_gpu_category) { case kMultiGpuCategoryPrimary: candidates.push_back(gpu_info.gpu); break; case kMultiGpuCategorySecondary: candidates = gpu_info.secondary_gpus; break; case kMultiGpuCategoryAny: candidates = gpu_info.secondary_gpus; candidates.push_back(gpu_info.gpu); break; case kMultiGpuCategoryActive: case kMultiGpuCategoryNone: // If gpu category is not specified, default to the active gpu. if (gpu_info.gpu.active || gpu_info.secondary_gpus.empty()) candidates.push_back(gpu_info.gpu); for (auto& gpu : gpu_info.secondary_gpus) { if (gpu.active) candidates.push_back(gpu); } if (candidates.empty()) candidates.push_back(gpu_info.gpu); } if (vendor_id != 0 || intel_gpu_series_list_size > 0 || intel_gpu_generation.IsSpecified()) { bool found = false; if (intel_gpu_series_list_size > 0) { for (size_t ii = 0; !found && ii < candidates.size(); ++ii) { IntelGpuSeriesType candidate_series = GetIntelGpuSeriesType( candidates[ii].vendor_id, candidates[ii].device_id); if (candidate_series == IntelGpuSeriesType::kUnknown) continue; for (size_t jj = 0; jj < intel_gpu_series_list_size; ++jj) { if (candidate_series == intel_gpu_series_list[jj]) { found = true; break; } } } } else if (intel_gpu_generation.IsSpecified()) { for (auto& candidate : candidates) { std::string candidate_generation = GetIntelGpuGeneration(candidate.vendor_id, candidate.device_id); if (candidate_generation.empty()) continue; if (intel_gpu_generation.Contains(candidate_generation)) { found = true; break; } } } else { if (device_size == 0) { for (auto& candidate : candidates) { if (vendor_id == candidate.vendor_id) { found = true; break; } } } else { for (size_t ii = 0; !found && ii < device_size; ++ii) { uint32_t device_id = devices[ii].device_id; #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) uint32_t revision = devices[ii].revision; #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) for (auto& candidate : candidates) { if (vendor_id != candidate.vendor_id || device_id != candidate.device_id) continue; #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) if (revision && revision != candidate.revision) continue; #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) found = true; break; } } } } if (!found) return false; } switch (multi_gpu_style) { case kMultiGpuStyleOptimus: if (!gpu_info.optimus) return false; break; case kMultiGpuStyleAMDSwitchable: if (!gpu_info.amd_switchable) return false; break; case kMultiGpuStyleAMDSwitchableDiscrete: if (!gpu_info.amd_switchable) return false; // The discrete GPU is always the primary GPU. // This is guaranteed by GpuInfoCollector. if (!gpu_info.gpu.active) return false; break; case kMultiGpuStyleAMDSwitchableIntegrated: if (!gpu_info.amd_switchable) return false; // Assume the integrated GPU is the first in the secondary GPU list. if (gpu_info.secondary_gpus.size() == 0 || !gpu_info.secondary_gpus[0].active) return false; break; case kMultiGpuStyleNone: break; } if (driver_info) { // We don't have a reliable way to check driver version without // also checking for vendor. DCHECK(vendor_id != 0 || candidates.size() < 2); // Remove candidate GPUs made by different vendors. auto behind_last = std::remove_if(candidates.begin(), candidates.end(), [vendor_id = vendor_id](const GPUInfo::GPUDevice& gpu) { return (vendor_id && vendor_id != gpu.vendor_id); }); candidates.erase(behind_last, candidates.end()); if (!driver_info->Contains(candidates)) return false; } if (gl_strings && !gl_strings->Contains(gpu_info)) { return false; } if (machine_model_info && !machine_model_info->Contains(gpu_info)) { return false; } if (more && !more->Contains(gpu_info)) { return false; } return true; } bool GpuControlList::Entry::Contains(OsType target_os_type, const std::string& target_os_version, const GPUInfo& gpu_info) const { static crash_reporter::CrashKeyString<8> crash_key( "GpuControlList::Entry::id"); crash_reporter::ScopedCrashKeyString scoped_crash_key( &crash_key, base::StringPrintf("%d", id)); if (!conditions.Contains(target_os_type, target_os_version, gpu_info)) { return false; } for (size_t ii = 0; ii < exception_size; ++ii) { if (exceptions[ii].Contains(target_os_type, target_os_version, gpu_info) && !exceptions[ii].NeedsMoreInfo(gpu_info)) { return false; } } return true; } bool GpuControlList::Entry::AppliesToTestGroup( uint32_t target_test_group) const { // If an entry specifies non-zero test group, then the entry only applies // if that test group is enabled (as specified in |target_test_group|). if (conditions.more && conditions.more->test_group) return conditions.more->test_group == target_test_group; return true; } bool GpuControlList::Conditions::NeedsMoreInfo(const GPUInfo& gpu_info) const { // We only check for missing info that might be collected with a gl context. // If certain info is missing due to some error, say, we fail to collect // vendor_id/device_id, then even if we launch GPU process and create a gl // context, we won't gather such missing info, so we still return false. const GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu(); if (driver_info) { if (driver_info->driver_vendor && active_gpu.driver_vendor.empty()) { return true; } if (driver_info->driver_version.IsSpecified() && active_gpu.driver_version.empty()) { return true; } } if (((more && more->gl_version.IsSpecified()) || (gl_strings && gl_strings->gl_version)) && gpu_info.gl_version.empty()) { return true; } if (gl_strings && gl_strings->gl_vendor && gpu_info.gl_vendor.empty()) return true; if (gl_strings && gl_strings->gl_renderer && gpu_info.gl_renderer.empty()) return true; if (more && more->pixel_shader_version.IsSpecified() && gpu_info.pixel_shader_version.empty()) { return true; } return false; } bool GpuControlList::Entry::NeedsMoreInfo(const GPUInfo& gpu_info, bool consider_exceptions) const { if (conditions.NeedsMoreInfo(gpu_info)) return true; if (consider_exceptions) { for (size_t ii = 0; ii < exception_size; ++ii) { if (exceptions[ii].NeedsMoreInfo(gpu_info)) return true; } } return false; } base::Value::List GpuControlList::Entry::GetFeatureNames( const FeatureMap& feature_map) const { base::Value::List feature_names; for (size_t ii = 0; ii < feature_size; ++ii) { auto iter = feature_map.find(features[ii]); DCHECK(iter != feature_map.end()); feature_names.Append(iter->second); } for (size_t ii = 0; ii < disabled_extension_size; ++ii) { std::string name = base::StringPrintf("disable(%s)", disabled_extensions[ii]); feature_names.Append(name); } return feature_names; } GpuControlList::GpuControlList(const GpuControlListData& data) : entry_count_(data.entry_count), entries_(data.entries), max_entry_id_(0), needs_more_info_(false), control_list_logging_enabled_(false) { DCHECK_LT(0u, entry_count_); // Assume the newly last added entry has the largest ID. max_entry_id_ = entries_[entry_count_ - 1].id; } GpuControlList::~GpuControlList() = default; std::set GpuControlList::MakeDecision(GpuControlList::OsType os, const std::string& os_version, const GPUInfo& gpu_info) { return MakeDecision(os, os_version, gpu_info, 0); } std::set GpuControlList::MakeDecision(GpuControlList::OsType os, const std::string& os_version, const GPUInfo& gpu_info, uint32_t target_test_group) { active_entries_.clear(); std::set features; needs_more_info_ = false; // Has all features permanently in the list without any possibility of // removal in the future (subset of "features" set). std::set permanent_features; // Has all features absent from "features" set that could potentially be // included later with more information. std::set potential_features; if (os == kOsAny) os = GetOsType(); std::string processed_os_version = os_version; if (processed_os_version.empty()) processed_os_version = base::SysInfo::OperatingSystemVersion(); // Get rid of the non numbers because later processing expects a valid // version string in the format of "a.b.c". size_t pos = processed_os_version.find_first_not_of("0123456789."); if (pos != std::string::npos) processed_os_version = processed_os_version.substr(0, pos); for (size_t ii = 0; ii < entry_count_; ++ii) { const Entry& entry = entries_[ii]; DCHECK_NE(0u, entry.id); if (!entry.AppliesToTestGroup(target_test_group)) continue; if (entry.Contains(os, processed_os_version, gpu_info)) { bool needs_more_info_main = entry.NeedsMoreInfo(gpu_info, false); bool needs_more_info_exception = entry.NeedsMoreInfo(gpu_info, true); if (control_list_logging_enabled_) entry.LogControlListMatch(control_list_logging_name_); // Only look at main entry info when deciding what to add to "features" // set. If we don't have enough info for an exception, it's safer if we // just ignore the exception and assume the exception doesn't apply. for (size_t jj = 0; jj < entry.feature_size; ++jj) { int32_t feature = entry.features[jj]; if (needs_more_info_main) { if (!features.count(feature)) potential_features.insert(feature); } else { features.insert(feature); potential_features.erase(feature); if (!needs_more_info_exception) permanent_features.insert(feature); } } if (!needs_more_info_main) active_entries_.push_back(base::checked_cast(ii)); } } needs_more_info_ = permanent_features.size() < features.size() || !potential_features.empty(); return features; } const std::vector& GpuControlList::GetActiveEntries() const { return active_entries_; } std::vector GpuControlList::GetEntryIDsFromIndices( const std::vector& entry_indices) const { std::vector ids; for (auto index : entry_indices) { DCHECK_LT(index, entry_count_); ids.push_back(entries_[index].id); } return ids; } std::vector GpuControlList::GetDisabledExtensions() { std::set disabled_extensions; for (auto index : active_entries_) { DCHECK_LT(index, entry_count_); const Entry& entry = entries_[index]; for (size_t ii = 0; ii < entry.disabled_extension_size; ++ii) { disabled_extensions.insert(entry.disabled_extensions[ii]); } } return std::vector(disabled_extensions.begin(), disabled_extensions.end()); } std::vector GpuControlList::GetDisabledWebGLExtensions() { std::set disabled_webgl_extensions; for (auto index : active_entries_) { DCHECK_LT(index, entry_count_); const Entry& entry = entries_[index]; for (size_t ii = 0; ii < entry.disabled_webgl_extension_size; ++ii) { disabled_webgl_extensions.insert(entry.disabled_webgl_extensions[ii]); } } return std::vector(disabled_webgl_extensions.begin(), disabled_webgl_extensions.end()); } void GpuControlList::GetReasons(base::Value::List& problem_list, const std::string& tag, const std::vector& entries) const { for (auto index : entries) { DCHECK_LT(index, entry_count_); const Entry& entry = entries_[index]; base::Value::Dict problem; problem.Set("description", entry.description); base::Value::List cr_bugs; for (size_t jj = 0; jj < entry.cr_bug_size; ++jj) cr_bugs.Append( base::Int64ToValue(static_cast(entry.cr_bugs[jj]))); problem.Set("crBugs", std::move(cr_bugs)); base::Value::List features = entry.GetFeatureNames(feature_map_); problem.Set("affectedGpuSettings", std::move(features)); DCHECK(tag == "workarounds" || tag == "disabledFeatures"); problem.Set("tag", tag); problem_list.Append(std::move(problem)); } } size_t GpuControlList::num_entries() const { return entry_count_; } uint32_t GpuControlList::max_entry_id() const { return max_entry_id_; } // static GpuControlList::OsType GpuControlList::GetOsType() { #if BUILDFLAG(IS_CHROMEOS) return kOsChromeOS; #elif BUILDFLAG(IS_WIN) return kOsWin; #elif BUILDFLAG(IS_ANDROID) return kOsAndroid; #elif BUILDFLAG(IS_FUCHSIA) return kOsFuchsia; #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_OPENBSD) return kOsLinux; #elif BUILDFLAG(IS_MAC) return kOsMacosx; #else return kOsAny; #endif } void GpuControlList::AddSupportedFeature( const std::string& feature_name, int feature_id) { feature_map_[feature_id] = feature_name; } // static bool GpuControlList::AreEntryIndicesValid( const std::vector& entry_indices, size_t total_entries) { for (auto index : entry_indices) { if (index >= total_entries) return false; } return true; } } // namespace gpu