/* -*- 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 "mozilla/layers/CompositorManagerParent.h" #include "mozilla/gfx/GPUParent.h" #include "mozilla/gfx/CanvasManagerParent.h" #include "mozilla/webrender/RenderThread.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/layers/CompositorBridgeParent.h" #include "mozilla/layers/ContentCompositorBridgeParent.h" #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/RemoteTextureMap.h" #include "mozilla/layers/SharedSurfacesParent.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" #include "gfxPlatform.h" #include "VsyncSource.h" namespace mozilla { namespace layers { StaticMonitor CompositorManagerParent::sMonitor; StaticRefPtr CompositorManagerParent::sInstance; CompositorManagerParent::ManagerMap CompositorManagerParent::sManagers; /* static */ already_AddRefed CompositorManagerParent::CreateSameProcess(uint32_t aNamespace) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); StaticMonitorAutoLock lock(sMonitor); // We are creating a manager for the UI process, inside the combined GPU/UI // process. It is created more-or-less the same but we retain a reference to // the parent to access state. if (NS_WARN_IF(sInstance)) { MOZ_ASSERT_UNREACHABLE("Already initialized"); return nullptr; } // The child is responsible for setting up the IPC channel in the same // process case because if we open from the child perspective, we can do it // on the main thread and complete before we return the manager handles. RefPtr parent = new CompositorManagerParent(dom::ContentParentId(), aNamespace); parent->SetOtherProcessId(base::GetCurrentProcId()); return parent.forget(); } /* static */ bool CompositorManagerParent::Create( Endpoint&& aEndpoint, dom::ContentParentId aChildId, uint32_t aNamespace, bool aIsRoot) { MOZ_ASSERT(NS_IsMainThread()); // We are creating a manager for the another process, inside the GPU process // (or UI process if it subsumbed the GPU process). MOZ_ASSERT(aEndpoint.OtherPid() != base::GetCurrentProcId()); if (!CompositorThreadHolder::IsActive()) { return false; } RefPtr bridge = new CompositorManagerParent(aChildId, aNamespace); RefPtr runnable = NewRunnableMethod&&, bool>( "CompositorManagerParent::Bind", bridge, &CompositorManagerParent::Bind, std::move(aEndpoint), aIsRoot); CompositorThread()->Dispatch(runnable.forget()); return true; } /* static */ already_AddRefed CompositorManagerParent::CreateSameProcessWidgetCompositorBridge( CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize, uint64_t aInnerWindowId) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); // When we are in a combined UI / GPU process, InProcessCompositorSession // requires both the parent and child PCompositorBridge actors for its own // construction, which is done on the main thread. Normally // CompositorBridgeParent is created on the compositor thread via the IPDL // plumbing (CompositorManagerParent::AllocPCompositorBridgeParent). Thus to // actually get a reference to the parent, we would need to block on the // compositor thread until it handles our constructor message. Because only // one one IPDL constructor is permitted per parent and child protocol, we // cannot make the normal case async and this case sync. Instead what we do // is leave the constructor async (a boon to the content process setup) and // create the parent ahead of time. It will pull the preinitialized parent // from the queue when it receives the message and give that to IPDL. // Note that the static mutex not only is used to protect sInstance, but also // mPendingCompositorBridges. StaticMonitorAutoLock lock(sMonitor); if (NS_WARN_IF(!sInstance)) { return nullptr; } TimeDuration vsyncRate = gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher()->GetVsyncRate(); RefPtr bridge = new CompositorBridgeParent( sInstance, aScale, vsyncRate, aOptions, aUseExternalSurfaceSize, aSurfaceSize, aInnerWindowId); sInstance->mPendingCompositorBridges.AppendElement(bridge); return bridge.forget(); } CompositorManagerParent::CompositorManagerParent( dom::ContentParentId aContentId, uint32_t aNamespace) : mCompositorThreadHolder(CompositorThreadHolder::GetSingleton()), mSharedSurfacesHolder(MakeRefPtr(aNamespace)), mContentId(aContentId), mNamespace(aNamespace) {} CompositorManagerParent::~CompositorManagerParent() = default; void CompositorManagerParent::Bind( Endpoint&& aEndpoint, bool aIsRoot) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); if (NS_WARN_IF(!aEndpoint.Bind(this))) { return; } BindComplete(aIsRoot); } void CompositorManagerParent::BindComplete(bool aIsRoot) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || NS_IsMainThread()); StaticMonitorAutoLock lock(sMonitor); if (aIsRoot) { MOZ_ASSERT(!sInstance); sInstance = this; } MOZ_RELEASE_ASSERT(sManagers.try_emplace(mNamespace, this).second); } void CompositorManagerParent::ActorDestroy(ActorDestroyReason aReason) { GetCurrentSerialEventTarget()->Dispatch( NewRunnableMethod("layers::CompositorManagerParent::DeferredDestroy", this, &CompositorManagerParent::DeferredDestroy)); if (mRemoteTextureTxnScheduler) { mRemoteTextureTxnScheduler = nullptr; } StaticMonitorAutoLock lock(sMonitor); if (sInstance == this) { sInstance = nullptr; } MOZ_RELEASE_ASSERT(sManagers.erase(mNamespace) > 0); sMonitor.NotifyAll(); } void CompositorManagerParent::DeferredDestroy() { mCompositorThreadHolder = nullptr; } /* static */ void CompositorManagerParent::ShutdownInternal() { nsTArray> actors; // We move here because we may attempt to acquire the same lock during the // destroy to remove the reference in sManagers. { StaticMonitorAutoLock lock(sMonitor); actors.SetCapacity(sManagers.size()); for (auto& i : sManagers) { actors.AppendElement(i.second); } } for (auto& actor : actors) { actor->Close(); } } /* static */ void CompositorManagerParent::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); CompositorThread()->Dispatch(NS_NewRunnableFunction( "layers::CompositorManagerParent::Shutdown", []() -> void { CompositorManagerParent::ShutdownInternal(); })); } /* static */ void CompositorManagerParent::WaitForSharedSurface( const wr::ExternalImageId& aId) { uint32_t extNamespace = static_cast(wr::AsUint64(aId) >> 32); uint32_t resourceId = static_cast(wr::AsUint64(aId)); StaticMonitorAutoLock lock(sMonitor); while (true) { const auto i = sManagers.find(extNamespace); if (NS_WARN_IF(i == sManagers.end())) { break; } // We know that when the resource ID is allocated, we either fail to have // shared the surface with the compositor process, and so we don't use the // external image ID, or we have queued an IPDL message over the // corresponding CompositorManagerParent object to map that surface into // memory. They are dispatched in order, so we can safely wait until either // the actor is closed, or the last seen resource ID reaches the target. if (i->second->mLastSharedSurfaceResourceId >= resourceId) { break; } lock.Wait(); } } already_AddRefed CompositorManagerParent::AllocPCompositorBridgeParent( const CompositorBridgeOptions& aOpt) { switch (aOpt.type()) { case CompositorBridgeOptions::TContentCompositorOptions: { RefPtr bridge = new ContentCompositorBridgeParent(this); return bridge.forget(); } case CompositorBridgeOptions::TWidgetCompositorOptions: { // Only the UI process is allowed to create widget compositors in the // compositor process. gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton(); if (NS_WARN_IF(!gpu || OtherPid() != gpu->OtherPid())) { MOZ_ASSERT_UNREACHABLE("Child cannot create widget compositor!"); break; } const WidgetCompositorOptions& opt = aOpt.get_WidgetCompositorOptions(); RefPtr bridge = new CompositorBridgeParent( this, opt.scale(), opt.vsyncRate(), opt.options(), opt.useExternalSurfaceSize(), opt.surfaceSize(), opt.innerWindowId()); return bridge.forget(); } case CompositorBridgeOptions::TSameProcessWidgetCompositorOptions: { // If the GPU and UI process are combined, we actually already created the // CompositorBridgeParent, so we need to reuse that to inject it into the // IPDL framework. if (NS_WARN_IF(OtherPid() != base::GetCurrentProcId())) { MOZ_ASSERT_UNREACHABLE("Child cannot create same process compositor!"); break; } // Note that the static mutex not only is used to protect sInstance, but // also mPendingCompositorBridges. StaticMonitorAutoLock lock(sMonitor); if (mPendingCompositorBridges.IsEmpty()) { break; } RefPtr bridge = mPendingCompositorBridges[0]; mPendingCompositorBridges.RemoveElementAt(0); return bridge.forget(); } default: break; } return nullptr; } /* static */ void CompositorManagerParent::AddSharedSurface( const wr::ExternalImageId& aId, gfx::SourceSurfaceSharedData* aSurface) { MOZ_ASSERT(XRE_IsParentProcess()); StaticMonitorAutoLock lock(sMonitor); if (NS_WARN_IF(!sInstance)) { return; } if (NS_WARN_IF(!sInstance->OwnsExternalImageId(aId))) { MOZ_ASSERT_UNREACHABLE("Wrong namespace?"); return; } SharedSurfacesParent::AddSameProcess(aId, aSurface); uint32_t resourceId = static_cast(wr::AsUint64(aId)); MOZ_RELEASE_ASSERT(sInstance->mLastSharedSurfaceResourceId < resourceId); sInstance->mLastSharedSurfaceResourceId = resourceId; sMonitor.NotifyAll(); } mozilla::ipc::IPCResult CompositorManagerParent::RecvAddSharedSurface( const wr::ExternalImageId& aId, SurfaceDescriptorShared&& aDesc) { if (NS_WARN_IF(!OwnsExternalImageId(aId))) { MOZ_ASSERT_UNREACHABLE("Wrong namespace?"); return IPC_OK(); } SharedSurfacesParent::Add(aId, std::move(aDesc), OtherPid()); StaticMonitorAutoLock lock(sMonitor); uint32_t resourceId = static_cast(wr::AsUint64(aId)); MOZ_RELEASE_ASSERT(mLastSharedSurfaceResourceId < resourceId); mLastSharedSurfaceResourceId = resourceId; sMonitor.NotifyAll(); return IPC_OK(); } mozilla::ipc::IPCResult CompositorManagerParent::RecvRemoveSharedSurface( const wr::ExternalImageId& aId) { if (NS_WARN_IF(!OwnsExternalImageId(aId))) { MOZ_ASSERT_UNREACHABLE("Wrong namespace?"); return IPC_OK(); } SharedSurfacesParent::Remove(aId); return IPC_OK(); } mozilla::ipc::IPCResult CompositorManagerParent::RecvReportSharedSurfacesMemory( ReportSharedSurfacesMemoryResolver&& aResolver) { SharedSurfacesMemoryReport report; SharedSurfacesParent::AccumulateMemoryReport(mNamespace, report); aResolver(std::move(report)); return IPC_OK(); } mozilla::ipc::IPCResult CompositorManagerParent::RecvNotifyMemoryPressure() { nsTArray compositorBridges; ManagedPCompositorBridgeParent(compositorBridges); for (auto bridge : compositorBridges) { static_cast(bridge)->NotifyMemoryPressure(); } return IPC_OK(); } mozilla::ipc::IPCResult CompositorManagerParent::RecvReportMemory( ReportMemoryResolver&& aResolver) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MemoryReport aggregate; PodZero(&aggregate); // Accumulate RenderBackend usage. nsTArray compositorBridges; ManagedPCompositorBridgeParent(compositorBridges); for (auto bridge : compositorBridges) { static_cast(bridge)->AccumulateMemoryReport( &aggregate); } // Accumulate Renderer usage asynchronously, and resolve. // // Note that the IPDL machinery requires aResolver to be called on this // thread, so we can't just pass it over to the renderer thread. We use // an intermediate MozPromise instead. wr::RenderThread::AccumulateMemoryReport(aggregate)->Then( CompositorThread(), __func__, [resolver = std::move(aResolver)](MemoryReport aReport) { resolver(aReport); }, [](bool) { MOZ_ASSERT_UNREACHABLE("MemoryReport promises are never rejected"); }); return IPC_OK(); } mozilla::ipc::IPCResult CompositorManagerParent::RecvInitCanvasManager( Endpoint&& aEndpoint) { gfx::CanvasManagerParent::Init(std::move(aEndpoint), mSharedSurfacesHolder, mContentId); mRemoteTextureTxnScheduler = RemoteTextureTxnScheduler::Create(this); return IPC_OK(); } /* static */ void CompositorManagerParent::NotifyWebRenderError(wr::WebRenderError aError) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); StaticMonitorAutoLock lock(sMonitor); if (NS_WARN_IF(!sInstance)) { return; } Unused << sInstance->SendNotifyWebRenderError(aError); } } // namespace layers } // namespace mozilla