/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "GPUProcessManager.h" #include "gfxConfig.h" #include "gfxPlatform.h" #include "GPUProcessHost.h" #include "GPUProcessListener.h" #include "mozilla/AppShutdown.h" #include "mozilla/MemoryReportingProcess.h" #include "mozilla/Preferences.h" #include "mozilla/RDDChild.h" #include "mozilla/RDDProcessManager.h" #include "mozilla/RemoteMediaManagerChild.h" #include "mozilla/RemoteMediaManagerParent.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticPtr.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/StaticPrefs_layers.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/GPUChild.h" #include "mozilla/gfx/GPUParent.h" #include "mozilla/glean/GfxMetrics.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/ipc/ProcessChild.h" #include "mozilla/layers/APZCTreeManagerChild.h" #include "mozilla/layers/APZInputBridgeChild.h" #include "mozilla/layers/CompositorBridgeChild.h" #include "mozilla/layers/CompositorBridgeParent.h" #include "mozilla/layers/CompositorManagerChild.h" #include "mozilla/layers/CompositorManagerParent.h" #include "mozilla/layers/CompositorOptions.h" #include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/ImageBridgeParent.h" #include "mozilla/layers/InProcessCompositorSession.h" #include "mozilla/layers/LayerTreeOwnerTracker.h" #include "mozilla/layers/RemoteCompositorSession.h" #include "mozilla/layers/VideoBridgeParent.h" #include "mozilla/webrender/RenderThread.h" #include "mozilla/widget/PlatformWidgetTypes.h" #include "nsAppRunner.h" #include "mozilla/widget/CompositorWidget.h" #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING # include "mozilla/widget/CompositorWidgetChild.h" #endif #include "nsIWidget.h" #include "nsContentUtils.h" #include "VRManagerChild.h" #include "VRManagerParent.h" #include "VsyncBridgeChild.h" #include "VsyncIOThreadHolder.h" #include "VsyncSource.h" #include "nsExceptionHandler.h" #include "nsPrintfCString.h" #ifdef MOZ_WMF_MEDIA_ENGINE # include "mozilla/ipc/UtilityMediaServiceChild.h" #endif #if defined(MOZ_WIDGET_ANDROID) # include "mozilla/java/SurfaceControlManagerWrappers.h" # include "mozilla/widget/AndroidUiThread.h" # include "mozilla/layers/UiCompositorControllerChild.h" #endif // defined(MOZ_WIDGET_ANDROID) #if defined(XP_WIN) # include "gfxWindowsPlatform.h" # include "mozilla/gfx/DeviceManagerDx.h" #endif namespace mozilla { namespace gfx { using namespace mozilla::layers; static StaticAutoPtr sSingleton; GPUProcessManager* GPUProcessManager::Get() { return sSingleton; } void GPUProcessManager::Initialize() { MOZ_ASSERT(XRE_IsParentProcess()); sSingleton = new GPUProcessManager(); } void GPUProcessManager::Shutdown() { if (!sSingleton) { return; } sSingleton->ShutdownInternal(); sSingleton = nullptr; } GPUProcessManager::GPUProcessManager() : mTaskFactory(this), mNextNamespace(0), mIdNamespace(0), mResourceId(0), mUnstableProcessAttempts(0), mTotalProcessAttempts(0), mDeviceResetCount(0), mAppInForeground(true), mProcess(nullptr), mProcessToken(0), mGPUChild(nullptr) { MOZ_COUNT_CTOR(GPUProcessManager); mIdNamespace = AllocateNamespace(); mDeviceResetLastTime = TimeStamp::Now(); LayerTreeOwnerTracker::Initialize(); CompositorBridgeParent::InitializeStatics(); } GPUProcessManager::~GPUProcessManager() { MOZ_COUNT_DTOR(GPUProcessManager); LayerTreeOwnerTracker::Shutdown(); // The GPU process should have already been shut down. MOZ_ASSERT(!mProcess && !mGPUChild); // We should have already removed observers. MOZ_DIAGNOSTIC_ASSERT(!mObserver); MOZ_DIAGNOSTIC_ASSERT(!mBatteryObserver); } NS_IMPL_ISUPPORTS(GPUProcessManager::Observer, nsIObserver); GPUProcessManager::Observer::Observer() { nsContentUtils::RegisterShutdownObserver(this); Preferences::AddStrongObserver(this, ""); if (nsCOMPtr obsServ = services::GetObserverService()) { obsServ->AddObserver(this, "application-foreground", false); obsServ->AddObserver(this, "application-background", false); obsServ->AddObserver(this, "screen-information-changed", false); obsServ->AddObserver(this, "xpcom-will-shutdown", false); } } void GPUProcessManager::Observer::Shutdown() { nsContentUtils::UnregisterShutdownObserver(this); Preferences::RemoveObserver(this, ""); if (nsCOMPtr obsServ = services::GetObserverService()) { obsServ->RemoveObserver(this, "application-foreground"); obsServ->RemoveObserver(this, "application-background"); obsServ->RemoveObserver(this, "screen-information-changed"); obsServ->RemoveObserver(this, "xpcom-will-shutdown"); } } NS_IMETHODIMP GPUProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (auto* gpm = GPUProcessManager::Get()) { gpm->NotifyObserve(aTopic, aData); } return NS_OK; } void GPUProcessManager::NotifyObserve(const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { StopBatteryObserving(); } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { ShutdownInternal(); } else if (!strcmp(aTopic, "nsPref:changed")) { OnPreferenceChange(aData); } else if (!strcmp(aTopic, "application-foreground")) { SetAppInForeground(true); } else if (!strcmp(aTopic, "application-background")) { SetAppInForeground(false); } else if (!strcmp(aTopic, "screen-information-changed")) { ScreenInformationChanged(); } } GPUProcessManager::BatteryObserver::BatteryObserver() { hal::RegisterBatteryObserver(this); } void GPUProcessManager::BatteryObserver::Notify( const hal::BatteryInformation& aBatteryInfo) { if (auto* gpm = GPUProcessManager::Get()) { gpm->NotifyBatteryInfo(aBatteryInfo); } } void GPUProcessManager::BatteryObserver::Shutdown() { hal::UnregisterBatteryObserver(this); } void GPUProcessManager::OnPreferenceChange(const char16_t* aData) { if (!mGPUChild && !IsGPUProcessLaunching()) { return; } // We know prefs are ASCII here. NS_LossyConvertUTF16toASCII strData(aData); mozilla::dom::Pref pref(strData, /* isLocked */ false, /* isSanitized */ false, Nothing(), Nothing()); Preferences::GetPreference(&pref, GeckoProcessType_GPU, /* remoteType */ ""_ns); if (mGPUChild) { MOZ_ASSERT(mQueuedPrefs.IsEmpty()); mGPUChild->SendPreferenceUpdate(pref); } else { mQueuedPrefs.AppendElement(pref); } } void GPUProcessManager::ScreenInformationChanged() { #if defined(XP_WIN) if (!!mGPUChild) { mGPUChild->SendScreenInformationChanged(); } #endif } void GPUProcessManager::NotifyBatteryInfo( const hal::BatteryInformation& aBatteryInfo) { if (mGPUChild) { mGPUChild->SendNotifyBatteryInfo(aBatteryInfo); } } void GPUProcessManager::MaybeCrashIfGpuProcessOnceStable() { if (StaticPrefs::layers_gpu_process_allow_fallback_to_parent_AtStartup()) { return; } MOZ_RELEASE_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS)); MOZ_RELEASE_ASSERT(!mProcessStableOnce, "Fallback to parent process not allowed!"); } void GPUProcessManager::ResetProcessStable() { mTotalProcessAttempts++; mProcessStable = false; mProcessAttemptLastTime = TimeStamp::Now(); } bool GPUProcessManager::IsProcessStable(const TimeStamp& aNow) { if (mTotalProcessAttempts > 0) { auto delta = (int32_t)(aNow - mProcessAttemptLastTime).ToMilliseconds(); if (delta < StaticPrefs::layers_gpu_process_stable_min_uptime_ms()) { return false; } } return mProcessStable; } nsresult GPUProcessManager::LaunchGPUProcess() { if (mProcess) { return NS_OK; } if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)) { return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; } // Start listening for pref changes so we can // forward them to the process once it is running. if (!mObserver) { mObserver = new Observer(); } // Start the Vsync I/O thread so can use it as soon as the process launches. EnsureVsyncIOThread(); mTotalProcessAttempts++; mozilla::glean::gpu_process::total_launch_attempts.Set(mTotalProcessAttempts); mProcessAttemptLastTime = TimeStamp::Now(); mProcessStable = false; geckoargs::ChildProcessArgs extraArgs; ipc::ProcessChild::AddPlatformBuildID(extraArgs); // The subprocess is launched asynchronously, so we wait for a callback to // acquire the IPDL actor. mProcess = new GPUProcessHost(this); if (!mProcess->Launch(std::move(extraArgs))) { DisableGPUProcess("Failed to launch GPU process"); return NS_ERROR_FAILURE; } return NS_OK; } bool GPUProcessManager::IsGPUProcessLaunching() { MOZ_ASSERT(NS_IsMainThread()); return !!mProcess && !mGPUChild; } void GPUProcessManager::DisableGPUProcess(const char* aMessage) { MaybeDisableGPUProcess(aMessage, /* aAllowRestart */ false); } bool GPUProcessManager::MaybeDisableGPUProcess(const char* aMessage, bool aAllowRestart) { if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { return true; } bool wantRestart; { // Collect the gfxVar updates into a single message. gfxVarsCollectUpdates collect; if (!aAllowRestart) { gfxConfig::SetFailed(Feature::GPU_PROCESS, FeatureStatus::Failed, aMessage); gfxVars::SetGPUProcessEnabled(false); } if (mLastError) { wantRestart = FallbackFromAcceleration(mLastError.value(), mLastErrorMsg.ref()); mLastError.reset(); mLastErrorMsg.reset(); } else { wantRestart = gfxPlatform::FallbackFromAcceleration( FeatureStatus::Unavailable, aMessage, "FEATURE_FAILURE_GPU_PROCESS_ERROR"_ns); } if (aAllowRestart && wantRestart) { // The fallback method can make use of the GPU process. return false; } if (aAllowRestart) { gfxConfig::SetFailed(Feature::GPU_PROCESS, FeatureStatus::Failed, aMessage); gfxVars::SetGPUProcessEnabled(false); } MOZ_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS)); gfxCriticalNote << aMessage; gfxPlatform::DisableGPUProcess(); MaybeCrashIfGpuProcessOnceStable(); } mozilla::glean::gpu_process::feature_status.Set( gfxConfig::GetFeature(Feature::GPU_PROCESS) .GetStatusAndFailureIdString()); mozilla::glean::gpu_process::crash_fallbacks.Get("disabled"_ns).Add(1); DestroyProcess(); ShutdownVsyncIOThread(); // Now the stability state is based upon the in process compositor session. ResetProcessStable(); // We may have been in the middle of guaranteeing our various services are // available when one failed. Some callers may fallback to using the same // process equivalent, and we need to make sure those services are setup // correctly. We cannot re-enter DisableGPUProcess from this call because we // know that it is disabled in the config above. if (NS_WARN_IF(NS_FAILED(EnsureGPUReady()))) { MOZ_ASSERT(AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)); } else { DebugOnly ready = EnsureProtocolsReady(); MOZ_ASSERT(ready); } // If we disable the GPU process during reinitialization after a previous // crash, then we need to tell the content processes again, because they // need to rebind to the UI process. HandleProcessLost(); return true; } bool GPUProcessManager::IsGPUReady() const { // If we have disabled the GPU process, then we know we are always ready. if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { MOZ_ASSERT(!mProcess); MOZ_ASSERT(!mGPUChild); return true; } // If we have a GPUChild, then we know the process has finished launching. if (mGPUChild) { return mGPUChild->IsGPUReady(); } return false; } nsresult GPUProcessManager::EnsureGPUReady() { MOZ_ASSERT(NS_IsMainThread()); // Common case is we already have a GPU process. if (mProcess && mProcess->IsConnected() && mGPUChild) { MOZ_DIAGNOSTIC_ASSERT(mGPUChild->IsGPUReady()); return NS_OK; } // Next most common case is that we are compositing in the parent process. if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { MOZ_DIAGNOSTIC_ASSERT(!mProcess); MOZ_DIAGNOSTIC_ASSERT(!mGPUChild); return NS_OK; } // We aren't ready and in shutdown, we should just abort. if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)) { return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; } while (true) { // We allow the GPU process to launch if we are: // 1) in the foreground, as the application is being actively used. // 2) if we have no launch failures, because even if we are backgrounded, we // can try once to secure it. This is useful for geckoview-junit tests. // 3) if our pref is set to allow background launches; this is false by // default on Android due to its issues with keeping the GPU process // alive in the background, and true on all other platforms. // // If we are not in a position to try launching and/or waiting for the GPU // process, then we should just abort for now. The higher levels will fail // to create the content process, but all of this should get recreated when // the app comes back into the foreground. if (!mAppInForeground && mLaunchProcessAttempts > 0 && !StaticPrefs::layers_gpu_process_launch_in_background()) { return NS_ERROR_ABORT; } // Launch the GPU process if it is enabled but hasn't been (re-)launched // yet, and wait for it to complete the handshake. As part of WaitForLaunch, // we know that OnProcessLaunchComplete has been called. If it succeeds, // we know that mGPUChild has been set and we already waited for it to be // ready. If it fails, then we know that the GPU process must have been // destroyed and/or disabled. nsresult rv = LaunchGPUProcess(); if (NS_SUCCEEDED(rv) && mProcess->WaitForLaunch() && mGPUChild) { MOZ_DIAGNOSTIC_ASSERT(mGPUChild->IsGPUReady()); break; } MOZ_RELEASE_ASSERT(rv != NS_ERROR_ILLEGAL_DURING_SHUTDOWN); MOZ_RELEASE_ASSERT(!mProcess); MOZ_RELEASE_ASSERT(!mGPUChild); MOZ_DIAGNOSTIC_ASSERT(mLaunchProcessAttempts > 0); if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { break; } } return NS_OK; } bool GPUProcessManager::EnsureProtocolsReady() { return EnsureCompositorManagerChild() && EnsureImageBridgeChild() && EnsureVRManager(); } bool GPUProcessManager::EnsureCompositorManagerChild() { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (CompositorManagerChild::IsInitialized(mProcessToken)) { return true; } if (!mGPUChild) { CompositorManagerChild::InitSameProcess(AllocateNamespace(), mProcessToken); return true; } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PCompositorManager::CreateEndpoints( mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PCompositorManager endpoints"); return true; } uint32_t cmNamespace = AllocateNamespace(); mGPUChild->SendInitCompositorManager(std::move(parentPipe), cmNamespace); CompositorManagerChild::Init(std::move(childPipe), cmNamespace, mProcessToken); return true; } bool GPUProcessManager::EnsureImageBridgeChild() { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (ImageBridgeChild::GetSingleton()) { return true; } if (!mGPUChild) { ImageBridgeChild::InitSameProcess(AllocateNamespace()); return true; } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PImageBridge::CreateEndpoints( mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PImageBridge endpoints"); return true; } mGPUChild->SendInitImageBridge(std::move(parentPipe)); ImageBridgeChild::InitWithGPUProcess(std::move(childPipe), AllocateNamespace()); return true; } bool GPUProcessManager::EnsureVRManager() { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (VRManagerChild::IsCreated()) { return true; } if (!mGPUChild) { VRManagerChild::InitSameProcess(); return true; } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PVRManager::CreateEndpoints(mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PVRManager endpoints"); return true; } mGPUChild->SendInitVRManager(std::move(parentPipe)); VRManagerChild::InitWithGPUProcess(std::move(childPipe)); return true; } #if defined(MOZ_WIDGET_ANDROID) RefPtr GPUProcessManager::CreateUiCompositorController(nsIWidget* aWidget, const LayersId aId) { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (!mGPUChild) { return UiCompositorControllerChild::CreateForSameProcess(aId, aWidget); } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PUiCompositorController::CreateEndpoints( mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PUiCompositorController endpoints"); return nullptr; } mGPUChild->SendInitUiCompositorController(aId, std::move(parentPipe)); RefPtr result = UiCompositorControllerChild::CreateForGPUProcess( mProcessToken, std::move(childPipe), aWidget); if (result) { result->SetCompositorSurfaceManager( mProcess->GetCompositorSurfaceManager()); } return result; } #endif // defined(MOZ_WIDGET_ANDROID) void GPUProcessManager::OnProcessLaunchComplete(GPUProcessHost* aHost) { MOZ_ASSERT(mProcess && mProcess == aHost); // By definition, the process failing to launch is an unstable attempt. While // we did not get to the point where we are using the features, we should just // follow the same fallback procedure. auto* gpuChild = mProcess->GetActor(); if (NS_WARN_IF(!mProcess->IsConnected()) || NS_WARN_IF(!gpuChild) || NS_WARN_IF(!gpuChild->EnsureGPUReady())) { ++mLaunchProcessAttempts; if (mLaunchProcessAttempts > uint32_t(StaticPrefs::layers_gpu_process_max_launch_attempts())) { char disableMessage[64]; SprintfLiteral(disableMessage, "Failed to launch GPU process after %d attempts", mLaunchProcessAttempts); DisableGPUProcess(disableMessage); } else { DestroyProcess(/* aUnexpectedShutdown */ true); } return; } mLaunchProcessAttempts = 0; mGPUChild = gpuChild; mProcessToken = mProcess->GetProcessToken(); #if defined(XP_WIN) if (mAppInForeground) { SetProcessIsForeground(); } #endif // Set a high priority for the newly-created gpu process. int pID = mProcess->GetChildProcessId(); hal::SetProcessPriority(pID, hal::PROCESS_PRIORITY_FOREGROUND_HIGH); ipc::Endpoint vsyncParent; ipc::Endpoint vsyncChild; nsresult rv = PVsyncBridge::CreateEndpoints( mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(), &vsyncParent, &vsyncChild); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PVsyncBridge endpoints"); return; } mVsyncBridge = VsyncBridgeChild::Create(mVsyncIOThread, mProcessToken, std::move(vsyncChild)); mGPUChild->SendInitVsyncBridge(std::move(vsyncParent)); MOZ_DIAGNOSTIC_ASSERT(!mBatteryObserver); if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { mBatteryObserver = new BatteryObserver(); } // Flush any pref updates that happened during launch and weren't // included in the blobs set up in LaunchGPUProcess. for (const mozilla::dom::Pref& pref : mQueuedPrefs) { (void)NS_WARN_IF(!mGPUChild->SendPreferenceUpdate(pref)); } mQueuedPrefs.Clear(); CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::GPUProcessStatus, "Running"); CrashReporter::RecordAnnotationU32( CrashReporter::Annotation::GPUProcessLaunchCount, mTotalProcessAttempts); ReinitializeRendering(); } void GPUProcessManager::OnProcessDeclaredStable() { mProcessStable = true; } static bool ShouldLimitDeviceResets(uint32_t count, int32_t deltaMilliseconds) { // We decide to limit by comparing the amount of resets that have happened // and time since the last reset to two prefs. int32_t timeLimit = StaticPrefs::gfx_device_reset_threshold_ms_AtStartup(); int32_t countLimit = StaticPrefs::gfx_device_reset_limit_AtStartup(); bool hasTimeLimit = timeLimit >= 0; bool hasCountLimit = countLimit >= 0; bool triggeredTime = deltaMilliseconds < timeLimit; bool triggeredCount = count > (uint32_t)countLimit; // If we have both prefs set then it needs to trigger both limits, // otherwise we only test the pref that is set or none if (hasTimeLimit && hasCountLimit) { return triggeredTime && triggeredCount; } else if (hasTimeLimit) { return triggeredTime; } else if (hasCountLimit) { return triggeredCount; } return false; } void GPUProcessManager::ResetCompositors() { // Note: this will recreate devices in addition to recreating compositors. // This isn't optimal, but this is only used on linux where acceleration // isn't enabled by default, and this way we don't need a new code path. SimulateDeviceReset(); } void GPUProcessManager::SimulateDeviceReset() { // Make sure we rebuild environment and configuration for accelerated // features. gfxPlatform::GetPlatform()->CompositorUpdated(); if (mProcess) { if (mGPUChild) { mGPUChild->SendSimulateDeviceReset(); } } else { wr::RenderThread::Get()->SimulateDeviceReset(); } } bool GPUProcessManager::FallbackFromAcceleration(wr::WebRenderError aError, const nsCString& aMsg) { if (aError == wr::WebRenderError::INITIALIZE) { return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "WebRender initialization failed", aMsg); } else if (aError == wr::WebRenderError::MAKE_CURRENT) { return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "Failed to make render context current", "FEATURE_FAILURE_WEBRENDER_MAKE_CURRENT"_ns); } else if (aError == wr::WebRenderError::RENDER) { return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "Failed to render WebRender", "FEATURE_FAILURE_WEBRENDER_RENDER"_ns); } else if (aError == wr::WebRenderError::NEW_SURFACE) { // If we cannot create a new Surface even in the final fallback // configuration then force a crash. return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "Failed to create new surface", "FEATURE_FAILURE_WEBRENDER_NEW_SURFACE"_ns, /* aCrashAfterFinalFallback */ true); } else if (aError == wr::WebRenderError::BEGIN_DRAW) { return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "BeginDraw() failed", "FEATURE_FAILURE_WEBRENDER_BEGIN_DRAW"_ns); } else if (aError == wr::WebRenderError::EXCESSIVE_RESETS) { return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "Device resets exceeded threshold", "FEATURE_FAILURE_WEBRENDER_EXCESSIVE_RESETS"_ns); } else { MOZ_ASSERT_UNREACHABLE("Invalid value"); return gfxPlatform::FallbackFromAcceleration( gfx::FeatureStatus::Unavailable, "Unhandled failure reason", "FEATURE_FAILURE_WEBRENDER_UNHANDLED"_ns); } } void GPUProcessManager::DisableWebRenderConfig(wr::WebRenderError aError, const nsCString& aMsg) { // Clear out any cached errors from a remote device reset. mLastError.reset(); mLastErrorMsg.reset(); bool wantRestart; { // Collect the gfxVar updates into a single message. gfxVarsCollectUpdates collect; // Disable WebRender wantRestart = FallbackFromAcceleration(aError, aMsg); gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(false); gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(false); } // If we still have the GPU process, and we fallback to a new configuration // that prefers to have the GPU process, reset the counter. Because we // updated the gfxVars, we call GPUChild::EnsureGPUReady to force us to wait // for the update to be processed before creating new compositor sessions. // Otherwise we risk them being out of sync with the content/parent processes. if (wantRestart && mProcess && mGPUChild) { mUnstableProcessAttempts = 1; mGPUChild->EnsureGPUReady(/* aForceSync */ true); } } void GPUProcessManager::DisableWebRender(wr::WebRenderError aError, const nsCString& aMsg) { DisableWebRenderConfig(aError, aMsg); if (mProcess) { DestroyRemoteCompositorSessions(); } else { DestroyInProcessCompositorSessions(); } NotifyListenersOnCompositeDeviceReset(); } void GPUProcessManager::NotifyWebRenderError(wr::WebRenderError aError) { gfxCriticalNote << "Handling webrender error " << (unsigned int)aError; #ifdef XP_WIN if (aError == wr::WebRenderError::VIDEO_OVERLAY) { gfxVarsCollectUpdates collect; gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(false); gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(false); return; } if (aError == wr::WebRenderError::VIDEO_HW_OVERLAY) { gfxVars::SetUseWebRenderDCompVideoHwOverlayWin(false); return; } if (aError == wr::WebRenderError::VIDEO_SW_OVERLAY) { gfxVars::SetUseWebRenderDCompVideoSwOverlayWin(false); return; } #else if (aError == wr::WebRenderError::VIDEO_OVERLAY || aError == wr::WebRenderError::VIDEO_HW_OVERLAY || aError == wr::WebRenderError::VIDEO_SW_OVERLAY) { MOZ_ASSERT_UNREACHABLE("unexpected to be called"); return; } #endif // If we have a stable GPU process, this may just be due to an OOM or bad // driver state. In that case, we should consider restarting the GPU process // to hopefully alleviate the situation. if (mProcess && (IsProcessStable(TimeStamp::Now()) || (kIsAndroid && !mAppInForeground))) { mProcess->KillProcess(/* aGenerateMinidump */ false); mLastError = Some(aError); mLastErrorMsg = Some(""_ns); return; } DisableWebRender(aError, ""_ns); } /* static */ void GPUProcessManager::RecordDeviceReset(DeviceResetReason aReason) { if (aReason != DeviceResetReason::FORCED_RESET) { glean::gfx::device_reset_reason.AccumulateSingleSample(uint32_t(aReason)); } CrashReporter::RecordAnnotationU32( CrashReporter::Annotation::DeviceResetReason, static_cast(aReason)); } /* static */ void GPUProcessManager::NotifyDeviceReset(DeviceResetReason aReason, DeviceResetDetectPlace aPlace) { if (!NS_IsMainThread()) { NS_DispatchToMainThread(NS_NewRunnableFunction( "gfx::GPUProcessManager::NotifyDeviceReset", [aReason, aPlace]() -> void { gfx::GPUProcessManager::NotifyDeviceReset(aReason, aPlace); })); return; } #ifdef XP_WIN // Reset and reinitialize the compositor devices if (auto* deviceManager = DeviceManagerDx::Get()) { deviceManager->MaybeResetAndReacquireDevices(); } #else gfx::GPUProcessManager::RecordDeviceReset(aReason); #endif if (XRE_IsGPUProcess()) { if (auto* gpuParent = GPUParent::GetSingleton()) { // End up to GPUProcessManager::OnRemoteProcessDeviceReset() gpuParent->NotifyDeviceReset(aReason, aPlace); } else { MOZ_ASSERT_UNREACHABLE("unexpected to be called"); } return; } MOZ_ASSERT(XRE_IsParentProcess()); if (auto* gpm = GPUProcessManager::Get()) { gpm->OnInProcessDeviceReset(aReason, aPlace); } else { MOZ_ASSERT_UNREACHABLE("unexpected to be called"); } } bool GPUProcessManager::OnDeviceReset(bool aTrackThreshold) { // Ignore resets for thresholding if requested. if (!aTrackThreshold) { return false; } // Detect whether the device is resetting too quickly or too much // indicating that we should give up and use software mDeviceResetCount++; auto newTime = TimeStamp::Now(); auto delta = (int32_t)(newTime - mDeviceResetLastTime).ToMilliseconds(); mDeviceResetLastTime = newTime; // Returns true if we should disable acceleration due to the reset. return ShouldLimitDeviceResets(mDeviceResetCount, delta); } void GPUProcessManager::OnInProcessDeviceReset(DeviceResetReason aReason, DeviceResetDetectPlace aPlace) { gfxCriticalNote << "Detect DeviceReset " << aReason << " " << aPlace << " in Parent process"; bool guilty; switch (aReason) { case DeviceResetReason::HUNG: case DeviceResetReason::RESET: case DeviceResetReason::INVALID_CALL: guilty = true; break; default: guilty = false; break; } if (OnDeviceReset(guilty)) { gfxCriticalNoteOnce << "In-process device reset threshold exceeded"; #ifdef MOZ_WIDGET_GTK // FIXME(aosmond): Should we disable WebRender on other platforms? DisableWebRenderConfig(wr::WebRenderError::EXCESSIVE_RESETS, nsCString()); #endif } #ifdef XP_WIN // Ensure device reset handling before re-creating in process sessions. // Normally nsWindow::OnPaint() already handled it. gfxWindowsPlatform::GetPlatform()->HandleDeviceReset(); #endif DestroyInProcessCompositorSessions(); NotifyListenersOnCompositeDeviceReset(); } void GPUProcessManager::OnRemoteProcessDeviceReset( GPUProcessHost* aHost, const DeviceResetReason& aReason, const DeviceResetDetectPlace& aPlace) { gfxCriticalNote << "Detect DeviceReset " << aReason << " " << aPlace << " in GPU process"; if (OnDeviceReset(/* aTrackThreshold */ true)) { // If we have a stable GPU process, this may just be due to an OOM or bad // driver state. In that case, we should consider restarting the GPU process // to hopefully alleviate the situation. if (mProcess && (IsProcessStable(TimeStamp::Now()) || (kIsAndroid && !mAppInForeground))) { mProcess->KillProcess(/* aGenerateMinidump */ false); mLastError = Some(wr::WebRenderError::EXCESSIVE_RESETS); mLastErrorMsg = Some(""_ns); return; } DisableWebRenderConfig(wr::WebRenderError::EXCESSIVE_RESETS, ""_ns); } DestroyRemoteCompositorSessions(); NotifyListenersOnCompositeDeviceReset(); } void GPUProcessManager::NotifyListenersOnCompositeDeviceReset() { nsTArray> listeners; listeners.AppendElements(mListeners); for (const auto& listener : listeners) { listener->OnCompositorDeviceReset(); } } void GPUProcessManager::OnProcessUnexpectedShutdown(GPUProcessHost* aHost) { MOZ_ASSERT(mProcess && mProcess == aHost); if (StaticPrefs::layers_gpu_process_crash_also_crashes_browser()) { MOZ_CRASH("GPU process crashed and pref is set to crash the browser."); } CompositorManagerChild::OnGPUProcessLost(aHost->GetProcessToken()); DestroyProcess(/* aUnexpectedShutdown */ true); // If the process didn't live long enough, increment our unstable attempts // counter so that we don't end up in a restart loop. If the process did live // long enough, reset the counter so that we don't disable the process too // eagerly. if (IsProcessStable(TimeStamp::Now())) { mProcessStableOnce = true; mUnstableProcessAttempts = 0; } else if (kIsAndroid && !mAppInForeground) { // On Android if the process is lost whilst in the background it was // probably killed by the OS, and it may never have had a chance to have // been declared stable prior to being killed. We don't want this happening // repeatedly to result in the GPU process being disabled, so treat any // process lost whilst in the background as stable. mUnstableProcessAttempts = 0; } else { mUnstableProcessAttempts++; mozilla::glean::gpu_process::unstable_launch_attempts.Set( mUnstableProcessAttempts); } if (mUnstableProcessAttempts > uint32_t(StaticPrefs::layers_gpu_process_max_restarts())) { char disableMessage[64]; SprintfLiteral(disableMessage, "GPU process disabled after %d attempts", mTotalProcessAttempts); if (!MaybeDisableGPUProcess(disableMessage, /* aAllowRestart */ true)) { // Fallback wants the GPU process. Reset our counter. MOZ_DIAGNOSTIC_ASSERT(gfxConfig::IsEnabled(Feature::GPU_PROCESS)); mUnstableProcessAttempts = 0; HandleProcessLost(); } else { MOZ_DIAGNOSTIC_ASSERT(!gfxConfig::IsEnabled(Feature::GPU_PROCESS)); } } else if (mUnstableProcessAttempts > uint32_t(StaticPrefs:: layers_gpu_process_max_restarts_with_decoder()) && mDecodeVideoOnGpuProcess) { mDecodeVideoOnGpuProcess = false; mozilla::glean::gpu_process::crash_fallbacks.Get("decoding_disabled"_ns) .Add(1); HandleProcessLost(); } else { mozilla::glean::gpu_process::crash_fallbacks.Get("none"_ns).Add(1); HandleProcessLost(); } } void GPUProcessManager::HandleProcessLost() { MOZ_ASSERT(NS_IsMainThread()); // The shutdown and restart sequence for the GPU process is as follows: // // (1) The GPU process dies. IPDL will enqueue an ActorDestroy message on // each channel owning a bridge to the GPU process, on the thread owning // that channel. // // (2) The first channel to process its ActorDestroy message will post a // message to the main thread to call NotifyRemoteActorDestroyed on the // GPUProcessManager, which calls OnProcessUnexpectedShutdown if it has // not handled shutdown for this process yet. OnProcessUnexpectedShutdown // is responsible for tearing down the old process and deciding whether // or not to disable the GPU process. It then calls this function, // HandleProcessLost. // // (3) We then notify each widget that its session with the compositor is now // invalid. The widget is responsible for destroying its layer manager // and CompositorBridgeChild. Note that at this stage, not all actors may // have received ActorDestroy yet. CompositorBridgeChild may attempt to // send messages, and if this happens, it will probably report a // MsgDropped error. This is okay. // // (4) At this point, the UI process has a clean slate: no layers should // exist for the old compositor. We may make a decision on whether or not // to re-launch the GPU process. Or, on Android if the app is in the // background we may decide to wait until it comes to the foreground // before re-launching. // // (5) When we do decide to re-launch, or continue without a GPU process, we // notify each ContentParent of the lost connection. It will request new // endpoints from the GPUProcessManager and forward them to its // ContentChild. The parent-side of these endpoints may come from the // compositor thread of the UI process, or the compositor thread of the // GPU process. However, no actual compositors should exist yet. // // (6) Each ContentChild will receive new endpoints. It will destroy its // Compositor/ImageBridgeChild singletons and recreate them, as well // as invalidate all retained layers. // // (7) In addition, each ContentChild will ask each of its BrowserChildren // to re-request association with the compositor for the window // owning the tab. The sequence of calls looks like: // (a) [CONTENT] ContentChild::RecvReinitRendering // (b) [CONTENT] BrowserChild::ReinitRendering // (c) [CONTENT] BrowserChild::SendEnsureLayersConnected // (d) [UI] BrowserParent::RecvEnsureLayersConnected // (e) [UI] RemoteLayerTreeOwner::EnsureLayersConnected // (f) [UI] CompositorBridgeChild::SendNotifyChildRecreated // // Note that at step (e), RemoteLayerTreeOwner will call // GetWindowRenderer on the nsIWidget owning the tab. This step ensures // that a compositor exists for the window. If we decided to launch a new // GPU Process, at this point we block until the process has launched and // we're able to create a new window compositor. Otherwise, if // compositing is now in-process, this will simply create a new // CompositorBridgeParent in the UI process. If there are multiple tabs // in the same window, additional tabs will simply return the already- // established compositor. // // Finally, this step serves one other crucial function: tabs must be // associated with a window compositor or else they can't forward // layer transactions. So this step both ensures that a compositor // exists, and that the tab can forward layers. // // (8) Last, if the window had no remote tabs, step (7) will not have // applied, and the window will not have a new compositor just yet. The // next refresh tick and paint will ensure that one exists, again via // nsIWidget::GetWindowRenderer. On Android, we called // nsIWidgetListener::RequestRepaint back in step (3) to ensure this // tick occurs, but on other platforms this is not necessary. DestroyRemoteCompositorSessions(); #ifdef MOZ_WIDGET_ANDROID java::SurfaceControlManager::GetInstance()->OnGpuProcessLoss(); #endif // Re-launch the process if immediately if the GPU process is still enabled. // Except on Android if the app is in the background, where we want to wait // until the app is in the foreground again. if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { #ifdef MOZ_WIDGET_ANDROID if (mAppInForeground) { #else { #endif (void)LaunchGPUProcess(); } } else { // If the GPU process is disabled we can reinitialize rendering immediately. // This will be handled in OnProcessLaunchComplete() if the GPU process is // enabled. ReinitializeRendering(); } } void GPUProcessManager::ReinitializeRendering() { // Notify content. This will ensure that each content process re-establishes // a connection to the compositor thread (whether it's in-process or in a // newly launched GPU process). nsTArray> listeners; listeners.AppendElements(mListeners); // Make sure any fallback renderers get destroyed first. for (const auto& listener : listeners) { listener->OnCompositorDestroyBackgrounded(); } // Then do the recreations. for (const auto& listener : listeners) { listener->OnCompositorUnexpectedShutdown(); } // Notify any observers that the compositor has been reinitialized, // eg the ZoomConstraintsClients for parent process documents. nsCOMPtr observerService = services::GetObserverService(); if (observerService) { observerService->NotifyObservers(nullptr, "compositor-reinitialized", nullptr); } } void GPUProcessManager::DestroyRemoteCompositorSessions() { // Build a list of sessions to notify, since notification might delete // entries from the list. nsTArray> sessions; for (auto& session : mRemoteSessions) { sessions.AppendElement(session); } // Notify each widget that we have lost the GPU process. This will ensure // that each widget destroys its layer manager and CompositorBridgeChild. for (const auto& session : sessions) { session->NotifySessionLost(); } } void GPUProcessManager::DestroyInProcessCompositorSessions() { // Build a list of sessions to notify, since notification might delete // entries from the list. nsTArray> sessions; for (auto& session : mInProcessSessions) { sessions.AppendElement(session); } // Notify each widget that we have lost the GPU process. This will ensure // that each widget destroys its layer manager and CompositorBridgeChild. for (const auto& session : sessions) { session->NotifySessionLost(); } // Ensure our stablility state is reset so that we don't necessarily crash // right away on some WebRender errors. CompositorBridgeParent::ResetStable(); ResetProcessStable(); } void GPUProcessManager::NotifyRemoteActorDestroyed( const uint64_t& aProcessToken) { if (!NS_IsMainThread()) { RefPtr task = mTaskFactory.NewRunnableMethod( &GPUProcessManager::NotifyRemoteActorDestroyed, aProcessToken); NS_DispatchToMainThread(task.forget()); return; } if (mProcessToken != aProcessToken) { // This token is for an older process; we can safely ignore it. return; } // One of the bridged top-level actors for the GPU process has been // prematurely terminated, and we're receiving a notification. This // can happen if the ActorDestroy for a bridged protocol fires // before the ActorDestroy for PGPUChild. OnProcessUnexpectedShutdown(mProcess); } void GPUProcessManager::ShutdownInternal() { if (mObserver) { mObserver->Shutdown(); mObserver = nullptr; } DestroyProcess(); mVsyncIOThread = nullptr; } void GPUProcessManager::KillProcess(bool aGenerateMinidump) { if (!NS_IsMainThread()) { RefPtr task = mTaskFactory.NewRunnableMethod( &GPUProcessManager::KillProcess, aGenerateMinidump); NS_DispatchToMainThread(task.forget()); return; } if (!mProcess) { return; } mProcess->KillProcess(aGenerateMinidump); } void GPUProcessManager::CrashProcess() { if (!mProcess) { return; } mProcess->CrashProcess(); } void GPUProcessManager::DestroyProcess(bool aUnexpectedShutdown) { if (!mProcess) { return; } mProcess->Shutdown(aUnexpectedShutdown); mProcessToken = 0; mProcess = nullptr; mGPUChild = nullptr; mQueuedPrefs.Clear(); if (mVsyncBridge) { mVsyncBridge->Close(); mVsyncBridge = nullptr; } StopBatteryObserving(); CrashReporter::RecordAnnotationCString( CrashReporter::Annotation::GPUProcessStatus, "Destroyed"); } void GPUProcessManager::StopBatteryObserving() { if (mBatteryObserver) { mBatteryObserver->Shutdown(); mBatteryObserver = nullptr; } } already_AddRefed GPUProcessManager::CreateTopLevelCompositor( nsIWidget* aWidget, WebRenderLayerManager* aLayerManager, CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize, uint64_t aInnerWindowId, bool* aRetryOut) { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); MOZ_ASSERT(aRetryOut); if (!EnsureProtocolsReady()) { *aRetryOut = false; return nullptr; } LayersId layerTreeId = AllocateLayerTreeId(); RefPtr session; if (mGPUChild) { session = CreateRemoteSession(aWidget, aLayerManager, layerTreeId, aScale, aOptions, aUseExternalSurfaceSize, aSurfaceSize, aInnerWindowId); if (NS_WARN_IF(!session)) { // This may have failed for intermittent reasons, or perhaps indicates we // are fundamentally unable to use acceleration. // OnProcessUnexpectedShutdown will first attempt to relaunch the GPU // process in the same configuration a number of times, then fallback from // acceleration, then finally disable the GPU process if it continues to // fail. OnProcessUnexpectedShutdown(mProcess); *aRetryOut = true; return nullptr; } } else { session = InProcessCompositorSession::Create( aWidget, aLayerManager, layerTreeId, aScale, aOptions, aUseExternalSurfaceSize, aSurfaceSize, AllocateNamespace(), aInnerWindowId); } #if defined(MOZ_WIDGET_ANDROID) if (session) { // Nothing to do if controller gets a nullptr auto controller = CreateUiCompositorController(aWidget, session->RootLayerTreeId()); MOZ_ASSERT(controller); session->SetUiCompositorControllerChild(std::move(controller)); } #endif // defined(MOZ_WIDGET_ANDROID) *aRetryOut = false; return session.forget(); } RefPtr GPUProcessManager::CreateRemoteSession( nsIWidget* aWidget, WebRenderLayerManager* aLayerManager, const LayersId& aRootLayerTreeId, CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize, uint64_t aInnerWindowId) { #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING widget::CompositorWidgetInitData initData; aWidget->GetCompositorWidgetInitData(&initData); RefPtr child = CompositorManagerChild::CreateWidgetCompositorBridge( mProcessToken, aLayerManager, AllocateNamespace(), aScale, aOptions, aUseExternalSurfaceSize, aSurfaceSize, aInnerWindowId); if (!child) { gfxCriticalNote << "Failed to create CompositorBridgeChild"; return nullptr; } RefPtr dispatcher = aWidget->GetCompositorVsyncDispatcher(); RefPtr observer = new widget::CompositorWidgetVsyncObserver(mVsyncBridge, aRootLayerTreeId); widget::CompositorWidgetChild* widget = new widget::CompositorWidgetChild(dispatcher, observer, initData); if (!child->SendPCompositorWidgetConstructor(widget, std::move(initData))) { return nullptr; } if (!widget->Initialize()) { return nullptr; } if (!child->SendInitialize(aRootLayerTreeId)) { return nullptr; } RefPtr apz = nullptr; if (aOptions.UseAPZ()) { PAPZCTreeManagerChild* papz = child->SendPAPZCTreeManagerConstructor(LayersId{0}); if (!papz) { return nullptr; } apz = static_cast(papz); ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PAPZInputBridge::CreateEndpoints( mGPUChild->OtherEndpointProcInfo(), ipc::EndpointProcInfo::Current(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { return nullptr; } mGPUChild->SendInitAPZInputBridge(aRootLayerTreeId, std::move(parentPipe)); RefPtr inputBridge = APZInputBridgeChild::Create(mProcessToken, std::move(childPipe)); if (!inputBridge) { return nullptr; } apz->SetInputBridge(inputBridge); } return new RemoteCompositorSession(aWidget, child, widget, apz, aRootLayerTreeId); #else gfxCriticalNote << "Platform does not support out-of-process compositing"; return nullptr; #endif } bool GPUProcessManager::CreateContentBridges( ipc::EndpointProcInfo aOtherProcess, ipc::Endpoint* aOutCompositor, ipc::Endpoint* aOutImageBridge, ipc::Endpoint* aOutVRBridge, ipc::Endpoint* aOutVideoManager, dom::ContentParentId aChildId, nsTArray* aNamespaces) { const uint32_t cmNamespace = AllocateNamespace(); if (!CreateContentCompositorManager(aOtherProcess, aChildId, cmNamespace, aOutCompositor) || !CreateContentImageBridge(aOtherProcess, aChildId, aOutImageBridge) || !CreateContentVRManager(aOtherProcess, aChildId, aOutVRBridge)) { return false; } // RemoteMediaManager is only supported in the GPU process, so we allow this // to be fallible. CreateContentRemoteMediaManager(aOtherProcess, aChildId, aOutVideoManager); // Allocates 3 namespaces(for CompositorManagerChild, CompositorBridgeChild // and ImageBridgeChild) aNamespaces->AppendElement(cmNamespace); aNamespaces->AppendElement(AllocateNamespace()); aNamespaces->AppendElement(AllocateNamespace()); return true; } bool GPUProcessManager::CreateContentCompositorManager( ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId, uint32_t aNamespace, ipc::Endpoint* aOutEndpoint) { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); ipc::Endpoint parentPipe; ipc::Endpoint childPipe; ipc::EndpointProcInfo parentInfo = mGPUChild ? mGPUChild->OtherEndpointProcInfo() : ipc::EndpointProcInfo::Current(); nsresult rv = PCompositorManager::CreateEndpoints(parentInfo, aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content compositor manager: " << hexa(int(rv)); return false; } if (mGPUChild) { mGPUChild->SendNewContentCompositorManager(std::move(parentPipe), aChildId, aNamespace); } else if (!CompositorManagerParent::Create(std::move(parentPipe), aChildId, aNamespace, /* aIsRoot */ false)) { return false; } *aOutEndpoint = std::move(childPipe); return true; } bool GPUProcessManager::CreateContentImageBridge( ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId, ipc::Endpoint* aOutEndpoint) { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (!EnsureImageBridgeChild()) { return false; } ipc::EndpointProcInfo parentInfo = mGPUChild ? mGPUChild->OtherEndpointProcInfo() : ipc::EndpointProcInfo::Current(); ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PImageBridge::CreateEndpoints(parentInfo, aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content compositor bridge: " << hexa(int(rv)); return false; } if (mGPUChild) { mGPUChild->SendNewContentImageBridge(std::move(parentPipe), aChildId); } else { if (!ImageBridgeParent::CreateForContent(std::move(parentPipe), aChildId)) { return false; } } *aOutEndpoint = std::move(childPipe); return true; } base::ProcessId GPUProcessManager::GPUProcessPid() { base::ProcessId gpuPid = mGPUChild ? mGPUChild->OtherPid() : base::kInvalidProcessId; return gpuPid; } ipc::EndpointProcInfo GPUProcessManager::GPUEndpointProcInfo() { return mGPUChild ? mGPUChild->OtherEndpointProcInfo() : ipc::EndpointProcInfo::Invalid(); } bool GPUProcessManager::CreateContentVRManager( ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId, ipc::Endpoint* aOutEndpoint) { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (NS_WARN_IF(!EnsureVRManager())) { return false; } ipc::EndpointProcInfo parentInfo = mGPUChild ? mGPUChild->OtherEndpointProcInfo() : ipc::EndpointProcInfo::Current(); ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PVRManager::CreateEndpoints(parentInfo, aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content compositor bridge: " << hexa(int(rv)); return false; } if (mGPUChild) { mGPUChild->SendNewContentVRManager(std::move(parentPipe), aChildId); } else { if (!VRManagerParent::CreateForContent(std::move(parentPipe), aChildId)) { return false; } } *aOutEndpoint = std::move(childPipe); return true; } void GPUProcessManager::CreateContentRemoteMediaManager( ipc::EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId, ipc::Endpoint* aOutEndpoint) { MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); if (!mGPUChild || !StaticPrefs::media_gpu_process_decoder() || !mDecodeVideoOnGpuProcess) { return; } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PRemoteMediaManager::CreateEndpoints( mGPUChild->OtherEndpointProcInfo(), aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content video decoder: " << hexa(int(rv)); return; } mGPUChild->SendNewContentRemoteMediaManager(std::move(parentPipe), aChildId); *aOutEndpoint = std::move(childPipe); } #ifdef MOZ_WMF_MEDIA_ENGINE nsresult GPUProcessManager::CreateUtilityMFCDMVideoBridge( mozilla::ipc::UtilityMediaServiceChild* aChild, mozilla::ipc::EndpointProcInfo aOtherProcess) { MOZ_ASSERT(aChild); MOZ_ASSERT(aChild->CanSend()); ipc::Endpoint childPipe; nsresult rv = EnsureVideoBridge(VideoBridgeSource::MFMediaEngineCDMProcess, aOtherProcess, &childPipe); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } gfx::ContentDeviceData contentDeviceData; gfxPlatform::GetPlatform()->BuildContentDeviceData(&contentDeviceData); aChild->SendInitVideoBridge(std::move(childPipe), contentDeviceData); return NS_OK; } #endif nsresult GPUProcessManager::CreateRddVideoBridge(RDDProcessManager* aRDD, RDDChild* aChild) { MOZ_ASSERT(aRDD); MOZ_ASSERT(aChild); MOZ_ASSERT(aChild->CanSend()); ipc::Endpoint childPipe; nsresult rv = EnsureVideoBridge(VideoBridgeSource::RddProcess, aChild->OtherEndpointProcInfo(), &childPipe); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } gfx::ContentDeviceData contentDeviceData; gfxPlatform::GetPlatform()->BuildContentDeviceData(&contentDeviceData); aChild->SendInitVideoBridge(std::move(childPipe), !aRDD->AttemptedRDDProcess(), contentDeviceData); return NS_OK; } nsresult GPUProcessManager::EnsureVideoBridge( layers::VideoBridgeSource aSource, mozilla::ipc::EndpointProcInfo aOtherProcess, mozilla::ipc::Endpoint* aOutChildPipe) { MOZ_ASSERT(aOutChildPipe); MOZ_DIAGNOSTIC_ASSERT(IsGPUReady()); ipc::EndpointProcInfo gpuInfo = mGPUChild ? mGPUChild->OtherEndpointProcInfo() : ipc::EndpointProcInfo::Current(); // The child end is the producer of video frames; the parent end is the // consumer. ipc::Endpoint parentPipe; nsresult rv = PVideoBridge::CreateEndpoints(gpuInfo, aOtherProcess, &parentPipe, aOutChildPipe); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (mGPUChild) { mGPUChild->SendInitVideoBridge(std::move(parentPipe), aSource); } else { VideoBridgeParent::Open(std::move(parentPipe), aSource); } return NS_OK; } void GPUProcessManager::UnmapLayerTreeId(LayersId aLayersId, base::ProcessId aOwningId) { // If the GPU process is down, but not disabled, there is no need to relaunch // here because the layers ID is already invalid. if (mGPUChild) { mGPUChild->SendRemoveLayerTreeIdMapping( LayerTreeIdMapping(aLayersId, aOwningId)); } else if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { CompositorBridgeParent::DeallocateLayerTreeId(aLayersId); } LayerTreeOwnerTracker::Get()->Unmap(aLayersId, aOwningId); } bool GPUProcessManager::IsLayerTreeIdMapped(LayersId aLayersId, base::ProcessId aRequestingId) { return LayerTreeOwnerTracker::Get()->IsMapped(aLayersId, aRequestingId); } LayersId GPUProcessManager::AllocateLayerTreeId() { // Allocate tree id by using id namespace. // By it, tree id does not conflict with external image id and // async image pipeline id. MOZ_ASSERT(NS_IsMainThread()); // Increment the resource id by two instead of one so that each // WebRenderLayerManager and WebRenderBridgeParent gets two distinct // pipeline IDs they can use. // This is gross but the steps to create a temporary pipeline // ID from the content process or the compositor thread are too // complex and expensive. // TODO: Ideally, we'd allocate only the namespace here and let the // WR layer manager produce any number of pipeline IDs. mResourceId += 2; if (mResourceId >= UINT32_MAX - 1) { // Move to next id namespace. mIdNamespace = AllocateNamespace(); mResourceId = 2; } uint64_t layerTreeId = mIdNamespace; layerTreeId = (layerTreeId << 32) | mResourceId; return LayersId{layerTreeId}; } // See the comment in AllocateLayerTreeId above. // For now this is only used for view-transition snapshots of the old state, // it's probably best to avoid using this for anything else. wr::PipelineId GetTemporaryWebRenderPipelineId(wr::PipelineId aMainPipeline) { // Sanity check that we are have the expected even number for // the main pipeline handle. MOZ_ASSERT(aMainPipeline.mHandle % 2 == 0); auto id = aMainPipeline; id.mHandle += 1; return id; } uint32_t GPUProcessManager::AllocateNamespace() { MOZ_ASSERT(NS_IsMainThread()); return ++mNextNamespace; } bool GPUProcessManager::AllocateAndConnectLayerTreeId( PCompositorBridgeChild* aCompositorBridge, base::ProcessId aOtherPid, LayersId* aOutLayersId, CompositorOptions* aOutCompositorOptions) { MOZ_ASSERT(aOutLayersId); LayersId layersId = AllocateLayerTreeId(); *aOutLayersId = layersId; // We always map the layer ID in the parent process so that we can recover // from GPU process crashes. In that case, the tree will be shared with the // new GPU process at initialization. LayerTreeOwnerTracker::Get()->Map(layersId, aOtherPid); if (NS_WARN_IF(NS_FAILED(EnsureGPUReady()))) { return false; } // If we have a CompositorBridgeChild, then we need to call // CompositorBridgeParent::NotifyChildCreated. If this is in the GPU process, // we can combine it with LayerTreeOwnerTracker::Map to minimize IPC. // messages. if (aCompositorBridge) { if (mGPUChild) { return aCompositorBridge->SendMapAndNotifyChildCreated( layersId, aOtherPid, aOutCompositorOptions); } return aCompositorBridge->SendNotifyChildCreated(layersId, aOutCompositorOptions); } // If we don't have a CompositorBridgeChild, we just need to call // LayerTreeOwnerTracker::Map in the compositing process. if (mGPUChild) { mGPUChild->SendAddLayerTreeIdMapping( LayerTreeIdMapping(layersId, aOtherPid)); } return false; } void GPUProcessManager::EnsureVsyncIOThread() { if (mVsyncIOThread) { return; } mVsyncIOThread = new VsyncIOThreadHolder(); MOZ_RELEASE_ASSERT(mVsyncIOThread->Start()); } void GPUProcessManager::ShutdownVsyncIOThread() { mVsyncIOThread = nullptr; } void GPUProcessManager::RegisterRemoteProcessSession( RemoteCompositorSession* aSession) { mRemoteSessions.AppendElement(aSession); } void GPUProcessManager::UnregisterRemoteProcessSession( RemoteCompositorSession* aSession) { mRemoteSessions.RemoveElement(aSession); } void GPUProcessManager::RegisterInProcessSession( InProcessCompositorSession* aSession) { mInProcessSessions.AppendElement(aSession); } void GPUProcessManager::UnregisterInProcessSession( InProcessCompositorSession* aSession) { mInProcessSessions.RemoveElement(aSession); } void GPUProcessManager::AddListener(GPUProcessListener* aListener) { if (!mListeners.Contains(aListener)) { mListeners.AppendElement(aListener); } } void GPUProcessManager::RemoveListener(GPUProcessListener* aListener) { mListeners.RemoveElement(aListener); } bool GPUProcessManager::NotifyGpuObservers(const char* aTopic) { if (mGPUChild) { nsCString topic(aTopic); mGPUChild->SendNotifyGpuObservers(topic); return true; } if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { nsCOMPtr obsSvc = mozilla::services::GetObserverService(); MOZ_ASSERT(obsSvc); if (obsSvc) { obsSvc->NotifyObservers(nullptr, aTopic, nullptr); } return true; } // If we still are using a GPU process, but do not have one ready at the // moment, we can drop these notifications since there is nothing to do. return false; } class GPUMemoryReporter : public MemoryReportingProcess { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GPUMemoryReporter, override) bool IsAlive() const override { if (GPUProcessManager* gpm = GPUProcessManager::Get()) { return !!gpm->GetGPUChild(); } return false; } bool SendRequestMemoryReport( const uint32_t& aGeneration, const bool& aAnonymize, const bool& aMinimizeMemoryUsage, const Maybe& aDMDFile) override { GPUChild* child = GetChild(); if (!child) { return false; } return child->SendRequestMemoryReport(aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile); } int32_t Pid() const override { if (GPUChild* child = GetChild()) { return (int32_t)child->OtherPid(); } return 0; } private: GPUChild* GetChild() const { if (GPUProcessManager* gpm = GPUProcessManager::Get()) { return gpm->GetGPUChild(); } return nullptr; } protected: ~GPUMemoryReporter() = default; }; RefPtr GPUProcessManager::GetProcessMemoryReporter() { // If we are in the middle of launching a GPU process, we can wait for it to // finish, otherwise if there is no GPU process, we should just return now to // avoid launching it again. if (!mProcess || AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown) || !mProcess->WaitForLaunch()) { return nullptr; } return MakeRefPtr(); } void GPUProcessManager::SetAppInForeground(bool aInForeground) { if (mAppInForeground == aInForeground) { return; } mAppInForeground = aInForeground; #if defined(XP_WIN) SetProcessIsForeground(); #endif // If we moved into the foreground, then we need to make sure the GPU process // completes its launch. Otherwise listeners may be left dangling from // previous calls that returned NS_ERROR_ABORT due to being in the background. if (aInForeground && gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { (void)LaunchGPUProcess(); } } #if defined(XP_WIN) void GPUProcessManager::SetProcessIsForeground() { NTSTATUS WINAPI NtSetInformationProcess( IN HANDLE process_handle, IN ULONG info_class, IN PVOID process_information, IN ULONG information_length); constexpr unsigned int NtProcessInformationForeground = 25; static bool alreadyInitialized = false; static decltype(NtSetInformationProcess)* setInformationProcess = nullptr; if (!alreadyInitialized) { alreadyInitialized = true; nsModuleHandle module(LoadLibrary(L"ntdll.dll")); if (module) { setInformationProcess = (decltype(NtSetInformationProcess)*)GetProcAddress( module, "NtSetInformationProcess"); } } if (MOZ_UNLIKELY(!setInformationProcess)) { return; } unsigned pid = GPUProcessPid(); if (pid <= 0) { return; } // Using the handle from mProcess->GetChildProcessHandle() fails; // the PROCESS_SET_INFORMATION permission is probably missing. nsAutoHandle processHandle( ::OpenProcess(PROCESS_SET_INFORMATION, FALSE, pid)); if (!processHandle) { return; } BOOLEAN foreground = mAppInForeground; setInformationProcess(processHandle, NtProcessInformationForeground, (PVOID)&foreground, sizeof(foreground)); } #endif RefPtr GPUProcessManager::TestTriggerMetrics() { if (!NS_WARN_IF(!mGPUChild)) { return mGPUChild->SendTestTriggerMetrics(); } return PGPUChild::TestTriggerMetricsPromise::CreateAndReject( ipc::ResponseRejectReason::SendError, __func__); } } // namespace gfx } // namespace mozilla