| // Copyright 2016 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/service/service_utils.h" | |
| #include <cmath> | |
| #include <string> | |
| #include <string_view> | |
| #include "base/command_line.h" | |
| #include "base/logging.h" | |
| #include "base/memory_coordinator/memory_consumer.h" | |
| #include "base/memory_coordinator/utils.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_split.h" | |
| #include "base/strings/string_util.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_utils.h" | |
| #include "gpu/command_buffer/service/context_group.h" | |
| #include "gpu/command_buffer/service/gpu_switches.h" | |
| #include "gpu/config/gpu_finch_features.h" | |
| #include "gpu/config/gpu_switches.h" | |
| #include "skia/buildflags.h" | |
| #include "ui/gl/gl_bindings.h" | |
| #include "ui/gl/gl_features.h" | |
| #include "ui/gl/gl_implementation.h" | |
| #include "ui/gl/gl_surface_egl.h" | |
| #include "ui/gl/gl_switches.h" | |
| #include "ui/gl/gl_utils.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| bool GetUintFromSwitch(const base::CommandLine* command_line, | |
| std::string_view switch_string, | |
| uint32_t* value) { | |
| if (!command_line->HasSwitch(switch_string)) { | |
| return false; | |
| } | |
| std::string switch_value(command_line->GetSwitchValueASCII(switch_string)); | |
| return base::StringToUint(switch_value, value); | |
| } | |
| // Parse the value of --use-vulkan from the command line. If unspecified and | |
| // features::kVulkan is enabled (GrContext is going to use vulkan), default to | |
| // the native implementation. | |
| VulkanImplementationName ParseVulkanImplementationName( | |
| const base::CommandLine* command_line) { | |
| #if BUILDFLAG(IS_ANDROID) | |
| if (command_line->HasSwitch(switches::kWebViewDrawFunctorUsesVulkan)) { | |
| return VulkanImplementationName::kForcedNative; | |
| } | |
| #endif | |
| if (command_line->HasSwitch(switches::kUseVulkan)) { | |
| auto value = command_line->GetSwitchValueASCII(switches::kUseVulkan); | |
| if (value.empty() || value == switches::kVulkanImplementationNameNative) { | |
| return VulkanImplementationName::kForcedNative; | |
| } else if (value == switches::kVulkanImplementationNameSwiftshader) { | |
| return VulkanImplementationName::kSwiftshader; | |
| } | |
| } | |
| if (features::IsUsingVulkan()) { | |
| // If the vulkan feature is enabled from command line, we will force to use | |
| // vulkan even if it is blocklisted. | |
| return base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine( | |
| features::kVulkan.name, | |
| base::FeatureList::OVERRIDE_ENABLE_FEATURE) | |
| ? VulkanImplementationName::kForcedNative | |
| : VulkanImplementationName::kNative; | |
| } | |
| // GrContext is not going to use Vulkan. | |
| return VulkanImplementationName::kNone; | |
| } | |
| WebGPUAdapterName ParseWebGPUAdapterName( | |
| const base::CommandLine* command_line) { | |
| if (command_line->HasSwitch(switches::kUseWebGPUAdapter)) { | |
| auto value = command_line->GetSwitchValueASCII(switches::kUseWebGPUAdapter); | |
| static const struct { | |
| const char* name; | |
| WebGPUAdapterName value; | |
| } kAdapterNames[] = { | |
| {"", WebGPUAdapterName::kDefault}, | |
| {"default", WebGPUAdapterName::kDefault}, | |
| {"d3d11", WebGPUAdapterName::kD3D11}, | |
| {"opengles", WebGPUAdapterName::kOpenGLES}, | |
| {"swiftshader", WebGPUAdapterName::kSwiftShader}, | |
| }; | |
| for (const auto& adapter_name : kAdapterNames) { | |
| if (value == adapter_name.name) { | |
| return adapter_name.value; | |
| } | |
| } | |
| DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUAdapter << "=" | |
| << value << "."; | |
| } | |
| return WebGPUAdapterName::kDefault; | |
| } | |
| WebGPUPowerPreference ParseWebGPUPowerPreference( | |
| const base::CommandLine* command_line) { | |
| if (command_line->HasSwitch(switches::kUseWebGPUPowerPreference)) { | |
| auto value = | |
| command_line->GetSwitchValueASCII(switches::kUseWebGPUPowerPreference); | |
| if (value.empty()) { | |
| return WebGPUPowerPreference::kNone; | |
| } else if (value == "none") { | |
| return WebGPUPowerPreference::kNone; | |
| } else if (value == "default-low-power") { | |
| return WebGPUPowerPreference::kDefaultLowPower; | |
| } else if (value == "default-high-performance") { | |
| return WebGPUPowerPreference::kDefaultHighPerformance; | |
| } else if (value == "force-low-power") { | |
| return WebGPUPowerPreference::kForceLowPower; | |
| } else if (value == "force-high-performance") { | |
| return WebGPUPowerPreference::kForceHighPerformance; | |
| } else { | |
| DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUPowerPreference | |
| << "=" << value << "."; | |
| } | |
| } | |
| return WebGPUPowerPreference::kNone; | |
| } | |
| } // namespace | |
| gl::GLContextAttribs GenerateGLContextAttribsForDecoder( | |
| ContextType context_type, | |
| gl::GpuPreference gpu_preference, | |
| const ContextGroup* context_group) { | |
| gl::GLContextAttribs attribs; | |
| attribs.gpu_preference = gpu_preference; | |
| if (context_group->use_passthrough_cmd_decoder()) { | |
| attribs.webgl_compatibility_context = IsWebGLContextType(context_type); | |
| attribs.enable_all_extensions = false; | |
| attribs.hardened_context = !attribs.webgl_compatibility_context; | |
| // Always use the global texture and semaphore share group for the | |
| // passthrough command decoder | |
| attribs.global_texture_share_group = true; | |
| attribs.global_semaphore_share_group = true; | |
| attribs.robust_resource_initialization = true; | |
| attribs.robust_buffer_access = true; | |
| attribs.allow_client_arrays = false; | |
| // Request a specific context version instead of always 3.0 | |
| if (IsWebGL2OrES3ContextType(context_type)) { | |
| attribs.client_major_es_version = 3; | |
| attribs.client_minor_es_version = 0; | |
| attribs.allow_es_version_fallback = | |
| !features::ShouldFallbackToSWIfGLES3NotSupported(); | |
| } else { | |
| DCHECK(IsWebGL1OrES2ContextType(context_type)); | |
| attribs.client_major_es_version = 2; | |
| attribs.client_minor_es_version = 0; | |
| attribs.allow_es_version_fallback = false; | |
| } | |
| } else { | |
| attribs.client_major_es_version = 3; | |
| attribs.client_minor_es_version = 0; | |
| } | |
| return attribs; | |
| } | |
| gl::GLContextAttribs GenerateGLContextAttribsForCompositor( | |
| bool use_passthrough_cmd_decoder) { | |
| gl::GLContextAttribs attribs; | |
| if (use_passthrough_cmd_decoder) { | |
| // Always use the global texture and semaphore share group for the | |
| // passthrough command decoder | |
| attribs.global_texture_share_group = true; | |
| attribs.global_semaphore_share_group = true; | |
| attribs.passthrough_shaders = features::IsANGLEPassthroughShadersAllowed(); | |
| // Disable resource initialization and buffer bounds checks for trusted | |
| // contexts. | |
| attribs.robust_resource_initialization = false; | |
| attribs.robust_buffer_access = false; | |
| attribs.allow_client_arrays = true; | |
| } | |
| attribs.client_major_es_version = 3; | |
| attribs.client_minor_es_version = 0; | |
| return attribs; | |
| } | |
| bool UsePassthroughCommandDecoder(const base::CommandLine* command_line) { | |
| return gl::UsePassthroughCommandDecoder(command_line); | |
| } | |
| GpuPreferences ParseGpuPreferences(const base::CommandLine* command_line) { | |
| GpuPreferences gpu_preferences; | |
| gpu_preferences.compile_shader_always_succeeds = | |
| command_line->HasSwitch(switches::kCompileShaderAlwaysSucceeds); | |
| gpu_preferences.disable_gl_error_limit = | |
| command_line->HasSwitch(switches::kDisableGLErrorLimit); | |
| gpu_preferences.disable_glsl_translator = | |
| command_line->HasSwitch(switches::kDisableGLSLTranslator); | |
| gpu_preferences.disable_shader_name_hashing = | |
| command_line->HasSwitch(switches::kDisableShaderNameHashing); | |
| gpu_preferences.enable_gpu_command_logging = | |
| command_line->HasSwitch(switches::kEnableGPUCommandLogging); | |
| gpu_preferences.enable_gpu_debugging = | |
| command_line->HasSwitch(switches::kEnableGPUDebugging); | |
| gpu_preferences.enable_gpu_service_logging_gpu = | |
| command_line->HasSwitch(switches::kEnableGPUServiceLoggingGPU); | |
| gpu_preferences.enable_gpu_driver_debug_logging = | |
| command_line->HasSwitch(switches::kEnableGPUDriverDebugLogging); | |
| gpu_preferences.disable_gpu_program_cache = | |
| !features::IsShaderDiskCacheEnabled(command_line); | |
| gpu_preferences.enforce_gl_minimums = | |
| command_line->HasSwitch(switches::kEnforceGLMinimums); | |
| if (GetUintFromSwitch( | |
| command_line, switches::kForceGpuMemDiscardableLimitMb, | |
| &gpu_preferences.force_gpu_mem_discardable_limit_bytes)) { | |
| gpu_preferences.force_gpu_mem_discardable_limit_bytes *= 1024 * 1024; | |
| } | |
| GetUintFromSwitch(command_line, switches::kForceMaxTextureSize, | |
| &gpu_preferences.force_max_texture_size); | |
| if (GetUintFromSwitch(command_line, switches::kGpuProgramCacheSizeKb, | |
| &gpu_preferences.gpu_program_cache_size)) { | |
| gpu_preferences.gpu_program_cache_size *= 1024; | |
| } | |
| gpu_preferences.disable_gpu_shader_disk_cache = | |
| command_line->HasSwitch(switches::kDisableGpuShaderDiskCache); | |
| gpu_preferences.enable_threaded_texture_mailboxes = | |
| command_line->HasSwitch(switches::kEnableThreadedTextureMailboxes); | |
| gpu_preferences.gl_shader_interm_output = | |
| command_line->HasSwitch(switches::kGLShaderIntermOutput); | |
| gpu_preferences.enable_gpu_service_logging = | |
| command_line->HasSwitch(switches::kEnableGPUServiceLogging); | |
| gpu_preferences.enable_gpu_service_tracing = | |
| command_line->HasSwitch(switches::kEnableGPUServiceTracing); | |
| gpu_preferences.use_passthrough_cmd_decoder = | |
| gpu::gles2::UsePassthroughCommandDecoder(command_line); | |
| if (command_line->HasSwitch(switches::kIgnoreGpuBlocklist)) { | |
| std::string value = | |
| command_line->GetSwitchValueASCII(switches::kIgnoreGpuBlocklist); | |
| if (value.empty()) { | |
| gpu_preferences.ignore_gpu_blocklist = true; | |
| } else { | |
| auto pieces = base::SplitStringPiece(value, ",", base::TRIM_WHITESPACE, | |
| base::SPLIT_WANT_NONEMPTY); | |
| std::vector<uint32_t> ignored_entries; | |
| bool success = true; | |
| for (const auto& piece : pieces) { | |
| uint32_t entry_id; | |
| if (!base::StringToUint(piece, &entry_id)) { | |
| success = false; | |
| break; | |
| } | |
| ignored_entries.push_back(entry_id); | |
| } | |
| if (success) { | |
| gpu_preferences.ignored_gpu_blocklist_entries = | |
| std::move(ignored_entries); | |
| } | |
| } | |
| } | |
| gpu_preferences.enable_webgpu = | |
| command_line->HasSwitch(switches::kEnableUnsafeWebGPU) || | |
| base::FeatureList::IsEnabled(features::kWebGPUService); | |
| gpu_preferences.enable_unsafe_webgpu = | |
| command_line->HasSwitch(switches::kEnableUnsafeWebGPU); | |
| gpu_preferences.enable_webgpu_developer_features = | |
| command_line->HasSwitch(switches::kEnableWebGPUDeveloperFeatures); | |
| gpu_preferences.use_webgpu_adapter = ParseWebGPUAdapterName(command_line); | |
| gpu_preferences.use_webgpu_power_preference = | |
| ParseWebGPUPowerPreference(command_line); | |
| gpu_preferences.force_webgpu_compat = | |
| command_line->HasSwitch(switches::kForceWebGPUCompat); | |
| if (command_line->HasSwitch(switches::kEnableDawnBackendValidation)) { | |
| auto value = command_line->GetSwitchValueASCII( | |
| switches::kEnableDawnBackendValidation); | |
| if (value.empty() || value == "full") { | |
| gpu_preferences.enable_dawn_backend_validation = | |
| DawnBackendValidationLevel::kFull; | |
| } else if (value == "partial") { | |
| gpu_preferences.enable_dawn_backend_validation = | |
| DawnBackendValidationLevel::kPartial; | |
| } | |
| } | |
| if (command_line->HasSwitch(switches::kEnableDawnFeatures)) { | |
| gpu_preferences.enabled_dawn_features_list = base::SplitString( | |
| command_line->GetSwitchValueASCII(switches::kEnableDawnFeatures), ",", | |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
| } | |
| if (command_line->HasSwitch(switches::kDisableDawnFeatures)) { | |
| gpu_preferences.disabled_dawn_features_list = base::SplitString( | |
| command_line->GetSwitchValueASCII(switches::kDisableDawnFeatures), ",", | |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
| } | |
| gpu_preferences.gr_context_type = ParseDefaultGrContextType(command_line); | |
| if (gpu_preferences.gr_context_type == GrContextType::kGraphiteDawn || | |
| gpu_preferences.gr_context_type == GrContextType::kVulkan) { | |
| // Set the default fallback type to GL so that the tests can fall back to GL | |
| // without GpuDataManagerImplPrivate. | |
| gpu_preferences.fallback_gr_context_types = {GrContextType::kGL}; | |
| } else { | |
| gpu_preferences.fallback_gr_context_types.clear(); | |
| } | |
| gpu_preferences.use_vulkan = features::IsUsingVulkan() | |
| ? ParseVulkanImplementationName(command_line) | |
| : VulkanImplementationName::kNone; | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| // Vulkan Surface is not used on Fuchsia. | |
| gpu_preferences.disable_vulkan_surface = true; | |
| #else | |
| gpu_preferences.disable_vulkan_surface = | |
| command_line->HasSwitch(switches::kDisableVulkanSurface); | |
| #endif | |
| gpu_preferences.enable_gpu_blocked_time_metric = | |
| command_line->HasSwitch(switches::kEnableGpuBlockedTime); | |
| return gpu_preferences; | |
| } | |
| GrContextType ParseDefaultGrContextType(const base::CommandLine* command_line) { | |
| #if BUILDFLAG(SKIA_USE_DAWN) | |
| if (base::FeatureList::IsEnabled(features::kLateGraphiteFeatureCheck)) { | |
| // With late check, only the disable flag gates Graphite; the full | |
| // feature/device check is deferred to the GPU process post-blocklist. | |
| if (!command_line->HasSwitch(switches::kDisableSkiaGraphite)) { | |
| if (command_line->HasSwitch(switches::kSkiaGraphiteVulkanBackend)) { | |
| return GrContextType::kGraphiteVulkan; | |
| } | |
| return GrContextType::kGraphiteDawn; | |
| } | |
| } else { | |
| if (features::IsSkiaGraphiteEnabled(command_line)) { | |
| if (command_line->HasSwitch(switches::kSkiaGraphiteVulkanBackend)) { | |
| return GrContextType::kGraphiteVulkan; | |
| } | |
| auto value = | |
| command_line->GetSwitchValueASCII(switches::kSkiaGraphiteDawnBackend); | |
| if (value.empty() || value == switches::kSkiaGraphiteDawnBackendD3D11 || | |
| value == switches::kSkiaGraphiteDawnBackendD3D12 || | |
| value == switches::kSkiaGraphiteDawnBackendMetal || | |
| value == switches::kSkiaGraphiteDawnBackendOpenGLES || | |
| value == switches::kSkiaGraphiteDawnBackendSwiftshader || | |
| value == switches::kSkiaGraphiteDawnBackendVulkan) { | |
| return GrContextType::kGraphiteDawn; | |
| } | |
| LOG(ERROR) | |
| << "Skia Graphite enabled but no valid Dawn backend found for \"" | |
| << value << "\" - falling back to Ganesh!"; | |
| } | |
| } | |
| #endif // BUILDFLAG(SKIA_USE_DAWN) | |
| if (features::IsUsingVulkan()) { | |
| return GrContextType::kVulkan; | |
| } | |
| return GrContextType::kGL; | |
| } | |
| bool MSAAIsSlow(const GpuDriverBugWorkarounds& workarounds) { | |
| // Only query the kEnableMSAAOnNewIntelGPUs feature flag if the host device | |
| // is affected by the experiment (i.e. is a new Intel GPU). | |
| // This is to avoid activating the experiment on hosts that are irrelevant | |
| // to the study in order to boost statistical power. | |
| bool affected_by_experiment = | |
| workarounds.msaa_is_slow && !workarounds.msaa_is_slow_2; | |
| return affected_by_experiment ? !base::FeatureList::IsEnabled( | |
| features::kEnableMSAAOnNewIntelGPUs) | |
| : workarounds.msaa_is_slow; | |
| } | |
| } // namespace gles2 | |
| namespace { | |
| // Multiplier policy for kAggressiveShaderCacheLimits enabled. | |
| double GetAggressiveMemoryLimitMultiplier(base::MemoryLimit memory_limit) { | |
| #if BUILDFLAG(IS_ANDROID) | |
| // Android ignores pressure notifications in aggressive mode. | |
| return 1.0; | |
| #else | |
| // Desktop ignores pressure above the Moderate threshold (50%). | |
| if (memory_limit >= base::MemoryLimit::ModeratePressureThreshold()) { | |
| return 1.0; | |
| } | |
| // Interpolate multiplier from 1.0 down to 0.25 between Moderate (50%) and | |
| // Critical (0%). | |
| double t = static_cast<double>( | |
| base::MemoryLimit::ModeratePressureThreshold().percent() - | |
| memory_limit.percent()) / | |
| base::MemoryLimit::ModeratePressureThreshold().percent(); | |
| return std::lerp(1.0, 0.25, t); | |
| #endif | |
| } | |
| // Multiplier policy for kAggressiveShaderCacheLimits disabled. | |
| double GetDefaultMemoryLimitMultiplier(base::MemoryLimit memory_limit) { | |
| // Scale quadratically (e.g., 25% cache size at 50% memory limit). | |
| double ratio = memory_limit.ratio(); | |
| return ratio * ratio; | |
| } | |
| } // namespace | |
| size_t UpdateShaderCacheSizeOnMemoryPressure( | |
| size_t max_cache_size, | |
| base::MemoryPressureLevel memory_pressure_level) { | |
| base::MemoryLimit memory_limit = base::MemoryLimit::NoPressureThreshold(); | |
| switch (memory_pressure_level) { | |
| case base::MEMORY_PRESSURE_LEVEL_NONE: | |
| memory_limit = base::MemoryLimit::NoPressureThreshold(); | |
| break; | |
| case base::MEMORY_PRESSURE_LEVEL_MODERATE: | |
| memory_limit = base::MemoryLimit::ModeratePressureThreshold(); | |
| break; | |
| case base::MEMORY_PRESSURE_LEVEL_CRITICAL: | |
| memory_limit = base::MemoryLimit::CriticalPressureThreshold(); | |
| break; | |
| } | |
| return UpdateShaderCacheSizeOnMemoryLimit(max_cache_size, memory_limit); | |
| } | |
| size_t UpdateShaderCacheSizeOnMemoryLimit(size_t max_cache_size, | |
| base::MemoryLimit memory_limit) { | |
| // Handle limits greater than 100%. Scales linearly. | |
| if (memory_limit > base::MemoryLimit::NoPressureThreshold()) { | |
| return memory_limit.Scale(max_cache_size); | |
| } | |
| double multiplier = | |
| base::FeatureList::IsEnabled(features::kAggressiveShaderCacheLimits) | |
| ? GetAggressiveMemoryLimitMultiplier(memory_limit) | |
| : GetDefaultMemoryLimitMultiplier(memory_limit); | |
| return static_cast<size_t>(max_cache_size * multiplier); | |
| } | |
| } // namespace gpu |