diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/layers/ipc/CompositorBridgeParent.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/ipc/CompositorBridgeParent.cpp')
-rw-r--r-- | gfx/layers/ipc/CompositorBridgeParent.cpp | 2008 |
1 files changed, 2008 insertions, 0 deletions
diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp new file mode 100644 index 0000000000..3982791357 --- /dev/null +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -0,0 +1,2008 @@ +/* -*- 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/CompositorBridgeParent.h" + +#include <stdio.h> // for fprintf, stdout +#include <stdint.h> // for uint64_t +#include <map> // for _Rb_tree_iterator, etc +#include <utility> // for pair + +#include "apz/src/APZCTreeManager.h" // for APZCTreeManager +#include "base/process.h" // for ProcessId +#include "gfxContext.h" // for gfxContext +#include "gfxPlatform.h" // for gfxPlatform +#include "TreeTraversal.h" // for ForEachNode +#ifdef MOZ_WIDGET_GTK +# include "gfxPlatformGtk.h" // for gfxPlatform +#endif +#include "mozilla/AutoRestore.h" // for AutoRestore +#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for IntSize +#include "mozilla/gfx/gfxVars.h" // for gfxVars +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/layers/APZCTreeManagerParent.h" // for APZCTreeManagerParent +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/APZThreadUtils.h" // for APZThreadUtils +#include "mozilla/layers/APZUpdater.h" // for APZUpdater +#include "mozilla/layers/CompositionRecorder.h" // for CompositionRecorder +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/CompositorManagerParent.h" // for CompositorManagerParent +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/CompositorVsyncScheduler.h" +#include "mozilla/layers/ContentCompositorBridgeParent.h" +#include "mozilla/layers/FrameUniformityData.h" +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/layers/LayerTreeOwnerTracker.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/OMTASampler.h" +#include "mozilla/layers/RemoteContentController.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/media/MediaSystemResourceService.h" // for MediaSystemResourceService +#include "mozilla/mozalloc.h" // for operator new, etc +#include "mozilla/PodOperations.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Telemetry.h" +#include "mozilla/glean/GleanMetrics.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsTArray.h" // for nsTArray +#include "nsThreadUtils.h" // for NS_IsMainThread +#ifdef XP_WIN +# include "mozilla/layers/CompositorD3D11.h" +# include "mozilla/widget/WinCompositorWidget.h" +# include "mozilla/WindowsVersion.h" +#endif +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/Unused.h" +#include "mozilla/Hal.h" +#include "mozilla/HalTypes.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/VsyncDispatcher.h" +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +# include "VsyncSource.h" +#endif +#include "mozilla/widget/CompositorWidget.h" +#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING +# include "mozilla/widget/CompositorWidgetParent.h" +#endif +#ifdef XP_WIN +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +namespace mozilla { + +namespace layers { + +using namespace mozilla::ipc; +using namespace mozilla::gfx; + +using base::ProcessId; + +using mozilla::Telemetry::LABELS_CONTENT_FRAME_TIME_REASON; + +/* static*/ +StaticMonitor CompositorBridgeParent::sIndirectLayerTreesLock; + +/* static */ +CompositorBridgeParent::LayerTreeMap CompositorBridgeParent::sIndirectLayerTrees + MOZ_GUARDED_BY(CompositorBridgeParent::sIndirectLayerTreesLock); + +/// Equivalent to asserting CompositorThreadHolder::IsInCompositorThread with +/// the addition that it doesn't assert if the compositor thread holder is +/// already gone during late shutdown. +static void AssertIsInCompositorThread() { + MOZ_RELEASE_ASSERT(!CompositorThread() || + CompositorThreadHolder::IsInCompositorThread()); +} + +CompositorBridgeParentBase::CompositorBridgeParentBase( + CompositorManagerParent* aManager) + : mCanSend(true), mCompositorManager(aManager) {} + +CompositorBridgeParentBase::~CompositorBridgeParentBase() = default; + +ProcessId CompositorBridgeParentBase::GetChildProcessId() { return OtherPid(); } + +dom::ContentParentId CompositorBridgeParentBase::GetContentId() { + return mCompositorManager->GetContentId(); +} + +void CompositorBridgeParentBase::NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) { + RefPtr<TextureHost> texture = TextureHost::AsTextureHost(aTexture); + if (!texture) { + return; + } + + if (!(texture->GetFlags() & TextureFlags::RECYCLE) && + !(texture->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END)) { + return; + } + + uint64_t textureId = TextureHost::GetTextureSerial(aTexture); + mPendingAsyncMessage.push_back(OpNotifyNotUsed(textureId, aTransactionId)); +} + +void CompositorBridgeParentBase::SendAsyncMessage( + const nsTArray<AsyncParentMessageData>& aMessage) { + Unused << SendParentAsyncMessages(aMessage); +} + +bool CompositorBridgeParentBase::AllocShmem(size_t aSize, ipc::Shmem* aShmem) { + return PCompositorBridgeParent::AllocShmem(aSize, aShmem); +} + +bool CompositorBridgeParentBase::AllocUnsafeShmem(size_t aSize, + ipc::Shmem* aShmem) { + return PCompositorBridgeParent::AllocUnsafeShmem(aSize, aShmem); +} + +bool CompositorBridgeParentBase::DeallocShmem(ipc::Shmem& aShmem) { + return PCompositorBridgeParent::DeallocShmem(aShmem); +} + +CompositorBridgeParent::LayerTreeState::LayerTreeState() + : mApzcTreeManagerParent(nullptr), + mParent(nullptr), + mContentCompositorBridgeParent(nullptr) {} + +CompositorBridgeParent::LayerTreeState::~LayerTreeState() { + if (mController) { + mController->Destroy(); + } +} + +template <typename Lambda> +inline void CompositorBridgeParent::ForEachIndirectLayerTree( + const Lambda& aCallback) { + sIndirectLayerTreesLock.AssertCurrentThreadOwns(); + for (auto it = sIndirectLayerTrees.begin(); it != sIndirectLayerTrees.end(); + it++) { + LayerTreeState* state = &it->second; + if (state->mParent == this) { + aCallback(state, it->first); + } + } +} + +/*static*/ template <typename Lambda> +inline void CompositorBridgeParent::ForEachWebRenderBridgeParent( + const Lambda& aCallback) { + sIndirectLayerTreesLock.AssertCurrentThreadOwns(); + for (auto& it : sIndirectLayerTrees) { + LayerTreeState* state = &it.second; + if (state->mWrBridge) { + aCallback(state->mWrBridge); + } + } +} + +/** + * A global map referencing each compositor by ID. + * + * This map is used by the ImageBridge protocol to trigger + * compositions without having to keep references to the + * compositor + */ +typedef std::map<uint64_t, CompositorBridgeParent*> CompositorMap; +static StaticAutoPtr<CompositorMap> sCompositorMap; + +void CompositorBridgeParent::Setup() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sCompositorMap); + sCompositorMap = new CompositorMap; +} + +void CompositorBridgeParent::FinishShutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sCompositorMap) { + MOZ_ASSERT(sCompositorMap->empty()); + sCompositorMap = nullptr; + } + + // TODO: this should be empty by now... + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + sIndirectLayerTrees.clear(); +} + +CompositorBridgeParent::CompositorBridgeParent( + CompositorManagerParent* aManager, CSSToLayoutDeviceScale aScale, + const TimeDuration& aVsyncRate, const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize, + uint64_t aInnerWindowId) + : CompositorBridgeParentBase(aManager), + mWidget(nullptr), + mScale(aScale), + mVsyncRate(aVsyncRate), + mPaused(false), + mHaveCompositionRecorder(false), + mIsForcedFirstPaint(false), + mUseExternalSurfaceSize(aUseExternalSurfaceSize), + mEGLSurfaceSize(aSurfaceSize), + mOptions(aOptions), + mCompositorBridgeID(0), + mRootLayerTreeID{0}, + mInnerWindowId(aInnerWindowId), + mCompositorScheduler(nullptr), + mAnimationStorage(nullptr) {} + +void CompositorBridgeParent::InitSameProcess(widget::CompositorWidget* aWidget, + const LayersId& aLayerTreeId) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + mWidget = aWidget; + mRootLayerTreeID = aLayerTreeId; +#if defined(XP_WIN) + // when run in headless mode, no WinCompositorWidget is created + if (widget::WinCompositorWidget* windows = mWidget->AsWindows()) { + windows->SetRootLayerTreeID(mRootLayerTreeID); + } +#endif + + Initialize(); +} + +bool CompositorBridgeParent::IsPaused() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + return mPaused; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvInitialize( + const LayersId& aRootLayerTreeId) { + MOZ_ASSERT(XRE_IsGPUProcess()); + + mRootLayerTreeID = aRootLayerTreeId; +#ifdef XP_WIN + // headless mode is probably always same-process; but just in case... + if (widget::WinCompositorWidget* windows = mWidget->AsWindows()) { + windows->SetRootLayerTreeID(mRootLayerTreeID); + } +#endif + + Initialize(); + return IPC_OK(); +} + +void CompositorBridgeParent::Initialize() { + MOZ_ASSERT(CompositorThread(), + "The compositor thread must be Initialized before instanciating a " + "CompositorBridgeParent."); + + if (mOptions.UseAPZ()) { + MOZ_ASSERT(!mApzcTreeManager); + MOZ_ASSERT(!mApzSampler); + MOZ_ASSERT(!mApzUpdater); + mApzcTreeManager = APZCTreeManager::Create(mRootLayerTreeID); + mApzSampler = new APZSampler(mApzcTreeManager, true); + mApzUpdater = new APZUpdater(mApzcTreeManager, true); + } + + CompositorAnimationStorage* animationStorage = GetAnimationStorage(); + mOMTASampler = new OMTASampler(animationStorage, mRootLayerTreeID); + + mPaused = mOptions.InitiallyPaused(); + + mCompositorBridgeID = 0; + // FIXME: This holds on the the fact that right now the only thing that + // can destroy this instance is initialized on the compositor thread after + // this task has been processed. + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch(NewRunnableFunction( + "AddCompositorRunnable", &AddCompositor, this, &mCompositorBridgeID)); + + { // scope lock + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + sIndirectLayerTrees[mRootLayerTreeID].mParent = this; + } +} + +LayersId CompositorBridgeParent::RootLayerTreeId() { + MOZ_ASSERT(mRootLayerTreeID.IsValid()); + return mRootLayerTreeID; +} + +CompositorBridgeParent::~CompositorBridgeParent() { + MOZ_DIAGNOSTIC_ASSERT( + !mCanSend, + "ActorDestroy or RecvWillClose should have been called first."); + MOZ_DIAGNOSTIC_ASSERT(mRefCnt == 0, + "ActorDealloc should have been called first."); + nsTArray<PTextureParent*> textures; + ManagedPTextureParent(textures); + // We expect all textures to be destroyed by now. + MOZ_DIAGNOSTIC_ASSERT(textures.Length() == 0); + for (unsigned int i = 0; i < textures.Length(); ++i) { + RefPtr<TextureHost> tex = TextureHost::AsTextureHost(textures[i]); + tex->DeallocateDeviceData(); + } + // Check if WebRender/Compositor was shutdown. + if (mWrBridge) { + gfxCriticalNote << "CompositorBridgeParent destroyed without shutdown"; + } +} + +void CompositorBridgeParent::ForceIsFirstPaint() { + if (mWrBridge) { + mIsForcedFirstPaint = true; + } +} + +void CompositorBridgeParent::StopAndClearResources() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mPaused = true; + + // We need to clear the APZ tree before we destroy the WebRender API below, + // because in the case of async scene building that will shut down the updater + // thread and we need to run the task before that happens. + MOZ_ASSERT((mApzSampler != nullptr) == (mApzcTreeManager != nullptr)); + MOZ_ASSERT((mApzUpdater != nullptr) == (mApzcTreeManager != nullptr)); + if (mApzUpdater) { + mApzSampler->Destroy(); + mApzSampler = nullptr; + mApzUpdater->ClearTree(mRootLayerTreeID); + mApzUpdater = nullptr; + mApzcTreeManager = nullptr; + } + + if (mWrBridge) { + // Ensure we are not holding the sIndirectLayerTreesLock when destroying + // the WebRenderBridgeParent instances because it may block on WR. + std::vector<RefPtr<WebRenderBridgeParent>> indirectBridgeParents; + { // scope lock + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachIndirectLayerTree([&](LayerTreeState* lts, LayersId) -> void { + if (lts->mWrBridge) { + indirectBridgeParents.emplace_back(lts->mWrBridge.forget()); + } + lts->mParent = nullptr; + }); + } + for (const RefPtr<WebRenderBridgeParent>& bridge : indirectBridgeParents) { + bridge->Destroy(); + } + indirectBridgeParents.clear(); + + RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI(); + // Ensure we are not holding the sIndirectLayerTreesLock here because we + // are going to block on WR threads in order to shut it down properly. + mWrBridge->Destroy(); + mWrBridge = nullptr; + + if (api) { + // Make extra sure we are done cleaning WebRender up before continuing. + // After that we wont have a way to talk to a lot of the webrender parts. + api->FlushSceneBuilder(); + api = nullptr; + } + + if (mAsyncImageManager) { + mAsyncImageManager->Destroy(); + // WebRenderAPI should be already destructed + mAsyncImageManager = nullptr; + } + } + + // This must be destroyed now since it accesses the widget. + if (mCompositorScheduler) { + mCompositorScheduler->Destroy(); + mCompositorScheduler = nullptr; + } + + if (mOMTASampler) { + mOMTASampler->Destroy(); + mOMTASampler = nullptr; + } + + // After this point, it is no longer legal to access the widget. + mWidget = nullptr; + + // Clear mAnimationStorage here to ensure that the compositor thread + // still exists when we destroy it. + mAnimationStorage = nullptr; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvWillClose() { + StopAndClearResources(); + // Once we get the WillClose message, the client side is going to go away + // soon and we can't be guaranteed that sending messages will work. + mCanSend = false; + return IPC_OK(); +} + +void CompositorBridgeParent::DeferredDestroy() { + MOZ_ASSERT(!NS_IsMainThread()); + mSelfRef = nullptr; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvPause() { + PauseComposition(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvRequestFxrOutput() { +#ifdef XP_WIN + // Continue forwarding the request to the Widget + SwapChain + mWidget->AsWindows()->RequestFxrOutput(); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvResume() { + ResumeComposition(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvResumeAsync() { + ResumeComposition(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +CompositorBridgeParent::RecvWaitOnTransactionProcessed() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvFlushRendering( + const wr::RenderReasons& aReasons) { + if (mWrBridge) { + mWrBridge->FlushRendering(aReasons); + return IPC_OK(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyMemoryPressure() { + NotifyMemoryPressure(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvFlushRenderingAsync( + const wr::RenderReasons& aReasons) { + if (mWrBridge) { + mWrBridge->FlushRendering(aReasons, false); + return IPC_OK(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvForcePresent( + const wr::RenderReasons& aReasons) { + if (mWrBridge) { + mWrBridge->ScheduleForcedGenerateFrame(aReasons); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvStartFrameTimeRecording( + const int32_t& aBufferSize, uint32_t* aOutStartIndex) { + if (mWrBridge) { + *aOutStartIndex = mWrBridge->StartFrameTimeRecording(aBufferSize); + } else { + *aOutStartIndex = 0; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvStopFrameTimeRecording( + const uint32_t& aStartIndex, nsTArray<float>* intervals) { + if (mWrBridge) { + mWrBridge->StopFrameTimeRecording(aStartIndex, *intervals); + } + return IPC_OK(); +} + +void CompositorBridgeParent::ActorDestroy(ActorDestroyReason why) { + mCanSend = false; + + StopAndClearResources(); + + RemoveCompositor(mCompositorBridgeID); + + { // scope lock + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + sIndirectLayerTrees.erase(mRootLayerTreeID); + } + + // There are chances that the ref count reaches zero on the main thread + // shortly after this function returns while some ipdl code still needs to run + // on this thread. We must keep the compositor parent alive untill the code + // handling message reception is finished on this thread. + mSelfRef = this; + NS_GetCurrentThread()->Dispatch( + NewRunnableMethod("layers::CompositorBridgeParent::DeferredDestroy", this, + &CompositorBridgeParent::DeferredDestroy)); +} + +void CompositorBridgeParent::ScheduleRenderOnCompositorThread( + wr::RenderReasons aReasons) { + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch(NewRunnableMethod<wr::RenderReasons>( + "layers::CompositorBridgeParent::ScheduleComposition", this, + &CompositorBridgeParent::ScheduleComposition, aReasons)); +} + +void CompositorBridgeParent::PauseComposition() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread(), + "PauseComposition() can only be called on the compositor thread"); + + if (!mPaused) { + mPaused = true; + + TimeStamp now = TimeStamp::Now(); + if (mWrBridge) { + mWrBridge->Pause(); + NotifyPipelineRendered(mWrBridge->PipelineId(), + mWrBridge->GetCurrentEpoch(), VsyncId(), now, now, + now); + } + } +} + +bool CompositorBridgeParent::ResumeComposition() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread(), + "ResumeComposition() can only be called on the compositor thread"); + + bool resumed = mWidget->OnResumeComposition(); + resumed = resumed && mWrBridge->Resume(); + + if (!resumed) { +#ifdef MOZ_WIDGET_ANDROID + // We can't get a surface. This could be because the activity changed + // between the time resume was scheduled and now. + __android_log_print( + ANDROID_LOG_INFO, "CompositorBridgeParent", + "Unable to renew compositor surface; remaining in paused state"); +#endif + return false; + } + + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mPaused = false; + + mCompositorScheduler->ForceComposeToTarget(wr::RenderReasons::WIDGET, nullptr, + nullptr); + return true; +} + +void CompositorBridgeParent::SetEGLSurfaceRect(int x, int y, int width, + int height) { + NS_ASSERTION(mUseExternalSurfaceSize, + "Compositor created without UseExternalSurfaceSize provided"); + mEGLSurfaceSize.SizeTo(width, height); +} + +bool CompositorBridgeParent::ResumeCompositionAndResize(int x, int y, int width, + int height) { + SetEGLSurfaceRect(x, y, width, height); + return ResumeComposition(); +} + +void CompositorBridgeParent::ScheduleComposition(wr::RenderReasons aReasons) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (mPaused) { + return; + } + + if (mWrBridge) { + mWrBridge->ScheduleGenerateFrame(aReasons); + } +} + +PAPZCTreeManagerParent* CompositorBridgeParent::AllocPAPZCTreeManagerParent( + const LayersId& aLayersId) { + // This should only ever get called in the GPU process. + MOZ_ASSERT(XRE_IsGPUProcess()); + // We should only ever get this if APZ is enabled in this compositor. + MOZ_ASSERT(mOptions.UseAPZ()); + // The mApzcTreeManager and mApzUpdater should have been created via + // RecvInitialize() + MOZ_ASSERT(mApzcTreeManager); + MOZ_ASSERT(mApzUpdater); + // The main process should pass in 0 because we assume mRootLayerTreeID + MOZ_ASSERT(!aLayersId.IsValid()); + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& state = + sIndirectLayerTrees[mRootLayerTreeID]; + MOZ_ASSERT(state.mParent.get() == this); + MOZ_ASSERT(!state.mApzcTreeManagerParent); + state.mApzcTreeManagerParent = new APZCTreeManagerParent( + mRootLayerTreeID, mApzcTreeManager, mApzUpdater); + + return state.mApzcTreeManagerParent; +} + +bool CompositorBridgeParent::DeallocPAPZCTreeManagerParent( + PAPZCTreeManagerParent* aActor) { + delete aActor; + return true; +} + +void CompositorBridgeParent::AllocateAPZCTreeManagerParent( + const StaticMonitorAutoLock& aProofOfLayerTreeStateLock, + const LayersId& aLayersId, LayerTreeState& aState) { + MOZ_ASSERT(aState.mParent == this); + MOZ_ASSERT(mApzcTreeManager); + MOZ_ASSERT(mApzUpdater); + MOZ_ASSERT(!aState.mApzcTreeManagerParent); + aState.mApzcTreeManagerParent = + new APZCTreeManagerParent(aLayersId, mApzcTreeManager, mApzUpdater); +} + +PAPZParent* CompositorBridgeParent::AllocPAPZParent(const LayersId& aLayersId) { + // This is the CompositorBridgeParent for a window, and so should only be + // creating a PAPZ instance if it lives in the GPU process. Instances that + // live in the UI process should going through SetControllerForLayerTree. + MOZ_RELEASE_ASSERT(XRE_IsGPUProcess()); + + // We should only ever get this if APZ is enabled on this compositor. + MOZ_RELEASE_ASSERT(mOptions.UseAPZ()); + + // The main process should pass in 0 because we assume mRootLayerTreeID + MOZ_RELEASE_ASSERT(!aLayersId.IsValid()); + + RemoteContentController* controller = new RemoteContentController(); + + // Increment the controller's refcount before we return it. This will keep the + // controller alive until it is released by IPDL in DeallocPAPZParent. + controller->AddRef(); + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& state = + sIndirectLayerTrees[mRootLayerTreeID]; + MOZ_RELEASE_ASSERT(!state.mController); + state.mController = controller; + + return controller; +} + +bool CompositorBridgeParent::DeallocPAPZParent(PAPZParent* aActor) { + RemoteContentController* controller = + static_cast<RemoteContentController*>(aActor); + controller->Release(); + return true; +} + +RefPtr<APZSampler> CompositorBridgeParent::GetAPZSampler() const { + return mApzSampler; +} + +RefPtr<APZUpdater> CompositorBridgeParent::GetAPZUpdater() const { + return mApzUpdater; +} + +RefPtr<OMTASampler> CompositorBridgeParent::GetOMTASampler() const { + return mOMTASampler; +} + +CompositorBridgeParent* +CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + const LayersId& aLayersId) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + return sIndirectLayerTrees[aLayersId].mParent; +} + +/*static*/ +RefPtr<CompositorBridgeParent> +CompositorBridgeParent::GetCompositorBridgeParentFromWindowId( + const wr::WindowId& aWindowId) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + for (auto it = sIndirectLayerTrees.begin(); it != sIndirectLayerTrees.end(); + it++) { + LayerTreeState* state = &it->second; + if (!state->mWrBridge) { + continue; + } + // state->mWrBridge might be a root WebRenderBridgeParent or one of a + // content process, but in either case the state->mParent will be the same. + // So we don't need to distinguish between the two. + if (RefPtr<wr::WebRenderAPI> api = state->mWrBridge->GetWebRenderAPI()) { + if (api->GetId() == aWindowId) { + return state->mParent; + } + } + } + return nullptr; +} + +bool CompositorBridgeParent::SetTestSampleTime(const LayersId& aId, + const TimeStamp& aTime) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (aTime.IsNull()) { + return false; + } + + mTestTime = Some(aTime); + if (mApzcTreeManager) { + mApzcTreeManager->SetTestSampleTime(mTestTime); + } + + if (mWrBridge) { + mWrBridge->FlushRendering(wr::RenderReasons::TESTING); + return true; + } + + return true; +} + +void CompositorBridgeParent::LeaveTestMode(const LayersId& aId) { + mTestTime = Nothing(); + if (mApzcTreeManager) { + mApzcTreeManager->SetTestSampleTime(mTestTime); + } +} + +CompositorAnimationStorage* CompositorBridgeParent::GetAnimationStorage() { + if (!mAnimationStorage) { + mAnimationStorage = new CompositorAnimationStorage(this); + } + return mAnimationStorage; +} + +void CompositorBridgeParent::NotifyJankedAnimations( + const JankedAnimations& aJankedAnimations) { + MOZ_ASSERT(!aJankedAnimations.empty()); + + if (StaticPrefs::layout_animation_prerender_partial_jank()) { + return; + } + + for (const auto& entry : aJankedAnimations) { + const LayersId& layersId = entry.first; + const nsTArray<uint64_t>& animations = entry.second; + if (layersId == mRootLayerTreeID) { + if (mWrBridge) { + Unused << SendNotifyJankedAnimations(LayersId{0}, animations); + } + // It unlikely happens multiple processes have janked animations at same + // time, so it should be fine with enumerating sIndirectLayerTrees every + // time. + } else if (const LayerTreeState* state = GetIndirectShadowTree(layersId)) { + if (ContentCompositorBridgeParent* cpcp = + state->mContentCompositorBridgeParent) { + Unused << cpcp->SendNotifyJankedAnimations(layersId, animations); + } + } + } +} + +void CompositorBridgeParent::SetTestAsyncScrollOffset( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aPoint) { + if (mApzUpdater) { + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->SetTestAsyncScrollOffset(aLayersId, aScrollId, aPoint); + } +} + +void CompositorBridgeParent::SetTestAsyncZoom( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) { + if (mApzUpdater) { + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->SetTestAsyncZoom(aLayersId, aScrollId, aZoom); + } +} + +void CompositorBridgeParent::FlushApzRepaints(const LayersId& aLayersId) { + MOZ_ASSERT(mApzUpdater); + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->RunOnControllerThread( + aLayersId, NS_NewRunnableFunction( + "layers::CompositorBridgeParent::FlushApzRepaints", + [=]() { APZCTreeManager::FlushApzRepaints(aLayersId); })); +} + +void CompositorBridgeParent::GetAPZTestData(const LayersId& aLayersId, + APZTestData* aOutData) { + if (mApzUpdater) { + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->GetAPZTestData(aLayersId, aOutData); + } +} + +void CompositorBridgeParent::GetFrameUniformity(const LayersId& aLayersId, + FrameUniformityData* aOutData) { +} + +void CompositorBridgeParent::SetConfirmedTargetAPZC( + const LayersId& aLayersId, const uint64_t& aInputBlockId, + nsTArray<ScrollableLayerGuid>&& aTargets) { + if (!mApzcTreeManager || !mApzUpdater) { + return; + } + // Need to specifically bind this since it's overloaded. + void (APZCTreeManager::*setTargetApzcFunc)( + uint64_t, const nsTArray<ScrollableLayerGuid>&) = + &APZCTreeManager::SetTargetAPZC; + RefPtr<Runnable> task = + NewRunnableMethod<uint64_t, + StoreCopyPassByRRef<nsTArray<ScrollableLayerGuid>>>( + "layers::CompositorBridgeParent::SetConfirmedTargetAPZC", + mApzcTreeManager.get(), setTargetApzcFunc, aInputBlockId, + std::move(aTargets)); + mApzUpdater->RunOnUpdaterThread(aLayersId, task.forget()); +} + +void CompositorBridgeParent::SetFixedLayerMargins(ScreenIntCoord aTop, + ScreenIntCoord aBottom) { + if (mApzcTreeManager) { + mApzcTreeManager->SetFixedLayerMargins(aTop, aBottom); + } + + ScheduleComposition(wr::RenderReasons::RESIZE); +} + +void CompositorBridgeParent::AddCompositor(CompositorBridgeParent* compositor, + uint64_t* outID) { + AssertIsInCompositorThread(); + + static uint64_t sNextID = 1; + + ++sNextID; + (*sCompositorMap)[sNextID] = compositor; + *outID = sNextID; +} + +CompositorBridgeParent* CompositorBridgeParent::RemoveCompositor(uint64_t id) { + AssertIsInCompositorThread(); + + CompositorMap::iterator it = sCompositorMap->find(id); + if (it == sCompositorMap->end()) { + return nullptr; + } + CompositorBridgeParent* retval = it->second; + sCompositorMap->erase(it); + return retval; +} + +void CompositorBridgeParent::NotifyVsync(const VsyncEvent& aVsync, + const LayersId& aLayersId) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + auto it = sIndirectLayerTrees.find(aLayersId); + if (it == sIndirectLayerTrees.end()) return; + + CompositorBridgeParent* cbp = it->second.mParent; + if (!cbp || !cbp->mWidget) return; + + RefPtr<VsyncObserver> obs = cbp->mWidget->GetVsyncObserver(); + if (!obs) return; + + obs->NotifyVsync(aVsync); +} + +/* static */ +void CompositorBridgeParent::ScheduleForcedComposition( + const LayersId& aLayersId, wr::RenderReasons aReasons) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + auto it = sIndirectLayerTrees.find(aLayersId); + if (it == sIndirectLayerTrees.end()) { + return; + } + + CompositorBridgeParent* cbp = it->second.mParent; + if (!cbp || !cbp->mWidget) { + return; + } + + if (cbp->mWrBridge) { + cbp->mWrBridge->ScheduleForcedGenerateFrame(aReasons); + } +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyChildCreated( + const LayersId& child, CompositorOptions* aOptions) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + NotifyChildCreated(child); + *aOptions = mOptions; + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyChildRecreated( + const LayersId& aChild, CompositorOptions* aOptions) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + + if (sIndirectLayerTrees.find(aChild) != sIndirectLayerTrees.end()) { + NS_WARNING("Invalid to register the same layer tree twice"); + return IPC_FAIL_NO_REASON(this); + } + + NotifyChildCreated(aChild); + *aOptions = mOptions; + return IPC_OK(); +} + +void CompositorBridgeParent::NotifyChildCreated(LayersId aChild) { + sIndirectLayerTreesLock.AssertCurrentThreadOwns(); + sIndirectLayerTrees[aChild].mParent = this; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvMapAndNotifyChildCreated( + const LayersId& aChild, const base::ProcessId& aOwnerPid, + CompositorOptions* aOptions) { + // We only use this message when the remote compositor is in the GPU process. + // It is harmless to call it, though. + MOZ_ASSERT(XRE_IsGPUProcess()); + + LayerTreeOwnerTracker::Get()->Map(aChild, aOwnerPid); + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + NotifyChildCreated(aChild); + *aOptions = mOptions; + return IPC_OK(); +} + +enum class CompositorOptionsChangeKind { + eSupported, + eBestEffort, + eUnsupported +}; + +static CompositorOptionsChangeKind ClassifyCompositorOptionsChange( + const CompositorOptions& aOld, const CompositorOptions& aNew) { + if (aOld == aNew) { + return CompositorOptionsChangeKind::eSupported; + } + if (aOld.EqualsIgnoringApzEnablement(aNew)) { + return CompositorOptionsChangeKind::eBestEffort; + } + return CompositorOptionsChangeKind::eUnsupported; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvAdoptChild( + const LayersId& child) { + RefPtr<APZUpdater> oldApzUpdater; + APZCTreeManagerParent* parent; + bool apzEnablementChanged = false; + RefPtr<WebRenderBridgeParent> childWrBridge; + + // Before adopting the child, save the old compositor's root content + // controller. We may need this to clear old layer transforms associated + // with the child. + // This is outside the lock because GetGeckoContentControllerForRoot() + // does its own locking. + RefPtr<GeckoContentController> oldRootController = + GetGeckoContentControllerForRoot(child); + + { // scope lock + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + // If child is already belong to this CompositorBridgeParent, + // no need to handle adopting child. + if (sIndirectLayerTrees[child].mParent == this) { + return IPC_OK(); + } + + if (sIndirectLayerTrees[child].mParent) { + switch (ClassifyCompositorOptionsChange( + sIndirectLayerTrees[child].mParent->mOptions, mOptions)) { + case CompositorOptionsChangeKind::eUnsupported: { + MOZ_ASSERT(false, + "Moving tab between windows whose compositor options" + "differ in unsupported ways. Things may break in " + "unexpected ways"); + break; + } + case CompositorOptionsChangeKind::eBestEffort: { + NS_WARNING( + "Moving tab between windows with different APZ enablement. " + "This is supported on a best-effort basis, but some things may " + "break."); + apzEnablementChanged = true; + break; + } + case CompositorOptionsChangeKind::eSupported: { + // The common case, no action required. + break; + } + } + oldApzUpdater = sIndirectLayerTrees[child].mParent->mApzUpdater; + } + if (mWrBridge) { + childWrBridge = sIndirectLayerTrees[child].mWrBridge; + } + parent = sIndirectLayerTrees[child].mApzcTreeManagerParent; + } + + if (childWrBridge) { + MOZ_ASSERT(mWrBridge); + RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI(); + api = api->Clone(); + wr::Epoch newEpoch = childWrBridge->UpdateWebRender( + mWrBridge->CompositorScheduler(), std::move(api), + mWrBridge->AsyncImageManager(), + mWrBridge->GetTextureFactoryIdentifier()); + // Pretend we composited, since parent CompositorBridgeParent was replaced. + TimeStamp now = TimeStamp::Now(); + NotifyPipelineRendered(childWrBridge->PipelineId(), newEpoch, VsyncId(), + now, now, now); + } + + { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + // Update sIndirectLayerTrees[child].mParent after + // WebRenderBridgeParent::UpdateWebRender(). + NotifyChildCreated(child); + } + + if (oldApzUpdater) { + // If we are moving a child from an APZ-enabled window to an APZ-disabled + // window (which can happen if e.g. a WebExtension moves a tab into a + // popup window), try to handle it gracefully by clearing the old layer + // transforms associated with the child. (Since the new compositor is + // APZ-disabled, there will be nothing to update the transforms going + // forward.) + if (!mApzUpdater && oldRootController) { + // Tell the old APZCTreeManager not to send any more layer transforms + // for this layers ids. + oldApzUpdater->MarkAsDetached(child); + + // Clear the current transforms. + nsTArray<MatrixMessage> clear; + clear.AppendElement(MatrixMessage(Nothing(), ScreenRect(), child)); + oldRootController->NotifyLayerTransforms(std::move(clear)); + } + } + if (mApzUpdater) { + if (parent) { + MOZ_ASSERT(mApzcTreeManager); + parent->ChildAdopted(mApzcTreeManager, mApzUpdater); + } + mApzUpdater->NotifyLayerTreeAdopted(child, oldApzUpdater); + } + if (apzEnablementChanged) { + Unused << SendCompositorOptionsChanged(child, mOptions); + } + return IPC_OK(); +} + +PWebRenderBridgeParent* CompositorBridgeParent::AllocPWebRenderBridgeParent( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize& aSize, + const WindowKind& aWindowKind) { + MOZ_ASSERT(wr::AsLayersId(aPipelineId) == mRootLayerTreeID); + MOZ_ASSERT(!mWrBridge); + MOZ_ASSERT(!mCompositorScheduler); + MOZ_ASSERT(mWidget); + +#ifdef XP_WIN + if (mWidget && mWidget->AsWindows()) { + const auto options = mWidget->GetCompositorOptions(); + if (!options.UseSoftwareWebRender() && + (DeviceManagerDx::Get()->CanUseDComp() || + gfxVars::UseWebRenderFlipSequentialWin())) { + mWidget->AsWindows()->EnsureCompositorWindow(); + } else if (options.UseSoftwareWebRender() && + mWidget->AsWindows()->GetCompositorHwnd()) { + mWidget->AsWindows()->DestroyCompositorWindow(); + } + } +#endif + + RefPtr<widget::CompositorWidget> widget = mWidget; + wr::WrWindowId windowId = wr::NewWindowId(); + if (mApzUpdater) { + // If APZ is enabled, we need to register the APZ updater with the window id + // before the updater thread is created in WebRenderAPI::Create, so + // that the callback from the updater thread can find the right APZUpdater. + mApzUpdater->SetWebRenderWindowId(windowId); + } + if (mApzSampler) { + // Same as for mApzUpdater, but for the sampler thread. + mApzSampler->SetWebRenderWindowId(windowId); + } + if (mOMTASampler) { + // Same, but for the OMTA sampler. + mOMTASampler->SetWebRenderWindowId(windowId); + } + + nsCString error("FEATURE_FAILURE_WEBRENDER_INITIALIZE_UNSPECIFIED"); + RefPtr<wr::WebRenderAPI> api = wr::WebRenderAPI::Create( + this, std::move(widget), windowId, aSize, aWindowKind, error); + if (!api) { + mWrBridge = + WebRenderBridgeParent::CreateDestroyed(aPipelineId, std::move(error)); + mWrBridge.get()->AddRef(); // IPDL reference + return mWrBridge; + } + +#ifdef MOZ_WIDGET_ANDROID + // On Android, WebRenderAPI::Resume() call is triggered from Java side. But + // Java side does not know about fallback to RenderCompositorOGLSWGL. In this + // fallback case, RenderCompositor::Resume() needs to be called from gfx code. + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!mPaused && mWidget->GetCompositorOptions().UseSoftwareWebRender() && + mWidget->GetCompositorOptions().AllowSoftwareWebRenderOGL()) { + api->Resume(); + } +#endif + + wr::TransactionBuilder txn(api); + txn.SetRootPipeline(aPipelineId); + api->SendTransaction(txn); + + bool useCompositorWnd = false; +#ifdef XP_WIN + // Headless mode uses HeadlessWidget. + if (mWidget->AsWindows()) { + useCompositorWnd = !!mWidget->AsWindows()->GetCompositorHwnd(); + } +#endif + mAsyncImageManager = + new AsyncImagePipelineManager(api->Clone(), useCompositorWnd); + RefPtr<AsyncImagePipelineManager> asyncMgr = mAsyncImageManager; + mWrBridge = new WebRenderBridgeParent(this, aPipelineId, mWidget, nullptr, + std::move(api), std::move(asyncMgr), + mVsyncRate); + mWrBridge.get()->AddRef(); // IPDL reference + + mAsyncImageManager->SetTextureFactoryIdentifier( + mWrBridge->GetTextureFactoryIdentifier()); + + mCompositorScheduler = mWrBridge->CompositorScheduler(); + MOZ_ASSERT(mCompositorScheduler); + { // scope lock + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + MOZ_ASSERT(sIndirectLayerTrees[mRootLayerTreeID].mWrBridge == nullptr); + sIndirectLayerTrees[mRootLayerTreeID].mWrBridge = mWrBridge; + } + return mWrBridge; +} + +bool CompositorBridgeParent::DeallocPWebRenderBridgeParent( + PWebRenderBridgeParent* aActor) { + WebRenderBridgeParent* parent = static_cast<WebRenderBridgeParent*>(aActor); + { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + auto it = sIndirectLayerTrees.find(wr::AsLayersId(parent->PipelineId())); + if (it != sIndirectLayerTrees.end()) { + it->second.mWrBridge = nullptr; + } + } + parent->Release(); // IPDL reference + return true; +} + +void CompositorBridgeParent::NotifyMemoryPressure() { + if (mWrBridge) { + RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI(); + if (api) { + api->NotifyMemoryPressure(); + } + } +} + +void CompositorBridgeParent::AccumulateMemoryReport(wr::MemoryReport* aReport) { + if (mWrBridge) { + RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI(); + if (api) { + api->AccumulateMemoryReport(aReport); + } + } +} + +/*static*/ +void CompositorBridgeParent::InitializeStatics() { + gfxVars::SetForceSubpixelAAWherePossibleListener(&UpdateQualitySettings); + gfxVars::SetWebRenderDebugFlagsListener(&UpdateDebugFlags); + gfxVars::SetWebRenderBoolParametersListener(&UpdateWebRenderBoolParameters); + gfxVars::SetWebRenderBatchingLookbackListener(&UpdateWebRenderParameters); + gfxVars::SetWebRenderBlobTileSizeListener(&UpdateWebRenderParameters); + gfxVars::SetWebRenderBatchedUploadThresholdListener( + &UpdateWebRenderParameters); + + gfxVars::SetWebRenderProfilerUIListener(&UpdateWebRenderProfilerUI); +} + +/*static*/ +void CompositorBridgeParent::UpdateQualitySettings() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch( + NewRunnableFunction("CompositorBridgeParent::UpdateQualitySettings", + &CompositorBridgeParent::UpdateQualitySettings)); + } + + // If there is no compositor thread, e.g. due to shutdown, then we can + // safefully just ignore this request. + return; + } + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + if (!wrBridge->IsRootWebRenderBridgeParent()) { + return; + } + wrBridge->UpdateQualitySettings(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateDebugFlags() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch( + NewRunnableFunction("CompositorBridgeParent::UpdateDebugFlags", + &CompositorBridgeParent::UpdateDebugFlags)); + } + + // If there is no compositor thread, e.g. due to shutdown, then we can + // safefully just ignore this request. + return; + } + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + if (!wrBridge->IsRootWebRenderBridgeParent()) { + return; + } + wrBridge->UpdateDebugFlags(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateWebRenderBoolParameters() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch(NewRunnableFunction( + "CompositorBridgeParent::UpdateWebRenderBoolParameters", + &CompositorBridgeParent::UpdateWebRenderBoolParameters)); + } + + return; + } + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + if (!wrBridge->IsRootWebRenderBridgeParent()) { + return; + } + wrBridge->UpdateBoolParameters(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateWebRenderParameters() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch(NewRunnableFunction( + "CompositorBridgeParent::UpdateWebRenderParameters", + &CompositorBridgeParent::UpdateWebRenderParameters)); + } + + return; + } + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + if (!wrBridge->IsRootWebRenderBridgeParent()) { + return; + } + wrBridge->UpdateParameters(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateWebRenderProfilerUI() { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + if (!wrBridge->IsRootWebRenderBridgeParent()) { + return; + } + wrBridge->UpdateProfilerUI(); + }); +} + +RefPtr<WebRenderBridgeParent> CompositorBridgeParent::GetWebRenderBridgeParent() + const { + return mWrBridge; +} + +Maybe<TimeStamp> CompositorBridgeParent::GetTestingTimeStamp() const { + return mTestTime; +} + +void EraseLayerState(LayersId aId) { + RefPtr<APZUpdater> apz; + RefPtr<WebRenderBridgeParent> wrBridge; + + { // scope lock + StaticMonitorAutoLock lock(CompositorBridgeParent::sIndirectLayerTreesLock); + auto iter = CompositorBridgeParent::sIndirectLayerTrees.find(aId); + if (iter != CompositorBridgeParent::sIndirectLayerTrees.end()) { + CompositorBridgeParent* parent = iter->second.mParent; + if (parent) { + apz = parent->GetAPZUpdater(); + } + wrBridge = iter->second.mWrBridge; + CompositorBridgeParent::sIndirectLayerTrees.erase(iter); + } + } + + if (apz) { + apz->NotifyLayerTreeRemoved(aId); + } + + if (wrBridge) { + wrBridge->Destroy(); + } +} + +/*static*/ +void CompositorBridgeParent::DeallocateLayerTreeId(LayersId aId) { + MOZ_ASSERT(NS_IsMainThread()); + // Here main thread notifies compositor to remove an element from + // sIndirectLayerTrees. This removed element might be queried soon. + // Checking the elements of sIndirectLayerTrees exist or not before using. + if (!CompositorThread()) { + gfxCriticalError() << "Attempting to post to an invalid Compositor Thread"; + return; + } + CompositorThread()->Dispatch( + NewRunnableFunction("EraseLayerStateRunnable", &EraseLayerState, aId)); +} + +static void UpdateControllerForLayersId(LayersId aLayersId, + GeckoContentController* aController) { + // Adopt ref given to us by SetControllerForLayerTree() + StaticMonitorAutoLock lock(CompositorBridgeParent::sIndirectLayerTreesLock); + CompositorBridgeParent::sIndirectLayerTrees[aLayersId].mController = + already_AddRefed<GeckoContentController>(aController); +} + +ScopedLayerTreeRegistration::ScopedLayerTreeRegistration( + LayersId aLayersId, GeckoContentController* aController) + : mLayersId(aLayersId) { + StaticMonitorAutoLock lock(CompositorBridgeParent::sIndirectLayerTreesLock); + CompositorBridgeParent::sIndirectLayerTrees[aLayersId].mController = + aController; +} + +ScopedLayerTreeRegistration::~ScopedLayerTreeRegistration() { + StaticMonitorAutoLock lock(CompositorBridgeParent::sIndirectLayerTreesLock); + CompositorBridgeParent::sIndirectLayerTrees.erase(mLayersId); +} + +/*static*/ +void CompositorBridgeParent::SetControllerForLayerTree( + LayersId aLayersId, GeckoContentController* aController) { + // This ref is adopted by UpdateControllerForLayersId(). + aController->AddRef(); + CompositorThread()->Dispatch(NewRunnableFunction( + "UpdateControllerForLayersIdRunnable", &UpdateControllerForLayersId, + aLayersId, aController)); +} + +/*static*/ +already_AddRefed<IAPZCTreeManager> CompositorBridgeParent::GetAPZCTreeManager( + LayersId aLayersId) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aLayersId); + if (sIndirectLayerTrees.end() == cit) { + return nullptr; + } + LayerTreeState* lts = &cit->second; + + RefPtr<IAPZCTreeManager> apzctm = + lts->mParent ? lts->mParent->mApzcTreeManager.get() : nullptr; + return apzctm.forget(); +} + +static void InsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (profiler_thread_is_being_profiled_for_markers()) { + // Tracks when a vsync occurs according to the HardwareComposer. + struct VsyncMarker { + static constexpr mozilla::Span<const char> MarkerTypeName() { + return mozilla::MakeStringSpan("VsyncTimestamp"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) {} + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + // Nothing outside the defaults. + return schema; + } + }; + profiler_add_marker("VsyncTimestamp", geckoprofiler::category::GRAPHICS, + MarkerTiming::InstantAt(aVsyncTimestamp), + VsyncMarker{}); + } +} + +/*static */ +void CompositorBridgeParent::PostInsertVsyncProfilerMarker( + TimeStamp aVsyncTimestamp) { + // Called in the vsync thread + if (profiler_is_active() && CompositorThreadHolder::IsActive()) { + CompositorThread()->Dispatch( + NewRunnableFunction("InsertVsyncProfilerMarkerRunnable", + InsertVsyncProfilerMarker, aVsyncTimestamp)); + } +} + +widget::PCompositorWidgetParent* +CompositorBridgeParent::AllocPCompositorWidgetParent( + const CompositorWidgetInitData& aInitData) { +#if defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING) + if (mWidget) { + // Should not create two widgets on the same compositor. + return nullptr; + } + + widget::CompositorWidgetParent* widget = + new widget::CompositorWidgetParent(aInitData, mOptions); + widget->AddRef(); + + // Sending the constructor acts as initialization as well. + mWidget = widget; + return widget; +#else + return nullptr; +#endif +} + +bool CompositorBridgeParent::DeallocPCompositorWidgetParent( + PCompositorWidgetParent* aActor) { +#if defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING) + static_cast<widget::CompositorWidgetParent*>(aActor)->Release(); + return true; +#else + return false; +#endif +} + +CompositorController* +CompositorBridgeParent::LayerTreeState::GetCompositorController() const { + return mParent; +} + +void CompositorBridgeParent::NotifyDidSceneBuild( + RefPtr<const wr::WebRenderPipelineInfo> aInfo) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (mPaused) { + return; + } + + if (mWrBridge) { + mWrBridge->NotifyDidSceneBuild(aInfo); + } +} + +void CompositorBridgeParent::NotifyDidRender(const VsyncId& aCompositeStartId, + TimeStamp& aCompositeStart, + TimeStamp& aRenderStart, + TimeStamp& aCompositeEnd, + wr::RendererStats* aStats) { + if (!mWrBridge) { + return; + } + + MOZ_RELEASE_ASSERT(mWrBridge->IsRootWebRenderBridgeParent()); + + RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID); + + if (uiController && mIsForcedFirstPaint) { + uiController->NotifyFirstPaint(); + mIsForcedFirstPaint = false; + } + + nsTArray<CompositionPayload> payload = + mWrBridge->TakePendingScrollPayload(aCompositeStartId); + if (!payload.IsEmpty()) { + RecordCompositionPayloadsPresented(aCompositeEnd, payload); + } + + nsTArray<ImageCompositeNotificationInfo> notifications; + mWrBridge->ExtractImageCompositeNotifications(¬ifications); + if (!notifications.IsEmpty()) { + Unused << ImageBridgeParent::NotifyImageComposites(notifications); + } +} + +bool CompositorBridgeParent::sStable = false; +uint32_t CompositorBridgeParent::sFramesComposited = 0; + +/* static */ void CompositorBridgeParent::ResetStable() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch( + NewRunnableFunction("CompositorBridgeParent::ResetStable", + &CompositorBridgeParent::ResetStable)); + } + + // If there is no compositor thread, e.g. due to shutdown, then we can + // safefully just ignore this request. + return; + } + + sStable = false; + sFramesComposited = 0; +} + +void CompositorBridgeParent::MaybeDeclareStable() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (sStable) { + return; + } + + // Once we render as many frames as the threshold, we declare this instance of + // the GPU process 'stable'. This causes the parent process to always respawn + // the GPU process if it crashes. + if (++sFramesComposited >= + StaticPrefs::layers_gpu_process_stable_frame_threshold()) { + sStable = true; + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "CompositorBridgeParent::MaybeDeclareStable", []() -> void { + if (XRE_IsParentProcess()) { + GPUProcessManager* gpm = GPUProcessManager::Get(); + if (gpm) { + gpm->OnProcessDeclaredStable(); + } + } else { + gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton(); + if (gpu && gpu->CanSend()) { + Unused << gpu->SendDeclareStable(); + } + } + })); + } +} + +void CompositorBridgeParent::NotifyPipelineRendered( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + const VsyncId& aCompositeStartId, TimeStamp& aCompositeStart, + TimeStamp& aRenderStart, TimeStamp& aCompositeEnd, + wr::RendererStats* aStats) { + if (!mWrBridge || !mAsyncImageManager) { + return; + } + + bool isRoot = mWrBridge->PipelineId() == aPipelineId; + RefPtr<WebRenderBridgeParent> wrBridge = + isRoot ? mWrBridge + : RefPtr<WebRenderBridgeParent>( + mAsyncImageManager->GetWrBridge(aPipelineId)); + if (!wrBridge) { + return; + } + + CompositorBridgeParentBase* compBridge = + isRoot ? this : wrBridge->GetCompositorBridge(); + if (!compBridge) { + return; + } + + MOZ_RELEASE_ASSERT(isRoot == wrBridge->IsRootWebRenderBridgeParent()); + + wrBridge->RemoveEpochDataPriorTo(aEpoch); + + nsTArray<FrameStats> stats; + nsTArray<TransactionId> transactions; + + RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID); + + wrBridge->FlushTransactionIdsForEpoch( + aEpoch, aCompositeStartId, aCompositeStart, aRenderStart, aCompositeEnd, + uiController, aStats, stats, transactions); + if (transactions.IsEmpty()) { + MOZ_ASSERT(stats.IsEmpty()); + return; + } + + MaybeDeclareStable(); + + LayersId layersId = isRoot ? LayersId{0} : wrBridge->GetLayersId(); + Unused << compBridge->SendDidComposite(layersId, transactions, + aCompositeStart, aCompositeEnd); + + if (!stats.IsEmpty()) { + Unused << SendNotifyFrameStats(stats); + } +} + +RefPtr<AsyncImagePipelineManager> +CompositorBridgeParent::GetAsyncImagePipelineManager() const { + return mAsyncImageManager; +} + +/* static */ CompositorBridgeParent::LayerTreeState* +CompositorBridgeParent::GetIndirectShadowTree(LayersId aId) { + // Only the compositor thread should use this method variant + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aId); + if (sIndirectLayerTrees.end() == cit) { + return nullptr; + } + return &cit->second; +} + +/* static */ +bool CompositorBridgeParent::CallWithIndirectShadowTree( + LayersId aId, + const std::function<void(CompositorBridgeParent::LayerTreeState&)>& aFunc) { + // Note that this does not make things universally threadsafe just because the + // sIndirectLayerTreesLock mutex is held. This is because the compositor + // thread can mutate the LayerTreeState outside the lock. It does however + // ensure that the *storage* for the LayerTreeState remains stable, since we + // should always hold the lock when adding/removing entries to the map. + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aId); + if (sIndirectLayerTrees.end() == cit) { + return false; + } + aFunc(cit->second); + return true; +} + +static CompositorBridgeParent::LayerTreeState* GetStateForRoot( + LayersId aContentLayersId, const StaticMonitorAutoLock& aProofOfLock) { + CompositorBridgeParent::sIndirectLayerTreesLock.AssertCurrentThreadOwns(); + CompositorBridgeParent::LayerTreeState* contentState = nullptr; + auto itr = CompositorBridgeParent::sIndirectLayerTrees.find(aContentLayersId); + if (CompositorBridgeParent::sIndirectLayerTrees.end() != itr) { + contentState = &itr->second; + } + + // |contentState| is the state for the content process, but we want the + // APZCTMParent for the parent process owning that content process. So we have + // to jump to the LayerTreeState for the root layer tree id for that layer + // tree, and use the mApzcTreeManagerParent from that. This should also work + // with nested content processes, because RootLayerTreeId() will bypass any + // intermediate processes' ids and go straight to the root. + if (contentState && contentState->mParent) { + LayersId rootLayersId = contentState->mParent->RootLayerTreeId(); + itr = CompositorBridgeParent::sIndirectLayerTrees.find(rootLayersId); + CompositorBridgeParent::LayerTreeState* rootState = + (CompositorBridgeParent::sIndirectLayerTrees.end() != itr) + ? &itr->second + : nullptr; + return rootState; + } + + // Don't return contentState, that would be a lie! + return nullptr; +} + +/* static */ +APZCTreeManagerParent* CompositorBridgeParent::GetApzcTreeManagerParentForRoot( + LayersId aContentLayersId) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState* state = + GetStateForRoot(aContentLayersId, lock); + return state ? state->mApzcTreeManagerParent : nullptr; +} + +/* static */ +GeckoContentController* +CompositorBridgeParent::GetGeckoContentControllerForRoot( + LayersId aContentLayersId) { + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState* state = + GetStateForRoot(aContentLayersId, lock); + return state ? state->mController.get() : nullptr; +} + +PTextureParent* CompositorBridgeParent::AllocPTextureParent( + const SurfaceDescriptor& aSharedData, ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const LayersId& aId, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + return TextureHost::CreateIPDLActor( + this, aSharedData, std::move(aReadLock), aLayersBackend, aFlags, + mCompositorManager->GetContentId(), aSerial, aExternalImageId); +} + +bool CompositorBridgeParent::DeallocPTextureParent(PTextureParent* actor) { + return TextureHost::DestroyIPDLActor(actor); +} + +bool CompositorBridgeParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void CompositorBridgeParent::NotifyWebRenderDisableNativeCompositor() { + MOZ_ASSERT(CompositorThread()->IsOnCurrentThread()); + if (mWrBridge) { + mWrBridge->DisableNativeCompositor(); + } +} + +int32_t RecordContentFrameTime( + const VsyncId& aTxnId, const TimeStamp& aVsyncStart, + const TimeStamp& aTxnStart, const VsyncId& aCompositeId, + const TimeStamp& aCompositeEnd, const TimeDuration& aFullPaintTime, + const TimeDuration& aVsyncRate, bool aContainsSVGGroup, + bool aRecordUploadStats, wr::RendererStats* aStats /* = nullptr */) { + double latencyMs = (aCompositeEnd - aTxnStart).ToMilliseconds(); + double latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + int32_t fracLatencyNorm = lround(latencyNorm * 100.0); + + if (profiler_thread_is_being_profiled_for_markers()) { + struct ContentFrameMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("CONTENT_FRAME_TIME"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) {} + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + // Nothing outside the defaults. + return schema; + } + }; + + profiler_add_marker("CONTENT_FRAME_TIME", geckoprofiler::category::GRAPHICS, + MarkerTiming::Interval(aTxnStart, aCompositeEnd), + ContentFrameMarker{}); + } + + mozilla::glean::gfx_content_frame_time::from_paint.AccumulateSamples( + {static_cast<unsigned long long>(fracLatencyNorm)}); + + if (!(aTxnId == VsyncId()) && aVsyncStart) { + latencyMs = (aCompositeEnd - aVsyncStart).ToMilliseconds(); + latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + fracLatencyNorm = lround(latencyNorm * 100.0); + int32_t result = fracLatencyNorm; + mozilla::glean::gfx_content_frame_time::from_vsync.AccumulateSamples( + {static_cast<unsigned long long>(fracLatencyNorm)}); + + if (aContainsSVGGroup) { + mozilla::glean::gfx_content_frame_time::with_svg.AccumulateSamples( + {static_cast<unsigned long long>(fracLatencyNorm)}); + } + + // Record CONTENT_FRAME_TIME_REASON. + // + // Note that deseralizing a layers update (RecvUpdate) can delay the receipt + // of the composite vsync message + // (CompositorBridgeParent::CompositeToTarget), since they're using the same + // thread. This can mean that compositing might start significantly late, + // but this code will still detect it as having successfully started on the + // right vsync (which is somewhat correct). We'd now have reduced time left + // in the vsync interval to finish compositing, so the chances of a missed + // frame increases. This is effectively including the RecvUpdate work as + // part of the 'compositing' phase for this metric, but it isn't included in + // COMPOSITE_TIME, and *is* included in CONTENT_FULL_PAINT_TIME. + // + // Also of note is that when the root WebRenderBridgeParent decides to + // skip a composite (due to the Renderer being busy), that won't notify + // child WebRenderBridgeParents. That failure will show up as the + // composite starting late (since it did), but it's really a fault of a + // slow composite on the previous frame, not a slow + // CONTENT_FULL_PAINT_TIME. It would be nice to have a separate bucket for + // this category (scene was ready on the next vsync, but we chose not to + // composite), but I can't find a way to locate the right child + // WebRenderBridgeParents from the root. WebRender notifies us of the + // child pipelines contained within a render, after it finishes, but I + // can't see how to query what child pipeline would have been rendered, + // when we choose to not do it. + if (fracLatencyNorm < 200) { + // Success + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::OnTime); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel::eOnTime) + .Add(); + } else { + if (aCompositeId == VsyncId()) { + // aCompositeId is 0, possibly something got trigged from + // outside vsync? + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::NoVsyncNoId); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel::eNoVsyncNoId) + .Add(); + } else if (aTxnId >= aCompositeId) { + // Vsync ids are nonsensical, maybe we're trying to catch up? + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::NoVsync); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel::eNoVsync) + .Add(); + } else if (aCompositeId - aTxnId > 1) { + // Composite started late (and maybe took too long as well) + if (aFullPaintTime >= TimeDuration::FromMilliseconds(20)) { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLong); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel:: + eMissedCompositeLong) + .Add(); + } else if (aFullPaintTime >= TimeDuration::FromMilliseconds(10)) { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeMid); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel:: + eMissedCompositeMid) + .Add(); + } else if (aFullPaintTime >= TimeDuration::FromMilliseconds(5)) { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLow); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel:: + eMissedCompositeLow) + .Add(); + } else { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet( + glean::gfx_content_frame_time::ReasonLabel::eMissedComposite) + .Add(); + } + } else { + // Composite started on time, but must have taken too long. + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite); + mozilla::glean::gfx_content_frame_time::reason + .EnumGet(glean::gfx_content_frame_time::ReasonLabel::eSlowComposite) + .Add(); + } + } + + if (aRecordUploadStats) { + if (aStats) { + latencyMs -= (double(aStats->resource_upload_time) / 1000000.0); + latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + fracLatencyNorm = lround(latencyNorm * 100.0); + } + mozilla::glean::gfx_content_frame_time::without_resource_upload + .AccumulateSamples( + {static_cast<unsigned long long>(fracLatencyNorm)}); + + if (aStats) { + latencyMs -= (double(aStats->gpu_cache_upload_time) / 1000000.0); + latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + fracLatencyNorm = lround(latencyNorm * 100.0); + } + mozilla::glean::gfx_content_frame_time::without_resource_upload + .AccumulateSamples( + {static_cast<unsigned long long>(fracLatencyNorm)}); + } + return result; + } + + return 0; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvBeginRecording( + const TimeStamp& aRecordingStart, BeginRecordingResolver&& aResolve) { + if (mHaveCompositionRecorder) { + aResolve(false); + return IPC_OK(); + } + + if (mWrBridge) { + mWrBridge->BeginRecording(aRecordingStart); + } + + mHaveCompositionRecorder = true; + aResolve(true); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecording( + EndRecordingResolver&& aResolve) { + if (!mHaveCompositionRecorder) { + aResolve(Nothing()); + return IPC_OK(); + } + + if (mWrBridge) { + mWrBridge->EndRecording()->Then( + NS_GetCurrentThread(), __func__, + [resolve{aResolve}](FrameRecording&& recording) { + resolve(Some(std::move(recording))); + }, + [resolve{aResolve}]() { resolve(Nothing()); }); + } else { + aResolve(Nothing()); + } + + mHaveCompositionRecorder = false; + + return IPC_OK(); +} + +void CompositorBridgeParent::FlushPendingWrTransactionEventsWithWait() { + if (!mWrBridge) { + return; + } + + std::vector<RefPtr<WebRenderBridgeParent>> bridgeParents; + { // scope lock + StaticMonitorAutoLock lock(sIndirectLayerTreesLock); + ForEachIndirectLayerTree([&](LayerTreeState* lts, LayersId) -> void { + if (lts->mWrBridge) { + bridgeParents.emplace_back(lts->mWrBridge); + } + }); + } + + for (auto& bridge : bridgeParents) { + bridge->FlushPendingWrTransactionEventsWithWait(); + } +} + +void RecordCompositionPayloadsPresented( + const TimeStamp& aCompositionEndTime, + const nsTArray<CompositionPayload>& aPayloads) { + if (aPayloads.Length()) { + TimeStamp presented = aCompositionEndTime; + for (const CompositionPayload& payload : aPayloads) { + if (profiler_thread_is_being_profiled_for_markers()) { + MOZ_RELEASE_ASSERT(payload.mType <= kHighestCompositionPayloadType); + nsAutoCString name( + kCompositionPayloadTypeNames[uint8_t(payload.mType)]); + name.AppendLiteral(" Payload Presented"); + // This doesn't really need to be a text marker. Once we have a version + // of profiler_add_marker that accepts both a start time and an end + // time, we could use that here. + nsPrintfCString text( + "Latency: %dms", + int32_t((presented - payload.mTimeStamp).ToMilliseconds())); + PROFILER_MARKER_TEXT( + name, GRAPHICS, + MarkerTiming::Interval(payload.mTimeStamp, presented), text); + } + + if (payload.mType == CompositionPayloadType::eKeyPress) { + glean::performance_interaction::keypress_present_latency + .AccumulateRawDuration(presented - payload.mTimeStamp); + } else if (payload.mType == CompositionPayloadType::eAPZScroll) { + mozilla::glean::gfx::scroll_present_latency.AccumulateRawDuration( + presented - payload.mTimeStamp); + } else if (payload.mType == + CompositionPayloadType::eMouseUpFollowedByClick) { + glean::performance_interaction::mouseup_click_present_latency + .AccumulateRawDuration(presented - payload.mTimeStamp); + } + } + } +} + +} // namespace layers +} // namespace mozilla |