| // 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 "content/browser/gpu/gpu_data_manager_impl_private.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/post_task.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/version.h" |
| #include "build/build_config.h" |
| #include "cc/base/switches.h" |
| #include "components/viz/common/features.h" |
| #include "content/browser/gpu/gpu_memory_buffer_manager_singleton.h" |
| #include "content/browser/gpu/gpu_process_host.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/gpu_data_manager_observer.h" |
| #include "content/public/browser/gpu_utils.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_constants.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "gpu/command_buffer/service/gpu_switches.h" |
| #include "gpu/config/gpu_blocklist.h" |
| #include "gpu/config/gpu_driver_bug_list.h" |
| #include "gpu/config/gpu_driver_bug_workaround_type.h" |
| #include "gpu/config/gpu_feature_info.h" |
| #include "gpu/config/gpu_feature_type.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "gpu/config/gpu_info_collector.h" |
| #include "gpu/config/gpu_preferences.h" |
| #include "gpu/config/gpu_switches.h" |
| #include "gpu/config/gpu_util.h" |
| #include "gpu/config/software_rendering_list_autogen.h" |
| #include "gpu/ipc/common/memory_stats.h" |
| #include "gpu/ipc/host/gpu_memory_buffer_support.h" |
| #include "gpu/ipc/host/shader_disk_cache.h" |
| #include "media/media_buildflags.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/gfx/switches.h" |
| #include "ui/gl/buildflags.h" |
| #include "ui/gl/gl_implementation.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/gl/gpu_preference.h" |
| #include "ui/gl/gpu_switching_manager.h" |
| |
| #if defined(OS_ANDROID) |
| #include "base/android/application_status_listener.h" |
| #endif |
| #if defined(USE_OZONE) |
| #include "ui/ozone/public/ozone_platform.h" |
| #endif |
| #if defined(OS_MACOSX) |
| #include <ApplicationServices/ApplicationServices.h> |
| #endif // OS_MACOSX |
| #if defined(OS_WIN) |
| #include "base/win/windows_version.h" |
| #endif // OS_WIN |
| |
| namespace content { |
| |
| namespace { |
| |
| #if defined(OS_ANDROID) |
| // NOINLINE to ensure this function is used in crash reports. |
| NOINLINE void FatalGpuProcessLaunchFailureOnBackground() { |
| if (!base::android::ApplicationStatusListener::HasVisibleActivities()) { |
| // We expect the platform to aggressively kill services when the app is |
| // backgrounded. A FATAL error creates a dialog notifying users that the |
| // app has crashed which doesn't look good. So we use SIGKILL instead. But |
| // still do a crash dump for 1% cases to make sure we're not regressing this |
| // case. |
| if (base::RandInt(1, 100) == 1) |
| base::debug::DumpWithoutCrashing(); |
| kill(getpid(), SIGKILL); |
| } |
| } |
| #endif |
| |
| #if defined(OS_WIN) |
| int GetGpuBlacklistHistogramValueWin(gpu::GpuFeatureStatus status) { |
| // The enums are defined as: |
| // Enabled VERSION_PRE_XP = 0, |
| // Blacklisted VERSION_PRE_XP = 1, |
| // Disabled VERSION_PRE_XP = 2, |
| // Software VERSION_PRE_XP = 3, |
| // Unknown VERSION_PRE_XP = 4, |
| // Enabled VERSION_XP = 5, |
| // ... |
| static const base::win::Version version = base::win::GetVersion(); |
| if (version == base::win::Version::WIN_LAST) |
| return -1; |
| DCHECK_NE(gpu::kGpuFeatureStatusMax, status); |
| int entry_index = static_cast<int>(version) * gpu::kGpuFeatureStatusMax; |
| return entry_index + static_cast<int>(status); |
| } |
| #endif // OS_WIN |
| |
| // Send UMA histograms about the enabled features and GPU properties. |
| void UpdateFeatureStats(const gpu::GpuFeatureInfo& gpu_feature_info) { |
| // Update applied entry stats. |
| std::unique_ptr<gpu::GpuBlocklist> blacklist(gpu::GpuBlocklist::Create()); |
| DCHECK(blacklist.get() && blacklist->max_entry_id() > 0); |
| uint32_t max_entry_id = blacklist->max_entry_id(); |
| // Use entry 0 to capture the total number of times that data |
| // was recorded in this histogram in order to have a convenient |
| // denominator to compute blacklist percentages for the rest of the |
| // entries. |
| UMA_HISTOGRAM_EXACT_LINEAR("GPU.BlacklistTestResultsPerEntry", 0, |
| max_entry_id + 1); |
| if (!gpu_feature_info.applied_gpu_blacklist_entries.empty()) { |
| std::vector<uint32_t> entry_ids = blacklist->GetEntryIDsFromIndices( |
| gpu_feature_info.applied_gpu_blacklist_entries); |
| DCHECK_EQ(gpu_feature_info.applied_gpu_blacklist_entries.size(), |
| entry_ids.size()); |
| for (auto id : entry_ids) { |
| DCHECK_GE(max_entry_id, id); |
| UMA_HISTOGRAM_EXACT_LINEAR("GPU.BlacklistTestResultsPerEntry", id, |
| max_entry_id + 1); |
| } |
| } |
| |
| // Update feature status stats. |
| const base::CommandLine& command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| const gpu::GpuFeatureType kGpuFeatures[] = { |
| gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS, |
| gpu::GPU_FEATURE_TYPE_ACCELERATED_GL, |
| gpu::GPU_FEATURE_TYPE_GPU_RASTERIZATION, |
| gpu::GPU_FEATURE_TYPE_OOP_RASTERIZATION, |
| gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL, |
| gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL2}; |
| const std::string kGpuBlacklistFeatureHistogramNames[] = { |
| "GPU.BlacklistFeatureTestResults.Accelerated2dCanvas", |
| "GPU.BlacklistFeatureTestResults.GpuCompositing", |
| "GPU.BlacklistFeatureTestResults.GpuRasterization", |
| "GPU.BlacklistFeatureTestResults.OopRasterization", |
| "GPU.BlacklistFeatureTestResults.Webgl", |
| "GPU.BlacklistFeatureTestResults.Webgl2"}; |
| const bool kGpuFeatureUserFlags[] = { |
| command_line.HasSwitch(switches::kDisableAccelerated2dCanvas), |
| command_line.HasSwitch(switches::kDisableGpu), |
| command_line.HasSwitch(switches::kDisableGpuRasterization), |
| command_line.HasSwitch(switches::kDisableOopRasterization), |
| command_line.HasSwitch(switches::kDisableWebGL), |
| (command_line.HasSwitch(switches::kDisableWebGL) || |
| command_line.HasSwitch(switches::kDisableWebGL2))}; |
| #if defined(OS_WIN) |
| const std::string kGpuBlacklistFeatureHistogramNamesWin[] = { |
| "GPU.BlacklistFeatureTestResultsWindows2.Accelerated2dCanvas", |
| "GPU.BlacklistFeatureTestResultsWindows2.GpuCompositing", |
| "GPU.BlacklistFeatureTestResultsWindows2.GpuRasterization", |
| "GPU.BlacklistFeatureTestResultsWindows2.OopRasterization", |
| "GPU.BlacklistFeatureTestResultsWindows2.Webgl", |
| "GPU.BlacklistFeatureTestResultsWindows2.Webgl2"}; |
| #endif |
| const size_t kNumFeatures = |
| sizeof(kGpuFeatures) / sizeof(gpu::GpuFeatureType); |
| for (size_t i = 0; i < kNumFeatures; ++i) { |
| // We can't use UMA_HISTOGRAM_ENUMERATION here because the same name is |
| // expected if the macro is used within a loop. |
| gpu::GpuFeatureStatus value = |
| gpu_feature_info.status_values[kGpuFeatures[i]]; |
| if (value == gpu::kGpuFeatureStatusEnabled && kGpuFeatureUserFlags[i]) |
| value = gpu::kGpuFeatureStatusDisabled; |
| base::HistogramBase* histogram_pointer = base::LinearHistogram::FactoryGet( |
| kGpuBlacklistFeatureHistogramNames[i], 1, gpu::kGpuFeatureStatusMax, |
| gpu::kGpuFeatureStatusMax + 1, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| histogram_pointer->Add(value); |
| #if defined(OS_WIN) |
| int value_win = GetGpuBlacklistHistogramValueWin(value); |
| if (value_win >= 0) { |
| int32_t max_sample = static_cast<int32_t>(base::win::Version::WIN_LAST) * |
| gpu::kGpuFeatureStatusMax; |
| histogram_pointer = base::LinearHistogram::FactoryGet( |
| kGpuBlacklistFeatureHistogramNamesWin[i], 1, max_sample, |
| max_sample + 1, base::HistogramBase::kUmaTargetedHistogramFlag); |
| histogram_pointer->Add(value_win); |
| } |
| #endif |
| } |
| } |
| |
| void UpdateDriverBugListStats(const gpu::GpuFeatureInfo& gpu_feature_info) { |
| // Use entry 0 to capture the total number of times that data was recorded |
| // in this histogram in order to have a convenient denominator to compute |
| // driver bug list percentages for the rest of the entries. |
| base::UmaHistogramSparse("GPU.DriverBugTestResultsPerEntry", 0); |
| |
| if (!gpu_feature_info.applied_gpu_driver_bug_list_entries.empty()) { |
| std::unique_ptr<gpu::GpuDriverBugList> bug_list( |
| gpu::GpuDriverBugList::Create()); |
| DCHECK(bug_list.get() && bug_list->max_entry_id() > 0); |
| std::vector<uint32_t> entry_ids = bug_list->GetEntryIDsFromIndices( |
| gpu_feature_info.applied_gpu_driver_bug_list_entries); |
| DCHECK_EQ(gpu_feature_info.applied_gpu_driver_bug_list_entries.size(), |
| entry_ids.size()); |
| for (auto id : entry_ids) { |
| DCHECK_GE(bug_list->max_entry_id(), id); |
| base::UmaHistogramSparse("GPU.DriverBugTestResultsPerEntry", id); |
| } |
| } |
| } |
| |
| #if defined(OS_MACOSX) |
| void DisplayReconfigCallback(CGDirectDisplayID display, |
| CGDisplayChangeSummaryFlags flags, |
| void* gpu_data_manager) { |
| if (flags == kCGDisplayBeginConfigurationFlag) |
| return; // This call contains no information about the display change |
| |
| GpuDataManagerImpl* manager = |
| reinterpret_cast<GpuDataManagerImpl*>(gpu_data_manager); |
| DCHECK(manager); |
| |
| bool gpu_changed = false; |
| if (flags & kCGDisplayAddFlag) { |
| gpu::GPUInfo gpu_info; |
| if (gpu::CollectBasicGraphicsInfo(&gpu_info)) { |
| gpu_changed = manager->UpdateActiveGpu(gpu_info.active_gpu().vendor_id, |
| gpu_info.active_gpu().device_id); |
| } |
| } |
| |
| if (gpu_changed) |
| manager->HandleGpuSwitch(); |
| } |
| #endif // OS_MACOSX |
| |
| // Block all domains' use of 3D APIs for this many milliseconds if |
| // approaching a threshold where system stability might be compromised. |
| const int64_t kBlockAllDomainsMs = 10000; |
| const int kNumResetsWithinDuration = 1; |
| |
| // Enums for UMA histograms. |
| enum BlockStatusHistogram { |
| BLOCK_STATUS_NOT_BLOCKED, |
| BLOCK_STATUS_SPECIFIC_DOMAIN_BLOCKED, |
| BLOCK_STATUS_ALL_DOMAINS_BLOCKED, |
| BLOCK_STATUS_MAX |
| }; |
| |
| void OnVideoMemoryUsageStats( |
| GpuDataManager::VideoMemoryUsageStatsCallback callback, |
| const gpu::VideoMemoryUsageStats& stats) { |
| base::PostTask(FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(std::move(callback), stats)); |
| } |
| |
| void RequestVideoMemoryUsageStats( |
| GpuDataManager::VideoMemoryUsageStatsCallback callback, |
| GpuProcessHost* host) { |
| if (!host) |
| return; |
| host->gpu_service()->GetVideoMemoryUsageStats( |
| base::BindOnce(&OnVideoMemoryUsageStats, std::move(callback))); |
| } |
| |
| // Determines if SwiftShader is available as a fallback for WebGL. |
| bool SwiftShaderAllowed() { |
| #if !BUILDFLAG(ENABLE_SWIFTSHADER) |
| return false; |
| #else |
| return !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableSoftwareRasterizer); |
| #endif |
| } |
| |
| // These values are logged to UMA. Entries should not be renumbered and numeric |
| // values should never be reused. Please keep in sync with "CompositingMode" in |
| // src/tools/metrics/histograms/enums.xml. |
| enum class CompositingMode { |
| kSoftware = 0, |
| kGL = 1, |
| kVulkan = 2, |
| kMetal = 3, |
| kMaxValue = kMetal |
| }; |
| |
| } // anonymous namespace |
| |
| GpuDataManagerImplPrivate::GpuDataManagerImplPrivate(GpuDataManagerImpl* owner) |
| : owner_(owner), |
| observer_list_(base::MakeRefCounted<GpuDataManagerObserverList>()) { |
| DCHECK(owner_); |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kDisableGpu)) { |
| DisableHardwareAcceleration(); |
| } else if (command_line->HasSwitch(switches::kDisableGpuCompositing)) { |
| SetGpuCompositingDisabled(); |
| } |
| |
| if (command_line->HasSwitch(switches::kSingleProcess) || |
| command_line->HasSwitch(switches::kInProcessGPU)) { |
| AppendGpuCommandLine(command_line, GPU_PROCESS_KIND_SANDBOXED); |
| } |
| |
| #if defined(OS_MACOSX) |
| CGDisplayRegisterReconfigurationCallback(DisplayReconfigCallback, owner_); |
| #endif // OS_MACOSX |
| |
| // For testing only. |
| if (command_line->HasSwitch(switches::kDisableDomainBlockingFor3DAPIs)) |
| domain_blocking_enabled_ = false; |
| |
| // Do not change kTimerInterval without also changing the UMA histogram name, |
| // as histogram data from before/after the change will not be comparable. |
| constexpr base::TimeDelta kTimerInterval = base::TimeDelta::FromMinutes(5); |
| compositing_mode_timer_.Start( |
| FROM_HERE, kTimerInterval, this, |
| &GpuDataManagerImplPrivate::RecordCompositingMode); |
| } |
| |
| GpuDataManagerImplPrivate::~GpuDataManagerImplPrivate() { |
| #if defined(OS_MACOSX) |
| CGDisplayRemoveReconfigurationCallback(DisplayReconfigCallback, owner_); |
| #endif |
| } |
| |
| void GpuDataManagerImplPrivate::BlacklistWebGLForTesting() { |
| // This function is for testing only, so disable histograms. |
| update_histograms_ = false; |
| |
| gpu::GpuFeatureInfo gpu_feature_info; |
| for (int ii = 0; ii < gpu::NUMBER_OF_GPU_FEATURE_TYPES; ++ii) { |
| if (ii == static_cast<int>(gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL)) |
| gpu_feature_info.status_values[ii] = gpu::kGpuFeatureStatusBlacklisted; |
| else |
| gpu_feature_info.status_values[ii] = gpu::kGpuFeatureStatusEnabled; |
| } |
| UpdateGpuFeatureInfo(gpu_feature_info, base::nullopt); |
| NotifyGpuInfoUpdate(); |
| } |
| |
| gpu::GPUInfo GpuDataManagerImplPrivate::GetGPUInfo() const { |
| return gpu_info_; |
| } |
| |
| gpu::GPUInfo GpuDataManagerImplPrivate::GetGPUInfoForHardwareGpu() const { |
| return gpu_info_for_hardware_gpu_; |
| } |
| |
| bool GpuDataManagerImplPrivate::GpuAccessAllowed(std::string* reason) const { |
| switch (gpu_mode_) { |
| case gpu::GpuMode::HARDWARE_ACCELERATED: |
| return true; |
| case gpu::GpuMode::SWIFTSHADER: |
| DCHECK(SwiftShaderAllowed()); |
| return true; |
| default: |
| if (reason) { |
| // If SwiftShader is allowed, then we are here because it was blocked. |
| if (SwiftShaderAllowed()) { |
| *reason = "GPU process crashed too many times with SwiftShader."; |
| } else { |
| *reason = "GPU access is disabled "; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableGpu)) |
| *reason += "through commandline switch --disable-gpu."; |
| else if (hardware_disabled_by_fallback_) |
| *reason += "due to frequent crashes."; |
| else |
| *reason += "in chrome://settings."; |
| } |
| } |
| return false; |
| } |
| } |
| |
| bool GpuDataManagerImplPrivate::GpuProcessStartAllowed() const { |
| if (GpuAccessAllowed(nullptr)) |
| return true; |
| |
| #if !defined(TOOLKIT_QT) |
| #if defined(USE_X11) || defined(OS_MACOSX) || defined(OS_FUCHSIA) |
| // If GPU access is disabled with OOP-D we run the display compositor in: |
| // Browser process: Windows |
| // GPU process: Linux, Mac and Fuchsia |
| // N/A: Android and Chrome OS (GPU access can't be disabled) |
| if (features::IsVizDisplayCompositorEnabled()) |
| return true; |
| #endif |
| #endif |
| |
| return false; |
| } |
| |
| void GpuDataManagerImplPrivate::RequestDxdiagDx12VulkanGpuInfoIfNeeded( |
| GpuInfoRequest request, |
| bool delayed) { |
| if (request & kGpuInfoRequestDxDiag) { |
| // Delay is not supported in DxDiag request |
| DCHECK(!delayed); |
| RequestDxDiagNodeData(); |
| } |
| |
| if (request & kGpuInfoRequestDx12Vulkan) |
| RequestGpuSupportedRuntimeVersion(delayed); |
| } |
| |
| void GpuDataManagerImplPrivate::RequestDxDiagNodeData() { |
| #if defined(OS_WIN) |
| if (gpu_info_dx_diag_requested_) |
| return; |
| gpu_info_dx_diag_requested_ = true; |
| GpuProcessHost::CallOnIO( |
| GPU_PROCESS_KIND_UNSANDBOXED_NO_GL, true /* force_create */, |
| base::BindOnce([](GpuProcessHost* host) { |
| if (!host) { |
| GpuDataManagerImpl::GetInstance()->UpdateDxDiagNodeRequestStatus( |
| false); |
| return; |
| } |
| GpuDataManagerImpl::GetInstance()->UpdateDxDiagNodeRequestStatus(true); |
| host->gpu_service()->RequestCompleteGpuInfo( |
| base::BindOnce([](const gpu::DxDiagNode& dx_diagnostics) { |
| GpuDataManagerImpl::GetInstance()->UpdateDxDiagNode( |
| dx_diagnostics); |
| })); |
| })); |
| #endif |
| } |
| |
| void GpuDataManagerImplPrivate::RequestGpuSupportedRuntimeVersion( |
| bool delayed) { |
| #if defined(OS_WIN) |
| base::OnceClosure task = base::BindOnce([]() { |
| if (GpuDataManagerImpl::GetInstance()->Dx12VulkanRequested()) |
| return; |
| GpuProcessHost* host = GpuProcessHost::Get( |
| GPU_PROCESS_KIND_UNSANDBOXED_NO_GL, true /* force_create */); |
| if (!host) { |
| GpuDataManagerImpl::GetInstance()->UpdateDx12VulkanRequestStatus(false); |
| return; |
| } |
| GpuDataManagerImpl::GetInstance()->UpdateDx12VulkanRequestStatus(true); |
| host->gpu_service()->GetGpuSupportedRuntimeVersion( |
| base::BindOnce([](const gpu::Dx12VulkanVersionInfo& info) { |
| GpuDataManagerImpl::GetInstance()->UpdateDx12VulkanInfo(info); |
| })); |
| }); |
| |
| if (delayed) { |
| base::PostDelayedTask(FROM_HERE, {BrowserThread::IO}, std::move(task), |
| base::TimeDelta::FromSeconds(120)); |
| } else { |
| base::PostTask(FROM_HERE, {BrowserThread::IO}, std::move(task)); |
| } |
| #endif |
| } |
| |
| bool GpuDataManagerImplPrivate::IsEssentialGpuInfoAvailable() const { |
| // We always update GPUInfo and GpuFeatureInfo from GPU process together. |
| return IsGpuFeatureInfoAvailable(); |
| } |
| |
| bool GpuDataManagerImplPrivate::IsDx12VulkanVersionAvailable() const { |
| #if defined(OS_WIN) |
| // Certain gpu_integration_test needs dx12/Vulkan info. If this info is |
| // needed, --no-delay-for-dx12-vulkan-info-collection should be added to the |
| // browser command line, so that the collection of this info isn't delayed. |
| // This function returns the status of availability to the tests based on |
| // whether gpu info has been requested or not. |
| |
| return gpu_info_dx12_vulkan_valid_ || !gpu_info_dx12_vulkan_requested_ || |
| gpu_info_dx12_vulkan_request_failed_; |
| #else |
| return true; |
| #endif |
| } |
| |
| bool GpuDataManagerImplPrivate::IsGpuFeatureInfoAvailable() const { |
| return gpu_feature_info_.IsInitialized(); |
| } |
| |
| gpu::GpuFeatureStatus GpuDataManagerImplPrivate::GetFeatureStatus( |
| gpu::GpuFeatureType feature) const { |
| DCHECK(feature >= 0 && feature < gpu::NUMBER_OF_GPU_FEATURE_TYPES); |
| DCHECK(gpu_feature_info_.IsInitialized()); |
| return gpu_feature_info_.status_values[feature]; |
| } |
| |
| void GpuDataManagerImplPrivate::RequestVideoMemoryUsageStatsUpdate( |
| GpuDataManager::VideoMemoryUsageStatsCallback callback) const { |
| GpuProcessHost::CallOnIO( |
| GPU_PROCESS_KIND_SANDBOXED, false /* force_create */, |
| base::BindOnce(&RequestVideoMemoryUsageStats, std::move(callback))); |
| } |
| |
| void GpuDataManagerImplPrivate::AddObserver(GpuDataManagerObserver* observer) { |
| observer_list_->AddObserver(observer); |
| } |
| |
| void GpuDataManagerImplPrivate::RemoveObserver( |
| GpuDataManagerObserver* observer) { |
| observer_list_->RemoveObserver(observer); |
| } |
| |
| void GpuDataManagerImplPrivate::UnblockDomainFrom3DAPIs(const GURL& url) { |
| // This method must do two things: |
| // |
| // 1. If the specific domain is blocked, then unblock it. |
| // |
| // 2. Reset our notion of how many GPU resets have occurred recently. |
| // This is necessary even if the specific domain was blocked. |
| // Otherwise, if we call Are3DAPIsBlocked with the same domain right |
| // after unblocking it, it will probably still be blocked because of |
| // the recent GPU reset caused by that domain. |
| // |
| // These policies could be refined, but at a certain point the behavior |
| // will become difficult to explain. |
| |
| // Shortcut in the common case where no blocking has occurred. This |
| // is important to not regress navigation performance, since this is |
| // now called on every user-initiated navigation. |
| if (blocked_domains_.empty() && timestamps_of_gpu_resets_.empty()) |
| return; |
| |
| std::string domain = GetDomainFromURL(url); |
| |
| blocked_domains_.erase(domain); |
| timestamps_of_gpu_resets_.clear(); |
| } |
| |
| void GpuDataManagerImplPrivate::UpdateGpuInfo( |
| const gpu::GPUInfo& gpu_info, |
| const base::Optional<gpu::GPUInfo>& gpu_info_for_hardware_gpu) { |
| #if defined(OS_WIN) |
| // If GPU process crashes and launches again, GPUInfo will be sent back from |
| // the new GPU process again, and may overwrite the DX12, Vulkan, DxDiagNode |
| // info we already collected. This is to make sure it doesn't happen. |
| gpu::DxDiagNode dx_diagnostics = gpu_info_.dx_diagnostics; |
| gpu::Dx12VulkanVersionInfo dx12_vulkan_version_info = |
| gpu_info_.dx12_vulkan_version_info; |
| #endif |
| gpu_info_ = gpu_info; |
| #if defined(OS_WIN) |
| if (!dx_diagnostics.IsEmpty()) { |
| gpu_info_.dx_diagnostics = dx_diagnostics; |
| } |
| if (!dx12_vulkan_version_info.IsEmpty()) { |
| gpu_info_.dx12_vulkan_version_info = dx12_vulkan_version_info; |
| } |
| #endif // OS_WIN |
| |
| if (!gpu_info_for_hardware_gpu_.IsInitialized()) { |
| if (gpu_info_for_hardware_gpu) { |
| DCHECK(gpu_info_for_hardware_gpu->IsInitialized()); |
| gpu_info_for_hardware_gpu_ = gpu_info_for_hardware_gpu.value(); |
| } else { |
| gpu_info_for_hardware_gpu_ = gpu_info_; |
| } |
| } |
| |
| GetContentClient()->SetGpuInfo(gpu_info_); |
| NotifyGpuInfoUpdate(); |
| } |
| |
| #if defined(OS_WIN) |
| void GpuDataManagerImplPrivate::UpdateDxDiagNode( |
| const gpu::DxDiagNode& dx_diagnostics) { |
| gpu_info_.dx_diagnostics = dx_diagnostics; |
| // No need to call GetContentClient()->SetGpuInfo(). |
| NotifyGpuInfoUpdate(); |
| } |
| |
| void GpuDataManagerImplPrivate::UpdateDx12VulkanInfo( |
| const gpu::Dx12VulkanVersionInfo& dx12_vulkan_version_info) { |
| gpu_info_.dx12_vulkan_version_info = dx12_vulkan_version_info; |
| gpu_info_dx12_vulkan_valid_ = true; |
| |
| // No need to call GetContentClient()->SetGpuInfo(). |
| NotifyGpuInfoUpdate(); |
| } |
| |
| void GpuDataManagerImplPrivate::UpdateDxDiagNodeRequestStatus( |
| bool request_continues) { |
| gpu_info_dx_diag_request_failed_ = !request_continues; |
| |
| if (gpu_info_dx_diag_request_failed_) |
| NotifyGpuInfoUpdate(); |
| } |
| |
| void GpuDataManagerImplPrivate::UpdateDx12VulkanRequestStatus( |
| bool request_continues) { |
| gpu_info_dx12_vulkan_requested_ = true; |
| gpu_info_dx12_vulkan_request_failed_ = !request_continues; |
| |
| if (gpu_info_dx12_vulkan_request_failed_) |
| NotifyGpuInfoUpdate(); |
| } |
| |
| bool GpuDataManagerImplPrivate::Dx12VulkanRequested() const { |
| return gpu_info_dx12_vulkan_requested_; |
| } |
| #endif |
| |
| void GpuDataManagerImplPrivate::UpdateGpuFeatureInfo( |
| const gpu::GpuFeatureInfo& gpu_feature_info, |
| const base::Optional<gpu::GpuFeatureInfo>& |
| gpu_feature_info_for_hardware_gpu) { |
| gpu_feature_info_ = gpu_feature_info; |
| if (!gpu_feature_info_for_hardware_gpu_.IsInitialized()) { |
| if (gpu_feature_info_for_hardware_gpu.has_value()) { |
| DCHECK(gpu_feature_info_for_hardware_gpu->IsInitialized()); |
| gpu_feature_info_for_hardware_gpu_ = |
| gpu_feature_info_for_hardware_gpu.value(); |
| } else { |
| gpu_feature_info_for_hardware_gpu_ = gpu_feature_info_; |
| } |
| } |
| if (update_histograms_) { |
| UpdateFeatureStats(gpu_feature_info_); |
| UpdateDriverBugListStats(gpu_feature_info_); |
| } |
| } |
| |
| void GpuDataManagerImplPrivate::UpdateGpuExtraInfo( |
| const gpu::GpuExtraInfo& gpu_extra_info) { |
| gpu_extra_info_ = gpu_extra_info; |
| } |
| |
| gpu::GpuFeatureInfo GpuDataManagerImplPrivate::GetGpuFeatureInfo() const { |
| return gpu_feature_info_; |
| } |
| |
| gpu::GpuFeatureInfo GpuDataManagerImplPrivate::GetGpuFeatureInfoForHardwareGpu() |
| const { |
| return gpu_feature_info_for_hardware_gpu_; |
| } |
| |
| gpu::GpuExtraInfo GpuDataManagerImplPrivate::GetGpuExtraInfo() const { |
| return gpu_extra_info_; |
| } |
| |
| bool GpuDataManagerImplPrivate::IsGpuCompositingDisabled() const { |
| return disable_gpu_compositing_ || |
| gpu_mode_ != gpu::GpuMode::HARDWARE_ACCELERATED; |
| } |
| |
| void GpuDataManagerImplPrivate::SetGpuCompositingDisabled() { |
| if (!IsGpuCompositingDisabled()) { |
| disable_gpu_compositing_ = true; |
| if (gpu_feature_info_.IsInitialized()) |
| NotifyGpuInfoUpdate(); |
| } |
| } |
| |
| void GpuDataManagerImplPrivate::AppendGpuCommandLine( |
| base::CommandLine* command_line, |
| GpuProcessKind kind) const { |
| DCHECK(command_line); |
| const base::CommandLine* browser_command_line = |
| base::CommandLine::ForCurrentProcess(); |
| |
| gpu::GpuPreferences gpu_prefs = GetGpuPreferencesFromCommandLine(); |
| UpdateGpuPreferences(&gpu_prefs, kind); |
| |
| command_line->AppendSwitchASCII(switches::kGpuPreferences, |
| gpu_prefs.ToSwitchValue()); |
| |
| std::string use_gl; |
| switch (gpu_mode_) { |
| case gpu::GpuMode::HARDWARE_ACCELERATED: |
| use_gl = browser_command_line->GetSwitchValueASCII(switches::kUseGL); |
| break; |
| case gpu::GpuMode::SWIFTSHADER: |
| use_gl = gl::kGLImplementationSwiftShaderForWebGLName; |
| break; |
| default: |
| use_gl = gl::kGLImplementationDisabledName; |
| } |
| if (!use_gl.empty()) { |
| command_line->AppendSwitchASCII(switches::kUseGL, use_gl); |
| } |
| |
| #if !defined(OS_MACOSX) |
| // MacOSX bots use real GPU in tests. |
| if (browser_command_line->HasSwitch(switches::kHeadless)) { |
| if (command_line->HasSwitch(switches::kUseGL)) { |
| use_gl = command_line->GetSwitchValueASCII(switches::kUseGL); |
| // Don't append kOverrideUseSoftwareGLForTests when we need to enable GPU |
| // hardware for headless chromium. |
| if (use_gl != gl::kGLImplementationEGLName) |
| command_line->AppendSwitch(switches::kOverrideUseSoftwareGLForTests); |
| } |
| } |
| #endif // !OS_MACOSX |
| } |
| |
| void GpuDataManagerImplPrivate::UpdateGpuPreferences( |
| gpu::GpuPreferences* gpu_preferences, |
| GpuProcessKind kind) const { |
| DCHECK(gpu_preferences); |
| |
| // For performance reasons, discourage storing VideoFrames in a biplanar |
| // GpuMemoryBuffer if this is not native, see https://crbug.com/791676. |
| if (auto* gpu_memory_buffer_manager = |
| GpuMemoryBufferManagerSingleton::GetInstance()) { |
| gpu_preferences->disable_biplanar_gpu_memory_buffers_for_video_frames = |
| !gpu_memory_buffer_manager->IsNativeGpuMemoryBufferConfiguration( |
| gfx::BufferFormat::YUV_420_BIPLANAR, |
| gfx::BufferUsage::GPU_READ_CPU_READ_WRITE); |
| } |
| |
| gpu_preferences->gpu_program_cache_size = |
| gpu::ShaderDiskCache::CacheSizeBytes(); |
| |
| gpu_preferences->texture_target_exception_list = |
| gpu::CreateBufferUsageAndFormatExceptionList(); |
| |
| gpu_preferences->watchdog_starts_backgrounded = !application_is_visible_; |
| |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| gpu_preferences->gpu_startup_dialog = |
| #if defined(OS_WIN) |
| (kind == GPU_PROCESS_KIND_UNSANDBOXED_NO_GL && |
| command_line->HasSwitch(switches::kGpu2StartupDialog)) || |
| #endif |
| (kind == GPU_PROCESS_KIND_SANDBOXED && |
| command_line->HasSwitch(switches::kGpuStartupDialog)); |
| |
| #if defined(OS_WIN) |
| if (kind == GPU_PROCESS_KIND_UNSANDBOXED_NO_GL) { |
| gpu_preferences->disable_gpu_watchdog = true; |
| } |
| #endif |
| |
| #if defined(USE_OZONE) |
| gpu_preferences->message_pump_type = ui::OzonePlatform::GetInstance() |
| ->GetPlatformProperties() |
| .message_pump_type_for_gpu; |
| #endif |
| } |
| |
| void GpuDataManagerImplPrivate::DisableHardwareAcceleration() { |
| if (!HardwareAccelerationEnabled()) |
| return; |
| |
| SetGpuCompositingDisabled(); |
| if (SwiftShaderAllowed()) { |
| gpu_mode_ = gpu::GpuMode::SWIFTSHADER; |
| } else { |
| OnGpuBlocked(); |
| } |
| } |
| |
| bool GpuDataManagerImplPrivate::HardwareAccelerationEnabled() const { |
| return gpu_mode_ == gpu::GpuMode::HARDWARE_ACCELERATED; |
| } |
| |
| void GpuDataManagerImplPrivate::OnGpuBlocked() { |
| // Decide which gpu mode to use now that gpu access is blocked. |
| if (features::IsVizDisplayCompositorEnabled()) { |
| gpu_mode_ = gpu::GpuMode::DISPLAY_COMPOSITOR; |
| } else { |
| gpu_mode_ = gpu::GpuMode::DISABLED; |
| } |
| |
| base::Optional<gpu::GpuFeatureInfo> gpu_feature_info_for_hardware_gpu; |
| if (gpu_feature_info_.IsInitialized()) |
| gpu_feature_info_for_hardware_gpu = gpu_feature_info_; |
| gpu::GpuFeatureInfo gpu_feature_info = gpu::ComputeGpuFeatureInfoWithNoGpu(); |
| UpdateGpuFeatureInfo(gpu_feature_info, gpu_feature_info_for_hardware_gpu); |
| |
| // Some observers might be waiting. |
| NotifyGpuInfoUpdate(); |
| } |
| |
| void GpuDataManagerImplPrivate::AddLogMessage( |
| int level, const std::string& header, const std::string& message) { |
| // Some clients emit many log messages. This has been observed to consume GBs |
| // of memory in the wild |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=798012. Use a limit |
| // of 1000 messages to prevent excess memory usage. |
| const int kLogMessageLimit = 1000; |
| |
| log_messages_.push_back(LogMessage(level, header, message)); |
| if (log_messages_.size() > kLogMessageLimit) |
| log_messages_.erase(log_messages_.begin()); |
| } |
| |
| void GpuDataManagerImplPrivate::ProcessCrashed( |
| base::TerminationStatus exit_code) { |
| observer_list_->Notify( |
| FROM_HERE, &GpuDataManagerObserver::OnGpuProcessCrashed, exit_code); |
| } |
| |
| std::unique_ptr<base::ListValue> GpuDataManagerImplPrivate::GetLogMessages() |
| const { |
| auto value = std::make_unique<base::ListValue>(); |
| for (size_t ii = 0; ii < log_messages_.size(); ++ii) { |
| auto dict = std::make_unique<base::DictionaryValue>(); |
| dict->SetInteger("level", log_messages_[ii].level); |
| dict->SetString("header", log_messages_[ii].header); |
| dict->SetString("message", log_messages_[ii].message); |
| value->Append(std::move(dict)); |
| } |
| return value; |
| } |
| |
| void GpuDataManagerImplPrivate::HandleGpuSwitch() { |
| base::AutoUnlock unlock(owner_->lock_); |
| // Notify observers in the browser process. |
| ui::GpuSwitchingManager::GetInstance()->NotifyGpuSwitched( |
| active_gpu_heuristic_); |
| // Pass the notification to the GPU process to notify observers there. |
| GpuProcessHost::CallOnIO( |
| GPU_PROCESS_KIND_SANDBOXED, false /* force_create */, |
| base::BindOnce( |
| [](gl::GpuPreference active_gpu, GpuProcessHost* host) { |
| if (host) |
| host->gpu_service()->GpuSwitched(active_gpu); |
| }, |
| active_gpu_heuristic_)); |
| } |
| |
| bool GpuDataManagerImplPrivate::UpdateActiveGpu(uint32_t vendor_id, |
| uint32_t device_id) { |
| // Heuristics for dual-GPU detection. |
| bool is_dual_gpu = gpu_info_.secondary_gpus.size() == 1; |
| const uint32_t kIntelID = 0x8086; |
| bool saw_intel_gpu = false; |
| bool saw_non_intel_gpu = false; |
| |
| if (gpu_info_.gpu.vendor_id == vendor_id && |
| gpu_info_.gpu.device_id == device_id) { |
| // The primary GPU is active. |
| if (gpu_info_.gpu.active) |
| return false; |
| gpu_info_.gpu.active = true; |
| for (size_t ii = 0; ii < gpu_info_.secondary_gpus.size(); ++ii) { |
| gpu_info_.secondary_gpus[ii].active = false; |
| } |
| } else { |
| // A secondary GPU is active. |
| for (size_t ii = 0; ii < gpu_info_.secondary_gpus.size(); ++ii) { |
| if (gpu_info_.secondary_gpus[ii].vendor_id == vendor_id && |
| gpu_info_.secondary_gpus[ii].device_id == device_id) { |
| if (gpu_info_.secondary_gpus[ii].active) |
| return false; |
| gpu_info_.secondary_gpus[ii].active = true; |
| } else { |
| gpu_info_.secondary_gpus[ii].active = false; |
| } |
| } |
| gpu_info_.gpu.active = false; |
| } |
| active_gpu_heuristic_ = gl::GpuPreference::kDefault; |
| if (is_dual_gpu) { |
| if (gpu_info_.gpu.vendor_id == kIntelID) { |
| saw_intel_gpu = true; |
| } else { |
| saw_non_intel_gpu = true; |
| } |
| if (gpu_info_.secondary_gpus[0].vendor_id == kIntelID) { |
| saw_intel_gpu = true; |
| } else { |
| saw_non_intel_gpu = true; |
| } |
| if (saw_intel_gpu && saw_non_intel_gpu) { |
| if (vendor_id == kIntelID) { |
| active_gpu_heuristic_ = gl::GpuPreference::kLowPower; |
| } else { |
| active_gpu_heuristic_ = gl::GpuPreference::kHighPerformance; |
| } |
| } |
| } |
| GetContentClient()->SetGpuInfo(gpu_info_); |
| NotifyGpuInfoUpdate(); |
| return true; |
| } |
| |
| void GpuDataManagerImplPrivate::BlockDomainFrom3DAPIs(const GURL& url, |
| gpu::DomainGuilt guilt) { |
| BlockDomainFrom3DAPIsAtTime(url, guilt, base::Time::Now()); |
| } |
| |
| bool GpuDataManagerImplPrivate::Are3DAPIsBlocked(const GURL& top_origin_url, |
| int render_process_id, |
| int render_frame_id, |
| ThreeDAPIType requester) { |
| return Are3DAPIsBlockedAtTime(top_origin_url, base::Time::Now()) != |
| DomainBlockStatus::kNotBlocked; |
| } |
| |
| void GpuDataManagerImplPrivate::DisableDomainBlockingFor3DAPIsForTesting() { |
| domain_blocking_enabled_ = false; |
| } |
| |
| void GpuDataManagerImplPrivate::NotifyGpuInfoUpdate() { |
| observer_list_->Notify(FROM_HERE, &GpuDataManagerObserver::OnGpuInfoUpdate); |
| } |
| |
| bool GpuDataManagerImplPrivate::IsGpuProcessUsingHardwareGpu() const { |
| if (base::StartsWith(gpu_info_.gl_renderer, "Google SwiftShader", |
| base::CompareCase::SENSITIVE)) |
| return false; |
| if (gpu_info_.gl_renderer == "Disabled") |
| return false; |
| return true; |
| } |
| |
| void GpuDataManagerImplPrivate::SetApplicationVisible(bool is_visible) { |
| application_is_visible_ = is_visible; |
| } |
| |
| std::string GpuDataManagerImplPrivate::GetDomainFromURL(const GURL& url) const { |
| // For the moment, we just use the host, or its IP address, as the |
| // entry in the set, rather than trying to figure out the top-level |
| // domain. This does mean that a.foo.com and b.foo.com will be |
| // treated independently in the blocking of a given domain, but it |
| // would require a third-party library to reliably figure out the |
| // top-level domain from a URL. |
| if (!url.has_host()) { |
| return std::string(); |
| } |
| |
| return url.host(); |
| } |
| |
| void GpuDataManagerImplPrivate::BlockDomainFrom3DAPIsAtTime( |
| const GURL& url, |
| gpu::DomainGuilt guilt, |
| base::Time at_time) { |
| if (!domain_blocking_enabled_) |
| return; |
| |
| std::string domain = GetDomainFromURL(url); |
| |
| blocked_domains_[domain] = guilt; |
| timestamps_of_gpu_resets_.push_back(at_time); |
| } |
| |
| GpuDataManagerImplPrivate::DomainBlockStatus |
| GpuDataManagerImplPrivate::Are3DAPIsBlockedAtTime(const GURL& url, |
| base::Time at_time) const { |
| if (!domain_blocking_enabled_) |
| return DomainBlockStatus::kNotBlocked; |
| |
| // Note: adjusting the policies in this code will almost certainly |
| // require adjusting the associated unit tests. |
| std::string domain = GetDomainFromURL(url); |
| |
| { |
| if (blocked_domains_.find(domain) != blocked_domains_.end()) { |
| // Err on the side of caution, and assume that if a particular |
| // domain shows up in the block map, it's there for a good |
| // reason and don't let its presence there automatically expire. |
| return DomainBlockStatus::kBlocked; |
| } |
| } |
| |
| // Look at the timestamps of the recent GPU resets to see if there are |
| // enough within the threshold which would cause us to blacklist all |
| // domains. This doesn't need to be overly precise -- if time goes |
| // backward due to a system clock adjustment, that's fine. |
| // |
| // TODO(kbr): make this pay attention to the TDR thresholds in the |
| // Windows registry, but make sure it continues to be testable. |
| { |
| auto iter = timestamps_of_gpu_resets_.begin(); |
| int num_resets_within_timeframe = 0; |
| while (iter != timestamps_of_gpu_resets_.end()) { |
| base::Time time = *iter; |
| base::TimeDelta delta_t = at_time - time; |
| |
| // If this entry has "expired", just remove it. |
| if (delta_t.InMilliseconds() > kBlockAllDomainsMs) { |
| iter = timestamps_of_gpu_resets_.erase(iter); |
| continue; |
| } |
| |
| ++num_resets_within_timeframe; |
| ++iter; |
| } |
| |
| if (num_resets_within_timeframe >= kNumResetsWithinDuration) { |
| return DomainBlockStatus::kAllDomainsBlocked; |
| } |
| } |
| |
| return DomainBlockStatus::kNotBlocked; |
| } |
| |
| int64_t GpuDataManagerImplPrivate::GetBlockAllDomainsDurationInMs() const { |
| return kBlockAllDomainsMs; |
| } |
| |
| gpu::GpuMode GpuDataManagerImplPrivate::GetGpuMode() const { |
| return gpu_mode_; |
| } |
| |
| void GpuDataManagerImplPrivate::FallBackToNextGpuMode() { |
| #if defined(OS_ANDROID) || defined(OS_CHROMEOS) || defined(OS_FUCHSIA) |
| // Android and Chrome OS can't switch to software compositing. If the GPU |
| // process initialization fails or GPU process is too unstable then crash the |
| // browser process to reset everything. |
| // On Fuchsia Vulkan must be used when it's enabled by the WebEngine embedder. |
| // Falling back to SW compositing in that case is not supported. |
| #if defined(OS_ANDROID) |
| FatalGpuProcessLaunchFailureOnBackground(); |
| #endif |
| LOG(FATAL) << "GPU process isn't usable. Goodbye."; |
| #else |
| switch (gpu_mode_) { |
| case gpu::GpuMode::HARDWARE_ACCELERATED: |
| hardware_disabled_by_fallback_ = true; |
| DisableHardwareAcceleration(); |
| break; |
| case gpu::GpuMode::SWIFTSHADER: |
| OnGpuBlocked(); |
| break; |
| case gpu::GpuMode::DISPLAY_COMPOSITOR: |
| // The GPU process is frequently crashing with only the display compositor |
| // running. This should never happen so something is wrong. Crash the |
| // browser process to reset everything. |
| LOG(FATAL) << "The display compositor is frequently crashing. Goodbye."; |
| break; |
| case gpu::GpuMode::DISABLED: |
| case gpu::GpuMode::UNKNOWN: |
| // We are already at GpuMode::DISABLED. We shouldn't be launching the GPU |
| // process for it to fail. |
| NOTREACHED(); |
| } |
| #endif |
| } |
| |
| void GpuDataManagerImplPrivate::RecordCompositingMode() { |
| CompositingMode compositing_mode; |
| if (IsGpuCompositingDisabled()) { |
| compositing_mode = CompositingMode::kSoftware; |
| } else { |
| // TODO(penghuang): Record kVulkan here if we're using Vulkan. |
| compositing_mode = CompositingMode::kGL; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("GPU.CompositingMode", compositing_mode); |
| } |
| |
| } // namespace content |