/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=99: */ /* 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 "GPUProcessHost.h" #include "GPUProcessListener.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/layers/APZCTreeManager.h" #include "mozilla/layers/APZCTreeManagerChild.h" #include "mozilla/layers/CompositorBridgeParent.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/widget/PlatformWidgetTypes.h" #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING # include "mozilla/widget/CompositorWidgetChild.h" #endif #include "nsBaseWidget.h" #include "nsContentUtils.h" #include "VsyncBridgeChild.h" #include "VsyncIOThreadHolder.h" #include "VsyncSource.h" #include "mozilla/dom/VideoDecoderManagerChild.h" #include "mozilla/dom/VideoDecoderManagerParent.h" #include "MediaPrefs.h" 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() { sSingleton = nullptr; } GPUProcessManager::GPUProcessManager() : mTaskFactory(this), mNextLayerTreeId(0), mNumProcessAttempts(0), mDeviceResetCount(0), mProcess(nullptr), mGPUChild(nullptr) { MOZ_COUNT_CTOR(GPUProcessManager); mObserver = new Observer(this); nsContentUtils::RegisterShutdownObserver(mObserver); mDeviceResetLastTime = TimeStamp::Now(); LayerTreeOwnerTracker::Initialize(); } 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_ASSERT(!mObserver); } NS_IMPL_ISUPPORTS(GPUProcessManager::Observer, nsIObserver); GPUProcessManager::Observer::Observer(GPUProcessManager* aManager) : mManager(aManager) { } NS_IMETHODIMP GPUProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { mManager->OnXPCOMShutdown(); } return NS_OK; } void GPUProcessManager::OnXPCOMShutdown() { if (mObserver) { nsContentUtils::UnregisterShutdownObserver(mObserver); mObserver = nullptr; } CleanShutdown(); } void GPUProcessManager::LaunchGPUProcess() { if (mProcess) { return; } // Start the Vsync I/O thread so can use it as soon as the process launches. EnsureVsyncIOThread(); mNumProcessAttempts++; // The subprocess is launched asynchronously, so we wait for a callback to // acquire the IPDL actor. mProcess = new GPUProcessHost(this); if (!mProcess->Launch()) { DisableGPUProcess("Failed to launch GPU process"); } } void GPUProcessManager::DisableGPUProcess(const char* aMessage) { if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { return; } gfxConfig::SetFailed(Feature::GPU_PROCESS, FeatureStatus::Failed, aMessage); gfxCriticalNote << aMessage; DestroyProcess(); ShutdownVsyncIOThread(); } void GPUProcessManager::EnsureGPUReady() { if (mProcess && !mProcess->IsConnected()) { if (!mProcess->WaitForLaunch()) { // If this fails, we should have fired OnProcessLaunchComplete and // removed the process. MOZ_ASSERT(!mProcess && !mGPUChild); return; } } if (mGPUChild) { mGPUChild->EnsureGPUReady(); } } void GPUProcessManager::EnsureImageBridgeChild() { if (ImageBridgeChild::GetSingleton()) { return; } EnsureGPUReady(); if (!mGPUChild) { ImageBridgeChild::InitSameProcess(); return; } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PImageBridge::CreateEndpoints( mGPUChild->OtherPid(), base::GetCurrentProcId(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PImageBridge endpoints"); return; } mGPUChild->SendInitImageBridge(Move(parentPipe)); ImageBridgeChild::InitWithGPUProcess(Move(childPipe)); } void GPUProcessManager::OnProcessLaunchComplete(GPUProcessHost* aHost) { MOZ_ASSERT(mProcess && mProcess == aHost); if (!mProcess->IsConnected()) { DisableGPUProcess("Failed to launch GPU process"); return; } mGPUChild = mProcess->GetActor(); mProcessToken = mProcess->GetProcessToken(); Endpoint vsyncParent; Endpoint vsyncChild; nsresult rv = PVsyncBridge::CreateEndpoints( mGPUChild->OtherPid(), base::GetCurrentProcId(), &vsyncParent, &vsyncChild); if (NS_FAILED(rv)) { DisableGPUProcess("Failed to create PVsyncBridge endpoints"); return; } mVsyncBridge = VsyncBridgeChild::Create(mVsyncIOThread, mProcessToken, Move(vsyncChild)); mGPUChild->SendInitVsyncBridge(Move(vsyncParent)); nsTArray mappings; LayerTreeOwnerTracker::Get()->Iterate([&](uint64_t aLayersId, base::ProcessId aProcessId) { mappings.AppendElement(LayerTreeIdMapping(aLayersId, aProcessId)); }); mGPUChild->SendAddLayerTreeIdMapping(mappings); } 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 = gfxPrefs::DeviceResetThresholdMilliseconds(); int32_t countLimit = gfxPrefs::DeviceResetLimitCount(); bool hasTimeLimit = timeLimit != -1; bool hasCountLimit = countLimit != -1; 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::OnProcessDeviceReset(GPUProcessHost* aHost) { // 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; if (ShouldLimitDeviceResets(mDeviceResetCount, delta)) { DestroyProcess(); DisableGPUProcess("GPU processed experienced too many device resets"); HandleProcessLost(); return; } // We're good, do a reset like normal for (auto& session : mRemoteSessions) { session->NotifyDeviceReset(); } } void GPUProcessManager::OnProcessUnexpectedShutdown(GPUProcessHost* aHost) { MOZ_ASSERT(mProcess && mProcess == aHost); DestroyProcess(); if (mNumProcessAttempts > uint32_t(gfxPrefs::GPUProcessDevMaxRestarts())) { DisableGPUProcess("GPU processed crashed too many times"); } HandleProcessLost(); } void GPUProcessManager::HandleProcessLost() { if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) { LaunchGPUProcess(); } // 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. // // (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. Currently, we do not relaunch it, // and any new compositors will be created in-process and will default // to software. // // (5) Next 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 TabChildren // 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] TabChild::ReinitRendering // (c) [CONTENT] TabChild::SendEnsureLayersConnected // (d) [UI] TabParent::RecvEnsureLayersConnected // (e) [UI] RenderFrameParent::EnsureLayersConnected // (f) [UI] CompositorBridgeChild::SendNotifyChildRecreated // // Note that at step (e), RenderFrameParent will call GetLayerManager // 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::GetLayerManager. // 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(); } // 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). for (const auto& listener : mListeners) { listener->OnCompositorUnexpectedShutdown(); } } 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::CleanShutdown() { DestroyProcess(); mVsyncIOThread = nullptr; } void GPUProcessManager::KillProcess() { if (!mProcess) { return; } mProcess->KillProcess(); } void GPUProcessManager::DestroyProcess() { if (!mProcess) { return; } mProcess->Shutdown(); mProcessToken = 0; mProcess = nullptr; mGPUChild = nullptr; if (mVsyncBridge) { mVsyncBridge->Close(); mVsyncBridge = nullptr; } } RefPtr GPUProcessManager::CreateTopLevelCompositor(nsBaseWidget* aWidget, LayerManager* aLayerManager, CSSToLayoutDeviceScale aScale, bool aUseAPZ, bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) { uint64_t layerTreeId = AllocateLayerTreeId(); EnsureGPUReady(); EnsureImageBridgeChild(); if (mGPUChild) { RefPtr session = CreateRemoteSession( aWidget, aLayerManager, layerTreeId, aScale, aUseAPZ, aUseExternalSurfaceSize, aSurfaceSize); if (session) { return session; } // We couldn't create a remote compositor, so abort the process. DisableGPUProcess("Failed to create remote compositor"); } return InProcessCompositorSession::Create( aWidget, aLayerManager, layerTreeId, aScale, aUseAPZ, aUseExternalSurfaceSize, aSurfaceSize); } RefPtr GPUProcessManager::CreateRemoteSession(nsBaseWidget* aWidget, LayerManager* aLayerManager, const uint64_t& aRootLayerTreeId, CSSToLayoutDeviceScale aScale, bool aUseAPZ, bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) { #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PCompositorBridge::CreateEndpoints( mGPUChild->OtherPid(), base::GetCurrentProcId(), &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Failed to create PCompositorBridge endpoints: " << hexa(int(rv)); return nullptr; } RefPtr child = CompositorBridgeChild::CreateRemote( mProcessToken, aLayerManager, Move(childPipe)); if (!child) { gfxCriticalNote << "Failed to create CompositorBridgeChild"; return nullptr; } CompositorWidgetInitData initData; aWidget->GetCompositorWidgetInitData(&initData); TimeDuration vsyncRate = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay().GetVsyncRate(); bool ok = mGPUChild->SendNewWidgetCompositor( Move(parentPipe), aScale, vsyncRate, aUseExternalSurfaceSize, aSurfaceSize); if (!ok) { return nullptr; } RefPtr dispatcher = aWidget->GetCompositorVsyncDispatcher(); RefPtr observer = new CompositorWidgetVsyncObserver(mVsyncBridge, aRootLayerTreeId); CompositorWidgetChild* widget = new CompositorWidgetChild(dispatcher, observer); if (!child->SendPCompositorWidgetConstructor(widget, initData)) { return nullptr; } if (!child->SendInitialize(aRootLayerTreeId)) { return nullptr; } RefPtr apz = nullptr; if (aUseAPZ) { PAPZCTreeManagerChild* papz = child->SendPAPZCTreeManagerConstructor(0); if (!papz) { return nullptr; } apz = static_cast(papz); } RefPtr session = new RemoteCompositorSession(aWidget, child, widget, apz, aRootLayerTreeId); return session.forget(); #else gfxCriticalNote << "Platform does not support out-of-process compositing"; return nullptr; #endif } bool GPUProcessManager::CreateContentBridges(base::ProcessId aOtherProcess, ipc::Endpoint* aOutCompositor, ipc::Endpoint* aOutImageBridge, ipc::Endpoint* aOutVideoManager) { if (!CreateContentCompositorBridge(aOtherProcess, aOutCompositor) || !CreateContentImageBridge(aOtherProcess, aOutImageBridge)) { return false; } // VideoDeocderManager is only supported in the GPU process, so we allow this to be // fallible. CreateContentVideoDecoderManager(aOtherProcess, aOutVideoManager); return true; } bool GPUProcessManager::CreateContentCompositorBridge(base::ProcessId aOtherProcess, ipc::Endpoint* aOutEndpoint) { EnsureGPUReady(); ipc::Endpoint parentPipe; ipc::Endpoint childPipe; base::ProcessId gpuPid = mGPUChild ? mGPUChild->OtherPid() : base::GetCurrentProcId(); nsresult rv = PCompositorBridge::CreateEndpoints( gpuPid, aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content compositor bridge: " << hexa(int(rv)); return false; } if (mGPUChild) { mGPUChild->SendNewContentCompositorBridge(Move(parentPipe)); } else { if (!CompositorBridgeParent::CreateForContent(Move(parentPipe))) { return false; } } *aOutEndpoint = Move(childPipe); return true; } bool GPUProcessManager::CreateContentImageBridge(base::ProcessId aOtherProcess, ipc::Endpoint* aOutEndpoint) { EnsureImageBridgeChild(); base::ProcessId gpuPid = mGPUChild ? mGPUChild->OtherPid() : base::GetCurrentProcId(); ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = PImageBridge::CreateEndpoints( gpuPid, aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content compositor bridge: " << hexa(int(rv)); return false; } if (mGPUChild) { mGPUChild->SendNewContentImageBridge(Move(parentPipe)); } else { if (!ImageBridgeParent::CreateForContent(Move(parentPipe))) { return false; } } *aOutEndpoint = Move(childPipe); return true; } base::ProcessId GPUProcessManager::GPUProcessPid() { base::ProcessId gpuPid = mGPUChild ? mGPUChild->OtherPid() : -1; return gpuPid; } void GPUProcessManager::CreateContentVideoDecoderManager(base::ProcessId aOtherProcess, ipc::Endpoint* aOutEndpoint) { if (!mGPUChild || !MediaPrefs::PDMUseGPUDecoder()) { return; } ipc::Endpoint parentPipe; ipc::Endpoint childPipe; nsresult rv = dom::PVideoDecoderManager::CreateEndpoints( mGPUChild->OtherPid(), aOtherProcess, &parentPipe, &childPipe); if (NS_FAILED(rv)) { gfxCriticalNote << "Could not create content video decoder: " << hexa(int(rv)); return; } mGPUChild->SendNewContentVideoDecoderManager(Move(parentPipe)); *aOutEndpoint = Move(childPipe); return; } already_AddRefed GPUProcessManager::GetAPZCTreeManagerForLayers(uint64_t aLayersId) { return CompositorBridgeParent::GetAPZCTreeManager(aLayersId); } void GPUProcessManager::MapLayerTreeId(uint64_t aLayersId, base::ProcessId aOwningId) { LayerTreeOwnerTracker::Get()->Map(aLayersId, aOwningId); if (mGPUChild) { AutoTArray mappings; mappings.AppendElement(LayerTreeIdMapping(aLayersId, aOwningId)); mGPUChild->SendAddLayerTreeIdMapping(mappings); } } void GPUProcessManager::UnmapLayerTreeId(uint64_t aLayersId, base::ProcessId aOwningId) { LayerTreeOwnerTracker::Get()->Unmap(aLayersId, aOwningId); if (mGPUChild) { mGPUChild->SendRemoveLayerTreeIdMapping(LayerTreeIdMapping(aLayersId, aOwningId)); return; } CompositorBridgeParent::DeallocateLayerTreeId(aLayersId); } bool GPUProcessManager::IsLayerTreeIdMapped(uint64_t aLayersId, base::ProcessId aRequestingId) { return LayerTreeOwnerTracker::Get()->IsMapped(aLayersId, aRequestingId); } uint64_t GPUProcessManager::AllocateLayerTreeId() { MOZ_ASSERT(NS_IsMainThread()); return ++mNextLayerTreeId; } void GPUProcessManager::EnsureVsyncIOThread() { if (mVsyncIOThread) { return; } mVsyncIOThread = new VsyncIOThreadHolder(); MOZ_RELEASE_ASSERT(mVsyncIOThread->Start()); } void GPUProcessManager::ShutdownVsyncIOThread() { mVsyncIOThread = nullptr; } void GPUProcessManager::RegisterSession(RemoteCompositorSession* aSession) { mRemoteSessions.AppendElement(aSession); } void GPUProcessManager::UnregisterSession(RemoteCompositorSession* aSession) { mRemoteSessions.RemoveElement(aSession); } void GPUProcessManager::AddListener(GPUProcessListener* aListener) { mListeners.AppendElement(aListener); } void GPUProcessManager::RemoveListener(GPUProcessListener* aListener) { mListeners.RemoveElement(aListener); } bool GPUProcessManager::NotifyGpuObservers(const char* aTopic) { if (!mGPUChild) { return false; } nsCString topic(aTopic); mGPUChild->SendNotifyGpuObservers(topic); return true; } } // namespace gfx } // namespace mozilla